@boxcustodia/library 2.0.0-alpha.13 → 2.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1083 -715
  3. package/dist/index.es.js +7077 -56175
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Examples.tsx +1 -1
  7. package/src/__doc__/Intro.mdx +3 -3
  8. package/src/__doc__/Tabs.mdx +112 -0
  9. package/src/__doc__/V2.mdx +1246 -0
  10. package/src/components/accordion/accordion.stories.tsx +143 -0
  11. package/src/components/accordion/accordion.tsx +135 -0
  12. package/src/components/accordion/index.ts +1 -0
  13. package/src/components/alert/alert.stories.tsx +24 -4
  14. package/src/components/alert/alert.tsx +17 -9
  15. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  16. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  17. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  18. package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
  19. package/src/components/auto-complete/auto-complete.tsx +420 -68
  20. package/src/components/auto-complete/index.ts +0 -1
  21. package/src/components/avatar/avatar.stories.tsx +162 -21
  22. package/src/components/avatar/avatar.tsx +79 -20
  23. package/src/components/button/button.stories.tsx +219 -294
  24. package/src/components/button/button.test.tsx +10 -17
  25. package/src/components/button/button.tsx +78 -19
  26. package/src/components/button/components/base-button.tsx +30 -53
  27. package/src/components/button/index.ts +0 -1
  28. package/src/components/calendar/calendar.stories.tsx +1 -1
  29. package/src/components/calendar/calendar.tsx +4 -4
  30. package/src/components/card/card.stories.tsx +141 -69
  31. package/src/components/card/card.tsx +155 -54
  32. package/src/components/center/center.stories.tsx +22 -39
  33. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  34. package/src/components/checkbox/checkbox.tsx +76 -15
  35. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  36. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  37. package/src/components/combobox/combobox.stories.tsx +33 -23
  38. package/src/components/combobox/combobox.tsx +119 -103
  39. package/src/components/date-picker/date-input.stories.tsx +14 -6
  40. package/src/components/date-picker/date-input.tsx +2 -2
  41. package/src/components/date-picker/date-picker.model.ts +13 -4
  42. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  43. package/src/components/date-picker/date-picker.tsx +28 -14
  44. package/src/components/dialog/dialog.stories.tsx +18 -0
  45. package/src/components/dialog/dialog.test.tsx +1 -1
  46. package/src/components/dialog/dialog.tsx +51 -20
  47. package/src/components/divider/divider.stories.tsx +6 -0
  48. package/src/components/dropzone/dropzone.stories.tsx +71 -90
  49. package/src/components/dropzone/dropzone.tsx +383 -105
  50. package/src/components/dropzone/index.ts +0 -1
  51. package/src/components/empty/empty.stories.tsx +165 -0
  52. package/src/components/empty/empty.tsx +156 -0
  53. package/src/components/empty/index.ts +1 -0
  54. package/src/components/field/field.stories.tsx +226 -3
  55. package/src/components/field/field.tsx +77 -42
  56. package/src/components/form/form.stories.tsx +320 -197
  57. package/src/components/form/form.tsx +3 -23
  58. package/src/components/index.ts +2 -6
  59. package/src/components/input/input.stories.tsx +5 -5
  60. package/src/components/input/input.tsx +4 -4
  61. package/src/components/kbd/kbd.stories.tsx +1 -0
  62. package/src/components/label/label.stories.tsx +16 -0
  63. package/src/components/label/label.tsx +13 -2
  64. package/src/components/loader/loader.stories.tsx +7 -5
  65. package/src/components/loader/loader.tsx +8 -3
  66. package/src/components/menu/menu-primitives.tsx +207 -196
  67. package/src/components/menu/menu.stories.tsx +276 -146
  68. package/src/components/menu/menu.tsx +146 -54
  69. package/src/components/number-input/number-input.stories.tsx +27 -4
  70. package/src/components/number-input/number-input.test.tsx +2 -2
  71. package/src/components/number-input/number-input.tsx +25 -29
  72. package/src/components/otp/index.ts +1 -0
  73. package/src/components/otp/otp.stories.tsx +209 -0
  74. package/src/components/otp/otp.tsx +100 -0
  75. package/src/components/pagination/index.ts +1 -0
  76. package/src/components/pagination/pagination.model.ts +2 -0
  77. package/src/components/pagination/pagination.stories.tsx +154 -59
  78. package/src/components/pagination/pagination.test.tsx +122 -57
  79. package/src/components/pagination/pagination.tsx +575 -77
  80. package/src/components/password/password.stories.tsx +18 -3
  81. package/src/components/password/password.tsx +26 -10
  82. package/src/components/popover/popover.stories.tsx +26 -5
  83. package/src/components/popover/popover.tsx +15 -23
  84. package/src/components/progress/progress.stories.tsx +1 -0
  85. package/src/components/radio-group/index.ts +1 -0
  86. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  87. package/src/components/radio-group/radio-group.tsx +212 -0
  88. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  89. package/src/components/select/select.stories.tsx +118 -19
  90. package/src/components/select/select.tsx +67 -62
  91. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  92. package/src/components/stack/stack.stories.tsx +179 -89
  93. package/src/components/stack/stack.tsx +2 -2
  94. package/src/components/stepper/index.ts +1 -1
  95. package/src/components/stepper/stepper.stories.tsx +767 -83
  96. package/src/components/stepper/stepper.test.tsx +18 -18
  97. package/src/components/stepper/stepper.tsx +554 -0
  98. package/src/components/switch/switch.stories.tsx +15 -1
  99. package/src/components/switch/switch.tsx +17 -4
  100. package/src/components/table/index.ts +0 -2
  101. package/src/components/table/table.stories.tsx +131 -18
  102. package/src/components/table/table.test.tsx +1 -1
  103. package/src/components/table/table.tsx +183 -77
  104. package/src/components/tabs/tabs.stories.tsx +373 -155
  105. package/src/components/tabs/tabs.test.tsx +12 -12
  106. package/src/components/tabs/tabs.tsx +72 -149
  107. package/src/components/tag/index.ts +0 -1
  108. package/src/components/tag/tag.stories.tsx +155 -120
  109. package/src/components/tag/tag.tsx +47 -95
  110. package/src/components/textarea/textarea.stories.tsx +8 -22
  111. package/src/components/textarea/textarea.tsx +17 -79
  112. package/src/components/timeline/timeline.stories.tsx +323 -42
  113. package/src/components/timeline/timeline.tsx +359 -132
  114. package/src/components/toast/toast.stories.tsx +1 -0
  115. package/src/components/tooltip/tooltip.tsx +11 -9
  116. package/src/components/tree/index.ts +0 -1
  117. package/src/components/tree/tree.stories.tsx +365 -408
  118. package/src/components/tree/tree.test.tsx +163 -0
  119. package/src/components/tree/tree.tsx +212 -36
  120. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  121. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  122. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  123. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  124. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  125. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  126. package/src/hooks/usePagination/usePagination.tsx +36 -24
  127. package/src/styles/theme.css +1 -1
  128. package/src/utils/form.tsx +67 -37
  129. package/src/utils/index.ts +1 -1
  130. package/src/__doc__/Migration.mdx +0 -451
  131. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  132. package/src/components/background-image/background-image.stories.tsx +0 -21
  133. package/src/components/background-image/background-image.test.tsx +0 -29
  134. package/src/components/background-image/background-image.tsx +0 -23
  135. package/src/components/background-image/index.ts +0 -1
  136. package/src/components/button/button.variants.ts +0 -44
  137. package/src/components/button/components/loader-overlay.tsx +0 -21
  138. package/src/components/button/components/loading-icon.tsx +0 -47
  139. package/src/components/dropzone/upload-primitives.tsx +0 -310
  140. package/src/components/dropzone/use-dropzone.ts +0 -122
  141. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  142. package/src/components/empty-state/empty-state.tsx +0 -39
  143. package/src/components/empty-state/index.ts +0 -1
  144. package/src/components/heading/heading.stories.tsx +0 -74
  145. package/src/components/heading/heading.tsx +0 -28
  146. package/src/components/heading/heading.variants.ts +0 -27
  147. package/src/components/heading/index.ts +0 -1
  148. package/src/components/kbd/kbd.variants.ts +0 -26
  149. package/src/components/menu/util/render-menu-item.tsx +0 -54
  150. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  151. package/src/components/multi-select/index.ts +0 -1
  152. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  153. package/src/components/multi-select/multi-select.tsx +0 -300
  154. package/src/components/multi-select/multi-select.variants.ts +0 -22
  155. package/src/components/pagination/components/pagination-option.tsx +0 -27
  156. package/src/components/show/index.ts +0 -1
  157. package/src/components/show/show.stories.tsx +0 -197
  158. package/src/components/show/show.test.tsx +0 -41
  159. package/src/components/show/show.tsx +0 -16
  160. package/src/components/stepper/Stepper.tsx +0 -190
  161. package/src/components/stepper/context/stepper-context.tsx +0 -11
  162. package/src/components/table/table-primitives.tsx +0 -122
  163. package/src/components/table/table.model.ts +0 -20
  164. package/src/components/table-pagination/index.ts +0 -2
  165. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  167. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  168. package/src/components/table-pagination/table-pagination.tsx +0 -108
  169. package/src/components/tabs/context/tabs-context.tsx +0 -14
  170. package/src/components/tag/tag.variants.ts +0 -31
  171. package/src/components/timeline/timeline-status.ts +0 -5
  172. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  173. package/src/components/tree/tree-primitives.tsx +0 -126
