shelving 1.264.1 → 1.264.2
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/package.json +1 -1
- package/ui/README.md +2 -2
- package/ui/block/Blockquote.module.css +0 -3
- package/ui/block/Card.md +3 -7
- package/ui/block/Card.module.css +0 -3
- package/ui/block/Heading.md +0 -1
- package/ui/block/Heading.module.css +0 -3
- package/ui/block/Label.module.css +0 -3
- package/ui/block/List.md +1 -2
- package/ui/block/List.module.css +0 -3
- package/ui/block/Panel.md +1 -2
- package/ui/block/Panel.module.css +0 -3
- package/ui/block/Paragraph.md +1 -2
- package/ui/block/Paragraph.module.css +0 -3
- package/ui/block/Preformatted.module.css +0 -3
- package/ui/block/Subheading.md +0 -1
- package/ui/block/Subheading.module.css +0 -3
- package/ui/block/Title.md +0 -1
- package/ui/block/Title.module.css +0 -3
- package/ui/button/Button.md +1 -2
- package/ui/button/Button.module.css +0 -3
- package/ui/docs/DocumentationPage.test.tsx +2 -4
- package/ui/docs/DocumentationParams.js +1 -1
- package/ui/docs/DocumentationParams.tsx +1 -1
- package/ui/docs/DocumentationProperties.js +1 -1
- package/ui/docs/DocumentationProperties.tsx +1 -1
- package/ui/docs/DocumentationReferences.js +1 -1
- package/ui/docs/DocumentationReferences.tsx +1 -1
- package/ui/docs/DocumentationReturns.js +1 -1
- package/ui/docs/DocumentationReturns.tsx +1 -1
- package/ui/docs/DocumentationThrows.js +1 -1
- package/ui/docs/DocumentationThrows.tsx +1 -1
- package/ui/form/Field.test.tsx +1 -1
- package/ui/inline/Deleted.md +4 -5
- package/ui/inline/Deleted.module.css +1 -3
- package/ui/inline/Inserted.md +4 -5
- package/ui/inline/Inserted.module.css +1 -3
- package/ui/inline/Link.md +2 -3
- package/ui/inline/Link.module.css +1 -3
- package/ui/inline/Mark.js +1 -2
- package/ui/inline/Mark.md +5 -5
- package/ui/inline/Mark.module.css +2 -3
- package/ui/inline/Mark.tsx +0 -2
- package/ui/input/SchemaInput.test.tsx +3 -2
- package/ui/layout/CenteredLayout.d.ts +5 -7
- package/ui/layout/CenteredLayout.js +8 -4
- package/ui/layout/CenteredLayout.md +9 -2
- package/ui/layout/CenteredLayout.module.css +16 -22
- package/ui/layout/CenteredLayout.tsx +18 -12
- package/ui/layout/SidebarLayout.js +12 -5
- package/ui/layout/SidebarLayout.md +4 -4
- package/ui/layout/SidebarLayout.module.css +6 -9
- package/ui/layout/SidebarLayout.tsx +20 -17
- package/ui/misc/MetaContext.test.tsx +2 -3
- package/ui/misc/Tag.md +1 -2
- package/ui/misc/Tag.module.css +0 -3
- package/ui/notice/Message.module.css +0 -3
- package/ui/notice/Notice.md +1 -2
- package/ui/notice/Notice.module.css +0 -3
- package/ui/router/Router.test.tsx +1 -4
- package/ui/style/TINT_CLASS.md +1 -1
- package/ui/style/Width.d.ts +2 -1
- package/ui/style/Width.module.css +47 -17
- package/ui/style/Width.tsx +22 -1
- package/ui/style/getWidthClass.md +5 -9
- package/ui/table/Table.md +1 -1
- package/ui/tree/TreeButton.test.tsx +2 -5
- package/ui/tree/TreeContext.test.tsx +2 -3
- package/ui/tree/TreeMarkup.test.tsx +2 -5
- package/ui/tree/TreeRouter.test.tsx +2 -5
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
|
+
import type { IndentVariants } from "../style/Indent.js";
|
|
3
|
+
import type { PaddingVariants } from "../style/Padding.js";
|
|
4
|
+
import type { WidthVariants } from "../style/Width.js";
|
|
2
5
|
import type { OptionalChildProps } from "../util/props.js";
|
|
3
6
|
/**
|
|
4
7
|
* Props for `<CenteredLayout>` — optional `children` and a `fullWidth` flag to drop the max-width.
|
|
5
8
|
*
|
|
6
9
|
* @see https://shelving.cc/ui/CenteredLayoutProps
|
|
7
10
|
*/
|
|
8
|
-
export interface CenteredLayoutProps extends OptionalChildProps {
|
|
9
|
-
/**
|
|
10
|
-
* Drop the narrow max-width and let content fill the width.
|
|
11
|
-
* @default false
|
|
12
|
-
*/
|
|
13
|
-
fullWidth?: boolean;
|
|
11
|
+
export interface CenteredLayoutProps extends WidthVariants, PaddingVariants, IndentVariants, OptionalChildProps {
|
|
14
12
|
}
|
|
15
13
|
/**
|
|
16
14
|
* Layout that centres its content with no header/footer and a narrow max-width.
|
|
@@ -19,4 +17,4 @@ export interface CenteredLayoutProps extends OptionalChildProps {
|
|
|
19
17
|
* @kind component
|
|
20
18
|
* @see https://shelving.cc/ui/CenteredLayout
|
|
21
19
|
*/
|
|
22
|
-
export declare function CenteredLayout({ children,
|
|
20
|
+
export declare function CenteredLayout({ children, ...props }: CenteredLayoutProps): ReactElement;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { RouteCache } from "../router/RouteCache.js";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { getBlockClass } from "../style/Block.js";
|
|
4
|
+
import { getClass, getModuleClass } from "../util/css.js";
|
|
5
|
+
import LAYOUT_CSS from "./CenteredLayout.module.css";
|
|
6
|
+
const LAYOUT_MAIN_CLASS = getModuleClass(LAYOUT_CSS, "main");
|
|
7
|
+
const LAYOUT_INNER_CLASS = getModuleClass(LAYOUT_CSS, "inner");
|
|
5
8
|
/**
|
|
6
9
|
* Layout that centres its content with no header/footer and a narrow max-width.
|
|
7
10
|
* - Used for e.g. login/register/error/form pages where the content is the only focus.
|
|
@@ -9,8 +12,9 @@ import CENTERED_LAYOUT_CSS from "./CenteredLayout.module.css";
|
|
|
9
12
|
* @kind component
|
|
10
13
|
* @see https://shelving.cc/ui/CenteredLayout
|
|
11
14
|
*/
|
|
12
|
-
export function CenteredLayout({ children,
|
|
15
|
+
export function CenteredLayout({ children, ...props }) {
|
|
13
16
|
// Wrap the scrolling `<main>` in `<RouteCache>` so recently-visited pages stay mounted but hidden,
|
|
14
17
|
// keeping their scroll position and state intact across back/forward navigation.
|
|
15
|
-
return (_jsx(RouteCache, { children: _jsx("main", { className:
|
|
18
|
+
return (_jsx(RouteCache, { children: _jsx("main", { className: LAYOUT_MAIN_CLASS, children: _jsx("div", { className: getClass(LAYOUT_INNER_CLASS, //
|
|
19
|
+
getBlockClass(props)), children: children }) }) }));
|
|
16
20
|
}
|
|
@@ -27,6 +27,13 @@ Layouts compose naturally as `<Router>` route values — wrap a group of routes
|
|
|
27
27
|
|
|
28
28
|
## Styling
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
| Variable | Styles | Default |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `--centered-layout-width` | Max width of the centred column | `var(--width-narrow)` |
|
|
33
|
+
| `--centered-layout-space` | Top/bottom padding of the scroll area | `var(--space-normal)` |
|
|
34
|
+
| `--centered-layout-padding` | Left/right padding of the scroll area | `var(--space-normal)` |
|
|
35
|
+
| `--centered-layout-background` | Page background while the layout is mounted | Unset — the `body` default from `Typography.module.css` shows |
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
The max-width cap is dropped entirely when `fullWidth` is set. The outer element owns its scroll, padding, and safe-area behaviour directly — it also reads the `--layout-inset-top` / `-bottom` / `-left` / `-right` hooks owned by `Layout.ts` (`useSafeKeyboardArea()` writes `--layout-inset-bottom`).
|
|
38
|
+
|
|
39
|
+
**Global tokens it reads** — `--width-narrow` and `--space-normal`.
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
@import "../style/layers.css";
|
|
2
2
|
@import "../style/Space.module.css";
|
|
3
|
-
@import "../style/Tint.module.css";
|
|
4
3
|
@import "../style/Width.module.css";
|
|
5
4
|
|
|
6
|
-
/* Low-priority padding defaults live in `@layer defaults` so consumers can override them */
|
|
7
|
-
/* with normal-specificity selectors without needing `:where()` tricks. */
|
|
8
|
-
@layer defaults {
|
|
9
|
-
.main {
|
|
10
|
-
/* Top/bottom use `layout-space` and left/right use `layout-padding`, plus safe-area additions. */
|
|
11
|
-
padding: var(--layout-space, var(--space-normal)) var(--layout-padding, var(--space-normal));
|
|
12
|
-
padding-top: calc(var(--layout-space, var(--space-normal)) + max(var(--layout-inset-top, 0px), env(safe-area-inset-top, 0px)));
|
|
13
|
-
padding-bottom: calc(var(--layout-space, var(--space-normal)) + max(var(--layout-inset-bottom, 0px), env(safe-area-inset-bottom, 0px)));
|
|
14
|
-
padding-left: calc(var(--layout-padding, var(--space-normal)) + max(var(--layout-inset-left, 0px), env(safe-area-inset-left, 0px)));
|
|
15
|
-
padding-right: calc(var(--layout-padding, var(--space-normal)) + max(var(--layout-inset-right, 0px), env(safe-area-inset-right, 0px)));
|
|
16
|
-
|
|
17
|
-
/* Grow to fill the body. */
|
|
18
|
-
flex: 1;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
5
|
@layer components {
|
|
23
6
|
/*
|
|
24
7
|
* Lock the document and let `.main` own the page scroll.
|
|
@@ -51,11 +34,22 @@
|
|
|
51
34
|
position: fixed;
|
|
52
35
|
inset: 0;
|
|
53
36
|
|
|
54
|
-
/*
|
|
55
|
-
background: var(--layout-
|
|
37
|
+
/* Deliberately no fallback: when the hook is unset this declaration fails and the body default from Typography.module.css shows. */
|
|
38
|
+
background: var(--centered-layout-background);
|
|
56
39
|
}
|
|
57
40
|
|
|
58
41
|
.main {
|
|
42
|
+
/* Box */
|
|
43
|
+
border-top: env(safe-area-inset-top, 0px) solid transparent;
|
|
44
|
+
border-bottom: env(safe-area-inset-bottom, 0px) solid transparent;
|
|
45
|
+
border-left: env(safe-area-inset-left, 0px) solid transparent;
|
|
46
|
+
border-right: env(safe-area-inset-right, 0px) solid transparent;
|
|
47
|
+
padding-block: var(--centered-layout-padding, var(--space-normal));
|
|
48
|
+
padding-inline: var(--centered-layout-indent, var(--space-normal));
|
|
49
|
+
|
|
50
|
+
/* Grow to fill the body. */
|
|
51
|
+
flex: 1;
|
|
52
|
+
|
|
59
53
|
/* Content — a flex column whose inner column centres itself with `margin: auto` (both axes). */
|
|
60
54
|
display: flex;
|
|
61
55
|
flex-direction: column;
|
|
@@ -70,9 +64,9 @@
|
|
|
70
64
|
scroll-behavior: smooth;
|
|
71
65
|
}
|
|
72
66
|
|
|
73
|
-
.
|
|
74
|
-
width:
|
|
75
|
-
max-width:
|
|
67
|
+
.inner {
|
|
68
|
+
width: var(--centered-layout-width, var(--width-narrow));
|
|
69
|
+
max-width: 100%;
|
|
76
70
|
margin: auto;
|
|
77
71
|
}
|
|
78
72
|
}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import { RouteCache } from "../router/RouteCache.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getBlockClass } from "../style/Block.js";
|
|
4
|
+
import type { IndentVariants } from "../style/Indent.js";
|
|
5
|
+
import type { PaddingVariants } from "../style/Padding.js";
|
|
6
|
+
import type { WidthVariants } from "../style/Width.js";
|
|
7
|
+
import { getClass, getModuleClass } from "../util/css.js";
|
|
4
8
|
import type { OptionalChildProps } from "../util/props.js";
|
|
5
|
-
import
|
|
9
|
+
import LAYOUT_CSS from "./CenteredLayout.module.css";
|
|
10
|
+
|
|
11
|
+
const LAYOUT_MAIN_CLASS = getModuleClass(LAYOUT_CSS, "main");
|
|
12
|
+
const LAYOUT_INNER_CLASS = getModuleClass(LAYOUT_CSS, "inner");
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* Props for `<CenteredLayout>` — optional `children` and a `fullWidth` flag to drop the max-width.
|
|
9
16
|
*
|
|
10
17
|
* @see https://shelving.cc/ui/CenteredLayoutProps
|
|
11
18
|
*/
|
|
12
|
-
export interface CenteredLayoutProps extends OptionalChildProps {
|
|
13
|
-
/**
|
|
14
|
-
* Drop the narrow max-width and let content fill the width.
|
|
15
|
-
* @default false
|
|
16
|
-
*/
|
|
17
|
-
fullWidth?: boolean;
|
|
18
|
-
}
|
|
19
|
+
export interface CenteredLayoutProps extends WidthVariants, PaddingVariants, IndentVariants, OptionalChildProps {}
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Layout that centres its content with no header/footer and a narrow max-width.
|
|
@@ -24,13 +25,18 @@ export interface CenteredLayoutProps extends OptionalChildProps {
|
|
|
24
25
|
* @kind component
|
|
25
26
|
* @see https://shelving.cc/ui/CenteredLayout
|
|
26
27
|
*/
|
|
27
|
-
export function CenteredLayout({ children,
|
|
28
|
+
export function CenteredLayout({ children, ...props }: CenteredLayoutProps): ReactElement {
|
|
28
29
|
// Wrap the scrolling `<main>` in `<RouteCache>` so recently-visited pages stay mounted but hidden,
|
|
29
30
|
// keeping their scroll position and state intact across back/forward navigation.
|
|
30
31
|
return (
|
|
31
32
|
<RouteCache>
|
|
32
|
-
<main className={
|
|
33
|
-
<div
|
|
33
|
+
<main className={LAYOUT_MAIN_CLASS}>
|
|
34
|
+
<div
|
|
35
|
+
className={getClass(
|
|
36
|
+
LAYOUT_INNER_CLASS, //
|
|
37
|
+
getBlockClass(props),
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
34
40
|
{children}
|
|
35
41
|
</div>
|
|
36
42
|
</main>
|
|
@@ -5,7 +5,13 @@ import { Button } from "../button/Button.js";
|
|
|
5
5
|
import { requireMetaURL } from "../misc/MetaContext.js";
|
|
6
6
|
import { RouteCache } from "../router/RouteCache.js";
|
|
7
7
|
import { getClass, getModuleClass } from "../util/css.js";
|
|
8
|
-
import
|
|
8
|
+
import LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
9
|
+
const LAYOUT_SIDEBAR_CLASS = getModuleClass(LAYOUT_CSS, "sidebar");
|
|
10
|
+
const LAYOUT_CONTENT_CLASS = getModuleClass(LAYOUT_CSS, "content");
|
|
11
|
+
const LAYOUT_TOGGLE_CLASS = getModuleClass(LAYOUT_CSS, "toggle");
|
|
12
|
+
const LAYOUT_OVERLAY_CLASS = getModuleClass(LAYOUT_CSS, "overlay");
|
|
13
|
+
const LAYOUT_MAIN_CLASS = getModuleClass(LAYOUT_CSS, "main");
|
|
14
|
+
const LAYOUT_OPEN_CLASS = getModuleClass(LAYOUT_CSS, "open");
|
|
9
15
|
/**
|
|
10
16
|
* Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.
|
|
11
17
|
* - The sidebar is rendered as `<nav>` — it almost always contains the page's primary navigation.
|
|
@@ -25,12 +31,13 @@ export function SidebarLayout({ sidebar, children, right = false }) {
|
|
|
25
31
|
if (path)
|
|
26
32
|
setOpen(false);
|
|
27
33
|
}, [path]);
|
|
28
|
-
const sidebarEl = (_jsx("nav", { className: getClass(
|
|
34
|
+
const sidebarEl = (_jsx("nav", { className: getClass(LAYOUT_SIDEBAR_CLASS, //
|
|
35
|
+
open && LAYOUT_OPEN_CLASS), children: sidebar }, "sidebar"));
|
|
29
36
|
// Wrap the scrolling content column in `<RouteCache>` so recently-visited pages stay mounted but hidden
|
|
30
37
|
// — keeping the scroll position of this `.content` container (and all page state) intact across
|
|
31
38
|
// back/forward navigation. The sidebar and drawer state stay outside the cache, so they are neither
|
|
32
39
|
// duplicated nor remounted as the URL changes.
|
|
33
|
-
const contentEl = (_jsx(RouteCache, { children: _jsxs("div", { className:
|
|
34
|
-
const overlayEl = open && (_jsx("button", { type: "button", className:
|
|
35
|
-
return
|
|
40
|
+
const contentEl = (_jsx(RouteCache, { children: _jsxs("div", { className: LAYOUT_CONTENT_CLASS, children: [_jsx("div", { className: LAYOUT_TOGGLE_CLASS, children: _jsx(Button, { title: open ? "Close menu" : "Show menu", onClick: () => setOpen(o => !o), children: open ? _jsx(XMarkIcon, {}) : _jsx(Bars3Icon, {}) }) }), children] }) }, "content"));
|
|
41
|
+
const overlayEl = open && (_jsx("button", { type: "button", className: LAYOUT_OVERLAY_CLASS, "aria-label": "Close menu", onClick: () => setOpen(false) }, "overlay"));
|
|
42
|
+
return _jsx("main", { className: LAYOUT_MAIN_CLASS, children: right ? [contentEl, sidebarEl, overlayEl] : [sidebarEl, contentEl, overlayEl] });
|
|
36
43
|
}
|
|
@@ -48,11 +48,11 @@ useEffect(useSafeKeyboardArea, []);
|
|
|
48
48
|
| Variable | Styles | Default |
|
|
49
49
|
|---|---|---|
|
|
50
50
|
| `--sidebar-layout-width` | Width of the side column (and drawer) | `17.5rem` |
|
|
51
|
-
| `--sidebar-layout-background` | Page background while the layout is mounted | `
|
|
52
|
-
| `--sidebar-layout-sidebar-background` | Sidebar column fill |
|
|
53
|
-
| `--sidebar-layout-content-background` | Main content column fill |
|
|
51
|
+
| `--sidebar-layout-background` | Page background while the layout is mounted | Unset — the `body` default from `Typography.module.css` shows |
|
|
52
|
+
| `--sidebar-layout-sidebar-background` | Sidebar column fill | Unset — the page background shows |
|
|
53
|
+
| `--sidebar-layout-content-background` | Main content column fill | Unset — the page background shows |
|
|
54
54
|
| `--sidebar-layout-border` | Divider between sidebar and content | `var(--stroke-normal) solid var(--tint-80)` |
|
|
55
55
|
|
|
56
56
|
The sidebar and content columns own their own scroll behaviour directly (this layout no longer composes a shared `.layout` class). `useSafeKeyboardArea()` still writes `--layout-inset-bottom` for layouts that pad to the safe area.
|
|
57
57
|
|
|
58
|
-
**Global tokens it reads** —
|
|
58
|
+
**Global tokens it reads** — `--tint-80`, plus `--space-normal`, `--stroke-normal`, `--duration-normal`, and `--color-shadow`.
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
inset: 0;
|
|
51
51
|
|
|
52
52
|
/* Style */
|
|
53
|
-
|
|
53
|
+
/* Deliberately no fallback: when the hook is unset this declaration fails and the body default from Typography.module.css shows. */
|
|
54
|
+
background: var(--sidebar-layout-background);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
.main {
|
|
@@ -74,7 +75,8 @@
|
|
|
74
75
|
padding: var(--space-normal);
|
|
75
76
|
|
|
76
77
|
/* Style */
|
|
77
|
-
|
|
78
|
+
/* Deliberately no fallback: when the hook is unset this declaration fails and the page background shows. */
|
|
79
|
+
background: var(--sidebar-layout-sidebar-background);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
.content {
|
|
@@ -91,13 +93,8 @@
|
|
|
91
93
|
scroll-behavior: smooth;
|
|
92
94
|
|
|
93
95
|
/* Style */
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.contentInner {
|
|
98
|
-
width: 100%;
|
|
99
|
-
padding: 0;
|
|
100
|
-
margin: 0 auto;
|
|
96
|
+
/* Deliberately no fallback: when the hook is unset this declaration fails and the page background shows. */
|
|
97
|
+
background: var(--sidebar-layout-content-background);
|
|
101
98
|
}
|
|
102
99
|
|
|
103
100
|
/* Wrapper for the menu toggle button — hidden on wide viewports, shown on narrow ones (see media query). */
|
|
@@ -5,7 +5,14 @@ import { requireMetaURL } from "../misc/MetaContext.js";
|
|
|
5
5
|
import { RouteCache } from "../router/RouteCache.js";
|
|
6
6
|
import { getClass, getModuleClass } from "../util/css.js";
|
|
7
7
|
import type { OptionalChildProps } from "../util/props.js";
|
|
8
|
-
import
|
|
8
|
+
import LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
9
|
+
|
|
10
|
+
const LAYOUT_SIDEBAR_CLASS = getModuleClass(LAYOUT_CSS, "sidebar");
|
|
11
|
+
const LAYOUT_CONTENT_CLASS = getModuleClass(LAYOUT_CSS, "content");
|
|
12
|
+
const LAYOUT_TOGGLE_CLASS = getModuleClass(LAYOUT_CSS, "toggle");
|
|
13
|
+
const LAYOUT_OVERLAY_CLASS = getModuleClass(LAYOUT_CSS, "overlay");
|
|
14
|
+
const LAYOUT_MAIN_CLASS = getModuleClass(LAYOUT_CSS, "main");
|
|
15
|
+
const LAYOUT_OPEN_CLASS = getModuleClass(LAYOUT_CSS, "open");
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* Props for `<SidebarLayout>` — the `sidebar` column content, main `children`, and a `right` placement flag.
|
|
@@ -42,39 +49,35 @@ export function SidebarLayout({ sidebar, children, right = false }: SidebarLayou
|
|
|
42
49
|
const sidebarEl = (
|
|
43
50
|
<nav
|
|
44
51
|
key="sidebar"
|
|
45
|
-
className={getClass(
|
|
52
|
+
className={getClass(
|
|
53
|
+
LAYOUT_SIDEBAR_CLASS, //
|
|
54
|
+
open && LAYOUT_OPEN_CLASS,
|
|
55
|
+
)}
|
|
46
56
|
>
|
|
47
57
|
{sidebar}
|
|
48
58
|
</nav>
|
|
49
59
|
);
|
|
60
|
+
|
|
50
61
|
// Wrap the scrolling content column in `<RouteCache>` so recently-visited pages stay mounted but hidden
|
|
51
62
|
// — keeping the scroll position of this `.content` container (and all page state) intact across
|
|
52
63
|
// back/forward navigation. The sidebar and drawer state stay outside the cache, so they are neither
|
|
53
64
|
// duplicated nor remounted as the URL changes.
|
|
54
65
|
const contentEl = (
|
|
55
66
|
<RouteCache key="content">
|
|
56
|
-
<div className={
|
|
57
|
-
<div className={
|
|
67
|
+
<div className={LAYOUT_CONTENT_CLASS}>
|
|
68
|
+
<div className={LAYOUT_TOGGLE_CLASS}>
|
|
58
69
|
<Button title={open ? "Close menu" : "Show menu"} onClick={() => setOpen(o => !o)}>
|
|
59
70
|
{open ? <XMarkIcon /> : <Bars3Icon />}
|
|
60
71
|
</Button>
|
|
61
72
|
</div>
|
|
62
|
-
|
|
73
|
+
{children}
|
|
63
74
|
</div>
|
|
64
75
|
</RouteCache>
|
|
65
76
|
);
|
|
77
|
+
|
|
66
78
|
const overlayEl = open && (
|
|
67
|
-
<button
|
|
68
|
-
key="overlay"
|
|
69
|
-
type="button"
|
|
70
|
-
className={getModuleClass(SIDEBAR_LAYOUT_CSS, "overlay")}
|
|
71
|
-
aria-label="Close menu"
|
|
72
|
-
onClick={() => setOpen(false)}
|
|
73
|
-
/>
|
|
74
|
-
);
|
|
75
|
-
return (
|
|
76
|
-
<main className={getModuleClass(SIDEBAR_LAYOUT_CSS, "main")}>
|
|
77
|
-
{right ? [contentEl, sidebarEl, overlayEl] : [sidebarEl, contentEl, overlayEl]}
|
|
78
|
-
</main>
|
|
79
|
+
<button key="overlay" type="button" className={LAYOUT_OVERLAY_CLASS} aria-label="Close menu" onClick={() => setOpen(false)} />
|
|
79
80
|
);
|
|
81
|
+
|
|
82
|
+
return <main className={LAYOUT_MAIN_CLASS}>{right ? [contentEl, sidebarEl, overlayEl] : [sidebarEl, contentEl, overlayEl]}</main>;
|
|
80
83
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
-
import { RequiredError } from "
|
|
5
|
-
import { createMeta } from "
|
|
6
|
-
import { MetaContext, requireMetaURL } from "./MetaContext.js";
|
|
4
|
+
import { RequiredError } from "shelving/error";
|
|
5
|
+
import { createMeta, MetaContext, requireMetaURL } from "shelving/ui";
|
|
7
6
|
|
|
8
7
|
/** Render `requireMetaURL().path` from inside a component so its `use(MetaContext)` call is valid. */
|
|
9
8
|
function Probe(): ReactNode {
|
package/ui/misc/Tag.md
CHANGED
|
@@ -20,11 +20,10 @@ import { Tag } from "shelving/ui";
|
|
|
20
20
|
|
|
21
21
|
## Styling
|
|
22
22
|
|
|
23
|
-
`Tag` paints from the [tint ladder](/ui/TINT_CLASS); override these hooks at `:root` (or any ancestor scope) to retheme.
|
|
23
|
+
`Tag` paints from the [tint ladder](/ui/TINT_CLASS); override these hooks at `:root` (or any ancestor scope) to retheme. Apply `color=` / `status=` (on the tag or an ancestor scope) to recolour the whole tag at once.
|
|
24
24
|
|
|
25
25
|
| Variable | Styles | Default |
|
|
26
26
|
|---|---|---|
|
|
27
|
-
| `--tag-tint` | Tint anchor for the tag scope | `inherit` (flows from `color=` / `status=` / parent) |
|
|
28
27
|
| `--tag-background` | Surface fill | `var(--tint-50)` |
|
|
29
28
|
| `--tag-hover-background` | Surface fill when an interactive tag is hovered | `var(--tint-55)` |
|
|
30
29
|
| `--tag-color` | Text colour | `var(--tint-100)` |
|
package/ui/misc/Tag.module.css
CHANGED
package/ui/notice/Notice.md
CHANGED
|
@@ -28,11 +28,10 @@ import { LOADING_NOTICE } from "shelving/ui";
|
|
|
28
28
|
|
|
29
29
|
## Styling
|
|
30
30
|
|
|
31
|
-
`Notice` paints from the [tint ladder](/ui/TINT_CLASS); override these hooks at `:root` (or any ancestor scope) to retheme.
|
|
31
|
+
`Notice` paints from the [tint ladder](/ui/TINT_CLASS); override these hooks at `:root` (or any ancestor scope) to retheme. Its `status` (or a `color=` / `status=` on an ancestor scope) sets the tint that the surface, border, and text all derive from; reach for a per-property hook for a single change.
|
|
32
32
|
|
|
33
33
|
| Variable | Styles | Default |
|
|
34
34
|
|---|---|---|
|
|
35
|
-
| `--notice-tint` | Tint anchor for the notice scope | `inherit` (flows from `color=` / `status=` / parent) |
|
|
36
35
|
| `--notice-background` | Surface fill | `var(--tint-90)` |
|
|
37
36
|
| `--notice-color` | Text colour | `var(--tint-50)` |
|
|
38
37
|
| `--notice-border` | Border shorthand | `var(--notice-stroke) solid var(--tint-80)` |
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
-
import { MetaContext } from "
|
|
4
|
-
import { createMeta } from "../util/meta.js";
|
|
5
|
-
import { Router } from "./Router.js";
|
|
6
|
-
import type { RouteProps } from "./Routes.js";
|
|
3
|
+
import { createMeta, MetaContext, type RouteProps, Router } from "shelving/ui";
|
|
7
4
|
|
|
8
5
|
const ROUTES = {
|
|
9
6
|
"/": () => <main>Home</main>,
|
package/ui/style/TINT_CLASS.md
CHANGED
|
@@ -42,7 +42,7 @@ Pairings follow contrast: long text reads at `00`-on-`90` or `00`-on-`100`; shor
|
|
|
42
42
|
A theme is a CSS file of custom-property overrides at `:root`, imported after the base styles. Work from broadest to narrowest:
|
|
43
43
|
|
|
44
44
|
1. **Move a palette colour.** Overriding `--color-gray` moves the default anchor, retinting every neutral ladder in the app — the broadest possible change. Overriding `--color-red`, `--color-primary`, etc. re-aims every variant and status that maps to it.
|
|
45
|
-
2. **Retint
|
|
45
|
+
2. **Retint a region.** Apply `color=` / `status=` to a container — `<Card color="purple">`, or a `<Panel color="purple">` wrapping a whole section — and everything inside re-tints, with text, border, surface, and hover shades all derived for free. Components don't expose a per-component `--x-tint` hook; the variant moves the anchor, not a bespoke variable.
|
|
46
46
|
3. **Override one property.** Per-property hooks are the scalpel: `--button-radius: 999px`, `--card-border: none`, `--tag-case: none`.
|
|
47
47
|
|
|
48
48
|
**Don't override individual ladder steps (`--tint-90`, etc.) at `:root`.** The ladder is *recomputed* from the anchor inside every `TINT_CLASS` scope — which includes every component that accepts `color=` or `status=` — so a step override at `:root` only reaches untinted regions and produces inconsistent surfaces. Move the anchor (option 1 or 2) instead, and the steps follow.
|
package/ui/style/Width.d.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* - `narrow` / `normal` / `wide` — fixed widths from the `--width-*` tokens (capped at 100%).
|
|
4
4
|
* - `full` — take the full available width.
|
|
5
5
|
* - `fit` — shrink to fit the content's intrinsic width (`fit-content`).
|
|
6
|
+
* - `1x`–`32x` — fixed multiples of `--space-normal` (1x–8x at 1x intervals, then 10x/12x, then 16x–32x at 4x intervals).
|
|
6
7
|
*
|
|
7
8
|
* @see https://shelving.cc/ui/WidthVariant
|
|
8
9
|
*/
|
|
9
|
-
export type WidthVariant = "
|
|
10
|
+
export type WidthVariant = "narrow" | "normal" | "wide" | "full" | "fit" | "1x" | "2x" | "3x" | "4x" | "5x" | "6x" | "7x" | "8x" | "10x" | "12x" | "16x" | "20x" | "24x" | "28x" | "32x";
|
|
10
11
|
/**
|
|
11
12
|
* Variant props that set (or unconstrain) a component's width, e.g. `width="narrow"` or `width="12x"`.
|
|
12
13
|
*
|
|
@@ -4,14 +4,9 @@
|
|
|
4
4
|
@layer defaults {
|
|
5
5
|
:root {
|
|
6
6
|
/* Semantic widths */
|
|
7
|
-
--width-xxnarrow: 16rem;
|
|
8
|
-
--width-xnarrow: 24rem;
|
|
9
7
|
--width-narrow: 36rem;
|
|
10
8
|
--width-normal: 48rem;
|
|
11
9
|
--width-wide: 80rem;
|
|
12
|
-
--width-xwide: 96rem;
|
|
13
|
-
--width-xxwide: 112rem;
|
|
14
|
-
--width-xxxwide: 128rem;
|
|
15
10
|
}
|
|
16
11
|
|
|
17
12
|
/* Frame shared by every width value (weak default — a component's own margin can still beat the centering). */
|
|
@@ -28,12 +23,6 @@
|
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
/* Semantic widths. */
|
|
31
|
-
.width-xxnarrow {
|
|
32
|
-
--width: var(--width-xxnarrow);
|
|
33
|
-
}
|
|
34
|
-
.width-xnarrow {
|
|
35
|
-
--width: var(--width-xnarrow);
|
|
36
|
-
}
|
|
37
26
|
.width-narrow {
|
|
38
27
|
--width: var(--width-narrow);
|
|
39
28
|
}
|
|
@@ -43,12 +32,6 @@
|
|
|
43
32
|
.width-wide {
|
|
44
33
|
--width: var(--width-wide);
|
|
45
34
|
}
|
|
46
|
-
.width-xwide {
|
|
47
|
-
--width: var(--width-xwide);
|
|
48
|
-
}
|
|
49
|
-
.width-xxwide {
|
|
50
|
-
--width: var(--width-xxwide);
|
|
51
|
-
}
|
|
52
35
|
.width-full {
|
|
53
36
|
--width: 100%;
|
|
54
37
|
}
|
|
@@ -56,6 +39,53 @@
|
|
|
56
39
|
--width: fit-content;
|
|
57
40
|
}
|
|
58
41
|
|
|
42
|
+
/* Numeric widths — multiples of `--space-normal` (no `--width-*` tokens; calculated directly). */
|
|
43
|
+
.width-1x {
|
|
44
|
+
--width: calc(1 * var(--space-normal));
|
|
45
|
+
}
|
|
46
|
+
.width-2x {
|
|
47
|
+
--width: calc(2 * var(--space-normal));
|
|
48
|
+
}
|
|
49
|
+
.width-3x {
|
|
50
|
+
--width: calc(3 * var(--space-normal));
|
|
51
|
+
}
|
|
52
|
+
.width-4x {
|
|
53
|
+
--width: calc(4 * var(--space-normal));
|
|
54
|
+
}
|
|
55
|
+
.width-5x {
|
|
56
|
+
--width: calc(5 * var(--space-normal));
|
|
57
|
+
}
|
|
58
|
+
.width-6x {
|
|
59
|
+
--width: calc(6 * var(--space-normal));
|
|
60
|
+
}
|
|
61
|
+
.width-7x {
|
|
62
|
+
--width: calc(7 * var(--space-normal));
|
|
63
|
+
}
|
|
64
|
+
.width-8x {
|
|
65
|
+
--width: calc(8 * var(--space-normal));
|
|
66
|
+
}
|
|
67
|
+
.width-10x {
|
|
68
|
+
--width: calc(10 * var(--space-normal));
|
|
69
|
+
}
|
|
70
|
+
.width-12x {
|
|
71
|
+
--width: calc(12 * var(--space-normal));
|
|
72
|
+
}
|
|
73
|
+
.width-16x {
|
|
74
|
+
--width: calc(16 * var(--space-normal));
|
|
75
|
+
}
|
|
76
|
+
.width-20x {
|
|
77
|
+
--width: calc(20 * var(--space-normal));
|
|
78
|
+
}
|
|
79
|
+
.width-24x {
|
|
80
|
+
--width: calc(24 * var(--space-normal));
|
|
81
|
+
}
|
|
82
|
+
.width-28x {
|
|
83
|
+
--width: calc(28 * var(--space-normal));
|
|
84
|
+
}
|
|
85
|
+
.width-32x {
|
|
86
|
+
--width: calc(32 * var(--space-normal));
|
|
87
|
+
}
|
|
88
|
+
|
|
59
89
|
/* Grow modifier — turn the chosen width into a floor and expand to fill (flex items + table columns). */
|
|
60
90
|
.grow {
|
|
61
91
|
flex-grow: 1;
|
package/ui/style/Width.tsx
CHANGED
|
@@ -6,10 +6,31 @@ import WIDTH_CSS from "./Width.module.css";
|
|
|
6
6
|
* - `narrow` / `normal` / `wide` — fixed widths from the `--width-*` tokens (capped at 100%).
|
|
7
7
|
* - `full` — take the full available width.
|
|
8
8
|
* - `fit` — shrink to fit the content's intrinsic width (`fit-content`).
|
|
9
|
+
* - `1x`–`32x` — fixed multiples of `--space-normal` (1x–8x at 1x intervals, then 10x/12x, then 16x–32x at 4x intervals).
|
|
9
10
|
*
|
|
10
11
|
* @see https://shelving.cc/ui/WidthVariant
|
|
11
12
|
*/
|
|
12
|
-
export type WidthVariant =
|
|
13
|
+
export type WidthVariant =
|
|
14
|
+
| "narrow"
|
|
15
|
+
| "normal"
|
|
16
|
+
| "wide"
|
|
17
|
+
| "full"
|
|
18
|
+
| "fit"
|
|
19
|
+
| "1x"
|
|
20
|
+
| "2x"
|
|
21
|
+
| "3x"
|
|
22
|
+
| "4x"
|
|
23
|
+
| "5x"
|
|
24
|
+
| "6x"
|
|
25
|
+
| "7x"
|
|
26
|
+
| "8x"
|
|
27
|
+
| "10x"
|
|
28
|
+
| "12x"
|
|
29
|
+
| "16x"
|
|
30
|
+
| "20x"
|
|
31
|
+
| "24x"
|
|
32
|
+
| "28x"
|
|
33
|
+
| "32x";
|
|
13
34
|
|
|
14
35
|
/**
|
|
15
36
|
* Variant props that set (or unconstrain) a component's width, e.g. `width="narrow"` or `width="12x"`.
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# getWidthClass
|
|
2
2
|
|
|
3
|
-
The `width` variant prop sets a component's inline-size — `<Card width="narrow">`, `<Block width="wide">`, `<Cell width="
|
|
3
|
+
The `width` variant prop sets a component's inline-size — `<Card width="narrow">`, `<Block width="wide">`, `<Cell width="narrow">`. It's an **override** for one-off layout; for an app-wide change, retune the width variables below in a theme file. `<Section>` already defaults to the `--width-normal` width, so most pages never set `width` at all.
|
|
4
4
|
|
|
5
5
|
`getWidthClass({ width, grow })` maps the props to a width class. Every value is capped at 100% of the container.
|
|
6
6
|
|
|
7
7
|
**Values:**
|
|
8
8
|
|
|
9
|
-
- `
|
|
9
|
+
- `narrow` / `normal` / `wide` — fixed widths from the variables below.
|
|
10
10
|
- `full` — the full available width.
|
|
11
11
|
- `fit` — shrink to the content's intrinsic width (`fit-content`).
|
|
12
|
+
- `1x` / `2x` / `3x` / `4x` / `5x` / `6x` / `7x` / `8x` / `10x` / `12x` / `16x` / `20x` / `24x` / `28x` / `32x` — fixed multiples of `--space-normal` (no per-step variables; calculated directly).
|
|
12
13
|
|
|
13
14
|
**The `grow` flag** turns the chosen `width` into a floor rather than an exact size: it applies the value as `min-inline-size` and adds `flex-grow: 1`, so the element expands to fill the available space when it's a flex item. Set it on a `<Cell>` (`width="12x" grow`) to give that column a 192px minimum that absorbs the remaining width and keeps the table from collapsing the column on a narrow viewport — cells honour `min-width`, so the table scrolls instead.
|
|
14
15
|
|
|
@@ -18,11 +19,6 @@ The following `:root` variables are defined by this module and can be overridden
|
|
|
18
19
|
|
|
19
20
|
| Variable | Default | Used for |
|
|
20
21
|
|---|---|---
|
|
21
|
-
| `--width-
|
|
22
|
-
| `--width-xnarrow` | `24rem` |
|
|
23
|
-
| `--width-narrow` | `36rem` |
|
|
22
|
+
| `--width-narrow` | `36rem` | Focused single-purpose column, e.g. forms and login pages. |
|
|
24
23
|
| `--width-normal` | `48rem` | Standard content column. |
|
|
25
|
-
| `--width-wide` | `80rem` |
|
|
26
|
-
| `--width-xwide` | `96rem` |
|
|
27
|
-
| `--width-xxwide` | `112rem` |
|
|
28
|
-
| `--width-xxxwide` | `128rem` |
|
|
24
|
+
| `--width-wide` | `80rem` | Full dashboard-style content. |
|
package/ui/table/Table.md
CHANGED
|
@@ -81,7 +81,7 @@ The following table does the same but with a minimum width on the description co
|
|
|
81
81
|
<tr>
|
|
82
82
|
<Cell nowrap>{name}</Cell>
|
|
83
83
|
<Cell nowrap>{location}</Cell>
|
|
84
|
-
<Cell width="
|
|
84
|
+
<Cell width="24x" grow>{description}</Cell>
|
|
85
85
|
</tr>
|
|
86
86
|
</Table>
|
|
87
87
|
```
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { createMeta } from "../util/meta.js";
|
|
7
|
-
import { TreeButton } from "./TreeButton.js";
|
|
8
|
-
import { TreeProvider } from "./TreeContext.js";
|
|
4
|
+
import { createMeta, MetaContext, TreeButton, TreeProvider } from "shelving/ui";
|
|
5
|
+
import type { DocumentationElement, TreeElement } from "shelving/util/tree";
|
|
9
6
|
|
|
10
7
|
/** Tree with a class member so both flat (`Store.get`) and canonical-path references resolve. */
|
|
11
8
|
const get: DocumentationElement = {
|