@adamosuiteservices/ui 1.2.4 → 1.3.5

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 (178) hide show
  1. package/README.md +4 -0
  2. package/dist/accordion-rounded.cjs +1 -1
  3. package/dist/accordion-rounded.js +1 -1
  4. package/dist/accordion.cjs +1 -1
  5. package/dist/accordion.js +1 -1
  6. package/dist/avatar.cjs +1 -1
  7. package/dist/avatar.js +1 -1
  8. package/dist/badge.cjs +1 -1
  9. package/dist/badge.js +1 -1
  10. package/dist/breadcrumb.cjs +1 -0
  11. package/dist/breadcrumb.js +105 -0
  12. package/dist/{button-C1n6snOY.js → button-2GdKenQI.js} +1 -1
  13. package/dist/{button-BV-_FVKZ.cjs → button-DEQVHMrX.cjs} +1 -1
  14. package/dist/button-group.cjs +1 -1
  15. package/dist/button-group.js +2 -2
  16. package/dist/button.cjs +1 -1
  17. package/dist/button.js +1 -1
  18. package/dist/calendar.cjs +1 -1
  19. package/dist/calendar.js +1 -1
  20. package/dist/{checkbox-BrmXPKTn.js → checkbox-Dr487kAg.js} +3 -3
  21. package/dist/{checkbox-Lq-HvSgc.cjs → checkbox-YWAnswaW.cjs} +1 -1
  22. package/dist/checkbox.cjs +1 -1
  23. package/dist/checkbox.js +1 -1
  24. package/dist/collapsible.cjs +1 -1
  25. package/dist/collapsible.js +1 -1
  26. package/dist/combobox.cjs +1 -1
  27. package/dist/combobox.js +6 -6
  28. package/dist/components/ui/breadcrumb/breadcrumb.d.ts +11 -0
  29. package/dist/components/ui/breadcrumb/breadcrumb.stories.d.ts +26 -0
  30. package/dist/components/ui/breadcrumb/index.d.ts +1 -0
  31. package/dist/components/ui/dialog/dialog.d.ts +2 -1
  32. package/dist/context-menu.cjs +1 -1
  33. package/dist/context-menu.js +2 -2
  34. package/dist/custom-layered-styles.css +1 -1
  35. package/dist/dialog.cjs +1 -1
  36. package/dist/dialog.js +33 -19
  37. package/dist/dropdown-menu.cjs +1 -1
  38. package/dist/dropdown-menu.js +3 -3
  39. package/dist/ellipsis-CryjZKZn.js +15 -0
  40. package/dist/ellipsis-Ct9VTDOG.cjs +6 -0
  41. package/dist/field.cjs +1 -1
  42. package/dist/field.js +2 -2
  43. package/dist/hover-card.cjs +1 -1
  44. package/dist/hover-card.js +6 -6
  45. package/dist/{index-CAOY367Y.js → index-B0M7VOwp.js} +2 -2
  46. package/dist/{index-B-ZRqW0J.js → index-BBoIAjAs.js} +3 -3
  47. package/dist/{index-gO_QEiaK.cjs → index-BDs8lUfq.cjs} +1 -1
  48. package/dist/index-BFyr34mw.cjs +5 -0
  49. package/dist/index-BMWt1NBG.js +79 -0
  50. package/dist/{index-yR-v1A4G.js → index-BX9hz-JD.js} +1 -1
  51. package/dist/{index-BGiGvaq8.cjs → index-BcGMAmWE.cjs} +1 -1
  52. package/dist/{index-IKJMQref.cjs → index-Bd0gQB0k.cjs} +1 -1
  53. package/dist/{index-VIUqZjyP.cjs → index-BeWgla7c.cjs} +1 -1
  54. package/dist/{index-EUea2gfp.js → index-BpWB3aFK.js} +1 -1
  55. package/dist/index-BvLQnI56.js +59 -0
  56. package/dist/{index-CwUFT-GQ.js → index-C0YiLSjW.js} +4 -4
  57. package/dist/{index-o0sNTcKe.js → index-CBjZooac.js} +2 -2
  58. package/dist/{index-DnS_sBBe.cjs → index-COuvjZLM.cjs} +1 -1
  59. package/dist/index-CTjlbbt9.cjs +1 -0
  60. package/dist/index-CUWMxxKG.js +97 -0
  61. package/dist/{index-C329e3yQ.js → index-CZZ3llmi.js} +2 -2
  62. package/dist/index-CjyiloO7.cjs +1 -0
  63. package/dist/{index-D3wSWKST.cjs → index-Cmx9M9cZ.cjs} +1 -1
  64. package/dist/index-CocSS1YK.cjs +1 -0
  65. package/dist/index-CzRiuk60.cjs +1 -0
  66. package/dist/index-DFPDUUq7.js +658 -0
  67. package/dist/{index-D3S7dBDI.cjs → index-DIwmXz1u.cjs} +1 -1
  68. package/dist/index-DLcqcWxM.js +29 -0
  69. package/dist/index-DMLQL2aG.js +286 -0
  70. package/dist/{index-DXQ-7kNJ.cjs → index-DMs8RL3E.cjs} +1 -1
  71. package/dist/{index-Ce3QBKyj.cjs → index-Dbj9vHNq.cjs} +1 -1
  72. package/dist/{index-BRLtxFFr.cjs → index-DmGzwG2z.cjs} +1 -1
  73. package/dist/{index-P1sVIHE3.js → index-PYkEXTqJ.js} +1 -1
  74. package/dist/{index-DulPG3F9.js → index-Se4vRnIO.js} +3 -3
  75. package/dist/index-_XxjJPRD.cjs +1 -0
  76. package/dist/{index-B-cHTKrs.js → index-yWvyIlmA.js} +4 -4
  77. package/dist/input-group.cjs +1 -1
  78. package/dist/input-group.js +1 -1
  79. package/dist/{label-Cne2J57f.cjs → label-BjXORCBM.cjs} +1 -1
  80. package/dist/{label-Ky8qBEC3.js → label-CmwGvhy1.js} +1 -1
  81. package/dist/label.cjs +1 -1
  82. package/dist/label.js +1 -1
  83. package/dist/pagination.cjs +1 -6
  84. package/dist/pagination.js +58 -69
  85. package/dist/popover-3rIoNCXs.js +306 -0
  86. package/dist/popover-FCKBtFo-.cjs +1 -0
  87. package/dist/popover.cjs +1 -1
  88. package/dist/popover.js +1 -1
  89. package/dist/progress.cjs +1 -1
  90. package/dist/progress.js +1 -1
  91. package/dist/radio-group.cjs +1 -1
  92. package/dist/radio-group.js +5 -5
  93. package/dist/select.cjs +2 -2
  94. package/dist/select.js +585 -542
  95. package/dist/{separator-CGnu_jIu.cjs → separator-BaZqZZ9R.cjs} +1 -1
  96. package/dist/{separator-BH73A90k.js → separator-DR7lQjv9.js} +1 -1
  97. package/dist/separator.cjs +1 -1
  98. package/dist/separator.js +1 -1
  99. package/dist/{sheet-CcxnJ6LH.cjs → sheet-CU-sFSaJ.cjs} +1 -1
  100. package/dist/{sheet-_DVpQIVF.js → sheet-UZWAbdXr.js} +1 -1
  101. package/dist/sheet.cjs +1 -1
  102. package/dist/sheet.js +1 -1
  103. package/dist/sidebar.cjs +1 -1
  104. package/dist/sidebar.js +4 -4
  105. package/dist/slider.cjs +1 -1
  106. package/dist/slider.js +3 -3
  107. package/dist/styles.css +1 -1
  108. package/dist/switch.cjs +1 -1
  109. package/dist/switch.js +2 -2
  110. package/dist/tabs-underline.cjs +1 -1
  111. package/dist/tabs-underline.js +1 -1
  112. package/dist/tabs.cjs +1 -1
  113. package/dist/tabs.js +1 -1
  114. package/dist/toaster.cjs +1 -1
  115. package/dist/toaster.js +1 -1
  116. package/dist/toggle.cjs +1 -1
  117. package/dist/toggle.js +1 -1
  118. package/dist/tooltip.cjs +1 -1
  119. package/dist/tooltip.js +114 -108
  120. package/dist/typography.cjs +1 -1
  121. package/dist/typography.js +16 -16
  122. package/docs/AI-GUIDE.md +321 -0
  123. package/docs/components/layout/sidebar.md +330 -0
  124. package/docs/components/layout/toaster.md +436 -0
  125. package/docs/components/ui/accordion-rounded.md +583 -0
  126. package/docs/components/ui/accordion.md +267 -0
  127. package/docs/components/ui/alert.md +671 -0
  128. package/docs/components/ui/avatar.md +588 -0
  129. package/docs/components/ui/badge.md +1024 -0
  130. package/docs/components/ui/breadcrumb.md +614 -0
  131. package/docs/components/ui/button-group.md +1002 -0
  132. package/docs/components/ui/button.md +1078 -0
  133. package/docs/components/ui/calendar.md +1159 -0
  134. package/docs/components/ui/card.md +1265 -0
  135. package/docs/components/ui/checkbox.md +292 -0
  136. package/docs/components/ui/collapsible.md +320 -0
  137. package/docs/components/ui/combobox.md +328 -0
  138. package/docs/components/ui/command.md +454 -0
  139. package/docs/components/ui/context-menu.md +540 -0
  140. package/docs/components/ui/dialog.md +628 -0
  141. package/docs/components/ui/dropdown-menu.md +731 -0
  142. package/docs/components/ui/field.md +706 -0
  143. package/docs/components/ui/hover-card.md +446 -0
  144. package/docs/components/ui/input-group.md +509 -0
  145. package/docs/components/ui/input.md +362 -0
  146. package/docs/components/ui/kbd.md +434 -0
  147. package/docs/components/ui/label.md +359 -0
  148. package/docs/components/ui/pagination.md +650 -0
  149. package/docs/components/ui/popover.md +536 -0
  150. package/docs/components/ui/progress.md +182 -0
  151. package/docs/components/ui/radio-group.md +311 -0
  152. package/docs/components/ui/select.md +352 -0
  153. package/docs/components/ui/separator.md +214 -0
  154. package/docs/components/ui/sheet.md +142 -0
  155. package/docs/components/ui/skeleton.md +140 -0
  156. package/docs/components/ui/slider.md +341 -0
  157. package/docs/components/ui/spinner.md +170 -0
  158. package/docs/components/ui/switch.md +402 -0
  159. package/docs/components/ui/table.md +183 -0
  160. package/docs/components/ui/tabs-underline.md +106 -0
  161. package/docs/components/ui/tabs.md +122 -0
  162. package/docs/components/ui/textarea.md +243 -0
  163. package/docs/components/ui/toggle.md +243 -0
  164. package/docs/components/ui/tooltip.md +320 -0
  165. package/docs/components/ui/typography.md +191 -0
  166. package/package.json +11 -5
  167. package/dist/index-6oTEokEx.js +0 -82
  168. package/dist/index-B-NyefE0.js +0 -243
  169. package/dist/index-BKbK2GzY.cjs +0 -1
  170. package/dist/index-BMitW9UR.cjs +0 -1
  171. package/dist/index-BpvjJ_T6.cjs +0 -5
  172. package/dist/index-C5wjudc-.js +0 -36
  173. package/dist/index-CezwiPd_.js +0 -615
  174. package/dist/index-D02K8KOB.js +0 -54
  175. package/dist/index-D7hQvndv.cjs +0 -1
  176. package/dist/index-DQvx1rG_.cjs +0 -1
  177. package/dist/popover-BjdTqaB8.cjs +0 -1
  178. package/dist/popover-EnVfE0YA.js +0 -263
