@charcuterie/ui 4.5.1 → 4.7.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.
- package/README.md +12 -0
- package/dist/LaneTimeline/LaneTimeline.d.ts +132 -0
- package/dist/LaneTimeline/LaneTimeline.d.ts.map +1 -0
- package/dist/LaneTimeline/LaneTimeline.js +165 -0
- package/dist/LaneTimeline/LaneTimeline.js.map +1 -0
- package/dist/LaneTimeline/LaneTimelineBar.d.ts +97 -0
- package/dist/LaneTimeline/LaneTimelineBar.d.ts.map +1 -0
- package/dist/LaneTimeline/LaneTimelineBar.js +69 -0
- package/dist/LaneTimeline/LaneTimelineBar.js.map +1 -0
- package/dist/LaneTimeline/LaneTimelineLane.d.ts +84 -0
- package/dist/LaneTimeline/LaneTimelineLane.d.ts.map +1 -0
- package/dist/LaneTimeline/LaneTimelineLane.js +109 -0
- package/dist/LaneTimeline/LaneTimelineLane.js.map +1 -0
- package/dist/LaneTimeline/laneTimelineGeometry.d.ts +268 -0
- package/dist/LaneTimeline/laneTimelineGeometry.d.ts.map +1 -0
- package/dist/LaneTimeline/laneTimelineGeometry.js +277 -0
- package/dist/LaneTimeline/laneTimelineGeometry.js.map +1 -0
- package/dist/LaneTimeline/useTrackMeasurements.d.ts +69 -0
- package/dist/LaneTimeline/useTrackMeasurements.d.ts.map +1 -0
- package/dist/LaneTimeline/useTrackMeasurements.js +134 -0
- package/dist/LaneTimeline/useTrackMeasurements.js.map +1 -0
- package/dist/Overlay/OverlayPanel.d.ts.map +1 -1
- package/dist/Overlay/OverlayPanel.js +54 -2
- package/dist/Overlay/OverlayPanel.js.map +1 -1
- package/dist/Overlay/OverlayStack.d.ts +5 -0
- package/dist/Overlay/OverlayStack.d.ts.map +1 -1
- package/dist/Overlay/OverlayStack.js +2 -0
- package/dist/Overlay/OverlayStack.js.map +1 -1
- package/dist/Toast/ToastRegion.d.ts +61 -0
- package/dist/Toast/ToastRegion.d.ts.map +1 -1
- package/dist/Toast/ToastRegion.js +80 -3
- package/dist/Toast/ToastRegion.js.map +1 -1
- package/dist/Toolbar/useToolbarOverflow.d.ts.map +1 -1
- package/dist/Toolbar/useToolbarOverflow.js +39 -0
- package/dist/Toolbar/useToolbarOverflow.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/LaneTimeline/LaneTimeline.mdx +191 -0
- package/src/LaneTimeline/LaneTimeline.stories.tsx +561 -0
- package/src/LaneTimeline/LaneTimeline.test.tsx +330 -0
- package/src/LaneTimeline/LaneTimeline.tsx +381 -0
- package/src/LaneTimeline/LaneTimelineBar.tsx +237 -0
- package/src/LaneTimeline/LaneTimelineLane.tsx +332 -0
- package/src/LaneTimeline/laneTimelineGeometry.test.ts +429 -0
- package/src/LaneTimeline/laneTimelineGeometry.ts +476 -0
- package/src/LaneTimeline/useTrackMeasurements.ts +165 -0
- package/src/Overlay/OverlayPanel.tsx +63 -2
- package/src/Overlay/OverlayStack.tsx +7 -1
- package/src/Toast/Toast.stories.tsx +154 -0
- package/src/Toast/ToastRegion.test.tsx +172 -0
- package/src/Toast/ToastRegion.tsx +120 -32
- package/src/Toolbar/Toolbar.test.tsx +84 -0
- package/src/Toolbar/useToolbarOverflow.ts +43 -0
- package/src/index.ts +13 -0
- package/src/sourceRules.test.ts +22 -1
|
@@ -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
|
|
package/src/index.ts
CHANGED
|
@@ -237,6 +237,19 @@ export {
|
|
|
237
237
|
INTENT_HOVER_CLASS,
|
|
238
238
|
INTENT_SOLID_FILL_CLASS,
|
|
239
239
|
} from "./intentStyles.ts"
|
|
240
|
+
export type { LaneTimelineProps } from "./LaneTimeline/LaneTimeline.tsx"
|
|
241
|
+
export { LaneTimeline } from "./LaneTimeline/LaneTimeline.tsx"
|
|
242
|
+
export type { TimelineItem } from "./LaneTimeline/LaneTimelineBar.tsx"
|
|
243
|
+
export type { TimelineLane } from "./LaneTimeline/LaneTimelineLane.tsx"
|
|
244
|
+
export type {
|
|
245
|
+
BarPlacement,
|
|
246
|
+
TimelineAxis,
|
|
247
|
+
TimelineSpan,
|
|
248
|
+
} from "./LaneTimeline/laneTimelineGeometry.ts"
|
|
249
|
+
export {
|
|
250
|
+
toBarPlacement,
|
|
251
|
+
toTimelineAxis,
|
|
252
|
+
} from "./LaneTimeline/laneTimelineGeometry.ts"
|
|
240
253
|
export type { LightboxProps } from "./Lightbox/Lightbox.tsx"
|
|
241
254
|
export { Lightbox } from "./Lightbox/Lightbox.tsx"
|
|
242
255
|
export type {
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -233,6 +233,13 @@ test("a container-query component is never storied in a shrink-to-fit cell", ()
|
|
|
233
233
|
// owner is in whenever he browses zoomed in.
|
|
234
234
|
"DatePicker",
|
|
235
235
|
"EmptyState",
|
|
236
|
+
// `LaneTimeline` declares one and **never queries it**, which is
|
|
237
|
+
// the `Main` case rather than a mistake: it wants the
|
|
238
|
+
// `contain: inline-size` that rides along, because the component
|
|
239
|
+
// measures its own box to decide whether to draw a date axis at
|
|
240
|
+
// all. Without the containment a shrink-wrapping parent makes
|
|
241
|
+
// that answer its own input.
|
|
242
|
+
"LaneTimeline",
|
|
236
243
|
"Main",
|
|
237
244
|
"MarkdownEditor",
|
|
238
245
|
"MediaTile",
|
|
@@ -591,7 +598,21 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
591
598
|
// from `ActionTiles`' "what do you want to do" and would have
|
|
592
599
|
// meant one component whose icon is sometimes a 20px glyph beside
|
|
593
600
|
// a title and sometimes a 144px circle above it.
|
|
594
|
-
|
|
601
|
+
//
|
|
602
|
+
// `LaneTimeline` — one horizontal lane per group across a date
|
|
603
|
+
// axis, with a multi-day thing drawn as a bar — is +1 -> 66. It is
|
|
604
|
+
// the first component that folds on a **measurement of its own
|
|
605
|
+
// box** rather than on a container query, and the reason is in the
|
|
606
|
+
// arithmetic: the question is not how wide the box is but how wide
|
|
607
|
+
// one day is, which is the width divided by a number that lives in
|
|
608
|
+
// the data. `laneTimelineGeometry.ts`, `LaneTimelineBar.tsx`,
|
|
609
|
+
// `LaneTimelineLane.tsx` and `useTrackMeasurements.ts` are its
|
|
610
|
+
// members and stay out of this count by the `<Name>/<Name>.tsx`
|
|
611
|
+
// rule. The geometry module is still exported from the barrel — a
|
|
612
|
+
// consumer deciding whether an item is on screen must ask the
|
|
613
|
+
// question the same way the bars are placed, or the two answers
|
|
614
|
+
// drift by a day and nothing says so.
|
|
615
|
+
expect(componentNames.length).toBe(66)
|
|
595
616
|
|
|
596
617
|
for (const name of componentNames) {
|
|
597
618
|
expect(barrel).toContain(`export { ${name} }`)
|