@adamosuiteservices/ui 1.2.5 → 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.
- package/dist/accordion-rounded.cjs +1 -1
- package/dist/accordion-rounded.js +1 -1
- package/dist/accordion.cjs +1 -1
- package/dist/accordion.js +1 -1
- package/dist/avatar.cjs +1 -1
- package/dist/avatar.js +1 -1
- package/dist/badge.cjs +1 -1
- package/dist/badge.js +1 -1
- package/dist/breadcrumb.cjs +1 -0
- package/dist/breadcrumb.js +105 -0
- package/dist/{button-C1n6snOY.js → button-2GdKenQI.js} +1 -1
- package/dist/{button-BV-_FVKZ.cjs → button-DEQVHMrX.cjs} +1 -1
- package/dist/button-group.cjs +1 -1
- package/dist/button-group.js +2 -2
- package/dist/button.cjs +1 -1
- package/dist/button.js +1 -1
- package/dist/calendar.cjs +1 -1
- package/dist/calendar.js +1 -1
- package/dist/{checkbox-BrmXPKTn.js → checkbox-Dr487kAg.js} +3 -3
- package/dist/{checkbox-Lq-HvSgc.cjs → checkbox-YWAnswaW.cjs} +1 -1
- package/dist/checkbox.cjs +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/collapsible.cjs +1 -1
- package/dist/collapsible.js +1 -1
- package/dist/combobox.cjs +1 -1
- package/dist/combobox.js +6 -6
- package/dist/components/ui/breadcrumb/breadcrumb.d.ts +11 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.d.ts +26 -0
- package/dist/components/ui/breadcrumb/index.d.ts +1 -0
- package/dist/components/ui/dialog/dialog.d.ts +2 -1
- package/dist/context-menu.cjs +1 -1
- package/dist/context-menu.js +2 -2
- package/dist/custom-layered-styles.css +1 -1
- package/dist/dialog.cjs +1 -1
- package/dist/dialog.js +33 -19
- package/dist/dropdown-menu.cjs +1 -1
- package/dist/dropdown-menu.js +3 -3
- package/dist/ellipsis-CryjZKZn.js +15 -0
- package/dist/ellipsis-Ct9VTDOG.cjs +6 -0
- package/dist/field.cjs +1 -1
- package/dist/field.js +2 -2
- package/dist/hover-card.cjs +1 -1
- package/dist/hover-card.js +6 -6
- package/dist/{index-CAOY367Y.js → index-B0M7VOwp.js} +2 -2
- package/dist/{index-B-ZRqW0J.js → index-BBoIAjAs.js} +3 -3
- package/dist/{index-gO_QEiaK.cjs → index-BDs8lUfq.cjs} +1 -1
- package/dist/index-BFyr34mw.cjs +5 -0
- package/dist/index-BMWt1NBG.js +79 -0
- package/dist/{index-yR-v1A4G.js → index-BX9hz-JD.js} +1 -1
- package/dist/{index-BGiGvaq8.cjs → index-BcGMAmWE.cjs} +1 -1
- package/dist/{index-IKJMQref.cjs → index-Bd0gQB0k.cjs} +1 -1
- package/dist/{index-VIUqZjyP.cjs → index-BeWgla7c.cjs} +1 -1
- package/dist/{index-EUea2gfp.js → index-BpWB3aFK.js} +1 -1
- package/dist/index-BvLQnI56.js +59 -0
- package/dist/{index-CwUFT-GQ.js → index-C0YiLSjW.js} +4 -4
- package/dist/{index-o0sNTcKe.js → index-CBjZooac.js} +2 -2
- package/dist/{index-DnS_sBBe.cjs → index-COuvjZLM.cjs} +1 -1
- package/dist/index-CTjlbbt9.cjs +1 -0
- package/dist/index-CUWMxxKG.js +97 -0
- package/dist/{index-C329e3yQ.js → index-CZZ3llmi.js} +2 -2
- package/dist/index-CjyiloO7.cjs +1 -0
- package/dist/{index-D3wSWKST.cjs → index-Cmx9M9cZ.cjs} +1 -1
- package/dist/index-CocSS1YK.cjs +1 -0
- package/dist/index-CzRiuk60.cjs +1 -0
- package/dist/index-DFPDUUq7.js +658 -0
- package/dist/{index-D3S7dBDI.cjs → index-DIwmXz1u.cjs} +1 -1
- package/dist/index-DLcqcWxM.js +29 -0
- package/dist/index-DMLQL2aG.js +286 -0
- package/dist/{index-DXQ-7kNJ.cjs → index-DMs8RL3E.cjs} +1 -1
- package/dist/{index-Ce3QBKyj.cjs → index-Dbj9vHNq.cjs} +1 -1
- package/dist/{index-BRLtxFFr.cjs → index-DmGzwG2z.cjs} +1 -1
- package/dist/{index-P1sVIHE3.js → index-PYkEXTqJ.js} +1 -1
- package/dist/{index-DulPG3F9.js → index-Se4vRnIO.js} +3 -3
- package/dist/index-_XxjJPRD.cjs +1 -0
- package/dist/{index-B-cHTKrs.js → index-yWvyIlmA.js} +4 -4
- package/dist/input-group.cjs +1 -1
- package/dist/input-group.js +1 -1
- package/dist/{label-Cne2J57f.cjs → label-BjXORCBM.cjs} +1 -1
- package/dist/{label-Ky8qBEC3.js → label-CmwGvhy1.js} +1 -1
- package/dist/label.cjs +1 -1
- package/dist/label.js +1 -1
- package/dist/pagination.cjs +1 -6
- package/dist/pagination.js +58 -69
- package/dist/popover-3rIoNCXs.js +306 -0
- package/dist/popover-FCKBtFo-.cjs +1 -0
- package/dist/popover.cjs +1 -1
- package/dist/popover.js +1 -1
- package/dist/progress.cjs +1 -1
- package/dist/progress.js +1 -1
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.js +5 -5
- package/dist/select.cjs +2 -2
- package/dist/select.js +585 -542
- package/dist/{separator-CGnu_jIu.cjs → separator-BaZqZZ9R.cjs} +1 -1
- package/dist/{separator-BH73A90k.js → separator-DR7lQjv9.js} +1 -1
- package/dist/separator.cjs +1 -1
- package/dist/separator.js +1 -1
- package/dist/{sheet-CcxnJ6LH.cjs → sheet-CU-sFSaJ.cjs} +1 -1
- package/dist/{sheet-_DVpQIVF.js → sheet-UZWAbdXr.js} +1 -1
- package/dist/sheet.cjs +1 -1
- package/dist/sheet.js +1 -1
- package/dist/sidebar.cjs +1 -1
- package/dist/sidebar.js +4 -4
- package/dist/slider.cjs +1 -1
- package/dist/slider.js +3 -3
- package/dist/styles.css +1 -1
- package/dist/switch.cjs +1 -1
- package/dist/switch.js +2 -2
- package/dist/tabs-underline.cjs +1 -1
- package/dist/tabs-underline.js +1 -1
- package/dist/tabs.cjs +1 -1
- package/dist/tabs.js +1 -1
- package/dist/toaster.cjs +1 -1
- package/dist/toaster.js +1 -1
- package/dist/toggle.cjs +1 -1
- package/dist/toggle.js +1 -1
- package/dist/tooltip.cjs +1 -1
- package/dist/tooltip.js +114 -108
- package/dist/typography.cjs +1 -1
- package/dist/typography.js +1 -1
- package/docs/AI-GUIDE.md +321 -0
- package/docs/components/layout/sidebar.md +330 -0
- package/docs/components/layout/toaster.md +436 -0
- package/docs/components/ui/accordion-rounded.md +583 -0
- package/docs/components/ui/accordion.md +267 -0
- package/docs/components/ui/alert.md +671 -0
- package/docs/components/ui/avatar.md +588 -0
- package/docs/components/ui/badge.md +1024 -0
- package/docs/components/ui/breadcrumb.md +614 -0
- package/docs/components/ui/button-group.md +1002 -0
- package/docs/components/ui/button.md +1078 -0
- package/docs/components/ui/calendar.md +1159 -0
- package/docs/components/ui/card.md +1265 -0
- package/docs/components/ui/checkbox.md +292 -0
- package/docs/components/ui/collapsible.md +320 -0
- package/docs/components/ui/combobox.md +328 -0
- package/docs/components/ui/command.md +454 -0
- package/docs/components/ui/context-menu.md +540 -0
- package/docs/components/ui/dialog.md +628 -0
- package/docs/components/ui/dropdown-menu.md +731 -0
- package/docs/components/ui/field.md +706 -0
- package/docs/components/ui/hover-card.md +446 -0
- package/docs/components/ui/input-group.md +509 -0
- package/docs/components/ui/input.md +362 -0
- package/docs/components/ui/kbd.md +434 -0
- package/docs/components/ui/label.md +359 -0
- package/docs/components/ui/pagination.md +650 -0
- package/docs/components/ui/popover.md +536 -0
- package/docs/components/ui/progress.md +182 -0
- package/docs/components/ui/radio-group.md +311 -0
- package/docs/components/ui/select.md +352 -0
- package/docs/components/ui/separator.md +214 -0
- package/docs/components/ui/sheet.md +142 -0
- package/docs/components/ui/skeleton.md +140 -0
- package/docs/components/ui/slider.md +341 -0
- package/docs/components/ui/spinner.md +170 -0
- package/docs/components/ui/switch.md +402 -0
- package/docs/components/ui/table.md +183 -0
- package/docs/components/ui/tabs-underline.md +106 -0
- package/docs/components/ui/tabs.md +122 -0
- package/docs/components/ui/textarea.md +243 -0
- package/docs/components/ui/toggle.md +243 -0
- package/docs/components/ui/tooltip.md +320 -0
- package/docs/components/ui/typography.md +191 -0
- package/package.json +11 -5
- package/dist/index-6oTEokEx.js +0 -82
- package/dist/index-B-NyefE0.js +0 -243
- package/dist/index-BKbK2GzY.cjs +0 -1
- package/dist/index-BMitW9UR.cjs +0 -1
- package/dist/index-BpvjJ_T6.cjs +0 -5
- package/dist/index-C5wjudc-.js +0 -36
- package/dist/index-CezwiPd_.js +0 -615
- package/dist/index-D02K8KOB.js +0 -54
- package/dist/index-D7hQvndv.cjs +0 -1
- package/dist/index-DQvx1rG_.cjs +0 -1
- package/dist/popover-BjdTqaB8.cjs +0 -1
- package/dist/popover-EnVfE0YA.js +0 -263
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Checkbox
|
|
2
|
+
|
|
3
|
+
Checkbox basado en Radix UI con 3 estados (checked, unchecked, indeterminate), validación con `aria-invalid`, focus ring, y estado disabled.
|
|
4
|
+
|
|
5
|
+
## Importación
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Checkbox } from "@adamosuiteservices/ui/checkbox";
|
|
9
|
+
import { Label } from "@adamosuiteservices/ui/label";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Anatomía
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// Componente único, sin subcomponentes
|
|
16
|
+
<Checkbox />
|
|
17
|
+
|
|
18
|
+
// Con label (patrón recomendado)
|
|
19
|
+
<div className="flex items-center gap-3">
|
|
20
|
+
<Checkbox id="terms" />
|
|
21
|
+
<Label htmlFor="terms">Accept terms</Label>
|
|
22
|
+
</div>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Props Principales
|
|
26
|
+
|
|
27
|
+
| Prop | Tipo | Default | Descripción |
|
|
28
|
+
| ----------------- | ----------------------------------------------- | ------- | -------------------------------------------- |
|
|
29
|
+
| `checked` | `boolean \| "indeterminate"` | - | Estado controlado (true/false/indeterminate) |
|
|
30
|
+
| `defaultChecked` | `boolean` | `false` | Estado inicial no controlado |
|
|
31
|
+
| `onCheckedChange` | `(checked: boolean \| "indeterminate") => void` | - | Callback al cambiar estado |
|
|
32
|
+
| `disabled` | `boolean` | `false` | Desactiva interacción |
|
|
33
|
+
| `required` | `boolean` | `false` | Campo requerido (HTML5) |
|
|
34
|
+
| `name` | `string` | - | Nombre para formularios |
|
|
35
|
+
| `value` | `string` | `"on"` | Valor para formularios |
|
|
36
|
+
| `aria-invalid` | `boolean` | `false` | Muestra estado error (border rojo + ring) |
|
|
37
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
38
|
+
|
|
39
|
+
## Estados del Checkbox
|
|
40
|
+
|
|
41
|
+
### 1. Unchecked (Default)
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Checkbox />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Checked
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// No controlado
|
|
51
|
+
<Checkbox defaultChecked />;
|
|
52
|
+
|
|
53
|
+
// Controlado
|
|
54
|
+
const [checked, setChecked] = useState(true);
|
|
55
|
+
<Checkbox checked={checked} onCheckedChange={setChecked} />;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Indeterminate
|
|
59
|
+
|
|
60
|
+
Estado especial para "select all" cuando algunos items están seleccionados.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<Checkbox checked="indeterminate" />;
|
|
64
|
+
|
|
65
|
+
// Ejemplo práctico
|
|
66
|
+
const allChecked = items.every(Boolean);
|
|
67
|
+
const someChecked = items.some(Boolean) && !allChecked;
|
|
68
|
+
|
|
69
|
+
<Checkbox
|
|
70
|
+
checked={allChecked ? true : someChecked ? "indeterminate" : false}
|
|
71
|
+
onCheckedChange={(checked) => setItems(items.map(() => checked === true))}
|
|
72
|
+
/>;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Visual**: Muestra un icono de guión (`MinusIcon`) en lugar de check.
|
|
76
|
+
|
|
77
|
+
### 4. Disabled
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<Checkbox disabled />
|
|
81
|
+
<Checkbox disabled defaultChecked />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Estilos**: `opacity-50` + `cursor-not-allowed`
|
|
85
|
+
|
|
86
|
+
### 5. Invalid (Error State)
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<Checkbox aria-invalid />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Estilos**: Border rojo (`border-destructive`) + ring rojo (`ring-destructive/20`)
|
|
93
|
+
|
|
94
|
+
## Patrones de Uso
|
|
95
|
+
|
|
96
|
+
### Con Label Simple
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<div className="flex items-center gap-3">
|
|
100
|
+
<Checkbox id="notifications" />
|
|
101
|
+
<Label htmlFor="notifications">Enable notifications</Label>
|
|
102
|
+
</div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Con Label y Descripción
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<div className="flex items-start gap-3">
|
|
109
|
+
<Checkbox id="marketing" />
|
|
110
|
+
<div className="grid gap-1">
|
|
111
|
+
<Label htmlFor="marketing">Marketing emails</Label>
|
|
112
|
+
<p className="text-sm text-muted-foreground">
|
|
113
|
+
Receive updates about new products
|
|
114
|
+
</p>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Lista de Opciones
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
const options = ["Email", "SMS", "Push"];
|
|
123
|
+
|
|
124
|
+
<div className="space-y-4">
|
|
125
|
+
{options.map((option) => (
|
|
126
|
+
<div key={option} className="flex items-center gap-3">
|
|
127
|
+
<Checkbox id={option.toLowerCase()} />
|
|
128
|
+
<Label htmlFor={option.toLowerCase()}>{option} notifications</Label>
|
|
129
|
+
</div>
|
|
130
|
+
))}
|
|
131
|
+
</div>;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Select All con Indeterminate
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
const [items, setItems] = useState([false, true, false]);
|
|
138
|
+
const allChecked = items.every(Boolean);
|
|
139
|
+
const isIndeterminate = items.some(Boolean) && !allChecked;
|
|
140
|
+
|
|
141
|
+
<div className="space-y-4">
|
|
142
|
+
{/* Parent checkbox */}
|
|
143
|
+
<div className="flex items-center gap-3">
|
|
144
|
+
<Checkbox
|
|
145
|
+
checked={allChecked ? true : isIndeterminate ? "indeterminate" : false}
|
|
146
|
+
onCheckedChange={(checked) => setItems(items.map(() => checked === true))}
|
|
147
|
+
/>
|
|
148
|
+
<Label className="font-medium">Select all</Label>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Child checkboxes */}
|
|
152
|
+
<div className="ml-6 space-y-3">
|
|
153
|
+
{items.map((checked, i) => (
|
|
154
|
+
<div key={i} className="flex items-center gap-3">
|
|
155
|
+
<Checkbox
|
|
156
|
+
checked={checked}
|
|
157
|
+
onCheckedChange={(newChecked) => {
|
|
158
|
+
const newItems = [...items];
|
|
159
|
+
newItems[i] = newChecked as boolean;
|
|
160
|
+
setItems(newItems);
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
<Label>Item {i + 1}</Label>
|
|
164
|
+
</div>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
</div>;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Card/Button Style
|
|
171
|
+
|
|
172
|
+
Para checkboxes clickeables como botones.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
<Label className="flex items-start gap-3 rounded-lg border p-4 cursor-pointer hover:bg-accent/50 has-aria-checked:border-primary has-aria-checked:bg-primary/5 transition-colors">
|
|
176
|
+
<Checkbox id="card-option" />
|
|
177
|
+
<div className="grid gap-1.5">
|
|
178
|
+
<p className="text-sm font-medium">Enable feature</p>
|
|
179
|
+
<p className="text-sm text-muted-foreground">Description</p>
|
|
180
|
+
</div>
|
|
181
|
+
</Label>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Nota**: `has-aria-checked` detecta cuando el checkbox está checked para estilos.
|
|
185
|
+
|
|
186
|
+
### Formulario de Preferencias
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
function PreferencesForm() {
|
|
190
|
+
const [prefs, setPrefs] = useState({
|
|
191
|
+
marketing: false,
|
|
192
|
+
analytics: true,
|
|
193
|
+
social: false,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<form className="space-y-4">
|
|
198
|
+
{Object.entries(prefs).map(([key, value]) => (
|
|
199
|
+
<div key={key} className="flex items-center gap-3">
|
|
200
|
+
<Checkbox
|
|
201
|
+
id={key}
|
|
202
|
+
checked={value}
|
|
203
|
+
onCheckedChange={(checked) =>
|
|
204
|
+
setPrefs((prev) => ({ ...prev, [key]: checked as boolean }))
|
|
205
|
+
}
|
|
206
|
+
/>
|
|
207
|
+
<Label htmlFor={key}>{key} preferences</Label>
|
|
208
|
+
</div>
|
|
209
|
+
))}
|
|
210
|
+
<Button type="submit">Save</Button>
|
|
211
|
+
</form>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Terms & Conditions
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
const [agreed, setAgreed] = useState(false);
|
|
220
|
+
|
|
221
|
+
<div className="flex items-start gap-3">
|
|
222
|
+
<Checkbox
|
|
223
|
+
id="terms"
|
|
224
|
+
checked={agreed}
|
|
225
|
+
onCheckedChange={(checked) => setAgreed(checked as boolean)}
|
|
226
|
+
aria-invalid={!agreed}
|
|
227
|
+
/>
|
|
228
|
+
<Label htmlFor="terms" className="text-sm">
|
|
229
|
+
I agree to the{" "}
|
|
230
|
+
<a href="/terms" className="underline">terms and conditions</a>
|
|
231
|
+
</Label>
|
|
232
|
+
</div>
|
|
233
|
+
<Button disabled={!agreed}>Continue</Button>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Estilos Internos (Data Attributes)
|
|
237
|
+
|
|
238
|
+
### Estados Visuales
|
|
239
|
+
|
|
240
|
+
- **Checked**: `data-[state=checked]` → `bg-primary`, `text-primary-foreground`, `border-primary`
|
|
241
|
+
- **Unchecked**: Default → `border-input`, `bg-transparent` (dark: `bg-input/30`)
|
|
242
|
+
- **Indeterminate**: `data-[state=indeterminate]` → Muestra `MinusIcon` en lugar de `CheckIcon`
|
|
243
|
+
- **Focus**: `focus-visible:ring-[3px]`, `focus-visible:ring-ring/50`, `focus-visible:border-ring`
|
|
244
|
+
- **Invalid**: `aria-invalid:border-destructive`, `aria-invalid:ring-destructive/20`
|
|
245
|
+
|
|
246
|
+
### Iconos
|
|
247
|
+
|
|
248
|
+
- **CheckIcon**: Se muestra cuando `state=checked` (oculto con `group-data-[state=indeterminate]:hidden`)
|
|
249
|
+
- **MinusIcon**: Se muestra cuando `state=indeterminate` (oculto con `group-data-[state=checked]:hidden`)
|
|
250
|
+
- Tamaño: `size-3.5`, color blanco (`text-white`) para check, primary para minus
|
|
251
|
+
|
|
252
|
+
## Casos de Uso Comunes
|
|
253
|
+
|
|
254
|
+
**Términos y condiciones**: Single checkbox requerido para continuar
|
|
255
|
+
**Preferencias de notificación**: Múltiples checkboxes independientes
|
|
256
|
+
**Select all**: Checkbox parent con estado indeterminate
|
|
257
|
+
**Filtros**: Checkboxes para filtrar listas/tablas
|
|
258
|
+
**Permisos**: Grant/revoke permissions con checkboxes
|
|
259
|
+
**Multi-select lists**: Selección múltiple en tablas/listas
|
|
260
|
+
|
|
261
|
+
## Accesibilidad
|
|
262
|
+
|
|
263
|
+
- ✅ **Navegación teclado**: Space para toggle, Tab para navegar
|
|
264
|
+
- ✅ **ARIA**: `role="checkbox"`, `aria-checked="true|false|mixed"` (mixed = indeterminate)
|
|
265
|
+
- ✅ **Labels**: Siempre usar `<Label htmlFor="id">` para clickear label
|
|
266
|
+
- ✅ **Focus visible**: Ring azul en focus con teclado
|
|
267
|
+
- ✅ **Screen readers**: Anuncia estado checked/unchecked/indeterminate
|
|
268
|
+
- ✅ **Peer class**: Permite estilos CSS basados en estado para labels
|
|
269
|
+
|
|
270
|
+
## Notas de Implementación
|
|
271
|
+
|
|
272
|
+
- **Basado en Radix UI**: `@radix-ui/react-checkbox`
|
|
273
|
+
- **Tamaño**: `size-5` (20px × 20px), no tiene variantes de tamaño
|
|
274
|
+
- **Border radius**: `rounded-[6px]` fijo
|
|
275
|
+
- **Shadow**: `shadow-xs` sutil
|
|
276
|
+
- **Transition**: `transition-shadow` solo en shadow (no en background para performance)
|
|
277
|
+
- **Peer utility**: Tiene clase `peer` para estilos con `peer-*:` en hermanos
|
|
278
|
+
- **Data slots**: `data-slot="checkbox"` y `data-slot="checkbox-indicator"`
|
|
279
|
+
- **Form integration**: Soporta `name` y `value` para formularios nativos
|
|
280
|
+
- **RTL ready**: Funciona correctamente en right-to-left
|
|
281
|
+
|
|
282
|
+
## Troubleshooting
|
|
283
|
+
|
|
284
|
+
**Label no clickeable**: Asegúrate de usar `id` en Checkbox y `htmlFor` en Label
|
|
285
|
+
**Estado no cambia**: Verifica que uses `onCheckedChange` en modo controlado
|
|
286
|
+
**Indeterminate no se ve**: El valor debe ser string `"indeterminate"`, no boolean
|
|
287
|
+
**Focus ring no aparece**: Verifica que no haya `outline-none` sin `focus-visible:ring`
|
|
288
|
+
|
|
289
|
+
## Referencias
|
|
290
|
+
|
|
291
|
+
- **Radix UI Checkbox**: https://www.radix-ui.com/primitives/docs/components/checkbox
|
|
292
|
+
- **ARIA Checkbox**: https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Collapsible
|
|
2
|
+
|
|
3
|
+
Componente interactivo basado en Radix UI para expandir/colapsar contenido con animaciones suaves. Útil para FAQs, menús de navegación, y secciones opcionales.
|
|
4
|
+
|
|
5
|
+
## Importación
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
Collapsible,
|
|
10
|
+
CollapsibleTrigger,
|
|
11
|
+
CollapsibleContent,
|
|
12
|
+
} from "@adamosuiteservices/ui/collapsible";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Anatomía
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
<Collapsible>
|
|
19
|
+
<CollapsibleTrigger>Click to toggle</CollapsibleTrigger>
|
|
20
|
+
<CollapsibleContent>Hidden content that expands/collapses</CollapsibleContent>
|
|
21
|
+
</Collapsible>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Componentes**: 3 (Collapsible root, CollapsibleTrigger, CollapsibleContent)
|
|
25
|
+
|
|
26
|
+
## Props Principales
|
|
27
|
+
|
|
28
|
+
### Collapsible (Root)
|
|
29
|
+
|
|
30
|
+
| Prop | Tipo | Default | Descripción |
|
|
31
|
+
| -------------- | ------------------------- | ------- | ----------------------------------- |
|
|
32
|
+
| `open` | `boolean` | - | Estado controlado (abierto/cerrado) |
|
|
33
|
+
| `defaultOpen` | `boolean` | `false` | Estado inicial no controlado |
|
|
34
|
+
| `onOpenChange` | `(open: boolean) => void` | - | Callback al cambiar estado |
|
|
35
|
+
| `disabled` | `boolean` | `false` | Desactiva interacción |
|
|
36
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
37
|
+
|
|
38
|
+
### CollapsibleTrigger
|
|
39
|
+
|
|
40
|
+
| Prop | Tipo | Default | Descripción |
|
|
41
|
+
| ----------- | --------- | ------- | ----------------------------------- |
|
|
42
|
+
| `asChild` | `boolean` | `false` | Usa child como trigger (Radix Slot) |
|
|
43
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
44
|
+
|
|
45
|
+
### CollapsibleContent
|
|
46
|
+
|
|
47
|
+
| Prop | Tipo | Default | Descripción |
|
|
48
|
+
| ------------ | --------- | ------- | ----------------------------------------- |
|
|
49
|
+
| `forceMount` | `boolean` | `false` | Fuerza montaje en DOM aunque esté cerrado |
|
|
50
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
51
|
+
|
|
52
|
+
## Patrones de Uso
|
|
53
|
+
|
|
54
|
+
### Básico (No Controlado)
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<Collapsible>
|
|
58
|
+
<CollapsibleTrigger>Show details</CollapsibleTrigger>
|
|
59
|
+
<CollapsibleContent>
|
|
60
|
+
<p>Additional information here.</p>
|
|
61
|
+
</CollapsibleContent>
|
|
62
|
+
</Collapsible>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Controlado con Estado
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
69
|
+
|
|
70
|
+
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
71
|
+
<CollapsibleTrigger>{isOpen ? "Hide" : "Show"} content</CollapsibleTrigger>
|
|
72
|
+
<CollapsibleContent>
|
|
73
|
+
<p>Controlled content</p>
|
|
74
|
+
</CollapsibleContent>
|
|
75
|
+
</Collapsible>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Con Botón Custom (asChild)
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { Button } from "@adamosuiteservices/ui/button";
|
|
82
|
+
import { ChevronsUpDownIcon } from "lucide-react";
|
|
83
|
+
|
|
84
|
+
<Collapsible>
|
|
85
|
+
<CollapsibleTrigger asChild>
|
|
86
|
+
<Button variant="outline" size="sm">
|
|
87
|
+
<ChevronsUpDownIcon />
|
|
88
|
+
Toggle
|
|
89
|
+
</Button>
|
|
90
|
+
</CollapsibleTrigger>
|
|
91
|
+
<CollapsibleContent>
|
|
92
|
+
<p>Content</p>
|
|
93
|
+
</CollapsibleContent>
|
|
94
|
+
</Collapsible>;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Con Chevron Rotatorio
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
101
|
+
|
|
102
|
+
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
103
|
+
<CollapsibleTrigger className="flex w-full items-center justify-between p-4 border rounded-lg hover:bg-muted">
|
|
104
|
+
<span>Section Title</span>
|
|
105
|
+
<ChevronDownIcon
|
|
106
|
+
className={`h-4 w-4 transition-transform ${isOpen ? "rotate-180" : ""}`}
|
|
107
|
+
/>
|
|
108
|
+
</CollapsibleTrigger>
|
|
109
|
+
<CollapsibleContent className="p-4">
|
|
110
|
+
<p>Expandable content</p>
|
|
111
|
+
</CollapsibleContent>
|
|
112
|
+
</Collapsible>;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### FAQ Pattern
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<div className="space-y-4">
|
|
119
|
+
<Collapsible>
|
|
120
|
+
<CollapsibleTrigger className="flex w-full items-center justify-between border p-4 rounded-lg hover:bg-muted">
|
|
121
|
+
<span className="font-medium">What is this?</span>
|
|
122
|
+
<ChevronRightIcon className="h-4 w-4" />
|
|
123
|
+
</CollapsibleTrigger>
|
|
124
|
+
<CollapsibleContent className="px-4 pb-4">
|
|
125
|
+
<p className="text-sm text-muted-foreground">
|
|
126
|
+
This is a reusable UI component library.
|
|
127
|
+
</p>
|
|
128
|
+
</CollapsibleContent>
|
|
129
|
+
</Collapsible>
|
|
130
|
+
|
|
131
|
+
<Collapsible>
|
|
132
|
+
<CollapsibleTrigger className="flex w-full items-center justify-between border p-4 rounded-lg hover:bg-muted">
|
|
133
|
+
<span className="font-medium">How to install?</span>
|
|
134
|
+
<ChevronRightIcon className="h-4 w-4" />
|
|
135
|
+
</CollapsibleTrigger>
|
|
136
|
+
<CollapsibleContent className="px-4 pb-4">
|
|
137
|
+
<p className="text-sm text-muted-foreground">
|
|
138
|
+
Use: <code>npm install @adamosuiteservices/ui</code>
|
|
139
|
+
</p>
|
|
140
|
+
</CollapsibleContent>
|
|
141
|
+
</Collapsible>
|
|
142
|
+
</div>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Navigation Menu
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const [openSections, setOpenSections] = useState<string[]>(["settings"]);
|
|
149
|
+
|
|
150
|
+
const toggleSection = (id: string) => {
|
|
151
|
+
setOpenSections((prev) =>
|
|
152
|
+
prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id]
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
<div className="space-y-2">
|
|
157
|
+
<Collapsible
|
|
158
|
+
open={openSections.includes("settings")}
|
|
159
|
+
onOpenChange={() => toggleSection("settings")}
|
|
160
|
+
>
|
|
161
|
+
<CollapsibleTrigger className="flex w-full items-center gap-2 p-3 rounded-lg hover:bg-muted">
|
|
162
|
+
<SettingsIcon className="h-4 w-4" />
|
|
163
|
+
<span className="flex-1 font-medium">Settings</span>
|
|
164
|
+
<ChevronDownIcon
|
|
165
|
+
className={`h-4 w-4 transition-transform ${
|
|
166
|
+
openSections.includes("settings") ? "rotate-180" : ""
|
|
167
|
+
}`}
|
|
168
|
+
/>
|
|
169
|
+
</CollapsibleTrigger>
|
|
170
|
+
<CollapsibleContent className="ml-6 space-y-1">
|
|
171
|
+
<a href="#" className="block px-3 py-2 text-sm rounded hover:bg-muted">
|
|
172
|
+
Profile
|
|
173
|
+
</a>
|
|
174
|
+
<a href="#" className="block px-3 py-2 text-sm rounded hover:bg-muted">
|
|
175
|
+
Security
|
|
176
|
+
</a>
|
|
177
|
+
</CollapsibleContent>
|
|
178
|
+
</Collapsible>
|
|
179
|
+
|
|
180
|
+
<Collapsible
|
|
181
|
+
open={openSections.includes("account")}
|
|
182
|
+
onOpenChange={() => toggleSection("account")}
|
|
183
|
+
>
|
|
184
|
+
<CollapsibleTrigger className="flex w-full items-center gap-2 p-3 rounded-lg hover:bg-muted">
|
|
185
|
+
<UserIcon className="h-4 w-4" />
|
|
186
|
+
<span className="flex-1 font-medium">Account</span>
|
|
187
|
+
<ChevronDownIcon
|
|
188
|
+
className={`h-4 w-4 transition-transform ${
|
|
189
|
+
openSections.includes("account") ? "rotate-180" : ""
|
|
190
|
+
}`}
|
|
191
|
+
/>
|
|
192
|
+
</CollapsibleTrigger>
|
|
193
|
+
<CollapsibleContent className="ml-6 space-y-1">
|
|
194
|
+
<a href="#" className="block px-3 py-2 text-sm rounded hover:bg-muted">
|
|
195
|
+
Billing
|
|
196
|
+
</a>
|
|
197
|
+
<a href="#" className="block px-3 py-2 text-sm rounded hover:bg-muted">
|
|
198
|
+
API Keys
|
|
199
|
+
</a>
|
|
200
|
+
</CollapsibleContent>
|
|
201
|
+
</Collapsible>
|
|
202
|
+
</div>;
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Lista de Archivos
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
<Collapsible defaultOpen className="w-full space-y-2">
|
|
209
|
+
<CollapsibleTrigger className="w-full border p-3 rounded-lg text-left font-medium hover:bg-muted">
|
|
210
|
+
Recent Files
|
|
211
|
+
</CollapsibleTrigger>
|
|
212
|
+
<CollapsibleContent className="space-y-1">
|
|
213
|
+
<div className="border p-3 rounded text-sm">document.pdf</div>
|
|
214
|
+
<div className="border p-3 rounded text-sm">presentation.pptx</div>
|
|
215
|
+
<div className="border p-3 rounded text-sm">spreadsheet.xlsx</div>
|
|
216
|
+
</CollapsibleContent>
|
|
217
|
+
</Collapsible>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Sección Deshabilitada
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
<Collapsible disabled>
|
|
224
|
+
<CollapsibleTrigger className="w-full border p-3 rounded-lg text-left font-medium opacity-50 cursor-not-allowed">
|
|
225
|
+
Coming Soon
|
|
226
|
+
</CollapsibleTrigger>
|
|
227
|
+
<CollapsibleContent>
|
|
228
|
+
<p className="p-3 text-sm text-muted-foreground">Not available yet.</p>
|
|
229
|
+
</CollapsibleContent>
|
|
230
|
+
</Collapsible>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Con Layout de Tarjeta
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
<Collapsible className="border rounded-lg p-4">
|
|
237
|
+
<div className="flex items-center justify-between mb-2">
|
|
238
|
+
<h4 className="font-semibold">@user starred 3 repos</h4>
|
|
239
|
+
<CollapsibleTrigger asChild>
|
|
240
|
+
<Button variant="outline" size="sm">
|
|
241
|
+
<ChevronsUpDownIcon />
|
|
242
|
+
</Button>
|
|
243
|
+
</CollapsibleTrigger>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div className="border px-4 py-2 rounded font-mono text-sm">
|
|
247
|
+
@radix-ui/primitives
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<CollapsibleContent className="mt-2 space-y-2">
|
|
251
|
+
<div className="border px-4 py-2 rounded font-mono text-sm">
|
|
252
|
+
@radix-ui/colors
|
|
253
|
+
</div>
|
|
254
|
+
<div className="border px-4 py-2 rounded font-mono text-sm">
|
|
255
|
+
@stitches/react
|
|
256
|
+
</div>
|
|
257
|
+
</CollapsibleContent>
|
|
258
|
+
</Collapsible>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Casos de Uso Comunes
|
|
262
|
+
|
|
263
|
+
**FAQ sections**: Lista de preguntas frecuentes expandibles
|
|
264
|
+
**Navigation menus**: Menús colapsables con subsecciones
|
|
265
|
+
**Details/Summary**: Información adicional opcional
|
|
266
|
+
**File browsers**: Estructuras de carpetas expandibles
|
|
267
|
+
**Settings panels**: Grupos de configuraciones organizadas
|
|
268
|
+
**Table rows**: Filas de tabla con detalles expandibles
|
|
269
|
+
|
|
270
|
+
## Estilos y Animaciones
|
|
271
|
+
|
|
272
|
+
### Data States
|
|
273
|
+
|
|
274
|
+
- **Abierto**: `data-state="open"` en todos los componentes
|
|
275
|
+
- **Cerrado**: `data-state="closed"` en todos los componentes
|
|
276
|
+
- **Disabled**: `data-disabled` cuando `disabled={true}`
|
|
277
|
+
|
|
278
|
+
### Animación Suave
|
|
279
|
+
|
|
280
|
+
El contenido usa animaciones CSS automáticas (height, opacity) al expandir/colapsar. No requiere configuración adicional.
|
|
281
|
+
|
|
282
|
+
### Custom Animations
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
<CollapsibleContent className="data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp">
|
|
286
|
+
Content with custom animations
|
|
287
|
+
</CollapsibleContent>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Accesibilidad
|
|
291
|
+
|
|
292
|
+
- ✅ **Navegación teclado**: Enter/Space para toggle en trigger, Tab para navegar
|
|
293
|
+
- ✅ **ARIA**: `aria-expanded`, `aria-controls` automáticos en trigger
|
|
294
|
+
- ✅ **Screen readers**: Anuncia estado expandido/colapsado
|
|
295
|
+
- ✅ **Focus visible**: Trigger recibe focus del teclado
|
|
296
|
+
- ✅ **Disabled state**: `aria-disabled` cuando disabled
|
|
297
|
+
|
|
298
|
+
## Notas de Implementación
|
|
299
|
+
|
|
300
|
+
- **Basado en Radix UI**: `@radix-ui/react-collapsible`
|
|
301
|
+
- **No tiene estilos propios**: Wrapper limpio, agrega tus propios estilos
|
|
302
|
+
- **Data slots**: `data-slot="collapsible"`, `data-slot="collapsible-trigger"`, `data-slot="collapsible-content"`
|
|
303
|
+
- **asChild pattern**: CollapsibleTrigger soporta `asChild` para usar componentes custom
|
|
304
|
+
- **Animaciones automáticas**: Radix maneja transiciones de height/opacity
|
|
305
|
+
- **forceMount**: Útil para animaciones custom o cuando necesitas el DOM montado siempre
|
|
306
|
+
- **Multiple instances**: Puedes tener múltiples Collapsibles independientes
|
|
307
|
+
- **Accordion vs Collapsible**: Usa Accordion cuando solo uno debe estar abierto, Collapsible para control independiente
|
|
308
|
+
|
|
309
|
+
## Troubleshooting
|
|
310
|
+
|
|
311
|
+
**Animación no suave**: Radix maneja animaciones automáticamente, si no se ve suave verifica que no haya `transition` conflictivos en CSS
|
|
312
|
+
**Trigger no clickeable**: Verifica que esté dentro de `<Collapsible>`, no como hermano
|
|
313
|
+
**asChild no funciona**: Asegúrate que el child acepta props `onClick` y `aria-*`
|
|
314
|
+
**Content siempre visible**: Verifica que no tengas `forceMount` activado sin intención
|
|
315
|
+
**Estado no sincroniza**: En modo controlado, asegúrate de usar ambos `open` y `onOpenChange`
|
|
316
|
+
|
|
317
|
+
## Referencias
|
|
318
|
+
|
|
319
|
+
- **Radix UI Collapsible**: https://www.radix-ui.com/primitives/docs/components/collapsible
|
|
320
|
+
- **ARIA Disclosure**: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
|