@@ -0,0 +1,143 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import React from "react";
3
+
4
+ import {
5
+ Accordion,
6
+ AccordionContent,
7
+ AccordionItem,
8
+ AccordionRoot,
9
+ AccordionTrigger,
10
+ } from "./accordion";
11
+
12
+ const richContent = (
13
+ <span>
14
+ Content can include <strong>rich markup</strong> like bold text, links, or
15
+ any React node.
16
+ </span>
17
+ );
18
+
19
+ /**
20
+ * Collapsible sections that expand/collapse to show or hide content.
21
+ * Use the composite `Accordion` for simple item lists, or compose primitives
22
+ * (`AccordionRoot`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`) for
23
+ * full control. Supports single or multiple open panels via `multiple`.
24
+ */
25
+ const meta: Meta<typeof Accordion> = {
26
+ title: "Components/Accordion",
27
+ component: Accordion,
28
+ args: {
29
+ items: [
30
+ {
31
+ value: "item-1",
32
+ trigger: "What is this library?",
33
+ content:
34
+ "A collection of accessible, customizable UI components built on Base UI and styled with Tailwind CSS.",
35
+ },
36
+ {
37
+ value: "item-2",
38
+ trigger: "Is it open source?",
39
+ content: richContent,
40
+ },
41
+ {
42
+ value: "item-3",
43
+ trigger: "Can I customize the theme?",
44
+ content:
45
+ "Override any token via @theme in your CSS after importing the library stylesheet.",
46
+ },
47
+ ],
48
+ },
49
+ tags: ["new"],
50
+ };
51
+
52
+ export default meta;
53
+ type Story = StoryObj<typeof Accordion>;
54
+
55
+ export const Default: Story = {};
56
+
57
+ /**
58
+ * `className` styles the accordion root. `classNames` exposes the `item`,
59
+ * `trigger`, and `content` slots for fine-grained styling.
60
+ *
61
+ * For structural changes (extra slots, custom layouts) use the primitives.
62
+ */
63
+ export const WithClassNames: Story = {
64
+ args: {
65
+ className: "max-w-md gap-2",
66
+ classNames: {
67
+ item: "rounded-md border border-input px-3 not-last:border-b",
68
+ trigger: "py-3 text-base",
69
+ content: "text-muted-foreground",
70
+ },
71
+ },
72
+ };
73
+
74
+ /**
75
+ * Allow multiple panels to be open simultaneously via `multiple`.
76
+ */
77
+ export const Multiple: Story = {
78
+ args: {
79
+ multiple: true,
80
+ defaultValue: ["item-1", "item-2"],
81
+ },
82
+ };
83
+
84
+ /**
85
+ * Controlled accordion — external state drives which panel is open.
86
+ */
87
+ export const Controlled: Story = {
88
+ render: (args) => {
89
+ const [value, setValue] = React.useState<string[]>(["item-1"]);
90
+ return <Accordion {...args} value={value} onValueChange={setValue} />;
91
+ },
92
+ };
93
+
94
+ export const Disabled: Story = {
95
+ args: {
96
+ items: [
97
+ {
98
+ value: "item-1",
99
+ trigger: "Available section",
100
+ content: "This section can be opened.",
101
+ },
102
+ {
103
+ value: "item-2",
104
+ trigger: "Disabled section",
105
+ content: "This section is disabled.",
106
+ disabled: true,
107
+ },
108
+ ],
109
+ },
110
+ };
111
+
112
+ /**
113
+ * Build accordion manually using primitives when you need custom slots,
114
+ * additional markup between items, or conditional rendering.
115
+ */
116
+ export const Primitive: Story = {
117
+ render: () => (
118
+ <AccordionRoot defaultValue={["item-1"]}>
119
+ <AccordionItem value="item-1">
120
+ <AccordionTrigger>What is this library?</AccordionTrigger>
121
+ <AccordionContent>
122
+ A collection of accessible, customizable UI components.
123
+ </AccordionContent>
124
+ </AccordionItem>
125
+ <AccordionItem value="item-2">
126
+ <AccordionTrigger>Is it open source?</AccordionTrigger>
127
+ <AccordionContent>{richContent}</AccordionContent>
128
+ </AccordionItem>
129
+ </AccordionRoot>
130
+ ),
131
+ parameters: {
132
+ docs: {
133
+ source: {
134
+ code: `<AccordionRoot defaultValue={["item-1"]}>
135
+ <AccordionItem value="item-1">
136
+ <AccordionTrigger>Question</AccordionTrigger>
137
+ <AccordionContent>Answer</AccordionContent>
138
+ </AccordionItem>
139
+ </AccordionRoot>`,
140
+ },
141
+ },
142
+ },
143
+ };
@@ -0,0 +1,135 @@
1
+ import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
2
+ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
3
+ import { type ReactNode } from "react";
4
+
5
+ import { cn } from "../../lib";
6
+
7
+ export function AccordionRoot({
8
+ className,
9
+ ...props
10
+ }: AccordionPrimitive.Root.Props) {
11
+ return (
12
+ <AccordionPrimitive.Root
13
+ data-slot="accordion"
14
+ className={cn("flex w-full flex-col", className)}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ export function AccordionItem({
21
+ className,
22
+ ...props
23
+ }: AccordionPrimitive.Item.Props) {
24
+ return (
25
+ <AccordionPrimitive.Item
26
+ data-slot="accordion-item"
27
+ className={cn("not-last:border-b", className)}
28
+ {...props}
29
+ />
30
+ );
31
+ }
32
+
33
+ export function AccordionTrigger({
34
+ className,
35
+ children,
36
+ ...props
37
+ }: AccordionPrimitive.Trigger.Props) {
38
+ return (
39
+ <AccordionPrimitive.Header className="flex">
40
+ <AccordionPrimitive.Trigger
41
+ data-slot="accordion-trigger"
42
+ className={cn(
43
+ "group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
44
+ className,
45
+ )}
46
+ {...props}
47
+ >
48
+ {children}
49
+ <ChevronDownIcon
50
+ data-slot="accordion-trigger-icon"
51
+ className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
52
+ />
53
+ <ChevronUpIcon
54
+ data-slot="accordion-trigger-icon"
55
+ className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
56
+ />
57
+ </AccordionPrimitive.Trigger>
58
+ </AccordionPrimitive.Header>
59
+ );
60
+ }
61
+
62
+ export function AccordionContent({
63
+ className,
64
+ children,
65
+ ...props
66
+ }: AccordionPrimitive.Panel.Props) {
67
+ return (
68
+ <AccordionPrimitive.Panel
69
+ data-slot="accordion-content"
70
+ className="h-(--accordion-panel-height) overflow-hidden text-sm transition-[height] duration-150 ease-out data-starting-style:h-0 data-ending-style:h-0"
71
+ {...props}
72
+ >
73
+ <div
74
+ className={cn(
75
+ "pb-2.5 pt-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
76
+ className,
77
+ )}
78
+ >
79
+ {children}
80
+ </div>
81
+ </AccordionPrimitive.Panel>
82
+ );
83
+ }
84
+
85
+ interface AccordionItem {
86
+ value: string;
87
+ trigger: ReactNode;
88
+ content: ReactNode;
89
+ disabled?: boolean;
90
+ }
91
+
92
+ interface AccordionProps
93
+ extends Omit<AccordionPrimitive.Root.Props, "children" | "className"> {
94
+ items: AccordionItem[];
95
+ /** Styles the accordion root container. */
96
+ className?: string;
97
+ /** Styles applied to each internal slot. */
98
+ classNames?: {
99
+ /** Each collapsible item wrapper. */
100
+ item?: string;
101
+ /** Trigger button that toggles the item. */
102
+ trigger?: string;
103
+ /** Panel that contains the item content. */
104
+ content?: string;
105
+ };
106
+ }
107
+
108
+ export function Accordion({
109
+ items,
110
+ className,
111
+ classNames,
112
+ ...props
113
+ }: AccordionProps) {
114
+ return (
115
+ <AccordionRoot className={className} {...props}>
116
+ {items.map(({ value, trigger, content, disabled }) => (
117
+ <AccordionItem
118
+ key={value}
119
+ value={value}
120
+ disabled={disabled}
121
+ className={classNames?.item}
122
+ >
123
+ <AccordionTrigger className={classNames?.trigger}>
124
+ {trigger}
125
+ </AccordionTrigger>
126
+ <AccordionContent className={classNames?.content}>
127
+ {content}
128
+ </AccordionContent>
129
+ </AccordionItem>
130
+ ))}
131
+ </AccordionRoot>
132
+ );
133
+ }
134
+
135
+ export { AccordionPrimitive };
@@ -0,0 +1 @@
1
+ export * from "./accordion";
@@ -20,10 +20,9 @@ import {
20
20
  * background tint, and icon color.
21
21
  *
22
22
  * Accepts `title`, `description`, `icon`, and `action` as props for the common case.
23
- * For full layout control, compose the primitives directly: `AlertRoot`, `AlertTitle`,
24
- * `AlertDescription`, and `AlertAction`. Each prop also has an escape-hatch counterpart
25
- * (`titleProps`, `descriptionProps`, `actionProps`) to override a specific primitive
26
- * without rebuilding the whole structure.
23
+ * `className` styles the alert root; `classNames` exposes the `title`, `description`,
24
+ * and `action` slots for fine-grained styling. For full layout control, compose the
25
+ * primitives directly: `AlertRoot`, `AlertTitle`, `AlertDescription`, and `AlertAction`.
27
26
  */
28
27
  const meta: Meta<typeof Alert> = {
29
28
  title: "Components/Alert",
@@ -46,7 +45,9 @@ const meta: Meta<typeof Alert> = {
46
45
  },
47
46
  title: { control: "text" },
48
47
  description: { control: "text" },
48
+ classNames: { control: false },
49
49
  },
50
+ tags: ["new"],
50
51
  };
51
52
 
52
53
  export default meta;
@@ -54,6 +55,25 @@ type Story = StoryObj<typeof Alert>;
54
55
 
55
56
  export const Default: Story = {};
56
57
 
58
+ /**
59
+ * `className` styles the alert root. `classNames` exposes the `title`,
60
+ * `description`, and `action` slots for fine-grained styling without
61
+ * dropping to primitives.
62
+ */
63
+ export const WithClassNames: Story = {
64
+ args: {
65
+ variant: "info",
66
+ title: "Custom styled alert",
67
+ description: "Each slot can be styled independently.",
68
+ icon: <InfoIcon className="size-4" />,
69
+ className: "border-info/50 bg-info/10",
70
+ classNames: {
71
+ title: "text-base",
72
+ description: "text-foreground/80",
73
+ },
74
+ },
75
+ };
76
+
57
77
  /**
58
78
  * Use `variant="error"` for validation failures or destructive outcomes.
59
79
  * Pair with an icon to reinforce the severity.
@@ -79,9 +79,15 @@ interface AlertProps extends Omit<ComponentProps<typeof AlertRoot>, "title"> {
79
79
  description?: ReactNode;
80
80
  icon?: ReactNode;
81
81
  action?: ReactNode;
82
- titleProps?: ComponentProps<typeof AlertTitle>;
83
- descriptionProps?: ComponentProps<typeof AlertDescription>;
84
- actionProps?: ComponentProps<typeof AlertAction>;
82
+ /** Styles applied to each internal slot. */
83
+ classNames?: {
84
+ /** Alert title text. */
85
+ title?: string;
86
+ /** Supporting description below the title. */
87
+ description?: string;
88
+ /** Action area on the right of the alert. */
89
+ action?: string;
90
+ };
85
91
  }
86
92
 
87
93
  export function Alert({
@@ -89,20 +95,22 @@ export function Alert({
89
95
  description,
90
96
  icon,
91
97
  action,
92
- titleProps,
93
- descriptionProps,
94
- actionProps,
98
+ classNames,
95
99
  children,
96
100
  ...props
97
101
  }: AlertProps) {
98
102
  return (
99
103
  <AlertRoot {...props}>
100
104
  {icon}
101
- {title && <AlertTitle {...titleProps}>{title}</AlertTitle>}
105
+ {title && <AlertTitle className={classNames?.title}>{title}</AlertTitle>}
102
106
  {description && (
103
- <AlertDescription {...descriptionProps}>{description}</AlertDescription>
107
+ <AlertDescription className={classNames?.description}>
108
+ {description}
109
+ </AlertDescription>
110
+ )}
111
+ {action && (
112
+ <AlertAction className={classNames?.action}>{action}</AlertAction>
104
113
  )}
105
- {action && <AlertAction {...actionProps}>{action}</AlertAction>}
106
114
  {children}
107
115
  </AlertRoot>
108
116
  );
@@ -47,6 +47,7 @@ const meta: Meta<typeof AlertDialog> = {
47
47
  control: "select",
48
48
  options: ["default", "error", "secondary", "outline", "ghost"],
49
49
  },
50
+ classNames: { control: false },
50
51
  },
51
52
  };
52
53
 
@@ -55,6 +56,29 @@ type Story = StoryObj<typeof AlertDialog>;
55
56
 
56
57
  export const Default: Story = {};
57
58
 
59
+ /**
60
+ * `className` styles the popup panel. `classNames` exposes the `backdrop`,
61
+ * `header`, `title`, `description`, `footer`, `closeButton`, and `actionButton`
62
+ * slots — use for fine-grained styling without dropping to primitives.
63
+ *
64
+ * For structural changes (custom layouts, extra slots) use the primitives directly.
65
+ */
66
+ export const WithClassNames: Story = {
67
+ args: {
68
+ className: "max-w-md",
69
+ classNames: {
70
+ backdrop: "bg-black/70 backdrop-blur-sm",
71
+ title: "text-xl text-error",
72
+ description: "text-base",
73
+ footer: "sm:justify-between",
74
+ actionButton: "min-w-32",
75
+ },
76
+ title: "Custom styled dialog",
77
+ description: "Slots can be styled without touching the primitives.",
78
+ actionText: "Confirm",
79
+ },
80
+ };
81
+
58
82
  /**
59
83
  * Use `variant="error"` for irreversible actions like deletes.
60
84
  * The error button signals danger — reserve it for data loss scenarios.
@@ -1,7 +1,7 @@
1
1
  import { render, screen } from "@testing-library/react";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
  import { AlertDialog } from "../../components";
4
- import { click } from "../../utils";
4
+ import { click } from "../../utils/tests";
5
5
 
6
6
  describe("AlertDialog component", () => {
7
7
  it("should show custom title and description", () => {
@@ -69,18 +69,26 @@ export function AlertDialogViewport({
69
69
  /**
70
70
  * Dialog content container. Includes Portal, Backdrop and Viewport internally.
71
71
  * Pass `portalProps` to configure the portal (e.g. a custom `container`).
72
+ * `classNames` styles the internal `backdrop` and `viewport` slots.
72
73
  */
73
74
  export function AlertDialogPopup({
74
75
  className,
76
+ classNames,
75
77
  portalProps,
76
78
  ...props
77
79
  }: AlertDialogBase.Popup.Props & {
78
80
  portalProps?: AlertDialogBase.Portal.Props;
81
+ classNames?: {
82
+ /** Overlay rendered behind the dialog. */
83
+ backdrop?: string;
84
+ /** Full-screen container that centers the popup. */
85
+ viewport?: string;
86
+ };
79
87
  }) {
80
88
  return (
81
89
  <AlertDialogBase.Portal {...portalProps}>
82
- <AlertDialogBackdrop />
83
- <AlertDialogViewport>
90
+ <AlertDialogBackdrop className={classNames?.backdrop} />
91
+ <AlertDialogViewport className={classNames?.viewport}>
84
92
  <AlertDialogBase.Popup
85
93
  data-slot="alert-dialog-popup"
86
94
  className={cn(
@@ -194,8 +202,25 @@ export type AlertDialogProps = Omit<AlertDialogBase.Root.Props, "children"> & {
194
202
  closeText?: ReactNode;
195
203
  /** Label for the action button. @default "Confirm" */
196
204
  actionText?: ReactNode;
197
- /** Additional className forwarded to the popup panel. */
205
+ /** Styles the dialog popup panel. */
198
206
  className?: string;
207
+ /** Styles applied to each internal slot. */
208
+ classNames?: {
209
+ /** Overlay rendered behind the dialog. */
210
+ backdrop?: string;
211
+ /** Layout wrapper for title and description. */
212
+ header?: string;
213
+ /** Dialog heading. */
214
+ title?: string;
215
+ /** Supporting text below the title. */
216
+ description?: string;
217
+ /** Layout wrapper for action buttons. */
218
+ footer?: string;
219
+ /** Cancel button. */
220
+ closeButton?: string;
221
+ /** Action button. */
222
+ actionButton?: string;
223
+ };
199
224
  };
200
225
 
201
226
  /**
@@ -205,6 +230,10 @@ export type AlertDialogProps = Omit<AlertDialogBase.Root.Props, "children"> & {
205
230
  * making it composable with `<Tooltip>` at the primitive level.
206
231
  * For full structural control, use the exported primitives directly.
207
232
  *
233
+ * `className` styles the popup panel. `classNames` exposes the `backdrop`,
234
+ * `header`, `title`, `description`, `footer`, `closeButton`, and `actionButton`
235
+ * slots for fine-grained tweaks without dropping to primitives.
236
+ *
208
237
  * @example
209
238
  * ```tsx
210
239
  * <AlertDialog
@@ -228,29 +257,48 @@ export function AlertDialog({
228
257
  closeText = "Cancel",
229
258
  actionText = "Confirm",
230
259
  className,
260
+ classNames,
231
261
  ...props
232
262
  }: AlertDialogProps) {
233
263
  return (
234
264
  <AlertDialogRoot {...props}>
235
265
  {trigger && <AlertDialogTrigger render={trigger} />}
236
- <AlertDialogPopup className={className}>
266
+ <AlertDialogPopup
267
+ className={className}
268
+ classNames={{ backdrop: classNames?.backdrop }}
269
+ >
237
270
  {(title || description) && (
238
- <AlertDialogHeader>
239
- {title && <AlertDialogTitle>{title}</AlertDialogTitle>}
271
+ <AlertDialogHeader className={classNames?.header}>
272
+ {title && (
273
+ <AlertDialogTitle className={classNames?.title}>
274
+ {title}
275
+ </AlertDialogTitle>
276
+ )}
240
277
  {description && (
241
- <AlertDialogDescription>{description}</AlertDialogDescription>
278
+ <AlertDialogDescription className={classNames?.description}>
279
+ {description}
280
+ </AlertDialogDescription>
242
281
  )}
243
282
  </AlertDialogHeader>
244
283
  )}
245
284
  {children}
246
- <AlertDialogFooter>
285
+ <AlertDialogFooter className={classNames?.footer}>
247
286
  <AlertDialogClose
248
- render={<Button variant="secondary">{closeText}</Button>}
287
+ render={
288
+ <Button variant="secondary" className={classNames?.closeButton}>
289
+ {closeText}
290
+ </Button>
291
+ }
249
292
  onClick={onClose}
250
293
  />
251
294
  <AlertDialogClose
252
295
  render={
253
- <Button variant={variant ?? "default"}>{actionText}</Button>
296
+ <Button
297
+ variant={variant ?? "default"}
298
+ className={classNames?.actionButton}
299
+ >
300
+ {actionText}
301
+ </Button>
254
302
  }
255
303
  onClick={onAction}
256
304
  />