@charcuterie/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +161 -0
  3. package/dist/Alert/Alert.d.ts +88 -0
  4. package/dist/Alert/Alert.d.ts.map +1 -0
  5. package/dist/Alert/Alert.js +67 -0
  6. package/dist/Alert/Alert.js.map +1 -0
  7. package/dist/Badge/Badge.d.ts +54 -0
  8. package/dist/Badge/Badge.d.ts.map +1 -0
  9. package/dist/Badge/Badge.js +54 -0
  10. package/dist/Badge/Badge.js.map +1 -0
  11. package/dist/Badge/useIsTextClipped.d.ts +22 -0
  12. package/dist/Badge/useIsTextClipped.d.ts.map +1 -0
  13. package/dist/Badge/useIsTextClipped.js +57 -0
  14. package/dist/Badge/useIsTextClipped.js.map +1 -0
  15. package/dist/Button/Button.d.ts +58 -0
  16. package/dist/Button/Button.d.ts.map +1 -0
  17. package/dist/Button/Button.js +37 -0
  18. package/dist/Button/Button.js.map +1 -0
  19. package/dist/Card/Card.d.ts +44 -0
  20. package/dist/Card/Card.d.ts.map +1 -0
  21. package/dist/Card/Card.js +59 -0
  22. package/dist/Card/Card.js.map +1 -0
  23. package/dist/EmptyState/EmptyState.d.ts +42 -0
  24. package/dist/EmptyState/EmptyState.d.ts.map +1 -0
  25. package/dist/EmptyState/EmptyState.js +37 -0
  26. package/dist/EmptyState/EmptyState.js.map +1 -0
  27. package/dist/IconButton/IconButton.d.ts +34 -0
  28. package/dist/IconButton/IconButton.d.ts.map +1 -0
  29. package/dist/IconButton/IconButton.js +23 -0
  30. package/dist/IconButton/IconButton.js.map +1 -0
  31. package/dist/LiveStatusIndicator/LiveStatusIndicator.d.ts +37 -0
  32. package/dist/LiveStatusIndicator/LiveStatusIndicator.d.ts.map +1 -0
  33. package/dist/LiveStatusIndicator/LiveStatusIndicator.js +39 -0
  34. package/dist/LiveStatusIndicator/LiveStatusIndicator.js.map +1 -0
  35. package/dist/MediaTile/MediaTile.d.ts +46 -0
  36. package/dist/MediaTile/MediaTile.d.ts.map +1 -0
  37. package/dist/MediaTile/MediaTile.js +79 -0
  38. package/dist/MediaTile/MediaTile.js.map +1 -0
  39. package/dist/MediaTile/mediaStatus.d.ts +22 -0
  40. package/dist/MediaTile/mediaStatus.d.ts.map +1 -0
  41. package/dist/MediaTile/mediaStatus.js +24 -0
  42. package/dist/MediaTile/mediaStatus.js.map +1 -0
  43. package/dist/Modal/Modal.d.ts +66 -0
  44. package/dist/Modal/Modal.d.ts.map +1 -0
  45. package/dist/Modal/Modal.js +111 -0
  46. package/dist/Modal/Modal.js.map +1 -0
  47. package/dist/Modal/lockScrollBehind.d.ts +22 -0
  48. package/dist/Modal/lockScrollBehind.d.ts.map +1 -0
  49. package/dist/Modal/lockScrollBehind.js +48 -0
  50. package/dist/Modal/lockScrollBehind.js.map +1 -0
  51. package/dist/Popover/Popover.d.ts +66 -0
  52. package/dist/Popover/Popover.d.ts.map +1 -0
  53. package/dist/Popover/Popover.js +88 -0
  54. package/dist/Popover/Popover.js.map +1 -0
  55. package/dist/ProgressBar/ProgressBar.d.ts +51 -0
  56. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -0
  57. package/dist/ProgressBar/ProgressBar.js +50 -0
  58. package/dist/ProgressBar/ProgressBar.js.map +1 -0
  59. package/dist/ProgressBar/progressValue.d.ts +44 -0
  60. package/dist/ProgressBar/progressValue.d.ts.map +1 -0
  61. package/dist/ProgressBar/progressValue.js +55 -0
  62. package/dist/ProgressBar/progressValue.js.map +1 -0
  63. package/dist/SegmentedControl/SegmentedControl.d.ts +59 -0
  64. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -0
  65. package/dist/SegmentedControl/SegmentedControl.js +129 -0
  66. package/dist/SegmentedControl/SegmentedControl.js.map +1 -0
  67. package/dist/SegmentedControl/SegmentedOption.d.ts +28 -0
  68. package/dist/SegmentedControl/SegmentedOption.d.ts.map +1 -0
  69. package/dist/SegmentedControl/SegmentedOption.js +40 -0
  70. package/dist/SegmentedControl/SegmentedOption.js.map +1 -0
  71. package/dist/Skeleton/Skeleton.d.ts +30 -0
  72. package/dist/Skeleton/Skeleton.d.ts.map +1 -0
  73. package/dist/Skeleton/Skeleton.js +43 -0
  74. package/dist/Skeleton/Skeleton.js.map +1 -0
  75. package/dist/Spinner/Spinner.d.ts +37 -0
  76. package/dist/Spinner/Spinner.d.ts.map +1 -0
  77. package/dist/Spinner/Spinner.js +33 -0
  78. package/dist/Spinner/Spinner.js.map +1 -0
  79. package/dist/Tabs/TabTrigger.d.ts +32 -0
  80. package/dist/Tabs/TabTrigger.d.ts.map +1 -0
  81. package/dist/Tabs/TabTrigger.js +62 -0
  82. package/dist/Tabs/TabTrigger.js.map +1 -0
  83. package/dist/Tabs/Tabs.d.ts +95 -0
  84. package/dist/Tabs/Tabs.d.ts.map +1 -0
  85. package/dist/Tabs/Tabs.js +195 -0
  86. package/dist/Tabs/Tabs.js.map +1 -0
  87. package/dist/VisuallyHidden/VisuallyHidden.d.ts +25 -0
  88. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  89. package/dist/VisuallyHidden/VisuallyHidden.js +23 -0
  90. package/dist/VisuallyHidden/VisuallyHidden.js.map +1 -0
  91. package/dist/controlStyles.d.ts +50 -0
  92. package/dist/controlStyles.d.ts.map +1 -0
  93. package/dist/controlStyles.js +60 -0
  94. package/dist/controlStyles.js.map +1 -0
  95. package/dist/expectNoAxeViolations.testHelpers.d.ts +19 -0
  96. package/dist/expectNoAxeViolations.testHelpers.d.ts.map +1 -0
  97. package/dist/expectNoAxeViolations.testHelpers.js +32 -0
  98. package/dist/expectNoAxeViolations.testHelpers.js.map +1 -0
  99. package/dist/index.d.ts +75 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +55 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/intentStyles.d.ts +76 -0
  104. package/dist/intentStyles.d.ts.map +1 -0
  105. package/dist/intentStyles.js +150 -0
  106. package/dist/intentStyles.js.map +1 -0
  107. package/dist/mountStory.testHelpers.d.ts +30 -0
  108. package/dist/mountStory.testHelpers.d.ts.map +1 -0
  109. package/dist/mountStory.testHelpers.js +19 -0
  110. package/dist/mountStory.testHelpers.js.map +1 -0
  111. package/dist/statusIntent.d.ts +33 -0
  112. package/dist/statusIntent.d.ts.map +1 -0
  113. package/dist/statusIntent.js +99 -0
  114. package/dist/statusIntent.js.map +1 -0
  115. package/dist/styles.css +130 -0
  116. package/dist/testing/expectAgentDrivable.d.ts +53 -0
  117. package/dist/testing/expectAgentDrivable.d.ts.map +1 -0
  118. package/dist/testing/expectAgentDrivable.js +163 -0
  119. package/dist/testing/expectAgentDrivable.js.map +1 -0
  120. package/dist/testing/index.d.ts +11 -0
  121. package/dist/testing/index.d.ts.map +1 -0
  122. package/dist/testing/index.js +10 -0
  123. package/dist/testing/index.js.map +1 -0
  124. package/dist/toClassName.d.ts +12 -0
  125. package/dist/toClassName.d.ts.map +1 -0
  126. package/dist/toClassName.js +12 -0
  127. package/dist/toClassName.js.map +1 -0
  128. package/dist/tokens.d.ts +16 -0
  129. package/dist/tokens.d.ts.map +1 -0
  130. package/dist/tokens.js +16 -0
  131. package/dist/tokens.js.map +1 -0
  132. package/package.json +70 -0
  133. package/src/Alert/Alert.mdx +70 -0
  134. package/src/Alert/Alert.stories.tsx +218 -0
  135. package/src/Alert/Alert.test.tsx +112 -0
  136. package/src/Alert/Alert.tsx +166 -0
  137. package/src/Badge/Badge.mdx +43 -0
  138. package/src/Badge/Badge.stories.tsx +229 -0
  139. package/src/Badge/Badge.test.tsx +153 -0
  140. package/src/Badge/Badge.tsx +130 -0
  141. package/src/Badge/useIsTextClipped.ts +77 -0
  142. package/src/Button/Button.mdx +44 -0
  143. package/src/Button/Button.stories.tsx +175 -0
  144. package/src/Button/Button.test.tsx +114 -0
  145. package/src/Button/Button.tsx +125 -0
  146. package/src/Card/Card.mdx +53 -0
  147. package/src/Card/Card.stories.tsx +207 -0
  148. package/src/Card/Card.test.tsx +56 -0
  149. package/src/Card/Card.tsx +142 -0
  150. package/src/EmptyState/EmptyState.mdx +40 -0
  151. package/src/EmptyState/EmptyState.stories.tsx +183 -0
  152. package/src/EmptyState/EmptyState.test.tsx +41 -0
  153. package/src/EmptyState/EmptyState.tsx +114 -0
  154. package/src/IconButton/IconButton.mdx +34 -0
  155. package/src/IconButton/IconButton.stories.tsx +191 -0
  156. package/src/IconButton/IconButton.test.tsx +70 -0
  157. package/src/IconButton/IconButton.tsx +54 -0
  158. package/src/LiveStatusIndicator/LiveStatusIndicator.mdx +43 -0
  159. package/src/LiveStatusIndicator/LiveStatusIndicator.stories.tsx +199 -0
  160. package/src/LiveStatusIndicator/LiveStatusIndicator.test.tsx +63 -0
  161. package/src/LiveStatusIndicator/LiveStatusIndicator.tsx +116 -0
  162. package/src/MediaTile/MediaTile.mdx +51 -0
  163. package/src/MediaTile/MediaTile.stories.tsx +211 -0
  164. package/src/MediaTile/MediaTile.test.tsx +41 -0
  165. package/src/MediaTile/MediaTile.tsx +227 -0
  166. package/src/MediaTile/mediaStatus.ts +28 -0
  167. package/src/Modal/Modal.mdx +113 -0
  168. package/src/Modal/Modal.stories.tsx +272 -0
  169. package/src/Modal/Modal.test.tsx +248 -0
  170. package/src/Modal/Modal.tsx +228 -0
  171. package/src/Modal/lockScrollBehind.ts +59 -0
  172. package/src/Popover/Popover.mdx +102 -0
  173. package/src/Popover/Popover.stories.tsx +247 -0
  174. package/src/Popover/Popover.test.tsx +191 -0
  175. package/src/Popover/Popover.tsx +164 -0
  176. package/src/ProgressBar/ProgressBar.mdx +48 -0
  177. package/src/ProgressBar/ProgressBar.stories.tsx +232 -0
  178. package/src/ProgressBar/ProgressBar.test.tsx +74 -0
  179. package/src/ProgressBar/ProgressBar.tsx +172 -0
  180. package/src/ProgressBar/progressValue.test.ts +101 -0
  181. package/src/ProgressBar/progressValue.ts +87 -0
  182. package/src/SegmentedControl/SegmentedControl.mdx +71 -0
  183. package/src/SegmentedControl/SegmentedControl.stories.tsx +150 -0
  184. package/src/SegmentedControl/SegmentedControl.test.tsx +208 -0
  185. package/src/SegmentedControl/SegmentedControl.tsx +219 -0
  186. package/src/SegmentedControl/SegmentedOption.tsx +94 -0
  187. package/src/Skeleton/Skeleton.mdx +30 -0
  188. package/src/Skeleton/Skeleton.stories.tsx +130 -0
  189. package/src/Skeleton/Skeleton.test.tsx +47 -0
  190. package/src/Skeleton/Skeleton.tsx +106 -0
  191. package/src/Spinner/Spinner.mdx +36 -0
  192. package/src/Spinner/Spinner.stories.tsx +118 -0
  193. package/src/Spinner/Spinner.test.tsx +38 -0
  194. package/src/Spinner/Spinner.tsx +92 -0
  195. package/src/Tabs/TabTrigger.tsx +126 -0
  196. package/src/Tabs/Tabs.mdx +126 -0
  197. package/src/Tabs/Tabs.stories.tsx +187 -0
  198. package/src/Tabs/Tabs.test.tsx +283 -0
  199. package/src/Tabs/Tabs.tsx +333 -0
  200. package/src/VisuallyHidden/VisuallyHidden.mdx +29 -0
  201. package/src/VisuallyHidden/VisuallyHidden.stories.tsx +58 -0
  202. package/src/VisuallyHidden/VisuallyHidden.test.tsx +31 -0
  203. package/src/VisuallyHidden/VisuallyHidden.tsx +43 -0
  204. package/src/argTypes.storyHelpers.ts +115 -0
  205. package/src/board.storyHelpers.tsx +150 -0
  206. package/src/controlStyles.ts +79 -0
  207. package/src/expectNoAxeViolations.testHelpers.ts +41 -0
  208. package/src/icons.storyHelpers.tsx +116 -0
  209. package/src/index.ts +132 -0
  210. package/src/intentStyles.ts +207 -0
  211. package/src/mdxReferences.test.ts +95 -0
  212. package/src/mountStory.testHelpers.ts +41 -0
  213. package/src/sourceRules.test.ts +303 -0
  214. package/src/statusIntent.test.ts +136 -0
  215. package/src/statusIntent.ts +137 -0
  216. package/src/storyControls.test.ts +128 -0
  217. package/src/styles.css +130 -0
  218. package/src/tailwindCandidates.test.ts +377 -0
  219. package/src/testing/expectAgentDrivable.test.tsx +71 -0
  220. package/src/testing/expectAgentDrivable.ts +273 -0
  221. package/src/testing/index.ts +17 -0
  222. package/src/toClassName.ts +13 -0
  223. package/src/tokens.ts +16 -0
