@verdify/ui 0.2.1 → 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 (146) hide show
  1. package/LICENSE +12 -0
  2. package/dist/components/accordion/accordion.variants.d.ts.map +1 -1
  3. package/dist/components/accordion/accordion.variants.js +2 -1
  4. package/dist/components/accordion/accordion.variants.js.map +1 -1
  5. package/dist/components/agent-badge/agent-badge.variants.d.ts.map +1 -1
  6. package/dist/components/agent-badge/agent-badge.variants.js.map +1 -1
  7. package/dist/components/alert/alert.variants.d.ts.map +1 -1
  8. package/dist/components/alert/alert.variants.js +3 -2
  9. package/dist/components/alert/alert.variants.js.map +1 -1
  10. package/dist/components/breadcrumb/breadcrumb.variants.d.ts.map +1 -1
  11. package/dist/components/breadcrumb/breadcrumb.variants.js +2 -1
  12. package/dist/components/breadcrumb/breadcrumb.variants.js.map +1 -1
  13. package/dist/components/button/button.variants.d.ts.map +1 -1
  14. package/dist/components/button/button.variants.js +2 -1
  15. package/dist/components/button/button.variants.js.map +1 -1
  16. package/dist/components/card/card.variants.d.ts.map +1 -1
  17. package/dist/components/card/card.variants.js +2 -1
  18. package/dist/components/card/card.variants.js.map +1 -1
  19. package/dist/components/checkbox/checkbox.js +1 -1
  20. package/dist/components/checkbox/checkbox.js.map +1 -1
  21. package/dist/components/checkbox/checkbox.variants.d.ts.map +1 -1
  22. package/dist/components/checkbox/checkbox.variants.js +2 -1
  23. package/dist/components/checkbox/checkbox.variants.js.map +1 -1
  24. package/dist/components/command-palette/command-palette.variants.d.ts +1 -1
  25. package/dist/components/command-palette/command-palette.variants.d.ts.map +1 -1
  26. package/dist/components/command-palette/command-palette.variants.js +5 -3
  27. package/dist/components/command-palette/command-palette.variants.js.map +1 -1
  28. package/dist/components/consent-toggle/consent-toggle.variants.d.ts +1 -1
  29. package/dist/components/consent-toggle/consent-toggle.variants.d.ts.map +1 -1
  30. package/dist/components/consent-toggle/consent-toggle.variants.js +1 -1
  31. package/dist/components/consent-toggle/consent-toggle.variants.js.map +1 -1
  32. package/dist/components/credential-card/credential-card.variants.d.ts +3 -3
  33. package/dist/components/credential-card/credential-card.variants.d.ts.map +1 -1
  34. package/dist/components/credential-card/credential-card.variants.js +3 -3
  35. package/dist/components/credential-card/credential-card.variants.js.map +1 -1
  36. package/dist/components/data-grid/data-grid.variants.d.ts +1 -1
  37. package/dist/components/data-grid/data-grid.variants.d.ts.map +1 -1
  38. package/dist/components/data-grid/data-grid.variants.js +11 -10
  39. package/dist/components/data-grid/data-grid.variants.js.map +1 -1
  40. package/dist/components/dialog/dialog.variants.d.ts.map +1 -1
  41. package/dist/components/dialog/dialog.variants.js +3 -2
  42. package/dist/components/dialog/dialog.variants.js.map +1 -1
  43. package/dist/components/identity-chip/identity-chip.variants.d.ts.map +1 -1
  44. package/dist/components/identity-chip/identity-chip.variants.js +3 -2
  45. package/dist/components/identity-chip/identity-chip.variants.js.map +1 -1
  46. package/dist/components/input/input.variants.d.ts.map +1 -1
  47. package/dist/components/input/input.variants.js +3 -2
  48. package/dist/components/input/input.variants.js.map +1 -1
  49. package/dist/components/label/label.variants.js +1 -1
  50. package/dist/components/label/label.variants.js.map +1 -1
  51. package/dist/components/menu/menu.d.ts.map +1 -1
  52. package/dist/components/menu/menu.js +1 -1
  53. package/dist/components/menu/menu.js.map +1 -1
  54. package/dist/components/menu/menu.variants.d.ts +1 -1
  55. package/dist/components/menu/menu.variants.d.ts.map +1 -1
  56. package/dist/components/menu/menu.variants.js +3 -2
  57. package/dist/components/menu/menu.variants.js.map +1 -1
  58. package/dist/components/pagination/pagination.variants.d.ts.map +1 -1
  59. package/dist/components/pagination/pagination.variants.js +2 -1
  60. package/dist/components/pagination/pagination.variants.js.map +1 -1
  61. package/dist/components/popover/popover.variants.d.ts.map +1 -1
  62. package/dist/components/popover/popover.variants.js +4 -3
  63. package/dist/components/popover/popover.variants.js.map +1 -1
  64. package/dist/components/progress/progress.variants.d.ts +1 -1
  65. package/dist/components/progress/progress.variants.d.ts.map +1 -1
  66. package/dist/components/progress/progress.variants.js +1 -1
  67. package/dist/components/progress/progress.variants.js.map +1 -1
  68. package/dist/components/radio/radio.d.ts.map +1 -1
  69. package/dist/components/radio/radio.js +2 -1
  70. package/dist/components/radio/radio.js.map +1 -1
  71. package/dist/components/select/select.variants.d.ts +3 -3
  72. package/dist/components/select/select.variants.d.ts.map +1 -1
  73. package/dist/components/select/select.variants.js +5 -4
  74. package/dist/components/select/select.variants.js.map +1 -1
  75. package/dist/components/sheet/sheet.variants.d.ts.map +1 -1
  76. package/dist/components/sheet/sheet.variants.js +3 -2
  77. package/dist/components/sheet/sheet.variants.js.map +1 -1
  78. package/dist/components/sidebar/sidebar.variants.d.ts +1 -1
  79. package/dist/components/sidebar/sidebar.variants.d.ts.map +1 -1
  80. package/dist/components/sidebar/sidebar.variants.js +4 -3
  81. package/dist/components/sidebar/sidebar.variants.js.map +1 -1
  82. package/dist/components/switch/switch.variants.d.ts.map +1 -1
  83. package/dist/components/switch/switch.variants.js +2 -1
  84. package/dist/components/switch/switch.variants.js.map +1 -1
  85. package/dist/components/table/table.d.ts.map +1 -1
  86. package/dist/components/table/table.js +1 -1
  87. package/dist/components/table/table.js.map +1 -1
  88. package/dist/components/table/table.variants.d.ts.map +1 -1
  89. package/dist/components/table/table.variants.js +9 -7
  90. package/dist/components/table/table.variants.js.map +1 -1
  91. package/dist/components/tabs/tabs.variants.d.ts.map +1 -1
  92. package/dist/components/tabs/tabs.variants.js +3 -2
  93. package/dist/components/tabs/tabs.variants.js.map +1 -1
  94. package/dist/components/textarea/textarea.js +2 -2
  95. package/dist/components/textarea/textarea.js.map +1 -1
  96. package/dist/components/textarea/textarea.variants.d.ts.map +1 -1
  97. package/dist/components/textarea/textarea.variants.js +2 -1
  98. package/dist/components/textarea/textarea.variants.js.map +1 -1
  99. package/dist/components/toast/toast.variants.d.ts.map +1 -1
  100. package/dist/components/toast/toast.variants.js +3 -2
  101. package/dist/components/toast/toast.variants.js.map +1 -1
  102. package/dist/components/trust-score/trust-score.variants.d.ts +1 -1
  103. package/dist/components/trust-score/trust-score.variants.d.ts.map +1 -1
  104. package/dist/components/trust-score/trust-score.variants.js +1 -1
  105. package/dist/components/trust-score/trust-score.variants.js.map +1 -1
  106. package/dist/index.d.ts +1 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +3 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/lib/focus-ring.d.ts +2 -0
  111. package/dist/lib/focus-ring.d.ts.map +1 -0
  112. package/dist/lib/focus-ring.js +5 -0
  113. package/dist/lib/focus-ring.js.map +1 -0
  114. package/package.json +18 -19
  115. package/registry/accordion.json +3 -2
  116. package/registry/agent-badge.json +1 -1
  117. package/registry/alert.json +3 -2
  118. package/registry/breadcrumb.json +3 -2
  119. package/registry/button.json +3 -2
  120. package/registry/card.json +3 -2
  121. package/registry/checkbox.json +4 -3
  122. package/registry/command-palette.json +3 -2
  123. package/registry/consent-toggle.json +1 -1
  124. package/registry/credential-card.json +1 -1
  125. package/registry/data-grid.json +2 -1
  126. package/registry/dialog.json +3 -2
  127. package/registry/focus-ring.json +16 -0
  128. package/registry/identity-chip.json +2 -1
  129. package/registry/init.json +1 -1
  130. package/registry/input.json +3 -2
  131. package/registry/label.json +1 -1
  132. package/registry/menu.json +4 -3
  133. package/registry/pagination.json +3 -2
  134. package/registry/popover.json +3 -2
  135. package/registry/progress.json +1 -1
  136. package/registry/radio.json +3 -2
  137. package/registry/select.json +3 -2
  138. package/registry/sheet.json +3 -2
  139. package/registry/sidebar.json +3 -2
  140. package/registry/switch.json +3 -2
  141. package/registry/table.json +3 -2
  142. package/registry/tabs.json +3 -2
  143. package/registry/textarea.json +3 -2
  144. package/registry/toast.json +3 -2
  145. package/registry/trust-score.json +1 -1
  146. 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 sheetScrimVariants = 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)",
@@ -21,7 +22,7 @@ const sheetPanelVariants = cva(
21
22
  "data-[state=open]:opacity-100 data-[state=closed]:opacity-0",
22
23
  // the panel takes focus when there is no obvious first control; its ring is never removed
23
24
  "outline-none",
24
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2"
25
+ focusRing
25
26
  ],
