@verdify/ui 0.2.2 → 0.3.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 (124) hide show
  1. package/dist/components/accordion/accordion.variants.d.ts.map +1 -1
  2. package/dist/components/accordion/accordion.variants.js +2 -1
  3. package/dist/components/accordion/accordion.variants.js.map +1 -1
  4. package/dist/components/alert/alert.variants.d.ts.map +1 -1
  5. package/dist/components/alert/alert.variants.js +3 -2
  6. package/dist/components/alert/alert.variants.js.map +1 -1
  7. package/dist/components/breadcrumb/breadcrumb.variants.d.ts.map +1 -1
  8. package/dist/components/breadcrumb/breadcrumb.variants.js +2 -1
  9. package/dist/components/breadcrumb/breadcrumb.variants.js.map +1 -1
  10. package/dist/components/button/button.variants.d.ts.map +1 -1
  11. package/dist/components/button/button.variants.js +2 -1
  12. package/dist/components/button/button.variants.js.map +1 -1
  13. package/dist/components/card/card.variants.d.ts.map +1 -1
  14. package/dist/components/card/card.variants.js +2 -1
  15. package/dist/components/card/card.variants.js.map +1 -1
  16. package/dist/components/checkbox/checkbox.variants.d.ts.map +1 -1
  17. package/dist/components/checkbox/checkbox.variants.js +2 -1
  18. package/dist/components/checkbox/checkbox.variants.js.map +1 -1
  19. package/dist/components/command-palette/command-palette.variants.d.ts +1 -1
  20. package/dist/components/command-palette/command-palette.variants.d.ts.map +1 -1
  21. package/dist/components/command-palette/command-palette.variants.js +5 -3
  22. package/dist/components/command-palette/command-palette.variants.js.map +1 -1
  23. package/dist/components/credential-card/credential-card.variants.d.ts +2 -2
  24. package/dist/components/credential-card/credential-card.variants.d.ts.map +1 -1
  25. package/dist/components/credential-card/credential-card.variants.js +2 -2
  26. package/dist/components/credential-card/credential-card.variants.js.map +1 -1
  27. package/dist/components/data-grid/data-grid.variants.d.ts +1 -1
  28. package/dist/components/data-grid/data-grid.variants.d.ts.map +1 -1
  29. package/dist/components/data-grid/data-grid.variants.js +7 -6
  30. package/dist/components/data-grid/data-grid.variants.js.map +1 -1
  31. package/dist/components/dialog/dialog.variants.d.ts.map +1 -1
  32. package/dist/components/dialog/dialog.variants.js +3 -2
  33. package/dist/components/dialog/dialog.variants.js.map +1 -1
  34. package/dist/components/identity-chip/identity-chip.variants.d.ts.map +1 -1
  35. package/dist/components/identity-chip/identity-chip.variants.js +3 -2
  36. package/dist/components/identity-chip/identity-chip.variants.js.map +1 -1
  37. package/dist/components/input/input.variants.d.ts.map +1 -1
  38. package/dist/components/input/input.variants.js +2 -1
  39. package/dist/components/input/input.variants.js.map +1 -1
  40. package/dist/components/menu/menu.d.ts.map +1 -1
  41. package/dist/components/menu/menu.js +1 -1
  42. package/dist/components/menu/menu.js.map +1 -1
  43. package/dist/components/menu/menu.variants.d.ts +1 -1
  44. package/dist/components/menu/menu.variants.d.ts.map +1 -1
  45. package/dist/components/menu/menu.variants.js +3 -2
  46. package/dist/components/menu/menu.variants.js.map +1 -1
  47. package/dist/components/pagination/pagination.variants.d.ts.map +1 -1
  48. package/dist/components/pagination/pagination.variants.js +2 -1
  49. package/dist/components/pagination/pagination.variants.js.map +1 -1
  50. package/dist/components/popover/popover.variants.d.ts.map +1 -1
  51. package/dist/components/popover/popover.variants.js +4 -3
  52. package/dist/components/popover/popover.variants.js.map +1 -1
  53. package/dist/components/radio/radio.d.ts.map +1 -1
  54. package/dist/components/radio/radio.js +2 -1
  55. package/dist/components/radio/radio.js.map +1 -1
  56. package/dist/components/select/select.variants.d.ts +1 -1
  57. package/dist/components/select/select.variants.d.ts.map +1 -1
  58. package/dist/components/select/select.variants.js +3 -2
  59. package/dist/components/select/select.variants.js.map +1 -1
  60. package/dist/components/sheet/sheet.variants.d.ts.map +1 -1
  61. package/dist/components/sheet/sheet.variants.js +3 -2
  62. package/dist/components/sheet/sheet.variants.js.map +1 -1
  63. package/dist/components/sidebar/sidebar.variants.d.ts +1 -1
  64. package/dist/components/sidebar/sidebar.variants.d.ts.map +1 -1
  65. package/dist/components/sidebar/sidebar.variants.js +4 -3
  66. package/dist/components/sidebar/sidebar.variants.js.map +1 -1
  67. package/dist/components/switch/switch.variants.d.ts.map +1 -1
  68. package/dist/components/switch/switch.variants.js +2 -1
  69. package/dist/components/switch/switch.variants.js.map +1 -1
  70. package/dist/components/table/table.variants.d.ts.map +1 -1
  71. package/dist/components/table/table.variants.js +5 -3
  72. package/dist/components/table/table.variants.js.map +1 -1
  73. package/dist/components/tabs/tabs.variants.d.ts.map +1 -1
  74. package/dist/components/tabs/tabs.variants.js +3 -2
  75. package/dist/components/tabs/tabs.variants.js.map +1 -1
  76. package/dist/components/textarea/textarea.js +1 -1
  77. package/dist/components/textarea/textarea.js.map +1 -1
  78. package/dist/components/textarea/textarea.variants.d.ts.map +1 -1
  79. package/dist/components/textarea/textarea.variants.js +2 -1
  80. package/dist/components/textarea/textarea.variants.js.map +1 -1
  81. package/dist/components/toast/toast.variants.d.ts.map +1 -1
  82. package/dist/components/toast/toast.variants.js +3 -2
  83. package/dist/components/toast/toast.variants.js.map +1 -1
  84. package/dist/components/trust-score/trust-score.variants.d.ts +1 -1
  85. package/dist/components/trust-score/trust-score.variants.d.ts.map +1 -1
  86. package/dist/components/trust-score/trust-score.variants.js +1 -1
  87. package/dist/components/trust-score/trust-score.variants.js.map +1 -1
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +3 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/lib/focus-ring.d.ts +2 -0
  93. package/dist/lib/focus-ring.d.ts.map +1 -0
  94. package/dist/lib/focus-ring.js +5 -0
  95. package/dist/lib/focus-ring.js.map +1 -0
  96. package/package.json +3 -3
  97. package/registry/accordion.json +3 -2
  98. package/registry/alert.json +3 -2
  99. package/registry/breadcrumb.json +3 -2
  100. package/registry/button.json +3 -2
  101. package/registry/card.json +3 -2
  102. package/registry/checkbox.json +3 -2
  103. package/registry/command-palette.json +3 -2
  104. package/registry/credential-card.json +1 -1
  105. package/registry/data-grid.json +2 -1
  106. package/registry/dialog.json +3 -2
  107. package/registry/focus-ring.json +16 -0
  108. package/registry/identity-chip.json +2 -1
  109. package/registry/init.json +1 -1
  110. package/registry/input.json +3 -2
  111. package/registry/menu.json +4 -3
  112. package/registry/pagination.json +3 -2
  113. package/registry/popover.json +3 -2
  114. package/registry/radio.json +3 -2
  115. package/registry/select.json +3 -2
  116. package/registry/sheet.json +3 -2
  117. package/registry/sidebar.json +3 -2
  118. package/registry/switch.json +3 -2
  119. package/registry/table.json +2 -1
  120. package/registry/tabs.json +3 -2
  121. package/registry/textarea.json +3 -2
  122. package/registry/toast.json +3 -2
  123. package/registry/trust-score.json +1 -1
  124. package/registry.json +4 -0
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const dataGridVariants = cva([
3
4
  "relative w-full overflow-auto",
4
5
  "bg-surface-canvas text-body text-text-primary",
@@ -17,7 +18,7 @@ const dataGridCellVariants = cva(
17
18
  [
18
19
  "px-(--space-3) align-middle text-start text-body text-text-primary",
19
20
  // the roving active cell's focus ring — always visible, never removed (2.4.7)
20
- "outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
21
+ focusRing,
21
22
  // keep the active cell clear of the sticky header + a pinned column before it takes focus (2.4.11)
22
23
  "scroll-mt-(--space-12) scroll-ms-(--space-12)"
23
24
  ],
@@ -55,7 +56,7 @@ const dataGridCellVariants = cva(
55
56
  const dataGridColumnHeaderVariants = cva(
56
57
  [
57
58
  "px-(--space-3) align-middle text-start text-label text-text-secondary",
58
- "outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
59
+ focusRing,
59
60
  "scroll-mt-(--space-12) scroll-ms-(--space-12)"
60
61
  ],
61
62
  {
@@ -63,9 +64,9 @@ const dataGridColumnHeaderVariants = cva(
63
64
  defaultVariants: { density: "comfortable" }
64
65
  }
65
66
  );
66
- const dataGridSortButtonClass = "inline-flex items-center gap-(--space-1) -mx-(--space-1) px-(--space-1) rounded-(--radius-sm) text-label text-action-ghost-fg cursor-pointer select-none hover:bg-action-ghost-bg-hover transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant) min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2";
67
+ const dataGridSortButtonClass = "inline-flex items-center gap-(--space-1) -mx-(--space-1) px-(--space-1) rounded-(--radius-sm) text-label text-action-ghost-fg cursor-pointer select-none hover:bg-action-ghost-bg-hover transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant) min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) " + focusRing;
67
68
  const dataGridSortCaretClass = "inline-flex h-(--size-icon-sm) w-(--size-icon-sm) shrink-0 items-center justify-center";
68
- const dataGridSelectionCellClass = "px-(--space-3) align-middle text-start outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 scroll-mt-(--space-12) scroll-ms-(--space-12)";
69
+ const dataGridSelectionCellClass = "px-(--space-3) align-middle text-start " + focusRing + " scroll-mt-(--space-12) scroll-ms-(--space-12)";
69
70
  const dataGridBulkBarClass = "flex items-center gap-(--space-3) px-(--space-3) py-(--space-2) bg-surface-raised border-t border-border-default transition-opacity duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant)";
70
71
  const dataGridBulkCountClass = "text-label text-text-secondary";
71
72
  const dataGridBulkActionVariants = cva(
@@ -75,7 +76,7 @@ const dataGridBulkActionVariants = cva(
75
76
  "min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)",
76
77
  "transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)",
77
78
  "motion-reduce:duration-(--motion-duration-instant)",
78
- "outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2"
79
+ focusRing
79
80
  ],
80
81
  {
81
82
  variants: {
@@ -89,7 +90,7 @@ const dataGridBulkActionVariants = cva(
89
90
  defaultVariants: { destructive: false }
90
91
  }
91
92
  );
92
- const dataGridEmptyClass = "px-(--space-3) py-(--space-6) text-center text-body text-text-muted";
93
+ const dataGridEmptyClass = "px-(--space-3) py-(--space-6) text-center text-body text-text-secondary";
93
94
  const dataGridStatusRegionClass = "sr-only";
94
95
  export {
95
96
  dataGridBulkActionVariants,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/data-grid/data-grid.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// A DataGrid shows many rows of structured records in a scrollable, operable, two-dimensional grid\n// you navigate one cell at a time (spec §1). It is a NEUTRAL data surface (spec §1/§3): neutrals\n// carry roughly 90% of it, and a dense grid earns its legibility from restraint. It paints from the\n// surface, text, and border roles; it reaches the --color-action-* tier only for the controls it\n// hosts (the sortable-header ghost accent, the row-hover affordance, the bulk-bar actions, and the\n// NEUTRAL selection accent) and the --color-status-* tier ONLY inside a cell that reports a real\n// state, paired with text — never as a row tint, a header fill, or the selection accent, and NEVER\n// a brand token as a status (brand != state, G-U2). Selection and the active cell are NEUTRAL action\n// states; a verified/trust state is a status cell (spec §3/§4/§8).\n\n// The scroll container <div role=\"grid\"> (spec §2 grid / §5). The neutral canvas surface and the\n// default cell text role, framed by the outer surface border at the md radius, with a fixed-height\n// scroll viewport (the caller sizes it via className — the token set has no grid-height scale, the\n// caller-owned-dimension precedent J). It NEVER wears the brand violet or a status fill (spec §3/§8)\n// — those belong to the controls and the status cells inside it. The active cell is kept scrolled\n// clear of the sticky header and pinned columns by scroll-margin (2.4.11 Focus Not Obscured).\nexport const dataGridVariants = cva([\n \"relative w-full overflow-auto\",\n \"bg-surface-canvas text-body text-text-primary\",\n \"border border-surface-border rounded-(--radius-md)\",\n]);\n\nexport type DataGridVariantProps = VariantProps<typeof dataGridVariants>;\n\n// The inner table element. The grid is a real <table> for the row/column relationship (1.3.1),\n// border-collapsed so the gridlines read as single hairlines, and start-aligned so it mirrors under\n// dir=\"rtl\" (G-U6).\nexport const dataGridTableClass = \"w-full border-collapse text-start\";\n\n// The column-header row <tr> (spec §2 column-header-row / §4 Default / §5). It is STICKY — it stays\n// pinned to the top of the scroll viewport while rows scroll under it — on the raised neutral surface\n// with the sm elevation so it reads ABOVE the scrolling rows (spec §4/§5). z on the sticky layer so a\n// scrolled cell never paints over the pinned header. A header row NEVER wears a status or brand tint\n// (spec §3/§8).\nexport const dataGridHeaderRowClass =\n \"sticky top-0 z-(--z-index-sticky) bg-surface-raised shadow-sm \" +\n \"border-b border-border-default\";\n\n// The shared cell padding by density (spec §3 density / §5 --space-*). Density tightens the VERTICAL\n// padding only, ABOVE the a11y floor — any in-cell control keeps its own --size-target-* floor\n// (DEC-B: never a fixed height below the floor). Horizontal inline padding is constant.\nconst cellPaddingVariants = {\n density: {\n comfortable: \"py-(--space-3)\",\n compact: \"py-(--space-1)\",\n },\n} as const;\n\n// A data row <tr> (spec §4 Default/Hover/Selected). RESTING: no fill, on the canvas. HOVER: a\n// restrained GHOST fill to track the eye across a wide row — an AFFORDANCE, never the sole carrier of\n// meaning, and never a selection (nothing is selected until you act, spec §4 Hover). SELECTED\n// (aria-selected): the NEUTRAL secondary-action selection accent — selection is a neutral action\n// state, NOT verified green and NOT the brand violet (selecting a row never implies it is verified;\n// brand != state, G-U2, spec §4/§8). Selection is encoded by the row checkbox + aria-selected, so a\n// grayscale reader reads it from the checkbox, not the fill (1.4.1). Motion is the fast token\n// transition on the verdify easing, instant under reduced motion — never the 350ms VerifiedBadge-only\n// theatre (a row hover/select is a plain transition, G-U3).\nexport const dataGridRowClass =\n \"border-b border-border-default \" +\n \"hover:bg-action-ghost-bg-hover \" +\n \"aria-selected:bg-action-secondary-bg-hover \" +\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant)\";\n\n// A data cell <td role=\"gridcell\"> (spec §2 cell, §4/§5). It is ONE focusable unit in the roving\n// grid: exactly one cell is the active cell (tabindex=0) and shows the visible 2px focus ring; every\n// other cell is tabindex=-1. The ring is part of the base and is NEVER removed (spec §4 Focus /\n// 2.4.7). scroll-margin keeps the active cell clear of the sticky header + pinned columns before it\n// takes focus (spec §7, 2.4.11 Focus Not Obscured). Default: the primary text color at the body type\n// role. A `mono` cell takes the monospace role and is isolated LTR so an identifier/key/timestamp\n// stays readable inside an RTL layout (spec §3/§5, G-U6). A `secondary` cell is de-emphasized\n// auxiliary text. A `status` cell carries the status fg paired with the cell's words — the status\n// color lives in the CELL only, never the row or header (spec §3), and NEVER a brand token (brand !=\n// state, G-U2); status-*-bg is the one neutral raised surface, so meaning is carried by the fg + the\n// text, not a saturated fill.\nexport const dataGridCellVariants = cva(\n [\n \"px-(--space-3) align-middle text-start text-body text-text-primary\",\n // the roving active cell's focus ring — always visible, never removed (2.4.7)\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n // keep the active cell clear of the sticky header + a pinned column before it takes focus (2.4.11)\n \"scroll-mt-(--space-12) scroll-ms-(--space-12)\",\n ],\n {\n variants: {\n ...cellPaddingVariants,\n mono: {\n // identifier/key/timestamp: the monospace role, isolated LTR inside RTL text (G-U6)\n true: \"text-mono [direction:ltr]\",\n false: \"\",\n },\n secondary: {\n // de-emphasized secondary cell text (spec §5 --color-text-secondary)\n true: \"text-text-secondary\",\n false: \"\",\n },\n status: {\n none: \"\",\n // each status is the fg only, paired with the cell's text — the words carry the meaning,\n // the fg reinforces it (spec §3/§5); never a saturated -bg fill, never the brand\n verified: \"text-status-verified-on-surface\",\n signal: \"text-status-signal-on-surface\",\n caution: \"text-status-caution-on-surface\",\n critical: \"text-status-critical-on-surface\",\n },\n },\n defaultVariants: {\n density: \"comfortable\",\n mono: false,\n secondary: false,\n status: \"none\",\n },\n },\n);\n\nexport type DataGridCellVariantProps = VariantProps<typeof dataGridCellVariants>;\n\n// A column-header cell <th role=\"columnheader\"> (spec §2 column-header / §4/§5). The header LABEL is\n// the SECONDARY text color at the label type role (the quiet, tracked column label) on the raised\n// header surface — a header NEVER wears a status or brand tint (spec §3/§8). It is also a roving\n// active-cell target, so it carries the same focus ring + scroll-margin as a data cell.\nexport const dataGridColumnHeaderVariants = cva(\n [\n \"px-(--space-3) align-middle text-start text-label text-text-secondary\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n \"scroll-mt-(--space-12) scroll-ms-(--space-12)\",\n ],\n {\n variants: { ...cellPaddingVariants },\n defaultVariants: { density: \"comfortable\" },\n },\n);\n\nexport type DataGridColumnHeaderVariantProps = VariantProps<typeof dataGridColumnHeaderVariants>;\n\n// The SORTABLE-header control (spec §2/§4 Sorted/§6/§7): a real <button> inside the columnheader, so\n// it reads as the control it is and toggles the sort on Enter. It is the GHOST action accent — the\n// label + caret in the ghost fg with the restrained ghost hover fill (spec §4 sortable-header hover /\n// §5) — the action tier is legitimate here because it is a control the grid HOSTS, not a header tint.\n// It carries the target-size floor (40px desktop / 44px touch, spec §7) and inherits the active\n// cell's focus ring from the columnheader. Motion is the fast token transition, never the deliberate\n// verified-check theatre (G-U3). aria-sort lives on the parent th, and the caret encodes direction\n// alongside it so it never rests on color alone (spec §4 Sorted / 1.4.1).\nexport const dataGridSortButtonClass =\n \"inline-flex items-center gap-(--space-1) -mx-(--space-1) px-(--space-1) rounded-(--radius-sm) \" +\n \"text-label text-action-ghost-fg cursor-pointer select-none \" +\n \"hover:bg-action-ghost-bg-hover \" +\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) \" +\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\";\n\n// The sort-direction caret (spec §4 Sorted / §5): the sm icon role, decorative (the direction is also\n// encoded by aria-sort on the th + the glyph's shape via data-direction, so it never rests on color\n// alone — 1.4.1). It inherits the ghost accent color from the button.\nexport const dataGridSortCaretClass =\n \"inline-flex h-(--size-icon-sm) w-(--size-icon-sm) shrink-0 items-center justify-center\";\n\n// The selection cell <td role=\"gridcell\"> and the select-all header cell (spec §2 selection-cell /\n// §5): the leading cell that holds the row's Checkbox, carrying the active-cell focus ring +\n// scroll-margin like any other cell. The checkbox is the committed Checkbox component (reused, not\n// re-rolled), which already binds the control surface + border tokens (spec §5) and the brand action\n// accent on the CHECKED box — a checked checkbox is the brand accent, never status-verified (G-U2):\n// selection is a neutral action state. This wrapper is just the cell padding + the roving focus ring.\nexport const dataGridSelectionCellClass =\n \"px-(--space-3) align-middle text-start \" +\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 \" +\n \"scroll-mt-(--space-12) scroll-ms-(--space-12)\";\n\n// The bulk-action bar (spec §2 bulk-action-bar / §4/§5): a toolbar that appears when rows are\n// selected, on the raised neutral surface with the sm elevation and a hairline top border, holding\n// the selection actions + a clear-selection control. It is a NEUTRAL surface — the COLOR lives on the\n// action buttons it holds, never on the bar (spec §3/§8). Motion is the fast token transition (the\n// bar's appear/transition), never the deliberate verified-check theatre (G-U3).\nexport const dataGridBulkBarClass =\n \"flex items-center gap-(--space-3) px-(--space-3) py-(--space-2) \" +\n \"bg-surface-raised border-t border-border-default \" +\n \"transition-opacity duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant)\";\n\n// The selected-count label in the bulk-action bar (spec §2): the secondary text at the label role —\n// the count of selected rows, in words, so selection is announced, never color alone (1.4.1 / 4.1.3).\nexport const dataGridBulkCountClass = \"text-label text-text-secondary\";\n\n// A bulk-action button (spec §2/§5): the PRIMARY selection action is the primary ACTION accent; a\n// `destructive` action (for example, revoke a key) is the destructive ACTION accent — a risk signal\n// named in TEXT, never a status color (spec §5/§8). Both carry the visible focus ring + target-size\n// floor. Motion is the fast token transition, never the deliberate verified-check theatre (G-U3).\nexport const dataGridBulkActionVariants = cva(\n [\n \"inline-flex items-center justify-center gap-(--space-1) rounded-(--radius-md) px-(--space-3)\",\n \"text-label font-medium cursor-pointer\",\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n ],\n {\n variants: {\n destructive: {\n // a destructive bulk action — a risk signal in the ACTION tier, NEVER a status token\n true: \"bg-action-destructive-bg text-action-destructive-fg border border-action-destructive-border\",\n // the default bulk action — the primary action accent\n false: \"bg-action-primary-bg text-action-primary-fg border border-action-primary-border hover:bg-action-primary-bg-hover\",\n },\n },\n defaultVariants: { destructive: false },\n },\n);\n\nexport type DataGridBulkActionVariantProps = VariantProps<typeof dataGridBulkActionVariants>;\n\n// The empty-state cell (spec §2/§4 Empty): a plain line spanning the full grid width, in the muted\n// text color — an empty grid is NOT an error and never reads as one (no status color). Its copy says\n// why it is empty and what to do next, in plain words ending in a period (spec §4 Empty / voice).\nexport const dataGridEmptyClass =\n \"px-(--space-3) py-(--space-6) text-center text-body text-text-muted\";\n\n// The off-screen-capable live region (spec §2 status-region / §7 4.1.3): announces the resolved row\n// count, sort + filter changes, and the selection count politely; a blocking row-load error\n// assertively. Always sr-only (it never paints — the visual state carries the same information for\n// sighted users), so it reaches assistive tech as TEXT, never color alone (1.4.1 / 4.1.3).\nexport const dataGridStatusRegionClass = \"sr-only\";\n"],"mappings":"AAAA,SAAS,WAA8B;AAkBhC,MAAM,mBAAmB,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,MAAM,qBAAqB;AAO3B,MAAM,yBACX;AAMF,MAAM,sBAAsB;AAAA,EAC1B,SAAS;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AACF;AAWO,MAAM,mBACX;AAiBK,MAAM,uBAAuB;AAAA,EAClC;AAAA,IACE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,GAAG;AAAA,MACH,MAAM;AAAA;AAAA,QAEJ,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA,QAET,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA,QAAQ;AAAA,QACN,MAAM;AAAA;AAAA;AAAA,QAGN,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAQO,MAAM,+BAA+B;AAAA,EAC1C;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU,EAAE,GAAG,oBAAoB;AAAA,IACnC,iBAAiB,EAAE,SAAS,cAAc;AAAA,EAC5C;AACF;AAYO,MAAM,0BACX;AAWK,MAAM,yBACX;AAQK,MAAM,6BACX;AASK,MAAM,uBACX;AAOK,MAAM,yBAAyB;AAM/B,MAAM,6BAA6B;AAAA,EACxC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,aAAa;AAAA;AAAA,QAEX,MAAM;AAAA;AAAA,QAEN,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,aAAa,MAAM;AAAA,EACxC;AACF;AAOO,MAAM,qBACX;AAMK,MAAM,4BAA4B;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/data-grid/data-grid.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// A DataGrid shows many rows of structured records in a scrollable, operable, two-dimensional grid\n// you navigate one cell at a time (spec §1). It is a NEUTRAL data surface (spec §1/§3): neutrals\n// carry roughly 90% of it, and a dense grid earns its legibility from restraint. It paints from the\n// surface, text, and border roles; it reaches the --color-action-* tier only for the controls it\n// hosts (the sortable-header ghost accent, the row-hover affordance, the bulk-bar actions, and the\n// NEUTRAL selection accent) and the --color-status-* tier ONLY inside a cell that reports a real\n// state, paired with text — never as a row tint, a header fill, or the selection accent, and NEVER\n// a brand token as a status (brand != state, G-U2). Selection and the active cell are NEUTRAL action\n// states; a verified/trust state is a status cell (spec §3/§4/§8).\n\n// The scroll container <div role=\"grid\"> (spec §2 grid / §5). The neutral canvas surface and the\n// default cell text role, framed by the outer surface border at the md radius, with a fixed-height\n// scroll viewport (the caller sizes it via className — the token set has no grid-height scale, the\n// caller-owned-dimension precedent J). It NEVER wears the brand violet or a status fill (spec §3/§8)\n// — those belong to the controls and the status cells inside it. The active cell is kept scrolled\n// clear of the sticky header and pinned columns by scroll-margin (2.4.11 Focus Not Obscured).\nexport const dataGridVariants = cva([\n \"relative w-full overflow-auto\",\n \"bg-surface-canvas text-body text-text-primary\",\n \"border border-surface-border rounded-(--radius-md)\",\n]);\n\nexport type DataGridVariantProps = VariantProps<typeof dataGridVariants>;\n\n// The inner table element. The grid is a real <table> for the row/column relationship (1.3.1),\n// border-collapsed so the gridlines read as single hairlines, and start-aligned so it mirrors under\n// dir=\"rtl\" (G-U6).\nexport const dataGridTableClass = \"w-full border-collapse text-start\";\n\n// The column-header row <tr> (spec §2 column-header-row / §4 Default / §5). It is STICKY — it stays\n// pinned to the top of the scroll viewport while rows scroll under it — on the raised neutral surface\n// with the sm elevation so it reads ABOVE the scrolling rows (spec §4/§5). z on the sticky layer so a\n// scrolled cell never paints over the pinned header. A header row NEVER wears a status or brand tint\n// (spec §3/§8).\nexport const dataGridHeaderRowClass =\n \"sticky top-0 z-(--z-index-sticky) bg-surface-raised shadow-sm \" +\n \"border-b border-border-default\";\n\n// The shared cell padding by density (spec §3 density / §5 --space-*). Density tightens the VERTICAL\n// padding only, ABOVE the a11y floor — any in-cell control keeps its own --size-target-* floor\n// (DEC-B: never a fixed height below the floor). Horizontal inline padding is constant.\nconst cellPaddingVariants = {\n density: {\n comfortable: \"py-(--space-3)\",\n compact: \"py-(--space-1)\",\n },\n} as const;\n\n// A data row <tr> (spec §4 Default/Hover/Selected). RESTING: no fill, on the canvas. HOVER: a\n// restrained GHOST fill to track the eye across a wide row — an AFFORDANCE, never the sole carrier of\n// meaning, and never a selection (nothing is selected until you act, spec §4 Hover). SELECTED\n// (aria-selected): the NEUTRAL secondary-action selection accent — selection is a neutral action\n// state, NOT verified green and NOT the brand violet (selecting a row never implies it is verified;\n// brand != state, G-U2, spec §4/§8). Selection is encoded by the row checkbox + aria-selected, so a\n// grayscale reader reads it from the checkbox, not the fill (1.4.1). Motion is the fast token\n// transition on the verdify easing, instant under reduced motion — never the 350ms VerifiedBadge-only\n// theatre (a row hover/select is a plain transition, G-U3).\nexport const dataGridRowClass =\n \"border-b border-border-default \" +\n \"hover:bg-action-ghost-bg-hover \" +\n \"aria-selected:bg-action-secondary-bg-hover \" +\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant)\";\n\n// A data cell <td role=\"gridcell\"> (spec §2 cell, §4/§5). It is ONE focusable unit in the roving\n// grid: exactly one cell is the active cell (tabindex=0) and shows the visible 2px focus ring; every\n// other cell is tabindex=-1. The ring is part of the base and is NEVER removed (spec §4 Focus /\n// 2.4.7). scroll-margin keeps the active cell clear of the sticky header + pinned columns before it\n// takes focus (spec §7, 2.4.11 Focus Not Obscured). Default: the primary text color at the body type\n// role. A `mono` cell takes the monospace role and is isolated LTR so an identifier/key/timestamp\n// stays readable inside an RTL layout (spec §3/§5, G-U6). A `secondary` cell is de-emphasized\n// auxiliary text. A `status` cell carries the status fg paired with the cell's words — the status\n// color lives in the CELL only, never the row or header (spec §3), and NEVER a brand token (brand !=\n// state, G-U2); status-*-bg is the one neutral raised surface, so meaning is carried by the fg + the\n// text, not a saturated fill.\nexport const dataGridCellVariants = cva(\n [\n \"px-(--space-3) align-middle text-start text-body text-text-primary\",\n // the roving active cell's focus ring — always visible, never removed (2.4.7)\n focusRing,\n // keep the active cell clear of the sticky header + a pinned column before it takes focus (2.4.11)\n \"scroll-mt-(--space-12) scroll-ms-(--space-12)\",\n ],\n {\n variants: {\n ...cellPaddingVariants,\n mono: {\n // identifier/key/timestamp: the monospace role, isolated LTR inside RTL text (G-U6)\n true: \"text-mono [direction:ltr]\",\n false: \"\",\n },\n secondary: {\n // de-emphasized secondary cell text (spec §5 --color-text-secondary)\n true: \"text-text-secondary\",\n false: \"\",\n },\n status: {\n none: \"\",\n // each status is the fg only, paired with the cell's text — the words carry the meaning,\n // the fg reinforces it (spec §3/§5); never a saturated -bg fill, never the brand\n verified: \"text-status-verified-on-surface\",\n signal: \"text-status-signal-on-surface\",\n caution: \"text-status-caution-on-surface\",\n critical: \"text-status-critical-on-surface\",\n },\n },\n defaultVariants: {\n density: \"comfortable\",\n mono: false,\n secondary: false,\n status: \"none\",\n },\n },\n);\n\nexport type DataGridCellVariantProps = VariantProps<typeof dataGridCellVariants>;\n\n// A column-header cell <th role=\"columnheader\"> (spec §2 column-header / §4/§5). The header LABEL is\n// the SECONDARY text color at the label type role (the quiet, tracked column label) on the raised\n// header surface — a header NEVER wears a status or brand tint (spec §3/§8). It is also a roving\n// active-cell target, so it carries the same focus ring + scroll-margin as a data cell.\nexport const dataGridColumnHeaderVariants = cva(\n [\n \"px-(--space-3) align-middle text-start text-label text-text-secondary\",\n focusRing,\n \"scroll-mt-(--space-12) scroll-ms-(--space-12)\",\n ],\n {\n variants: { ...cellPaddingVariants },\n defaultVariants: { density: \"comfortable\" },\n },\n);\n\nexport type DataGridColumnHeaderVariantProps = VariantProps<typeof dataGridColumnHeaderVariants>;\n\n// The SORTABLE-header control (spec §2/§4 Sorted/§6/§7): a real <button> inside the columnheader, so\n// it reads as the control it is and toggles the sort on Enter. It is the GHOST action accent — the\n// label + caret in the ghost fg with the restrained ghost hover fill (spec §4 sortable-header hover /\n// §5) — the action tier is legitimate here because it is a control the grid HOSTS, not a header tint.\n// It carries the target-size floor (40px desktop / 44px touch, spec §7) and inherits the active\n// cell's focus ring from the columnheader. Motion is the fast token transition, never the deliberate\n// verified-check theatre (G-U3). aria-sort lives on the parent th, and the caret encodes direction\n// alongside it so it never rests on color alone (spec §4 Sorted / 1.4.1).\nexport const dataGridSortButtonClass =\n \"inline-flex items-center gap-(--space-1) -mx-(--space-1) px-(--space-1) rounded-(--radius-sm) \" +\n \"text-label text-action-ghost-fg cursor-pointer select-none \" +\n \"hover:bg-action-ghost-bg-hover \" +\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) \" +\n focusRing;\n\n// The sort-direction caret (spec §4 Sorted / §5): the sm icon role, decorative (the direction is also\n// encoded by aria-sort on the th + the glyph's shape via data-direction, so it never rests on color\n// alone — 1.4.1). It inherits the ghost accent color from the button.\nexport const dataGridSortCaretClass =\n \"inline-flex h-(--size-icon-sm) w-(--size-icon-sm) shrink-0 items-center justify-center\";\n\n// The selection cell <td role=\"gridcell\"> and the select-all header cell (spec §2 selection-cell /\n// §5): the leading cell that holds the row's Checkbox, carrying the active-cell focus ring +\n// scroll-margin like any other cell. The checkbox is the committed Checkbox component (reused, not\n// re-rolled), which already binds the control surface + border tokens (spec §5) and the brand action\n// accent on the CHECKED box — a checked checkbox is the brand accent, never status-verified (G-U2):\n// selection is a neutral action state. This wrapper is just the cell padding + the roving focus ring.\nexport const dataGridSelectionCellClass =\n \"px-(--space-3) align-middle text-start \" +\n focusRing + \" \" +\n \"scroll-mt-(--space-12) scroll-ms-(--space-12)\";\n\n// The bulk-action bar (spec §2 bulk-action-bar / §4/§5): a toolbar that appears when rows are\n// selected, on the raised neutral surface with the sm elevation and a hairline top border, holding\n// the selection actions + a clear-selection control. It is a NEUTRAL surface — the COLOR lives on the\n// action buttons it holds, never on the bar (spec §3/§8). Motion is the fast token transition (the\n// bar's appear/transition), never the deliberate verified-check theatre (G-U3).\nexport const dataGridBulkBarClass =\n \"flex items-center gap-(--space-3) px-(--space-3) py-(--space-2) \" +\n \"bg-surface-raised border-t border-border-default \" +\n \"transition-opacity duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant)\";\n\n// The selected-count label in the bulk-action bar (spec §2): the secondary text at the label role —\n// the count of selected rows, in words, so selection is announced, never color alone (1.4.1 / 4.1.3).\nexport const dataGridBulkCountClass = \"text-label text-text-secondary\";\n\n// A bulk-action button (spec §2/§5): the PRIMARY selection action is the primary ACTION accent; a\n// `destructive` action (for example, revoke a key) is the destructive ACTION accent — a risk signal\n// named in TEXT, never a status color (spec §5/§8). Both carry the visible focus ring + target-size\n// floor. Motion is the fast token transition, never the deliberate verified-check theatre (G-U3).\nexport const dataGridBulkActionVariants = cva(\n [\n \"inline-flex items-center justify-center gap-(--space-1) rounded-(--radius-md) px-(--space-3)\",\n \"text-label font-medium cursor-pointer\",\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n focusRing,\n ],\n {\n variants: {\n destructive: {\n // a destructive bulk action — a risk signal in the ACTION tier, NEVER a status token\n true: \"bg-action-destructive-bg text-action-destructive-fg border border-action-destructive-border\",\n // the default bulk action — the primary action accent\n false: \"bg-action-primary-bg text-action-primary-fg border border-action-primary-border hover:bg-action-primary-bg-hover\",\n },\n },\n defaultVariants: { destructive: false },\n },\n);\n\nexport type DataGridBulkActionVariantProps = VariantProps<typeof dataGridBulkActionVariants>;\n\n// The empty-state cell (spec §2/§4 Empty): a plain line spanning the full grid width — an empty grid\n// is NOT an error and never reads as one (no status color). Its copy says why it is empty and what to\n// do next (spec §4 Empty / voice). Essential text, so it uses the SECONDARY text color (AA), not the\n// decorative-only muted role (accessibility.md).\nexport const dataGridEmptyClass =\n \"px-(--space-3) py-(--space-6) text-center text-body text-text-secondary\";\n\n// The off-screen-capable live region (spec §2 status-region / §7 4.1.3): announces the resolved row\n// count, sort + filter changes, and the selection count politely; a blocking row-load error\n// assertively. Always sr-only (it never paints — the visual state carries the same information for\n// sighted users), so it reaches assistive tech as TEXT, never color alone (1.4.1 / 4.1.3).\nexport const dataGridStatusRegionClass = \"sr-only\";\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAkBnB,MAAM,mBAAmB,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,MAAM,qBAAqB;AAO3B,MAAM,yBACX;AAMF,MAAM,sBAAsB;AAAA,EAC1B,SAAS;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AACF;AAWO,MAAM,mBACX;AAiBK,MAAM,uBAAuB;AAAA,EAClC;AAAA,IACE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,GAAG;AAAA,MACH,MAAM;AAAA;AAAA,QAEJ,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA,QAET,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA,QAAQ;AAAA,QACN,MAAM;AAAA;AAAA;AAAA,QAGN,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAQO,MAAM,+BAA+B;AAAA,EAC1C;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU,EAAE,GAAG,oBAAoB;AAAA,IACnC,iBAAiB,EAAE,SAAS,cAAc;AAAA,EAC5C;AACF;AAYO,MAAM,0BACX,iYAMA;AAKK,MAAM,yBACX;AAQK,MAAM,6BACX,4CACA,YAAY;AAQP,MAAM,uBACX;AAOK,MAAM,yBAAyB;AAM/B,MAAM,6BAA6B;AAAA,EACxC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,aAAa;AAAA;AAAA,QAEX,MAAM;AAAA;AAAA,QAEN,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,aAAa,MAAM;AAAA,EACxC;AACF;AAQO,MAAM,qBACX;AAMK,MAAM,4BAA4B;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.variants.d.ts","sourceRoot":"","sources":["../../../src/components/dialog/dialog.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAgBlE,eAAO,MAAM,mBAAmB,oFAK9B,CAAC;AAcH,eAAO,MAAM,mBAAmB;;8EA+B/B,CAAC;AAIF,eAAO,MAAM,iBAAiB,mGACoE,CAAC;AAInG,eAAO,MAAM,gBAAgB,8BAA8B,CAAC;AAI5D,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AAKtE,eAAO,MAAM,eAAe,iEACoC,CAAC;AAMjE,eAAO,MAAM,iBAAiB,gGACiE,CAAC;AAOhG,eAAO,MAAM,mBAAmB,oFAa9B,CAAC;AAIH,eAAO,MAAM,qBAAqB,0CAA0C,CAAC;AAE7E,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"dialog.variants.d.ts","sourceRoot":"","sources":["../../../src/components/dialog/dialog.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAiBlE,eAAO,MAAM,mBAAmB,oFAK9B,CAAC;AAcH,eAAO,MAAM,mBAAmB;;8EA+B/B,CAAC;AAIF,eAAO,MAAM,iBAAiB,mGACoE,CAAC;AAInG,eAAO,MAAM,gBAAgB,8BAA8B,CAAC;AAI5D,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AAKtE,eAAO,MAAM,eAAe,iEACoC,CAAC;AAMjE,eAAO,MAAM,iBAAiB,gGACiE,CAAC;AAOhG,eAAO,MAAM,mBAAmB,oFAa9B,CAAC;AAIH,eAAO,MAAM,qBAAqB,0CAA0C,CAAC;AAE7E,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const dialogScrimVariants = cva([
3
4
  "fixed inset-0 z-(--z-index-modal) bg-scrim-dark",
4
5
  "transition-opacity duration-(--motion-duration-base) ease-(--motion-easing-verdify)",
@@ -23,7 +24,7 @@ const dialogPanelVariants = cva(
23
24
  "data-[state=open]:scale-100 data-[state=closed]:scale-95",
24
25
  // the panel takes focus when there is no obvious first control; its ring is never removed
25
26
  "outline-none",
26
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2"
27
+ focusRing
27
28
  ],
28
29
  {
29
30
  variants: {
@@ -54,7 +55,7 @@ const dialogCloseVariants = cva([
54
55
  "sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop)",
55
56
  // visible 2px focus ring at 2px offset; never removed
56
57
  "outline-none",
57
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2"
58
+ focusRing
58
59
  ]);
59
60
  const dialogCloseGlyphClass = "h-(--size-icon-md) w-(--size-icon-md)";
60
61
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/dialog/dialog.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// Dialog is a NEUTRAL overlay surface (spec §1/§3/§5/§8): brand violet and Verified Green are\n// accents, neutrals carry the surface. The PANEL and the SCRIM are neutral; Sovereign Violet\n// appears only on a footer PRIMARY action — through Button, not here — and Verified Green never\n// appears on the dialog as decoration. A destructive confirm uses the destructive ACTION\n// treatment on its confirm button (Button), never a red panel. So NOTHING in this file binds an\n// --color-action-primary-* or --color-status-* fill (brand != state, G-U2). This is the ONLY\n// token-binding site (skill §5 hard rule).\n\n// The scrim: the dimming layer behind the panel that separates the dialog from the page and\n// absorbs outside clicks (spec §2 scrim, §5 --color-scrim-*). It is a neutral dim on the modal\n// z-layer, decorative (no role). The fade is a PLAIN base transition + verdify easing, instant\n// under reduced motion — never the 350ms VerifiedBadge-only theatre (G-U3 motion-theatre gate).\n// Enter/exit ride Radix's data-state on the overlay (attribute-selector variants, not arbitrary\n// values). On a light surface the dark scrim token applies (spec §5: scrim-dark on light).\nexport const dialogScrimVariants = cva([\n \"fixed inset-0 z-(--z-index-modal) bg-scrim-dark\",\n \"transition-opacity duration-(--motion-duration-base) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\",\n]);\n\n// The panel: the raised container holding the dialog content; it takes role=dialog + the focus\n// trap (Radix). A NEUTRAL raised surface (--color-surface-raised) with the outer surface border,\n// the lg corner radius, the lg elevation shadow above the scrim, centered on the modal z-layer.\n// It never exceeds the viewport and scrolls its BODY when content overflows (spec §3) — the\n// panel caps its own height to the viewport less the gutter; the DialogBody owns the scroll. The\n// open/close transition is the BASE duration + verdify easing, instant under reduced motion, and\n// rides Radix's data-state (attribute-selector enter/exit, not arbitrary values). NEVER the\n// deliberate verified-check theatre (G-U3). Panel padding/gaps come from --space-*.\n//\n// size = the panel's MAX WIDTH only (spec §3 sm/md/lg, md default), bound to the --container-*\n// scale; the panel is full-width up to that cap and centered. There is no fixed height — the\n// height emerges from the content up to the viewport cap.\nexport const dialogPanelVariants = cva(\n [\n // centered on the modal layer; full available width up to the size cap, with side gutters\n \"fixed left-1/2 top-1/2 z-(--z-index-modal) -translate-x-1/2 -translate-y-1/2\",\n \"flex w-[calc(100%-var(--space-8))] flex-col gap-(--space-4)\",\n // never taller than the viewport less the gutter; the body scrolls within (spec §3)\n \"max-h-[calc(100dvh-var(--space-8))]\",\n // neutral raised surface + outer border + lg radius + lg elevation; panel inset padding\n \"bg-surface-raised border border-surface-border rounded-(--radius-lg) shadow-(--shadow-lg)\",\n \"p-(--space-6)\",\n // base open/close transition + verdify easing, instant under reduced motion (NEVER deliberate)\n \"transition-[opacity,transform] duration-(--motion-duration-base) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // enter/exit ride Radix data-state — attribute-selector variants, not arbitrary values\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\",\n \"data-[state=open]:scale-100 data-[state=closed]:scale-95\",\n // the panel takes focus when there is no obvious first control; its ring is never removed\n \"outline-none\",\n \"focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n ],\n {\n variants: {\n // size = max width only (spec §3). md is the default. Bound to the --container-* scale.\n size: {\n sm: \"max-w-(--container-sm)\",\n md: \"max-w-(--container-md)\",\n lg: \"max-w-(--container-lg)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n// The header: the top region holding the title and the optional close button on the inline-end.\n// Logical-property layout (G-U6); a neutral hairline divider under it continues the surface.\nexport const dialogHeaderClass =\n \"flex items-start justify-between gap-(--space-4) border-b border-border-default pb-(--space-4)\";\n\n// The title: names the dialog in one short statement; it is the accessible name (Radix wires\n// aria-labelledby). The h2 type role in primary text (spec §5 --text-h2 / --color-text-primary).\nexport const dialogTitleClass = \"text-h2 text-text-primary\";\n\n// The description: optional supporting text under the title, associated with the panel for\n// screen readers (Radix wires aria-describedby). Body type role in secondary text (spec §5).\nexport const dialogDescriptionClass = \"text-body text-text-secondary\";\n\n// The body: the scrollable content region between header and footer. The panel caps its height\n// to the viewport; the body takes the remaining space and scrolls when content overflows (spec\n// §3). Body text is the body type role in secondary text (spec §5 --text-body / text-secondary).\nexport const dialogBodyClass =\n \"min-h-0 flex-1 overflow-y-auto text-body text-text-secondary\";\n\n// The footer: the action region holding the primary and any secondary/cancel action, aligned to\n// the inline-end with a neutral hairline divider above it. The actions themselves are Buttons —\n// the dialog spec does not restate their --color-action-* bindings (spec §5 note). Logical-\n// property layout (G-U6): actions flow inline-end with a gap.\nexport const dialogFooterClass =\n \"flex items-center justify-end gap-(--space-2) border-t border-border-default pt-(--space-4)\";\n\n// The close button: the dismiss control in the header. A NEUTRAL ghost surface — the glyph in\n// --color-action-ghost-fg at rest, the restrained ghost hover fill (spec §5 ghost-fg /\n// ghost-bg-hover), the md radius, the persistent focus ring, the target-size floor (44px touch /\n// 40px pointer, spec §7 2.5.8 / DEC-B) with the height EMERGING from the floor, never fixed\n// below it. fast functional hover motion + verdify easing, instant under reduced motion (G-U3).\nexport const dialogCloseVariants = cva([\n \"inline-flex items-center justify-center rounded-(--radius-md)\",\n // neutral ghost surface: glyph color at rest + restrained hover fill (no bg/border at rest)\n \"text-action-ghost-fg hover:bg-action-ghost-bg-hover\",\n // fast functional hover transition + verdify easing, instant under reduced motion (NEVER deliberate)\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // target-size floor: 44px touch / 40px pointer; the close button is square at the floor (DEC-B)\n \"min-h-(--size-target-mobile) min-w-(--size-target-mobile)\",\n \"sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop)\",\n // visible 2px focus ring at 2px offset; never removed\n \"outline-none\",\n \"focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n]);\n\n// The close glyph: a neutral X, --size-icon-md, drawn with currentColor so it inherits the\n// button's ghost-fg. Decorative (aria-hidden) — the button carries the accessible name (spec §7).\nexport const dialogCloseGlyphClass = \"h-(--size-icon-md) w-(--size-icon-md)\";\n\nexport type DialogPanelVariantProps = VariantProps<typeof dialogPanelVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AAgBhC,MAAM,sBAAsB,IAAI;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAcM,MAAM,sBAAsB;AAAA,EACjC;AAAA;AAAA,IAEE;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA,MAER,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,KAAK;AAAA,EAChC;AACF;AAIO,MAAM,oBACX;AAIK,MAAM,mBAAmB;AAIzB,MAAM,yBAAyB;AAK/B,MAAM,kBACX;AAMK,MAAM,oBACX;AAOK,MAAM,sBAAsB,IAAI;AAAA,EACrC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AACF,CAAC;AAIM,MAAM,wBAAwB;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/dialog/dialog.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// Dialog is a NEUTRAL overlay surface (spec §1/§3/§5/§8): brand violet and Verified Green are\n// accents, neutrals carry the surface. The PANEL and the SCRIM are neutral; Sovereign Violet\n// appears only on a footer PRIMARY action — through Button, not here — and Verified Green never\n// appears on the dialog as decoration. A destructive confirm uses the destructive ACTION\n// treatment on its confirm button (Button), never a red panel. So NOTHING in this file binds an\n// --color-action-primary-* or --color-status-* fill (brand != state, G-U2). This is the ONLY\n// token-binding site (skill §5 hard rule).\n\n// The scrim: the dimming layer behind the panel that separates the dialog from the page and\n// absorbs outside clicks (spec §2 scrim, §5 --color-scrim-*). It is a neutral dim on the modal\n// z-layer, decorative (no role). The fade is a PLAIN base transition + verdify easing, instant\n// under reduced motion — never the 350ms VerifiedBadge-only theatre (G-U3 motion-theatre gate).\n// Enter/exit ride Radix's data-state on the overlay (attribute-selector variants, not arbitrary\n// values). On a light surface the dark scrim token applies (spec §5: scrim-dark on light).\nexport const dialogScrimVariants = cva([\n \"fixed inset-0 z-(--z-index-modal) bg-scrim-dark\",\n \"transition-opacity duration-(--motion-duration-base) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\",\n]);\n\n// The panel: the raised container holding the dialog content; it takes role=dialog + the focus\n// trap (Radix). A NEUTRAL raised surface (--color-surface-raised) with the outer surface border,\n// the lg corner radius, the lg elevation shadow above the scrim, centered on the modal z-layer.\n// It never exceeds the viewport and scrolls its BODY when content overflows (spec §3) — the\n// panel caps its own height to the viewport less the gutter; the DialogBody owns the scroll. The\n// open/close transition is the BASE duration + verdify easing, instant under reduced motion, and\n// rides Radix's data-state (attribute-selector enter/exit, not arbitrary values). NEVER the\n// deliberate verified-check theatre (G-U3). Panel padding/gaps come from --space-*.\n//\n// size = the panel's MAX WIDTH only (spec §3 sm/md/lg, md default), bound to the --container-*\n// scale; the panel is full-width up to that cap and centered. There is no fixed height — the\n// height emerges from the content up to the viewport cap.\nexport const dialogPanelVariants = cva(\n [\n // centered on the modal layer; full available width up to the size cap, with side gutters\n \"fixed left-1/2 top-1/2 z-(--z-index-modal) -translate-x-1/2 -translate-y-1/2\",\n \"flex w-[calc(100%-var(--space-8))] flex-col gap-(--space-4)\",\n // never taller than the viewport less the gutter; the body scrolls within (spec §3)\n \"max-h-[calc(100dvh-var(--space-8))]\",\n // neutral raised surface + outer border + lg radius + lg elevation; panel inset padding\n \"bg-surface-raised border border-surface-border rounded-(--radius-lg) shadow-(--shadow-lg)\",\n \"p-(--space-6)\",\n // base open/close transition + verdify easing, instant under reduced motion (NEVER deliberate)\n \"transition-[opacity,transform] duration-(--motion-duration-base) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // enter/exit ride Radix data-state — attribute-selector variants, not arbitrary values\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\",\n \"data-[state=open]:scale-100 data-[state=closed]:scale-95\",\n // the panel takes focus when there is no obvious first control; its ring is never removed\n \"outline-none\",\n focusRing,\n ],\n {\n variants: {\n // size = max width only (spec §3). md is the default. Bound to the --container-* scale.\n size: {\n sm: \"max-w-(--container-sm)\",\n md: \"max-w-(--container-md)\",\n lg: \"max-w-(--container-lg)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n// The header: the top region holding the title and the optional close button on the inline-end.\n// Logical-property layout (G-U6); a neutral hairline divider under it continues the surface.\nexport const dialogHeaderClass =\n \"flex items-start justify-between gap-(--space-4) border-b border-border-default pb-(--space-4)\";\n\n// The title: names the dialog in one short statement; it is the accessible name (Radix wires\n// aria-labelledby). The h2 type role in primary text (spec §5 --text-h2 / --color-text-primary).\nexport const dialogTitleClass = \"text-h2 text-text-primary\";\n\n// The description: optional supporting text under the title, associated with the panel for\n// screen readers (Radix wires aria-describedby). Body type role in secondary text (spec §5).\nexport const dialogDescriptionClass = \"text-body text-text-secondary\";\n\n// The body: the scrollable content region between header and footer. The panel caps its height\n// to the viewport; the body takes the remaining space and scrolls when content overflows (spec\n// §3). Body text is the body type role in secondary text (spec §5 --text-body / text-secondary).\nexport const dialogBodyClass =\n \"min-h-0 flex-1 overflow-y-auto text-body text-text-secondary\";\n\n// The footer: the action region holding the primary and any secondary/cancel action, aligned to\n// the inline-end with a neutral hairline divider above it. The actions themselves are Buttons —\n// the dialog spec does not restate their --color-action-* bindings (spec §5 note). Logical-\n// property layout (G-U6): actions flow inline-end with a gap.\nexport const dialogFooterClass =\n \"flex items-center justify-end gap-(--space-2) border-t border-border-default pt-(--space-4)\";\n\n// The close button: the dismiss control in the header. A NEUTRAL ghost surface — the glyph in\n// --color-action-ghost-fg at rest, the restrained ghost hover fill (spec §5 ghost-fg /\n// ghost-bg-hover), the md radius, the persistent focus ring, the target-size floor (44px touch /\n// 40px pointer, spec §7 2.5.8 / DEC-B) with the height EMERGING from the floor, never fixed\n// below it. fast functional hover motion + verdify easing, instant under reduced motion (G-U3).\nexport const dialogCloseVariants = cva([\n \"inline-flex items-center justify-center rounded-(--radius-md)\",\n // neutral ghost surface: glyph color at rest + restrained hover fill (no bg/border at rest)\n \"text-action-ghost-fg hover:bg-action-ghost-bg-hover\",\n // fast functional hover transition + verdify easing, instant under reduced motion (NEVER deliberate)\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // target-size floor: 44px touch / 40px pointer; the close button is square at the floor (DEC-B)\n \"min-h-(--size-target-mobile) min-w-(--size-target-mobile)\",\n \"sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop)\",\n // visible 2px focus ring at 2px offset; never removed\n \"outline-none\",\n focusRing,\n]);\n\n// The close glyph: a neutral X, --size-icon-md, drawn with currentColor so it inherits the\n// button's ghost-fg. Decorative (aria-hidden) — the button carries the accessible name (spec §7).\nexport const dialogCloseGlyphClass = \"h-(--size-icon-md) w-(--size-icon-md)\";\n\nexport type DialogPanelVariantProps = VariantProps<typeof dialogPanelVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAgBnB,MAAM,sBAAsB,IAAI;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAcM,MAAM,sBAAsB;AAAA,EACjC;AAAA;AAAA,IAEE;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA,MAER,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,KAAK;AAAA,EAChC;AACF;AAIO,MAAM,oBACX;AAIK,MAAM,mBAAmB;AAIzB,MAAM,yBAAyB;AAK/B,MAAM,kBACX;AAMK,MAAM,oBACX;AAOK,MAAM,sBAAsB,IAAI;AAAA,EACrC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AACF,CAAC;AAIM,MAAM,wBAAwB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"identity-chip.variants.d.ts","sourceRoot":"","sources":["../../../src/components/identity-chip/identity-chip.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAqClE,eAAO,MAAM,oBAAoB;;8EA6ChC,CAAC;AAKF,eAAO,MAAM,qBAAqB,kDAAkD,CAAC;AAKrF,eAAO,MAAM,0BAA0B,sDAAsD,CAAC;AAK9F,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAQ7D,eAAO,MAAM,8BAA8B,QASiB,CAAC;AAI7D,eAAO,MAAM,4BAA4B,0CAA0C,CAAC;AAEpF,MAAM,MAAM,wBAAwB,GAAG,YAAY,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"identity-chip.variants.d.ts","sourceRoot":"","sources":["../../../src/components/identity-chip/identity-chip.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAsClE,eAAO,MAAM,oBAAoB;;8EA6ChC,CAAC;AAKF,eAAO,MAAM,qBAAqB,kDAAkD,CAAC;AAKrF,eAAO,MAAM,0BAA0B,sDAAsD,CAAC;AAK9F,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAQ7D,eAAO,MAAM,8BAA8B,QASiB,CAAC;AAI7D,eAAO,MAAM,4BAA4B,0CAA0C,CAAC;AAEpF,MAAM,MAAM,wBAAwB,GAAG,YAAY,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const identityChipVariants = cva(
3
4
  [
4
5
  // shape / layout: a single inline row, never shrinking in a flex line, names truncatable
@@ -31,7 +32,7 @@ const identityChipVariants = cva(
31
32
  // restrained ghost hover/press fill — quiet neutral, never a status or brand color (spec §4)
32
33
  "hover:bg-action-ghost-bg-hover active:bg-action-ghost-bg-hover",
33
34
  // focus ring — always visible, never removed (spec §4/§7)
34
- "outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
35
+ focusRing,
35
36
  // disabled — DEC-C: dim the label via the disabled TOKEN, inert, never opacity-60 (spec §4)
36
37
  "aria-disabled:pointer-events-none aria-disabled:text-text-disabled"
37
38
  ],
@@ -48,7 +49,7 @@ const identityChipVariants = cva(
48
49
  const identityChipNameClass = "min-w-0 truncate text-label text-text-primary";
49
50
  const identityChipSecondaryClass = "min-w-0 truncate text-caption text-text-secondary";
50
51
  const identityChipTextClass = "flex min-w-0 flex-col";
51
- const identityChipRemoveControlClass = "inline-flex shrink-0 items-center justify-center rounded-(--radius-full) min-h-(--size-target-mobile) min-w-(--size-target-mobile) sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop) text-action-ghost-fg cursor-pointer transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant) hover:bg-action-ghost-bg-hover active:bg-action-ghost-bg-hover outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 disabled:pointer-events-none disabled:text-text-disabled";
52
+ const identityChipRemoveControlClass = "inline-flex shrink-0 items-center justify-center rounded-(--radius-full) min-h-(--size-target-mobile) min-w-(--size-target-mobile) sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop) text-action-ghost-fg cursor-pointer transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant) hover:bg-action-ghost-bg-hover active:bg-action-ghost-bg-hover " + focusRing + " disabled:pointer-events-none disabled:text-text-disabled";
52
53
  const identityChipRemoveGlyphClass = "h-(--size-icon-sm) w-(--size-icon-sm)";
53
54
  export {
54
55
  identityChipNameClass,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/identity-chip/identity-chip.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// An IdentityChip is a compact inline reference to a single identity — a person or an AI agent\n// (spec §1). It COMPOSES the committed primitives — the Avatar for the picture, the optional\n// AgentBadge for the actor kind, the optional VerifiedBadge for a surfaced verification, and a\n// Skeleton in the chip's shape while the identity is still resolving — rather than reinventing\n// any of them. This file binds ONLY the chip body's neutral surface + text-role + layout classes\n// and (for the interactive variants) the ghost hover/press fill, the focus ring, the target-size\n// floor, and the fast functional transition; every composed primitive owns its own tokens.\n//\n// brand != state (spec §3/§5/§8). A chip carries NO status of its own — it reports who, never a\n// result — so the chip body consumes NOTHING from the status tier: no --color-status-* token is\n// bound here, and the verified-status green lives entirely inside the composed VerifiedBadge,\n// never painted on the chip. The brand violet (Sovereign Violet, the action accent) is never a\n// chip FILL either: the brand is not a status and a chip is not an action, so the body paints\n// from neutral surface / text / border roles. The interactive variants are real controls, so\n// they legitimately bind the ACTION-GHOST hover/press fill and the focus ring — the restrained\n// neutral control treatment, never a brand-colored or status-colored chip.\n//\n// The motion is the functional fast transition on verdify easing, collapsing to the instant\n// endpoint under reduced motion — never the 350ms VerifiedBadge-only theatre duration: a chip\n// is not a verification (G-U3).\n\n// The chip container (spec §2/§3/§4/§5). A self-contained rounded unit that holds the avatar,\n// the name, and the reserved badge positions in a single inline row at the --space-1 gap with\n// --space-2 inline padding, on the NEUTRAL raised surface with the muted hairline that separates\n// it from a same-colored surface. `relative` so a loading-state Skeleton placed inside can sit\n// `absolute inset-0` (the committed Avatar/Skeleton pattern). `min-w-0` so the name can truncate\n// rather than overflow when the chip is width-constrained (spec §2 — the name is never dropped).\n//\n// The `variant` axis is about HOW the chip is used (spec §3), never status and never a brand\n// fill — so NONE of the variants recolors the body. `static` (default) is a passive frame: no\n// focus, no hit target, no hover (spec §3/§4/§6). `interactive` and `removable` are real controls\n// and add the ghost hover/press fill, the focus ring, the target-size floor, and the fast\n// functional transition — the difference is the keyboard model and which element takes focus\n// (the body for `interactive`, the trailing remove-control for `removable`), carried by the tsx,\n// not by recoloring here.\nexport const identityChipVariants = cva(\n [\n // shape / layout: a single inline row, never shrinking in a flex line, names truncatable\n \"inline-flex min-w-0 shrink-0 items-center gap-(--space-1) px-(--space-2)\",\n // full radius + the neutral raised surface + the muted separating hairline (spec §5)\n \"rounded-(--radius-full) bg-surface-raised border border-surface-border-muted\",\n // relative so a loading Skeleton can be positioned absolute inset-0 inside (Avatar pattern)\n \"relative\",\n // global-first: never wrap the row; logical alignment so it mirrors under dir=rtl (G-U6)\n \"whitespace-nowrap text-start\",\n ],\n {\n variants: {\n // STRUCTURAL axis = spec §3 (how the chip is USED, never status, never a brand fill).\n variant: {\n // static (default): a read-only inline reference — non-interactive, no focus, no hit\n // target, no hover. The common case (spec §3).\n static: \"\",\n // interactive: the chip body is itself a control (an account-switcher trigger, a chip\n // that opens the profile). It takes the focus ring, the target-size floor, the restrained\n // ghost hover/press fill, and the fast functional transition (spec §3/§4). DEC-C: a\n // disabled control dims the label/name via the disabled TOKEN, never a blanket opacity.\n interactive: [\n \"cursor-pointer\",\n // fast functional motion on verdify easing; instant under reduced motion (NEVER deliberate)\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // target-size floor — 44px touch / 40px pointer (spec §5/§7, WCAG 2.5.8)\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n // restrained ghost hover/press fill — quiet neutral, never a status or brand color (spec §4)\n \"hover:bg-action-ghost-bg-hover active:bg-action-ghost-bg-hover\",\n // focus ring — always visible, never removed (spec §4/§7)\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n // disabled — DEC-C: dim the label via the disabled TOKEN, inert, never opacity-60 (spec §4)\n \"aria-disabled:pointer-events-none aria-disabled:text-text-disabled\",\n ],\n // removable: a token in an editable field. The chip BODY stays presentational (no focus\n // ring / target floor of its own) — the trailing remove-control is the focusable part and\n // owns the target size + focus ring (set on removeControlClass). The body keeps the static\n // surface (spec §3/§4).\n removable: \"\",\n },\n },\n defaultVariants: { variant: \"static\" },\n },\n);\n\n// The display name (spec §2/§5): the identity's name in the LABEL type role + the PRIMARY text\n// color. It carries the identity in the accessibility tree as text, so it is never the only part\n// dropped at small sizes — it truncates with an ellipsis rather than disappearing (spec §2/§7).\nexport const identityChipNameClass = \"min-w-0 truncate text-label text-text-primary\";\n\n// The optional supporting line (spec §2/§5): one handle / role / profile-context line that\n// disambiguates two identities sharing a name, in the CAPTION type role + the SECONDARY text\n// color. Supporting detail only — never a credential value and never a status color.\nexport const identityChipSecondaryClass = \"min-w-0 truncate text-caption text-text-secondary\";\n\n// The text block (spec §2): stacks the name above the optional secondary line; takes the\n// remaining inline space between the avatar and the reserved badge positions and lets the name\n// truncate (min-w-0) rather than overflow.\nexport const identityChipTextClass = \"flex min-w-0 flex-col\";\n\n// The trailing remove-control of the `removable` variant (spec §2/§4/§5/§6). It is the ONE\n// focusable part of a removable chip and owns its own activation, so it carries the target-size\n// floor, the focus ring, and the ghost hover/press fill — the control treatment lives HERE, not\n// on the chip body. The glyph takes the action-ghost foreground at the sm icon role; under reduced\n// motion the transition collapses to the instant endpoint. DEC-C: a disabled remove dims via the\n// disabled TOKEN, never a blanket opacity.\nexport const identityChipRemoveControlClass =\n \"inline-flex shrink-0 items-center justify-center rounded-(--radius-full) \" +\n \"min-h-(--size-target-mobile) min-w-(--size-target-mobile) \" +\n \"sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop) \" +\n \"text-action-ghost-fg cursor-pointer \" +\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"hover:bg-action-ghost-bg-hover active:bg-action-ghost-bg-hover \" +\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 \" +\n \"disabled:pointer-events-none disabled:text-text-disabled\";\n\n// The remove-control glyph (spec §5): one small decorative mark at the sm icon role, inheriting\n// the control's action-ghost foreground via currentColor.\nexport const identityChipRemoveGlyphClass = \"h-(--size-icon-sm) w-(--size-icon-sm)\";\n\nexport type IdentityChipVariantProps = VariantProps<typeof identityChipVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AAqChC,MAAM,uBAAuB;AAAA,EAClC;AAAA;AAAA,IAEE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA,MAER,SAAS;AAAA;AAAA;AAAA,QAGP,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,QAKR,aAAa;AAAA,UACX;AAAA;AAAA,UAEA;AAAA,UACA;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA;AAAA,QACF;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,SAAS,SAAS;AAAA,EACvC;AACF;AAKO,MAAM,wBAAwB;AAK9B,MAAM,6BAA6B;AAKnC,MAAM,wBAAwB;AAQ9B,MAAM,iCACX;AAYK,MAAM,+BAA+B;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/identity-chip/identity-chip.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// An IdentityChip is a compact inline reference to a single identity — a person or an AI agent\n// (spec §1). It COMPOSES the committed primitives — the Avatar for the picture, the optional\n// AgentBadge for the actor kind, the optional VerifiedBadge for a surfaced verification, and a\n// Skeleton in the chip's shape while the identity is still resolving — rather than reinventing\n// any of them. This file binds ONLY the chip body's neutral surface + text-role + layout classes\n// and (for the interactive variants) the ghost hover/press fill, the focus ring, the target-size\n// floor, and the fast functional transition; every composed primitive owns its own tokens.\n//\n// brand != state (spec §3/§5/§8). A chip carries NO status of its own — it reports who, never a\n// result — so the chip body consumes NOTHING from the status tier: no --color-status-* token is\n// bound here, and the verified-status green lives entirely inside the composed VerifiedBadge,\n// never painted on the chip. The brand violet (Sovereign Violet, the action accent) is never a\n// chip FILL either: the brand is not a status and a chip is not an action, so the body paints\n// from neutral surface / text / border roles. The interactive variants are real controls, so\n// they legitimately bind the ACTION-GHOST hover/press fill and the focus ring — the restrained\n// neutral control treatment, never a brand-colored or status-colored chip.\n//\n// The motion is the functional fast transition on verdify easing, collapsing to the instant\n// endpoint under reduced motion — never the 350ms VerifiedBadge-only theatre duration: a chip\n// is not a verification (G-U3).\n\n// The chip container (spec §2/§3/§4/§5). A self-contained rounded unit that holds the avatar,\n// the name, and the reserved badge positions in a single inline row at the --space-1 gap with\n// --space-2 inline padding, on the NEUTRAL raised surface with the muted hairline that separates\n// it from a same-colored surface. `relative` so a loading-state Skeleton placed inside can sit\n// `absolute inset-0` (the committed Avatar/Skeleton pattern). `min-w-0` so the name can truncate\n// rather than overflow when the chip is width-constrained (spec §2 — the name is never dropped).\n//\n// The `variant` axis is about HOW the chip is used (spec §3), never status and never a brand\n// fill — so NONE of the variants recolors the body. `static` (default) is a passive frame: no\n// focus, no hit target, no hover (spec §3/§4/§6). `interactive` and `removable` are real controls\n// and add the ghost hover/press fill, the focus ring, the target-size floor, and the fast\n// functional transition — the difference is the keyboard model and which element takes focus\n// (the body for `interactive`, the trailing remove-control for `removable`), carried by the tsx,\n// not by recoloring here.\nexport const identityChipVariants = cva(\n [\n // shape / layout: a single inline row, never shrinking in a flex line, names truncatable\n \"inline-flex min-w-0 shrink-0 items-center gap-(--space-1) px-(--space-2)\",\n // full radius + the neutral raised surface + the muted separating hairline (spec §5)\n \"rounded-(--radius-full) bg-surface-raised border border-surface-border-muted\",\n // relative so a loading Skeleton can be positioned absolute inset-0 inside (Avatar pattern)\n \"relative\",\n // global-first: never wrap the row; logical alignment so it mirrors under dir=rtl (G-U6)\n \"whitespace-nowrap text-start\",\n ],\n {\n variants: {\n // STRUCTURAL axis = spec §3 (how the chip is USED, never status, never a brand fill).\n variant: {\n // static (default): a read-only inline reference — non-interactive, no focus, no hit\n // target, no hover. The common case (spec §3).\n static: \"\",\n // interactive: the chip body is itself a control (an account-switcher trigger, a chip\n // that opens the profile). It takes the focus ring, the target-size floor, the restrained\n // ghost hover/press fill, and the fast functional transition (spec §3/§4). DEC-C: a\n // disabled control dims the label/name via the disabled TOKEN, never a blanket opacity.\n interactive: [\n \"cursor-pointer\",\n // fast functional motion on verdify easing; instant under reduced motion (NEVER deliberate)\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // target-size floor — 44px touch / 40px pointer (spec §5/§7, WCAG 2.5.8)\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n // restrained ghost hover/press fill — quiet neutral, never a status or brand color (spec §4)\n \"hover:bg-action-ghost-bg-hover active:bg-action-ghost-bg-hover\",\n // focus ring — always visible, never removed (spec §4/§7)\n focusRing,\n // disabled — DEC-C: dim the label via the disabled TOKEN, inert, never opacity-60 (spec §4)\n \"aria-disabled:pointer-events-none aria-disabled:text-text-disabled\",\n ],\n // removable: a token in an editable field. The chip BODY stays presentational (no focus\n // ring / target floor of its own) — the trailing remove-control is the focusable part and\n // owns the target size + focus ring (set on removeControlClass). The body keeps the static\n // surface (spec §3/§4).\n removable: \"\",\n },\n },\n defaultVariants: { variant: \"static\" },\n },\n);\n\n// The display name (spec §2/§5): the identity's name in the LABEL type role + the PRIMARY text\n// color. It carries the identity in the accessibility tree as text, so it is never the only part\n// dropped at small sizes — it truncates with an ellipsis rather than disappearing (spec §2/§7).\nexport const identityChipNameClass = \"min-w-0 truncate text-label text-text-primary\";\n\n// The optional supporting line (spec §2/§5): one handle / role / profile-context line that\n// disambiguates two identities sharing a name, in the CAPTION type role + the SECONDARY text\n// color. Supporting detail only — never a credential value and never a status color.\nexport const identityChipSecondaryClass = \"min-w-0 truncate text-caption text-text-secondary\";\n\n// The text block (spec §2): stacks the name above the optional secondary line; takes the\n// remaining inline space between the avatar and the reserved badge positions and lets the name\n// truncate (min-w-0) rather than overflow.\nexport const identityChipTextClass = \"flex min-w-0 flex-col\";\n\n// The trailing remove-control of the `removable` variant (spec §2/§4/§5/§6). It is the ONE\n// focusable part of a removable chip and owns its own activation, so it carries the target-size\n// floor, the focus ring, and the ghost hover/press fill — the control treatment lives HERE, not\n// on the chip body. The glyph takes the action-ghost foreground at the sm icon role; under reduced\n// motion the transition collapses to the instant endpoint. DEC-C: a disabled remove dims via the\n// disabled TOKEN, never a blanket opacity.\nexport const identityChipRemoveControlClass =\n \"inline-flex shrink-0 items-center justify-center rounded-(--radius-full) \" +\n \"min-h-(--size-target-mobile) min-w-(--size-target-mobile) \" +\n \"sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop) \" +\n \"text-action-ghost-fg cursor-pointer \" +\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"hover:bg-action-ghost-bg-hover active:bg-action-ghost-bg-hover \" +\n focusRing + \" \" +\n \"disabled:pointer-events-none disabled:text-text-disabled\";\n\n// The remove-control glyph (spec §5): one small decorative mark at the sm icon role, inheriting\n// the control's action-ghost foreground via currentColor.\nexport const identityChipRemoveGlyphClass = \"h-(--size-icon-sm) w-(--size-icon-sm)\";\n\nexport type IdentityChipVariantProps = VariantProps<typeof identityChipVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAqCnB,MAAM,uBAAuB;AAAA,EAClC;AAAA;AAAA,IAEE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA,MAER,SAAS;AAAA;AAAA;AAAA,QAGP,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,QAKR,aAAa;AAAA,UACX;AAAA;AAAA,UAEA;AAAA,UACA;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA;AAAA;AAAA,UAEA;AAAA,QACF;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,SAAS,SAAS;AAAA,EACvC;AACF;AAKO,MAAM,wBAAwB;AAK9B,MAAM,6BAA6B;AAKnC,MAAM,wBAAwB;AAQ9B,MAAM,iCACX,mbAOA,YAAY;AAKP,MAAM,+BAA+B;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"input.variants.d.ts","sourceRoot":"","sources":["../../../src/components/input/input.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAKlE,eAAO,MAAM,aAAa;;;;8EAmDzB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAInE,eAAO,MAAM,oBAAoB;;8EAK/B,CAAC"}
1
+ {"version":3,"file":"input.variants.d.ts","sourceRoot":"","sources":["../../../src/components/input/input.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAMlE,eAAO,MAAM,aAAa;;;;8EAmDzB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAInE,eAAO,MAAM,oBAAoB;;8EAK/B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const inputVariants = cva(
3
4
  [
4
5
  // shape + resting field: control-* carries the field, neutrals not brand
@@ -15,7 +16,7 @@ const inputVariants = cva(
15
16
  // focus: visible 2px signal-blue ring at 2px offset + focused border, never
16
17
  // removed (2.4.7); border+ring meet 3:1 non-text contrast (1.4.11)
17
18
  "outline-none",
18
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
19
+ focusRing,
19
20
  "focus-visible:border-border-focus",
20
21
  // colors transition functionally (no theatre); border + ring only
21
22
  "transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/input/input.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// The text field. Token binding lives ONLY here. Native <input>, no Radix.\n// The closed state set for a text field is default·hover·focus·disabled·read-only·error\n// (input.md §4) — loading and pressed do NOT apply and are dropped.\nexport const inputVariants = cva(\n [\n // shape + resting field: control-* carries the field, neutrals not brand\n \"block w-full rounded-md border bg-control-bg text-control-fg\",\n \"border-control-border placeholder:text-control-placeholder\",\n // DEC-A — the value SIZE is text-base (16px) so iOS never zooms on focus; the\n // brand BODY line-height + letter-spacing ride along via the role-suffix vars.\n // text-body itself (0.9375rem / 15px) is NEVER bound on a form field: under the\n // role-aware cn it would collapse against text-base, and 15px would reintroduce\n // the iOS focus-zoom that the 16px reset exists to prevent.\n \"text-base leading-(--text-body--line-height) tracking-(--text-body--letter-spacing)\",\n // hover shows a text caret; the border does NOT change color (restraint)\n \"cursor-text\",\n // focus: visible 2px signal-blue ring at 2px offset + focused border, never\n // removed (2.4.7); border+ring meet 3:1 non-text contrast (1.4.11)\n \"outline-none\",\n \"focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n \"focus-visible:border-border-focus\",\n // colors transition functionally (no theatre); border + ring only\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n // disabled: muted value, non-interactive (native disabled drives tab skip)\n \"disabled:cursor-not-allowed disabled:text-text-disabled\",\n // read-only: editable-looking, selectable, stays in the tab order\n \"read-only:cursor-default\",\n // ERROR is the only colored field state — it borrows the STATUS color, never\n // the brand (§3, §8). Driven by the native aria-invalid attribute.\n \"aria-invalid:border-status-critical-border\",\n \"aria-invalid:focus-visible:ring-status-critical-border\",\n // 44px mobile / 40px desktop target floor, logical block-size. DEC-B: tokens\n // expose only target-size FLOORS, no height scale — every size anchors this\n // floor and never sets a fixed height below it (a11y). Resting height emerges\n // from the size variant's vertical padding above this floor.\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n ],\n {\n variants: {\n // DEC-B — the 16px no-zoom reset is a hard floor on every form-field size, so\n // (unlike a non-field control) the type role is held constant and the sizes\n // differ ONLY by vertical padding (density) ABOVE the shared target floor:\n // --space-1 (0.25rem) <= --space-2 (0.5rem) gives a coherent sm <= md height\n // progression, both >= the floor.\n size: {\n md: \"py-(--space-2)\",\n sm: \"py-(--space-1)\",\n },\n // logical inline padding; widened on the slot side to reserve room\n leadingSlot: { true: \"ps-(--space-9)\", false: \"ps-(--space-3)\" },\n trailingSlot: { true: \"pe-(--space-9)\", false: \"pe-(--space-3)\" },\n },\n defaultVariants: { size: \"md\", leadingSlot: false, trailingSlot: false },\n },\n);\n\nexport type InputVariantProps = VariantProps<typeof inputVariants>;\n\n// The message below the field. The error help text borrows the field's STATUS\n// color (the only colored field state); neutral help text is muted secondary.\nexport const inputMessageVariants = cva(\"mt-(--space-1) text-caption\", {\n variants: {\n tone: { help: \"text-text-secondary\", error: \"text-status-critical-on-surface\" },\n },\n defaultVariants: { tone: \"help\" },\n});\n"],"mappings":"AAAA,SAAS,WAA8B;AAKhC,MAAM,gBAAgB;AAAA,EAC3B;AAAA;AAAA,IAEE;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA;AAAA,MAEA,aAAa,EAAE,MAAM,kBAAkB,OAAO,iBAAiB;AAAA,MAC/D,cAAc,EAAE,MAAM,kBAAkB,OAAO,iBAAiB;AAAA,IAClE;AAAA,IACA,iBAAiB,EAAE,MAAM,MAAM,aAAa,OAAO,cAAc,MAAM;AAAA,EACzE;AACF;AAMO,MAAM,uBAAuB,IAAI,+BAA+B;AAAA,EACrE,UAAU;AAAA,IACR,MAAM,EAAE,MAAM,uBAAuB,OAAO,kCAAkC;AAAA,EAChF;AAAA,EACA,iBAAiB,EAAE,MAAM,OAAO;AAClC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/input/input.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// The text field. Token binding lives ONLY here. Native <input>, no Radix.\n// The closed state set for a text field is default·hover·focus·disabled·read-only·error\n// (input.md §4) — loading and pressed do NOT apply and are dropped.\nexport const inputVariants = cva(\n [\n // shape + resting field: control-* carries the field, neutrals not brand\n \"block w-full rounded-md border bg-control-bg text-control-fg\",\n \"border-control-border placeholder:text-control-placeholder\",\n // DEC-A — the value SIZE is text-base (16px) so iOS never zooms on focus; the\n // brand BODY line-height + letter-spacing ride along via the role-suffix vars.\n // text-body itself (0.9375rem / 15px) is NEVER bound on a form field: under the\n // role-aware cn it would collapse against text-base, and 15px would reintroduce\n // the iOS focus-zoom that the 16px reset exists to prevent.\n \"text-base leading-(--text-body--line-height) tracking-(--text-body--letter-spacing)\",\n // hover shows a text caret; the border does NOT change color (restraint)\n \"cursor-text\",\n // focus: visible 2px signal-blue ring at 2px offset + focused border, never\n // removed (2.4.7); border+ring meet 3:1 non-text contrast (1.4.11)\n \"outline-none\",\n focusRing,\n \"focus-visible:border-border-focus\",\n // colors transition functionally (no theatre); border + ring only\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n // disabled: muted value, non-interactive (native disabled drives tab skip)\n \"disabled:cursor-not-allowed disabled:text-text-disabled\",\n // read-only: editable-looking, selectable, stays in the tab order\n \"read-only:cursor-default\",\n // ERROR is the only colored field state — it borrows the STATUS color, never\n // the brand (§3, §8). Driven by the native aria-invalid attribute.\n \"aria-invalid:border-status-critical-border\",\n \"aria-invalid:focus-visible:ring-status-critical-border\",\n // 44px mobile / 40px desktop target floor, logical block-size. DEC-B: tokens\n // expose only target-size FLOORS, no height scale — every size anchors this\n // floor and never sets a fixed height below it (a11y). Resting height emerges\n // from the size variant's vertical padding above this floor.\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n ],\n {\n variants: {\n // DEC-B — the 16px no-zoom reset is a hard floor on every form-field size, so\n // (unlike a non-field control) the type role is held constant and the sizes\n // differ ONLY by vertical padding (density) ABOVE the shared target floor:\n // --space-1 (0.25rem) <= --space-2 (0.5rem) gives a coherent sm <= md height\n // progression, both >= the floor.\n size: {\n md: \"py-(--space-2)\",\n sm: \"py-(--space-1)\",\n },\n // logical inline padding; widened on the slot side to reserve room\n leadingSlot: { true: \"ps-(--space-9)\", false: \"ps-(--space-3)\" },\n trailingSlot: { true: \"pe-(--space-9)\", false: \"pe-(--space-3)\" },\n },\n defaultVariants: { size: \"md\", leadingSlot: false, trailingSlot: false },\n },\n);\n\nexport type InputVariantProps = VariantProps<typeof inputVariants>;\n\n// The message below the field. The error help text borrows the field's STATUS\n// color (the only colored field state); neutral help text is muted secondary.\nexport const inputMessageVariants = cva(\"mt-(--space-1) text-caption\", {\n variants: {\n tone: { help: \"text-text-secondary\", error: \"text-status-critical-on-surface\" },\n },\n defaultVariants: { tone: \"help\" },\n});\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAKnB,MAAM,gBAAgB;AAAA,EAC3B;AAAA;AAAA,IAEE;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA;AAAA,MAEA,aAAa,EAAE,MAAM,kBAAkB,OAAO,iBAAiB;AAAA,MAC/D,cAAc,EAAE,MAAM,kBAAkB,OAAO,iBAAiB;AAAA,IAClE;AAAA,IACA,iBAAiB,EAAE,MAAM,MAAM,aAAa,OAAO,cAAc,MAAM;AAAA,EACzE;AACF;AAMO,MAAM,uBAAuB,IAAI,+BAA+B;AAAA,EACrE,UAAU;AAAA,IACR,MAAM,EAAE,MAAM,uBAAuB,OAAO,kCAAkC;AAAA,EAChF;AAAA,EACA,iBAAiB,EAAE,MAAM,OAAO;AAClC,CAAC;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/components/menu/menu.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEjE,OAAO,EASL,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,SACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,IAAI,CAAC;CAAG;AAE9E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,2CAEpC;AAED,MAAM,WAAW,gBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC;CAAG;AAEjF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,4FAYtB,CAAC;AAEH,MAAM,WAAW,gBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC;CAAG;AAEjF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,yFAkBtB,CAAC;AAEH,MAAM,WAAW,aACf,SAAQ,IAAI,CACR,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,IAAI,CAAC,EACjE,OAAO,CACR,EACD,oBAAoB;IACtB,kHAAkH;IAClH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,sFAmBnB,CAAC;AAEH,MAAM,WAAW,cACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,KAAK,CAAC;IAC1E;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,uFAyBpB,CAAC;AAEH,MAAM,WAAW,cACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,KAAK,CAAC;CAAG;AAE/E;;;GAGG;AACH,eAAO,MAAM,SAAS,uFAOpB,CAAC;AAEH,MAAM,WAAW,kBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,SAAS,CAAC;CAAG;AAEnF;;;GAGG;AACH,eAAO,MAAM,aAAa,2FAWxB,CAAC;AAEH,MAAM,WAAW,YACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,GAAG,CAAC;CAAG;AAE7E;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,2CAE1C;AAED,MAAM,WAAW,mBACf,SAAQ,IAAI,CACR,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,UAAU,CAAC,EACvE,OAAO,CACR,EACD,oBAAoB;IACtB,yEAAyE;IACzE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,4FAmBzB,CAAC;AAEH,MAAM,WAAW,mBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,UAAU,CAAC;CAAG;AAEpF;;;GAGG;AACH,eAAO,MAAM,cAAc,4FAiBzB,CAAC"}
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/components/menu/menu.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEjE,OAAO,EASL,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,SACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,IAAI,CAAC;CAAG;AAE9E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,2CAEpC;AAED,MAAM,WAAW,gBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC;CAAG;AAEjF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,4FAYtB,CAAC;AAEH,MAAM,WAAW,gBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC;CAAG;AAEjF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,yFAkBtB,CAAC;AAEH,MAAM,WAAW,aACf,SAAQ,IAAI,CACR,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,IAAI,CAAC,EACjE,OAAO,CACR,EACD,oBAAoB;IACtB,kHAAkH;IAClH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,sFAuBnB,CAAC;AAEH,MAAM,WAAW,cACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,KAAK,CAAC;IAC1E;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,uFAyBpB,CAAC;AAEH,MAAM,WAAW,cACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,KAAK,CAAC;CAAG;AAE/E;;;GAGG;AACH,eAAO,MAAM,SAAS,uFAOpB,CAAC;AAEH,MAAM,WAAW,kBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,SAAS,CAAC;CAAG;AAEnF;;;GAGG;AACH,eAAO,MAAM,aAAa,2FAWxB,CAAC;AAEH,MAAM,WAAW,YACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,GAAG,CAAC;CAAG;AAE7E;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,2CAE1C;AAED,MAAM,WAAW,mBACf,SAAQ,IAAI,CACR,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,UAAU,CAAC,EACvE,OAAO,CACR,EACD,oBAAoB;IACtB,yEAAyE;IACzE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,4FAmBzB,CAAC;AAEH,MAAM,WAAW,mBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,qBAAqB,CAAC,UAAU,CAAC;CAAG;AAEpF;;;GAGG;AACH,eAAO,MAAM,cAAc,4FAiBzB,CAAC"}
@@ -49,7 +49,7 @@ const MenuItem = React.forwardRef(function MenuItem2({ className, destructive, i
49
49
  children: [
50
50
  icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: menuItemIconClass, children: icon }) : null,
51
51
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children }),
52
- shortcut ? /* @__PURE__ */ jsx("span", { className: menuItemShortcutClass, children: shortcut }) : null
52
+ shortcut ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: menuItemShortcutClass, children: shortcut }) : null
53
53
  ]
54
54
  }
55
55
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/menu/menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\";\nimport { cn } from \"../../lib/cn\";\nimport {\n menuTriggerClass,\n menuPopupClass,\n menuItemVariants,\n menuItemIconClass,\n menuItemShortcutClass,\n menuSubChevronClass,\n menuLabelClass,\n menuSeparatorClass,\n type MenuItemVariantProps,\n} from \"./menu.variants\";\n\nexport interface MenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/**\n * Menu is a popup list of ACTIONS that a trigger opens — the row of commands behind a button, an\n * avatar, or a row's overflow control (spec §1). Reach for it when you want to fire an action (open,\n * rename, sign out, revoke a key), not pick a value: use Select to choose one option from a list, and\n * use the Sidebar for page-level navigation. Each item runs a command and then the menu closes.\n *\n * It is a NEUTRAL surface (spec §3): the popup, items, and separators are neutral, and the brand\n * violet never marks an item as \"the special one.\" The one colored item is the destructive item,\n * which takes the destructive ACTION treatment because the command it runs is irreversible — a risk\n * signal, not a status result; a verified result is never reported by a menu item (brand != state).\n *\n * Wraps the Radix DropdownMenu primitive (WAI-ARIA APG menu-button + menu pattern), which provides\n * the portal, roving tabindex, type-ahead, submenu, and Escape/arrow keyboard model — a stateful\n * primitive, so this file is `'use client'`.\n */\nexport function Menu(props: MenuProps) {\n return <DropdownMenuPrimitive.Root {...props} />;\n}\n\nexport interface MenuTriggerProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger> {}\n\n/**\n * The control that opens the menu (spec §2 trigger): the one stop in the page tab order for this\n * control, carrying the focus ring. Radix sets `aria-haspopup=\"menu\"`, `aria-expanded`, and\n * `aria-controls` (pointing at the popup) for you. Pass `asChild` to wrap your own Button so the\n * trigger inherits its role, keyboard, and focus ring rather than nesting a second button; the bare\n * (non-`asChild`) form renders the default neutral-ghost trigger.\n */\nexport const MenuTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,\n MenuTriggerProps\n>(function MenuTrigger({ className, asChild, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Trigger\n ref={ref}\n asChild={asChild}\n className={asChild ? className : cn(menuTriggerClass, className)}\n {...props}\n />\n );\n});\n\nexport interface MenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * Renders the portal and the popup (spec §2 popup): the floating `role=\"menu\"` surface that opens on\n * activation, raised above the page and anchored to the trigger. On open, focus moves into the popup\n * (first item, or last on Up) and roving tabindex tracks the active item; on close — by Escape,\n * activation, or click-away — focus returns to the trigger (Radix, spec §6/§7). The menu is NOT a\n * modal dialog: focus is not trapped, and Tab leaves the menu rather than stepping through items.\n * A neutral raised surface; brand violet and Verified Green never appear here (spec §3/§5/§8).\n */\nexport const MenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n MenuContentProps\n>(function MenuContent({ className, sideOffset = 4, loop = true, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n // `loop` wraps arrow movement at the ends (spec §6: \"wrapping at the ends\") — Radix leaves\n // it OFF by default, so we default it ON to honor the frozen keyboard model. Disabled items\n // are skipped by the roving handler for free (Radix, spec §4/§6).\n loop={loop}\n className={cn(menuPopupClass, className)}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n});\n\nexport interface MenuItemProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>,\n \"color\"\n >,\n MenuItemVariantProps {\n /** The leading icon (spec §2): decorative, sized by `--size-icon-md`; the item names itself by its label text. */\n icon?: React.ReactNode;\n /** A trailing shortcut hint (spec §2): text such as \"⌘K\", in the muted label role; never a focus stop. */\n shortcut?: React.ReactNode;\n}\n\n/**\n * One command row (spec §2 item, §4 states): a `role=\"menuitem\"` whose activation runs its command\n * and closes the menu, returning focus to the trigger (Radix). It holds an optional leading icon, a\n * label, and an optional trailing shortcut hint. Pointer hover and keyboard arrow movement share ONE\n * highlight (Radix `data-highlighted`), so the active item is the same for both (spec §4 Hover).\n *\n * `destructive` (spec §3 `item=destructive`) marks the ONE colored item — a command that is\n * irreversible (revoke a key, delete a profile). It takes the destructive ACTION treatment and must\n * name the consequence in its TEXT, never by color alone (spec §7/§8). A `disabled` item stays in the\n * menu and readable to assistive technology (`aria-disabled`), is skipped by arrow movement, and does\n * not fire on activation (spec §4 Disabled / §7).\n */\nexport const MenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n MenuItemProps\n>(function MenuItem({ className, destructive, icon, shortcut, children, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(menuItemVariants({ destructive }), className)}\n {...props}\n >\n {icon ? (\n <span aria-hidden=\"true\" className={menuItemIconClass}>\n {icon}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n {shortcut ? <span className={menuItemShortcutClass}>{shortcut}</span> : null}\n </DropdownMenuPrimitive.Item>\n );\n});\n\nexport interface MenuGroupProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group> {\n /**\n * The non-interactive heading that partitions the popup (spec §2 group / group-label). It names the\n * group for assistive technology via `aria-labelledby` (Radix `Label`) and is NEVER a focus stop.\n */\n label?: React.ReactNode;\n}\n\n/**\n * A set of related items under a non-interactive `group-label` that partitions the popup (spec §2\n * group). The items read as a related set (`role=\"group\"` named by the label); the label is never a\n * menuitem and never a focus stop.\n */\nexport const MenuGroup = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Group>,\n MenuGroupProps\n>(function MenuGroup({ label, children, ...props }, ref) {\n // Radix Group + Label do NOT auto-wire aria-labelledby (the Label renders a bare div with no id),\n // so the group would read as an unnamed role=group. We hand-wire it the same way Separator\n // hand-rolls a named anatomy when the primitive can't carry it (skill: compose the role by hand\n // when Radix can't express the spec's anatomy): generate a stable id on the Label and point the\n // Group's aria-labelledby at it, so the items read as a related set named by the label (spec §2\n // group / §7 group named by its label).\n const labelId = React.useId();\n return (\n <DropdownMenuPrimitive.Group\n ref={ref}\n aria-labelledby={label ? labelId : undefined}\n {...props}\n >\n {label ? (\n <DropdownMenuPrimitive.Label id={labelId} className={menuLabelClass}>\n {label}\n </DropdownMenuPrimitive.Label>\n ) : null}\n {children}\n </DropdownMenuPrimitive.Group>\n );\n});\n\nexport interface MenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {}\n\n/**\n * A standalone non-interactive section label (spec §2 group-label) for a label that is not wrapped in\n * a `MenuGroup`. Like the group label it is the muted label-role heading and is never a focus stop.\n */\nexport const MenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n MenuLabelProps\n>(function MenuLabel({ className, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Label ref={ref} className={cn(menuLabelClass, className)} {...props} />\n );\n});\n\nexport interface MenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/**\n * A thin neutral divider between groups (spec §2 separator): decorative (`role=\"separator\"`), never a\n * focus stop.\n */\nexport const MenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n MenuSeparatorProps\n>(function MenuSeparator({ className, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(menuSeparatorClass, className)}\n {...props}\n />\n );\n});\n\nexport interface MenuSubProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub> {}\n\n/**\n * A submenu: an item that opens a nested popup of its own items (spec §2 submenu). Keep nesting\n * shallow — deep trees are hard to operate by keyboard (spec §2). Wraps `MenuSubTrigger` +\n * `MenuSubContent`.\n */\nexport function MenuSub(props: MenuSubProps) {\n return <DropdownMenuPrimitive.Sub {...props} />;\n}\n\nexport interface MenuSubTriggerProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger>,\n \"color\"\n >,\n MenuItemVariantProps {\n /** The leading icon (spec §2): decorative, sized by `--size-icon-md`. */\n icon?: React.ReactNode;\n}\n\n/**\n * The item that opens a submenu (spec §2/§6): a `role=\"menuitem\"` with `aria-haspopup=\"menu\"` and its\n * own `aria-expanded` (Radix). Right opens the submenu and focuses its first item; Left closes it and\n * returns focus here. It carries the same row treatment as a `MenuItem`, plus a trailing chevron\n * pointing to the inline-end.\n */\nexport const MenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n MenuSubTriggerProps\n>(function MenuSubTrigger({ className, destructive, icon, children, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(menuItemVariants({ destructive }), className)}\n {...props}\n >\n {icon ? (\n <span aria-hidden=\"true\" className={menuItemIconClass}>\n {icon}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n <ChevronGlyph />\n </DropdownMenuPrimitive.SubTrigger>\n );\n});\n\nexport interface MenuSubContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> {}\n\n/**\n * The nested popup of a submenu (spec §2 submenu): the same neutral raised `role=\"menu\"` surface as\n * `MenuContent`, anchored to its `MenuSubTrigger`.\n */\nexport const MenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n MenuSubContentProps\n>(function MenuSubContent({ className, sideOffset = 2, loop = true, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n sideOffset={sideOffset}\n // `loop` wraps arrow movement at the ends inside the submenu too (spec §6), matching the\n // parent popup; Radix leaves it OFF by default.\n loop={loop}\n className={cn(menuPopupClass, className)}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n});\n\n// The submenu chevron — inline SVG (no icon dep), --size-icon-md, pointing to the inline-end to\n// signal the nested popup. Decorative (aria-hidden); aria-haspopup/aria-expanded carry the state, not\n// the glyph (spec §2/§7). Drawn with currentColor so it inherits the row's color.\nfunction ChevronGlyph() {\n return (\n <span data-testid=\"menu-sub-chevron\" aria-hidden=\"true\" className={menuSubChevronClass}>\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" focusable=\"false\">\n <path d=\"M6 4l4 4-4 4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </span>\n );\n}\n"],"mappings":";AAoCS,cAuFL,YAvFK;AAlCT,YAAY,WAAW;AACvB,SAAS,gBAAgB,6BAA6B;AACtD,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAoBA,SAAS,KAAK,OAAkB;AACrC,SAAO,oBAAC,sBAAsB,MAAtB,EAA4B,GAAG,OAAO;AAChD;AAYO,MAAM,cAAc,MAAM,WAG/B,SAASA,aAAY,EAAE,WAAW,SAAS,GAAG,MAAM,GAAG,KAAK;AAC5D,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MACA,WAAW,UAAU,YAAY,GAAG,kBAAkB,SAAS;AAAA,MAC9D,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAaM,MAAM,cAAc,MAAM,WAG/B,SAASC,aAAY,EAAE,WAAW,aAAa,GAAG,OAAO,MAAM,GAAG,MAAM,GAAG,KAAK;AAChF,SACE,oBAAC,sBAAsB,QAAtB,EACC;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MAIA;AAAA,MACA,WAAW,GAAG,gBAAgB,SAAS;AAAA,MACtC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ,CAAC;AA0BM,MAAM,WAAW,MAAM,WAG5B,SAASC,UAAS,EAAE,WAAW,aAAa,MAAM,UAAU,UAAU,GAAG,MAAM,GAAG,KAAK;AACvF,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,WAAW,GAAG,iBAAiB,EAAE,YAAY,CAAC,GAAG,SAAS;AAAA,MACzD,GAAG;AAAA,MAEH;AAAA,eACC,oBAAC,UAAK,eAAY,QAAO,WAAW,mBACjC,gBACH,IACE;AAAA,QACJ,oBAAC,UAAK,WAAU,2BAA2B,UAAS;AAAA,QACnD,WAAW,oBAAC,UAAK,WAAW,uBAAwB,oBAAS,IAAU;AAAA;AAAA;AAAA,EAC1E;AAEJ,CAAC;AAgBM,MAAM,YAAY,MAAM,WAG7B,SAASC,WAAU,EAAE,OAAO,UAAU,GAAG,MAAM,GAAG,KAAK;AAOvD,QAAM,UAAU,MAAM,MAAM;AAC5B,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,mBAAiB,QAAQ,UAAU;AAAA,MAClC,GAAG;AAAA,MAEH;AAAA,gBACC,oBAAC,sBAAsB,OAAtB,EAA4B,IAAI,SAAS,WAAW,gBAClD,iBACH,IACE;AAAA,QACH;AAAA;AAAA;AAAA,EACH;AAEJ,CAAC;AASM,MAAM,YAAY,MAAM,WAG7B,SAASC,WAAU,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACjD,SACE,oBAAC,sBAAsB,OAAtB,EAA4B,KAAU,WAAW,GAAG,gBAAgB,SAAS,GAAI,GAAG,OAAO;AAEhG,CAAC;AASM,MAAM,gBAAgB,MAAM,WAGjC,SAASC,eAAc,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACrD,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,WAAW,GAAG,oBAAoB,SAAS;AAAA,MAC1C,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAUM,SAAS,QAAQ,OAAqB;AAC3C,SAAO,oBAAC,sBAAsB,KAAtB,EAA2B,GAAG,OAAO;AAC/C;AAkBO,MAAM,iBAAiB,MAAM,WAGlC,SAASC,gBAAe,EAAE,WAAW,aAAa,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK;AACnF,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,WAAW,GAAG,iBAAiB,EAAE,YAAY,CAAC,GAAG,SAAS;AAAA,MACzD,GAAG;AAAA,MAEH;AAAA,eACC,oBAAC,UAAK,eAAY,QAAO,WAAW,mBACjC,gBACH,IACE;AAAA,QACJ,oBAAC,UAAK,WAAU,2BAA2B,UAAS;AAAA,QACpD,oBAAC,gBAAa;AAAA;AAAA;AAAA,EAChB;AAEJ,CAAC;AASM,MAAM,iBAAiB,MAAM,WAGlC,SAASC,gBAAe,EAAE,WAAW,aAAa,GAAG,OAAO,MAAM,GAAG,MAAM,GAAG,KAAK;AACnF,SACE,oBAAC,sBAAsB,QAAtB,EACC;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MAGA;AAAA,MACA,WAAW,GAAG,gBAAgB,SAAS;AAAA,MACtC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ,CAAC;AAKD,SAAS,eAAe;AACtB,SACE,oBAAC,UAAK,eAAY,oBAAmB,eAAY,QAAO,WAAW,qBACjE,8BAAC,SAAI,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,WAAU,SACrF,8BAAC,UAAK,GAAE,gBAAe,eAAc,SAAQ,gBAAe,SAAQ,GACtE,GACF;AAEJ;","names":["MenuTrigger","MenuContent","MenuItem","MenuGroup","MenuLabel","MenuSeparator","MenuSubTrigger","MenuSubContent"]}
1
+ {"version":3,"sources":["../../../src/components/menu/menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\";\nimport { cn } from \"../../lib/cn\";\nimport {\n menuTriggerClass,\n menuPopupClass,\n menuItemVariants,\n menuItemIconClass,\n menuItemShortcutClass,\n menuSubChevronClass,\n menuLabelClass,\n menuSeparatorClass,\n type MenuItemVariantProps,\n} from \"./menu.variants\";\n\nexport interface MenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/**\n * Menu is a popup list of ACTIONS that a trigger opens — the row of commands behind a button, an\n * avatar, or a row's overflow control (spec §1). Reach for it when you want to fire an action (open,\n * rename, sign out, revoke a key), not pick a value: use Select to choose one option from a list, and\n * use the Sidebar for page-level navigation. Each item runs a command and then the menu closes.\n *\n * It is a NEUTRAL surface (spec §3): the popup, items, and separators are neutral, and the brand\n * violet never marks an item as \"the special one.\" The one colored item is the destructive item,\n * which takes the destructive ACTION treatment because the command it runs is irreversible — a risk\n * signal, not a status result; a verified result is never reported by a menu item (brand != state).\n *\n * Wraps the Radix DropdownMenu primitive (WAI-ARIA APG menu-button + menu pattern), which provides\n * the portal, roving tabindex, type-ahead, submenu, and Escape/arrow keyboard model — a stateful\n * primitive, so this file is `'use client'`.\n */\nexport function Menu(props: MenuProps) {\n return <DropdownMenuPrimitive.Root {...props} />;\n}\n\nexport interface MenuTriggerProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger> {}\n\n/**\n * The control that opens the menu (spec §2 trigger): the one stop in the page tab order for this\n * control, carrying the focus ring. Radix sets `aria-haspopup=\"menu\"`, `aria-expanded`, and\n * `aria-controls` (pointing at the popup) for you. Pass `asChild` to wrap your own Button so the\n * trigger inherits its role, keyboard, and focus ring rather than nesting a second button; the bare\n * (non-`asChild`) form renders the default neutral-ghost trigger.\n */\nexport const MenuTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,\n MenuTriggerProps\n>(function MenuTrigger({ className, asChild, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Trigger\n ref={ref}\n asChild={asChild}\n className={asChild ? className : cn(menuTriggerClass, className)}\n {...props}\n />\n );\n});\n\nexport interface MenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * Renders the portal and the popup (spec §2 popup): the floating `role=\"menu\"` surface that opens on\n * activation, raised above the page and anchored to the trigger. On open, focus moves into the popup\n * (first item, or last on Up) and roving tabindex tracks the active item; on close — by Escape,\n * activation, or click-away — focus returns to the trigger (Radix, spec §6/§7). The menu is NOT a\n * modal dialog: focus is not trapped, and Tab leaves the menu rather than stepping through items.\n * A neutral raised surface; brand violet and Verified Green never appear here (spec §3/§5/§8).\n */\nexport const MenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n MenuContentProps\n>(function MenuContent({ className, sideOffset = 4, loop = true, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n // `loop` wraps arrow movement at the ends (spec §6: \"wrapping at the ends\") — Radix leaves\n // it OFF by default, so we default it ON to honor the frozen keyboard model. Disabled items\n // are skipped by the roving handler for free (Radix, spec §4/§6).\n loop={loop}\n className={cn(menuPopupClass, className)}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n});\n\nexport interface MenuItemProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>,\n \"color\"\n >,\n MenuItemVariantProps {\n /** The leading icon (spec §2): decorative, sized by `--size-icon-md`; the item names itself by its label text. */\n icon?: React.ReactNode;\n /** A trailing shortcut hint (spec §2): text such as \"⌘K\", in the muted label role; never a focus stop. */\n shortcut?: React.ReactNode;\n}\n\n/**\n * One command row (spec §2 item, §4 states): a `role=\"menuitem\"` whose activation runs its command\n * and closes the menu, returning focus to the trigger (Radix). It holds an optional leading icon, a\n * label, and an optional trailing shortcut hint. Pointer hover and keyboard arrow movement share ONE\n * highlight (Radix `data-highlighted`), so the active item is the same for both (spec §4 Hover).\n *\n * `destructive` (spec §3 `item=destructive`) marks the ONE colored item — a command that is\n * irreversible (revoke a key, delete a profile). It takes the destructive ACTION treatment and must\n * name the consequence in its TEXT, never by color alone (spec §7/§8). A `disabled` item stays in the\n * menu and readable to assistive technology (`aria-disabled`), is skipped by arrow movement, and does\n * not fire on activation (spec §4 Disabled / §7).\n */\nexport const MenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n MenuItemProps\n>(function MenuItem({ className, destructive, icon, shortcut, children, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(menuItemVariants({ destructive }), className)}\n {...props}\n >\n {icon ? (\n <span aria-hidden=\"true\" className={menuItemIconClass}>\n {icon}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n {shortcut ? (\n <span aria-hidden=\"true\" className={menuItemShortcutClass}>\n {shortcut}\n </span>\n ) : null}\n </DropdownMenuPrimitive.Item>\n );\n});\n\nexport interface MenuGroupProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group> {\n /**\n * The non-interactive heading that partitions the popup (spec §2 group / group-label). It names the\n * group for assistive technology via `aria-labelledby` (Radix `Label`) and is NEVER a focus stop.\n */\n label?: React.ReactNode;\n}\n\n/**\n * A set of related items under a non-interactive `group-label` that partitions the popup (spec §2\n * group). The items read as a related set (`role=\"group\"` named by the label); the label is never a\n * menuitem and never a focus stop.\n */\nexport const MenuGroup = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Group>,\n MenuGroupProps\n>(function MenuGroup({ label, children, ...props }, ref) {\n // Radix Group + Label do NOT auto-wire aria-labelledby (the Label renders a bare div with no id),\n // so the group would read as an unnamed role=group. We hand-wire it the same way Separator\n // hand-rolls a named anatomy when the primitive can't carry it (skill: compose the role by hand\n // when Radix can't express the spec's anatomy): generate a stable id on the Label and point the\n // Group's aria-labelledby at it, so the items read as a related set named by the label (spec §2\n // group / §7 group named by its label).\n const labelId = React.useId();\n return (\n <DropdownMenuPrimitive.Group\n ref={ref}\n aria-labelledby={label ? labelId : undefined}\n {...props}\n >\n {label ? (\n <DropdownMenuPrimitive.Label id={labelId} className={menuLabelClass}>\n {label}\n </DropdownMenuPrimitive.Label>\n ) : null}\n {children}\n </DropdownMenuPrimitive.Group>\n );\n});\n\nexport interface MenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {}\n\n/**\n * A standalone non-interactive section label (spec §2 group-label) for a label that is not wrapped in\n * a `MenuGroup`. Like the group label it is the muted label-role heading and is never a focus stop.\n */\nexport const MenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n MenuLabelProps\n>(function MenuLabel({ className, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Label ref={ref} className={cn(menuLabelClass, className)} {...props} />\n );\n});\n\nexport interface MenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/**\n * A thin neutral divider between groups (spec §2 separator): decorative (`role=\"separator\"`), never a\n * focus stop.\n */\nexport const MenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n MenuSeparatorProps\n>(function MenuSeparator({ className, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(menuSeparatorClass, className)}\n {...props}\n />\n );\n});\n\nexport interface MenuSubProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub> {}\n\n/**\n * A submenu: an item that opens a nested popup of its own items (spec §2 submenu). Keep nesting\n * shallow — deep trees are hard to operate by keyboard (spec §2). Wraps `MenuSubTrigger` +\n * `MenuSubContent`.\n */\nexport function MenuSub(props: MenuSubProps) {\n return <DropdownMenuPrimitive.Sub {...props} />;\n}\n\nexport interface MenuSubTriggerProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger>,\n \"color\"\n >,\n MenuItemVariantProps {\n /** The leading icon (spec §2): decorative, sized by `--size-icon-md`. */\n icon?: React.ReactNode;\n}\n\n/**\n * The item that opens a submenu (spec §2/§6): a `role=\"menuitem\"` with `aria-haspopup=\"menu\"` and its\n * own `aria-expanded` (Radix). Right opens the submenu and focuses its first item; Left closes it and\n * returns focus here. It carries the same row treatment as a `MenuItem`, plus a trailing chevron\n * pointing to the inline-end.\n */\nexport const MenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n MenuSubTriggerProps\n>(function MenuSubTrigger({ className, destructive, icon, children, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(menuItemVariants({ destructive }), className)}\n {...props}\n >\n {icon ? (\n <span aria-hidden=\"true\" className={menuItemIconClass}>\n {icon}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n <ChevronGlyph />\n </DropdownMenuPrimitive.SubTrigger>\n );\n});\n\nexport interface MenuSubContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> {}\n\n/**\n * The nested popup of a submenu (spec §2 submenu): the same neutral raised `role=\"menu\"` surface as\n * `MenuContent`, anchored to its `MenuSubTrigger`.\n */\nexport const MenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n MenuSubContentProps\n>(function MenuSubContent({ className, sideOffset = 2, loop = true, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n sideOffset={sideOffset}\n // `loop` wraps arrow movement at the ends inside the submenu too (spec §6), matching the\n // parent popup; Radix leaves it OFF by default.\n loop={loop}\n className={cn(menuPopupClass, className)}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n});\n\n// The submenu chevron — inline SVG (no icon dep), --size-icon-md, pointing to the inline-end to\n// signal the nested popup. Decorative (aria-hidden); aria-haspopup/aria-expanded carry the state, not\n// the glyph (spec §2/§7). Drawn with currentColor so it inherits the row's color.\nfunction ChevronGlyph() {\n return (\n <span data-testid=\"menu-sub-chevron\" aria-hidden=\"true\" className={menuSubChevronClass}>\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" focusable=\"false\">\n <path d=\"M6 4l4 4-4 4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </span>\n );\n}\n"],"mappings":";AAoCS,cAuFL,YAvFK;AAlCT,YAAY,WAAW;AACvB,SAAS,gBAAgB,6BAA6B;AACtD,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAoBA,SAAS,KAAK,OAAkB;AACrC,SAAO,oBAAC,sBAAsB,MAAtB,EAA4B,GAAG,OAAO;AAChD;AAYO,MAAM,cAAc,MAAM,WAG/B,SAASA,aAAY,EAAE,WAAW,SAAS,GAAG,MAAM,GAAG,KAAK;AAC5D,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MACA,WAAW,UAAU,YAAY,GAAG,kBAAkB,SAAS;AAAA,MAC9D,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAaM,MAAM,cAAc,MAAM,WAG/B,SAASC,aAAY,EAAE,WAAW,aAAa,GAAG,OAAO,MAAM,GAAG,MAAM,GAAG,KAAK;AAChF,SACE,oBAAC,sBAAsB,QAAtB,EACC;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MAIA;AAAA,MACA,WAAW,GAAG,gBAAgB,SAAS;AAAA,MACtC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ,CAAC;AA0BM,MAAM,WAAW,MAAM,WAG5B,SAASC,UAAS,EAAE,WAAW,aAAa,MAAM,UAAU,UAAU,GAAG,MAAM,GAAG,KAAK;AACvF,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,WAAW,GAAG,iBAAiB,EAAE,YAAY,CAAC,GAAG,SAAS;AAAA,MACzD,GAAG;AAAA,MAEH;AAAA,eACC,oBAAC,UAAK,eAAY,QAAO,WAAW,mBACjC,gBACH,IACE;AAAA,QACJ,oBAAC,UAAK,WAAU,2BAA2B,UAAS;AAAA,QACnD,WACC,oBAAC,UAAK,eAAY,QAAO,WAAW,uBACjC,oBACH,IACE;AAAA;AAAA;AAAA,EACN;AAEJ,CAAC;AAgBM,MAAM,YAAY,MAAM,WAG7B,SAASC,WAAU,EAAE,OAAO,UAAU,GAAG,MAAM,GAAG,KAAK;AAOvD,QAAM,UAAU,MAAM,MAAM;AAC5B,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,mBAAiB,QAAQ,UAAU;AAAA,MAClC,GAAG;AAAA,MAEH;AAAA,gBACC,oBAAC,sBAAsB,OAAtB,EAA4B,IAAI,SAAS,WAAW,gBAClD,iBACH,IACE;AAAA,QACH;AAAA;AAAA;AAAA,EACH;AAEJ,CAAC;AASM,MAAM,YAAY,MAAM,WAG7B,SAASC,WAAU,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACjD,SACE,oBAAC,sBAAsB,OAAtB,EAA4B,KAAU,WAAW,GAAG,gBAAgB,SAAS,GAAI,GAAG,OAAO;AAEhG,CAAC;AASM,MAAM,gBAAgB,MAAM,WAGjC,SAASC,eAAc,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACrD,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,WAAW,GAAG,oBAAoB,SAAS;AAAA,MAC1C,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAUM,SAAS,QAAQ,OAAqB;AAC3C,SAAO,oBAAC,sBAAsB,KAAtB,EAA2B,GAAG,OAAO;AAC/C;AAkBO,MAAM,iBAAiB,MAAM,WAGlC,SAASC,gBAAe,EAAE,WAAW,aAAa,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK;AACnF,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA,WAAW,GAAG,iBAAiB,EAAE,YAAY,CAAC,GAAG,SAAS;AAAA,MACzD,GAAG;AAAA,MAEH;AAAA,eACC,oBAAC,UAAK,eAAY,QAAO,WAAW,mBACjC,gBACH,IACE;AAAA,QACJ,oBAAC,UAAK,WAAU,2BAA2B,UAAS;AAAA,QACpD,oBAAC,gBAAa;AAAA;AAAA;AAAA,EAChB;AAEJ,CAAC;AASM,MAAM,iBAAiB,MAAM,WAGlC,SAASC,gBAAe,EAAE,WAAW,aAAa,GAAG,OAAO,MAAM,GAAG,MAAM,GAAG,KAAK;AACnF,SACE,oBAAC,sBAAsB,QAAtB,EACC;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MAGA;AAAA,MACA,WAAW,GAAG,gBAAgB,SAAS;AAAA,MACtC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ,CAAC;AAKD,SAAS,eAAe;AACtB,SACE,oBAAC,UAAK,eAAY,oBAAmB,eAAY,QAAO,WAAW,qBACjE,8BAAC,SAAI,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,WAAU,SACrF,8BAAC,UAAK,GAAE,gBAAe,eAAc,SAAQ,gBAAe,SAAQ,GACtE,GACF;AAEJ;","names":["MenuTrigger","MenuContent","MenuItem","MenuGroup","MenuLabel","MenuSeparator","MenuSubTrigger","MenuSubContent"]}
@@ -8,6 +8,6 @@ export type MenuItemVariantProps = VariantProps<typeof menuItemVariants>;
8
8
  export declare const menuItemIconClass = "inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center";
9
9
  export declare const menuItemShortcutClass = "ms-auto ps-(--space-4) text-label text-text-muted";
10
10
  export declare const menuSubChevronClass = "ms-auto inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center";
11
- export declare const menuLabelClass = "px-(--space-2) py-(--space-1) text-label text-text-muted select-none";
11
+ export declare const menuLabelClass = "px-(--space-2) py-(--space-1) text-label text-text-secondary select-none";
12
12
  export declare const menuSeparatorClass = "-mx-(--space-1) my-(--space-1) h-px bg-border-default";
13
13
  //# sourceMappingURL=menu.variants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"menu.variants.d.ts","sourceRoot":"","sources":["../../../src/components/menu/menu.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAmBlE,eAAO,MAAM,gBAAgB,QAQ+B,CAAC;AAU7D,eAAO,MAAM,cAAc,QAKoC,CAAC;AAyBhE,eAAO,MAAM,gBAAgB;;8EAoC5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAMzE,eAAO,MAAM,iBAAiB,2FAC4D,CAAC;AAK3F,eAAO,MAAM,qBAAqB,sDACmB,CAAC;AAItD,eAAO,MAAM,mBAAmB,mGACkE,CAAC;AAKnG,eAAO,MAAM,cAAc,yEAC6C,CAAC;AAMzE,eAAO,MAAM,kBAAkB,0DAC0B,CAAC"}
1
+ {"version":3,"file":"menu.variants.d.ts","sourceRoot":"","sources":["../../../src/components/menu/menu.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAoBlE,eAAO,MAAM,gBAAgB,QAQ+B,CAAC;AAU7D,eAAO,MAAM,cAAc,QAKoC,CAAC;AAyBhE,eAAO,MAAM,gBAAgB;;8EAoC5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAMzE,eAAO,MAAM,iBAAiB,2FAC4D,CAAC;AAK3F,eAAO,MAAM,qBAAqB,sDACmB,CAAC;AAItD,eAAO,MAAM,mBAAmB,mGACkE,CAAC;AAMnG,eAAO,MAAM,cAAc,6EACiD,CAAC;AAM7E,eAAO,MAAM,kBAAkB,0DAC0B,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { cva } from "class-variance-authority";
2
- const menuTriggerClass = "inline-flex items-center justify-center gap-(--space-2) rounded-(--radius-md) px-(--space-3) text-label text-action-ghost-fg cursor-pointer select-none hover:bg-action-ghost-bg-hover transition-[color,background-color] duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant) min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 disabled:pointer-events-none disabled:text-text-disabled";
2
+ import { focusRing } from "../../lib/focus-ring";
3
+ const menuTriggerClass = "inline-flex items-center justify-center gap-(--space-2) rounded-(--radius-md) px-(--space-3) text-label text-action-ghost-fg cursor-pointer select-none hover:bg-action-ghost-bg-hover transition-[color,background-color] duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant) min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) " + focusRing + " disabled:pointer-events-none disabled:text-text-disabled";
3
4
  const menuPopupClass = "z-(--z-index-popover) min-w-(--container-sm) overflow-hidden p-(--space-1) bg-surface-raised border border-surface-border rounded-(--radius-md) shadow-(--shadow-md) transition-opacity duration-(--motion-duration-fast) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant) data-[state=open]:opacity-100 data-[state=closed]:opacity-0";
4
5
  const menuItemVariants = cva(
5
6
  [
@@ -41,7 +42,7 @@ const menuItemVariants = cva(
41
42
  const menuItemIconClass = "inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center";
42
43
  const menuItemShortcutClass = "ms-auto ps-(--space-4) text-label text-text-muted";
43
44
  const menuSubChevronClass = "ms-auto inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center";
44
- const menuLabelClass = "px-(--space-2) py-(--space-1) text-label text-text-muted select-none";
45
+ const menuLabelClass = "px-(--space-2) py-(--space-1) text-label text-text-secondary select-none";
45
46
  const menuSeparatorClass = "-mx-(--space-1) my-(--space-1) h-px bg-border-default";
46
47
  export {
47
48
  menuItemIconClass,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/menu/menu.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// Menu is a popup list of ACTIONS a trigger opens (spec §1). It is a NEUTRAL surface (spec §3): its\n// items, popup, and separators are neutral, and the brand violet NEVER marks an item as \"the\n// special one.\" The ONE colored item is the DESTRUCTIVE item, which takes the destructive ACTION\n// treatment because the command it runs is irreversible — a RISK signal, not a status result. A\n// verified meaning is never reported by a menu item (that is VerifiedBadge's job), so NOTHING here\n// binds a --color-status-* token and the brand action-primary tier never appears (brand != state,\n// G-U2). This is the ONLY token-binding site (skill §5 hard rule). All open/close motion is the\n// FAST token transition on the verdify easing, instant under reduced motion — never the 350ms\n// VerifiedBadge-only theatre duration (G-U3).\n\n// The trigger: the one stop in the page tab order for this control (spec §2 trigger). A NEUTRAL\n// ghost surface — the label/glyph in the ghost action fg at rest, the restrained ghost hover fill,\n// the md radius, the persistent 2px focus ring (never removed, spec §4 Focus), and the target-size\n// floor (44px touch / 40px pointer, spec §7 2.5.8 / DEC-B) with the height EMERGING from the floor.\n// A disabled trigger dims via the disabled TOKEN (DEC-C), never a blanket opacity. fast functional\n// hover motion + verdify easing, instant under reduced motion (G-U3). This styles the DEFAULT\n// (non-asChild) trigger; when a Button is passed via `asChild` it carries its own treatment.\nexport const menuTriggerClass =\n \"inline-flex items-center justify-center gap-(--space-2) rounded-(--radius-md) px-(--space-3) \" +\n \"text-label text-action-ghost-fg cursor-pointer select-none \" +\n \"hover:bg-action-ghost-bg-hover \" +\n \"transition-[color,background-color] duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) \" +\n \"outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 \" +\n \"disabled:pointer-events-none disabled:text-text-disabled\";\n\n// The popup (spec §2 popup, §5): the floating surface that opens on activation, raised above the\n// page and anchored to the trigger. A NEUTRAL raised surface (--color-surface-raised) with the\n// outer surface border, the md corner radius, and the md elevation shadow above the page, on the\n// POPOVER z-layer (a menu is a non-modal popover layer, not the modal layer). It NEVER wears a brand\n// or status fill (spec §3/§8). The open/close fade is a PLAIN fast transition + verdify easing,\n// instant under reduced motion — never the 350ms VerifiedBadge-only theatre (G-U3). Enter/exit ride\n// Radix's data-state on the content (attribute-selector variants, not arbitrary values). Inset\n// padding from --space-*; a SubContent shares the same surface treatment.\nexport const menuPopupClass =\n \"z-(--z-index-popover) min-w-(--container-sm) overflow-hidden p-(--space-1) \" +\n \"bg-surface-raised border border-surface-border rounded-(--radius-md) shadow-(--shadow-md) \" +\n \"transition-opacity duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\";\n\n// One command row (spec §2 item, §4 states). A neutral row at rest; the active/hovered item shares\n// ONE highlight (pointer and keyboard both drive Radix's data-highlighted, spec §4 Hover) painted\n// with the ghost hover fill.\n//\n// RESTING (default): the LABEL in the PRIMARY text color (spec §5 --color-text-primary) at the BODY\n// type role (spec §5 --text-body), on the popup surface with NO fill (spec §4 Default).\n// HIGHLIGHTED (data-highlighted): the restrained ghost-action hover fill (spec §5\n// --color-action-ghost-bg-hover) — pointer hover AND keyboard arrow movement set the same\n// data-highlighted, so the two share one highlight (spec §4 Hover/Active). No motion beyond the\n// token transition.\n// DISABLED (data-disabled): dims via the disabled TOKEN (DEC-C), never a blanket opacity; Radix\n// keeps it readable to AT (aria-disabled) and skips it in arrow movement (spec §4 Disabled / §7).\n// FOCUS: the open popup tracks the active item by ROVING FOCUS and shows the active fill, not a\n// second ring (spec §4 Focus) — so an item does NOT paint its own focus-visible ring; the active\n// fill is the focus affordance inside the menu.\n// Motion: fast token transition + verdify easing, instant under reduced motion (NEVER the check\n// theatre, G-U3). Target-size floor on every row (44px touch / 40px pointer, spec §7 2.5.8), never a\n// fixed height below the floor.\n//\n// `destructive` is the spec §3 `item=destructive` axis — the ONE colored item: the destructive\n// ACTION treatment (label/icon in the destructive fg; the highlight fill is the destructive bg). It\n// is a RISK signal, not the brand and NEVER status-verified (G-U2); the risk is also named in the\n// item's text + icon, never carried by color alone (spec §7/§8, 1.4.1).\nexport const menuItemVariants = cva(\n [\n // shape + the icon-to-label gap + logical inline padding so it mirrors under RTL (G-U6)\n \"relative flex items-center gap-(--space-2) rounded-(--radius-md) px-(--space-2)\",\n // type ROLE + the resting (neutral) label color, no fill, pointer cursor\n \"text-body text-text-primary no-underline cursor-pointer select-none\",\n // the shared pointer+keyboard highlight: the restrained ghost-action fill (spec §4 Hover/Active)\n \"data-[highlighted]:bg-action-ghost-bg-hover\",\n // motion: fast + verdify easing, instant under reduced motion (NEVER the check theatre, G-U3)\n \"transition-[color,background-color] duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // target-size floor — 44px touch / 40px pointer, on every row (spec §7, 2.5.8), never a fixed\n // height below the floor\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n // the open popup tracks the active item by ROVING focus + the active fill, not a second ring\n // (spec §4 Focus) — no per-item focus-visible ring inside the menu\n \"outline-none\",\n // disabled (non-operable) row — DEC-C: dim via the disabled TOKEN, never opacity. Radix drives\n // it via data-disabled and keeps the label readable to AT (aria-disabled), skipping arrow focus.\n \"data-[disabled]:pointer-events-none data-[disabled]:text-text-disabled\",\n ],\n {\n variants: {\n // the spec §3 `item=destructive` axis — the ONE colored item: the destructive ACTION\n // treatment, a RISK signal (not the brand, NEVER status-verified — G-U2). At rest the label +\n // icon take the destructive fg; the shared highlight fill is the destructive bg.\n destructive: {\n true: [\n \"text-action-destructive-fg\",\n \"data-[highlighted]:bg-action-destructive-bg\",\n ],\n false: \"\",\n },\n },\n defaultVariants: { destructive: false },\n },\n);\n\nexport type MenuItemVariantProps = VariantProps<typeof menuItemVariants>;\n\n// The leading item icon (spec §2/§5): the md icon role, decorative (the item names itself by its\n// label text, not the glyph). It inherits the row's color via currentColor, so a destructive row's\n// icon is the destructive fg and a disabled row's icon dims with the disabled token. shrink-0 so it\n// never collapses.\nexport const menuItemIconClass =\n \"inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center\";\n\n// The trailing shortcut hint on an item (spec §2/§5): the keyboard hint pushed to the inline-end, in\n// the MUTED text color at the LABEL type role (spec §5 --color-text-muted / --text-label).\n// Decorative wayfinding, never a focus stop; logical inline-end placement (G-U6).\nexport const menuItemShortcutClass =\n \"ms-auto ps-(--space-4) text-label text-text-muted\";\n\n// The submenu chevron on a SubTrigger (spec §2 submenu, §5): the md icon role, decorative; it\n// points to the inline-end to signal the nested popup. Inherits the row color via currentColor.\nexport const menuSubChevronClass =\n \"ms-auto inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center\";\n\n// The group label (spec §2 group-label, §5): the non-interactive heading that partitions the popup;\n// it is NEVER a focus stop. The MUTED text color at the LABEL type role (spec §5 --color-text-muted\n// / --text-label). Logical inline padding so it mirrors under RTL (G-U6).\nexport const menuLabelClass =\n \"px-(--space-2) py-(--space-1) text-label text-text-muted select-none\";\n\n// The separator (spec §2 separator, §5): a thin neutral divider between groups. It is decorative and\n// never takes focus. A neutral hairline in the default border color (spec §5 --color-border-default),\n// with a little vertical breathing room. Negated logical inline margins keep the rule flush to the\n// popup's inner padding edge (it spans the popup inset, mirrors under RTL — G-U6).\nexport const menuSeparatorClass =\n \"-mx-(--space-1) my-(--space-1) h-px bg-border-default\";\n"],"mappings":"AAAA,SAAS,WAA8B;AAmBhC,MAAM,mBACX;AAiBK,MAAM,iBACX;AA6BK,MAAM,mBAAmB;AAAA,EAC9B;AAAA;AAAA,IAEE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA,MAIR,aAAa;AAAA,QACX,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,QACF;AAAA,QACA,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,aAAa,MAAM;AAAA,EACxC;AACF;AAQO,MAAM,oBACX;AAKK,MAAM,wBACX;AAIK,MAAM,sBACX;AAKK,MAAM,iBACX;AAMK,MAAM,qBACX;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/menu/menu.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// Menu is a popup list of ACTIONS a trigger opens (spec §1). It is a NEUTRAL surface (spec §3): its\n// items, popup, and separators are neutral, and the brand violet NEVER marks an item as \"the\n// special one.\" The ONE colored item is the DESTRUCTIVE item, which takes the destructive ACTION\n// treatment because the command it runs is irreversible — a RISK signal, not a status result. A\n// verified meaning is never reported by a menu item (that is VerifiedBadge's job), so NOTHING here\n// binds a --color-status-* token and the brand action-primary tier never appears (brand != state,\n// G-U2). This is the ONLY token-binding site (skill §5 hard rule). All open/close motion is the\n// FAST token transition on the verdify easing, instant under reduced motion — never the 350ms\n// VerifiedBadge-only theatre duration (G-U3).\n\n// The trigger: the one stop in the page tab order for this control (spec §2 trigger). A NEUTRAL\n// ghost surface — the label/glyph in the ghost action fg at rest, the restrained ghost hover fill,\n// the md radius, the persistent 2px focus ring (never removed, spec §4 Focus), and the target-size\n// floor (44px touch / 40px pointer, spec §7 2.5.8 / DEC-B) with the height EMERGING from the floor.\n// A disabled trigger dims via the disabled TOKEN (DEC-C), never a blanket opacity. fast functional\n// hover motion + verdify easing, instant under reduced motion (G-U3). This styles the DEFAULT\n// (non-asChild) trigger; when a Button is passed via `asChild` it carries its own treatment.\nexport const menuTriggerClass =\n \"inline-flex items-center justify-center gap-(--space-2) rounded-(--radius-md) px-(--space-3) \" +\n \"text-label text-action-ghost-fg cursor-pointer select-none \" +\n \"hover:bg-action-ghost-bg-hover \" +\n \"transition-[color,background-color] duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop) \" +\n focusRing + \" \" +\n \"disabled:pointer-events-none disabled:text-text-disabled\";\n\n// The popup (spec §2 popup, §5): the floating surface that opens on activation, raised above the\n// page and anchored to the trigger. A NEUTRAL raised surface (--color-surface-raised) with the\n// outer surface border, the md corner radius, and the md elevation shadow above the page, on the\n// POPOVER z-layer (a menu is a non-modal popover layer, not the modal layer). It NEVER wears a brand\n// or status fill (spec §3/§8). The open/close fade is a PLAIN fast transition + verdify easing,\n// instant under reduced motion — never the 350ms VerifiedBadge-only theatre (G-U3). Enter/exit ride\n// Radix's data-state on the content (attribute-selector variants, not arbitrary values). Inset\n// padding from --space-*; a SubContent shares the same surface treatment.\nexport const menuPopupClass =\n \"z-(--z-index-popover) min-w-(--container-sm) overflow-hidden p-(--space-1) \" +\n \"bg-surface-raised border border-surface-border rounded-(--radius-md) shadow-(--shadow-md) \" +\n \"transition-opacity duration-(--motion-duration-fast) ease-(--motion-easing-verdify) \" +\n \"motion-reduce:duration-(--motion-duration-instant) \" +\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\";\n\n// One command row (spec §2 item, §4 states). A neutral row at rest; the active/hovered item shares\n// ONE highlight (pointer and keyboard both drive Radix's data-highlighted, spec §4 Hover) painted\n// with the ghost hover fill.\n//\n// RESTING (default): the LABEL in the PRIMARY text color (spec §5 --color-text-primary) at the BODY\n// type role (spec §5 --text-body), on the popup surface with NO fill (spec §4 Default).\n// HIGHLIGHTED (data-highlighted): the restrained ghost-action hover fill (spec §5\n// --color-action-ghost-bg-hover) — pointer hover AND keyboard arrow movement set the same\n// data-highlighted, so the two share one highlight (spec §4 Hover/Active). No motion beyond the\n// token transition.\n// DISABLED (data-disabled): dims via the disabled TOKEN (DEC-C), never a blanket opacity; Radix\n// keeps it readable to AT (aria-disabled) and skips it in arrow movement (spec §4 Disabled / §7).\n// FOCUS: the open popup tracks the active item by ROVING FOCUS and shows the active fill, not a\n// second ring (spec §4 Focus) — so an item does NOT paint its own focus-visible ring; the active\n// fill is the focus affordance inside the menu.\n// Motion: fast token transition + verdify easing, instant under reduced motion (NEVER the check\n// theatre, G-U3). Target-size floor on every row (44px touch / 40px pointer, spec §7 2.5.8), never a\n// fixed height below the floor.\n//\n// `destructive` is the spec §3 `item=destructive` axis — the ONE colored item: the destructive\n// ACTION treatment (label/icon in the destructive fg; the highlight fill is the destructive bg). It\n// is a RISK signal, not the brand and NEVER status-verified (G-U2); the risk is also named in the\n// item's text + icon, never carried by color alone (spec §7/§8, 1.4.1).\nexport const menuItemVariants = cva(\n [\n // shape + the icon-to-label gap + logical inline padding so it mirrors under RTL (G-U6)\n \"relative flex items-center gap-(--space-2) rounded-(--radius-md) px-(--space-2)\",\n // type ROLE + the resting (neutral) label color, no fill, pointer cursor\n \"text-body text-text-primary no-underline cursor-pointer select-none\",\n // the shared pointer+keyboard highlight: the restrained ghost-action fill (spec §4 Hover/Active)\n \"data-[highlighted]:bg-action-ghost-bg-hover\",\n // motion: fast + verdify easing, instant under reduced motion (NEVER the check theatre, G-U3)\n \"transition-[color,background-color] duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n // target-size floor — 44px touch / 40px pointer, on every row (spec §7, 2.5.8), never a fixed\n // height below the floor\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n // the open popup tracks the active item by ROVING focus + the active fill, not a second ring\n // (spec §4 Focus) — no per-item focus-visible ring inside the menu\n \"outline-none\",\n // disabled (non-operable) row — DEC-C: dim via the disabled TOKEN, never opacity. Radix drives\n // it via data-disabled and keeps the label readable to AT (aria-disabled), skipping arrow focus.\n \"data-[disabled]:pointer-events-none data-[disabled]:text-text-disabled\",\n ],\n {\n variants: {\n // the spec §3 `item=destructive` axis — the ONE colored item: the destructive ACTION\n // treatment, a RISK signal (not the brand, NEVER status-verified — G-U2). At rest the label +\n // icon take the destructive fg; the shared highlight fill is the destructive bg.\n destructive: {\n true: [\n \"text-action-destructive-fg\",\n \"data-[highlighted]:bg-action-destructive-bg\",\n ],\n false: \"\",\n },\n },\n defaultVariants: { destructive: false },\n },\n);\n\nexport type MenuItemVariantProps = VariantProps<typeof menuItemVariants>;\n\n// The leading item icon (spec §2/§5): the md icon role, decorative (the item names itself by its\n// label text, not the glyph). It inherits the row's color via currentColor, so a destructive row's\n// icon is the destructive fg and a disabled row's icon dims with the disabled token. shrink-0 so it\n// never collapses.\nexport const menuItemIconClass =\n \"inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center\";\n\n// The trailing shortcut hint on an item (spec §2/§5): the keyboard hint pushed to the inline-end, in\n// the MUTED text color at the LABEL type role (spec §5 --color-text-muted / --text-label).\n// Decorative wayfinding, never a focus stop; logical inline-end placement (G-U6).\nexport const menuItemShortcutClass =\n \"ms-auto ps-(--space-4) text-label text-text-muted\";\n\n// The submenu chevron on a SubTrigger (spec §2 submenu, §5): the md icon role, decorative; it\n// points to the inline-end to signal the nested popup. Inherits the row color via currentColor.\nexport const menuSubChevronClass =\n \"ms-auto inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center\";\n\n// The group label (spec §2 group-label, §5): the non-interactive heading that partitions the popup;\n// it is NEVER a focus stop. It is essential de-emphasized text (it names the group), so it uses the\n// SECONDARY text color (AA) at the LABEL type role (spec §5 --color-text-secondary / --text-label) —\n// not the decorative-only muted role (accessibility.md). Logical inline padding mirrors under RTL.\nexport const menuLabelClass =\n \"px-(--space-2) py-(--space-1) text-label text-text-secondary select-none\";\n\n// The separator (spec §2 separator, §5): a thin neutral divider between groups. It is decorative and\n// never takes focus. A neutral hairline in the default border color (spec §5 --color-border-default),\n// with a little vertical breathing room. Negated logical inline margins keep the rule flush to the\n// popup's inner padding edge (it spans the popup inset, mirrors under RTL — G-U6).\nexport const menuSeparatorClass =\n \"-mx-(--space-1) my-(--space-1) h-px bg-border-default\";\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAmBnB,MAAM,mBACX,kZAMA,YAAY;AAWP,MAAM,iBACX;AA6BK,MAAM,mBAAmB;AAAA,EAC9B;AAAA;AAAA,IAEE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA,MAIR,aAAa;AAAA,QACX,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,QACF;AAAA,QACA,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,aAAa,MAAM;AAAA,EACxC;AACF;AAQO,MAAM,oBACX;AAKK,MAAM,wBACX;AAIK,MAAM,sBACX;AAMK,MAAM,iBACX;AAMK,MAAM,qBACX;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.variants.d.ts","sourceRoot":"","sources":["../../../src/components/pagination/pagination.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAalE,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAIrE,eAAO,MAAM,mBAAmB,gDACe,CAAC;AAGhD,eAAO,MAAM,mBAAmB,6BAA6B,CAAC;AAY9D,eAAO,MAAM,yBAAyB;;;8EA6CrC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,YAAY,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI3F,eAAO,MAAM,mBAAmB,2FAC0D,CAAC;AAI3F,eAAO,MAAM,qBAAqB,uFACoD,CAAC;AAKvF,eAAO,MAAM,uBAAuB,2GACsE,CAAC"}
1
+ {"version":3,"file":"pagination.variants.d.ts","sourceRoot":"","sources":["../../../src/components/pagination/pagination.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAclE,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAIrE,eAAO,MAAM,mBAAmB,gDACe,CAAC;AAGhD,eAAO,MAAM,mBAAmB,6BAA6B,CAAC;AAY9D,eAAO,MAAM,yBAAyB;;;8EA6CrC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,YAAY,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI3F,eAAO,MAAM,mBAAmB,2FAC0D,CAAC;AAI3F,eAAO,MAAM,qBAAqB,uFACoD,CAAC;AAKvF,eAAO,MAAM,uBAAuB,2GACsE,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const paginationNavClass = "bg-surface-canvas py-(--space-2)";
3
4
  const paginationListClass = "flex flex-wrap items-center gap-(--space-4)";
4
5
  const paginationItemClass = "inline-flex items-center";
@@ -18,7 +19,7 @@ const paginationControlVariants = cva(
18
19
  "min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)",
19
20
  // focus ring — identical on every state, never removed (spec §4 / 2.4.7)
20
21
  "outline-none",
21
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
22
+ focusRing,
22
23
  // disabled prev/next — DEC-C: dim via the disabled TOKEN for BOTH the native-button form
23
24
  // (`disabled`) and the link form (`aria-disabled`), never a blanket opacity. The component
24
25
  // also strips href + tabindex on a disabled link so it cannot navigate or be tabbed to.