@adamosuiteservices/ui 2.11.21 → 2.12.1

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 (49) hide show
  1. package/dist/{button-BzVY7053.js → button-CMWUcYpz.js} +54 -54
  2. package/dist/button-CmLeuaI0.cjs +75 -0
  3. package/dist/button-group.cjs +6 -6
  4. package/dist/button-group.js +20 -20
  5. package/dist/button.cjs +1 -1
  6. package/dist/button.js +1 -1
  7. package/dist/{calendar-IAVS_7up.cjs → calendar-DUvRetFW.cjs} +1 -1
  8. package/dist/{calendar-BIttOd3g.js → calendar-yJWQTCer.js} +1 -1
  9. package/dist/calendar.cjs +1 -1
  10. package/dist/calendar.js +1 -1
  11. package/dist/{combobox-DI-WK3Ng.js → combobox-BvjehNR7.js} +89 -86
  12. package/dist/combobox-CDXlen0X.cjs +40 -0
  13. package/dist/combobox.cjs +1 -1
  14. package/dist/combobox.js +1 -1
  15. package/dist/components/ui/button/button.d.ts +1 -1
  16. package/dist/components/ui/input-otp/index.d.ts +1 -0
  17. package/dist/components/ui/input-otp/input-otp.d.ts +11 -0
  18. package/dist/context-menu.cjs +38 -36
  19. package/dist/context-menu.js +54 -52
  20. package/dist/date-picker-selector.cjs +1 -1
  21. package/dist/date-picker-selector.js +4 -4
  22. package/dist/dropdown-menu.cjs +48 -52
  23. package/dist/dropdown-menu.js +164 -168
  24. package/dist/{input-DFvZLcgm.js → input-BCiOr4Fy.js} +7 -6
  25. package/dist/{input-CWDpI1Ua.cjs → input-Bz5k4w94.cjs} +7 -6
  26. package/dist/input-group.cjs +5 -5
  27. package/dist/input-group.js +10 -10
  28. package/dist/input-otp.cjs +68 -0
  29. package/dist/input-otp.js +384 -0
  30. package/dist/input.cjs +1 -1
  31. package/dist/input.js +1 -1
  32. package/dist/pagination.cjs +2 -2
  33. package/dist/pagination.js +4 -3
  34. package/dist/select.cjs +22 -20
  35. package/dist/select.js +112 -111
  36. package/dist/sidebar.cjs +1 -1
  37. package/dist/sidebar.js +1 -1
  38. package/dist/styles.css +1 -1
  39. package/docs/components/ui/button-group.md +986 -984
  40. package/docs/components/ui/button.md +1156 -1137
  41. package/docs/components/ui/combobox.md +33 -2
  42. package/docs/components/ui/command.md +484 -454
  43. package/docs/components/ui/context-menu.md +574 -540
  44. package/docs/components/ui/dropdown-menu.md +743 -709
  45. package/docs/components/ui/input.md +362 -362
  46. package/docs/components/ui/select.md +357 -352
  47. package/package.json +8 -3
  48. package/dist/button-B_VHdPPV.cjs +0 -76
  49. package/dist/combobox-DplJzBX6.cjs +0 -37
