@epilot/spark-ui 0.26.1 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/components/alert-dialog.cjs +1 -1
  2. package/dist/components/alert-dialog.js +1 -1
  3. package/dist/components/avatar.d.cts +1 -1
  4. package/dist/components/avatar.d.ts +1 -1
  5. package/dist/components/badge.cjs +10 -2
  6. package/dist/components/badge.cjs.map +1 -1
  7. package/dist/components/badge.d.cts +2 -1
  8. package/dist/components/badge.d.ts +2 -1
  9. package/dist/components/badge.js +10 -2
  10. package/dist/components/badge.js.map +1 -1
  11. package/dist/components/banner.d.cts +1 -1
  12. package/dist/components/banner.d.ts +1 -1
  13. package/dist/components/box.d.cts +1 -1
  14. package/dist/components/box.d.ts +1 -1
  15. package/dist/components/bubble.cjs +1 -1
  16. package/dist/components/bubble.cjs.map +1 -1
  17. package/dist/components/bubble.d.cts +1 -1
  18. package/dist/components/bubble.d.ts +1 -1
  19. package/dist/components/bubble.js +1 -1
  20. package/dist/components/bubble.js.map +1 -1
  21. package/dist/components/button-group.d.cts +1 -1
  22. package/dist/components/button-group.d.ts +1 -1
  23. package/dist/components/button.cjs +28 -6
  24. package/dist/components/button.cjs.map +1 -1
  25. package/dist/components/button.d.cts +7 -6
  26. package/dist/components/button.d.ts +7 -6
  27. package/dist/components/button.js +28 -6
  28. package/dist/components/button.js.map +1 -1
  29. package/dist/components/callout.d.cts +1 -1
  30. package/dist/components/callout.d.ts +1 -1
  31. package/dist/components/card.cjs +8 -4
  32. package/dist/components/card.cjs.map +1 -1
  33. package/dist/components/card.d.cts +6 -2
  34. package/dist/components/card.d.ts +6 -2
  35. package/dist/components/card.js +8 -4
  36. package/dist/components/card.js.map +1 -1
  37. package/dist/components/checkbox-group.cjs +31 -0
  38. package/dist/components/checkbox-group.cjs.map +1 -0
  39. package/dist/components/checkbox-group.d.cts +27 -0
  40. package/dist/components/checkbox-group.d.ts +27 -0
  41. package/dist/components/checkbox-group.js +28 -0
  42. package/dist/components/checkbox-group.js.map +1 -0
  43. package/dist/components/checkbox.d.cts +1 -1
  44. package/dist/components/checkbox.d.ts +1 -1
  45. package/dist/components/dialog.cjs +1 -1
  46. package/dist/components/dialog.js +1 -1
  47. package/dist/components/empty-state.d.cts +2 -2
  48. package/dist/components/empty-state.d.ts +2 -2
  49. package/dist/components/field.cjs +110 -15
  50. package/dist/components/field.cjs.map +1 -1
  51. package/dist/components/field.d.cts +16 -3
  52. package/dist/components/field.d.ts +16 -3
  53. package/dist/components/field.js +111 -17
  54. package/dist/components/field.js.map +1 -1
  55. package/dist/components/input-group.cjs +9 -9
  56. package/dist/components/input-group.cjs.map +1 -1
  57. package/dist/components/input-group.js +9 -9
  58. package/dist/components/input-group.js.map +1 -1
  59. package/dist/components/input.d.cts +1 -1
  60. package/dist/components/input.d.ts +1 -1
  61. package/dist/components/list.cjs +21 -5
  62. package/dist/components/list.cjs.map +1 -1
  63. package/dist/components/list.d.cts +13 -5
  64. package/dist/components/list.d.ts +13 -5
  65. package/dist/components/list.js +21 -5
  66. package/dist/components/list.js.map +1 -1
  67. package/dist/components/marker.cjs +1 -1
  68. package/dist/components/marker.cjs.map +1 -1
  69. package/dist/components/marker.d.cts +1 -1
  70. package/dist/components/marker.d.ts +1 -1
  71. package/dist/components/marker.js +1 -1
  72. package/dist/components/marker.js.map +1 -1
  73. package/dist/components/pill.d.cts +1 -1
  74. package/dist/components/pill.d.ts +1 -1
  75. package/dist/components/radio.cjs +6 -2
  76. package/dist/components/radio.cjs.map +1 -1
  77. package/dist/components/radio.d.cts +4 -2
  78. package/dist/components/radio.d.ts +4 -2
  79. package/dist/components/radio.js +6 -2
  80. package/dist/components/radio.js.map +1 -1
  81. package/dist/components/rating.cjs +167 -0
  82. package/dist/components/rating.cjs.map +1 -0
  83. package/dist/components/rating.d.cts +50 -0
  84. package/dist/components/rating.d.ts +50 -0
  85. package/dist/components/rating.js +162 -0
  86. package/dist/components/rating.js.map +1 -0
  87. package/dist/components/scroll-area.cjs +9 -2
  88. package/dist/components/scroll-area.cjs.map +1 -1
  89. package/dist/components/scroll-area.d.cts +1 -1
  90. package/dist/components/scroll-area.d.ts +1 -1
  91. package/dist/components/scroll-area.js +10 -3
  92. package/dist/components/scroll-area.js.map +1 -1
  93. package/dist/components/segmented-control.d.cts +1 -1
  94. package/dist/components/segmented-control.d.ts +1 -1
  95. package/dist/components/select.d.cts +1 -1
  96. package/dist/components/select.d.ts +1 -1
  97. package/dist/components/selection-item.cjs +42 -0
  98. package/dist/components/selection-item.cjs.map +1 -0
  99. package/dist/components/selection-item.d.cts +25 -0
  100. package/dist/components/selection-item.d.ts +25 -0
  101. package/dist/components/selection-item.js +39 -0
  102. package/dist/components/selection-item.js.map +1 -0
  103. package/dist/components/sheet.cjs +1 -1
  104. package/dist/components/sheet.js +1 -1
  105. package/dist/components/skeleton.cjs +1 -1
  106. package/dist/components/skeleton.cjs.map +1 -1
  107. package/dist/components/skeleton.js +1 -1
  108. package/dist/components/skeleton.js.map +1 -1
  109. package/dist/components/slider.cjs +1 -1
  110. package/dist/components/slider.cjs.map +1 -1
  111. package/dist/components/slider.js +1 -1
  112. package/dist/components/slider.js.map +1 -1
  113. package/dist/components/spinner.cjs +81 -8
  114. package/dist/components/spinner.cjs.map +1 -1
  115. package/dist/components/spinner.js +81 -8
  116. package/dist/components/spinner.js.map +1 -1
  117. package/dist/components/stepper.cjs +3 -2
  118. package/dist/components/stepper.cjs.map +1 -1
  119. package/dist/components/stepper.js +3 -2
  120. package/dist/components/stepper.js.map +1 -1
  121. package/dist/components/switch-group.cjs +32 -0
  122. package/dist/components/switch-group.cjs.map +1 -0
  123. package/dist/components/switch-group.d.cts +26 -0
  124. package/dist/components/switch-group.d.ts +26 -0
  125. package/dist/components/switch-group.js +29 -0
  126. package/dist/components/switch-group.js.map +1 -0
  127. package/dist/components/switch.d.cts +1 -1
  128. package/dist/components/switch.d.ts +1 -1
  129. package/dist/components/tabs.cjs +32 -23
  130. package/dist/components/tabs.cjs.map +1 -1
  131. package/dist/components/tabs.d.cts +5 -5
  132. package/dist/components/tabs.d.ts +5 -5
  133. package/dist/components/tabs.js +32 -23
  134. package/dist/components/tabs.js.map +1 -1
  135. package/dist/components/textarea.d.cts +1 -1
  136. package/dist/components/textarea.d.ts +1 -1
  137. package/dist/components/theme.cjs +44 -2
  138. package/dist/components/theme.cjs.map +1 -1
  139. package/dist/components/theme.d.cts +4 -2
  140. package/dist/components/theme.d.ts +4 -2
  141. package/dist/components/theme.js +44 -2
  142. package/dist/components/theme.js.map +1 -1
  143. package/dist/components/toast.cjs +1 -1
  144. package/dist/components/toast.js +1 -1
  145. package/dist/components/toggle-group.d.cts +1 -1
  146. package/dist/components/toggle-group.d.ts +1 -1
  147. package/dist/components/toggle.d.cts +2 -2
  148. package/dist/components/toggle.d.ts +2 -2
  149. package/dist/lib/accent-theme.d.cts +1 -1
  150. package/dist/lib/accent-theme.d.ts +1 -1
  151. package/dist/lib/container-chrome.cjs +4 -4
  152. package/dist/lib/container-chrome.cjs.map +1 -1
  153. package/dist/lib/container-chrome.d.cts +1 -1
  154. package/dist/lib/container-chrome.d.ts +1 -1
  155. package/dist/lib/container-chrome.js +4 -4
  156. package/dist/lib/container-chrome.js.map +1 -1
  157. package/dist/lib/input-box-variants.cjs +8 -8
  158. package/dist/lib/input-box-variants.cjs.map +1 -1
  159. package/dist/lib/input-box-variants.d.cts +1 -1
  160. package/dist/lib/input-box-variants.d.ts +1 -1
  161. package/dist/lib/input-box-variants.js +8 -8
  162. package/dist/lib/input-box-variants.js.map +1 -1
  163. package/dist/lib/selection-list.cjs +17 -0
  164. package/dist/lib/selection-list.cjs.map +1 -0
  165. package/dist/lib/selection-list.d.cts +11 -0
  166. package/dist/lib/selection-list.d.ts +11 -0
  167. package/dist/lib/selection-list.js +15 -0
  168. package/dist/lib/selection-list.js.map +1 -0
  169. package/dist/lib/theme-types.cjs.map +1 -1
  170. package/dist/lib/theme-types.d.cts +2 -2
  171. package/dist/lib/theme-types.d.ts +2 -2
  172. package/dist/lib/theme-types.js.map +1 -1
  173. package/dist/lib/toggle-group-context.d.cts +2 -1
  174. package/dist/lib/toggle-group-context.d.ts +2 -1
  175. package/dist/styles/spark-default.css +12 -1
  176. package/dist/styles/theme.css +98 -0
  177. package/dist/{theme-types-Bn5E15-P.d.ts → theme-types-CKZzOmSa.d.cts} +11 -2
  178. package/dist/{theme-types-Bn5E15-P.d.cts → theme-types-CKZzOmSa.d.ts} +11 -2
  179. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"stepper.cjs","names":["React","ChevronLeft","ChevronRight"],"sources":["../../src/components/stepper.tsx"],"sourcesContent":["import { ChevronLeft, ChevronRight } from '@epilot/spark-icons'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\ninterface StepperProps {\n value: number\n onValueChange: (value: number) => void\n /**\n * Fires once after the user stops interacting (default: 400ms after the last\n * click). Use this for side effects like persisting the value, so rapid\n * clicks don't fan out into a request per click.\n */\n onValueCommit?: (value: number) => void\n min?: number\n max?: number\n step?: number\n formatValue?: (value: number) => string\n disabled?: boolean\n className?: string\n /** Delay (ms) after the last click before `onValueCommit` fires. */\n commitDelayMs?: number\n /** Accessible label, e.g. \"Sunshare percentage\". */\n 'aria-label'?: string\n /** Accessible label for the decrement button. */\n decreaseLabel: string\n /** Accessible label for the increment button. */\n increaseLabel: string\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n (\n {\n value,\n onValueChange,\n onValueCommit,\n min = 0,\n max = 100,\n step = 1,\n formatValue = (v) => String(v),\n disabled,\n className,\n commitDelayMs = 400,\n 'aria-label': ariaLabel,\n decreaseLabel,\n increaseLabel,\n },\n ref,\n ) => {\n const commitTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n\n React.useEffect(\n () => () => {\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n },\n [],\n )\n\n const scheduleCommit = (next: number) => {\n if (!onValueCommit) return\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n commitTimerRef.current = setTimeout(() => {\n commitTimerRef.current = null\n onValueCommit(next)\n }, commitDelayMs)\n }\n\n const decrement = () => {\n const next = Math.max(min, value - step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n const increment = () => {\n const next = Math.min(max, value + step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n\n const atMin = value <= min\n const atMax = value >= max\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex h-12 w-full items-center justify-between rounded-md bg-base-gray-soft px-2',\n className,\n )}\n >\n <button\n type=\"button\"\n aria-label={decreaseLabel}\n onClick={decrement}\n disabled={disabled || atMin}\n className=\"touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors hover:bg-gray-a3 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none\"\n >\n <ChevronLeft className=\"[--spark-icon-size:16px]\" />\n </button>\n <span\n className=\"text-base font-semibold text-gray tabular-nums select-none\"\n aria-live=\"polite\"\n >\n {formatValue(value)}\n </span>\n <button\n type=\"button\"\n aria-label={increaseLabel}\n onClick={increment}\n disabled={disabled || atMax}\n className=\"touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors hover:bg-gray-a3 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none\"\n >\n <ChevronRight className=\"[--spark-icon-size:16px]\" />\n </button>\n </div>\n )\n },\n)\n\nStepper.displayName = 'Stepper'\n\nexport { Stepper }\nexport type { StepperProps }\n"],"mappings":";;;;;;;;;AA6BA,MAAM,UAAUA,MAAM,YAElB,EACE,OACA,eACA,eACA,MAAM,GACN,MAAM,KACN,OAAO,GACP,eAAe,MAAM,OAAO,CAAC,GAC7B,UACA,WACA,gBAAgB,KAChB,cAAc,WACd,eACA,iBAEF,QACG;CACH,MAAM,iBAAiBA,MAAM,OAC3B,IACF;CAEA,MAAM,sBACQ;EACV,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;CACjE,GACA,CAAC,CACH;CAEA,MAAM,kBAAkB,SAAiB;EACvC,IAAI,CAAC,eAAe;EACpB,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;EAC/D,eAAe,UAAU,iBAAiB;GACxC,eAAe,UAAU;GACzB,cAAc,IAAI;EACpB,GAAG,aAAa;CAClB;CAEA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CACA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CAEA,MAAM,QAAQ,SAAS;CACvB,MAAM,QAAQ,SAAS;CAEvB,OACE,iBAAA,GAAA,kBAAA,MAAC,OAAD;EACO;EACL,MAAK;EACL,cAAY;EACZ,YAAA,GAAA,GAAA,IACE,0FACA,SACF;YAPF;GASE,iBAAA,GAAA,kBAAA,KAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAU;cAEV,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,aAAD,EAAa,WAAU,2BAA4B,CAAA;GAC7C,CAAA;GACR,iBAAA,GAAA,kBAAA,KAAC,QAAD;IACE,WAAU;IACV,aAAU;cAET,YAAY,KAAK;GACd,CAAA;GACN,iBAAA,GAAA,kBAAA,KAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAU;cAEV,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,cAAD,EAAc,WAAU,2BAA4B,CAAA;GAC9C,CAAA;EACL;;AAET,CACF;AAEA,QAAQ,cAAc"}
