@boxcustodia/library 2.0.0-alpha.12 → 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 (174) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1087 -720
  3. package/dist/index.es.js +7011 -56097
  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 +99 -77
  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 +126 -51
  48. package/src/components/divider/divider.tsx +16 -16
  49. package/src/components/dropzone/dropzone.stories.tsx +71 -90
  50. package/src/components/dropzone/dropzone.tsx +383 -105
  51. package/src/components/dropzone/index.ts +0 -1
  52. package/src/components/empty/empty.stories.tsx +165 -0
  53. package/src/components/empty/empty.tsx +156 -0
  54. package/src/components/empty/index.ts +1 -0
  55. package/src/components/field/field.stories.tsx +227 -4
  56. package/src/components/field/field.tsx +77 -42
  57. package/src/components/form/form.stories.tsx +320 -197
  58. package/src/components/form/form.tsx +3 -23
  59. package/src/components/index.ts +2 -6
  60. package/src/components/input/input.stories.tsx +5 -5
  61. package/src/components/input/input.tsx +4 -4
  62. package/src/components/kbd/kbd.stories.tsx +1 -0
  63. package/src/components/label/label.stories.tsx +16 -0
  64. package/src/components/label/label.tsx +13 -2
  65. package/src/components/loader/loader.stories.tsx +7 -5
  66. package/src/components/loader/loader.tsx +8 -3
  67. package/src/components/menu/menu-primitives.tsx +207 -196
  68. package/src/components/menu/menu.stories.tsx +276 -146
  69. package/src/components/menu/menu.tsx +146 -54
  70. package/src/components/number-input/number-input.stories.tsx +27 -4
  71. package/src/components/number-input/number-input.test.tsx +2 -2
  72. package/src/components/number-input/number-input.tsx +31 -33
  73. package/src/components/otp/index.ts +1 -0
  74. package/src/components/otp/otp.stories.tsx +209 -0
  75. package/src/components/otp/otp.tsx +100 -0
  76. package/src/components/pagination/index.ts +1 -0
  77. package/src/components/pagination/pagination.model.ts +2 -0
  78. package/src/components/pagination/pagination.stories.tsx +154 -59
  79. package/src/components/pagination/pagination.test.tsx +122 -57
  80. package/src/components/pagination/pagination.tsx +575 -77
  81. package/src/components/password/password.stories.tsx +18 -3
  82. package/src/components/password/password.tsx +29 -9
  83. package/src/components/popover/popover.stories.tsx +26 -5
  84. package/src/components/popover/popover.tsx +15 -23
  85. package/src/components/progress/progress.stories.tsx +1 -0
  86. package/src/components/radio-group/index.ts +1 -0
  87. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  88. package/src/components/radio-group/radio-group.tsx +212 -0
  89. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  90. package/src/components/select/select.stories.tsx +118 -19
  91. package/src/components/select/select.tsx +67 -62
  92. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  93. package/src/components/stack/stack.stories.tsx +179 -89
  94. package/src/components/stack/stack.tsx +2 -2
  95. package/src/components/stepper/index.ts +1 -1
  96. package/src/components/stepper/stepper.stories.tsx +767 -83
  97. package/src/components/stepper/stepper.test.tsx +18 -18
  98. package/src/components/stepper/stepper.tsx +554 -0
  99. package/src/components/switch/switch.stories.tsx +15 -1
  100. package/src/components/switch/switch.tsx +17 -4
  101. package/src/components/table/index.ts +0 -2
  102. package/src/components/table/table.stories.tsx +131 -18
  103. package/src/components/table/table.test.tsx +1 -1
  104. package/src/components/table/table.tsx +183 -77
  105. package/src/components/tabs/tabs.stories.tsx +373 -155
  106. package/src/components/tabs/tabs.test.tsx +12 -12
  107. package/src/components/tabs/tabs.tsx +72 -149
  108. package/src/components/tag/index.ts +0 -1
  109. package/src/components/tag/tag.stories.tsx +155 -120
  110. package/src/components/tag/tag.tsx +47 -95
  111. package/src/components/textarea/textarea.stories.tsx +8 -22
  112. package/src/components/textarea/textarea.tsx +17 -79
  113. package/src/components/timeline/timeline.stories.tsx +323 -42
  114. package/src/components/timeline/timeline.tsx +359 -132
  115. package/src/components/toast/toast.stories.tsx +1 -0
  116. package/src/components/tooltip/tooltip.tsx +11 -9
  117. package/src/components/tree/index.ts +0 -1
  118. package/src/components/tree/tree.stories.tsx +365 -408
  119. package/src/components/tree/tree.test.tsx +163 -0
  120. package/src/components/tree/tree.tsx +212 -36
  121. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  122. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  123. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  124. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  125. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  126. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  127. package/src/hooks/usePagination/usePagination.tsx +36 -24
  128. package/src/styles/theme.css +1 -1
  129. package/src/utils/form.tsx +67 -37
  130. package/src/utils/index.ts +1 -1
  131. package/src/__doc__/Migration.mdx +0 -475
  132. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  133. package/src/components/background-image/background-image.stories.tsx +0 -21
  134. package/src/components/background-image/background-image.test.tsx +0 -29
  135. package/src/components/background-image/background-image.tsx +0 -23
  136. package/src/components/background-image/index.ts +0 -1
  137. package/src/components/button/button.variants.ts +0 -44
  138. package/src/components/button/components/loader-overlay.tsx +0 -21
  139. package/src/components/button/components/loading-icon.tsx +0 -47
  140. package/src/components/dropzone/upload-primitives.tsx +0 -310
  141. package/src/components/dropzone/use-dropzone.ts +0 -122
  142. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  143. package/src/components/empty-state/empty-state.tsx +0 -39
  144. package/src/components/empty-state/index.ts +0 -1
  145. package/src/components/heading/heading.stories.tsx +0 -74
  146. package/src/components/heading/heading.tsx +0 -28
  147. package/src/components/heading/heading.variants.ts +0 -27
  148. package/src/components/heading/index.ts +0 -1
  149. package/src/components/kbd/kbd.variants.ts +0 -26
  150. package/src/components/menu/util/render-menu-item.tsx +0 -54
  151. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  152. package/src/components/multi-select/index.ts +0 -1
  153. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  154. package/src/components/multi-select/multi-select.tsx +0 -300
  155. package/src/components/multi-select/multi-select.variants.ts +0 -22
  156. package/src/components/pagination/components/pagination-option.tsx +0 -27
  157. package/src/components/show/index.ts +0 -1
  158. package/src/components/show/show.stories.tsx +0 -197
  159. package/src/components/show/show.test.tsx +0 -41
  160. package/src/components/show/show.tsx +0 -16
  161. package/src/components/stepper/Stepper.tsx +0 -190
  162. package/src/components/stepper/context/stepper-context.tsx +0 -11
  163. package/src/components/table/table-primitives.tsx +0 -122
  164. package/src/components/table/table.model.ts +0 -20
  165. package/src/components/table-pagination/index.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  167. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  168. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  169. package/src/components/table-pagination/table-pagination.tsx +0 -108
  170. package/src/components/tabs/context/tabs-context.tsx +0 -14
  171. package/src/components/tag/tag.variants.ts +0 -31
  172. package/src/components/timeline/timeline-status.ts +0 -5
  173. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  174. package/src/components/tree/tree-primitives.tsx +0 -126
