@charcuterie/ui 4.5.0 → 4.6.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 (226) hide show
  1. package/README.md +7 -2
  2. package/dist/Card/Card.d.ts +5 -2
  3. package/dist/Card/Card.d.ts.map +1 -1
  4. package/dist/Card/Card.js +23 -3
  5. package/dist/Card/Card.js.map +1 -1
  6. package/dist/Overlay/OverlayPanel.d.ts.map +1 -1
  7. package/dist/Overlay/OverlayPanel.js +54 -2
  8. package/dist/Overlay/OverlayPanel.js.map +1 -1
  9. package/dist/Overlay/OverlayStack.d.ts +5 -0
  10. package/dist/Overlay/OverlayStack.d.ts.map +1 -1
  11. package/dist/Overlay/OverlayStack.js +2 -0
  12. package/dist/Overlay/OverlayStack.js.map +1 -1
  13. package/dist/Toast/ToastRegion.d.ts +61 -0
  14. package/dist/Toast/ToastRegion.d.ts.map +1 -1
  15. package/dist/Toast/ToastRegion.js +80 -3
  16. package/dist/Toast/ToastRegion.js.map +1 -1
  17. package/dist/Toolbar/useToolbarOverflow.d.ts.map +1 -1
  18. package/dist/Toolbar/useToolbarOverflow.js +39 -0
  19. package/dist/Toolbar/useToolbarOverflow.js.map +1 -1
  20. package/package.json +3 -2
  21. package/src/Accordion/Accordion.mdx +1 -1
  22. package/src/Accordion/Accordion.stories.tsx +3 -1
  23. package/src/Accordion/Accordion.test.tsx +4 -4
  24. package/src/ActionTiles/ActionTiles.mdx +1 -1
  25. package/src/ActionTiles/ActionTiles.stories.tsx +3 -1
  26. package/src/ActionTiles/ActionTiles.test.tsx +3 -3
  27. package/src/AdaptiveGrid/AdaptiveGrid.mdx +1 -1
  28. package/src/AdaptiveGrid/AdaptiveGrid.stories.tsx +3 -1
  29. package/src/AdaptiveGrid/AdaptiveGrid.test.tsx +5 -5
  30. package/src/Alert/Alert.mdx +1 -1
  31. package/src/Alert/Alert.stories.tsx +3 -1
  32. package/src/Alert/Alert.test.tsx +2 -2
  33. package/src/Avatar/Avatar.mdx +1 -1
  34. package/src/Avatar/Avatar.stories.tsx +4 -1
  35. package/src/Avatar/Avatar.test.tsx +2 -2
  36. package/src/Badge/Badge.mdx +1 -1
  37. package/src/Badge/Badge.stories.tsx +3 -1
  38. package/src/Badge/Badge.test.tsx +3 -3
  39. package/src/BadgeButton/BadgeButton.mdx +1 -1
  40. package/src/BadgeButton/BadgeButton.stories.tsx +3 -1
  41. package/src/BadgeButton/BadgeButton.test.tsx +2 -2
  42. package/src/Board/Board.mdx +1 -1
  43. package/src/Board/Board.stories.tsx +4 -1
  44. package/src/Board/Board.test.tsx +2 -2
  45. package/src/Button/Button.mdx +1 -1
  46. package/src/Button/Button.stories.tsx +3 -1
  47. package/src/Button/Button.test.tsx +2 -2
  48. package/src/ButtonLink/ButtonLink.mdx +1 -1
  49. package/src/ButtonLink/ButtonLink.stories.tsx +3 -1
  50. package/src/ButtonLink/ButtonLink.test.tsx +3 -3
  51. package/src/Card/Card.mdx +21 -1
  52. package/src/Card/Card.stories.tsx +91 -1
  53. package/src/Card/Card.test.tsx +60 -3
  54. package/src/Card/Card.tsx +24 -2
  55. package/src/Checkbox/Checkbox.mdx +1 -1
  56. package/src/Checkbox/Checkbox.stories.tsx +3 -1
  57. package/src/Checkbox/Checkbox.test.tsx +4 -4
  58. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.mdx +1 -1
  59. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.stories.tsx +3 -1
  60. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.test.tsx +5 -5
  61. package/src/ColorSchemeToggle/ColorSchemeToggle.mdx +1 -1
  62. package/src/ColorSchemeToggle/ColorSchemeToggle.stories.tsx +3 -1
  63. package/src/ColorSchemeToggle/ColorSchemeToggle.test.tsx +5 -5
  64. package/src/Combobox/Combobox.mdx +1 -1
  65. package/src/Combobox/Combobox.stories.tsx +3 -1
  66. package/src/Combobox/Combobox.test.tsx +2 -2
  67. package/src/CopyButton/CopyButton.mdx +1 -1
  68. package/src/CopyButton/CopyButton.stories.tsx +4 -1
  69. package/src/CopyButton/CopyButton.test.tsx +3 -3
  70. package/src/DataTable/DataTable.mdx +1 -1
  71. package/src/DataTable/DataTable.stories.tsx +3 -1
  72. package/src/DataTable/DataTable.test.tsx +3 -3
  73. package/src/DatePicker/DatePicker.mdx +1 -1
  74. package/src/DatePicker/DatePicker.stories.tsx +3 -1
  75. package/src/DatePicker/DatePicker.test.tsx +3 -3
  76. package/src/DatePicker/plainDate.test.ts +21 -3
  77. package/src/Dialog/Dialog.mdx +1 -1
  78. package/src/Dialog/Dialog.stories.tsx +3 -1
  79. package/src/Dialog/Dialog.test.tsx +2 -2
  80. package/src/DropRail/DropRail.mdx +1 -1
  81. package/src/DropRail/DropRail.stories.tsx +4 -1
  82. package/src/DropRail/DropRail.test.tsx +4 -4
  83. package/src/EmptyState/EmptyState.mdx +1 -1
  84. package/src/EmptyState/EmptyState.stories.tsx +3 -1
  85. package/src/EmptyState/EmptyState.test.tsx +2 -2
  86. package/src/Field/Field.mdx +1 -1
  87. package/src/Field/Field.stories.tsx +3 -1
  88. package/src/Field/Field.test.tsx +4 -4
  89. package/src/FileDropZone/FileDropZone.mdx +1 -1
  90. package/src/FileDropZone/FileDropZone.stories.tsx +3 -1
  91. package/src/FileDropZone/FileDropZone.test.tsx +3 -3
  92. package/src/Header/Header.mdx +1 -1
  93. package/src/Header/Header.stories.tsx +3 -1
  94. package/src/IconButton/IconButton.mdx +1 -1
  95. package/src/IconButton/IconButton.stories.tsx +3 -1
  96. package/src/IconButton/IconButton.test.tsx +2 -2
  97. package/src/Lightbox/Lightbox.mdx +1 -1
  98. package/src/Lightbox/Lightbox.stories.tsx +3 -1
  99. package/src/Lightbox/Lightbox.test.tsx +2 -2
  100. package/src/Listbox/Listbox.mdx +1 -1
  101. package/src/Listbox/Listbox.stories.tsx +3 -1
  102. package/src/Listbox/Listbox.test.tsx +5 -5
  103. package/src/LiveStatusIndicator/LiveStatusIndicator.mdx +1 -1
  104. package/src/LiveStatusIndicator/LiveStatusIndicator.stories.tsx +3 -1
  105. package/src/LiveStatusIndicator/LiveStatusIndicator.test.tsx +2 -2
  106. package/src/LogViewer/LogViewer.mdx +1 -1
  107. package/src/LogViewer/LogViewer.stories.tsx +3 -1
  108. package/src/LogViewer/LogViewer.test.tsx +2 -2
  109. package/src/Main/Main.mdx +1 -1
  110. package/src/Main/Main.stories.tsx +3 -1
  111. package/src/Main/Main.test.tsx +3 -3
  112. package/src/MarkdownEditor/MarkdownEditor.mdx +1 -1
  113. package/src/MarkdownEditor/MarkdownEditor.stories.tsx +3 -1
  114. package/src/MarkdownEditor/MarkdownEditor.test.tsx +4 -4
  115. package/src/MarkdownLine/MarkdownLine.mdx +1 -1
  116. package/src/MarkdownLine/MarkdownLine.stories.tsx +4 -1
  117. package/src/MarkdownLine/MarkdownLine.test.tsx +3 -3
  118. package/src/MediaTile/MediaTile.stories.tsx +3 -1
  119. package/src/MediaTile/MediaTile.test.tsx +2 -2
  120. package/src/Menu/Menu.mdx +1 -1
  121. package/src/Menu/Menu.stories.tsx +3 -1
  122. package/src/Modal/Modal.mdx +1 -1
  123. package/src/Modal/Modal.stories.tsx +3 -1
  124. package/src/Modal/Modal.test.tsx +3 -3
  125. package/src/Nav/Nav.mdx +1 -1
  126. package/src/Nav/Nav.stories.tsx +3 -1
  127. package/src/Nav/Nav.test.tsx +6 -6
  128. package/src/NavBar/NavBar.mdx +1 -1
  129. package/src/NavBar/NavBar.stories.tsx +3 -1
  130. package/src/NavBar/NavBar.test.tsx +3 -3
  131. package/src/Overlay/OverlayPanel.tsx +63 -2
  132. package/src/Overlay/OverlayStack.tsx +7 -1
  133. package/src/Picker/Picker.mdx +1 -1
  134. package/src/Picker/Picker.stories.tsx +3 -1
  135. package/src/Picker/Picker.test.tsx +2 -2
  136. package/src/Popover/Popover.mdx +1 -1
  137. package/src/Popover/Popover.stories.tsx +3 -1
  138. package/src/Popover/Popover.test.tsx +2 -2
  139. package/src/PortraitTiles/PortraitTiles.mdx +1 -1
  140. package/src/PortraitTiles/PortraitTiles.stories.tsx +3 -1
  141. package/src/PortraitTiles/PortraitTiles.test.tsx +5 -5
  142. package/src/ProgressBar/ProgressBar.mdx +1 -1
  143. package/src/ProgressBar/ProgressBar.stories.tsx +3 -1
  144. package/src/ProgressBar/ProgressBar.test.tsx +2 -2
  145. package/src/QueryBuilder/QueryBuilder.mdx +1 -1
  146. package/src/QueryBuilder/QueryBuilder.stories.tsx +3 -1
  147. package/src/QueryBuilder/QueryBuilder.test.tsx +2 -2
  148. package/src/RadioGroup/RadioGroup.mdx +1 -1
  149. package/src/RadioGroup/RadioGroup.stories.tsx +3 -1
  150. package/src/RadioGroup/RadioGroup.test.tsx +2 -2
  151. package/src/Rail/Rail.mdx +1 -1
  152. package/src/Rail/Rail.stories.tsx +3 -1
  153. package/src/Rail/Rail.test.tsx +2 -2
  154. package/src/RangeSlider/RangeSlider.mdx +1 -1
  155. package/src/RangeSlider/RangeSlider.stories.tsx +3 -1
  156. package/src/RangeSlider/RangeSlider.test.tsx +7 -3
  157. package/src/ReorderList/ReorderList.mdx +1 -1
  158. package/src/ReorderList/ReorderList.stories.tsx +3 -1
  159. package/src/ReorderList/ReorderList.test.tsx +7 -7
  160. package/src/RouterLink/UnstyledLink.stories.tsx +4 -1
  161. package/src/RouterLink/UnstyledLink.test.tsx +2 -2
  162. package/src/SearchInput/SearchInput.mdx +1 -1
  163. package/src/SearchInput/SearchInput.stories.tsx +3 -1
  164. package/src/SegmentedControl/SegmentedControl.mdx +1 -1
  165. package/src/SegmentedControl/SegmentedControl.stories.tsx +3 -1
  166. package/src/SegmentedControl/SegmentedControl.test.tsx +2 -2
  167. package/src/Select/Select.mdx +1 -1
  168. package/src/Select/Select.stories.tsx +3 -1
  169. package/src/Select/Select.test.tsx +4 -4
  170. package/src/Shell/Shell.mdx +1 -1
  171. package/src/Shell/Shell.stories.tsx +3 -1
  172. package/src/Shell/Shell.test.tsx +2 -2
  173. package/src/Skeleton/Skeleton.mdx +1 -1
  174. package/src/Skeleton/Skeleton.stories.tsx +3 -1
  175. package/src/Skeleton/Skeleton.test.tsx +2 -2
  176. package/src/Slider/Slider.mdx +1 -1
  177. package/src/Slider/Slider.stories.tsx +3 -1
  178. package/src/Slider/Slider.test.tsx +2 -2
  179. package/src/SortableTableHeader/SortableTableHeader.mdx +1 -1
  180. package/src/SortableTableHeader/SortableTableHeader.stories.tsx +3 -1
  181. package/src/SortableTableHeader/SortableTableHeader.test.tsx +3 -3
  182. package/src/Spinner/Spinner.mdx +1 -1
  183. package/src/Spinner/Spinner.stories.tsx +3 -1
  184. package/src/Spinner/Spinner.test.tsx +2 -2
  185. package/src/Stepper/Stepper.mdx +1 -1
  186. package/src/Stepper/Stepper.stories.tsx +4 -1
  187. package/src/Stepper/Stepper.test.tsx +7 -7
  188. package/src/Swatch/Swatch.mdx +1 -1
  189. package/src/Swatch/Swatch.stories.tsx +4 -1
  190. package/src/Swatch/Swatch.test.tsx +3 -3
  191. package/src/Switch/Switch.mdx +1 -1
  192. package/src/Switch/Switch.stories.tsx +3 -1
  193. package/src/Switch/Switch.test.tsx +2 -2
  194. package/src/Tabs/Tabs.mdx +1 -1
  195. package/src/Tabs/Tabs.stories.tsx +3 -1
  196. package/src/Tabs/Tabs.test.tsx +3 -3
  197. package/src/TextLink/TextLink.mdx +1 -1
  198. package/src/TextLink/TextLink.stories.tsx +3 -1
  199. package/src/TextLink/TextLink.test.tsx +4 -4
  200. package/src/TimecodeInput/TimecodeInput.mdx +1 -1
  201. package/src/TimecodeInput/TimecodeInput.stories.tsx +3 -1
  202. package/src/Toast/Toast.mdx +1 -1
  203. package/src/Toast/Toast.stories.tsx +157 -1
  204. package/src/Toast/Toast.test.tsx +2 -2
  205. package/src/Toast/ToastRegion.test.tsx +172 -0
  206. package/src/Toast/ToastRegion.tsx +120 -32
  207. package/src/Toolbar/Toolbar.mdx +1 -1
  208. package/src/Toolbar/Toolbar.stories.tsx +3 -1
  209. package/src/Toolbar/Toolbar.test.tsx +89 -5
  210. package/src/Toolbar/useToolbarOverflow.ts +43 -0
  211. package/src/Tooltip/Tooltip.mdx +1 -1
  212. package/src/Tooltip/Tooltip.stories.tsx +3 -1
  213. package/src/Tooltip/Tooltip.test.tsx +2 -2
  214. package/src/VirtualizedGrid/VirtualizedGrid.mdx +1 -1
  215. package/src/VirtualizedGrid/VirtualizedGrid.stories.tsx +4 -1
  216. package/src/VirtualizedGrid/VirtualizedGrid.test.tsx +5 -5
  217. package/src/VisuallyHidden/VisuallyHidden.mdx +1 -1
  218. package/src/VisuallyHidden/VisuallyHidden.stories.tsx +3 -1
  219. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.mdx +1 -1
  220. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.stories.tsx +3 -1
  221. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.test.tsx +3 -3
  222. package/src/markdownEditorCodeMirror/MarkdownView.mdx +1 -1
  223. package/src/markdownEditorCodeMirror/MarkdownView.stories.tsx +4 -1
  224. package/src/markdownEditorCodeMirror/MarkdownView.test.tsx +44 -20
  225. package/src/reactRouter/ReactRouterAdapter.stories.tsx +3 -1
  226. package/src/reactRouter/ReactRouterAdapter.test.tsx +2 -2