1
+ {"version":3,"file":"stepper.cjs","names":["React","ChevronLeft","ChevronRight"],"sources":["../../src/components/stepper.tsx"],"sourcesContent":["import { ChevronLeft, ChevronRight } from '@epilot/spark-icons'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\nconst STEP_BUTTON =\n 'touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors outline-hidden hover:bg-gray-a3 focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none'\n\ninterface StepperProps {\n value: number\n onValueChange: (value: number) => void\n /**\n * Fires once after the user stops interacting (default: 400ms after the last\n * click). Use this for side effects like persisting the value, so rapid\n * clicks don't fan out into a request per click.\n */\n onValueCommit?: (value: number) => void\n min?: number\n max?: number\n step?: number\n formatValue?: (value: number) => string\n disabled?: boolean\n className?: string\n /** Delay (ms) after the last click before `onValueCommit` fires. */\n commitDelayMs?: number\n /** Accessible label, e.g. \"Sunshare percentage\". */\n 'aria-label'?: string\n /** Accessible label for the decrement button. */\n decreaseLabel: string\n /** Accessible label for the increment button. */\n increaseLabel: string\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n (\n {\n value,\n onValueChange,\n onValueCommit,\n min = 0,\n max = 100,\n step = 1,\n formatValue = (v) => String(v),\n disabled,\n className,\n commitDelayMs = 400,\n 'aria-label': ariaLabel,\n decreaseLabel,\n increaseLabel,\n },\n ref,\n ) => {\n const commitTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n\n React.useEffect(\n () => () => {\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n },\n [],\n )\n\n const scheduleCommit = (next: number) => {\n if (!onValueCommit) return\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n commitTimerRef.current = setTimeout(() => {\n commitTimerRef.current = null\n onValueCommit(next)\n }, commitDelayMs)\n }\n\n const decrement = () => {\n const next = Math.max(min, value - step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n const increment = () => {\n const next = Math.min(max, value + step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n\n const atMin = value <= min\n const atMax = value >= max\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex h-12 w-full items-center justify-between rounded-md bg-base-gray-soft px-2',\n className,\n )}\n >\n <button\n type=\"button\"\n aria-label={decreaseLabel}\n onClick={decrement}\n disabled={disabled || atMin}\n className={STEP_BUTTON}\n >\n <ChevronLeft className=\"[--spark-icon-size:16px]\" />\n </button>\n <span\n className=\"text-base font-semibold text-gray tabular-nums select-none\"\n aria-live=\"polite\"\n >\n {formatValue(value)}\n </span>\n <button\n type=\"button\"\n aria-label={increaseLabel}\n onClick={increment}\n disabled={disabled || atMax}\n className={STEP_BUTTON}\n >\n <ChevronRight className=\"[--spark-icon-size:16px]\" />\n </button>\n </div>\n )\n },\n)\n\nStepper.displayName = 'Stepper'\n\nexport { Stepper }\nexport type { StepperProps }\n"],"mappings":";;;;;;;;;AAIA,MAAM,cACJ;AA2BF,MAAM,UAAUA,MAAM,YAElB,EACE,OACA,eACA,eACA,MAAM,GACN,MAAM,KACN,OAAO,GACP,eAAe,MAAM,OAAO,CAAC,GAC7B,UACA,WACA,gBAAgB,KAChB,cAAc,WACd,eACA,iBAEF,QACG;CACH,MAAM,iBAAiBA,MAAM,OAC3B,IACF;CAEA,MAAM,sBACQ;EACV,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;CACjE,GACA,CAAC,CACH;CAEA,MAAM,kBAAkB,SAAiB;EACvC,IAAI,CAAC,eAAe;EACpB,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;EAC/D,eAAe,UAAU,iBAAiB;GACxC,eAAe,UAAU;GACzB,cAAc,IAAI;EACpB,GAAG,aAAa;CAClB;CAEA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CACA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CAEA,MAAM,QAAQ,SAAS;CACvB,MAAM,QAAQ,SAAS;CAEvB,OACE,iBAAA,GAAA,kBAAA,MAAC,OAAD;EACO;EACL,MAAK;EACL,cAAY;EACZ,YAAA,GAAA,GAAA,IACE,0FACA,SACF;YAPF;GASE,iBAAA,GAAA,kBAAA,KAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAW;cAEX,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,aAAD,EAAa,WAAU,2BAA4B,CAAA;GAC7C,CAAA;GACR,iBAAA,GAAA,kBAAA,KAAC,QAAD;IACE,WAAU;IACV,aAAU;cAET,YAAY,KAAK;GACd,CAAA;GACN,iBAAA,GAAA,kBAAA,KAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAW;cAEX,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,cAAD,EAAc,WAAU,2BAA4B,CAAA;GAC9C,CAAA;EACL;;AAET,CACF;AAEA,QAAQ,cAAc"}
@@ -4,6 +4,7 @@ import * as React$1 from "react";
4
4
  import { ChevronLeft, ChevronRight } from "@epilot/spark-icons";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/components/stepper.tsx
