@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
@@ -1,5 +1,6 @@
1
1
  import { useMediaQuery } from "@charcuterie/logic"
2
2
  import { matchMediaMatcher } from "@charcuterie/logic/browser"
3
+ import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
3
4
  import type { Meta, StoryObj } from "@storybook/react"
4
5
  import type { ReactNode } from "react"
5
6
  import { useState } from "react"
@@ -150,7 +151,8 @@ type Story = StoryObj<typeof meta>
150
151
  * collapse is not progressive: almost everything lives in the
151
152
  * popover permanently.
152
153
  */
153
- export const Default: Story = {
154
+ export const Playground: Story = {
155
+ parameters: playgroundParameters,
154
156
  render: (toolbarProps) => (
155
157
  <Frame inlineSize="34rem">
156
158
  <Toolbar {...toolbarProps} />
@@ -15,13 +15,13 @@ import * as stories from "./Toolbar.stories.tsx"
15
15
  const {
16
16
  AllStates,
17
17
  AllVariants,
18
- Default,
18
+ Playground,
19
19
  InAppShell,
20
20
  Interactive,
21
21
  } = composeStories(stories)
22
22
 
23
23
  test("the toolbar is named, and its actions are buttons", async () => {
24
- const { canvas } = await mountStory(Default)
24
+ const { canvas } = await mountStory(Playground)
25
25
 
26
26
  const toolbar = expectAgentDrivable(canvas, {
27
27
  name: "Deck actions",
@@ -43,7 +43,7 @@ test("the toolbar is named, and its actions are buttons", async () => {
43
43
  * popover permanently.
44
44
  */
45
45
  test("a bar with room shows no overflow trigger", async () => {
46
- const { canvas } = await mountStory(Default)
46
+ const { canvas } = await mountStory(Playground)
47
47
 
48
48
  const toolbar = expectAgentDrivable(canvas, {
49
49
  name: "Deck actions",
@@ -73,7 +73,7 @@ test("a bar with room shows no overflow trigger", async () => {
73
73
  * the app's own component and most of them forward no `tabIndex`.
74
74
  */
75
75
  test("the toolbar is one tab stop", async () => {
76
- const { canvas } = await mountStory(Default)
76
+ const { canvas } = await mountStory(Playground)
77
77
 
78
78
  const toolbar = expectAgentDrivable(canvas, {
79
79
  name: "Deck actions",
@@ -98,7 +98,7 @@ test("the toolbar is one tab stop", async () => {
98
98
  })
99
99
 
100
100
  test("the arrow keys move along the row", async () => {
101
- const { canvas } = await mountStory(Default)
101
+ const { canvas } = await mountStory(Playground)
102
102
 
103
103
  const start = expectAgentDrivable(canvas, {
104
104
  name: "Start rip",
@@ -284,3 +284,87 @@ test("the shell mounts exactly one toolbar", async () => {
284
284
  }),
285
285
  ).toHaveLength(1)
286
286
  })
287
+
288
+ /**
289
+ * The font race, which is latent rather than observed: it needs the
290
+ * two faces to disagree across a collapse boundary, and no story in
291
+ * the library crosses one today. The test builds that boundary on
292
+ * purpose, so the mechanism is checked rather than waited for.
293
+ *
294
+ * Every width the bar measures is a **text** width. On a cold cache
295
+ * the first measurement is taken in the fallback face, and when the
296
+ * real face swaps in the items change size — but the container does
297
+ * not, so the `ResizeObserver` stays quiet, and nothing re-renders,
298
+ * so the layout effect does not run either. The bar keeps an answer
299
+ * computed against a font that is no longer on screen.
300
+ *
301
+ * The stub below is that swap with the timing made explicit: the bar
302
+ * mounts while `document.fonts.ready` is still pending, the items
303
+ * are widened with no change to the container's box, and the
304
+ * promise is then resolved. A bar that only measures on mount and
305
+ * on resize sees neither event and stays wrong.
306
+ */
307
+ test("the bar re-measures once the webfont has loaded", async () => {
308
+ const realFonts = document.fonts
309
+
310
+ let markFontsReady = () => {}
311
+
312
+ const fontsReady = new Promise<FontFaceSet>((resolve) => {
313
+ markFontsReady = () => {
314
+ resolve(realFonts)
315
+ }
316
+ })
317
+
318
+ // Layered over the real set rather than replacing it, so anything
319
+ // else that reads `document.fonts` during the mount still works.
320
+ Object.defineProperty(document, "fonts", {
321
+ configurable: true,
322
+ value: Object.create(realFonts, {
323
+ ready: {
324
+ get: () => fontsReady,
325
+ },
326
+ }),
327
+ })
328
+
329
+ // Widens every action without touching the bar's own box — the
330
+ // `Frame` around `Playground` is a fixed `34rem`. That is exactly
331
+ // what a font swap does, and it is why the `ResizeObserver` is
332
+ // not a substitute for this effect.
333
+ const widenActions = document.createElement("style")
334
+
335
+ widenActions.textContent = `[role="toolbar"] button { padding-inline: 4rem; }`
336
+
337
+ try {
338
+ const { canvas } = await mountStory(Playground)
339
+
340
+ const toolbar = expectAgentDrivable(canvas, {
341
+ name: "Deck actions",
342
+ role: "toolbar",
343
+ })
344
+
345
+ // It fits in the fallback face.
346
+ await expect(
347
+ within(toolbar).queryByRole("button", {
348
+ name: "More actions",
349
+ }),
350
+ ).toBeNull()
351
+
352
+ document.head.append(widenActions)
353
+
354
+ markFontsReady()
355
+
356
+ await waitFor(() => {
357
+ expect(
358
+ within(toolbar).getByRole("button", {
359
+ name: "More actions",
360
+ }),
361
+ ).toBeInTheDocument()
362
+ })
363
+ } finally {
364
+ widenActions.remove()
365
+
366
+ // Back to the prototype getter — the stub was an own property
367
+ // shadowing it.
368
+ Reflect.deleteProperty(document, "fonts")
369
+ }
370
+ })
@@ -159,6 +159,49 @@ export const useToolbarOverflow = ({
159
159
  */
160
160
  useLayoutEffect(measure)
161
161
 
162
+ /**
163
+ * Measure again once the webfont has actually arrived.
164
+ *
165
+ * Every width above is a **text** width, so it is only as correct
166
+ * as the font that was loaded when it was read. On a cold cache
167
+ * the first measurement happens in the fallback face, the items
168
+ * come out a few pixels off, and `chooseVisibleCount` keeps or
169
+ * drops one action it should not have — then never revisits it,
170
+ * because swapping a font changes no box the `ResizeObserver`
171
+ * watches and triggers no render.
172
+ *
173
+ * The user-visible symptom is a bar that settles one button wrong
174
+ * and stays there. It needs the two faces to disagree across a
175
+ * collapse boundary, so it is latent rather than observed: no
176
+ * story in the library crosses one today, which is why `vrt`
177
+ * reports nothing either way. That makes it a gap to close, not a
178
+ * regression to chase — the bar is supposed to measure what is on
179
+ * screen, and until now it could not.
180
+ *
181
+ * `document.fonts.ready` resolves immediately when the fonts are
182
+ * already in, so a warm load pays one microtask and
183
+ * `setVisibleCount` bails on the unchanged answer.
184
+ */
185
+ useLayoutEffect(() => {
186
+ // jsdom has no `document.fonts`, and neither does a very old
187
+ // browser. Nothing to wait for is not an error.
188
+ if (!document.fonts) {
189
+ return
190
+ }
191
+
192
+ let isActive = true
193
+
194
+ document.fonts.ready.then(() => {
195
+ if (isActive) {
196
+ measure()
197
+ }
198
+ })
199
+
200
+ return () => {
201
+ isActive = false
202
+ }
203
+ }, [measure])
204
+
162
205
  useLayoutEffect(() => {
163
206
  const container = containerRef.current
164
207
 
@@ -9,7 +9,7 @@ import * as TooltipStories from "./Tooltip.stories";
9
9
  Replaces mux-magic's `FieldTooltip` — 130 lines of hand-rolled positioning, including a
10
10
  `computePosition` that reimplements `flip` and `shift` against `window.innerHeight`.
11
11
 
12
- <Canvas of={TooltipStories.Default} />
12
+ <Canvas of={TooltipStories.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
 
3
4
  import { placementArgType } from "../argTypes.storyHelpers.ts"
@@ -26,7 +27,8 @@ export default meta
26
27
 
27
28
  type Story = StoryObj<typeof meta>
28
29
 
29
- export const Default: Story = {
30
+ export const Playground: Story = {
31
+ parameters: playgroundParameters,
30
32
  args: {
31
33
  children: <Button appearance="outline">Retry</Button>,
32
34
  label: "Re-reads the disc from title 1.",
@@ -7,12 +7,12 @@ import { mountStory } from "../mountStory.testHelpers.ts"
7
7
  import { expectAgentDrivable } from "../testing/index.ts"
8
8
  import * as stories from "./Tooltip.stories.tsx"
9
9
 
10
- const { AllVariants, Default, Interactive } =
10
+ const { AllVariants, Playground, Interactive } =
11
11
  composeStories(stories)
12
12
 
13
13
  test("nothing is shown until something asks", async () => {
14
14
  const { body, canvas, canvasElement } =
15
- await mountStory(Default)
15
+ await mountStory(Playground)
16
16
 
17
17
  expectAgentDrivable(canvas, {
18
18
  name: "Retry",
@@ -10,7 +10,7 @@ import * as VirtualizedGridStories from "./VirtualizedGrid.stories";
10
10
  two-box structure, same content cap. The one difference is that a list of 2,000 costs
11
11
  about as much as a list of 40.
12
12
 
13
- <Canvas of={VirtualizedGridStories.Default} />
13
+ <Canvas of={VirtualizedGridStories.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 { useState } from "react"
3
4
 
@@ -107,7 +108,9 @@ type Story = StoryObj<typeof meta>
107
108
  * Inspect the DOM and count: the row elements are the ones on
108
109
  * screen plus `overscanRows` above and below.
109
110
  */
110
- export const Default: Story = {}
111
+ export const Playground: Story = {
112
+ parameters: playgroundParameters,
113
+ }
111
114
 
112
115
  /**
113
116
  * The app-shell contract: `Main` is the one vertical scroll region,
@@ -38,7 +38,7 @@ import * as stories from "./VirtualizedGrid.stories.tsx"
38
38
 
39
39
  const {
40
40
  AllStates,
41
- Default,
41
+ Playground,
42
42
  InHiddenTabPanel,
43
43
  InShellScrollRegion,
44
44
  } = composeStories(stories)
@@ -84,7 +84,7 @@ const getRowIndexes = (canvasElement: HTMLElement) => [
84
84
  ]
85
85
 
86
86
  test("2,000 items mount as a few dozen cells", async () => {
87
- const { canvasElement } = await mountStory(Default)
87
+ const { canvasElement } = await mountStory(Playground)
88
88
 
89
89
  await waitFor(async () => {
90
90
  await expect(
@@ -102,7 +102,7 @@ test("2,000 items mount as a few dozen cells", async () => {
102
102
  })
103
103
 
104
104
  test("the page is still as tall as the whole list", async () => {
105
- const { canvasElement } = await mountStory(Default)
105
+ const { canvasElement } = await mountStory(Playground)
106
106
 
107
107
  await waitFor(async () => {
108
108
  await expect(
@@ -123,7 +123,7 @@ test("the page is still as tall as the whole list", async () => {
123
123
  })
124
124
 
125
125
  test("scrolling changes which rows exist", async () => {
126
- const { canvasElement } = await mountStory(Default)
126
+ const { canvasElement } = await mountStory(Playground)
127
127
 
128
128
  await waitFor(async () => {
129
129
  await expect(
@@ -325,7 +325,7 @@ test("a grid inside a hidden panel mounts nothing and reveals without scrolling"
325
325
  })
326
326
 
327
327
  test("a screen reader is told the real length", async () => {
328
- const { canvasElement } = await mountStory(Default)
328
+ const { canvasElement } = await mountStory(Playground)
329
329
 
330
330
  await waitFor(async () => {
331
331
  await expect(
@@ -11,7 +11,7 @@ other component in M3 leans on: it is how a `Spinner` says "Loading…" without
11
11
  how a `ProgressBar`'s label stays discoverable when the design does not show it, and how an
12
12
  `IconButton`'s glyph gets a name.
13
13
 
14
- <Canvas of={VisuallyHiddenStories.Default} />
14
+ <Canvas of={VisuallyHiddenStories.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
 
3
4
  import {
@@ -28,7 +29,8 @@ export default meta
28
29
 
29
30
  type Story = StoryObj<typeof meta>
30
31
 
31
- export const Default: Story = {
32
+ export const Playground: Story = {
33
+ parameters: playgroundParameters,
32
34
  args: { children: "Announced, never printed." },
33
35
  render: (visuallyHiddenProps) => (
34
36
  <StoryGrid columns={2}>
@@ -16,7 +16,7 @@ It is **opt-in**, from a subpath:
16
16
  import { MarkdownEditorCodeMirror } from "@charcuterie/ui/markdown-editor-codemirror"
17
17
  ```
18
18
 
19
- <Canvas of={MarkdownEditorCodeMirrorStories.Default} />
19
+ <Canvas of={MarkdownEditorCodeMirrorStories.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 {
@@ -91,7 +92,8 @@ type Story = StoryObj<typeof meta>
91
92
  * genuinely larger, `**` is nowhere on screen, the checkboxes are
92
93
  * checkboxes, and the bare URL is a link.
93
94
  */
94
- export const Default: Story = {
95
+ export const Playground: Story = {
96
+ parameters: playgroundParameters,
95
97
  args: {
96
98
  defaultValue: SAMPLE,
97
99
  label: "Description",
@@ -5,7 +5,7 @@ import { test } from "vitest"
5
5
  import { mountStory } from "../mountStory.testHelpers.ts"
6
6
  import * as stories from "./MarkdownEditorCodeMirror.stories.tsx"
7
7
 
8
- const { AllStates, CaretReveal, Default, Uploading } =
8
+ const { AllStates, CaretReveal, Playground, Uploading } =
9
9
  composeStories(stories)
10
10
 
11
11
  type Canvas = Awaited<
@@ -169,7 +169,7 @@ test("a click low in an empty field lands in the document", async () => {
169
169
  * anywhere in this stack would have quietly taken away.
170
170
  */
171
171
  test("a document taller than the frame grows it", async () => {
172
- const { canvas } = await mountStory(Default)
172
+ const { canvas } = await mountStory(Playground)
173
173
 
174
174
  const content = canvas.getByRole("textbox", {
175
175
  name: "Description",
@@ -235,7 +235,7 @@ test("the toolbar offers Image when uploads are wired", async () => {
235
235
  /** A button that opens a file picker and then has nowhere to send
236
236
  * the file is worse than no button. */
237
237
  test("the toolbar hides Image when nothing can take the file", async () => {
238
- const { body, canvas } = await mountStory(Default)
238
+ const { body, canvas } = await mountStory(Playground)
239
239
 
240
240
  const overflow = canvas.queryByRole("button", {
241
241
  name: "More actions",
@@ -15,7 +15,7 @@ It is **opt-in**, from the same subpath as the editor:
15
15
  import { MarkdownView } from "@charcuterie/ui/markdown-editor-codemirror"
16
16
  ```
17
17
 
18
- <Canvas of={MarkdownViewStories.Default} />
18
+ <Canvas of={MarkdownViewStories.Playground} />
19
19
 
20
20
  ## Props
21
21
 
@@ -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
 
@@ -183,7 +184,9 @@ type Story = StoryObj<typeof meta>
183
184
  * `getAllByRole("toolbar")` returns nothing, because nothing in this
184
185
  * component can render one.
185
186
  */
186
- export const Default: Story = {}
187
+ export const Playground: Story = {
188
+ parameters: playgroundParameters,
189
+ }
187
190
 
188
191
  /**
189
192
  * The same string, in both surfaces, side by side.
@@ -9,7 +9,7 @@ import * as stories from "./MarkdownView.stories.tsx"
9
9
 
10
10
  const {
11
11
  AllStates,
12
- Default,
12
+ Playground,
13
13
  HostileMarkdown,
14
14
  Interactive,
15
15
  NestedLinkText,
@@ -26,7 +26,7 @@ const {
26
26
  * there is the fix.
27
27
  */
28
28
  test("there is no toolbar, not a hidden one", async () => {
29
- const { canvas } = await mountStory(Default)
29
+ const { canvas } = await mountStory(Playground)
30
30
 
31
31
  await expect(canvas.queryAllByRole("toolbar")).toEqual([])
32
32
 
@@ -47,7 +47,7 @@ test("there is no toolbar, not a hidden one", async () => {
47
47
  * asserted together.
48
48
  */
49
49
  test("it is an article whose headings are headings", async () => {
50
- const { canvas } = await mountStory(Default)
50
+ const { canvas } = await mountStory(Playground)
51
51
 
52
52
  const article = expectAgentDrivable(canvas, {
53
53
  name: "Description",
@@ -86,12 +86,28 @@ test("it is an article whose headings are headings", async () => {
86
86
  test("it renders what the editor renders", async () => {
87
87
  const { canvas } = await mountStory(SameAsTheEditor)
88
88
 
89
- const [viewTable, editorTable] =
90
- canvas.getAllByRole("table")
91
-
92
- await expect(viewTable).toBeDefined()
93
-
94
- await expect(editorTable).toBeDefined()
89
+ // ⚠️ WAIT FOR THE COUNT, not for "a table".
90
+ //
91
+ // This test is the only one in the file that mounts a story with
92
+ // BOTH surfaces, so it is the only one that has to wait for
93
+ // CodeMirror. `MarkdownView` paints its table in the first commit;
94
+ // CodeMirror parses and decorates the document afterwards. A
95
+ // synchronous `getAllByRole("table")` therefore returned ONE table
96
+ // on a slow runner, `editorTable` was `undefined`, and the failure
97
+ // read "expected undefined to be defined" — which says nothing
98
+ // about the real cause (CI, 2026-09-14).
99
+ //
100
+ // ⚠️ A bare `findAllByRole` does NOT fix it. `findAllBy*` retries
101
+ // until the query matches AT LEAST ONE element, and one element is
102
+ // exactly the broken state: the view's table is there from the
103
+ // start. The count is the condition, so the count goes in the wait.
104
+ const [viewTable, editorTable] = await waitFor(() => {
105
+ const tables = canvas.getAllByRole("table")
106
+
107
+ expect(tables).toHaveLength(2)
108
+
109
+ return tables
110
+ })
95
111
 
96
112
  const toCellText = (table: HTMLElement) =>
97
113
  Array.from(table.querySelectorAll("th, td")).map(
@@ -103,10 +119,16 @@ test("it renders what the editor renders", async () => {
103
119
  ).toEqual(toCellText(editorTable as HTMLElement))
104
120
 
105
121
  // Three task boxes in each, and the first one ticked in each.
106
- const checkboxes: HTMLInputElement[] =
107
- canvas.getAllByRole("checkbox")
122
+ // Same wait, same reason: CodeMirror's boxes arrive with its
123
+ // decorations, so a count taken too early sees only the view's.
124
+ const checkboxes = await waitFor(() => {
125
+ const boxes: HTMLInputElement[] =
126
+ canvas.getAllByRole("checkbox")
108
127
 
109
- await expect(checkboxes).toHaveLength(6)
128
+ expect(boxes).toHaveLength(6)
129
+
130
+ return boxes
131
+ })
110
132
 
111
133
  await expect(
112
134
  checkboxes.filter((checkbox) => checkbox.checked),
@@ -114,11 +136,13 @@ test("it renders what the editor renders", async () => {
114
136
 
115
137
  // The bare URL, autolinked on both sides with nothing typed
116
138
  // around it.
117
- await expect(
118
- canvas.getAllByText(
119
- "https://example.invalid/product?id=1234",
120
- ).length,
121
- ).toBe(2)
139
+ await waitFor(() => {
140
+ expect(
141
+ canvas.getAllByText(
142
+ "https://example.invalid/product?id=1234",
143
+ ),
144
+ ).toHaveLength(2)
145
+ })
122
146
  })
123
147
 
124
148
  /**
@@ -265,7 +289,7 @@ test("hostile markdown renders as characters, not as behaviour", async () => {
265
289
  * else noticing.
266
290
  */
267
291
  test("the text can be selected", async () => {
268
- const { canvas } = await mountStory(Default)
292
+ const { canvas } = await mountStory(Playground)
269
293
 
270
294
  const article = canvas.getByRole("article")
271
295
 
@@ -360,7 +384,7 @@ test.each([
360
384
  * survives a change to the base font size.
361
385
  */
362
386
  test("the document is set at prose leading", async () => {
363
- const { canvas } = await mountStory(Default)
387
+ const { canvas } = await mountStory(Playground)
364
388
 
365
389
  const article = canvas.getByRole("article")
366
390
 
@@ -379,7 +403,7 @@ test("the document is set at prose leading", async () => {
379
403
  * backticks comes out as a wall of emphasis nobody asked for.
380
404
  */
381
405
  test("a code span is smaller than the prose it sits in", async () => {
382
- const { canvas } = await mountStory(Default)
406
+ const { canvas } = await mountStory(Playground)
383
407
 
384
408
  const article = canvas.getByRole("article")
385
409
 
@@ -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 {
3
4
  MemoryRouter,
@@ -92,7 +93,8 @@ const EpisodePage = () => (
92
93
  * `TextLink` becomes a soft navigation and `Main` keeps the
93
94
  * reader's place because `ReactRouterAdapter` is above them both.
94
95
  */
95
- export const Default: Story = {
96
+ export const Playground: Story = {
97
+ parameters: playgroundParameters,
96
98
  render: () => (
97
99
  <MemoryRouter initialEntries={["/library"]}>
98
100
  <ReactRouterAdapter>
@@ -11,7 +11,7 @@ import {
11
11
  } from "../viewport.testHelpers.ts"
12
12
  import * as stories from "./ReactRouterAdapter.stories.tsx"
13
13
 
14
- const { Default } = composeStories(stories)
14
+ const { Playground } = composeStories(stories)
15
15
 
16
16
  // The offsets are module state, so they outlive a mount. A test
17
17
  // must not.
@@ -33,7 +33,7 @@ afterEach(() => {
33
33
  test("an app renders one component and gets both seams", async () => {
34
34
  await setViewport(DESKTOP)
35
35
 
36
- const { canvas } = await mountStory(Default)
36
+ const { canvas } = await mountStory(Playground)
37
37
 
38
38
  const main = canvas.getByRole("main")
39
39