@@ -1,182 +1,400 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { useState } from "react";
3
2
  import {
4
- Button,
5
- Input,
6
- TabContent,
7
- TabList,
8
- Tabs,
9
- TabTrigger,
10
- } from "../../components";
11
- import { useStep } from "../../hooks";
3
+ BellIcon,
4
+ CreditCardIcon,
5
+ LockIcon,
6
+ PaletteIcon,
7
+ UserIcon,
8
+ } from "lucide-react";
9
+ import React from "react";
10
+ import { Button } from "../../components";
11
+ import { Tabs, TabsList, TabsPanel, TabsPrimitive, TabsTab } from "./tabs";
12
12
 
13
+ /**
14
+ * Accessible tab component built on Base UI Tabs primitives.
15
+ *
16
+ * `variant` lives on `TabsList`, not on `Tabs`. Two variants are available:
17
+ * `"underline"` (default) renders a line indicator at the edge of the active
18
+ * tab; `"background"` renders a rounded background behind the active tab.
19
+ *
20
+ * Use `value` + `onValueChange` for controlled state.
21
+ * Set `orientation="vertical"` on `Tabs` to switch to a column layout.
22
+ */
13
23
  const meta: Meta<typeof Tabs> = {
14
- title: "Data display/Tabs",
24
+ title: "Components/Tabs",
15
25
  component: Tabs,
16
- args: {},
26
+ parameters: {
27
+ layout: "centered",
28
+ },
29
+ subcomponents: {
30
+ TabsList: TabsList as React.ComponentType<unknown>,
31
+ TabsTab: TabsTab as React.ComponentType<unknown>,
32
+ TabsPanel: TabsPanel as React.ComponentType<unknown>,
33
+ },
34
+ tags: ["beta"],
17
35
  };
