@charcuterie/ui 2.8.0 → 2.10.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 +9 -0
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +3 -1
- package/dist/Dialog/Dialog.js.map +1 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +1 -1
- package/dist/Field/Field.js.map +1 -1
- package/dist/Field/FieldGroup.d.ts.map +1 -1
- package/dist/Field/FieldGroup.js +1 -1
- package/dist/Field/FieldGroup.js.map +1 -1
- package/dist/Header/Header.d.ts +52 -0
- package/dist/Header/Header.d.ts.map +1 -0
- package/dist/Header/Header.js +55 -0
- package/dist/Header/Header.js.map +1 -0
- package/dist/Main/Main.d.ts +113 -0
- package/dist/Main/Main.d.ts.map +1 -0
- package/dist/Main/Main.js +123 -0
- package/dist/Main/Main.js.map +1 -0
- package/dist/Rail/Rail.d.ts +61 -0
- package/dist/Rail/Rail.d.ts.map +1 -0
- package/dist/Rail/Rail.js +64 -0
- package/dist/Rail/Rail.js.map +1 -0
- package/dist/Shell/Shell.d.ts +112 -0
- package/dist/Shell/Shell.d.ts.map +1 -0
- package/dist/Shell/Shell.js +116 -0
- package/dist/Shell/Shell.js.map +1 -0
- package/dist/Shell/contentWidth.d.ts +63 -0
- package/dist/Shell/contentWidth.d.ts.map +1 -0
- package/dist/Shell/contentWidth.js +74 -0
- package/dist/Shell/contentWidth.js.map +1 -0
- package/dist/Shell/shellContext.d.ts +36 -0
- package/dist/Shell/shellContext.d.ts.map +1 -0
- package/dist/Shell/shellContext.js +32 -0
- package/dist/Shell/shellContext.js.map +1 -0
- package/dist/SortableTableHeader/SortableTableHeader.d.ts.map +1 -1
- package/dist/SortableTableHeader/SortableTableHeader.js +7 -1
- package/dist/SortableTableHeader/SortableTableHeader.js.map +1 -1
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +5 -1
- package/dist/Tooltip/Tooltip.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mountStory.testHelpers.d.ts.map +1 -1
- package/dist/mountStory.testHelpers.js +56 -0
- package/dist/mountStory.testHelpers.js.map +1 -1
- package/dist/viewport.testHelpers.d.ts +31 -0
- package/dist/viewport.testHelpers.d.ts.map +1 -0
- package/dist/viewport.testHelpers.js +34 -0
- package/dist/viewport.testHelpers.js.map +1 -0
- package/package.json +2 -2
- package/src/Dialog/Dialog.tsx +17 -1
- package/src/Field/Field.tsx +10 -2
- package/src/Field/FieldGroup.tsx +4 -2
- package/src/Header/Header.mdx +72 -0
- package/src/Header/Header.stories.tsx +221 -0
- package/src/Header/Header.test.tsx +122 -0
- package/src/Header/Header.tsx +118 -0
- package/src/Main/Main.mdx +149 -0
- package/src/Main/Main.stories.tsx +164 -0
- package/src/Main/Main.test.tsx +167 -0
- package/src/Main/Main.tsx +174 -0
- package/src/Rail/Rail.mdx +75 -0
- package/src/Rail/Rail.stories.tsx +177 -0
- package/src/Rail/Rail.test.tsx +150 -0
- package/src/Rail/Rail.tsx +102 -0
- package/src/Shell/Shell.mdx +186 -0
- package/src/Shell/Shell.stories.tsx +185 -0
- package/src/Shell/Shell.test.tsx +267 -0
- package/src/Shell/Shell.tsx +170 -0
- package/src/Shell/contentWidth.ts +89 -0
- package/src/Shell/shellContext.ts +41 -0
- package/src/SortableTableHeader/SortableTableHeader.tsx +6 -1
- package/src/Tooltip/Tooltip.tsx +5 -1
- package/src/index.ts +16 -0
- package/src/mountStory.testHelpers.ts +63 -0
- package/src/shell.storyHelpers.tsx +258 -0
- package/src/sourceRules.test.ts +17 -1
- package/src/viewport.testHelpers.ts +47 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,0BAA0B,EAC1B,iCAAiC,EACjC,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,iCAAiC,CAAA;AAKxC,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAK7C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAMxD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA2C,CAAA;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAElD,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAK3B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAK5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAwB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAKlD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAuB,CAAA;AAK/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountStory.testHelpers.d.ts","sourceRoot":"","sources":["../src/mountStory.testHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvC,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAA;AAEvC;;;;;GAKG;AACH,KAAK,cAAc,GAAG;IACpB,GAAG,EAAE,CAAC,OAAO,EAAE;QACb,aAAa,EAAE,WAAW,CAAA;KAC3B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpB,CAAA;
|
|
1
|
+
{"version":3,"file":"mountStory.testHelpers.d.ts","sourceRoot":"","sources":["../src/mountStory.testHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvC,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAA;AAEvC;;;;;GAKG;AACH,KAAK,cAAc,GAAG;IACpB,GAAG,EAAE,CAAC,OAAO,EAAE;QACb,aAAa,EAAE,WAAW,CAAA;KAC3B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpB,CAAA;AA+GD,eAAO,MAAM,UAAU,GACrB,OAAO,cAAc,KACpB,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,WAAW,CAAA;CAC3B,CA0BA,CAAA"}
|
|
@@ -46,6 +46,61 @@ import { within } from "storybook/test";
|
|
|
46
46
|
* triggers still open from inside `canvas`.
|
|
47
47
|
*/
|
|
48
48
|
const mountedCanvases = [];
|
|
49
|
+
/**
|
|
50
|
+
* One frame past the mount, so post-mount effects have applied
|
|
51
|
+
* before anything reads the DOM.
|
|
52
|
+
*
|
|
53
|
+
* `storybook-addon-pseudo-states` applies its forced
|
|
54
|
+
* `:hover`/`:focus`/`:active` classes **a tick after render**, not
|
|
55
|
+
* during it — `vrtCapture.mjs` documents the same window and closes
|
|
56
|
+
* it with a settle, after "a real flake seen on `*--all-states`
|
|
57
|
+
* stories".
|
|
58
|
+
*
|
|
59
|
+
* This suite has the identical window and never closed it, and the
|
|
60
|
+
* consequence is worse here than a pixel diff: the a11y addon's
|
|
61
|
+
* `afterEach` runs axe the moment `run()` resolves, so a story
|
|
62
|
+
* whose forced state lands late is audited **un-forced**, and one
|
|
63
|
+
* whose state lands early is audited **hovered**. That is a
|
|
64
|
+
* contrast check reading `intent-accent-solid` on one run and
|
|
65
|
+
* `intent-accent-solid-hover` on the next — which is precisely the
|
|
66
|
+
* shape of `ButtonLink`'s intermittent `color-contrast` failure in
|
|
67
|
+
* CI, reported against `#6A64F0`, the daylight `solidHover`.
|
|
68
|
+
*
|
|
69
|
+
* Two frames rather than a fixed delay: React flushes effects and
|
|
70
|
+
* the addon applies classes within a frame, so this is a barrier
|
|
71
|
+
* tied to the browser's own scheduling instead of a millisecond
|
|
72
|
+
* count guessed against one machine. `vrtCapture` can afford its
|
|
73
|
+
* 400ms because it takes ~700 shots; this runs before every one of
|
|
74
|
+
* 222 tests.
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* Captured at module load, **before any test can replace it**.
|
|
78
|
+
*
|
|
79
|
+
* `Toast.test.tsx` stubs `requestAnimationFrame` and *holds* the
|
|
80
|
+
* callbacks, deliberately, to prove that a dismiss arriving during
|
|
81
|
+
* the enter frame is not undone by it. A settle that called the
|
|
82
|
+
* live `globalThis.requestAnimationFrame` would hand its own
|
|
83
|
+
* continuation to that stub and never be released — which is
|
|
84
|
+
* exactly what happened: three Toast tests went from passing to a
|
|
85
|
+
* flat 15s timeout.
|
|
86
|
+
*
|
|
87
|
+
* Binding the native one keeps a true frame barrier while staying
|
|
88
|
+
* immune to a stub, and it does not disturb the test doing the
|
|
89
|
+
* stubbing: the component's frames still go to its stub, only this
|
|
90
|
+
* helper's do not.
|
|
91
|
+
*/
|
|
92
|
+
const requestNativeFrame = globalThis.requestAnimationFrame?.bind(globalThis);
|
|
93
|
+
const settlePostMountEffects = () => new Promise((resolve) => {
|
|
94
|
+
if (!requestNativeFrame) {
|
|
95
|
+
globalThis.setTimeout(resolve, 0);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
requestNativeFrame(() => {
|
|
99
|
+
requestNativeFrame(() => {
|
|
100
|
+
resolve();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
49
104
|
export const mountStory = async (story) => {
|
|
50
105
|
for (const previous of mountedCanvases.splice(0)) {
|
|
51
106
|
previous.remove();
|
|
@@ -57,6 +112,7 @@ export const mountStory = async (story) => {
|
|
|
57
112
|
document.body.append(canvasElement);
|
|
58
113
|
mountedCanvases.push(canvasElement);
|
|
59
114
|
await story.run({ canvasElement });
|
|
115
|
+
await settlePostMountEffects();
|
|
60
116
|
return {
|
|
61
117
|
body: within(document.body),
|
|
62
118
|
canvas: within(canvasElement),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountStory.testHelpers.js","sourceRoot":"","sources":["../src/mountStory.testHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAgBvC;;;;;;;;;;GAUG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,eAAe,GAAkB,EAAE,CAAA;AAEzC,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAC7B,KAAqB,EAKpB,EAAE;IACH,KAAK,MAAM,QAAQ,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,QAAQ,CAAC,MAAM,EAAE,CAAA;IACnB,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC,gBAAgB,CAChD,2BAA2B,CAC5B,EAAE,CAAC;QACF,UAAU,CAAC,MAAM,EAAE,CAAA;IACrB,CAAC;IAED,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAEnD,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;IAEnC,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IAEnC,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,CAAC,CAAA;IAElC,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC;QAC7B,aAAa;KACd,CAAA;AACH,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"mountStory.testHelpers.js","sourceRoot":"","sources":["../src/mountStory.testHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAgBvC;;;;;;;;;;GAUG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,eAAe,GAAkB,EAAE,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,kBAAkB,GACtB,UAAU,CAAC,qBAAqB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;AAEpD,MAAM,sBAAsB,GAAG,GAAG,EAAE,CAClC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;IAC5B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACxB,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QAEjC,OAAM;IACR,CAAC;IAED,kBAAkB,CAAC,GAAG,EAAE;QACtB,kBAAkB,CAAC,GAAG,EAAE;YACtB,OAAO,EAAE,CAAA;QACX,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEJ,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAC7B,KAAqB,EAKpB,EAAE;IACH,KAAK,MAAM,QAAQ,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,QAAQ,CAAC,MAAM,EAAE,CAAA;IACnB,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC,gBAAgB,CAChD,2BAA2B,CAC5B,EAAE,CAAC;QACF,UAAU,CAAC,MAAM,EAAE,CAAA;IACrB,CAAC;IAED,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAEnD,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;IAEnC,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IAEnC,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,CAAC,CAAA;IAElC,MAAM,sBAAsB,EAAE,CAAA;IAE9B,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC;QAC7B,aAAa;KACd,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resize the browser and **wait until the page agrees**.
|
|
3
|
+
*
|
|
4
|
+
* `page.viewport()` resolves when the driver has asked for the
|
|
5
|
+
* resize, not when the tester frame has been laid out at the new
|
|
6
|
+
* size. On the first resize in a file that gap is real: the
|
|
7
|
+
* assertion runs against the *previous* width, so a
|
|
8
|
+
* horizontal-scroll test at 390px silently measures a 1440px page
|
|
9
|
+
* and reports a number nobody can explain. It is a race, so it
|
|
10
|
+
* fails intermittently — which is the worst possible property for
|
|
11
|
+
* the one gate that says the shell does not scroll sideways.
|
|
12
|
+
*
|
|
13
|
+
* Polling `innerWidth` is the honest wait: it is the same number
|
|
14
|
+
* the media queries and the layout resolved against, so when it
|
|
15
|
+
* matches, the thing under test really is at the width the test
|
|
16
|
+
* claims.
|
|
17
|
+
*/
|
|
18
|
+
export declare const setViewport: ({ height, width, }: {
|
|
19
|
+
height: number;
|
|
20
|
+
width: number;
|
|
21
|
+
}) => Promise<void>;
|
|
22
|
+
/** A phone in portrait — the width plex-channels scrolls at. */
|
|
23
|
+
export declare const PHONE: {
|
|
24
|
+
height: number;
|
|
25
|
+
width: number;
|
|
26
|
+
};
|
|
27
|
+
export declare const DESKTOP: {
|
|
28
|
+
height: number;
|
|
29
|
+
width: number;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=viewport.testHelpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.testHelpers.d.ts","sourceRoot":"","sources":["../src/viewport.testHelpers.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,WAAW,GAAU,oBAG/B;IACD,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACd,KAAG,OAAO,CAAC,IAAI,CAgBf,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,KAAK;;;CAA8B,CAAA;AAEhD,eAAO,MAAM,OAAO;;;CAA+B,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { page } from "@vitest/browser/context";
|
|
2
|
+
/**
|
|
3
|
+
* Resize the browser and **wait until the page agrees**.
|
|
4
|
+
*
|
|
5
|
+
* `page.viewport()` resolves when the driver has asked for the
|
|
6
|
+
* resize, not when the tester frame has been laid out at the new
|
|
7
|
+
* size. On the first resize in a file that gap is real: the
|
|
8
|
+
* assertion runs against the *previous* width, so a
|
|
9
|
+
* horizontal-scroll test at 390px silently measures a 1440px page
|
|
10
|
+
* and reports a number nobody can explain. It is a race, so it
|
|
11
|
+
* fails intermittently — which is the worst possible property for
|
|
12
|
+
* the one gate that says the shell does not scroll sideways.
|
|
13
|
+
*
|
|
14
|
+
* Polling `innerWidth` is the honest wait: it is the same number
|
|
15
|
+
* the media queries and the layout resolved against, so when it
|
|
16
|
+
* matches, the thing under test really is at the width the test
|
|
17
|
+
* claims.
|
|
18
|
+
*/
|
|
19
|
+
export const setViewport = async ({ height, width, }) => {
|
|
20
|
+
await page.viewport(width, height);
|
|
21
|
+
const deadline = Date.now() + 2000;
|
|
22
|
+
while (globalThis.innerWidth !== width) {
|
|
23
|
+
if (Date.now() > deadline) {
|
|
24
|
+
throw new Error(`The viewport never reached ${width}px — it is ${globalThis.innerWidth}px. Every width assertion after this point would be measuring the wrong page.`);
|
|
25
|
+
}
|
|
26
|
+
await new Promise((resolve) => {
|
|
27
|
+
globalThis.requestAnimationFrame(resolve);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/** A phone in portrait — the width plex-channels scrolls at. */
|
|
32
|
+
export const PHONE = { height: 844, width: 390 };
|
|
33
|
+
export const DESKTOP = { height: 900, width: 1440 };
|
|
34
|
+
//# sourceMappingURL=viewport.testHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.testHelpers.js","sourceRoot":"","sources":["../src/viewport.testHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAE9C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,EAChC,MAAM,EACN,KAAK,GAIN,EAAiB,EAAE;IAClB,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAElC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;IAElC,OAAO,UAAU,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,8BAA8B,KAAK,cAAc,UAAU,CAAC,UAAU,+EAA+E,CACtJ,CAAA;QACH,CAAC;QAED,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC5B,UAAU,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAA;QAC3C,CAAC,CAAC,CAAA;IACJ,CAAC;AACH,CAAC,CAAA;AAED,gEAAgE;AAChE,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAA;AAEhD,MAAM,CAAC,MAAM,OAAO,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcuterie/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "The Charcuterie components. Tokens for look, @charcuterie/logic for state, ARIA that agents can actually drive.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@charcuterie/logic": "1.2.0",
|
|
52
|
-
"@charcuterie/tokens": "1.
|
|
52
|
+
"@charcuterie/tokens": "1.3.0",
|
|
53
53
|
"@floating-ui/react": "^0.27.20",
|
|
54
54
|
"@tanstack/react-virtual": "^3.13.12"
|
|
55
55
|
},
|
package/src/Dialog/Dialog.tsx
CHANGED
|
@@ -119,7 +119,23 @@ export const Dialog = ({
|
|
|
119
119
|
) : null}
|
|
120
120
|
</header>
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
{/*
|
|
123
|
+
`tabIndex={0}` is load-bearing, not decoration. This region
|
|
124
|
+
scrolls, and a scrollable region that cannot be focused cannot
|
|
125
|
+
be scrolled by anyone driving the keyboard — there is nothing
|
|
126
|
+
to put the caret in and no way to reach the overflow. axe
|
|
127
|
+
gates it as `scrollable-region-focusable` (serious).
|
|
128
|
+
|
|
129
|
+
It only surfaced once the body actually overflowed, which is
|
|
130
|
+
why it could land green: the rule fires on a region that is
|
|
131
|
+
scrollable *right now*, so a dialog whose content happened to
|
|
132
|
+
fit stayed silent.
|
|
133
|
+
*/}
|
|
134
|
+
<div
|
|
135
|
+
className="min-h-0 flex-1 overflow-auto p-4"
|
|
136
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: a scroll container must be focusable or a keyboard user can see the pane and cannot scroll it — axe's `scrollable-region-focusable`, which fails this component without the line. Same call, and the same wording, as `LogViewer`'s scroll pane. The rule is right about `<div>`s in general and wrong about scrollers.
|
|
137
|
+
tabIndex={0}
|
|
138
|
+
>
|
|
123
139
|
{children}
|
|
124
140
|
</div>
|
|
125
141
|
|
package/src/Field/Field.tsx
CHANGED
|
@@ -202,9 +202,17 @@ export const Field = ({
|
|
|
202
202
|
|
|
203
203
|
{clonedControl}
|
|
204
204
|
|
|
205
|
+
{/*
|
|
206
|
+
Both of these are `text-sm`, not `text-xs`, since 2026-08-10.
|
|
207
|
+
A field description is the instruction for the input directly
|
|
208
|
+
above it, and an error is the one thing the user has to read
|
|
209
|
+
to get unstuck — neither survives being the smallest text on
|
|
210
|
+
the page. `aria-describedby` already ties them to the control;
|
|
211
|
+
the size is what decides whether a sighted user reads them.
|
|
212
|
+
*/}
|
|
205
213
|
{description === undefined ? null : (
|
|
206
214
|
<p
|
|
207
|
-
className="text-content-secondary text-
|
|
215
|
+
className="text-content-secondary text-sm"
|
|
208
216
|
id={descriptionId}
|
|
209
217
|
>
|
|
210
218
|
{description}
|
|
@@ -213,7 +221,7 @@ export const Field = ({
|
|
|
213
221
|
|
|
214
222
|
{error === undefined ? null : (
|
|
215
223
|
<p
|
|
216
|
-
className="text-intent-danger-content text-
|
|
224
|
+
className="text-intent-danger-content text-sm"
|
|
217
225
|
id={errorId}
|
|
218
226
|
>
|
|
219
227
|
{error}
|
package/src/Field/FieldGroup.tsx
CHANGED
|
@@ -116,9 +116,11 @@ export const FieldGroup = ({
|
|
|
116
116
|
|
|
117
117
|
{children}
|
|
118
118
|
|
|
119
|
+
{/* `text-sm` since 2026-08-10, matching `Field` — same reasoning,
|
|
120
|
+
and the two must not disagree about how big a description is. */}
|
|
119
121
|
{description === undefined ? null : (
|
|
120
122
|
<p
|
|
121
|
-
className="text-content-secondary text-
|
|
123
|
+
className="text-content-secondary text-sm"
|
|
122
124
|
id={descriptionId}
|
|
123
125
|
>
|
|
124
126
|
{description}
|
|
@@ -127,7 +129,7 @@ export const FieldGroup = ({
|
|
|
127
129
|
|
|
128
130
|
{error === undefined ? null : (
|
|
129
131
|
<p
|
|
130
|
-
className="text-intent-danger-content text-
|
|
132
|
+
className="text-intent-danger-content text-sm"
|
|
131
133
|
id={errorId}
|
|
132
134
|
>
|
|
133
135
|
{error}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
import * as HeaderStories from "./Header.stories";
|
|
4
|
+
|
|
5
|
+
<Meta of={HeaderStories} />
|
|
6
|
+
|
|
7
|
+
# Header
|
|
8
|
+
|
|
9
|
+
The `<header>` landmark, the app's name, and the controls that belong to the frame rather
|
|
10
|
+
than to the page. Ten repos hand-roll this row; two of them wrote the same string.
|
|
11
|
+
|
|
12
|
+
<Canvas of={HeaderStories.Default} />
|
|
13
|
+
|
|
14
|
+
## Props
|
|
15
|
+
|
|
16
|
+
<Controls />
|
|
17
|
+
|
|
18
|
+
<Canvas of={HeaderStories.AllStates} />
|
|
19
|
+
|
|
20
|
+
## `isSticky` sets `position` **and** `z-index`
|
|
21
|
+
|
|
22
|
+
That pairing is the entire reason the prop is a boolean rather than two class names.
|
|
23
|
+
|
|
24
|
+
mux-magic's `PageHeader` is documented as a sticky header and sets only `z-index`
|
|
25
|
+
(`PageHeader.tsx:173`). It never sets `position: sticky`, so it scrolls away like any other
|
|
26
|
+
block and the z-index it carries has nothing to stack against — it is a flex-column shell
|
|
27
|
+
wearing a sticky header's clothes. The CSS is valid, the intent is stated in a comment, and
|
|
28
|
+
nobody scrolls a component in a review.
|
|
29
|
+
|
|
30
|
+
<Canvas of={HeaderStories.AllVariants} />
|
|
31
|
+
|
|
32
|
+
The z-index is `--layer-sticky` (100) from the token scale, never a hand-picked number. The
|
|
33
|
+
fleet's headers currently sit at `z-40` (mail-sifter, points-market), `z-50` (rip-deck),
|
|
34
|
+
`z-10` (spoolbuddy) and `z-[100]` (mux-magic) with no ordering guarantee between a header
|
|
35
|
+
and anything else. On the scale, `sticky` is below `dropdown` (200), `modal` (400) and
|
|
36
|
+
`toast` (500) — so a `Menu` opened *from* the header paints over it, by construction.
|
|
37
|
+
|
|
38
|
+
<Canvas of={HeaderStories.NotSticky} />
|
|
39
|
+
|
|
40
|
+
## It invents no colours
|
|
41
|
+
|
|
42
|
+
`bg-surface-raised/85`, `backdrop-blur-md`, `border-border-subtle`. The fleet's copy of this
|
|
43
|
+
line is `bg-surface-base/90` — the header at *the same colour as the page behind it*,
|
|
44
|
+
leaning entirely on the blur to separate the two, so it disappears against a page that has
|
|
45
|
+
not been scrolled. `raised` means *more separated from base*, which is the role this
|
|
46
|
+
element actually wants.
|
|
47
|
+
|
|
48
|
+
## The width comes from `Shell`
|
|
49
|
+
|
|
50
|
+
<Canvas of={HeaderStories.Responsive} />
|
|
51
|
+
|
|
52
|
+
The `<header>` is full-bleed — the border and the translucent fill run edge to edge — and
|
|
53
|
+
only its **inner row** is capped. That is what stops a constrained header from reading as a
|
|
54
|
+
floating island on an ultrawide, and it is the half points-market got right while its
|
|
55
|
+
`<main>` went uncapped.
|
|
56
|
+
|
|
57
|
+
The cap itself comes from `Shell`'s `contentWidth` through context, so the title always
|
|
58
|
+
sits directly above the content it names. Passing `contentWidth` here overrides it for the
|
|
59
|
+
rare case where they genuinely differ.
|
|
60
|
+
|
|
61
|
+
## Slots, in render order
|
|
62
|
+
|
|
63
|
+
`heading` → `children` → `actions`. Every one is optional: a header with only actions is
|
|
64
|
+
image-viewer's `TitleBar`, one with only a heading is board-games'. `children` is the middle
|
|
65
|
+
— a search field, a breadcrumb, a status pill — and `actions` is pushed to the far end with
|
|
66
|
+
`ms-auto`, so it stays at the end in RTL too.
|
|
67
|
+
|
|
68
|
+
<Canvas of={HeaderStories.Interactive} />
|
|
69
|
+
|
|
70
|
+
`headingLevel` defaults to `1`, because on most pages the app's name *is* the document's
|
|
71
|
+
top-level heading. An app whose page title is its `h1` sets `headingLevel={2}` here rather
|
|
72
|
+
than leaving two of them.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
|
|
3
|
+
import { Badge } from "../Badge/Badge.tsx"
|
|
4
|
+
import { Button } from "../Button/Button.tsx"
|
|
5
|
+
import {
|
|
6
|
+
StoryCell,
|
|
7
|
+
StorySection,
|
|
8
|
+
} from "../board.storyHelpers.tsx"
|
|
9
|
+
import { Main } from "../Main/Main.tsx"
|
|
10
|
+
import { Shell } from "../Shell/Shell.tsx"
|
|
11
|
+
import {
|
|
12
|
+
HeaderSchemeToggle,
|
|
13
|
+
PageContent,
|
|
14
|
+
ScrollFiller,
|
|
15
|
+
} from "../shell.storyHelpers.tsx"
|
|
16
|
+
import { Header } from "./Header.tsx"
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
title: "Components/Header",
|
|
20
|
+
component: Header,
|
|
21
|
+
parameters: { layout: "fullscreen" },
|
|
22
|
+
argTypes: {
|
|
23
|
+
headingLevel: {
|
|
24
|
+
control: { type: "radio" },
|
|
25
|
+
options: [1, 2],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
args: {
|
|
29
|
+
headingLevel: 1,
|
|
30
|
+
isSticky: true,
|
|
31
|
+
},
|
|
32
|
+
} satisfies Meta<typeof Header>
|
|
33
|
+
|
|
34
|
+
export default meta
|
|
35
|
+
|
|
36
|
+
type Story = StoryObj<typeof meta>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The fleet's header, as one component: the app's name, the
|
|
40
|
+
* controls that belong to the frame, a real `<header>` landmark,
|
|
41
|
+
* and a fill that is `surface-raised` rather than the page's own
|
|
42
|
+
* colour.
|
|
43
|
+
*/
|
|
44
|
+
export const Default: Story = {
|
|
45
|
+
args: { heading: "Rip Deck" },
|
|
46
|
+
render: (headerProps) => (
|
|
47
|
+
<Shell>
|
|
48
|
+
<Header
|
|
49
|
+
{...headerProps}
|
|
50
|
+
actions={<HeaderSchemeToggle />}
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
<Main>
|
|
54
|
+
<PageContent />
|
|
55
|
+
</Main>
|
|
56
|
+
</Shell>
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The three slots, in the order they render: the heading, the
|
|
62
|
+
* middle (`children` — a search field, a breadcrumb, a status
|
|
63
|
+
* pill), and `actions`, which is pushed to the far end.
|
|
64
|
+
*
|
|
65
|
+
* Every one is optional. A header with only actions is the
|
|
66
|
+
* image-viewer's `TitleBar`; a header with only a heading is
|
|
67
|
+
* board-games'.
|
|
68
|
+
*/
|
|
69
|
+
export const AllStates: Story = {
|
|
70
|
+
render: (headerProps) => (
|
|
71
|
+
<StorySection title="Ten repos hand-roll this row. These are the four shapes they hand-roll it into.">
|
|
72
|
+
<StoryCell align="stretch" label="heading only">
|
|
73
|
+
<Header {...headerProps} heading="Board Games" />
|
|
74
|
+
</StoryCell>
|
|
75
|
+
|
|
76
|
+
<StoryCell align="stretch" label="heading + actions">
|
|
77
|
+
<Header
|
|
78
|
+
{...headerProps}
|
|
79
|
+
actions={<HeaderSchemeToggle />}
|
|
80
|
+
heading="Points Market"
|
|
81
|
+
/>
|
|
82
|
+
</StoryCell>
|
|
83
|
+
|
|
84
|
+
<StoryCell
|
|
85
|
+
align="stretch"
|
|
86
|
+
label="heading + middle + actions"
|
|
87
|
+
>
|
|
88
|
+
<Header
|
|
89
|
+
{...headerProps}
|
|
90
|
+
actions={<HeaderSchemeToggle />}
|
|
91
|
+
heading="Mail Sifter"
|
|
92
|
+
>
|
|
93
|
+
<Badge intent="info">412 unread</Badge>
|
|
94
|
+
</Header>
|
|
95
|
+
</StoryCell>
|
|
96
|
+
|
|
97
|
+
<StoryCell align="stretch" label="actions only">
|
|
98
|
+
<Header {...headerProps}>
|
|
99
|
+
<Button appearance="ghost" size="sm">
|
|
100
|
+
Open
|
|
101
|
+
</Button>
|
|
102
|
+
</Header>
|
|
103
|
+
</StoryCell>
|
|
104
|
+
</StorySection>
|
|
105
|
+
),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* **`isSticky` sets `position` and `z-index` together, and that
|
|
110
|
+
* is the point.** mux-magic's `PageHeader` is documented as a
|
|
111
|
+
* sticky header and sets only the z-index, so it scrolls away
|
|
112
|
+
* like any other block — valid CSS, stated intent, no effect.
|
|
113
|
+
*
|
|
114
|
+
* Scroll the panel below. The z-index is `--layer-sticky` (100)
|
|
115
|
+
* from the token scale, so a `Menu` opened from the header — at
|
|
116
|
+
* `--layer-modal` (400) — still paints over it.
|
|
117
|
+
*/
|
|
118
|
+
export const AllVariants: Story = {
|
|
119
|
+
render: (headerProps) => (
|
|
120
|
+
<div className="h-96 overflow-y-auto border border-border-subtle">
|
|
121
|
+
<Shell>
|
|
122
|
+
<Header
|
|
123
|
+
{...headerProps}
|
|
124
|
+
actions={<HeaderSchemeToggle />}
|
|
125
|
+
heading="Scroll me"
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
<Main>
|
|
129
|
+
<PageContent />
|
|
130
|
+
|
|
131
|
+
<ScrollFiller />
|
|
132
|
+
</Main>
|
|
133
|
+
</Shell>
|
|
134
|
+
</div>
|
|
135
|
+
),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `isSticky={false}` — the header scrolls away with the page, and
|
|
140
|
+
* takes its z-index with it rather than leaving a stacking
|
|
141
|
+
* context nobody asked for. board-games' `AppShell.tsx` is this
|
|
142
|
+
* one: the same structure as mail-sifter's and points-market's,
|
|
143
|
+
* minus `sticky`.
|
|
144
|
+
*/
|
|
145
|
+
export const NotSticky: Story = {
|
|
146
|
+
args: { heading: "Board Games", isSticky: false },
|
|
147
|
+
render: (headerProps) => (
|
|
148
|
+
<div className="h-96 overflow-y-auto border border-border-subtle">
|
|
149
|
+
<Shell>
|
|
150
|
+
<Header
|
|
151
|
+
{...headerProps}
|
|
152
|
+
actions={<HeaderSchemeToggle />}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<Main>
|
|
156
|
+
<PageContent />
|
|
157
|
+
|
|
158
|
+
<ScrollFiller />
|
|
159
|
+
</Main>
|
|
160
|
+
</Shell>
|
|
161
|
+
</div>
|
|
162
|
+
),
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The width contract. **The `<header>` is full-bleed and only its
|
|
167
|
+
* inner row is capped**, which is what stops a constrained header
|
|
168
|
+
* from looking like a floating island on an ultrawide — and the
|
|
169
|
+
* cap it uses is `Shell`'s, so the title always sits directly
|
|
170
|
+
* above the content it names.
|
|
171
|
+
*
|
|
172
|
+
* points-market is the app where those two disagree: its header
|
|
173
|
+
* row is capped at 80rem and its `<main>` is not capped at all.
|
|
174
|
+
*/
|
|
175
|
+
export const Responsive: Story = {
|
|
176
|
+
render: (headerProps) => (
|
|
177
|
+
<Shell contentWidth="sm">
|
|
178
|
+
<Header
|
|
179
|
+
{...headerProps}
|
|
180
|
+
actions={<HeaderSchemeToggle />}
|
|
181
|
+
heading="Narrow by contract"
|
|
182
|
+
/>
|
|
183
|
+
|
|
184
|
+
<Main>
|
|
185
|
+
<PageContent />
|
|
186
|
+
</Main>
|
|
187
|
+
</Shell>
|
|
188
|
+
),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* What an agent reaches for. The header is a landmark, so
|
|
193
|
+
* `getByRole("banner")` scopes every query inside it — which is
|
|
194
|
+
* how one of the three "Settings" buttons on a page becomes
|
|
195
|
+
* addressable.
|
|
196
|
+
*/
|
|
197
|
+
export const Interactive: Story = {
|
|
198
|
+
args: { heading: "Rip Deck" },
|
|
199
|
+
render: (headerProps) => (
|
|
200
|
+
<Shell>
|
|
201
|
+
<Header
|
|
202
|
+
{...headerProps}
|
|
203
|
+
actions={
|
|
204
|
+
<>
|
|
205
|
+
<Button appearance="outline" size="sm">
|
|
206
|
+
Settings
|
|
207
|
+
</Button>
|
|
208
|
+
|
|
209
|
+
<HeaderSchemeToggle />
|
|
210
|
+
</>
|
|
211
|
+
}
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
<Main>
|
|
215
|
+
<Button appearance="outline" size="sm">
|
|
216
|
+
Settings
|
|
217
|
+
</Button>
|
|
218
|
+
</Main>
|
|
219
|
+
</Shell>
|
|
220
|
+
),
|
|
221
|
+
}
|