@@ -0,0 +1,122 @@
1
+ # Tabs
2
+
3
+ Pestañas con background. Basado en Radix UI.
4
+
5
+ ## Descripción
6
+
7
+ El componente `Tabs` permite organizar contenido en secciones que se pueden alternar.
8
+
9
+ ## Importación
10
+
11
+ ```typescript
12
+ import {
13
+ Tabs,
14
+ TabsList,
15
+ TabsTrigger,
16
+ TabsContent,
17
+ } from "@adamosuiteservices/ui/tabs";
18
+ ```
19
+
20
+ ## Anatomía
21
+
22
+ ```tsx
23
+ <Tabs defaultValue="account">
24
+ <TabsList>
25
+ <TabsTrigger value="account">Account</TabsTrigger>
26
+ <TabsTrigger value="password">Password</TabsTrigger>
27
+ </TabsList>
28
+ <TabsContent value="account">Account settings content</TabsContent>
29
+ <TabsContent value="password">Password settings content</TabsContent>
30
+ </Tabs>
31
+ ```
32
+
33
+ **Componentes**: 4 (Tabs, TabsList, TabsTrigger, TabsContent)
34
+
35
+ ## Props
36
+
37
+ ### Tabs (Root)
38
+
39
+ | Prop | Tipo | Descripción |
40
+ | --------------- | ------------------------- | -------------------------- |
41
+ | `defaultValue` | `string` | Tab inicial (uncontrolled) |
42
+ | `value` | `string` | Tab controlado |
43
+ | `onValueChange` | `(value: string) => void` | Callback al cambiar |
44
+
45
+ ### TabsTrigger
46
+
47
+ | Prop | Tipo | Descripción |
48
+ | ---------- | --------- | ------------------- |
49
+ | `value` | `string` | Valor único del tab |
50
+ | `disabled` | `boolean` | Deshabilitar tab |
51
+
52
+ ## Patrones de Uso
53
+
54
+ ### Básico
55
+
56
+ ```tsx
57
+ <Tabs defaultValue="account">
58
+ <TabsList>
59
+ <TabsTrigger value="account">Account</TabsTrigger>
60
+ <TabsTrigger value="password">Password</TabsTrigger>
61
+ </TabsList>
62
+ <TabsContent value="account">Account settings content</TabsContent>
63
+ <TabsContent value="password">Password settings content</TabsContent>
64
+ </Tabs>
65
+ ```
66
+
67
+ ### Controlado
68
+
69
+ ```tsx
70
+ const [activeTab, setActiveTab] = useState("overview");
71
+
72
+ <Tabs value={activeTab} onValueChange={setActiveTab}>
73
+ <TabsList>
74
+ <TabsTrigger value="overview">Overview</TabsTrigger>
75
+ <TabsTrigger value="analytics">Analytics</TabsTrigger>
76
+ </TabsList>
77
+ <TabsContent value="overview">Overview content</TabsContent>
78
+ <TabsContent value="analytics">Analytics content</TabsContent>
79
+ </Tabs>;
80
+ ```
81
+
82
+ ### Con Iconos
83
+
84
+ ```tsx
85
+ import { UserIcon, KeyIcon, HomeIcon, SettingsIcon } from "lucide-react";
86
+
87
+ <TabsList>
88
+ <TabsTrigger value="home">
89
+ <HomeIcon />
90
+ Home
91
+ </TabsTrigger>
92
+ <TabsTrigger value="settings">
93
+ <SettingsIcon />
94
+ Settings
95
+ </TabsTrigger>
96
+ <TabsTrigger value="profile">
97
+ <UserIcon />
98
+ Profile
99
+ </TabsTrigger>
100
+ <TabsTrigger value="security">
101
+ <KeyIcon />
102
+ Security
103
+ </TabsTrigger>
104
+ </TabsList>;
105
+ ```
106
+
107
+ ## Casos de Uso
108
+
109
+ **Settings**: Secciones de configuración
110
+ **Navigation**: Navegación por secciones
111
+ **Data views**: Diferentes vistas de datos
112
+
113
+ ## Estilos Base
114
+
115
+ - **List**: `bg-muted rounded-lg h-9`
116
+ - **Trigger active**: `bg-primary text-primary-foreground shadow-sm`
117
+ - **Trigger**: `rounded-md px-2 py-1 font-semibold`
118
+
119
+ ## Referencias
120
+
121
+ - **Radix UI Tabs**: <https://www.radix-ui.com/primitives/docs/components/tabs>
122
+ - **shadcn/ui Tabs**: <https://ui.shadcn.com/docs/components/tabs>
@@ -0,0 +1,243 @@
1
+ # Textarea
2
+
3
+ Campo de texto multilínea con auto-resize. Wrapper nativo de `<textarea>` HTML.
4
+
5
+ ## Descripción
6
+
7
+ El componente `Textarea` proporciona un campo de entrada de texto multilínea.
8
+
9
+ ## Importación
10
+
11
+ ```typescript
12
+ import { Textarea } from "@adamosuiteservices/ui/textarea";
13
+ ```
14
+
15
+ ## Anatomía
16
+
17
+ ```tsx
18
+ <Textarea placeholder="Type your message here." />
19
+ ```
20
+
21
+ **Componentes**: 1 (Textarea)
22
+
23
+ ## Props
24
+
25
+ | Prop | Tipo | Descripción |
26
+ | -------------- | -------------------------- | ---------------------------- |
27
+ | `placeholder` | `string` | Texto placeholder |
28
+ | `value` | `string` | Valor controlado |
29
+ | `defaultValue` | `string` | Valor inicial (uncontrolled) |
30
+ | `onChange` | `(e: ChangeEvent) => void` | Callback al cambiar |
31
+ | `disabled` | `boolean` | Deshabilita el textarea |
32
+ | `readOnly` | `boolean` | Solo lectura |
33
+ | `required` | `boolean` | Campo requerido |
34
+ | `rows` | `number` | Número de filas |
35
+ | `maxLength` | `number` | Longitud máxima |
36
+ | `className` | `string` | Clases CSS adicionales |
37
+
38
+ **Nota**: Acepta todas las props HTML de `<textarea>`
39
+
40
+ ## Patrones de Uso
41
+
42
+ ### Básico
43
+
44
+ ```tsx
45
+ import { Textarea } from "@adamosuiteservices/ui/textarea";
46
+
47
+ <Textarea placeholder="Type your message here." />;
48
+ ```
49
+
50
+ ### Con Label
51
+
52
+ ```tsx
53
+ import { Label } from "@adamosuiteservices/ui/label";
54
+
55
+ <div className="grid w-full gap-2">
56
+ <Label htmlFor="message">Your message</Label>
57
+ <Textarea placeholder="Type your message here." id="message" />
58
+ </div>;
59
+ ```
60
+
61
+ ### Controlado con Contador
62
+
63
+ ```tsx
64
+ import { useState } from "react";
65
+
66
+ function App() {
67
+ const [value, setValue] = useState("");
68
+
69
+ return (
70
+ <div className="grid w-full gap-2">
71
+ <Label htmlFor="feedback">
72
+ Your feedback ({value.length}/500 characters)
73
+ </Label>
74
+ <Textarea
75
+ id="feedback"
76
+ placeholder="Share your thoughts..."
77
+ value={value}
78
+ onChange={(e) => setValue(e.target.value)}
79
+ maxLength={500}
80
+ />
81
+ <div className="flex justify-between text-sm text-muted-foreground">
82
+ <span>{value.length} characters</span>
83
+ <span>{500 - value.length} remaining</span>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
88
+ ```
89
+
90
+ ### Con Validación
91
+
92
+ ```tsx
93
+ import { Button } from "@adamosuiteservices/ui/button";
94
+ import {
95
+ Card,
96
+ CardContent,
97
+ CardFooter,
98
+ CardHeader,
99
+ CardTitle,
100
+ } from "@adamosuiteservices/ui/card";
101
+
102
+ function CommentForm() {
103
+ const [comment, setComment] = useState("");
104
+ const [error, setError] = useState("");
105
+
106
+ const handleSubmit = () => {
107
+ if (comment.trim().length < 10) {
108
+ setError("Comment must be at least 10 characters long");
109
+ return;
110
+ }
111
+ setError("");
112
+ // Submit logic
113
+ };
114
+
115
+ return (
116
+ <Card>
117
+ <CardHeader>
118
+ <CardTitle>Leave a Comment</CardTitle>
119
+ </CardHeader>
120
+ <CardContent className="space-y-2">
121
+ <Textarea
122
+ placeholder="What did you think?"
123
+ value={comment}
124
+ onChange={(e) => {
125
+ setComment(e.target.value);
126
+ if (error) setError("");
127
+ }}
128
+ aria-invalid={!!error}
129
+ rows={4}
130
+ />
131
+ {error && <p className="text-destructive text-sm">{error}</p>}
132
+ </CardContent>
133
+ <CardFooter>
134
+ <Button onClick={handleSubmit}>Submit Comment</Button>
135
+ </CardFooter>
136
+ </Card>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ### Chat Input con Enter
142
+
143
+ ```tsx
144
+ function ChatInput() {
145
+ const [message, setMessage] = useState("");
146
+
147
+ const handleSend = () => {
148
+ if (message.trim()) {
149
+ // Send message logic
150
+ setMessage("");
151
+ }
152
+ };
153
+
154
+ const handleKeyDown = (e: React.KeyboardEvent) => {
155
+ if (e.key === "Enter" && !e.shiftKey) {
156
+ e.preventDefault();
157
+ handleSend();
158
+ }
159
+ };
160
+
161
+ return (
162
+ <div className="relative">
163
+ <Textarea
164
+ placeholder="Type a message... (Enter to send, Shift+Enter for new line)"
165
+ value={message}
166
+ onChange={(e) => setMessage(e.target.value)}
167
+ onKeyDown={handleKeyDown}
168
+ rows={3}
169
+ className="pr-12 resize-none"
170
+ />
171
+ <Button
172
+ size="sm"
173
+ className="absolute bottom-2 right-2"
174
+ onClick={handleSend}
175
+ >
176
+ Send
177
+ </Button>
178
+ </div>
179
+ );
180
+ }
181
+ ```
182
+
183
+ ### Resize Options
184
+
185
+ ```tsx
186
+ // Auto-resize (default con field-sizing-content)
187
+ <Textarea placeholder="Auto-resize..." className="min-h-[80px] resize-none" />
188
+
189
+ // Manual resize (ambas direcciones)
190
+ <Textarea placeholder="Drag corner to resize..." className="min-h-[80px] resize-both" />
191
+
192
+ // Vertical resize only
193
+ <Textarea placeholder="Resize vertically..." className="min-h-[80px] resize-y" />
194
+
195
+ // No resize
196
+ <Textarea placeholder="Fixed size..." rows={4} className="resize-none" />
197
+ ```
198
+
199
+ ## Casos de Uso
200
+
201
+ **Comentarios**: Feedback, reviews, mensajes
202
+ **Formularios**: Descripción, bio, notas
203
+ **Chat**: Mensajes multilínea
204
+ **Code editor**: Snippets de código
205
+ **Feedback**: Formularios de opinión
206
+
207
+ ## Estilos Base
208
+
209
+ - **Min height**: `min-h-16` default
210
+ - **Border**: `border-input` con `shadow-xs`
211
+ - **Focus**: `ring-ring/50` con `ring-[3px]`
212
+ - **Padding**: `px-3 py-2`
213
+ - **Font**: `text-base` en móvil, `text-sm` en md+
214
+ - **Field sizing**: `field-sizing-content` para auto-resize
215
+ - **Invalid**: `border-destructive` con `ring-destructive/20`
216
+
217
+ ## Accesibilidad
218
+
219
+ - ✅ **Label**: Asociar con `htmlFor` e `id`
220
+ - ✅ **ARIA**: `aria-invalid` para errores
221
+ - ✅ **Placeholder**: No usar como label única
222
+ - ✅ **Error messages**: Describir errores claramente
223
+ - ✅ **Keyboard**: Enter, Tab, Shift+Enter funcionan nativamente
224
+
225
+ ## Notas de Implementación
226
+
227
+ - **HTML nativo**: Wrapper de `<textarea>` HTML nativo
228
+ - **Field sizing**: `field-sizing-content` permite auto-resize basado en contenido
229
+ - **No external library**: Sin dependencias de Radix UI
230
+ - **Data attribute**: `data-slot="textarea"` para identificación
231
+
232
+ ## Troubleshooting
233
+
234
+ **No auto-resize**: Verifica `field-sizing-content` en className (Chrome 123+)
235
+ **Resize manual no funciona**: Usa `resize-y`, `resize-both`, o remueve `resize-none`
236
+ **Height fijo**: Usa prop `rows` para altura específica
237
+ **Contador de caracteres**: Usa `value.length` y `maxLength`
238
+ **Enter no funciona en chat**: Usa `onKeyDown` con `e.key === "Enter" && !e.shiftKey`
239
+
240
+ ## Referencias
241
+
242
+ - **MDN textarea**: <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea>
243
+ - **shadcn/ui Textarea**: <https://ui.shadcn.com/docs/components/textarea>
@@ -0,0 +1,243 @@
1
+ # Toggle
2
+
3
+ Botón de dos estados (on/off). Basado en Radix UI con variantes CVA.
4
+
5
+ ## Descripción
6
+
7
+ El componente `Toggle` es un botón de dos estados (presionado/no presionado).
8
+
9
+ ## Importación
10
+
11
+ ```typescript
12
+ import { Toggle } from "@adamosuiteservices/ui/toggle";
13
+ ```
14
+
15
+ ## Anatomía
16
+
17
+ ```tsx
18
+ <Toggle aria-label="Toggle italic">
19
+ <ItalicIcon />
20
+ </Toggle>
21
+ ```
22
+
23
+ **Componentes**: 1 (Toggle)
24
+
25
+ ## Props
26
+
27
+ | Prop | Tipo | Descripción |
28
+ | ----------------- | ---------------------------- | ----------------------------- |
29
+ | `defaultPressed` | `boolean` | Estado inicial (uncontrolled) |
30
+ | `pressed` | `boolean` | Estado controlado |
31
+ | `onPressedChange` | `(pressed: boolean) => void` | Callback al cambiar |
32
+ | `disabled` | `boolean` | Deshabilita el toggle |
33
+ | `variant` | `"default" \| "outline"` | Estilo visual |
34
+ | `size` | `"sm" \| "default" \| "lg"` | Tamaño del toggle |
35
+ | `className` | `string` | Clases CSS adicionales |
36
+
37
+ ## Patrones de Uso
38
+
39
+ ### Básico
40
+
41
+ ```tsx
42
+ import { Toggle } from "@adamosuiteservices/ui/toggle";
43
+ import { ItalicIcon } from "lucide-react";
44
+
45
+ <Toggle aria-label="Toggle italic">
46
+ <ItalicIcon />
47
+ </Toggle>;
48
+ ```
49
+
50
+ ### Con Texto
51
+
52
+ ```tsx
53
+ <Toggle aria-label="Toggle bold">
54
+ <BoldIcon />
55
+ Bold
56
+ </Toggle>
57
+ ```
58
+
59
+ ### Variantes
60
+
61
+ ```tsx
62
+ // Default (transparent background)
63
+ <Toggle aria-label="Default">
64
+ <ItalicIcon />
65
+ </Toggle>
66
+
67
+ // Outline (con border)
68
+ <Toggle variant="outline" aria-label="Outline">
69
+ <ItalicIcon />
70
+ </Toggle>
71
+ ```
72
+
73
+ ### Tamaños
74
+
75
+ ```tsx
76
+ import { UnderlineIcon } from "lucide-react";
77
+
78
+ <Toggle size="sm"><UnderlineIcon /></Toggle>
79
+ <Toggle size="default"><UnderlineIcon /></Toggle>
80
+ <Toggle size="lg"><UnderlineIcon /></Toggle>
81
+ ```
82
+
83
+ ### Controlado
84
+
85
+ ```tsx
86
+ import { useState } from "react";
87
+
88
+ function App() {
89
+ const [isPressed, setIsPressed] = useState(false);
90
+
91
+ return (
92
+ <Toggle
93
+ pressed={isPressed}
94
+ onPressedChange={setIsPressed}
95
+ aria-label="Toggle bold"
96
+ >
97
+ <BoldIcon />
98
+ {isPressed ? "Bold (On)" : "Bold (Off)"}
99
+ </Toggle>
100
+ );
101
+ }
102
+ ```
103
+
104
+ ### Text Editor Formatting
105
+
106
+ ```tsx
107
+ import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
108
+
109
+ function TextEditor() {
110
+ const [formatting, setFormatting] = useState({
111
+ bold: false,
112
+ italic: false,
113
+ underline: false,
114
+ });
115
+
116
+ return (
117
+ <div className="flex gap-1">
118
+ <Toggle
119
+ pressed={formatting.bold}
120
+ onPressedChange={(pressed) =>
121
+ setFormatting((prev) => ({ ...prev, bold: pressed }))
122
+ }
123
+ size="sm"
124
+ >
125
+ <BoldIcon />
126
+ </Toggle>
127
+ <Toggle
128
+ pressed={formatting.italic}
129
+ onPressedChange={(pressed) =>
130
+ setFormatting((prev) => ({ ...prev, italic: pressed }))
131
+ }
132
+ size="sm"
133
+ >
134
+ <ItalicIcon />
135
+ </Toggle>
136
+ <Toggle
137
+ pressed={formatting.underline}
138
+ onPressedChange={(pressed) =>
139
+ setFormatting((prev) => ({ ...prev, underline: pressed }))
140
+ }
141
+ size="sm"
142
+ >
143
+ <UnderlineIcon />
144
+ </Toggle>
145
+ </div>
146
+ );
147
+ }
148
+ ```
149
+
150
+ ### Media Controls
151
+
152
+ ```tsx
153
+ import { MicIcon, MicOffIcon, VideoIcon, VideoOffIcon } from "lucide-react";
154
+
155
+ function MediaControls() {
156
+ const [media, setMedia] = useState({
157
+ microphone: false,
158
+ camera: false,
159
+ });
160
+
161
+ return (
162
+ <div className="flex gap-3">
163
+ <Toggle
164
+ pressed={media.microphone}
165
+ onPressedChange={(pressed) =>
166
+ setMedia((prev) => ({ ...prev, microphone: pressed }))
167
+ }
168
+ variant="outline"
169
+ className={
170
+ !media.microphone ? "bg-destructive text-destructive-foreground" : ""
171
+ }
172
+ >
173
+ {media.microphone ? <MicIcon /> : <MicOffIcon />}
174
+ </Toggle>
175
+
176
+ <Toggle
177
+ pressed={media.camera}
178
+ onPressedChange={(pressed) =>
179
+ setMedia((prev) => ({ ...prev, camera: pressed }))
180
+ }
181
+ variant="outline"
182
+ className={
183
+ !media.camera ? "bg-destructive text-destructive-foreground" : ""
184
+ }
185
+ >
186
+ {media.camera ? <VideoIcon /> : <VideoOffIcon />}
187
+ </Toggle>
188
+ </div>
189
+ );
190
+ }
191
+ ```
192
+
193
+ ### Deshabilitado
194
+
195
+ ```tsx
196
+ <Toggle disabled><UnderlineIcon /></Toggle>
197
+ <Toggle disabled defaultPressed><UnderlineIcon /></Toggle>
198
+ ```
199
+
200
+ ## Casos de Uso
201
+
202
+ **Text formatting**: Bold, italic, underline toggles
203
+ **Media controls**: Mic/camera on/off
204
+ **View options**: List/grid view toggle
205
+ **Preferences**: Dark mode, notifications
206
+ **Filters**: Show/hide options
207
+
208
+ ## Estilos Base
209
+
210
+ - **Sizes**: `h-8 min-w-8` (sm), `h-9 min-w-9` (default), `h-10 min-w-10` (lg)
211
+ - **State off**: `bg-transparent`
212
+ - **State on**: `bg-accent text-accent-foreground`
213
+ - **Hover**: `bg-muted text-muted-foreground`
214
+ - **Outline**: `border border-input shadow-xs`
215
+ - **Focus**: `ring-ring/50` con `ring-[3px]`
216
+
217
+ ## Accesibilidad
218
+
219
+ - ✅ **Role**: `role="button"` con `aria-pressed`
220
+ - ✅ **ARIA**: `aria-label` requerido para icon-only
221
+ - ✅ **Keyboard**: Space/Enter para toggle
222
+ - ✅ **State**: Screen readers anuncian pressed/not pressed
223
+ - ✅ **Focus**: Focus ring visible
224
+
225
+ ## Notas de Implementación
226
+
227
+ - **Radix UI**: Basado en `@radix-ui/react-toggle`
228
+ - **CVA**: Variantes con class-variance-authority
229
+ - **Data state**: `data-state="on|off"` para estilos
230
+ - **Controlled**: Usa `pressed` + `onPressedChange`
231
+ - **Uncontrolled**: Usa `defaultPressed`
232
+
233
+ ## Troubleshooting
234
+
235
+ **Estado no cambia**: En modo controlado usa `pressed` + `onPressedChange`, no `defaultPressed`
236
+ **No accesible**: Agrega `aria-label` en toggles solo con iconos
237
+ **Estilo no actualiza**: Verifica `data-state="on"` aplicado cuando pressed
238
+ **Variant no funciona**: Usa `variant="outline"` o `variant="default"`
239
+
240
+ ## Referencias
241
+
242
+ - **Radix UI Toggle**: <https://www.radix-ui.com/primitives/docs/components/toggle>
243
+ - **shadcn/ui Toggle**: <https://ui.shadcn.com/docs/components/toggle>