@hex-core/components 1.3.1 → 1.4.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.
Files changed (186) hide show
  1. package/README.md +183 -9
  2. package/dist/accordion.d.ts +13 -0
  3. package/dist/accordion.js +62 -0
  4. package/dist/accordion.js.map +1 -0
  5. package/dist/alert-dialog.d.ts +34 -0
  6. package/dist/alert-dialog.js +125 -0
  7. package/dist/alert-dialog.js.map +1 -0
  8. package/dist/alert.d.ts +17 -0
  9. package/dist/alert.js +54 -0
  10. package/dist/alert.js.map +1 -0
  11. package/dist/aspect-ratio.d.ts +7 -0
  12. package/dist/aspect-ratio.js +8 -0
  13. package/dist/aspect-ratio.js.map +1 -0
  14. package/dist/avatar.d.ts +11 -0
  15. package/dist/avatar.js +44 -0
  16. package/dist/avatar.js.map +1 -0
  17. package/dist/badge.d.ts +22 -0
  18. package/dist/badge.js +36 -0
  19. package/dist/badge.js.map +1 -0
  20. package/dist/breadcrumb.d.ts +27 -0
  21. package/dist/breadcrumb.js +120 -0
  22. package/dist/breadcrumb.js.map +1 -0
  23. package/dist/button-variants-Bx6gCUFp.d.ts +19 -0
  24. package/dist/button.d.ts +13 -0
  25. package/dist/button.js +113 -0
  26. package/dist/button.js.map +1 -0
  27. package/dist/calendar.d.ts +17 -0
  28. package/dist/calendar.js +126 -0
  29. package/dist/calendar.js.map +1 -0
  30. package/dist/card.d.ts +16 -0
  31. package/dist/card.js +68 -0
  32. package/dist/card.js.map +1 -0
  33. package/dist/checkbox.d.ts +11 -0
  34. package/dist/checkbox.js +65 -0
  35. package/dist/checkbox.js.map +1 -0
  36. package/dist/cluster.d.ts +34 -0
  37. package/dist/cluster.js +50 -0
  38. package/dist/cluster.js.map +1 -0
  39. package/dist/collapsible.d.ts +11 -0
  40. package/dist/collapsible.js +10 -0
  41. package/dist/collapsible.js.map +1 -0
  42. package/dist/color-picker.d.ts +44 -0
  43. package/dist/color-picker.js +321 -0
  44. package/dist/color-picker.js.map +1 -0
  45. package/dist/combobox.d.ts +45 -0
  46. package/dist/combobox.js +226 -0
  47. package/dist/combobox.js.map +1 -0
  48. package/dist/command.d.ts +111 -0
  49. package/dist/command.js +232 -0
  50. package/dist/command.js.map +1 -0
  51. package/dist/container.d.ts +41 -0
  52. package/dist/container.js +39 -0
  53. package/dist/container.js.map +1 -0
  54. package/dist/context-menu.d.ts +37 -0
  55. package/dist/context-menu.js +130 -0
  56. package/dist/context-menu.js.map +1 -0
  57. package/dist/data-table.d.ts +33 -0
  58. package/dist/data-table.js +103 -0
  59. package/dist/data-table.js.map +1 -0
  60. package/dist/date-picker.d.ts +43 -0
  61. package/dist/date-picker.js +221 -0
  62. package/dist/date-picker.js.map +1 -0
  63. package/dist/dialog.d.ts +46 -0
  64. package/dist/dialog.js +125 -0
  65. package/dist/dialog.js.map +1 -0
  66. package/dist/drawer.d.ts +41 -0
  67. package/dist/drawer.js +82 -0
  68. package/dist/drawer.js.map +1 -0
  69. package/dist/dropdown-menu.d.ts +39 -0
  70. package/dist/dropdown-menu.js +133 -0
  71. package/dist/dropdown-menu.js.map +1 -0
  72. package/dist/dropzone.d.ts +54 -0
  73. package/dist/dropzone.js +194 -0
  74. package/dist/dropzone.js.map +1 -0
  75. package/dist/file-tree.d.ts +53 -0
  76. package/dist/file-tree.js +322 -0
  77. package/dist/file-tree.js.map +1 -0
  78. package/dist/form.d.ts +45 -0
  79. package/dist/form.js +114 -0
  80. package/dist/form.js.map +1 -0
  81. package/dist/grid.d.ts +50 -0
  82. package/dist/grid.js +58 -0
  83. package/dist/grid.js.map +1 -0
  84. package/dist/hover-card.d.ts +11 -0
  85. package/dist/hover-card.js +34 -0
  86. package/dist/hover-card.js.map +1 -0
  87. package/dist/index.d.ts +98 -1571
  88. package/dist/index.js +527 -5536
  89. package/dist/index.js.map +1 -1
  90. package/dist/input-otp.d.ts +19 -0
  91. package/dist/input-otp.js +71 -0
  92. package/dist/input-otp.js.map +1 -0
  93. package/dist/input.d.ts +6 -0
  94. package/dist/input.js +40 -0
  95. package/dist/input.js.map +1 -0
  96. package/dist/label.d.ts +11 -0
  97. package/dist/label.js +22 -0
  98. package/dist/label.js.map +1 -0
  99. package/dist/menubar.d.ts +35 -0
  100. package/dist/menubar.js +106 -0
  101. package/dist/menubar.js.map +1 -0
  102. package/dist/multi-combobox.d.ts +51 -0
  103. package/dist/multi-combobox.js +258 -0
  104. package/dist/multi-combobox.js.map +1 -0
  105. package/dist/navigation-menu.d.ts +23 -0
  106. package/dist/navigation-menu.js +108 -0
  107. package/dist/navigation-menu.js.map +1 -0
  108. package/dist/pagination.d.ts +40 -0
  109. package/dist/pagination.js +195 -0
  110. package/dist/pagination.js.map +1 -0
  111. package/dist/popover.d.ts +13 -0
  112. package/dist/popover.js +35 -0
  113. package/dist/popover.js.map +1 -0
  114. package/dist/progress.d.ts +10 -0
  115. package/dist/progress.js +38 -0
  116. package/dist/progress.js.map +1 -0
  117. package/dist/radio-group.d.ts +9 -0
  118. package/dist/radio-group.js +44 -0
  119. package/dist/radio-group.js.map +1 -0
  120. package/dist/resizable.d.ts +28 -0
  121. package/dist/resizable.js +66 -0
  122. package/dist/resizable.js.map +1 -0
  123. package/dist/schemas.d.ts +121 -0
  124. package/dist/schemas.js +4643 -0
  125. package/dist/schemas.js.map +1 -0
  126. package/dist/scroll-area.d.ts +18 -0
  127. package/dist/scroll-area.js +55 -0
  128. package/dist/scroll-area.js.map +1 -0
  129. package/dist/select.d.ts +21 -0
  130. package/dist/select.js +136 -0
  131. package/dist/select.js.map +1 -0
  132. package/dist/separator.d.ts +11 -0
  133. package/dist/separator.js +29 -0
  134. package/dist/separator.js.map +1 -0
  135. package/dist/sheet.d.ts +39 -0
  136. package/dist/sheet.js +140 -0
  137. package/dist/sheet.js.map +1 -0
  138. package/dist/sidebar.d.ts +75 -0
  139. package/dist/sidebar.js +201 -0
  140. package/dist/sidebar.js.map +1 -0
  141. package/dist/skeleton.d.ts +11 -0
  142. package/dist/skeleton.js +21 -0
  143. package/dist/skeleton.js.map +1 -0
  144. package/dist/slider.d.ts +20 -0
  145. package/dist/slider.js +55 -0
  146. package/dist/slider.js.map +1 -0
  147. package/dist/sonner.d.ts +14 -0
  148. package/dist/sonner.js +27 -0
  149. package/dist/sonner.js.map +1 -0
  150. package/dist/spacer.d.ts +38 -0
  151. package/dist/spacer.js +43 -0
  152. package/dist/spacer.js.map +1 -0
  153. package/dist/stack.d.ts +34 -0
  154. package/dist/stack.js +49 -0
  155. package/dist/stack.js.map +1 -0
  156. package/dist/stepper.d.ts +48 -0
  157. package/dist/stepper.js +226 -0
  158. package/dist/stepper.js.map +1 -0
  159. package/dist/switch.d.ts +11 -0
  160. package/dist/switch.js +47 -0
  161. package/dist/switch.js.map +1 -0
  162. package/dist/table.d.ts +24 -0
  163. package/dist/table.js +85 -0
  164. package/dist/table.js.map +1 -0
  165. package/dist/tabs.d.ts +13 -0
  166. package/dist/tabs.js +57 -0
  167. package/dist/tabs.js.map +1 -0
  168. package/dist/textarea.d.ts +10 -0
  169. package/dist/textarea.js +36 -0
  170. package/dist/textarea.js.map +1 -0
  171. package/dist/time-picker.d.ts +34 -0
  172. package/dist/time-picker.js +50 -0
  173. package/dist/time-picker.js.map +1 -0
  174. package/dist/timeline.d.ts +42 -0
  175. package/dist/timeline.js +84 -0
  176. package/dist/timeline.js.map +1 -0
  177. package/dist/toggle-group.d.ts +17 -0
  178. package/dist/toggle-group.js +83 -0
  179. package/dist/toggle-group.js.map +1 -0
  180. package/dist/toggle.d.ts +19 -0
  181. package/dist/toggle.js +49 -0
  182. package/dist/toggle.js.map +1 -0
  183. package/dist/tooltip.d.ts +13 -0
  184. package/dist/tooltip.js +33 -0
  185. package/dist/tooltip.js.map +1 -0
  186. package/package.json +68 -16
