From d89fa3cb541dc0e7b7b0878b32d2a5f41357e3c1 Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 5 Jan 2026 21:55:18 +0800 Subject: [PATCH] feat: Add close button option to toast component - Add `closeButton` prop to BaseToastProps interface - Update dismissable prop description for clarity - Add close button styling to classNames - Pass closeButton prop to external toast configuration - Update Storybook stories to include closeButton control - Rename "Dismissable Control" story to "Close Button Control" --- .../ui/src/components/primitives/sonner.d.ts | 4 ++- .../ui/src/components/primitives/sonner.tsx | 10 ++++--- .../components/primitives/Sonner.stories.tsx | 27 ++++++++++++------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/primitives/sonner.d.ts b/packages/ui/src/components/primitives/sonner.d.ts index 830b19bcc3..d9877b54be 100644 --- a/packages/ui/src/components/primitives/sonner.d.ts +++ b/packages/ui/src/components/primitives/sonner.d.ts @@ -18,12 +18,14 @@ interface BaseToastProps { colored?: boolean /** Duration in milliseconds before auto-dismissal */ duration?: number - /** Whether the toast can be manually dismissed */ + /** If 'false', it'll prevent the user from dismissing the toast. Defaults to false. */ dismissable?: boolean /** Callback function when toast is dismissed */ onDismiss?: () => void /** Action button or custom React node */ button?: Action | ReactNode + /** Whether to show a close button. Defaults to false */ + closeButton?: boolean /** Custom class names for toast sub-components */ classNames?: ToastClassnames } diff --git a/packages/ui/src/components/primitives/sonner.tsx b/packages/ui/src/components/primitives/sonner.tsx index eb4c556c00..698d84752f 100644 --- a/packages/ui/src/components/primitives/sonner.tsx +++ b/packages/ui/src/components/primitives/sonner.tsx @@ -316,12 +316,14 @@ interface BaseToastProps { colored?: boolean /** Duration in milliseconds before auto-dismissal */ duration?: number - /** Whether the toast can be manually dismissed */ + /** If 'false', it'll prevent the user from dismissing the toast. Defaults to false. */ dismissable?: boolean /** Callback function when toast is dismissed */ onDismiss?: () => void /** Action button or custom React node */ button?: Action | ReactNode + /** Whether to show a close button. Defaults to false */ + closeButton?: boolean /** Custom class names for toast sub-components */ classNames?: ToastClassnames } @@ -388,7 +390,8 @@ function toast(props: ToastProps) { props.classNames?.actionButton ), icon: cn('size-6 min-w-6', props.description && 'self-start'), - loader: cn('!static ![--size:24px]') + loader: cn('!static ![--size:24px]'), + closeButton: cn('absolute size-5 min-w-5 top-[5px] right-1.5 [&_svg]:size-5') } const { classNames: externalClassNames, ...rest } = props delete externalClassNames?.toast @@ -400,7 +403,8 @@ function toast(props: ToastProps) { duration: rest.duration, action: rest.button, dismissible: rest.dismissable, - onDismiss: rest.onDismiss + onDismiss: rest.onDismiss, + closeButton: rest.closeButton } satisfies ExternalToast switch (props.type) { default: diff --git a/packages/ui/stories/components/primitives/Sonner.stories.tsx b/packages/ui/stories/components/primitives/Sonner.stories.tsx index 92562c120e..207a12f64b 100644 --- a/packages/ui/stories/components/primitives/Sonner.stories.tsx +++ b/packages/ui/stories/components/primitives/Sonner.stories.tsx @@ -10,6 +10,7 @@ interface PlaygroundArgs { colored: boolean duration: number dismissable: boolean + closeButton: boolean withButton: boolean buttonLabel: string } @@ -49,6 +50,7 @@ export const Playground: StoryObj = { colored: false, duration: 4000, dismissable: true, + closeButton: false, withButton: false, buttonLabel: 'Action' }, @@ -76,7 +78,11 @@ export const Playground: StoryObj = { }, dismissable: { control: 'boolean', - description: 'Whether the toast can be manually dismissed' + description: 'Whether the toast can be dismissed by user interaction (click, swipe)' + }, + closeButton: { + control: 'boolean', + description: 'Whether to show a close button' }, withButton: { control: 'boolean', @@ -95,6 +101,7 @@ export const Playground: StoryObj = { colored: args.colored, duration: args.duration, dismissable: args.dismissable, + closeButton: args.closeButton, ...(args.withButton && { button: { label: args.buttonLabel || 'Action', @@ -608,30 +615,30 @@ export const CustomToast: Story = { } } -// Dismissable Control -export const DismissableControl: Story = { +// Close Button Control +export const CloseButtonControl: Story = { render: () => { return (
)