shelving 1.264.1 → 1.265.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/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/form/Progress.d.ts +9 -6
- package/ui/form/Progress.js +12 -6
- package/ui/form/Progress.md +62 -0
- package/ui/form/Progress.module.css +18 -21
- package/ui/form/Progress.tsx +19 -22
- 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 +7 -8
- package/ui/layout/CenteredLayout.js +16 -4
- package/ui/layout/CenteredLayout.md +14 -6
- package/ui/layout/CenteredLayout.module.css +16 -22
- package/ui/layout/CenteredLayout.tsx +30 -13
- 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
package/ui/form/Progress.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
2
|
import { formatPercent } from "../../util/format.js";
|
|
3
|
-
import {
|
|
3
|
+
import { type ColorVariants, getColorClass } from "../style/Color.js";
|
|
4
|
+
import { getStatusClass, type StatusVariants } from "../style/Status.js";
|
|
4
5
|
import { getClass, getModuleClass } from "../util/css.js";
|
|
5
6
|
import styles from "./Progress.module.css";
|
|
6
7
|
|
|
@@ -9,43 +10,39 @@ import styles from "./Progress.module.css";
|
|
|
9
10
|
*
|
|
10
11
|
* @see https://shelving.cc/ui/ProgressProps
|
|
11
12
|
*/
|
|
12
|
-
export interface ProgressProps {
|
|
13
|
+
export interface ProgressProps extends ColorVariants, StatusVariants {
|
|
13
14
|
value: number;
|
|
14
15
|
min?: number;
|
|
15
16
|
max?: number;
|
|
16
|
-
success?: boolean;
|
|
17
|
-
warning?: boolean;
|
|
18
|
-
danger?: boolean;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
20
|
* Show progress as a single continuous horizontal bar, filled to `value` within the `min`–`max` range (matches `getPercent()` and `formatPercent()`).
|
|
23
|
-
* -
|
|
21
|
+
* - Renders a native `<progress>` element, so `role="progressbar"` and the value/max semantics come from the browser.
|
|
22
|
+
* - `<progress>` has no `min` attribute (its implicit minimum is `0`), so the range is normalised to `value - min` / `max - min` before it's handed to the element.
|
|
23
|
+
* - The browser clamps `value` to the `0`–`max` range, so an out-of-range `value` shows an empty or full bar rather than overspilling.
|
|
24
|
+
* - Paints from the tint ladder, so `color=` / `status=` recolour the fill (and track) by moving the tint anchor.
|
|
24
25
|
*
|
|
25
26
|
* @returns A progress bar element.
|
|
26
27
|
* @kind component
|
|
27
28
|
* @example <Progress value={3} max={4} />
|
|
29
|
+
* @example <Progress value={90} status="success" />
|
|
28
30
|
* @see https://shelving.cc/ui/Progress
|
|
29
31
|
*/
|
|
30
|
-
export function Progress({ value, min = 0, max = 100,
|
|
31
|
-
|
|
32
|
-
const
|
|
32
|
+
export function Progress({ value, min = 0, max = 100, ...props }: ProgressProps): ReactElement {
|
|
33
|
+
// `<progress>` has no `min`, so shift the range to a `0`-based one. A non-positive span would make `<progress>` indeterminate, so fall back to an empty determinate bar.
|
|
34
|
+
const span = max - min;
|
|
33
35
|
|
|
34
36
|
return (
|
|
35
|
-
<
|
|
37
|
+
<progress
|
|
36
38
|
className={getClass(
|
|
37
|
-
getModuleClass(styles, "
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
danger && getModuleClass(styles, "danger"),
|
|
39
|
+
getModuleClass(styles, "progress"), //
|
|
40
|
+
getColorClass(props),
|
|
41
|
+
getStatusClass(props),
|
|
41
42
|
)}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
aria-valuemax={max}
|
|
45
|
-
aria-valuenow={value}
|
|
43
|
+
value={span > 0 ? value - min : 0}
|
|
44
|
+
max={span > 0 ? span : 1}
|
|
46
45
|
aria-valuetext={formatPercent(value - min, max - min)}
|
|
47
|
-
|
|
48
|
-
<span className={getModuleClass(styles, "fill")} style={fillStyle} />
|
|
49
|
-
</figure>
|
|
46
|
+
/>
|
|
50
47
|
);
|
|
51
48
|
}
|
package/ui/inline/Deleted.md
CHANGED
|
@@ -5,7 +5,7 @@ Deleted text — renders a `<del>` element to mark content that has been removed
|
|
|
5
5
|
**Things to know:**
|
|
6
6
|
|
|
7
7
|
- Use `Deleted` for content genuinely removed from a document (edits, diffs, retired prices), and `<Inserted>` for content added — they pair up.
|
|
8
|
-
- It
|
|
8
|
+
- It colours itself directly from `--color-red` rather than the tint ladder, so a deletion stays a consistent red even inside a tinted region; change it with `--deleted-color`.
|
|
9
9
|
- Inside `<Prose>` a raw `<del>` picks up the same styling, so Markdown-rendered deletions match component ones.
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -22,13 +22,12 @@ import { Paragraph, Deleted, Inserted } from "shelving/ui";
|
|
|
22
22
|
|
|
23
23
|
## Styling
|
|
24
24
|
|
|
25
|
-
`Deleted`
|
|
25
|
+
`Deleted` colours its text directly from `--color-red` (not the tint ladder), so deletions stay a consistent red regardless of the surrounding tint. Override `--deleted-color` to recolour.
|
|
26
26
|
|
|
27
27
|
| Variable | Styles | Default |
|
|
28
28
|
|---|---|---|
|
|
29
|
-
| `--deleted-
|
|
30
|
-
| `--deleted-color` | Text colour | `var(--tint-50)` |
|
|
29
|
+
| `--deleted-color` | Text colour | `var(--color-red)` |
|
|
31
30
|
| `--deleted-decoration` | Text decoration | `line-through var(--stroke-normal)` |
|
|
32
31
|
| `--deleted-weight` | Font weight | `var(--weight-strong)` |
|
|
33
32
|
|
|
34
|
-
**Global tokens it reads:** `--color-red`, `--stroke-normal`, `--weight-strong
|
|
33
|
+
**Global tokens it reads:** `--color-red`, `--stroke-normal`, and `--weight-strong`.
|
|
@@ -6,10 +6,8 @@
|
|
|
6
6
|
@layer components {
|
|
7
7
|
.deleted,
|
|
8
8
|
.prose del {
|
|
9
|
-
--tint-50: var(--deleted-tint, var(--color-red));
|
|
10
|
-
|
|
11
9
|
/* Style */
|
|
12
|
-
color: var(--deleted-color, var(--
|
|
10
|
+
color: var(--deleted-color, var(--color-red));
|
|
13
11
|
text-decoration: var(--deleted-decoration, line-through var(--stroke-normal));
|
|
14
12
|
|
|
15
13
|
/* Text */
|
package/ui/inline/Inserted.md
CHANGED
|
@@ -5,7 +5,7 @@ Inserted text — renders an `<ins>` element to mark content that has been added
|
|
|
5
5
|
**Things to know:**
|
|
6
6
|
|
|
7
7
|
- Use `Inserted` for content genuinely added to a document (edits, diffs, new prices), and `<Deleted>` for content removed — they pair up.
|
|
8
|
-
- It
|
|
8
|
+
- It colours itself directly from `--color-green` rather than the tint ladder, so an insertion stays a consistent green even inside a tinted region; change it with `--inserted-color`.
|
|
9
9
|
- Inside `<Prose>` a raw `<ins>` picks up the same styling, so Markdown-rendered insertions match component ones.
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -22,13 +22,12 @@ import { Paragraph, Deleted, Inserted } from "shelving/ui";
|
|
|
22
22
|
|
|
23
23
|
## Styling
|
|
24
24
|
|
|
25
|
-
`Inserted`
|
|
25
|
+
`Inserted` colours its text directly from `--color-green` (not the tint ladder), so insertions stay a consistent green regardless of the surrounding tint. Override `--inserted-color` to recolour.
|
|
26
26
|
|
|
27
27
|
| Variable | Styles | Default |
|
|
28
28
|
|---|---|---|
|
|
29
|
-
| `--inserted-
|
|
30
|
-
| `--inserted-color` | Text colour | `var(--tint-50)` |
|
|
29
|
+
| `--inserted-color` | Text colour | `var(--color-green)` |
|
|
31
30
|
| `--inserted-decoration` | Text decoration | `underline var(--stroke-normal)` |
|
|
32
31
|
| `--inserted-weight` | Font weight | `var(--weight-strong)` |
|
|
33
32
|
|
|
34
|
-
**Global tokens it reads:** `--color-green`, `--stroke-normal`, `--weight-strong
|
|
33
|
+
**Global tokens it reads:** `--color-green`, `--stroke-normal`, and `--weight-strong`.
|
|
@@ -6,10 +6,8 @@
|
|
|
6
6
|
@layer components {
|
|
7
7
|
.inserted,
|
|
8
8
|
.prose ins {
|
|
9
|
-
--tint-50: var(--inserted-tint, var(--color-green));
|
|
10
|
-
|
|
11
9
|
/* Style */
|
|
12
|
-
color: var(--inserted-color, var(--
|
|
10
|
+
color: var(--inserted-color, var(--color-green));
|
|
13
11
|
text-decoration: var(--inserted-decoration, underline var(--stroke-normal));
|
|
14
12
|
|
|
15
13
|
/* Text */
|
package/ui/inline/Link.md
CHANGED
|
@@ -31,12 +31,11 @@ import { Link } from "shelving/ui";
|
|
|
31
31
|
|
|
32
32
|
## Styling
|
|
33
33
|
|
|
34
|
-
`Link`
|
|
34
|
+
`Link` colours itself from the global `--color-link` token by default. Inside a tinted scope (`color=` / `status=` on an ancestor) `--color-link` resolves to `inherit`, so links adopt the surrounding tinted text colour. Override `--link-color` to set it directly.
|
|
35
35
|
|
|
36
36
|
| Variable | Styles | Default |
|
|
37
37
|
|---|---|---|
|
|
38
|
-
| `--link-
|
|
39
|
-
| `--link-color` | Text colour | `var(--tint-50)` |
|
|
38
|
+
| `--link-color` | Text colour | `var(--color-link)` |
|
|
40
39
|
| `--link-weight` | Font weight | `var(--weight-strong)` |
|
|
41
40
|
|
|
42
41
|
**Global tokens it reads:** `--color-link`, `--weight-strong`, and `--stroke-normal` (the underline thickness).
|
package/ui/inline/Mark.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { TINT_CLASS } from "../style/Tint.js";
|
|
3
2
|
import { getTypographyClass } from "../style/Typography.js";
|
|
4
3
|
import { getClass, getModuleClass } from "../util/css.js";
|
|
5
4
|
import MARK_CSS from "./Mark.module.css";
|
|
@@ -12,5 +11,5 @@ const MARK_CLASS = getModuleClass(MARK_CSS, "mark");
|
|
|
12
11
|
*/
|
|
13
12
|
export function Mark({ children, ...props }) {
|
|
14
13
|
return (_jsx("mark", { className: getClass(MARK_CLASS, //
|
|
15
|
-
|
|
14
|
+
getTypographyClass(props)), children: children }));
|
|
16
15
|
}
|
package/ui/inline/Mark.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mark
|
|
2
2
|
|
|
3
|
-
Highlighted text — renders a `<mark>` element to call attention to a run of text, such as a matched search term. Painted as a small inline pill with a yellow background by default.
|
|
3
|
+
Highlighted text — renders a `<mark>` element to call attention to a run of text, such as a matched search term. Painted as a small inline pill with a translucent yellow background by default.
|
|
4
4
|
|
|
5
5
|
**Things to know:**
|
|
6
6
|
|
|
@@ -20,15 +20,15 @@ import { Mark } from "shelving/ui";
|
|
|
20
20
|
|
|
21
21
|
## Styling
|
|
22
22
|
|
|
23
|
-
`Mark`
|
|
23
|
+
`Mark` highlights with a translucent wash of `--color-yellow` — `color-mix(…, transparent)`, so the text underneath stays legible — with the text painted from `--tint-00`. Override `--mark-tint` to change the highlight hue, `--mark-background` to replace the fill outright, or `--mark-color` for the text.
|
|
24
24
|
|
|
25
25
|
| Variable | Styles | Default |
|
|
26
26
|
|---|---|---|
|
|
27
|
-
| `--mark-tint` |
|
|
28
|
-
| `--mark-background` | Background fill | `var(--tint-
|
|
27
|
+
| `--mark-tint` | Highlight hue, mixed 50% with `transparent` for the background | `var(--color-yellow)` |
|
|
28
|
+
| `--mark-background` | Background fill | `color-mix(in oklch, var(--mark-tint, var(--color-yellow)), transparent)` |
|
|
29
29
|
| `--mark-color` | Text colour | `var(--tint-00)` |
|
|
30
30
|
| `--mark-padding` | Inline padding | `var(--space-xxsmall)` |
|
|
31
31
|
| `--mark-radius` | Corner radius | `var(--radius-xxsmall)` |
|
|
32
32
|
| `--mark-weight` | Font weight | `var(--weight-strong)` |
|
|
33
33
|
|
|
34
|
-
**Global tokens it reads:** `--color-yellow`, `--space-xxsmall`, `--radius-xxsmall`, `--weight-strong`, and the tint-ladder
|
|
34
|
+
**Global tokens it reads:** `--color-yellow`, `--space-xxsmall`, `--radius-xxsmall`, `--weight-strong`, and the tint-ladder step `--tint-00`.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@import "../style/layers.css";
|
|
2
|
+
@import "../style/Color.module.css";
|
|
2
3
|
@import "../style/Radius.module.css";
|
|
3
4
|
@import "../style/Typography.module.css";
|
|
4
5
|
@import "../style/Tint.module.css";
|
|
@@ -6,8 +7,6 @@
|
|
|
6
7
|
@layer components {
|
|
7
8
|
.mark,
|
|
8
9
|
.prose mark {
|
|
9
|
-
--tint-50: var(--mark-tint, var(--color-yellow));
|
|
10
|
-
|
|
11
10
|
/* Box */
|
|
12
11
|
display: inline-block;
|
|
13
12
|
padding-inline: var(--mark-padding, var(--space-xxsmall));
|
|
@@ -15,7 +14,7 @@
|
|
|
15
14
|
vertical-align: baseline;
|
|
16
15
|
|
|
17
16
|
/* Style */
|
|
18
|
-
background: var(--mark-background, var(--tint-
|
|
17
|
+
background-color: var(--mark-background, color-mix(in oklch, var(--mark-tint, var(--color-yellow)), transparent));
|
|
19
18
|
color: var(--mark-color, var(--tint-00));
|
|
20
19
|
|
|
21
20
|
/* Text */
|
package/ui/inline/Mark.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
|
-
import { TINT_CLASS } from "../style/Tint.js";
|
|
3
2
|
import { getTypographyClass, type TypographyVariants } from "../style/Typography.js";
|
|
4
3
|
import { getClass, getModuleClass } from "../util/css.js";
|
|
5
4
|
import type { OptionalChildProps } from "../util/props.js";
|
|
@@ -25,7 +24,6 @@ export function Mark({ children, ...props }: MarkProps): ReactElement {
|
|
|
25
24
|
<mark
|
|
26
25
|
className={getClass(
|
|
27
26
|
MARK_CLASS, //
|
|
28
|
-
TINT_CLASS,
|
|
29
27
|
getTypographyClass(props),
|
|
30
28
|
)}
|
|
31
29
|
>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
-
import {
|
|
4
|
-
import { StringSchemaInput } from "
|
|
3
|
+
import { StringSchema } from "shelving/schema";
|
|
4
|
+
import { StringSchemaInput } from "shelving/ui";
|
|
5
|
+
import { PASSTHROUGH } from "shelving/util/function";
|
|
5
6
|
|
|
6
7
|
describe("StringSchemaInput", () => {
|
|
7
8
|
test("formats the initial value to its clean sanitized value", () => {
|
|
@@ -1,16 +1,15 @@
|
|
|
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
|
-
* Props for `<CenteredLayout>` — optional `children`
|
|
7
|
+
* Props for `<CenteredLayout>` — optional `children` plus `width` (of the centred column) and `padding` / `indent`
|
|
8
|
+
* (block / inline padding of the scroll area) variants.
|
|
5
9
|
*
|
|
6
10
|
* @see https://shelving.cc/ui/CenteredLayoutProps
|
|
7
11
|
*/
|
|
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;
|
|
12
|
+
export interface CenteredLayoutProps extends WidthVariants, PaddingVariants, IndentVariants, OptionalChildProps {
|
|
14
13
|
}
|
|
15
14
|
/**
|
|
16
15
|
* Layout that centres its content with no header/footer and a narrow max-width.
|
|
@@ -19,4 +18,4 @@ export interface CenteredLayoutProps extends OptionalChildProps {
|
|
|
19
18
|
* @kind component
|
|
20
19
|
* @see https://shelving.cc/ui/CenteredLayout
|
|
21
20
|
*/
|
|
22
|
-
export declare function CenteredLayout({ children,
|
|
21
|
+
export declare function CenteredLayout({ children, ...props }: CenteredLayoutProps): ReactElement;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { RouteCache } from "../router/RouteCache.js";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { getIndentClass } from "../style/Indent.js";
|
|
4
|
+
import { getPaddingClass } from "../style/Padding.js";
|
|
5
|
+
import { getWidthClass } from "../style/Width.js";
|
|
6
|
+
import { getClass, getModuleClass } from "../util/css.js";
|
|
7
|
+
import LAYOUT_CSS from "./CenteredLayout.module.css";
|
|
8
|
+
const LAYOUT_MAIN_CLASS = getModuleClass(LAYOUT_CSS, "main");
|
|
9
|
+
const LAYOUT_INNER_CLASS = getModuleClass(LAYOUT_CSS, "inner");
|
|
5
10
|
/**
|
|
6
11
|
* Layout that centres its content with no header/footer and a narrow max-width.
|
|
7
12
|
* - Used for e.g. login/register/error/form pages where the content is the only focus.
|
|
@@ -9,8 +14,15 @@ import CENTERED_LAYOUT_CSS from "./CenteredLayout.module.css";
|
|
|
9
14
|
* @kind component
|
|
10
15
|
* @see https://shelving.cc/ui/CenteredLayout
|
|
11
16
|
*/
|
|
12
|
-
export function CenteredLayout({ children,
|
|
17
|
+
export function CenteredLayout({ children, ...props }) {
|
|
13
18
|
// Wrap the scrolling `<main>` in `<RouteCache>` so recently-visited pages stay mounted but hidden,
|
|
14
19
|
// keeping their scroll position and state intact across back/forward navigation.
|
|
15
|
-
|
|
20
|
+
//
|
|
21
|
+
// The `padding` / `indent` variants set the scroll area's block / inline padding on `<main>`, while the `width`
|
|
22
|
+
// variant sizes the centred `.inner` column. The column stays free of variant/block classes so its `margin: auto`
|
|
23
|
+
// (which does the vertical + horizontal centring) is never zeroed by the `:first-child` / `:last-child` margin
|
|
24
|
+
// collapses that `.block` carries in `@layer overrides`.
|
|
25
|
+
return (_jsx(RouteCache, { children: _jsx("main", { className: getClass(LAYOUT_MAIN_CLASS, //
|
|
26
|
+
getPaddingClass(props), getIndentClass(props)), children: _jsx("div", { className: getClass(LAYOUT_INNER_CLASS, //
|
|
27
|
+
getWidthClass(props)), children: children }) }) }));
|
|
16
28
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# CenteredLayout
|
|
2
2
|
|
|
3
|
-
A full-viewport layout that centres its content horizontally inside a narrow max-width column. Good for login, registration, error, and other focused single-purpose pages where the content is the only thing on screen.
|
|
3
|
+
A full-viewport layout that centres its content — vertically and horizontally — inside a narrow max-width column. Good for login, registration, error, and other focused single-purpose pages where the content is the only thing on screen. When the content is taller than the viewport it scrolls vertically, pinned to the top so nothing is clipped.
|
|
4
4
|
|
|
5
5
|
**Things to know:**
|
|
6
6
|
|
|
7
|
-
- Pass
|
|
7
|
+
- Pass the `width` variant to resize the centred column (e.g. `width="normal"`, `width="wide"`, or `width="full"` to fill the available width).
|
|
8
|
+
- Pass the `padding` (block / top + bottom) and `indent` (inline / left + right) variants to change the space around the content.
|
|
8
9
|
- Like the other full-viewport layouts it owns scroll, padding, and safe-area insets so individual pages don't have to.
|
|
9
10
|
|
|
10
11
|
## Usage
|
|
@@ -14,8 +15,8 @@ import { CenteredLayout, Section } from "shelving/ui";
|
|
|
14
15
|
|
|
15
16
|
function LoginPage() {
|
|
16
17
|
return (
|
|
17
|
-
<CenteredLayout>
|
|
18
|
-
<Section
|
|
18
|
+
<CenteredLayout width="narrow">
|
|
19
|
+
<Section>
|
|
19
20
|
<LoginForm/>
|
|
20
21
|
</Section>
|
|
21
22
|
</CenteredLayout>
|
|
@@ -27,6 +28,13 @@ Layouts compose naturally as `<Router>` route values — wrap a group of routes
|
|
|
27
28
|
|
|
28
29
|
## Styling
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
| Variable | Styles | Default |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `--centered-layout-width` | Width of the centred column (capped at 100%) | `var(--width-narrow)` |
|
|
34
|
+
| `--centered-layout-padding` | Block (top/bottom) padding of the scroll area | `var(--space-normal)` |
|
|
35
|
+
| `--centered-layout-indent` | Inline (left/right) padding of the scroll area | `var(--space-normal)` |
|
|
36
|
+
| `--centered-layout-background` | Page background while the layout is mounted | Unset — the `body` default from `Typography.module.css` shows |
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
The column width and the scroll-area padding can also be set per-instance via the `width`, `padding`, and `indent` variant props. The outer element owns its scroll, padding, and safe-area behaviour directly — safe-area insets are applied as transparent borders so they stack with (rather than replace) the padding.
|
|
39
|
+
|
|
40
|
+
**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 { getIndentClass, type IndentVariants } from "../style/Indent.js";
|
|
4
|
+
import { getPaddingClass, type PaddingVariants } from "../style/Padding.js";
|
|
5
|
+
import { getWidthClass, type WidthVariants } from "../style/Width.js";
|
|
6
|
+
import { getClass, getModuleClass } from "../util/css.js";
|
|
4
7
|
import type { OptionalChildProps } from "../util/props.js";
|
|
5
|
-
import
|
|
8
|
+
import LAYOUT_CSS from "./CenteredLayout.module.css";
|
|
9
|
+
|
|
10
|
+
const LAYOUT_MAIN_CLASS = getModuleClass(LAYOUT_CSS, "main");
|
|
11
|
+
const LAYOUT_INNER_CLASS = getModuleClass(LAYOUT_CSS, "inner");
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
|
-
* Props for `<CenteredLayout>` — optional `children`
|
|
14
|
+
* Props for `<CenteredLayout>` — optional `children` plus `width` (of the centred column) and `padding` / `indent`
|
|
15
|
+
* (block / inline padding of the scroll area) variants.
|
|
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,29 @@ 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.
|
|
31
|
+
//
|
|
32
|
+
// The `padding` / `indent` variants set the scroll area's block / inline padding on `<main>`, while the `width`
|
|
33
|
+
// variant sizes the centred `.inner` column. The column stays free of variant/block classes so its `margin: auto`
|
|
34
|
+
// (which does the vertical + horizontal centring) is never zeroed by the `:first-child` / `:last-child` margin
|
|
35
|
+
// collapses that `.block` carries in `@layer overrides`.
|
|
30
36
|
return (
|
|
31
37
|
<RouteCache>
|
|
32
|
-
<main
|
|
33
|
-
|
|
38
|
+
<main
|
|
39
|
+
className={getClass(
|
|
40
|
+
LAYOUT_MAIN_CLASS, //
|
|
41
|
+
getPaddingClass(props),
|
|
42
|
+
getIndentClass(props),
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
className={getClass(
|
|
47
|
+
LAYOUT_INNER_CLASS, //
|
|
48
|
+
getWidthClass(props),
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
34
51
|
{children}
|
|
35
52
|
</div>
|
|
36
53
|
</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). */
|