@@ -0,0 +1,4643 @@
1
+ // src/primitives/button/button.schema.ts
2
+ var buttonSchema = {
3
+ name: "button",
4
+ displayName: "Button",
5
+ description: "A versatile button component with multiple variants, sizes, and states. Supports icons, loading state, and composition via asChild.",
6
+ category: "primitive",
7
+ subcategory: "actions",
8
+ props: [
9
+ {
10
+ name: "variant",
11
+ type: "enum",
12
+ required: false,
13
+ default: "default",
14
+ description: "The visual style of the button",
15
+ enumValues: ["default", "destructive", "outline", "secondary", "ghost", "link"]
16
+ },
17
+ {
18
+ name: "size",
19
+ type: "enum",
20
+ required: false,
21
+ default: "default",
22
+ description: "The size of the button",
23
+ enumValues: ["default", "sm", "lg", "icon"]
24
+ },
25
+ {
26
+ name: "asChild",
27
+ type: "boolean",
28
+ required: false,
29
+ default: false,
30
+ description: "Render as a Slot component, merging props with the child element. Use to render as a link or other element."
31
+ },
32
+ {
33
+ name: "loading",
34
+ type: "boolean",
35
+ required: false,
36
+ default: false,
37
+ description: "Show loading spinner and disable interaction"
38
+ },
39
+ {
40
+ name: "disabled",
41
+ type: "boolean",
42
+ required: false,
43
+ default: false,
44
+ description: "Disable the button"
45
+ },
46
+ {
47
+ name: "className",
48
+ type: "string",
49
+ required: false,
50
+ description: "Additional CSS classes to merge with the component styles"
51
+ }
52
+ ],
53
+ variants: [
54
+ {
55
+ name: "variant",
56
+ description: "Visual style variants",
57
+ values: [
58
+ {
59
+ value: "default",
60
+ description: "Primary filled button with subtle shadow for main actions",
61
+ useWhen: "the single most important action on the screen \u2014 exactly one per view (Save, Submit, Continue)"
62
+ },
63
+ {
64
+ value: "destructive",
65
+ description: "Red button with shadow for dangerous/irreversible actions",
66
+ useWhen: "the action cannot be undone without recreating data: Delete, Archive, Deactivate, Leave team, Force-quit"
67
+ },
68
+ {
69
+ value: "outline",
70
+ description: "Bordered button with hover fill for secondary actions",
71
+ useWhen: "tertiary actions on a flat (non-elevated) surface; signals 'optional' next to a primary"
72
+ },
73
+ {
74
+ value: "secondary",
75
+ description: "Muted filled button for less prominent actions",
76
+ useWhen: "the second-most-important action next to a primary CTA: Cancel, Save Draft, Skip"
77
+ },
78
+ {
79
+ value: "ghost",
80
+ description: "Transparent button, background appears on hover",
81
+ useWhen: "low-emphasis action inside a list, toolbar, or row where chrome should disappear at rest"
82
+ },
83
+ {
84
+ value: "link",
85
+ description: "Styled as a hyperlink with underline on hover, no padding",
86
+ useWhen: "an inline action inside flowing text, or a 'Learn more' affordance in an empty state"
87
+ }
88
+ ],
89
+ default: "default"
90
+ },
91
+ {
92
+ name: "size",
93
+ description: "Size variants",
94
+ values: [
95
+ {
96
+ value: "default",
97
+ description: "Standard size (h-10, px-4)",
98
+ useWhen: "default everywhere; the only size you need on most surfaces"
99
+ },
100
+ {
101
+ value: "sm",
102
+ description: "Compact size (h-9, px-3)",
103
+ useWhen: "buttons living inside a row, toolbar, or table cell where vertical density matters"
104
+ },
105
+ {
106
+ value: "lg",
107
+ description: "Large size (h-11, px-8, text-base)",
108
+ useWhen: "the single hero CTA on a marketing page, pricing card, or empty-state action"
109
+ },
110
+ {
111
+ value: "icon",
112
+ description: "Square icon-only size (h-10, w-10)",
113
+ useWhen: "icon-only actions (close, settings, more). Always pair with aria-label"
114
+ }
115
+ ],
116
+ default: "default"
117
+ }
118
+ ],
119
+ slots: [
120
+ {
121
+ name: "children",
122
+ description: "Button label content",
123
+ required: true,
124
+ acceptedTypes: ["ReactNode"]
125
+ }
126
+ ],
127
+ dependencies: {
128
+ npm: ["class-variance-authority", "@radix-ui/react-slot", "clsx", "tailwind-merge"],
129
+ internal: [],
130
+ peer: ["react", "react-dom"]
131
+ },
132
+ tokensUsed: [
133
+ "primary",
134
+ "primary-foreground",
135
+ "destructive",
136
+ "destructive-foreground",
137
+ "secondary",
138
+ "secondary-foreground",
139
+ "accent",
140
+ "accent-foreground",
141
+ "background",
142
+ "input",
143
+ "ring"
144
+ ],
145
+ examples: [
146
+ {
147
+ title: "Basic usage",
148
+ description: "A simple primary button",
149
+ code: "<Button>Click me</Button>",
150
+ composition: ["form-action"]
151
+ },
152
+ {
153
+ title: "Variants",
154
+ description: "Different visual styles",
155
+ code: '<>\n <Button variant="default">Primary</Button>\n <Button variant="outline">Outline</Button>\n <Button variant="secondary">Secondary</Button>\n <Button variant="ghost">Ghost</Button>\n <Button variant="destructive">Delete</Button>\n <Button variant="link">Link</Button>\n</>',
156
+ composition: ["showcase"]
157
+ },
158
+ {
159
+ title: "Destructive confirm in dialog",
160
+ description: "The canonical destructive action, paired with Cancel inside an alert-dialog footer",
161
+ code: '<AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant="outline">Delete account</Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>\n <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction variant="destructive">Delete</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n</AlertDialog>',
162
+ composition: ["dialog", "alert-dialog", "destructive", "confirm"]
163
+ },
164
+ {
165
+ title: "Form action pair",
166
+ description: "Primary submit + secondary cancel \u2014 the universal end-of-form pattern",
167
+ code: '<div className="flex justify-end gap-2">\n <Button variant="secondary" type="button">Cancel</Button>\n <Button type="submit">Save changes</Button>\n</div>',
168
+ composition: ["form", "form-action"]
169
+ },
170
+ {
171
+ title: "With loading state",
172
+ description: "Button showing a spinner while loading",
173
+ code: "<Button loading>Submitting...</Button>",
174
+ composition: ["form-action", "async"]
175
+ },
176
+ {
177
+ title: "As link",
178
+ description: "Button rendered as an anchor tag",
179
+ code: '<Button asChild>\n <a href="/login">Login</a>\n</Button>',
180
+ composition: ["navigation", "as-child"]
181
+ },
182
+ {
183
+ title: "Icon button",
184
+ description: "Square button with just an icon",
185
+ code: '<Button variant="outline" size="icon" aria-label="Settings">\n <SettingsIcon />\n</Button>',
186
+ composition: ["icon-only", "toolbar"]
187
+ },
188
+ {
189
+ title: "Leading icon",
190
+ description: "Button with an icon before its label \u2014 clarifies intent without sacrificing readability",
191
+ code: "<Button>\n <PlusIcon />\n Add member\n</Button>",
192
+ composition: ["icon-leading", "form-action"]
193
+ }
194
+ ],
195
+ ai: {
196
+ whenToUse: "Use for clickable actions: form submissions, confirmations, triggering operations. Use 'default' variant for primary CTAs, 'outline' or 'secondary' for less important actions, 'ghost' for toolbar-style actions.",
197
+ whenNotToUse: "Don't use for navigation between pages (use Link or anchor with asChild). Don't use 'destructive' for non-dangerous actions. Don't use for toggling state (use Toggle or Switch).",
198
+ commonMistakes: [
199
+ "Nesting interactive elements inside asChild button",
200
+ "Using <Button> with plain onClick for navigation instead of <Button asChild><a href=...></a></Button> \u2014 breaks middle-click, cmd-click, and right-click \u2192 'open in new tab', and skips the browser history entry",
201
+ "Using <Button variant='destructive'> for recoverable actions like 'Reset filters' \u2014 reserve destructive for delete/archive/leave; use 'secondary' or 'ghost' for resets",
202
+ "Using <Button size='icon'> without an aria-label \u2014 screen readers have nothing to announce; always pair icon-only buttons with aria-label or visible text"
203
+ ],
204
+ antiPatterns: [
205
+ {
206
+ mistake: "Using <Button> to flip a boolean state on/off (volume mute, dark mode, airplane mode)",
207
+ insteadUse: "switch",
208
+ why: "Switch communicates the on/off semantic to assistive tech (role=switch + aria-checked). A button announces 'press' which loses the state semantic."
209
+ },
210
+ {
211
+ mistake: "Using <Button> as a tab to switch between sibling content panels",
212
+ insteadUse: "tabs",
213
+ why: "Tabs ship role=tablist + roving tabindex + arrow-key navigation. Buttons in a row don't \u2014 assistive tech will announce them as N independent actions instead of a tab group."
214
+ },
215
+ {
216
+ mistake: "Using <Button> to expand/collapse a content section",
217
+ insteadUse: "accordion",
218
+ why: "Accordion handles aria-expanded / aria-controls automatically and ships the chevron rotation animation. A bare button doing the same job is missing those affordances."
219
+ }
220
+ ],
221
+ relatedComponents: ["toggle", "toggle-group", "dropdown-menu", "badge"],
222
+ accessibilityNotes: "Automatically handles focus ring, disabled state, and aria attributes. Icon-only buttons MUST have aria-label. Loading state automatically sets disabled.",
223
+ tokenBudget: 500
224
+ },
225
+ tags: ["button", "action", "cta", "form", "interactive", "click"]
226
+ };
227
+
228
+ // src/primitives/input/input.schema.ts
229
+ var inputSchema = {
230
+ name: "input",
231
+ displayName: "Input",
232
+ description: "A styled text input with smooth focus transitions, shadow effects, and full HTML input compatibility.",
233
+ category: "primitive",
234
+ subcategory: "forms",
235
+ props: [
236
+ {
237
+ name: "type",
238
+ type: "enum",
239
+ required: false,
240
+ default: "text",
241
+ description: "The HTML input type",
242
+ enumValues: ["text", "password", "email", "number", "search", "tel", "url", "file", "hidden"]
243
+ },
244
+ {
245
+ name: "placeholder",
246
+ type: "string",
247
+ required: false,
248
+ description: "Placeholder text shown when the input is empty"
249
+ },
250
+ {
251
+ name: "disabled",
252
+ type: "boolean",
253
+ required: false,
254
+ default: false,
255
+ description: "Disable the input"
256
+ },
257
+ {
258
+ name: "value",
259
+ type: "string",
260
+ required: false,
261
+ description: "Controlled input value"
262
+ },
263
+ {
264
+ name: "defaultValue",
265
+ type: "string",
266
+ required: false,
267
+ description: "Default value for uncontrolled usage"
268
+ },
269
+ {
270
+ name: "onChange",
271
+ type: "function",
272
+ required: false,
273
+ description: "Change handler for controlled usage: (e: ChangeEvent<HTMLInputElement>) => void"
274
+ },
275
+ {
276
+ name: "className",
277
+ type: "string",
278
+ required: false,
279
+ description: "Additional CSS classes to merge with the component styles"
280
+ }
281
+ ],
282
+ variants: [],
283
+ slots: [],
284
+ dependencies: {
285
+ npm: ["clsx", "tailwind-merge"],
286
+ internal: [],
287
+ peer: ["react", "react-dom"]
288
+ },
289
+ tokensUsed: ["input", "background", "ring", "muted-foreground", "foreground"],
290
+ examples: [
291
+ {
292
+ title: "Basic usage",
293
+ description: "A simple text input",
294
+ code: '<Input placeholder="Enter your name" />'
295
+ },
296
+ {
297
+ title: "Email input",
298
+ description: "An email-type input",
299
+ code: '<Input type="email" placeholder="you@example.com" />'
300
+ },
301
+ {
302
+ title: "File upload",
303
+ description: "File input with styled file button",
304
+ code: '<Input type="file" />'
305
+ },
306
+ {
307
+ title: "With label",
308
+ description: "Input paired with a Label component",
309
+ code: '<div className="grid w-full max-w-sm gap-1.5">\n <Label htmlFor="email">Email</Label>\n <Input type="email" id="email" placeholder="Email" />\n</div>'
310
+ }
311
+ ],
312
+ ai: {
313
+ whenToUse: "Use for single-line text input: names, emails, passwords, search, numbers. Always pair with a Label for accessibility.",
314
+ whenNotToUse: "Don't use for multi-line text (use Textarea). Don't use for selection from predefined options (use Select). Don't use for rich text editing.",
315
+ commonMistakes: [
316
+ "Missing associated Label element for accessibility",
317
+ "Using type='number' without min/max constraints",
318
+ "Not providing placeholder text for context"
319
+ ],
320
+ relatedComponents: ["label", "textarea", "form"],
321
+ accessibilityNotes: "Always pair with a Label using htmlFor/id. Consider aria-describedby for helper text or error messages.",
322
+ tokenBudget: 300
323
+ },
324
+ tags: ["input", "text", "form", "field", "text-field"]
325
+ };
326
+
327
+ // src/primitives/label/label.schema.ts
328
+ var labelSchema = {
329
+ name: "label",
330
+ displayName: "Label",
331
+ description: "An accessible label component built on Radix UI Label primitive. Associates with form controls via htmlFor.",
332
+ category: "primitive",
333
+ subcategory: "forms",
334
+ props: [
335
+ {
336
+ name: "htmlFor",
337
+ type: "string",
338
+ required: false,
339
+ description: "The id of the form control this label is associated with"
340
+ }
341
+ ],
342
+ variants: [],
343
+ slots: [
344
+ {
345
+ name: "children",
346
+ description: "Label text content",
347
+ required: true,
348
+ acceptedTypes: ["ReactNode"]
349
+ }
350
+ ],
351
+ dependencies: {
352
+ npm: ["@radix-ui/react-label", "class-variance-authority", "clsx", "tailwind-merge"],
353
+ internal: [],
354
+ peer: ["react", "react-dom"]
355
+ },
356
+ tokensUsed: [],
357
+ examples: [
358
+ {
359
+ title: "Basic usage",
360
+ description: "A label paired with an input",
361
+ code: '<div className="grid gap-1.5">\n <Label htmlFor="name">Name</Label>\n <Input id="name" placeholder="Enter your name" />\n</div>'
362
+ },
363
+ {
364
+ title: "Required field",
365
+ description: "Label with required indicator",
366
+ code: '<Label htmlFor="email">\n Email <span className="text-destructive">*</span>\n</Label>'
367
+ }
368
+ ],
369
+ ai: {
370
+ whenToUse: "Use as a label for every form input, select, textarea, checkbox, or radio group. Always use htmlFor to associate with the control's id.",
371
+ whenNotToUse: "Don't use as a standalone text element \u2014 use a paragraph or heading instead. Don't use for non-form contexts.",
372
+ commonMistakes: [
373
+ "Forgetting to set htmlFor matching the input's id",
374
+ "Using Label for non-form text content",
375
+ "Nesting interactive elements inside Label"
376
+ ],
377
+ relatedComponents: ["input", "textarea", "checkbox", "select", "form"],
378
+ accessibilityNotes: "Clicking the label focuses the associated control. Automatically communicates the label to screen readers. Use htmlFor/id pairing, not nesting.",
379
+ tokenBudget: 200
380
+ },
381
+ tags: ["label", "form", "accessibility", "text"]
382
+ };
383
+
384
+ // src/primitives/textarea/textarea.schema.ts
385
+ var textareaSchema = {
386
+ name: "textarea",
387
+ displayName: "Textarea",
388
+ description: "A styled multi-line text input with smooth focus transitions and shadow effects.",
389
+ category: "primitive",
390
+ subcategory: "forms",
391
+ props: [
392
+ { name: "placeholder", type: "string", required: false, description: "Placeholder text" },
393
+ { name: "rows", type: "number", required: false, default: 3, description: "Number of visible text rows" },
394
+ { name: "disabled", type: "boolean", required: false, default: false, description: "Disable the textarea" },
395
+ { name: "value", type: "string", required: false, description: "Controlled textarea value" },
396
+ { name: "defaultValue", type: "string", required: false, description: "Default value for uncontrolled usage" },
397
+ { name: "onChange", type: "function", required: false, description: "Change handler: (e: ChangeEvent<HTMLTextAreaElement>) => void" },
398
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
399
+ ],
400
+ variants: [],
401
+ slots: [],
402
+ dependencies: {
403
+ npm: ["clsx", "tailwind-merge"],
404
+ internal: [],
405
+ peer: ["react", "react-dom"]
406
+ },
407
+ tokensUsed: ["input", "background", "ring", "muted-foreground"],
408
+ examples: [
409
+ { title: "Basic", description: "Simple textarea", code: '<Textarea placeholder="Type your message..." />' },
410
+ { title: "With label", description: "Textarea paired with a label", code: '<div className="grid gap-1.5">\n <Label htmlFor="message">Message</Label>\n <Textarea id="message" placeholder="Type your message..." />\n</div>' }
411
+ ],
412
+ ai: {
413
+ whenToUse: "Use for multi-line text input: comments, descriptions, messages, notes. Always pair with a Label.",
414
+ whenNotToUse: "Don't use for single-line input (use Input). Don't use for rich text editing.",
415
+ commonMistakes: ["Missing associated Label", "Not setting a reasonable min-height or rows"],
416
+ relatedComponents: ["input", "label", "form"],
417
+ accessibilityNotes: "Always pair with a Label using htmlFor/id. Consider aria-describedby for character limits.",
418
+ tokenBudget: 250
419
+ },
420
+ tags: ["textarea", "text", "form", "multiline", "input"]
421
+ };
422
+
423
+ // src/primitives/checkbox/checkbox.schema.ts
424
+ var checkboxSchema = {
425
+ name: "checkbox",
426
+ displayName: "Checkbox",
427
+ description: "An accessible checkbox with checked, unchecked, and indeterminate states. Built on Radix UI.",
428
+ category: "primitive",
429
+ subcategory: "forms",
430
+ props: [
431
+ { name: "checked", type: "boolean", required: false, description: "Controlled checked state" },
432
+ { name: "defaultChecked", type: "boolean", required: false, description: "Default checked for uncontrolled" },
433
+ { name: "onCheckedChange", type: "function", required: false, description: "Callback: (checked: boolean | 'indeterminate') => void" },
434
+ { name: "disabled", type: "boolean", required: false, default: false, description: "Disable the checkbox" },
435
+ { name: "required", type: "boolean", required: false, default: false, description: "Mark as required for form validation" },
436
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
437
+ ],
438
+ variants: [],
439
+ slots: [],
440
+ dependencies: {
441
+ npm: ["@radix-ui/react-checkbox", "clsx", "tailwind-merge"],
442
+ internal: [],
443
+ peer: ["react", "react-dom"]
444
+ },
445
+ tokensUsed: ["input", "primary", "primary-foreground", "ring"],
446
+ examples: [
447
+ { title: "Basic", description: "Checkbox with label", code: '<div className="flex items-center gap-2">\n <Checkbox id="terms" />\n <Label htmlFor="terms">Accept terms</Label>\n</div>' },
448
+ { title: "Controlled", description: "Controlled checkbox", code: "const [checked, setChecked] = useState(false);\n<Checkbox checked={checked} onCheckedChange={setChecked} />" },
449
+ {
450
+ title: "Indeterminate (parent/children)",
451
+ description: "Parent renders a dash when some (but not all) children are selected",
452
+ code: 'const [items, setItems] = useState({ email: true, push: false });\nconst count = Object.values(items).filter(Boolean).length;\nconst parent = count === 0 ? false : count === 2 ? true : "indeterminate";\n\n<Checkbox\n checked={parent}\n onCheckedChange={(v) => setItems({ email: v === true, push: v === true })}\n/>'
453
+ }
454
+ ],
455
+ ai: {
456
+ whenToUse: "Use for boolean toggles in forms (agree to terms, enable options), multi-select lists, or parent/children trees where the parent reflects partial selection via the `indeterminate` state.",
457
+ whenNotToUse: "Don't use for mutually exclusive options (use RadioGroup). Don't use for instant toggles (use Switch).",
458
+ commonMistakes: [
459
+ "Missing Label pairing",
460
+ "Using onChange instead of onCheckedChange",
461
+ "Forgetting that onCheckedChange can receive 'indeterminate' as well as boolean"
462
+ ],
463
+ relatedComponents: ["label", "switch", "form"],
464
+ accessibilityNotes: "Always pair with Label via htmlFor/id. Radix handles aria-checked automatically.",
465
+ tokenBudget: 300
466
+ },
467
+ tags: ["checkbox", "form", "toggle", "boolean", "check"]
468
+ };
469
+
470
+ // src/primitives/switch/switch.schema.ts
471
+ var switchSchema = {
472
+ name: "switch",
473
+ displayName: "Switch",
474
+ description: "An accessible toggle switch for instant on/off settings. Built on Radix UI.",
475
+ category: "primitive",
476
+ subcategory: "forms",
477
+ props: [
478
+ { name: "checked", type: "boolean", required: false, description: "Controlled checked state" },
479
+ { name: "defaultChecked", type: "boolean", required: false, description: "Default for uncontrolled" },
480
+ { name: "onCheckedChange", type: "function", required: false, description: "Callback: (checked: boolean) => void" },
481
+ { name: "disabled", type: "boolean", required: false, default: false, description: "Disable the switch" },
482
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
483
+ ],
484
+ variants: [],
485
+ slots: [],
486
+ dependencies: {
487
+ npm: ["@radix-ui/react-switch", "clsx", "tailwind-merge"],
488
+ internal: [],
489
+ peer: ["react", "react-dom"]
490
+ },
491
+ tokensUsed: ["primary", "input", "background", "ring"],
492
+ examples: [
493
+ {
494
+ title: "Basic",
495
+ description: "Switch with label",
496
+ code: '<div className="flex items-center gap-2">\n <Switch id="airplane" />\n <Label htmlFor="airplane">Airplane Mode</Label>\n</div>',
497
+ composition: ["form", "boolean"]
498
+ },
499
+ {
500
+ title: "Settings row",
501
+ description: "Right-aligned switch in a settings list \u2014 the canonical Apple-style preference UI",
502
+ code: '<div className="flex items-center justify-between py-3">\n <div>\n <Label htmlFor="notifications">Email notifications</Label>\n <p className="text-sm text-muted-foreground">Daily digest, mentions, and replies</p>\n </div>\n <Switch id="notifications" />\n</div>',
503
+ composition: ["settings", "preference-row", "boolean"]
504
+ }
505
+ ],
506
+ ai: {
507
+ whenToUse: "Use for instant-effect boolean settings: dark mode, notifications on/off, feature toggles, airplane mode. The change takes effect immediately \u2014 no submit step.",
508
+ whenNotToUse: "Don't use for form fields that get submitted in a batch (use Checkbox). Don't use for mutually exclusive options (use RadioGroup). Don't use to filter a list of options (use Toggle or ToggleGroup).",
509
+ commonMistakes: [
510
+ "Using Switch for form fields that need explicit submit",
511
+ "Missing <Label> for the switch \u2014 without htmlFor/id pairing, screen readers announce the toggle without context",
512
+ "Putting the switch label INSIDE the switch instead of next to it"
513
+ ],
514
+ antiPatterns: [
515
+ {
516
+ mistake: "Using Switch inside a form that gets submitted with a Save button",
517
+ insteadUse: "checkbox",
518
+ why: "Switch implies the change applies immediately. A Save button suggests batch submission \u2014 that's Checkbox semantics. Mixing them confuses the user about whether they need to click Save."
519
+ },
520
+ {
521
+ mistake: "Putting two Switches side-by-side to choose between mutually exclusive options",
522
+ insteadUse: "radio-group",
523
+ why: "Switches don't represent 'pick one of N'. RadioGroup announces the group + the active selection to assistive tech and prevents the user from selecting both."
524
+ }
525
+ ],
526
+ relatedComponents: ["checkbox", "label", "form", "toggle"],
527
+ accessibilityNotes: "Always pair with Label (htmlFor / id). Radix handles role='switch' + aria-checked + keyboard activation (Space).",
528
+ tokenBudget: 350
529
+ },
530
+ tags: ["switch", "toggle", "form", "boolean", "setting"]
531
+ };
532
+
533
+ // src/primitives/badge/badge.schema.ts
534
+ var badgeSchema = {
535
+ name: "badge",
536
+ displayName: "Badge",
537
+ description: "A small status indicator with multiple style variants. Used for tags, statuses, and categorization.",
538
+ category: "primitive",
539
+ subcategory: "display",
540
+ props: [
541
+ {
542
+ name: "variant",
543
+ type: "enum",
544
+ required: false,
545
+ default: "default",
546
+ description: "Visual style",
547
+ enumValues: ["default", "secondary", "destructive", "outline"]
548
+ },
549
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
550
+ ],
551
+ variants: [
552
+ {
553
+ name: "variant",
554
+ description: "Visual style variants",
555
+ values: [
556
+ { value: "default", description: "Primary colored badge" },
557
+ { value: "secondary", description: "Muted background badge" },
558
+ { value: "destructive", description: "Red/danger badge" },
559
+ { value: "outline", description: "Bordered badge, no fill" }
560
+ ],
561
+ default: "default"
562
+ }
563
+ ],
564
+ slots: [{ name: "children", description: "Badge content text", required: true, acceptedTypes: ["ReactNode"] }],
565
+ dependencies: {
566
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
567
+ internal: [],
568
+ peer: ["react", "react-dom"]
569
+ },
570
+ tokensUsed: ["primary", "primary-foreground", "secondary", "secondary-foreground", "destructive", "destructive-foreground", "foreground", "ring"],
571
+ examples: [
572
+ { title: "Variants", description: "All badge styles", code: '<>\n <Badge>Default</Badge>\n <Badge variant="secondary">Secondary</Badge>\n <Badge variant="destructive">Error</Badge>\n <Badge variant="outline">Outline</Badge>\n</>' }
573
+ ],
574
+ ai: {
575
+ whenToUse: "Use for status indicators, tags, counts, categories. Place next to headings, in lists, or in table cells.",
576
+ whenNotToUse: "Don't use for interactive actions (use Button). Don't use for long text content.",
577
+ commonMistakes: ["Using destructive variant for non-error states", "Badge text too long"],
578
+ relatedComponents: ["button", "card"],
579
+ accessibilityNotes: "Purely decorative by default. Add role='status' for dynamic status badges.",
580
+ tokenBudget: 200
581
+ },
582
+ tags: ["badge", "tag", "status", "label", "indicator"]
583
+ };
584
+
585
+ // src/primitives/separator/separator.schema.ts
586
+ var separatorSchema = {
587
+ name: "separator",
588
+ displayName: "Separator",
589
+ description: "A visual divider between content sections with horizontal or vertical orientation.",
590
+ category: "primitive",
591
+ subcategory: "layout",
592
+ props: [
593
+ { name: "orientation", type: "enum", required: false, default: "horizontal", description: "Direction of the separator", enumValues: ["horizontal", "vertical"] },
594
+ { name: "decorative", type: "boolean", required: false, default: true, description: "If true, separator is purely visual and hidden from screen readers" },
595
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
596
+ ],
597
+ variants: [],
598
+ slots: [],
599
+ dependencies: {
600
+ npm: ["@radix-ui/react-separator", "clsx", "tailwind-merge"],
601
+ internal: [],
602
+ peer: ["react", "react-dom"]
603
+ },
604
+ tokensUsed: ["border"],
605
+ examples: [
606
+ { title: "Horizontal", description: "Default horizontal divider", code: "<Separator />" },
607
+ { title: "Vertical", description: "Vertical divider in a flex row", code: '<div className="flex h-5 items-center gap-4">\n <span>Left</span>\n <Separator orientation="vertical" />\n <span>Right</span>\n</div>' }
608
+ ],
609
+ ai: {
610
+ whenToUse: "Use to visually separate content sections, menu items, or sidebar groups.",
611
+ whenNotToUse: "Don't use for spacing (use margin/padding). Don't use between every list item.",
612
+ commonMistakes: ["Using as spacing instead of semantic separation", "Forgetting orientation='vertical' needs parent height"],
613
+ relatedComponents: ["card", "breadcrumb", "dropdown-menu", "menubar"],
614
+ accessibilityNotes: "Set decorative=false if the separator conveys semantic meaning. Radix handles role='separator'.",
615
+ tokenBudget: 150
616
+ },
617
+ tags: ["separator", "divider", "hr", "layout"]
618
+ };
619
+
620
+ // src/components/card/card.schema.ts
621
+ var cardSchema = {
622
+ name: "card",
623
+ displayName: "Card",
624
+ description: "A container component with header, content, and footer sections. Includes subtle shadow and hover effects.",
625
+ category: "component",
626
+ subcategory: "layout",
627
+ props: [
628
+ { name: "className", type: "string", required: false, description: "Additional CSS classes on the root card" }
629
+ ],
630
+ variants: [],
631
+ slots: [
632
+ { name: "children", description: "Card content \u2014 use CardHeader, CardContent, CardFooter subcomponents", required: true, acceptedTypes: ["ReactNode"] }
633
+ ],
634
+ dependencies: {
635
+ npm: ["clsx", "tailwind-merge"],
636
+ internal: [],
637
+ peer: ["react", "react-dom"]
638
+ },
639
+ tokensUsed: ["card", "card-foreground", "border", "muted-foreground"],
640
+ examples: [
641
+ {
642
+ title: "Complete card",
643
+ description: "Card with header, content, and footer",
644
+ code: "<Card>\n <CardHeader>\n <CardTitle>Create project</CardTitle>\n <CardDescription>Deploy your new project in one-click.</CardDescription>\n </CardHeader>\n <CardContent>\n <p>Card content here</p>\n </CardContent>\n <CardFooter>\n <Button>Deploy</Button>\n </CardFooter>\n</Card>",
645
+ composition: ["showcase", "settings"]
646
+ },
647
+ {
648
+ title: "Stat card",
649
+ description: "Single-metric dashboard card \u2014 title + large number + trend",
650
+ code: '<Card>\n <CardHeader className="pb-2">\n <CardDescription>Total revenue</CardDescription>\n <CardTitle className="text-3xl tabular-nums">$45,231.89</CardTitle>\n </CardHeader>\n <CardContent>\n <p className="text-xs text-muted-foreground">+20.1% from last month</p>\n </CardContent>\n</Card>',
651
+ composition: ["dashboard", "stat", "metric"]
652
+ },
653
+ {
654
+ title: "Form section card",
655
+ description: "Card framing a form section, with submit pinned to the footer",
656
+ code: '<Card>\n <CardHeader>\n <CardTitle>Profile</CardTitle>\n <CardDescription>Update your account details.</CardDescription>\n </CardHeader>\n <CardContent className="space-y-4">\n <Input placeholder="Display name" />\n <Input type="email" placeholder="Email" />\n </CardContent>\n <CardFooter className="justify-end gap-2">\n <Button variant="secondary">Cancel</Button>\n <Button type="submit">Save</Button>\n </CardFooter>\n</Card>',
657
+ composition: ["form", "settings", "form-action"]
658
+ }
659
+ ],
660
+ ai: {
661
+ whenToUse: "Use to group related content with a visual boundary: settings panels, product listings, dashboard widgets, form sections, content previews. Each card should feel like one self-contained unit.",
662
+ whenNotToUse: "Don't use for full-page layouts (use plain divs). Don't use a card to hold one tiny thing (use a Badge or inline content). Don't use a card as a button \u2014 use Button with asChild.",
663
+ commonMistakes: [
664
+ "Nesting cards",
665
+ "Using Card when a simple div with border would suffice",
666
+ "Forgetting CardContent padding when placing forms inside"
667
+ ],
668
+ antiPatterns: [
669
+ {
670
+ mistake: "Nesting <Card> inside <Card> to group sub-sections",
671
+ insteadUse: "separator",
672
+ why: "Two raised surfaces inside each other muddles the elevation model \u2014 readers can't tell which container 'owns' an action. Use Separator + extra padding within a single Card to visually divide sections, or split into sibling Cards."
673
+ },
674
+ {
675
+ mistake: "Putting an entire Card inside a <button> or <a> to make it clickable",
676
+ insteadUse: "button",
677
+ why: "Browsers don't fully support nested interactive content; screen readers announce the full card text every focus. Use <Card asChild><a>...</a></Card> patterns or move just the action inside CardFooter."
678
+ },
679
+ {
680
+ mistake: "Using Card for a top-bar / app-shell wrapper",
681
+ insteadUse: "container",
682
+ why: "Card adds elevation chrome that fights with the chrome of the app shell. Use Container for layout boundaries and reserve Card for the content INSIDE the layout."
683
+ }
684
+ ],
685
+ relatedComponents: ["button", "separator", "container", "stack"],
686
+ accessibilityNotes: "Card is a div by default. Add role='region' and aria-labelledby (pointing to CardTitle's id) for landmark cards. CardTitle renders h3 \u2014 ensure heading hierarchy is correct on the page.",
687
+ tokenBudget: 400
688
+ },
689
+ tags: ["card", "container", "panel", "layout", "surface"]
690
+ };
691
+
692
+ // src/components/tabs/tabs.schema.ts
693
+ var tabsSchema = {
694
+ name: "tabs",
695
+ displayName: "Tabs",
696
+ description: "A tabbed interface with accessible keyboard navigation. Built on Radix UI Tabs.",
697
+ category: "component",
698
+ subcategory: "navigation",
699
+ props: [
700
+ { name: "defaultValue", type: "string", required: false, description: "Default active tab value (uncontrolled)" },
701
+ { name: "value", type: "string", required: false, description: "Controlled active tab value" },
702
+ { name: "onValueChange", type: "function", required: false, description: "Callback: (value: string) => void" },
703
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
704
+ ],
705
+ variants: [],
706
+ slots: [
707
+ { name: "children", description: "TabsList + TabsContent elements", required: true, acceptedTypes: ["ReactNode"] }
708
+ ],
709
+ dependencies: {
710
+ npm: ["@radix-ui/react-tabs", "clsx", "tailwind-merge"],
711
+ internal: [],
712
+ peer: ["react", "react-dom"]
713
+ },
714
+ tokensUsed: ["muted", "muted-foreground", "background", "foreground", "ring"],
715
+ examples: [
716
+ {
717
+ title: "Basic tabs",
718
+ description: "Two-tab interface",
719
+ code: '<Tabs defaultValue="account">\n <TabsList>\n <TabsTrigger value="account">Account</TabsTrigger>\n <TabsTrigger value="password">Password</TabsTrigger>\n </TabsList>\n <TabsContent value="account">Account settings here.</TabsContent>\n <TabsContent value="password">Password settings here.</TabsContent>\n</Tabs>'
720
+ }
721
+ ],
722
+ ai: {
723
+ whenToUse: "Use to organize content into switchable panels: settings pages, dashboards, product details with multiple sections.",
724
+ whenNotToUse: "Don't use for navigation between pages (use router/links). Don't use for steppers/wizards (use a stepper component).",
725
+ commonMistakes: ["Missing defaultValue causing no tab selected initially", "TabsTrigger value not matching TabsContent value", "Using for page navigation instead of in-page content switching"],
726
+ relatedComponents: ["card", "separator"],
727
+ accessibilityNotes: "Full keyboard navigation built-in (arrow keys, Home, End). Radix handles aria-selected, role='tabpanel', etc.",
728
+ tokenBudget: 350
729
+ },
730
+ tags: ["tabs", "navigation", "panel", "tabbed", "sections"]
731
+ };
732
+
733
+ // src/components/accordion/accordion.schema.ts
734
+ var accordionSchema = {
735
+ name: "accordion",
736
+ displayName: "Accordion",
737
+ description: "A vertically stacked set of collapsible content sections. Built on Radix UI Accordion.",
738
+ category: "component",
739
+ subcategory: "disclosure",
740
+ props: [
741
+ { name: "type", type: "enum", required: true, description: "Single allows one item open at a time, multiple allows many", enumValues: ["single", "multiple"] },
742
+ { name: "defaultValue", type: "string", required: false, description: "Default open item(s): string for type='single', string[] for type='multiple'" },
743
+ { name: "value", type: "string", required: false, description: "Controlled open item(s): string for type='single', string[] for type='multiple'" },
744
+ { name: "onValueChange", type: "function", required: false, description: "Callback when open items change: (value: string) => void for single, (value: string[]) => void for multiple" },
745
+ { name: "collapsible", type: "boolean", required: false, default: false, description: "Allow all items to be closed (type='single' only)" },
746
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
747
+ ],
748
+ variants: [],
749
+ slots: [
750
+ { name: "children", description: "AccordionItem elements", required: true, acceptedTypes: ["ReactNode"] }
751
+ ],
752
+ dependencies: {
753
+ npm: ["@radix-ui/react-accordion", "clsx", "tailwind-merge"],
754
+ internal: [],
755
+ peer: ["react", "react-dom"]
756
+ },
757
+ tokensUsed: ["border"],
758
+ examples: [
759
+ {
760
+ title: "FAQ accordion",
761
+ description: "Single-open accordion for FAQ sections",
762
+ code: '<Accordion type="single" collapsible>\n <AccordionItem value="item-1">\n <AccordionTrigger>Is it accessible?</AccordionTrigger>\n <AccordionContent>Yes, it adheres to the WAI-ARIA design pattern.</AccordionContent>\n </AccordionItem>\n <AccordionItem value="item-2">\n <AccordionTrigger>Is it styled?</AccordionTrigger>\n <AccordionContent>Yes, with Tailwind CSS and smooth animations.</AccordionContent>\n </AccordionItem>\n</Accordion>'
763
+ }
764
+ ],
765
+ ai: {
766
+ whenToUse: "Use for FAQ sections, settings groups, or any content that benefits from progressive disclosure. Use type='single' for FAQs, type='multiple' for settings.",
767
+ whenNotToUse: "Don't use for navigation (use tabs). Don't use for a single collapsible (use Collapsible).",
768
+ commonMistakes: ["Forgetting type prop (it's required)", "Not setting collapsible=true for single type when all items should be closeable", "Missing value on AccordionItem"],
769
+ relatedComponents: ["tabs", "card"],
770
+ accessibilityNotes: "Full keyboard navigation (arrow keys, Home, End, Enter/Space). Radix handles aria-expanded, aria-controls, role='region'.",
771
+ tokenBudget: 400
772
+ },
773
+ tags: ["accordion", "collapsible", "faq", "disclosure", "expandable"]
774
+ };
775
+
776
+ // src/components/dialog/dialog.schema.ts
777
+ var dialogSchema = {
778
+ name: "dialog",
779
+ displayName: "Dialog",
780
+ description: "A modal dialog that interrupts the user with important content. Built on Radix UI with focus trap, escape handling, and scroll lock.",
781
+ category: "component",
782
+ subcategory: "overlay",
783
+ props: [
784
+ {
785
+ name: "open",
786
+ type: "boolean",
787
+ required: false,
788
+ description: "Controlled open state"
789
+ },
790
+ {
791
+ name: "defaultOpen",
792
+ type: "boolean",
793
+ required: false,
794
+ default: false,
795
+ description: "Default open state for uncontrolled usage"
796
+ },
797
+ {
798
+ name: "onOpenChange",
799
+ type: "function",
800
+ required: false,
801
+ description: "Callback fired when open state changes: (open: boolean) => void"
802
+ },
803
+ {
804
+ name: "modal",
805
+ type: "boolean",
806
+ required: false,
807
+ default: true,
808
+ description: "When true, content outside the dialog is inert"
809
+ }
810
+ ],
811
+ variants: [],
812
+ slots: [
813
+ {
814
+ name: "children",
815
+ description: "DialogTrigger + DialogContent (with DialogHeader, DialogFooter, etc.)",
816
+ required: true,
817
+ acceptedTypes: ["ReactNode"]
818
+ }
819
+ ],
820
+ dependencies: {
821
+ npm: ["@radix-ui/react-dialog", "clsx", "tailwind-merge"],
822
+ internal: [],
823
+ peer: ["react", "react-dom"]
824
+ },
825
+ tokensUsed: ["background", "foreground", "muted-foreground", "border", "ring"],
826
+ examples: [
827
+ {
828
+ title: "Edit profile",
829
+ description: "Dialog wrapping a short form \u2014 the canonical 'edit detail in place' pattern",
830
+ code: '<Dialog>\n <DialogTrigger asChild>\n <Button variant="outline">Edit Profile</Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Edit profile</DialogTitle>\n <DialogDescription>Make changes to your profile here.</DialogDescription>\n </DialogHeader>\n <div className="grid gap-4 py-4">\n <Input placeholder="Name" />\n </div>\n <DialogFooter>\n <Button>Save changes</Button>\n </DialogFooter>\n </DialogContent>\n</Dialog>',
831
+ composition: ["form", "edit-in-place"]
832
+ },
833
+ {
834
+ title: "Confirm dialog",
835
+ description: "Non-destructive confirmation with primary + secondary actions",
836
+ code: '<Dialog>\n <DialogTrigger asChild>\n <Button>Publish post</Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Publish post?</DialogTitle>\n <DialogDescription>Your followers will be notified by email.</DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <DialogClose asChild>\n <Button variant="secondary">Cancel</Button>\n </DialogClose>\n <Button>Publish</Button>\n </DialogFooter>\n </DialogContent>\n</Dialog>',
837
+ composition: ["confirm", "form-action"]
838
+ }
839
+ ],
840
+ ai: {
841
+ whenToUse: "Use for focused, interruptive tasks: short forms, confirmations, detail views. The user must address the dialog before continuing.",
842
+ whenNotToUse: "Don't use for destructive confirmations (use AlertDialog). Don't use for complex multi-step flows (use a full page). Don't use for non-critical info (use Tooltip or Popover). Don't use for mobile-bottom-sheet patterns (use Drawer).",
843
+ commonMistakes: [
844
+ "Nesting dialogs inside each other",
845
+ "Forgetting DialogTitle (breaks accessibility \u2014 screen readers need it)",
846
+ "Using DialogDescription for long-form content (keep it short)",
847
+ "Putting too many primary actions in DialogFooter"
848
+ ],
849
+ antiPatterns: [
850
+ {
851
+ mistake: "Using <Dialog> for irreversible actions like Delete account",
852
+ insteadUse: "alert-dialog",
853
+ why: "AlertDialog uses role='alertdialog' which forces the user to interact (cannot click-outside to dismiss) and announces destructively to screen readers. Dialog can be dismissed by clicking the overlay \u2014 the wrong default for delete confirmations."
854
+ },
855
+ {
856
+ mistake: "Using <Dialog> for a long multi-step wizard",
857
+ insteadUse: "stepper",
858
+ why: "Modals trap focus and disable scroll on the page below \u2014 that fights long content. Multi-step flows belong on a full page with a Stepper for orientation."
859
+ },
860
+ {
861
+ mistake: "Using <Dialog> for the mobile-only 'pull up from bottom' interaction",
862
+ insteadUse: "drawer",
863
+ why: "Drawer is purpose-built for the bottom-sheet gesture (swipe handle, drag-to-dismiss). Dialog will technically work but loses the native-feeling affordance."
864
+ },
865
+ {
866
+ mistake: "Putting two equal-weight primary buttons in DialogFooter ('Save and continue' + 'Save and close')",
867
+ insteadUse: "button",
868
+ why: "Two primaries dilute the call to action \u2014 users hesitate. Pick one primary; demote the other to secondary or split them via a DropdownMenu attached to a single Button."
869
+ }
870
+ ],
871
+ relatedComponents: ["alert-dialog", "popover", "sheet", "drawer"],
872
+ accessibilityNotes: "Radix traps focus, handles Escape to close, and wires aria-labelledby/describedby to DialogTitle/DialogDescription. Always include a DialogTitle. DialogContent is constrained to `max-h-[calc(100vh-2rem)]` and scrolls internally so long content stays inside the focus trap.",
873
+ tokenBudget: 600
874
+ },
875
+ tags: ["dialog", "modal", "overlay", "popup", "form"]
876
+ };
877
+
878
+ // src/components/alert-dialog/alert-dialog.schema.ts
879
+ var alertDialogSchema = {
880
+ name: "alert-dialog",
881
+ displayName: "Alert Dialog",
882
+ description: "A modal dialog for destructive confirmations. The user must explicitly accept or cancel \u2014 there is no close button. Built on Radix UI AlertDialog.",
883
+ category: "component",
884
+ subcategory: "overlay",
885
+ props: [
886
+ { name: "open", type: "boolean", required: false, description: "Controlled open state" },
887
+ {
888
+ name: "defaultOpen",
889
+ type: "boolean",
890
+ required: false,
891
+ default: false,
892
+ description: "Default open state for uncontrolled usage"
893
+ },
894
+ {
895
+ name: "onOpenChange",
896
+ type: "function",
897
+ required: false,
898
+ description: "Callback fired on open state change: (open: boolean) => void"
899
+ }
900
+ ],
901
+ variants: [],
902
+ slots: [
903
+ {
904
+ name: "children",
905
+ description: "AlertDialogTrigger + AlertDialogContent (with Header, Footer, Action, Cancel)",
906
+ required: true,
907
+ acceptedTypes: ["ReactNode"]
908
+ }
909
+ ],
910
+ dependencies: {
911
+ npm: ["@radix-ui/react-alert-dialog", "clsx", "tailwind-merge"],
912
+ internal: [],
913
+ peer: ["react", "react-dom"]
914
+ },
915
+ tokensUsed: [
916
+ "background",
917
+ "foreground",
918
+ "muted-foreground",
919
+ "destructive",
920
+ "destructive-foreground",
921
+ "accent",
922
+ "accent-foreground",
923
+ "border",
924
+ "input",
925
+ "ring"
926
+ ],
927
+ examples: [
928
+ {
929
+ title: "Destructive confirmation",
930
+ description: "Confirm before deleting a resource",
931
+ code: '<AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant="destructive">Delete account</Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>\n <AlertDialogDescription>\n This action cannot be undone. This will permanently delete your account.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction>Yes, delete</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n</AlertDialog>'
932
+ }
933
+ ],
934
+ ai: {
935
+ whenToUse: "Use for destructive or irreversible confirmations: delete account, discard changes, permanent actions. The user must explicitly choose Action or Cancel.",
936
+ whenNotToUse: "Don't use for non-destructive dialogs (use Dialog). Don't use for simple notifications (use Toast). Don't use when there's only one action to take.",
937
+ commonMistakes: [
938
+ "Using Dialog when AlertDialog is semantically required",
939
+ "Omitting AlertDialogCancel (user must have an escape hatch)",
940
+ "Putting more than one AlertDialogAction (the pattern expects one destructive action)",
941
+ "Making the action button non-destructive styled"
942
+ ],
943
+ relatedComponents: ["dialog", "toast"],
944
+ accessibilityNotes: "Radix sets role='alertdialog', traps focus, focuses AlertDialogCancel by default, and closes on Escape. Clicks outside the dialog are prevented (user must choose Cancel or Action).",
945
+ tokenBudget: 650
946
+ },
947
+ tags: ["alert-dialog", "confirm", "destructive", "modal", "overlay"]
948
+ };
949
+
950
+ // src/components/dropdown-menu/dropdown-menu.schema.ts
951
+ var dropdownMenuSchema = {
952
+ name: "dropdown-menu",
953
+ displayName: "Dropdown Menu",
954
+ description: "A menu of actions displayed to the user when a trigger is activated. Supports items, checkboxes, radio groups, sub-menus, and keyboard shortcuts.",
955
+ category: "component",
956
+ subcategory: "overlay",
957
+ props: [
958
+ { name: "open", type: "boolean", required: false, description: "Controlled open state" },
959
+ {
960
+ name: "defaultOpen",
961
+ type: "boolean",
962
+ required: false,
963
+ default: false,
964
+ description: "Default open state"
965
+ },
966
+ {
967
+ name: "onOpenChange",
968
+ type: "function",
969
+ required: false,
970
+ description: "Callback on open state change: (open: boolean) => void"
971
+ },
972
+ {
973
+ name: "modal",
974
+ type: "boolean",
975
+ required: false,
976
+ default: true,
977
+ description: "When true, interaction outside the menu is blocked"
978
+ }
979
+ ],
980
+ variants: [],
981
+ slots: [
982
+ {
983
+ name: "children",
984
+ description: "DropdownMenuTrigger + DropdownMenuContent (with Items, CheckboxItems, etc.)",
985
+ required: true,
986
+ acceptedTypes: ["ReactNode"]
987
+ }
988
+ ],
989
+ dependencies: {
990
+ npm: ["@radix-ui/react-dropdown-menu", "clsx", "tailwind-merge"],
991
+ internal: [],
992
+ peer: ["react", "react-dom"]
993
+ },
994
+ tokensUsed: ["popover", "popover-foreground", "accent", "accent-foreground", "muted", "muted-foreground", "border"],
995
+ examples: [
996
+ {
997
+ title: "Basic dropdown",
998
+ description: "Standard action menu",
999
+ code: '<DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant="outline">Open Menu</Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuLabel>My Account</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem>Profile</DropdownMenuItem>\n <DropdownMenuItem>Settings</DropdownMenuItem>\n <DropdownMenuItem>\n Log out\n <DropdownMenuShortcut>\u2318Q</DropdownMenuShortcut>\n </DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>'
1000
+ }
1001
+ ],
1002
+ ai: {
1003
+ whenToUse: "Use for action menus triggered by a button: user menus, row-action menus, toolbar overflow. Include DropdownMenuLabel for context, DropdownMenuSeparator for grouping.",
1004
+ whenNotToUse: "Don't use for navigation between pages (use NavigationMenu or links). Don't use for selection inputs (use Select or Combobox). Don't use for right-click menus (use ContextMenu).",
1005
+ commonMistakes: [
1006
+ "Using DropdownMenu as a form Select (use Select instead)",
1007
+ "Putting interactive elements directly in the trigger without asChild",
1008
+ "Too many items without grouping (use DropdownMenuLabel + DropdownMenuSeparator)",
1009
+ "Forgetting DropdownMenuShortcut for keyboard-accessible actions"
1010
+ ],
1011
+ relatedComponents: ["select", "context-menu", "popover"],
1012
+ accessibilityNotes: "Full keyboard navigation: arrow keys, Home, End, typeahead, Escape. Radix handles role='menu', role='menuitem', and aria-labelledby.",
1013
+ tokenBudget: 700
1014
+ },
1015
+ tags: ["dropdown", "menu", "actions", "overflow", "contextual"]
1016
+ };
1017
+
1018
+ // src/components/popover/popover.schema.ts
1019
+ var popoverSchema = {
1020
+ name: "popover",
1021
+ displayName: "Popover",
1022
+ description: "Floating content anchored to a trigger element. Non-modal by default \u2014 clicks outside dismiss it. Use for inline forms, info, or quick actions.",
1023
+ category: "component",
1024
+ subcategory: "overlay",
1025
+ props: [
1026
+ { name: "open", type: "boolean", required: false, description: "Controlled open state" },
1027
+ {
1028
+ name: "defaultOpen",
1029
+ type: "boolean",
1030
+ required: false,
1031
+ default: false,
1032
+ description: "Default open state"
1033
+ },
1034
+ {
1035
+ name: "onOpenChange",
1036
+ type: "function",
1037
+ required: false,
1038
+ description: "Callback on open state change: (open: boolean) => void"
1039
+ },
1040
+ {
1041
+ name: "modal",
1042
+ type: "boolean",
1043
+ required: false,
1044
+ default: false,
1045
+ description: "When true, content outside the popover is inert"
1046
+ }
1047
+ ],
1048
+ variants: [],
1049
+ slots: [
1050
+ {
1051
+ name: "children",
1052
+ description: "PopoverTrigger + PopoverContent",
1053
+ required: true,
1054
+ acceptedTypes: ["ReactNode"]
1055
+ }
1056
+ ],
1057
+ dependencies: {
1058
+ npm: ["@radix-ui/react-popover", "clsx", "tailwind-merge"],
1059
+ internal: [],
1060
+ peer: ["react", "react-dom"]
1061
+ },
1062
+ tokensUsed: ["popover", "popover-foreground", "border"],
1063
+ examples: [
1064
+ {
1065
+ title: "Basic popover",
1066
+ description: "Quick settings anchored to a button",
1067
+ code: '<Popover>\n <PopoverTrigger asChild>\n <Button variant="outline">Open</Button>\n </PopoverTrigger>\n <PopoverContent>\n <div className="space-y-2">\n <h4 className="font-medium">Dimensions</h4>\n <p className="text-sm text-muted-foreground">Set the dimensions for the layer.</p>\n </div>\n </PopoverContent>\n</Popover>'
1068
+ }
1069
+ ],
1070
+ ai: {
1071
+ whenToUse: "Use for inline forms, quick settings, info panels, or color pickers. Anchored to a trigger, non-modal, dismisses on outside click.",
1072
+ whenNotToUse: "Don't use for critical tasks that interrupt (use Dialog). Don't use for hover-only info (use Tooltip or HoverCard). Don't use for menu actions (use DropdownMenu).",
1073
+ commonMistakes: [
1074
+ "Using Popover when the user must address the content (should be Dialog)",
1075
+ "Missing asChild on PopoverTrigger when using a styled Button",
1076
+ "Popover content too wide \u2014 keep it focused and compact"
1077
+ ],
1078
+ relatedComponents: ["tooltip", "hover-card", "dialog", "dropdown-menu"],
1079
+ accessibilityNotes: "Radix manages focus, aria-expanded on the trigger, and closes on Escape. Content is portalled to body so stacking contexts don't clip it.",
1080
+ tokenBudget: 400
1081
+ },
1082
+ tags: ["popover", "overlay", "floating", "inline", "anchored"]
1083
+ };
1084
+
1085
+ // src/components/tooltip/tooltip.schema.ts
1086
+ var tooltipSchema = {
1087
+ name: "tooltip",
1088
+ displayName: "Tooltip",
1089
+ description: "A small floating label that reveals on hover or focus. Wrap your app in TooltipProvider, then use Tooltip/TooltipTrigger/TooltipContent per tooltip.",
1090
+ category: "component",
1091
+ subcategory: "overlay",
1092
+ props: [
1093
+ {
1094
+ name: "delayDuration",
1095
+ type: "number",
1096
+ required: false,
1097
+ default: 700,
1098
+ description: "[TooltipProvider prop] Milliseconds before the tooltip appears on hover"
1099
+ },
1100
+ {
1101
+ name: "disableHoverableContent",
1102
+ type: "boolean",
1103
+ required: false,
1104
+ default: false,
1105
+ description: "[TooltipProvider prop] When true, tooltip dismisses when cursor enters it"
1106
+ },
1107
+ {
1108
+ name: "open",
1109
+ type: "boolean",
1110
+ required: false,
1111
+ description: "[Tooltip root prop] Controlled open state"
1112
+ },
1113
+ {
1114
+ name: "defaultOpen",
1115
+ type: "boolean",
1116
+ required: false,
1117
+ default: false,
1118
+ description: "[Tooltip root prop] Default open state for uncontrolled usage"
1119
+ },
1120
+ {
1121
+ name: "onOpenChange",
1122
+ type: "function",
1123
+ required: false,
1124
+ description: "[Tooltip root prop] Callback on open state change: (open: boolean) => void"
1125
+ }
1126
+ ],
1127
+ variants: [],
1128
+ slots: [
1129
+ {
1130
+ name: "children",
1131
+ description: "TooltipTrigger + TooltipContent, all inside a TooltipProvider",
1132
+ required: true,
1133
+ acceptedTypes: ["ReactNode"]
1134
+ }
1135
+ ],
1136
+ dependencies: {
1137
+ npm: ["@radix-ui/react-tooltip", "clsx", "tailwind-merge"],
1138
+ internal: [],
1139
+ peer: ["react", "react-dom"]
1140
+ },
1141
+ tokensUsed: ["primary", "primary-foreground"],
1142
+ examples: [
1143
+ {
1144
+ title: "Basic tooltip",
1145
+ description: "Icon button with hover label",
1146
+ code: '<TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant="outline" size="icon" aria-label="Add">+</Button>\n </TooltipTrigger>\n <TooltipContent>\n <p>Add item</p>\n </TooltipContent>\n </Tooltip>\n</TooltipProvider>'
1147
+ }
1148
+ ],
1149
+ ai: {
1150
+ whenToUse: "Use for terse hover/focus-reveal info: icon button labels, abbreviation expansions, keyboard shortcut hints. Content should fit in one line.",
1151
+ whenNotToUse: "Don't use for rich content with images or actions (use HoverCard or Popover). Don't use for the only way to convey essential info \u2014 it's invisible to touch users.",
1152
+ commonMistakes: [
1153
+ "Forgetting TooltipProvider at the app root",
1154
+ "Tooltip content too long (keep it under one line)",
1155
+ "Using Tooltip as the only label for icon buttons (still add aria-label)",
1156
+ "Triggering tooltips on non-interactive elements"
1157
+ ],
1158
+ relatedComponents: ["hover-card", "popover"],
1159
+ accessibilityNotes: "Triggers on focus and hover. Radix sets role='tooltip' and aria-describedby. Still pair icon buttons with aria-label since tooltips don't announce on touch.",
1160
+ tokenBudget: 300
1161
+ },
1162
+ tags: ["tooltip", "hint", "label", "hover", "overlay"]
1163
+ };
1164
+
1165
+ // src/primitives/select/select.schema.ts
1166
+ var selectSchema = {
1167
+ name: "select",
1168
+ displayName: "Select",
1169
+ description: "An accessible dropdown select for choosing one option from a list. Built on Radix UI Select with full keyboard navigation, typeahead, and RTL support.",
1170
+ category: "primitive",
1171
+ subcategory: "forms",
1172
+ props: [
1173
+ {
1174
+ name: "value",
1175
+ type: "string",
1176
+ required: false,
1177
+ description: "[Select root prop] Controlled selected value"
1178
+ },
1179
+ {
1180
+ name: "defaultValue",
1181
+ type: "string",
1182
+ required: false,
1183
+ description: "[Select root prop] Default selected value for uncontrolled usage"
1184
+ },
1185
+ {
1186
+ name: "onValueChange",
1187
+ type: "function",
1188
+ required: false,
1189
+ description: "[Select root prop] Callback on value change: (value: string) => void"
1190
+ },
1191
+ {
1192
+ name: "disabled",
1193
+ type: "boolean",
1194
+ required: false,
1195
+ default: false,
1196
+ description: "[Select root prop] Disable the entire select"
1197
+ },
1198
+ {
1199
+ name: "required",
1200
+ type: "boolean",
1201
+ required: false,
1202
+ default: false,
1203
+ description: "[Select root prop] Mark as required for form validation"
1204
+ },
1205
+ {
1206
+ name: "name",
1207
+ type: "string",
1208
+ required: false,
1209
+ description: "[Select root prop] Form field name (for native form submission)"
1210
+ }
1211
+ ],
1212
+ variants: [],
1213
+ slots: [
1214
+ {
1215
+ name: "children",
1216
+ description: "SelectTrigger + SelectContent (with SelectItems, Groups, Labels)",
1217
+ required: true,
1218
+ acceptedTypes: ["ReactNode"]
1219
+ }
1220
+ ],
1221
+ dependencies: {
1222
+ npm: ["@radix-ui/react-select", "clsx", "tailwind-merge"],
1223
+ internal: [],
1224
+ peer: ["react", "react-dom"]
1225
+ },
1226
+ tokensUsed: [
1227
+ "input",
1228
+ "background",
1229
+ "ring",
1230
+ "muted-foreground",
1231
+ "popover",
1232
+ "popover-foreground",
1233
+ "accent",
1234
+ "accent-foreground",
1235
+ "muted",
1236
+ "border"
1237
+ ],
1238
+ examples: [
1239
+ {
1240
+ title: "Basic select",
1241
+ description: "Choose a timezone",
1242
+ code: '<Select>\n <SelectTrigger className="w-[180px]">\n <SelectValue placeholder="Select a fruit" />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n <SelectLabel>Fruits</SelectLabel>\n <SelectItem value="apple">Apple</SelectItem>\n <SelectItem value="banana">Banana</SelectItem>\n <SelectItem value="orange">Orange</SelectItem>\n </SelectGroup>\n </SelectContent>\n</Select>'
1243
+ }
1244
+ ],
1245
+ ai: {
1246
+ whenToUse: "Use for choosing one option from a known, finite list (<= ~20 items): timezones, categories, roles, country codes. Pair with Label.",
1247
+ whenNotToUse: "Don't use for large searchable lists (use Combobox). Don't use for boolean choices (use Switch/Checkbox). Don't use for action menus (use DropdownMenu). Don't use for multi-select (needs a different component).",
1248
+ commonMistakes: [
1249
+ "Missing Label pairing",
1250
+ "Forgetting SelectValue inside SelectTrigger",
1251
+ "Using Select when the list is large (use Combobox)",
1252
+ "Putting non-SelectItem children inside SelectContent"
1253
+ ],
1254
+ relatedComponents: ["combobox", "dropdown-menu", "radio-group"],
1255
+ accessibilityNotes: "Full keyboard nav: arrow keys, Home, End, typeahead, Escape to close. Radix handles role='combobox' on trigger, role='listbox' on content, aria-selected on items.",
1256
+ tokenBudget: 800
1257
+ },
1258
+ tags: ["select", "dropdown", "form", "field", "options", "choose"]
1259
+ };
1260
+
1261
+ // src/primitives/radio-group/radio-group.schema.ts
1262
+ var radioGroupSchema = {
1263
+ name: "radio-group",
1264
+ displayName: "Radio Group",
1265
+ description: "A set of mutually exclusive radio options. Built on Radix UI RadioGroup with roving focus and arrow-key navigation.",
1266
+ category: "primitive",
1267
+ subcategory: "forms",
1268
+ props: [
1269
+ { name: "value", type: "string", required: false, description: "Controlled selected value" },
1270
+ {
1271
+ name: "defaultValue",
1272
+ type: "string",
1273
+ required: false,
1274
+ description: "Default selected value for uncontrolled usage"
1275
+ },
1276
+ {
1277
+ name: "onValueChange",
1278
+ type: "function",
1279
+ required: false,
1280
+ description: "Callback on value change: (value: string) => void"
1281
+ },
1282
+ {
1283
+ name: "disabled",
1284
+ type: "boolean",
1285
+ required: false,
1286
+ default: false,
1287
+ description: "Disable all items"
1288
+ },
1289
+ {
1290
+ name: "name",
1291
+ type: "string",
1292
+ required: false,
1293
+ description: "Form field name (for native form submission)"
1294
+ },
1295
+ {
1296
+ name: "orientation",
1297
+ type: "enum",
1298
+ required: false,
1299
+ default: "vertical",
1300
+ description: "Layout direction",
1301
+ enumValues: ["horizontal", "vertical"]
1302
+ }
1303
+ ],
1304
+ variants: [],
1305
+ slots: [
1306
+ {
1307
+ name: "children",
1308
+ description: "RadioGroupItem elements, typically paired with Labels",
1309
+ required: true,
1310
+ acceptedTypes: ["ReactNode"]
1311
+ }
1312
+ ],
1313
+ dependencies: {
1314
+ npm: ["@radix-ui/react-radio-group", "clsx", "tailwind-merge"],
1315
+ internal: [],
1316
+ peer: ["react", "react-dom"]
1317
+ },
1318
+ tokensUsed: ["input", "primary", "ring"],
1319
+ examples: [
1320
+ {
1321
+ title: "Basic radio group",
1322
+ description: "Select a notification preference",
1323
+ code: '<RadioGroup defaultValue="comfortable">\n <div className="flex items-center gap-2">\n <RadioGroupItem value="default" id="r1" />\n <Label htmlFor="r1">Default</Label>\n </div>\n <div className="flex items-center gap-2">\n <RadioGroupItem value="comfortable" id="r2" />\n <Label htmlFor="r2">Comfortable</Label>\n </div>\n <div className="flex items-center gap-2">\n <RadioGroupItem value="compact" id="r3" />\n <Label htmlFor="r3">Compact</Label>\n </div>\n</RadioGroup>'
1324
+ }
1325
+ ],
1326
+ ai: {
1327
+ whenToUse: "Use for mutually exclusive choices from a short list (2-5 options) where all options should be visible. Pair each RadioGroupItem with a Label.",
1328
+ whenNotToUse: "Don't use for many options (use Select). Don't use for boolean toggles (use Switch or Checkbox). Don't use for multi-select.",
1329
+ commonMistakes: [
1330
+ "Missing Label for each RadioGroupItem",
1331
+ "Using for more than 5 options (use Select)",
1332
+ "Using htmlFor id mismatch between Label and RadioGroupItem"
1333
+ ],
1334
+ relatedComponents: ["select", "checkbox", "label"],
1335
+ accessibilityNotes: "Radix implements the WAI-ARIA radio group pattern. Arrow keys move focus+selection. Radix handles aria-checked, role='radiogroup', role='radio'.",
1336
+ tokenBudget: 400
1337
+ },
1338
+ tags: ["radio", "radio-group", "form", "choice", "mutually-exclusive"]
1339
+ };
1340
+
1341
+ // src/primitives/slider/slider.schema.ts
1342
+ var sliderSchema = {
1343
+ name: "slider",
1344
+ displayName: "Slider",
1345
+ description: "A range input with draggable thumbs. Supports single value, ranges (two thumbs), custom steps, and full keyboard control.",
1346
+ category: "primitive",
1347
+ subcategory: "forms",
1348
+ props: [
1349
+ {
1350
+ name: "value",
1351
+ type: "object",
1352
+ required: false,
1353
+ description: "Controlled array of thumb values (number[]), e.g. [50] for single, [20, 80] for range"
1354
+ },
1355
+ {
1356
+ name: "defaultValue",
1357
+ type: "object",
1358
+ required: false,
1359
+ description: "Default array of thumb values (number[]) for uncontrolled usage"
1360
+ },
1361
+ {
1362
+ name: "onValueChange",
1363
+ type: "function",
1364
+ required: false,
1365
+ description: "Callback on value change: (value: number[]) => void"
1366
+ },
1367
+ { name: "min", type: "number", required: false, default: 0, description: "Minimum value" },
1368
+ { name: "max", type: "number", required: false, default: 100, description: "Maximum value" },
1369
+ {
1370
+ name: "step",
1371
+ type: "number",
1372
+ required: false,
1373
+ default: 1,
1374
+ description: "Step interval between valid values"
1375
+ },
1376
+ {
1377
+ name: "disabled",
1378
+ type: "boolean",
1379
+ required: false,
1380
+ default: false,
1381
+ description: "Disable the slider"
1382
+ },
1383
+ {
1384
+ name: "orientation",
1385
+ type: "enum",
1386
+ required: false,
1387
+ default: "horizontal",
1388
+ description: "Slider direction",
1389
+ enumValues: ["horizontal", "vertical"]
1390
+ },
1391
+ {
1392
+ name: "aria-label",
1393
+ type: "string",
1394
+ required: false,
1395
+ description: "Accessible label for the slider as a whole. Mirrored onto a single thumb automatically; for range sliders prefer thumbLabels."
1396
+ },
1397
+ {
1398
+ name: "aria-labelledby",
1399
+ type: "string",
1400
+ required: false,
1401
+ description: "Id of an external visible label that names the slider."
1402
+ },
1403
+ {
1404
+ name: "thumbLabels",
1405
+ type: "object",
1406
+ required: false,
1407
+ description: "Per-thumb accessible labels (string[]). Required for range sliders so each thumb has a meaningful name (e.g. ['Minimum price', 'Maximum price']). For a single-thumb slider, the Root's aria-label is mirrored onto the thumb automatically and thumbLabels is only needed when overriding that default."
1408
+ }
1409
+ ],
1410
+ variants: [],
1411
+ slots: [],
1412
+ dependencies: {
1413
+ npm: ["@radix-ui/react-slider", "clsx", "tailwind-merge"],
1414
+ internal: [],
1415
+ peer: ["react", "react-dom"]
1416
+ },
1417
+ tokensUsed: ["secondary", "primary", "background", "ring"],
1418
+ examples: [
1419
+ {
1420
+ title: "Volume control with display",
1421
+ description: "Single-thumb slider paired with the live numeric value \u2014 the canonical 'continuous range with feedback' pattern",
1422
+ code: 'function VolumeControl() {\n const [value, setValue] = React.useState([60]);\n return (\n <div className="space-y-2">\n <div className="flex justify-between text-sm">\n <Label htmlFor="vol">Volume</Label>\n <span className="text-muted-foreground tabular-nums">{value[0]}%</span>\n </div>\n <Slider id="vol" value={value} onValueChange={setValue} max={100} step={1} aria-label="Volume" />\n </div>\n );\n}',
1423
+ composition: ["form", "settings", "continuous-range", "with-display"]
1424
+ },
1425
+ {
1426
+ title: "Price-range filter",
1427
+ description: "Two-thumb range slider with per-thumb labels \u2014 the canonical e-commerce filter UI",
1428
+ code: '<Slider\n defaultValue={[20, 80]}\n max={100}\n step={1}\n thumbLabels={["Minimum price", "Maximum price"]}\n/>',
1429
+ composition: ["filter", "range", "ecommerce"]
1430
+ }
1431
+ ],
1432
+ ai: {
1433
+ whenToUse: "Use for continuous numeric inputs with a known range: volume, brightness, price range filter, opacity. Pair value with a visible number display when the exact value matters.",
1434
+ whenNotToUse: "Don't use when the user needs to enter an exact number (use Input type=number). Don't use for discrete choices (use Select or RadioGroup). Don't use for boolean on/off (use Switch).",
1435
+ commonMistakes: [
1436
+ "Using Slider for exact values without showing the number",
1437
+ "Missing min/max bounds",
1438
+ "Using step=1 for fractional values (set step=0.01)",
1439
+ "Not providing aria-label / aria-labelledby when there's no visible label",
1440
+ "Range slider with only Root aria-label and no thumbLabels \u2014 both thumbs fall back to '(N of 2)' indexed names instead of meaningful per-thumb labels",
1441
+ "thumbLabels.length !== value.length \u2014 extra labels are ignored, missing labels fall back to indexed names (dev-mode warning)"
1442
+ ],
1443
+ antiPatterns: [
1444
+ {
1445
+ mistake: "Using Slider with min=0/max=1 to represent on/off",
1446
+ insteadUse: "switch",
1447
+ why: "Slider is 'continuous range'. Switch is 'boolean'. Assistive tech announces them differently \u2014 Slider says '0 of 1, 1 of 1'; Switch says 'on / off'. Always reach for Switch when the values are binary."
1448
+ },
1449
+ {
1450
+ mistake: "Using Slider for picking a number from a small discrete set (1\u20135 rating, 'pages per row')",
1451
+ insteadUse: "radio-group",
1452
+ why: "Sliders make exact target values hard to land on. RadioGroup gives 5 buttons that name themselves and are easier with assistive tech. Reach for Slider only when 50+ values are valid."
1453
+ },
1454
+ {
1455
+ mistake: "Using Slider for the user to type a known exact number (price, age, count)",
1456
+ insteadUse: "input",
1457
+ why: "Drag-to-set is slower and less accurate than typing. Use <Input type='number'> when the user has the exact value in mind."
1458
+ }
1459
+ ],
1460
+ relatedComponents: ["input", "switch", "radio-group"],
1461
+ accessibilityNotes: "Arrow keys step by step, Home/End jump to min/max, PageUp/PageDown step larger. Radix handles aria-valuemin/max/now. Each thumb has its own accessible name: explicit via thumbLabels[i], else mirrored from the Root's aria-label (single thumb) or indexed '(i of N)' fallback (range). Add aria-label / aria-labelledby on the Root when there's no visible label.",
1462
+ tokenBudget: 450
1463
+ },
1464
+ tags: ["slider", "range", "form", "numeric", "input"]
1465
+ };
1466
+
1467
+ // src/primitives/toggle/toggle.schema.ts
1468
+ var toggleSchema = {
1469
+ name: "toggle",
1470
+ displayName: "Toggle",
1471
+ description: "A two-state button that stays pressed when toggled on. Used for formatting toolbars (bold/italic) or option toggles.",
1472
+ category: "primitive",
1473
+ subcategory: "forms",
1474
+ props: [
1475
+ {
1476
+ name: "pressed",
1477
+ type: "boolean",
1478
+ required: false,
1479
+ description: "Controlled pressed state"
1480
+ },
1481
+ {
1482
+ name: "defaultPressed",
1483
+ type: "boolean",
1484
+ required: false,
1485
+ default: false,
1486
+ description: "Default pressed state for uncontrolled usage"
1487
+ },
1488
+ {
1489
+ name: "onPressedChange",
1490
+ type: "function",
1491
+ required: false,
1492
+ description: "Callback on pressed change: (pressed: boolean) => void"
1493
+ },
1494
+ {
1495
+ name: "variant",
1496
+ type: "enum",
1497
+ required: false,
1498
+ default: "default",
1499
+ description: "Visual style",
1500
+ enumValues: ["default", "outline"]
1501
+ },
1502
+ {
1503
+ name: "size",
1504
+ type: "enum",
1505
+ required: false,
1506
+ default: "default",
1507
+ description: "Toggle size",
1508
+ enumValues: ["default", "sm", "lg"]
1509
+ },
1510
+ {
1511
+ name: "disabled",
1512
+ type: "boolean",
1513
+ required: false,
1514
+ default: false,
1515
+ description: "Disable the toggle"
1516
+ }
1517
+ ],
1518
+ variants: [
1519
+ {
1520
+ name: "variant",
1521
+ description: "Visual variants",
1522
+ values: [
1523
+ { value: "default", description: "Transparent ghost-style toggle" },
1524
+ { value: "outline", description: "Bordered toggle" }
1525
+ ],
1526
+ default: "default"
1527
+ },
1528
+ {
1529
+ name: "size",
1530
+ description: "Size variants",
1531
+ values: [
1532
+ { value: "default", description: "Standard size (h-10)" },
1533
+ { value: "sm", description: "Compact size (h-9)" },
1534
+ { value: "lg", description: "Large size (h-11)" }
1535
+ ],
1536
+ default: "default"
1537
+ }
1538
+ ],
1539
+ slots: [
1540
+ {
1541
+ name: "children",
1542
+ description: "Toggle label or icon",
1543
+ required: true,
1544
+ acceptedTypes: ["ReactNode"]
1545
+ }
1546
+ ],
1547
+ dependencies: {
1548
+ npm: ["@radix-ui/react-toggle", "class-variance-authority", "clsx", "tailwind-merge"],
1549
+ internal: [],
1550
+ peer: ["react", "react-dom"]
1551
+ },
1552
+ tokensUsed: ["muted", "muted-foreground", "accent", "accent-foreground", "input", "ring"],
1553
+ examples: [
1554
+ {
1555
+ title: "Basic toggle",
1556
+ description: "Bold text toggle",
1557
+ code: '<Toggle aria-label="Toggle bold">B</Toggle>'
1558
+ }
1559
+ ],
1560
+ ai: {
1561
+ whenToUse: "Use for binary on/off actions that persist: toolbar formatting buttons (bold, italic), layout mode switches, filter toggles. Not submitted as form data.",
1562
+ whenNotToUse: "Don't use for instant settings (use Switch). Don't use for form boolean fields (use Checkbox). Don't use for choosing one of many (use ToggleGroup).",
1563
+ commonMistakes: [
1564
+ "Using for form field submission (use Checkbox instead)",
1565
+ "Forgetting aria-label on icon-only toggles",
1566
+ "Using Toggle when ToggleGroup's single-select mode is needed"
1567
+ ],
1568
+ relatedComponents: ["toggle-group", "switch", "checkbox", "button"],
1569
+ accessibilityNotes: "Radix sets aria-pressed correctly. Icon-only toggles MUST have aria-label. Space/Enter toggles state.",
1570
+ tokenBudget: 400
1571
+ },
1572
+ tags: ["toggle", "button", "pressed", "two-state", "toolbar"]
1573
+ };
1574
+
1575
+ // src/primitives/toggle-group/toggle-group.schema.ts
1576
+ var toggleGroupSchema = {
1577
+ name: "toggle-group",
1578
+ displayName: "Toggle Group",
1579
+ description: "A set of toggles where one or multiple can be pressed. Inherits Toggle's variant/size via context. Useful for alignment/formatting toolbars.",
1580
+ category: "primitive",
1581
+ subcategory: "forms",
1582
+ props: [
1583
+ {
1584
+ name: "type",
1585
+ type: "enum",
1586
+ required: true,
1587
+ description: "Single allows one pressed at a time, multiple allows many",
1588
+ enumValues: ["single", "multiple"]
1589
+ },
1590
+ {
1591
+ name: "value",
1592
+ type: "object",
1593
+ required: false,
1594
+ description: "Controlled pressed value(s). string when type='single', string[] when type='multiple'"
1595
+ },
1596
+ {
1597
+ name: "defaultValue",
1598
+ type: "object",
1599
+ required: false,
1600
+ description: "Default pressed value(s). string when type='single', string[] when type='multiple'"
1601
+ },
1602
+ {
1603
+ name: "onValueChange",
1604
+ type: "function",
1605
+ required: false,
1606
+ description: "Callback on value change"
1607
+ },
1608
+ {
1609
+ name: "variant",
1610
+ type: "enum",
1611
+ required: false,
1612
+ default: "default",
1613
+ description: "Inherited by all ToggleGroupItems",
1614
+ enumValues: ["default", "outline"]
1615
+ },
1616
+ {
1617
+ name: "size",
1618
+ type: "enum",
1619
+ required: false,
1620
+ default: "default",
1621
+ description: "Inherited by all ToggleGroupItems",
1622
+ enumValues: ["default", "sm", "lg"]
1623
+ },
1624
+ {
1625
+ name: "disabled",
1626
+ type: "boolean",
1627
+ required: false,
1628
+ default: false,
1629
+ description: "Disable all items"
1630
+ }
1631
+ ],
1632
+ variants: [],
1633
+ slots: [
1634
+ {
1635
+ name: "children",
1636
+ description: "ToggleGroupItem elements",
1637
+ required: true,
1638
+ acceptedTypes: ["ReactNode"]
1639
+ }
1640
+ ],
1641
+ dependencies: {
1642
+ npm: [
1643
+ "@radix-ui/react-toggle-group",
1644
+ "@radix-ui/react-toggle",
1645
+ "class-variance-authority",
1646
+ "clsx",
1647
+ "tailwind-merge"
1648
+ ],
1649
+ internal: ["toggle"],
1650
+ peer: ["react", "react-dom"]
1651
+ },
1652
+ tokensUsed: ["muted", "accent", "accent-foreground", "ring"],
1653
+ examples: [
1654
+ {
1655
+ title: "Text alignment group",
1656
+ description: "Single-select toggle group for text alignment",
1657
+ code: '<ToggleGroup type="single" defaultValue="left">\n <ToggleGroupItem value="left" aria-label="Left align">L</ToggleGroupItem>\n <ToggleGroupItem value="center" aria-label="Center align">C</ToggleGroupItem>\n <ToggleGroupItem value="right" aria-label="Right align">R</ToggleGroupItem>\n</ToggleGroup>'
1658
+ },
1659
+ {
1660
+ title: "Formatting group",
1661
+ description: "Multiple-select toggle group for text formatting",
1662
+ code: '<ToggleGroup type="multiple">\n <ToggleGroupItem value="bold" aria-label="Bold">B</ToggleGroupItem>\n <ToggleGroupItem value="italic" aria-label="Italic">I</ToggleGroupItem>\n <ToggleGroupItem value="underline" aria-label="Underline">U</ToggleGroupItem>\n</ToggleGroup>'
1663
+ }
1664
+ ],
1665
+ ai: {
1666
+ whenToUse: "Use for toolbar toggles where users pick one of many (type='single') or multiple (type='multiple'): text alignment, formatting (bold/italic/underline), view modes.",
1667
+ whenNotToUse: "Don't use for form radio fields (use RadioGroup). Don't use for standalone two-state buttons (use Toggle). Don't use for navigation (use Tabs).",
1668
+ commonMistakes: [
1669
+ "Forgetting the type prop (required)",
1670
+ "Missing aria-label on icon-only items",
1671
+ "Using for form submission without name prop"
1672
+ ],
1673
+ relatedComponents: ["toggle", "radio-group", "tabs"],
1674
+ accessibilityNotes: "Radix implements the WAI-ARIA toolbar pattern with roving focus. Arrow keys move focus, Space/Enter toggles. Each icon-only item needs aria-label.",
1675
+ tokenBudget: 500
1676
+ },
1677
+ tags: ["toggle-group", "toolbar", "formatting", "alignment", "multi-select"]
1678
+ };
1679
+
1680
+ // src/components/form/form.schema.ts
1681
+ var formSchema = {
1682
+ name: "form",
1683
+ displayName: "Form",
1684
+ description: "A form primitive built on react-hook-form + zod. Provides Form/FormField/FormItem/FormLabel/FormControl/FormDescription/FormMessage with automatic aria wiring and error display.",
1685
+ category: "component",
1686
+ subcategory: "forms",
1687
+ props: [],
1688
+ variants: [],
1689
+ slots: [
1690
+ {
1691
+ name: "children",
1692
+ description: "FormField + FormItem subtrees",
1693
+ required: true,
1694
+ acceptedTypes: ["ReactNode"]
1695
+ }
1696
+ ],
1697
+ dependencies: {
1698
+ npm: [
1699
+ "react-hook-form",
1700
+ "@hookform/resolvers",
1701
+ "zod",
1702
+ "@radix-ui/react-label",
1703
+ "@radix-ui/react-slot",
1704
+ "clsx",
1705
+ "tailwind-merge"
1706
+ ],
1707
+ internal: ["label"],
1708
+ peer: ["react", "react-dom"]
1709
+ },
1710
+ tokensUsed: ["destructive", "muted-foreground"],
1711
+ examples: [
1712
+ {
1713
+ title: "Zod-validated form",
1714
+ description: "Username form with react-hook-form + zod",
1715
+ code: 'import { zodResolver } from "@hookform/resolvers/zod";\nimport { useForm } from "react-hook-form";\nimport { z } from "zod";\n\nconst formSchema = z.object({\n username: z.string().min(2, "Username must be at least 2 characters"),\n});\n\nfunction ProfileForm() {\n const form = useForm({\n resolver: zodResolver(formSchema),\n defaultValues: { username: "" },\n });\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit((values) => console.log(values))} className="space-y-6">\n <FormField\n control={form.control}\n name="username"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Username</FormLabel>\n <FormControl><Input placeholder="shadcn" {...field} /></FormControl>\n <FormDescription>Your public display name.</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type="submit">Submit</Button>\n </form>\n </Form>\n );\n}'
1716
+ }
1717
+ ],
1718
+ ai: {
1719
+ whenToUse: "Use for any form that needs validation, per-field errors, and accessible aria-describedby/aria-invalid wiring. Pair with zod schemas via @hookform/resolvers/zod.",
1720
+ whenNotToUse: "Don't use for trivial single-input forms that don't need validation (render a plain Input + Button). Don't use for server actions forms in Next.js 16 (consider useActionState + useFormStatus instead).",
1721
+ commonMistakes: [
1722
+ "Forgetting to spread {...field} into the form control (connects value/onChange)",
1723
+ "Putting FormLabel outside FormItem (loses htmlFor wiring)",
1724
+ "Using useForm without a resolver when zod validation is desired",
1725
+ "Calling form.handleSubmit without a callback"
1726
+ ],
1727
+ relatedComponents: ["input", "textarea", "select", "checkbox", "radio-group", "switch"],
1728
+ accessibilityNotes: "FormControl automatically wires id, aria-describedby, and aria-invalid. FormLabel uses htmlFor matching the control id. Errors are announced via FormMessage with matching aria-describedby.",
1729
+ tokenBudget: 900
1730
+ },
1731
+ tags: ["form", "react-hook-form", "zod", "validation", "field"]
1732
+ };
1733
+
1734
+ // src/primitives/avatar/avatar.schema.ts
1735
+ var avatarSchema = {
1736
+ name: "avatar",
1737
+ displayName: "Avatar",
1738
+ description: "A user profile image with a fallback (usually initials) rendered when the image is missing or fails to load. Built on Radix UI Avatar \u2014 AvatarFallback accepts a delayMs prop to avoid flashing during fast loads.",
1739
+ category: "primitive",
1740
+ subcategory: "display",
1741
+ props: [{ name: "className", type: "string", required: false, description: "Additional CSS classes on the root" }],
1742
+ variants: [],
1743
+ slots: [
1744
+ {
1745
+ name: "children",
1746
+ description: "AvatarImage + AvatarFallback",
1747
+ required: true,
1748
+ acceptedTypes: ["ReactNode"]
1749
+ }
1750
+ ],
1751
+ dependencies: {
1752
+ npm: ["@radix-ui/react-avatar", "clsx", "tailwind-merge"],
1753
+ internal: ["lib/utils"],
1754
+ peer: ["react", "react-dom"]
1755
+ },
1756
+ tokensUsed: ["muted", "muted-foreground"],
1757
+ examples: [
1758
+ {
1759
+ title: "Basic avatar",
1760
+ description: "Image with initials fallback",
1761
+ code: '<Avatar>\n <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />\n <AvatarFallback>CN</AvatarFallback>\n</Avatar>'
1762
+ }
1763
+ ],
1764
+ ai: {
1765
+ whenToUse: "Use for user profile images: headers, comments, user lists. Always include AvatarFallback for accessibility and loading states.",
1766
+ whenNotToUse: "Don't use for decorative icons (use an <img> or icon component). Don't use for product/brand images (use <img> with proper sizing).",
1767
+ commonMistakes: [
1768
+ "Missing alt text on AvatarImage",
1769
+ "No AvatarFallback \u2014 shows nothing when image is missing or errors",
1770
+ "Omitting delayMs on AvatarFallback causes flicker for fast-loading images",
1771
+ "Using for non-circular images (override rounded-full if needed)"
1772
+ ],
1773
+ relatedComponents: ["badge", "card"],
1774
+ accessibilityNotes: "AvatarImage requires alt text. AvatarFallback renders initials or an icon \u2014 ensure the visible text is meaningful.",
1775
+ tokenBudget: 250
1776
+ },
1777
+ tags: ["avatar", "profile", "user", "image", "display"]
1778
+ };
1779
+
1780
+ // src/primitives/skeleton/skeleton.schema.ts
1781
+ var skeletonSchema = {
1782
+ name: "skeleton",
1783
+ displayName: "Skeleton",
1784
+ description: "A pulsing placeholder shown while content is loading. Pair with explicit dimensions.",
1785
+ category: "primitive",
1786
+ subcategory: "feedback",
1787
+ props: [
1788
+ {
1789
+ name: "className",
1790
+ type: "string",
1791
+ required: false,
1792
+ description: "Width/height and any additional styling via Tailwind classes"
1793
+ }
1794
+ ],
1795
+ variants: [],
1796
+ slots: [],
1797
+ dependencies: {
1798
+ npm: ["clsx", "tailwind-merge"],
1799
+ internal: ["lib/utils"],
1800
+ peer: ["react", "react-dom"]
1801
+ },
1802
+ tokensUsed: ["muted"],
1803
+ examples: [
1804
+ {
1805
+ title: "Card skeleton",
1806
+ description: "Avatar + two lines of text placeholder",
1807
+ code: '<div className="flex items-center gap-4">\n <Skeleton className="h-12 w-12 rounded-full" />\n <div className="space-y-2">\n <Skeleton className="h-4 w-[250px]" />\n <Skeleton className="h-4 w-[200px]" />\n </div>\n</div>'
1808
+ }
1809
+ ],
1810
+ ai: {
1811
+ whenToUse: "Use during async data loads to show the shape of forthcoming content. Match the dimensions and layout of the real content to avoid layout shift on load.",
1812
+ whenNotToUse: "Don't use for fast operations (<200ms \u2014 users prefer a brief spinner or nothing). Don't use as a permanent empty state (use proper empty-state UI).",
1813
+ commonMistakes: [
1814
+ "Skeleton dimensions don't match loaded content \u2014 causes layout shift",
1815
+ "Leaving Skeleton visible for long loads without a timeout/retry",
1816
+ "Using Skeleton for interactive elements users might tap"
1817
+ ],
1818
+ relatedComponents: ["progress", "avatar", "card", "table"],
1819
+ accessibilityNotes: "Add aria-busy='true' on the loading container and a visually hidden status (aria-live='polite') to announce load completion to screen readers.",
1820
+ tokenBudget: 200
1821
+ },
1822
+ tags: ["skeleton", "loading", "placeholder", "shimmer"]
1823
+ };
1824
+
1825
+ // src/primitives/progress/progress.schema.ts
1826
+ var progressSchema = {
1827
+ name: "progress",
1828
+ displayName: "Progress",
1829
+ description: "A horizontal progress bar showing completion from 0 to 100%. Built on Radix UI Progress.",
1830
+ category: "primitive",
1831
+ subcategory: "feedback",
1832
+ props: [
1833
+ {
1834
+ name: "value",
1835
+ type: "number",
1836
+ required: false,
1837
+ description: "Current value (0\u2013max). Omit to render at 0% (both visually and in ARIA \u2014 the component clamps undefined to 0). Use Skeleton for indeterminate loading states."
1838
+ },
1839
+ { name: "max", type: "number", required: false, default: 100, description: "Maximum value" },
1840
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
1841
+ ],
1842
+ variants: [],
1843
+ slots: [],
1844
+ dependencies: {
1845
+ npm: ["@radix-ui/react-progress", "clsx", "tailwind-merge"],
1846
+ internal: ["lib/utils"],
1847
+ peer: ["react", "react-dom"]
1848
+ },
1849
+ tokensUsed: ["secondary", "primary"],
1850
+ examples: [
1851
+ {
1852
+ title: "Basic progress",
1853
+ description: "At 60%",
1854
+ code: "<Progress value={60} />"
1855
+ }
1856
+ ],
1857
+ ai: {
1858
+ whenToUse: "Use for deterministic progress where completion is measurable: file uploads, multi-step form completion, batch processing. Pair with a visible numeric label where precision matters.",
1859
+ whenNotToUse: "Don't use for indeterminate waits (use a spinner or Skeleton). Don't use for completion over long time-spans without a human-readable ETA.",
1860
+ commonMistakes: [
1861
+ "Passing 0\u20131 float instead of 0\u2013100",
1862
+ "Not labeling the progress (users can't tell what's progressing)",
1863
+ "Updating too frequently (>30fps) \u2014 wastes renders"
1864
+ ],
1865
+ relatedComponents: ["skeleton", "slider", "sonner"],
1866
+ accessibilityNotes: "Radix wires role='progressbar', aria-valuenow, aria-valuemin, aria-valuemax. Pair with a visible label or aria-label for context.",
1867
+ tokenBudget: 250
1868
+ },
1869
+ tags: ["progress", "loading", "bar", "feedback", "determinate"]
1870
+ };
1871
+
1872
+ // src/primitives/scroll-area/scroll-area.schema.ts
1873
+ var scrollAreaSchema = {
1874
+ name: "scroll-area",
1875
+ displayName: "Scroll Area",
1876
+ description: "A scrollable region with custom-styled scrollbars that match the design system. Content must be explicitly sized.",
1877
+ category: "primitive",
1878
+ subcategory: "layout",
1879
+ props: [
1880
+ {
1881
+ name: "type",
1882
+ type: "enum",
1883
+ required: false,
1884
+ default: "hover",
1885
+ description: "When scrollbars are visible",
1886
+ enumValues: ["auto", "always", "scroll", "hover"]
1887
+ },
1888
+ { name: "className", type: "string", required: false, description: "Set dimensions via Tailwind (e.g. h-72 w-48)" },
1889
+ {
1890
+ name: "viewportTabIndex",
1891
+ type: "number",
1892
+ required: false,
1893
+ default: 0,
1894
+ description: "tabIndex applied to the scroll viewport. Defaults to 0 so keyboard users can scroll without a pointer; pass -1 to skip the viewport in the tab order when wrapping decorative or already-keyboard-reachable content."
1895
+ }
1896
+ ],
1897
+ variants: [],
1898
+ slots: [
1899
+ {
1900
+ name: "children",
1901
+ description: "Any content \u2014 will be wrapped in a scrollable viewport",
1902
+ required: true,
1903
+ acceptedTypes: ["ReactNode"]
1904
+ }
1905
+ ],
1906
+ dependencies: {
1907
+ npm: ["@radix-ui/react-scroll-area", "clsx", "tailwind-merge"],
1908
+ internal: ["lib/utils"],
1909
+ peer: ["react", "react-dom"]
1910
+ },
1911
+ tokensUsed: ["border"],
1912
+ examples: [
1913
+ {
1914
+ title: "Fixed-height list",
1915
+ description: "50 items in a 200px-tall scrollable region",
1916
+ code: '<ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">\n {Array.from({ length: 50 }).map((_, i) => (\n <div key={i} className="text-sm">Item {i + 1}</div>\n ))}\n</ScrollArea>'
1917
+ }
1918
+ ],
1919
+ ai: {
1920
+ whenToUse: "Use when you need styled scrollbars that match the design system \u2014 sidebars, code blocks, large lists in dialogs. Must have explicit dimensions (height/width).",
1921
+ whenNotToUse: "Don't use for the whole page (use native browser scrollbars). Don't use for content that should grow freely (omit the ScrollArea and use overflow-auto directly).",
1922
+ commonMistakes: [
1923
+ "Forgetting to set height/width \u2014 scrollbars don't appear",
1924
+ "Using for the whole page",
1925
+ "Nesting ScrollAreas (confusing UX)",
1926
+ "Wrapping decorative or already-keyboard-reachable content without setting viewportTabIndex={-1} \u2014 adds an unnecessary tab stop"
1927
+ ],
1928
+ relatedComponents: [],
1929
+ accessibilityNotes: "The viewport is keyboard-focusable by default (viewportTabIndex=0) so users can scroll long content via arrow keys / PgUp / PgDn / Home / End without a pointer. Pass viewportTabIndex={-1} when the contents are already in the tab order or purely decorative. For very long lists, consider pagination or virtualization.",
1930
+ tokenBudget: 350
1931
+ },
1932
+ tags: ["scroll-area", "scroll", "overflow", "scrollbar", "layout"]
1933
+ };
1934
+
1935
+ // src/primitives/aspect-ratio/aspect-ratio.schema.ts
1936
+ var aspectRatioSchema = {
1937
+ name: "aspect-ratio",
1938
+ displayName: "Aspect Ratio",
1939
+ description: "Constrain children to a specific width-to-height ratio (e.g. 16/9 for video, 1/1 for square).",
1940
+ category: "primitive",
1941
+ subcategory: "layout",
1942
+ props: [
1943
+ {
1944
+ name: "ratio",
1945
+ type: "number",
1946
+ required: false,
1947
+ default: 1,
1948
+ description: "Width-to-height ratio (e.g. 16/9 \u2248 1.778)"
1949
+ }
1950
+ ],
1951
+ variants: [],
1952
+ slots: [
1953
+ {
1954
+ name: "children",
1955
+ description: "Content to constrain (usually an image or iframe)",
1956
+ required: true,
1957
+ acceptedTypes: ["ReactNode"]
1958
+ }
1959
+ ],
1960
+ dependencies: {
1961
+ npm: ["@radix-ui/react-aspect-ratio"],
1962
+ internal: [],
1963
+ peer: ["react", "react-dom"]
1964
+ },
1965
+ tokensUsed: [],
1966
+ examples: [
1967
+ {
1968
+ title: "16:9 video thumbnail",
1969
+ description: "Image constrained to widescreen ratio",
1970
+ code: '<AspectRatio ratio={16 / 9} className="bg-muted">\n <img src="/hero.jpg" alt="Hero" className="h-full w-full rounded-md object-cover" />\n</AspectRatio>'
1971
+ }
1972
+ ],
1973
+ ai: {
1974
+ whenToUse: "Use when an image or iframe must maintain a specific ratio regardless of container width: video thumbnails, product images, hero banners, embeds.",
1975
+ whenNotToUse: "Don't use for content with known fixed dimensions (just use width/height). Don't use for text content (ratios don't make sense for prose).",
1976
+ commonMistakes: [
1977
+ "Passing ratio as a string instead of a number (use {16/9}, not '16/9')",
1978
+ "Forgetting that children must fill 100% width + height (add object-cover or similar)"
1979
+ ],
1980
+ relatedComponents: ["card", "avatar", "skeleton"],
1981
+ accessibilityNotes: "AspectRatio is purely structural. Ensure inner <img> has alt text and inner <iframe> has a descriptive title.",
1982
+ tokenBudget: 200
1983
+ },
1984
+ tags: ["aspect-ratio", "layout", "image", "video", "ratio"]
1985
+ };
1986
+
1987
+ // src/primitives/container/container.schema.ts
1988
+ var containerSchema = {
1989
+ name: "container",
1990
+ displayName: "Container",
1991
+ description: "Centered max-width wrapper that constrains content to readable widths. Sizes map to `--container-*` prose-width tokens; padding maps to `--space-*`.",
1992
+ category: "primitive",
1993
+ subcategory: "layout",
1994
+ props: [
1995
+ {
1996
+ name: "size",
1997
+ type: "enum",
1998
+ required: false,
1999
+ default: "lg",
2000
+ description: "Max-width preset bound to `--container-*` tokens (sm=33rem, md=40rem, lg=50rem, xl=66rem, full=100%).",
2001
+ enumValues: ["sm", "md", "lg", "xl", "full"]
2002
+ },
2003
+ {
2004
+ name: "padding",
2005
+ type: "enum",
2006
+ required: false,
2007
+ default: "md",
2008
+ description: "Horizontal padding bound to `--space-*` tokens (none=0, sm=0.75rem, md=1rem, lg=2rem).",
2009
+ enumValues: ["none", "sm", "md", "lg"]
2010
+ },
2011
+ {
2012
+ name: "asChild",
2013
+ type: "boolean",
2014
+ required: false,
2015
+ default: false,
2016
+ description: "Render as a different element via Radix `Slot`. Use to render as `<main>`, `<section>`, etc. for landmark semantics."
2017
+ }
2018
+ ],
2019
+ variants: [
2020
+ {
2021
+ name: "size",
2022
+ description: "Max-width preset bound to `--container-*` tokens.",
2023
+ values: [
2024
+ { value: "sm", description: "33rem (\u2248530px) \u2014 narrow column for short content." },
2025
+ { value: "md", description: "40rem (\u2248640px) \u2014 comfortable reading width." },
2026
+ { value: "lg", description: "50rem (\u2248800px) \u2014 default; standard article width." },
2027
+ { value: "xl", description: "66rem (\u22481056px) \u2014 wider canvas for dashboards or marketing." },
2028
+ { value: "full", description: "100% \u2014 disable max-width clamp." }
2029
+ ],
2030
+ default: "lg"
2031
+ },
2032
+ {
2033
+ name: "padding",
2034
+ description: "Horizontal padding bound to `--space-*` tokens.",
2035
+ values: [
2036
+ { value: "none", description: "0 \u2014 flush to container edges." },
2037
+ { value: "sm", description: "0.75rem \u2014 tight gutter." },
2038
+ { value: "md", description: "1rem \u2014 default; standard reading gutter." },
2039
+ { value: "lg", description: "2rem \u2014 generous gutter for marketing pages." }
2040
+ ],
2041
+ default: "md"
2042
+ }
2043
+ ],
2044
+ slots: [
2045
+ {
2046
+ name: "children",
2047
+ description: "Page content to constrain \u2014 typically a section, article, or grid.",
2048
+ required: true,
2049
+ acceptedTypes: ["ReactNode"]
2050
+ }
2051
+ ],
2052
+ dependencies: {
2053
+ npm: ["class-variance-authority", "@radix-ui/react-slot"],
2054
+ internal: ["lib/utils"],
2055
+ peer: ["react"]
2056
+ },
2057
+ tokensUsed: [
2058
+ "--container-sm",
2059
+ "--container-md",
2060
+ "--container-lg",
2061
+ "--container-xl",
2062
+ "--space-3",
2063
+ "--space-4",
2064
+ "--space-8"
2065
+ ],
2066
+ examples: [
2067
+ {
2068
+ title: "Reading-width article",
2069
+ description: "lg size (50rem) + md padding for an article body.",
2070
+ code: '<Container size="lg" padding="md">\n <article>...</article>\n</Container>'
2071
+ },
2072
+ {
2073
+ title: "Full-width hero",
2074
+ description: "Use size=full when you want the section to span the viewport.",
2075
+ code: '<Container size="full" padding="none">\n <Hero />\n</Container>'
2076
+ }
2077
+ ],
2078
+ ai: {
2079
+ whenToUse: "Use to constrain page content to readable widths. Pair with `Stack` or `Grid` inside for vertical/grid layouts. Default for any centered article, settings page, or marketing section.",
2080
+ whenNotToUse: 'Don\'t use inside another `Container` (double-clamping). Don\'t use as a drop-in for `<main>` semantics \u2014 it\'s a layout primitive, not a landmark. For full-bleed sections (edge-to-edge banners), pass `size="full" padding="none"`.',
2081
+ commonMistakes: [
2082
+ "Wrapping a `Container` in another `Container` and getting an unexpectedly narrow column",
2083
+ 'Using `padding="none"` and forgetting that children still need their own gutter \u2014 the parent contributes nothing',
2084
+ "Treating `lg` as the largest size \u2014 `xl` (66rem) and `full` are bigger"
2085
+ ],
2086
+ relatedComponents: ["stack", "grid", "cluster"],
2087
+ accessibilityNotes: "Container is presentational. Wrap in a semantic landmark (`<main>`, `<section>`, `<article>`) when the page structure needs it; Container itself renders a plain `<div>`.",
2088
+ tokenBudget: 250
2089
+ },
2090
+ tags: ["container", "layout", "wrapper", "max-width", "primitive"]
2091
+ };
2092
+
2093
+ // src/primitives/stack/stack.schema.ts
2094
+ var stackSchema = {
2095
+ name: "stack",
2096
+ displayName: "Stack",
2097
+ description: 'Vertical flex flow with token-bound gap. The headless equivalent of `<div className="flex flex-col gap-X">` with consistent spacing scale.',
2098
+ category: "primitive",
2099
+ subcategory: "layout",
2100
+ props: [
2101
+ {
2102
+ name: "gap",
2103
+ type: "enum",
2104
+ required: false,
2105
+ default: "md",
2106
+ description: "Vertical spacing between children, bound to `--gap-*` tokens.",
2107
+ enumValues: ["xs", "sm", "md", "lg", "xl"]
2108
+ },
2109
+ {
2110
+ name: "align",
2111
+ type: "enum",
2112
+ required: false,
2113
+ default: "stretch",
2114
+ description: "Cross-axis alignment (CSS `align-items`).",
2115
+ enumValues: ["start", "center", "end", "stretch"]
2116
+ },
2117
+ {
2118
+ name: "justify",
2119
+ type: "enum",
2120
+ required: false,
2121
+ default: "start",
2122
+ description: "Main-axis distribution (CSS `justify-content`).",
2123
+ enumValues: ["start", "center", "end", "between"]
2124
+ }
2125
+ ],
2126
+ variants: [
2127
+ {
2128
+ name: "gap",
2129
+ description: "Vertical gap between children, bound to `--gap-*` tokens.",
2130
+ values: [
2131
+ { value: "xs", description: "0.25rem \u2014 barely-there spacing." },
2132
+ { value: "sm", description: "0.5rem \u2014 tight grouping." },
2133
+ { value: "md", description: "1rem \u2014 default; standard rhythm." },
2134
+ { value: "lg", description: "1.5rem \u2014 section-level spacing." },
2135
+ { value: "xl", description: "2rem \u2014 major separation." }
2136
+ ],
2137
+ default: "md"
2138
+ },
2139
+ {
2140
+ name: "align",
2141
+ description: "Cross-axis alignment (CSS `align-items`).",
2142
+ values: [
2143
+ { value: "start", description: "Children align to left edge." },
2144
+ { value: "center", description: "Children center horizontally." },
2145
+ { value: "end", description: "Children align to right edge." },
2146
+ { value: "stretch", description: "Default \u2014 children fill container width." }
2147
+ ],
2148
+ default: "stretch"
2149
+ },
2150
+ {
2151
+ name: "justify",
2152
+ description: "Main-axis distribution (CSS `justify-content`).",
2153
+ values: [
2154
+ { value: "start", description: "Default \u2014 children pack to top." },
2155
+ { value: "center", description: "Children center vertically." },
2156
+ { value: "end", description: "Children pack to bottom." },
2157
+ { value: "between", description: "First child to top, last to bottom, even distribution." }
2158
+ ],
2159
+ default: "start"
2160
+ }
2161
+ ],
2162
+ slots: [
2163
+ {
2164
+ name: "children",
2165
+ description: "Items to stack vertically.",
2166
+ required: true,
2167
+ acceptedTypes: ["ReactNode"]
2168
+ }
2169
+ ],
2170
+ dependencies: {
2171
+ npm: ["class-variance-authority"],
2172
+ internal: ["lib/utils"],
2173
+ peer: ["react"]
2174
+ },
2175
+ tokensUsed: ["--gap-xs", "--gap-sm", "--gap-md", "--gap-lg", "--gap-xl"],
2176
+ examples: [
2177
+ {
2178
+ title: "Form sections",
2179
+ description: "Lg gap separates labelled groups; nested Stack with sm gap groups label+input.",
2180
+ code: '<Stack gap="lg">\n <Stack gap="sm"><Label>Email</Label><Input /></Stack>\n <Stack gap="sm"><Label>Password</Label><Input type="password" /></Stack>\n <Button>Submit</Button>\n</Stack>'
2181
+ },
2182
+ {
2183
+ title: "Centered hero",
2184
+ description: "Center children horizontally for a centered call-to-action stack.",
2185
+ code: '<Stack gap="md" align="center">\n <h1>Title</h1>\n <p>Subtitle</p>\n <Button>Get started</Button>\n</Stack>'
2186
+ }
2187
+ ],
2188
+ ai: {
2189
+ whenToUse: "Use anywhere you'd write `flex flex-col gap-X`. Default for vertical lists of dissimilar items (label + input + helper text), section bodies, sidebar items, button groups stacked vertically.",
2190
+ whenNotToUse: "Don't use for tabular data \u2014 use `<table>` or DataTable. Don't use for grid-like layouts \u2014 use `Grid`. Don't reach for `Stack` when a single child needs no spacing \u2014 just render the child.",
2191
+ commonMistakes: [
2192
+ 'Setting `gap="md"` then adding `mt-*` / `space-y-*` on individual children \u2014 pick one spacing system',
2193
+ 'Using `align="center"` and wondering why children expand to full width \u2014 that\'s the `stretch` default for cross-axis',
2194
+ "Nesting Stack inside Stack with the same gap when one Stack with two children would suffice"
2195
+ ],
2196
+ relatedComponents: ["cluster", "grid", "container"],
2197
+ accessibilityNotes: "Stack is presentational. Wrap stacked navigation links in a `<nav>`, stacked form fields in a `<form>`, etc. \u2014 Stack does not contribute landmark semantics.",
2198
+ tokenBudget: 250
2199
+ },
2200
+ tags: ["stack", "layout", "flex", "column", "vertical", "primitive"]
2201
+ };
2202
+
2203
+ // src/primitives/cluster/cluster.schema.ts
2204
+ var clusterSchema = {
2205
+ name: "cluster",
2206
+ displayName: "Cluster",
2207
+ description: "Horizontal flex flow with wrap and token-bound gap. Use for tag lists, button rows, breadcrumbs, and any group of equally-weighted inline items.",
2208
+ category: "primitive",
2209
+ subcategory: "layout",
2210
+ props: [
2211
+ {
2212
+ name: "gap",
2213
+ type: "enum",
2214
+ required: false,
2215
+ default: "md",
2216
+ description: "Gap between items, bound to `--gap-*` tokens. Applies to both row and column gaps when wrapping.",
2217
+ enumValues: ["xs", "sm", "md", "lg", "xl"]
2218
+ },
2219
+ {
2220
+ name: "align",
2221
+ type: "enum",
2222
+ required: false,
2223
+ default: "center",
2224
+ description: "Cross-axis alignment (CSS `align-items`). `baseline` aligns text-baselines for mixed-size siblings; `stretch` makes items fill row height (useful for wrap-card layouts).",
2225
+ enumValues: ["start", "center", "end", "stretch", "baseline"]
2226
+ },
2227
+ {
2228
+ name: "justify",
2229
+ type: "enum",
2230
+ required: false,
2231
+ default: "start",
2232
+ description: "Main-axis distribution (CSS `justify-content`).",
2233
+ enumValues: ["start", "center", "end", "between"]
2234
+ }
2235
+ ],
2236
+ variants: [
2237
+ {
2238
+ name: "gap",
2239
+ description: "Gap between items, bound to `--gap-*` tokens (applies to row + column gaps when wrapping).",
2240
+ values: [
2241
+ { value: "xs", description: "0.25rem \u2014 barely-there spacing." },
2242
+ { value: "sm", description: "0.5rem \u2014 tight chip group." },
2243
+ { value: "md", description: "1rem \u2014 default; standard rhythm." },
2244
+ { value: "lg", description: "1.5rem \u2014 generous separation." },
2245
+ { value: "xl", description: "2rem \u2014 section-scale spacing." }
2246
+ ],
2247
+ default: "md"
2248
+ },
2249
+ {
2250
+ name: "align",
2251
+ description: "Cross-axis alignment (CSS `align-items`).",
2252
+ values: [
2253
+ { value: "start", description: "Items align to top of row." },
2254
+ { value: "center", description: "Default \u2014 items center vertically in the row." },
2255
+ { value: "end", description: "Items align to bottom of row." },
2256
+ { value: "stretch", description: "Items fill row height \u2014 use for wrap layouts of equal-height cards." },
2257
+ { value: "baseline", description: "Text-baselines align across mixed-size siblings." }
2258
+ ],
2259
+ default: "center"
2260
+ },
2261
+ {
2262
+ name: "justify",
2263
+ description: "Main-axis distribution (CSS `justify-content`).",
2264
+ values: [
2265
+ { value: "start", description: "Default \u2014 items pack to start of row." },
2266
+ { value: "center", description: "Items center horizontally." },
2267
+ { value: "end", description: "Items pack to end of row." },
2268
+ { value: "between", description: "First item flush left, last flush right, even distribution." }
2269
+ ],
2270
+ default: "start"
2271
+ }
2272
+ ],
2273
+ slots: [
2274
+ {
2275
+ name: "children",
2276
+ description: "Items to lay out horizontally with wrap.",
2277
+ required: true,
2278
+ acceptedTypes: ["ReactNode"]
2279
+ }
2280
+ ],
2281
+ dependencies: {
2282
+ npm: ["class-variance-authority"],
2283
+ internal: ["lib/utils"],
2284
+ peer: ["react"]
2285
+ },
2286
+ tokensUsed: ["--gap-xs", "--gap-sm", "--gap-md", "--gap-lg", "--gap-xl"],
2287
+ examples: [
2288
+ {
2289
+ title: "Tag chips",
2290
+ description: "Small gap, wraps when overflowing the row.",
2291
+ code: '<Cluster gap="sm">\n {tags.map((t) => <Badge key={t}>{t}</Badge>)}\n</Cluster>'
2292
+ },
2293
+ {
2294
+ title: "Action bar",
2295
+ description: "Right-aligned buttons inside a panel footer.",
2296
+ code: '<Cluster gap="sm" justify="end">\n <Button variant="ghost">Cancel</Button>\n <Button>Save</Button>\n</Cluster>'
2297
+ }
2298
+ ],
2299
+ ai: {
2300
+ whenToUse: "Use for any horizontal row of items that should wrap when space runs out: tag clouds, breadcrumbs, button rows, social-link icon strips, inline metadata badges. Pick `Cluster` over `flex` when you want predictable wrap + gap behavior.",
2301
+ whenNotToUse: "Don't use for two-element rows where you need precise positional control (left/right) \u2014 use `Stack` rotated or a flex with `justify-between` directly. Don't use for grid-aligned layouts where columns must line up across rows \u2014 use `Grid`.",
2302
+ commonMistakes: [
2303
+ "Forgetting that `Cluster` wraps \u2014 adding `flex-nowrap` defeats the purpose; if you don't want wrap, use a flex row or `Stack` rotated",
2304
+ "Setting `align=\"baseline\"` for icon+text rows where the icon's bbox doesn't have a baseline \u2014 use `center` instead",
2305
+ "Reaching for `Cluster` for tabular alignment \u2014 use `Grid` or a real `<table>` when columns must line up"
2306
+ ],
2307
+ relatedComponents: ["stack", "grid", "container"],
2308
+ accessibilityNotes: "Cluster is presentational. Lists of navigational items should be wrapped in `<nav>` (and ideally `<ul>` / `<li>`). Lists of tags can use a list element for screen-reader semantics; the visual wrap is independent.",
2309
+ tokenBudget: 250
2310
+ },
2311
+ tags: ["cluster", "layout", "flex", "wrap", "horizontal", "primitive"]
2312
+ };
2313
+
2314
+ // src/primitives/grid/grid.schema.ts
2315
+ var gridSchema = {
2316
+ name: "grid",
2317
+ displayName: "Grid",
2318
+ description: 'CSS grid with column-count presets and token-bound gap. Pass `cols="auto-fit"` + `minColWidth` for responsive grids without media queries.',
2319
+ category: "primitive",
2320
+ subcategory: "layout",
2321
+ props: [
2322
+ {
2323
+ name: "cols",
2324
+ type: "enum",
2325
+ required: false,
2326
+ default: "3",
2327
+ description: 'Number of fixed columns, or `"auto-fit"` for responsive tracks (use with `minColWidth`).',
2328
+ enumValues: ["1", "2", "3", "4", "6", "auto-fit"]
2329
+ },
2330
+ {
2331
+ name: "gap",
2332
+ type: "enum",
2333
+ required: false,
2334
+ default: "md",
2335
+ description: "Gap between cells, bound to `--gap-*` tokens.",
2336
+ enumValues: ["xs", "sm", "md", "lg", "xl"]
2337
+ },
2338
+ {
2339
+ name: "align",
2340
+ type: "enum",
2341
+ required: false,
2342
+ default: "stretch",
2343
+ description: "Cell vertical alignment within their grid row.",
2344
+ enumValues: ["start", "center", "end", "stretch"]
2345
+ },
2346
+ {
2347
+ name: "minColWidth",
2348
+ type: "string",
2349
+ required: false,
2350
+ default: "16rem",
2351
+ description: 'Min track size for `cols="auto-fit"` (e.g. `"20rem"`). Ignored when `cols` is a fixed integer.'
2352
+ }
2353
+ ],
2354
+ variants: [
2355
+ {
2356
+ name: "cols",
2357
+ description: "Column count or `auto-fit` mode.",
2358
+ values: [
2359
+ { value: "1", description: "Single column \u2014 items stack vertically inside the grid." },
2360
+ { value: "2", description: "Two even columns." },
2361
+ { value: "3", description: "Default \u2014 three even columns." },
2362
+ { value: "4", description: "Four even columns." },
2363
+ { value: "6", description: "Six even columns \u2014 dense layouts." },
2364
+ { value: "auto-fit", description: "Tracks repeat to fill width, never below `minColWidth`." }
2365
+ ],
2366
+ default: "3"
2367
+ },
2368
+ {
2369
+ name: "gap",
2370
+ description: "Gap between cells, bound to `--gap-*` tokens.",
2371
+ values: [
2372
+ { value: "xs", description: "0.25rem \u2014 minimal separation." },
2373
+ { value: "sm", description: "0.5rem \u2014 tight grid." },
2374
+ { value: "md", description: "1rem \u2014 default; standard rhythm." },
2375
+ { value: "lg", description: "1.5rem \u2014 generous separation." },
2376
+ { value: "xl", description: "2rem \u2014 major separation between cards." }
2377
+ ],
2378
+ default: "md"
2379
+ },
2380
+ {
2381
+ name: "align",
2382
+ description: "Cell vertical alignment within their grid row.",
2383
+ values: [
2384
+ { value: "start", description: "Cells align to top of row." },
2385
+ { value: "center", description: "Cells center vertically." },
2386
+ { value: "end", description: "Cells align to bottom of row." },
2387
+ { value: "stretch", description: "Default \u2014 cells fill row height." }
2388
+ ],
2389
+ default: "stretch"
2390
+ }
2391
+ ],
2392
+ slots: [
2393
+ {
2394
+ name: "children",
2395
+ description: "Grid cells \u2014 typically Cards, images, or any uniform block.",
2396
+ required: true,
2397
+ acceptedTypes: ["ReactNode"]
2398
+ }
2399
+ ],
2400
+ dependencies: {
2401
+ npm: ["class-variance-authority"],
2402
+ internal: ["lib/utils"],
2403
+ peer: ["react"]
2404
+ },
2405
+ tokensUsed: ["--gap-xs", "--gap-sm", "--gap-md", "--gap-lg", "--gap-xl"],
2406
+ examples: [
2407
+ {
2408
+ title: "Three-column card grid",
2409
+ description: "Fixed 3 columns with medium gap.",
2410
+ code: '<Grid cols={3} gap="md">\n {items.map((i) => <Card key={i.id}>{i.title}</Card>)}\n</Grid>'
2411
+ },
2412
+ {
2413
+ title: "Responsive auto-fit",
2414
+ description: "Tracks fit as many 20rem columns as the viewport allows; no media queries needed.",
2415
+ code: '<Grid cols="auto-fit" minColWidth="20rem" gap="lg">\n {items.map(...)}\n</Grid>'
2416
+ }
2417
+ ],
2418
+ ai: {
2419
+ whenToUse: 'Use for visually-aligned grids of similar items: card galleries, image walls, dashboard tiles, settings panels. Prefer `cols="auto-fit"` + `minColWidth` over hand-written breakpoints when the column count should adapt to viewport width.',
2420
+ whenNotToUse: "Don't use for one-dimensional flows \u2014 use `Stack` (vertical) or `Cluster` (horizontal). Don't use for tabular data \u2014 use `<table>` or DataTable. Don't fight `Grid` to make uneven columns; if cells need different sizes, use a flexbox layout or a CSS grid with named tracks directly.",
2421
+ commonMistakes: [
2422
+ 'Setting `cols={5}` and getting nothing \u2014 the preset only supports 1/2/3/4/6 (deliberate, to avoid odd visual rhythms); use `cols="auto-fit"` for arbitrary counts',
2423
+ 'Passing `cols="auto-fit"` without `minColWidth` \u2014 the default `16rem` may not match your design intent',
2424
+ "Using `Grid` for two children when `Cluster` or `Stack` would communicate intent better"
2425
+ ],
2426
+ relatedComponents: ["stack", "cluster", "container", "card"],
2427
+ accessibilityNotes: "Grid is presentational. If the grid renders a list of similar items, wrap in `<ul>`/`<li>` for screen-reader semantics \u2014 Grid only handles visual layout.",
2428
+ tokenBudget: 300
2429
+ },
2430
+ tags: ["grid", "layout", "css-grid", "responsive", "auto-fit", "primitive"]
2431
+ };
2432
+
2433
+ // src/primitives/spacer/spacer.schema.ts
2434
+ var spacerSchema = {
2435
+ name: "spacer",
2436
+ displayName: "Spacer",
2437
+ description: "Declarative whitespace block bound to `--space-*` tokens. Use when sibling spacing can't come from a parent's `gap`.",
2438
+ category: "primitive",
2439
+ subcategory: "layout",
2440
+ props: [
2441
+ {
2442
+ name: "size",
2443
+ type: "enum",
2444
+ required: false,
2445
+ default: "md",
2446
+ description: "Spacing token (xs=0.25rem, sm=0.5rem, md=1rem, lg=2rem, xl=4rem). Bound to `--space-*`.",
2447
+ enumValues: ["xs", "sm", "md", "lg", "xl"]
2448
+ },
2449
+ {
2450
+ name: "axis",
2451
+ type: "enum",
2452
+ required: false,
2453
+ default: "vertical",
2454
+ description: "Which axis to expand. Vertical = height; horizontal = width; both = square.",
2455
+ enumValues: ["vertical", "horizontal", "both"]
2456
+ }
2457
+ ],
2458
+ variants: [
2459
+ {
2460
+ name: "size",
2461
+ description: "Spacer extent bound to `--space-*` tokens.",
2462
+ values: [
2463
+ { value: "xs", description: "0.25rem \u2014 micro-gap." },
2464
+ { value: "sm", description: "0.5rem \u2014 tight separation." },
2465
+ { value: "md", description: "1rem \u2014 default; standard breathing room." },
2466
+ { value: "lg", description: "2rem \u2014 section-scale separation." },
2467
+ { value: "xl", description: "4rem \u2014 major separation between hero areas." }
2468
+ ],
2469
+ default: "md"
2470
+ },
2471
+ {
2472
+ name: "axis",
2473
+ description: "Which axis the spacer extends along.",
2474
+ values: [
2475
+ { value: "vertical", description: "Default \u2014 fixed height, 1px width." },
2476
+ { value: "horizontal", description: "Fixed width, 1px height \u2014 for flex rows." },
2477
+ { value: "both", description: "Square block \u2014 rare; usually pick one axis." }
2478
+ ],
2479
+ default: "vertical"
2480
+ }
2481
+ ],
2482
+ slots: [],
2483
+ dependencies: {
2484
+ npm: ["class-variance-authority"],
2485
+ internal: ["lib/utils"],
2486
+ peer: ["react"]
2487
+ },
2488
+ tokensUsed: ["--space-1", "--space-2", "--space-4", "--space-8", "--space-16"],
2489
+ examples: [
2490
+ {
2491
+ title: "Vertical breathing room",
2492
+ description: "Push two sections apart inside a parent that doesn't manage gap.",
2493
+ code: '<>\n <Hero />\n <Spacer size="xl" />\n <Features />\n</>'
2494
+ },
2495
+ {
2496
+ title: "Horizontal flex spacer",
2497
+ description: "Push siblings to opposite ends of a flex row.",
2498
+ code: '<div className="flex items-center">\n <Logo />\n <Spacer axis="horizontal" size="lg" />\n <Nav />\n</div>'
2499
+ }
2500
+ ],
2501
+ ai: {
2502
+ whenToUse: "Use as an explicit whitespace primitive when you can't (or don't want to) use `gap` on the parent \u2014 typically: pushing siblings apart inside a flex row that already has gap-0, or inserting one-off vertical breathing room between top-level sections that aren't wrapped in a Stack.",
2503
+ whenNotToUse: "Don't use Spacer when a `Stack` or `Cluster` parent's `gap` would do the same thing \u2014 that scales better and keeps the spacing concern with the layout primitive. Don't use Spacer to flush content to one edge of a flex container \u2014 use `ml-auto` / `justify-between` directly.",
2504
+ commonMistakes: [
2505
+ "Using `<Spacer />` between every child in a Stack \u2014 just set the Stack's `gap` instead",
2506
+ 'Using `axis="both"` and getting a square block where you wanted a line \u2014 `both` is rare, usually you want vertical or horizontal',
2507
+ `Setting size="lg" on a horizontal spacer and getting nothing visible because the parent isn't a flex row \u2014 Spacer reserves space, it doesn't push siblings on its own`
2508
+ ],
2509
+ relatedComponents: ["stack", "cluster", "separator"],
2510
+ accessibilityNotes: 'Spacer is `aria-hidden="true"` \u2014 screen readers skip it. Don\'t use a Spacer where a `Separator` would convey meaning (a thematic break should be `Separator`, not `Spacer`).',
2511
+ tokenBudget: 200
2512
+ },
2513
+ tags: ["spacer", "layout", "whitespace", "spacing", "primitive"]
2514
+ };
2515
+
2516
+ // src/components/collapsible/collapsible.schema.ts
2517
+ var collapsibleSchema = {
2518
+ name: "collapsible",
2519
+ displayName: "Collapsible",
2520
+ description: "A single section that can be expanded or collapsed. For multiple independent sections use Accordion with type='multiple'.",
2521
+ category: "component",
2522
+ subcategory: "disclosure",
2523
+ props: [
2524
+ { name: "open", type: "boolean", required: false, description: "Controlled open state" },
2525
+ { name: "defaultOpen", type: "boolean", required: false, default: false, description: "Default open state" },
2526
+ { name: "onOpenChange", type: "function", required: false, description: "Callback on open change: (open: boolean) => void" },
2527
+ { name: "disabled", type: "boolean", required: false, default: false, description: "Disable toggling" }
2528
+ ],
2529
+ variants: [],
2530
+ slots: [
2531
+ { name: "children", description: "CollapsibleTrigger + CollapsibleContent", required: true, acceptedTypes: ["ReactNode"] }
2532
+ ],
2533
+ dependencies: {
2534
+ npm: ["@radix-ui/react-collapsible"],
2535
+ internal: [],
2536
+ peer: ["react", "react-dom"]
2537
+ },
2538
+ tokensUsed: [],
2539
+ examples: [
2540
+ {
2541
+ title: "Show more",
2542
+ description: "Expand additional content below a preview",
2543
+ code: '<Collapsible>\n <div>Yesterday at 9:00 AM</div>\n <CollapsibleTrigger asChild>\n <Button variant="ghost" size="sm">Toggle</Button>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div>Additional details here</div>\n </CollapsibleContent>\n</Collapsible>'
2544
+ }
2545
+ ],
2546
+ ai: {
2547
+ whenToUse: "Use for a single show-more/show-less section: 'View full details', 'Advanced settings', preview cards with expandable notes.",
2548
+ whenNotToUse: "Don't use for multiple related sections (use Accordion). Don't use for overlays (use Dialog/Popover). Don't use for navigation (use DropdownMenu).",
2549
+ commonMistakes: [
2550
+ "Using Collapsible for multiple sections instead of Accordion",
2551
+ "Missing asChild when passing a Button as trigger",
2552
+ "Not animating content height (Radix exposes --radix-collapsible-content-height for CSS keyframes)"
2553
+ ],
2554
+ relatedComponents: ["accordion", "dropdown-menu"],
2555
+ accessibilityNotes: "Radix sets aria-expanded on the trigger and aria-controls \u2192 content id. Trigger is keyboard-operable (Enter/Space).",
2556
+ tokenBudget: 250
2557
+ },
2558
+ tags: ["collapsible", "disclosure", "expand", "show-more"]
2559
+ };
2560
+
2561
+ // src/components/hover-card/hover-card.schema.ts
2562
+ var hoverCardSchema = {
2563
+ name: "hover-card",
2564
+ displayName: "Hover Card",
2565
+ description: "Rich floating content revealed on hover or focus. Use when Tooltip is too small and Popover requires a click.",
2566
+ category: "component",
2567
+ subcategory: "overlay",
2568
+ props: [
2569
+ { name: "open", type: "boolean", required: false, description: "Controlled open state" },
2570
+ { name: "defaultOpen", type: "boolean", required: false, default: false, description: "Default open state" },
2571
+ { name: "onOpenChange", type: "function", required: false, description: "Callback on open change" },
2572
+ { name: "openDelay", type: "number", required: false, default: 700, description: "Milliseconds before the card appears" },
2573
+ { name: "closeDelay", type: "number", required: false, default: 300, description: "Milliseconds before the card closes after leaving" }
2574
+ ],
2575
+ variants: [],
2576
+ slots: [
2577
+ { name: "children", description: "HoverCardTrigger + HoverCardContent", required: true, acceptedTypes: ["ReactNode"] }
2578
+ ],
2579
+ dependencies: {
2580
+ npm: ["@radix-ui/react-hover-card", "clsx", "tailwind-merge"],
2581
+ internal: ["lib/utils"],
2582
+ peer: ["react", "react-dom"]
2583
+ },
2584
+ tokensUsed: ["popover", "popover-foreground", "border"],
2585
+ examples: [
2586
+ {
2587
+ title: "User profile preview",
2588
+ description: "Username link that expands into a mini profile on hover",
2589
+ code: '<HoverCard>\n <HoverCardTrigger asChild>\n <a href="#">@shadcn</a>\n </HoverCardTrigger>\n <HoverCardContent>\n <div className="flex gap-3">\n <Avatar><AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /><AvatarFallback>CN</AvatarFallback></Avatar>\n <div>\n <h4 className="font-semibold">@shadcn</h4>\n <p className="text-xs text-muted-foreground">Builds UI components</p>\n </div>\n </div>\n </HoverCardContent>\n</HoverCard>'
2590
+ }
2591
+ ],
2592
+ ai: {
2593
+ whenToUse: "Use for rich hover previews: user profile cards, link previews, inline references. Contains multiple elements \u2014 more than a tooltip can hold.",
2594
+ whenNotToUse: "Don't use for simple hover labels (use Tooltip). Don't use for click-triggered content (use Popover). Don't use as primary info on touch devices \u2014 hover doesn't exist there.",
2595
+ commonMistakes: [
2596
+ "Using HoverCard for critical info (invisible on touch)",
2597
+ "Too-short openDelay causes flicker on mouse-over traffic",
2598
+ "Omitting asChild on HoverCardTrigger with a custom element"
2599
+ ],
2600
+ relatedComponents: ["tooltip", "popover"],
2601
+ accessibilityNotes: "Radix opens on hover and keyboard focus. Content must be meaningful on focus as well as hover. Consider an alternative for touch users.",
2602
+ tokenBudget: 400
2603
+ },
2604
+ tags: ["hover-card", "preview", "overlay", "rich-tooltip"]
2605
+ };
2606
+
2607
+ // src/components/context-menu/context-menu.schema.ts
2608
+ var contextMenuSchema = {
2609
+ name: "context-menu",
2610
+ displayName: "Context Menu",
2611
+ description: "Right-click (or long-press on touch) menu anchored to the trigger region. Same item vocabulary as DropdownMenu.",
2612
+ category: "component",
2613
+ subcategory: "overlay",
2614
+ props: [
2615
+ { name: "onOpenChange", type: "function", required: false, description: "Callback on open change" },
2616
+ { name: "modal", type: "boolean", required: false, default: true, description: "When true, interaction outside is blocked" },
2617
+ { name: "dir", type: "enum", required: false, description: "Reading direction", enumValues: ["ltr", "rtl"] }
2618
+ ],
2619
+ variants: [],
2620
+ slots: [
2621
+ {
2622
+ name: "children",
2623
+ description: "ContextMenuTrigger + ContextMenuContent. Content accepts ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioGroup/ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, and ContextMenuShortcut.",
2624
+ required: true,
2625
+ acceptedTypes: ["ReactNode"]
2626
+ }
2627
+ ],
2628
+ dependencies: {
2629
+ npm: ["@radix-ui/react-context-menu", "clsx", "tailwind-merge"],
2630
+ internal: ["lib/utils"],
2631
+ peer: ["react", "react-dom"]
2632
+ },
2633
+ tokensUsed: ["popover", "popover-foreground", "accent", "accent-foreground", "border", "foreground", "muted-foreground"],
2634
+ examples: [
2635
+ {
2636
+ title: "Right-click menu",
2637
+ description: "Right-click the trigger region for actions",
2638
+ code: '<ContextMenu>\n <ContextMenuTrigger className="flex h-40 items-center justify-center rounded-md border border-dashed">Right-click here</ContextMenuTrigger>\n <ContextMenuContent>\n <ContextMenuItem>Back</ContextMenuItem>\n <ContextMenuItem disabled>Forward</ContextMenuItem>\n <ContextMenuSeparator />\n <ContextMenuItem>Reload<ContextMenuShortcut>\u2318R</ContextMenuShortcut></ContextMenuItem>\n </ContextMenuContent>\n</ContextMenu>'
2639
+ }
2640
+ ],
2641
+ ai: {
2642
+ whenToUse: "Use for right-click menus on a specific region: file-manager-style actions, canvas/editor context actions, row-level actions in tables.",
2643
+ whenNotToUse: "Don't use for actions triggered by a button (use DropdownMenu). Don't use as the only way to access an action \u2014 must have a keyboard/button alternative.",
2644
+ commonMistakes: [
2645
+ "Using ContextMenu as the only affordance (unreachable on touch)",
2646
+ "Triggering on the whole document (put it on a specific region)",
2647
+ "Missing a keyboard alternative for items"
2648
+ ],
2649
+ relatedComponents: ["dropdown-menu", "menubar"],
2650
+ accessibilityNotes: "Triggered via right-click or Shift+F10 on keyboard. Radix handles role='menu', aria-labelledby, focus management.",
2651
+ tokenBudget: 700
2652
+ },
2653
+ tags: ["context-menu", "right-click", "menu", "actions"]
2654
+ };
2655
+
2656
+ // src/components/menubar/menubar.schema.ts
2657
+ var menubarSchema = {
2658
+ name: "menubar",
2659
+ displayName: "Menubar",
2660
+ description: "Desktop-app style menu bar (File / Edit / View). Horizontal menu strip with nested dropdowns.",
2661
+ category: "component",
2662
+ subcategory: "navigation",
2663
+ props: [
2664
+ { name: "value", type: "string", required: false, description: "Controlled open menu id" },
2665
+ { name: "defaultValue", type: "string", required: false, description: "Default open menu for uncontrolled usage" },
2666
+ { name: "onValueChange", type: "function", required: false, description: "Callback when open menu changes" },
2667
+ { name: "loop", type: "boolean", required: false, default: true, description: "When true, arrow-key navigation wraps" },
2668
+ { name: "dir", type: "enum", required: false, description: "Reading direction", enumValues: ["ltr", "rtl"] },
2669
+ { name: "className", type: "string", required: false, description: "Additional CSS classes on the Menubar root" }
2670
+ ],
2671
+ variants: [],
2672
+ slots: [
2673
+ { name: "children", description: "MenubarMenu elements (each containing MenubarTrigger + MenubarContent)", required: true, acceptedTypes: ["ReactNode"] }
2674
+ ],
2675
+ dependencies: {
2676
+ npm: ["@radix-ui/react-menubar", "clsx", "tailwind-merge"],
2677
+ internal: ["lib/utils"],
2678
+ peer: ["react", "react-dom"]
2679
+ },
2680
+ tokensUsed: ["background", "popover", "popover-foreground", "accent", "accent-foreground", "muted", "muted-foreground"],
2681
+ examples: [
2682
+ {
2683
+ title: "File / Edit bar",
2684
+ description: "Two-menu bar with keyboard shortcuts",
2685
+ code: "<Menubar>\n <MenubarMenu>\n <MenubarTrigger>File</MenubarTrigger>\n <MenubarContent>\n <MenubarItem>New Tab<MenubarShortcut>\u2318T</MenubarShortcut></MenubarItem>\n <MenubarItem>New Window<MenubarShortcut>\u2318N</MenubarShortcut></MenubarItem>\n <MenubarSeparator />\n <MenubarItem>Share</MenubarItem>\n <MenubarSeparator />\n <MenubarItem>Print\u2026<MenubarShortcut>\u2318P</MenubarShortcut></MenubarItem>\n </MenubarContent>\n </MenubarMenu>\n <MenubarMenu>\n <MenubarTrigger>Edit</MenubarTrigger>\n <MenubarContent>\n <MenubarItem>Undo<MenubarShortcut>\u2318Z</MenubarShortcut></MenubarItem>\n <MenubarItem>Redo<MenubarShortcut>\u21E7\u2318Z</MenubarShortcut></MenubarItem>\n </MenubarContent>\n </MenubarMenu>\n</Menubar>"
2686
+ }
2687
+ ],
2688
+ ai: {
2689
+ whenToUse: "Use for desktop-app shell menus: editors, IDEs, creative tools. Provides persistent menu bar with keyboard shortcuts.",
2690
+ whenNotToUse: "Don't use for website navigation (use NavigationMenu). Don't use for single-button menus (use DropdownMenu). Poor fit for mobile \u2014 consider a hamburger menu.",
2691
+ commonMistakes: [
2692
+ "Using for website navigation (user expectations don't match)",
2693
+ "Missing shortcuts (expected affordance in menubar UX)",
2694
+ "Deeply nested sub-menus (>2 levels feels labyrinthine)"
2695
+ ],
2696
+ relatedComponents: ["navigation-menu", "dropdown-menu"],
2697
+ accessibilityNotes: "Full WAI-ARIA menubar pattern: arrow keys navigate menus, Enter/Space opens, Escape closes. Radix handles roles and state.",
2698
+ tokenBudget: 700
2699
+ },
2700
+ tags: ["menubar", "menu", "desktop", "app-shell", "navigation"]
2701
+ };
2702
+
2703
+ // src/components/navigation-menu/navigation-menu.schema.ts
2704
+ var navigationMenuSchema = {
2705
+ name: "navigation-menu",
2706
+ displayName: "Navigation Menu",
2707
+ description: "Website-style mega-menu with hover-triggered content panels. Used for marketing/site navigation headers.",
2708
+ category: "component",
2709
+ subcategory: "navigation",
2710
+ props: [
2711
+ { name: "value", type: "string", required: false, description: "Controlled active menu value" },
2712
+ { name: "onValueChange", type: "function", required: false, description: "Callback when active menu changes" },
2713
+ { name: "delayDuration", type: "number", required: false, default: 200, description: "Delay before opening a menu on hover (ms)" },
2714
+ { name: "orientation", type: "enum", required: false, default: "horizontal", description: "Layout direction", enumValues: ["horizontal", "vertical"] }
2715
+ ],
2716
+ variants: [],
2717
+ slots: [
2718
+ { name: "children", description: "NavigationMenuList containing NavigationMenuItem elements", required: true, acceptedTypes: ["ReactNode"] }
2719
+ ],
2720
+ dependencies: {
2721
+ npm: ["@radix-ui/react-navigation-menu", "class-variance-authority", "clsx", "tailwind-merge"],
2722
+ internal: ["lib/utils"],
2723
+ peer: ["react", "react-dom"]
2724
+ },
2725
+ tokensUsed: ["background", "accent", "accent-foreground", "popover", "popover-foreground", "border"],
2726
+ examples: [
2727
+ {
2728
+ title: "Simple nav",
2729
+ description: "Top-level link + mega-menu trigger",
2730
+ code: '<NavigationMenu>\n <NavigationMenuList>\n <NavigationMenuItem>\n <NavigationMenuTrigger>Products</NavigationMenuTrigger>\n <NavigationMenuContent>\n <ul className="grid w-[400px] gap-3 p-4">\n <li><NavigationMenuLink href="/docs">Docs</NavigationMenuLink></li>\n <li><NavigationMenuLink href="/pricing">Pricing</NavigationMenuLink></li>\n </ul>\n </NavigationMenuContent>\n </NavigationMenuItem>\n <NavigationMenuItem>\n <NavigationMenuLink className={navigationMenuTriggerStyle()} href="/about">About</NavigationMenuLink>\n </NavigationMenuItem>\n </NavigationMenuList>\n</NavigationMenu>'
2731
+ }
2732
+ ],
2733
+ ai: {
2734
+ whenToUse: "Use for marketing-site top nav with grouped links and mega-menus: Products, Resources, Pricing flyouts. Desktop-first but keyboard accessible.",
2735
+ whenNotToUse: "Don't use for app shell menus (use Menubar). Don't use for single dropdowns (use DropdownMenu). On mobile, pair with a separate hamburger/Drawer pattern \u2014 NavigationMenu collapses poorly on small screens.",
2736
+ commonMistakes: [
2737
+ "Mixing regular <a> with NavigationMenuLink \u2014 must use NavigationMenuLink for keyboard/roving focus",
2738
+ "Forgetting the viewport \u2014 handled automatically when using the composed NavigationMenu root",
2739
+ "Too many top-level items overflow on mobile"
2740
+ ],
2741
+ relatedComponents: ["menubar", "dropdown-menu"],
2742
+ accessibilityNotes: "Radix implements the WAI-ARIA menu-button pattern with hover-intent delays and focus trapping in content. Links inside NavigationMenuLink get roving tabindex.",
2743
+ tokenBudget: 800
2744
+ },
2745
+ tags: ["navigation-menu", "mega-menu", "nav", "header", "site"]
2746
+ };
2747
+
2748
+ // src/components/breadcrumb/breadcrumb.schema.ts
2749
+ var breadcrumbSchema = {
2750
+ name: "breadcrumb",
2751
+ displayName: "Breadcrumb",
2752
+ description: "A path trail showing the user's location within a hierarchy, with links back to ancestors and a non-interactive current page.",
2753
+ category: "component",
2754
+ subcategory: "navigation",
2755
+ props: [
2756
+ { name: "className", type: "string", required: false, description: "Additional CSS classes on the nav element" }
2757
+ ],
2758
+ variants: [],
2759
+ slots: [
2760
+ { name: "children", description: "BreadcrumbList containing BreadcrumbItem + BreadcrumbSeparator elements", required: true, acceptedTypes: ["ReactNode"] }
2761
+ ],
2762
+ dependencies: {
2763
+ npm: ["@radix-ui/react-slot", "clsx", "tailwind-merge"],
2764
+ internal: ["lib/utils"],
2765
+ peer: ["react", "react-dom"]
2766
+ },
2767
+ tokensUsed: ["foreground", "muted-foreground"],
2768
+ examples: [
2769
+ {
2770
+ title: "Three-level path",
2771
+ description: "Home / Components / Breadcrumb",
2772
+ code: '<Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href="/">Home</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbLink href="/docs/components">Components</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n</Breadcrumb>'
2773
+ }
2774
+ ],
2775
+ ai: {
2776
+ whenToUse: "Use to show location within a hierarchical site or app: docs pages, product categories, nested settings. Include the current page as a non-link BreadcrumbPage.",
2777
+ whenNotToUse: "Don't use for primary navigation (use NavigationMenu). Don't use for flat sites without hierarchy. Don't use when the hierarchy is too deep to display \u2014 truncate with BreadcrumbEllipsis.",
2778
+ commonMistakes: [
2779
+ "Making the current page a link (use BreadcrumbPage)",
2780
+ "Showing just one item (defeats the purpose)",
2781
+ "Using plain text separators without aria-hidden"
2782
+ ],
2783
+ relatedComponents: ["navigation-menu"],
2784
+ accessibilityNotes: "Root <nav aria-label='breadcrumb'> creates a landmark. BreadcrumbPage has aria-current='page'. Separators are aria-hidden (decorative). BreadcrumbEllipsis is decorative (SVG aria-hidden) with a sr-only 'More pages' label.",
2785
+ tokenBudget: 400
2786
+ },
2787
+ tags: ["breadcrumb", "navigation", "path", "trail", "hierarchy"]
2788
+ };
2789
+
2790
+ // src/components/alert/alert.schema.ts
2791
+ var alertSchema = {
2792
+ name: "alert",
2793
+ displayName: "Alert",
2794
+ description: "An inline notification banner for important messages. Supports default and destructive variants with optional leading icon.",
2795
+ category: "component",
2796
+ subcategory: "feedback",
2797
+ props: [
2798
+ {
2799
+ name: "variant",
2800
+ type: "enum",
2801
+ required: false,
2802
+ default: "default",
2803
+ description: "Visual style",
2804
+ enumValues: ["default", "destructive"]
2805
+ },
2806
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
2807
+ ],
2808
+ variants: [
2809
+ {
2810
+ name: "variant",
2811
+ description: "Alert style",
2812
+ values: [
2813
+ { value: "default", description: "Neutral inline notification" },
2814
+ { value: "destructive", description: "Error or warning with red accent + tinted background" }
2815
+ ],
2816
+ default: "default"
2817
+ }
2818
+ ],
2819
+ slots: [
2820
+ {
2821
+ name: "children",
2822
+ description: "Optional icon SVG + AlertTitle + AlertDescription",
2823
+ required: true,
2824
+ acceptedTypes: ["ReactNode"]
2825
+ }
2826
+ ],
2827
+ dependencies: {
2828
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
2829
+ internal: ["lib/utils"],
2830
+ peer: ["react", "react-dom"]
2831
+ },
2832
+ tokensUsed: ["background", "foreground", "destructive", "border"],
2833
+ examples: [
2834
+ {
2835
+ title: "Info alert",
2836
+ description: "Default alert with title and description",
2837
+ code: "<Alert>\n <AlertTitle>Heads up!</AlertTitle>\n <AlertDescription>You can add components to your app via the CLI.</AlertDescription>\n</Alert>"
2838
+ },
2839
+ {
2840
+ title: "Destructive alert",
2841
+ description: "Error alert",
2842
+ code: '<Alert variant="destructive">\n <AlertTitle>Error</AlertTitle>\n <AlertDescription>Your session has expired. Please sign in again.</AlertDescription>\n</Alert>'
2843
+ }
2844
+ ],
2845
+ ai: {
2846
+ whenToUse: "Use for inline, persistent messages that contextualize a page or section: info banners, warning about deprecated features, error summaries above forms.",
2847
+ whenNotToUse: "Don't use for transient messages (use Toast/Sonner). Don't use for modal confirmations (use AlertDialog). Don't use as the only way to communicate a critical error \u2014 pair with field-level feedback.",
2848
+ commonMistakes: [
2849
+ "Using destructive for non-error messages",
2850
+ "Missing AlertTitle (reduces scannability)",
2851
+ "Stacking multiple Alerts on the same page instead of grouping"
2852
+ ],
2853
+ relatedComponents: ["alert-dialog", "sonner"],
2854
+ accessibilityNotes: "Root renders role='alert' so screen readers announce it. For non-urgent info banners consider role='status' or aria-live='polite' via className overrides.",
2855
+ tokenBudget: 350
2856
+ },
2857
+ tags: ["alert", "notification", "banner", "info", "warning", "error"]
2858
+ };
2859
+
2860
+ // src/components/sonner/sonner.schema.ts
2861
+ var sonnerSchema = {
2862
+ name: "sonner",
2863
+ displayName: "Sonner (Toast)",
2864
+ description: "Ephemeral toast notifications via Sonner. Render <Toaster /> once at app root, then call toast() anywhere.",
2865
+ category: "component",
2866
+ subcategory: "feedback",
2867
+ props: [
2868
+ {
2869
+ name: "position",
2870
+ type: "enum",
2871
+ required: false,
2872
+ default: "bottom-right",
2873
+ description: "Where toasts appear on screen",
2874
+ enumValues: [
2875
+ "top-left",
2876
+ "top-center",
2877
+ "top-right",
2878
+ "bottom-left",
2879
+ "bottom-center",
2880
+ "bottom-right"
2881
+ ]
2882
+ },
2883
+ {
2884
+ name: "richColors",
2885
+ type: "boolean",
2886
+ required: false,
2887
+ default: false,
2888
+ description: "Enable success/error/warning color variants via toast.success/error/warning"
2889
+ },
2890
+ {
2891
+ name: "closeButton",
2892
+ type: "boolean",
2893
+ required: false,
2894
+ default: false,
2895
+ description: "Show a close button on each toast"
2896
+ },
2897
+ {
2898
+ name: "theme",
2899
+ type: "enum",
2900
+ required: false,
2901
+ default: "system",
2902
+ description: "Visual theme",
2903
+ enumValues: ["light", "dark", "system"]
2904
+ }
2905
+ ],
2906
+ variants: [],
2907
+ slots: [],
2908
+ dependencies: {
2909
+ npm: ["sonner"],
2910
+ internal: [],
2911
+ peer: ["react", "react-dom"]
2912
+ },
2913
+ tokensUsed: ["background", "foreground", "border", "muted", "muted-foreground", "primary", "primary-foreground"],
2914
+ examples: [
2915
+ {
2916
+ title: "App setup + fire toast",
2917
+ description: "Render Toaster once, call toast() anywhere",
2918
+ code: '// In your root layout:\n<Toaster />\n\n// Anywhere in your app:\nimport { toast } from "@/components/ui/sonner";\n\n<Button onClick={() => toast("Event created", { description: "Friday, Dec 11 at 10:00 AM" })}>\n Show toast\n</Button>'
2919
+ }
2920
+ ],
2921
+ ai: {
2922
+ whenToUse: "Use for transient feedback: save confirmations, error messages, background task completion. Pairs well with mutation handlers (onSuccess/onError).",
2923
+ whenNotToUse: "Don't use for persistent info (use Alert). Don't use for destructive confirmations (use AlertDialog). Don't use for critical errors that block user workflow.",
2924
+ commonMistakes: [
2925
+ "Rendering multiple <Toaster /> components (one is enough)",
2926
+ "Calling toast() during server rendering (must be client-side)",
2927
+ "Using toast for messages the user needs to re-read (they auto-dismiss)"
2928
+ ],
2929
+ relatedComponents: ["alert", "alert-dialog"],
2930
+ accessibilityNotes: "Sonner handles aria-live='polite' on the toast region so screen readers announce new toasts. Critical messages should still use Alert/AlertDialog for persistent visibility.",
2931
+ tokenBudget: 450
2932
+ },
2933
+ tags: ["toast", "sonner", "notification", "transient", "feedback"]
2934
+ };
2935
+
2936
+ // src/components/table/table.schema.ts
2937
+ var tableSchema = {
2938
+ name: "table",
2939
+ displayName: "Table",
2940
+ description: "Styled HTML table primitives (Table / TableHeader / TableBody / TableRow / TableHead / TableCell / TableCaption / TableFooter). Low-level building blocks \u2014 use DataTable for sorting/filtering/pagination.",
2941
+ category: "component",
2942
+ subcategory: "data",
2943
+ props: [
2944
+ { name: "className", type: "string", required: false, description: "Additional CSS classes on the <table>" }
2945
+ ],
2946
+ variants: [],
2947
+ slots: [
2948
+ {
2949
+ name: "children",
2950
+ description: "TableHeader + TableBody + TableFooter + TableCaption. Use TableRow/TableHead/TableCell inside.",
2951
+ required: true,
2952
+ acceptedTypes: ["ReactNode"]
2953
+ }
2954
+ ],
2955
+ dependencies: {
2956
+ npm: ["clsx", "tailwind-merge"],
2957
+ internal: ["lib/utils"],
2958
+ peer: ["react", "react-dom"]
2959
+ },
2960
+ tokensUsed: ["muted", "muted-foreground", "border"],
2961
+ examples: [
2962
+ {
2963
+ title: "Basic table",
2964
+ description: "Three-column styled table with header + rows",
2965
+ code: '<Table>\n <TableCaption>A list of your recent invoices.</TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead>Invoice</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className="text-right">Amount</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n <TableRow>\n <TableCell className="font-medium">INV001</TableCell>\n <TableCell>Paid</TableCell>\n <TableCell className="text-right">$250.00</TableCell>\n </TableRow>\n </TableBody>\n</Table>'
2966
+ }
2967
+ ],
2968
+ ai: {
2969
+ whenToUse: "Use for simple tabular data where you render rows manually: invoice lists, pricing rows, settings tables. Responsive container wraps the <table> to allow horizontal scroll on small screens.",
2970
+ whenNotToUse: "Don't use for large datasets that need sorting/filtering/pagination (use DataTable). Don't use for layout (use CSS grid/flex). Don't use for <form> field arrays (use native fields).",
2971
+ commonMistakes: [
2972
+ "Using <div> grids instead of a real <table> for tabular data (breaks a11y)",
2973
+ "Putting interactive controls in headers without keyboard semantics",
2974
+ "Missing TableCaption when the table has no other label"
2975
+ ],
2976
+ relatedComponents: ["data-table", "pagination"],
2977
+ accessibilityNotes: "Semantic <table> / <thead> / <tbody> is used, so screen readers announce rows/columns. Include a TableCaption or aria-label. Mark column sort buttons with aria-sort.",
2978
+ tokenBudget: 450
2979
+ },
2980
+ tags: ["table", "data", "rows", "tabular"]
2981
+ };
2982
+
2983
+ // src/components/data-table/data-table.schema.ts
2984
+ var dataTableSchema = {
2985
+ name: "data-table",
2986
+ displayName: "Data Table",
2987
+ description: "Generic data-driven table built on TanStack Table + Hex UI Table primitives. Pass columns + data; add sorting/filtering/pagination via TanStack hooks.",
2988
+ category: "component",
2989
+ subcategory: "data",
2990
+ props: [
2991
+ { name: "columns", type: "object", required: true, description: "ColumnDef<TData, TValue>[] from @tanstack/react-table" },
2992
+ { name: "data", type: "object", required: true, description: "Array of row data" },
2993
+ {
2994
+ name: "caption",
2995
+ type: "ReactNode",
2996
+ required: false,
2997
+ description: "Visible caption rendered below the table; announced by screen readers when entering the table"
2998
+ },
2999
+ {
3000
+ name: "aria-label",
3001
+ type: "string",
3002
+ required: false,
3003
+ description: "Accessible label forwarded as aria-label on the underlying <table>; use when no visible caption is shown"
3004
+ }
3005
+ ],
3006
+ variants: [],
3007
+ slots: [],
3008
+ dependencies: {
3009
+ npm: ["@tanstack/react-table", "clsx", "tailwind-merge"],
3010
+ internal: ["lib/utils", "components/table/table"],
3011
+ peer: ["react", "react-dom"]
3012
+ },
3013
+ tokensUsed: ["border", "muted", "muted-foreground"],
3014
+ examples: [
3015
+ {
3016
+ title: "Basic data table",
3017
+ description: "Three-column table rendered from TanStack column defs",
3018
+ code: 'import type { ColumnDef } from "@tanstack/react-table";\nimport { DataTable } from "@/components/ui/data-table";\n\ntype Payment = { id: string; amount: number; status: "pending" | "paid" | "failed"; email: string };\n\nconst columns: ColumnDef<Payment>[] = [\n { accessorKey: "status", header: "Status" },\n { accessorKey: "email", header: "Email" },\n { accessorKey: "amount", header: "Amount" },\n];\n\nconst data: Payment[] = [\n { id: "1", amount: 100, status: "paid", email: "a@x.com" },\n { id: "2", amount: 250, status: "pending", email: "b@x.com" },\n];\n\nexport function Example() {\n return <DataTable columns={columns} data={data} />;\n}'
3019
+ }
3020
+ ],
3021
+ ai: {
3022
+ whenToUse: "Use for tabular data that needs sorting, filtering, pagination, or row selection. Define columns once, feed data \u2014 TanStack handles the row model. Add more features incrementally (getSortedRowModel, getFilteredRowModel, getPaginationRowModel).",
3023
+ whenNotToUse: "Don't use for static/simple tables (use Table primitives directly). Don't use for virtualized very-large lists (use TanStack Virtual). Don't use for grid layouts (use CSS grid). DataTable is a Client Component (uses useReactTable hook) \u2014 fetch data in a Server Component and pass it as props.",
3024
+ commonMistakes: [
3025
+ "Forgetting getCoreRowModel() (table renders nothing)",
3026
+ "Recreating columns array on every render (breaks memoization \u2014 wrap in useMemo or define outside the component)",
3027
+ "Using accessorKey with nested paths without accessorFn",
3028
+ "Not adding filter/sort row models when those features are needed",
3029
+ "Shipping a table without `caption` or `aria-label` \u2014 the table is unlabelled to assistive tech"
3030
+ ],
3031
+ relatedComponents: ["table", "pagination"],
3032
+ accessibilityNotes: "Pass either `caption` (visible) or `aria-label` so screen readers announce the table when the user enters it. Add aria-sort to sortable column headers. Announce filter/sort changes via aria-live for dynamic updates.",
3033
+ tokenBudget: 900
3034
+ },
3035
+ tags: ["data-table", "tanstack", "sortable", "filterable", "paginated"]
3036
+ };
3037
+
3038
+ // src/components/pagination/pagination.schema.ts
3039
+ var paginationSchema = {
3040
+ name: "pagination",
3041
+ displayName: "Pagination",
3042
+ description: "Composable pagination controls (Pagination / PaginationContent / PaginationItem / PaginationLink / PaginationPrevious / PaginationNext / PaginationEllipsis). Link-based by default \u2014 pair with client-side navigation or server params.",
3043
+ category: "component",
3044
+ subcategory: "navigation",
3045
+ props: [
3046
+ { name: "className", type: "string", required: false, description: "Additional CSS classes on the <nav>" }
3047
+ ],
3048
+ variants: [],
3049
+ slots: [
3050
+ {
3051
+ name: "children",
3052
+ description: "PaginationContent containing PaginationItem elements (PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis)",
3053
+ required: true,
3054
+ acceptedTypes: ["ReactNode"]
3055
+ }
3056
+ ],
3057
+ dependencies: {
3058
+ npm: ["clsx", "tailwind-merge"],
3059
+ internal: ["lib/utils", "primitives/button/button"],
3060
+ peer: ["react", "react-dom"]
3061
+ },
3062
+ tokensUsed: ["accent", "accent-foreground", "input", "background"],
3063
+ examples: [
3064
+ {
3065
+ title: "Basic pagination",
3066
+ description: "Previous + 3 pages + ellipsis + Next with current page marked",
3067
+ code: '<Pagination>\n <PaginationContent>\n <PaginationItem>\n <PaginationPrevious href="#" />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href="#">1</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href="#" isActive>2</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href="#">3</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationEllipsis />\n </PaginationItem>\n <PaginationItem>\n <PaginationNext href="#" />\n </PaginationItem>\n </PaginationContent>\n</Pagination>'
3068
+ }
3069
+ ],
3070
+ ai: {
3071
+ whenToUse: "Use for navigating between pages of a paginated dataset: blog lists, search results, table rows. Use PaginationEllipsis to truncate long ranges.",
3072
+ whenNotToUse: "Don't use for infinite scroll (use IntersectionObserver). Don't use for step-by-step wizards (use a stepper). Don't use for fewer than ~3 pages (just show all the items).",
3073
+ commonMistakes: [
3074
+ "Using PaginationLink without href (anchor is not keyboard-reachable)",
3075
+ "Forgetting isActive on the current page (no visual or aria-current feedback)",
3076
+ "Showing every page number on long ranges \u2014 use PaginationEllipsis to truncate",
3077
+ "Using onClick-only <button> instead of PaginationLink \u2014 loses right-click-open-new-tab affordance; prefer href + Next.js Link when client-side routing is needed"
3078
+ ],
3079
+ relatedComponents: ["table", "data-table"],
3080
+ accessibilityNotes: "Root is role='navigation' aria-label='pagination'. Active link gets aria-current='page'. Previous/Next have aria-label. Ellipsis is decorative (aria-hidden) with a sr-only 'More pages' label.",
3081
+ tokenBudget: 500
3082
+ },
3083
+ tags: ["pagination", "pages", "navigation", "list"]
3084
+ };
3085
+
3086
+ // src/components/calendar/calendar.schema.ts
3087
+ var calendarSchema = {
3088
+ name: "calendar",
3089
+ displayName: "Calendar",
3090
+ description: "Date grid built on react-day-picker v9. Supports single, multiple, and range selection modes. Keyboard navigable and localizable.",
3091
+ category: "component",
3092
+ subcategory: "input",
3093
+ props: [
3094
+ {
3095
+ name: "mode",
3096
+ type: "string",
3097
+ required: false,
3098
+ default: "single",
3099
+ description: "Selection mode: 'single' | 'multiple' | 'range'"
3100
+ },
3101
+ {
3102
+ name: "selected",
3103
+ type: "object",
3104
+ required: false,
3105
+ description: "Controlled selected value (Date, Date[], or DateRange depending on mode)"
3106
+ },
3107
+ {
3108
+ name: "onSelect",
3109
+ type: "function",
3110
+ required: false,
3111
+ description: "Callback when selection changes"
3112
+ },
3113
+ {
3114
+ name: "disabled",
3115
+ type: "object",
3116
+ required: false,
3117
+ description: "DayPicker Matcher \u2014 accepts a Date, Date[], { from, to }, { before | after }, or a (date: Date) => boolean predicate"
3118
+ },
3119
+ {
3120
+ name: "showOutsideDays",
3121
+ type: "boolean",
3122
+ required: false,
3123
+ default: true,
3124
+ description: "Render days from the previous/next month in the grid"
3125
+ },
3126
+ {
3127
+ name: "numberOfMonths",
3128
+ type: "number",
3129
+ required: false,
3130
+ default: 1,
3131
+ description: "How many months to display side-by-side"
3132
+ },
3133
+ {
3134
+ name: "defaultMonth",
3135
+ type: "object",
3136
+ required: false,
3137
+ description: "The month to render first (uncontrolled). Date object."
3138
+ },
3139
+ {
3140
+ name: "fromDate",
3141
+ type: "object",
3142
+ required: false,
3143
+ description: "Earliest selectable date (Date). Days before are disabled."
3144
+ },
3145
+ {
3146
+ name: "toDate",
3147
+ type: "object",
3148
+ required: false,
3149
+ description: "Latest selectable date (Date). Days after are disabled."
3150
+ },
3151
+ {
3152
+ name: "locale",
3153
+ type: "object",
3154
+ required: false,
3155
+ description: "date-fns Locale object (e.g. `import { es } from 'date-fns/locale'`) for weekday/month labels"
3156
+ },
3157
+ {
3158
+ name: "weekStartsOn",
3159
+ type: "number",
3160
+ required: false,
3161
+ default: 0,
3162
+ description: "First day of the week (0 = Sunday, 1 = Monday, \u2026, 6 = Saturday)"
3163
+ },
3164
+ {
3165
+ name: "classNames",
3166
+ type: "object",
3167
+ required: false,
3168
+ description: "Per-part className overrides (merged with defaults)"
3169
+ }
3170
+ ],
3171
+ variants: [],
3172
+ slots: [],
3173
+ dependencies: {
3174
+ npm: ["react-day-picker", "date-fns", "clsx", "tailwind-merge"],
3175
+ internal: ["lib/utils"],
3176
+ peer: ["react", "react-dom"]
3177
+ },
3178
+ tokensUsed: ["accent", "accent-foreground", "primary", "primary-foreground", "muted-foreground", "ring"],
3179
+ examples: [
3180
+ {
3181
+ title: "Single date selection",
3182
+ description: "Bind a Date state to selected/onSelect",
3183
+ code: 'import { useState } from "react";\nimport { Calendar } from "@/components/ui/calendar";\n\nexport function Example() {\n const [date, setDate] = useState<Date | undefined>(new Date());\n return <Calendar mode="single" selected={date} onSelect={setDate} className="rounded-md border" />;\n}'
3184
+ },
3185
+ {
3186
+ title: "Range selection",
3187
+ description: "Pick a start and end date",
3188
+ code: 'import { useState } from "react";\nimport type { DateRange } from "react-day-picker";\nimport { Calendar } from "@/components/ui/calendar";\n\nexport function Example() {\n const [range, setRange] = useState<DateRange | undefined>();\n return <Calendar mode="range" selected={range} onSelect={setRange} numberOfMonths={2} />;\n}'
3189
+ }
3190
+ ],
3191
+ ai: {
3192
+ whenToUse: "Use for inline date selection UIs, or as the month-grid inside a DatePicker (wrapped in a Popover). Supports single date, multi-date, and range modes.",
3193
+ whenNotToUse: "Don't use when only a text date input is needed (use Input type=date). Don't embed inside a very narrow container \u2014 the grid needs ~280px min width. For scheduling UIs with time, combine with a separate time picker.",
3194
+ commonMistakes: [
3195
+ "Passing a string to selected (must be a Date, Date[], or DateRange object)",
3196
+ "Forgetting mode prop (default is single, but being explicit avoids confusion)",
3197
+ "Overriding classNames completely instead of spreading \u2014 loses default styling",
3198
+ "Using inside a Server Component without marking the consumer 'use client'"
3199
+ ],
3200
+ relatedComponents: ["date-picker", "popover", "input"],
3201
+ accessibilityNotes: "react-day-picker wires aria-label, aria-selected, and keyboard navigation (arrows, Home/End, PageUp/Down). Focus rings on day buttons use the ring token.",
3202
+ tokenBudget: 800
3203
+ },
3204
+ tags: ["calendar", "date", "date-picker", "input"]
3205
+ };
3206
+
3207
+ // src/components/date-picker/date-picker.schema.ts
3208
+ var datePickerSchema = {
3209
+ name: "date-picker",
3210
+ displayName: "Date Picker",
3211
+ description: "Date input composed from Popover + Calendar. Shows the selected date formatted via date-fns, opens a calendar grid on click.",
3212
+ category: "component",
3213
+ subcategory: "input",
3214
+ props: [
3215
+ {
3216
+ name: "value",
3217
+ type: "object",
3218
+ required: false,
3219
+ description: "Controlled selected Date"
3220
+ },
3221
+ {
3222
+ name: "onChange",
3223
+ type: "function",
3224
+ required: false,
3225
+ description: "Callback when the user selects a date: (date: Date | undefined) => void"
3226
+ },
3227
+ {
3228
+ name: "placeholder",
3229
+ type: "string",
3230
+ required: false,
3231
+ default: "Pick a date",
3232
+ description: "Text shown on the trigger when no date is selected"
3233
+ },
3234
+ {
3235
+ name: "dateFormat",
3236
+ type: "string",
3237
+ required: false,
3238
+ default: "PPP",
3239
+ description: "date-fns format token for the trigger label (e.g. 'PPP', 'yyyy-MM-dd')"
3240
+ },
3241
+ {
3242
+ name: "disabled",
3243
+ type: "boolean",
3244
+ required: false,
3245
+ default: false,
3246
+ description: "Disable the picker trigger"
3247
+ },
3248
+ {
3249
+ name: "aria-label",
3250
+ type: "string",
3251
+ required: false,
3252
+ description: "Accessible label \u2014 required when no adjacent visible <label> is used"
3253
+ },
3254
+ {
3255
+ name: "captionLayout",
3256
+ type: "string",
3257
+ required: false,
3258
+ description: "Caption layout forwarded to react-day-picker. Use 'dropdown' (or 'dropdown-years' / 'dropdown-months') for native <select> navigation \u2014 common for birth-date pickers. Default is 'label' (chevron buttons only)."
3259
+ },
3260
+ {
3261
+ name: "startMonth",
3262
+ type: "object",
3263
+ required: false,
3264
+ description: "Earliest month/year navigable in the calendar (Date). Forwarded to react-day-picker. Pair with captionLayout='dropdown'."
3265
+ },
3266
+ {
3267
+ name: "endMonth",
3268
+ type: "object",
3269
+ required: false,
3270
+ description: "Latest month/year navigable in the calendar (Date). Forwarded to react-day-picker. Pair with captionLayout='dropdown'."
3271
+ }
3272
+ ],
3273
+ variants: [],
3274
+ slots: [],
3275
+ dependencies: {
3276
+ npm: ["react-day-picker", "date-fns", "@radix-ui/react-popover", "clsx", "tailwind-merge"],
3277
+ internal: ["components/calendar/calendar", "components/popover/popover", "lib/utils"],
3278
+ peer: ["react", "react-dom"]
3279
+ },
3280
+ tokensUsed: ["background", "border", "input", "ring", "accent", "accent-foreground", "muted-foreground"],
3281
+ examples: [
3282
+ {
3283
+ title: "Basic date picker",
3284
+ description: "Bind a Date state and render the picker",
3285
+ code: 'import { useState } from "react";\nimport { DatePicker } from "@/components/ui/date-picker";\n\nexport function Example() {\n const [date, setDate] = useState<Date | undefined>();\n return <DatePicker value={date} onChange={setDate} />;\n}'
3286
+ },
3287
+ {
3288
+ title: "Birth-date picker with year dropdown",
3289
+ description: "Use captionLayout='dropdown' with explicit startMonth/endMonth to get native <select> year navigation",
3290
+ code: 'import { useState } from "react";\nimport { DatePicker } from "@/components/ui/date-picker";\n\nexport function Example() {\n const [dob, setDob] = useState<Date | undefined>();\n return (\n <DatePicker\n value={dob}\n onChange={setDob}\n placeholder="Date of birth"\n captionLayout="dropdown"\n startMonth={new Date(1925, 0)}\n endMonth={new Date(new Date().getFullYear(), 11)}\n />\n );\n}'
3291
+ }
3292
+ ],
3293
+ ai: {
3294
+ whenToUse: "Use for selecting a single date in a form. Shows a formatted text label and opens a month grid on click. Composes Popover + Calendar + button trigger. For far-away years (birthdays, historical dates), pass captionLayout='dropdown' plus startMonth/endMonth.",
3295
+ whenNotToUse: "Don't use for date ranges (compose Calendar mode='range' + Popover yourself). Don't use for native mobile date UX (<input type='date'> is often better on phones). Don't use if you need time selection \u2014 combine with a separate time picker.",
3296
+ commonMistakes: [
3297
+ "Passing a string to value \u2014 must be a Date object",
3298
+ "Missing aria-label when the picker has no adjacent visible <label>",
3299
+ "Overriding className in a way that hurts focus ring visibility",
3300
+ "Forgetting that the popover auto-closes on select \u2014 provide onChange to capture the value",
3301
+ "Setting captionLayout='dropdown' without startMonth/endMonth \u2014 react-day-picker defaults to \xB1100 years, producing a 200-option dropdown"
3302
+ ],
3303
+ relatedComponents: ["calendar", "popover", "input"],
3304
+ accessibilityNotes: "Trigger is a real <button> with focus ring. When rendered without a visible label, pass aria-label. The popover portals and traps keyboard focus inside Calendar until the user selects or presses Escape.",
3305
+ tokenBudget: 700
3306
+ },
3307
+ tags: ["date-picker", "date", "input", "popover", "calendar"]
3308
+ };
3309
+
3310
+ // src/components/input-otp/input-otp.schema.ts
3311
+ var inputOTPSchema = {
3312
+ name: "input-otp",
3313
+ displayName: "Input OTP",
3314
+ description: "One-time-password / verification-code entry built on the input-otp library. Renders N character slots with active/caret state and auto-advance on type.",
3315
+ category: "component",
3316
+ subcategory: "forms",
3317
+ props: [
3318
+ {
3319
+ name: "maxLength",
3320
+ type: "number",
3321
+ required: true,
3322
+ description: "Total number of slots (typically 4\u20138 for OTPs)"
3323
+ },
3324
+ {
3325
+ name: "value",
3326
+ type: "string",
3327
+ required: false,
3328
+ description: "Controlled value \u2014 the current entered string"
3329
+ },
3330
+ {
3331
+ name: "onChange",
3332
+ type: "function",
3333
+ required: false,
3334
+ description: "Callback fired as the user types: (value: string) => void"
3335
+ },
3336
+ {
3337
+ name: "onComplete",
3338
+ type: "function",
3339
+ required: false,
3340
+ description: "Called when all slots are filled (useful to auto-submit)"
3341
+ },
3342
+ {
3343
+ name: "pattern",
3344
+ type: "string",
3345
+ required: false,
3346
+ description: "Regex to restrict input (use REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, or REGEXP_ONLY_DIGITS_AND_CHARS)"
3347
+ },
3348
+ {
3349
+ name: "disabled",
3350
+ type: "boolean",
3351
+ required: false,
3352
+ default: false,
3353
+ description: "Disable the whole input"
3354
+ }
3355
+ ],
3356
+ variants: [],
3357
+ slots: [
3358
+ {
3359
+ name: "children",
3360
+ description: "InputOTPGroup with InputOTPSlot index={0..maxLength-1}, optional InputOTPSeparator",
3361
+ required: true,
3362
+ acceptedTypes: ["ReactNode"]
3363
+ }
3364
+ ],
3365
+ dependencies: {
3366
+ npm: ["input-otp", "clsx", "tailwind-merge"],
3367
+ internal: ["lib/utils"],
3368
+ peer: ["react", "react-dom"]
3369
+ },
3370
+ tokensUsed: ["input", "ring", "background", "foreground", "muted-foreground"],
3371
+ examples: [
3372
+ {
3373
+ title: "6-digit OTP with separator",
3374
+ description: "Two groups of 3 slots divided by a bullet",
3375
+ code: 'import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/ui/input-otp";\nimport { REGEXP_ONLY_DIGITS } from "input-otp";\n\nexport function Example() {\n return (\n <InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS}>\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n </InputOTPGroup>\n <InputOTPSeparator />\n <InputOTPGroup>\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n );\n}'
3376
+ }
3377
+ ],
3378
+ ai: {
3379
+ whenToUse: "Use for one-time password, email verification code, 2FA code, or any fixed-length code entry. Auto-advances on type, supports paste of the full code, and supports regex validation.",
3380
+ whenNotToUse: "Don't use for variable-length codes (use a plain Input). Don't use for passwords (use Input type='password'). Don't use for open-ended short text \u2014 the slot UI implies a code.",
3381
+ commonMistakes: [
3382
+ "Forgetting to render maxLength slots \u2014 the underlying input's maxLength won't match the visible UI",
3383
+ "Using pattern without importing one of the REGEXP_ONLY_* constants from 'input-otp'",
3384
+ "Wrapping the whole thing in a <form> without a submit handler \u2014 onComplete is often a better auto-submit hook",
3385
+ "Overriding slot className in a way that removes the first/last border-radius rules"
3386
+ ],
3387
+ relatedComponents: ["input", "form"],
3388
+ accessibilityNotes: "input-otp manages a single hidden <input> so screen readers hear one field of N characters. Each slot is a visual representation. The active slot gets a focus ring via the ring token.",
3389
+ tokenBudget: 700
3390
+ },
3391
+ tags: ["input-otp", "otp", "verification", "2fa", "code", "pin"]
3392
+ };
3393
+
3394
+ // src/components/command/command.schema.ts
3395
+ var commandSchema = {
3396
+ name: "command",
3397
+ displayName: "Command",
3398
+ description: "Composable command menu built on cmdk \u2014 search input + filtered list with keyboard navigation. Use as an inline palette or, wrapped in CommandDialog, as a \u2318K-style launcher.",
3399
+ category: "component",
3400
+ subcategory: "overlay",
3401
+ props: [
3402
+ {
3403
+ name: "shouldFilter",
3404
+ type: "boolean",
3405
+ required: false,
3406
+ default: true,
3407
+ description: "Built-in filtering. Set to false for fully-controlled filtering."
3408
+ },
3409
+ {
3410
+ name: "filter",
3411
+ type: "function",
3412
+ required: false,
3413
+ description: "Custom scoring function: (value, search, keywords?) => number (0..1)"
3414
+ },
3415
+ {
3416
+ name: "value",
3417
+ type: "string",
3418
+ required: false,
3419
+ description: "Controlled active-item value"
3420
+ },
3421
+ {
3422
+ name: "onValueChange",
3423
+ type: "function",
3424
+ required: false,
3425
+ description: "Callback when the highlighted item changes"
3426
+ },
3427
+ {
3428
+ name: "loop",
3429
+ type: "boolean",
3430
+ required: false,
3431
+ default: false,
3432
+ description: "Loop arrow-key navigation at the ends of the list"
3433
+ },
3434
+ {
3435
+ name: "label",
3436
+ type: "string",
3437
+ required: false,
3438
+ description: "Accessible label for the menu (not shown visually)"
3439
+ }
3440
+ ],
3441
+ variants: [],
3442
+ slots: [
3443
+ {
3444
+ name: "children",
3445
+ description: "CommandInput + CommandList with CommandEmpty, CommandGroup, CommandItem, CommandSeparator",
3446
+ required: true,
3447
+ acceptedTypes: ["ReactNode"]
3448
+ }
3449
+ ],
3450
+ dependencies: {
3451
+ npm: ["cmdk", "@radix-ui/react-dialog", "clsx", "tailwind-merge"],
3452
+ internal: ["components/dialog/dialog", "lib/utils"],
3453
+ peer: ["react", "react-dom"]
3454
+ },
3455
+ tokensUsed: ["popover", "popover-foreground", "accent", "accent-foreground", "border", "muted-foreground"],
3456
+ examples: [
3457
+ {
3458
+ title: "Command dialog launcher (\u2318K)",
3459
+ description: "Toggle a command palette with keyboard shortcut",
3460
+ code: 'import { useEffect, useState } from "react";\nimport { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";\n\nexport function Example() {\n const [open, setOpen] = useState(false);\n useEffect(() => {\n const down = (e: KeyboardEvent) => {\n if (e.key === "k" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setOpen(v => !v);\n }\n };\n document.addEventListener("keydown", down);\n return () => document.removeEventListener("keydown", down);\n }, []);\n return (\n <CommandDialog open={open} onOpenChange={setOpen}>\n <CommandInput placeholder="Type a command or search\u2026" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n <CommandGroup heading="Suggestions">\n <CommandItem onSelect={() => setOpen(false)}>Profile</CommandItem>\n <CommandItem onSelect={() => setOpen(false)}>Settings</CommandItem>\n </CommandGroup>\n </CommandList>\n </CommandDialog>\n );\n}'
3461
+ }
3462
+ ],
3463
+ ai: {
3464
+ whenToUse: "Use for searchable menus, command palettes, \u2318K launchers, or as the list body of a Combobox. Built-in fuzzy filter + arrow-key nav + Enter-to-select.",
3465
+ whenNotToUse: "Don't use for small static lists (use plain DropdownMenu). Don't use for large data tables (use DataTable). If you need a select input with a single bound value, Combobox is the higher-level wrapper.",
3466
+ commonMistakes: [
3467
+ "Forgetting CommandList \u2014 items won't be scrollable or grouped properly",
3468
+ "Giving CommandItem non-unique values (breaks filtering and controlled state)",
3469
+ "Overriding CommandInput className to remove the border/padding \u2014 breaks the \u2318K icon layout",
3470
+ "Not rendering CommandEmpty \u2014 the list looks broken when a search has no matches",
3471
+ "Querying CommandSeparator via cmdk's internal Separator state \u2014 Hex UI renders it as a presentational div with role='none' (and the `data-cmdk-separator` attribute preserved for selector compatibility) so it can sit inside CommandList's role=listbox without violating ARIA"
3472
+ ],
3473
+ relatedComponents: ["combobox", "dialog", "dropdown-menu"],
3474
+ accessibilityNotes: "cmdk wires role=listbox/option and aria-activedescendant. Use the `label` prop on Command for a screen-reader-only name when no visible heading exists. CommandSeparator renders with role='none' (still selectable via `[data-cmdk-separator]`) so listbox-children rules are satisfied.",
3475
+ tokenBudget: 900
3476
+ },
3477
+ tags: ["command", "cmdk", "palette", "search", "launcher"]
3478
+ };
3479
+
3480
+ // src/components/combobox/combobox.schema.ts
3481
+ var comboboxSchema = {
3482
+ name: "combobox",
3483
+ displayName: "Combobox",
3484
+ description: "Searchable single-select input. Composes Popover + Command (cmdk) + a styled trigger. Pass a list of { value, label } options.",
3485
+ category: "component",
3486
+ subcategory: "input",
3487
+ props: [
3488
+ {
3489
+ name: "options",
3490
+ type: "object",
3491
+ required: true,
3492
+ description: "Array of { value: string, label: string, disabled?: boolean }"
3493
+ },
3494
+ {
3495
+ name: "value",
3496
+ type: "string",
3497
+ required: false,
3498
+ description: "Controlled selected option value"
3499
+ },
3500
+ {
3501
+ name: "onChange",
3502
+ type: "function",
3503
+ required: false,
3504
+ description: "Callback when the user picks an option: (value: string) => void"
3505
+ },
3506
+ {
3507
+ name: "placeholder",
3508
+ type: "string",
3509
+ required: false,
3510
+ default: "Select\u2026",
3511
+ description: "Text shown on the trigger when nothing is selected"
3512
+ },
3513
+ {
3514
+ name: "searchPlaceholder",
3515
+ type: "string",
3516
+ required: false,
3517
+ default: "Search\u2026",
3518
+ description: "Placeholder for the filter input"
3519
+ },
3520
+ {
3521
+ name: "emptyText",
3522
+ type: "string",
3523
+ required: false,
3524
+ default: "No results found.",
3525
+ description: "Shown inside the list when the search has no matches"
3526
+ },
3527
+ {
3528
+ name: "disabled",
3529
+ type: "boolean",
3530
+ required: false,
3531
+ default: false,
3532
+ description: "Disable the trigger"
3533
+ },
3534
+ {
3535
+ name: "aria-label",
3536
+ type: "string",
3537
+ required: false,
3538
+ description: "Accessible label \u2014 required when no adjacent visible label is used"
3539
+ },
3540
+ {
3541
+ name: "aria-labelledby",
3542
+ type: "string",
3543
+ required: false,
3544
+ description: "Id of an external visible label that names the combobox"
3545
+ }
3546
+ ],
3547
+ variants: [],
3548
+ slots: [],
3549
+ dependencies: {
3550
+ npm: ["cmdk", "@radix-ui/react-popover", "clsx", "tailwind-merge"],
3551
+ internal: [
3552
+ "components/command/command",
3553
+ "components/popover/popover",
3554
+ "lib/utils"
3555
+ ],
3556
+ peer: ["react", "react-dom"]
3557
+ },
3558
+ tokensUsed: ["background", "input", "ring", "accent", "accent-foreground", "muted-foreground"],
3559
+ examples: [
3560
+ {
3561
+ title: "Framework picker",
3562
+ description: "Searchable single-select with a small static list",
3563
+ code: 'import { useState } from "react";\nimport { Combobox } from "@/components/ui/combobox";\n\nconst frameworks = [\n { value: "next", label: "Next.js" },\n { value: "remix", label: "Remix" },\n { value: "astro", label: "Astro" },\n { value: "nuxt", label: "Nuxt" },\n];\n\nexport function Example() {\n const [value, setValue] = useState<string>();\n return <Combobox options={frameworks} value={value} onChange={setValue} placeholder="Pick a framework" />;\n}'
3564
+ }
3565
+ ],
3566
+ ai: {
3567
+ whenToUse: "Use for a select input when the list is >~8 items or users benefit from typing to narrow. Fuzzy search + keyboard nav + selected-item checkmark.",
3568
+ whenNotToUse: "Don't use for native-select parity on mobile (use Select). Don't use for multi-select (this component is single-value \u2014 compose Command + Popover yourself for multi). Don't use for free-text entry (use Input).",
3569
+ commonMistakes: [
3570
+ "Passing duplicate option values (breaks selection and filtering)",
3571
+ "Two options with identical labels \u2014 cmdk dedupes by the Item's filter value (the label here), so one will be dropped from the list",
3572
+ "Using the label as the value \u2014 fine if stable, but prefer a short stable `value` string",
3573
+ "Forgetting to bind value + onChange \u2014 uncontrolled mode doesn't exist on this wrapper",
3574
+ "Mixing translated labels without keying on value \u2014 label changes won't update selection",
3575
+ "Missing aria-label / aria-labelledby \u2014 role='combobox' does not allow name from contents, so without one of these the trigger has no accessible name"
3576
+ ],
3577
+ relatedComponents: ["command", "popover", "select"],
3578
+ accessibilityNotes: "Trigger has role='combobox' + aria-expanded + aria-haspopup='listbox'. aria-controls points at the inner CommandList (a useId-stabilized listbox). Pass aria-label or aria-labelledby \u2014 combobox does not derive its name from contents.",
3579
+ tokenBudget: 900
3580
+ },
3581
+ tags: ["combobox", "select", "search", "cmdk", "input"]
3582
+ };
3583
+
3584
+ // src/components/multi-combobox/multi-combobox.schema.ts
3585
+ var multiComboboxSchema = {
3586
+ name: "multi-combobox",
3587
+ displayName: "MultiCombobox",
3588
+ description: "Searchable multi-select input. Composes Popover + Command (cmdk) + a styled trigger. Trigger shows '{n} selected'; each option exposes aria-selected.",
3589
+ category: "component",
3590
+ subcategory: "input",
3591
+ props: [
3592
+ {
3593
+ name: "options",
3594
+ type: "object",
3595
+ required: true,
3596
+ description: "Array of { value: string, label: string, disabled?: boolean }"
3597
+ },
3598
+ {
3599
+ name: "value",
3600
+ type: "object",
3601
+ required: false,
3602
+ description: "Controlled selected values (string[])"
3603
+ },
3604
+ {
3605
+ name: "onChange",
3606
+ type: "function",
3607
+ required: false,
3608
+ description: "Callback when the user toggles an option: (values: string[]) => void"
3609
+ },
3610
+ {
3611
+ name: "placeholder",
3612
+ type: "string",
3613
+ required: false,
3614
+ default: "Select\u2026",
3615
+ description: "Text shown on the trigger when nothing is selected"
3616
+ },
3617
+ {
3618
+ name: "searchPlaceholder",
3619
+ type: "string",
3620
+ required: false,
3621
+ default: "Search\u2026",
3622
+ description: "Placeholder for the filter input"
3623
+ },
3624
+ {
3625
+ name: "emptyText",
3626
+ type: "string",
3627
+ required: false,
3628
+ default: "No results found.",
3629
+ description: "Shown inside the list when the search has no matches"
3630
+ },
3631
+ {
3632
+ name: "maxSelected",
3633
+ type: "number",
3634
+ required: false,
3635
+ description: "Soft cap on selections \u2014 once reached, unselected options become aria-disabled and clicks are ignored"
3636
+ },
3637
+ {
3638
+ name: "closeOnSelect",
3639
+ type: "boolean",
3640
+ required: false,
3641
+ default: false,
3642
+ description: "Close the popover after every pick. Default false matches multi-select UX (Linear/Notion)."
3643
+ },
3644
+ {
3645
+ name: "disabled",
3646
+ type: "boolean",
3647
+ required: false,
3648
+ default: false,
3649
+ description: "Disable the trigger"
3650
+ },
3651
+ {
3652
+ name: "aria-label",
3653
+ type: "string",
3654
+ required: false,
3655
+ description: "Accessible label \u2014 required when no adjacent visible label is used"
3656
+ },
3657
+ {
3658
+ name: "aria-labelledby",
3659
+ type: "string",
3660
+ required: false,
3661
+ description: "Id of an external visible label that names the combobox"
3662
+ }
3663
+ ],
3664
+ variants: [],
3665
+ slots: [],
3666
+ dependencies: {
3667
+ npm: ["cmdk", "@radix-ui/react-popover", "clsx", "tailwind-merge"],
3668
+ internal: [
3669
+ "components/command/command",
3670
+ "components/popover/popover",
3671
+ "lib/utils"
3672
+ ],
3673
+ peer: ["react", "react-dom"]
3674
+ },
3675
+ tokensUsed: [
3676
+ "background",
3677
+ "input",
3678
+ "ring",
3679
+ "accent",
3680
+ "accent-foreground",
3681
+ "muted-foreground"
3682
+ ],
3683
+ examples: [
3684
+ {
3685
+ title: "Tag picker",
3686
+ description: "Multi-select with a small static list and chip count",
3687
+ code: 'import { useState } from "react";\nimport { MultiCombobox } from "@/components/ui/multi-combobox";\n\nconst tags = [\n { value: "bug", label: "Bug" },\n { value: "feature", label: "Feature" },\n { value: "question", label: "Question" },\n { value: "docs", label: "Documentation" },\n];\n\nexport function Example() {\n const [picks, setPicks] = useState<string[]>([]);\n return (\n <MultiCombobox\n options={tags}\n value={picks}\n onChange={setPicks}\n placeholder="Pick tags"\n aria-label="Tags"\n />\n );\n}'
3688
+ },
3689
+ {
3690
+ title: "Capped selection",
3691
+ description: "Limit the number of items the user can pick",
3692
+ code: 'import { useState } from "react";\nimport { MultiCombobox } from "@/components/ui/multi-combobox";\n\nexport function Example() {\n const [picks, setPicks] = useState<string[]>([]);\n return (\n <MultiCombobox\n options={tags}\n value={picks}\n onChange={setPicks}\n maxSelected={3}\n aria-label="Up to 3 tags"\n />\n );\n}'
3693
+ }
3694
+ ],
3695
+ ai: {
3696
+ whenToUse: "Use to pick multiple items from a list of >~8 options where users benefit from typing to narrow. Common for tags, recipients, filters. Trigger shows count, each option exposes aria-selected.",
3697
+ whenNotToUse: "Don't use for single-select (use Combobox). Don't use for free-text entry (use Input or a tag input). Don't use for very large lists (>500 options) without server-side filtering \u2014 cmdk filters in-memory.",
3698
+ commonMistakes: [
3699
+ "Passing duplicate option values \u2014 Set-based selection treats them as one",
3700
+ "Two options with identical labels \u2014 cmdk dedupes by the Item's filter value (the label here), so one will be dropped from the list",
3701
+ "Forgetting that value is string[] not string \u2014 passing a single string breaks Array iteration",
3702
+ "Setting closeOnSelect={true} for a power-user picker \u2014 multi-select normally stays open until the user dismisses",
3703
+ "Missing aria-label / aria-labelledby \u2014 role='combobox' does not derive its name from contents, so the trigger has no accessible name without one",
3704
+ "Relying on maxSelected to enforce business rules \u2014 the cap is a UX hint; always validate the array length on submit"
3705
+ ],
3706
+ relatedComponents: ["combobox", "command", "popover", "select"],
3707
+ accessibilityNotes: "Trigger has role='combobox' + aria-expanded + aria-haspopup='listbox'. aria-controls points at the inner CommandList only when open. Each option carries aria-selected; capped/disabled options carry aria-disabled. A visually-hidden aria-live='polite' region inside the trigger announces selection-count changes.",
3708
+ tokenBudget: 1100
3709
+ },
3710
+ tags: ["combobox", "multi-select", "select", "search", "cmdk", "input"]
3711
+ };
3712
+
3713
+ // src/components/stepper/stepper.schema.ts
3714
+ var stepperSchema = {
3715
+ name: "stepper",
3716
+ displayName: "Stepper",
3717
+ description: "Linear progress indicator for multi-step flows (form wizards, onboarding, checkout). Pure semantic <ol>/<li> with aria-current='step' on the active step and a per-step error status override.",
3718
+ category: "component",
3719
+ subcategory: "navigation",
3720
+ props: [
3721
+ {
3722
+ name: "steps",
3723
+ type: "object",
3724
+ required: true,
3725
+ description: "Ordered list of { id, label, description?, disabled?, status? }. `status` overrides the index-derived value."
3726
+ },
3727
+ {
3728
+ name: "current",
3729
+ type: "number",
3730
+ required: true,
3731
+ description: "Index of the current step (controlled)."
3732
+ },
3733
+ {
3734
+ name: "orientation",
3735
+ type: "string",
3736
+ required: false,
3737
+ default: "horizontal",
3738
+ description: "Layout direction: 'horizontal' | 'vertical'"
3739
+ },
3740
+ {
3741
+ name: "size",
3742
+ type: "string",
3743
+ required: false,
3744
+ default: "md",
3745
+ description: "Indicator size: 'sm' | 'md'"
3746
+ },
3747
+ {
3748
+ name: "onStepClick",
3749
+ type: "function",
3750
+ required: false,
3751
+ description: "When provided, each step renders as a clickable <button>; otherwise steps are non-interactive <span>s. Signature: (index: number) => void"
3752
+ },
3753
+ {
3754
+ name: "aria-label",
3755
+ type: "string",
3756
+ required: true,
3757
+ description: "Required accessible name for the ordered list (e.g. 'Onboarding steps', 'Checkout progress')"
3758
+ }
3759
+ ],
3760
+ variants: [
3761
+ {
3762
+ name: "orientation",
3763
+ description: "Layout direction",
3764
+ values: [
3765
+ { value: "horizontal", description: "Steps laid out left-to-right" },
3766
+ { value: "vertical", description: "Steps stacked top-to-bottom" }
3767
+ ],
3768
+ default: "horizontal"
3769
+ },
3770
+ {
3771
+ name: "size",
3772
+ description: "Indicator size",
3773
+ values: [
3774
+ { value: "sm", description: "Compact indicator (1.75rem)" },
3775
+ {
3776
+ value: "md",
3777
+ description: "Default indicator (matches control-height-sm)"
3778
+ }
3779
+ ],
3780
+ default: "md"
3781
+ }
3782
+ ],
3783
+ slots: [],
3784
+ dependencies: {
3785
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
3786
+ internal: ["lib/utils"],
3787
+ peer: ["react", "react-dom"]
3788
+ },
3789
+ tokensUsed: [
3790
+ "primary",
3791
+ "primary-foreground",
3792
+ "foreground",
3793
+ "muted-foreground",
3794
+ "input",
3795
+ "destructive",
3796
+ "destructive-foreground",
3797
+ "ring"
3798
+ ],
3799
+ examples: [
3800
+ {
3801
+ title: "Form wizard",
3802
+ description: "Three-step horizontal stepper with the second step active",
3803
+ code: 'import { Stepper } from "@/components/ui/stepper";\n\nexport function Example() {\n return (\n <Stepper\n aria-label="Onboarding"\n current={1}\n steps={[\n { id: "account", label: "Account", description: "Email + password" },\n { id: "profile", label: "Profile", description: "Name + photo" },\n { id: "confirm", label: "Confirm" },\n ]}\n />\n );\n}'
3804
+ },
3805
+ {
3806
+ title: "With error state",
3807
+ description: "Mark a failed step explicitly with status='error'",
3808
+ code: 'import { Stepper } from "@/components/ui/stepper";\n\nexport function Example() {\n return (\n <Stepper\n aria-label="Checkout"\n current={2}\n steps={[\n { id: "cart", label: "Cart" },\n { id: "shipping", label: "Shipping", status: "error" },\n { id: "payment", label: "Payment" },\n ]}\n />\n );\n}'
3809
+ },
3810
+ {
3811
+ title: "Vertical, clickable",
3812
+ description: "Vertical orientation with onStepClick to jump back",
3813
+ code: 'import { Stepper } from "@/components/ui/stepper";\n\nexport function Example() {\n return (\n <Stepper\n aria-label="Settings"\n orientation="vertical"\n current={1}\n onStepClick={(i) => console.log(i)}\n steps={[\n { id: "profile", label: "Profile" },\n { id: "security", label: "Security" },\n { id: "billing", label: "Billing" },\n ]}\n />\n );\n}'
3814
+ }
3815
+ ],
3816
+ ai: {
3817
+ whenToUse: "Use to communicate progress through a multi-step flow with a known fixed sequence: form wizards, onboarding, checkout, ticket triage. Mark per-step error status when validation fails.",
3818
+ whenNotToUse: "Don't use for free navigation across unrelated sections (use Tabs). Don't use for indeterminate progress (use Progress with no value). Don't use for >7 steps \u2014 collapse into a multi-screen wizard with a sub-stepper instead.",
3819
+ commonMistakes: [
3820
+ "Forgetting aria-label \u2014 the <ol> needs an accessible name to be understood as a step list",
3821
+ "Setting current to an out-of-range index \u2014 derives all steps as 'upcoming'",
3822
+ "Mixing index-derived status with manual status overrides without intent \u2014 once you set status on one step, set it on all of them or know the precedence rules",
3823
+ "Making the stepper interactive (onStepClick) but allowing forward jumps before validation \u2014 gate jumps in your handler",
3824
+ "Treating it as a tab control \u2014 Stepper communicates direction; users can't pick step 5 then go back to 2 to review without your wiring"
3825
+ ],
3826
+ relatedComponents: ["progress", "tabs", "breadcrumb"],
3827
+ accessibilityNotes: "Renders <ol> with the provided aria-label. The active step's interactive element gets aria-current='step'. Completed steps prefix the label with visually-hidden 'Completed:'; error steps prefix with 'Error:' and set aria-invalid='true' on the indicator. Connector lines are aria-hidden. When onStepClick is omitted, steps are plain <span>s \u2014 not fake buttons.",
3828
+ tokenBudget: 1400
3829
+ },
3830
+ tags: ["stepper", "wizard", "progress", "navigation", "form"]
3831
+ };
3832
+
3833
+ // src/components/timeline/timeline.schema.ts
3834
+ var timelineSchema = {
3835
+ name: "timeline",
3836
+ displayName: "Timeline",
3837
+ description: "Vertical chronological event feed for activity logs, audit trails, release notes, and notification streams. Pure semantic <ol>/<li> with a status-colored indicator and an optional icon override.",
3838
+ category: "component",
3839
+ subcategory: "data-display",
3840
+ props: [
3841
+ {
3842
+ name: "events",
3843
+ type: "object",
3844
+ required: true,
3845
+ description: "Ordered list of { id, title, timestamp?, description?, icon?, status? } events."
3846
+ },
3847
+ {
3848
+ name: "size",
3849
+ type: "string",
3850
+ required: false,
3851
+ default: "md",
3852
+ description: "Indicator size: 'sm' | 'md'"
3853
+ },
3854
+ {
3855
+ name: "aria-label",
3856
+ type: "string",
3857
+ required: true,
3858
+ description: "Required accessible name for the ordered list (e.g. 'Activity log', 'Release notes')"
3859
+ }
3860
+ ],
3861
+ variants: [
3862
+ {
3863
+ name: "size",
3864
+ description: "Indicator size",
3865
+ values: [
3866
+ { value: "sm", description: "Compact 1.25rem indicator" },
3867
+ { value: "md", description: "Default 1.75rem indicator" }
3868
+ ],
3869
+ default: "md"
3870
+ }
3871
+ ],
3872
+ slots: [],
3873
+ dependencies: {
3874
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
3875
+ internal: ["lib/utils"],
3876
+ peer: ["react", "react-dom"]
3877
+ },
3878
+ tokensUsed: [
3879
+ "background",
3880
+ "foreground",
3881
+ "muted-foreground",
3882
+ "input",
3883
+ "primary",
3884
+ "destructive",
3885
+ "destructive-foreground",
3886
+ "ring"
3887
+ ],
3888
+ examples: [
3889
+ {
3890
+ title: "Activity log",
3891
+ description: "Three-entry vertical feed with mixed status colors",
3892
+ code: 'import { Timeline } from "@/components/ui/timeline";\n\nexport function Example() {\n return (\n <Timeline\n aria-label="Activity"\n events={[\n { id: "1", title: "Pull request opened", timestamp: "2 hours ago", status: "info" },\n { id: "2", title: "CI passed", timestamp: "1 hour ago", status: "success" },\n { id: "3", title: "Merged to main", timestamp: "12 minutes ago", description: "Squash + merge by @oscar", status: "success" },\n ]}\n />\n );\n}'
3893
+ },
3894
+ {
3895
+ title: "Custom icon",
3896
+ description: "Override the default dot with a custom node",
3897
+ code: 'import { Timeline } from "@/components/ui/timeline";\n\nexport function Example() {\n return (\n <Timeline\n aria-label="Release notes"\n events={[\n { id: "v1", title: "v1.0", timestamp: "Apr 24", icon: <span>\u26A1</span> },\n { id: "v2", title: "v1.1", timestamp: "Apr 27", icon: <span>\u{1F41B}</span>, status: "warning" },\n ]}\n />\n );\n}'
3898
+ }
3899
+ ],
3900
+ ai: {
3901
+ whenToUse: "Use to show a chronological event feed: activity logs, audit trails, release notes, notification history, ticket events. Each event has a title and optional timestamp + description.",
3902
+ whenNotToUse: "Don't use for project schedules / Gantt charts (build a custom layout). Don't use for navigation between time periods (use Tabs or Stepper). Don't use for paginated data (use Table or DataTable). Don't use for >50 events without virtualization \u2014 Timeline renders every item.",
3903
+ commonMistakes: [
3904
+ "Forgetting aria-label \u2014 the <ol> needs an accessible name to be understood as a feed",
3905
+ "Using duplicate event ids \u2014 breaks React keys and event reconciliation on re-render",
3906
+ "Stuffing the description with rich layouts that overflow the timeline rail \u2014 keep it short or move to a Card",
3907
+ "Setting status='error' on every event for emphasis \u2014 color loses meaning when overused",
3908
+ "Mixing controlled timestamps as Date objects without formatting \u2014 Timeline accepts ReactNode, so format upstream (date-fns) before passing in"
3909
+ ],
3910
+ relatedComponents: ["card", "stepper", "separator"],
3911
+ accessibilityNotes: "Renders <ol> with the provided aria-label. The status-colored indicator and connector line are aria-hidden \u2014 meaning is carried entirely by the title/timestamp/description text. No aria-current; events are historical, not navigational. For >50 events consider a windowing solution outside Timeline.",
3912
+ tokenBudget: 1100
3913
+ },
3914
+ tags: ["timeline", "feed", "activity", "audit-log", "history"]
3915
+ };
3916
+
3917
+ // src/components/dropzone/dropzone.schema.ts
3918
+ var dropzoneSchema = {
3919
+ name: "dropzone",
3920
+ displayName: "Dropzone",
3921
+ description: "Drag-and-drop file input built on the native HTML5 drag-drop API plus a visually-hidden <input type='file'> for keyboard + screen-reader access. Filters by accept/maxSize/maxFiles before emitting.",
3922
+ category: "component",
3923
+ subcategory: "input",
3924
+ props: [
3925
+ {
3926
+ name: "onFilesSelected",
3927
+ type: "function",
3928
+ required: false,
3929
+ description: "Callback fired with the accepted File[] on pick or drop."
3930
+ },
3931
+ {
3932
+ name: "onFilesRejected",
3933
+ type: "function",
3934
+ required: false,
3935
+ description: "Callback fired with the rejected File[] when files fail accept/maxSize/maxFiles, OR when multiple={false} and extras were sliced off. Receives the rejected File[]. Use to surface 'wrong type' / 'too large' toasts."
3936
+ },
3937
+ {
3938
+ name: "accept",
3939
+ type: "string",
3940
+ required: false,
3941
+ description: "Accept attribute forwarded to the hidden file input. Three forms: MIME type ('application/pdf'), MIME wildcard ('image/*'), or extension with leading dot ('.csv'). Extension matching is suffix-based (mirrors HTML5) \u2014 '.csv' will match 'notes.md.csv' as well."
3942
+ },
3943
+ {
3944
+ name: "multiple",
3945
+ type: "boolean",
3946
+ required: false,
3947
+ default: true,
3948
+ description: "Allow multiple files. With `multiple={false}`, the first accepted file flows to onFilesSelected and any extras flow to onFilesRejected so consumers can toast them."
3949
+ },
3950
+ {
3951
+ name: "maxFiles",
3952
+ type: "number",
3953
+ required: false,
3954
+ description: "Cap on the number of accepted files. Excess files are dropped silently \u2014 surface in your handler if needed."
3955
+ },
3956
+ {
3957
+ name: "maxSize",
3958
+ type: "number",
3959
+ required: false,
3960
+ description: "Maximum size per file in bytes. Files over the cap are filtered before onFilesSelected fires."
3961
+ },
3962
+ {
3963
+ name: "disabled",
3964
+ type: "boolean",
3965
+ required: false,
3966
+ default: false,
3967
+ description: "Disable interaction (click + drop + keyboard)."
3968
+ },
3969
+ {
3970
+ name: "children",
3971
+ type: "object",
3972
+ required: false,
3973
+ description: "Render override for the body. Pass a node, or a function receiving { isDragOver, isDisabled, openFileDialog } for layout control."
3974
+ },
3975
+ {
3976
+ name: "aria-label",
3977
+ type: "string",
3978
+ required: true,
3979
+ description: "Required accessible name for the drop area."
3980
+ }
3981
+ ],
3982
+ variants: [],
3983
+ slots: [
3984
+ {
3985
+ name: "children",
3986
+ description: "Body of the drop area (icon + copy). Optional \u2014 sensible default included.",
3987
+ required: false,
3988
+ acceptedTypes: ["ReactNode", "function"]
3989
+ }
3990
+ ],
3991
+ dependencies: {
3992
+ npm: ["clsx", "tailwind-merge"],
3993
+ internal: ["lib/utils"],
3994
+ peer: ["react", "react-dom"]
3995
+ },
3996
+ tokensUsed: [
3997
+ "background",
3998
+ "input",
3999
+ "primary",
4000
+ "accent",
4001
+ "accent-foreground",
4002
+ "muted-foreground",
4003
+ "ring"
4004
+ ],
4005
+ examples: [
4006
+ {
4007
+ title: "Image upload",
4008
+ description: "Filter to images only with a 5MB cap",
4009
+ code: 'import { useState } from "react";\nimport { Dropzone } from "@/components/ui/dropzone";\n\nexport function Example() {\n const [files, setFiles] = useState<File[]>([]);\n return (\n <>\n <Dropzone\n accept="image/*"\n maxSize={5 * 1024 * 1024}\n onFilesSelected={(picked) => setFiles((f) => [...f, ...picked])}\n aria-label="Upload images"\n />\n <ul>{files.map((f) => <li key={f.name}>{f.name}</li>)}</ul>\n </>\n );\n}'
4010
+ },
4011
+ {
4012
+ title: "Custom body via render prop",
4013
+ description: "Take full control of the drop area visuals",
4014
+ code: 'import { Dropzone } from "@/components/ui/dropzone";\n\nexport function Example() {\n return (\n <Dropzone aria-label="Upload" onFilesSelected={(f) => console.log(f)}>\n {({ isDragOver }) => (\n <span>{isDragOver ? "Release to upload" : "Drop a file or click"}</span>\n )}\n </Dropzone>\n );\n}'
4015
+ }
4016
+ ],
4017
+ ai: {
4018
+ whenToUse: "Use for any file upload UX where drag-drop is helpful (image uploaders, CSV import, attachment pickers). Built on native HTML5 + a hidden <input type='file'> so keyboard and screen-reader users get the same affordance.",
4019
+ whenNotToUse: "Don't use for camera/microphone capture (use <input type='file' capture> directly or a media-specific component). Don't use for chunked/resumable uploads \u2014 Dropzone only emits File[]; pair with a real upload pipeline (tus, S3 multipart). Don't use for clipboard paste image upload \u2014 listen on document for that.",
4020
+ commonMistakes: [
4021
+ "Forgetting aria-label \u2014 without it the drop area is announced as just 'button' to screen readers",
4022
+ "Setting accept='image' (missing the slash or wildcard) \u2014 must be a MIME type, MIME prefix with /*, or a .extension",
4023
+ "Forgetting that extension matching is suffix-based \u2014 '.csv' will match 'notes.md.csv'. Acceptable (mirrors HTML5) but document upstream if business logic requires strict suffix-only",
4024
+ "Treating maxFiles as enforcement \u2014 files past the cap are dropped silently; if business rules require it, validate again on submit",
4025
+ "Forgetting to clear the file input value after a successful upload \u2014 Dropzone resets the hidden input automatically; if you wrap it, do the same",
4026
+ "Calling preventDefault on parent containers' onDragOver \u2014 without it the browser opens the file when dropped on the window outside the dropzone",
4027
+ "Treating empty drops as no-op silently when the user expected feedback \u2014 pair with onFilesRejected to surface filter failures"
4028
+ ],
4029
+ relatedComponents: ["input", "button", "card"],
4030
+ accessibilityNotes: "The drop area is a role='button' div with tabIndex=0 and the required aria-label; Enter and Space open the file dialog. The file input is visually-hidden but live in the DOM so keyboard focus + assistive-tech file pickers work. Drag-state is exposed via data-drag-over for CSS-only state styling. When disabled, the drop area is removed from the tab order and aria-disabled is set.",
4031
+ tokenBudget: 1500
4032
+ },
4033
+ tags: ["dropzone", "file-upload", "drag-drop", "input"]
4034
+ };
4035
+
4036
+ // src/components/time-picker/time-picker.schema.ts
4037
+ var timePickerSchema = {
4038
+ name: "time-picker",
4039
+ displayName: "Time Picker",
4040
+ description: "Time input \u2014 styled wrapper around the native <input type='time'>. Value is a 'HH:MM' (or 'HH:MM:SS' with step=1) string. The browser provides 12/24-hour locale handling, keyboard arrow spinning, and screen-reader announcement.",
4041
+ category: "component",
4042
+ subcategory: "input",
4043
+ props: [
4044
+ {
4045
+ name: "value",
4046
+ type: "string",
4047
+ required: false,
4048
+ description: "Controlled time value as 'HH:MM' or 'HH:MM:SS' (24-hour)."
4049
+ },
4050
+ {
4051
+ name: "onChange",
4052
+ type: "function",
4053
+ required: false,
4054
+ description: "Callback when the user picks a time: (value: string) => void. Value is always 24-hour 'HH:MM' (or 'HH:MM:SS' when step=1)."
4055
+ },
4056
+ {
4057
+ name: "step",
4058
+ type: "number",
4059
+ required: false,
4060
+ description: "Step in seconds. Use 60 (default) for HH:MM, 1 for HH:MM:SS, 300 for 5-minute steps, 900 for 15-minute. Note: Chrome/Edge picker dropdown UI does NOT visually snap minutes to the step \u2014 it only affects keyboard arrow stepping and form validation. Off-step values are still rejected."
4061
+ },
4062
+ {
4063
+ name: "min",
4064
+ type: "string",
4065
+ required: false,
4066
+ description: "Earliest selectable time as 'HH:MM'."
4067
+ },
4068
+ {
4069
+ name: "max",
4070
+ type: "string",
4071
+ required: false,
4072
+ description: "Latest selectable time as 'HH:MM'."
4073
+ },
4074
+ {
4075
+ name: "disabled",
4076
+ type: "boolean",
4077
+ required: false,
4078
+ default: false,
4079
+ description: "Disable the input."
4080
+ },
4081
+ {
4082
+ name: "aria-label",
4083
+ type: "string",
4084
+ required: false,
4085
+ description: "Accessible label \u2014 required when no adjacent visible <label> is used."
4086
+ }
4087
+ ],
4088
+ variants: [],
4089
+ slots: [],
4090
+ dependencies: {
4091
+ npm: ["clsx", "tailwind-merge"],
4092
+ internal: ["lib/utils"],
4093
+ peer: ["react", "react-dom"]
4094
+ },
4095
+ tokensUsed: ["background", "input", "ring"],
4096
+ examples: [
4097
+ {
4098
+ title: "Basic time picker",
4099
+ description: "Bind a string state and render the picker",
4100
+ code: 'import { useState } from "react";\nimport { TimePicker } from "@/components/ui/time-picker";\n\nexport function Example() {\n const [time, setTime] = useState<string>();\n return <TimePicker value={time} onChange={setTime} aria-label="Meeting time" />;\n}'
4101
+ },
4102
+ {
4103
+ title: "With seconds + 5-minute step",
4104
+ description: "step={1} adds a seconds segment; step={300} steps minutes by 5 in browsers that support it",
4105
+ code: 'import { useState } from "react";\nimport { TimePicker } from "@/components/ui/time-picker";\n\nexport function Example() {\n const [time, setTime] = useState<string>("09:00");\n return <TimePicker value={time} onChange={setTime} step={300} min="09:00" max="17:00" aria-label="Working hours start" />;\n}'
4106
+ }
4107
+ ],
4108
+ ai: {
4109
+ whenToUse: "Use for selecting a time of day in a form (meeting time, reminder, business hours start/end). Browser handles 12/24-hour locale automatically based on user system settings; the wire format is always 24-hour 'HH:MM'. Component is controlled-only \u2014 pair `value` with `onChange`; for uncontrolled use, the input behaves as a controlled empty input with no upstream state.",
4110
+ whenNotToUse: "Don't use for durations (hours/minutes elapsed) \u2014 use composite Input fields. Don't use for date+time together \u2014 combine with DatePicker. Don't use when explicit AM/PM segments matter for layout \u2014 compose Input + Select yourself; the native input renders AM/PM only in 12-hour locales. Don't use when you need pixel-identical chrome across browsers \u2014 the dropdown indicator is webkit-styled (Chrome/Edge/Safari) and absent in Firefox.",
4111
+ commonMistakes: [
4112
+ "Passing a Date object to value \u2014 must be a string in 'HH:MM' format",
4113
+ "Treating onChange as 12-hour \u2014 the value is always 24-hour, the browser only displays it in user locale",
4114
+ "Missing aria-label when there's no adjacent visible <label> \u2014 input gets no accessible name",
4115
+ "Setting step to a value that doesn't divide evenly into 3600 (e.g. step=7) \u2014 confusing UX in browsers that snap to it",
4116
+ "Expecting Chrome/Edge's picker dropdown to visually skip minutes per step \u2014 it doesn't. The minute scroll wheel always shows every minute; step only constrains keyboard arrow stepping and form validation",
4117
+ "Setting min/max with seconds when step!=1 \u2014 the seconds segment is hidden, so users can't actually reach values that need it",
4118
+ "Expecting the dropdown picker indicator to render in Firefox \u2014 only webkit browsers paint it; Firefox renders the input without that affordance",
4119
+ "Expecting an uncontrolled mode \u2014 TimePicker is controlled-only. Pass both value + onChange, or omit both and accept that user input has no place to live"
4120
+ ],
4121
+ relatedComponents: ["date-picker", "input", "select"],
4122
+ accessibilityNotes: "Native <input type='time'> \u2014 assistive tech announces hour/minute (and seconds if shown) segments individually. Arrow keys spin each segment. Disabled state is announced. Pass aria-label or wrap with a <label htmlFor>. Visual chrome (icon, dropdown affordance) is browser-controlled and varies across Chrome/Edge/Safari (rendered) and Firefox (absent).",
4123
+ tokenBudget: 700
4124
+ },
4125
+ tags: ["time-picker", "time", "input", "form"]
4126
+ };
4127
+
4128
+ // src/components/file-tree/file-tree.schema.ts
4129
+ var fileTreeSchema = {
4130
+ name: "file-tree",
4131
+ displayName: "File Tree",
4132
+ description: "Hierarchical tree view for files, folders, and any nested navigation. Implements the WAI-ARIA tree pattern with role='tree' / 'treeitem' / 'group', aria-level, aria-expanded, aria-selected, and full keyboard navigation (Up/Down/Left/Right/Home/End/Enter/Space).",
4133
+ category: "component",
4134
+ subcategory: "navigation",
4135
+ props: [
4136
+ {
4137
+ name: "nodes",
4138
+ type: "object",
4139
+ required: true,
4140
+ description: "Tree of { id, name, children?, icon?, disabled? }. Presence of `children` (even an empty array) marks the node as a folder."
4141
+ },
4142
+ {
4143
+ name: "defaultExpanded",
4144
+ type: "object",
4145
+ required: false,
4146
+ description: "Uncontrolled \u2014 initial expanded ids (string[])."
4147
+ },
4148
+ {
4149
+ name: "expanded",
4150
+ type: "object",
4151
+ required: false,
4152
+ description: "Controlled expanded ids (string[]). Pair with onExpandedChange."
4153
+ },
4154
+ {
4155
+ name: "onExpandedChange",
4156
+ type: "function",
4157
+ required: false,
4158
+ description: "Fired with the new expanded ids: (ids: string[]) => void"
4159
+ },
4160
+ {
4161
+ name: "selected",
4162
+ type: "string",
4163
+ required: false,
4164
+ description: "Controlled selected node id."
4165
+ },
4166
+ {
4167
+ name: "onSelect",
4168
+ type: "function",
4169
+ required: false,
4170
+ description: "Fired when the user activates a node via click, Enter, or Space: (id: string) => void"
4171
+ },
4172
+ {
4173
+ name: "aria-label",
4174
+ type: "string",
4175
+ required: true,
4176
+ description: "Required accessible name for the tree (e.g. 'File explorer', 'Settings sections')."
4177
+ }
4178
+ ],
4179
+ variants: [],
4180
+ slots: [],
4181
+ dependencies: {
4182
+ npm: ["clsx", "tailwind-merge"],
4183
+ internal: ["lib/utils"],
4184
+ peer: ["react", "react-dom"]
4185
+ },
4186
+ tokensUsed: [
4187
+ "accent",
4188
+ "accent-foreground",
4189
+ "muted-foreground",
4190
+ "ring"
4191
+ ],
4192
+ examples: [
4193
+ {
4194
+ title: "Basic file tree",
4195
+ description: "Uncontrolled expanded set; selected state controlled",
4196
+ code: 'import { useState } from "react";\nimport { FileTree } from "@/components/ui/file-tree";\n\nconst nodes = [\n {\n id: "src",\n name: "src",\n children: [\n { id: "src/index.tsx", name: "index.tsx" },\n {\n id: "src/components",\n name: "components",\n children: [\n { id: "src/components/Button.tsx", name: "Button.tsx" },\n { id: "src/components/Input.tsx", name: "Input.tsx" },\n ],\n },\n ],\n },\n { id: "package.json", name: "package.json" },\n];\n\nexport function Example() {\n const [selected, setSelected] = useState<string>();\n return (\n <FileTree\n aria-label="Project files"\n nodes={nodes}\n defaultExpanded={["src"]}\n selected={selected}\n onSelect={setSelected}\n />\n );\n}'
4197
+ }
4198
+ ],
4199
+ ai: {
4200
+ whenToUse: "Use for hierarchical navigation: file/folder explorers, settings sections, org charts, taxonomy browsers. Renders a real ARIA tree with full keyboard support, so it works for sighted, keyboard, and screen-reader users.",
4201
+ whenNotToUse: "Don't use for flat lists (use ScrollArea + a list). Don't use for navigation menus (use NavigationMenu). Don't use for very deep trees (>5 levels) without virtualization \u2014 every node is rendered. Don't use for selecting multiple files concurrently \u2014 multi-select tree UX is a different beast; ship a separate component when you need it.",
4202
+ commonMistakes: [
4203
+ "Mixing controlled `expanded` with `defaultExpanded` \u2014 pass exactly one",
4204
+ "Using non-stable node ids (e.g. array index) \u2014 collapsing/expanding shifts state",
4205
+ "Marking a leaf with `children: []` instead of omitting `children` \u2014 empty array still flags it as a folder, so the chevron shows",
4206
+ "Forgetting aria-label \u2014 the tree gets no accessible name and screen readers announce just 'tree'",
4207
+ "Calling onSelect to navigate without de-bouncing arrow-key focus changes \u2014 focus moves on arrows but does NOT call onSelect; only Enter/Space/click selects, so navigation should hang off onSelect, not focused state",
4208
+ "Expecting row-click to toggle expand \u2014 per WAI-ARIA tree pattern the row click only selects; toggling is the chevron button (or ArrowRight/Left, or Enter/Space when the row is focused). Common surprise after coming from VS Code-style trees",
4209
+ "Passing `selected` pointing at a node inside a collapsed branch \u2014 the tree falls back to the first visible node for tab focus, so the consumer can't rely on tabIndex to land on the selected target until it's revealed via expanded"
4210
+ ],
4211
+ relatedComponents: ["accordion", "navigation-menu", "sidebar"],
4212
+ accessibilityNotes: "Root: role='tree' with aria-label. Each node: role='treeitem' with aria-level, aria-expanded (folders only), aria-selected, tabIndex=0 only on the active visible node (roving tabindex). Children container: role='group'. Click semantics: row click selects only; the chevron is a separate decorative button that toggles. Keyboard: ArrowDown/Up move through visible non-disabled nodes (disabled nodes are skipped); ArrowRight expands a closed folder or moves to first child; ArrowLeft collapses an open folder or moves to parent; Home/End jump to first/last visible; Enter/Space activate (toggle on folders, select on all).",
4213
+ tokenBudget: 2e3
4214
+ },
4215
+ tags: ["file-tree", "tree", "navigation", "explorer", "hierarchy"]
4216
+ };
4217
+
4218
+ // src/components/color-picker/color-picker.schema.ts
4219
+ var colorPickerSchema = {
4220
+ name: "color-picker",
4221
+ displayName: "Color Picker",
4222
+ description: "HSL-native color picker that edits an HSL triplet directly via three sliders (H/S/L). Hex input is a display adapter; sliders are the source of truth so the value round-trips losslessly through the `@hex-core/tokens` triplet format.",
4223
+ category: "component",
4224
+ subcategory: "input",
4225
+ props: [
4226
+ {
4227
+ name: "value",
4228
+ type: "string",
4229
+ required: true,
4230
+ description: 'Current color as an HSL triplet string (`"<H> <S>% <L>%"`, e.g. `"240 5.9% 10%"`). Match the format used by `@hex-core/tokens`.'
4231
+ },
4232
+ {
4233
+ name: "onChange",
4234
+ type: "function",
4235
+ required: true,
4236
+ description: "Called with the next HSL triplet when the user drags a slider or commits a valid hex value. Not called for invalid hex input."
4237
+ },
4238
+ {
4239
+ name: "disabled",
4240
+ type: "boolean",
4241
+ required: false,
4242
+ default: false,
4243
+ description: "Disable interaction. Trigger renders dimmed; mouse and keyboard activation are blocked by the native `disabled` attribute."
4244
+ },
4245
+ {
4246
+ name: "aria-label",
4247
+ type: "string",
4248
+ required: false,
4249
+ default: '"Pick color"',
4250
+ description: "Accessible name for the trigger button."
4251
+ },
4252
+ {
4253
+ name: "className",
4254
+ type: "string",
4255
+ required: false,
4256
+ description: "Additional class names merged onto the trigger."
4257
+ }
4258
+ ],
4259
+ variants: [],
4260
+ slots: [],
4261
+ dependencies: {
4262
+ npm: ["@radix-ui/react-popover", "@radix-ui/react-slider", "@radix-ui/react-label"],
4263
+ internal: [
4264
+ "primitives/slider/slider",
4265
+ "primitives/input/input",
4266
+ "primitives/label/label",
4267
+ "components/popover/popover",
4268
+ "lib/color",
4269
+ "lib/utils"
4270
+ ],
4271
+ peer: ["react", "react-dom"]
4272
+ },
4273
+ tokensUsed: ["input", "background", "ring", "border", "muted-foreground"],
4274
+ examples: [
4275
+ {
4276
+ title: "Edit a token live",
4277
+ description: "Bind a state variable to a CSS custom property to preview a token edit in real time.",
4278
+ code: 'const [color, setColor] = React.useState("240 5.9% 10%");\n\nreturn (\n <div style={{ "--primary": color } as React.CSSProperties}>\n <ColorPicker value={color} onChange={setColor} aria-label="Primary color" />\n <Button>Live preview</Button>\n </div>\n);'
4279
+ },
4280
+ {
4281
+ title: "Disabled",
4282
+ description: "Prevent edits while a parent operation is in flight.",
4283
+ code: '<ColorPicker value="240 5.9% 10%" onChange={() => {}} disabled />'
4284
+ }
4285
+ ],
4286
+ ai: {
4287
+ whenToUse: "Use whenever the user is editing a color that will round-trip through the `@hex-core/tokens` HSL triplet format \u2014 token editors, theme builders, branding panels, custom-color surfaces in design tools.",
4288
+ whenNotToUse: "Don't use for picking a color from a fixed palette \u2014 use a `Select` or `RadioGroup` of swatches. Don't use for image-based color sampling (eyedropper) \u2014 that's a separate primitive. Don't reach for ColorPicker when only a hex string matters: bind it directly to `<input type=\"color\">` for the simplest cases.",
4289
+ commonMistakes: [
4290
+ "Treating the value as hex \u2014 the prop is an HSL triplet, not a hex string. Use `hexToHslTriplet` and `hslTripletToHex` from `@hex-core/components/lib/color` if you need to bridge.",
4291
+ "Forgetting to wrap the value in `hsl(...)` when applying it as a CSS color: `style={{ color: \\`hsl(${value})\\` }}`.",
4292
+ "Calling `onChange` synchronously inside a parent's render \u2014 the picker batches slider updates and that pattern can desync controlled state."
4293
+ ],
4294
+ relatedComponents: ["slider", "input", "label", "popover"],
4295
+ accessibilityNotes: 'Each slider has a per-axis `aria-label` (Hue / Saturation / Lightness). The trigger button needs an explicit `aria-label` describing what color is being edited (e.g. `"Primary color"`) \u2014 the default `"Pick color"` is generic. The hex input is keyboard-accessible and round-trips with the sliders.',
4296
+ tokenBudget: 350
4297
+ },
4298
+ tags: ["color-picker", "color", "hsl", "hex", "form", "theme-editor", "primitive"]
4299
+ };
4300
+
4301
+ // src/components/sheet/sheet.schema.ts
4302
+ var sheetSchema = {
4303
+ name: "sheet",
4304
+ displayName: "Sheet",
4305
+ description: "Side drawer built on Radix Dialog with a directional slide animation. Use for navigation, filters, quick edit, or any off-canvas panel.",
4306
+ category: "component",
4307
+ subcategory: "overlay",
4308
+ props: [
4309
+ { name: "open", type: "boolean", required: false, description: "Controlled open state" },
4310
+ {
4311
+ name: "defaultOpen",
4312
+ type: "boolean",
4313
+ required: false,
4314
+ default: false,
4315
+ description: "Default open state for uncontrolled usage"
4316
+ },
4317
+ {
4318
+ name: "onOpenChange",
4319
+ type: "function",
4320
+ required: false,
4321
+ description: "Callback when open state changes: (open: boolean) => void"
4322
+ },
4323
+ {
4324
+ name: "modal",
4325
+ type: "boolean",
4326
+ required: false,
4327
+ default: true,
4328
+ description: "When true, content outside the sheet is inert"
4329
+ }
4330
+ ],
4331
+ variants: [
4332
+ {
4333
+ name: "side",
4334
+ description: "Which edge the sheet slides in from",
4335
+ values: [
4336
+ { value: "top", description: "Slides down from the top edge" },
4337
+ { value: "bottom", description: "Slides up from the bottom edge" },
4338
+ { value: "left", description: "Slides in from the left edge" },
4339
+ { value: "right", description: "Slides in from the right edge (default)" }
4340
+ ],
4341
+ default: "right"
4342
+ }
4343
+ ],
4344
+ slots: [
4345
+ {
4346
+ name: "children",
4347
+ description: "SheetTrigger + SheetContent (with SheetHeader/Footer/Title/Description)",
4348
+ required: true,
4349
+ acceptedTypes: ["ReactNode"]
4350
+ }
4351
+ ],
4352
+ dependencies: {
4353
+ npm: ["@radix-ui/react-dialog", "class-variance-authority", "clsx", "tailwind-merge"],
4354
+ internal: ["lib/utils"],
4355
+ peer: ["react", "react-dom"]
4356
+ },
4357
+ tokensUsed: ["background", "foreground", "muted-foreground", "border", "ring"],
4358
+ examples: [
4359
+ {
4360
+ title: "Right-side sheet",
4361
+ description: "Quick edit panel anchored to the right edge",
4362
+ code: '<Sheet>\n <SheetTrigger asChild>\n <Button variant="outline">Open</Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>Edit profile</SheetTitle>\n <SheetDescription>Make changes and save when done.</SheetDescription>\n </SheetHeader>\n <div className="grid gap-4 py-4">\n <Input placeholder="Name" />\n </div>\n <SheetFooter>\n <Button>Save</Button>\n </SheetFooter>\n </SheetContent>\n</Sheet>'
4363
+ }
4364
+ ],
4365
+ ai: {
4366
+ whenToUse: "Use for off-canvas panels \u2014 mobile nav menus, filter panels, side forms, detail views, or multi-step flows. Slides in from an edge, dismisses on outside click, Escape, or close button.",
4367
+ whenNotToUse: "Don't use for center-focused modals (use Dialog). Don't use for transient bottom sheets on mobile (use Drawer). Don't use for dropdown menus or quick popover actions (use DropdownMenu or Popover).",
4368
+ commonMistakes: [
4369
+ "Forgetting SheetTitle \u2014 Radix warns at runtime and screen readers announce an unnamed dialog",
4370
+ "Putting a full page's content inside a sheet \u2014 too much friction; use a route instead",
4371
+ "Overriding the side slide animation without matching the 'side' variant",
4372
+ "Not handling controlled open state after SheetClose \u2014 use onOpenChange not manual state"
4373
+ ],
4374
+ relatedComponents: ["dialog", "drawer", "popover"],
4375
+ accessibilityNotes: "Radix traps focus, handles Escape to close, and wires aria-labelledby/describedby to SheetTitle/Description. The Close button has sr-only text. Always include a SheetTitle.",
4376
+ tokenBudget: 700
4377
+ },
4378
+ tags: ["sheet", "drawer", "side-panel", "off-canvas", "overlay"]
4379
+ };
4380
+
4381
+ // src/components/drawer/drawer.schema.ts
4382
+ var drawerSchema = {
4383
+ name: "drawer",
4384
+ displayName: "Drawer",
4385
+ description: "Bottom-sheet drawer built on vaul. Mobile-native feel: drag-to-dismiss, snap points, body-scale-on-open. Use for quick mobile actions, filters, pickers.",
4386
+ category: "component",
4387
+ subcategory: "overlay",
4388
+ props: [
4389
+ { name: "open", type: "boolean", required: false, description: "Controlled open state" },
4390
+ {
4391
+ name: "defaultOpen",
4392
+ type: "boolean",
4393
+ required: false,
4394
+ default: false,
4395
+ description: "Default open state"
4396
+ },
4397
+ {
4398
+ name: "onOpenChange",
4399
+ type: "function",
4400
+ required: false,
4401
+ description: "Callback when open state changes: (open: boolean) => void"
4402
+ },
4403
+ {
4404
+ name: "shouldScaleBackground",
4405
+ type: "boolean",
4406
+ required: false,
4407
+ default: true,
4408
+ description: "Scale the <body> element when the drawer opens (creates depth)"
4409
+ },
4410
+ {
4411
+ name: "snapPoints",
4412
+ type: "object",
4413
+ required: false,
4414
+ description: "Array of snap positions ('40%', 400, '100%') \u2014 defines resting heights the user can snap to"
4415
+ },
4416
+ {
4417
+ name: "activeSnapPoint",
4418
+ type: "object",
4419
+ required: false,
4420
+ description: "Controlled active snap point value (matches one entry in snapPoints)"
4421
+ },
4422
+ {
4423
+ name: "closeThreshold",
4424
+ type: "number",
4425
+ required: false,
4426
+ default: 0.25,
4427
+ description: "Fraction of height the user must drag down to close (0..1)"
4428
+ },
4429
+ {
4430
+ name: "dismissible",
4431
+ type: "boolean",
4432
+ required: false,
4433
+ default: true,
4434
+ description: "Allow drag-to-dismiss and outside-click-to-dismiss"
4435
+ }
4436
+ ],
4437
+ variants: [],
4438
+ slots: [
4439
+ {
4440
+ name: "children",
4441
+ description: "DrawerTrigger + DrawerContent (with DrawerHeader/Footer/Title/Description)",
4442
+ required: true,
4443
+ acceptedTypes: ["ReactNode"]
4444
+ }
4445
+ ],
4446
+ dependencies: {
4447
+ npm: ["vaul", "@radix-ui/react-dialog", "clsx", "tailwind-merge"],
4448
+ internal: ["lib/utils"],
4449
+ peer: ["react", "react-dom"]
4450
+ },
4451
+ tokensUsed: ["background", "foreground", "muted", "muted-foreground", "border"],
4452
+ examples: [
4453
+ {
4454
+ title: "Basic drawer",
4455
+ description: "Mobile-friendly bottom sheet with a quick action",
4456
+ code: '<Drawer>\n <DrawerTrigger asChild>\n <Button variant="outline">Open drawer</Button>\n </DrawerTrigger>\n <DrawerContent>\n <DrawerHeader>\n <DrawerTitle>Edit profile</DrawerTitle>\n <DrawerDescription>Make changes to your profile.</DrawerDescription>\n </DrawerHeader>\n <div className="p-4"><Input placeholder="Name" /></div>\n <DrawerFooter>\n <Button>Save</Button>\n <DrawerClose asChild>\n <Button variant="outline">Cancel</Button>\n </DrawerClose>\n </DrawerFooter>\n </DrawerContent>\n</Drawer>'
4457
+ }
4458
+ ],
4459
+ ai: {
4460
+ whenToUse: "Use on mobile-first or mobile-primary UX when a native app-like bottom sheet matters. Good for filters, quick pickers, confirm-then-do flows, or anywhere a user expects drag-to-dismiss.",
4461
+ whenNotToUse: "Don't use on desktop-primary UIs (use Dialog or Sheet). Don't use for side navigation (use Sheet). Don't use for transient info (use Popover or Tooltip). Don't use when you must prevent dismissal \u2014 drawers invite drag-down.",
4462
+ commonMistakes: [
4463
+ "Forgetting DrawerTitle \u2014 vaul/Radix warn and screen readers announce an unnamed dialog",
4464
+ "Placing long forms inside a drawer without snap points \u2014 content gets cramped",
4465
+ "Disabling shouldScaleBackground when the background context-cue matters for UX",
4466
+ "Wrapping DrawerContent in Portal yourself \u2014 DrawerContent already portals via DrawerPortal"
4467
+ ],
4468
+ relatedComponents: ["sheet", "dialog"],
4469
+ accessibilityNotes: "vaul delegates to Radix Dialog: focus trap, Escape to close, aria-labelledby/describedby wired to DrawerTitle/Description. The top handle is decorative (aria-hidden).",
4470
+ tokenBudget: 700
4471
+ },
4472
+ tags: ["drawer", "bottom-sheet", "vaul", "mobile", "overlay"]
4473
+ };
4474
+
4475
+ // src/components/resizable/resizable.schema.ts
4476
+ var resizableSchema = {
4477
+ name: "resizable",
4478
+ displayName: "Resizable",
4479
+ description: "Draggable split panes built on react-resizable-panels v4. Horizontal or vertical, with keyboard-accessible handles and persistable layout.",
4480
+ category: "component",
4481
+ subcategory: "layout",
4482
+ props: [
4483
+ {
4484
+ name: "orientation",
4485
+ type: "string",
4486
+ required: false,
4487
+ default: "horizontal",
4488
+ description: "Group orientation: 'horizontal' | 'vertical'"
4489
+ },
4490
+ {
4491
+ name: "defaultLayout",
4492
+ type: "object",
4493
+ required: false,
4494
+ description: "Array of initial panel sizes in order (percentages summing to 100)"
4495
+ },
4496
+ {
4497
+ name: "onLayoutChange",
4498
+ type: "function",
4499
+ required: false,
4500
+ description: "Fires when the user drags a handle; receives the new layout array"
4501
+ },
4502
+ {
4503
+ name: "disabled",
4504
+ type: "boolean",
4505
+ required: false,
4506
+ default: false,
4507
+ description: "Disable resizing for the whole group"
4508
+ }
4509
+ ],
4510
+ variants: [],
4511
+ slots: [
4512
+ {
4513
+ name: "children",
4514
+ description: "Alternating ResizablePanel + ResizableHandle nodes",
4515
+ required: true,
4516
+ acceptedTypes: ["ReactNode"]
4517
+ }
4518
+ ],
4519
+ dependencies: {
4520
+ npm: ["react-resizable-panels", "clsx", "tailwind-merge"],
4521
+ internal: ["lib/utils"],
4522
+ peer: ["react", "react-dom"]
4523
+ },
4524
+ tokensUsed: ["border", "ring"],
4525
+ examples: [
4526
+ {
4527
+ title: "Horizontal split pane",
4528
+ description: "Two resizable panels with a grab-grip handle",
4529
+ code: '<ResizablePanelGroup orientation="horizontal" className="min-h-[300px] max-w-md rounded-lg border">\n <ResizablePanel defaultSize={50}>\n <div className="flex h-full items-center justify-center p-6">One</div>\n </ResizablePanel>\n <ResizableHandle withHandle />\n <ResizablePanel defaultSize={50}>\n <div className="flex h-full items-center justify-center p-6">Two</div>\n </ResizablePanel>\n</ResizablePanelGroup>'
4530
+ }
4531
+ ],
4532
+ ai: {
4533
+ whenToUse: "Use for editor-style layouts (file tree + editor), dashboards with configurable panels, or any UI where users need to trade space between regions. Layouts can be persisted to localStorage via the group's id.",
4534
+ whenNotToUse: "Don't use for responsive layouts \u2014 use CSS grid/flex with breakpoints. Don't use for modal layouts (use Dialog/Sheet). Don't nest deeply (>2 levels) \u2014 it hurts a11y and perception. Don't use if panels need to collapse/expand as a single action (use Collapsible).",
4535
+ commonMistakes: [
4536
+ "Forgetting ResizableHandle between panels \u2014 they won't resize",
4537
+ "Using 'cols'/'rows' instead of orientation='horizontal'/'vertical' (old v1 API)",
4538
+ "Not providing defaultSize on each panel \u2014 initial layout will be uneven",
4539
+ "Rendering panel content that changes DOM size during drag \u2014 react-resizable-panels performance suffers",
4540
+ "Omitting a group id when you want layout to persist via localStorage"
4541
+ ],
4542
+ relatedComponents: ["separator", "collapsible"],
4543
+ accessibilityNotes: "ResizableHandle is focusable and resizable via keyboard arrows. role='separator' is set, with aria-valuenow/min/max wired by react-resizable-panels. The grab-grip is aria-hidden (decorative).",
4544
+ tokenBudget: 700
4545
+ },
4546
+ tags: ["resizable", "split-pane", "layout", "panels"]
4547
+ };
4548
+
4549
+ // src/components/sidebar/sidebar.schema.ts
4550
+ var sidebarSchema = {
4551
+ name: "sidebar",
4552
+ displayName: "Sidebar",
4553
+ description: "App-shell sidebar with collapsible width, context-driven open state, and composable Header/Content/Footer/Item parts. Provider-based so any descendant can toggle it.",
4554
+ category: "component",
4555
+ subcategory: "navigation",
4556
+ props: [
4557
+ {
4558
+ name: "open",
4559
+ type: "boolean",
4560
+ required: false,
4561
+ description: "Controlled open state \u2014 read from SidebarProvider"
4562
+ },
4563
+ {
4564
+ name: "defaultOpen",
4565
+ type: "boolean",
4566
+ required: false,
4567
+ default: true,
4568
+ description: "Initial open state (uncontrolled)"
4569
+ },
4570
+ {
4571
+ name: "onOpenChange",
4572
+ type: "function",
4573
+ required: false,
4574
+ description: "Callback when open state flips: (open: boolean) => void"
4575
+ },
4576
+ {
4577
+ name: "side",
4578
+ type: "string",
4579
+ required: false,
4580
+ default: "left",
4581
+ description: "Which edge the sidebar sits on: 'left' | 'right'"
4582
+ }
4583
+ ],
4584
+ variants: [
4585
+ {
4586
+ name: "side",
4587
+ description: "Which edge the sidebar docks against",
4588
+ values: [
4589
+ { value: "left", description: "Docks to the left edge (default)" },
4590
+ { value: "right", description: "Docks to the right edge" }
4591
+ ],
4592
+ default: "left"
4593
+ },
4594
+ {
4595
+ name: "state",
4596
+ description: "Width state (derived from SidebarProvider open value)",
4597
+ values: [
4598
+ { value: "open", description: "Sidebar expanded at full width" },
4599
+ { value: "closed", description: "Sidebar collapsed to zero width" }
4600
+ ],
4601
+ default: "open"
4602
+ }
4603
+ ],
4604
+ slots: [
4605
+ {
4606
+ name: "children",
4607
+ description: "SidebarHeader + SidebarContent + SidebarFooter (any combination)",
4608
+ required: true,
4609
+ acceptedTypes: ["ReactNode"]
4610
+ }
4611
+ ],
4612
+ dependencies: {
4613
+ npm: ["@radix-ui/react-slot", "class-variance-authority", "clsx", "tailwind-merge"],
4614
+ internal: ["lib/utils"],
4615
+ peer: ["react", "react-dom"]
4616
+ },
4617
+ tokensUsed: ["background", "foreground", "border", "accent", "accent-foreground", "muted-foreground", "ring"],
4618
+ examples: [
4619
+ {
4620
+ title: "App shell with collapsible sidebar",
4621
+ description: "Provider holds open state; the trigger toggles it",
4622
+ code: '<SidebarProvider>\n <Sidebar>\n <SidebarHeader>\n <span className="font-semibold">Acme</span>\n </SidebarHeader>\n <SidebarContent>\n <SidebarItem active>Dashboard</SidebarItem>\n <SidebarItem>Projects</SidebarItem>\n <SidebarItem>Settings</SidebarItem>\n </SidebarContent>\n <SidebarFooter>Signed in as jane</SidebarFooter>\n </Sidebar>\n <main className="flex-1 p-4">\n <SidebarTrigger />\n <h1>Hello</h1>\n </main>\n</SidebarProvider>'
4623
+ }
4624
+ ],
4625
+ ai: {
4626
+ whenToUse: "Use for persistent app-shell navigation: admin dashboards, document editors, SaaS sidebars. The Provider pattern lets any descendant component toggle the sidebar (e.g. a topbar button on mobile).",
4627
+ whenNotToUse: "Don't use for mobile-first UX (use Sheet \u2014 sidebar collapses to zero-width but Sheet gives a native drawer feel). Don't use for marketing sites (no shell). Don't use for contextual menus (use DropdownMenu or NavigationMenu).",
4628
+ commonMistakes: [
4629
+ "Rendering Sidebar outside SidebarProvider \u2014 useSidebar throws",
4630
+ "Forgetting that SidebarProvider is a flex container \u2014 main content must be its direct sibling",
4631
+ "Using the wrong ordering for side='right' \u2014 SidebarProvider handles this via order-last",
4632
+ "Overriding the width variant manually instead of toggling open state"
4633
+ ],
4634
+ relatedComponents: ["sheet", "navigation-menu", "separator"],
4635
+ accessibilityNotes: "Sidebar is an <aside> landmark (not a modal \u2014 no focus trap). When collapsed, the aside sets inert + aria-hidden so its children are removed from the tab order and the accessibility tree. SidebarTrigger exposes aria-expanded and a rotating aria-label (suppressed when asChild so the consumer's visible label/aria-label wins). SidebarItem uses aria-current='page' when active. Focus rings use the ring token.",
4636
+ tokenBudget: 900
4637
+ },
4638
+ tags: ["sidebar", "navigation", "app-shell", "layout"]
4639
+ };
4640
+
4641
+ export { accordionSchema, alertDialogSchema, alertSchema, aspectRatioSchema, avatarSchema, badgeSchema, breadcrumbSchema, buttonSchema, calendarSchema, cardSchema, checkboxSchema, clusterSchema, collapsibleSchema, colorPickerSchema, comboboxSchema, commandSchema, containerSchema, contextMenuSchema, dataTableSchema, datePickerSchema, dialogSchema, drawerSchema, dropdownMenuSchema, dropzoneSchema, fileTreeSchema, formSchema, gridSchema, hoverCardSchema, inputOTPSchema, inputSchema, labelSchema, menubarSchema, multiComboboxSchema, navigationMenuSchema, paginationSchema, popoverSchema, progressSchema, radioGroupSchema, resizableSchema, scrollAreaSchema, selectSchema, separatorSchema, sheetSchema, sidebarSchema, skeletonSchema, sliderSchema, sonnerSchema, spacerSchema, stackSchema, stepperSchema, switchSchema, tableSchema, tabsSchema, textareaSchema, timePickerSchema, timelineSchema, toggleGroupSchema, toggleSchema, tooltipSchema };
4642
+ //# sourceMappingURL=schemas.js.map
4643
+ //# sourceMappingURL=schemas.js.map