Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/quiet-ties-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@o2s/blocks.order-details': minor
'@o2s/blocks.ticket-list': minor
'@o2s/ui': minor
---

- add MoreActionsMenu component and refactor ActionList
- migrate OrderDetails and TicketList to unified ActionList API
- improve breadcrumbs visibility and card border styling
72 changes: 26 additions & 46 deletions packages/blocks/order-details/src/frontend/OrderDetails.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { Typography } from '@o2s/ui/elements/typography';
import { Model, Request } from '../api-harmonization/order-details.client';
import { sdk } from '../sdk';

import { OrderDetailsPureProps } from './OrderDetails.types';
import { Action, OrderDetailsPureProps } from './OrderDetails.types';

const ProgressBar: React.FC<
Readonly<{
Expand Down Expand Up @@ -166,22 +166,27 @@ export const OrderDetailsPure: React.FC<Readonly<OrderDetailsPureProps>> = ({
});
};

const buttons = [
const actionsDefinition: Action[] = [
{
label: data.payOnlineLabel,
icon: 'ArrowUpRight',
variant: 'destructive',
},
{
label: data.reorderLabel,
icon: 'IterationCw',
variant: data.order.overdue.isOverdue ? 'secondary' : 'default',
className: data.order.overdue.isOverdue ? 'flex-1' : '',
},
{
label: data.trackOrderLabel,
icon: 'Truck',
variant: data.order.overdue.isOverdue ? 'ghost' : 'secondary',
className: data.order.overdue.isOverdue ? 'w-full justify-start h-8' : 'flex-1',
},
];

const actions = data.order.overdue.isOverdue ? buttons : buttons.slice(1);
const actions = data.order.overdue.isOverdue ? actionsDefinition : actionsDefinition.slice(1);

const t = useTranslations();

Expand All @@ -206,49 +211,24 @@ export const OrderDetailsPure: React.FC<Readonly<OrderDetailsPureProps>> = ({
<div className="flex flex-row justify-end">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center w-full sm:w-auto">
<ActionList
visibleActions={[
<TooltipHover
key={actions[0]?.label}
trigger={(setIsOpen) => (
<Button
variant={data.order.overdue.isOverdue ? 'destructive' : 'default'}
onClick={() => setIsOpen(true)}
>
{actions[0]?.icon && <DynamicIcon name={actions[0].icon} size={16} />}
{actions[0]?.label}
</Button>
)}
content={<p>{t('general.comingSoon')}</p>}
/>,
<TooltipHover
key={actions[1]?.label}
trigger={(setIsOpen) => (
<Button variant={'secondary'} onClick={() => setIsOpen(true)}>
{actions[1]?.icon && <DynamicIcon name={actions[1].icon} size={16} />}
{actions[1]?.label}
</Button>
)}
content={<p>{t('general.comingSoon')}</p>}
/>,
]}
dropdownActions={actions.slice(2).map((action) => (
<TooltipHover
key={action.label}
trigger={(setIsOpen) => (
<Button
variant="ghost"
size="sm"
disabled
className="w-full justify-start h-8"
onClick={() => setIsOpen(true)}
>
{action.icon && <DynamicIcon name={action.icon} size={16} />}
{action.label}
</Button>
)}
content={<p>{t('general.comingSoon')}</p>}
/>
))}
actions={actions
.filter((action) => action.label)
.map((action, index) => (
<TooltipHover
key={`${action.label}-${index}`}
trigger={(setIsOpen) => (
<Button
variant={action.variant}
onClick={() => setIsOpen(true)}
className={action.className}
>
{action.icon && <DynamicIcon name={action.icon} size={16} />}
{action.label}
</Button>
)}
content={<p>{t('general.comingSoon')}</p>}
/>
))}
showMoreLabel={data.labels.showMore}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { VariantProps } from 'class-variance-authority';
import { defineRouting } from 'next-intl/routing';

import { baseVariant } from '@o2s/ui/lib/utils';

import type { Model } from '../api-harmonization/order-details.client';

export interface OrderDetailsProps {
Expand All @@ -16,3 +19,8 @@ export type OrderDetailsPureProps = OrderDetailsProps & Model.OrderDetailsBlock;
export interface OrderDetailsRendererProps extends Omit<OrderDetailsProps, 'orderId'> {
slug: string[];
}

export type Action = {
variant: VariantProps<typeof baseVariant>['variant'];
className?: string;
} & ({ label: string; icon?: string } | { label?: string; icon: string });
62 changes: 36 additions & 26 deletions packages/blocks/ticket-list/src/frontend/TicketList.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Typography } from '@o2s/ui/elements/typography';
import { Model, Request } from '../api-harmonization/ticket-list.client';
import { sdk } from '../sdk';

import { TicketListPureProps } from './TicketList.types';
import { Action, TicketListPureProps } from './TicketList.types';

export const TicketListPure: React.FC<TicketListPureProps> = ({ locale, accessToken, routing, meta, ...component }) => {
const { Link: LinkComponent } = createNavigation(routing);
Expand Down Expand Up @@ -90,6 +90,24 @@ export const TicketListPure: React.FC<TicketListPureProps> = ({ locale, accessTo
});
};

const variantConfig: Array<{ variant: Action['variant']; className: string }> = [
{ variant: 'default', className: 'no-underline hover:no-underline' },
{ variant: 'secondary', className: 'no-underline hover:no-underline flex-1' },
{
variant: 'ghost',
className:
'flex items-center gap-2 !no-underline hover:!no-underline cursor-pointer h-8 w-full justify-start',
},
];

const actions: Action[] = (data.forms ?? []).map((form, index) => ({
label: form.label,
icon: form.icon,
url: form.url || '',
variant: variantConfig[index]?.variant ?? 'default',
className: variantConfig[index]?.className ?? '',
}));

// Define columns configuration outside JSX for better readability
const columns = data.table.columns.map((column) => {
switch (column.id) {
Expand Down Expand Up @@ -125,7 +143,7 @@ export const TicketListPure: React.FC<TicketListPureProps> = ({ locale, accessTo
};
}
}) as DataListColumnConfig<Model.Ticket>[];
const actions = data.table.actions
const tableActions = data.table.actions
? {
...data.table.actions,
render: (ticket: Model.Ticket) => {
Expand All @@ -152,29 +170,21 @@ export const TicketListPure: React.FC<TicketListPureProps> = ({ locale, accessTo

{data.forms && (
<ActionList
visibleActions={data.forms.slice(0, 2).map((form, index) => (
<Button
asChild
variant={index === 0 ? 'default' : 'secondary'}
key={form.label}
className="no-underline hover:no-underline"
>
<LinkComponent href={form.url}>
{form.icon && <DynamicIcon name={form.icon} size={16} />}
{form.label}
</LinkComponent>
</Button>
))}
dropdownActions={data.forms.slice(2).map((form) => (
<LinkComponent
href={form.url}
key={form.label}
className="flex items-center gap-2 !no-underline hover:!no-underline cursor-pointer"
>
{form.icon && <DynamicIcon name={form.icon} size={16} />}
{form.label}
</LinkComponent>
))}
actions={actions
.filter((action) => action.label)
.map((action, index) => (
<Button
asChild
variant={action.variant}
key={`${action.label}-${index}`}
className={action.className}
>
<LinkComponent href={action.url}>
{action.icon && <DynamicIcon name={action.icon} size={16} />}
{action.label}
</LinkComponent>
</Button>
))}
showMoreLabel={data.labels.showMore}
/>
)}
Expand Down Expand Up @@ -221,7 +231,7 @@ export const TicketListPure: React.FC<TicketListPureProps> = ({ locale, accessTo
viewMode={viewMode}
data={data.tickets.data}
columns={columns}
actions={actions}
actions={tableActions}
cardHeaderSlots={data.cardHeaderSlots}
enableRowSelection={component.enableRowSelection}
selectedRows={selectedRows}
Expand Down
9 changes: 9 additions & 0 deletions packages/blocks/ticket-list/src/frontend/TicketList.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { VariantProps } from 'class-variance-authority';
import { defineRouting } from 'next-intl/routing';

import { baseVariant } from '@o2s/ui/lib/utils';

import type { Model } from '../api-harmonization/ticket-list.client';

export interface TicketListProps {
Expand All @@ -17,3 +20,9 @@ export type TicketListPureProps = TicketListProps & Model.TicketListBlock;
export type TicketListRendererProps = Omit<TicketListProps, ''> & {
slug: string[];
};

export type Action = {
url: string;
variant: VariantProps<typeof baseVariant>['variant'];
className?: string;
} & ({ label: string; icon?: string } | { label?: string; icon: string });
52 changes: 5 additions & 47 deletions packages/ui/src/components/ActionList/ActionList.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite';

import { Button } from '@o2s/ui/elements/button';
import { Link } from '@o2s/ui/elements/link';

import { ActionList } from './ActionList';

Expand All @@ -19,7 +18,7 @@ export const Default: Story = {
args: {
showMoreLabel: 'Show more actions',
triggerVariant: 'outline',
visibleActions: [
actions: [
<Button key="primary" variant="primary">
Primary Action
</Button>,
Expand All @@ -33,67 +32,40 @@ export const Default: Story = {
Tertiary Action 2
</Button>,
],
dropdownActions: [
<Link key="primary" variant="primary">
Primary Action
</Link>,
<Link key="secondary" variant="secondary">
Secondary Action
</Link>,
<Link key="tertiary1" variant="outline">
Tertiary Action 1
</Link>,
<Link key="tertiary2" variant="ghost">
Tertiary Action 2
</Link>,
],
},
};

export const SingleAction: Story = {
args: {
showMoreLabel: 'Show more actions',
visibleActions: [
actions: [
<Button key="primary" variant="primary">
Single Action
</Button>,
],
dropdownActions: [
<Link key="primary" variant="primary">
Single Action
</Link>,
],
},
};

export const TwoActions: Story = {
args: {
showMoreLabel: 'Show more actions',
visibleActions: [
actions: [
<Button key="primary" variant="primary">
Primary Action
</Button>,
<Button key="secondary" variant="secondary">
Secondary Action
</Button>,
],
dropdownActions: [
<Link key="primary" variant="primary">
Primary Action
</Link>,
<Link key="secondary" variant="secondary">
Secondary Action
</Link>,
],
},
};

export const WithDifferentVariant: Story = {
args: {
showMoreLabel: 'Show more actions',
triggerVariant: 'destructive',
visibleActions: [
<Button key="primary" variant="primary">
actions: [
<Button key="primary" variant="destructive">
Primary Action
</Button>,
<Button key="secondary" variant="secondary">
Expand All @@ -106,19 +78,5 @@ export const WithDifferentVariant: Story = {
Tertiary Action 2
</Button>,
],
dropdownActions: [
<Link key="primary" variant="primary">
Primary Action
</Link>,
<Link key="secondary" variant="secondary">
Secondary Action
</Link>,
<Link key="tertiary1" variant="outline">
Tertiary Action 1
</Link>,
<Link key="tertiary2" variant="ghost">
Tertiary Action 2
</Link>,
],
},
};
Loading
Loading