26
27
  {
27
28
  variants: {
@@ -80,7 +81,7 @@ const sheetCloseVariants = cva([
80
81
  "sm:min-h-(--size-target-desktop) sm:min-w-(--size-target-desktop)",
81
82
  // visible 2px focus ring at 2px offset; never removed
82
83
  "outline-none",
83
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2"
84
+ focusRing
84
85
  ]);
85
86
  const sheetCloseGlyphClass = "h-(--size-icon-md) w-(--size-icon-md)";
86
87
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/sheet/sheet.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// Sheet is a NEUTRAL overlay surface (spec §1/§3/§5/§8): brand violet and Verified Green are\n// accents that enter only through the components placed INSIDE the panel (a primary Button, a\n// VerifiedBadge), never on the PANEL or the SCRIM themselves. Restraint over volume — neutrals\n// carry the surface. So NOTHING in this file binds an --color-action-primary-* or --color-status-*\n// fill (brand != state, G-U2). This is the ONLY token-binding site (skill §5 hard rule).\n\n// The scrim: the dimming layer between the page and the panel; it signals the page behind is inert\n// and gives the focus trap a visible edge (spec §2 scrim, §5 --color-scrim-*). A neutral dim on the\n// modal z-layer, decorative (no role). The fade is a PLAIN base transition + verdify easing,\n// instant under reduced motion — never the 350ms VerifiedBadge-only theatre (G-U3 motion-theatre\n// gate). Enter/exit ride Radix's data-state on the overlay (attribute-selector variants, not\n// arbitrary values). On a light surface the dark scrim token applies (spec §5: scrim-dark).\nexport const sheetScrimVariants = 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 anchored to ONE viewport edge; 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 on its LEADING corners, the lg elevation shadow above the scrim, fixed on\n// the modal z-layer. It never fills the viewport (spec §3: lg never grows to full screen — a sheet\n// that fills the screen should be a route) and scrolls its BODY when content overflows (spec §4\n// scrolled) — the panel is a flex column; the SheetBody owns the scroll. Panel padding/gaps come\n// from --space-*.\n//\n// side (spec §3) = the docking edge AND the slide axis:\n// - inline-end (default) / inline-start: pinned to the full BLOCK extent (inset-y-0), sized by\n// WIDTH, sliding along the inline axis (translate-x). LTR right / left; mirrors under dir=rtl\n// because start/end are logical (G-U6).\n// - block-end: pinned to the full INLINE extent (inset-x-0), sized by HEIGHT, sliding up from the\n// bottom (translate-y). Reads as a bottom sheet on narrow/touch viewports.\n//\n// size (spec §3) = the CROSS-AXIS extent only — a WIDTH for an inline side, a HEIGHT for a block\n// side. md is the default. Resolved per-side by the compoundVariants below (an inline side maps\n// size -> w-(--container-*); a block side maps size -> h-(--container-*)). Bound to the shared\n// --container-* scale, the same scale Dialog caps its width with.\n//\n// The slide+fade open/close is the BASE duration + verdify easing, instant under reduced motion,\n// and rides Radix's data-state (attribute-selector enter/exit, not arbitrary values). NEVER the\n// deliberate verified-check theatre (G-U3). The closed translate is a per-side keyword utility\n// (translate-x-full / -translate-x-full / translate-y-full), set in the side variant.\nexport const sheetPanelVariants = cva(\n [\n \"fixed z-(--z-index-modal)\",\n // a flex column: header + footer stay pinned, the body scrolls within (spec §4 scrolled)\n \"flex flex-col gap-(--space-4)\",\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 slide+fade open/close + 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 — the fade is shared across sides (the per-side slide lives\n // in the side variant); attribute-selector variants, not arbitrary values\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\",\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 // side = the docking edge + slide axis (spec §3). Logical inset properties (G-U6): inset-y-0\n // / inset-x-0 pin the full extent along the docked edge; start-0 / end-0 / bottom-0 dock it.\n // The closed-state translate slides the panel fully off its own edge; open returns it to 0.\n side: {\n \"inline-end\": [\n \"inset-y-0 end-0 h-full\",\n \"data-[state=open]:translate-x-0 data-[state=closed]:translate-x-full\",\n ],\n \"inline-start\": [\n \"inset-y-0 start-0 h-full\",\n \"data-[state=open]:translate-x-0 data-[state=closed]:-translate-x-full\",\n ],\n \"block-end\": [\n \"inset-x-0 bottom-0 w-full\",\n \"data-[state=open]:translate-y-0 data-[state=closed]:translate-y-full\",\n ],\n },\n // size = cross-axis extent. The concrete axis (width vs height) is resolved per-side by the\n // compoundVariants below — this key only carries the default for type inference.\n size: {\n sm: \"\",\n md: \"\",\n lg: \"\",\n },\n },\n compoundVariants: [\n // inline sides: size -> WIDTH (the cross-axis of a side that pins the block extent)\n { side: [\"inline-end\", \"inline-start\"], size: \"sm\", class: \"w-(--container-sm)\" },\n { side: [\"inline-end\", \"inline-start\"], size: \"md\", class: \"w-(--container-md)\" },\n { side: [\"inline-end\", \"inline-start\"], size: \"lg\", class: \"w-(--container-lg)\" },\n // block side: size -> HEIGHT (the cross-axis of a side that pins the inline extent)\n { side: \"block-end\", size: \"sm\", class: \"h-(--container-sm)\" },\n { side: \"block-end\", size: \"md\", class: \"h-(--container-md)\" },\n { side: \"block-end\", size: \"lg\", class: \"h-(--container-lg)\" },\n ],\n defaultVariants: { side: \"inline-end\", size: \"md\" },\n },\n);\n\n// The header: the top region holding the title and the close button on the inline-end (spec §2\n// header). Logical-property layout (G-U6); a MUTED surface-border hairline divider under it (spec\n// §5 --color-surface-border-muted) — the panel's inner dividers are muted, distinct from its outer\n// surface-border edge.\nexport const sheetHeaderClass =\n \"flex items-start justify-between gap-(--space-4) border-b border-surface-border-muted pb-(--space-4)\";\n\n// The title: names the task as a statement, sentence case (spec §2). It IS the panel's accessible\n// name (Radix wires aria-labelledby). The h2 type role in primary text (spec §5 --text-h2 /\n// --color-text-primary).\nexport const sheetTitleClass = \"text-h2 text-text-primary\";\n\n// The description: optional supporting text under the title, associated with the panel for screen\n// readers (Radix wires aria-describedby). Body type role in secondary text (spec §5).\nexport const sheetDescriptionClass = \"text-body text-text-secondary\";\n\n// The body: the scrollable content region between header and footer (spec §2 body, §4 scrolled).\n// The panel is a fixed-height flex column; the body takes the remaining space and is the ONLY part\n// that scrolls — the header and footer stay pinned. Body text is the body type role in secondary\n// text (spec §5 --text-body / text-secondary).\nexport const sheetBodyClass =\n \"min-h-0 flex-1 overflow-y-auto text-body text-text-secondary\";\n\n// The footer: the optional action region (spec §2 footer). The primary action sits at the\n// inline-end with a Cancel beside it; the actions are Buttons — the sheet spec does not restate\n// their --color-action-* bindings (spec §5 note). A MUTED surface-border hairline divider above it\n// (spec §5). Logical-property layout (G-U6): actions flow inline-end with a gap.\nexport const sheetFooterClass =\n \"flex items-center justify-end gap-(--space-2) border-t border-surface-border-muted pt-(--space-4)\";\n\n// The close button: the dismiss control in the header, always present and reachable — a sheet is\n// never closable by the scrim alone (spec §2 close, §8). 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 below\n// it. fast functional hover motion + verdify easing, instant under reduced motion (G-U3).\nexport const sheetCloseVariants = 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 button's\n// ghost-fg. Decorative (aria-hidden) — the button carries the accessible name (spec §7).\nexport const sheetCloseGlyphClass = \"h-(--size-icon-md) w-(--size-icon-md)\";\n\nexport type SheetPanelVariantProps = VariantProps<typeof sheetPanelVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AAchC,MAAM,qBAAqB,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AA0BM,MAAM,qBAAqB;AAAA,EAChC;AAAA,IACE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA,MAIR,MAAM;AAAA,QACJ,cAAc;AAAA,UACZ;AAAA,UACA;AAAA,QACF;AAAA,QACA,gBAAgB;AAAA,UACd;AAAA,UACA;AAAA,QACF;AAAA,QACA,aAAa;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA,MAGA,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,kBAAkB;AAAA;AAAA,MAEhB,EAAE,MAAM,CAAC,cAAc,cAAc,GAAG,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAChF,EAAE,MAAM,CAAC,cAAc,cAAc,GAAG,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAChF,EAAE,MAAM,CAAC,cAAc,cAAc,GAAG,MAAM,MAAM,OAAO,qBAAqB;AAAA;AAAA,MAEhF,EAAE,MAAM,aAAa,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAC7D,EAAE,MAAM,aAAa,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAC7D,EAAE,MAAM,aAAa,MAAM,MAAM,OAAO,qBAAqB;AAAA,IAC/D;AAAA,IACA,iBAAiB,EAAE,MAAM,cAAc,MAAM,KAAK;AAAA,EACpD;AACF;AAMO,MAAM,mBACX;AAKK,MAAM,kBAAkB;AAIxB,MAAM,wBAAwB;AAM9B,MAAM,iBACX;AAMK,MAAM,mBACX;AAQK,MAAM,qBAAqB,IAAI;AAAA,EACpC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AACF,CAAC;AAIM,MAAM,uBAAuB;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/sheet/sheet.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// Sheet is a NEUTRAL overlay surface (spec §1/§3/§5/§8): brand violet and Verified Green are\n// accents that enter only through the components placed INSIDE the panel (a primary Button, a\n// VerifiedBadge), never on the PANEL or the SCRIM themselves. Restraint over volume — neutrals\n// carry the surface. So NOTHING in this file binds an --color-action-primary-* or --color-status-*\n// fill (brand != state, G-U2). This is the ONLY token-binding site (skill §5 hard rule).\n\n// The scrim: the dimming layer between the page and the panel; it signals the page behind is inert\n// and gives the focus trap a visible edge (spec §2 scrim, §5 --color-scrim-*). A neutral dim on the\n// modal z-layer, decorative (no role). The fade is a PLAIN base transition + verdify easing,\n// instant under reduced motion — never the 350ms VerifiedBadge-only theatre (G-U3 motion-theatre\n// gate). Enter/exit ride Radix's data-state on the overlay (attribute-selector variants, not\n// arbitrary values). On a light surface the dark scrim token applies (spec §5: scrim-dark).\nexport const sheetScrimVariants = 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 anchored to ONE viewport edge; 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 on its LEADING corners, the lg elevation shadow above the scrim, fixed on\n// the modal z-layer. It never fills the viewport (spec §3: lg never grows to full screen — a sheet\n// that fills the screen should be a route) and scrolls its BODY when content overflows (spec §4\n// scrolled) — the panel is a flex column; the SheetBody owns the scroll. Panel padding/gaps come\n// from --space-*.\n//\n// side (spec §3) = the docking edge AND the slide axis:\n// - inline-end (default) / inline-start: pinned to the full BLOCK extent (inset-y-0), sized by\n// WIDTH, sliding along the inline axis (translate-x). LTR right / left; mirrors under dir=rtl\n// because start/end are logical (G-U6).\n// - block-end: pinned to the full INLINE extent (inset-x-0), sized by HEIGHT, sliding up from the\n// bottom (translate-y). Reads as a bottom sheet on narrow/touch viewports.\n//\n// size (spec §3) = the CROSS-AXIS extent only — a WIDTH for an inline side, a HEIGHT for a block\n// side. md is the default. Resolved per-side by the compoundVariants below (an inline side maps\n// size -> w-(--container-*); a block side maps size -> h-(--container-*)). Bound to the shared\n// --container-* scale, the same scale Dialog caps its width with.\n//\n// The slide+fade open/close is the BASE duration + verdify easing, instant under reduced motion,\n// and rides Radix's data-state (attribute-selector enter/exit, not arbitrary values). NEVER the\n// deliberate verified-check theatre (G-U3). The closed translate is a per-side keyword utility\n// (translate-x-full / -translate-x-full / translate-y-full), set in the side variant.\nexport const sheetPanelVariants = cva(\n [\n \"fixed z-(--z-index-modal)\",\n // a flex column: header + footer stay pinned, the body scrolls within (spec §4 scrolled)\n \"flex flex-col gap-(--space-4)\",\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 slide+fade open/close + 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 — the fade is shared across sides (the per-side slide lives\n // in the side variant); attribute-selector variants, not arbitrary values\n \"data-[state=open]:opacity-100 data-[state=closed]:opacity-0\",\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 // side = the docking edge + slide axis (spec §3). Logical inset properties (G-U6): inset-y-0\n // / inset-x-0 pin the full extent along the docked edge; start-0 / end-0 / bottom-0 dock it.\n // The closed-state translate slides the panel fully off its own edge; open returns it to 0.\n side: {\n \"inline-end\": [\n \"inset-y-0 end-0 h-full\",\n \"data-[state=open]:translate-x-0 data-[state=closed]:translate-x-full\",\n ],\n \"inline-start\": [\n \"inset-y-0 start-0 h-full\",\n \"data-[state=open]:translate-x-0 data-[state=closed]:-translate-x-full\",\n ],\n \"block-end\": [\n \"inset-x-0 bottom-0 w-full\",\n \"data-[state=open]:translate-y-0 data-[state=closed]:translate-y-full\",\n ],\n },\n // size = cross-axis extent. The concrete axis (width vs height) is resolved per-side by the\n // compoundVariants below — this key only carries the default for type inference.\n size: {\n sm: \"\",\n md: \"\",\n lg: \"\",\n },\n },\n compoundVariants: [\n // inline sides: size -> WIDTH (the cross-axis of a side that pins the block extent)\n { side: [\"inline-end\", \"inline-start\"], size: \"sm\", class: \"w-(--container-sm)\" },\n { side: [\"inline-end\", \"inline-start\"], size: \"md\", class: \"w-(--container-md)\" },\n { side: [\"inline-end\", \"inline-start\"], size: \"lg\", class: \"w-(--container-lg)\" },\n // block side: size -> HEIGHT (the cross-axis of a side that pins the inline extent)\n { side: \"block-end\", size: \"sm\", class: \"h-(--container-sm)\" },\n { side: \"block-end\", size: \"md\", class: \"h-(--container-md)\" },\n { side: \"block-end\", size: \"lg\", class: \"h-(--container-lg)\" },\n ],\n defaultVariants: { side: \"inline-end\", size: \"md\" },\n },\n);\n\n// The header: the top region holding the title and the close button on the inline-end (spec §2\n// header). Logical-property layout (G-U6); a MUTED surface-border hairline divider under it (spec\n// §5 --color-surface-border-muted) — the panel's inner dividers are muted, distinct from its outer\n// surface-border edge.\nexport const sheetHeaderClass =\n \"flex items-start justify-between gap-(--space-4) border-b border-surface-border-muted pb-(--space-4)\";\n\n// The title: names the task as a statement, sentence case (spec §2). It IS the panel's accessible\n// name (Radix wires aria-labelledby). The h2 type role in primary text (spec §5 --text-h2 /\n// --color-text-primary).\nexport const sheetTitleClass = \"text-h2 text-text-primary\";\n\n// The description: optional supporting text under the title, associated with the panel for screen\n// readers (Radix wires aria-describedby). Body type role in secondary text (spec §5).\nexport const sheetDescriptionClass = \"text-body text-text-secondary\";\n\n// The body: the scrollable content region between header and footer (spec §2 body, §4 scrolled).\n// The panel is a fixed-height flex column; the body takes the remaining space and is the ONLY part\n// that scrolls — the header and footer stay pinned. Body text is the body type role in secondary\n// text (spec §5 --text-body / text-secondary).\nexport const sheetBodyClass =\n \"min-h-0 flex-1 overflow-y-auto text-body text-text-secondary\";\n\n// The footer: the optional action region (spec §2 footer). The primary action sits at the\n// inline-end with a Cancel beside it; the actions are Buttons — the sheet spec does not restate\n// their --color-action-* bindings (spec §5 note). A MUTED surface-border hairline divider above it\n// (spec §5). Logical-property layout (G-U6): actions flow inline-end with a gap.\nexport const sheetFooterClass =\n \"flex items-center justify-end gap-(--space-2) border-t border-surface-border-muted pt-(--space-4)\";\n\n// The close button: the dismiss control in the header, always present and reachable — a sheet is\n// never closable by the scrim alone (spec §2 close, §8). 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 below\n// it. fast functional hover motion + verdify easing, instant under reduced motion (G-U3).\nexport const sheetCloseVariants = 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 button's\n// ghost-fg. Decorative (aria-hidden) — the button carries the accessible name (spec §7).\nexport const sheetCloseGlyphClass = \"h-(--size-icon-md) w-(--size-icon-md)\";\n\nexport type SheetPanelVariantProps = VariantProps<typeof sheetPanelVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAcnB,MAAM,qBAAqB,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AA0BM,MAAM,qBAAqB;AAAA,EAChC;AAAA,IACE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA,MAIR,MAAM;AAAA,QACJ,cAAc;AAAA,UACZ;AAAA,UACA;AAAA,QACF;AAAA,QACA,gBAAgB;AAAA,UACd;AAAA,UACA;AAAA,QACF;AAAA,QACA,aAAa;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA,MAGA,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,kBAAkB;AAAA;AAAA,MAEhB,EAAE,MAAM,CAAC,cAAc,cAAc,GAAG,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAChF,EAAE,MAAM,CAAC,cAAc,cAAc,GAAG,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAChF,EAAE,MAAM,CAAC,cAAc,cAAc,GAAG,MAAM,MAAM,OAAO,qBAAqB;AAAA;AAAA,MAEhF,EAAE,MAAM,aAAa,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAC7D,EAAE,MAAM,aAAa,MAAM,MAAM,OAAO,qBAAqB;AAAA,MAC7D,EAAE,MAAM,aAAa,MAAM,MAAM,OAAO,qBAAqB;AAAA,IAC/D;AAAA,IACA,iBAAiB,EAAE,MAAM,cAAc,MAAM,KAAK;AAAA,EACpD;AACF;AAMO,MAAM,mBACX;AAKK,MAAM,kBAAkB;AAIxB,MAAM,wBAAwB;AAM9B,MAAM,iBACX;AAMK,MAAM,mBACX;AAQK,MAAM,qBAAqB,IAAI;AAAA,EACpC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AACF,CAAC;AAIM,MAAM,uBAAuB;","names":[]}
@@ -5,7 +5,7 @@ export declare const sidebarRailVariants: (props?: ({
5
5
  export type SidebarRailVariantProps = VariantProps<typeof sidebarRailVariants>;
6
6
  export declare const sidebarHeaderClass = "flex items-center gap-(--space-2) px-(--space-2) py-(--space-2) border-b border-surface-border-muted";
7
7
  export declare const sidebarGroupClass = "flex flex-col gap-(--space-1) py-(--space-2)";
8
- export declare const sidebarGroupLabelClass = "px-(--space-2) py-(--space-1) text-caption text-text-muted select-none";
8
+ export declare const sidebarGroupLabelClass = "px-(--space-2) py-(--space-1) text-caption text-text-secondary select-none";
9
9
  export declare const sidebarListClass = "flex flex-col gap-(--space-1) m-0 p-0 list-none";
10
10
  export declare const sidebarItemVariants: (props?: ({} & import("class-variance-authority/types").ClassProp) | undefined) => string;
11
11
  export type SidebarItemVariantProps = VariantProps<typeof sidebarItemVariants>;
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar.variants.d.ts","sourceRoot":"","sources":["../../../src/components/sidebar/sidebar.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAgBlE,eAAO,MAAM,mBAAmB;;8EAsB/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAK/E,eAAO,MAAM,kBAAkB,yGACyE,CAAC;AAIzG,eAAO,MAAM,iBAAiB,iDAAiD,CAAC;AAIhF,eAAO,MAAM,sBAAsB,2EACuC,CAAC;AAI3E,eAAO,MAAM,gBAAgB,oDAAoD,CAAC;AAsBlF,eAAO,MAAM,mBAAmB,2FA8B/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAM/E,eAAO,MAAM,oBAAoB,gHAC8E,CAAC;AAKhH,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAI/D,eAAO,MAAM,wBAAwB,sEACgC,CAAC;AAItE,eAAO,MAAM,kBAAkB,8FAC8D,CAAC;AAM9F,eAAO,MAAM,0BAA0B,2FAatC,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG,YAAY,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAIhG,eAAO,MAAM,wBAAwB,oOAC8L,CAAC"}
1
+ {"version":3,"file":"sidebar.variants.d.ts","sourceRoot":"","sources":["../../../src/components/sidebar/sidebar.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAiBlE,eAAO,MAAM,mBAAmB;;8EAsB/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAK/E,eAAO,MAAM,kBAAkB,yGACyE,CAAC;AAIzG,eAAO,MAAM,iBAAiB,iDAAiD,CAAC;AAKhF,eAAO,MAAM,sBAAsB,+EAC2C,CAAC;AAI/E,eAAO,MAAM,gBAAgB,oDAAoD,CAAC;AAsBlF,eAAO,MAAM,mBAAmB,2FA8B/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAM/E,eAAO,MAAM,oBAAoB,gHAC8E,CAAC;AAKhH,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAI/D,eAAO,MAAM,wBAAwB,sEACgC,CAAC;AAItE,eAAO,MAAM,kBAAkB,8FAC8D,CAAC;AAM9F,eAAO,MAAM,0BAA0B,2FAatC,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG,YAAY,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAIhG,eAAO,MAAM,wBAAwB,oOAC8L,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const sidebarRailVariants = cva(
3
4
  [
4
5
  // surface + elevation + column layout with the rail insets
@@ -24,7 +25,7 @@ const sidebarRailVariants = cva(
24
25
  );
25
26
  const sidebarHeaderClass = "flex items-center gap-(--space-2) px-(--space-2) py-(--space-2) border-b border-surface-border-muted";
26
27
  const sidebarGroupClass = "flex flex-col gap-(--space-1) py-(--space-2)";
27
- const sidebarGroupLabelClass = "px-(--space-2) py-(--space-1) text-caption text-text-muted select-none";
28
+ const sidebarGroupLabelClass = "px-(--space-2) py-(--space-1) text-caption text-text-secondary select-none";
28
29
  const sidebarListClass = "flex flex-col gap-(--space-1) m-0 p-0 list-none";
29
30
  const sidebarItemVariants = cva(
30
31
  [
@@ -50,7 +51,7 @@ const sidebarItemVariants = cva(
50
51
  "min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)",
51
52
  // focus ring — identical on every state, never removed; persists expanded and collapsed
52
53
  "outline-none",
53
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
54
+ focusRing,
54
55
  // disabled (non-operable) item — DEC-C: dim via the disabled TOKEN, never opacity. aria-disabled
55
56
  // drives it because an <a> has no native disabled; the component also strips href + tabindex.
56
57
  "aria-disabled:pointer-events-none aria-disabled:text-text-disabled"
@@ -70,7 +71,7 @@ const sidebarCollapseToggleClass = cva(
70
71
  "motion-reduce:duration-(--motion-duration-instant)",
71
72
  "min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)",
72
73
  "outline-none",
73
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
74
+ focusRing,
74
75
  "disabled:pointer-events-none disabled:text-text-disabled"
75
76
  ],
76
77
  { variants: {}, defaultVariants: {} }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/sidebar/sidebar.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// A Sidebar is the primary navigation rail down one edge of an app shell (spec §1). It is a\n// NEUTRAL surface (spec §3): the rail does not wear the brand. The only accent is on the CURRENT\n// item, which takes the primary ACTION (brand) alias — where you are — never a status color: a\n// current item reports your location, not a verification result, so it binds nothing from the\n// status tier (brand != state, G-U2). A verified meaning belongs to VerifiedBadge. The rail\n// paints from the surface, text, action(primary + ghost), and border aliases only (spec §5).\n\n// The nav landmark wrapping the rail. The raised neutral surface against the page canvas, an edge\n// against the content area (a logical inline-end border when docked inline-start, inline-start when\n// docked inline-end — G-U6, so it mirrors under dir=\"rtl\"), the sm elevation where it floats above\n// content, and the expand/collapse WIDTH transition. Motion is the BASE token transition on the\n// verdify easing, collapsing to the instant endpoint under reduced motion — never the 350ms\n// VerifiedBadge-only theatre duration (toggling the rail is a plain transition, not theatre — spec\n// §4 Collapsed, G-U3). The rail is NEVER tinted with the brand or a status fill (spec §3/§8).\nexport const sidebarRailVariants = cva(\n [\n // surface + elevation + column layout with the rail insets\n \"flex flex-col gap-(--space-2) bg-surface-raised shadow-sm\",\n \"p-(--space-2)\",\n // the expand/collapse width transition — base duration + verdify easing, instant under\n // reduced motion (NEVER the deliberate verified-check theatre, G-U3)\n \"transition-[width] duration-(--motion-duration-base) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n ],\n {\n variants: {\n // SIDE axis (spec §3): the rail docks at a logical edge and carries its border on the edge\n // that faces the content area — inline-end when docked inline-start, inline-start when docked\n // inline-end. Logical properties only (border-e / border-s), so it mirrors under dir=\"rtl\".\n side: {\n \"inline-start\": \"border-e border-surface-border\",\n \"inline-end\": \"border-s border-surface-border\",\n },\n },\n defaultVariants: { side: \"inline-start\" },\n },\n);\n\nexport type SidebarRailVariantProps = VariantProps<typeof sidebarRailVariants>;\n\n// The header slot (spec §2): the top slot for a product mark or workspace switcher. It is NOT a\n// navigation item and is skipped by item arrow movement. A separator from the list below it via a\n// muted hairline (spec §5, surface-border-muted). Plain structural slot — no variant axis.\nexport const sidebarHeaderClass =\n \"flex items-center gap-(--space-2) px-(--space-2) py-(--space-2) border-b border-surface-border-muted\";\n\n// A labeled cluster of items (spec §2 group). Restraint over volume keeps the rail scannable, so a\n// group is plain structure; its dividers use the muted hairline (spec §5). No variant axis.\nexport const sidebarGroupClass = \"flex flex-col gap-(--space-1) py-(--space-2)\";\n\n// The group heading (spec §2/§5): the cluster label in the MUTED text color at the CAPTION type\n// role. Decorative-weight wayfinding, not a navigation item.\nexport const sidebarGroupLabelClass =\n \"px-(--space-2) py-(--space-1) text-caption text-text-muted select-none\";\n\n// The item list (the <ul>): a flush column of items. The list carries no text color — each item\n// sets its own. The roving arrow-key handler is wired by the component on this element.\nexport const sidebarListClass = \"flex flex-col gap-(--space-1) m-0 p-0 list-none\";\n\n// One navigation item (spec §2 item, §4 states). A native <a href> link.\n//\n// RESTING (default): the LABEL in the SECONDARY text color and the icon in the GHOST action fg, on\n// the rail surface with NO fill (spec §4 Default).\n// HOVER: a restrained ghost-action hover fill; the cursor is a pointer (spec §4 Hover). The only\n// fill a resting item ever paints.\n// CURRENT (aria-current=page): the leading INDICATOR BAR is painted in the primary ACTION (brand)\n// alias — where you are — and the label lifts to the PRIMARY text color. This spec offers two\n// indicator treatments (spec §5: an indicator \"bar OR fill\"); this component uses the restrained\n// BAR (a logical inline-start border accent) so the rail never wears a brand SURFACE (spec §3/§8).\n// The current state is never carried by color alone: the indicator bar shape AND aria-current also\n// encode it, so it survives a contrast or color-blind reading (spec §4 Current / 1.4.1, brand !=\n// state — NEVER status-verified).\n// FOCUS: the visible 2px focus ring, part of the base on every state, never removed; it persists in\n// both the expanded and collapsed rail (spec §4 Focus / 2.4.7).\n// DISABLED (aria-disabled): dims via the disabled TOKEN (DEC-C), never a blanket opacity; the\n// component also strips href + tabindex and skips it in arrow movement, while the label stays\n// readable to AT (spec §4 Disabled / §7).\n// Motion is the fast token transition on the verdify easing, instant under reduced motion — never\n// the 350ms VerifiedBadge-only theatre duration (G-U3).\nexport const sidebarItemVariants = cva(\n [\n // shape + the icon-to-label gap + logical inline padding so it mirrors under RTL; the leading\n // indicator bar is a left/start accent rendered via a logical inline-start border that is\n // transparent at rest and painted in the action alias when current\n \"relative flex items-center gap-(--space-3) rounded-(--radius-md) px-(--space-3)\",\n \"border-s-2 border-transparent\",\n // type ROLE + resting label color, no fill, pointer cursor\n \"text-label text-text-secondary no-underline cursor-pointer select-none\",\n // hover: the restrained ghost-action fill (the only fill a resting item paints)\n \"hover:bg-action-ghost-bg-hover\",\n // CURRENT: the leading indicator BAR is painted in the brand action alias and the label lifts to\n // the primary text color. This is the action(primary) alias (where you are), NEVER status-verified\n // (brand != state, G-U2). The indicator bar SHAPE + aria-current carry the state alongside the\n // color, so it survives a color-blind read; the rail itself stays a neutral surface (no brand fill).\n \"aria-[current=page]:border-s-action-primary-bg aria-[current=page]:text-text-primary\",\n // motion: fast + verdify easing, instant under reduced motion (NEVER the check theatre, G-U3)\n \"transition-[color,background-color,border-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 item (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 // focus ring — identical on every state, never removed; persists expanded and collapsed\n \"outline-none\",\n \"focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n // disabled (non-operable) item — DEC-C: dim via the disabled TOKEN, never opacity. aria-disabled\n // drives it because an <a> has no native disabled; the component also strips href + tabindex.\n \"aria-disabled:pointer-events-none aria-disabled:text-text-disabled\",\n ],\n { variants: {}, defaultVariants: {} },\n);\n\nexport type SidebarItemVariantProps = VariantProps<typeof sidebarItemVariants>;\n\n// The leading item icon (spec §5): the md icon role, decorative (the item names itself by its label\n// text, not the glyph). At rest it is the GHOST action fg; it inherits the disabled token when the\n// item is disabled (aria-disabled on the parent link). When the item is current it lifts with the\n// label to the primary text color. shrink-0 so it never collapses when labels are hidden.\nexport const sidebarItemIconClass =\n \"inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center text-action-ghost-fg\";\n\n// The item label text (spec §5): the part hidden in the collapsed rail. When collapsed the label is\n// visually hidden but stays in the accessibility tree (sr-only), so the link keeps its accessible\n// name without depending on a tooltip being open (spec §4 Collapsed / §7).\nexport const sidebarItemLabelClass = \"min-w-0 flex-1 truncate\";\n\n// A trailing count or status on an item (spec §2): text or an aria-label, never color alone (spec\n// §7). Plain secondary caption text, hidden with the label when collapsed.\nexport const sidebarItemTrailingClass =\n \"ms-auto inline-flex items-center text-caption text-text-secondary\";\n\n// The footer slot (spec §2): a bottom slot for an account or settings entry; its items follow the\n// same item rules. Pushed to the bottom; separated from the list above by the muted hairline.\nexport const sidebarFooterClass =\n \"mt-auto flex flex-col gap-(--space-1) pt-(--space-2) border-t border-surface-border-muted\";\n\n// The collapse-toggle (spec §2/§4/§6/§7): a native <button>, NOT an item, reachable in the rail's\n// tab order. It carries the ghost-action glyph and the same neutral hover fill, focus ring, and\n// target-size floor as an item. Its glyph is decorative (aria-hidden); aria-expanded + aria-label\n// carry the action. Same fast motion, never the deliberate theatre.\nexport const sidebarCollapseToggleClass = cva(\n [\n \"inline-flex items-center justify-center gap-(--space-2) rounded-(--radius-md) px-(--space-2)\",\n \"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\",\n \"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 { variants: {}, defaultVariants: {} },\n);\n\nexport type SidebarCollapseToggleVariantProps = VariantProps<typeof sidebarCollapseToggleClass>;\n\n// The collapse-toggle glyph (spec §5): the md icon role, decorative. It rotates to mirror the rail\n// width (collapsed vs expanded) as a reinforcement; aria-expanded carries the state, not the glyph.\nexport const sidebarCollapseIconClass =\n \"inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center transition-transform duration-(--motion-duration-base) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant)\";\n"],"mappings":"AAAA,SAAS,WAA8B;AAgBhC,MAAM,sBAAsB;AAAA,EACjC;AAAA;AAAA,IAEE;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA,MAIR,MAAM;AAAA,QACJ,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAChB;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,eAAe;AAAA,EAC1C;AACF;AAOO,MAAM,qBACX;AAIK,MAAM,oBAAoB;AAI1B,MAAM,yBACX;AAIK,MAAM,mBAAmB;AAsBzB,MAAM,sBAAsB;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,IAIE;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA,EACF;AAAA,EACA,EAAE,UAAU,CAAC,GAAG,iBAAiB,CAAC,EAAE;AACtC;AAQO,MAAM,uBACX;AAKK,MAAM,wBAAwB;AAI9B,MAAM,2BACX;AAIK,MAAM,qBACX;AAMK,MAAM,6BAA6B;AAAA,EACxC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,EAAE,UAAU,CAAC,GAAG,iBAAiB,CAAC,EAAE;AACtC;AAMO,MAAM,2BACX;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/sidebar/sidebar.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// A Sidebar is the primary navigation rail down one edge of an app shell (spec §1). It is a\n// NEUTRAL surface (spec §3): the rail does not wear the brand. The only accent is on the CURRENT\n// item, which takes the primary ACTION (brand) alias — where you are — never a status color: a\n// current item reports your location, not a verification result, so it binds nothing from the\n// status tier (brand != state, G-U2). A verified meaning belongs to VerifiedBadge. The rail\n// paints from the surface, text, action(primary + ghost), and border aliases only (spec §5).\n\n// The nav landmark wrapping the rail. The raised neutral surface against the page canvas, an edge\n// against the content area (a logical inline-end border when docked inline-start, inline-start when\n// docked inline-end — G-U6, so it mirrors under dir=\"rtl\"), the sm elevation where it floats above\n// content, and the expand/collapse WIDTH transition. Motion is the BASE token transition on the\n// verdify easing, collapsing to the instant endpoint under reduced motion — never the 350ms\n// VerifiedBadge-only theatre duration (toggling the rail is a plain transition, not theatre — spec\n// §4 Collapsed, G-U3). The rail is NEVER tinted with the brand or a status fill (spec §3/§8).\nexport const sidebarRailVariants = cva(\n [\n // surface + elevation + column layout with the rail insets\n \"flex flex-col gap-(--space-2) bg-surface-raised shadow-sm\",\n \"p-(--space-2)\",\n // the expand/collapse width transition — base duration + verdify easing, instant under\n // reduced motion (NEVER the deliberate verified-check theatre, G-U3)\n \"transition-[width] duration-(--motion-duration-base) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n ],\n {\n variants: {\n // SIDE axis (spec §3): the rail docks at a logical edge and carries its border on the edge\n // that faces the content area — inline-end when docked inline-start, inline-start when docked\n // inline-end. Logical properties only (border-e / border-s), so it mirrors under dir=\"rtl\".\n side: {\n \"inline-start\": \"border-e border-surface-border\",\n \"inline-end\": \"border-s border-surface-border\",\n },\n },\n defaultVariants: { side: \"inline-start\" },\n },\n);\n\nexport type SidebarRailVariantProps = VariantProps<typeof sidebarRailVariants>;\n\n// The header slot (spec §2): the top slot for a product mark or workspace switcher. It is NOT a\n// navigation item and is skipped by item arrow movement. A separator from the list below it via a\n// muted hairline (spec §5, surface-border-muted). Plain structural slot — no variant axis.\nexport const sidebarHeaderClass =\n \"flex items-center gap-(--space-2) px-(--space-2) py-(--space-2) border-b border-surface-border-muted\";\n\n// A labeled cluster of items (spec §2 group). Restraint over volume keeps the rail scannable, so a\n// group is plain structure; its dividers use the muted hairline (spec §5). No variant axis.\nexport const sidebarGroupClass = \"flex flex-col gap-(--space-1) py-(--space-2)\";\n\n// The group heading (spec §2/§5): the cluster label in the SECONDARY text color at the CAPTION type\n// role. It is essential de-emphasized text — it names the cluster of items — so it uses\n// --color-text-secondary (AA), not the decorative-only muted role (accessibility.md).\nexport const sidebarGroupLabelClass =\n \"px-(--space-2) py-(--space-1) text-caption text-text-secondary select-none\";\n\n// The item list (the <ul>): a flush column of items. The list carries no text color — each item\n// sets its own. The roving arrow-key handler is wired by the component on this element.\nexport const sidebarListClass = \"flex flex-col gap-(--space-1) m-0 p-0 list-none\";\n\n// One navigation item (spec §2 item, §4 states). A native <a href> link.\n//\n// RESTING (default): the LABEL in the SECONDARY text color and the icon in the GHOST action fg, on\n// the rail surface with NO fill (spec §4 Default).\n// HOVER: a restrained ghost-action hover fill; the cursor is a pointer (spec §4 Hover). The only\n// fill a resting item ever paints.\n// CURRENT (aria-current=page): the leading INDICATOR BAR is painted in the primary ACTION (brand)\n// alias — where you are — and the label lifts to the PRIMARY text color. This spec offers two\n// indicator treatments (spec §5: an indicator \"bar OR fill\"); this component uses the restrained\n// BAR (a logical inline-start border accent) so the rail never wears a brand SURFACE (spec §3/§8).\n// The current state is never carried by color alone: the indicator bar shape AND aria-current also\n// encode it, so it survives a contrast or color-blind reading (spec §4 Current / 1.4.1, brand !=\n// state — NEVER status-verified).\n// FOCUS: the visible 2px focus ring, part of the base on every state, never removed; it persists in\n// both the expanded and collapsed rail (spec §4 Focus / 2.4.7).\n// DISABLED (aria-disabled): dims via the disabled TOKEN (DEC-C), never a blanket opacity; the\n// component also strips href + tabindex and skips it in arrow movement, while the label stays\n// readable to AT (spec §4 Disabled / §7).\n// Motion is the fast token transition on the verdify easing, instant under reduced motion — never\n// the 350ms VerifiedBadge-only theatre duration (G-U3).\nexport const sidebarItemVariants = cva(\n [\n // shape + the icon-to-label gap + logical inline padding so it mirrors under RTL; the leading\n // indicator bar is a left/start accent rendered via a logical inline-start border that is\n // transparent at rest and painted in the action alias when current\n \"relative flex items-center gap-(--space-3) rounded-(--radius-md) px-(--space-3)\",\n \"border-s-2 border-transparent\",\n // type ROLE + resting label color, no fill, pointer cursor\n \"text-label text-text-secondary no-underline cursor-pointer select-none\",\n // hover: the restrained ghost-action fill (the only fill a resting item paints)\n \"hover:bg-action-ghost-bg-hover\",\n // CURRENT: the leading indicator BAR is painted in the brand action alias and the label lifts to\n // the primary text color. This is the action(primary) alias (where you are), NEVER status-verified\n // (brand != state, G-U2). The indicator bar SHAPE + aria-current carry the state alongside the\n // color, so it survives a color-blind read; the rail itself stays a neutral surface (no brand fill).\n \"aria-[current=page]:border-s-action-primary-bg aria-[current=page]:text-text-primary\",\n // motion: fast + verdify easing, instant under reduced motion (NEVER the check theatre, G-U3)\n \"transition-[color,background-color,border-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 item (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 // focus ring — identical on every state, never removed; persists expanded and collapsed\n \"outline-none\",\n focusRing,\n // disabled (non-operable) item — DEC-C: dim via the disabled TOKEN, never opacity. aria-disabled\n // drives it because an <a> has no native disabled; the component also strips href + tabindex.\n \"aria-disabled:pointer-events-none aria-disabled:text-text-disabled\",\n ],\n { variants: {}, defaultVariants: {} },\n);\n\nexport type SidebarItemVariantProps = VariantProps<typeof sidebarItemVariants>;\n\n// The leading item icon (spec §5): the md icon role, decorative (the item names itself by its label\n// text, not the glyph). At rest it is the GHOST action fg; it inherits the disabled token when the\n// item is disabled (aria-disabled on the parent link). When the item is current it lifts with the\n// label to the primary text color. shrink-0 so it never collapses when labels are hidden.\nexport const sidebarItemIconClass =\n \"inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center text-action-ghost-fg\";\n\n// The item label text (spec §5): the part hidden in the collapsed rail. When collapsed the label is\n// visually hidden but stays in the accessibility tree (sr-only), so the link keeps its accessible\n// name without depending on a tooltip being open (spec §4 Collapsed / §7).\nexport const sidebarItemLabelClass = \"min-w-0 flex-1 truncate\";\n\n// A trailing count or status on an item (spec §2): text or an aria-label, never color alone (spec\n// §7). Plain secondary caption text, hidden with the label when collapsed.\nexport const sidebarItemTrailingClass =\n \"ms-auto inline-flex items-center text-caption text-text-secondary\";\n\n// The footer slot (spec §2): a bottom slot for an account or settings entry; its items follow the\n// same item rules. Pushed to the bottom; separated from the list above by the muted hairline.\nexport const sidebarFooterClass =\n \"mt-auto flex flex-col gap-(--space-1) pt-(--space-2) border-t border-surface-border-muted\";\n\n// The collapse-toggle (spec §2/§4/§6/§7): a native <button>, NOT an item, reachable in the rail's\n// tab order. It carries the ghost-action glyph and the same neutral hover fill, focus ring, and\n// target-size floor as an item. Its glyph is decorative (aria-hidden); aria-expanded + aria-label\n// carry the action. Same fast motion, never the deliberate theatre.\nexport const sidebarCollapseToggleClass = cva(\n [\n \"inline-flex items-center justify-center gap-(--space-2) rounded-(--radius-md) px-(--space-2)\",\n \"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\",\n focusRing,\n \"disabled:pointer-events-none disabled:text-text-disabled\",\n ],\n { variants: {}, defaultVariants: {} },\n);\n\nexport type SidebarCollapseToggleVariantProps = VariantProps<typeof sidebarCollapseToggleClass>;\n\n// The collapse-toggle glyph (spec §5): the md icon role, decorative. It rotates to mirror the rail\n// width (collapsed vs expanded) as a reinforcement; aria-expanded carries the state, not the glyph.\nexport const sidebarCollapseIconClass =\n \"inline-flex h-(--size-icon-md) w-(--size-icon-md) shrink-0 items-center justify-center transition-transform duration-(--motion-duration-base) ease-(--motion-easing-verdify) motion-reduce:duration-(--motion-duration-instant)\";\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAgBnB,MAAM,sBAAsB;AAAA,EACjC;AAAA;AAAA,IAEE;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA,MAIR,MAAM;AAAA,QACJ,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAChB;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,eAAe;AAAA,EAC1C;AACF;AAOO,MAAM,qBACX;AAIK,MAAM,oBAAoB;AAK1B,MAAM,yBACX;AAIK,MAAM,mBAAmB;AAsBzB,MAAM,sBAAsB;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,IAIE;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA,EACF;AAAA,EACA,EAAE,UAAU,CAAC,GAAG,iBAAiB,CAAC,EAAE;AACtC;AAQO,MAAM,uBACX;AAKK,MAAM,wBAAwB;AAI9B,MAAM,2BACX;AAIK,MAAM,qBACX;AAMK,MAAM,6BAA6B;AAAA,EACxC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,EAAE,UAAU,CAAC,GAAG,iBAAiB,CAAC,EAAE;AACtC;AAMO,MAAM,2BACX;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"switch.variants.d.ts","sourceRoot":"","sources":["../../../src/components/switch/switch.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AASlE,eAAO,MAAM,mBAAmB;;8EA8B/B,CAAC;AAUF,eAAO,MAAM,mBAAmB;;;8EAgB/B,CAAC;AAMF,eAAO,MAAM,qBAAqB,oFAGhC,CAAC;AAMH,eAAO,MAAM,mBAAmB;;8EAQ/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"switch.variants.d.ts","sourceRoot":"","sources":["../../../src/components/switch/switch.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAUlE,eAAO,MAAM,mBAAmB;;8EA8B/B,CAAC;AAUF,eAAO,MAAM,mBAAmB;;;8EAgB/B,CAAC;AAMF,eAAO,MAAM,qBAAqB,oFAGhC,CAAC;AAMH,eAAO,MAAM,mBAAmB;;8EAQ/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,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 switchTrackVariants = cva(
3
4
  [
4
5
  "relative inline-flex shrink-0 items-center rounded-full border",
@@ -11,7 +12,7 @@ const switchTrackVariants = cva(
11
12
  "transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)",
12
13
  // visible 2px signal-blue ring at 2px offset, on every state, never removed
13
14
  "outline-none",
14
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
15
+ focusRing,
15
16
  // disabled: no pointer; the checked value still reads to AT. DEC-C / spec §4:
16
17
  // reduced emphasis comes from dimming the thumb (the indicator) to the disabled
17
18
  // TOKEN — see switchThumbVariants — NOT a blanket opacity-60 dim of the track.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/switch/switch.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// The track: a native <button role=\"switch\">. Off binds control-*; the\n// aria-checked: state-variant binds the on (action-primary) track — never a\n// status-* utility (brand ≠ state). The visible 2px focus ring is never removed.\n// The target-size floor lives on the hit-area wrapper (switchHitAreaVariants), NOT\n// here: a min-height floor on the SAME node as the h-5/h-6 track would force both\n// sizes to the 44px floor (used height = max(min-height, height)), erasing the\n// size variant. The track keeps only its own h-5/h-6 visible height.\nexport const switchTrackVariants = cva(\n [\n \"relative inline-flex shrink-0 items-center rounded-full border\",\n // off (resting) track + resting border\n \"bg-control-bg border-control-border\",\n // on track (state-variant) + on hover / pressed\n \"aria-checked:bg-action-primary-bg aria-checked:border-action-primary-bg\",\n \"aria-checked:hover:bg-action-primary-bg-hover aria-checked:active:bg-action-primary-bg-active\",\n // fast, functional track-tint — no theatre\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n // visible 2px signal-blue ring at 2px offset, on every state, never removed\n \"outline-none\",\n \"focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2\",\n // disabled: no pointer; the checked value still reads to AT. DEC-C / spec §4:\n // reduced emphasis comes from dimming the thumb (the indicator) to the disabled\n // TOKEN — see switchThumbVariants — NOT a blanket opacity-60 dim of the track.\n \"disabled:pointer-events-none\",\n \"aria-busy:pointer-events-none\",\n ],\n {\n variants: {\n // visible track height/width only — the hit-area floor is a separate wrapper,\n // so sm renders a genuinely shorter visible track than md\n size: {\n md: \"h-6 w-11 px-0.5\",\n sm: \"h-5 w-9 px-0.5\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n// The thumb: control-fg fill, slides start→end. Position (not color) carries on/off\n// state, via the aria-checked: translate variant on the parent button.\n//\n// DEC-C / spec §4: a disabled switch dims the thumb (its indicator) to\n// --color-text-disabled — the SAME token the label dims to — NOT a blanket\n// opacity-60 dim of the track. The thumb is a child of the button, not a sibling\n// of a peer input, so the colour is driven by an explicit `disabled` cva variant\n// the component passes (mirrors switchLabelVariants); enabled keeps control-fg.\nexport const switchThumbVariants = cva(\n [\n \"pointer-events-none block rounded-full shadow-sm\",\n \"translate-x-0 aria-checked:translate-x-full\",\n \"transition-transform duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n ],\n {\n variants: {\n size: {\n md: \"h-5 w-5\",\n sm: \"h-4 w-4\",\n },\n disabled: { true: \"bg-text-disabled\", false: \"bg-control-fg\" },\n },\n defaultVariants: { size: \"md\", disabled: false },\n },\n);\n\n// The hit-area wrapper around the track. This — NOT the visible track — carries the\n// target-size floor (44px touch / 40px pointer) so the touch target always meets the\n// minimum while the track keeps its smaller h-5/h-6 visible height. inline-flex +\n// items-center vertically centres the shorter track within the taller hit area.\nexport const switchHitAreaVariants = cva([\n \"inline-flex shrink-0 items-center justify-center\",\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n]);\n\n// The label naming the setting. The <label> element is nested in a column wrapper,\n// not a sibling of the button, so peer-disabled never reaches it; the disabled\n// colour is driven by the explicit `disabled` variant the component passes (mirrors\n// checkboxLabelVariants / radioLabelVariants).\nexport const switchLabelVariants = cva(\n [\"text-label text-text-primary select-none\"],\n {\n variants: {\n disabled: { true: \"text-text-disabled\", false: \"\" },\n },\n defaultVariants: { disabled: false },\n },\n);\n\nexport type SwitchVariantProps = VariantProps<typeof switchTrackVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AAShC,MAAM,sBAAsB;AAAA,EACjC;AAAA,IACE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA,MAGR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,KAAK;AAAA,EAChC;AACF;AAUO,MAAM,sBAAsB;AAAA,EACjC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,MACA,UAAU,EAAE,MAAM,oBAAoB,OAAO,gBAAgB;AAAA,IAC/D;AAAA,IACA,iBAAiB,EAAE,MAAM,MAAM,UAAU,MAAM;AAAA,EACjD;AACF;AAMO,MAAM,wBAAwB,IAAI;AAAA,EACvC;AAAA,EACA;AACF,CAAC;AAMM,MAAM,sBAAsB;AAAA,EACjC,CAAC,0CAA0C;AAAA,EAC3C;AAAA,IACE,UAAU;AAAA,MACR,UAAU,EAAE,MAAM,sBAAsB,OAAO,GAAG;AAAA,IACpD;AAAA,IACA,iBAAiB,EAAE,UAAU,MAAM;AAAA,EACrC;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/switch/switch.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// The track: a native <button role=\"switch\">. Off binds control-*; the\n// aria-checked: state-variant binds the on (action-primary) track — never a\n// status-* utility (brand ≠ state). The visible 2px focus ring is never removed.\n// The target-size floor lives on the hit-area wrapper (switchHitAreaVariants), NOT\n// here: a min-height floor on the SAME node as the h-5/h-6 track would force both\n// sizes to the 44px floor (used height = max(min-height, height)), erasing the\n// size variant. The track keeps only its own h-5/h-6 visible height.\nexport const switchTrackVariants = cva(\n [\n \"relative inline-flex shrink-0 items-center rounded-full border\",\n // off (resting) track + resting border\n \"bg-control-bg border-control-border\",\n // on track (state-variant) + on hover / pressed\n \"aria-checked:bg-action-primary-bg aria-checked:border-action-primary-bg\",\n \"aria-checked:hover:bg-action-primary-bg-hover aria-checked:active:bg-action-primary-bg-active\",\n // fast, functional track-tint — no theatre\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n // visible 2px signal-blue ring at 2px offset, on every state, never removed\n \"outline-none\",\n focusRing,\n // disabled: no pointer; the checked value still reads to AT. DEC-C / spec §4:\n // reduced emphasis comes from dimming the thumb (the indicator) to the disabled\n // TOKEN — see switchThumbVariants — NOT a blanket opacity-60 dim of the track.\n \"disabled:pointer-events-none\",\n \"aria-busy:pointer-events-none\",\n ],\n {\n variants: {\n // visible track height/width only — the hit-area floor is a separate wrapper,\n // so sm renders a genuinely shorter visible track than md\n size: {\n md: \"h-6 w-11 px-0.5\",\n sm: \"h-5 w-9 px-0.5\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n// The thumb: control-fg fill, slides start→end. Position (not color) carries on/off\n// state, via the aria-checked: translate variant on the parent button.\n//\n// DEC-C / spec §4: a disabled switch dims the thumb (its indicator) to\n// --color-text-disabled — the SAME token the label dims to — NOT a blanket\n// opacity-60 dim of the track. The thumb is a child of the button, not a sibling\n// of a peer input, so the colour is driven by an explicit `disabled` cva variant\n// the component passes (mirrors switchLabelVariants); enabled keeps control-fg.\nexport const switchThumbVariants = cva(\n [\n \"pointer-events-none block rounded-full shadow-sm\",\n \"translate-x-0 aria-checked:translate-x-full\",\n \"transition-transform duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n ],\n {\n variants: {\n size: {\n md: \"h-5 w-5\",\n sm: \"h-4 w-4\",\n },\n disabled: { true: \"bg-text-disabled\", false: \"bg-control-fg\" },\n },\n defaultVariants: { size: \"md\", disabled: false },\n },\n);\n\n// The hit-area wrapper around the track. This — NOT the visible track — carries the\n// target-size floor (44px touch / 40px pointer) so the touch target always meets the\n// minimum while the track keeps its smaller h-5/h-6 visible height. inline-flex +\n// items-center vertically centres the shorter track within the taller hit area.\nexport const switchHitAreaVariants = cva([\n \"inline-flex shrink-0 items-center justify-center\",\n \"min-h-(--size-target-mobile) sm:min-h-(--size-target-desktop)\",\n]);\n\n// The label naming the setting. The <label> element is nested in a column wrapper,\n// not a sibling of the button, so peer-disabled never reaches it; the disabled\n// colour is driven by the explicit `disabled` variant the component passes (mirrors\n// checkboxLabelVariants / radioLabelVariants).\nexport const switchLabelVariants = cva(\n [\"text-label text-text-primary select-none\"],\n {\n variants: {\n disabled: { true: \"text-text-disabled\", false: \"\" },\n },\n defaultVariants: { disabled: false },\n },\n);\n\nexport type SwitchVariantProps = VariantProps<typeof switchTrackVariants>;\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AASnB,MAAM,sBAAsB;AAAA,EACjC;AAAA,IACE;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA,MAGR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,KAAK;AAAA,EAChC;AACF;AAUO,MAAM,sBAAsB;AAAA,EACjC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,MACA,UAAU,EAAE,MAAM,oBAAoB,OAAO,gBAAgB;AAAA,IAC/D;AAAA,IACA,iBAAiB,EAAE,MAAM,MAAM,UAAU,MAAM;AAAA,EACjD;AACF;AAMO,MAAM,wBAAwB,IAAI;AAAA,EACvC;AAAA,EACA;AACF,CAAC;AAMM,MAAM,sBAAsB;AAAA,EACjC,CAAC,0CAA0C;AAAA,EAC3C;AAAA,IACE,UAAU;AAAA,MACR,UAAU,EAAE,MAAM,sBAAsB,OAAO,GAAG;AAAA,IACpD;AAAA,IACA,iBAAiB,EAAE,UAAU,MAAM;AAAA,EACrC;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.tsx"],"names":[],"mappings":"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"}
@@ -25,7 +25,7 @@ const Table = React.forwardRef(function Table2({ className, density = "comfortab
25
25
  const ctx = React.useMemo(() => ({ density, rule }), [density, rule]);
26
26
  const stickyCtx = React.useMemo(() => ({ sticky: stickyHeader }), [stickyHeader]);
27
27
  return /* @__PURE__ */ jsx(TableContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(TableStickyContext.Provider, { value: stickyCtx, children: [
28
- /* @__PURE__ */ jsx("table", { ref, className: cn(tableVariants(), className), ...props, children }),
28
+ /* @__PURE__ */ jsx("div", { className: "w-full overflow-x-auto", children: /* @__PURE__ */ jsx("table", { ref, className: cn(tableVariants(), className), ...props, children }) }),
29
29
  /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: announcement })
30
30
  ] }) });
31
31
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/table/table.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n tableVariants,\n tableCaptionClass,\n tableHeaderVariants,\n tableBodyVariants,\n tableRowVariants,\n tableCellVariants,\n tableRowHeaderVariants,\n tableHeadVariants,\n tableSortButtonClass,\n tableSortCaretClass,\n tableEmptyClass,\n tableSkeletonCellClass,\n type TableCellVariantProps,\n} from \"./table.variants\";\n\n/** The row density (spec §3): `comfortable` (default) for general reading, `compact` for dense console views. */\nexport type TableDensity = \"comfortable\" | \"compact\";\n/** The rule treatment (spec §3): row hairlines (`horizontal`, default), `grid` (rows + columns), or `zebra` (alternating tint). */\nexport type TableRule = \"horizontal\" | \"grid\" | \"zebra\";\n/** A cell's reported state (spec §3/§4): a status color appears only inside a cell that reports a real state, paired with text. */\nexport type TableCellStatus = NonNullable<TableCellVariantProps[\"status\"]>;\n/** A sort direction (spec §4/§7): reflected as `aria-sort` on the header and a non-color caret. */\nexport type TableSortDirection = \"ascending\" | \"descending\" | \"none\";\n\n// The presentation axes set ONCE on the root <table> travel to every cell via context (the proven\n// Dialog/Sheet/Tabs root-context pattern), so callers set density/rule on the Table and never repeat\n// them on each cell. `sticky` is read by the header. This is why the file is `'use client'` — it\n// uses React context (a hook). No cell roving is wired: a static Table's cells are read, not\n// operated; focus belongs to the interactive controls only (spec §6) — cell-by-cell arrow movement\n// is DataGrid behavior, not Table behavior, so there is no hand-rolled roving layer here.\ntype TableContextValue = { density: TableDensity; rule: TableRule };\nconst TableContext = React.createContext<TableContextValue>({\n density: \"comfortable\",\n rule: \"horizontal\",\n});\n\nexport interface TableProps extends React.TableHTMLAttributes<HTMLTableElement> {\n /** Row density (spec §3). `comfortable` (default) or `compact`. Applies to all cells via context. */\n density?: TableDensity;\n /** Rule treatment (spec §3). `horizontal` (default), `grid`, or `zebra`. Applies to rows/cells via context. */\n rule?: TableRule;\n /**\n * Pin the header row while the body scrolls (spec §3 sticky-header). The header gets the stronger\n * border-strong divider where a heavier separation reads better. Selection and sort are unchanged.\n */\n stickyHeader?: boolean;\n /**\n * The polite live-region message (spec §7/§8, WCAG 4.1.3 Status Messages). When a sort or a filter\n * changes the visible rows, set this to the result so it is announced — for example\n * \"Sorted by status, ascending. 42 rows.\" The visual reorder is not announced on its own, so a\n * silent re-sort breaks §8 (\"Don't re-sort silently\"). The caller owns the count (mirroring\n * CommandPalette): pass the new string whenever the visible rows change. Rendered into an sr-only\n * `role=\"status\" aria-live=\"polite\"` node, so it reaches assistive tech as text, never color alone.\n */\n announcement?: string;\n}\n\n/**\n * A Table presents structured data in rows and columns so you can read, compare, and sort records —\n * a list of API keys, verification events, or registered agents (spec §1). It is a SEMANTIC\n * `<table>`: the data has a real row-and-column relationship and the native markup carries that\n * relationship into the accessibility tree (1.3.1), not just the pixels. Reach for a DataGrid when\n * the data needs virtualized rows, in-cell editing, column resizing, or roving-focus cell navigation.\n *\n * Neutrals carry the table — it is a reading surface, not an accent surface (spec §3): most of it is\n * neutral text and hairline borders. A status color appears ONLY inside a cell that reports a real\n * state, paired with text (never a header, a whole row, or a decoration); the brand violet is NEVER\n * a Table variant — the brand is not a status, so it never tints a row, header, sort, or selection\n * (brand != state). For a first-class verified result in a cell, use the VerifiedBadge molecule.\n *\n * Name the table with a `<caption>` (preferred) or `aria-labelledby`/`aria-label`. It owns no\n * keyboard model of its own — focus belongs to the controls it hosts (a sortable header, a row\n * Checkbox, a row action), which keep their native tab stops in reading order (spec §6).\n */\nexport const Table = React.forwardRef<HTMLTableElement, TableProps>(function Table(\n { className, density = \"comfortable\", rule = \"horizontal\", stickyHeader = false, announcement, children, ...props },\n ref,\n) {\n const ctx = React.useMemo<TableContextValue>(() => ({ density, rule }), [density, rule]);\n // sticky is read by the header through a second, header-only context so the <thead> can pin\n // without the cell context carrying a presentation flag it does not use.\n const stickyCtx = React.useMemo(() => ({ sticky: stickyHeader }), [stickyHeader]);\n return (\n <TableContext.Provider value={ctx}>\n <TableStickyContext.Provider value={stickyCtx}>\n <table ref={ref} className={cn(tableVariants(), className)} {...props}>\n {children}\n </table>\n {/* The polite live region (spec §7/§8, WCAG 4.1.3): the result of a sort or filter — the new\n order and row count — announced as text, since the visual reorder is not announced on its\n own (a silent re-sort is the §8 \"Don't\"). Always present (the live region must exist before\n its text changes to be announced); sr-only so it never paints, and a sibling of the <table>,\n not a child, since a <span> is not valid table content. The caller feeds `announcement`. */}\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </span>\n </TableStickyContext.Provider>\n </TableContext.Provider>\n );\n});\n\nconst TableStickyContext = React.createContext<{ sticky: boolean }>({ sticky: false });\n\nexport type TableCaptionProps = React.HTMLAttributes<HTMLTableCaptionElement>;\n\n/**\n * The table's accessible name (spec §2/§7) — a `<caption>` naming what the data is, for example\n * \"Verification events, last 30 days\". Prefer it over a detached heading; add `className=\"sr-only\"`\n * to keep the name in the accessibility tree while hiding it visually.\n */\nexport const TableCaption = React.forwardRef<HTMLTableCaptionElement, TableCaptionProps>(\n function TableCaption({ className, ...props }, ref) {\n return <caption ref={ref} className={cn(tableCaptionClass, className)} {...props} />;\n },\n);\n\nexport type TableHeaderProps = React.HTMLAttributes<HTMLTableSectionElement>;\n\n/**\n * The `<thead>` holding the column-header row (spec §2). When the table's `stickyHeader` is set, the\n * header pins to the top of the scroll container with the stronger divider (spec §3/§5). The pinning\n * is read from the Table via context, so callers set it once on the Table.\n */\nexport const TableHeader = React.forwardRef<HTMLTableSectionElement, TableHeaderProps>(\n function TableHeader({ className, ...props }, ref) {\n const { sticky } = React.useContext(TableStickyContext);\n return <thead ref={ref} className={cn(tableHeaderVariants({ sticky }), className)} {...props} />;\n },\n);\n\nexport interface TableBodyProps extends React.HTMLAttributes<HTMLTableSectionElement> {\n /**\n * The body is resolving (spec §4 Loading). Renders `skeletonRows` × `columns` Skeleton cells in the\n * table's own column layout — keeping the header and column widths stable so the table does not\n * reflow when data arrives — and marks the body `aria-busy=\"true\"`. A wait is a plain wait, not\n * theatre: the deliberate verified-check duration is never spent here.\n */\n loading?: boolean;\n /** How many skeleton rows to show while loading (spec §4 Loading). Default `3`. */\n skeletonRows?: number;\n /** How many columns the skeleton rows span, so the placeholder matches the real column layout. Default `1`. */\n columns?: number;\n}\n\n/**\n * The `<tbody>` holding the data rows (spec §2). In the `zebra` rule it alternates a neutral\n * raised-surface tint on even rows (read from the Table via context). While `loading`, it is\n * `aria-busy` and shows Skeleton rows in the column layout; the skeletons are decorative and the\n * body owns the wait (the Skeleton itself announces nothing, spec §4 Loading / §7).\n */\nexport const TableBody = React.forwardRef<HTMLTableSectionElement, TableBodyProps>(\n function TableBody({ className, loading = false, skeletonRows = 3, columns = 1, children, ...props }, ref) {\n const { rule } = React.useContext(TableContext);\n return (\n <tbody\n ref={ref}\n aria-busy={loading || undefined}\n className={cn(tableBodyVariants({ rule }), className)}\n {...props}\n >\n {loading\n ? Array.from({ length: Math.max(1, skeletonRows) }, (_, r) => (\n <TableRow key={`skeleton-${r}`}>\n {Array.from({ length: Math.max(1, columns) }, (_, c) => (\n // a placeholder data cell in the column layout; decorative, so the wait is owned by\n // the body's aria-busy, not the skeleton (spec §4 Loading / §7).\n <td key={c} data-testid=\"table-skeleton-cell\" className={tableSkeletonCellClass}>\n <Skeleton variant=\"text\" />\n </td>\n ))}\n </TableRow>\n ))\n : children}\n </tbody>\n );\n },\n);\n\nexport type TableFooterProps = React.HTMLAttributes<HTMLTableSectionElement>;\n\n/**\n * The `<tfoot>` summary row (spec §2) — a total or an aggregate. It is DATA, not pagination;\n * pagination is a separate Pagination control beside the table.\n */\nexport const TableFooter = React.forwardRef<HTMLTableSectionElement, TableFooterProps>(\n function TableFooter({ className, ...props }, ref) {\n return (\n <tfoot ref={ref} className={cn(\"border-t border-border-default\", className)} {...props} />\n );\n },\n);\n\nexport interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {\n /**\n * A selectable row is currently selected (spec §4 Selected). Sets `aria-selected=\"true\"` and the\n * restrained neutral raised-surface fill. Selection is encoded by the row Checkbox's checked state\n * AND `aria-selected`, never by the fill alone, and NEVER a brand or status tint (brand != state).\n */\n selected?: boolean;\n}\n\n/**\n * A `<tr>` (spec §2/§4). A body row gets the restrained raised-surface hover fill (an affordance, to\n * track the eye across a wide row — nothing is selected until you act) and, when `selected`, the same\n * neutral fill plus `aria-selected`. The rule treatment (hairline / grid / zebra) is read from the\n * Table via context. Used inside `<thead>`, `<tbody>`, and `<tfoot>`.\n */\nexport const TableRow = React.forwardRef<HTMLTableRowElement, TableRowProps>(\n function TableRow({ className, selected = false, ...props }, ref) {\n const { rule } = React.useContext(TableContext);\n return (\n <tr\n ref={ref}\n aria-selected={selected || undefined}\n className={cn(tableRowVariants({ rule }), className)}\n {...props}\n />\n );\n },\n);\n\nexport interface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {\n /**\n * This column can be re-sorted from its header (spec §3 sortable). Renders the header label as a\n * real `<button>` (the control it is) with a direction caret, and reflects `aria-sort` on the\n * `<th>`. Enable per column, not table-wide, so only meaningfully sortable columns advertise it.\n */\n sortable?: boolean;\n /**\n * The current sort direction for this column (spec §4 Sorted), reflected as `aria-sort` on the\n * `<th>` and as the caret glyph. Only one column is the sort column at a time — set `\"ascending\"`\n * or `\"descending\"` on it and `\"none\"` (the default) on the rest.\n */\n sortDirection?: TableSortDirection;\n /** Fired when the sortable header is activated (click / Enter / Space), so the caller re-sorts and updates `sortDirection`. */\n onSort?: () => void;\n /**\n * The column name used in the sort control's accessible name (\"Sort by {label}, {direction}\"),\n * when it differs from the visible children. Defaults to the children's text.\n */\n sortLabel?: string;\n}\n\n// The direction caret (spec §4 Sorted): decorative — aria-sort on the th + the glyph SHAPE\n// (data-direction) encode the direction, so it never rests on color alone (1.4.1). Inline SVG, no\n// icon dep; it points up for ascending, down for descending, and shows a neutral both-ways glyph\n// when the column is sortable but not the active sort column.\nfunction SortCaret({ direction }: { direction: TableSortDirection }) {\n return (\n <span\n data-testid=\"table-sort-caret\"\n data-direction={direction}\n aria-hidden=\"true\"\n className={tableSortCaretClass}\n >\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" focusable=\"false\" aria-hidden=\"true\">\n {direction === \"ascending\" ? (\n <path d=\"M4 10l4-4 4 4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n ) : direction === \"descending\" ? (\n <path d=\"M4 6l4 4 4-4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n ) : (\n // sortable but not the active column: a quiet both-directions glyph\n <path d=\"M5 6.5l3-3 3 3M5 9.5l3 3 3-3\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n )}\n </svg>\n </span>\n );\n}\n\nconst NEXT_DIRECTION_WORD: Record<TableSortDirection, string> = {\n none: \"ascending\",\n ascending: \"descending\",\n descending: \"ascending\",\n};\n\n/**\n * A `<th scope=\"col\">` column header (spec §2/§7). A plain header is a quiet, tracked label in the\n * secondary text color. A `sortable` header wraps that label in a real `<button>` carrying the\n * ghost-action accent, a visible focus ring, the target-size floor, and a direction caret, and the\n * `<th>` reflects `aria-sort` — so sort direction reaches assistive tech as data and never rests on\n * color alone (1.4.1). The sort button's accessible name names the action and the column (\"Sort by\n * status, ascending\"). Density + grid rule are read from the Table via context.\n */\nexport const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(function TableHead(\n { className, sortable = false, sortDirection = \"none\", onSort, sortLabel, children, ...props },\n ref,\n) {\n const { density, rule } = React.useContext(TableContext);\n const ariaSort = sortable ? sortDirection : undefined;\n const label = sortLabel ?? (typeof children === \"string\" ? children : undefined);\n return (\n <th\n ref={ref}\n scope=\"col\"\n // exactly one header carries aria-sort at a time (spec §4 Sorted); the rest are \"none\"/absent\n aria-sort={ariaSort}\n className={cn(tableHeadVariants({ density, rule }), className)}\n {...props}\n >\n {sortable ? (\n <button\n type=\"button\"\n onClick={onSort}\n // the control's name includes the ACTION and the COLUMN, plus the NEXT direction it will\n // sort to — so a screen-reader user knows what activating it does (spec §7). aria-sort on\n // the th already exposes the CURRENT state.\n aria-label={label ? `Sort by ${label}, ${NEXT_DIRECTION_WORD[sortDirection]}` : undefined}\n className={tableSortButtonClass}\n >\n {children}\n <SortCaret direction={sortDirection} />\n </button>\n ) : (\n children\n )}\n </th>\n );\n});\n\nexport type TableRowHeaderProps = React.ThHTMLAttributes<HTMLTableCellElement>;\n\n/**\n * A `<th scope=\"row\">` row-header cell (spec §2/§7) — the row's natural label (an identifier, a\n * name), tying the row's cells to it. It reads at the same body weight as a data cell but is promoted\n * to a header for the relationship (1.3.1). Density + grid rule are read from the Table via context.\n */\nexport const TableRowHeader = React.forwardRef<HTMLTableCellElement, TableRowHeaderProps>(\n function TableRowHeader({ className, ...props }, ref) {\n const { density, rule } = React.useContext(TableContext);\n return (\n <th\n ref={ref}\n scope=\"row\"\n className={cn(tableRowHeaderVariants({ density, rule }), className)}\n {...props}\n />\n );\n },\n);\n\nexport interface TableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {\n /**\n * The cell reports a real STATE (spec §3/§4): `verified`, `signal`, `caution`, or `critical`. The\n * status color lives in the CELL only (never the row or header), paired with text — so a grayscale\n * reader still reads the state from the words. NEVER a brand token. For a first-class verified\n * result use the VerifiedBadge molecule inside a plain cell, not a hand-tinted cell.\n */\n status?: TableCellStatus;\n /** A numeric cell (spec §4/§5): tabular figures so digits align down the column, end-aligned, in the PRIMARY data text role. */\n numeric?: boolean;\n /** De-emphasized AUXILIARY cell text (spec §5) — a timestamp, a unit. Takes the muted role; independent of `numeric`. */\n auxiliary?: boolean;\n}\n\n/**\n * A `<td>` data cell (spec §2 cell). Holds text, a number, a Badge, an Avatar, or a small inline\n * control. A plain cell is neutral primary text. A `numeric` cell uses tabular figures and stays in\n * the PRIMARY data text role (the muted role is reserved for `auxiliary` text — a timestamp, a unit —\n * per spec §5); a cell that reports a real state takes a `status` treatment (the status fg paired with\n * text). It is NOT a focus stop — focus belongs to any interactive control inside it (spec §6).\n * Density + grid rule are read from the Table via context.\n */\nexport const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(function TableCell(\n { className, status = \"none\", numeric = false, auxiliary = false, ...props },\n ref,\n) {\n const { density, rule } = React.useContext(TableContext);\n return (\n <td\n ref={ref}\n className={cn(tableCellVariants({ density, rule, numeric, auxiliary, status }), className)}\n {...props}\n />\n );\n});\n\nexport interface TableEmptyProps extends React.TdHTMLAttributes<HTMLTableCellElement> {\n /** How many columns the empty line spans, so it fills the table's own width (spec §2/§4 Empty). */\n colSpan?: number;\n}\n\n/**\n * The empty-state row (spec §2/§4 Empty): a single full-width cell stating there is nothing yet and\n * what to do next, in plain words ending in a period. An empty table is NOT an error and never reads\n * as one — no status color. Render it inside the `<tbody>` when there are zero rows after loading.\n */\nexport const TableEmpty = React.forwardRef<HTMLTableCellElement, TableEmptyProps>(\n function TableEmpty({ className, colSpan = 1, children, ...props }, ref) {\n return (\n <tr>\n <td ref={ref} colSpan={colSpan} className={cn(tableEmptyClass, className)} {...props}>\n {children}\n </td>\n </tr>\n );\n },\n);\n"],"mappings":";AA0FM,SACE,KADF;AAxFN,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAkBP,MAAM,eAAe,MAAM,cAAiC;AAAA,EAC1D,SAAS;AAAA,EACT,MAAM;AACR,CAAC;AAwCM,MAAM,QAAQ,MAAM,WAAyC,SAASA,OAC3E,EAAE,WAAW,UAAU,eAAe,OAAO,cAAc,eAAe,OAAO,cAAc,UAAU,GAAG,MAAM,GAClH,KACA;AACA,QAAM,MAAM,MAAM,QAA2B,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC,SAAS,IAAI,CAAC;AAGvF,QAAM,YAAY,MAAM,QAAQ,OAAO,EAAE,QAAQ,aAAa,IAAI,CAAC,YAAY,CAAC;AAChF,SACE,oBAAC,aAAa,UAAb,EAAsB,OAAO,KAC5B,+BAAC,mBAAmB,UAAnB,EAA4B,OAAO,WAClC;AAAA,wBAAC,WAAM,KAAU,WAAW,GAAG,cAAc,GAAG,SAAS,GAAI,GAAG,OAC7D,UACH;AAAA,IAMA,oBAAC,UAAK,MAAK,UAAS,aAAU,UAAS,WAAU,WAC9C,wBACH;AAAA,KACF,GACF;AAEJ,CAAC;AAED,MAAM,qBAAqB,MAAM,cAAmC,EAAE,QAAQ,MAAM,CAAC;AAS9E,MAAM,eAAe,MAAM;AAAA,EAChC,SAASC,cAAa,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AAClD,WAAO,oBAAC,aAAQ,KAAU,WAAW,GAAG,mBAAmB,SAAS,GAAI,GAAG,OAAO;AAAA,EACpF;AACF;AASO,MAAM,cAAc,MAAM;AAAA,EAC/B,SAASC,aAAY,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACjD,UAAM,EAAE,OAAO,IAAI,MAAM,WAAW,kBAAkB;AACtD,WAAO,oBAAC,WAAM,KAAU,WAAW,GAAG,oBAAoB,EAAE,OAAO,CAAC,GAAG,SAAS,GAAI,GAAG,OAAO;AAAA,EAChG;AACF;AAsBO,MAAM,YAAY,MAAM;AAAA,EAC7B,SAASC,WAAU,EAAE,WAAW,UAAU,OAAO,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK;AACzG,UAAM,EAAE,KAAK,IAAI,MAAM,WAAW,YAAY;AAC9C,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAW,WAAW;AAAA,QACtB,WAAW,GAAG,kBAAkB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,QACnD,GAAG;AAAA,QAEH,oBACG,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,YAAY,EAAE,GAAG,CAAC,GAAG,MACpD,oBAAC,YACE,gBAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,EAAE,GAAG,CAACC,IAAG;AAAA;AAAA;AAAA,UAGhD,oBAAC,QAAW,eAAY,uBAAsB,WAAW,wBACvD,8BAAC,YAAS,SAAQ,QAAO,KADlB,CAET;AAAA,SACD,KAPY,YAAY,CAAC,EAQ5B,CACD,IACD;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AAQO,MAAM,cAAc,MAAM;AAAA,EAC/B,SAASC,aAAY,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACjD,WACE,oBAAC,WAAM,KAAU,WAAW,GAAG,kCAAkC,SAAS,GAAI,GAAG,OAAO;AAAA,EAE5F;AACF;AAiBO,MAAM,WAAW,MAAM;AAAA,EAC5B,SAASC,UAAS,EAAE,WAAW,WAAW,OAAO,GAAG,MAAM,GAAG,KAAK;AAChE,UAAM,EAAE,KAAK,IAAI,MAAM,WAAW,YAAY;AAC9C,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,iBAAe,YAAY;AAAA,QAC3B,WAAW,GAAG,iBAAiB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,QAClD,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AA4BA,SAAS,UAAU,EAAE,UAAU,GAAsC;AACnE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAY;AAAA,MACZ,kBAAgB;AAAA,MAChB,eAAY;AAAA,MACZ,WAAW;AAAA,MAEX,8BAAC,SAAI,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,WAAU,SAAQ,eAAY,QACxG,wBAAc,cACb,oBAAC,UAAK,GAAE,iBAAgB,eAAc,SAAQ,gBAAe,SAAQ,IACnE,cAAc,eAChB,oBAAC,UAAK,GAAE,gBAAe,eAAc,SAAQ,gBAAe,SAAQ;AAAA;AAAA,QAGpE,oBAAC,UAAK,GAAE,gCAA+B,eAAc,SAAQ,gBAAe,SAAQ;AAAA,SAExF;AAAA;AAAA,EACF;AAEJ;AAEA,MAAM,sBAA0D;AAAA,EAC9D,MAAM;AAAA,EACN,WAAW;AAAA,EACX,YAAY;AACd;AAUO,MAAM,YAAY,MAAM,WAAiD,SAASC,WACvF,EAAE,WAAW,WAAW,OAAO,gBAAgB,QAAQ,QAAQ,WAAW,UAAU,GAAG,MAAM,GAC7F,KACA;AACA,QAAM,EAAE,SAAS,KAAK,IAAI,MAAM,WAAW,YAAY;AACvD,QAAM,WAAW,WAAW,gBAAgB;AAC5C,QAAM,QAAQ,cAAc,OAAO,aAAa,WAAW,WAAW;AACtE,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAM;AAAA,MAEN,aAAW;AAAA,MACX,WAAW,GAAG,kBAAkB,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AAAA,MAC5D,GAAG;AAAA,MAEH,qBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UAIT,cAAY,QAAQ,WAAW,KAAK,KAAK,oBAAoB,aAAa,CAAC,KAAK;AAAA,UAChF,WAAW;AAAA,UAEV;AAAA;AAAA,YACD,oBAAC,aAAU,WAAW,eAAe;AAAA;AAAA;AAAA,MACvC,IAEA;AAAA;AAAA,EAEJ;AAEJ,CAAC;AASM,MAAM,iBAAiB,MAAM;AAAA,EAClC,SAASC,gBAAe,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACpD,UAAM,EAAE,SAAS,KAAK,IAAI,MAAM,WAAW,YAAY;AACvD,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAM;AAAA,QACN,WAAW,GAAG,uBAAuB,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AAAA,QACjE,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AAwBO,MAAM,YAAY,MAAM,WAAiD,SAASC,WACvF,EAAE,WAAW,SAAS,QAAQ,UAAU,OAAO,YAAY,OAAO,GAAG,MAAM,GAC3E,KACA;AACA,QAAM,EAAE,SAAS,KAAK,IAAI,MAAM,WAAW,YAAY;AACvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,kBAAkB,EAAE,SAAS,MAAM,SAAS,WAAW,OAAO,CAAC,GAAG,SAAS;AAAA,MACxF,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAYM,MAAM,aAAa,MAAM;AAAA,EAC9B,SAASC,YAAW,EAAE,WAAW,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK;AACvE,WACE,oBAAC,QACC,8BAAC,QAAG,KAAU,SAAkB,WAAW,GAAG,iBAAiB,SAAS,GAAI,GAAG,OAC5E,UACH,GACF;AAAA,EAEJ;AACF;","names":["Table","TableCaption","TableHeader","TableBody","_","TableFooter","TableRow","TableHead","TableRowHeader","TableCell","TableEmpty"]}
1
+ {"version":3,"sources":["../../../src/components/table/table.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n tableVariants,\n tableCaptionClass,\n tableHeaderVariants,\n tableBodyVariants,\n tableRowVariants,\n tableCellVariants,\n tableRowHeaderVariants,\n tableHeadVariants,\n tableSortButtonClass,\n tableSortCaretClass,\n tableEmptyClass,\n tableSkeletonCellClass,\n type TableCellVariantProps,\n} from \"./table.variants\";\n\n/** The row density (spec §3): `comfortable` (default) for general reading, `compact` for dense console views. */\nexport type TableDensity = \"comfortable\" | \"compact\";\n/** The rule treatment (spec §3): row hairlines (`horizontal`, default), `grid` (rows + columns), or `zebra` (alternating tint). */\nexport type TableRule = \"horizontal\" | \"grid\" | \"zebra\";\n/** A cell's reported state (spec §3/§4): a status color appears only inside a cell that reports a real state, paired with text. */\nexport type TableCellStatus = NonNullable<TableCellVariantProps[\"status\"]>;\n/** A sort direction (spec §4/§7): reflected as `aria-sort` on the header and a non-color caret. */\nexport type TableSortDirection = \"ascending\" | \"descending\" | \"none\";\n\n// The presentation axes set ONCE on the root <table> travel to every cell via context (the proven\n// Dialog/Sheet/Tabs root-context pattern), so callers set density/rule on the Table and never repeat\n// them on each cell. `sticky` is read by the header. This is why the file is `'use client'` — it\n// uses React context (a hook). No cell roving is wired: a static Table's cells are read, not\n// operated; focus belongs to the interactive controls only (spec §6) — cell-by-cell arrow movement\n// is DataGrid behavior, not Table behavior, so there is no hand-rolled roving layer here.\ntype TableContextValue = { density: TableDensity; rule: TableRule };\nconst TableContext = React.createContext<TableContextValue>({\n density: \"comfortable\",\n rule: \"horizontal\",\n});\n\nexport interface TableProps extends React.TableHTMLAttributes<HTMLTableElement> {\n /** Row density (spec §3). `comfortable` (default) or `compact`. Applies to all cells via context. */\n density?: TableDensity;\n /** Rule treatment (spec §3). `horizontal` (default), `grid`, or `zebra`. Applies to rows/cells via context. */\n rule?: TableRule;\n /**\n * Pin the header row while the body scrolls (spec §3 sticky-header). The header gets the stronger\n * border-strong divider where a heavier separation reads better. Selection and sort are unchanged.\n */\n stickyHeader?: boolean;\n /**\n * The polite live-region message (spec §7/§8, WCAG 4.1.3 Status Messages). When a sort or a filter\n * changes the visible rows, set this to the result so it is announced — for example\n * \"Sorted by status, ascending. 42 rows.\" The visual reorder is not announced on its own, so a\n * silent re-sort breaks §8 (\"Don't re-sort silently\"). The caller owns the count (mirroring\n * CommandPalette): pass the new string whenever the visible rows change. Rendered into an sr-only\n * `role=\"status\" aria-live=\"polite\"` node, so it reaches assistive tech as text, never color alone.\n */\n announcement?: string;\n}\n\n/**\n * A Table presents structured data in rows and columns so you can read, compare, and sort records —\n * a list of API keys, verification events, or registered agents (spec §1). It is a SEMANTIC\n * `<table>`: the data has a real row-and-column relationship and the native markup carries that\n * relationship into the accessibility tree (1.3.1), not just the pixels. Reach for a DataGrid when\n * the data needs virtualized rows, in-cell editing, column resizing, or roving-focus cell navigation.\n *\n * Neutrals carry the table — it is a reading surface, not an accent surface (spec §3): most of it is\n * neutral text and hairline borders. A status color appears ONLY inside a cell that reports a real\n * state, paired with text (never a header, a whole row, or a decoration); the brand violet is NEVER\n * a Table variant — the brand is not a status, so it never tints a row, header, sort, or selection\n * (brand != state). For a first-class verified result in a cell, use the VerifiedBadge molecule.\n *\n * Name the table with a `<caption>` (preferred) or `aria-labelledby`/`aria-label`. It owns no\n * keyboard model of its own — focus belongs to the controls it hosts (a sortable header, a row\n * Checkbox, a row action), which keep their native tab stops in reading order (spec §6).\n */\nexport const Table = React.forwardRef<HTMLTableElement, TableProps>(function Table(\n { className, density = \"comfortable\", rule = \"horizontal\", stickyHeader = false, announcement, children, ...props },\n ref,\n) {\n const ctx = React.useMemo<TableContextValue>(() => ({ density, rule }), [density, rule]);\n // sticky is read by the header through a second, header-only context so the <thead> can pin\n // without the cell context carrying a presentation flag it does not use.\n const stickyCtx = React.useMemo(() => ({ sticky: stickyHeader }), [stickyHeader]);\n return (\n <TableContext.Provider value={ctx}>\n <TableStickyContext.Provider value={stickyCtx}>\n {/* overflow-x-auto: the table may be wider than the viewport on 320–414px screens.\n The scroll container keeps the page body from overflowing while letting a wide\n table scroll horizontally within its own bounds — the same pattern DataGrid uses\n for its `overflow-auto` scroll container (spec §5). The sticky header (if any)\n pins within this scroll container, which is correct behavior. */}\n <div className=\"w-full overflow-x-auto\">\n <table ref={ref} className={cn(tableVariants(), className)} {...props}>\n {children}\n </table>\n </div>\n {/* The polite live region (spec §7/§8, WCAG 4.1.3): the result of a sort or filter — the new\n order and row count — announced as text, since the visual reorder is not announced on its\n own (a silent re-sort is the §8 \"Don't\"). Always present (the live region must exist before\n its text changes to be announced); sr-only so it never paints, and a sibling of the <table>,\n not a child, since a <span> is not valid table content. The caller feeds `announcement`. */}\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </span>\n </TableStickyContext.Provider>\n </TableContext.Provider>\n );\n});\n\nconst TableStickyContext = React.createContext<{ sticky: boolean }>({ sticky: false });\n\nexport type TableCaptionProps = React.HTMLAttributes<HTMLTableCaptionElement>;\n\n/**\n * The table's accessible name (spec §2/§7) — a `<caption>` naming what the data is, for example\n * \"Verification events, last 30 days\". Prefer it over a detached heading; add `className=\"sr-only\"`\n * to keep the name in the accessibility tree while hiding it visually.\n */\nexport const TableCaption = React.forwardRef<HTMLTableCaptionElement, TableCaptionProps>(\n function TableCaption({ className, ...props }, ref) {\n return <caption ref={ref} className={cn(tableCaptionClass, className)} {...props} />;\n },\n);\n\nexport type TableHeaderProps = React.HTMLAttributes<HTMLTableSectionElement>;\n\n/**\n * The `<thead>` holding the column-header row (spec §2). When the table's `stickyHeader` is set, the\n * header pins to the top of the scroll container with the stronger divider (spec §3/§5). The pinning\n * is read from the Table via context, so callers set it once on the Table.\n */\nexport const TableHeader = React.forwardRef<HTMLTableSectionElement, TableHeaderProps>(\n function TableHeader({ className, ...props }, ref) {\n const { sticky } = React.useContext(TableStickyContext);\n return <thead ref={ref} className={cn(tableHeaderVariants({ sticky }), className)} {...props} />;\n },\n);\n\nexport interface TableBodyProps extends React.HTMLAttributes<HTMLTableSectionElement> {\n /**\n * The body is resolving (spec §4 Loading). Renders `skeletonRows` × `columns` Skeleton cells in the\n * table's own column layout — keeping the header and column widths stable so the table does not\n * reflow when data arrives — and marks the body `aria-busy=\"true\"`. A wait is a plain wait, not\n * theatre: the deliberate verified-check duration is never spent here.\n */\n loading?: boolean;\n /** How many skeleton rows to show while loading (spec §4 Loading). Default `3`. */\n skeletonRows?: number;\n /** How many columns the skeleton rows span, so the placeholder matches the real column layout. Default `1`. */\n columns?: number;\n}\n\n/**\n * The `<tbody>` holding the data rows (spec §2). In the `zebra` rule it alternates a neutral\n * raised-surface tint on even rows (read from the Table via context). While `loading`, it is\n * `aria-busy` and shows Skeleton rows in the column layout; the skeletons are decorative and the\n * body owns the wait (the Skeleton itself announces nothing, spec §4 Loading / §7).\n */\nexport const TableBody = React.forwardRef<HTMLTableSectionElement, TableBodyProps>(\n function TableBody({ className, loading = false, skeletonRows = 3, columns = 1, children, ...props }, ref) {\n const { rule } = React.useContext(TableContext);\n return (\n <tbody\n ref={ref}\n aria-busy={loading || undefined}\n className={cn(tableBodyVariants({ rule }), className)}\n {...props}\n >\n {loading\n ? Array.from({ length: Math.max(1, skeletonRows) }, (_, r) => (\n <TableRow key={`skeleton-${r}`}>\n {Array.from({ length: Math.max(1, columns) }, (_, c) => (\n // a placeholder data cell in the column layout; decorative, so the wait is owned by\n // the body's aria-busy, not the skeleton (spec §4 Loading / §7).\n <td key={c} data-testid=\"table-skeleton-cell\" className={tableSkeletonCellClass}>\n <Skeleton variant=\"text\" />\n </td>\n ))}\n </TableRow>\n ))\n : children}\n </tbody>\n );\n },\n);\n\nexport type TableFooterProps = React.HTMLAttributes<HTMLTableSectionElement>;\n\n/**\n * The `<tfoot>` summary row (spec §2) — a total or an aggregate. It is DATA, not pagination;\n * pagination is a separate Pagination control beside the table.\n */\nexport const TableFooter = React.forwardRef<HTMLTableSectionElement, TableFooterProps>(\n function TableFooter({ className, ...props }, ref) {\n return (\n <tfoot ref={ref} className={cn(\"border-t border-border-default\", className)} {...props} />\n );\n },\n);\n\nexport interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {\n /**\n * A selectable row is currently selected (spec §4 Selected). Sets `aria-selected=\"true\"` and the\n * restrained neutral raised-surface fill. Selection is encoded by the row Checkbox's checked state\n * AND `aria-selected`, never by the fill alone, and NEVER a brand or status tint (brand != state).\n */\n selected?: boolean;\n}\n\n/**\n * A `<tr>` (spec §2/§4). A body row gets the restrained raised-surface hover fill (an affordance, to\n * track the eye across a wide row — nothing is selected until you act) and, when `selected`, the same\n * neutral fill plus `aria-selected`. The rule treatment (hairline / grid / zebra) is read from the\n * Table via context. Used inside `<thead>`, `<tbody>`, and `<tfoot>`.\n */\nexport const TableRow = React.forwardRef<HTMLTableRowElement, TableRowProps>(\n function TableRow({ className, selected = false, ...props }, ref) {\n const { rule } = React.useContext(TableContext);\n return (\n <tr\n ref={ref}\n aria-selected={selected || undefined}\n className={cn(tableRowVariants({ rule }), className)}\n {...props}\n />\n );\n },\n);\n\nexport interface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {\n /**\n * This column can be re-sorted from its header (spec §3 sortable). Renders the header label as a\n * real `<button>` (the control it is) with a direction caret, and reflects `aria-sort` on the\n * `<th>`. Enable per column, not table-wide, so only meaningfully sortable columns advertise it.\n */\n sortable?: boolean;\n /**\n * The current sort direction for this column (spec §4 Sorted), reflected as `aria-sort` on the\n * `<th>` and as the caret glyph. Only one column is the sort column at a time — set `\"ascending\"`\n * or `\"descending\"` on it and `\"none\"` (the default) on the rest.\n */\n sortDirection?: TableSortDirection;\n /** Fired when the sortable header is activated (click / Enter / Space), so the caller re-sorts and updates `sortDirection`. */\n onSort?: () => void;\n /**\n * The column name used in the sort control's accessible name (\"Sort by {label}, {direction}\"),\n * when it differs from the visible children. Defaults to the children's text.\n */\n sortLabel?: string;\n}\n\n// The direction caret (spec §4 Sorted): decorative — aria-sort on the th + the glyph SHAPE\n// (data-direction) encode the direction, so it never rests on color alone (1.4.1). Inline SVG, no\n// icon dep; it points up for ascending, down for descending, and shows a neutral both-ways glyph\n// when the column is sortable but not the active sort column.\nfunction SortCaret({ direction }: { direction: TableSortDirection }) {\n return (\n <span\n data-testid=\"table-sort-caret\"\n data-direction={direction}\n aria-hidden=\"true\"\n className={tableSortCaretClass}\n >\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" focusable=\"false\" aria-hidden=\"true\">\n {direction === \"ascending\" ? (\n <path d=\"M4 10l4-4 4 4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n ) : direction === \"descending\" ? (\n <path d=\"M4 6l4 4 4-4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n ) : (\n // sortable but not the active column: a quiet both-directions glyph\n <path d=\"M5 6.5l3-3 3 3M5 9.5l3 3 3-3\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n )}\n </svg>\n </span>\n );\n}\n\nconst NEXT_DIRECTION_WORD: Record<TableSortDirection, string> = {\n none: \"ascending\",\n ascending: \"descending\",\n descending: \"ascending\",\n};\n\n/**\n * A `<th scope=\"col\">` column header (spec §2/§7). A plain header is a quiet, tracked label in the\n * secondary text color. A `sortable` header wraps that label in a real `<button>` carrying the\n * ghost-action accent, a visible focus ring, the target-size floor, and a direction caret, and the\n * `<th>` reflects `aria-sort` — so sort direction reaches assistive tech as data and never rests on\n * color alone (1.4.1). The sort button's accessible name names the action and the column (\"Sort by\n * status, ascending\"). Density + grid rule are read from the Table via context.\n */\nexport const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(function TableHead(\n { className, sortable = false, sortDirection = \"none\", onSort, sortLabel, children, ...props },\n ref,\n) {\n const { density, rule } = React.useContext(TableContext);\n const ariaSort = sortable ? sortDirection : undefined;\n const label = sortLabel ?? (typeof children === \"string\" ? children : undefined);\n return (\n <th\n ref={ref}\n scope=\"col\"\n // exactly one header carries aria-sort at a time (spec §4 Sorted); the rest are \"none\"/absent\n aria-sort={ariaSort}\n className={cn(tableHeadVariants({ density, rule }), className)}\n {...props}\n >\n {sortable ? (\n <button\n type=\"button\"\n onClick={onSort}\n // the control's name includes the ACTION and the COLUMN, plus the NEXT direction it will\n // sort to — so a screen-reader user knows what activating it does (spec §7). aria-sort on\n // the th already exposes the CURRENT state.\n aria-label={label ? `Sort by ${label}, ${NEXT_DIRECTION_WORD[sortDirection]}` : undefined}\n className={tableSortButtonClass}\n >\n {children}\n <SortCaret direction={sortDirection} />\n </button>\n ) : (\n children\n )}\n </th>\n );\n});\n\nexport type TableRowHeaderProps = React.ThHTMLAttributes<HTMLTableCellElement>;\n\n/**\n * A `<th scope=\"row\">` row-header cell (spec §2/§7) — the row's natural label (an identifier, a\n * name), tying the row's cells to it. It reads at the same body weight as a data cell but is promoted\n * to a header for the relationship (1.3.1). Density + grid rule are read from the Table via context.\n */\nexport const TableRowHeader = React.forwardRef<HTMLTableCellElement, TableRowHeaderProps>(\n function TableRowHeader({ className, ...props }, ref) {\n const { density, rule } = React.useContext(TableContext);\n return (\n <th\n ref={ref}\n scope=\"row\"\n className={cn(tableRowHeaderVariants({ density, rule }), className)}\n {...props}\n />\n );\n },\n);\n\nexport interface TableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {\n /**\n * The cell reports a real STATE (spec §3/§4): `verified`, `signal`, `caution`, or `critical`. The\n * status color lives in the CELL only (never the row or header), paired with text — so a grayscale\n * reader still reads the state from the words. NEVER a brand token. For a first-class verified\n * result use the VerifiedBadge molecule inside a plain cell, not a hand-tinted cell.\n */\n status?: TableCellStatus;\n /** A numeric cell (spec §4/§5): tabular figures so digits align down the column, end-aligned, in the PRIMARY data text role. */\n numeric?: boolean;\n /** De-emphasized AUXILIARY cell text (spec §5) — a timestamp, a unit. Takes the muted role; independent of `numeric`. */\n auxiliary?: boolean;\n}\n\n/**\n * A `<td>` data cell (spec §2 cell). Holds text, a number, a Badge, an Avatar, or a small inline\n * control. A plain cell is neutral primary text. A `numeric` cell uses tabular figures and stays in\n * the PRIMARY data text role (the muted role is reserved for `auxiliary` text — a timestamp, a unit —\n * per spec §5); a cell that reports a real state takes a `status` treatment (the status fg paired with\n * text). It is NOT a focus stop — focus belongs to any interactive control inside it (spec §6).\n * Density + grid rule are read from the Table via context.\n */\nexport const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(function TableCell(\n { className, status = \"none\", numeric = false, auxiliary = false, ...props },\n ref,\n) {\n const { density, rule } = React.useContext(TableContext);\n return (\n <td\n ref={ref}\n className={cn(tableCellVariants({ density, rule, numeric, auxiliary, status }), className)}\n {...props}\n />\n );\n});\n\nexport interface TableEmptyProps extends React.TdHTMLAttributes<HTMLTableCellElement> {\n /** How many columns the empty line spans, so it fills the table's own width (spec §2/§4 Empty). */\n colSpan?: number;\n}\n\n/**\n * The empty-state row (spec §2/§4 Empty): a single full-width cell stating there is nothing yet and\n * what to do next, in plain words ending in a period. An empty table is NOT an error and never reads\n * as one — no status color. Render it inside the `<tbody>` when there are zero rows after loading.\n */\nexport const TableEmpty = React.forwardRef<HTMLTableCellElement, TableEmptyProps>(\n function TableEmpty({ className, colSpan = 1, children, ...props }, ref) {\n return (\n <tr>\n <td ref={ref} colSpan={colSpan} className={cn(tableEmptyClass, className)} {...props}>\n {children}\n </td>\n </tr>\n );\n },\n);\n"],"mappings":";AA0FM,SAOI,KAPJ;AAxFN,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAkBP,MAAM,eAAe,MAAM,cAAiC;AAAA,EAC1D,SAAS;AAAA,EACT,MAAM;AACR,CAAC;AAwCM,MAAM,QAAQ,MAAM,WAAyC,SAASA,OAC3E,EAAE,WAAW,UAAU,eAAe,OAAO,cAAc,eAAe,OAAO,cAAc,UAAU,GAAG,MAAM,GAClH,KACA;AACA,QAAM,MAAM,MAAM,QAA2B,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC,SAAS,IAAI,CAAC;AAGvF,QAAM,YAAY,MAAM,QAAQ,OAAO,EAAE,QAAQ,aAAa,IAAI,CAAC,YAAY,CAAC;AAChF,SACE,oBAAC,aAAa,UAAb,EAAsB,OAAO,KAC5B,+BAAC,mBAAmB,UAAnB,EAA4B,OAAO,WAMlC;AAAA,wBAAC,SAAI,WAAU,0BACb,8BAAC,WAAM,KAAU,WAAW,GAAG,cAAc,GAAG,SAAS,GAAI,GAAG,OAC7D,UACH,GACF;AAAA,IAMA,oBAAC,UAAK,MAAK,UAAS,aAAU,UAAS,WAAU,WAC9C,wBACH;AAAA,KACF,GACF;AAEJ,CAAC;AAED,MAAM,qBAAqB,MAAM,cAAmC,EAAE,QAAQ,MAAM,CAAC;AAS9E,MAAM,eAAe,MAAM;AAAA,EAChC,SAASC,cAAa,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AAClD,WAAO,oBAAC,aAAQ,KAAU,WAAW,GAAG,mBAAmB,SAAS,GAAI,GAAG,OAAO;AAAA,EACpF;AACF;AASO,MAAM,cAAc,MAAM;AAAA,EAC/B,SAASC,aAAY,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACjD,UAAM,EAAE,OAAO,IAAI,MAAM,WAAW,kBAAkB;AACtD,WAAO,oBAAC,WAAM,KAAU,WAAW,GAAG,oBAAoB,EAAE,OAAO,CAAC,GAAG,SAAS,GAAI,GAAG,OAAO;AAAA,EAChG;AACF;AAsBO,MAAM,YAAY,MAAM;AAAA,EAC7B,SAASC,WAAU,EAAE,WAAW,UAAU,OAAO,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK;AACzG,UAAM,EAAE,KAAK,IAAI,MAAM,WAAW,YAAY;AAC9C,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAW,WAAW;AAAA,QACtB,WAAW,GAAG,kBAAkB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,QACnD,GAAG;AAAA,QAEH,oBACG,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,YAAY,EAAE,GAAG,CAAC,GAAG,MACpD,oBAAC,YACE,gBAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,EAAE,GAAG,CAACC,IAAG;AAAA;AAAA;AAAA,UAGhD,oBAAC,QAAW,eAAY,uBAAsB,WAAW,wBACvD,8BAAC,YAAS,SAAQ,QAAO,KADlB,CAET;AAAA,SACD,KAPY,YAAY,CAAC,EAQ5B,CACD,IACD;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AAQO,MAAM,cAAc,MAAM;AAAA,EAC/B,SAASC,aAAY,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACjD,WACE,oBAAC,WAAM,KAAU,WAAW,GAAG,kCAAkC,SAAS,GAAI,GAAG,OAAO;AAAA,EAE5F;AACF;AAiBO,MAAM,WAAW,MAAM;AAAA,EAC5B,SAASC,UAAS,EAAE,WAAW,WAAW,OAAO,GAAG,MAAM,GAAG,KAAK;AAChE,UAAM,EAAE,KAAK,IAAI,MAAM,WAAW,YAAY;AAC9C,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,iBAAe,YAAY;AAAA,QAC3B,WAAW,GAAG,iBAAiB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,QAClD,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AA4BA,SAAS,UAAU,EAAE,UAAU,GAAsC;AACnE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAY;AAAA,MACZ,kBAAgB;AAAA,MAChB,eAAY;AAAA,MACZ,WAAW;AAAA,MAEX,8BAAC,SAAI,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,WAAU,SAAQ,eAAY,QACxG,wBAAc,cACb,oBAAC,UAAK,GAAE,iBAAgB,eAAc,SAAQ,gBAAe,SAAQ,IACnE,cAAc,eAChB,oBAAC,UAAK,GAAE,gBAAe,eAAc,SAAQ,gBAAe,SAAQ;AAAA;AAAA,QAGpE,oBAAC,UAAK,GAAE,gCAA+B,eAAc,SAAQ,gBAAe,SAAQ;AAAA,SAExF;AAAA;AAAA,EACF;AAEJ;AAEA,MAAM,sBAA0D;AAAA,EAC9D,MAAM;AAAA,EACN,WAAW;AAAA,EACX,YAAY;AACd;AAUO,MAAM,YAAY,MAAM,WAAiD,SAASC,WACvF,EAAE,WAAW,WAAW,OAAO,gBAAgB,QAAQ,QAAQ,WAAW,UAAU,GAAG,MAAM,GAC7F,KACA;AACA,QAAM,EAAE,SAAS,KAAK,IAAI,MAAM,WAAW,YAAY;AACvD,QAAM,WAAW,WAAW,gBAAgB;AAC5C,QAAM,QAAQ,cAAc,OAAO,aAAa,WAAW,WAAW;AACtE,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAM;AAAA,MAEN,aAAW;AAAA,MACX,WAAW,GAAG,kBAAkB,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AAAA,MAC5D,GAAG;AAAA,MAEH,qBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UAIT,cAAY,QAAQ,WAAW,KAAK,KAAK,oBAAoB,aAAa,CAAC,KAAK;AAAA,UAChF,WAAW;AAAA,UAEV;AAAA;AAAA,YACD,oBAAC,aAAU,WAAW,eAAe;AAAA;AAAA;AAAA,MACvC,IAEA;AAAA;AAAA,EAEJ;AAEJ,CAAC;AASM,MAAM,iBAAiB,MAAM;AAAA,EAClC,SAASC,gBAAe,EAAE,WAAW,GAAG,MAAM,GAAG,KAAK;AACpD,UAAM,EAAE,SAAS,KAAK,IAAI,MAAM,WAAW,YAAY;AACvD,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAM;AAAA,QACN,WAAW,GAAG,uBAAuB,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AAAA,QACjE,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AAwBO,MAAM,YAAY,MAAM,WAAiD,SAASC,WACvF,EAAE,WAAW,SAAS,QAAQ,UAAU,OAAO,YAAY,OAAO,GAAG,MAAM,GAC3E,KACA;AACA,QAAM,EAAE,SAAS,KAAK,IAAI,MAAM,WAAW,YAAY;AACvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,kBAAkB,EAAE,SAAS,MAAM,SAAS,WAAW,OAAO,CAAC,GAAG,SAAS;AAAA,MACxF,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAYM,MAAM,aAAa,MAAM;AAAA,EAC9B,SAASC,YAAW,EAAE,WAAW,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK;AACvE,WACE,oBAAC,QACC,8BAAC,QAAG,KAAU,SAAkB,WAAW,GAAG,iBAAiB,SAAS,GAAI,GAAG,OAC5E,UACH,GACF;AAAA,EAEJ;AACF;","names":["Table","TableCaption","TableHeader","TableBody","_","TableFooter","TableRow","TableHead","TableRowHeader","TableCell","TableEmpty"]}
@@ -1 +1 @@
1
- {"version":3,"file":"table.variants.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAclE,eAAO,MAAM,aAAa,oFAGxB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAInE,eAAO,MAAM,iBAAiB,6DAA6D,CAAC;AAM5F,eAAO,MAAM,mBAAmB;;8EAQ9B,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAQ/E,eAAO,MAAM,iBAAiB;;8EAW5B,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAU3E,eAAO,MAAM,gBAAgB;;8EAiB5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AA4BzE,eAAO,MAAM,iBAAiB;;;;;;8EAiC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAK3E,eAAO,MAAM,sBAAsB;;;8EAMlC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,YAAY,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAKrF,eAAO,MAAM,iBAAiB;;;8EAM7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAU3E,eAAO,MAAM,oBAAoB,QAOgE,CAAC;AAKlG,eAAO,MAAM,mBAAmB,2FAC0D,CAAC;AAI3F,eAAO,MAAM,eAAe,4EAC+C,CAAC;AAM5E,eAAO,MAAM,sBAAsB,kCAAkC,CAAC"}
1
+ {"version":3,"file":"table.variants.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAelE,eAAO,MAAM,aAAa,oFAGxB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAInE,eAAO,MAAM,iBAAiB,6DAA6D,CAAC;AAM5F,eAAO,MAAM,mBAAmB;;8EAQ9B,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAQ/E,eAAO,MAAM,iBAAiB;;8EAW5B,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAU3E,eAAO,MAAM,gBAAgB;;8EAiB5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AA4BzE,eAAO,MAAM,iBAAiB;;;;;;8EAkC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAK3E,eAAO,MAAM,sBAAsB;;;8EAMlC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,YAAY,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAKrF,eAAO,MAAM,iBAAiB;;;8EAM7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAU3E,eAAO,MAAM,oBAAoB,QAOtB,CAAC;AAKZ,eAAO,MAAM,mBAAmB,2FAC0D,CAAC;AAI3F,eAAO,MAAM,eAAe,4EAC+C,CAAC;AAM5E,eAAO,MAAM,sBAAsB,kCAAkC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const tableVariants = cva([
3
4
  "w-full border-collapse text-start",
4
5
  "bg-surface-canvas text-body text-text-primary"
@@ -66,18 +67,19 @@ const tableCellVariants = cva(
66
67
  false: ""
67
68
  },
68
69
  auxiliary: {
69
- // de-emphasized auxiliary cell text — a timestamp, a unit (spec §5 --color-text-muted)
70
- true: "text-text-muted",
70
+ // de-emphasized auxiliary cell text — a timestamp, a unit. Essential (it conveys data), so
71
+ // it uses secondary (AA), not the decorative-only muted role (accessibility.md).
72
+ true: "text-text-secondary",
71
73
  false: ""
72
74
  },
73
75
  status: {
74
76
  none: "",
75
77
  // each status is the fg only, paired with the cell's text — the cell's words carry the
76
78
  // meaning, the fg reinforces it (spec §3/§5); never a saturated -bg fill, never the brand
77
- verified: "text-status-verified-fg",
78
- signal: "text-status-signal-fg",
79
- caution: "text-status-caution-fg",
80
- critical: "text-status-critical-fg"
79
+ verified: "text-status-verified-on-surface",
80
+ signal: "text-status-signal-on-surface",
81
+ caution: "text-status-caution-on-surface",
82
+ critical: "text-status-critical-on-surface"
81
83
  }
82
84
  },
83
85
  defaultVariants: {
@@ -103,7 +105,7 @@ const tableHeadVariants = cva(
103
105
  defaultVariants: { density: "comfortable", rule: "horizontal" }
104
106
  }
105
107
  );
106
- const tableSortButtonClass = "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";
108
+ const tableSortButtonClass = "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;
107
109
  const tableSortCaretClass = "inline-flex h-(--size-icon-sm) w-(--size-icon-sm) shrink-0 items-center justify-center";
108
110
  const tableEmptyClass = "px-(--space-3) py-(--space-4) text-center text-body text-text-secondary";
109
111
  const tableSkeletonCellClass = "px-(--space-3) py-(--space-3)";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/table/table.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\n// A Table presents structured data in rows and columns (spec §1). Neutrals carry the table —\n// restraint over volume (spec §3): it is a READING surface, not an accent surface. It paints from\n// the surface, text, and border roles only; it reaches the --color-action-* tier ONLY for the\n// controls it hosts (a sortable header's ghost accent + focus affordances) and the --color-status-*\n// tier ONLY for a cell that reports a real state, paired with text — never for a row, a header, or\n// a selected state, and NEVER a brand token at all (brand != state, G-U2). The status meaning lives\n// in the cell's words + the status fg, so a grayscale or color-blind reader still reads it (1.4.1).\n\n// The <table> container (spec §2): the neutral canvas surface and the default cell text role. It\n// collapses its borders so the row/column hairline rules read as single lines, and aligns text on\n// the logical start edge so it mirrors under dir=\"rtl\" (G-U6). The table NEVER wears the brand\n// violet or a status fill (spec §3/§8) — those belong to the controls and badges inside cells.\nexport const tableVariants = cva([\n \"w-full border-collapse text-start\",\n \"bg-surface-canvas text-body text-text-primary\",\n]);\n\nexport type TableVariantProps = VariantProps<typeof tableVariants>;\n\n// The <caption> (spec §2/§7): the table's accessible name, in the secondary text color at the\n// label type role. A reading caption, not an accent — never the brand or a status color.\nexport const tableCaptionClass = \"text-start text-label text-text-secondary mb-(--space-2)\";\n\n// The <thead> (spec §2). The header divider is a hairline by default; on a sticky-header table the\n// header pins to the top of the scroll container and the divider strengthens to border-strong,\n// where a heavier separation reads better as the body scrolls under it (spec §4/§5). z on the\n// sticky layer so a scrolled body cell never paints over the pinned header.\nexport const tableHeaderVariants = cva(\"border-b border-border-default\", {\n variants: {\n sticky: {\n true: \"sticky top-0 z-(--z-index-sticky) bg-surface-canvas border-b-border-strong\",\n false: \"\",\n },\n },\n defaultVariants: { sticky: false },\n});\n\nexport type TableHeaderVariantProps = VariantProps<typeof tableHeaderVariants>;\n\n// The <tbody> (spec §3 rule). The `zebra` rule replaces row hairlines with an alternating NEUTRAL\n// surface step on even rows, for a long table — the tint is a surface step, NEVER a status or brand\n// color (spec §3/§8). The arbitrary selector `[&>tr:nth-child(even)]` is a SELECTOR (its body\n// starts with `&`, not a raw value or a bare `--token`), so it is gate-legitimate, not an arbitrary\n// VALUE. While loading, the body is aria-busy (set on the element, not bound here) and shows\n// skeleton rows in the column layout (spec §4 Loading).\nexport const tableBodyVariants = cva(\"\", {\n variants: {\n rule: {\n // hairlines/grid carry their rules on the rows + cells, so the body adds nothing\n horizontal: \"\",\n grid: \"\",\n // zebra: the neutral raised surface step on even rows (no rules)\n zebra: \"[&>tr:nth-child(even)]:bg-surface-raised\",\n },\n },\n defaultVariants: { rule: \"horizontal\" },\n});\n\nexport type TableBodyVariantProps = VariantProps<typeof tableBodyVariants>;\n\n// A <tr> body row (spec §4 Default/Hover/Selected). RESTING: no fill, on the canvas. HOVER: a\n// restrained raised-surface fill to track the eye across a wide row — an AFFORDANCE, not a\n// selection (nothing is selected until you act, spec §4 Hover). SELECTED (aria-selected): the same\n// restrained raised-surface fill, encoded by the Checkbox state + aria-selected, NEVER a brand or\n// status tint (brand != state, G-U2) — so a grayscale reader reads selection from the checkbox, not\n// the fill. Motion is the fast token transition on the verdify easing, instant under reduced motion\n// — never the 350ms VerifiedBadge-only theatre (a row hover/select is a plain transition, G-U3).\n// The `zebra` rule turns off the per-row hairline; `horizontal`/`grid` keep the bottom hairline.\nexport const tableRowVariants = cva(\n [\n \"hover:bg-surface-raised\",\n \"aria-selected:bg-surface-raised\",\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n ],\n {\n variants: {\n rule: {\n horizontal: \"border-b border-border-default\",\n grid: \"border-b border-border-default\",\n zebra: \"\",\n },\n },\n defaultVariants: { rule: \"horizontal\" },\n },\n);\n\nexport type TableRowVariantProps = VariantProps<typeof tableRowVariants>;\n\n// The shared cell padding by density (spec §3 density / §5 --space-*). Density tightens the VERTICAL\n// padding only, ABOVE the a11y floor — the row controls keep their own --size-target-* floor (DEC-B:\n// never a fixed height below the floor). The grid rule adds a logical inline-end column rule so it\n// mirrors under dir=\"rtl\" (G-U6). Horizontal inline padding is constant.\nconst cellPaddingVariants = {\n density: {\n comfortable: \"py-(--space-3)\",\n compact: \"py-(--space-1)\",\n },\n rule: {\n horizontal: \"\",\n // a logical column rule on each cell, for a wide numeric table's column guide (spec §3 grid)\n grid: \"border-e border-border-default last:border-e-0\",\n zebra: \"\",\n },\n} as const;\n\n// A <td> data cell (spec §2 cell, §4/§5). Default: the primary text color at the body type role.\n// A `numeric` cell uses TABULAR figures so digits align down the column and end-aligns them (spec §4\n// Default/§5) — numeric DATA is primary text, NOT muted (spec §4 assigns cell text the primary role;\n// the muted role is reserved by spec §5 for de-emphasized AUXILIARY text, a timestamp or a unit). An\n// `auxiliary` cell takes the muted role explicitly (spec §5 --color-text-muted), independent of\n// `numeric`. A cell that reports a real STATE carries the status fg paired with text — 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// cell's words, not a saturated fill.\nexport const tableCellVariants = cva(\n [\"px-(--space-3) align-middle text-start text-body text-text-primary\"],\n {\n variants: {\n ...cellPaddingVariants,\n numeric: {\n // tabular figures + end-aligned numbers down the column — primary data text, not muted\n true: \"text-end tabular-nums\",\n false: \"\",\n },\n auxiliary: {\n // de-emphasized auxiliary cell text — a timestamp, a unit (spec §5 --color-text-muted)\n true: \"text-text-muted\",\n false: \"\",\n },\n status: {\n none: \"\",\n // each status is the fg only, paired with the cell's text — the cell's words carry the\n // meaning, the fg reinforces it (spec §3/§5); never a saturated -bg fill, never the brand\n verified: \"text-status-verified-fg\",\n signal: \"text-status-signal-fg\",\n caution: \"text-status-caution-fg\",\n critical: \"text-status-critical-fg\",\n },\n },\n defaultVariants: {\n density: \"comfortable\",\n rule: \"horizontal\",\n numeric: false,\n auxiliary: false,\n status: \"none\",\n },\n },\n);\n\nexport type TableCellVariantProps = VariantProps<typeof tableCellVariants>;\n\n// A <th scope=\"row\"> row-header cell (spec §2/§7): the row's natural label (an identifier, a name),\n// tying its cells to the row. The primary text color at the body type role — the same reading weight\n// as a data cell, just promoted to a header for the relationship (1.3.1). Density + grid rule apply.\nexport const tableRowHeaderVariants = cva(\n [\"px-(--space-3) align-middle text-start font-normal text-body text-text-primary\"],\n {\n variants: { ...cellPaddingVariants },\n defaultVariants: { density: \"comfortable\", rule: \"horizontal\" },\n },\n);\n\nexport type TableRowHeaderVariantProps = VariantProps<typeof tableRowHeaderVariants>;\n\n// A <th scope=\"col\"> column header (spec §2/§4/§5). The header LABEL is the SECONDARY text color at\n// the label type role (the quiet, tracked column label), on the canvas — a header NEVER wears a\n// status or brand tint (spec §3/§8). Density + grid rule apply to the cell padding.\nexport const tableHeadVariants = cva(\n [\"px-(--space-3) align-middle text-start text-label text-text-secondary\"],\n {\n variants: { ...cellPaddingVariants },\n defaultVariants: { density: \"comfortable\", rule: \"horizontal\" },\n },\n);\n\nexport type TableHeadVariantProps = VariantProps<typeof tableHeadVariants>;\n\n// The SORTABLE-header control (spec §2/§4/§6/§7): a real <button> inside the <th>, so it reads as\n// the control it is and re-sorts on Enter/Space. It is the GHOST action accent — the label + caret\n// in the ghost fg with the restrained ghost hover fill (spec §4 Sortable-header hover / §5) — the\n// action tier is legitimate here because it is a control the table HOSTS, not a row/header tint. It\n// carries the visible 2px focus ring (never removed) and the target-size floor (40px desktop / 44px\n// touch, spec §7). Motion is the fast token transition, never the deliberate verified-check theatre\n// (G-U3). aria-sort lives on the parent <th>, and the direction caret encodes direction alongside\n// it so it never rests on color alone (spec §4 Sorted / 1.4.1).\nexport const tableSortButtonClass =\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\n// also encoded by aria-sort on the th + the glyph's shape via data-direction, so it never rests on\n// color alone — 1.4.1). It inherits the ghost accent color from the button.\nexport const tableSortCaretClass =\n \"inline-flex h-(--size-icon-sm) w-(--size-icon-sm) shrink-0 items-center justify-center\";\n\n// The empty-state cell (spec §2/§4 Empty): a plain line spanning the full table width, in the\n// secondary text color — an empty table is not an error and never reads as one (no status color).\nexport const tableEmptyClass =\n \"px-(--space-3) py-(--space-4) text-center text-body text-text-secondary\";\n\n// One skeleton placeholder cell while the body resolves (spec §4 Loading): keeps the column layout\n// stable so the table does not reflow when data arrives. The Skeleton itself is decorative + neutral\n// (it binds no brand/status — that invariant lives in the Skeleton component); this is just the cell\n// padding wrapping it.\nexport const tableSkeletonCellClass = \"px-(--space-3) py-(--space-3)\";\n"],"mappings":"AAAA,SAAS,WAA8B;AAchC,MAAM,gBAAgB,IAAI;AAAA,EAC/B;AAAA,EACA;AACF,CAAC;AAMM,MAAM,oBAAoB;AAM1B,MAAM,sBAAsB,IAAI,kCAAkC;AAAA,EACvE,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,QAAQ,MAAM;AACnC,CAAC;AAUM,MAAM,oBAAoB,IAAI,IAAI;AAAA,EACvC,UAAU;AAAA,IACR,MAAM;AAAA;AAAA,MAEJ,YAAY;AAAA,MACZ,MAAM;AAAA;AAAA,MAEN,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,aAAa;AACxC,CAAC;AAYM,MAAM,mBAAmB;AAAA,EAC9B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,YAAY;AAAA,QACZ,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,aAAa;AAAA,EACxC;AACF;AAQA,MAAM,sBAAsB;AAAA,EAC1B,SAAS;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AAAA,EACA,MAAM;AAAA,IACJ,YAAY;AAAA;AAAA,IAEZ,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AACF;AAWO,MAAM,oBAAoB;AAAA,EAC/B,CAAC,oEAAoE;AAAA,EACrE;AAAA,IACE,UAAU;AAAA,MACR,GAAG;AAAA,MACH,SAAS;AAAA;AAAA,QAEP,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,SAAS;AAAA,MACT,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAOO,MAAM,yBAAyB;AAAA,EACpC,CAAC,gFAAgF;AAAA,EACjF;AAAA,IACE,UAAU,EAAE,GAAG,oBAAoB;AAAA,IACnC,iBAAiB,EAAE,SAAS,eAAe,MAAM,aAAa;AAAA,EAChE;AACF;AAOO,MAAM,oBAAoB;AAAA,EAC/B,CAAC,uEAAuE;AAAA,EACxE;AAAA,IACE,UAAU,EAAE,GAAG,oBAAoB;AAAA,IACnC,iBAAiB,EAAE,SAAS,eAAe,MAAM,aAAa;AAAA,EAChE;AACF;AAYO,MAAM,uBACX;AAWK,MAAM,sBACX;AAIK,MAAM,kBACX;AAMK,MAAM,yBAAyB;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/table/table.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { focusRing } from \"../../lib/focus-ring\";\n\n// A Table presents structured data in rows and columns (spec §1). Neutrals carry the table —\n// restraint over volume (spec §3): it is a READING surface, not an accent surface. It paints from\n// the surface, text, and border roles only; it reaches the --color-action-* tier ONLY for the\n// controls it hosts (a sortable header's ghost accent + focus affordances) and the --color-status-*\n// tier ONLY for a cell that reports a real state, paired with text — never for a row, a header, or\n// a selected state, and NEVER a brand token at all (brand != state, G-U2). The status meaning lives\n// in the cell's words + the status fg, so a grayscale or color-blind reader still reads it (1.4.1).\n\n// The <table> container (spec §2): the neutral canvas surface and the default cell text role. It\n// collapses its borders so the row/column hairline rules read as single lines, and aligns text on\n// the logical start edge so it mirrors under dir=\"rtl\" (G-U6). The table NEVER wears the brand\n// violet or a status fill (spec §3/§8) — those belong to the controls and badges inside cells.\nexport const tableVariants = cva([\n \"w-full border-collapse text-start\",\n \"bg-surface-canvas text-body text-text-primary\",\n]);\n\nexport type TableVariantProps = VariantProps<typeof tableVariants>;\n\n// The <caption> (spec §2/§7): the table's accessible name, in the secondary text color at the\n// label type role. A reading caption, not an accent — never the brand or a status color.\nexport const tableCaptionClass = \"text-start text-label text-text-secondary mb-(--space-2)\";\n\n// The <thead> (spec §2). The header divider is a hairline by default; on a sticky-header table the\n// header pins to the top of the scroll container and the divider strengthens to border-strong,\n// where a heavier separation reads better as the body scrolls under it (spec §4/§5). z on the\n// sticky layer so a scrolled body cell never paints over the pinned header.\nexport const tableHeaderVariants = cva(\"border-b border-border-default\", {\n variants: {\n sticky: {\n true: \"sticky top-0 z-(--z-index-sticky) bg-surface-canvas border-b-border-strong\",\n false: \"\",\n },\n },\n defaultVariants: { sticky: false },\n});\n\nexport type TableHeaderVariantProps = VariantProps<typeof tableHeaderVariants>;\n\n// The <tbody> (spec §3 rule). The `zebra` rule replaces row hairlines with an alternating NEUTRAL\n// surface step on even rows, for a long table — the tint is a surface step, NEVER a status or brand\n// color (spec §3/§8). The arbitrary selector `[&>tr:nth-child(even)]` is a SELECTOR (its body\n// starts with `&`, not a raw value or a bare `--token`), so it is gate-legitimate, not an arbitrary\n// VALUE. While loading, the body is aria-busy (set on the element, not bound here) and shows\n// skeleton rows in the column layout (spec §4 Loading).\nexport const tableBodyVariants = cva(\"\", {\n variants: {\n rule: {\n // hairlines/grid carry their rules on the rows + cells, so the body adds nothing\n horizontal: \"\",\n grid: \"\",\n // zebra: the neutral raised surface step on even rows (no rules)\n zebra: \"[&>tr:nth-child(even)]:bg-surface-raised\",\n },\n },\n defaultVariants: { rule: \"horizontal\" },\n});\n\nexport type TableBodyVariantProps = VariantProps<typeof tableBodyVariants>;\n\n// A <tr> body row (spec §4 Default/Hover/Selected). RESTING: no fill, on the canvas. HOVER: a\n// restrained raised-surface fill to track the eye across a wide row — an AFFORDANCE, not a\n// selection (nothing is selected until you act, spec §4 Hover). SELECTED (aria-selected): the same\n// restrained raised-surface fill, encoded by the Checkbox state + aria-selected, NEVER a brand or\n// status tint (brand != state, G-U2) — so a grayscale reader reads selection from the checkbox, not\n// the fill. Motion is the fast token transition on the verdify easing, instant under reduced motion\n// — never the 350ms VerifiedBadge-only theatre (a row hover/select is a plain transition, G-U3).\n// The `zebra` rule turns off the per-row hairline; `horizontal`/`grid` keep the bottom hairline.\nexport const tableRowVariants = cva(\n [\n \"hover:bg-surface-raised\",\n \"aria-selected:bg-surface-raised\",\n \"transition-colors duration-(--motion-duration-fast) ease-(--motion-easing-verdify)\",\n \"motion-reduce:duration-(--motion-duration-instant)\",\n ],\n {\n variants: {\n rule: {\n horizontal: \"border-b border-border-default\",\n grid: \"border-b border-border-default\",\n zebra: \"\",\n },\n },\n defaultVariants: { rule: \"horizontal\" },\n },\n);\n\nexport type TableRowVariantProps = VariantProps<typeof tableRowVariants>;\n\n// The shared cell padding by density (spec §3 density / §5 --space-*). Density tightens the VERTICAL\n// padding only, ABOVE the a11y floor — the row controls keep their own --size-target-* floor (DEC-B:\n// never a fixed height below the floor). The grid rule adds a logical inline-end column rule so it\n// mirrors under dir=\"rtl\" (G-U6). Horizontal inline padding is constant.\nconst cellPaddingVariants = {\n density: {\n comfortable: \"py-(--space-3)\",\n compact: \"py-(--space-1)\",\n },\n rule: {\n horizontal: \"\",\n // a logical column rule on each cell, for a wide numeric table's column guide (spec §3 grid)\n grid: \"border-e border-border-default last:border-e-0\",\n zebra: \"\",\n },\n} as const;\n\n// A <td> data cell (spec §2 cell, §4/§5). Default: the primary text color at the body type role.\n// A `numeric` cell uses TABULAR figures so digits align down the column and end-aligns them (spec §4\n// Default/§5) — numeric DATA is primary text, NOT muted (spec §4 assigns cell text the primary role;\n// the muted role is reserved by spec §5 for de-emphasized AUXILIARY text, a timestamp or a unit). An\n// `auxiliary` cell takes the muted role explicitly (spec §5 --color-text-muted), independent of\n// `numeric`. A cell that reports a real STATE carries the status fg paired with text — 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// cell's words, not a saturated fill.\nexport const tableCellVariants = cva(\n [\"px-(--space-3) align-middle text-start text-body text-text-primary\"],\n {\n variants: {\n ...cellPaddingVariants,\n numeric: {\n // tabular figures + end-aligned numbers down the column — primary data text, not muted\n true: \"text-end tabular-nums\",\n false: \"\",\n },\n auxiliary: {\n // de-emphasized auxiliary cell text — a timestamp, a unit. Essential (it conveys data), so\n // it uses secondary (AA), not the decorative-only muted role (accessibility.md).\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 cell's words carry the\n // meaning, 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 rule: \"horizontal\",\n numeric: false,\n auxiliary: false,\n status: \"none\",\n },\n },\n);\n\nexport type TableCellVariantProps = VariantProps<typeof tableCellVariants>;\n\n// A <th scope=\"row\"> row-header cell (spec §2/§7): the row's natural label (an identifier, a name),\n// tying its cells to the row. The primary text color at the body type role — the same reading weight\n// as a data cell, just promoted to a header for the relationship (1.3.1). Density + grid rule apply.\nexport const tableRowHeaderVariants = cva(\n [\"px-(--space-3) align-middle text-start font-normal text-body text-text-primary\"],\n {\n variants: { ...cellPaddingVariants },\n defaultVariants: { density: \"comfortable\", rule: \"horizontal\" },\n },\n);\n\nexport type TableRowHeaderVariantProps = VariantProps<typeof tableRowHeaderVariants>;\n\n// A <th scope=\"col\"> column header (spec §2/§4/§5). The header LABEL is the SECONDARY text color at\n// the label type role (the quiet, tracked column label), on the canvas — a header NEVER wears a\n// status or brand tint (spec §3/§8). Density + grid rule apply to the cell padding.\nexport const tableHeadVariants = cva(\n [\"px-(--space-3) align-middle text-start text-label text-text-secondary\"],\n {\n variants: { ...cellPaddingVariants },\n defaultVariants: { density: \"comfortable\", rule: \"horizontal\" },\n },\n);\n\nexport type TableHeadVariantProps = VariantProps<typeof tableHeadVariants>;\n\n// The SORTABLE-header control (spec §2/§4/§6/§7): a real <button> inside the <th>, so it reads as\n// the control it is and re-sorts on Enter/Space. It is the GHOST action accent — the label + caret\n// in the ghost fg with the restrained ghost hover fill (spec §4 Sortable-header hover / §5) — the\n// action tier is legitimate here because it is a control the table HOSTS, not a row/header tint. It\n// carries the visible 2px focus ring (never removed) and the target-size floor (40px desktop / 44px\n// touch, spec §7). Motion is the fast token transition, never the deliberate verified-check theatre\n// (G-U3). aria-sort lives on the parent <th>, and the direction caret encodes direction alongside\n// it so it never rests on color alone (spec §4 Sorted / 1.4.1).\nexport const tableSortButtonClass =\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\n// also encoded by aria-sort on the th + the glyph's shape via data-direction, so it never rests on\n// color alone — 1.4.1). It inherits the ghost accent color from the button.\nexport const tableSortCaretClass =\n \"inline-flex h-(--size-icon-sm) w-(--size-icon-sm) shrink-0 items-center justify-center\";\n\n// The empty-state cell (spec §2/§4 Empty): a plain line spanning the full table width, in the\n// secondary text color — an empty table is not an error and never reads as one (no status color).\nexport const tableEmptyClass =\n \"px-(--space-3) py-(--space-4) text-center text-body text-text-secondary\";\n\n// One skeleton placeholder cell while the body resolves (spec §4 Loading): keeps the column layout\n// stable so the table does not reflow when data arrives. The Skeleton itself is decorative + neutral\n// (it binds no brand/status — that invariant lives in the Skeleton component); this is just the cell\n// padding wrapping it.\nexport const tableSkeletonCellClass = \"px-(--space-3) py-(--space-3)\";\n"],"mappings":"AAAA,SAAS,WAA8B;AACvC,SAAS,iBAAiB;AAcnB,MAAM,gBAAgB,IAAI;AAAA,EAC/B;AAAA,EACA;AACF,CAAC;AAMM,MAAM,oBAAoB;AAM1B,MAAM,sBAAsB,IAAI,kCAAkC;AAAA,EACvE,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,QAAQ,MAAM;AACnC,CAAC;AAUM,MAAM,oBAAoB,IAAI,IAAI;AAAA,EACvC,UAAU;AAAA,IACR,MAAM;AAAA;AAAA,MAEJ,YAAY;AAAA,MACZ,MAAM;AAAA;AAAA,MAEN,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,aAAa;AACxC,CAAC;AAYM,MAAM,mBAAmB;AAAA,EAC9B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,YAAY;AAAA,QACZ,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,aAAa;AAAA,EACxC;AACF;AAQA,MAAM,sBAAsB;AAAA,EAC1B,SAAS;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AAAA,EACA,MAAM;AAAA,IACJ,YAAY;AAAA;AAAA,IAEZ,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AACF;AAWO,MAAM,oBAAoB;AAAA,EAC/B,CAAC,oEAAoE;AAAA,EACrE;AAAA,IACE,UAAU;AAAA,MACR,GAAG;AAAA,MACH,SAAS;AAAA;AAAA,QAEP,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA;AAAA,QAGT,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,SAAS;AAAA,MACT,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAOO,MAAM,yBAAyB;AAAA,EACpC,CAAC,gFAAgF;AAAA,EACjF;AAAA,IACE,UAAU,EAAE,GAAG,oBAAoB;AAAA,IACnC,iBAAiB,EAAE,SAAS,eAAe,MAAM,aAAa;AAAA,EAChE;AACF;AAOO,MAAM,oBAAoB;AAAA,EAC/B,CAAC,uEAAuE;AAAA,EACxE;AAAA,IACE,UAAU,EAAE,GAAG,oBAAoB;AAAA,IACnC,iBAAiB,EAAE,SAAS,eAAe,MAAM,aAAa;AAAA,EAChE;AACF;AAYO,MAAM,uBACX,iYAMA;AAKK,MAAM,sBACX;AAIK,MAAM,kBACX;AAMK,MAAM,yBAAyB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.variants.d.ts","sourceRoot":"","sources":["../../../src/components/tabs/tabs.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAWlE,eAAO,MAAM,gBAAgB;;8EAkB5B,CAAC;AAaF,eAAO,MAAM,eAAe;;;8EAmD3B,CAAC;AAOF,eAAO,MAAM,iBAAiB;;8EAmB7B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AACzE,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC;AACvE,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
1
+ {"version":3,"file":"tabs.variants.d.ts","sourceRoot":"","sources":["../../../src/components/tabs/tabs.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAYlE,eAAO,MAAM,gBAAgB;;8EAkB5B,CAAC;AAaF,eAAO,MAAM,eAAe;;;8EAmD3B,CAAC;AAOF,eAAO,MAAM,iBAAiB;;8EAmB7B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AACzE,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC;AACvE,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
+ import { focusRing } from "../../lib/focus-ring";
2
3
  const tabsListVariants = cva(
3
4
  [
4
5
  // logical-property layout (G-U6) — flex row by default, gapped; the canvas backs the row
@@ -38,7 +39,7 @@ const tabsTabVariants = cva(
38
39
  // visible 2px focus ring at 2px offset; persists whether the tab is selected or not, and is
39
40
  // DISTINCT from selection — arrows move focus across tabs while selection stays put (spec §4)
40
41
  "outline-none",
41
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2",
42
+ focusRing,
42
43
  // disabled — DEC-C: reduced emphasis via the disabled TOKEN (Radix sets data-disabled +
43
44
  // removes it from the roving sequence), never a blanket opacity on the control
44
45
  "data-[disabled]:pointer-events-none data-[disabled]:text-text-disabled"
@@ -76,7 +77,7 @@ const tabsPanelVariants = cva(
76
77
  "px-(--space-4) py-(--space-4)",
77
78
  // visible focus ring when the panel itself takes focus (it is tabindex=0); never removed
78
79
  "outline-none",
79
- "focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2"
80
+ focusRing
80
81
  ],
81
82
  {
82
83
  variants: {