7
+ const STEP_BUTTON = "touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors outline-hidden hover:bg-gray-a3 focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none";
7
8
  const Stepper = React$1.forwardRef(({ value, onValueChange, onValueCommit, min = 0, max = 100, step = 1, formatValue = (v) => String(v), disabled, className, commitDelayMs = 400, "aria-label": ariaLabel, decreaseLabel, increaseLabel }, ref) => {
8
9
  const commitTimerRef = React$1.useRef(null);
9
10
  React$1.useEffect(() => () => {
@@ -44,7 +45,7 @@ const Stepper = React$1.forwardRef(({ value, onValueChange, onValueCommit, min =
44
45
  "aria-label": decreaseLabel,
45
46
  onClick: decrement,
46
47
  disabled: disabled || atMin,
47
- className: "touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors hover:bg-gray-a3 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none",
48
+ className: STEP_BUTTON,
48
49
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "[--spark-icon-size:16px]" })
49
50
  }),
50
51
  /* @__PURE__ */ jsx("span", {
@@ -57,7 +58,7 @@ const Stepper = React$1.forwardRef(({ value, onValueChange, onValueCommit, min =
57
58
  "aria-label": increaseLabel,
58
59
  onClick: increment,
59
60
  disabled: disabled || atMax,
60
- className: "touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors hover:bg-gray-a3 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none",
61
+ className: STEP_BUTTON,
61
62
  children: /* @__PURE__ */ jsx(ChevronRight, { className: "[--spark-icon-size:16px]" })
62
63
  })
63
64
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"stepper.js","names":["React"],"sources":["../../src/components/stepper.tsx"],"sourcesContent":["import { ChevronLeft, ChevronRight } from '@epilot/spark-icons'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\ninterface StepperProps {\n value: number\n onValueChange: (value: number) => void\n /**\n * Fires once after the user stops interacting (default: 400ms after the last\n * click). Use this for side effects like persisting the value, so rapid\n * clicks don't fan out into a request per click.\n */\n onValueCommit?: (value: number) => void\n min?: number\n max?: number\n step?: number\n formatValue?: (value: number) => string\n disabled?: boolean\n className?: string\n /** Delay (ms) after the last click before `onValueCommit` fires. */\n commitDelayMs?: number\n /** Accessible label, e.g. \"Sunshare percentage\". */\n 'aria-label'?: string\n /** Accessible label for the decrement button. */\n decreaseLabel: string\n /** Accessible label for the increment button. */\n increaseLabel: string\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n (\n {\n value,\n onValueChange,\n onValueCommit,\n min = 0,\n max = 100,\n step = 1,\n formatValue = (v) => String(v),\n disabled,\n className,\n commitDelayMs = 400,\n 'aria-label': ariaLabel,\n decreaseLabel,\n increaseLabel,\n },\n ref,\n ) => {\n const commitTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n\n React.useEffect(\n () => () => {\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n },\n [],\n )\n\n const scheduleCommit = (next: number) => {\n if (!onValueCommit) return\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n commitTimerRef.current = setTimeout(() => {\n commitTimerRef.current = null\n onValueCommit(next)\n }, commitDelayMs)\n }\n\n const decrement = () => {\n const next = Math.max(min, value - step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n const increment = () => {\n const next = Math.min(max, value + step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n\n const atMin = value <= min\n const atMax = value >= max\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex h-12 w-full items-center justify-between rounded-md bg-base-gray-soft px-2',\n className,\n )}\n >\n <button\n type=\"button\"\n aria-label={decreaseLabel}\n onClick={decrement}\n disabled={disabled || atMin}\n className=\"touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors hover:bg-gray-a3 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none\"\n >\n <ChevronLeft className=\"[--spark-icon-size:16px]\" />\n </button>\n <span\n className=\"text-base font-semibold text-gray tabular-nums select-none\"\n aria-live=\"polite\"\n >\n {formatValue(value)}\n </span>\n <button\n type=\"button\"\n aria-label={increaseLabel}\n onClick={increment}\n disabled={disabled || atMax}\n className=\"touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors hover:bg-gray-a3 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none\"\n >\n <ChevronRight className=\"[--spark-icon-size:16px]\" />\n </button>\n </div>\n )\n },\n)\n\nStepper.displayName = 'Stepper'\n\nexport { Stepper }\nexport type { StepperProps }\n"],"mappings":";;;;;;AA6BA,MAAM,UAAUA,QAAM,YAElB,EACE,OACA,eACA,eACA,MAAM,GACN,MAAM,KACN,OAAO,GACP,eAAe,MAAM,OAAO,CAAC,GAC7B,UACA,WACA,gBAAgB,KAChB,cAAc,WACd,eACA,iBAEF,QACG;CACH,MAAM,iBAAiBA,QAAM,OAC3B,IACF;CAEA,QAAM,sBACQ;EACV,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;CACjE,GACA,CAAC,CACH;CAEA,MAAM,kBAAkB,SAAiB;EACvC,IAAI,CAAC,eAAe;EACpB,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;EAC/D,eAAe,UAAU,iBAAiB;GACxC,eAAe,UAAU;GACzB,cAAc,IAAI;EACpB,GAAG,aAAa;CAClB;CAEA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CACA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CAEA,MAAM,QAAQ,SAAS;CACvB,MAAM,QAAQ,SAAS;CAEvB,OACE,qBAAC,OAAD;EACO;EACL,MAAK;EACL,cAAY;EACZ,WAAW,GACT,0FACA,SACF;YAPF;GASE,oBAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAU;cAEV,oBAAC,aAAD,EAAa,WAAU,2BAA4B,CAAA;GAC7C,CAAA;GACR,oBAAC,QAAD;IACE,WAAU;IACV,aAAU;cAET,YAAY,KAAK;GACd,CAAA;GACN,oBAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAU;cAEV,oBAAC,cAAD,EAAc,WAAU,2BAA4B,CAAA;GAC9C,CAAA;EACL;;AAET,CACF;AAEA,QAAQ,cAAc"}
1
+ {"version":3,"file":"stepper.js","names":["React"],"sources":["../../src/components/stepper.tsx"],"sourcesContent":["import { ChevronLeft, ChevronRight } from '@epilot/spark-icons'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\nconst STEP_BUTTON =\n 'touch-target inline-flex size-8 items-center justify-center rounded-sm text-gray transition-colors outline-hidden hover:bg-gray-a3 focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-40 disabled:hover:bg-transparent motion-reduce:transition-none'\n\ninterface StepperProps {\n value: number\n onValueChange: (value: number) => void\n /**\n * Fires once after the user stops interacting (default: 400ms after the last\n * click). Use this for side effects like persisting the value, so rapid\n * clicks don't fan out into a request per click.\n */\n onValueCommit?: (value: number) => void\n min?: number\n max?: number\n step?: number\n formatValue?: (value: number) => string\n disabled?: boolean\n className?: string\n /** Delay (ms) after the last click before `onValueCommit` fires. */\n commitDelayMs?: number\n /** Accessible label, e.g. \"Sunshare percentage\". */\n 'aria-label'?: string\n /** Accessible label for the decrement button. */\n decreaseLabel: string\n /** Accessible label for the increment button. */\n increaseLabel: string\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n (\n {\n value,\n onValueChange,\n onValueCommit,\n min = 0,\n max = 100,\n step = 1,\n formatValue = (v) => String(v),\n disabled,\n className,\n commitDelayMs = 400,\n 'aria-label': ariaLabel,\n decreaseLabel,\n increaseLabel,\n },\n ref,\n ) => {\n const commitTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n\n React.useEffect(\n () => () => {\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n },\n [],\n )\n\n const scheduleCommit = (next: number) => {\n if (!onValueCommit) return\n if (commitTimerRef.current) clearTimeout(commitTimerRef.current)\n commitTimerRef.current = setTimeout(() => {\n commitTimerRef.current = null\n onValueCommit(next)\n }, commitDelayMs)\n }\n\n const decrement = () => {\n const next = Math.max(min, value - step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n const increment = () => {\n const next = Math.min(max, value + step)\n if (next !== value) {\n onValueChange(next)\n scheduleCommit(next)\n }\n }\n\n const atMin = value <= min\n const atMax = value >= max\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex h-12 w-full items-center justify-between rounded-md bg-base-gray-soft px-2',\n className,\n )}\n >\n <button\n type=\"button\"\n aria-label={decreaseLabel}\n onClick={decrement}\n disabled={disabled || atMin}\n className={STEP_BUTTON}\n >\n <ChevronLeft className=\"[--spark-icon-size:16px]\" />\n </button>\n <span\n className=\"text-base font-semibold text-gray tabular-nums select-none\"\n aria-live=\"polite\"\n >\n {formatValue(value)}\n </span>\n <button\n type=\"button\"\n aria-label={increaseLabel}\n onClick={increment}\n disabled={disabled || atMax}\n className={STEP_BUTTON}\n >\n <ChevronRight className=\"[--spark-icon-size:16px]\" />\n </button>\n </div>\n )\n },\n)\n\nStepper.displayName = 'Stepper'\n\nexport { Stepper }\nexport type { StepperProps }\n"],"mappings":";;;;;;AAIA,MAAM,cACJ;AA2BF,MAAM,UAAUA,QAAM,YAElB,EACE,OACA,eACA,eACA,MAAM,GACN,MAAM,KACN,OAAO,GACP,eAAe,MAAM,OAAO,CAAC,GAC7B,UACA,WACA,gBAAgB,KAChB,cAAc,WACd,eACA,iBAEF,QACG;CACH,MAAM,iBAAiBA,QAAM,OAC3B,IACF;CAEA,QAAM,sBACQ;EACV,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;CACjE,GACA,CAAC,CACH;CAEA,MAAM,kBAAkB,SAAiB;EACvC,IAAI,CAAC,eAAe;EACpB,IAAI,eAAe,SAAS,aAAa,eAAe,OAAO;EAC/D,eAAe,UAAU,iBAAiB;GACxC,eAAe,UAAU;GACzB,cAAc,IAAI;EACpB,GAAG,aAAa;CAClB;CAEA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CACA,MAAM,kBAAkB;EACtB,MAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,IAAI;EACvC,IAAI,SAAS,OAAO;GAClB,cAAc,IAAI;GAClB,eAAe,IAAI;EACrB;CACF;CAEA,MAAM,QAAQ,SAAS;CACvB,MAAM,QAAQ,SAAS;CAEvB,OACE,qBAAC,OAAD;EACO;EACL,MAAK;EACL,cAAY;EACZ,WAAW,GACT,0FACA,SACF;YAPF;GASE,oBAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAW;cAEX,oBAAC,aAAD,EAAa,WAAU,2BAA4B,CAAA;GAC7C,CAAA;GACR,oBAAC,QAAD;IACE,WAAU;IACV,aAAU;cAET,YAAY,KAAK;GACd,CAAA;GACN,oBAAC,UAAD;IACE,MAAK;IACL,cAAY;IACZ,SAAS;IACT,UAAU,YAAY;IACtB,WAAW;cAEX,oBAAC,cAAD,EAAc,WAAU,2BAA4B,CAAA;GAC9C,CAAA;EACL;;AAET,CACF;AAEA,QAAQ,cAAc"}
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_chunk = require("../chunk-DY84GLCh.cjs");
4
+ const require_lib_accent_colors = require("../lib/accent-colors.cjs");
5
+ const require_components_field = require("./field.cjs");
6
+ const require_lib_selection_list = require("../lib/selection-list.cjs");
7
+ let react = require("react");
8
+ react = require_chunk.__toESM(react);
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ let cn = require("cn");
11
+ //#region src/components/switch-group.tsx
12
+ /** Lays out switches as one group; each switch keeps its own checked state. */
13
+ function SwitchGroup({ className, styleVariant, color, gap = 3, listStyle, style, ...props }) {
14
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
15
+ role: "group",
16
+ "data-slot": "switch-group",
17
+ "data-style": styleVariant !== void 0 || color !== void 0 ? styleVariant ?? "solid" : void 0,
18
+ "data-accent-color": color ? require_lib_accent_colors.accentByColor[color] : void 0,
19
+ "data-list-style": listStyle,
20
+ "data-invalid": require_components_field.useFieldSetInvalid(),
21
+ className: (0, cn.cn)("grid", listStyle ? require_lib_selection_list.selectionListClasses[listStyle] : "gap-[calc(var(--spark-layout-gap)*var(--switch-group-gap-steps,3))]", className),
22
+ style: {
23
+ "--switch-group-gap-steps": gap,
24
+ ...style
25
+ },
26
+ ...props
27
+ });
28
+ }
29
+ //#endregion
30
+ exports.SwitchGroup = SwitchGroup;
31
+
32
+ //# sourceMappingURL=switch-group.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch-group.cjs","names":["accentByColor","useFieldSetInvalid","selectionListClasses"],"sources":["../../src/components/switch-group.tsx"],"sourcesContent":["import * as React from 'react'\nimport { accentByColor } from '../lib/accent-colors'\nimport { selectionListClasses } from '../lib/selection-list'\nimport type { SparkInputStyle, SparkStyle } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { useFieldSetInvalid } from './field'\nimport type { SparkSwitchColor } from './switch'\n\ntype SwitchGroupProps = React.ComponentProps<'div'> & {\n styleVariant?: SparkStyle\n color?: SparkSwitchColor\n /** How many `--spark-layout-gap` units apart each switch sits. */\n gap?: number\n /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */\n listStyle?: SparkInputStyle\n}\n\n/** Lays out switches as one group; each switch keeps its own checked state. */\nfunction SwitchGroup({\n className,\n styleVariant,\n color,\n gap = 3,\n listStyle,\n style,\n ...props\n}: SwitchGroupProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n const invalid = useFieldSetInvalid()\n\n return (\n <div\n role=\"group\"\n data-slot=\"switch-group\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n data-list-style={listStyle}\n data-invalid={invalid}\n className={cn(\n 'grid',\n listStyle\n ? selectionListClasses[listStyle]\n : 'gap-[calc(var(--spark-layout-gap)*var(--switch-group-gap-steps,3))]',\n className,\n )}\n style={\n { '--switch-group-gap-steps': gap, ...style } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\nexport { SwitchGroup }\nexport type { SwitchGroupProps }\n"],"mappings":";;;;;;;;;;;;AAkBA,SAAS,YAAY,EACnB,WACA,cACA,OACA,MAAM,GACN,WACA,OACA,GAAG,SACgB;CAMnB,OACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EACE,MAAK;EACL,aAAU;EACV,cATY,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;EASpD,qBARoB,QAAQA,0BAAAA,cAAc,SAAS,KAAA;EASnD,mBAAiB;EACjB,gBATYC,yBAAAA,mBASQ;EACpB,YAAA,GAAA,GAAA,IACE,QACA,YACIC,2BAAAA,qBAAqB,aACrB,uEACJ,SACF;EACA,OACE;GAAE,4BAA4B;GAAK,GAAG;EAAM;EAE9C,GAAI;CACL,CAAA;AAEL"}
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import { g as SparkStyle, l as SparkInputStyle } from "../theme-types-CKZzOmSa.cjs";
4
+ import { SparkSwitchColor } from "./switch.cjs";
5
+ import * as React$1 from "react";
6
+
7
+ //#region src/components/switch-group.d.ts
8
+ type SwitchGroupProps = React$1.ComponentProps<'div'> & {
9
+ styleVariant?: SparkStyle;
10
+ color?: SparkSwitchColor; /** How many `--spark-layout-gap` units apart each switch sits. */
11
+ gap?: number; /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */
12
+ listStyle?: SparkInputStyle;
13
+ };
14
+ /** Lays out switches as one group; each switch keeps its own checked state. */
15
+ declare function SwitchGroup({
16
+ className,
17
+ styleVariant,
18
+ color,
19
+ gap,
20
+ listStyle,
21
+ style,
22
+ ...props
23
+ }: SwitchGroupProps): import("react/jsx-runtime").JSX.Element;
24
+ //#endregion
25
+ export { SwitchGroup, type SwitchGroupProps };
26
+ //# sourceMappingURL=switch-group.d.cts.map
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import { g as SparkStyle, l as SparkInputStyle } from "../theme-types-CKZzOmSa.js";
4
+ import { SparkSwitchColor } from "./switch.js";
5
+ import * as React$1 from "react";
6
+
7
+ //#region src/components/switch-group.d.ts
8
+ type SwitchGroupProps = React$1.ComponentProps<'div'> & {
9
+ styleVariant?: SparkStyle;
10
+ color?: SparkSwitchColor; /** How many `--spark-layout-gap` units apart each switch sits. */
11
+ gap?: number; /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */
12
+ listStyle?: SparkInputStyle;
13
+ };
14
+ /** Lays out switches as one group; each switch keeps its own checked state. */
15
+ declare function SwitchGroup({
16
+ className,
17
+ styleVariant,
18
+ color,
19
+ gap,
20
+ listStyle,
21
+ style,
22
+ ...props
23
+ }: SwitchGroupProps): import("react/jsx-runtime").JSX.Element;
24
+ //#endregion
25
+ export { SwitchGroup, type SwitchGroupProps };
26
+ //# sourceMappingURL=switch-group.d.ts.map
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { cn } from "../utils/cn.js";
3
+ import { accentByColor } from "../lib/accent-colors.js";
4
+ import { useFieldSetInvalid } from "./field.js";
5
+ import { selectionListClasses } from "../lib/selection-list.js";
6
+ import "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ //#region src/components/switch-group.tsx
9
+ /** Lays out switches as one group; each switch keeps its own checked state. */
10
+ function SwitchGroup({ className, styleVariant, color, gap = 3, listStyle, style, ...props }) {
11
+ return /* @__PURE__ */ jsx("div", {
12
+ role: "group",
13
+ "data-slot": "switch-group",
14
+ "data-style": styleVariant !== void 0 || color !== void 0 ? styleVariant ?? "solid" : void 0,
15
+ "data-accent-color": color ? accentByColor[color] : void 0,
16
+ "data-list-style": listStyle,
17
+ "data-invalid": useFieldSetInvalid(),
18
+ className: cn("grid", listStyle ? selectionListClasses[listStyle] : "gap-[calc(var(--spark-layout-gap)*var(--switch-group-gap-steps,3))]", className),
19
+ style: {
20
+ "--switch-group-gap-steps": gap,
21
+ ...style
22
+ },
23
+ ...props
24
+ });
25
+ }
26
+ //#endregion
27
+ export { SwitchGroup };
28
+
29
+ //# sourceMappingURL=switch-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch-group.js","names":[],"sources":["../../src/components/switch-group.tsx"],"sourcesContent":["import * as React from 'react'\nimport { accentByColor } from '../lib/accent-colors'\nimport { selectionListClasses } from '../lib/selection-list'\nimport type { SparkInputStyle, SparkStyle } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { useFieldSetInvalid } from './field'\nimport type { SparkSwitchColor } from './switch'\n\ntype SwitchGroupProps = React.ComponentProps<'div'> & {\n styleVariant?: SparkStyle\n color?: SparkSwitchColor\n /** How many `--spark-layout-gap` units apart each switch sits. */\n gap?: number\n /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */\n listStyle?: SparkInputStyle\n}\n\n/** Lays out switches as one group; each switch keeps its own checked state. */\nfunction SwitchGroup({\n className,\n styleVariant,\n color,\n gap = 3,\n listStyle,\n style,\n ...props\n}: SwitchGroupProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n const invalid = useFieldSetInvalid()\n\n return (\n <div\n role=\"group\"\n data-slot=\"switch-group\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n data-list-style={listStyle}\n data-invalid={invalid}\n className={cn(\n 'grid',\n listStyle\n ? selectionListClasses[listStyle]\n : 'gap-[calc(var(--spark-layout-gap)*var(--switch-group-gap-steps,3))]',\n className,\n )}\n style={\n { '--switch-group-gap-steps': gap, ...style } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\nexport { SwitchGroup }\nexport type { SwitchGroupProps }\n"],"mappings":";;;;;;;;;AAkBA,SAAS,YAAY,EACnB,WACA,cACA,OACA,MAAM,GACN,WACA,OACA,GAAG,SACgB;CAMnB,OACE,oBAAC,OAAD;EACE,MAAK;EACL,aAAU;EACV,cATY,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;EASpD,qBARoB,QAAQ,cAAc,SAAS,KAAA;EASnD,mBAAiB;EACjB,gBATY,mBASQ;EACpB,WAAW,GACT,QACA,YACI,qBAAqB,aACrB,uEACJ,SACF;EACA,OACE;GAAE,4BAA4B;GAAK,GAAG;EAAM;EAE9C,GAAI;CACL,CAAA;AAEL"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { f as SparkStyle } from "../theme-types-Bn5E15-P.cjs";
3
+ import { g as SparkStyle } from "../theme-types-CKZzOmSa.cjs";
4
4
  import { Switch as Switch$1 } from "@base-ui/react/switch";
5
5
 
6
6
  //#region src/components/switch.d.ts
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { f as SparkStyle } from "../theme-types-Bn5E15-P.js";
3
+ import { g as SparkStyle } from "../theme-types-CKZzOmSa.js";
4
4
  import { Switch as Switch$1 } from "@base-ui/react/switch";
5
5
 
6
6
  //#region src/components/switch.d.ts
@@ -36,10 +36,11 @@ function Tabs(props) {
36
36
  ...props
37
37
  });
38
38
  }
39
+ const TabsVariantContext = react.createContext("underline");
39
40
  const REVEAL_PEEK = 40;
40
41
  const WORM_MS = 210;
41
42
  const TabsList = react.forwardRef(function TabsList({ className, style, variant = "underline", fill, ...props }, ref) {
42
- const resolved = variant === "pill" ? "pill" : "underline";
43
+ const resolved = variant === "pill" ? "pill" : variant === "portal" ? "portal" : "underline";
43
44
  const wrapperRef = react.useRef(null);
44
45
  const scrollerRef = react.useRef(null);
45
46
  const scrollerCallbackRef = react.useMemo(() => composeRefs(scrollerRef, ref), [ref]);
@@ -86,7 +87,7 @@ const TabsList = react.forwardRef(function TabsList({ className, style, variant
86
87
  }
87
88
  syncOverflow();
88
89
  if (active && changed) {
89
- reveal(active);
90
+ if (resolved !== "portal") reveal(active);
90
91
  lastActive = active;
91
92
  }
92
93
  mounted = true;
@@ -126,35 +127,43 @@ const TabsList = react.forwardRef(function TabsList({ className, style, variant
126
127
  className: (0, cn.cn)("touch-target absolute top-1/2 z-1 hidden size-10 -translate-y-1/2 cursor-pointer items-center justify-center rounded-[max(var(--spark-rounded-md),var(--spark-rounded-full))] bg-gray-1/72 text-gray-a11 backdrop-blur-sm hover:text-gray-12", dir === 1 ? "right-0 group-data-overflow-end/tabs:flex" : "left-0 group-data-overflow-start/tabs:flex"),
127
128
  children: dir === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_epilot_spark_icons.ChevronRight, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_epilot_spark_icons.ChevronLeft, {})
128
129
  });
129
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
130
- className: (0, cn.cn)("group/tabs relative max-w-full", fill ? "block w-full" : "inline-block", className),
131
- "data-fill": fill ? true : void 0,
132
- "data-slot": "tabs-list",
133
- "data-variant": resolved,
134
- ref: wrapperRef,
135
- style,
136
- children: [
137
- chevron(-1),
138
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_tabs.Tabs.List, {
139
- "data-slot": "tabs-scroller",
140
- ref: scrollerCallbackRef,
141
- className: (0, cn.cn)("relative no-scrollbar flex w-full items-stretch overflow-x-auto scroll-smooth motion-reduce:scroll-auto", resolved === "underline" && "gap-2", resolved === "underline" && "after:pointer-events-none after:absolute after:bottom-0 after:left-(--tabs-indicator-x,0px) after:h-1 after:w-(--tabs-indicator-w,0px) after:rounded-full after:bg-(--spark-tabs-indicator,var(--spark-indicator)) after:transition-none after:duration-200 after:ease-out after:content-[''] data-indicator-ready:after:transition-[left,width] motion-reduce:after:transition-none!", resolved === "pill" && "gap-0 rounded-md bg-gray-a3 p-1"),
142
- ...props
143
- }),
144
- chevron(1)
145
- ]
130
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabsVariantContext.Provider, {
131
+ value: resolved,
132
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
133
+ className: (0, cn.cn)("group/tabs relative max-w-full", fill ? "block w-full" : "inline-block", className),
134
+ "data-fill": fill ? true : void 0,
135
+ "data-slot": "tabs-list",
136
+ "data-variant": resolved,
137
+ ref: wrapperRef,
138
+ style,
139
+ children: [
140
+ resolved === "portal" ? null : chevron(-1),
141
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_tabs.Tabs.List, {
142
+ "data-slot": "tabs-scroller",
143
+ ref: scrollerCallbackRef,
144
+ className: (0, cn.cn)("relative flex", resolved === "portal" ? "h-10 items-center gap-4 overflow-visible text-gray" : "no-scrollbar w-full items-stretch overflow-x-auto scroll-smooth motion-reduce:scroll-auto", resolved === "portal" && (fill ? "w-full" : "w-fit"), resolved === "underline" && "gap-2", resolved === "underline" && "after:pointer-events-none after:absolute after:bottom-0 after:left-(--tabs-indicator-x,0px) after:h-1 after:w-(--tabs-indicator-w,0px) after:rounded-full after:bg-(--spark-tabs-indicator,var(--spark-indicator)) after:transition-none after:duration-200 after:ease-out after:content-[''] data-indicator-ready:after:transition-[left,width] motion-reduce:after:transition-none!", resolved === "underline" && "has-[[data-slot=tabs-trigger][data-active][aria-invalid=true]]:after:bg-input-invalid", resolved === "pill" && "gap-0 rounded-md bg-gray-a3 p-1"),
145
+ ...props
146
+ }),
147
+ resolved === "portal" ? null : chevron(1)
148
+ ]
149
+ })
146
150
  });
