@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/README.md
CHANGED
|
@@ -38,6 +38,15 @@ The router is **injected** rather than depended on — `RouterLinkProvider` at t
|
|
|
38
38
|
with `@charcuterie/ui/react-router` as an optional subpath adapter and a plain `<a>` when
|
|
39
39
|
nothing is provided. Recipe: **Guides/Routing** in Storybook.
|
|
40
40
|
|
|
41
|
+
**The unified app shell adds four**: `Shell`, `Header`, `Rail`, `Main` — the fleet's largest
|
|
42
|
+
single duplication, where ten of twelve UI repos hand-roll the page chrome and three of them
|
|
43
|
+
have a file named `AppShell.tsx` (two of those headers are a *byte-identical* class string,
|
|
44
|
+
arrived at independently). One `contentWidth` on `Shell` feeds both the header row and the
|
|
45
|
+
content column, `isSticky` writes `position` and `z-index` together, and a `Rail` collapses
|
|
46
|
+
by restyling rather than by rendering itself twice.
|
|
47
|
+
[Decision](../../docs/decisions/2026-08-10-the-app-shell-is-shell-header-rail-main.md);
|
|
48
|
+
`Shell`'s docs page carries three copy-wholesale templates.
|
|
49
|
+
|
|
41
50
|
## Install and wire
|
|
42
51
|
|
|
43
52
|
```ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,MAAM,GAAI,kGAUpB,WAAW,KAAG,
|
|
1
|
+
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,MAAM,GAAI,kGAUpB,WAAW,KAAG,SA4DhB,CAAA"}
|
package/dist/Dialog/Dialog.js
CHANGED
|
@@ -47,6 +47,8 @@ const SIZE_CLASS = {
|
|
|
47
47
|
export const Dialog = ({ children, className, footer, heading, headingLevel = 2, isDismissable = true, isVisible, onClose, size = "md", }) => {
|
|
48
48
|
const headingId = useUniqueId();
|
|
49
49
|
const Heading = `h${headingLevel}`;
|
|
50
|
-
return (_jsxs(Modal, { "aria-labelledby": headingId, className: toClassName(SIZE_CLASS[size], className), isDismissable: isDismissable, isVisible: isVisible, onClose: onClose, children: [_jsxs("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border-subtle p-4", children: [_jsx(Heading, { className: "font-semibold text-lg leading-tight", id: headingId, children: heading }), isDismissable ? (_jsx(Button, { appearance: "ghost", intent: "neutral", onClick: onClose, size: "sm", children: "Close" })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4",
|
|
50
|
+
return (_jsxs(Modal, { "aria-labelledby": headingId, className: toClassName(SIZE_CLASS[size], className), isDismissable: isDismissable, isVisible: isVisible, onClose: onClose, children: [_jsxs("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border-subtle p-4", children: [_jsx(Heading, { className: "font-semibold text-lg leading-tight", id: headingId, children: heading }), isDismissable ? (_jsx(Button, { appearance: "ghost", intent: "neutral", onClick: onClose, size: "sm", children: "Close" })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4",
|
|
51
|
+
// 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.
|
|
52
|
+
tabIndex: 0, children: children }), footer ? (_jsx("footer", { className: "flex shrink-0 flex-wrap justify-end gap-2 border-t border-border-subtle p-4", children: footer })) : null] }));
|
|
51
53
|
};
|
|
52
54
|
//# sourceMappingURL=Dialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA+B/C;;;;GAIG;AACH,MAAM,UAAU,GAA+B;IAC7C,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,gCAAgC;IACpC,iEAAiE;IACjE,8DAA8D;IAC9D,6DAA6D;IAC7D,kEAAkE;IAClE,+DAA+D;IAC/D,+DAA+D;IAC/D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,0DAA0D;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,QAAQ,EACR,SAAS,EACT,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,IAAI,GACC,EAAa,EAAE;IAC3B,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,MAAC,KAAK,uBACa,SAAS,EAC1B,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACnD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,aAEhB,kBAAQ,SAAS,EAAC,oFAAoF,aACpG,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,EAET,aAAa,CAAC,CAAC,CAAC,CACf,KAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,IAAI,sBAGF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,
|
|
1
|
+
{"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA+B/C;;;;GAIG;AACH,MAAM,UAAU,GAA+B;IAC7C,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,gCAAgC;IACpC,iEAAiE;IACjE,8DAA8D;IAC9D,6DAA6D;IAC7D,kEAAkE;IAClE,+DAA+D;IAC/D,+DAA+D;IAC/D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,0DAA0D;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,QAAQ,EACR,SAAS,EACT,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,IAAI,GACC,EAAa,EAAE;IAC3B,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,MAAC,KAAK,uBACa,SAAS,EAC1B,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACnD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,aAEhB,kBAAQ,SAAS,EAAC,oFAAoF,aACpG,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,EAET,aAAa,CAAC,CAAC,CAAC,CACf,KAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,IAAI,sBAGF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,EAcT,cACE,SAAS,EAAC,kCAAkC;gBAC5C,2WAA2W;gBAC3W,QAAQ,EAAE,CAAC,YAEV,QAAQ,GACL,EAEL,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,6EAA6E,YAC5F,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG;IACnC;;;;;OAKG;IACH,QAAQ,EAAE,YAAY,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,eAAO,MAAM,KAAK,GAAI,sFAQnB,UAAU,KAAG,
|
|
1
|
+
{"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG;IACnC;;;;;OAKG;IACH,QAAQ,EAAE,YAAY,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,eAAO,MAAM,KAAK,GAAI,sFAQnB,UAAU,KAAG,SAgHf,CAAA"}
|
package/dist/Field/Field.js
CHANGED
|
@@ -121,6 +121,6 @@ export const Field = ({ children, className, description, error, isRequired = fa
|
|
|
121
121
|
id: controlId,
|
|
122
122
|
required: isRequired || undefined,
|
|
123
123
|
}));
|
|
124
|
-
return (_jsxs("div", { className: toClassName("flex flex-col gap-1.5", className), children: [_jsxs("label", { className: "font-medium text-content-primary text-sm", htmlFor: controlId, children: [label, isRequired ? (_jsx("span", { "aria-hidden": "true", className: "ms-1 text-intent-danger-content", children: "*" })) : null] }), clonedControl, description === undefined ? null : (_jsx("p", { className: "text-content-secondary text-
|
|
124
|
+
return (_jsxs("div", { className: toClassName("flex flex-col gap-1.5", className), children: [_jsxs("label", { className: "font-medium text-content-primary text-sm", htmlFor: controlId, children: [label, isRequired ? (_jsx("span", { "aria-hidden": "true", className: "ms-1 text-intent-danger-content", children: "*" })) : null] }), clonedControl, description === undefined ? null : (_jsx("p", { className: "text-content-secondary text-sm", id: descriptionId, children: description })), error === undefined ? null : (_jsx("p", { className: "text-intent-danger-content text-sm", id: errorId, children: error }))] }));
|
|
125
125
|
};
|
|
126
126
|
//# sourceMappingURL=Field.js.map
|
package/dist/Field/Field.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.js","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA2B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,GAAG,iBAAiB,EACT,EAAa,EAAE;IAC1B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,OAAO,GAAI,QAAQ,CAAC,KAAyB,CAAC,EAAE,CAAA;IAEtD,MAAM,SAAS,GACb,iBAAiB,CAAC,EAAE,IAAI,OAAO,IAAI,GAAG,MAAM,UAAU,CAAA;IAExD,MAAM,aAAa,GAAG,GAAG,MAAM,cAAc,CAAA;IAE7C,MAAM,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAA;IAEjC,MAAM,WAAW,GAAG;QAClB,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;QAChD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;KACrC;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,MAAM,aAAa,GAAG,cAAc,CAClC,QAAQ,EACR,cAAc,CAAC,iBAAiB,EAAE;QAChC,kBAAkB,EAChB,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;QAC9C,6DAA6D;QAC7D,+DAA+D;QAC/D,iDAAiD;QACjD,cAAc,EACZ,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;QACxC,sDAAsD;QACtD,8DAA8D;QAC9D,8DAA8D;QAC9D,gDAAgD;QAChD,eAAe,EAAE,UAAU,IAAI,SAAS;QACxC,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,UAAU,IAAI,SAAS;KAClC,CAAC,CACH,CAAA;IAED,OAAO,CACL,eACE,SAAS,EAAE,WAAW,CACpB,uBAAuB,EACvB,SAAS,CACV,aAED,iBACE,SAAS,EAAC,0CAA0C,EACpD,OAAO,EAAE,SAAS,aAEjB,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BAKc,MAAM,EAClB,SAAS,EAAC,iCAAiC,kBAGtC,CACR,CAAC,CAAC,CAAC,IAAI,IACF,EAEP,aAAa,
|
|
1
|
+
{"version":3,"file":"Field.js","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA2B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,GAAG,iBAAiB,EACT,EAAa,EAAE;IAC1B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,OAAO,GAAI,QAAQ,CAAC,KAAyB,CAAC,EAAE,CAAA;IAEtD,MAAM,SAAS,GACb,iBAAiB,CAAC,EAAE,IAAI,OAAO,IAAI,GAAG,MAAM,UAAU,CAAA;IAExD,MAAM,aAAa,GAAG,GAAG,MAAM,cAAc,CAAA;IAE7C,MAAM,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAA;IAEjC,MAAM,WAAW,GAAG;QAClB,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;QAChD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;KACrC;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,MAAM,aAAa,GAAG,cAAc,CAClC,QAAQ,EACR,cAAc,CAAC,iBAAiB,EAAE;QAChC,kBAAkB,EAChB,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;QAC9C,6DAA6D;QAC7D,+DAA+D;QAC/D,iDAAiD;QACjD,cAAc,EACZ,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;QACxC,sDAAsD;QACtD,8DAA8D;QAC9D,8DAA8D;QAC9D,gDAAgD;QAChD,eAAe,EAAE,UAAU,IAAI,SAAS;QACxC,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,UAAU,IAAI,SAAS;KAClC,CAAC,CACH,CAAA;IAED,OAAO,CACL,eACE,SAAS,EAAE,WAAW,CACpB,uBAAuB,EACvB,SAAS,CACV,aAED,iBACE,SAAS,EAAC,0CAA0C,EACpD,OAAO,EAAE,SAAS,aAEjB,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BAKc,MAAM,EAClB,SAAS,EAAC,iCAAiC,kBAGtC,CACR,CAAC,CAAC,CAAC,IAAI,IACF,EAEP,aAAa,EAUb,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAClC,YACE,SAAS,EAAC,gCAAgC,EAC1C,EAAE,EAAE,aAAa,YAEhB,WAAW,GACV,CACL,EAEA,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC5B,YACE,SAAS,EAAC,oCAAoC,EAC9C,EAAE,EAAE,OAAO,YAEV,KAAK,GACJ,CACL,IACG,CACP,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldGroup.d.ts","sourceRoot":"","sources":["../../src/Field/FieldGroup.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;OAIG;IACH,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,UAAU,GAAI,iEAOxB,eAAe,KAAG,
|
|
1
|
+
{"version":3,"file":"FieldGroup.d.ts","sourceRoot":"","sources":["../../src/Field/FieldGroup.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;OAIG;IACH,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,UAAU,GAAI,iEAOxB,eAAe,KAAG,SAuEpB,CAAA"}
|
package/dist/Field/FieldGroup.js
CHANGED
|
@@ -60,6 +60,6 @@ export const FieldGroup = ({ children, className, description, error, isRequired
|
|
|
60
60
|
// last of which makes it refuse to shrink inside a flex or
|
|
61
61
|
// grid parent, which is the reason design systems avoid the
|
|
62
62
|
// element. All four are cleared here, once.
|
|
63
|
-
"m-0 flex min-w-0 flex-col gap-1.5 border-0 p-0", className), children: [_jsxs("legend", { className: "mb-1.5 p-0 font-medium text-content-primary text-sm", children: [label, isRequired ? (_jsx("span", { "aria-hidden": "true", className: "ms-1 text-intent-danger-content", children: "*" })) : null] }), children, description === undefined ? null : (_jsx("p", { className: "text-content-secondary text-
|
|
63
|
+
"m-0 flex min-w-0 flex-col gap-1.5 border-0 p-0", className), children: [_jsxs("legend", { className: "mb-1.5 p-0 font-medium text-content-primary text-sm", children: [label, isRequired ? (_jsx("span", { "aria-hidden": "true", className: "ms-1 text-intent-danger-content", children: "*" })) : null] }), children, description === undefined ? null : (_jsx("p", { className: "text-content-secondary text-sm", id: descriptionId, children: description })), error === undefined ? null : (_jsx("p", { className: "text-intent-danger-content text-sm", id: errorId, children: error }))] }));
|
|
64
64
|
};
|
|
65
65
|
//# sourceMappingURL=FieldGroup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldGroup.js","sourceRoot":"","sources":["../../src/Field/FieldGroup.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAkB/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,GAAG,KAAK,EAClB,KAAK,GACW,EAAa,EAAE;IAC/B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B,MAAM,aAAa,GAAG,GAAG,MAAM,cAAc,CAAA;IAE7C,MAAM,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAA;IAEjC,iEAAiE;IACjE,gEAAgE;IAChE,2BAA2B;IAC3B,MAAM,WAAW,GAAG;QAClB,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;QAChD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;KACrC;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,OAAO,CACL,wCAEI,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAE9C,SAAS,EAAE,WAAW;QACpB,4DAA4D;QAC5D,0DAA0D;QAC1D,2DAA2D;QAC3D,4DAA4D;QAC5D,4CAA4C;QAC5C,gDAAgD,EAChD,SAAS,CACV,aAED,kBAAQ,SAAS,EAAC,qDAAqD,aACpE,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BAIc,MAAM,EAClB,SAAS,EAAC,iCAAiC,kBAGtC,CACR,CAAC,CAAC,CAAC,IAAI,IACD,EAER,QAAQ,
|
|
1
|
+
{"version":3,"file":"FieldGroup.js","sourceRoot":"","sources":["../../src/Field/FieldGroup.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAkB/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,GAAG,KAAK,EAClB,KAAK,GACW,EAAa,EAAE;IAC/B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B,MAAM,aAAa,GAAG,GAAG,MAAM,cAAc,CAAA;IAE7C,MAAM,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAA;IAEjC,iEAAiE;IACjE,gEAAgE;IAChE,2BAA2B;IAC3B,MAAM,WAAW,GAAG;QAClB,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;QAChD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;KACrC;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,OAAO,CACL,wCAEI,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAE9C,SAAS,EAAE,WAAW;QACpB,4DAA4D;QAC5D,0DAA0D;QAC1D,2DAA2D;QAC3D,4DAA4D;QAC5D,4CAA4C;QAC5C,gDAAgD,EAChD,SAAS,CACV,aAED,kBAAQ,SAAS,EAAC,qDAAqD,aACpE,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BAIc,MAAM,EAClB,SAAS,EAAC,iCAAiC,kBAGtC,CACR,CAAC,CAAC,CAAC,IAAI,IACD,EAER,QAAQ,EAIR,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAClC,YACE,SAAS,EAAC,gCAAgC,EAC1C,EAAE,EAAE,aAAa,YAEhB,WAAW,GACV,CACL,EAEA,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC5B,YACE,SAAS,EAAC,oCAAoC,EAC9C,EAAE,EAAE,OAAO,YAEV,KAAK,GACJ,CACL,IACQ,CACZ,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef, type ReactNode } from "react";
|
|
2
|
+
import { type ContentWidth } from "../Shell/contentWidth.ts";
|
|
3
|
+
export type HeaderProps = ComponentPropsWithRef<"header"> & {
|
|
4
|
+
actions?: ReactNode;
|
|
5
|
+
contentWidth?: ContentWidth;
|
|
6
|
+
heading?: string;
|
|
7
|
+
headingLevel?: 1 | 2;
|
|
8
|
+
isSticky?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The page header — the `<header>` landmark, the app's name, and
|
|
12
|
+
* the controls that belong to the frame rather than to the page.
|
|
13
|
+
*
|
|
14
|
+
* **`isSticky` defaults to true, and it sets `position` as well as
|
|
15
|
+
* `z-index`.** That pairing is the whole point. mux-magic's
|
|
16
|
+
* `PageHeader` is documented as a sticky header and sets only
|
|
17
|
+
* `z-index` (`PageHeader.tsx:173`) — it is a flex-column shell that
|
|
18
|
+
* scrolls away like any other block, and the z-index it carries
|
|
19
|
+
* has nothing to stack against. The bug is invisible: the CSS is
|
|
20
|
+
* valid, the intent is stated in a comment, and nobody scrolls a
|
|
21
|
+
* component in a review. Here the two cannot come apart, because
|
|
22
|
+
* one boolean writes both.
|
|
23
|
+
*
|
|
24
|
+
* The z-index is `--layer-sticky` from the token scale, never a
|
|
25
|
+
* hand-picked number. The fleet's headers currently sit at `z-40`
|
|
26
|
+
* (mail-sifter, points-market), `z-50` (rip-deck), `z-10`
|
|
27
|
+
* (spoolbuddy) and `z-[100]` (mux-magic), with no ordering
|
|
28
|
+
* guarantee between a header and anything else — which is exactly
|
|
29
|
+
* what the scale exists to settle: `sticky` (100) is below
|
|
30
|
+
* `dropdown` (200), `modal` (400) and `toast` (500), so a menu
|
|
31
|
+
* opened *from* the header paints over it.
|
|
32
|
+
*
|
|
33
|
+
* ## The width comes from `Shell`
|
|
34
|
+
*
|
|
35
|
+
* The `<header>` itself is full-bleed — the border and the
|
|
36
|
+
* translucent fill run edge to edge — and only its **inner row** is
|
|
37
|
+
* capped, which is what makes a constrained header look right on an
|
|
38
|
+
* ultrawide. That cap is `Shell`'s `contentWidth`, not a second
|
|
39
|
+
* opinion: see `shellContext.ts` for the shipped app whose header
|
|
40
|
+
* and `<main>` disagree by 80rem.
|
|
41
|
+
*
|
|
42
|
+
* ## No invented colours
|
|
43
|
+
*
|
|
44
|
+
* `bg-surface-raised/85` + `backdrop-blur-md` + `border-border-subtle`.
|
|
45
|
+
* The fleet's copy of this line is `bg-surface-base/90`, which puts
|
|
46
|
+
* the header at the *same* colour as the page behind it and leans
|
|
47
|
+
* entirely on the blur to separate them — so it vanishes against a
|
|
48
|
+
* page that is not scrolled. `raised` means *more separated from
|
|
49
|
+
* base*, which is the role this element actually wants.
|
|
50
|
+
*/
|
|
51
|
+
export declare const Header: ({ actions, children, className, contentWidth, heading, headingLevel, isSticky, ...headerProps }: HeaderProps) => ReactNode;
|
|
52
|
+
//# sourceMappingURL=Header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/Header/Header.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EAEf,MAAM,OAAO,CAAA;AAEd,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,0BAA0B,CAAA;AAIjC,MAAM,MAAM,WAAW,GACrB,qBAAqB,CAAC,QAAQ,CAAC,GAAG;IAChC,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,MAAM,GAAI,iGASpB,WAAW,KAAG,SA4ChB,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, } from "react";
|
|
3
|
+
import { DEFAULT_CONTENT_WIDTH, toMaxInlineSize, } from "../Shell/contentWidth.js";
|
|
4
|
+
import { ShellContext } from "../Shell/shellContext.js";
|
|
5
|
+
import { toClassName } from "../toClassName.js";
|
|
6
|
+
/**
|
|
7
|
+
* The page header — the `<header>` landmark, the app's name, and
|
|
8
|
+
* the controls that belong to the frame rather than to the page.
|
|
9
|
+
*
|
|
10
|
+
* **`isSticky` defaults to true, and it sets `position` as well as
|
|
11
|
+
* `z-index`.** That pairing is the whole point. mux-magic's
|
|
12
|
+
* `PageHeader` is documented as a sticky header and sets only
|
|
13
|
+
* `z-index` (`PageHeader.tsx:173`) — it is a flex-column shell that
|
|
14
|
+
* scrolls away like any other block, and the z-index it carries
|
|
15
|
+
* has nothing to stack against. The bug is invisible: the CSS is
|
|
16
|
+
* valid, the intent is stated in a comment, and nobody scrolls a
|
|
17
|
+
* component in a review. Here the two cannot come apart, because
|
|
18
|
+
* one boolean writes both.
|
|
19
|
+
*
|
|
20
|
+
* The z-index is `--layer-sticky` from the token scale, never a
|
|
21
|
+
* hand-picked number. The fleet's headers currently sit at `z-40`
|
|
22
|
+
* (mail-sifter, points-market), `z-50` (rip-deck), `z-10`
|
|
23
|
+
* (spoolbuddy) and `z-[100]` (mux-magic), with no ordering
|
|
24
|
+
* guarantee between a header and anything else — which is exactly
|
|
25
|
+
* what the scale exists to settle: `sticky` (100) is below
|
|
26
|
+
* `dropdown` (200), `modal` (400) and `toast` (500), so a menu
|
|
27
|
+
* opened *from* the header paints over it.
|
|
28
|
+
*
|
|
29
|
+
* ## The width comes from `Shell`
|
|
30
|
+
*
|
|
31
|
+
* The `<header>` itself is full-bleed — the border and the
|
|
32
|
+
* translucent fill run edge to edge — and only its **inner row** is
|
|
33
|
+
* capped, which is what makes a constrained header look right on an
|
|
34
|
+
* ultrawide. That cap is `Shell`'s `contentWidth`, not a second
|
|
35
|
+
* opinion: see `shellContext.ts` for the shipped app whose header
|
|
36
|
+
* and `<main>` disagree by 80rem.
|
|
37
|
+
*
|
|
38
|
+
* ## No invented colours
|
|
39
|
+
*
|
|
40
|
+
* `bg-surface-raised/85` + `backdrop-blur-md` + `border-border-subtle`.
|
|
41
|
+
* The fleet's copy of this line is `bg-surface-base/90`, which puts
|
|
42
|
+
* the header at the *same* colour as the page behind it and leans
|
|
43
|
+
* entirely on the blur to separate them — so it vanishes against a
|
|
44
|
+
* page that is not scrolled. `raised` means *more separated from
|
|
45
|
+
* base*, which is the role this element actually wants.
|
|
46
|
+
*/
|
|
47
|
+
export const Header = ({ actions, children, className, contentWidth, heading, headingLevel = 1, isSticky = true, ...headerProps }) => {
|
|
48
|
+
const shell = useContext(ShellContext);
|
|
49
|
+
const Heading = `h${headingLevel}`;
|
|
50
|
+
const maxInlineSize = toMaxInlineSize(contentWidth ??
|
|
51
|
+
shell?.contentWidth ??
|
|
52
|
+
DEFAULT_CONTENT_WIDTH);
|
|
53
|
+
return (_jsx("header", { ...headerProps, className: toClassName("col-span-full row-start-1 border-border-subtle border-b bg-surface-raised/85 backdrop-blur-md", isSticky && "sticky top-0 z-[var(--layer-sticky)]", className), children: _jsxs("div", { className: "mx-auto flex w-full min-w-0 flex-wrap items-center gap-3 px-4 py-3 sm:px-6", style: { maxInlineSize }, children: [heading ? (_jsx(Heading, { className: "min-w-0 truncate font-semibold text-lg leading-tight", children: heading })) : null, children ? (_jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: children })) : null, actions ? (_jsx("div", { className: "ms-auto flex shrink-0 items-center gap-2", children: actions })) : null] }) }));
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=Header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Header.js","sourceRoot":"","sources":["../../src/Header/Header.tsx"],"names":[],"mappings":";AAAA,OAAO,EAGL,UAAU,GACX,MAAM,OAAO,CAAA;AAEd,OAAO,EAEL,qBAAqB,EACrB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAW/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,OAAO,EACP,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,QAAQ,GAAG,IAAI,EACf,GAAG,WAAW,EACF,EAAa,EAAE;IAC3B,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IAEtC,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,MAAM,aAAa,GAAG,eAAe,CACnC,YAAY;QACV,KAAK,EAAE,YAAY;QACnB,qBAAqB,CACxB,CAAA;IAED,OAAO,CACL,oBACM,WAAW,EACf,SAAS,EAAE,WAAW,CACpB,+FAA+F,EAC/F,QAAQ,IAAI,sCAAsC,EAClD,SAAS,CACV,YAED,eACE,SAAS,EAAC,4EAA4E,EACtF,KAAK,EAAE,EAAE,aAAa,EAAE,aAEvB,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,OAAO,IAAC,SAAS,EAAC,sDAAsD,YACtE,OAAO,GACA,CACX,CAAC,CAAC,CAAC,IAAI,EAEP,QAAQ,CAAC,CAAC,CAAC,CACV,cAAK,SAAS,EAAC,wCAAwC,YACpD,QAAQ,GACL,CACP,CAAC,CAAC,CAAC,IAAI,EAEP,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,0CAA0C,YACtD,OAAO,GACJ,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,GACC,CACV,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef, type ReactNode } from "react";
|
|
2
|
+
import { type ContentWidth } from "../Shell/contentWidth.ts";
|
|
3
|
+
export type MainProps = ComponentPropsWithRef<"main"> & {
|
|
4
|
+
contentWidth?: ContentWidth;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* The `<main>` landmark and the content column.
|
|
8
|
+
*
|
|
9
|
+
* ## The width is the whole component
|
|
10
|
+
*
|
|
11
|
+
* The owner's complaint, verbatim:
|
|
12
|
+
*
|
|
13
|
+
* > "All these apps seem to be really narrow with a very large
|
|
14
|
+
* > max-width. By that I mean 1 column, but waaaaaaay too wide. In
|
|
15
|
+
* > almost all cases, what I really want is something like Rip-Deck
|
|
16
|
+
* > where the app has a narrower main column but the wrapping grids
|
|
17
|
+
* > are all full-width in most of these apps only when you have too
|
|
18
|
+
* > many items."
|
|
19
|
+
*
|
|
20
|
+
* That is two requirements, and they pull opposite ways: a
|
|
21
|
+
* **narrow** column by default, which **widens** when the content
|
|
22
|
+
* is a grid with enough items to justify it. A fixed `max-w-7xl`
|
|
23
|
+
* answers neither — it is too wide for the one-column case, which
|
|
24
|
+
* is the complaint, and it is a hard ceiling on the many-items
|
|
25
|
+
* case.
|
|
26
|
+
*
|
|
27
|
+
* So the cap is a *value*, not a constant, and it arrives from
|
|
28
|
+
* three places in priority order: this component's own
|
|
29
|
+
* `contentWidth`, then `Shell`'s (the usual answer, so the header
|
|
30
|
+
* matches), then `screen.lg`.
|
|
31
|
+
*
|
|
32
|
+
* ### How it composes with the adaptive-columns hook
|
|
33
|
+
*
|
|
34
|
+
* rip-deck's `useLayoutColumns` — being lifted into this library
|
|
35
|
+
* separately — decides a column count from the viewport and folds
|
|
36
|
+
* it into a page cap with `contentMaxWidthRem(columns)`: 1 column →
|
|
37
|
+
* 56rem, 2 → 72rem, 3 → 106rem. It returns a **number of rem**, and
|
|
38
|
+
* `ContentWidth` accepts `` `${number}rem` `` for exactly this:
|
|
39
|
+
*
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const { columns } = useLayoutColumns({ cardCount: bays.length })
|
|
42
|
+
*
|
|
43
|
+
* <Shell contentWidth={`${contentMaxWidthRem(columns)}rem`}>
|
|
44
|
+
* <Header heading="Rip Deck" />
|
|
45
|
+
* <Main>
|
|
46
|
+
* <BayGrid columns={columns} />
|
|
47
|
+
* </Main>
|
|
48
|
+
* </Shell>
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* The seam is deliberately one prop and no hook import: this
|
|
52
|
+
* package must not depend on a layout hook to lay out a page, and
|
|
53
|
+
* the hook must not know that a `Shell` exists. Putting the value
|
|
54
|
+
* on `Shell` rather than on `Main` is what keeps the header's cap
|
|
55
|
+
* in step with the column's as the count changes.
|
|
56
|
+
*
|
|
57
|
+
* ## `@container`, and what `contain: inline-size` costs
|
|
58
|
+
*
|
|
59
|
+
* The content column establishes a container query, so an app's
|
|
60
|
+
* grids inside it respond to **the column's** width rather than
|
|
61
|
+
* the window's — a poster grid beside an open rail and the same
|
|
62
|
+
* grid full-bleed are the pair a media query cannot tell apart,
|
|
63
|
+
* and getting it wrong at intermediate widths is what the fleet's
|
|
64
|
+
* grids do today.
|
|
65
|
+
*
|
|
66
|
+
* `container-type: inline-size` implies `contain: layout style
|
|
67
|
+
* inline-size`, and both halves have consequences worth stating:
|
|
68
|
+
*
|
|
69
|
+
* - **The element may not be sized by its own contents.** Here
|
|
70
|
+
* that is free and in fact wanted: the column has a definite
|
|
71
|
+
* inline size from its parent — itself a grid item with
|
|
72
|
+
* `minmax(0, 1fr)` — so wide content can no longer widen it,
|
|
73
|
+
* which is half of why the page does not scroll sideways.
|
|
74
|
+
* - **`contain: layout` makes it a containing block for
|
|
75
|
+
* `position: fixed` descendants.** An app rendering a fixed
|
|
76
|
+
* element *inside* `Main` gets it positioned against the
|
|
77
|
+
* column, not the viewport. Every overlay in this library
|
|
78
|
+
* portals to `document.body` and is therefore unaffected
|
|
79
|
+
* ([decision](../../../../docs/decisions/2026-08-03-overlays-portal-to-the-body-not-the-top-layer.md));
|
|
80
|
+
* an app's own fixed chrome belongs in `Shell`, beside `Main`
|
|
81
|
+
* rather than in it.
|
|
82
|
+
*
|
|
83
|
+
* ## Two more things it does, both one line
|
|
84
|
+
*
|
|
85
|
+
* `tabIndex={-1}` so `Shell`'s skip link moves **focus** and not
|
|
86
|
+
* merely the reading cursor — without it, Safari and Firefox scroll
|
|
87
|
+
* to `<main>` and leave the next Tab back in the header.
|
|
88
|
+
*
|
|
89
|
+
* `wrap-anywhere` so an unbroken string — a filesystem path, a
|
|
90
|
+
* hash, a URL — wraps instead of widening the page.
|
|
91
|
+
*
|
|
92
|
+
* **`anywhere`, not `break-word`, and the difference is the whole
|
|
93
|
+
* point.** Both break a long word onto the next line, so they look
|
|
94
|
+
* identical in a screenshot. Only `anywhere` also shrinks the
|
|
95
|
+
* element's **min-content size** — which is what a flex or grid
|
|
96
|
+
* item's automatic minimum resolves against, and therefore what
|
|
97
|
+
* decides whether the string can push its own column wider. With
|
|
98
|
+
* `break-word` the ink wraps while the intrinsic contribution
|
|
99
|
+
* stays the full length of the token, so the layout above it can
|
|
100
|
+
* still be forced open. This is the second of the two overflow
|
|
101
|
+
* sources found in plex-channels, and the nastier one: it produces
|
|
102
|
+
* **no overflowing element box at all**, so every
|
|
103
|
+
* `getBoundingClientRect()` in a test reads clean while the page
|
|
104
|
+
* scrolls sideways.
|
|
105
|
+
*
|
|
106
|
+
* It is the cheap half of the horizontal-scroll fix; the other
|
|
107
|
+
* halves are `minmax(0, 1fr)` and `overflow-x: clip` in `Shell`. A
|
|
108
|
+
* `<table>` is the one thing none of them can save: tables do not
|
|
109
|
+
* wrap, so a wide one goes in an `overflow-x-auto` wrapper, which
|
|
110
|
+
* `Main.mdx` shows.
|
|
111
|
+
*/
|
|
112
|
+
export declare const Main: ({ children, className, contentWidth, id, ...mainProps }: MainProps) => ReactNode;
|
|
113
|
+
//# sourceMappingURL=Main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Main.d.ts","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EAEf,MAAM,OAAO,CAAA;AAEd,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,0BAA0B,CAAA;AAIjC,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC,GAAG;IACtD,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGG;AACH,eAAO,MAAM,IAAI,GAAI,yDAMlB,SAAS,KAAG,SA0Cd,CAAA"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useUniqueId } from "@charcuterie/logic";
|
|
3
|
+
import { useContext, } from "react";
|
|
4
|
+
import { DEFAULT_CONTENT_WIDTH, toMaxInlineSize, } from "../Shell/contentWidth.js";
|
|
5
|
+
import { ShellContext } from "../Shell/shellContext.js";
|
|
6
|
+
import { toClassName } from "../toClassName.js";
|
|
7
|
+
/**
|
|
8
|
+
* The `<main>` landmark and the content column.
|
|
9
|
+
*
|
|
10
|
+
* ## The width is the whole component
|
|
11
|
+
*
|
|
12
|
+
* The owner's complaint, verbatim:
|
|
13
|
+
*
|
|
14
|
+
* > "All these apps seem to be really narrow with a very large
|
|
15
|
+
* > max-width. By that I mean 1 column, but waaaaaaay too wide. In
|
|
16
|
+
* > almost all cases, what I really want is something like Rip-Deck
|
|
17
|
+
* > where the app has a narrower main column but the wrapping grids
|
|
18
|
+
* > are all full-width in most of these apps only when you have too
|
|
19
|
+
* > many items."
|
|
20
|
+
*
|
|
21
|
+
* That is two requirements, and they pull opposite ways: a
|
|
22
|
+
* **narrow** column by default, which **widens** when the content
|
|
23
|
+
* is a grid with enough items to justify it. A fixed `max-w-7xl`
|
|
24
|
+
* answers neither — it is too wide for the one-column case, which
|
|
25
|
+
* is the complaint, and it is a hard ceiling on the many-items
|
|
26
|
+
* case.
|
|
27
|
+
*
|
|
28
|
+
* So the cap is a *value*, not a constant, and it arrives from
|
|
29
|
+
* three places in priority order: this component's own
|
|
30
|
+
* `contentWidth`, then `Shell`'s (the usual answer, so the header
|
|
31
|
+
* matches), then `screen.lg`.
|
|
32
|
+
*
|
|
33
|
+
* ### How it composes with the adaptive-columns hook
|
|
34
|
+
*
|
|
35
|
+
* rip-deck's `useLayoutColumns` — being lifted into this library
|
|
36
|
+
* separately — decides a column count from the viewport and folds
|
|
37
|
+
* it into a page cap with `contentMaxWidthRem(columns)`: 1 column →
|
|
38
|
+
* 56rem, 2 → 72rem, 3 → 106rem. It returns a **number of rem**, and
|
|
39
|
+
* `ContentWidth` accepts `` `${number}rem` `` for exactly this:
|
|
40
|
+
*
|
|
41
|
+
* ```tsx
|
|
42
|
+
* const { columns } = useLayoutColumns({ cardCount: bays.length })
|
|
43
|
+
*
|
|
44
|
+
* <Shell contentWidth={`${contentMaxWidthRem(columns)}rem`}>
|
|
45
|
+
* <Header heading="Rip Deck" />
|
|
46
|
+
* <Main>
|
|
47
|
+
* <BayGrid columns={columns} />
|
|
48
|
+
* </Main>
|
|
49
|
+
* </Shell>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* The seam is deliberately one prop and no hook import: this
|
|
53
|
+
* package must not depend on a layout hook to lay out a page, and
|
|
54
|
+
* the hook must not know that a `Shell` exists. Putting the value
|
|
55
|
+
* on `Shell` rather than on `Main` is what keeps the header's cap
|
|
56
|
+
* in step with the column's as the count changes.
|
|
57
|
+
*
|
|
58
|
+
* ## `@container`, and what `contain: inline-size` costs
|
|
59
|
+
*
|
|
60
|
+
* The content column establishes a container query, so an app's
|
|
61
|
+
* grids inside it respond to **the column's** width rather than
|
|
62
|
+
* the window's — a poster grid beside an open rail and the same
|
|
63
|
+
* grid full-bleed are the pair a media query cannot tell apart,
|
|
64
|
+
* and getting it wrong at intermediate widths is what the fleet's
|
|
65
|
+
* grids do today.
|
|
66
|
+
*
|
|
67
|
+
* `container-type: inline-size` implies `contain: layout style
|
|
68
|
+
* inline-size`, and both halves have consequences worth stating:
|
|
69
|
+
*
|
|
70
|
+
* - **The element may not be sized by its own contents.** Here
|
|
71
|
+
* that is free and in fact wanted: the column has a definite
|
|
72
|
+
* inline size from its parent — itself a grid item with
|
|
73
|
+
* `minmax(0, 1fr)` — so wide content can no longer widen it,
|
|
74
|
+
* which is half of why the page does not scroll sideways.
|
|
75
|
+
* - **`contain: layout` makes it a containing block for
|
|
76
|
+
* `position: fixed` descendants.** An app rendering a fixed
|
|
77
|
+
* element *inside* `Main` gets it positioned against the
|
|
78
|
+
* column, not the viewport. Every overlay in this library
|
|
79
|
+
* portals to `document.body` and is therefore unaffected
|
|
80
|
+
* ([decision](../../../../docs/decisions/2026-08-03-overlays-portal-to-the-body-not-the-top-layer.md));
|
|
81
|
+
* an app's own fixed chrome belongs in `Shell`, beside `Main`
|
|
82
|
+
* rather than in it.
|
|
83
|
+
*
|
|
84
|
+
* ## Two more things it does, both one line
|
|
85
|
+
*
|
|
86
|
+
* `tabIndex={-1}` so `Shell`'s skip link moves **focus** and not
|
|
87
|
+
* merely the reading cursor — without it, Safari and Firefox scroll
|
|
88
|
+
* to `<main>` and leave the next Tab back in the header.
|
|
89
|
+
*
|
|
90
|
+
* `wrap-anywhere` so an unbroken string — a filesystem path, a
|
|
91
|
+
* hash, a URL — wraps instead of widening the page.
|
|
92
|
+
*
|
|
93
|
+
* **`anywhere`, not `break-word`, and the difference is the whole
|
|
94
|
+
* point.** Both break a long word onto the next line, so they look
|
|
95
|
+
* identical in a screenshot. Only `anywhere` also shrinks the
|
|
96
|
+
* element's **min-content size** — which is what a flex or grid
|
|
97
|
+
* item's automatic minimum resolves against, and therefore what
|
|
98
|
+
* decides whether the string can push its own column wider. With
|
|
99
|
+
* `break-word` the ink wraps while the intrinsic contribution
|
|
100
|
+
* stays the full length of the token, so the layout above it can
|
|
101
|
+
* still be forced open. This is the second of the two overflow
|
|
102
|
+
* sources found in plex-channels, and the nastier one: it produces
|
|
103
|
+
* **no overflowing element box at all**, so every
|
|
104
|
+
* `getBoundingClientRect()` in a test reads clean while the page
|
|
105
|
+
* scrolls sideways.
|
|
106
|
+
*
|
|
107
|
+
* It is the cheap half of the horizontal-scroll fix; the other
|
|
108
|
+
* halves are `minmax(0, 1fr)` and `overflow-x: clip` in `Shell`. A
|
|
109
|
+
* `<table>` is the one thing none of them can save: tables do not
|
|
110
|
+
* wrap, so a wide one goes in an `overflow-x-auto` wrapper, which
|
|
111
|
+
* `Main.mdx` shows.
|
|
112
|
+
*/
|
|
113
|
+
export const Main = ({ children, className, contentWidth, id, ...mainProps }) => {
|
|
114
|
+
const shell = useContext(ShellContext);
|
|
115
|
+
// Always called, never conditionally: a `Main` outside a `Shell`
|
|
116
|
+
// still needs an id an app can point its own skip link at.
|
|
117
|
+
const fallbackId = useUniqueId();
|
|
118
|
+
const maxInlineSize = toMaxInlineSize(contentWidth ??
|
|
119
|
+
shell?.contentWidth ??
|
|
120
|
+
DEFAULT_CONTENT_WIDTH);
|
|
121
|
+
return (_jsx("main", { ...mainProps, className: toClassName("col-start-1 row-start-3 min-w-0 md:col-start-2 md:row-start-2", className), id: id ?? shell?.mainId ?? fallbackId, tabIndex: -1, children: _jsx("div", { className: "@container mx-auto flex w-full min-w-0 flex-col gap-6 wrap-anywhere px-4 py-6 sm:px-6", style: { maxInlineSize }, children: children }) }));
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=Main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Main.js","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAGL,UAAU,GACX,MAAM,OAAO,CAAA;AAEd,OAAO,EAEL,qBAAqB,EACrB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAM/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,EAAE,EACF,GAAG,SAAS,EACF,EAAa,EAAE;IACzB,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IAEtC,iEAAiE;IACjE,2DAA2D;IAC3D,MAAM,UAAU,GAAG,WAAW,EAAE,CAAA;IAEhC,MAAM,aAAa,GAAG,eAAe,CACnC,YAAY;QACV,KAAK,EAAE,YAAY;QACnB,qBAAqB,CACxB,CAAA;IAED,OAAO,CACL,kBACM,SAAS,EACb,SAAS,EAAE,WAAW,CACpB,+DAA+D,EAC/D,SAAS,CACV,EACD,EAAE,EAAE,EAAE,IAAI,KAAK,EAAE,MAAM,IAAI,UAAU,EACrC,QAAQ,EAAE,CAAC,CAAC,YAaZ,cACE,SAAS,EAAC,uFAAuF,EACjG,KAAK,EAAE,EAAE,aAAa,EAAE,YAEvB,QAAQ,GACL,GACD,CACR,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
export type RailLandmark = "complementary" | "navigation";
|
|
3
|
+
export type RailSide = "end" | "start";
|
|
4
|
+
export type RailProps = ComponentPropsWithRef<"aside"> & {
|
|
5
|
+
label: string;
|
|
6
|
+
landmark?: RailLandmark;
|
|
7
|
+
side?: RailSide;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A side rail — the nav column, the filter panel, the drive list.
|
|
11
|
+
*
|
|
12
|
+
* `side` places it, `landmark` decides what it *is*, and `label`
|
|
13
|
+
* is required for a reason: two unnamed rails in one page are two
|
|
14
|
+
* landmarks of the same type with no way to tell them apart, which
|
|
15
|
+
* is axe's `landmark-unique` and, more to the point, is a screen
|
|
16
|
+
* reader announcing "complementary" twice. It is also what makes
|
|
17
|
+
* the rail scopeable — `getByRole("navigation", { name: "Sections" })`
|
|
18
|
+
* disambiguates a "Settings" link that also exists in the header.
|
|
19
|
+
*
|
|
20
|
+
* ## It collapses without duplicating itself
|
|
21
|
+
*
|
|
22
|
+
* Below `md` the shell is one column and the rail becomes a
|
|
23
|
+
* horizontally-scrolling strip above (`side="start"`) or below
|
|
24
|
+
* (`side="end"`) the content. Above it, the rail is a fixed-width
|
|
25
|
+
* column in the grid.
|
|
26
|
+
*
|
|
27
|
+
* **The same element, restyled — not a second copy.** The fleet's
|
|
28
|
+
* habit is to render the whole thing twice behind `hidden` and
|
|
29
|
+
* `lg:hidden`: mux-magic's `PageHeader` does it across two ~55-line
|
|
30
|
+
* blocks and mail-sifter's `TriageQueue` does it again. The cost is
|
|
31
|
+
* not bytes, it is that every control exists **twice in the DOM at
|
|
32
|
+
* every viewport** — so tests must be defensively scoped, an agent
|
|
33
|
+
* driving the page finds two of each button and cannot tell which
|
|
34
|
+
* is the visible one, and the two copies drift.
|
|
35
|
+
*
|
|
36
|
+
* A media query gets the whole job done here because only the
|
|
37
|
+
* rail's *position* changes, never its contents. plex-channels'
|
|
38
|
+
* `App.tsx` shows the pattern for when that is not true — a
|
|
39
|
+
* `useMediaQuery` over `matchMedia` through `useSyncExternalStore`,
|
|
40
|
+
* mounting exactly one instance and relocating it — and it is
|
|
41
|
+
* strictly more machinery for the same guarantee, so it is not what
|
|
42
|
+
* this component uses.
|
|
43
|
+
*
|
|
44
|
+
* The `md` breakpoint is 48rem, which is `screen.md`: Tailwind v4's
|
|
45
|
+
* default `--breakpoint-*` scale and this repo's `screen.*` scale
|
|
46
|
+
* carry the same five values, so `md:` and the token agree by
|
|
47
|
+
* construction rather than by coincidence maintained by hand.
|
|
48
|
+
*
|
|
49
|
+
* ## The narrow strip scrolls itself
|
|
50
|
+
*
|
|
51
|
+
* `overflow-x-auto` on the rail is what keeps a twelve-item nav
|
|
52
|
+
* from widening the *page* at 390px: the overflow is contained in
|
|
53
|
+
* an element that scrolls, which is the sanctioned answer for
|
|
54
|
+
* content that genuinely cannot wrap. `min-w-0` is on it for the
|
|
55
|
+
* same reason `Shell`'s middle track is `minmax(0, 1fr)` — a flex
|
|
56
|
+
* or grid child's automatic minimum is its content, and one long
|
|
57
|
+
* unbroken string otherwise pushes the whole layout out of the
|
|
58
|
+
* viewport.
|
|
59
|
+
*/
|
|
60
|
+
export declare const Rail: ({ children, className, label, landmark, side, ...railProps }: RailProps) => ReactNode;
|
|
61
|
+
//# sourceMappingURL=Rail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Rail.d.ts","sourceRoot":"","sources":["../../src/Rail/Rail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAId,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,YAAY,CAAA;AAEzD,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAA;AAEtC,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,OAAO,CAAC,GAAG;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,IAAI,CAAC,EAAE,QAAQ,CAAA;CAChB,CAAA;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,eAAO,MAAM,IAAI,GAAI,8DAOlB,SAAS,KAAG,SAkBd,CAAA"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { toClassName } from "../toClassName.js";
|
|
3
|
+
const LANDMARK_ELEMENT = {
|
|
4
|
+
complementary: "aside",
|
|
5
|
+
navigation: "nav",
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* A side rail — the nav column, the filter panel, the drive list.
|
|
9
|
+
*
|
|
10
|
+
* `side` places it, `landmark` decides what it *is*, and `label`
|
|
11
|
+
* is required for a reason: two unnamed rails in one page are two
|
|
12
|
+
* landmarks of the same type with no way to tell them apart, which
|
|
13
|
+
* is axe's `landmark-unique` and, more to the point, is a screen
|
|
14
|
+
* reader announcing "complementary" twice. It is also what makes
|
|
15
|
+
* the rail scopeable — `getByRole("navigation", { name: "Sections" })`
|
|
16
|
+
* disambiguates a "Settings" link that also exists in the header.
|
|
17
|
+
*
|
|
18
|
+
* ## It collapses without duplicating itself
|
|
19
|
+
*
|
|
20
|
+
* Below `md` the shell is one column and the rail becomes a
|
|
21
|
+
* horizontally-scrolling strip above (`side="start"`) or below
|
|
22
|
+
* (`side="end"`) the content. Above it, the rail is a fixed-width
|
|
23
|
+
* column in the grid.
|
|
24
|
+
*
|
|
25
|
+
* **The same element, restyled — not a second copy.** The fleet's
|
|
26
|
+
* habit is to render the whole thing twice behind `hidden` and
|
|
27
|
+
* `lg:hidden`: mux-magic's `PageHeader` does it across two ~55-line
|
|
28
|
+
* blocks and mail-sifter's `TriageQueue` does it again. The cost is
|
|
29
|
+
* not bytes, it is that every control exists **twice in the DOM at
|
|
30
|
+
* every viewport** — so tests must be defensively scoped, an agent
|
|
31
|
+
* driving the page finds two of each button and cannot tell which
|
|
32
|
+
* is the visible one, and the two copies drift.
|
|
33
|
+
*
|
|
34
|
+
* A media query gets the whole job done here because only the
|
|
35
|
+
* rail's *position* changes, never its contents. plex-channels'
|
|
36
|
+
* `App.tsx` shows the pattern for when that is not true — a
|
|
37
|
+
* `useMediaQuery` over `matchMedia` through `useSyncExternalStore`,
|
|
38
|
+
* mounting exactly one instance and relocating it — and it is
|
|
39
|
+
* strictly more machinery for the same guarantee, so it is not what
|
|
40
|
+
* this component uses.
|
|
41
|
+
*
|
|
42
|
+
* The `md` breakpoint is 48rem, which is `screen.md`: Tailwind v4's
|
|
43
|
+
* default `--breakpoint-*` scale and this repo's `screen.*` scale
|
|
44
|
+
* carry the same five values, so `md:` and the token agree by
|
|
45
|
+
* construction rather than by coincidence maintained by hand.
|
|
46
|
+
*
|
|
47
|
+
* ## The narrow strip scrolls itself
|
|
48
|
+
*
|
|
49
|
+
* `overflow-x-auto` on the rail is what keeps a twelve-item nav
|
|
50
|
+
* from widening the *page* at 390px: the overflow is contained in
|
|
51
|
+
* an element that scrolls, which is the sanctioned answer for
|
|
52
|
+
* content that genuinely cannot wrap. `min-w-0` is on it for the
|
|
53
|
+
* same reason `Shell`'s middle track is `minmax(0, 1fr)` — a flex
|
|
54
|
+
* or grid child's automatic minimum is its content, and one long
|
|
55
|
+
* unbroken string otherwise pushes the whole layout out of the
|
|
56
|
+
* viewport.
|
|
57
|
+
*/
|
|
58
|
+
export const Rail = ({ children, className, label, landmark = "complementary", side = "start", ...railProps }) => {
|
|
59
|
+
const Element = LANDMARK_ELEMENT[landmark];
|
|
60
|
+
return (_jsx(Element, { ...railProps, "aria-label": label, className: toClassName("flex min-w-0 gap-2 overflow-x-auto border-border-subtle p-4 md:row-start-2 md:w-64 md:flex-col md:overflow-x-visible", side === "start"
|
|
61
|
+
? "row-start-2 border-b md:col-start-1 md:border-b-0 md:border-e"
|
|
62
|
+
: "row-start-4 border-t md:col-start-3 md:border-t-0 md:border-s", className), children: children }));
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=Rail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Rail.js","sourceRoot":"","sources":["../../src/Rail/Rail.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAY/C,MAAM,gBAAgB,GAGlB;IACF,aAAa,EAAE,OAAO;IACtB,UAAU,EAAE,KAAK;CAClB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,KAAK,EACL,QAAQ,GAAG,eAAe,EAC1B,IAAI,GAAG,OAAO,EACd,GAAG,SAAS,EACF,EAAa,EAAE;IACzB,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAE1C,OAAO,CACL,KAAC,OAAO,OACF,SAAS,gBACD,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,sHAAsH,EACtH,IAAI,KAAK,OAAO;YACd,CAAC,CAAC,+DAA+D;YACjE,CAAC,CAAC,+DAA+D,EACnE,SAAS,CACV,YAEA,QAAQ,GACD,CACX,CAAA;AACH,CAAC,CAAA"}
|