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

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 +1083 -717
  3. package/dist/index.es.js +7059 -56179
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Changelog.mdx +6 -6
  7. package/src/__doc__/Examples.tsx +1 -1
  8. package/src/__doc__/Intro.mdx +3 -3
  9. package/src/__doc__/Tabs.mdx +112 -0
  10. package/src/__doc__/V2.mdx +1245 -0
  11. package/src/components/accordion/accordion.stories.tsx +143 -0
  12. package/src/components/accordion/accordion.tsx +135 -0
  13. package/src/components/accordion/index.ts +1 -0
  14. package/src/components/alert/alert.stories.tsx +24 -4
  15. package/src/components/alert/alert.tsx +17 -9
  16. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  17. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  18. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  19. package/src/components/auto-complete/auto-complete.stories.tsx +615 -200
  20. package/src/components/auto-complete/auto-complete.tsx +420 -68
  21. package/src/components/auto-complete/index.ts +0 -1
  22. package/src/components/avatar/avatar.stories.tsx +162 -21
  23. package/src/components/avatar/avatar.tsx +79 -20
  24. package/src/components/button/button.stories.tsx +236 -294
  25. package/src/components/button/button.test.tsx +10 -17
  26. package/src/components/button/button.tsx +53 -18
  27. package/src/components/button/components/base-button.tsx +25 -53
  28. package/src/components/button/index.ts +0 -1
  29. package/src/components/calendar/calendar.stories.tsx +1 -1
  30. package/src/components/calendar/calendar.tsx +4 -4
  31. package/src/components/card/card.stories.tsx +140 -69
  32. package/src/components/card/card.tsx +155 -54
  33. package/src/components/center/center.stories.tsx +22 -39
  34. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  35. package/src/components/checkbox/checkbox.tsx +76 -15
  36. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  37. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  38. package/src/components/combobox/combobox.stories.tsx +33 -23
  39. package/src/components/combobox/combobox.tsx +120 -104
  40. package/src/components/date-picker/date-input.stories.tsx +14 -6
  41. package/src/components/date-picker/date-input.tsx +3 -3
  42. package/src/components/date-picker/date-picker.model.ts +13 -4
  43. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  44. package/src/components/date-picker/date-picker.tsx +29 -15
  45. package/src/components/dialog/dialog.stories.tsx +18 -0
  46. package/src/components/dialog/dialog.test.tsx +1 -1
  47. package/src/components/dialog/dialog.tsx +51 -20
  48. package/src/components/divider/divider.stories.tsx +6 -0
  49. package/src/components/dropzone/dropzone.stories.tsx +70 -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 +164 -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 +226 -3
  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 +5 -5
  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 +275 -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 +29 -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 +153 -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 +26 -10
  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 +766 -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 +372 -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 +147 -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 +322 -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 +364 -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 +69 -37
  130. package/src/utils/index.ts +1 -1
  131. package/src/__doc__/Migration.mdx +0 -451
  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,22 +1,30 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { cva, VariantProps } from "class-variance-authority";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
3
  import { Flame, SearchIcon } from "lucide-react";
4
- import { ComponentProps } from "react";
5
- import { BaseButton, Button, Heading } from "../../components";
4
+ import { type ComponentProps } from "react";
5
+ import { BaseButton, Button, buttonVariants } from "../../components";
6
6
  import { cn } from "../../lib";
7
- import { ThemeProvider } from "../../providers";
8
7
 
9
8
  /**
10
- * Componente principal para ejecutar acciones.
9
+ * Primary action trigger built on the [Base UI Button](https://base-ui.com/react/components/button) primitive.
11
10
  *
12
- * Extendiende al componente `BaseButton` agregándole variantes
11
+ * **Loading** pass an async function to `onClick` and loading state is managed automatically.
12
+ * Control it externally with the `loading` prop. The spinner overlays content while preserving
13
+ * the button's original dimensions so layout never shifts.
14
+ *
15
+ * **Custom elements** — use the `render` prop to swap the underlying element.
16
+ * For links, apply `buttonVariants` directly to a plain `<a>` — Base UI always sets
17
+ * `role="button"` which overrides the semantic link role on `<a>` elements rendered via `render`.
18
+ *
19
+ * **Extension** — use `BaseButton` to build fully custom variants without inheriting any styles.
13
20
  */