package/src/Card/Card.tsx CHANGED
@@ -83,7 +83,10 @@ const PADDING_CLASS: Record<CardPadding, string> = {
83
83
  *
84
84
  * `aria-labelledby` rather than `aria-label`, because the heading is
85
85
  * on screen already and stating the name twice is how the two drift
86
- * apart.
86
+ * apart. A card with **no** `heading` keeps the `aria-labelledby`
87
+ * the call site passed, for the app that draws its own title row
88
+ * inside the card — the name still points at text on screen, which
89
+ * is the same rule one level out.
87
90
  *
88
91
  * **Density is not a prop here.** mux-magic's inventory turned up a
89
92
  * `CardDensityProvider` in the withdrawn bambuddy evidence, and it
@@ -94,6 +97,7 @@ const PADDING_CLASS: Record<CardPadding, string> = {
94
97
  * a layout fact rather than a density one.
95
98
  */
96
99
  export const Card = ({
100
+ "aria-labelledby": ariaLabelledBy,
97
101
  accentEdge,
98
102
  actions,
99
103
  children,
@@ -122,12 +126,30 @@ export const Card = ({
122
126
  ? style
123
127
  : { ...accentEdgeStyle, ...style }
124
128
 
129
+ // The caller's own pointer is the FALLBACK, not the loser. This
130
+ // attribute is written *after* the spread, so `undefined` here
131
+ // does not mean "leave whatever the call site passed": it
132
+ // overwrites it, React then drops the attribute entirely, and the
133
+ // card's accessible name falls back to its whole subtree with
134
+ // nothing failing — not typecheck, not axe, not a render. A
135
+ // `role="button"` tile in mail-sifter naming itself
136
+ // `aria-labelledby="recent-tile-heading"` announced as "Recent
137
+ // Mail 6 40 arrived in the last 24 hours, 34 done".
138
+ //
139
+ // `heading` still wins when it is given, because that is the name
140
+ // the reader can see and this component renders it. Do not
141
+ // "simplify" this back to a ternary ending in `undefined`.
142
+ //
143
+ // `aria-label` needs no equivalent: the component never writes
144
+ // one, so the spread carries the caller's through untouched.
145
+ const labelledById = heading ? headingId : ariaLabelledBy
146
+
125
147
  const Heading = `h${headingLevel}` as const
126
148
 
127
149
  return (
128
150
  <section
129
151
  {...sectionProps}
130
- aria-labelledby={heading ? headingId : undefined}
152
+ aria-labelledby={labelledById}
131
153
  className={toClassName(
132
154
  // `@container` here, not on a wrapper, is what lets the
133
155
  // header stack at narrow widths *inside a wide viewport* —
@@ -16,7 +16,7 @@ Here the box is `bg-surface-sunken`, its border `border-border-strong`, and the
16
16
  the kiosk with no per-app override. `BooleanField` becomes a thin wrapper that binds this
17
17
  to a `CommandField`.
18
18
 
19
- <Canvas of={CheckboxStories.Default} />
19
+ <Canvas of={CheckboxStories.Playground} />
20
20
 
21
21
  ## Props
22
22
 
@@ -1,3 +1,4 @@
1
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
1
2
  import type { Meta, StoryObj } from "@storybook/react"
2
3
 
3
4
  import { controlSizeArgType } from "../argTypes.storyHelpers.ts"
@@ -29,7 +30,8 @@ export default meta
29
30
 
30
31
  type Story = StoryObj<typeof meta>
31
32
 
32
- export const Default: Story = {
33
+ export const Playground: Story = {
34
+ parameters: playgroundParameters,
33
35
  args: { label: "Delete originals after import" },
34
36
  }
35
37
 
@@ -9,7 +9,7 @@ import * as stories from "./Checkbox.stories.tsx"
9
9
 
10
10
  const {
11
11
  AllStates,
12
- Default,
12
+ Playground,
13
13
  Interactive,
14
14
  WithDescriptions,
15
15
  WithValues,
@@ -17,7 +17,7 @@ const {
17
17
 
18
18
  test("it is a checkbox a screen reader can name", async () => {
19
19
  const { canvas, canvasElement } =
20
- await mountStory(Default)
20
+ await mountStory(Playground)
21
21
 
22
22
  const checkbox = expectAgentDrivable(canvas, {
23
23
  name: "Delete originals after import",
@@ -151,7 +151,7 @@ test("value names the member, and a group reads back the ticked ones", async ()
151
151
  })
152
152
 
153
153
  test("a box with no value stays out of the way", async () => {
154
- const { canvas } = await mountStory(Default)
154
+ const { canvas } = await mountStory(Playground)
155
155
 
156
156
  // `value` is optional and a lone boolean should omit it. React drops
157
157
  // an `undefined` attribute entirely rather than writing an empty
@@ -252,7 +252,7 @@ test("each box owns its own description, and the group owns the group's", async
252
252
  })
253
253
 
254
254
  test("a box with no description writes no aria-describedby", async () => {
255
- const { canvas } = await mountStory(Default)
255
+ const { canvas } = await mountStory(Playground)
256
256
 
257
257
  // The slot is opt-in. React drops an `undefined` attribute rather
258
258
  // than writing an empty string, so a box without a hint points at
@@ -12,7 +12,7 @@ the [`useColorScheme`](/) hook to the three defaults from `@charcuterie/logic/br
12
12
  [`ColorSchemeToggle`](?path=/docs/components-colorschemetoggle--docs). Drop it in and the app
13
13
  follows the OS, remembers the user's choice, and re-themes with zero React re-render.
14
14
 
15
- <Canvas of={ColorSchemeSwitcherStories.Default} />
15
+ <Canvas of={ColorSchemeSwitcherStories.Playground} />
16
16
 
17
17
  ## Props
18
18
 
@@ -3,6 +3,7 @@ import type {
3
3
  ColorSchemeResolver,
4
4
  ResolvedColorScheme,
5
5
  } from "@charcuterie/logic"
6
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
6
7
  import type { Meta, StoryObj } from "@storybook/react"
7
8
  import { useRef, useState } from "react"
8
9
  import type { ColorSchemeIcons } from "../ColorSchemeToggle/ColorSchemeToggle.tsx"
@@ -113,6 +114,7 @@ type Story = StoryObj<typeof meta>
113
114
  * scoped to the demo panel, which is exactly how a non-browser host
114
115
  * (Electron `nativeTheme`) wires it — by replacing those seams.
115
116
  */
116
- export const Default: Story = {
117
+ export const Playground: Story = {
118
+ parameters: playgroundParameters,
117
119
  render: () => <ScopedSwitcherDemo />,
118
120
  }
@@ -7,7 +7,7 @@ import { mountStory } from "../mountStory.testHelpers.ts"
7
7
  import { expectAgentDrivable } from "../testing/index.ts"
8
8
  import * as stories from "./ColorSchemeSwitcher.stories.tsx"
9
9
 
10
- const { Default } = composeStories(stories)
10
+ const { Playground } = composeStories(stories)
11
11
 
12
12
  /**
13
13
  * The connected switcher is agent-drivable and cycles like the
@@ -15,7 +15,7 @@ const { Default } = composeStories(stories)
15
15
  * reaches the applier: the demo panel's `data-scheme` tracks it.
16
16
  */
17
17
  test("it renders a named, drivable button", async () => {
18
- const { canvas } = await mountStory(Default)
18
+ const { canvas } = await mountStory(Playground)
19
19
 
20
20
  expectAgentDrivable(canvas, {
21
21
  name: /colour scheme/i,
@@ -24,7 +24,7 @@ test("it renders a named, drivable button", async () => {
24
24
  })
25
25
 
26
26
  test("system resolves through the injected resolver to the panel", async () => {
27
- const { canvasElement } = await mountStory(Default)
27
+ const { canvasElement } = await mountStory(Playground)
28
28
 
29
29
  const panel = canvasElement.querySelector("[data-scheme]")
30
30
 
@@ -33,7 +33,7 @@ test("system resolves through the injected resolver to the panel", async () => {
33
33
  })
34
34
 
35
35
  test("cycling to a concrete mode re-applies the scheme", async () => {
36
- const { canvasElement } = await mountStory(Default)
36
+ const { canvasElement } = await mountStory(Playground)
37
37
 
38
38
  const canvas = within(canvasElement)
39
39
 
@@ -53,7 +53,7 @@ test("cycling to a concrete mode re-applies the scheme", async () => {
53
53
  })
54
54
 
55
55
  test("the connected switcher forwards the neutral intent by default", async () => {
56
- const { canvasElement } = await mountStory(Default)
56
+ const { canvasElement } = await mountStory(Playground)
57
57
 
58
58
  const button = canvasElement.querySelector("button")
59
59
 
@@ -16,7 +16,7 @@ That split is deliberate. The browser dependency lives one layer up in
16
16
  runs unchanged in any runtime — a plain React app wires `onCycle` to `useState`, an Electron
17
17
  or React-Native host wires it to `useColorScheme` with its own resolver.
18
18
 
19
- <Canvas of={ColorSchemeToggleStories.Default} />
19
+ <Canvas of={ColorSchemeToggleStories.Playground} />
20
20
 
21
21
  ## Props
22
22
 
@@ -1,4 +1,5 @@
1
1
  import type { ColorSchemeMode } from "@charcuterie/logic"
2
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
2
3
  import type { Meta, StoryObj } from "@storybook/react"
3
4
  import { useState } from "react"
4
5
 
@@ -71,7 +72,8 @@ type Story = StoryObj<typeof meta>
71
72
  * The accessible name states the current mode and the next one, so
72
73
  * it changes as the button cycles — that is what the test asserts.
73
74
  */
74
- export const Default: Story = {
75
+ export const Playground: Story = {
76
+ parameters: playgroundParameters,
75
77
  render: (toggleProps) => {
76
78
  const [mode, setMode] =
77
79
  useState<ColorSchemeMode>("system")
@@ -10,7 +10,7 @@ import { mountStory } from "../mountStory.testHelpers.ts"
10
10
  import { expectAgentDrivable } from "../testing/index.ts"
11
11
  import meta, * as stories from "./ColorSchemeToggle.stories.tsx"
12
12
 
13
- const { AllModes, Default } = composeStories(stories)
13
+ const { AllModes, Playground } = composeStories(stories)
14
14
 
15
15
  /**
16
16
  * The name states the current mode — a screen reader that lands on
@@ -18,7 +18,7 @@ const { AllModes, Default } = composeStories(stories)
18
18
  * and an agent can find it.
19
19
  */
20
20
  test("the toggle is named for its current mode", async () => {
21
- const { canvas } = await mountStory(Default)
21
+ const { canvas } = await mountStory(Playground)
22
22
 
23
23
  expectAgentDrivable(canvas, {
24
24
  name: /colour scheme: system/i,
@@ -27,7 +27,7 @@ test("the toggle is named for its current mode", async () => {
27
27
  })
28
28
 
29
29
  test("a press cycles the mode and updates the name", async () => {
30
- const { canvas } = await mountStory(Default)
30
+ const { canvas } = await mountStory(Playground)
31
31
 
32
32
  const button = expectAgentDrivable(canvas, {
33
33
  name: /colour scheme: system/i,
@@ -53,7 +53,7 @@ test("a press cycles the mode and updates the name", async () => {
53
53
 
54
54
  test("the icon is hidden from assistive tech; the name carries it", async () => {
55
55
  const { canvas, canvasElement } =
56
- await mountStory(Default)
56
+ await mountStory(Playground)
57
57
 
58
58
  expectAgentDrivable(canvas, {
59
59
  name: /colour scheme/i,
@@ -90,7 +90,7 @@ test("each mode renders its own control", async () => {
90
90
  * action on real app chrome.
91
91
  */
92
92
  test("defaults to the neutral intent, not accent", async () => {
93
- const { canvasElement } = await mountStory(Default)
93
+ const { canvasElement } = await mountStory(Playground)
94
94
 
95
95
  const button = canvasElement.querySelector("button")
96
96
 
@@ -13,7 +13,7 @@ Use it when the list is long enough to search or filter; reach for
13
13
  [`Listbox`](?path=/docs/components-listbox--docs) for a short rich-option list, and the
14
14
  native [`Select`](?path=/docs/components-select--docs) for a plain one.
15
15
 
16
- <Canvas of={ComboboxStories.Default} />
16
+ <Canvas of={ComboboxStories.Playground} />
17
17
 
18
18
  ## Props
19
19
 
@@ -1,4 +1,5 @@
1
1
  import { useVisibility } from "@charcuterie/logic"
2
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
2
3
  import type { ControlSize } from "@charcuterie/tokens"
3
4
  import type { Meta, StoryObj } from "@storybook/react"
4
5
  import type { ReactNode } from "react"
@@ -150,7 +151,8 @@ const ComboboxHarness = ({
150
151
  )
151
152
  }
152
153
 
153
- export const Default: Story = {
154
+ export const Playground: Story = {
155
+ parameters: playgroundParameters,
154
156
  args: {
155
157
  isVisible: false,
156
158
  onDismiss: () => {},
@@ -11,7 +11,7 @@ const {
11
11
  AllVariants,
12
12
  AttachedInputDrillDown,
13
13
  ChosenValueOnOpen,
14
- Default,
14
+ Playground,
15
15
  DisabledFirstOption,
16
16
  Interactive,
17
17
  LongFooter,
@@ -42,7 +42,7 @@ const openMulti = async () => {
42
42
  }
43
43
 
44
44
  const openDefault = async () => {
45
- const mounted = await mountStory(Default)
45
+ const mounted = await mountStory(Playground)
46
46
 
47
47
  await userEvent.click(
48
48
  expectAgentDrivable(mounted.canvas, {
@@ -9,7 +9,7 @@ import * as CopyButtonStories from "./CopyButton.stories";
9
9
  **A button that puts a string on the clipboard and says so.** Press it, the label turns to
10
10
  `Copied`, and the caller is told whether that is true.
11
11
 
12
- <Canvas of={CopyButtonStories.Default} />
12
+ <Canvas of={CopyButtonStories.Playground} />
13
13
 
14
14
  ## Props
15
15
 
@@ -1,3 +1,4 @@
1
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
1
2
  import type { Meta, StoryObj } from "@storybook/react"
2
3
  import { userEvent, within } from "storybook/test"
3
4
 
@@ -29,7 +30,9 @@ export default meta
29
30
 
30
31
  type Story = StoryObj<typeof meta>
31
32
 
32
- export const Default: Story = {}
33
+ export const Playground: Story = {
34
+ parameters: playgroundParameters,
35
+ }
33
36
 
34
37
  /**
35
38
  * The confirmation, held open. `confirmDuration` is enormous here so
@@ -16,11 +16,11 @@ import { expectAgentDrivable } from "../testing/index.ts"
16
16
  import meta, * as stories from "./CopyButton.stories.tsx"
17
17
  import { copyText } from "./copyText.ts"
18
18
 
19
- const { Confirmed, Default, Refused } =
19
+ const { Confirmed, Playground, Refused } =
20
20
  composeStories(stories)
21
21
 
22
22
  test("is a button an agent can find by role and name", async () => {
23
- const { canvas } = await mountStory(Default)
23
+ const { canvas } = await mountStory(Playground)
24
24
 
25
25
  const button = expectAgentDrivable(canvas, {
26
26
  name: "Copy",
@@ -170,7 +170,7 @@ test("a prevented click copies nothing", async () => {
170
170
  })
171
171
 
172
172
  test("has no axe violations", async () => {
173
- const { canvasElement } = await mountStory(Default)
173
+ const { canvasElement } = await mountStory(Playground)
174
174
 
175
175
  await expectNoAxeViolations(canvasElement)
176
176
  })
@@ -10,7 +10,7 @@ import * as DataTableStories from "./DataTable.stories";
10
10
  optional selection column, and the sort *announcement* — the data itself stays the
11
11
  consumer's.
12
12
 
13
- <Canvas of={DataTableStories.Default} />
13
+ <Canvas of={DataTableStories.Playground} />
14
14
 
15
15
  ## Props
16
16
 
@@ -1,3 +1,4 @@
1
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
1
2
  import type { Meta, StoryObj } from "@storybook/react"
2
3
  import type { ReactNode } from "react"
3
4
  import { useState } from "react"
@@ -266,7 +267,8 @@ type Story = StoryObj<typeof meta>
266
267
  * of the split: the component says *which* column is sorted, the
267
268
  * consumer says what that means for a priority enum.
268
269
  */
269
- export const Default: Story = {
270
+ export const Playground: Story = {
271
+ parameters: playgroundParameters,
270
272
  render: () => (
271
273
  <TaskTable
272
274
  initialSort={{
@@ -11,7 +11,7 @@ import {
11
11
  } from "../viewport.testHelpers.ts"
12
12
  import * as stories from "./DataTable.stories.tsx"
13
13
 
14
- const { AllVariants, Default, Interactive, Responsive } =
14
+ const { AllVariants, Playground, Interactive, Responsive } =
15
15
  composeStories(stories)
16
16
 
17
17
  /**
@@ -23,7 +23,7 @@ const { AllVariants, Default, Interactive, Responsive } =
23
23
  */
24
24
  test("the table is drivable by role and name", async () => {
25
25
  const { canvas, canvasElement } =
26
- await mountStory(Default)
26
+ await mountStory(Playground)
27
27
 
28
28
  const table = expectAgentDrivable(canvas, {
29
29
  name: "Tasks",
@@ -43,7 +43,7 @@ test("the table is drivable by role and name", async () => {
43
43
  * cannot be sorted", where `"none"` would say the opposite.
44
44
  */
45
45
  test("sortable columns announce, unsortable ones stay silent", async () => {
46
- const { canvas } = await mountStory(Default)
46
+ const { canvas } = await mountStory(Playground)
47
47
 
48
48
  const updated = expectAgentDrivable(canvas, {
49
49
  name: "Updated",
@@ -11,7 +11,7 @@ it. The consumer that shaped it is **Docket** — due dates, scheduled dates, ph
11
11
  ranges, and a staleness threshold counted in days
12
12
  ([decision](../../../docs/decisions/2026-08-19-a-date-is-a-calendar-date-not-an-instant.md)).
13
13
 
14
- <Canvas of={DatePickerStories.Default} />
14
+ <Canvas of={DatePickerStories.Playground} />
15
15
 
16
16
  ## Props
17
17
 
@@ -1,3 +1,4 @@
1
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
1
2
  import type { Meta, StoryObj } from "@storybook/react"
2
3
  import type { ReactNode } from "react"
3
4
  import { useState } from "react"
@@ -99,7 +100,8 @@ const DatePickerHarness = ({
99
100
  * `19 aug`, `2026-08-19` and a bare `19` all resolve, and the line
100
101
  * underneath shows what they resolved to before anything commits.
101
102
  */
102
- export const Default: Story = {
103
+ export const Playground: Story = {
104
+ parameters: playgroundParameters,
103
105
  args: { label: "Due date" },
104
106
  render: () => <DatePickerHarness label="Due date" />,
105
107
  }
@@ -12,11 +12,11 @@ import {
12
12
  } from "../viewport.testHelpers.ts"
13
13
  import * as stories from "./DatePicker.stories.tsx"
14
14
 
15
- const { AllVariants, Default, Interactive } =
15
+ const { AllVariants, Playground, Interactive } =
16
16
  composeStories(stories)
17
17
 
18
18
  const openDefault = async () => {
19
- const mounted = await mountStory(Default)
19
+ const mounted = await mountStory(Playground)
20
20
 
21
21
  const input = expectAgentDrivable(mounted.canvas, {
22
22
  name: "Due date",
@@ -415,7 +415,7 @@ test("a container narrower than --cq-xs drops the weekday headers to one letter"
415
415
  // clamped to the space available and the calendar reads *that*.
416
416
  await setViewport({ height: 700, width: 260 })
417
417
 
418
- const { body, canvas } = await mountStory(Default)
418
+ const { body, canvas } = await mountStory(Playground)
419
419
 
420
420
  await userEvent.click(
421
421
  expectAgentDrivable(canvas, {
@@ -155,16 +155,34 @@ test("the day number round-trips across four centuries", () => {
155
155
  year: 2101,
156
156
  })
157
157
 
158
+ // ⚠️ ONE assertion, not 74 000 of them.
159
+ //
160
+ // This loop covers about 74 000 days, and it used to call `expect` on every
161
+ // one. Vitest builds an assertion object per call, so the assertion overhead
162
+ // WAS the whole cost of the test — it ran right up against the default 5 000
163
+ // ms timeout and started failing on 2026-09-14 when the hosted runners got
164
+ // slower. Collecting the mismatches and asserting once removes the overhead
165
+ // and keeps every day covered.
166
+ //
167
+ // It also reports better. The old shape stopped at the first bad day; this
168
+ // one names every day that failed, which is what you want from a round-trip
169
+ // over a century rule.
170
+ const mismatchedDayNumbers = []
171
+
158
172
  for (
159
173
  let dayNumber = from;
160
174
  dayNumber <= to;
161
175
  dayNumber += 1
162
176
  ) {
163
- expect(
164
- getDayNumber(getPlainDateFromDayNumber(dayNumber)),
165
- ).toBe(dayNumber)
177
+ if (
178
+ getDayNumber(getPlainDateFromDayNumber(dayNumber)) !==
179
+ dayNumber
180
+ )
181
+ mismatchedDayNumbers.push(dayNumber)
166
182
  }
167
183
 
184
+ expect(mismatchedDayNumbers).toEqual([])
185
+
168
186
  expect(
169
187
  getDayNumber({ day: 1, month: 1, year: 1970 }),
170
188
  ).toBe(0)
@@ -12,7 +12,7 @@ Close button, a scrolling body, an optional footer, and five sizes. This is what
12
12
  and hands the generic one back to the base layer
13
13
  ([decision](../../../docs/decisions/2026-08-03-modal-is-the-base-layer-and-dialog-is-the-component.md)).
14
14
 
15
- <Canvas of={DialogStories.Default} />
15
+ <Canvas of={DialogStories.Playground} />
16
16
 
17
17
  ## Props
18
18
 
@@ -1,4 +1,5 @@
1
1
  import { useVisibility } from "@charcuterie/logic"
2
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
2
3
  import type { Meta, StoryObj } from "@storybook/react"
3
4
  import type { ReactNode } from "react"
4
5
  import { useRef } from "react"
@@ -121,7 +122,8 @@ const InitialFocusDemo = (): ReactNode => {
121
122
  )
122
123
  }
123
124
 
124
- export const Default: Story = {
125
+ export const Playground: Story = {
126
+ parameters: playgroundParameters,
125
127
  args: {
126
128
  heading: "Bay 3",
127
129
  isVisible: false,
@@ -13,7 +13,7 @@ import * as stories from "./Dialog.stories.tsx"
13
13
  const {
14
14
  AllStates,
15
15
  AllVariants,
16
- Default,
16
+ Playground,
17
17
  InitialFocus,
18
18
  Interactive,
19
19
  NoBody,
@@ -49,7 +49,7 @@ test("a dialog with no body renders from heading and footer alone", async () =>
49
49
  })
50
50
 
51
51
  test("the dialog is reachable from a body-scoped query", async () => {
52
- const { body, canvas } = await mountStory(Default)
52
+ const { body, canvas } = await mountStory(Playground)
53
53
 
54
54
  await userEvent.click(
55
55
  expectAgentDrivable(canvas, {
@@ -9,7 +9,7 @@ import * as DropRailStories from "./DropRail.stories";
9
9
  Every destination at once, brought to the pointer instead of the pointer being carried to
10
10
  it.
11
11
 
12
- <Canvas of={DropRailStories.Default} />
12
+ <Canvas of={DropRailStories.Playground} />
13
13
 
14
14
  ## Props
15
15
 
@@ -1,3 +1,4 @@
1
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
1
2
  import type { Meta, StoryObj } from "@storybook/react"
2
3
  import { useState } from "react"
3
4
 
@@ -170,7 +171,9 @@ type Story = StoryObj<typeof meta>
170
171
  * group the card is already in — visible, unofferable, and saying so
171
172
  * to a screen reader rather than only by being dimmer.
172
173
  */
173
- export const Default: Story = {}
174
+ export const Playground: Story = {
175
+ parameters: playgroundParameters,
176
+ }
174
177
 
175
178
  /**
176
179
  * The case the component exists for. Thirty-four destinations do not
@@ -6,7 +6,7 @@ import { expectNoAxeViolations } from "../expectNoAxeViolations.testHelpers.ts"
6
6
  import { mountStory } from "../mountStory.testHelpers.ts"
7
7
  import * as stories from "./DropRail.stories.tsx"
8
8
 
9
- const { AllStates, Default, InAList, ManyTargets } =
9
+ const { AllStates, Playground, InAList, ManyTargets } =
10
10
  composeStories(stories)
11
11
 
12
12
  /**
@@ -25,7 +25,7 @@ const { AllStates, Default, InAList, ManyTargets } =
25
25
  */
26
26
 
27
27
  test("the rail is a listbox of destinations", async () => {
28
- const { canvas } = await mountStory(Default)
28
+ const { canvas } = await mountStory(Playground)
29
29
 
30
30
  const rail = canvas.getByRole("listbox", {
31
31
  name: "Move to which project",
@@ -51,7 +51,7 @@ test("a closed rail renders nothing at all", async () => {
51
51
  })
52
52
 
53
53
  test("the group a card is already in is shown but not offerable", async () => {
54
- const { canvas } = await mountStory(Default)
54
+ const { canvas } = await mountStory(Playground)
55
55
 
56
56
  const current = canvas.getByRole("option", {
57
57
  name: /Atlas Ingest/,
@@ -64,7 +64,7 @@ test("the group a card is already in is shown but not offerable", async () => {
64
64
  })
65
65
 
66
66
  test("an archived destination is refused", async () => {
67
- const { canvas } = await mountStory(Default)
67
+ const { canvas } = await mountStory(Playground)
68
68
 
69
69
  expect(
70
70
  canvas.getByRole("option", {
@@ -14,7 +14,7 @@ Designed fresh rather than ported: the two repos that had a real `EmptyState` wi
14
14
  variants were bambuddy and spoolbuddy, which are upstream projects rather than Kevin's
15
15
  code, so that evidence was withdrawn from the plan.
16
16
 
17
- <Canvas of={EmptyStateStories.Default} />
17
+ <Canvas of={EmptyStateStories.Playground} />
18
18
 
19
19
  ## Props
20
20
 
@@ -1,3 +1,4 @@
1
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
1
2
  import type { Meta, StoryObj } from "@storybook/react"
2
3
  import { Button } from "../Button/Button.tsx"
3
4
  import {
@@ -30,7 +31,8 @@ type Story = StoryObj<typeof meta>
30
31
  * The heading is the handle. Eleven copies of "Nothing here" in a
31
32
  * `<div>` are indistinguishable to an agent; a named heading is not.
32
33
  */
33
- export const Default: Story = {
34
+ export const Playground: Story = {
35
+ parameters: playgroundParameters,
34
36
  args: {
35
37
  description:
36
38
  "Insert a disc and rip-deck will pick it up automatically.",
@@ -6,11 +6,11 @@ import { mountStory } from "../mountStory.testHelpers.ts"
6
6
  import { expectAgentDrivable } from "../testing/index.ts"
7
7
  import * as stories from "./EmptyState.stories.tsx"
8
8
 
9
- const { Default, DeeplyNested, Interactive } =
9
+ const { Playground, DeeplyNested, Interactive } =
10
10
  composeStories(stories)
11
11
 
12
12
  test("the heading is the handle", async () => {
13
- const { canvas } = await mountStory(Default)
13
+ const { canvas } = await mountStory(Playground)
14
14
 
15
15
  expectAgentDrivable(canvas, {
16
16
  name: "No discs queued",
@@ -25,7 +25,7 @@ wrong in the *other* file. Half of them are broken and nothing says which half.
25
25
  the argument for a component rather than a convention — here the id is minted and consumed
26
26
  in one place and cannot be spelled twice.
27
27
 
28
- <Canvas of={FieldStories.Default} />
28
+ <Canvas of={FieldStories.Playground} />
29
29
 
30
30
  `Field` is also how a `Picker` (or `Button`, or an app's own control) sits next to
31
31
  its label **without butting against the last word**. Those controls are