147
151
  });
148
152
  function TabsTrigger({ className, children, ...props }) {
149
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_tabs.Tabs.Tab, {
153
+ const variant = react.useContext(TabsVariantContext);
154
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_base_ui_react_tabs.Tabs.Tab, {
150
155
  "data-slot": "tabs-trigger",
151
- className: (0, cn.cn)("group/trigger touch-target inline-flex min-w-(--tabs-trigger-minw,0px) shrink-0 cursor-pointer scroll-mx-10 items-center justify-center whitespace-nowrap text-gray-12 transition-colors motion-reduce:transition-none", "group-data-fill/tabs:shrink group-data-fill/tabs:grow group-data-fill/tabs:basis-0", "data-active:text-(--spark-tabs-ink,var(--spark-a11))", "focus-visible:outline-hidden", "group-data-[variant=pill]/tabs:focus-visible:ring-2 group-data-[variant=pill]/tabs:focus-visible:ring-offset-1", "data-disabled:pointer-events-none data-disabled:text-gray-a8", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4", "group-data-[variant=underline]/tabs:min-h-12 group-data-[variant=underline]/tabs:px-1 group-data-[variant=underline]/tabs:text-base group-data-[variant=underline]/tabs:font-bold group-data-[variant=underline]/tabs:[--tabs-trigger-minw:5rem]", "group-data-[variant=underline]/tabs:border-b-2 group-data-[variant=underline]/tabs:border-gray-a5", "group-data-[variant=pill]/tabs:h-8 group-data-[variant=pill]/tabs:rounded-sm group-data-[variant=pill]/tabs:px-4 group-data-[variant=pill]/tabs:text-sm group-data-[variant=pill]/tabs:font-medium", "group-data-[variant=pill]/tabs:not-data-active:hover:bg-gray-a3", "group-data-[variant=pill]/tabs:data-active:bg-gray-1 group-data-[variant=pill]/tabs:data-active:shadow-sm dark:group-data-[variant=pill]/tabs:data-active:bg-gray-a4 dark:group-data-[variant=pill]/tabs:data-active:inset-ring dark:group-data-[variant=pill]/tabs:data-active:inset-ring-gray-a6", className),
156
+ className: (0, cn.cn)("group/trigger touch-target inline-flex shrink-0 cursor-pointer scroll-mx-10 items-center justify-center whitespace-nowrap", variant === "portal" ? "min-w-20 flex-col rounded-sm text-base font-bold text-gray transition-none first:ml-0.5 last:mr-6 focus-visible:ring-2 focus-visible:ring-offset-0 data-disabled:opacity-50" : "min-w-(--tabs-trigger-minw,0px) text-gray-12 transition-colors motion-reduce:transition-none", "group-data-fill/tabs:shrink group-data-fill/tabs:grow group-data-fill/tabs:basis-0", variant === "portal" ? null : "data-active:text-(--spark-tabs-ink,var(--spark-a11))", "focus-visible:outline-hidden", "group-data-[variant=pill]/tabs:focus-visible:ring-2 group-data-[variant=pill]/tabs:focus-visible:ring-offset-1", "data-disabled:pointer-events-none", variant === "portal" ? null : "data-disabled:text-gray-a8", "aria-invalid:text-error-soft-foreground group-data-[variant=underline]/tabs:aria-invalid:border-input-invalid data-active:aria-invalid:text-error-soft-foreground", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4", "group-data-[variant=underline]/tabs:min-h-12 group-data-[variant=underline]/tabs:px-1 group-data-[variant=underline]/tabs:text-base group-data-[variant=underline]/tabs:font-bold group-data-[variant=underline]/tabs:[--tabs-trigger-minw:5rem]", "group-data-[variant=underline]/tabs:border-b-2 group-data-[variant=underline]/tabs:border-gray-a5", "group-data-[variant=pill]/tabs:h-8 group-data-[variant=pill]/tabs:rounded-sm group-data-[variant=pill]/tabs:px-4 group-data-[variant=pill]/tabs:text-sm group-data-[variant=pill]/tabs:font-medium", "group-data-[variant=pill]/tabs:not-data-active:hover:bg-gray-a3", "group-data-[variant=pill]/tabs:data-active:bg-gray-1 group-data-[variant=pill]/tabs:data-active:shadow-sm dark:group-data-[variant=pill]/tabs:data-active:bg-gray-a4 dark:group-data-[variant=pill]/tabs:data-active:inset-ring dark:group-data-[variant=pill]/tabs:data-active:inset-ring-gray-a6", className),
152
157
  ...props,
153
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
158
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
154
159
  "data-slot": "tabs-trigger-inner",
155
160
  className: (0, cn.cn)("inline-flex items-center gap-1", "group-data-[variant=underline]/tabs:rounded-md group-data-[variant=underline]/tabs:px-2 group-data-[variant=underline]/tabs:py-1 group-data-[variant=underline]/tabs:transition-colors", "group-data-[variant=underline]/tabs:group-hover/trigger:bg-gray-a3", "group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-2 group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-offset-1"),
156
161
  children
157
- })
162
+ }), variant === "portal" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
163
+ "aria-hidden": "true",
164
+ "data-slot": "tabs-indicator",
165
+ className: "mt-1.5 flex h-1 w-full px-4 group-data-active/trigger:bg-base-accent-solid"
166
+ }) : null]
158
167
  });
