@adamosuiteservices/ui 2.13.4 → 2.14.0
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/{button-Bn4LFAa9.js → button-B0lWuG-D.js} +27 -18
- package/dist/{button-Day6_fbu.cjs → button-DVrteFz9.cjs} +2 -2
- package/dist/button.cjs +1 -1
- package/dist/button.js +1 -1
- package/dist/{calendar-B1_ybTg0.js → calendar-CfqtuOWv.js} +1 -1
- package/dist/{calendar-CZkzHgYi.cjs → calendar-CpUN6BGK.cjs} +1 -1
- package/dist/calendar.cjs +1 -1
- package/dist/calendar.js +1 -1
- package/dist/{combobox-BOi7QzmO.js → combobox-B8HMlZy6.js} +1 -1
- package/dist/{combobox-0ndFo07_.cjs → combobox-Btj-hiBy.cjs} +1 -1
- package/dist/combobox.cjs +1 -1
- package/dist/combobox.js +1 -1
- package/dist/components/ui/alert/alert.d.ts +1 -1
- package/dist/components/ui/button/button.d.ts +3 -2
- package/dist/components/ui/card/card.d.ts +2 -2
- package/dist/date-picker-selector.cjs +1 -1
- package/dist/date-picker-selector.js +3 -3
- package/dist/file-upload.cjs +1 -1
- package/dist/file-upload.js +1 -1
- package/dist/full-screen-loader.cjs +1 -1
- package/dist/full-screen-loader.js +1 -1
- package/dist/input-group.cjs +1 -1
- package/dist/input-group.js +1 -1
- package/dist/pagination.cjs +1 -1
- package/dist/pagination.js +1 -1
- package/dist/sidebar.cjs +1 -1
- package/dist/sidebar.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/tabs.cjs +14 -16
- package/dist/tabs.js +17 -19
- package/docs/AI-GUIDE.md +321 -321
- package/docs/components/layout/full-screen-loader.md +2 -2
- package/docs/components/layout/sidebar.md +399 -399
- package/docs/components/layout/toaster.md +436 -436
- package/docs/components/ui/accordion-rounded.md +584 -584
- package/docs/components/ui/accordion.md +269 -269
- package/docs/components/ui/button.md +35 -23
- package/docs/components/ui/calendar.md +1159 -1159
- package/docs/components/ui/card.md +1455 -1455
- package/docs/components/ui/checkbox.md +292 -292
- package/docs/components/ui/collapsible.md +323 -323
- package/docs/components/ui/dialog.md +628 -628
- package/docs/components/ui/field.md +706 -706
- package/docs/components/ui/hover-card.md +446 -446
- package/docs/components/ui/kbd.md +434 -434
- package/docs/components/ui/label.md +359 -359
- package/docs/components/ui/pagination.md +650 -650
- package/docs/components/ui/popover.md +536 -536
- package/docs/components/ui/progress.md +182 -182
- package/docs/components/ui/radio-group.md +311 -311
- package/docs/components/ui/separator.md +214 -214
- package/docs/components/ui/sheet.md +174 -174
- package/docs/components/ui/skeleton.md +140 -140
- package/docs/components/ui/spinner.md +170 -170
- package/docs/components/ui/switch.md +408 -408
- package/docs/components/ui/tabs-underline.md +106 -106
- package/docs/components/ui/tabs.md +125 -122
- package/docs/components/ui/textarea.md +243 -243
- package/docs/components/ui/toggle.md +237 -237
- package/docs/components/ui/tooltip.md +317 -317
- package/docs/components/ui/typography.md +320 -320
- package/package.json +1 -1
|
@@ -1,706 +1,706 @@
|
|
|
1
|
-
# Field Component
|
|
2
|
-
|
|
3
|
-
Sistema de composición para formularios que agrupa inputs con labels, descripciones, errores y validación. Soporta layouts verticales, horizontales y responsive con soporte para todos los tipos de inputs.
|
|
4
|
-
|
|
5
|
-
## Descripción
|
|
6
|
-
|
|
7
|
-
El componente `Field` agrupa un input con su label, mensaje de ayuda y errores de validación.
|
|
8
|
-
|
|
9
|
-
## Importación
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import {
|
|
13
|
-
Field,
|
|
14
|
-
FieldLabel,
|
|
15
|
-
FieldDescription,
|
|
16
|
-
FieldError,
|
|
17
|
-
FieldGroup,
|
|
18
|
-
FieldSet,
|
|
19
|
-
FieldLegend,
|
|
20
|
-
FieldSeparator,
|
|
21
|
-
FieldContent,
|
|
22
|
-
FieldTitle,
|
|
23
|
-
} from "@adamosuiteservices/ui/field";
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Uso Básico
|
|
27
|
-
|
|
28
|
-
```tsx
|
|
29
|
-
<Field label="Email" description="We'll never share your email.">
|
|
30
|
-
<Input type="email" placeholder="email@example.com" />
|
|
31
|
-
</Field>
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Con Error
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
<Field label="Username" error="Username is already taken">
|
|
38
|
-
<Input aria-invalid />
|
|
39
|
-
</Field>
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Con Campo Requerido
|
|
43
|
-
|
|
44
|
-
```tsx
|
|
45
|
-
<Field label="Password" required>
|
|
46
|
-
<Input type="password" />
|
|
47
|
-
</Field>
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Componentes**: 10 (Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet, FieldLegend, FieldSeparator, FieldContent, FieldTitle)
|
|
51
|
-
|
|
52
|
-
## Props Principales```
|
|
53
|
-
|
|
54
|
-
### Field## Props
|
|
55
|
-
|
|
56
|
-
| Prop | Tipo | Default | Descripción || Prop | Tipo | Descripción |
|
|
57
|
-
|
|
58
|
-
|------|------|---------|-------------||------|------|-------------|
|
|
59
|
-
|
|
60
|
-
| `orientation` | `"vertical" \| "horizontal" \| "responsive"` | `"vertical"` | Layout del field || label | `string` | Etiqueta del campo |
|
|
61
|
-
|
|
62
|
-
| `data-invalid` | `boolean` | - | Marca field como inválido (aplica estilos de error) || description | `string` | Texto de ayuda |
|
|
63
|
-
|
|
64
|
-
| `data-disabled` | `boolean` | - | Marca field como deshabilitado || error | `string` | Mensaje de error |
|
|
65
|
-
|
|
66
|
-
| `className` | `string` | - | Clases CSS adicionales || required | `boolean` | Marca el campo como requerido |
|
|
67
|
-
|
|
68
|
-
**Orientaciones**:
|
|
69
|
-
|
|
70
|
-
- `vertical`: Label arriba, input abajo
|
|
71
|
-
- `horizontal`: Label e input lado a lado
|
|
72
|
-
- `responsive`: Vertical en mobile, horizontal en desktop (@md/field-group)
|
|
73
|
-
|
|
74
|
-
### FieldLabel
|
|
75
|
-
|
|
76
|
-
| Prop | Tipo | Descripción |
|
|
77
|
-
| ----------- | -------- | ---------------------- |
|
|
78
|
-
| `htmlFor` | `string` | ID del input asociado |
|
|
79
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
80
|
-
|
|
81
|
-
**Hereda props de**: `Label` component
|
|
82
|
-
|
|
83
|
-
### FieldDescription
|
|
84
|
-
|
|
85
|
-
| Prop | Tipo | Descripción |
|
|
86
|
-
| ----------- | -------- | ---------------------- |
|
|
87
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
88
|
-
|
|
89
|
-
**Estilos**: `text-sm`, `text-muted-foreground`, soporta links con `hover:text-primary`
|
|
90
|
-
|
|
91
|
-
### FieldError
|
|
92
|
-
|
|
93
|
-
| Prop | Tipo | Descripción |
|
|
94
|
-
| ----------- | --------------------------- | -------------------------------- |
|
|
95
|
-
| `children` | `ReactNode` | Mensaje de error custom |
|
|
96
|
-
| `errors` | `Array<{message?: string}>` | Array de errores (auto-formatea) |
|
|
97
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
98
|
-
|
|
99
|
-
**Comportamiento**:
|
|
100
|
-
|
|
101
|
-
- Si hay 1 error: muestra mensaje directo
|
|
102
|
-
- Si hay múltiples: muestra lista con bullets
|
|
103
|
-
- Auto-deduplica errores por mensaje
|
|
104
|
-
|
|
105
|
-
### FieldSet
|
|
106
|
-
|
|
107
|
-
| Prop | Tipo | Descripción |
|
|
108
|
-
| ----------- | -------- | ---------------------- |
|
|
109
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
110
|
-
|
|
111
|
-
**Estilos**: `flex flex-col gap-6`, ajuste automático de gap para checkbox/radio groups
|
|
112
|
-
|
|
113
|
-
### FieldLegend
|
|
114
|
-
|
|
115
|
-
| Prop | Tipo | Default | Descripción |
|
|
116
|
-
| ----------- | --------------------- | ---------- | ---------------------- |
|
|
117
|
-
| `variant` | `"legend" \| "label"` | `"legend"` | Estilo del legend |
|
|
118
|
-
| `className` | `string` | - | Clases CSS adicionales |
|
|
119
|
-
|
|
120
|
-
**Variantes**:
|
|
121
|
-
|
|
122
|
-
- `legend`: `text-base`, `font-medium`, `mb-3`
|
|
123
|
-
- `label`: `text-sm`, `font-medium`, `mb-3`
|
|
124
|
-
|
|
125
|
-
### FieldGroup
|
|
126
|
-
|
|
127
|
-
| Prop | Tipo | Descripción |
|
|
128
|
-
| ----------- | -------- | ---------------------- |
|
|
129
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
130
|
-
|
|
131
|
-
**Estilos**: `@container/field-group`, `flex flex-col gap-7`, auto-ajuste para nested groups
|
|
132
|
-
|
|
133
|
-
### FieldSeparator
|
|
134
|
-
|
|
135
|
-
| Prop | Tipo | Descripción |
|
|
136
|
-
| ----------- | ----------- | ------------------------------ |
|
|
137
|
-
| `children` | `ReactNode` | Texto opcional en el separador |
|
|
138
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
139
|
-
|
|
140
|
-
**Con children**: Muestra texto centrado sobre la línea
|
|
141
|
-
|
|
142
|
-
### FieldContent
|
|
143
|
-
|
|
144
|
-
| Prop | Tipo | Descripción |
|
|
145
|
-
| ----------- | -------- | ---------------------- |
|
|
146
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
147
|
-
|
|
148
|
-
**Uso**: Agrupa label + description en layouts horizontales
|
|
149
|
-
|
|
150
|
-
### FieldTitle
|
|
151
|
-
|
|
152
|
-
| Prop | Tipo | Descripción |
|
|
153
|
-
| ----------- | -------- | ---------------------- |
|
|
154
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
155
|
-
|
|
156
|
-
**Uso**: Título alternativo sin asociación a input (para sliders, groups, etc.)
|
|
157
|
-
|
|
158
|
-
## Patrones de Uso
|
|
159
|
-
|
|
160
|
-
### Input Field Básico
|
|
161
|
-
|
|
162
|
-
```tsx
|
|
163
|
-
import { Input } from "@adamosuiteservices/ui/input";
|
|
164
|
-
|
|
165
|
-
<Field>
|
|
166
|
-
<FieldLabel htmlFor="username">Username</FieldLabel>
|
|
167
|
-
<Input id="username" type="text" placeholder="Max Leiter" />
|
|
168
|
-
<FieldDescription>
|
|
169
|
-
Choose a unique username for your account.
|
|
170
|
-
</FieldDescription>
|
|
171
|
-
</Field>;
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Con Error de Validación
|
|
175
|
-
|
|
176
|
-
```tsx
|
|
177
|
-
<Field data-invalid>
|
|
178
|
-
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
179
|
-
<Input id="email" type="email" aria-invalid />
|
|
180
|
-
<FieldError>Enter a valid email address.</FieldError>
|
|
181
|
-
</Field>
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Múltiples Errores
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
|
-
<Field data-invalid>
|
|
188
|
-
<FieldLabel htmlFor="password">Password</FieldLabel>
|
|
189
|
-
<Input id="password" type="password" aria-invalid />
|
|
190
|
-
<FieldError
|
|
191
|
-
errors={[
|
|
192
|
-
{ message: "Password must be at least 8 characters long" },
|
|
193
|
-
{ message: "Password must contain at least one number" },
|
|
194
|
-
{ message: "Password must contain at least one special character" },
|
|
195
|
-
]}
|
|
196
|
-
/>
|
|
197
|
-
</Field>
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
**Renderiza**: Lista con bullets automática
|
|
201
|
-
|
|
202
|
-
### Textarea Field
|
|
203
|
-
|
|
204
|
-
```tsx
|
|
205
|
-
import { Textarea } from "@adamosuiteservices/ui/textarea";
|
|
206
|
-
|
|
207
|
-
<Field>
|
|
208
|
-
<FieldLabel htmlFor="feedback">Feedback</FieldLabel>
|
|
209
|
-
<Textarea
|
|
210
|
-
id="feedback"
|
|
211
|
-
placeholder="Your feedback helps us improve..."
|
|
212
|
-
rows={4}
|
|
213
|
-
/>
|
|
214
|
-
<FieldDescription>Share your thoughts about our service.</FieldDescription>
|
|
215
|
-
</Field>;
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Select Field
|
|
219
|
-
|
|
220
|
-
```tsx
|
|
221
|
-
import {
|
|
222
|
-
Select,
|
|
223
|
-
SelectContent,
|
|
224
|
-
SelectItem,
|
|
225
|
-
SelectTrigger,
|
|
226
|
-
SelectValue,
|
|
227
|
-
} from "@adamosuiteservices/ui/select";
|
|
228
|
-
|
|
229
|
-
<Field>
|
|
230
|
-
<FieldLabel>Department</FieldLabel>
|
|
231
|
-
<Select>
|
|
232
|
-
<SelectTrigger>
|
|
233
|
-
<SelectValue placeholder="Choose department" />
|
|
234
|
-
</SelectTrigger>
|
|
235
|
-
<SelectContent>
|
|
236
|
-
<SelectItem value="engineering">Engineering</SelectItem>
|
|
237
|
-
<SelectItem value="design">Design</SelectItem>
|
|
238
|
-
<SelectItem value="marketing">Marketing</SelectItem>
|
|
239
|
-
</SelectContent>
|
|
240
|
-
</Select>
|
|
241
|
-
<FieldDescription>Select your department or area of work.</FieldDescription>
|
|
242
|
-
</Field>;
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
### Switch Field (Horizontal)
|
|
246
|
-
|
|
247
|
-
```tsx
|
|
248
|
-
import { Switch } from "@adamosuiteservices/ui/switch";
|
|
249
|
-
|
|
250
|
-
<Field orientation="horizontal">
|
|
251
|
-
<Switch id="newsletter" />
|
|
252
|
-
<FieldLabel htmlFor="newsletter">Subscribe to the newsletter</FieldLabel>
|
|
253
|
-
</Field>;
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Switch con Descripción (Horizontal)
|
|
257
|
-
|
|
258
|
-
```tsx
|
|
259
|
-
<Field orientation="horizontal">
|
|
260
|
-
<FieldContent>
|
|
261
|
-
<FieldLabel htmlFor="2fa">Multi-factor authentication</FieldLabel>
|
|
262
|
-
<FieldDescription>
|
|
263
|
-
Enable multi-factor authentication. If you do not have a two-factor
|
|
264
|
-
device, you can use a one-time code sent to your email.
|
|
265
|
-
</FieldDescription>
|
|
266
|
-
</FieldContent>
|
|
267
|
-
<Switch id="2fa" />
|
|
268
|
-
</Field>
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Checkbox Group
|
|
272
|
-
|
|
273
|
-
```tsx
|
|
274
|
-
import { Checkbox } from "@adamosuiteservices/ui/checkbox";
|
|
275
|
-
|
|
276
|
-
<FieldSet>
|
|
277
|
-
<FieldLegend variant="label">Show these items on the desktop</FieldLegend>
|
|
278
|
-
<FieldDescription>
|
|
279
|
-
Select the items you want to show on the desktop.
|
|
280
|
-
</FieldDescription>
|
|
281
|
-
<FieldGroup className="gap-3">
|
|
282
|
-
<Field orientation="horizontal">
|
|
283
|
-
<Checkbox id="hard-disks" />
|
|
284
|
-
<FieldLabel htmlFor="hard-disks" className="font-normal">
|
|
285
|
-
Hard disks
|
|
286
|
-
</FieldLabel>
|
|
287
|
-
</Field>
|
|
288
|
-
<Field orientation="horizontal">
|
|
289
|
-
<Checkbox id="external-disks" />
|
|
290
|
-
<FieldLabel htmlFor="external-disks" className="font-normal">
|
|
291
|
-
External disks
|
|
292
|
-
</FieldLabel>
|
|
293
|
-
</Field>
|
|
294
|
-
<Field orientation="horizontal">
|
|
295
|
-
<Checkbox id="cds-dvds" />
|
|
296
|
-
<FieldLabel htmlFor="cds-dvds" className="font-normal">
|
|
297
|
-
CDs, DVDs, and iPods
|
|
298
|
-
</FieldLabel>
|
|
299
|
-
</Field>
|
|
300
|
-
</FieldGroup>
|
|
301
|
-
</FieldSet>;
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### Radio Group
|
|
305
|
-
|
|
306
|
-
```tsx
|
|
307
|
-
import { RadioGroup, RadioGroupItem } from "@adamosuiteservices/ui/radio-group";
|
|
308
|
-
|
|
309
|
-
<FieldSet>
|
|
310
|
-
<FieldLabel>Subscription Plan</FieldLabel>
|
|
311
|
-
<FieldDescription>
|
|
312
|
-
Yearly and lifetime plans offer significant savings.
|
|
313
|
-
</FieldDescription>
|
|
314
|
-
<RadioGroup defaultValue="monthly">
|
|
315
|
-
<Field orientation="horizontal">
|
|
316
|
-
<RadioGroupItem value="monthly" id="plan-monthly" />
|
|
317
|
-
<FieldLabel htmlFor="plan-monthly" className="font-normal">
|
|
318
|
-
Monthly ($9.99/month)
|
|
319
|
-
</FieldLabel>
|
|
320
|
-
</Field>
|
|
321
|
-
<Field orientation="horizontal">
|
|
322
|
-
<RadioGroupItem value="yearly" id="plan-yearly" />
|
|
323
|
-
<FieldLabel htmlFor="plan-yearly" className="font-normal">
|
|
324
|
-
Yearly ($99.99/year)
|
|
325
|
-
</FieldLabel>
|
|
326
|
-
</Field>
|
|
327
|
-
<Field orientation="horizontal">
|
|
328
|
-
<RadioGroupItem value="lifetime" id="plan-lifetime" />
|
|
329
|
-
<FieldLabel htmlFor="plan-lifetime" className="font-normal">
|
|
330
|
-
Lifetime ($299.99)
|
|
331
|
-
</FieldLabel>
|
|
332
|
-
</Field>
|
|
333
|
-
</RadioGroup>
|
|
334
|
-
</FieldSet>;
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### Choice Cards (Radio como Cards)
|
|
338
|
-
|
|
339
|
-
```tsx
|
|
340
|
-
<FieldSet>
|
|
341
|
-
<FieldLabel htmlFor="compute-environment">Compute Environment</FieldLabel>
|
|
342
|
-
<FieldDescription>
|
|
343
|
-
Select the compute environment for your cluster.
|
|
344
|
-
</FieldDescription>
|
|
345
|
-
<RadioGroup defaultValue="kubernetes">
|
|
346
|
-
<FieldLabel htmlFor="kubernetes">
|
|
347
|
-
<Field orientation="horizontal">
|
|
348
|
-
<FieldContent>
|
|
349
|
-
<FieldTitle>Kubernetes</FieldTitle>
|
|
350
|
-
<FieldDescription>
|
|
351
|
-
Run GPU workloads on a K8s configured cluster.
|
|
352
|
-
</FieldDescription>
|
|
353
|
-
</FieldContent>
|
|
354
|
-
<RadioGroupItem value="kubernetes" id="kubernetes" />
|
|
355
|
-
</Field>
|
|
356
|
-
</FieldLabel>
|
|
357
|
-
<FieldLabel htmlFor="vm">
|
|
358
|
-
<Field orientation="horizontal">
|
|
359
|
-
<FieldContent>
|
|
360
|
-
<FieldTitle>Virtual Machine</FieldTitle>
|
|
361
|
-
<FieldDescription>
|
|
362
|
-
Access a VM configured cluster to run GPU workloads.
|
|
363
|
-
</FieldDescription>
|
|
364
|
-
</FieldContent>
|
|
365
|
-
<RadioGroupItem value="vm" id="vm" />
|
|
366
|
-
</Field>
|
|
367
|
-
</FieldLabel>
|
|
368
|
-
</RadioGroup>
|
|
369
|
-
</FieldSet>
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
**Estilos automáticos**: FieldLabel envuelve Field → card con border, hover, checked state
|
|
373
|
-
|
|
374
|
-
### Slider Field
|
|
375
|
-
|
|
376
|
-
```tsx
|
|
377
|
-
import { Slider } from "@adamosuiteservices/ui/slider";
|
|
378
|
-
import { useState } from "react";
|
|
379
|
-
|
|
380
|
-
function PriceRange() {
|
|
381
|
-
const [value, setValue] = useState([200, 800]);
|
|
382
|
-
|
|
383
|
-
return (
|
|
384
|
-
<Field>
|
|
385
|
-
<FieldTitle>Price Range</FieldTitle>
|
|
386
|
-
<FieldDescription>
|
|
387
|
-
Set your budget range ($
|
|
388
|
-
<span className="font-medium tabular-nums">{value[0]}</span> - <span className="font-medium tabular-nums">
|
|
389
|
-
{value[1]}
|
|
390
|
-
</span>).
|
|
391
|
-
</FieldDescription>
|
|
392
|
-
<Slider
|
|
393
|
-
value={value}
|
|
394
|
-
onValueChange={setValue}
|
|
395
|
-
max={1000}
|
|
396
|
-
min={0}
|
|
397
|
-
step={10}
|
|
398
|
-
className="mt-2 w-full"
|
|
399
|
-
aria-label="Price Range"
|
|
400
|
-
/>
|
|
401
|
-
</Field>
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### FieldSet con Grupos
|
|
407
|
-
|
|
408
|
-
```tsx
|
|
409
|
-
<FieldSet>
|
|
410
|
-
<FieldLegend>Address Information</FieldLegend>
|
|
411
|
-
<FieldDescription>
|
|
412
|
-
We need your address to deliver your order.
|
|
413
|
-
</FieldDescription>
|
|
414
|
-
<FieldGroup>
|
|
415
|
-
<Field>
|
|
416
|
-
<FieldLabel htmlFor="street">Street Address</FieldLabel>
|
|
417
|
-
<Input id="street" type="text" placeholder="123 Main St" />
|
|
418
|
-
</Field>
|
|
419
|
-
<div className="grid grid-cols-2 gap-4">
|
|
420
|
-
<Field>
|
|
421
|
-
<FieldLabel htmlFor="city">City</FieldLabel>
|
|
422
|
-
<Input id="city" type="text" placeholder="New York" />
|
|
423
|
-
</Field>
|
|
424
|
-
<Field>
|
|
425
|
-
<FieldLabel htmlFor="zip">Postal Code</FieldLabel>
|
|
426
|
-
<Input id="zip" type="text" placeholder="90502" />
|
|
427
|
-
</Field>
|
|
428
|
-
</div>
|
|
429
|
-
</FieldGroup>
|
|
430
|
-
</FieldSet>
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
### Con Separadores
|
|
434
|
-
|
|
435
|
-
```tsx
|
|
436
|
-
<FieldGroup>
|
|
437
|
-
<Field>
|
|
438
|
-
<FieldLabel htmlFor="name">Name</FieldLabel>
|
|
439
|
-
<Input id="name" />
|
|
440
|
-
</Field>
|
|
441
|
-
|
|
442
|
-
<FieldSeparator />
|
|
443
|
-
|
|
444
|
-
<Field>
|
|
445
|
-
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
446
|
-
<Input id="email" type="email" />
|
|
447
|
-
</Field>
|
|
448
|
-
|
|
449
|
-
<FieldSeparator>or</FieldSeparator>
|
|
450
|
-
|
|
451
|
-
<Field>
|
|
452
|
-
<FieldLabel htmlFor="phone">Phone</FieldLabel>
|
|
453
|
-
<Input id="phone" type="tel" />
|
|
454
|
-
</Field>
|
|
455
|
-
</FieldGroup>
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
### Layout Responsive
|
|
459
|
-
|
|
460
|
-
```tsx
|
|
461
|
-
<FieldGroup>
|
|
462
|
-
<Field orientation="responsive">
|
|
463
|
-
<FieldContent>
|
|
464
|
-
<FieldLabel htmlFor="name">Name</FieldLabel>
|
|
465
|
-
<FieldDescription>
|
|
466
|
-
Provide your full name for identification
|
|
467
|
-
</FieldDescription>
|
|
468
|
-
</FieldContent>
|
|
469
|
-
<Input id="name" placeholder="Evil Rabbit" />
|
|
470
|
-
</Field>
|
|
471
|
-
|
|
472
|
-
<Field orientation="responsive">
|
|
473
|
-
<FieldContent>
|
|
474
|
-
<FieldLabel htmlFor="message">Message</FieldLabel>
|
|
475
|
-
<FieldDescription>
|
|
476
|
-
You can write your message here. Keep it short.
|
|
477
|
-
</FieldDescription>
|
|
478
|
-
</FieldContent>
|
|
479
|
-
<Textarea id="message" placeholder="Hello, world!" />
|
|
480
|
-
</Field>
|
|
481
|
-
</FieldGroup>
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
**Comportamiento**: Vertical en mobile, horizontal en `@md/field-group` (container query)
|
|
485
|
-
|
|
486
|
-
### Formulario Completo
|
|
487
|
-
|
|
488
|
-
```tsx
|
|
489
|
-
<form>
|
|
490
|
-
<FieldGroup>
|
|
491
|
-
<FieldSet>
|
|
492
|
-
<FieldLegend>Payment Method</FieldLegend>
|
|
493
|
-
<FieldDescription>
|
|
494
|
-
All transactions are secure and encrypted
|
|
495
|
-
</FieldDescription>
|
|
496
|
-
<FieldGroup>
|
|
497
|
-
<Field>
|
|
498
|
-
<FieldLabel htmlFor="card-name">Name on Card</FieldLabel>
|
|
499
|
-
<Input id="card-name" placeholder="Evil Rabbit" required />
|
|
500
|
-
</Field>
|
|
501
|
-
<Field>
|
|
502
|
-
<FieldLabel htmlFor="card-number">Card Number</FieldLabel>
|
|
503
|
-
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
|
504
|
-
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
|
505
|
-
</Field>
|
|
506
|
-
<div className="grid grid-cols-3 gap-4">
|
|
507
|
-
<Field>
|
|
508
|
-
<FieldLabel htmlFor="exp-month">Month</FieldLabel>
|
|
509
|
-
<Select>
|
|
510
|
-
<SelectTrigger id="exp-month">
|
|
511
|
-
<SelectValue placeholder="MM" />
|
|
512
|
-
</SelectTrigger>
|
|
513
|
-
<SelectContent>
|
|
514
|
-
<SelectItem value="01">01</SelectItem>
|
|
515
|
-
<SelectItem value="02">02</SelectItem>
|
|
516
|
-
{/* ... */}
|
|
517
|
-
</SelectContent>
|
|
518
|
-
</Select>
|
|
519
|
-
</Field>
|
|
520
|
-
<Field>
|
|
521
|
-
<FieldLabel htmlFor="exp-year">Year</FieldLabel>
|
|
522
|
-
<Select>
|
|
523
|
-
<SelectTrigger id="exp-year">
|
|
524
|
-
<SelectValue placeholder="YYYY" />
|
|
525
|
-
</SelectTrigger>
|
|
526
|
-
<SelectContent>
|
|
527
|
-
<SelectItem value="2024">2024</SelectItem>
|
|
528
|
-
{/* ... */}
|
|
529
|
-
</SelectContent>
|
|
530
|
-
</Select>
|
|
531
|
-
</Field>
|
|
532
|
-
<Field>
|
|
533
|
-
<FieldLabel htmlFor="cvv">CVV</FieldLabel>
|
|
534
|
-
<Input id="cvv" placeholder="123" required />
|
|
535
|
-
</Field>
|
|
536
|
-
</div>
|
|
537
|
-
</FieldGroup>
|
|
538
|
-
</FieldSet>
|
|
539
|
-
|
|
540
|
-
<FieldSeparator />
|
|
541
|
-
|
|
542
|
-
<FieldSet>
|
|
543
|
-
<FieldLegend>Billing Address</FieldLegend>
|
|
544
|
-
<FieldDescription>
|
|
545
|
-
The billing address associated with your payment method
|
|
546
|
-
</FieldDescription>
|
|
547
|
-
<FieldGroup>
|
|
548
|
-
<Field orientation="horizontal">
|
|
549
|
-
<Checkbox id="same-as-shipping" defaultChecked />
|
|
550
|
-
<FieldLabel htmlFor="same-as-shipping" className="font-normal">
|
|
551
|
-
Same as shipping address
|
|
552
|
-
</FieldLabel>
|
|
553
|
-
</Field>
|
|
554
|
-
</FieldGroup>
|
|
555
|
-
</FieldSet>
|
|
556
|
-
|
|
557
|
-
<Field orientation="horizontal">
|
|
558
|
-
<Button type="submit">Submit</Button>
|
|
559
|
-
<Button variant="outline" type="button">
|
|
560
|
-
Cancel
|
|
561
|
-
</Button>
|
|
562
|
-
</Field>
|
|
563
|
-
</FieldGroup>
|
|
564
|
-
</form>
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
## Casos de Uso Comunes
|
|
568
|
-
|
|
569
|
-
**Login forms**: Email/password con errores de validación
|
|
570
|
-
**Registration**: Multi-step forms con grupos de campos
|
|
571
|
-
**Settings panels**: Switches, checkboxes, selects agrupados
|
|
572
|
-
**Payment forms**: Cards, billing address, validation
|
|
573
|
-
**Profiles**: User info con descripción y ayuda
|
|
574
|
-
**Surveys**: Radio groups, checkboxes, text areas
|
|
575
|
-
|
|
576
|
-
## Estados y Data Attributes
|
|
577
|
-
|
|
578
|
-
### Field States
|
|
579
|
-
|
|
580
|
-
- **Invalid**: `data-invalid=true` → `text-destructive`
|
|
581
|
-
- **Disabled**: `data-disabled=true` → `opacity-50` en label
|
|
582
|
-
|
|
583
|
-
### Orientation
|
|
584
|
-
|
|
585
|
-
- **Vertical**: `data-orientation="vertical"` → `flex-col`
|
|
586
|
-
- **Horizontal**: `data-orientation="horizontal"` → `flex-row items-center`
|
|
587
|
-
- **Responsive**: `data-orientation="responsive"` → vertical → horizontal @md
|
|
588
|
-
|
|
589
|
-
### Auto-adjustments
|
|
590
|
-
|
|
591
|
-
- **Checkbox/Radio groups**: Gap reduce automáticamente a `gap-3`
|
|
592
|
-
- **Nested groups**: Gap reduce a `gap-4`
|
|
593
|
-
- **Choice cards**: Border, hover, checked state cuando FieldLabel envuelve Field
|
|
594
|
-
|
|
595
|
-
## Container Queries
|
|
596
|
-
|
|
597
|
-
FieldGroup usa `@container/field-group` para layouts responsive:
|
|
598
|
-
|
|
599
|
-
```tsx
|
|
600
|
-
<FieldGroup>
|
|
601
|
-
{" "}
|
|
602
|
-
{/* @container/field-group */}
|
|
603
|
-
<Field orientation="responsive">
|
|
604
|
-
{/* Vertical por defecto */}
|
|
605
|
-
{/* Horizontal en @md/field-group */}
|
|
606
|
-
</Field>
|
|
607
|
-
</FieldGroup>
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
**Breakpoint**: `@md` (aproximadamente 448px de ancho del container)
|
|
611
|
-
|
|
612
|
-
## Accesibilidad
|
|
613
|
-
|
|
614
|
-
- ✅ **Labels**: Asociación correcta con `htmlFor` e `id`
|
|
615
|
-
- ✅ **Descriptions**: Linked con `aria-describedby` implícitamente
|
|
616
|
-
- ✅ **Errors**: `role="alert"` para anuncio automático
|
|
617
|
-
- ✅ **Required fields**: Usa atributo `required` en inputs
|
|
618
|
-
- ✅ **Invalid state**: `aria-invalid` en inputs con errores
|
|
619
|
-
- ✅ **Fieldset/Legend**: Semántica correcta para grupos
|
|
620
|
-
- ✅ **Screen readers**: Anuncia labels, descriptions, errores en orden
|
|
621
|
-
- ✅ **Focus**: Visible en todos los inputs
|
|
622
|
-
|
|
623
|
-
## Notas de Implementación
|
|
624
|
-
|
|
625
|
-
- **Composable**: Cada parte es un componente independiente
|
|
626
|
-
- **Class Variance Authority**: Usa CVA para orientación variants
|
|
627
|
-
- **Container queries**: `@container/field-group` para responsive
|
|
628
|
-
- **Auto-styling**: Detecta checkbox/radio groups y ajusta gap
|
|
629
|
-
- **Choice cards**: FieldLabel detecta Field hijo y aplica card styles
|
|
630
|
-
- **Error deduplication**: Auto-deduplica errores por mensaje
|
|
631
|
-
- **Flexible**: Soporta cualquier tipo de input/select/textarea
|
|
632
|
-
- **No wrapper div**: Field es un `role="group"`, no agrega divs innecesarios
|
|
633
|
-
- **Data attributes**: `data-slot` en cada componente para CSS targeting
|
|
634
|
-
- **Disabled state**: Propaga desde Field a Label con opacity
|
|
635
|
-
|
|
636
|
-
## Integración con React Hook Form
|
|
637
|
-
|
|
638
|
-
```tsx
|
|
639
|
-
import { useForm } from "react-hook-form";
|
|
640
|
-
|
|
641
|
-
function MyForm() {
|
|
642
|
-
const {
|
|
643
|
-
register,
|
|
644
|
-
handleSubmit,
|
|
645
|
-
formState: { errors },
|
|
646
|
-
} = useForm();
|
|
647
|
-
|
|
648
|
-
return (
|
|
649
|
-
<form onSubmit={handleSubmit(onSubmit)}>
|
|
650
|
-
<Field data-invalid={!!errors.email}>
|
|
651
|
-
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
652
|
-
<Input
|
|
653
|
-
id="email"
|
|
654
|
-
{...register("email", { required: "Email is required" })}
|
|
655
|
-
aria-invalid={!!errors.email}
|
|
656
|
-
/>
|
|
657
|
-
{errors.email && <FieldError>{errors.email.message}</FieldError>}
|
|
658
|
-
</Field>
|
|
659
|
-
</form>
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
## Estilos Condicionales
|
|
665
|
-
|
|
666
|
-
### Links en Description
|
|
667
|
-
|
|
668
|
-
```tsx
|
|
669
|
-
<FieldDescription>
|
|
670
|
-
Read our <a href="/privacy">privacy policy</a> for more information.
|
|
671
|
-
</FieldDescription>
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
**Auto-styling**: Links tienen `underline`, `underline-offset-4`, `hover:text-primary`
|
|
675
|
-
|
|
676
|
-
### Choice Card Checked State
|
|
677
|
-
|
|
678
|
-
```tsx
|
|
679
|
-
<FieldLabel>
|
|
680
|
-
{" "}
|
|
681
|
-
{/* Wrapper que detecta Field hijo */}
|
|
682
|
-
<Field orientation="horizontal">
|
|
683
|
-
<FieldContent>...</FieldContent>
|
|
684
|
-
<RadioGroupItem value="option" id="option" />
|
|
685
|
-
</Field>
|
|
686
|
-
</FieldLabel>
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
**Auto-styling**: `has-data-[state=checked]:bg-primary/5`, `has-data-[state=checked]:border-primary`
|
|
690
|
-
|
|
691
|
-
## Troubleshooting
|
|
692
|
-
|
|
693
|
-
**Label no asociado con input**: Verifica que `htmlFor` en FieldLabel coincida con `id` en Input
|
|
694
|
-
**Description no se anuncia**: Screen readers anuncian description solo si está correctamente estructurada en Field
|
|
695
|
-
**Errors no aparecen**: Asegúrate de pasar `data-invalid` a Field y `aria-invalid` a Input
|
|
696
|
-
**Layout no responsive**: Verifica que Field esté dentro de FieldGroup para container queries
|
|
697
|
-
**Choice cards no tienen estilo**: FieldLabel debe envolver Field, no al revés
|
|
698
|
-
**Gap incorrecto en checkbox group**: FieldSet detecta checkbox/radio automáticamente, no uses className custom
|
|
699
|
-
**Múltiples errores no formatean**: Usa prop `errors` array en FieldError, no `children` string
|
|
700
|
-
**Disabled no aplica**: Usa `data-disabled` en Field root, no en componentes individuales
|
|
701
|
-
|
|
702
|
-
## Referencias
|
|
703
|
-
|
|
704
|
-
- **shadcn/ui Form**: <https://ui.shadcn.com/docs/components/form>
|
|
705
|
-
- **React Hook Form**: <https://react-hook-form.com/>
|
|
706
|
-
- **ARIA Forms**: <https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/form.html>
|
|
1
|
+
# Field Component
|
|
2
|
+
|
|
3
|
+
Sistema de composición para formularios que agrupa inputs con labels, descripciones, errores y validación. Soporta layouts verticales, horizontales y responsive con soporte para todos los tipos de inputs.
|
|
4
|
+
|
|
5
|
+
## Descripción
|
|
6
|
+
|
|
7
|
+
El componente `Field` agrupa un input con su label, mensaje de ayuda y errores de validación.
|
|
8
|
+
|
|
9
|
+
## Importación
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {
|
|
13
|
+
Field,
|
|
14
|
+
FieldLabel,
|
|
15
|
+
FieldDescription,
|
|
16
|
+
FieldError,
|
|
17
|
+
FieldGroup,
|
|
18
|
+
FieldSet,
|
|
19
|
+
FieldLegend,
|
|
20
|
+
FieldSeparator,
|
|
21
|
+
FieldContent,
|
|
22
|
+
FieldTitle,
|
|
23
|
+
} from "@adamosuiteservices/ui/field";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Uso Básico
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<Field label="Email" description="We'll never share your email.">
|
|
30
|
+
<Input type="email" placeholder="email@example.com" />
|
|
31
|
+
</Field>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Con Error
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
<Field label="Username" error="Username is already taken">
|
|
38
|
+
<Input aria-invalid />
|
|
39
|
+
</Field>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Con Campo Requerido
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<Field label="Password" required>
|
|
46
|
+
<Input type="password" />
|
|
47
|
+
</Field>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Componentes**: 10 (Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet, FieldLegend, FieldSeparator, FieldContent, FieldTitle)
|
|
51
|
+
|
|
52
|
+
## Props Principales```
|
|
53
|
+
|
|
54
|
+
### Field## Props
|
|
55
|
+
|
|
56
|
+
| Prop | Tipo | Default | Descripción || Prop | Tipo | Descripción |
|
|
57
|
+
|
|
58
|
+
|------|------|---------|-------------||------|------|-------------|
|
|
59
|
+
|
|
60
|
+
| `orientation` | `"vertical" \| "horizontal" \| "responsive"` | `"vertical"` | Layout del field || label | `string` | Etiqueta del campo |
|
|
61
|
+
|
|
62
|
+
| `data-invalid` | `boolean` | - | Marca field como inválido (aplica estilos de error) || description | `string` | Texto de ayuda |
|
|
63
|
+
|
|
64
|
+
| `data-disabled` | `boolean` | - | Marca field como deshabilitado || error | `string` | Mensaje de error |
|
|
65
|
+
|
|
66
|
+
| `className` | `string` | - | Clases CSS adicionales || required | `boolean` | Marca el campo como requerido |
|
|
67
|
+
|
|
68
|
+
**Orientaciones**:
|
|
69
|
+
|
|
70
|
+
- `vertical`: Label arriba, input abajo
|
|
71
|
+
- `horizontal`: Label e input lado a lado
|
|
72
|
+
- `responsive`: Vertical en mobile, horizontal en desktop (@md/field-group)
|
|
73
|
+
|
|
74
|
+
### FieldLabel
|
|
75
|
+
|
|
76
|
+
| Prop | Tipo | Descripción |
|
|
77
|
+
| ----------- | -------- | ---------------------- |
|
|
78
|
+
| `htmlFor` | `string` | ID del input asociado |
|
|
79
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
80
|
+
|
|
81
|
+
**Hereda props de**: `Label` component
|
|
82
|
+
|
|
83
|
+
### FieldDescription
|
|
84
|
+
|
|
85
|
+
| Prop | Tipo | Descripción |
|
|
86
|
+
| ----------- | -------- | ---------------------- |
|
|
87
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
88
|
+
|
|
89
|
+
**Estilos**: `text-sm`, `text-muted-foreground`, soporta links con `hover:text-primary`
|
|
90
|
+
|
|
91
|
+
### FieldError
|
|
92
|
+
|
|
93
|
+
| Prop | Tipo | Descripción |
|
|
94
|
+
| ----------- | --------------------------- | -------------------------------- |
|
|
95
|
+
| `children` | `ReactNode` | Mensaje de error custom |
|
|
96
|
+
| `errors` | `Array<{message?: string}>` | Array de errores (auto-formatea) |
|
|
97
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
98
|
+
|
|
99
|
+
**Comportamiento**:
|
|
100
|
+
|
|
101
|
+
- Si hay 1 error: muestra mensaje directo
|
|
102
|
+
- Si hay múltiples: muestra lista con bullets
|
|
103
|
+
- Auto-deduplica errores por mensaje
|
|
104
|
+
|
|
105
|
+
### FieldSet
|
|
106
|
+
|
|
107
|
+
| Prop | Tipo | Descripción |
|
|
108
|
+
| ----------- | -------- | ---------------------- |
|
|
109
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
110
|
+
|
|
111
|
+
**Estilos**: `flex flex-col gap-6`, ajuste automático de gap para checkbox/radio groups
|
|
112
|
+
|
|
113
|
+
### FieldLegend
|
|
114
|
+
|
|
115
|
+
| Prop | Tipo | Default | Descripción |
|
|
116
|
+
| ----------- | --------------------- | ---------- | ---------------------- |
|
|
117
|
+
| `variant` | `"legend" \| "label"` | `"legend"` | Estilo del legend |
|
|
118
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
119
|
+
|
|
120
|
+
**Variantes**:
|
|
121
|
+
|
|
122
|
+
- `legend`: `text-base`, `font-medium`, `mb-3`
|
|
123
|
+
- `label`: `text-sm`, `font-medium`, `mb-3`
|
|
124
|
+
|
|
125
|
+
### FieldGroup
|
|
126
|
+
|
|
127
|
+
| Prop | Tipo | Descripción |
|
|
128
|
+
| ----------- | -------- | ---------------------- |
|
|
129
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
130
|
+
|
|
131
|
+
**Estilos**: `@container/field-group`, `flex flex-col gap-7`, auto-ajuste para nested groups
|
|
132
|
+
|
|
133
|
+
### FieldSeparator
|
|
134
|
+
|
|
135
|
+
| Prop | Tipo | Descripción |
|
|
136
|
+
| ----------- | ----------- | ------------------------------ |
|
|
137
|
+
| `children` | `ReactNode` | Texto opcional en el separador |
|
|
138
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
139
|
+
|
|
140
|
+
**Con children**: Muestra texto centrado sobre la línea
|
|
141
|
+
|
|
142
|
+
### FieldContent
|
|
143
|
+
|
|
144
|
+
| Prop | Tipo | Descripción |
|
|
145
|
+
| ----------- | -------- | ---------------------- |
|
|
146
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
147
|
+
|
|
148
|
+
**Uso**: Agrupa label + description en layouts horizontales
|
|
149
|
+
|
|
150
|
+
### FieldTitle
|
|
151
|
+
|
|
152
|
+
| Prop | Tipo | Descripción |
|
|
153
|
+
| ----------- | -------- | ---------------------- |
|
|
154
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
155
|
+
|
|
156
|
+
**Uso**: Título alternativo sin asociación a input (para sliders, groups, etc.)
|
|
157
|
+
|
|
158
|
+
## Patrones de Uso
|
|
159
|
+
|
|
160
|
+
### Input Field Básico
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { Input } from "@adamosuiteservices/ui/input";
|
|
164
|
+
|
|
165
|
+
<Field>
|
|
166
|
+
<FieldLabel htmlFor="username">Username</FieldLabel>
|
|
167
|
+
<Input id="username" type="text" placeholder="Max Leiter" />
|
|
168
|
+
<FieldDescription>
|
|
169
|
+
Choose a unique username for your account.
|
|
170
|
+
</FieldDescription>
|
|
171
|
+
</Field>;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Con Error de Validación
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
<Field data-invalid>
|
|
178
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
179
|
+
<Input id="email" type="email" aria-invalid />
|
|
180
|
+
<FieldError>Enter a valid email address.</FieldError>
|
|
181
|
+
</Field>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Múltiples Errores
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
<Field data-invalid>
|
|
188
|
+
<FieldLabel htmlFor="password">Password</FieldLabel>
|
|
189
|
+
<Input id="password" type="password" aria-invalid />
|
|
190
|
+
<FieldError
|
|
191
|
+
errors={[
|
|
192
|
+
{ message: "Password must be at least 8 characters long" },
|
|
193
|
+
{ message: "Password must contain at least one number" },
|
|
194
|
+
{ message: "Password must contain at least one special character" },
|
|
195
|
+
]}
|
|
196
|
+
/>
|
|
197
|
+
</Field>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Renderiza**: Lista con bullets automática
|
|
201
|
+
|
|
202
|
+
### Textarea Field
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { Textarea } from "@adamosuiteservices/ui/textarea";
|
|
206
|
+
|
|
207
|
+
<Field>
|
|
208
|
+
<FieldLabel htmlFor="feedback">Feedback</FieldLabel>
|
|
209
|
+
<Textarea
|
|
210
|
+
id="feedback"
|
|
211
|
+
placeholder="Your feedback helps us improve..."
|
|
212
|
+
rows={4}
|
|
213
|
+
/>
|
|
214
|
+
<FieldDescription>Share your thoughts about our service.</FieldDescription>
|
|
215
|
+
</Field>;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Select Field
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import {
|
|
222
|
+
Select,
|
|
223
|
+
SelectContent,
|
|
224
|
+
SelectItem,
|
|
225
|
+
SelectTrigger,
|
|
226
|
+
SelectValue,
|
|
227
|
+
} from "@adamosuiteservices/ui/select";
|
|
228
|
+
|
|
229
|
+
<Field>
|
|
230
|
+
<FieldLabel>Department</FieldLabel>
|
|
231
|
+
<Select>
|
|
232
|
+
<SelectTrigger>
|
|
233
|
+
<SelectValue placeholder="Choose department" />
|
|
234
|
+
</SelectTrigger>
|
|
235
|
+
<SelectContent>
|
|
236
|
+
<SelectItem value="engineering">Engineering</SelectItem>
|
|
237
|
+
<SelectItem value="design">Design</SelectItem>
|
|
238
|
+
<SelectItem value="marketing">Marketing</SelectItem>
|
|
239
|
+
</SelectContent>
|
|
240
|
+
</Select>
|
|
241
|
+
<FieldDescription>Select your department or area of work.</FieldDescription>
|
|
242
|
+
</Field>;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Switch Field (Horizontal)
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
import { Switch } from "@adamosuiteservices/ui/switch";
|
|
249
|
+
|
|
250
|
+
<Field orientation="horizontal">
|
|
251
|
+
<Switch id="newsletter" />
|
|
252
|
+
<FieldLabel htmlFor="newsletter">Subscribe to the newsletter</FieldLabel>
|
|
253
|
+
</Field>;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Switch con Descripción (Horizontal)
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
<Field orientation="horizontal">
|
|
260
|
+
<FieldContent>
|
|
261
|
+
<FieldLabel htmlFor="2fa">Multi-factor authentication</FieldLabel>
|
|
262
|
+
<FieldDescription>
|
|
263
|
+
Enable multi-factor authentication. If you do not have a two-factor
|
|
264
|
+
device, you can use a one-time code sent to your email.
|
|
265
|
+
</FieldDescription>
|
|
266
|
+
</FieldContent>
|
|
267
|
+
<Switch id="2fa" />
|
|
268
|
+
</Field>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Checkbox Group
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { Checkbox } from "@adamosuiteservices/ui/checkbox";
|
|
275
|
+
|
|
276
|
+
<FieldSet>
|
|
277
|
+
<FieldLegend variant="label">Show these items on the desktop</FieldLegend>
|
|
278
|
+
<FieldDescription>
|
|
279
|
+
Select the items you want to show on the desktop.
|
|
280
|
+
</FieldDescription>
|
|
281
|
+
<FieldGroup className="gap-3">
|
|
282
|
+
<Field orientation="horizontal">
|
|
283
|
+
<Checkbox id="hard-disks" />
|
|
284
|
+
<FieldLabel htmlFor="hard-disks" className="font-normal">
|
|
285
|
+
Hard disks
|
|
286
|
+
</FieldLabel>
|
|
287
|
+
</Field>
|
|
288
|
+
<Field orientation="horizontal">
|
|
289
|
+
<Checkbox id="external-disks" />
|
|
290
|
+
<FieldLabel htmlFor="external-disks" className="font-normal">
|
|
291
|
+
External disks
|
|
292
|
+
</FieldLabel>
|
|
293
|
+
</Field>
|
|
294
|
+
<Field orientation="horizontal">
|
|
295
|
+
<Checkbox id="cds-dvds" />
|
|
296
|
+
<FieldLabel htmlFor="cds-dvds" className="font-normal">
|
|
297
|
+
CDs, DVDs, and iPods
|
|
298
|
+
</FieldLabel>
|
|
299
|
+
</Field>
|
|
300
|
+
</FieldGroup>
|
|
301
|
+
</FieldSet>;
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Radio Group
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
import { RadioGroup, RadioGroupItem } from "@adamosuiteservices/ui/radio-group";
|
|
308
|
+
|
|
309
|
+
<FieldSet>
|
|
310
|
+
<FieldLabel>Subscription Plan</FieldLabel>
|
|
311
|
+
<FieldDescription>
|
|
312
|
+
Yearly and lifetime plans offer significant savings.
|
|
313
|
+
</FieldDescription>
|
|
314
|
+
<RadioGroup defaultValue="monthly">
|
|
315
|
+
<Field orientation="horizontal">
|
|
316
|
+
<RadioGroupItem value="monthly" id="plan-monthly" />
|
|
317
|
+
<FieldLabel htmlFor="plan-monthly" className="font-normal">
|
|
318
|
+
Monthly ($9.99/month)
|
|
319
|
+
</FieldLabel>
|
|
320
|
+
</Field>
|
|
321
|
+
<Field orientation="horizontal">
|
|
322
|
+
<RadioGroupItem value="yearly" id="plan-yearly" />
|
|
323
|
+
<FieldLabel htmlFor="plan-yearly" className="font-normal">
|
|
324
|
+
Yearly ($99.99/year)
|
|
325
|
+
</FieldLabel>
|
|
326
|
+
</Field>
|
|
327
|
+
<Field orientation="horizontal">
|
|
328
|
+
<RadioGroupItem value="lifetime" id="plan-lifetime" />
|
|
329
|
+
<FieldLabel htmlFor="plan-lifetime" className="font-normal">
|
|
330
|
+
Lifetime ($299.99)
|
|
331
|
+
</FieldLabel>
|
|
332
|
+
</Field>
|
|
333
|
+
</RadioGroup>
|
|
334
|
+
</FieldSet>;
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Choice Cards (Radio como Cards)
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
<FieldSet>
|
|
341
|
+
<FieldLabel htmlFor="compute-environment">Compute Environment</FieldLabel>
|
|
342
|
+
<FieldDescription>
|
|
343
|
+
Select the compute environment for your cluster.
|
|
344
|
+
</FieldDescription>
|
|
345
|
+
<RadioGroup defaultValue="kubernetes">
|
|
346
|
+
<FieldLabel htmlFor="kubernetes">
|
|
347
|
+
<Field orientation="horizontal">
|
|
348
|
+
<FieldContent>
|
|
349
|
+
<FieldTitle>Kubernetes</FieldTitle>
|
|
350
|
+
<FieldDescription>
|
|
351
|
+
Run GPU workloads on a K8s configured cluster.
|
|
352
|
+
</FieldDescription>
|
|
353
|
+
</FieldContent>
|
|
354
|
+
<RadioGroupItem value="kubernetes" id="kubernetes" />
|
|
355
|
+
</Field>
|
|
356
|
+
</FieldLabel>
|
|
357
|
+
<FieldLabel htmlFor="vm">
|
|
358
|
+
<Field orientation="horizontal">
|
|
359
|
+
<FieldContent>
|
|
360
|
+
<FieldTitle>Virtual Machine</FieldTitle>
|
|
361
|
+
<FieldDescription>
|
|
362
|
+
Access a VM configured cluster to run GPU workloads.
|
|
363
|
+
</FieldDescription>
|
|
364
|
+
</FieldContent>
|
|
365
|
+
<RadioGroupItem value="vm" id="vm" />
|
|
366
|
+
</Field>
|
|
367
|
+
</FieldLabel>
|
|
368
|
+
</RadioGroup>
|
|
369
|
+
</FieldSet>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Estilos automáticos**: FieldLabel envuelve Field → card con border, hover, checked state
|
|
373
|
+
|
|
374
|
+
### Slider Field
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
import { Slider } from "@adamosuiteservices/ui/slider";
|
|
378
|
+
import { useState } from "react";
|
|
379
|
+
|
|
380
|
+
function PriceRange() {
|
|
381
|
+
const [value, setValue] = useState([200, 800]);
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<Field>
|
|
385
|
+
<FieldTitle>Price Range</FieldTitle>
|
|
386
|
+
<FieldDescription>
|
|
387
|
+
Set your budget range ($
|
|
388
|
+
<span className="font-medium tabular-nums">{value[0]}</span> - <span className="font-medium tabular-nums">
|
|
389
|
+
{value[1]}
|
|
390
|
+
</span>).
|
|
391
|
+
</FieldDescription>
|
|
392
|
+
<Slider
|
|
393
|
+
value={value}
|
|
394
|
+
onValueChange={setValue}
|
|
395
|
+
max={1000}
|
|
396
|
+
min={0}
|
|
397
|
+
step={10}
|
|
398
|
+
className="mt-2 w-full"
|
|
399
|
+
aria-label="Price Range"
|
|
400
|
+
/>
|
|
401
|
+
</Field>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### FieldSet con Grupos
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
<FieldSet>
|
|
410
|
+
<FieldLegend>Address Information</FieldLegend>
|
|
411
|
+
<FieldDescription>
|
|
412
|
+
We need your address to deliver your order.
|
|
413
|
+
</FieldDescription>
|
|
414
|
+
<FieldGroup>
|
|
415
|
+
<Field>
|
|
416
|
+
<FieldLabel htmlFor="street">Street Address</FieldLabel>
|
|
417
|
+
<Input id="street" type="text" placeholder="123 Main St" />
|
|
418
|
+
</Field>
|
|
419
|
+
<div className="grid grid-cols-2 gap-4">
|
|
420
|
+
<Field>
|
|
421
|
+
<FieldLabel htmlFor="city">City</FieldLabel>
|
|
422
|
+
<Input id="city" type="text" placeholder="New York" />
|
|
423
|
+
</Field>
|
|
424
|
+
<Field>
|
|
425
|
+
<FieldLabel htmlFor="zip">Postal Code</FieldLabel>
|
|
426
|
+
<Input id="zip" type="text" placeholder="90502" />
|
|
427
|
+
</Field>
|
|
428
|
+
</div>
|
|
429
|
+
</FieldGroup>
|
|
430
|
+
</FieldSet>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Con Separadores
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
<FieldGroup>
|
|
437
|
+
<Field>
|
|
438
|
+
<FieldLabel htmlFor="name">Name</FieldLabel>
|
|
439
|
+
<Input id="name" />
|
|
440
|
+
</Field>
|
|
441
|
+
|
|
442
|
+
<FieldSeparator />
|
|
443
|
+
|
|
444
|
+
<Field>
|
|
445
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
446
|
+
<Input id="email" type="email" />
|
|
447
|
+
</Field>
|
|
448
|
+
|
|
449
|
+
<FieldSeparator>or</FieldSeparator>
|
|
450
|
+
|
|
451
|
+
<Field>
|
|
452
|
+
<FieldLabel htmlFor="phone">Phone</FieldLabel>
|
|
453
|
+
<Input id="phone" type="tel" />
|
|
454
|
+
</Field>
|
|
455
|
+
</FieldGroup>
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Layout Responsive
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
<FieldGroup>
|
|
462
|
+
<Field orientation="responsive">
|
|
463
|
+
<FieldContent>
|
|
464
|
+
<FieldLabel htmlFor="name">Name</FieldLabel>
|
|
465
|
+
<FieldDescription>
|
|
466
|
+
Provide your full name for identification
|
|
467
|
+
</FieldDescription>
|
|
468
|
+
</FieldContent>
|
|
469
|
+
<Input id="name" placeholder="Evil Rabbit" />
|
|
470
|
+
</Field>
|
|
471
|
+
|
|
472
|
+
<Field orientation="responsive">
|
|
473
|
+
<FieldContent>
|
|
474
|
+
<FieldLabel htmlFor="message">Message</FieldLabel>
|
|
475
|
+
<FieldDescription>
|
|
476
|
+
You can write your message here. Keep it short.
|
|
477
|
+
</FieldDescription>
|
|
478
|
+
</FieldContent>
|
|
479
|
+
<Textarea id="message" placeholder="Hello, world!" />
|
|
480
|
+
</Field>
|
|
481
|
+
</FieldGroup>
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Comportamiento**: Vertical en mobile, horizontal en `@md/field-group` (container query)
|
|
485
|
+
|
|
486
|
+
### Formulario Completo
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
<form>
|
|
490
|
+
<FieldGroup>
|
|
491
|
+
<FieldSet>
|
|
492
|
+
<FieldLegend>Payment Method</FieldLegend>
|
|
493
|
+
<FieldDescription>
|
|
494
|
+
All transactions are secure and encrypted
|
|
495
|
+
</FieldDescription>
|
|
496
|
+
<FieldGroup>
|
|
497
|
+
<Field>
|
|
498
|
+
<FieldLabel htmlFor="card-name">Name on Card</FieldLabel>
|
|
499
|
+
<Input id="card-name" placeholder="Evil Rabbit" required />
|
|
500
|
+
</Field>
|
|
501
|
+
<Field>
|
|
502
|
+
<FieldLabel htmlFor="card-number">Card Number</FieldLabel>
|
|
503
|
+
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
|
504
|
+
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
|
505
|
+
</Field>
|
|
506
|
+
<div className="grid grid-cols-3 gap-4">
|
|
507
|
+
<Field>
|
|
508
|
+
<FieldLabel htmlFor="exp-month">Month</FieldLabel>
|
|
509
|
+
<Select>
|
|
510
|
+
<SelectTrigger id="exp-month">
|
|
511
|
+
<SelectValue placeholder="MM" />
|
|
512
|
+
</SelectTrigger>
|
|
513
|
+
<SelectContent>
|
|
514
|
+
<SelectItem value="01">01</SelectItem>
|
|
515
|
+
<SelectItem value="02">02</SelectItem>
|
|
516
|
+
{/* ... */}
|
|
517
|
+
</SelectContent>
|
|
518
|
+
</Select>
|
|
519
|
+
</Field>
|
|
520
|
+
<Field>
|
|
521
|
+
<FieldLabel htmlFor="exp-year">Year</FieldLabel>
|
|
522
|
+
<Select>
|
|
523
|
+
<SelectTrigger id="exp-year">
|
|
524
|
+
<SelectValue placeholder="YYYY" />
|
|
525
|
+
</SelectTrigger>
|
|
526
|
+
<SelectContent>
|
|
527
|
+
<SelectItem value="2024">2024</SelectItem>
|
|
528
|
+
{/* ... */}
|
|
529
|
+
</SelectContent>
|
|
530
|
+
</Select>
|
|
531
|
+
</Field>
|
|
532
|
+
<Field>
|
|
533
|
+
<FieldLabel htmlFor="cvv">CVV</FieldLabel>
|
|
534
|
+
<Input id="cvv" placeholder="123" required />
|
|
535
|
+
</Field>
|
|
536
|
+
</div>
|
|
537
|
+
</FieldGroup>
|
|
538
|
+
</FieldSet>
|
|
539
|
+
|
|
540
|
+
<FieldSeparator />
|
|
541
|
+
|
|
542
|
+
<FieldSet>
|
|
543
|
+
<FieldLegend>Billing Address</FieldLegend>
|
|
544
|
+
<FieldDescription>
|
|
545
|
+
The billing address associated with your payment method
|
|
546
|
+
</FieldDescription>
|
|
547
|
+
<FieldGroup>
|
|
548
|
+
<Field orientation="horizontal">
|
|
549
|
+
<Checkbox id="same-as-shipping" defaultChecked />
|
|
550
|
+
<FieldLabel htmlFor="same-as-shipping" className="font-normal">
|
|
551
|
+
Same as shipping address
|
|
552
|
+
</FieldLabel>
|
|
553
|
+
</Field>
|
|
554
|
+
</FieldGroup>
|
|
555
|
+
</FieldSet>
|
|
556
|
+
|
|
557
|
+
<Field orientation="horizontal">
|
|
558
|
+
<Button type="submit">Submit</Button>
|
|
559
|
+
<Button variant="outline" type="button">
|
|
560
|
+
Cancel
|
|
561
|
+
</Button>
|
|
562
|
+
</Field>
|
|
563
|
+
</FieldGroup>
|
|
564
|
+
</form>
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Casos de Uso Comunes
|
|
568
|
+
|
|
569
|
+
**Login forms**: Email/password con errores de validación
|
|
570
|
+
**Registration**: Multi-step forms con grupos de campos
|
|
571
|
+
**Settings panels**: Switches, checkboxes, selects agrupados
|
|
572
|
+
**Payment forms**: Cards, billing address, validation
|
|
573
|
+
**Profiles**: User info con descripción y ayuda
|
|
574
|
+
**Surveys**: Radio groups, checkboxes, text areas
|
|
575
|
+
|
|
576
|
+
## Estados y Data Attributes
|
|
577
|
+
|
|
578
|
+
### Field States
|
|
579
|
+
|
|
580
|
+
- **Invalid**: `data-invalid=true` → `text-destructive`
|
|
581
|
+
- **Disabled**: `data-disabled=true` → `opacity-50` en label
|
|
582
|
+
|
|
583
|
+
### Orientation
|
|
584
|
+
|
|
585
|
+
- **Vertical**: `data-orientation="vertical"` → `flex-col`
|
|
586
|
+
- **Horizontal**: `data-orientation="horizontal"` → `flex-row items-center`
|
|
587
|
+
- **Responsive**: `data-orientation="responsive"` → vertical → horizontal @md
|
|
588
|
+
|
|
589
|
+
### Auto-adjustments
|
|
590
|
+
|
|
591
|
+
- **Checkbox/Radio groups**: Gap reduce automáticamente a `gap-3`
|
|
592
|
+
- **Nested groups**: Gap reduce a `gap-4`
|
|
593
|
+
- **Choice cards**: Border, hover, checked state cuando FieldLabel envuelve Field
|
|
594
|
+
|
|
595
|
+
## Container Queries
|
|
596
|
+
|
|
597
|
+
FieldGroup usa `@container/field-group` para layouts responsive:
|
|
598
|
+
|
|
599
|
+
```tsx
|
|
600
|
+
<FieldGroup>
|
|
601
|
+
{" "}
|
|
602
|
+
{/* @container/field-group */}
|
|
603
|
+
<Field orientation="responsive">
|
|
604
|
+
{/* Vertical por defecto */}
|
|
605
|
+
{/* Horizontal en @md/field-group */}
|
|
606
|
+
</Field>
|
|
607
|
+
</FieldGroup>
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Breakpoint**: `@md` (aproximadamente 448px de ancho del container)
|
|
611
|
+
|
|
612
|
+
## Accesibilidad
|
|
613
|
+
|
|
614
|
+
- ✅ **Labels**: Asociación correcta con `htmlFor` e `id`
|
|
615
|
+
- ✅ **Descriptions**: Linked con `aria-describedby` implícitamente
|
|
616
|
+
- ✅ **Errors**: `role="alert"` para anuncio automático
|
|
617
|
+
- ✅ **Required fields**: Usa atributo `required` en inputs
|
|
618
|
+
- ✅ **Invalid state**: `aria-invalid` en inputs con errores
|
|
619
|
+
- ✅ **Fieldset/Legend**: Semántica correcta para grupos
|
|
620
|
+
- ✅ **Screen readers**: Anuncia labels, descriptions, errores en orden
|
|
621
|
+
- ✅ **Focus**: Visible en todos los inputs
|
|
622
|
+
|
|
623
|
+
## Notas de Implementación
|
|
624
|
+
|
|
625
|
+
- **Composable**: Cada parte es un componente independiente
|
|
626
|
+
- **Class Variance Authority**: Usa CVA para orientación variants
|
|
627
|
+
- **Container queries**: `@container/field-group` para responsive
|
|
628
|
+
- **Auto-styling**: Detecta checkbox/radio groups y ajusta gap
|
|
629
|
+
- **Choice cards**: FieldLabel detecta Field hijo y aplica card styles
|
|
630
|
+
- **Error deduplication**: Auto-deduplica errores por mensaje
|
|
631
|
+
- **Flexible**: Soporta cualquier tipo de input/select/textarea
|
|
632
|
+
- **No wrapper div**: Field es un `role="group"`, no agrega divs innecesarios
|
|
633
|
+
- **Data attributes**: `data-slot` en cada componente para CSS targeting
|
|
634
|
+
- **Disabled state**: Propaga desde Field a Label con opacity
|
|
635
|
+
|
|
636
|
+
## Integración con React Hook Form
|
|
637
|
+
|
|
638
|
+
```tsx
|
|
639
|
+
import { useForm } from "react-hook-form";
|
|
640
|
+
|
|
641
|
+
function MyForm() {
|
|
642
|
+
const {
|
|
643
|
+
register,
|
|
644
|
+
handleSubmit,
|
|
645
|
+
formState: { errors },
|
|
646
|
+
} = useForm();
|
|
647
|
+
|
|
648
|
+
return (
|
|
649
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
650
|
+
<Field data-invalid={!!errors.email}>
|
|
651
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
652
|
+
<Input
|
|
653
|
+
id="email"
|
|
654
|
+
{...register("email", { required: "Email is required" })}
|
|
655
|
+
aria-invalid={!!errors.email}
|
|
656
|
+
/>
|
|
657
|
+
{errors.email && <FieldError>{errors.email.message}</FieldError>}
|
|
658
|
+
</Field>
|
|
659
|
+
</form>
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
## Estilos Condicionales
|
|
665
|
+
|
|
666
|
+
### Links en Description
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
<FieldDescription>
|
|
670
|
+
Read our <a href="/privacy">privacy policy</a> for more information.
|
|
671
|
+
</FieldDescription>
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
**Auto-styling**: Links tienen `underline`, `underline-offset-4`, `hover:text-primary`
|
|
675
|
+
|
|
676
|
+
### Choice Card Checked State
|
|
677
|
+
|
|
678
|
+
```tsx
|
|
679
|
+
<FieldLabel>
|
|
680
|
+
{" "}
|
|
681
|
+
{/* Wrapper que detecta Field hijo */}
|
|
682
|
+
<Field orientation="horizontal">
|
|
683
|
+
<FieldContent>...</FieldContent>
|
|
684
|
+
<RadioGroupItem value="option" id="option" />
|
|
685
|
+
</Field>
|
|
686
|
+
</FieldLabel>
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**Auto-styling**: `has-data-[state=checked]:bg-primary/5`, `has-data-[state=checked]:border-primary`
|
|
690
|
+
|
|
691
|
+
## Troubleshooting
|
|
692
|
+
|
|
693
|
+
**Label no asociado con input**: Verifica que `htmlFor` en FieldLabel coincida con `id` en Input
|
|
694
|
+
**Description no se anuncia**: Screen readers anuncian description solo si está correctamente estructurada en Field
|
|
695
|
+
**Errors no aparecen**: Asegúrate de pasar `data-invalid` a Field y `aria-invalid` a Input
|
|
696
|
+
**Layout no responsive**: Verifica que Field esté dentro de FieldGroup para container queries
|
|
697
|
+
**Choice cards no tienen estilo**: FieldLabel debe envolver Field, no al revés
|
|
698
|
+
**Gap incorrecto en checkbox group**: FieldSet detecta checkbox/radio automáticamente, no uses className custom
|
|
699
|
+
**Múltiples errores no formatean**: Usa prop `errors` array en FieldError, no `children` string
|
|
700
|
+
**Disabled no aplica**: Usa `data-disabled` en Field root, no en componentes individuales
|
|
701
|
+
|
|
702
|
+
## Referencias
|
|
703
|
+
|
|
704
|
+
- **shadcn/ui Form**: <https://ui.shadcn.com/docs/components/form>
|
|
705
|
+
- **React Hook Form**: <https://react-hook-form.com/>
|
|
706
|
+
- **ARIA Forms**: <https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/form.html>
|