18
36
 
19
37
  export default meta;
20
38
  type Story = StoryObj<typeof Tabs>;
21
39
 
22
- const AccountStep = () => {
23
- return (
24
- <>
25
- <Input name="name" />
26
- <Input name="lastname" />
27
- </>
28
- );
40
+ // ---------------------------------------------------------------------------
41
+ // Shared content blocks
42
+ // ---------------------------------------------------------------------------
43
+
44
+ const AccountPanel = () => (
45
+ <div className="space-y-4 py-2">
46
+ <div className="flex items-center gap-4">
47
+ <div className="size-14 rounded-full bg-muted flex items-center justify-center text-muted-foreground">
48
+ <UserIcon className="size-6" />
49
+ </div>
50
+ <div>
51
+ <p className="font-medium">John Doe</p>
52
+ <p className="text-sm text-muted-foreground">john@example.com</p>
53
+ </div>
54
+ </div>
55
+ <div className="grid gap-3">
56
+ <div className="grid gap-1.5">
57
+ <label className="text-sm font-medium">Display name</label>
58
+ <input
59
+ defaultValue="John Doe"
60
+ className="h-9 rounded-md border bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring"
61
+ />
62
+ </div>
63
+ <div className="grid gap-1.5">
64
+ <label className="text-sm font-medium">Email</label>
65
+ <input
66
+ defaultValue="john@example.com"
67
+ type="email"
68
+ className="h-9 rounded-md border bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring"
69
+ />
70
+ </div>
71
+ </div>
72
+ <div className="flex justify-end">
73
+ <Button size="sm">Save changes</Button>
74
+ </div>
75
+ </div>
76
+ );
77
+
78
+ const SecurityPanel = () => (
79
+ <div className="space-y-3 py-2">
80
+ {[
81
+ {
82
+ label: "Password",
83
+ meta: "Last changed 3 months ago",
84
+ action: "Change",
85
+ },
86
+ {
87
+ label: "Two-factor authentication",
88
+ meta: "Add an extra layer of security",
89
+ action: "Enable",
90
+ },
91
+ {
92
+ label: "Active sessions",
93
+ meta: "2 devices currently signed in",
94
+ action: "Manage",
95
+ },
96
+ ].map(({ label, meta, action }) => (
97
+ <div
98
+ key={label}
99
+ className="flex items-center justify-between rounded-lg border p-3.5"
100
+ >
101
+ <div>
102
+ <p className="text-sm font-medium">{label}</p>
103
+ <p className="text-xs text-muted-foreground">{meta}</p>
104
+ </div>
105
+ <Button variant="outline" size="sm">
106
+ {action}
107
+ </Button>
108
+ </div>
109
+ ))}
110
+ </div>
111
+ );
112
+
113
+ const NotificationsPanel = () => (
114
+ <div className="space-y-3 py-2">
115
+ {[
116
+ { title: "Email notifications", desc: "Weekly digest and alerts" },
117
+ { title: "Push notifications", desc: "Desktop and mobile" },
118
+ { title: "Security alerts", desc: "Unusual sign-in activity" },
119
+ ].map(({ title, desc }) => (
120
+ <div key={title} className="flex items-center justify-between py-1">
121
+ <div>
122
+ <p className="text-sm font-medium">{title}</p>
123
+ <p className="text-xs text-muted-foreground">{desc}</p>
124
+ </div>
125
+ <div className="size-5 rounded border border-input bg-background" />
126
+ </div>
127
+ ))}
128
+ </div>
129
+ );
130
+
131
+ const BillingPanel = () => (
132
+ <div className="space-y-3 py-2">
133
+ <div className="rounded-lg border p-4 flex items-start gap-3">
134
+ <CreditCardIcon className="size-5 mt-0.5 text-muted-foreground" />
135
+ <div className="flex-1">
136
+ <p className="text-sm font-medium">Pro plan · $12/month</p>
137
+ <p className="text-xs text-muted-foreground">Renews on Jun 1, 2025</p>
138
+ </div>
139
+ <Button variant="outline" size="sm">
140
+ Manage
141
+ </Button>
142
+ </div>
143
+ <div className="rounded-lg border p-4">
144
+ <p className="text-sm font-medium mb-2">Payment method</p>
145
+ <p className="text-sm text-muted-foreground">Visa ending in 4242</p>
146
+ </div>
147
+ </div>
148
+ );
149
+
150
+ const AppearancePanel = () => (
151
+ <div className="space-y-3 py-2">
152
+ <div>
153
+ <p className="text-sm font-medium mb-2">Theme</p>
154
+ <div className="flex gap-2">
155
+ {["Light", "Dark", "System"].map((t) => (
156
+ <button
157
+ key={t}
158
+ className="rounded-md border px-3 py-1.5 text-sm data-[active]:border-primary data-[active]:bg-primary/5 cursor-pointer"
159
+ data-active={t === "Light" ? "" : undefined}
160
+ >
161
+ {t}
162
+ </button>
163
+ ))}
164
+ </div>
165
+ </div>
166
+ <div>
167
+ <p className="text-sm font-medium mb-2">Density</p>
168
+ <div className="flex gap-2">
169
+ {["Compact", "Comfortable", "Spacious"].map((d) => (
170
+ <button
171
+ key={d}
172
+ className="rounded-md border px-3 py-1.5 text-sm cursor-pointer"
173
+ >
174
+ {d}
175
+ </button>
176
+ ))}
177
+ </div>
178
+ </div>
179
+ </div>
180
+ );
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Stories
184
+ // ---------------------------------------------------------------------------
185
+
186
+ export const Default: Story = {
187
+ render: () => (
188
+ <Tabs defaultValue="account" className="w-[480px] h-[340px]">
189
+ <TabsList>
190
+ <TabsTab value="account">Account</TabsTab>
191
+ <TabsTab value="security">Security</TabsTab>
192
+ <TabsTab value="notifications">Notifications</TabsTab>
193
+ </TabsList>
194
+ <TabsPanel value="account">
195
+ <AccountPanel />
196
+ </TabsPanel>
197
+ <TabsPanel value="security">
198
+ <SecurityPanel />
199
+ </TabsPanel>
200
+ <TabsPanel value="notifications">
201
+ <NotificationsPanel />
202
+ </TabsPanel>
203
+ </Tabs>
204
+ ),
29
205
  };
30
206
 
31
- const PasswordStep = () => {
32
- return (
33
- <>
34
- <Input name="password" type="password" />
35
- <Input name="confirmation" type="password" />
36
- </>
37
- );
207
+ export const Background: Story = {
208
+ render: () => (
209
+ <Tabs defaultValue="account" className="w-[480px] h-[340px]">
210
+ <TabsList variant="background">
211
+ <TabsTab value="account">Account</TabsTab>
212
+ <TabsTab value="security">Security</TabsTab>
213
+ <TabsTab value="notifications">Notifications</TabsTab>
214
+ </TabsList>
215
+ <TabsPanel value="account">
216
+ <AccountPanel />
217
+ </TabsPanel>
218
+ <TabsPanel value="security">
219
+ <SecurityPanel />
220
+ </TabsPanel>
221
+ <TabsPanel value="notifications">
222
+ <NotificationsPanel />
223
+ </TabsPanel>
224
+ </Tabs>
225
+ ),
38
226
  };
39
227
 
40
- const FileStep = () => {
41
- return (
42
- <>
43
- <Input name="file" type="file" />
44
- </>
45
- );
228
+ export const Vertical: Story = {
229
+ render: () => (
230
+ <Tabs
231
+ defaultValue="account"
232
+ orientation="vertical"
233
+ className="w-[620px] h-[300px]"
234
+ >
235
+ <TabsList className="w-44 shrink-0">
236
+ <TabsTab value="account">Account</TabsTab>
237
+ <TabsTab value="security">Security</TabsTab>
238
+ <TabsTab value="billing">Billing</TabsTab>
239
+ <TabsTab value="appearance">Appearance</TabsTab>
240
+ </TabsList>
241
+ <div className="flex-1 min-w-0">
242
+ <TabsPanel value="account">
243
+ <AccountPanel />
244
+ </TabsPanel>
245
+ <TabsPanel value="security">
246
+ <SecurityPanel />
247
+ </TabsPanel>
248
+ <TabsPanel value="billing">
249
+ <BillingPanel />
250
+ </TabsPanel>
251
+ <TabsPanel value="appearance">
252
+ <AppearancePanel />
253
+ </TabsPanel>
254
+ </div>
255
+ </Tabs>
256
+ ),
46
257
  };
47
258
 
48
- const steps = [
49
- {
50
- value: "account",
51
- content: <AccountStep />,
52
- },
53
- {
54
- value: "password",
55
- content: <PasswordStep />,
56
- },
57
- {
58
- value: "config",
59
- content: <FileStep />,
60
- },
61
- ];
259
+ export const PillVertical: Story = {
260
+ render: () => (
261
+ <Tabs
262
+ defaultValue="account"
263
+ orientation="vertical"
264
+ className="w-[620px] h-[300px]"
265
+ >
266
+ <TabsList variant="background" className="w-44 shrink-0">
267
+ <TabsTab value="account">Account</TabsTab>
268
+ <TabsTab value="security">Security</TabsTab>
269
+ <TabsTab value="billing">Billing</TabsTab>
270
+ <TabsTab value="appearance">Appearance</TabsTab>
271
+ </TabsList>
272
+ <div className="flex-1 min-w-0">
273
+ <TabsPanel value="account">
274
+ <AccountPanel />
275
+ </TabsPanel>
276
+ <TabsPanel value="security">
277
+ <SecurityPanel />
278
+ </TabsPanel>
279
+ <TabsPanel value="billing">
280
+ <BillingPanel />
281
+ </TabsPanel>
282
+ <TabsPanel value="appearance">
283
+ <AppearancePanel />
284
+ </TabsPanel>
285
+ </div>
286
+ </Tabs>
287
+ ),
288
+ };
62
289
 
63
- export const Default: Story = {
64
- render: () => {
65
- return (
66
- <Tabs defaultValue="account" className="container">
67
- <TabList className="max-w-[500px] mx-auto">
68
- <TabTrigger value="account">Account</TabTrigger>
69
- <TabTrigger value="password">Password</TabTrigger>
70
- <TabTrigger value="config">File</TabTrigger>
71
- </TabList>
72
-
73
- <TabContent value="account" className="space-y-2 min-h-[150px]">
74
- <AccountStep />
75
- </TabContent>
76
-
77
- <TabContent value="password" className="space-y-2 min-h-[150px]">
78
- <PasswordStep />
79
- </TabContent>
80
-
81
- <TabContent value="config" className="space-y-2 min-h-[150px]">
82
- <FileStep />
83
- </TabContent>
84
- </Tabs>
85
- );
86
- },
290
+ export const WithIcons: Story = {
291
+ render: () => (
292
+ <Tabs defaultValue="account" className="w-[480px] h-[340px]">
293
+ <TabsList>
294
+ <TabsTab value="account">
295
+ <UserIcon />
296
+ Account
297
+ </TabsTab>
298
+ <TabsTab value="security">
299
+ <LockIcon />
300
+ Security
301
+ </TabsTab>
302
+ <TabsTab value="notifications">
303
+ <BellIcon />
304
+ Notifications
305
+ </TabsTab>
306
+ <TabsTab value="appearance">
307
+ <PaletteIcon />
308
+ Appearance
309
+ </TabsTab>
310
+ </TabsList>
311
+ <TabsPanel value="account">
312
+ <AccountPanel />
313
+ </TabsPanel>
314
+ <TabsPanel value="security">
315
+ <SecurityPanel />
316
+ </TabsPanel>
317
+ <TabsPanel value="notifications">
318
+ <NotificationsPanel />
319
+ </TabsPanel>
320
+ <TabsPanel value="appearance">
321
+ <AppearancePanel />
322
+ </TabsPanel>
323
+ </Tabs>
324
+ ),
87
325
  };
88
326
 
89
327
  /**
90
- * Se puede utilizar el hook `useStep` para facilitar el manejo de estado y validaciones
328
+ * Any tab can be disabled via the `disabled` prop on `TabsTab`.
329
+ * The indicator skips over disabled tabs.
91
330
  */
92
- export const Steps: Story = {
93
- render: () => {
94
- const { step, isFirstStep, isLastStep, next, back, findAndGo } =
95
- useStep(steps);
96
-
97
- return (
98
- <Tabs
99
- value={step.value}
100
- onChange={(step: string) => findAndGo({ value: step })}
101
- className="container"
102
- >
103
- <TabList className="max-w-[500px] mx-auto">
104
- {steps.map(({ value }) => (
105
- <TabTrigger key={value} value={value} className="capitalize">
106
- {value}
107
- </TabTrigger>
108
- ))}
109
- </TabList>
110
-
111
- {steps.map(({ value, content }) => (
112
- <TabContent
113
- key={value}
114
- value={value}
115
- className="space-y-2 min-h-[150px]"
116
- >
117
- {content}
118
- </TabContent>
119
- ))}
120
- <div className="flex justify-end mt-2 gap-x-2">
121
- <Button variant="ghost" disabled={isFirstStep} onClick={back}>
122
- Prev
123
- </Button>
124
- <Button disabled={isLastStep} onClick={next}>
125
- Next
126
- </Button>
127
- </div>
128
- </Tabs>
129
- );
130
- },
331
+ export const Disabled: Story = {
332
+ render: () => (
333
+ <Tabs defaultValue="account" className="w-[480px] h-[340px]">
334
+ <TabsList>
335
+ <TabsTab value="account">Account</TabsTab>
336
+ <TabsTab value="security">Security</TabsTab>
337
+ <TabsTab value="billing" disabled>
338
+ Billing
339
+ </TabsTab>
340
+ <TabsTab value="enterprise" disabled>
341
+ Enterprise
342
+ </TabsTab>
343
+ </TabsList>
344
+ <TabsPanel value="account">
345
+ <AccountPanel />
346
+ </TabsPanel>
347
+ <TabsPanel value="security">
348
+ <SecurityPanel />
349
+ </TabsPanel>
350
+ </Tabs>
351
+ ),
131
352
  };
132
353
 
133
354
  /**
134
- * Se puede utilizar el prop `variant` para cambiar el estilo del indicador
135
- * `default` (línea) y `background` (fondo)
355
+ * Use `TabsPrimitive` to access the raw Base UI API when the styled wrappers
356
+ * don't cover your use case — custom indicators, additional ARIA attributes, etc.
136
357
  */
137
- export const IndicatorVariant: Story = {
138
- render: () => {
139
- const [variant, setVariant] = useState<"default" | "background">("default");
140
-
141
- return (
142
- <div className="space-y-8">
143
- <div className="flex items-center gap-4">
144
- <label htmlFor="variant" className="text-sm font-medium">
145
- Variant:
146
- </label>
147
- <select
148
- id="variant"
149
- value={variant}
150
- onChange={(e) =>
151
- setVariant(e.target.value as "default" | "background")
152
- }
153
- className="px-3 py-1 rounded border"
154
- >
155
- <option value="default">Default</option>
156
- <option value="background">Background</option>
157
- </select>
158
- </div>
159
-
160
- <Tabs defaultValue="account" variant={variant}>
161
- <TabList>
162
- <TabTrigger value="account">Account</TabTrigger>
163
- <TabTrigger value="password">Password</TabTrigger>
164
- <TabTrigger value="config">Config</TabTrigger>
165
- </TabList>
166
-
167
- <TabContent value="account" className="space-y-2 min-h-[150px]">
168
- <div>Account</div>
169
- </TabContent>
170
-
171
- <TabContent value="password" className="space-y-2 min-h-[150px]">
172
- <div>Password</div>
173
- </TabContent>
174
-
175
- <TabContent value="config" className="space-y-2 min-h-[150px]">
176
- <div>Config</div>
177
- </TabContent>
178
- </Tabs>
179
- </div>
180
- );
358
+ export const Primitive: Story = {
359
+ render: () => (
360
+ <TabsPrimitive.Root
361
+ defaultValue="account"
362
+ className="w-[480px] h-[340px] flex flex-col gap-2"
363
+ >
364
+ <TabsPrimitive.List className="flex gap-1 border-b">
365
+ <TabsPrimitive.Tab
366
+ value="account"
367
+ className="px-4 py-2 text-sm font-medium data-active:border-b-2 data-active:border-primary cursor-pointer"
368
+ >
369
+ Account
370
+ </TabsPrimitive.Tab>
371
+ <TabsPrimitive.Tab
372
+ value="security"
373
+ className="px-4 py-2 text-sm font-medium data-active:border-b-2 data-active:border-primary cursor-pointer"
374
+ >
375
+ Security
376
+ </TabsPrimitive.Tab>
377
+ </TabsPrimitive.List>
378
+ <TabsPrimitive.Panel value="account">
379
+ <AccountPanel />
380
+ </TabsPrimitive.Panel>
381
+ <TabsPrimitive.Panel value="security">
382
+ <SecurityPanel />
383
+ </TabsPrimitive.Panel>
384
+ </TabsPrimitive.Root>
385
+ ),
386
+ parameters: {
387
+ docs: {
388
+ source: {
389
+ code: `<TabsPrimitive.Root defaultValue="account">
390
+ <TabsPrimitive.List>
391
+ <TabsPrimitive.Tab value="account">Account</TabsPrimitive.Tab>
392
+ <TabsPrimitive.Tab value="security">Security</TabsPrimitive.Tab>
393
+ </TabsPrimitive.List>
394
+ <TabsPrimitive.Panel value="account">…</TabsPrimitive.Panel>
395
+ <TabsPrimitive.Panel value="security">…</TabsPrimitive.Panel>
396
+ </TabsPrimitive.Root>`,
397
+ },
398
+ },
181
399
  },
182
400
  };
@@ -1,7 +1,7 @@
1
1
  import { render, screen, waitFor } from "@testing-library/react";
2
2
  import { describe, expect, it, vi } from "vitest";
3
- import { TabContent, TabList, Tabs, TabTrigger } from "../../components";
4
- import { click } from "../../utils";
3
+ import { TabList, Tabs, TabsContent, TabsTrigger } from "../../components";
4
+ import { click } from "../../utils/tests";
5
5
 
6
6
  describe("Tabs component", () => {
7
7
  it("should render correctly", () => {
@@ -14,8 +14,8 @@ describe("Tabs component", () => {
14
14
  render(
15
15
  <Tabs>
16
16
  <TabList>
17
- <TabTrigger value="trigger">Trigger</TabTrigger>
18
- <TabTrigger value="test">Test</TabTrigger>
17
+ <TabsTrigger value="trigger">Trigger</TabsTrigger>
18
+ <TabsTrigger value="test">Test</TabsTrigger>
19
19
  </TabList>
20
20
  </Tabs>,
21
21
  );
@@ -28,10 +28,10 @@ describe("Tabs component", () => {
28
28
  render(
29
29
  <Tabs defaultValue="trigger">
30
30
  <TabList>
31
- <TabTrigger value="trigger">Trigger</TabTrigger>
32
- <TabTrigger value="test">Test</TabTrigger>
31
+ <TabsTrigger value="trigger">Trigger</TabsTrigger>
32
+ <TabsTrigger value="test">Test</TabsTrigger>
33
33
  </TabList>
34
- <TabContent value="trigger">Contenido de trigger</TabContent>
34
+ <TabsContent value="trigger">Contenido de trigger</TabsContent>
35
35
  </Tabs>,
36
36
  );
37
37
 
@@ -41,13 +41,13 @@ describe("Tabs component", () => {
41
41
  it("should be controlled", () => {
42
42
  const mock = vi.fn();
43
43
  render(
44
- <Tabs value="trigger" onChange={mock}>
44
+ <Tabs value="trigger" onValueChange={mock}>
45
45
  <TabList>
46
- <TabTrigger value="trigger">Trigger</TabTrigger>
47
- <TabTrigger value="test">Test</TabTrigger>
46
+ <TabsTrigger value="trigger">Trigger</TabsTrigger>
47
+ <TabsTrigger value="test">Test</TabsTrigger>
48
48
  </TabList>
49
- <TabContent value="trigger">Contenido de trigger</TabContent>
50
- <TabContent value="test">Contenido de test</TabContent>
49
+ <TabsContent value="trigger">Contenido de trigger</TabsContent>
50
+ <TabsContent value="test">Contenido de test</TabsContent>
51
51
  </Tabs>,
52
52
  );
53
53