159
168
  }
160
169
  /** @deprecated Use `TabsTrigger` — it adapts to the `TabsList` variant automatically. */
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.cjs","names":["React","TabsPrimitive","ChevronRight","ChevronLeft"],"sources":["../../src/components/tabs.tsx"],"sourcesContent":["import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'\nimport { ChevronLeft, ChevronRight } from '@epilot/spark-icons'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\n// useLayoutEffect warns during SSR; the isomorphic swap is the standard Radix idiom.\nconst useIsomorphicLayoutEffect =\n typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect\n\n// Merge our internal scroller ref with a consumer-forwarded ref. Written locally\n// and React 19-correct: it propagates cleanup-returning callback refs and nulls\n// plain refs on unmount.\ntype PossibleRef<T> = React.Ref<T> | undefined\n\nfunction setRef<T>(ref: PossibleRef<T>, value: T | null) {\n if (typeof ref === 'function') return ref(value)\n if (ref) ref.current = value\n return undefined\n}\n\nfunction composeRefs<T>(...refs: PossibleRef<T>[]) {\n return (node: T) => {\n let hasCleanup = false\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node)\n if (typeof cleanup === 'function') hasCleanup = true\n return cleanup\n })\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < refs.length; i++) {\n const cleanup = cleanups[i]\n if (typeof cleanup === 'function') cleanup()\n else setRef(refs[i], null)\n }\n }\n }\n return undefined\n }\n}\n\ntype TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>\n\nfunction Tabs(props: TabsProps) {\n return <TabsPrimitive.Root data-slot=\"tabs\" {...props} />\n}\n\ntype TabsListVariant = 'underline'\n\n/**\n * @deprecated Not part of Spark's tab design — use `'underline'`. `'pill'` (a\n * segmented control) and the old text-only `'default'` still render for\n * back-compat but are undocumented and may be removed.\n */\ntype TabsListVariantLegacy = 'default' | 'pill'\n\ntype TabsListProps = Omit<\n React.ComponentProps<typeof TabsPrimitive.List>,\n 'style'\n> & {\n /** `underline` — the Spark tab: bold label + a sliding accent bar. */\n // oxlint-disable-next-line typescript/no-deprecated -- public back-compat union, kept until removal\n variant?: TabsListVariant | TabsListVariantLegacy\n /**\n * Stretch the strip to its container's full width and divide it evenly\n * among the triggers, instead of shrinking to fit their labels. Each\n * trigger's `--tabs-trigger-minw` floor still applies — once there's no\n * longer room to keep every trigger at least that wide, the strip hands\n * over to the scroller exactly like the non-`fill` case.\n */\n fill?: boolean\n // style lands on the plain wrapper div, so no Base UI state-callback form\n style?: React.CSSProperties\n}\n\n// how much of a neighboring tab a scroll-into-view reveals (matches scroll-mx-10)\nconst REVEAL_PEEK = 40\n// worm indicator: the accent bar stretches across old+new, then contracts to the\n// new tab. WORM_MS must be >= the bar's CSS transition (200ms) so the stretch fully\n// settles before the contract begins — then contract sets left+width from rest in\n// one synced transition, pinning the leading edge so the motion never jiggles (a\n// contract that starts mid-stretch desyncs the two edges → wobble on big jumps).\nconst WORM_MS = 210\n\nconst TabsList = React.forwardRef<HTMLDivElement, Omit<TabsListProps, 'ref'>>(\n function TabsList(\n { className, style, variant = 'underline', fill, ...props },\n ref,\n ) {\n // 'underline' is the only real variant. Deprecated 'pill' still renders (a\n // segmented control, kept for back-compat); anything else (incl. the old\n // text-only 'default') folds into underline.\n const resolved = variant === 'pill' ? 'pill' : 'underline'\n const wrapperRef = React.useRef<HTMLDivElement | null>(null)\n const scrollerRef = React.useRef<HTMLDivElement | null>(null)\n // stable identity (keyed on the forwarded ref) so React doesn't detach/re-attach\n // the scroller ref on every render — TabsList re-renders on each selection.\n const scrollerCallbackRef = React.useMemo(\n () => composeRefs<HTMLDivElement>(scrollerRef, ref),\n [ref],\n )\n\n // Sliding underline + overflow state + travel-into-view. The bar lives on the\n // SCROLLER (::after scrolls with the content) and follows the active trigger via\n // two measured vars; useLayoutEffect measures before paint (no flash); the\n // MutationObserver tracks selection + children + text changes; the ResizeObserver\n // re-measures on container/scroller reflow. Together they cover everything that\n // moves a tab: selection, add/remove, a counter Badge growing, and resize.\n useIsomorphicLayoutEffect(() => {\n const wrapper = wrapperRef.current\n const scroller = scrollerRef.current\n if (!wrapper || !scroller) return\n // the edge chevrons show per side while more content hides in that direction\n const syncOverflow = () => {\n // 2px epsilon absorbs sub-pixel rounding at fractional browser zoom\n // (scrollWidth/clientWidth round to int, scrollLeft is fractional). Assumes\n // LTR (scrollLeft >= 0) — Spark's tab strips are LTR.\n const max = scroller.scrollWidth - scroller.clientWidth\n wrapper.toggleAttribute('data-overflow-start', scroller.scrollLeft > 2)\n wrapper.toggleAttribute(\n 'data-overflow-end',\n scroller.scrollLeft < max - 2,\n )\n }\n // scroller-local reveal — deliberately NOT scrollIntoView, which also scrolls\n // ancestors and would yank the page when the list is half off-screen. The peek\n // keeps the neighboring tab partially visible (\"there's more here\").\n const reveal = (el: HTMLElement) => {\n // jsdom (unit tests) doesn't implement Element.scrollTo\n if (typeof scroller.scrollTo !== 'function') return\n const start = el.offsetLeft - REVEAL_PEEK\n const end = el.offsetLeft + el.offsetWidth + REVEAL_PEEK\n if (start < scroller.scrollLeft) {\n scroller.scrollTo({ left: Math.max(0, start) })\n } else if (end > scroller.scrollLeft + scroller.clientWidth) {\n scroller.scrollTo({ left: end - scroller.clientWidth })\n }\n }\n // matchMedia guarded for jsdom (unit tests); read .matches live so toggling the\n // OS reduce-motion setting mid-session takes effect on the next selection.\n const motionQuery =\n typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-reduced-motion: reduce)')\n : null\n const setBar = (x: number, w: number) => {\n scroller.style.setProperty('--tabs-indicator-x', `${x}px`)\n scroller.style.setProperty('--tabs-indicator-w', `${w}px`)\n }\n let lastActive: HTMLElement | null = null\n let mounted = false\n let wormTimer: ReturnType<typeof setTimeout> | undefined\n const measure = () => {\n const active = scroller.querySelector<HTMLElement>(\n '[data-slot=\"tabs-trigger\"][data-active]',\n )\n const changed = active !== lastActive\n if (resolved === 'underline') {\n if (!active) {\n // no selection → collapse the bar (width 0), forget the previous tab, and\n // cancel any pending contract, so a later selection never worms from a\n // stale/removed node\n clearTimeout(wormTimer)\n lastActive = null\n scroller.style.setProperty('--tabs-indicator-w', '0px')\n } else if (\n changed &&\n lastActive?.isConnected &&\n mounted &&\n !motionQuery?.matches\n ) {\n // worm: stretch across old + new (one edge holds, the other reaches),\n // then contract to the new tab (the trailing edge catches up). The union\n // makes it direction-agnostic. isConnected guards dynamic tab removal — a\n // detached old node reports offsets of 0 and would sweep the bar from x=0.\n const from = Math.min(lastActive.offsetLeft, active.offsetLeft)\n const to = Math.max(\n lastActive.offsetLeft + lastActive.offsetWidth,\n active.offsetLeft + active.offsetWidth,\n )\n setBar(from, to - from)\n clearTimeout(wormTimer)\n const el = active\n wormTimer = setTimeout(\n () => setBar(el.offsetLeft, el.offsetWidth),\n WORM_MS,\n )\n } else {\n // first paint, reduced motion, detached previous, or a resize (same tab)\n // → settle on the active tab, no worm\n clearTimeout(wormTimer)\n setBar(active.offsetLeft, active.offsetWidth)\n }\n }\n syncOverflow()\n // travel the active tab into view on selection change — and on mount, so a\n // defaulted off-screen tab is visible. reveal() is scroller-local, so it never\n // scrolls the page/ancestors.\n if (active && changed) {\n reveal(active)\n lastActive = active\n }\n mounted = true\n }\n measure()\n const readyFrame = requestAnimationFrame(() =>\n scroller.setAttribute('data-indicator-ready', ''),\n )\n scroller.addEventListener('scroll', syncOverflow, { passive: true })\n const mo = new MutationObserver(measure)\n mo.observe(scroller, {\n subtree: true,\n childList: true,\n // characterData catches a trigger's text growing (e.g. a counter Badge 9→120):\n // in overflow the scroller's border-box is pinned, so that only changes\n // scrollWidth (never reported by the ResizeObserver) yet shifts every tab.\n characterData: true,\n attributeFilter: ['data-active'],\n })\n const ro = new ResizeObserver(measure)\n ro.observe(scroller)\n return () => {\n cancelAnimationFrame(readyFrame)\n scroller.removeAttribute('data-indicator-ready')\n scroller.removeEventListener('scroll', syncOverflow)\n clearTimeout(wormTimer)\n mo.disconnect()\n ro.disconnect()\n }\n }, [resolved])\n\n const scrollByPage = (dir: 1 | -1) => () => {\n const scroller = scrollerRef.current\n if (typeof scroller?.scrollBy !== 'function') return\n // behavior omitted → follows the scroller's CSS scroll-behavior (smooth,\n // motion-reduce → auto)\n scroller.scrollBy({ left: dir * scroller.clientWidth * 0.6 })\n }\n const chevron = (dir: 1 | -1) => (\n <button\n aria-hidden=\"true\"\n data-slot={dir === 1 ? 'tabs-scroll-next' : 'tabs-scroll-prev'}\n onClick={scrollByPage(dir)}\n tabIndex={-1}\n type=\"button\"\n className={cn(\n 'touch-target absolute top-1/2 z-1 hidden size-10 -translate-y-1/2 cursor-pointer items-center justify-center rounded-[max(var(--spark-rounded-md),var(--spark-rounded-full))] bg-gray-1/72 text-gray-a11 backdrop-blur-sm hover:text-gray-12',\n dir === 1\n ? 'right-0 group-data-overflow-end/tabs:flex'\n : 'left-0 group-data-overflow-start/tabs:flex',\n )}\n >\n {dir === 1 ? <ChevronRight /> : <ChevronLeft />}\n </button>\n )\n\n return (\n // the wrapper is the public box (data-slot tabs-list, consumer className +\n // style) — group/tabs + data-variant here drive the trigger's group-data-[…]/tabs\n // styling, and data-overflow-start/end (set above) show/hide the chevrons.\n // className + style both land here so a consumer's layout (margin/width) moves\n // the whole control, not just the inner scroller (which would offset the chevrons).\n <div\n className={cn(\n 'group/tabs relative max-w-full',\n fill ? 'block w-full' : 'inline-block',\n className,\n )}\n data-fill={fill ? true : undefined}\n data-slot=\"tabs-list\"\n data-variant={resolved}\n ref={wrapperRef}\n style={style}\n >\n {chevron(-1)}\n <TabsPrimitive.List\n data-slot=\"tabs-scroller\"\n ref={scrollerCallbackRef}\n className={cn(\n 'relative no-scrollbar flex w-full items-stretch overflow-x-auto scroll-smooth motion-reduce:scroll-auto',\n resolved === 'underline' && 'gap-2',\n resolved === 'underline' &&\n // transition only once data-indicator-ready is set (a frame after the\n // first measure) so the bar appears in place instead of sweeping in\n \"after:pointer-events-none after:absolute after:bottom-0 after:left-(--tabs-indicator-x,0px) after:h-1 after:w-(--tabs-indicator-w,0px) after:rounded-full after:bg-(--spark-tabs-indicator,var(--spark-indicator)) after:transition-none after:duration-200 after:ease-out after:content-[''] data-indicator-ready:after:transition-[left,width] motion-reduce:after:transition-none!\",\n resolved === 'pill' && 'gap-0 rounded-md bg-gray-a3 p-1',\n )}\n {...props}\n />\n {chevron(1)}\n </div>\n )\n },\n)\n\ntype TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Tab>\n\nfunction TabsTrigger({ className, children, ...props }: TabsTriggerProps) {\n return (\n <TabsPrimitive.Tab\n data-slot=\"tabs-trigger\"\n className={cn(\n // touch-target on the base, not per-variant: pill is 32px and underline\n // 48px only at scaling=100% (min-h-12 tracks the knob), so both want the\n // coarse-pointer floor. The overlay never shrinks an already-large box.\n 'group/trigger touch-target inline-flex min-w-(--tabs-trigger-minw,0px) shrink-0 cursor-pointer scroll-mx-10 items-center justify-center whitespace-nowrap text-gray-12 transition-colors motion-reduce:transition-none',\n // fill: divide the strip evenly instead of sizing to each label — the\n // min-w floor above still applies, so a strip too narrow to keep every\n // trigger at least that wide still hands over to the scroller.\n 'group-data-fill/tabs:shrink group-data-fill/tabs:grow group-data-fill/tabs:basis-0',\n 'data-active:text-(--spark-tabs-ink,var(--spark-a11))',\n 'focus-visible:outline-hidden',\n 'group-data-[variant=pill]/tabs:focus-visible:ring-2 group-data-[variant=pill]/tabs:focus-visible:ring-offset-1',\n 'data-disabled:pointer-events-none data-disabled:text-gray-a8',\n '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',\n 'group-data-[variant=underline]/tabs:min-h-12 group-data-[variant=underline]/tabs:px-1 group-data-[variant=underline]/tabs:text-base group-data-[variant=underline]/tabs:font-bold group-data-[variant=underline]/tabs:[--tabs-trigger-minw:5rem]',\n 'group-data-[variant=underline]/tabs:border-b-2 group-data-[variant=underline]/tabs:border-gray-a5',\n 'group-data-[variant=pill]/tabs:h-8 group-data-[variant=pill]/tabs:rounded-sm group-data-[variant=pill]/tabs:px-4 group-data-[variant=pill]/tabs:text-sm group-data-[variant=pill]/tabs:font-medium',\n 'group-data-[variant=pill]/tabs:not-data-active:hover:bg-gray-a3',\n 'group-data-[variant=pill]/tabs:data-active:bg-gray-1 group-data-[variant=pill]/tabs:data-active:shadow-sm dark:group-data-[variant=pill]/tabs:data-active:bg-gray-a4 dark:group-data-[variant=pill]/tabs:data-active:inset-ring dark:group-data-[variant=pill]/tabs:data-active:inset-ring-gray-a6',\n className,\n )}\n {...props}\n >\n {/* the Radix two-box anatomy: the hover paints this compact label box (with\n clearance above the bar/track), not the full 48px hit target */}\n <span\n data-slot=\"tabs-trigger-inner\"\n className={cn(\n 'inline-flex items-center gap-1',\n 'group-data-[variant=underline]/tabs:rounded-md group-data-[variant=underline]/tabs:px-2 group-data-[variant=underline]/tabs:py-1 group-data-[variant=underline]/tabs:transition-colors',\n 'group-data-[variant=underline]/tabs:group-hover/trigger:bg-gray-a3',\n 'group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-2 group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-offset-1',\n )}\n >\n {children}\n </span>\n </TabsPrimitive.Tab>\n )\n}\n\n/** @deprecated Use `TabsTrigger` — it adapts to the `TabsList` variant automatically. */\nexport const TabsTriggerUnderline = TabsTrigger\n\n/** @deprecated Use `TabsTrigger` — it adapts to the `TabsList` variant automatically. */\nexport const TabsTriggerPill = TabsTrigger\n\ntype TabsContentProps = React.ComponentProps<typeof TabsPrimitive.Panel>\n\nfunction TabsContent({ className, ...props }: TabsContentProps) {\n return (\n <TabsPrimitive.Panel\n data-slot=\"tabs-content\"\n className={cn(\n 'focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:outline-hidden',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger }\nexport type { TabsListProps, TabsListVariant, TabsProps, TabsTriggerProps }\n"],"mappings":"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"}
1
+ {"version":3,"file":"tabs.cjs","names":["React","TabsPrimitive","ChevronRight","ChevronLeft"],"sources":["../../src/components/tabs.tsx"],"sourcesContent":["import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'\nimport { ChevronLeft, ChevronRight } from '@epilot/spark-icons'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\n// useLayoutEffect warns during SSR; the isomorphic swap is the standard Radix idiom.\nconst useIsomorphicLayoutEffect =\n typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect\n\n// Merge our internal scroller ref with a consumer-forwarded ref. Written locally\n// and React 19-correct: it propagates cleanup-returning callback refs and nulls\n// plain refs on unmount.\ntype PossibleRef<T> = React.Ref<T> | undefined\n\nfunction setRef<T>(ref: PossibleRef<T>, value: T | null) {\n if (typeof ref === 'function') return ref(value)\n if (ref) ref.current = value\n return undefined\n}\n\nfunction composeRefs<T>(...refs: PossibleRef<T>[]) {\n return (node: T) => {\n let hasCleanup = false\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node)\n if (typeof cleanup === 'function') hasCleanup = true\n return cleanup\n })\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < refs.length; i++) {\n const cleanup = cleanups[i]\n if (typeof cleanup === 'function') cleanup()\n else setRef(refs[i], null)\n }\n }\n }\n return undefined\n }\n}\n\ntype TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>\n\nfunction Tabs(props: TabsProps) {\n return <TabsPrimitive.Root data-slot=\"tabs\" {...props} />\n}\n\ntype TabsListVariant = 'underline' | 'portal'\n\n/**\n * @deprecated Not part of Spark's tab design — use `'underline'` or `'portal'`.\n * `'pill'` (a segmented control) and the old text-only `'default'` still render\n * for back-compat but are undocumented and may be removed.\n */\ntype TabsListVariantLegacy = 'default' | 'pill'\n\ntype ResolvedTabsVariant = 'underline' | 'pill' | 'portal'\n\nconst TabsVariantContext = React.createContext<ResolvedTabsVariant>('underline')\n\ntype TabsListProps = Omit<\n React.ComponentProps<typeof TabsPrimitive.List>,\n 'style'\n> & {\n /** `underline` — bold label and a sliding accent bar. `portal` — the end-customer portal tab: bold label, accent bar under the active tab only, no motion. */\n // oxlint-disable-next-line typescript/no-deprecated -- public back-compat union, kept until removal\n variant?: TabsListVariant | TabsListVariantLegacy\n /**\n * Stretch the strip to its container's full width and divide it evenly\n * among the triggers, instead of shrinking to fit their labels. Each\n * trigger's `--tabs-trigger-minw` floor still applies — once there's no\n * longer room to keep every trigger at least that wide, the strip hands\n * over to the scroller exactly like the non-`fill` case.\n */\n fill?: boolean\n // style lands on the plain wrapper div, so no Base UI state-callback form\n style?: React.CSSProperties\n}\n\n// how much of a neighboring tab a scroll-into-view reveals (matches scroll-mx-10)\nconst REVEAL_PEEK = 40\n// worm indicator: the accent bar stretches across old+new, then contracts to the\n// new tab. WORM_MS must be >= the bar's CSS transition (200ms) so the stretch fully\n// settles before the contract begins — then contract sets left+width from rest in\n// one synced transition, pinning the leading edge so the motion never jiggles (a\n// contract that starts mid-stretch desyncs the two edges → wobble on big jumps).\nconst WORM_MS = 210\n\nconst TabsList = React.forwardRef<HTMLDivElement, Omit<TabsListProps, 'ref'>>(\n function TabsList(\n { className, style, variant = 'underline', fill, ...props },\n ref,\n ) {\n const resolved: ResolvedTabsVariant =\n variant === 'pill'\n ? 'pill'\n : variant === 'portal'\n ? 'portal'\n : 'underline'\n const wrapperRef = React.useRef<HTMLDivElement | null>(null)\n const scrollerRef = React.useRef<HTMLDivElement | null>(null)\n // stable identity (keyed on the forwarded ref) so React doesn't detach/re-attach\n // the scroller ref on every render — TabsList re-renders on each selection.\n const scrollerCallbackRef = React.useMemo(\n () => composeRefs<HTMLDivElement>(scrollerRef, ref),\n [ref],\n )\n\n // Sliding underline + overflow state + travel-into-view. The bar lives on the\n // SCROLLER (::after scrolls with the content) and follows the active trigger via\n // two measured vars; useLayoutEffect measures before paint (no flash); the\n // MutationObserver tracks selection + children + text changes; the ResizeObserver\n // re-measures on container/scroller reflow. Together they cover everything that\n // moves a tab: selection, add/remove, a counter Badge growing, and resize.\n useIsomorphicLayoutEffect(() => {\n const wrapper = wrapperRef.current\n const scroller = scrollerRef.current\n if (!wrapper || !scroller) return\n // the edge chevrons show per side while more content hides in that direction\n const syncOverflow = () => {\n // 2px epsilon absorbs sub-pixel rounding at fractional browser zoom\n // (scrollWidth/clientWidth round to int, scrollLeft is fractional). Assumes\n // LTR (scrollLeft >= 0) — Spark's tab strips are LTR.\n const max = scroller.scrollWidth - scroller.clientWidth\n wrapper.toggleAttribute('data-overflow-start', scroller.scrollLeft > 2)\n wrapper.toggleAttribute(\n 'data-overflow-end',\n scroller.scrollLeft < max - 2,\n )\n }\n // scroller-local reveal — deliberately NOT scrollIntoView, which also scrolls\n // ancestors and would yank the page when the list is half off-screen. The peek\n // keeps the neighboring tab partially visible (\"there's more here\").\n const reveal = (el: HTMLElement) => {\n // jsdom (unit tests) doesn't implement Element.scrollTo\n if (typeof scroller.scrollTo !== 'function') return\n const start = el.offsetLeft - REVEAL_PEEK\n const end = el.offsetLeft + el.offsetWidth + REVEAL_PEEK\n if (start < scroller.scrollLeft) {\n scroller.scrollTo({ left: Math.max(0, start) })\n } else if (end > scroller.scrollLeft + scroller.clientWidth) {\n scroller.scrollTo({ left: end - scroller.clientWidth })\n }\n }\n // matchMedia guarded for jsdom (unit tests); read .matches live so toggling the\n // OS reduce-motion setting mid-session takes effect on the next selection.\n const motionQuery =\n typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-reduced-motion: reduce)')\n : null\n const setBar = (x: number, w: number) => {\n scroller.style.setProperty('--tabs-indicator-x', `${x}px`)\n scroller.style.setProperty('--tabs-indicator-w', `${w}px`)\n }\n let lastActive: HTMLElement | null = null\n let mounted = false\n let wormTimer: ReturnType<typeof setTimeout> | undefined\n const measure = () => {\n const active = scroller.querySelector<HTMLElement>(\n '[data-slot=\"tabs-trigger\"][data-active]',\n )\n const changed = active !== lastActive\n if (resolved === 'underline') {\n if (!active) {\n // no selection → collapse the bar (width 0), forget the previous tab, and\n // cancel any pending contract, so a later selection never worms from a\n // stale/removed node\n clearTimeout(wormTimer)\n lastActive = null\n scroller.style.setProperty('--tabs-indicator-w', '0px')\n } else if (\n changed &&\n lastActive?.isConnected &&\n mounted &&\n !motionQuery?.matches\n ) {\n // worm: stretch across old + new (one edge holds, the other reaches),\n // then contract to the new tab (the trailing edge catches up). The union\n // makes it direction-agnostic. isConnected guards dynamic tab removal — a\n // detached old node reports offsets of 0 and would sweep the bar from x=0.\n const from = Math.min(lastActive.offsetLeft, active.offsetLeft)\n const to = Math.max(\n lastActive.offsetLeft + lastActive.offsetWidth,\n active.offsetLeft + active.offsetWidth,\n )\n setBar(from, to - from)\n clearTimeout(wormTimer)\n const el = active\n wormTimer = setTimeout(\n () => setBar(el.offsetLeft, el.offsetWidth),\n WORM_MS,\n )\n } else {\n // first paint, reduced motion, detached previous, or a resize (same tab)\n // → settle on the active tab, no worm\n clearTimeout(wormTimer)\n setBar(active.offsetLeft, active.offsetWidth)\n }\n }\n syncOverflow()\n // travel the active tab into view on selection change — and on mount, so a\n // defaulted off-screen tab is visible. reveal() is scroller-local, so it never\n // scrolls the page/ancestors.\n if (active && changed) {\n if (resolved !== 'portal') reveal(active)\n lastActive = active\n }\n mounted = true\n }\n measure()\n const readyFrame = requestAnimationFrame(() =>\n scroller.setAttribute('data-indicator-ready', ''),\n )\n scroller.addEventListener('scroll', syncOverflow, { passive: true })\n const mo = new MutationObserver(measure)\n mo.observe(scroller, {\n subtree: true,\n childList: true,\n // characterData catches a trigger's text growing (e.g. a counter Badge 9→120):\n // in overflow the scroller's border-box is pinned, so that only changes\n // scrollWidth (never reported by the ResizeObserver) yet shifts every tab.\n characterData: true,\n attributeFilter: ['data-active'],\n })\n const ro = new ResizeObserver(measure)\n ro.observe(scroller)\n return () => {\n cancelAnimationFrame(readyFrame)\n scroller.removeAttribute('data-indicator-ready')\n scroller.removeEventListener('scroll', syncOverflow)\n clearTimeout(wormTimer)\n mo.disconnect()\n ro.disconnect()\n }\n }, [resolved])\n\n const scrollByPage = (dir: 1 | -1) => () => {\n const scroller = scrollerRef.current\n if (typeof scroller?.scrollBy !== 'function') return\n // behavior omitted → follows the scroller's CSS scroll-behavior (smooth,\n // motion-reduce → auto)\n scroller.scrollBy({ left: dir * scroller.clientWidth * 0.6 })\n }\n const chevron = (dir: 1 | -1) => (\n <button\n aria-hidden=\"true\"\n data-slot={dir === 1 ? 'tabs-scroll-next' : 'tabs-scroll-prev'}\n onClick={scrollByPage(dir)}\n tabIndex={-1}\n type=\"button\"\n className={cn(\n 'touch-target absolute top-1/2 z-1 hidden size-10 -translate-y-1/2 cursor-pointer items-center justify-center rounded-[max(var(--spark-rounded-md),var(--spark-rounded-full))] bg-gray-1/72 text-gray-a11 backdrop-blur-sm hover:text-gray-12',\n dir === 1\n ? 'right-0 group-data-overflow-end/tabs:flex'\n : 'left-0 group-data-overflow-start/tabs:flex',\n )}\n >\n {dir === 1 ? <ChevronRight /> : <ChevronLeft />}\n </button>\n )\n\n return (\n // the wrapper is the public box (data-slot tabs-list, consumer className +\n // style) — group/tabs + data-variant here drive the trigger's group-data-[…]/tabs\n // styling, and data-overflow-start/end (set above) show/hide the chevrons.\n // className + style both land here so a consumer's layout (margin/width) moves\n // the whole control, not just the inner scroller (which would offset the chevrons).\n <TabsVariantContext.Provider value={resolved}>\n <div\n className={cn(\n 'group/tabs relative max-w-full',\n fill ? 'block w-full' : 'inline-block',\n className,\n )}\n data-fill={fill ? true : undefined}\n data-slot=\"tabs-list\"\n data-variant={resolved}\n ref={wrapperRef}\n style={style}\n >\n {resolved === 'portal' ? null : chevron(-1)}\n <TabsPrimitive.List\n data-slot=\"tabs-scroller\"\n ref={scrollerCallbackRef}\n className={cn(\n 'relative flex',\n resolved === 'portal'\n ? 'h-10 items-center gap-4 overflow-visible text-gray'\n : 'no-scrollbar w-full items-stretch overflow-x-auto scroll-smooth motion-reduce:scroll-auto',\n resolved === 'portal' && (fill ? 'w-full' : 'w-fit'),\n resolved === 'underline' && 'gap-2',\n resolved === 'underline' &&\n // transition only once data-indicator-ready is set (a frame after the\n // first measure) so the bar appears in place instead of sweeping in\n \"after:pointer-events-none after:absolute after:bottom-0 after:left-(--tabs-indicator-x,0px) after:h-1 after:w-(--tabs-indicator-w,0px) after:rounded-full after:bg-(--spark-tabs-indicator,var(--spark-indicator)) after:transition-none after:duration-200 after:ease-out after:content-[''] data-indicator-ready:after:transition-[left,width] motion-reduce:after:transition-none!\",\n resolved === 'underline' &&\n 'has-[[data-slot=tabs-trigger][data-active][aria-invalid=true]]:after:bg-input-invalid',\n resolved === 'pill' && 'gap-0 rounded-md bg-gray-a3 p-1',\n )}\n {...props}\n />\n {resolved === 'portal' ? null : chevron(1)}\n </div>\n </TabsVariantContext.Provider>\n )\n },\n)\n\ntype TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Tab>\n\nfunction TabsTrigger({ className, children, ...props }: TabsTriggerProps) {\n const variant = React.useContext(TabsVariantContext)\n return (\n <TabsPrimitive.Tab\n data-slot=\"tabs-trigger\"\n className={cn(\n // touch-target on the base, not per-variant: pill is 32px and underline\n // 48px only at scaling=100% (min-h-12 tracks the knob), so both want the\n // coarse-pointer floor. The overlay never shrinks an already-large box.\n 'group/trigger touch-target inline-flex shrink-0 cursor-pointer scroll-mx-10 items-center justify-center whitespace-nowrap',\n variant === 'portal'\n ? 'min-w-20 flex-col rounded-sm text-base font-bold text-gray transition-none first:ml-0.5 last:mr-6 focus-visible:ring-2 focus-visible:ring-offset-0 data-disabled:opacity-50'\n : 'min-w-(--tabs-trigger-minw,0px) text-gray-12 transition-colors motion-reduce:transition-none',\n // fill: divide the strip evenly instead of sizing to each label — the\n // min-w floor above still applies, so a strip too narrow to keep every\n // trigger at least that wide still hands over to the scroller.\n 'group-data-fill/tabs:shrink group-data-fill/tabs:grow group-data-fill/tabs:basis-0',\n variant === 'portal'\n ? null\n : 'data-active:text-(--spark-tabs-ink,var(--spark-a11))',\n 'focus-visible:outline-hidden',\n 'group-data-[variant=pill]/tabs:focus-visible:ring-2 group-data-[variant=pill]/tabs:focus-visible:ring-offset-1',\n 'data-disabled:pointer-events-none',\n variant === 'portal' ? null : 'data-disabled:text-gray-a8',\n 'aria-invalid:text-error-soft-foreground group-data-[variant=underline]/tabs:aria-invalid:border-input-invalid data-active:aria-invalid:text-error-soft-foreground',\n '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',\n 'group-data-[variant=underline]/tabs:min-h-12 group-data-[variant=underline]/tabs:px-1 group-data-[variant=underline]/tabs:text-base group-data-[variant=underline]/tabs:font-bold group-data-[variant=underline]/tabs:[--tabs-trigger-minw:5rem]',\n 'group-data-[variant=underline]/tabs:border-b-2 group-data-[variant=underline]/tabs:border-gray-a5',\n 'group-data-[variant=pill]/tabs:h-8 group-data-[variant=pill]/tabs:rounded-sm group-data-[variant=pill]/tabs:px-4 group-data-[variant=pill]/tabs:text-sm group-data-[variant=pill]/tabs:font-medium',\n 'group-data-[variant=pill]/tabs:not-data-active:hover:bg-gray-a3',\n 'group-data-[variant=pill]/tabs:data-active:bg-gray-1 group-data-[variant=pill]/tabs:data-active:shadow-sm dark:group-data-[variant=pill]/tabs:data-active:bg-gray-a4 dark:group-data-[variant=pill]/tabs:data-active:inset-ring dark:group-data-[variant=pill]/tabs:data-active:inset-ring-gray-a6',\n className,\n )}\n {...props}\n >\n {/* the Radix two-box anatomy: the hover paints this compact label box (with\n clearance above the bar/track), not the full 48px hit target */}\n <span\n data-slot=\"tabs-trigger-inner\"\n className={cn(\n 'inline-flex items-center gap-1',\n 'group-data-[variant=underline]/tabs:rounded-md group-data-[variant=underline]/tabs:px-2 group-data-[variant=underline]/tabs:py-1 group-data-[variant=underline]/tabs:transition-colors',\n 'group-data-[variant=underline]/tabs:group-hover/trigger:bg-gray-a3',\n 'group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-2 group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-offset-1',\n )}\n >\n {children}\n </span>\n {variant === 'portal' ? (\n <span\n aria-hidden=\"true\"\n data-slot=\"tabs-indicator\"\n className=\"mt-1.5 flex h-1 w-full px-4 group-data-active/trigger:bg-base-accent-solid\"\n />\n ) : null}\n </TabsPrimitive.Tab>\n )\n}\n\n/** @deprecated Use `TabsTrigger` — it adapts to the `TabsList` variant automatically. */\nexport const TabsTriggerUnderline = TabsTrigger\n\n/** @deprecated Use `TabsTrigger` — it adapts to the `TabsList` variant automatically. */\nexport const TabsTriggerPill = TabsTrigger\n\ntype TabsContentProps = React.ComponentProps<typeof TabsPrimitive.Panel>\n\nfunction TabsContent({ className, ...props }: TabsContentProps) {\n return (\n <TabsPrimitive.Panel\n data-slot=\"tabs-content\"\n className={cn(\n 'focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:outline-hidden',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger }\nexport type { TabsListProps, TabsListVariant, TabsProps, TabsTriggerProps }\n"],"mappings":"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"}
@@ -6,15 +6,15 @@ import { Tabs as Tabs$1 } from "@base-ui/react/tabs";
6
6
  //#region src/components/tabs.d.ts
7
7
  type TabsProps = React$1.ComponentProps<typeof Tabs$1.Root>;
8
8
  declare function Tabs(props: TabsProps): import("react/jsx-runtime").JSX.Element;
9
- type TabsListVariant = 'underline';
9
+ type TabsListVariant = 'underline' | 'portal';
10
10
  /**
11
- * @deprecated Not part of Spark's tab design — use `'underline'`. `'pill'` (a
12
- * segmented control) and the old text-only `'default'` still render for
13
- * back-compat but are undocumented and may be removed.
11
+ * @deprecated Not part of Spark's tab design — use `'underline'` or `'portal'`.
12
+ * `'pill'` (a segmented control) and the old text-only `'default'` still render
13
+ * for back-compat but are undocumented and may be removed.
14
14
  */
15
15
  type TabsListVariantLegacy = 'default' | 'pill';
16
16
  type TabsListProps = Omit<React$1.ComponentProps<typeof Tabs$1.List>, 'style'> & {
17
- /** `underline` — the Spark tab: bold label + a sliding accent bar. */variant?: TabsListVariant | TabsListVariantLegacy;
17
+ /** `underline` — bold label and a sliding accent bar. `portal` — the end-customer portal tab: bold label, accent bar under the active tab only, no motion. */variant?: TabsListVariant | TabsListVariantLegacy;
18
18
  /**
19
19
  * Stretch the strip to its container's full width and divide it evenly
20
20
  * among the triggers, instead of shrinking to fit their labels. Each
@@ -6,15 +6,15 @@ import { Tabs as Tabs$1 } from "@base-ui/react/tabs";
6
6
  //#region src/components/tabs.d.ts
7
7
  type TabsProps = React$1.ComponentProps<typeof Tabs$1.Root>;
8
8
  declare function Tabs(props: TabsProps): import("react/jsx-runtime").JSX.Element;
9
- type TabsListVariant = 'underline';
9
+ type TabsListVariant = 'underline' | 'portal';
10
10
  /**
11
- * @deprecated Not part of Spark's tab design — use `'underline'`. `'pill'` (a
12
- * segmented control) and the old text-only `'default'` still render for
13
- * back-compat but are undocumented and may be removed.
11
+ * @deprecated Not part of Spark's tab design — use `'underline'` or `'portal'`.
12
+ * `'pill'` (a segmented control) and the old text-only `'default'` still render
13
+ * for back-compat but are undocumented and may be removed.
14
14
  */
15
15
  type TabsListVariantLegacy = 'default' | 'pill';
16
16
  type TabsListProps = Omit<React$1.ComponentProps<typeof Tabs$1.List>, 'style'> & {
17
- /** `underline` — the Spark tab: bold label + a sliding accent bar. */variant?: TabsListVariant | TabsListVariantLegacy;
17
+ /** `underline` — bold label and a sliding accent bar. `portal` — the end-customer portal tab: bold label, accent bar under the active tab only, no motion. */variant?: TabsListVariant | TabsListVariantLegacy;
18
18
  /**
19
19
  * Stretch the strip to its container's full width and divide it evenly
20
20
  * among the triggers, instead of shrinking to fit their labels. Each
@@ -33,10 +33,11 @@ function Tabs(props) {
33
33
  ...props
34
34
  });
35
35
  }
36
+ const TabsVariantContext = React$1.createContext("underline");
36
37
  const REVEAL_PEEK = 40;
37
38
  const WORM_MS = 210;
38
39
  const TabsList = React$1.forwardRef(function TabsList({ className, style, variant = "underline", fill, ...props }, ref) {
39
- const resolved = variant === "pill" ? "pill" : "underline";
40
+ const resolved = variant === "pill" ? "pill" : variant === "portal" ? "portal" : "underline";
40
41
  const wrapperRef = React$1.useRef(null);
41
42
  const scrollerRef = React$1.useRef(null);
42
43
  const scrollerCallbackRef = React$1.useMemo(() => composeRefs(scrollerRef, ref), [ref]);
@@ -83,7 +84,7 @@ const TabsList = React$1.forwardRef(function TabsList({ className, style, varian
83
84
  }
84
85
  syncOverflow();
85
86
  if (active && changed) {
86
- reveal(active);
87
+ if (resolved !== "portal") reveal(active);
87
88
  lastActive = active;
88
89
  }
89
90
  mounted = true;
@@ -123,35 +124,43 @@ const TabsList = React$1.forwardRef(function TabsList({ className, style, varian
123
124
  className: cn("touch-target absolute top-1/2 z-1 hidden size-10 -translate-y-1/2 cursor-pointer items-center justify-center rounded-[max(var(--spark-rounded-md),var(--spark-rounded-full))] bg-gray-1/72 text-gray-a11 backdrop-blur-sm hover:text-gray-12", dir === 1 ? "right-0 group-data-overflow-end/tabs:flex" : "left-0 group-data-overflow-start/tabs:flex"),
124
125
  children: dir === 1 ? /* @__PURE__ */ jsx(ChevronRight, {}) : /* @__PURE__ */ jsx(ChevronLeft, {})
125
126
  });
126
- return /* @__PURE__ */ jsxs("div", {
127
- className: cn("group/tabs relative max-w-full", fill ? "block w-full" : "inline-block", className),
128
- "data-fill": fill ? true : void 0,
129
- "data-slot": "tabs-list",
130
- "data-variant": resolved,
131
- ref: wrapperRef,
132
- style,
133
- children: [
134
- chevron(-1),
135
- /* @__PURE__ */ jsx(Tabs$1.List, {
136
- "data-slot": "tabs-scroller",
137
- ref: scrollerCallbackRef,
138
- className: cn("relative no-scrollbar flex w-full items-stretch overflow-x-auto scroll-smooth motion-reduce:scroll-auto", resolved === "underline" && "gap-2", resolved === "underline" && "after:pointer-events-none after:absolute after:bottom-0 after:left-(--tabs-indicator-x,0px) after:h-1 after:w-(--tabs-indicator-w,0px) after:rounded-full after:bg-(--spark-tabs-indicator,var(--spark-indicator)) after:transition-none after:duration-200 after:ease-out after:content-[''] data-indicator-ready:after:transition-[left,width] motion-reduce:after:transition-none!", resolved === "pill" && "gap-0 rounded-md bg-gray-a3 p-1"),
139
- ...props
140
- }),
141
- chevron(1)
142
- ]
127
+ return /* @__PURE__ */ jsx(TabsVariantContext.Provider, {
128
+ value: resolved,
129
+ children: /* @__PURE__ */ jsxs("div", {
130
+ className: cn("group/tabs relative max-w-full", fill ? "block w-full" : "inline-block", className),
131
+ "data-fill": fill ? true : void 0,
132
+ "data-slot": "tabs-list",
133
+ "data-variant": resolved,
134
+ ref: wrapperRef,
135
+ style,
136
+ children: [
137
+ resolved === "portal" ? null : chevron(-1),
138
+ /* @__PURE__ */ jsx(Tabs$1.List, {
139
+ "data-slot": "tabs-scroller",
140
+ ref: scrollerCallbackRef,
141
+ className: cn("relative flex", resolved === "portal" ? "h-10 items-center gap-4 overflow-visible text-gray" : "no-scrollbar w-full items-stretch overflow-x-auto scroll-smooth motion-reduce:scroll-auto", resolved === "portal" && (fill ? "w-full" : "w-fit"), resolved === "underline" && "gap-2", resolved === "underline" && "after:pointer-events-none after:absolute after:bottom-0 after:left-(--tabs-indicator-x,0px) after:h-1 after:w-(--tabs-indicator-w,0px) after:rounded-full after:bg-(--spark-tabs-indicator,var(--spark-indicator)) after:transition-none after:duration-200 after:ease-out after:content-[''] data-indicator-ready:after:transition-[left,width] motion-reduce:after:transition-none!", resolved === "underline" && "has-[[data-slot=tabs-trigger][data-active][aria-invalid=true]]:after:bg-input-invalid", resolved === "pill" && "gap-0 rounded-md bg-gray-a3 p-1"),
142
+ ...props
143
+ }),
144
+ resolved === "portal" ? null : chevron(1)
145
+ ]
146
+ })
143
147
  });
144
148
  });
145
149
  function TabsTrigger({ className, children, ...props }) {
146
- return /* @__PURE__ */ jsx(Tabs$1.Tab, {
150
+ const variant = React$1.useContext(TabsVariantContext);
151
+ return /* @__PURE__ */ jsxs(Tabs$1.Tab, {
147
152
  "data-slot": "tabs-trigger",
148
- className: cn("group/trigger touch-target inline-flex min-w-(--tabs-trigger-minw,0px) shrink-0 cursor-pointer scroll-mx-10 items-center justify-center whitespace-nowrap text-gray-12 transition-colors motion-reduce:transition-none", "group-data-fill/tabs:shrink group-data-fill/tabs:grow group-data-fill/tabs:basis-0", "data-active:text-(--spark-tabs-ink,var(--spark-a11))", "focus-visible:outline-hidden", "group-data-[variant=pill]/tabs:focus-visible:ring-2 group-data-[variant=pill]/tabs:focus-visible:ring-offset-1", "data-disabled:pointer-events-none data-disabled:text-gray-a8", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4", "group-data-[variant=underline]/tabs:min-h-12 group-data-[variant=underline]/tabs:px-1 group-data-[variant=underline]/tabs:text-base group-data-[variant=underline]/tabs:font-bold group-data-[variant=underline]/tabs:[--tabs-trigger-minw:5rem]", "group-data-[variant=underline]/tabs:border-b-2 group-data-[variant=underline]/tabs:border-gray-a5", "group-data-[variant=pill]/tabs:h-8 group-data-[variant=pill]/tabs:rounded-sm group-data-[variant=pill]/tabs:px-4 group-data-[variant=pill]/tabs:text-sm group-data-[variant=pill]/tabs:font-medium", "group-data-[variant=pill]/tabs:not-data-active:hover:bg-gray-a3", "group-data-[variant=pill]/tabs:data-active:bg-gray-1 group-data-[variant=pill]/tabs:data-active:shadow-sm dark:group-data-[variant=pill]/tabs:data-active:bg-gray-a4 dark:group-data-[variant=pill]/tabs:data-active:inset-ring dark:group-data-[variant=pill]/tabs:data-active:inset-ring-gray-a6", className),
153
+ className: cn("group/trigger touch-target inline-flex shrink-0 cursor-pointer scroll-mx-10 items-center justify-center whitespace-nowrap", variant === "portal" ? "min-w-20 flex-col rounded-sm text-base font-bold text-gray transition-none first:ml-0.5 last:mr-6 focus-visible:ring-2 focus-visible:ring-offset-0 data-disabled:opacity-50" : "min-w-(--tabs-trigger-minw,0px) text-gray-12 transition-colors motion-reduce:transition-none", "group-data-fill/tabs:shrink group-data-fill/tabs:grow group-data-fill/tabs:basis-0", variant === "portal" ? null : "data-active:text-(--spark-tabs-ink,var(--spark-a11))", "focus-visible:outline-hidden", "group-data-[variant=pill]/tabs:focus-visible:ring-2 group-data-[variant=pill]/tabs:focus-visible:ring-offset-1", "data-disabled:pointer-events-none", variant === "portal" ? null : "data-disabled:text-gray-a8", "aria-invalid:text-error-soft-foreground group-data-[variant=underline]/tabs:aria-invalid:border-input-invalid data-active:aria-invalid:text-error-soft-foreground", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4", "group-data-[variant=underline]/tabs:min-h-12 group-data-[variant=underline]/tabs:px-1 group-data-[variant=underline]/tabs:text-base group-data-[variant=underline]/tabs:font-bold group-data-[variant=underline]/tabs:[--tabs-trigger-minw:5rem]", "group-data-[variant=underline]/tabs:border-b-2 group-data-[variant=underline]/tabs:border-gray-a5", "group-data-[variant=pill]/tabs:h-8 group-data-[variant=pill]/tabs:rounded-sm group-data-[variant=pill]/tabs:px-4 group-data-[variant=pill]/tabs:text-sm group-data-[variant=pill]/tabs:font-medium", "group-data-[variant=pill]/tabs:not-data-active:hover:bg-gray-a3", "group-data-[variant=pill]/tabs:data-active:bg-gray-1 group-data-[variant=pill]/tabs:data-active:shadow-sm dark:group-data-[variant=pill]/tabs:data-active:bg-gray-a4 dark:group-data-[variant=pill]/tabs:data-active:inset-ring dark:group-data-[variant=pill]/tabs:data-active:inset-ring-gray-a6", className),
149
154
  ...props,
150
- children: /* @__PURE__ */ jsx("span", {
155
+ children: [/* @__PURE__ */ jsx("span", {
151
156
  "data-slot": "tabs-trigger-inner",
152
157
  className: cn("inline-flex items-center gap-1", "group-data-[variant=underline]/tabs:rounded-md group-data-[variant=underline]/tabs:px-2 group-data-[variant=underline]/tabs:py-1 group-data-[variant=underline]/tabs:transition-colors", "group-data-[variant=underline]/tabs:group-hover/trigger:bg-gray-a3", "group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-2 group-data-[variant=underline]/tabs:group-focus-visible/trigger:ring-offset-1"),
153
158
  children
154
- })
159
+ }), variant === "portal" ? /* @__PURE__ */ jsx("span", {
160
+ "aria-hidden": "true",
161
+ "data-slot": "tabs-indicator",
162
+ className: "mt-1.5 flex h-1 w-full px-4 group-data-active/trigger:bg-base-accent-solid"
163
+ }) : null]
155
164
  });
156
165
  }
157
166
  /** @deprecated Use `TabsTrigger` — it adapts to the `TabsList` variant automatically. */