14
21
  const meta: Meta<typeof Button> = {
15
- title: "Data entry/Button",
22
+ title: "Components/Button",
16
23
  component: Button,
17
24
  args: {
18
- children: "Click me!",
25
+ children: "Click me",
19
26
  },
27
+ parameters: { layout: "centered" },
20
28
  };
21
29
 
22
30
  export default meta;
@@ -24,161 +32,194 @@ type Story = StoryObj<typeof Button>;
24
32
 
25
33
  export const Default: Story = {};
26
34
 
27
- export const Outline: Story = {
28
- args: {
29
- variant: "outline",
30
- },
31
- };
32
-
33
- export const Secondary: Story = {
34
- args: {
35
- variant: "secondary",
36
- },
37
- };
38
-
39
- export const Ghost: Story = {
40
- args: {
41
- variant: "ghost",
42
- },
43
- };
44
-
45
- export const Link: Story = {
46
- args: {
47
- variant: "link",
48
- },
35
+ export const Variants: Story = {
36
+ render: (args) => (
37
+ <div className="flex flex-wrap items-center gap-3">
38
+ {(
39
+ [
40
+ "default",
41
+ "outline",
42
+ "secondary",
43
+ "ghost",
44
+ "link",
45
+ "error",
46
+ "success",
47
+ "warning",
48
+ ] as const
49
+ ).map((variant) => (
50
+ <Button key={variant} {...args} variant={variant}>
51
+ {variant}
52
+ </Button>
53
+ ))}
54
+ </div>
55
+ ),
49
56
  };
50
57
 
51
- export const Error: Story = {
52
- args: {
53
- variant: "error",
54
- },
55
- };
58
+ export const Sizes: Story = {
59
+ render: () => {
60
+ const textSizes = ["xs", "sm", "default", "lg"] as const;
61
+ const iconSizes = ["icon-xs", "icon-sm", "icon", "icon-lg"] as const;
56
62
 
57
- export const Success: Story = {
58
- args: {
59
- variant: "success",
63
+ return (
64
+ <div className="space-y-6">
65
+ <div className="flex flex-wrap items-end gap-4">
66
+ {textSizes.map((size) => (
67
+ <div key={size} className="flex flex-col items-center gap-2">
68
+ <p className="text-xs text-muted-foreground">{size}</p>
69
+ <Button size={size}>Click me</Button>
70
+ </div>
71
+ ))}
72
+ </div>
73
+ <div className="flex flex-wrap items-end gap-4">
74
+ {iconSizes.map((size) => (
75
+ <div key={size} className="flex flex-col items-center gap-2">
76
+ <p className="text-xs text-muted-foreground">{size}</p>
77
+ <Button size={size}>
78
+ <Flame />
79
+ </Button>
80
+ </div>
81
+ ))}
82
+ </div>
83
+ </div>
84
+ );
60
85
  },
61
86
  };
62
87
 
63
88
  /**
64
- * Se puede agregar un ícono al botón con la prop `icon`.
89
+ * Compose icons directly as children. Position is determined by JSX order —
90
+ * place the icon before or after the label. Use an icon-only size (`icon`,
91
+ * `icon-sm`, etc.) for square icon buttons.
65
92
  */
66
- export const Icon: Story = {
67
- args: {
68
- icon: <SearchIcon />,
69
- },
70
- };
93
+ export const WithIcon: Story = {
94
+ render: (args) => (
95
+ <div className="flex flex-wrap items-center gap-4">
96
+ <Button {...args}>
97
+ <SearchIcon />
98
+ Search
99
+ </Button>
100
+ <Button {...args}>
101
+ Search
102
+ <SearchIcon />
103
+ </Button>
104
+ <Button size="icon">
105
+ <SearchIcon />
106
+ </Button>
107
+ </div>
108
+ ),
109
+ parameters: {
110
+ docs: {
111
+ source: {
112
+ code: `<Button>
113
+ <SearchIcon />
114
+ Search
115
+ </Button>
71
116
 
72
- /**
73
- * Se puede cambiar la posición del ícono con la prop `iconPosition`.
74
- *
75
- * Los valores posibles son `start` y `end`.
76
- */
77
- export const IconPosition: Story = {
78
- args: {
79
- icon: <SearchIcon />,
80
- iconPosition: "end",
117
+ <Button>
118
+ Search
119
+ <SearchIcon />
120
+ </Button>
121
+
122
+ <Button size="icon">
123
+ <SearchIcon />
124
+ </Button>`,
125
+ },
126
+ },
81
127
  },
82
128
  };
83
129
 
84
130
  /**
85
- * El button recibe la propieda `loading` que permite controlar el estado de carga del botón.
86
- * Se puede cambiar la forma en la que se muestra el loader con la prop `loaderReplace`.
87
- * Si `loaderReplace` es `true`, el loader reemplaza todo el contenido manteniendo el ancho original del botón.
88
- * Por defecto, el loader se agrega al contenido existente o reemplaza al ícono en caso de que exista.
131
+ * When `onClick` returns a `Promise`, loading activates automatically and stops when
132
+ * the Promise settles no external state needed. Pass `loading` directly to control it
133
+ * from outside (e.g., from a form submission handler or mutation hook).
134
+ *
135
+ * While loading, the button is non-interactive (`pointer-events-none`, `aria-busy`)
136
+ * and the spinner overlays content without changing the button's dimensions.
89
137
  */
90
138
  export const Loading: Story = {
91
139
  render: () => {
92
- const sleep = (): Promise<void> => {
93
- return new Promise((resolve) => setTimeout(resolve, 2000));
94
- };
140
+ const sleep = (): Promise<void> =>
141
+ new Promise((resolve) => setTimeout(resolve, 2000));
95
142
 
96
143
  return (
97
- <div className="space-y-8">
98
- {/* Modo Append (default) */}
99
- <section>
100
- <Heading as="h2" className="mb-4">
101
- Modo Append (default)
102
- </Heading>
103
- <p className="text-sm text-muted-foreground mb-4">
104
- En este modo, el loader se agrega al contenido existente. Si hay un
105
- ícono, el loader lo reemplaza.
144
+ <div className="space-y-10">
145
+ <section className="space-y-3">
146
+ <h2 className="text-sm font-semibold">Auto — async onClick</h2>
147
+ <p className="text-sm text-muted-foreground">
148
+ Click any button. Loading starts when the Promise begins and stops
149
+ when it resolves.
106
150
  </p>
107
- <div className="flex flex-wrap items-center gap-4">
108
- <Button onClick={sleep}>Por izquierda</Button>
109
- <Button onClick={sleep} iconPosition="end">
110
- Por derecha
111
- </Button>
112
- <Button onClick={sleep} icon={<SearchIcon />}>
113
- Ícono inicio
151
+ <div className="flex flex-wrap items-center gap-3">
152
+ <Button onClick={sleep}>Save</Button>
153
+ <Button onClick={sleep}>
154
+ <SearchIcon />
155
+ Search
114
156
  </Button>
115
- <Button onClick={sleep} icon={<SearchIcon />} iconPosition="end">
116
- Ícono final
157
+ <Button onClick={sleep} size="icon">
158
+ <SearchIcon />
117
159
  </Button>
118
- <Button onClick={sleep} icon={<SearchIcon />} size="icon" />
119
160
  </div>
120
161
  </section>
121
162
 
122
- {/* Modo Replace */}
123
- <section>
124
- <Heading as="h2" className="mb-4">
125
- Modo Replace (loaderReplace = true)
126
- </Heading>
127
- <p className="text-sm text-muted-foreground mb-4">
128
- En este modo, el loader reemplaza todo el contenido manteniendo el
129
- ancho original del botón.
163
+ <section className="space-y-3">
164
+ <h2 className="text-sm font-semibold">Controlled</h2>
165
+ <p className="text-sm text-muted-foreground">
166
+ Pass the <code>loading</code> prop directly to control state
167
+ externally.
130
168
  </p>
131
- <div className="flex flex-wrap items-center gap-4">
132
- <Button onClick={sleep} loaderReplace>
133
- Solo texto
134
- </Button>
135
- <Button onClick={sleep} loaderReplace icon={<SearchIcon />}>
136
- Ícono inicio
169
+ <div className="flex flex-wrap items-center gap-3">
170
+ <Button loading>Save</Button>
171
+ <Button loading>
172
+ <SearchIcon />
173
+ Search
137
174
  </Button>
138
- <Button
139
- onClick={sleep}
140
- loaderReplace
141
- icon={<SearchIcon />}
142
- iconPosition="end"
143
- >
144
- Ícono final
175
+ <Button loading size="icon">
176
+ <SearchIcon />
145
177
  </Button>
146
- <Button
147
- onClick={sleep}
148
- loaderReplace
149
- icon={<SearchIcon />}
150
- size="icon"
151
- />
152
178
  </div>
153
179
  </section>
154
180
 
155
- {/* Variantes */}
156
- <section>
157
- <Heading as="h2" className="mb-4">
158
- Variantes
159
- </Heading>
160
- <p className="text-sm text-muted-foreground mb-4">
161
- El loader funciona con todas las variantes de botón.
162
- </p>
163
- <div className="flex flex-wrap items-center gap-4">
164
- <Button onClick={sleep} variant="outline" icon={<SearchIcon />}>
165
- Outline
166
- </Button>
167
- <Button onClick={sleep} variant="secondary" icon={<SearchIcon />}>
168
- Secondary
169
- </Button>
170
- <Button onClick={sleep} variant="ghost" icon={<SearchIcon />}>
171
- Ghost
172
- </Button>
173
- <Button onClick={sleep} variant="link" icon={<SearchIcon />}>
174
- Link
175
- </Button>
176
- <Button onClick={sleep} variant="error" icon={<SearchIcon />}>
177
- Error
178
- </Button>
179
- <Button onClick={sleep} variant="success" icon={<SearchIcon />}>
180
- Success
181
- </Button>
181
+ <section className="space-y-3">
182
+ <h2 className="text-sm font-semibold">All sizes</h2>
183
+ <div className="flex flex-wrap items-end gap-4">
184
+ {(["xs", "sm", "default", "lg"] as const).map((size) => (
185
+ <div key={size} className="flex flex-col items-center gap-2">
186
+ <p className="text-xs text-muted-foreground">{size}</p>
187
+ <Button loading size={size}>
188
+ Save
189
+ </Button>
190
+ </div>
191
+ ))}
192
+ {(["icon-xs", "icon-sm", "icon", "icon-lg"] as const).map(
193
+ (size) => (
194
+ <div key={size} className="flex flex-col items-center gap-2">
195
+ <p className="text-xs text-muted-foreground">{size}</p>
196
+ <Button loading size={size}>
197
+ <SearchIcon />
198
+ </Button>
199
+ </div>
200
+ ),
201
+ )}
202
+ </div>
203
+ </section>
204
+
205
+ <section className="space-y-3">
206
+ <h2 className="text-sm font-semibold">All variants</h2>
207
+ <div className="flex flex-wrap items-center gap-3">
208
+ {(
209
+ [
210
+ "default",
211
+ "outline",
212
+ "secondary",
213
+ "ghost",
214
+ "error",
215
+ "success",
216
+ "warning",
217
+ ] as const
218
+ ).map((variant) => (
219
+ <Button key={variant} onClick={sleep} variant={variant}>
220
+ {variant}
221
+ </Button>
222
+ ))}
182
223
  </div>
183
224
  </section>
184
225
  </div>
@@ -187,83 +228,55 @@ export const Loading: Story = {
187
228
  };
188
229
 
189
230
  /**
190
- * Al setear `asChild` a `true`, puedes renderizar cualquier elemento con los estilos, props y referencias del `Button`.
231
+ * Use the `render` prop to render the button as any element while keeping all styles
232
+ * and behavior. For navigation links, use `buttonVariants` on a plain `<a>` tag instead —
233
+ * Base UI always applies `role="button"` which overrides the semantic `<a>` role.
191
234
  *
192
- * En este caso, se usa un elemento `a` que hereda los estilos, props y referencias del `Button`.
193
- * Esto es útil para aplicar los mismos estilos y garantizar consistencia en la interfaz de usuario de su aplicación.
194
- * Se puede verificar abriendo la consola del navegador.
195
- */
196
- export const AsChild: Story = {
197
- args: {
198
- children: <a href="#">Click me</a>,
199
- variant: "error",
200
- asChild: true,
201
- },
202
- };
203
-
204
- /**
205
- * El componente recibe todas las propiedades de `button`, por lo que se puede customizar los estilos, accesibilidad, referencias, eventos, etc
206
- */
207
- export const Custom: Story = {
208
- args: {
209
- className: "bg-amber-500 hover:bg-amber-600",
210
- },
211
- };
212
-
213
- /**
214
- * Estos son los tamanos disponibles para el `Button`.
235
+ * ```tsx
236
+ * // Render as a different element
237
+ * <Button render={<div />}>Custom element</Button>
238
+ *
239
+ * // Link styled as a button — correct pattern for navigation
240
+ * <a href="/dashboard" className={buttonVariants({ variant: "outline" })}>
241
+ * Go to dashboard
242
+ * </a>
243
+ * ```
215
244
  */
216
- export const Sizes: Story = {
217
- render: () => {
218
- const sizes = ["default", "sm", "lg", "icon"] as const;
219
-
220
- return (
221
- <>
222
- {sizes.map((size) => (
223
- <div key={size} className="mb-1">
224
- <p className="font-semibold">{`size: ${size}`}</p>
225
- <Button size={size} key={size}>
226
- {size === "icon" ? <Flame /> : "Click me"}
227
- </Button>
228
- </div>
229
- ))}
230
- </>
231
- );
232
- },
233
- };
234
-
235
- export const Shapes: Story = {
236
- render: () => {
237
- const shapes = ["rounded", "square", "circle"] as const;
245
+ export const Render: Story = {
246
+ render: (args) => (
247
+ <div className="flex flex-wrap items-center gap-4">
248
+ <Button {...args} render={<div />}>
249
+ Rendered as div
250
+ </Button>
251
+ <a href="#" className={buttonVariants({ variant: "outline" })}>
252
+ Link as button
253
+ </a>
254
+ </div>
255
+ ),
256
+ parameters: {
257
+ docs: {
258
+ source: {
259
+ code: `<Button render={<div />}>Rendered as div</Button>
238
260
 
239
- return (
240
- <div className="flex flex-wrap items-center gap-4">
241
- {shapes.map((shape) => (
242
- <div key={shape} className="flex flex-col gap-2">
243
- <p className="font-semibold capitalize">{shape}</p>
244
- <Button shape={shape} key={shape} size="icon" icon={<Flame />} />
245
- <Button shape={shape} key={shape}>
246
- {shape}
247
- </Button>
248
- </div>
249
- ))}
250
- </div>
251
- );
261
+ <a href="/dashboard" className={buttonVariants({ variant: "outline" })}>
262
+ Link as button
263
+ </a>`,
264
+ },
265
+ },
252
266
  },
253
267
  };
254
268
 
255
- const buttonVariants = cva(
269
+ const customButtonVariants = cva(
256
270
  [
257
- "flex items-center justify-center gap-2 px-4 py-2",
258
- "text-sm font-medium transition-all",
259
- "rounded ring-offset-background",
260
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
271
+ "inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2",
272
+ "text-sm font-medium transition-all outline-none select-none",
273
+ "focus-visible:ring-3 focus-visible:ring-ring/50",
261
274
  "disabled:pointer-events-none disabled:opacity-50",
262
275
  ],
263
276
  {
264
277
  variants: {
265
278
  variant: {
266
- white: "bg-white text-primary border",
279
+ white: "border border-border bg-white text-primary",
267
280
  black: "bg-black text-white",
268
281
  },
269
282
  },
@@ -274,123 +287,52 @@ const buttonVariants = cva(
274
287
  );
275
288
 
276
289
  type BaseButtonProps = ComponentProps<typeof BaseButton>;
277
- interface Props extends BaseButtonProps, VariantProps<typeof buttonVariants> {}
290
+ interface CustomButtonProps
291
+ extends BaseButtonProps,
292
+ VariantProps<typeof customButtonVariants> {}
278
293
 
279
- const CustomButton = ({ variant, ...props }: Props) => {
280
- return <BaseButton className={cn(buttonVariants({ variant }))} {...props} />;
281
- };
294
+ const CustomButton = ({ variant, className, ...props }: CustomButtonProps) => (
295
+ <BaseButton
296
+ className={cn(customButtonVariants({ variant }), className)}
297
+ {...props}
298
+ />
299
+ );
282
300
 
283
301
  /**
284
- * #### Crea tus propias variantes
285
- * En caso de que necesites customizar demasiado a tu componente, lo mejor es crear tus propias variantes.
286
- * Para esto se puede importar el componente `BaseButton` el cuál no incluye estilos por defecto y se puede extender para crear variantes personalizadas.
287
- *
288
- * A continuación, se definen las variantes de botón que puedes extender o modificar a tus necesidades.
302
+ * `BaseButton` is an unstyled wrapper around the Base UI primitive. Use it to build
303
+ * fully custom button variants without inheriting any library styles.
304
+ * It supports all native button props plus `loading` and the `render` prop.
289
305
  *
290
306
  * ```tsx
291
- * import { ComponentProps } from 'react';
307
+ * import { type ComponentProps } from 'react';
292
308
  * import { cva, type VariantProps } from 'class-variance-authority';
293
309
  * import { BaseButton, cn } from '@boxcustodia/library';
294
310
  *
295
- * export const buttonVariants = cva(
296
- * [
297
- * 'flex items-center justify-center gap-2',
298
- * 'text-sm font-medium transition-all',
299
- * 'rounded-md ring-offset-background',
300
- * 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
301
- * 'disabled:pointer-events-none disabled:opacity-50',
302
- * 'hover:brightness-105',
303
- * ],
311
+ * const myVariants = cva(
312
+ * 'inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-all outline-none disabled:pointer-events-none disabled:opacity-50',
304
313
  * {
305
314
  * variants: {
306
315
  * variant: {
307
- * default: "bg-primary text-primary-foreground",
308
- * error: "bg-error text-error-foreground",
309
- * success: "bg-success text-success-foreground",
310
- * outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
311
- * secondary: "bg-secondary text-secondary-foreground hover:brightness-100 hover:bg-secondary/80",
312
- * ghost: "hover:bg-accent hover:brightness-100 hover:text-accent-foreground",
313
- * link: "text-primary underline-offset-4 hover:underline",
314
- * white: 'bg-white text-primary', <-- Nueva variante
315
- * black: 'bg-black text-white', <-- Nueva variante
316
- * },
317
- * size: {
318
- * default: "h-10 px-4 py-2",
319
- * sm: "h-9 rounded-md px-3",
320
- * lg: "h-11 rounded-md px-8",
321
- * icon: "w-10 aspect-square",
316
+ * white: 'border border-border bg-white text-primary',
317
+ * black: 'bg-black text-white',
322
318
  * },
323
319
  * },
324
- * defaultVariants: {
325
- * variant: 'default',
326
- * size: 'default',
327
- * },
320
+ * defaultVariants: { variant: 'white' },
328
321
  * },
329
322
  * );
330
323
  *
331
- * type BaseButtonProps = ComponentProps<typeof BaseButton>;
332
- * typer Props = BaseButtonProps, VariantProps<typeof buttonVariants>
333
- *
334
- * export const Button = ({ className, variant, size, ...props }: Props) => {
335
- * return (
336
- * <BaseButton
337
- * {...props}
338
- * className={cn(buttonVariants({ variant, size }), className)}
339
- * />
340
- * );
341
- * };
324
+ * type Props = ComponentProps<typeof BaseButton> & VariantProps<typeof myVariants>;
342
325
  *
326
+ * export const CustomButton = ({ variant, className, ...props }: Props) => (
327
+ * <BaseButton className={cn(myVariants({ variant }), className)} {...props} />
328
+ * );
343
329
  * ```
344
- *
345
330
  */
346
-
347
331
  export const CustomVariants: Story = {
348
332
  render: () => (
349
- <div className="space-y-4">
350
- <CustomButton variant="black">Default Button</CustomButton>
351
- <CustomButton variant="white">Error Button</CustomButton>
333
+ <div className="flex flex-wrap items-center gap-4">
334
+ <CustomButton variant="white">White button</CustomButton>
335
+ <CustomButton variant="black">Black button</CustomButton>
352
336
  </div>
353
337
  ),
354
338
  };
355
-
356
- /**
357
- * ThemeProvider permite customizar de manera global las propiedades del componente
358
- *
359
- * Ejemplo:
360
- * ```tsx
361
- * <ThemeProvider
362
- * theme={{
363
- * Button: {
364
- * className: "bg-red-500",
365
- * icon: <SearchIcon />,
366
- * iconPosition: "end",
367
- * loaderReplace: true,
368
- * },
369
- * }}
370
- * >
371
- * <Button>Default Button</Button>
372
- * </ThemeProvider>
373
- * ```
374
- */
375
- export const Theme: Story = {
376
- render: () => {
377
- const sleep = (): Promise<void> => {
378
- return new Promise((resolve) => setTimeout(resolve, 2000));
379
- };
380
-
381
- return (
382
- <ThemeProvider
383
- theme={{
384
- Button: {
385
- className: "bg-red-500",
386
- icon: <SearchIcon />,
387
- iconPosition: "end",
388
- loaderReplace: true,
389
- },
390
- }}
391
- >
392
- <Button onClick={sleep}>Default Button</Button>
393
- </ThemeProvider>
394
- );
395
- },
396
- };