@@ -1,454 +1,484 @@
1
- # Command
2
-
3
- Paleta de comandos estilo Cmd+K basada en `cmdk` con búsqueda fuzzy, navegación por teclado, grupos, separadores, y shortcuts. Útil para command palettes, búsqueda rápida, y navegación.
4
-
5
- ## Importación
6
-
7
- ```tsx
8
- import {
9
- Command,
10
- CommandInput,
11
- CommandList,
12
- CommandEmpty,
13
- CommandGroup,
14
- CommandItem,
15
- CommandSeparator,
16
- CommandShortcut,
17
- CommandDialog,
18
- } from "@adamosuiteservices/ui/command";
19
- ```
20
-
21
- ## Anatomía
22
-
23
- ```tsx
24
- <Command>
25
- <CommandInput placeholder="Search..." />
26
- <CommandList>
27
- <CommandEmpty>No results found.</CommandEmpty>
28
- <CommandGroup heading="Actions">
29
- <CommandItem>
30
- New File
31
- <CommandShortcut>⌘N</CommandShortcut>
32
- </CommandItem>
33
- <CommandItem>Open</CommandItem>
34
- </CommandGroup>
35
- <CommandSeparator />
36
- <CommandGroup heading="Settings">
37
- <CommandItem>Preferences</CommandItem>
38
- </CommandGroup>
39
- </CommandList>
40
- </Command>
41
- ```
42
-
43
- **Componentes**: 8 (Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut)
44
-
45
- ## Props Principales
46
-
47
- ### Command (Root)
48
-
49
- | Prop | Tipo | Default | Descripción |
50
- | --------------- | ------------------------------------------- | ----------- | -------------------------------------- |
51
- | `value` | `string` | - | Valor controlado del item seleccionado |
52
- | `onValueChange` | `(value: string) => void` | - | Callback cuando cambia selección |
53
- | `filter` | `(value: string, search: string) => number` | Fuzzy match | Función de filtrado custom |
54
- | `shouldFilter` | `boolean` | `true` | Habilita filtrado automático |
55
- | `loop` | `boolean` | `false` | Navegación circular con arrow keys |
56
- | `className` | `string` | - | Clases CSS adicionales |
57
-
58
- ### CommandInput
59
-
60
- | Prop | Tipo | Descripción |
61
- | --------------- | -------------------------- | -------------------------- |
62
- | `placeholder` | `string` | Texto placeholder |
63
- | `value` | `string` | Valor controlado del input |
64
- | `onValueChange` | `(search: string) => void` | Callback al escribir |
65
- | `className` | `string` | Clases CSS adicionales |
66
-
67
- ### CommandList
68
-
69
- | Prop | Tipo | Default | Descripción |
70
- | ----------- | ----------- | ------- | -------------------------- |
71
- | `className` | `string` | - | Clases CSS adicionales |
72
- | `children` | `ReactNode` | - | Grupos, items, separadores |
73
-
74
- **Estilos default**: `max-h-[300px]`, scroll vertical automático
75
-
76
- ### CommandItem
77
-
78
- | Prop | Tipo | Descripción |
79
- | ----------- | ------------------------- | ------------------------------------------ |
80
- | `value` | `string` | Valor único del item (usado para búsqueda) |
81
- | `onSelect` | `(value: string) => void` | Callback al seleccionar |
82
- | `disabled` | `boolean` | Desactiva selección |
83
- | `keywords` | `string[]` | Palabras clave adicionales para búsqueda |
84
- | `className` | `string` | Clases CSS adicionales |
85
-
86
- ### CommandDialog
87
-
88
- | Prop | Tipo | Default | Descripción |
89
- | ----------------- | ------------------------- | ------------------- | ------------------------ |
90
- | `open` | `boolean` | - | Estado del dialog |
91
- | `onOpenChange` | `(open: boolean) => void` | - | Callback al abrir/cerrar |
92
- | `title` | `string` | `"Command Palette"` | Título (sr-only) |
93
- | `description` | `string` | `"Search for..."` | Descripción (sr-only) |
94
- | `showCloseButton` | `boolean` | `true` | Muestra botón de cerrar |
95
- | `className` | `string` | - | Clases CSS adicionales |
96
-
97
- ## Patrones de Uso
98
-
99
- ### Básico (Inline)
100
-
101
- ```tsx
102
- <Command>
103
- <CommandInput placeholder="Type to search..." />
104
- <CommandList>
105
- <CommandEmpty>No results found.</CommandEmpty>
106
- <CommandGroup heading="Suggestions">
107
- <CommandItem>Calendar</CommandItem>
108
- <CommandItem>Search Emoji</CommandItem>
109
- <CommandItem>Calculator</CommandItem>
110
- </CommandGroup>
111
- </CommandList>
112
- </Command>
113
- ```
114
-
115
- ### Command Dialog (Cmd+K)
116
-
117
- ```tsx
118
- import { useEffect, useState } from "react";
119
-
120
- function CommandMenu() {
121
- const [open, setOpen] = useState(false);
122
-
123
- useEffect(() => {
124
- const down = (e: KeyboardEvent) => {
125
- if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
126
- e.preventDefault();
127
- setOpen((open) => !open);
128
- }
129
- };
130
-
131
- document.addEventListener("keydown", down);
132
- return () => document.removeEventListener("keydown", down);
133
- }, []);
134
-
135
- return (
136
- <CommandDialog open={open} onOpenChange={setOpen}>
137
- <CommandInput placeholder="Type a command or search..." />
138
- <CommandList>
139
- <CommandEmpty>No results found.</CommandEmpty>
140
- <CommandGroup heading="Actions">
141
- <CommandItem>
142
- New File
143
- <CommandShortcut>⌘N</CommandShortcut>
144
- </CommandItem>
145
- <CommandItem>
146
- Open File
147
- <CommandShortcut>⌘O</CommandShortcut>
148
- </CommandItem>
149
- </CommandGroup>
150
- </CommandList>
151
- </CommandDialog>
152
- );
153
- }
154
- ```
155
-
156
- ### Con Iconos
157
-
158
- ```tsx
159
- import { Icon } from "@adamosuiteservices/ui/icon";
160
-
161
- <Command>
162
- <CommandInput placeholder="Search..." />
163
- <CommandList>
164
- <CommandGroup heading="Suggestions">
165
- <CommandItem>
166
- <Icon symbol="calendar_month" />
167
- Calendar
168
- </CommandItem>
169
- <CommandItem>
170
- <Icon symbol="sentiment_satisfied" />
171
- Search Emoji
172
- </CommandItem>
173
- <CommandItem>
174
- <Icon symbol="rocket_launch" />
175
- Launch
176
- </CommandItem>
177
- </CommandGroup>
178
- </CommandList>
179
- </Command>;
180
- ```
181
-
182
- **Nota**: SVGs tienen `size-4` automático y `text-muted-foreground` si no tienen clase `text-*`.
183
-
184
- ### Múltiples Grupos con Separadores
185
-
186
- ```tsx
187
- <Command>
188
- <CommandInput placeholder="Search..." />
189
- <CommandList>
190
- <CommandGroup heading="Files">
191
- <CommandItem>New File</CommandItem>
192
- <CommandItem>New Folder</CommandItem>
193
- </CommandGroup>
194
-
195
- <CommandSeparator />
196
-
197
- <CommandGroup heading="Edit">
198
- <CommandItem>Copy</CommandItem>
199
- <CommandItem>Paste</CommandItem>
200
- </CommandGroup>
201
-
202
- <CommandSeparator />
203
-
204
- <CommandGroup heading="View">
205
- <CommandItem>Toggle Sidebar</CommandItem>
206
- <CommandItem>Full Screen</CommandItem>
207
- </CommandGroup>
208
- </CommandList>
209
- </Command>
210
- ```
211
-
212
- ### Con Callbacks (Ejecutar Acciones)
213
-
214
- ```tsx
215
- function App() {
216
- const runCommand = (command: string) => {
217
- console.log(`Running: ${command}`);
218
- // Ejecutar acción
219
- };
220
-
221
- return (
222
- <Command>
223
- <CommandInput placeholder="Search..." />
224
- <CommandList>
225
- <CommandGroup heading="Actions">
226
- <CommandItem onSelect={() => runCommand("new-file")}>
227
- New File
228
- </CommandItem>
229
- <CommandItem onSelect={() => runCommand("open-file")}>
230
- Open File
231
- </CommandItem>
232
- <CommandItem onSelect={() => runCommand("save")}>Save</CommandItem>
233
- </CommandGroup>
234
- </CommandList>
235
- </Command>
236
- );
237
- }
238
- ```
239
-
240
- ### Items Deshabilitados
241
-
242
- ```tsx
243
- <Command>
244
- <CommandList>
245
- <CommandGroup>
246
- <CommandItem>Available Option</CommandItem>
247
- <CommandItem disabled>Coming Soon</CommandItem>
248
- <CommandItem disabled>Premium Feature</CommandItem>
249
- </CommandGroup>
250
- </CommandList>
251
- </Command>
252
- ```
253
-
254
- **Estilos**: `opacity-50`, `pointer-events-none`, `data-[disabled=true]`
255
-
256
- ### Con Keywords para Búsqueda
257
-
258
- ```tsx
259
- <Command>
260
- <CommandInput placeholder="Search settings..." />
261
- <CommandList>
262
- <CommandGroup heading="Settings">
263
- <CommandItem
264
- value="theme"
265
- keywords={["appearance", "dark", "light", "color"]}
266
- >
267
- Theme Settings
268
- </CommandItem>
269
- <CommandItem
270
- value="shortcuts"
271
- keywords={["keyboard", "hotkeys", "bindings"]}
272
- >
273
- Keyboard Shortcuts
274
- </CommandItem>
275
- </CommandGroup>
276
- </CommandList>
277
- </Command>
278
- ```
279
-
280
- **Búsqueda**: Encuentra items por `value`, `children` text, o `keywords[]`
281
-
282
- ### Navegación de Páginas
283
-
284
- ```tsx
285
- function PageSearch() {
286
- const navigate = useNavigate();
287
- const [open, setOpen] = useState(false);
288
-
289
- return (
290
- <CommandDialog open={open} onOpenChange={setOpen}>
291
- <CommandInput placeholder="Search pages..." />
292
- <CommandList>
293
- <CommandGroup heading="Pages">
294
- <CommandItem
295
- onSelect={() => {
296
- navigate("/dashboard");
297
- setOpen(false);
298
- }}
299
- >
300
- Dashboard
301
- </CommandItem>
302
- <CommandItem
303
- onSelect={() => {
304
- navigate("/settings");
305
- setOpen(false);
306
- }}
307
- >
308
- Settings
309
- </CommandItem>
310
- <CommandItem
311
- onSelect={() => {
312
- navigate("/profile");
313
- setOpen(false);
314
- }}
315
- >
316
- Profile
317
- </CommandItem>
318
- </CommandGroup>
319
- </CommandList>
320
- </CommandDialog>
321
- );
322
- }
323
- ```
324
-
325
- ### Filtrado Custom
326
-
327
- ```tsx
328
- // Exacto match en lugar de fuzzy
329
- const exactMatch = (value: string, search: string) => {
330
- return value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0;
331
- };
332
-
333
- <Command filter={exactMatch}>
334
- <CommandInput placeholder="Search..." />
335
- <CommandList>
336
- <CommandGroup>
337
- <CommandItem>Apple</CommandItem>
338
- <CommandItem>Banana</CommandItem>
339
- <CommandItem>Cherry</CommandItem>
340
- </CommandGroup>
341
- </CommandList>
342
- </Command>;
343
- ```
344
-
345
- ### Sin Filtrado Automático
346
-
347
- ```tsx
348
- // Control manual del filtrado
349
- function CustomFilter() {
350
- const [search, setSearch] = useState("");
351
- const [filteredItems, setFilteredItems] = useState(allItems);
352
-
353
- useEffect(() => {
354
- setFilteredItems(
355
- allItems.filter((item) =>
356
- item.toLowerCase().includes(search.toLowerCase())
357
- )
358
- );
359
- }, [search]);
360
-
361
- return (
362
- <Command shouldFilter={false}>
363
- <CommandInput
364
- value={search}
365
- onValueChange={setSearch}
366
- placeholder="Search..."
367
- />
368
- <CommandList>
369
- <CommandGroup>
370
- {filteredItems.map((item) => (
371
- <CommandItem key={item}>{item}</CommandItem>
372
- ))}
373
- </CommandGroup>
374
- </CommandList>
375
- </Command>
376
- );
377
- }
378
- ```
379
-
380
- ## Casos de Uso Comunes
381
-
382
- **Command palette (Cmd+K)**: Búsqueda rápida de acciones/comandos
383
- **Page navigation**: Navegación rápida entre páginas
384
- **File picker**: Búsqueda de archivos en proyecto
385
- **Settings search**: Encontrar configuraciones
386
- **Action menu**: Menú de acciones contextuales
387
- **Spotlight search**: Búsqueda global tipo macOS
388
-
389
- ## Estados y Data Attributes
390
-
391
- ### CommandItem States
392
-
393
- - **Selected**: `data-[selected=true]` → `bg-accent`, `text-accent-foreground`
394
- - **Disabled**: `data-[disabled=true]` → `opacity-50`, `pointer-events-none`
395
-
396
- ### Empty State
397
-
398
- - Automático cuando búsqueda no encuentra resultados
399
- - Personalizable con `<CommandEmpty>`
400
-
401
- ## Navegación por Teclado
402
-
403
- - ✅ **Arrow Up/Down**: Navega entre items
404
- - ✅ **Enter**: Selecciona item enfocado
405
- - ✅ **Escape**: Cierra dialog (si CommandDialog)
406
- - ✅ **Type to search**: Filtrado automático
407
- - ✅ **Tab**: Sale del Command (no navega items)
408
- - ✅ **Home/End**: Primer/último item (si `loop={true}`)
409
-
410
- ## Accesibilidad
411
-
412
- - ✅ **ARIA**: `role="combobox"` en Command, `role="option"` en items
413
- - ✅ **Screen readers**: Anuncia items y cuenta de resultados
414
- - ✅ **Keyboard navigation**: Navegación completa por teclado
415
- - ✅ **Focus management**: Focus visible en items seleccionados
416
- - ✅ **Live regions**: Anuncia cuando cambian resultados
417
- - ✅ **Disabled items**: `aria-disabled` en items deshabilitados
418
-
419
- ## Notas de Implementación
420
-
421
- - **Basado en cmdk**: Librería `cmdk` de Paco Coursey
422
- - **Fuzzy search**: Búsqueda difusa por defecto (typo-tolerant)
423
- - **Performance**: Virtualización automática con muchos items
424
- - **Search icon**: Incluido automáticamente en CommandInput (Material Symbol "search")
425
- - **CommandDialog**: Wrapper de Command + Dialog con estilos predefinidos
426
- - **Data slots**: Cada componente tiene `data-slot` para CSS targeting
427
- - **Icon sizing**: Material Symbol icons automáticamente `text-lg` (18px) con shrink-0 y opacity-50
428
- - **CommandShortcut**: Elemento visual para shortcuts (no ejecuta código)
429
- - **Max height**: CommandList tiene `max-h-[300px]` default
430
- - **Scroll behavior**: `scroll-py-1` para padding al scrollear
431
-
432
- ## Estilos Especiales en CommandDialog
433
-
434
- Cuando se usa en Dialog, aplica estilos adicionales:
435
-
436
- - Items con `py-3` (más padding)
437
- - Input con `h-12`
438
- - SVGs con `h-5 w-5` (más grandes)
439
- - Groups con heading más prominente
440
-
441
- ## Troubleshooting
442
-
443
- **Búsqueda no funciona**: Verifica que `shouldFilter={true}` (default) o implementa filtrado manual
444
- **Items no se ven**: Asegúrate de envolver en `<CommandList>` y `<CommandGroup>`
445
- **Cmd+K no abre dialog**: Verifica que el `useEffect` esté ejecutándose y no haya conflictos con otros shortcuts
446
- **Empty no aparece**: Solo se muestra cuando no hay resultados después de búsqueda
447
- **Shortcuts no ejecutan**: `CommandShortcut` es solo visual, implementa lógica aparte
448
- **Dialog no cierra**: Asegúrate de llamar `setOpen(false)` en `onSelect` de items
449
- **Navegación circular no funciona**: Activa `loop={true}` en Command root
450
-
451
- ## Referencias
452
-
453
- - **cmdk**: https://cmdk.paco.me
454
- - **ARIA Combobox**: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
1
+ # Command
2
+
3
+ Paleta de comandos estilo Cmd+K basada en `cmdk` con búsqueda fuzzy, navegación por teclado, grupos, separadores, y shortcuts. Útil para command palettes, búsqueda rápida, y navegación.
4
+
5
+ ## Importación
6
+
7
+ ```tsx
8
+ import {
9
+ Command,
10
+ CommandInput,
11
+ CommandList,
12
+ CommandEmpty,
13
+ CommandGroup,
14
+ CommandItem,
15
+ CommandSeparator,
16
+ CommandShortcut,
17
+ CommandDialog,
18
+ } from "@adamosuiteservices/ui/command";
19
+ ```
20
+
21
+ ## Anatomía
22
+
23
+ ```tsx
24
+ <Command>
25
+ <CommandInput placeholder="Search..." />
26
+ <CommandList>
27
+ <CommandEmpty>No results found.</CommandEmpty>
28
+ <CommandGroup heading="Actions">
29
+ <CommandItem>
30
+ New File
31
+ <CommandShortcut>⌘N</CommandShortcut>
32
+ </CommandItem>
33
+ <CommandItem>Open</CommandItem>
34
+ </CommandGroup>
35
+ <CommandSeparator />
36
+ <CommandGroup heading="Settings">
37
+ <CommandItem>Preferences</CommandItem>
38
+ </CommandGroup>
39
+ </CommandList>
40
+ </Command>
41
+ ```
42
+
43
+ **Componentes**: 8 (Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut)
44
+
45
+ ## Props Principales
46
+
47
+ ### Command (Root)
48
+
49
+ | Prop | Tipo | Default | Descripción |
50
+ | --------------- | ------------------------------------------- | ----------- | -------------------------------------- |
51
+ | `value` | `string` | - | Valor controlado del item seleccionado |
52
+ | `onValueChange` | `(value: string) => void` | - | Callback cuando cambia selección |
53
+ | `filter` | `(value: string, search: string) => number` | Fuzzy match | Función de filtrado custom |
54
+ | `shouldFilter` | `boolean` | `true` | Habilita filtrado automático |
55
+ | `loop` | `boolean` | `false` | Navegación circular con arrow keys |
56
+ | `className` | `string` | - | Clases CSS adicionales |
57
+
58
+ ### CommandInput
59
+
60
+ | Prop | Tipo | Descripción |
61
+ | --------------- | -------------------------- | -------------------------- |
62
+ | `placeholder` | `string` | Texto placeholder |
63
+ | `value` | `string` | Valor controlado del input |
64
+ | `onValueChange` | `(search: string) => void` | Callback al escribir |
65
+ | `className` | `string` | Clases CSS adicionales |
66
+
67
+ ### CommandList
68
+
69
+ | Prop | Tipo | Default | Descripción |
70
+ | ----------- | ----------- | ------- | -------------------------- |
71
+ | `className` | `string` | - | Clases CSS adicionales |
72
+ | `children` | `ReactNode` | - | Grupos, items, separadores |
73
+
74
+ **Estilos default**: `max-h-[300px]`, scroll vertical automático
75
+
76
+ ### CommandItem
77
+
78
+ | Prop | Tipo | Descripción |
79
+ | ----------- | ------------------------- | ------------------------------------------ |
80
+ | `value` | `string` | Valor único del item (usado para búsqueda) |
81
+ | `onSelect` | `(value: string) => void` | Callback al seleccionar |
82
+ | `disabled` | `boolean` | Desactiva selección |
83
+ | `keywords` | `string[]` | Palabras clave adicionales para búsqueda |
84
+ | `className` | `string` | Clases CSS adicionales |
85
+
86
+ ### CommandDialog
87
+
88
+ | Prop | Tipo | Default | Descripción |
89
+ | ----------------- | ------------------------- | ------------------- | ------------------------ |
90
+ | `open` | `boolean` | - | Estado del dialog |
91
+ | `onOpenChange` | `(open: boolean) => void` | - | Callback al abrir/cerrar |
92
+ | `title` | `string` | `"Command Palette"` | Título (sr-only) |
93
+ | `description` | `string` | `"Search for..."` | Descripción (sr-only) |
94
+ | `showCloseButton` | `boolean` | `true` | Muestra botón de cerrar |
95
+ | `className` | `string` | - | Clases CSS adicionales |
96
+
97
+ ## Patrones de Uso
98
+
99
+ ### Básico (Inline)
100
+
101
+ ```tsx
102
+ <Command>
103
+ <CommandInput placeholder="Type to search..." />
104
+ <CommandList>
105
+ <CommandEmpty>No results found.</CommandEmpty>
106
+ <CommandGroup heading="Suggestions">
107
+ <CommandItem>Calendar</CommandItem>
108
+ <CommandItem>Search Emoji</CommandItem>
109
+ <CommandItem>Calculator</CommandItem>
110
+ </CommandGroup>
111
+ </CommandList>
112
+ </Command>
113
+ ```
114
+
115
+ ### Command Dialog (Cmd+K)
116
+
117
+ ```tsx
118
+ import { useEffect, useState } from "react";
119
+
120
+ function CommandMenu() {
121
+ const [open, setOpen] = useState(false);
122
+
123
+ useEffect(() => {
124
+ const down = (e: KeyboardEvent) => {
125
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
126
+ e.preventDefault();
127
+ setOpen((open) => !open);
128
+ }
129
+ };
130
+
131
+ document.addEventListener("keydown", down);
132
+ return () => document.removeEventListener("keydown", down);
133
+ }, []);
134
+
135
+ return (
136
+ <CommandDialog open={open} onOpenChange={setOpen}>
137
+ <CommandInput placeholder="Type a command or search..." />
138
+ <CommandList>
139
+ <CommandEmpty>No results found.</CommandEmpty>
140
+ <CommandGroup heading="Actions">
141
+ <CommandItem>
142
+ New File
143
+ <CommandShortcut>⌘N</CommandShortcut>
144
+ </CommandItem>
145
+ <CommandItem>
146
+ Open File
147
+ <CommandShortcut>⌘O</CommandShortcut>
148
+ </CommandItem>
149
+ </CommandGroup>
150
+ </CommandList>
151
+ </CommandDialog>
152
+ );
153
+ }
154
+ ```
155
+
156
+ ### Con Iconos
157
+
158
+ ```tsx
159
+ import { Icon } from "@adamosuiteservices/ui/icon";
160
+
161
+ <Command>
162
+ <CommandInput placeholder="Search..." />
163
+ <CommandList>
164
+ <CommandGroup heading="Suggestions">
165
+ <CommandItem>
166
+ <Icon symbol="calendar_month" />
167
+ Calendar
168
+ </CommandItem>
169
+ <CommandItem>
170
+ <Icon symbol="sentiment_satisfied" />
171
+ Search Emoji
172
+ </CommandItem>
173
+ <CommandItem>
174
+ <Icon symbol="rocket_launch" />
175
+ Launch
176
+ </CommandItem>
177
+ </CommandGroup>
178
+ </CommandList>
179
+ </Command>;
180
+ ```
181
+
182
+ **Nota**: SVGs tienen `size-4` automático y `text-muted-foreground` si no tienen clase `text-*`.
183
+
184
+ ### Múltiples Grupos con Separadores
185
+
186
+ ```tsx
187
+ <Command>
188
+ <CommandInput placeholder="Search..." />
189
+ <CommandList>
190
+ <CommandGroup heading="Files">
191
+ <CommandItem>New File</CommandItem>
192
+ <CommandItem>New Folder</CommandItem>
193
+ </CommandGroup>
194
+
195
+ <CommandSeparator />
196
+
197
+ <CommandGroup heading="Edit">
198
+ <CommandItem>Copy</CommandItem>
199
+ <CommandItem>Paste</CommandItem>
200
+ </CommandGroup>
201
+
202
+ <CommandSeparator />
203
+
204
+ <CommandGroup heading="View">
205
+ <CommandItem>Toggle Sidebar</CommandItem>
206
+ <CommandItem>Full Screen</CommandItem>
207
+ </CommandGroup>
208
+ </CommandList>
209
+ </Command>
210
+ ```
211
+
212
+ ### Con Callbacks (Ejecutar Acciones)
213
+
214
+ ```tsx
215
+ function App() {
216
+ const runCommand = (command: string) => {
217
+ console.log(`Running: ${command}`);
218
+ // Ejecutar acción
219
+ };
220
+
221
+ return (
222
+ <Command>
223
+ <CommandInput placeholder="Search..." />
224
+ <CommandList>
225
+ <CommandGroup heading="Actions">
226
+ <CommandItem onSelect={() => runCommand("new-file")}>
227
+ New File
228
+ </CommandItem>
229
+ <CommandItem onSelect={() => runCommand("open-file")}>
230
+ Open File
231
+ </CommandItem>
232
+ <CommandItem onSelect={() => runCommand("save")}>Save</CommandItem>
233
+ </CommandGroup>
234
+ </CommandList>
235
+ </Command>
236
+ );
237
+ }
238
+ ```
239
+
240
+ ### Items Deshabilitados
241
+
242
+ ```tsx
243
+ <Command>
244
+ <CommandList>
245
+ <CommandGroup>
246
+ <CommandItem>Available Option</CommandItem>
247
+ <CommandItem disabled>Coming Soon</CommandItem>
248
+ <CommandItem disabled>Premium Feature</CommandItem>
249
+ </CommandGroup>
250
+ </CommandList>
251
+ </Command>
252
+ ```
253
+
254
+ **Estilos**: `opacity-50`, `pointer-events-none`, `data-[disabled=true]`
255
+
256
+ ### Con Keywords para Búsqueda
257
+
258
+ ```tsx
259
+ <Command>
260
+ <CommandInput placeholder="Search settings..." />
261
+ <CommandList>
262
+ <CommandGroup heading="Settings">
263
+ <CommandItem
264
+ value="theme"
265
+ keywords={["appearance", "dark", "light", "color"]}
266
+ >
267
+ Theme Settings
268
+ </CommandItem>
269
+ <CommandItem
270
+ value="shortcuts"
271
+ keywords={["keyboard", "hotkeys", "bindings"]}
272
+ >
273
+ Keyboard Shortcuts
274
+ </CommandItem>
275
+ </CommandGroup>
276
+ </CommandList>
277
+ </Command>
278
+ ```
279
+
280
+ **Búsqueda**: Encuentra items por `value`, `children` text, o `keywords[]`
281
+
282
+ ### Navegación de Páginas
283
+
284
+ ```tsx
285
+ function PageSearch() {
286
+ const navigate = useNavigate();
287
+ const [open, setOpen] = useState(false);
288
+
289
+ return (
290
+ <CommandDialog open={open} onOpenChange={setOpen}>
291
+ <CommandInput placeholder="Search pages..." />
292
+ <CommandList>
293
+ <CommandGroup heading="Pages">
294
+ <CommandItem
295
+ onSelect={() => {
296
+ navigate("/dashboard");
297
+ setOpen(false);
298
+ }}
299
+ >
300
+ Dashboard
301
+ </CommandItem>
302
+ <CommandItem
303
+ onSelect={() => {
304
+ navigate("/settings");
305
+ setOpen(false);
306
+ }}
307
+ >
308
+ Settings
309
+ </CommandItem>
310
+ <CommandItem
311
+ onSelect={() => {
312
+ navigate("/profile");
313
+ setOpen(false);
314
+ }}
315
+ >
316
+ Profile
317
+ </CommandItem>
318
+ </CommandGroup>
319
+ </CommandList>
320
+ </CommandDialog>
321
+ );
322
+ }
323
+ ```
324
+
325
+ ### Filtrado Custom
326
+
327
+ ```tsx
328
+ // Exacto match en lugar de fuzzy
329
+ const exactMatch = (value: string, search: string) => {
330
+ return value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0;
331
+ };
332
+
333
+ <Command filter={exactMatch}>
334
+ <CommandInput placeholder="Search..." />
335
+ <CommandList>
336
+ <CommandGroup>
337
+ <CommandItem>Apple</CommandItem>
338
+ <CommandItem>Banana</CommandItem>
339
+ <CommandItem>Cherry</CommandItem>
340
+ </CommandGroup>
341
+ </CommandList>
342
+ </Command>;
343
+ ```
344
+
345
+ ### Sin Filtrado Automático
346
+
347
+ ```tsx
348
+ // Control manual del filtrado
349
+ function CustomFilter() {
350
+ const [search, setSearch] = useState("");
351
+ const [filteredItems, setFilteredItems] = useState(allItems);
352
+
353
+ useEffect(() => {
354
+ setFilteredItems(
355
+ allItems.filter((item) =>
356
+ item.toLowerCase().includes(search.toLowerCase())
357
+ )
358
+ );
359
+ }, [search]);
360
+
361
+ return (
362
+ <Command shouldFilter={false}>
363
+ <CommandInput
364
+ value={search}
365
+ onValueChange={setSearch}
366
+ placeholder="Search..."
367
+ />
368
+ <CommandList>
369
+ <CommandGroup>
370
+ {filteredItems.map((item) => (
371
+ <CommandItem key={item}>{item}</CommandItem>
372
+ ))}
373
+ </CommandGroup>
374
+ </CommandList>
375
+ </Command>
376
+ );
377
+ }
378
+ ```
379
+
380
+ ## Casos de Uso Comunes
381
+
382
+ **Command palette (Cmd+K)**: Búsqueda rápida de acciones/comandos
383
+ **Page navigation**: Navegación rápida entre páginas
384
+ **File picker**: Búsqueda de archivos en proyecto
385
+ **Settings search**: Encontrar configuraciones
386
+ **Action menu**: Menú de acciones contextuales
387
+ **Spotlight search**: Búsqueda global tipo macOS
388
+
389
+ ## Estados y Data Attributes
390
+
391
+ ### CommandItem States
392
+
393
+ - **Selected**: `data-[selected=true]` → `bg-accent`, `text-accent-foreground`
394
+ - **Disabled**: `data-[disabled=true]` → `opacity-50`, `pointer-events-none`
395
+
396
+ ### Empty State
397
+
398
+ - Automático cuando búsqueda no encuentra resultados
399
+ - Personalizable con `<CommandEmpty>`
400
+
401
+ ## Navegación por Teclado
402
+
403
+ - ✅ **Arrow Up/Down**: Navega entre items
404
+ - ✅ **Enter**: Selecciona item enfocado
405
+ - ✅ **Escape**: Cierra dialog (si CommandDialog)
406
+ - ✅ **Type to search**: Filtrado automático
407
+ - ✅ **Tab**: Sale del Command (no navega items)
408
+ - ✅ **Home/End**: Primer/último item (si `loop={true}`)
409
+
410
+ ## Accesibilidad
411
+
412
+ - ✅ **ARIA**: `role="combobox"` en Command, `role="option"` en items
413
+ - ✅ **Screen readers**: Anuncia items y cuenta de resultados
414
+ - ✅ **Keyboard navigation**: Navegación completa por teclado
415
+ - ✅ **Focus management**: Focus visible en items seleccionados
416
+ - ✅ **Live regions**: Anuncia cuando cambian resultados
417
+ - ✅ **Disabled items**: `aria-disabled` en items deshabilitados
418
+
419
+ ## Notas de Implementación
420
+
421
+ - **Basado en cmdk**: Librería `cmdk` de Paco Coursey
422
+ - **Fuzzy search**: Búsqueda difusa por defecto (typo-tolerant)
423
+ - **Performance**: Virtualización automática con muchos items
424
+ - **Search icon**: Incluido automáticamente en CommandInput (Material Symbol "search")
425
+ - **CommandDialog**: Wrapper de Command + Dialog con estilos predefinidos
426
+ - **Data slots**: Cada componente tiene `data-slot` para CSS targeting
427
+ - **Icon sizing**: Material Symbol icons automáticamente `text-lg` (18px) con shrink-0 y opacity-50
428
+ - **CommandShortcut**: Elemento visual para shortcuts (no ejecuta código)
429
+ - **Max height**: CommandList tiene `max-h-[300px]` default
430
+ - **Scroll behavior**: `scroll-py-1` para scrollear
431
+
432
+ ## Estilos Base
433
+
434
+ ### CommandInput Wrapper
435
+
436
+ - **Height**: `h-10` (40px)
437
+ - **Padding**: `px-3` horizontal
438
+ - **Border**: `border-b` bottom border
439
+ - **Rounded**: `rounded-t-[calc(var(--radius)-1px)]` - matches container
440
+ - **Icon styling**: Material Symbols icons with `text-lg` and `opacity-50`
441
+
442
+ ### CommandItem
443
+
444
+ - **Height**: `h-10` (40px)
445
+ - **Padding**: `px-3` horizontal, no vertical padding
446
+ - **Border**: `border-b` between items
447
+ - **Rounded corners**: Last item has `rounded-b-[calc(var(--radius)-1px)]` and no bottom border
448
+ - **Gap**: `gap-2` between content elements
449
+ - **Icon constraints**: None - icons use natural sizes
450
+
451
+ ### CommandGroup
452
+
453
+ - **Padding**: No padding on container (removed `p-1`)
454
+ - **Last item**: Automatically rounded at bottom and no border-b
455
+
456
+ ### CommandList
457
+
458
+ - **Max height**: `max-h-[300px]`
459
+ - **Scroll**: Vertical auto scroll
460
+ - **Padding**: `scroll-py-1`
461
+
462
+ ## Estilos Especiales en CommandDialog
463
+
464
+ Cuando se usa en Dialog, aplica estilos adicionales:
465
+
466
+ - Items con `py-3` (más padding)
467
+ - Input con `h-12`
468
+ - SVGs con `h-5 w-5` (más grandes)
469
+ - Groups con heading más prominente
470
+
471
+ ## Troubleshooting
472
+
473
+ **Búsqueda no funciona**: Verifica que `shouldFilter={true}` (default) o implementa filtrado manual
474
+ **Items no se ven**: Asegúrate de envolver en `<CommandList>` y `<CommandGroup>`
475
+ **Cmd+K no abre dialog**: Verifica que el `useEffect` esté ejecutándose y no haya conflictos con otros shortcuts
476
+ **Empty no aparece**: Solo se muestra cuando no hay resultados después de búsqueda
477
+ **Shortcuts no ejecutan**: `CommandShortcut` es solo visual, implementa lógica aparte
478
+ **Dialog no cierra**: Asegúrate de llamar `setOpen(false)` en `onSelect` de items
479
+ **Navegación circular no funciona**: Activa `loop={true}` en Command root
480
+
481
+ ## Referencias
482
+
483
+ - **cmdk**: https://cmdk.paco.me
484
+ - **ARIA Combobox**: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/