@@ -0,0 +1,95 @@
1
+ import type { ReactNode } from "react";
2
+ export type TabsActivation = "automatic" | "manual";
3
+ export type TabsOrientation = "horizontal" | "vertical";
4
+ export type TabItem = {
5
+ content: ReactNode;
6
+ /**
7
+ * A disabled tab is simply **not registered** with the roving
8
+ * group. Registration is membership, so the arrow keys skip it
9
+ * without any command in `RovingFocus` having to know what
10
+ * "disabled" means.
11
+ */
12
+ isDisabled?: boolean;
13
+ key: string;
14
+ label: ReactNode;
15
+ };
16
+ export type TabsProps = {
17
+ /** **Initial** only. Charcuterie owns it from then on. */
18
+ activeKey?: string;
19
+ /**
20
+ * `automatic` — the ARIA Authoring Practices default — shows a
21
+ * panel as soon as the arrow keys reach its tab. `manual` moves
22
+ * focus only, and Enter or Space commits.
23
+ *
24
+ * The distinction exists because **focus is not selection**, and
25
+ * it is the entire reason `RovingFocus` is its own state kind. A
26
+ * tab whose panel costs a network request wants `manual`, and
27
+ * the two modes differ by one line below — because two kinds are
28
+ * composed rather than one kind doing both jobs badly.
29
+ */
30
+ activation?: TabsActivation;
31
+ className?: string;
32
+ /** The tablist's accessible name. Required. */
33
+ label: string;
34
+ onChange?: (activeKey: string | null) => void;
35
+ orientation?: TabsOrientation;
36
+ tabs: TabItem[];
37
+ };
38
+ /**
39
+ * Kept at P0 as the **state layer's falsification test**, not on
40
+ * duplication grounds — the fifteen ad-hoc tab bars that used to
41
+ * justify it were in the withdrawn evidence. What it has to prove
42
+ * is that two kinds compose: one says which tab is **chosen**, the
43
+ * other says which tab is **tabbable**, and a tab bar needs both
44
+ * at once *and* needs them to disagree on purpose while a user
45
+ * arrows around in `manual` mode.
46
+ *
47
+ * The composition is two hooks and one decision:
48
+ *
49
+ * ```ts
50
+ * const selection = useSinglePicker({ selectedValue: initialKey })
51
+ * const focus = useRovingFocus({ activeValue: initialKey })
52
+ *
53
+ * // `automatic` is this line, and `manual` is its absence.
54
+ * if (activation === "automatic") selection.select(focus.activeValue)
55
+ * ```
56
+ *
57
+ * ### Selection, not visibility — and the difference is not cosmetic
58
+ *
59
+ * M4 built this on `VisibilityGroup`, and the panels do end up
60
+ * one-at-a-time either way, because the two cores are the same
61
+ * shape. But the thing that **registers** here is the tab, not the
62
+ * panel, and what a tab bar is asking is *which one did you pick*.
63
+ * `SinglePicker` is the kind that answers that; the panel's
64
+ * `hidden` is derived from the answer.
65
+ *
66
+ * Reading it the other way round cost something concrete: it made
67
+ * `aria-selected` a report about a *panel's* visibility, so a
68
+ * consumer wanting a tab bar with no panels at all — a segmented
69
+ * filter, a view switcher that swaps a route — had to model its
70
+ * choice as a group of visibilities it did not have. `Alert`'s
71
+ * sibling `SegmentedControl` is exactly that consumer, and it now
72
+ * shares this component's core rather than hand-rolling a fourth
73
+ * one.
74
+ * [Decision](../../../../docs/decisions/2026-07-30-tab-selection-is-a-single-picker.md).
75
+ *
76
+ * ### What it does not use, and why that is the right answer
77
+ *
78
+ * The plan expected `createLinkedIds` here as well. It does not
79
+ * fit, and forcing it would have been the wrong call: that kind
80
+ * exists for a **dynamic** trigger↔target pair — a `Popover` whose
81
+ * panel mounts and unmounts, where the multiset is what stops
82
+ * `aria-controls` pointing at a node React has already removed. A
83
+ * tab bar's pairing is static and known at render, so the ids come
84
+ * from one `useUniqueId` and cannot get out of step at all.
85
+ *
86
+ * ### Focus is moved deliberately, and only then
87
+ *
88
+ * `RovingFocus` is state; something still has to call `.focus()`.
89
+ * That happens only while the tablist already contains the focused
90
+ * element — otherwise mounting a tab bar, or any parent re-render
91
+ * that moved `activeValue`, yanks the caret out of whatever the
92
+ * user was actually doing.
93
+ */
94
+ export declare const Tabs: ({ activation, activeKey, className, label, onChange, orientation, tabs, }: TabsProps) => ReactNode;
95
+ //# sourceMappingURL=Tabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/Tabs.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,QAAQ,CAAA;AAEnD,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,UAAU,CAAA;AAEvD,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,SAAS,CAAA;IAClB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,IAAI,EAAE,OAAO,EAAE,CAAA;CAChB,CAAA;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,IAAI,GAAI,2EAQlB,SAAS,KAAG,SA6Md,CAAA"}
@@ -0,0 +1,195 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { selectTabIndex, useRovingFocus, useSinglePicker, useUniqueId, } from "@charcuterie/logic";
3
+ import { useCallback, useEffect, useRef } from "react";
4
+ import { toClassName } from "../toClassName.js";
5
+ import { TabTrigger } from "./TabTrigger.js";
6
+ const ORIENTATION_KEYS = {
7
+ horizontal: {
8
+ next: "ArrowRight",
9
+ previous: "ArrowLeft",
10
+ },
11
+ vertical: { next: "ArrowDown", previous: "ArrowUp" },
12
+ };
13
+ /**
14
+ * Kept at P0 as the **state layer's falsification test**, not on
15
+ * duplication grounds — the fifteen ad-hoc tab bars that used to
16
+ * justify it were in the withdrawn evidence. What it has to prove
17
+ * is that two kinds compose: one says which tab is **chosen**, the
18
+ * other says which tab is **tabbable**, and a tab bar needs both
19
+ * at once *and* needs them to disagree on purpose while a user
20
+ * arrows around in `manual` mode.
21
+ *
22
+ * The composition is two hooks and one decision:
23
+ *
24
+ * ```ts
25
+ * const selection = useSinglePicker({ selectedValue: initialKey })
26
+ * const focus = useRovingFocus({ activeValue: initialKey })
27
+ *
28
+ * // `automatic` is this line, and `manual` is its absence.
29
+ * if (activation === "automatic") selection.select(focus.activeValue)
30
+ * ```
31
+ *
32
+ * ### Selection, not visibility — and the difference is not cosmetic
33
+ *
34
+ * M4 built this on `VisibilityGroup`, and the panels do end up
35
+ * one-at-a-time either way, because the two cores are the same
36
+ * shape. But the thing that **registers** here is the tab, not the
37
+ * panel, and what a tab bar is asking is *which one did you pick*.
38
+ * `SinglePicker` is the kind that answers that; the panel's
39
+ * `hidden` is derived from the answer.
40
+ *
41
+ * Reading it the other way round cost something concrete: it made
42
+ * `aria-selected` a report about a *panel's* visibility, so a
43
+ * consumer wanting a tab bar with no panels at all — a segmented
44
+ * filter, a view switcher that swaps a route — had to model its
45
+ * choice as a group of visibilities it did not have. `Alert`'s
46
+ * sibling `SegmentedControl` is exactly that consumer, and it now
47
+ * shares this component's core rather than hand-rolling a fourth
48
+ * one.
49
+ * [Decision](../../../../docs/decisions/2026-07-30-tab-selection-is-a-single-picker.md).
50
+ *
51
+ * ### What it does not use, and why that is the right answer
52
+ *
53
+ * The plan expected `createLinkedIds` here as well. It does not
54
+ * fit, and forcing it would have been the wrong call: that kind
55
+ * exists for a **dynamic** trigger↔target pair — a `Popover` whose
56
+ * panel mounts and unmounts, where the multiset is what stops
57
+ * `aria-controls` pointing at a node React has already removed. A
58
+ * tab bar's pairing is static and known at render, so the ids come
59
+ * from one `useUniqueId` and cannot get out of step at all.
60
+ *
61
+ * ### Focus is moved deliberately, and only then
62
+ *
63
+ * `RovingFocus` is state; something still has to call `.focus()`.
64
+ * That happens only while the tablist already contains the focused
65
+ * element — otherwise mounting a tab bar, or any parent re-render
66
+ * that moved `activeValue`, yanks the caret out of whatever the
67
+ * user was actually doing.
68
+ */
69
+ export const Tabs = ({ activation = "automatic", activeKey, className, label, onChange, orientation = "horizontal", tabs, }) => {
70
+ const baseId = useUniqueId();
71
+ const tablistRef = useRef(null);
72
+ const tabElements = useRef(new Map());
73
+ const initialKey = activeKey ??
74
+ tabs.find((one) => !one.isDisabled)?.key ??
75
+ null;
76
+ const selection = useSinglePicker({
77
+ onChange,
78
+ selectedValue: initialKey,
79
+ });
80
+ const focus = useRovingFocus({
81
+ activeValue: initialKey,
82
+ });
83
+ const { select } = selection;
84
+ const { activeValue, setActiveValue } = focus;
85
+ /**
86
+ * Selected **or** pending, and the fallback is load-bearing.
87
+ *
88
+ * Members register from an effect, so on the first paint
89
+ * `selectedValue` is still null and the intent lives in
90
+ * `pendingValue` — a tab bar reading only the former renders with
91
+ * no tab selected and no panel shown, then corrects itself a
92
+ * frame later. It is a flash on a fast machine and a visible
93
+ * blank on a kiosk Pi.
94
+ *
95
+ * The core distinguishes the two on purpose, which is why this
96
+ * belongs here rather than in the selector — unlike
97
+ * `RovingFocus`, where the equivalent first-paint hole was in
98
+ * `selectTabIndex` itself and got fixed there.
99
+ */
100
+ const shownKey = selection.selectedValue ?? selection.pendingValue;
101
+ const selectTab = useCallback((key) => {
102
+ // Both, because a pointer press moves focus *and* chooses.
103
+ // Only selecting would leave `automatic`'s effect below to
104
+ // put the old panel straight back.
105
+ setActiveValue(key);
106
+ select(key);
107
+ }, [select, setActiveValue]);
108
+ useEffect(() => {
109
+ // Automatic activation, and the only behaviour that differs
110
+ // between the two modes. `select` is idempotent, so this is a
111
+ // no-op on every render where focus did not actually move.
112
+ if (activation === "automatic" &&
113
+ activeValue !== null) {
114
+ select(activeValue);
115
+ }
116
+ }, [activation, activeValue, select]);
117
+ useEffect(() => {
118
+ const tablist = tablistRef.current;
119
+ if (!tablist ||
120
+ activeValue === null ||
121
+ !tablist.contains(document.activeElement)) {
122
+ return;
123
+ }
124
+ tabElements.current.get(activeValue)?.focus();
125
+ }, [activeValue]);
126
+ const keys = ORIENTATION_KEYS[orientation];
127
+ return (_jsxs("div", { className: toClassName("flex flex-col gap-3", className), children: [_jsx("div", { "aria-label": label, "aria-orientation": orientation, className: toClassName("flex gap-1 border-border-subtle",
128
+ // A tab bar **scrolls**; it does not wrap and it does not
129
+ // paint outside its container. Without an `overflow`, a
130
+ // bar narrower than its tabs simply spilled across
131
+ // whatever sat beside it — visible in the `Responsive`
132
+ // board since M4, and invisible to a test asserting only
133
+ // that `scrollWidth > clientWidth`, which is true of a
134
+ // *spilling* bar too.
135
+ //
136
+ // The scrollbar is hidden rather than thin: a classic
137
+ // scrollbar takes layout space inside the scroll
138
+ // container, so the narrow bar would grow ~15px taller
139
+ // than the wide one and the underline would stop lining
140
+ // up across a board. Keyboard users never need it —
141
+ // `RovingFocus` calls `.focus()`, and the browser scrolls
142
+ // a focused tab into view.
143
+ "[scrollbar-width:none]",
144
+ // An `overflow` other than `visible` clips its
145
+ // descendants' **outlines**, and this component's focus
146
+ // ring is an outline sitting 2px *outside* the tab. The
147
+ // tab fills the bar's content box, so the ring would be
148
+ // clipped away on exactly the axis that now scrolls.
149
+ // Re-pointing the shared token inward keeps one ring
150
+ // definition — `FOCUS_RING_CLASS` already reads this
151
+ // variable — rather than appending a competing
152
+ // `outline-offset` utility and letting Tailwind's
153
+ // ordering decide the winner.
154
+ "[--focus-ring-offset:calc(var(--focus-ring-width)*-1)]", orientation === "horizontal"
155
+ ? "flex-row overflow-x-auto border-b"
156
+ : "flex-col items-stretch overflow-y-auto border-e"), onKeyDown: (keyEvent) => {
157
+ const commands = {
158
+ [keys.next]: focus.next,
159
+ [keys.previous]: focus.previous,
160
+ End: focus.last,
161
+ Home: focus.first,
162
+ };
163
+ const command = commands[keyEvent.key];
164
+ if (command) {
165
+ // Arrow keys scroll the page by default, and a tab bar
166
+ // that scrolls the window while it moves focus is the
167
+ // hand-rolled version of this component.
168
+ keyEvent.preventDefault();
169
+ command();
170
+ return;
171
+ }
172
+ // `manual`'s commit. In `automatic` the panel is showing
173
+ // already, so this is the no-op that makes Enter behave
174
+ // identically in both modes.
175
+ if ((keyEvent.key === "Enter" ||
176
+ keyEvent.key === " ") &&
177
+ activeValue !== null) {
178
+ keyEvent.preventDefault();
179
+ select(activeValue);
180
+ }
181
+ }, ref: tablistRef, role: "tablist", children: tabs.map((tab, index) => (_jsx(TabTrigger, { id: `${baseId}-tab-${index}`, isSelected: shownKey === tab.key, onSelect: selectTab, panelId: `${baseId}-panel-${index}`, registerFocus: focus.register, orientation: orientation, registerSelection: selection.register, tab: tab,
182
+ // The roving-tabindex rule read from the core rather
183
+ // than restated here: exactly one member is in the tab
184
+ // order, so Tab enters and leaves the bar once while
185
+ // the arrow keys move inside it.
186
+ tabIndex: selectTabIndex(focus, tab.key), trackElement: (key, element) => {
187
+ if (element) {
188
+ tabElements.current.set(key, element);
189
+ }
190
+ else {
191
+ tabElements.current.delete(key);
192
+ }
193
+ } }, tab.key))) }), tabs.map((tab, index) => (_jsx("div", { "aria-labelledby": `${baseId}-tab-${index}`, hidden: shownKey !== tab.key, id: `${baseId}-panel-${index}`, role: "tabpanel", children: tab.content }, tab.key)))] }));
194
+ };
195
+ //# sourceMappingURL=Tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tabs.js","sourceRoot":"","sources":["../../src/Tabs/Tabs.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from "react";
2
+ export type VisuallyHiddenProps = ComponentPropsWithRef<"span"> & {
3
+ children: ReactNode;
4
+ };
5
+ /**
6
+ * Text for assistive technology and for nobody else.
7
+ *
8
+ * A Layer-0 primitive, and the one every other component in M3
9
+ * leans on: it is how a `Spinner` says "Loading…" without printing
10
+ * it, how an `IconButton`'s glyph gets a name, and how a
11
+ * `ProgressBar`'s label stays discoverable when the design does not
12
+ * show it. Without it the alternative is `aria-label` everywhere,
13
+ * which silently loses translation and cannot hold markup.
14
+ *
15
+ * `sr-only` is Tailwind's own — the clip-rect technique, unchanged
16
+ * since it was `.visuallyhidden` in HTML5 Boilerplate. There is
17
+ * nothing to own here, and owning it would mean maintaining a
18
+ * clip-rect against browsers that have already agreed.
19
+ *
20
+ * Never `display: none` and never `visibility: hidden`: both remove
21
+ * the node from the accessibility tree, which is the exact opposite
22
+ * of the requirement.
23
+ */
24
+ export declare const VisuallyHidden: ({ children, className, ...spanProps }: VisuallyHiddenProps) => ReactNode;
25
+ //# sourceMappingURL=VisuallyHidden.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VisuallyHidden.d.ts","sourceRoot":"","sources":["../../src/VisuallyHidden/VisuallyHidden.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAId,MAAM,MAAM,mBAAmB,GAC7B,qBAAqB,CAAC,MAAM,CAAC,GAAG;IAC9B,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,cAAc,GAAI,uCAI5B,mBAAmB,KAAG,SAOxB,CAAA"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { toClassName } from "../toClassName.js";
3
+ /**
4
+ * Text for assistive technology and for nobody else.
5
+ *
6
+ * A Layer-0 primitive, and the one every other component in M3
7
+ * leans on: it is how a `Spinner` says "Loading…" without printing
8
+ * it, how an `IconButton`'s glyph gets a name, and how a
9
+ * `ProgressBar`'s label stays discoverable when the design does not
10
+ * show it. Without it the alternative is `aria-label` everywhere,
11
+ * which silently loses translation and cannot hold markup.
12
+ *
13
+ * `sr-only` is Tailwind's own — the clip-rect technique, unchanged
14
+ * since it was `.visuallyhidden` in HTML5 Boilerplate. There is
15
+ * nothing to own here, and owning it would mean maintaining a
16
+ * clip-rect against browsers that have already agreed.
17
+ *
18
+ * Never `display: none` and never `visibility: hidden`: both remove
19
+ * the node from the accessibility tree, which is the exact opposite
20
+ * of the requirement.
21
+ */
22
+ export const VisuallyHidden = ({ children, className, ...spanProps }) => (_jsx("span", { ...spanProps, className: toClassName("sr-only", className), children: children }));
23
+ //# sourceMappingURL=VisuallyHidden.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VisuallyHidden.js","sourceRoot":"","sources":["../../src/VisuallyHidden/VisuallyHidden.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,QAAQ,EACR,SAAS,EACT,GAAG,SAAS,EACQ,EAAa,EAAE,CAAC,CACpC,kBACM,SAAS,EACb,SAAS,EAAE,WAAW,CAAC,SAAS,EAAE,SAAS,CAAC,YAE3C,QAAQ,GACJ,CACR,CAAA"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Control sizing, read from the density axis rather than chosen per
3
+ * component.
4
+ *
5
+ * The values are never in this file — `h-(--control-height-md)`
6
+ * points at a variable that `[data-density]` rewrites, so the same
7
+ * `<Button size="md">` is 2.25rem on a desktop, 1.875rem in a
8
+ * compact bay list, and 3.25rem on the kiosk with no prop change
9
+ * and no re-render. That is the entire argument for a density axis
10
+ * over a `size="kiosk"` prop, and it only works because nothing
11
+ * here hardcodes a length.
12
+ *
13
+ * `text-sm`/`text-md`/`text-lg` are ours too, not Tailwind's:
14
+ * `theme.css` bridges `--text-*` onto `--font-size-*`, which the
15
+ * density axis also scales. See
16
+ * `packages/tokens/src/tailwindCollisions.test.ts`.
17
+ */
18
+ import type { ControlSize } from "@charcuterie/tokens";
19
+ export declare const CONTROL_SIZE_CLASS: Record<ControlSize, string>;
20
+ /**
21
+ * Square, and sized by the *height* token on both axes so an icon
22
+ * button lines up with the text button beside it in a toolbar. No
23
+ * inline padding at all — a glyph centres itself.
24
+ */
25
+ export declare const ICON_CONTROL_SIZE_CLASS: Record<ControlSize, string>;
26
+ /**
27
+ * The kiosk floor. `--control-min-touch-target` is 44px in every
28
+ * density — a finger does not get smaller because the list is
29
+ * dense — so this is opt-in per control rather than baked into the
30
+ * size classes, which would make `compact` a lie.
31
+ */
32
+ export declare const MIN_TOUCH_TARGET_CLASS = "min-h-(--control-min-touch-target) min-w-(--control-min-touch-target)";
33
+ /**
34
+ * Non-control sizing: badges, indicator dots, spinners. These are
35
+ * type-relative on purpose — a badge beside `text-sm` prose that
36
+ * stays 20px tall while the kiosk scales the prose to 1.19x reads
37
+ * as a rendering bug.
38
+ */
39
+ export declare const BADGE_SIZE_CLASS: {
40
+ readonly sm: "gap-1 px-2 py-0.5 text-xs";
41
+ readonly md: "gap-1.5 px-2.5 py-1 text-sm";
42
+ };
43
+ export type BadgeSize = keyof typeof BADGE_SIZE_CLASS;
44
+ export declare const DOT_SIZE_CLASS: {
45
+ readonly sm: "size-1.5";
46
+ readonly md: "size-2";
47
+ readonly lg: "size-2.5";
48
+ };
49
+ export declare const SPINNER_SIZE_CLASS: Record<ControlSize, string>;
50
+ //# sourceMappingURL=controlStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controlStyles.d.ts","sourceRoot":"","sources":["../src/controlStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CACrC,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAC1C,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,0EACsC,CAAA;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;CAGnB,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,gBAAgB,CAAA;AAErD,eAAO,MAAM,cAAc;;;;CAIjB,CAAA;AAEV,eAAO,MAAM,kBAAkB,EAAE,MAAM,CACrC,WAAW,EACX,MAAM,CAKP,CAAA"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Control sizing, read from the density axis rather than chosen per
3
+ * component.
4
+ *
5
+ * The values are never in this file — `h-(--control-height-md)`
6
+ * points at a variable that `[data-density]` rewrites, so the same
7
+ * `<Button size="md">` is 2.25rem on a desktop, 1.875rem in a
8
+ * compact bay list, and 3.25rem on the kiosk with no prop change
9
+ * and no re-render. That is the entire argument for a density axis
10
+ * over a `size="kiosk"` prop, and it only works because nothing
11
+ * here hardcodes a length.
12
+ *
13
+ * `text-sm`/`text-md`/`text-lg` are ours too, not Tailwind's:
14
+ * `theme.css` bridges `--text-*` onto `--font-size-*`, which the
15
+ * density axis also scales. See
16
+ * `packages/tokens/src/tailwindCollisions.test.ts`.
17
+ */
18
+ export const CONTROL_SIZE_CLASS = {
19
+ sm: "h-(--control-height-sm) gap-(--control-gap-sm) px-(--control-padding-inline-sm) text-sm",
20
+ md: "h-(--control-height-md) gap-(--control-gap-md) px-(--control-padding-inline-md) text-md",
21
+ lg: "h-(--control-height-lg) gap-(--control-gap-lg) px-(--control-padding-inline-lg) text-lg",
22
+ };
23
+ /**
24
+ * Square, and sized by the *height* token on both axes so an icon
25
+ * button lines up with the text button beside it in a toolbar. No
26
+ * inline padding at all — a glyph centres itself.
27
+ */
28
+ export const ICON_CONTROL_SIZE_CLASS = {
29
+ sm: "size-(--control-height-sm) px-0 text-sm",
30
+ md: "size-(--control-height-md) px-0 text-md",
31
+ lg: "size-(--control-height-lg) px-0 text-lg",
32
+ };
33
+ /**
34
+ * The kiosk floor. `--control-min-touch-target` is 44px in every
35
+ * density — a finger does not get smaller because the list is
36
+ * dense — so this is opt-in per control rather than baked into the
37
+ * size classes, which would make `compact` a lie.
38
+ */
39
+ export const MIN_TOUCH_TARGET_CLASS = "min-h-(--control-min-touch-target) min-w-(--control-min-touch-target)";
40
+ /**
41
+ * Non-control sizing: badges, indicator dots, spinners. These are
42
+ * type-relative on purpose — a badge beside `text-sm` prose that
43
+ * stays 20px tall while the kiosk scales the prose to 1.19x reads
44
+ * as a rendering bug.
45
+ */
46
+ export const BADGE_SIZE_CLASS = {
47
+ sm: "gap-1 px-2 py-0.5 text-xs",
48
+ md: "gap-1.5 px-2.5 py-1 text-sm",
49
+ };
50
+ export const DOT_SIZE_CLASS = {
51
+ sm: "size-1.5",
52
+ md: "size-2",
53
+ lg: "size-2.5",
54
+ };
55
+ export const SPINNER_SIZE_CLASS = {
56
+ sm: "size-3.5 border-2",
57
+ md: "size-4 border-2",
58
+ lg: "size-5 border-2",
59
+ };
60
+ //# sourceMappingURL=controlStyles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controlStyles.js","sourceRoot":"","sources":["../src/controlStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,CAAC,MAAM,kBAAkB,GAG3B;IACF,EAAE,EAAE,yFAAyF;IAC7F,EAAE,EAAE,yFAAyF;IAC7F,EAAE,EAAE,yFAAyF;CAC9F,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAGhC;IACF,EAAE,EAAE,yCAAyC;IAC7C,EAAE,EAAE,yCAAyC;IAC7C,EAAE,EAAE,yCAAyC;CAC9C,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GACjC,uEAAuE,CAAA;AAEzE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,EAAE,EAAE,2BAA2B;IAC/B,EAAE,EAAE,6BAA6B;CACzB,CAAA;AAIV,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,UAAU;CACN,CAAA;AAEV,MAAM,CAAC,MAAM,kBAAkB,GAG3B;IACF,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,iBAAiB;IACrB,EAAE,EAAE,iBAAiB;CACtB,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Run axe against a subtree, now.
3
+ *
4
+ * The a11y addon audits a story **once**, in an `afterEach` that
5
+ * fires when `run()` resolves — i.e. immediately after the first
6
+ * paint. That was enough while the assertions lived in `play`,
7
+ * because the addon's hook ran after the play had finished driving.
8
+ * With the tests split out, `run()` returns before a test has
9
+ * clicked anything, so the states that most need auditing — an open
10
+ * dialog, a shown popover, a selected tab panel — would never be
11
+ * seen by axe at all.
12
+ *
13
+ * So a driven state audits itself explicitly. The rule set is left
14
+ * at axe's default, matching the addon, except that `region` is
15
+ * disabled for the same reason the addon disables it: a component
16
+ * mounted on its own is not a page and owes nobody a landmark.
17
+ */
18
+ export declare const expectNoAxeViolations: (element: Element) => Promise<void>;
19
+ //# sourceMappingURL=expectNoAxeViolations.testHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expectNoAxeViolations.testHelpers.d.ts","sourceRoot":"","sources":["../src/expectNoAxeViolations.testHelpers.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,OAAO,KACf,OAAO,CAAC,IAAI,CAmBd,CAAA"}
@@ -0,0 +1,32 @@
1
+ import axe from "axe-core";
2
+ /**
3
+ * Run axe against a subtree, now.
4
+ *
5
+ * The a11y addon audits a story **once**, in an `afterEach` that
6
+ * fires when `run()` resolves — i.e. immediately after the first
7
+ * paint. That was enough while the assertions lived in `play`,
8
+ * because the addon's hook ran after the play had finished driving.
9
+ * With the tests split out, `run()` returns before a test has
10
+ * clicked anything, so the states that most need auditing — an open
11
+ * dialog, a shown popover, a selected tab panel — would never be
12
+ * seen by axe at all.
13
+ *
14
+ * So a driven state audits itself explicitly. The rule set is left
15
+ * at axe's default, matching the addon, except that `region` is
16
+ * disabled for the same reason the addon disables it: a component
17
+ * mounted on its own is not a page and owes nobody a landmark.
18
+ */
19
+ export const expectNoAxeViolations = async (element) => {
20
+ const results = await axe.run(element, {
21
+ rules: { region: { enabled: false } },
22
+ });
23
+ if (results.violations.length === 0) {
24
+ return;
25
+ }
26
+ throw new Error(`Expected no accessibility violations, found ${results.violations.length}:\n\n${results.violations
27
+ .map((violation) => ` ${violation.id} (${violation.impact}) — ${violation.help}\n${violation.nodes
28
+ .map((node) => ` ${node.html}`)
29
+ .join("\n")}`)
30
+ .join("\n\n")}`);
31
+ };
32
+ //# sourceMappingURL=expectNoAxeViolations.testHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expectNoAxeViolations.testHelpers.js","sourceRoot":"","sources":["../src/expectNoAxeViolations.testHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAA;AAE1B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EACxC,OAAgB,EACD,EAAE;IACjB,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE;QACrC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;KACtC,CAAC,CAAA;IAEF,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,OAAM;IACR,CAAC;IAED,MAAM,IAAI,KAAK,CACb,+CAA+C,OAAO,CAAC,UAAU,CAAC,MAAM,QAAQ,OAAO,CAAC,UAAU;SAC/F,GAAG,CACF,CAAC,SAAS,EAAE,EAAE,CACZ,KAAK,SAAS,CAAC,EAAE,KAAK,SAAS,CAAC,MAAM,OAAO,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK;SAC5E,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;SACjC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClB;SACA,IAAI,CAAC,MAAM,CAAC,EAAE,CAClB,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `@charcuterie/ui` — the one sanctioned barrel.
3
+ *
4
+ * Components never import each other through this file; they import
5
+ * the sibling module directly (`../Spinner/Spinner.tsx`). A barrel
6
+ * that a package's own internals go through turns every component
7
+ * into a dependency of every other one, which is how a 3 KB
8
+ * `Spinner` starts pulling in `MediaTile`.
9
+ *
10
+ * `"./src/*"` is also in `exports`, for the budget-sensitive
11
+ * consumers — `castkit/packages/slatecast` has 60 KB gz to spend and
12
+ * should be able to reach one component without the barrel. Same
13
+ * arrangement as `mux-magic/packages/tools`.
14
+ *
15
+ * The token **types and values** are re-exported at
16
+ * `@charcuterie/ui/tokens` rather than from here, so a React
17
+ * consumer's TypeScript never names a second package while Satori
18
+ * still gets `@charcuterie/tokens` with no React in sight.
19
+ *
20
+ * The token **stylesheet** is not, and M5 is why. There was a
21
+ * `"./tokens.css": "./node_modules/@charcuterie/tokens/dist/theme.css"`
22
+ * export here, and it resolved to nothing in the first real
23
+ * consumer: a hoisting linker puts `@charcuterie/tokens` at the
24
+ * *project* root, not inside this package, so that path exists only
25
+ * under a nesting one. A CSS `@import` fails silently in Tailwind —
26
+ * no error, no utilities, an unstyled app — which is exactly the
27
+ * class of failure `portal:` was chosen to catch
28
+ * ([decision](../../../docs/decisions/2026-07-29-consumers-link-tokens-by-portal-until-publish.md)).
29
+ * So a consumer installs both package names and imports
30
+ * `@charcuterie/tokens/theme.css` by its own name, which is what
31
+ * this package's README has always told it to do.
32
+ */
33
+ export type { AlertProps, AlertSize, } from "./Alert/Alert.tsx";
34
+ export { Alert } from "./Alert/Alert.tsx";
35
+ export type { BadgeProps } from "./Badge/Badge.tsx";
36
+ export { Badge } from "./Badge/Badge.tsx";
37
+ export type { ButtonProps } from "./Button/Button.tsx";
38
+ export { Button } from "./Button/Button.tsx";
39
+ export type { CardPadding, CardProps, CardSurface, } from "./Card/Card.tsx";
40
+ export { Card } from "./Card/Card.tsx";
41
+ export type { BadgeSize } from "./controlStyles.ts";
42
+ export { BADGE_SIZE_CLASS, CONTROL_SIZE_CLASS, DOT_SIZE_CLASS, ICON_CONTROL_SIZE_CLASS, MIN_TOUCH_TARGET_CLASS, SPINNER_SIZE_CLASS, } from "./controlStyles.ts";
43
+ export type { EmptyStateProps, EmptyStateSize, } from "./EmptyState/EmptyState.tsx";
44
+ export { EmptyState } from "./EmptyState/EmptyState.tsx";
45
+ export type { IconButtonProps } from "./IconButton/IconButton.tsx";
46
+ export { IconButton } from "./IconButton/IconButton.tsx";
47
+ export type { IntentAppearance } from "./intentStyles.ts";
48
+ export { DISABLED_CLASS, FOCUS_RING_CLASS, INTENT_APPEARANCE_CLASS, INTENT_CONTENT_CLASS, INTENT_HOVER_CLASS, INTENT_SOLID_FILL_CLASS, } from "./intentStyles.ts";
49
+ export type { LiveStatusIndicatorProps, LiveStatusIndicatorSize, } from "./LiveStatusIndicator/LiveStatusIndicator.tsx";
50
+ export { LiveStatusIndicator } from "./LiveStatusIndicator/LiveStatusIndicator.tsx";
51
+ export type { MediaTileProps, MediaTileRatio, } from "./MediaTile/MediaTile.tsx";
52
+ export { MediaTile } from "./MediaTile/MediaTile.tsx";
53
+ export type { MediaStatus } from "./MediaTile/mediaStatus.ts";
54
+ export { mediaTransitions } from "./MediaTile/mediaStatus.ts";
55
+ export type { ModalProps, ModalSize, } from "./Modal/Modal.tsx";
56
+ export { Modal } from "./Modal/Modal.tsx";
57
+ export type { PopoverProps } from "./Popover/Popover.tsx";
58
+ export { Popover } from "./Popover/Popover.tsx";
59
+ export type { ProgressBarProps, ProgressBarSize, } from "./ProgressBar/ProgressBar.tsx";
60
+ export { ProgressBar } from "./ProgressBar/ProgressBar.tsx";
61
+ export type { ProgressThreshold } from "./ProgressBar/progressValue.ts";
62
+ export { getProgressIntent, toProgressPercent, toProgressValue, } from "./ProgressBar/progressValue.ts";
63
+ export type { SegmentedControlProps, SegmentedItem, } from "./SegmentedControl/SegmentedControl.tsx";
64
+ export { SegmentedControl } from "./SegmentedControl/SegmentedControl.tsx";
65
+ export type { SkeletonProps, SkeletonShape, } from "./Skeleton/Skeleton.tsx";
66
+ export { Skeleton } from "./Skeleton/Skeleton.tsx";
67
+ export type { SpinnerProps } from "./Spinner/Spinner.tsx";
68
+ export { Spinner } from "./Spinner/Spinner.tsx";
69
+ export { getAsyncIntent, getAsyncLabel, getConnectionIntent, getConnectionLabel, getIsConnectionBusy, } from "./statusIntent.ts";
70
+ export type { TabItem, TabsActivation, TabsOrientation, TabsProps, } from "./Tabs/Tabs.tsx";
71
+ export { Tabs } from "./Tabs/Tabs.tsx";
72
+ export { toClassName } from "./toClassName.ts";
73
+ export type { VisuallyHiddenProps } from "./VisuallyHidden/VisuallyHidden.tsx";
74
+ export { VisuallyHidden } from "./VisuallyHidden/VisuallyHidden.tsx";
75
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}