@vadenai/mcp-server 0.1.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.
@@ -0,0 +1,789 @@
1
+ /**
2
+ * 全コンポーネントの構造メタデータ
3
+ *
4
+ * ComponentStyleConfig から抽出した静的情報。
5
+ * MCP Server 側でコンポーネントガイドを生成するためのデータソース。
6
+ *
7
+ * - type: "single" (cva ベース) / "multipart" (slots ベース)
8
+ * - variants: バリアントキーとそのオプション
9
+ * - slots: multipart の構成パーツ
10
+ * - dependencies: Radix UI 等の実行時依存
11
+ * - a11y: アクセシビリティ上の注意事項
12
+ * - states: 対応する UI ステート
13
+ * - doNot: よくある誤用・禁止事項
14
+ */
15
+ export const componentMetadata = {
16
+ // --- form ---
17
+ button: {
18
+ type: "single",
19
+ variants: {
20
+ variant: [
21
+ "default",
22
+ "destructive",
23
+ "outline",
24
+ "secondary",
25
+ "ghost",
26
+ "link",
27
+ ],
28
+ size: ["default", "sm", "lg", "icon"],
29
+ },
30
+ dependencies: ["@radix-ui/react-slot"],
31
+ a11y: [
32
+ "Use <button> for actions, <a> for navigation (use asChild with Link)",
33
+ "Always provide accessible label: visible text or aria-label for icon buttons",
34
+ "Disabled buttons must use disabled attribute, not aria-disabled",
35
+ ],
36
+ states: ["hover", "focus-visible", "active", "disabled"],
37
+ doNot: [
38
+ "Do not use color props — use variant instead",
39
+ "Do not hardcode border-radius — it comes from design tokens",
40
+ "Do not nest interactive elements inside a button",
41
+ "Do not use ghost/link variant for primary actions",
42
+ 'Do not omit the variant prop — always specify variant explicitly (e.g. variant="default") for clarity',
43
+ ],
44
+ },
45
+ input: {
46
+ type: "single",
47
+ variants: {},
48
+ dependencies: [],
49
+ a11y: [
50
+ "Prefer pairing with <Label> using htmlFor; if not possible, provide aria-label or aria-labelledby",
51
+ "Use aria-describedby for help text or error messages",
52
+ "Ensure visible focus ring (provided by default styles)",
53
+ ],
54
+ states: ["hover", "focus-visible", "disabled", "placeholder"],
55
+ doNot: [
56
+ "Do not use placeholder as label replacement — every input must have an accessible name (prefer visible Label with htmlFor; fallback to sr-only Label, aria-label, or aria-labelledby)",
57
+ "Do not remove the focus ring",
58
+ ],
59
+ },
60
+ textarea: {
61
+ type: "single",
62
+ variants: {},
63
+ dependencies: [],
64
+ a11y: [
65
+ "Always pair with <Label> using htmlFor",
66
+ "Use aria-describedby for character count or error messages",
67
+ ],
68
+ states: ["hover", "focus-visible", "disabled", "placeholder"],
69
+ doNot: ["Do not use placeholder as label replacement"],
70
+ },
71
+ label: {
72
+ type: "single",
73
+ variants: {},
74
+ dependencies: ["@radix-ui/react-label"],
75
+ a11y: ["Must be associated with a form control via htmlFor"],
76
+ states: ["peer-disabled"],
77
+ },
78
+ checkbox: {
79
+ type: "multipart",
80
+ slots: ["root", "indicator"],
81
+ dependencies: ["@radix-ui/react-checkbox"],
82
+ a11y: [
83
+ "Must have an accessible label (adjacent <Label> or aria-label)",
84
+ "Supports aria-checked='indeterminate' for tri-state",
85
+ ],
86
+ states: ["hover", "focus-visible", "checked", "disabled"],
87
+ },
88
+ "radio-group": {
89
+ type: "multipart",
90
+ slots: ["root", "item", "indicator"],
91
+ dependencies: ["@radix-ui/react-radio-group"],
92
+ a11y: [
93
+ "Group must have aria-label or aria-labelledby",
94
+ "Each item must have a value and accessible label",
95
+ ],
96
+ states: ["hover", "focus-visible", "checked", "disabled"],
97
+ },
98
+ select: {
99
+ type: "multipart",
100
+ slots: [
101
+ "trigger",
102
+ "content",
103
+ "item",
104
+ "label",
105
+ "separator",
106
+ "scrollUpButton",
107
+ "scrollDownButton",
108
+ ],
109
+ dependencies: ["@radix-ui/react-select"],
110
+ a11y: [
111
+ "Trigger must have an accessible label",
112
+ "Items support keyboard navigation by default",
113
+ ],
114
+ states: ["hover", "focus-visible", "open", "disabled"],
115
+ },
116
+ switch: {
117
+ type: "multipart",
118
+ slots: ["root", "thumb"],
119
+ dependencies: ["@radix-ui/react-switch"],
120
+ a11y: [
121
+ "Must have an accessible label",
122
+ "Use aria-checked (managed by Radix)",
123
+ ],
124
+ states: ["hover", "focus-visible", "checked", "disabled"],
125
+ },
126
+ slider: {
127
+ type: "multipart",
128
+ slots: ["root", "track", "range", "thumb"],
129
+ dependencies: ["@radix-ui/react-slider"],
130
+ a11y: [
131
+ "Must have aria-label or aria-labelledby",
132
+ "Supports keyboard arrows for value adjustment",
133
+ ],
134
+ states: ["hover", "focus-visible", "disabled"],
135
+ },
136
+ toggle: {
137
+ type: "single",
138
+ variants: {
139
+ variant: ["default", "outline"],
140
+ size: ["default", "sm", "lg"],
141
+ },
142
+ dependencies: ["@radix-ui/react-toggle"],
143
+ a11y: ["Use aria-pressed (managed by Radix)", "Provide accessible label"],
144
+ states: ["hover", "focus-visible", "pressed", "disabled"],
145
+ },
146
+ "toggle-group": {
147
+ type: "multipart",
148
+ slots: ["root", "item"],
149
+ dependencies: ["@radix-ui/react-toggle-group"],
150
+ a11y: [
151
+ "Group must have aria-label",
152
+ "Supports single or multiple selection",
153
+ ],
154
+ states: ["hover", "focus-visible", "pressed", "disabled"],
155
+ },
156
+ form: {
157
+ type: "multipart",
158
+ slots: ["item", "label", "control", "description", "message"],
159
+ dependencies: ["react-hook-form", "@hookform/resolvers"],
160
+ a11y: [
161
+ "FormField auto-connects label, description, and error message via aria attributes",
162
+ "Error messages announced via aria-live",
163
+ ],
164
+ states: ["error", "disabled"],
165
+ },
166
+ field: {
167
+ type: "multipart",
168
+ slots: [
169
+ "fieldSet",
170
+ "fieldLegend",
171
+ "fieldGroup",
172
+ "field",
173
+ "fieldContent",
174
+ "fieldLabel",
175
+ "fieldTitle",
176
+ "fieldDescription",
177
+ "fieldSeparator",
178
+ "fieldSeparatorContent",
179
+ "fieldError",
180
+ ],
181
+ variants: {
182
+ fieldOrientation: ["vertical", "horizontal", "responsive"],
183
+ legendVariant: ["legend", "label"],
184
+ },
185
+ dependencies: [],
186
+ a11y: ["Use fieldset/legend for grouping related fields"],
187
+ states: ["error", "disabled"],
188
+ },
189
+ "input-group": {
190
+ type: "multipart",
191
+ slots: ["root", "addon", "button", "text", "input", "textarea"],
192
+ variants: {
193
+ addonAlign: ["inline-start", "inline-end", "block-start", "block-end"],
194
+ buttonSize: ["xs", "sm", "icon-xs", "icon-sm"],
195
+ },
196
+ dependencies: [],
197
+ states: ["focus-within", "disabled"],
198
+ },
199
+ "input-otp": {
200
+ type: "multipart",
201
+ slots: [
202
+ "root",
203
+ "container",
204
+ "group",
205
+ "slot",
206
+ "slotActive",
207
+ "caret",
208
+ "caretBlink",
209
+ "separator",
210
+ ],
211
+ dependencies: ["input-otp"],
212
+ a11y: ["Auto-focuses next input on entry", "Supports paste for full code"],
213
+ states: ["focus-visible", "active", "disabled"],
214
+ },
215
+ calendar: {
216
+ type: "multipart",
217
+ slots: [
218
+ "root",
219
+ "months",
220
+ "month",
221
+ "caption",
222
+ "nav",
223
+ "table",
224
+ "head",
225
+ "row",
226
+ "cell",
227
+ "day",
228
+ "dayButton",
229
+ "dayButtonSelected",
230
+ "dayButtonToday",
231
+ "dayButtonRangeStart",
232
+ "dayButtonRangeMiddle",
233
+ "dayButtonRangeEnd",
234
+ ],
235
+ dependencies: ["react-day-picker"],
236
+ a11y: [
237
+ "Full keyboard navigation",
238
+ "Announces selected date to screen readers",
239
+ ],
240
+ states: [
241
+ "selected",
242
+ "today",
243
+ "range-start",
244
+ "range-middle",
245
+ "range-end",
246
+ "disabled",
247
+ "outside",
248
+ ],
249
+ },
250
+ combobox: {
251
+ type: "multipart",
252
+ slots: ["trigger", "input", "content", "item", "empty"],
253
+ dependencies: ["@radix-ui/react-popover", "cmdk"],
254
+ a11y: ["Supports typeahead search", "Keyboard navigation in list"],
255
+ states: ["open", "selected", "disabled"],
256
+ },
257
+ "date-picker": {
258
+ type: "multipart",
259
+ slots: ["trigger", "content", "calendar"],
260
+ dependencies: ["react-day-picker", "@radix-ui/react-popover"],
261
+ a11y: ["Combines popover + calendar accessibility"],
262
+ states: ["open", "selected", "disabled"],
263
+ },
264
+ "button-group": {
265
+ type: "multipart",
266
+ slots: ["root", "text", "separator"],
267
+ variants: {
268
+ orientation: ["horizontal", "vertical"],
269
+ },
270
+ dependencies: [],
271
+ states: ["hover", "focus-visible", "disabled"],
272
+ },
273
+ // --- data-display ---
274
+ badge: {
275
+ type: "single",
276
+ variants: {
277
+ variant: ["default", "secondary", "destructive", "outline"],
278
+ },
279
+ dependencies: [],
280
+ a11y: ["Use aria-label if badge content is not self-explanatory"],
281
+ states: [],
282
+ doNot: [
283
+ "Do not use badge for interactive elements — it is display-only",
284
+ "Do not override colors directly — use variant",
285
+ 'Do not use variant="default" for supplementary info — use variant="default" for primary categories and variant="secondary" for supplementary attributes or status labels',
286
+ ],
287
+ },
288
+ accordion: {
289
+ type: "multipart",
290
+ slots: ["item", "trigger", "content"],
291
+ dependencies: ["@radix-ui/react-accordion"],
292
+ a11y: [
293
+ "Trigger uses aria-expanded",
294
+ "Content uses role='region' with aria-labelledby",
295
+ "Full keyboard navigation (Arrow keys, Home, End)",
296
+ ],
297
+ states: ["open", "hover", "focus-visible", "disabled"],
298
+ },
299
+ avatar: {
300
+ type: "multipart",
301
+ slots: ["root", "image", "fallback"],
302
+ dependencies: ["@radix-ui/react-avatar"],
303
+ a11y: [
304
+ "Image must have alt text",
305
+ "Fallback shown when image fails to load",
306
+ ],
307
+ states: [],
308
+ },
309
+ carousel: {
310
+ type: "multipart",
311
+ slots: ["root", "content", "item", "previous", "next"],
312
+ dependencies: ["embla-carousel-react"],
313
+ a11y: [
314
+ "Previous/Next buttons must have aria-label",
315
+ "Use aria-roledescription='carousel' on root",
316
+ ],
317
+ states: ["hover", "focus-visible", "dragging"],
318
+ },
319
+ chart: {
320
+ type: "multipart",
321
+ slots: [
322
+ "container",
323
+ "tooltip",
324
+ "tooltipContent",
325
+ "legend",
326
+ "legendContent",
327
+ ],
328
+ dependencies: ["recharts"],
329
+ a11y: ["Provide text alternative or data table for charts"],
330
+ states: [],
331
+ },
332
+ collapsible: {
333
+ type: "multipart",
334
+ slots: ["root", "trigger", "content"],
335
+ dependencies: ["@radix-ui/react-collapsible"],
336
+ a11y: ["Trigger uses aria-expanded", "Content can be animated"],
337
+ states: ["open", "hover", "focus-visible"],
338
+ },
339
+ "data-table": {
340
+ type: "multipart",
341
+ slots: [
342
+ "root",
343
+ "header",
344
+ "body",
345
+ "footer",
346
+ "row",
347
+ "head",
348
+ "cell",
349
+ "caption",
350
+ ],
351
+ dependencies: ["@tanstack/react-table"],
352
+ a11y: ["Must have caption or aria-label", "Use <th> scope attribute"],
353
+ states: ["hover", "selected"],
354
+ },
355
+ table: {
356
+ type: "multipart",
357
+ slots: [
358
+ "root",
359
+ "header",
360
+ "body",
361
+ "footer",
362
+ "row",
363
+ "head",
364
+ "cell",
365
+ "caption",
366
+ ],
367
+ dependencies: [],
368
+ a11y: [
369
+ "Must have caption or aria-label",
370
+ "Use semantic <thead>/<tbody>/<tfoot>",
371
+ ],
372
+ states: ["hover"],
373
+ },
374
+ item: {
375
+ type: "multipart",
376
+ slots: [
377
+ "group",
378
+ "separator",
379
+ "item",
380
+ "media",
381
+ "content",
382
+ "title",
383
+ "description",
384
+ "actions",
385
+ "header",
386
+ "footer",
387
+ ],
388
+ variants: {
389
+ itemVariant: ["default", "outline", "muted"],
390
+ itemSize: ["default", "sm"],
391
+ mediaVariant: ["default", "icon", "image"],
392
+ },
393
+ dependencies: [],
394
+ states: ["hover", "focus-visible"],
395
+ },
396
+ kbd: {
397
+ type: "multipart",
398
+ slots: ["kbd", "group"],
399
+ dependencies: [],
400
+ a11y: ["Use <kbd> semantic element for keyboard shortcuts"],
401
+ states: [],
402
+ },
403
+ typography: {
404
+ type: "single",
405
+ variants: {},
406
+ dependencies: [],
407
+ states: [],
408
+ },
409
+ // --- feedback ---
410
+ alert: {
411
+ type: "multipart",
412
+ slots: ["root", "title", "description"],
413
+ variants: {
414
+ variant: ["default", "destructive"],
415
+ },
416
+ dependencies: [],
417
+ a11y: [
418
+ "Use role='alert' for urgent messages (destructive)",
419
+ "Use role='status' for informational messages",
420
+ ],
421
+ states: [],
422
+ doNot: ["Do not use alert for toast notifications — use sonner instead"],
423
+ },
424
+ progress: {
425
+ type: "multipart",
426
+ slots: ["root", "indicator"],
427
+ dependencies: ["@radix-ui/react-progress"],
428
+ a11y: [
429
+ "Uses role='progressbar' with aria-valuenow/aria-valuemin/aria-valuemax",
430
+ "Add aria-label describing what is loading",
431
+ ],
432
+ states: ["indeterminate"],
433
+ },
434
+ skeleton: {
435
+ type: "single",
436
+ variants: {},
437
+ dependencies: [],
438
+ a11y: ["Use aria-hidden='true' and provide sr-only loading text"],
439
+ states: ["animate-pulse"],
440
+ },
441
+ sonner: {
442
+ type: "multipart",
443
+ slots: ["toaster", "toast", "description", "actionButton", "cancelButton"],
444
+ dependencies: ["sonner"],
445
+ a11y: [
446
+ "Toasts announced via aria-live",
447
+ "Action buttons must be keyboard accessible",
448
+ ],
449
+ states: [],
450
+ },
451
+ spinner: {
452
+ type: "single",
453
+ variants: {},
454
+ dependencies: [],
455
+ a11y: ["Use aria-hidden='true' and provide sr-only loading text nearby"],
456
+ states: ["animate-spin"],
457
+ },
458
+ empty: {
459
+ type: "multipart",
460
+ slots: ["root", "header", "media", "title", "description", "content"],
461
+ dependencies: [],
462
+ states: [],
463
+ },
464
+ // --- overlay ---
465
+ dialog: {
466
+ type: "multipart",
467
+ slots: [
468
+ "root",
469
+ "trigger",
470
+ "portal",
471
+ "overlay",
472
+ "content",
473
+ "header",
474
+ "title",
475
+ "description",
476
+ "footer",
477
+ "close",
478
+ ],
479
+ dependencies: ["@radix-ui/react-dialog"],
480
+ a11y: [
481
+ "Focus trapped inside dialog when open",
482
+ "Must have title (DialogTitle required)",
483
+ "Escape key closes dialog",
484
+ "Returns focus to trigger on close",
485
+ ],
486
+ states: ["open"],
487
+ doNot: [
488
+ "Do not nest dialogs",
489
+ "Do not use dialog for simple confirmations — use alert-dialog",
490
+ ],
491
+ },
492
+ "alert-dialog": {
493
+ type: "multipart",
494
+ slots: [
495
+ "overlay",
496
+ "content",
497
+ "header",
498
+ "title",
499
+ "description",
500
+ "footer",
501
+ "action",
502
+ "cancel",
503
+ ],
504
+ dependencies: ["@radix-ui/react-alert-dialog"],
505
+ a11y: [
506
+ "Focus trapped inside dialog",
507
+ "Must have title and description",
508
+ "Cannot be dismissed by clicking overlay (intentional)",
509
+ ],
510
+ states: ["open"],
511
+ },
512
+ sheet: {
513
+ type: "multipart",
514
+ slots: ["overlay", "content", "header", "title", "description", "footer"],
515
+ dependencies: ["@radix-ui/react-dialog"],
516
+ a11y: ["Same as dialog: focus trap, title required, escape to close"],
517
+ states: ["open"],
518
+ },
519
+ drawer: {
520
+ type: "multipart",
521
+ slots: [
522
+ "root",
523
+ "trigger",
524
+ "portal",
525
+ "overlay",
526
+ "content",
527
+ "header",
528
+ "footer",
529
+ "title",
530
+ "description",
531
+ "close",
532
+ ],
533
+ dependencies: ["vaul"],
534
+ a11y: ["Supports drag-to-dismiss gesture", "Focus trapped when open"],
535
+ states: ["open", "dragging"],
536
+ },
537
+ "dropdown-menu": {
538
+ type: "multipart",
539
+ slots: [
540
+ "content",
541
+ "item",
542
+ "checkboxItem",
543
+ "radioItem",
544
+ "label",
545
+ "separator",
546
+ "shortcut",
547
+ "subTrigger",
548
+ "subContent",
549
+ ],
550
+ dependencies: ["@radix-ui/react-dropdown-menu"],
551
+ a11y: [
552
+ "Full keyboard navigation (arrows, type-ahead)",
553
+ "Items have role='menuitem'",
554
+ ],
555
+ states: ["open", "hover", "focus-visible"],
556
+ },
557
+ "context-menu": {
558
+ type: "multipart",
559
+ slots: [
560
+ "content",
561
+ "item",
562
+ "checkboxItem",
563
+ "radioItem",
564
+ "label",
565
+ "separator",
566
+ "shortcut",
567
+ "subTrigger",
568
+ "subContent",
569
+ ],
570
+ dependencies: ["@radix-ui/react-context-menu"],
571
+ a11y: ["Opened via right-click", "Full keyboard navigation"],
572
+ states: ["open", "hover", "focus-visible"],
573
+ },
574
+ popover: {
575
+ type: "multipart",
576
+ slots: ["trigger", "content"],
577
+ dependencies: ["@radix-ui/react-popover"],
578
+ a11y: ["Focus managed when open", "Escape to close"],
579
+ states: ["open"],
580
+ },
581
+ "hover-card": {
582
+ type: "multipart",
583
+ slots: ["trigger", "content"],
584
+ dependencies: ["@radix-ui/react-hover-card"],
585
+ a11y: [
586
+ "Content must be accessible without hover (keyboard/touch)",
587
+ "Do not put essential info only in hover card",
588
+ ],
589
+ states: ["open"],
590
+ doNot: ["Do not use for critical information that must be always visible"],
591
+ },
592
+ tooltip: {
593
+ type: "multipart",
594
+ slots: ["trigger", "content"],
595
+ dependencies: ["@radix-ui/react-tooltip"],
596
+ a11y: [
597
+ "Shown on hover AND focus",
598
+ "Content set as aria-describedby on trigger",
599
+ "Escape key dismisses tooltip",
600
+ ],
601
+ states: ["open"],
602
+ doNot: [
603
+ "Do not put interactive content in tooltips",
604
+ "Do not use tooltip as a replacement for labels",
605
+ ],
606
+ },
607
+ menubar: {
608
+ type: "multipart",
609
+ slots: [
610
+ "root",
611
+ "trigger",
612
+ "content",
613
+ "item",
614
+ "checkboxItem",
615
+ "radioItem",
616
+ "label",
617
+ "separator",
618
+ "shortcut",
619
+ "subContent",
620
+ "subTrigger",
621
+ ],
622
+ dependencies: ["@radix-ui/react-menubar"],
623
+ a11y: [
624
+ "Full keyboard navigation between menus",
625
+ "Follows WAI-ARIA menu pattern",
626
+ ],
627
+ states: ["open", "hover", "focus-visible"],
628
+ },
629
+ // --- navigation ---
630
+ breadcrumb: {
631
+ type: "multipart",
632
+ slots: ["root", "list", "item", "link", "page", "separator", "ellipsis"],
633
+ dependencies: [],
634
+ a11y: [
635
+ "Use <nav aria-label='Breadcrumb'>",
636
+ "Current page uses aria-current='page'",
637
+ ],
638
+ states: ["hover"],
639
+ },
640
+ "navigation-menu": {
641
+ type: "multipart",
642
+ slots: [
643
+ "root",
644
+ "list",
645
+ "item",
646
+ "trigger",
647
+ "content",
648
+ "link",
649
+ "indicator",
650
+ "viewport",
651
+ ],
652
+ dependencies: ["@radix-ui/react-navigation-menu"],
653
+ a11y: ["Full keyboard navigation", "Uses WAI-ARIA navigation pattern"],
654
+ states: ["open", "hover", "focus-visible", "active"],
655
+ },
656
+ pagination: {
657
+ type: "multipart",
658
+ slots: [
659
+ "root",
660
+ "content",
661
+ "item",
662
+ "link",
663
+ "linkDisabled",
664
+ "previous",
665
+ "next",
666
+ "ellipsis",
667
+ ],
668
+ dependencies: [],
669
+ a11y: [
670
+ "Use <nav aria-label='Pagination'>",
671
+ "Current page uses aria-current='page'",
672
+ ],
673
+ states: ["hover", "focus-visible", "disabled", "active"],
674
+ },
675
+ tabs: {
676
+ type: "multipart",
677
+ slots: ["root", "list", "trigger", "content"],
678
+ dependencies: ["@radix-ui/react-tabs"],
679
+ a11y: [
680
+ "TabList uses role='tablist'",
681
+ "Tabs use role='tab' with aria-selected",
682
+ "Content uses role='tabpanel' with aria-labelledby",
683
+ "Arrow keys navigate between tabs",
684
+ ],
685
+ states: ["hover", "focus-visible", "active", "disabled"],
686
+ doNot: [
687
+ "Do not use Tabs without TabsList and TabsTrigger — always include the full sub-component structure (Tabs > TabsList > TabsTrigger + TabsContent)",
688
+ "Do not use arbitrary tab values — use descriptive string IDs that match TabsTrigger value and TabsContent value",
689
+ ],
690
+ },
691
+ command: {
692
+ type: "multipart",
693
+ slots: [
694
+ "root",
695
+ "inputWrapper",
696
+ "input",
697
+ "list",
698
+ "empty",
699
+ "group",
700
+ "item",
701
+ "shortcut",
702
+ "separator",
703
+ ],
704
+ dependencies: ["cmdk"],
705
+ a11y: [
706
+ "Typeahead search",
707
+ "Keyboard navigation in list",
708
+ "Announcements via aria-live",
709
+ ],
710
+ states: ["hover", "focus-visible", "selected"],
711
+ },
712
+ sidebar: {
713
+ type: "multipart",
714
+ slots: [
715
+ "provider",
716
+ "sidebar",
717
+ "trigger",
718
+ "header",
719
+ "footer",
720
+ "content",
721
+ "group",
722
+ "groupLabel",
723
+ "groupContent",
724
+ "menu",
725
+ "menuItem",
726
+ "menuButton",
727
+ ],
728
+ variants: {
729
+ menuButtonVariant: ["default", "outline"],
730
+ menuButtonSize: ["default", "sm", "lg"],
731
+ },
732
+ dependencies: [],
733
+ a11y: [
734
+ "Use <aside> with aria-label",
735
+ "Toggle button must indicate expanded state",
736
+ ],
737
+ states: ["expanded", "collapsed", "hover", "focus-visible"],
738
+ },
739
+ // --- layout ---
740
+ card: {
741
+ type: "multipart",
742
+ slots: ["root", "header", "title", "description", "content", "footer"],
743
+ dependencies: [],
744
+ a11y: [
745
+ "If card is clickable, use <a> or <button> as root with asChild",
746
+ "Card title should be a heading element for document outline",
747
+ ],
748
+ states: ["hover"],
749
+ doNot: [
750
+ "Do not nest cards",
751
+ "Do not put too many actions in a single card",
752
+ "Do not use Card without CardHeader and CardTitle — always include them for consistent structure and accessibility",
753
+ "Do not place action buttons (submit, cancel) in CardContent — place actions in CardFooter for consistent layout",
754
+ ],
755
+ },
756
+ "aspect-ratio": {
757
+ type: "single",
758
+ variants: {},
759
+ dependencies: ["@radix-ui/react-aspect-ratio"],
760
+ a11y: ["Image inside must have alt text"],
761
+ states: [],
762
+ },
763
+ separator: {
764
+ type: "single",
765
+ variants: {
766
+ orientation: ["horizontal", "vertical"],
767
+ },
768
+ dependencies: ["@radix-ui/react-separator"],
769
+ a11y: ["Uses role='separator' with aria-orientation"],
770
+ states: [],
771
+ doNot: [
772
+ "Do not use <hr> or border classes for visual separation — use Separator component for semantic and accessible dividers",
773
+ ],
774
+ },
775
+ "scroll-area": {
776
+ type: "multipart",
777
+ slots: ["root", "viewport", "scrollbar", "thumb"],
778
+ dependencies: ["@radix-ui/react-scroll-area"],
779
+ a11y: ["Keyboard scrollable", "Scrollbar visible on hover/focus"],
780
+ states: ["hover"],
781
+ },
782
+ resizable: {
783
+ type: "multipart",
784
+ slots: ["panelGroup", "panel", "handle", "handleIcon", "handleIconSvg"],
785
+ dependencies: ["react-resizable-panels"],
786
+ a11y: ["Handle is keyboard accessible", "Supports arrow keys for resizing"],
787
+ states: ["hover", "focus-visible", "dragging"],
788
+ },
789
+ };