shelving 1.253.2 → 1.253.4

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.
Files changed (93) hide show
  1. package/package.json +3 -3
  2. package/ui/README.md +4 -4
  3. package/ui/app/App.d.ts +0 -12
  4. package/ui/app/App.js +0 -14
  5. package/ui/app/App.tsx +0 -14
  6. package/ui/block/Address.module.css +2 -0
  7. package/ui/block/Block.d.ts +7 -3
  8. package/ui/block/Block.js +2 -2
  9. package/ui/block/Block.module.css +4 -0
  10. package/ui/block/Block.tsx +8 -4
  11. package/ui/block/Blockquote.module.css +1 -1
  12. package/ui/block/Caption.module.css +2 -2
  13. package/ui/block/Card.d.ts +9 -2
  14. package/ui/block/Card.js +4 -3
  15. package/ui/block/Card.tsx +13 -5
  16. package/ui/block/Definitions.module.css +3 -4
  17. package/ui/block/Divider.module.css +1 -1
  18. package/ui/block/Heading.module.css +2 -3
  19. package/ui/block/Label.module.css +2 -4
  20. package/ui/block/List.module.css +3 -1
  21. package/ui/block/List.tsx +0 -1
  22. package/ui/block/Panel.d.ts +7 -7
  23. package/ui/block/Panel.js +2 -2
  24. package/ui/block/Panel.module.css +4 -1
  25. package/ui/block/Panel.tsx +11 -11
  26. package/ui/block/Paragraph.module.css +2 -0
  27. package/ui/block/Preformatted.module.css +2 -4
  28. package/ui/block/Section.d.ts +20 -29
  29. package/ui/block/Section.js +21 -33
  30. package/ui/block/Section.md +2 -2
  31. package/ui/block/Section.module.css +2 -0
  32. package/ui/block/Section.tsx +32 -46
  33. package/ui/block/Subheading.module.css +2 -3
  34. package/ui/block/Title.module.css +2 -3
  35. package/ui/block/Video.js +3 -1
  36. package/ui/block/Video.tsx +10 -1
  37. package/ui/form/Button.module.css +1 -3
  38. package/ui/form/Field.module.css +1 -2
  39. package/ui/form/Input.module.css +1 -3
  40. package/ui/inline/Code.module.css +1 -3
  41. package/ui/inline/Deleted.module.css +1 -1
  42. package/ui/inline/Inserted.module.css +1 -1
  43. package/ui/inline/Link.module.css +1 -1
  44. package/ui/inline/Mark.module.css +1 -1
  45. package/ui/inline/Small.module.css +1 -1
  46. package/ui/inline/Strong.module.css +1 -1
  47. package/ui/menu/Menu.module.css +1 -4
  48. package/ui/misc/Loading.module.css +1 -1
  49. package/ui/misc/StatusIcon.module.css +1 -1
  50. package/ui/misc/Tag.module.css +1 -3
  51. package/ui/notice/Message.module.css +1 -1
  52. package/ui/notice/Notice.module.css +1 -2
  53. package/ui/style/Flex.js +2 -1
  54. package/ui/style/Flex.module.css +1 -1
  55. package/ui/style/Flex.tsx +4 -1
  56. package/ui/style/Padding.js +1 -1
  57. package/ui/style/Padding.module.css +18 -18
  58. package/ui/style/Padding.tsx +1 -1
  59. package/ui/style/Radius.module.css +7 -8
  60. package/ui/style/Space.d.ts +1 -1
  61. package/ui/style/Space.js +1 -1
  62. package/ui/style/Space.module.css +32 -26
  63. package/ui/style/Space.tsx +3 -1
  64. package/ui/style/Tint.d.ts +6 -0
  65. package/ui/style/Tint.module.css +0 -6
  66. package/ui/style/Tint.tsx +28 -0
  67. package/ui/style/Typography.d.ts +37 -32
  68. package/ui/style/Typography.js +4 -8
  69. package/ui/style/Typography.module.css +163 -0
  70. package/ui/style/Typography.tsx +47 -59
  71. package/ui/style/getRadiusClass.md +8 -9
  72. package/ui/style/getSpaceClass.md +9 -10
  73. package/ui/style/getTypographyClass.md +64 -0
  74. package/ui/style/index.d.ts +0 -3
  75. package/ui/style/index.js +0 -3
  76. package/ui/style/index.tsx +0 -3
  77. package/ui/style/layers.css +2 -2
  78. package/ui/table/Table.module.css +1 -3
  79. package/ui/style/Font.d.ts +0 -24
  80. package/ui/style/Font.js +0 -13
  81. package/ui/style/Font.module.css +0 -50
  82. package/ui/style/Font.tsx +0 -31
  83. package/ui/style/Size.d.ts +0 -24
  84. package/ui/style/Size.js +0 -13
  85. package/ui/style/Size.module.css +0 -69
  86. package/ui/style/Size.tsx +0 -31
  87. package/ui/style/Weight.d.ts +0 -24
  88. package/ui/style/Weight.js +0 -13
  89. package/ui/style/Weight.module.css +0 -42
  90. package/ui/style/Weight.tsx +0 -31
  91. package/ui/style/getFontClass.md +0 -32
  92. package/ui/style/getSizeClass.md +0 -35
  93. package/ui/style/getWeightClass.md +0 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.253.2",
3
+ "version": "1.253.4",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,8 +15,8 @@
15
15
  "@types/bun": "^1.3.14",
16
16
  "@types/react": "^19.2.17",
17
17
  "@types/react-dom": "^19.2.3",
18
- "@typescript/native-preview": "^7.0.0-dev.20260613.1",
19
- "firebase": "^12.14.0",
18
+ "@typescript/native-preview": "^7.0.0-dev.20260620.1",
19
+ "firebase": "^12.15.0",
20
20
  "react": "^19.3.0-canary-fef12a01-20260413",
21
21
  "react-dom": "^19.3.0-canary-fef12a01-20260413",
22
22
  "typescript": "^5.9.3"
package/ui/README.md CHANGED
@@ -19,13 +19,13 @@ A few conventions run through every component:
19
19
 
20
20
  The styling system lives in `style/` and has four moving parts: design tokens, the tint scale, cascade layers, and the styling props. Components compose them in a predictable shape; consumers theme by overriding CSS custom properties at `:root`.
21
21
 
22
- **Design tokens.** Every design-token constant is defined at `:root`, split across the themed token modules in `style/` — each module owns one domain, documents the variables it defines, and is the page a theme author overrides. `style/layers.css` is the cascade-layer anchor; every `*.module.css` `@import`s it plus the specific token modules it references, so the tokens and the layer order reach every component regardless of bundle order. The domains are: colours (`getColorClass()`), font sizes (`getSizeClass()`), font weights (`getWeightClass()`), font faces (`getFontClass()`), spacing (`getSpaceClass()`), widths (`getWidthClass()`), radii (`getRadiusClass()`), strokes (`getStrokeClass()`), shadows (`getShadowClass()`), and durations (`getDurationClass()`). Each also defines the semantic aliases a theme usually targets (`--color-primary`, `--color-link`, `--space-paragraph`, …). Components read tokens via `var(--token)`.
22
+ **Design tokens.** Every design-token constant is defined at `:root`, split across the themed token modules in `style/` — each module owns one domain, documents the variables it defines, and is the page a theme author overrides. `style/layers.css` is the cascade-layer anchor; every `*.module.css` `@import`s it plus the specific token modules it references, so the tokens and the layer order reach every component regardless of bundle order. The domains are: colours (`getColorClass()`), typography font size, weight, family, and case (`getTypographyClass()`), spacing (`getSpaceClass()`), widths (`getWidthClass()`), radii (`getRadiusClass()`), strokes (`getStrokeClass()`), shadows (`getShadowClass()`), and durations (`getDurationClass()`). Each also defines the semantic aliases a theme usually targets (`--color-primary`, `--color-link`, `--space-paragraph`, …). Components read tokens via `var(--token)`.
23
23
 
24
24
  **The tint scale.** All colour flows from one anchor variable, `--tint-50`, from which a 21-step ladder is computed and *recomputed* under `TINT_CLASS` — the heart of how `color=` and `status=` retint a whole subtree. The ladder, the recompute trick, the painting conventions, and the theming guide all live on the `TINT_CLASS` page.
25
25
 
26
26
  **Cascade layers.** Styles are ordered by `@layer`, lowest to highest priority: `defaults` (`:root` tokens, the tint ladder, body baseline) → `components` (the bulk of the CSS: `.card`, `.button`, …) → `variants` (cross-cutting opt-in modifiers, which always beat components) → `overrides` (top-priority structural fixes like `:first-child` / `:last-child` margin collapses). Unlayered rules beat all layered rules, so a theme should set tokens at `:root` or wrap its rules in `@layer`.
27
27
 
28
- **Styling props.** The cross-cutting visual options are props, each backed by a helper in `style/` that maps the prop to a class. Colour and status move the tint anchor — `getColorClass()` and `getStatusClass()`; font size, weight, and family come from `getSizeClass()`, `getWeightClass()`, and `getFontClass()`, which `getTypographyClass()` combines with text alignment and tint; spacing, padding, and gap from `getSpaceClass()`, `getPaddingClass()`, and `getGapClass()`; width constraints from `getWidthClass()`; flex layout from `getFlexClass()`; and opt-in scrolling from `getScrollClass()`. Each helper's page lists its exact prop values and what they set. A component opts into the props it wants by extending the matching `*Props` interfaces and composing the `getXxxClass(props)` calls.
28
+ **Styling props.** The cross-cutting visual options are props, each backed by a helper in `style/` that maps the prop to a class. Colour and status move the tint anchor — `getColorClass()` and `getStatusClass()`; font size, weight, family, and case, plus text alignment, tint, and wrapping, all come from `getTypographyClass()`; spacing, padding, and gap from `getSpaceClass()`, `getPaddingClass()`, and `getGapClass()`; width constraints from `getWidthClass()`; flex layout from `getFlexClass()`; and opt-in scrolling from `getScrollClass()`. Each helper's page lists its exact prop values and what they set. A component opts into the props it wants by extending the matching `*Props` interfaces and composing the `getXxxClass(props)` calls.
29
29
 
30
30
  Each painting component also exposes its own theme hooks — a single tint hook (`--card-tint`) to recolour the whole component, plus per-property hooks (`--card-background`, `--card-radius`, …) for surgical overrides. Those are documented in each component's own **Styling** section (see `<Card>` for the precedent).
31
31
 
@@ -38,8 +38,8 @@ The recommended way to give an app its own look is a **theme stylesheet**, not s
38
38
  :root {
39
39
  --color-primary: oklch(58% 0.25 300); /* purple brand */
40
40
  --font-body: "Inter", system-ui;
41
- --radius: 0.5rem; /* tighter corners everywhere */
42
- --space: 1.125rem; /* roomier spacing scale */
41
+ --radius-normal: 0.5rem; /* tighter corners everywhere */
42
+ --space-normal: 1.125rem; /* roomier spacing scale */
43
43
  }
44
44
  ```
45
45
 
package/ui/app/App.d.ts CHANGED
@@ -1,16 +1,4 @@
1
1
  import type { ReactElement } from "react";
2
- import "../style/layers.css";
3
- import "../style/Color.module.css";
4
- import "../style/Duration.module.css";
5
- import "../style/Font.module.css";
6
- import "../style/Radius.module.css";
7
- import "../style/Shadow.module.css";
8
- import "../style/Size.module.css";
9
- import "../style/Space.module.css";
10
- import "../style/Stroke.module.css";
11
- import "../style/Tint.module.css";
12
- import "../style/Weight.module.css";
13
- import "../style/Width.module.css";
14
2
  import type { PossibleMeta } from "../util/index.js";
15
3
  import type { ChildProps } from "../util/props.js";
16
4
  /**
package/ui/app/App.js CHANGED
@@ -1,19 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { MetaContext, requireMeta } from "../misc/MetaContext.js";
3
- // Load the global design tokens, cascade-layer order, and body baseline. Each module owns its own
4
- // `:root` tokens (and body rules where relevant); see `modules/ui/README.md` for the styling system.
5
- import "../style/layers.css";
6
- import "../style/Color.module.css";
7
- import "../style/Duration.module.css";
8
- import "../style/Font.module.css";
9
- import "../style/Radius.module.css";
10
- import "../style/Shadow.module.css";
11
- import "../style/Size.module.css";
12
- import "../style/Space.module.css";
13
- import "../style/Stroke.module.css";
14
- import "../style/Tint.module.css";
15
- import "../style/Weight.module.css";
16
- import "../style/Width.module.css";
17
3
  /**
18
4
  * Root component for an application, providing the top-level `Meta` context and global styles.
19
5
  * - Descendants can read or update metadata via the provided `<Meta>` context.
package/ui/app/App.tsx CHANGED
@@ -1,19 +1,5 @@
1
1
  import type { ReactElement } from "react";
2
2
  import { MetaContext, requireMeta } from "../misc/MetaContext.js";
3
- // Load the global design tokens, cascade-layer order, and body baseline. Each module owns its own
4
- // `:root` tokens (and body rules where relevant); see `modules/ui/README.md` for the styling system.
5
- import "../style/layers.css";
6
- import "../style/Color.module.css";
7
- import "../style/Duration.module.css";
8
- import "../style/Font.module.css";
9
- import "../style/Radius.module.css";
10
- import "../style/Shadow.module.css";
11
- import "../style/Size.module.css";
12
- import "../style/Space.module.css";
13
- import "../style/Stroke.module.css";
14
- import "../style/Tint.module.css";
15
- import "../style/Weight.module.css";
16
- import "../style/Width.module.css";
17
3
  import type { PossibleMeta } from "../util/index.js";
18
4
  import type { ChildProps } from "../util/props.js";
19
5
 
@@ -1,5 +1,7 @@
1
1
  @import "../style/layers.css";
2
+ @import "../style/Color.module.css";
2
3
  @import "../style/Space.module.css";
4
+ @import "../style/Typography.module.css";
3
5
 
4
6
  @layer components {
5
7
  .address,
@@ -5,17 +5,21 @@ import { type TypographyVariants } from "../style/Typography.js";
5
5
  import { type WidthVariants } from "../style/Width.js";
6
6
  import type { OptionalChildProps } from "../util/props.js";
7
7
  /**
8
- * Semantic element names a `Block` may render as via its `as` prop.
8
+ * Semantic element names a block element may render as via its `as` prop.
9
9
  *
10
10
  * @see https://dhoulb.github.io/shelving/ui/block/Block/BlockElement
11
11
  */
12
- export type BlockElement = "div" | "section" | "header" | "footer" | "nav" | "aside" | "figure";
12
+ export type BlockElement = "div" | "section" | "header" | "footer" | "article" | "nav" | "aside" | "figure";
13
13
  /**
14
14
  * Props for `Block` — colour, space, typography, and width variants plus an optional `as` element override.
15
15
  *
16
16
  * @see https://dhoulb.github.io/shelving/ui/block/Block/BlockProps
17
17
  */
18
18
  export interface BlockProps extends ColorVariants, SpaceVariants, TypographyVariants, WidthVariants, OptionalChildProps {
19
+ /**
20
+ * Element this `<Block>` renders as, e.g. "header" to output a "<header>"
21
+ * @default "div"
22
+ */
19
23
  as?: BlockElement | undefined;
20
24
  }
21
25
  /**
@@ -38,4 +42,4 @@ export declare function getBlockClass(variants: BlockProps): string;
38
42
  * @example <Block as="aside" width="narrow"><Paragraph>Sidebar</Paragraph></Block>
39
43
  * @see https://dhoulb.github.io/shelving/ui/block/Block/Block
40
44
  */
41
- export declare function Block({ as: Component, children, ...props }: BlockProps): ReactElement;
45
+ export declare function Block({ as: Element, children, ...props }: BlockProps): ReactElement;
package/ui/block/Block.js CHANGED
@@ -29,6 +29,6 @@ export function getBlockClass(variants) {
29
29
  * @example <Block as="aside" width="narrow"><Paragraph>Sidebar</Paragraph></Block>
30
30
  * @see https://dhoulb.github.io/shelving/ui/block/Block/Block
31
31
  */
32
- export function Block({ as: Component = "div", children, ...props }) {
33
- return _jsx(Component, { className: getBlockClass(props), children: children });
32
+ export function Block({ as: Element = "div", children, ...props }) {
33
+ return _jsx(Element, { className: getBlockClass(props), children: children });
34
34
  }
@@ -1,4 +1,8 @@
1
1
  @import "../style/layers.css";
2
+ @import "../style/Color.module.css";
3
+ @import "../style/Space.module.css";
4
+ @import "../style/Typography.module.css";
5
+ @import "../style/Width.module.css";
2
6
 
3
7
  @layer components {
4
8
  .block {
@@ -10,11 +10,11 @@ import BLOCK_CSS from "./Block.module.css";
10
10
  const BLOCK_CLASS = getModuleClass(BLOCK_CSS, "block");
11
11
 
12
12
  /**
13
- * Semantic element names a `Block` may render as via its `as` prop.
13
+ * Semantic element names a block element may render as via its `as` prop.
14
14
  *
15
15
  * @see https://dhoulb.github.io/shelving/ui/block/Block/BlockElement
16
16
  */
17
- export type BlockElement = "div" | "section" | "header" | "footer" | "nav" | "aside" | "figure";
17
+ export type BlockElement = "div" | "section" | "header" | "footer" | "article" | "nav" | "aside" | "figure";
18
18
 
19
19
  /**
20
20
  * Props for `Block` — colour, space, typography, and width variants plus an optional `as` element override.
@@ -22,6 +22,10 @@ export type BlockElement = "div" | "section" | "header" | "footer" | "nav" | "as
22
22
  * @see https://dhoulb.github.io/shelving/ui/block/Block/BlockProps
23
23
  */
24
24
  export interface BlockProps extends ColorVariants, SpaceVariants, TypographyVariants, WidthVariants, OptionalChildProps {
25
+ /**
26
+ * Element this `<Block>` renders as, e.g. "header" to output a "<header>"
27
+ * @default "div"
28
+ */
25
29
  as?: BlockElement | undefined;
26
30
  }
27
31
 
@@ -54,6 +58,6 @@ export function getBlockClass(variants: BlockProps): string {
54
58
  * @example <Block as="aside" width="narrow"><Paragraph>Sidebar</Paragraph></Block>
55
59
  * @see https://dhoulb.github.io/shelving/ui/block/Block/Block
56
60
  */
57
- export function Block({ as: Component = "div", children, ...props }: BlockProps): ReactElement {
58
- return <Component className={getBlockClass(props)}>{children}</Component>;
61
+ export function Block({ as: Element = "div", children, ...props }: BlockProps): ReactElement {
62
+ return <Element className={getBlockClass(props)}>{children}</Element>;
59
63
  }
@@ -2,7 +2,7 @@
2
2
  @import "../style/Space.module.css";
3
3
  @import "../style/Stroke.module.css";
4
4
  @import "../style/Tint.module.css";
5
- @import "../style/Weight.module.css";
5
+ @import "../style/Typography.module.css";
6
6
 
7
7
  @layer components {
8
8
  .blockquote,
@@ -1,7 +1,7 @@
1
1
  @import "../style/layers.css";
2
- @import "../style/Size.module.css";
2
+ @import "../style/Typography.module.css";
3
3
  @import "../style/Space.module.css";
4
- @import "../style/Weight.module.css";
4
+ @import "../style/Color.module.css";
5
5
 
6
6
  @layer components {
7
7
  .caption,
@@ -6,15 +6,22 @@ import { type SpaceVariants } from "../style/Space.js";
6
6
  import { type StatusVariants } from "../style/Status.js";
7
7
  import { type TypographyVariants } from "../style/Typography.js";
8
8
  import { type WidthVariants } from "../style/Width.js";
9
+ import type { BlockElement } from "./Block.js";
9
10
  /**
10
11
  * Props for `Card` — combines `ClickableProps` (for navigable cards) with colour, status, padding, space, typography, and width variants.
11
12
  *
12
13
  * @see https://dhoulb.github.io/shelving/ui/block/Card/CardProps
13
14
  */
14
15
  export interface CardProps extends ClickableProps, ColorVariants, StatusVariants, PaddingVariants, SpaceVariants, TypographyVariants, WidthVariants {
16
+ /**
17
+ * Element this `<Card>` renders as, e.g. "header" to output a "<header>"
18
+ * @default "article"
19
+ */
20
+ as?: BlockElement | undefined;
15
21
  }
16
22
  /**
17
- * Cards are boxed areas for content to sit within — rendered as `<article>` since each card represents a self-contained piece of content.
23
+ * Cards are boxed areas for content to sit within.
24
+ * - Rendered as `<article>` since each card represents a self-contained piece of content (override with `props.as`).
18
25
  * - When `href` or `onClick` is set the card becomes navigable: a stretched overlay `<a>` / `<button>` covers the entire card while the children render normally inside.
19
26
  * - Real interactive elements inside the card (e.g. inline `<a>` links) stay clickable thanks to `position: relative; z-index: 2` rules in the stylesheet.
20
27
  * - Accepts a `status` colour and raw `ColorProps` — the card styles the box; lay out its contents however the use case needs.
@@ -25,4 +32,4 @@ export interface CardProps extends ClickableProps, ColorVariants, StatusVariants
25
32
  * @example <Card status="error"><Subheading>Not found</Subheading></Card>
26
33
  * @see https://dhoulb.github.io/shelving/ui/block/Card/Card
27
34
  */
28
- export declare function Card({ children, href, onClick, title, ...props }: CardProps): ReactElement;
35
+ export declare function Card({ as: Element, children, href, onClick, title, ...props }: CardProps): ReactElement;
package/ui/block/Card.js CHANGED
@@ -9,7 +9,8 @@ import { getWidthClass } from "../style/Width.js";
9
9
  import { getClass, getModuleClass } from "../util/css.js";
10
10
  import CARD_CSS from "./Card.module.css";
11
11
  /**
12
- * Cards are boxed areas for content to sit within — rendered as `<article>` since each card represents a self-contained piece of content.
12
+ * Cards are boxed areas for content to sit within.
13
+ * - Rendered as `<article>` since each card represents a self-contained piece of content (override with `props.as`).
13
14
  * - When `href` or `onClick` is set the card becomes navigable: a stretched overlay `<a>` / `<button>` covers the entire card while the children render normally inside.
14
15
  * - Real interactive elements inside the card (e.g. inline `<a>` links) stay clickable thanks to `position: relative; z-index: 2` rules in the stylesheet.
15
16
  * - Accepts a `status` colour and raw `ColorProps` — the card styles the box; lay out its contents however the use case needs.
@@ -20,7 +21,7 @@ import CARD_CSS from "./Card.module.css";
20
21
  * @example <Card status="error"><Subheading>Not found</Subheading></Card>
21
22
  * @see https://dhoulb.github.io/shelving/ui/block/Card/Card
22
23
  */
23
- export function Card({ children, href, onClick, title = "Open", ...props }) {
24
+ export function Card({ as: Element = "article", children, href, onClick, title = "Open", ...props }) {
24
25
  const overlay = (href || onClick) && (_jsx(Clickable, { title: title, href: href, onClick: onClick, ...props, className: getModuleClass(CARD_CSS, "overlay") }));
25
- return (_jsxs("article", { className: getClass(getModuleClass(CARD_CSS, "card"), getStatusClass(props), getColorClass(props), getPaddingClass(props), getSpaceClass(props), getTypographyClass(props), getWidthClass(props)), children: [overlay, overlay ? _jsx("div", { children: children }) : children] }));
26
+ return (_jsxs(Element, { className: getClass(getModuleClass(CARD_CSS, "card"), getStatusClass(props), getColorClass(props), getPaddingClass(props), getSpaceClass(props), getTypographyClass(props), getWidthClass(props)), children: [overlay, overlay ? _jsx("div", { children: children }) : children] }));
26
27
  }
package/ui/block/Card.tsx CHANGED
@@ -7,6 +7,7 @@ import { getStatusClass, type StatusVariants } from "../style/Status.js";
7
7
  import { getTypographyClass, type TypographyVariants } from "../style/Typography.js";
8
8
  import { getWidthClass, type WidthVariants } from "../style/Width.js";
9
9
  import { getClass, getModuleClass } from "../util/css.js";
10
+ import type { BlockElement } from "./Block.js";
10
11
  import CARD_CSS from "./Card.module.css";
11
12
 
12
13
  /**
@@ -21,10 +22,17 @@ export interface CardProps
21
22
  PaddingVariants,
22
23
  SpaceVariants,
23
24
  TypographyVariants,
24
- WidthVariants {}
25
+ WidthVariants {
26
+ /**
27
+ * Element this `<Card>` renders as, e.g. "header" to output a "<header>"
28
+ * @default "article"
29
+ */
30
+ as?: BlockElement | undefined;
31
+ }
25
32
 
26
33
  /**
27
- * Cards are boxed areas for content to sit within — rendered as `<article>` since each card represents a self-contained piece of content.
34
+ * Cards are boxed areas for content to sit within.
35
+ * - Rendered as `<article>` since each card represents a self-contained piece of content (override with `props.as`).
28
36
  * - When `href` or `onClick` is set the card becomes navigable: a stretched overlay `<a>` / `<button>` covers the entire card while the children render normally inside.
29
37
  * - Real interactive elements inside the card (e.g. inline `<a>` links) stay clickable thanks to `position: relative; z-index: 2` rules in the stylesheet.
30
38
  * - Accepts a `status` colour and raw `ColorProps` — the card styles the box; lay out its contents however the use case needs.
@@ -35,12 +43,12 @@ export interface CardProps
35
43
  * @example <Card status="error"><Subheading>Not found</Subheading></Card>
36
44
  * @see https://dhoulb.github.io/shelving/ui/block/Card/Card
37
45
  */
38
- export function Card({ children, href, onClick, title = "Open", ...props }: CardProps): ReactElement {
46
+ export function Card({ as: Element = "article", children, href, onClick, title = "Open", ...props }: CardProps): ReactElement {
39
47
  const overlay = (href || onClick) && (
40
48
  <Clickable title={title} href={href} onClick={onClick} {...props} className={getModuleClass(CARD_CSS, "overlay")} />
41
49
  );
42
50
  return (
43
- <article
51
+ <Element
44
52
  className={getClass(
45
53
  getModuleClass(CARD_CSS, "card"),
46
54
  getStatusClass(props),
@@ -53,6 +61,6 @@ export function Card({ children, href, onClick, title = "Open", ...props }: Card
53
61
  >
54
62
  {overlay}
55
63
  {overlay ? <div>{children}</div> : children}
56
- </article>
64
+ </Element>
57
65
  );
58
66
  }
@@ -1,9 +1,8 @@
1
1
  @import "../style/layers.css";
2
- @import "../style/Font.module.css";
3
- @import "../style/Size.module.css";
4
2
  @import "../style/Space.module.css";
5
- @import "../style/Tint.module.css";
6
- @import "../style/Weight.module.css";
3
+ @import "../style/Typography.module.css";
4
+ @import "../style/Color.module.css";
5
+ @import "../style/Gap.module.css";
7
6
 
8
7
  @layer components {
9
8
  .definitions,
@@ -1,7 +1,7 @@
1
1
  @import "../style/layers.css";
2
2
  @import "../style/Space.module.css";
3
3
  @import "../style/Stroke.module.css";
4
- @import "../style/Tint.module.css";
4
+ @import "../style/Color.module.css";
5
5
 
6
6
  @layer components {
7
7
  .divider,
@@ -1,8 +1,7 @@
1
1
  @import "../style/layers.css";
2
- @import "../style/Font.module.css";
3
- @import "../style/Size.module.css";
2
+ @import "../style/Color.module.css";
4
3
  @import "../style/Space.module.css";
5
- @import "../style/Weight.module.css";
4
+ @import "../style/Typography.module.css";
6
5
 
7
6
  @layer components {
8
7
  .heading,
@@ -1,9 +1,7 @@
1
1
  @import "../style/layers.css";
2
- @import "../style/Font.module.css";
3
- @import "../style/Size.module.css";
4
2
  @import "../style/Space.module.css";
5
- @import "../style/Tint.module.css";
6
- @import "../style/Weight.module.css";
3
+ @import "../style/Color.module.css";
4
+ @import "../style/Typography.module.css";
7
5
 
8
6
  @layer components {
9
7
  .label {
@@ -1,6 +1,8 @@
1
1
  @import "../style/layers.css";
2
2
  @import "../style/Space.module.css";
3
- @import "../style/Tint.module.css";
3
+ @import "../style/Color.module.css";
4
+ @import "../style/Gap.module.css";
5
+ @import "../style/Typography.module.css";
4
6
 
5
7
  @layer components {
6
8
  .unordered,
package/ui/block/List.tsx CHANGED
@@ -7,7 +7,6 @@ import { getClass, getModuleClass } from "../util/css.js";
7
7
  import LIST_CSS from "./List.module.css";
8
8
 
9
9
  const LIST_ORDERED_CLASS = getModuleClass(LIST_CSS, "ordered");
10
-
11
10
  const LIST_UNORDERED_CLASS = getModuleClass(LIST_CSS, "unordered");
12
11
 
13
12
  /**
@@ -4,18 +4,18 @@ import { type PaddingVariants } from "../style/Padding.js";
4
4
  import { type StatusVariants } from "../style/Status.js";
5
5
  import { type TypographyVariants } from "../style/Typography.js";
6
6
  import type { OptionalChildProps } from "../util/props.js";
7
- /**
8
- * Allowed semantic elements for a `<Panel>`.
9
- *
10
- * @see https://dhoulb.github.io/shelving/ui/block/Panel/PanelElement
11
- */
12
- export type PanelElement = "section" | "header" | "footer" | "nav" | "aside" | "article" | "div";
7
+ import type { BlockElement } from "./Block.js";
13
8
  /**
14
9
  * Props for `Panel` — colour, status, and typography variants plus optional `children`.
15
10
  *
16
11
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/PanelProps
17
12
  */
18
13
  export interface PanelProps extends ColorVariants, PaddingVariants, StatusVariants, TypographyVariants, OptionalChildProps {
14
+ /**
15
+ * Element this `<Panel>` renders as, e.g. "header" to output a "<header>"
16
+ * @default "section"
17
+ */
18
+ as?: BlockElement | undefined;
19
19
  }
20
20
  /**
21
21
  * Full-width vertical region that paints the current surface colour. Use to break a page into stacked
@@ -30,4 +30,4 @@ export interface PanelProps extends ColorVariants, PaddingVariants, StatusVarian
30
30
  * @example <Panel padding="xlarge" color="primary"><Title>Welcome</Title></Panel>
31
31
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/Panel
32
32
  */
33
- export declare function Panel({ children, ...props }: PanelProps): ReactElement;
33
+ export declare function Panel({ as: Element, children, ...props }: PanelProps): ReactElement;
package/ui/block/Panel.js CHANGED
@@ -19,7 +19,7 @@ const PANEL_CLASS = getModuleClass(PANEL_CSS, "panel");
19
19
  * @example <Panel padding="xlarge" color="primary"><Title>Welcome</Title></Panel>
20
20
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/Panel
21
21
  */
22
- export function Panel({ children, ...props }) {
23
- return (_jsx("div", { className: getClass(PANEL_CLASS, //
22
+ export function Panel({ as: Element = "section", children, ...props }) {
23
+ return (_jsx(Element, { className: getClass(PANEL_CLASS, //
24
24
  getStatusClass(props), getColorClass(props), getPaddingClass(props), getTypographyClass(props)), children: children }));
25
25
  }
@@ -1,6 +1,9 @@
1
1
  @import "../style/layers.css";
2
2
  @import "../style/Stroke.module.css";
3
- @import "../style/Tint.module.css";
3
+ @import "../style/Typography.module.css";
4
+ @import "../style/Status.module.css";
5
+ @import "../style/Color.module.css";
6
+ @import "../style/Padding.module.css";
4
7
 
5
8
  /*
6
9
  * Full-width vertical region that paints the current surface. Panels always have zero block-space
@@ -5,23 +5,23 @@ import { getStatusClass, type StatusVariants } from "../style/Status.js";
5
5
  import { getTypographyClass, type TypographyVariants } from "../style/Typography.js";
6
6
  import { getClass, getModuleClass } from "../util/css.js";
7
7
  import type { OptionalChildProps } from "../util/props.js";
8
+ import type { BlockElement } from "./Block.js";
8
9
  import PANEL_CSS from "./Panel.module.css";
9
10
 
10
11
  const PANEL_CLASS = getModuleClass(PANEL_CSS, "panel");
11
12
 
12
- /**
13
- * Allowed semantic elements for a `<Panel>`.
14
- *
15
- * @see https://dhoulb.github.io/shelving/ui/block/Panel/PanelElement
16
- */
17
- export type PanelElement = "section" | "header" | "footer" | "nav" | "aside" | "article" | "div";
18
-
19
13
  /**
20
14
  * Props for `Panel` — colour, status, and typography variants plus optional `children`.
21
15
  *
22
16
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/PanelProps
23
17
  */
24
- export interface PanelProps extends ColorVariants, PaddingVariants, StatusVariants, TypographyVariants, OptionalChildProps {}
18
+ export interface PanelProps extends ColorVariants, PaddingVariants, StatusVariants, TypographyVariants, OptionalChildProps {
19
+ /**
20
+ * Element this `<Panel>` renders as, e.g. "header" to output a "<header>"
21
+ * @default "section"
22
+ */
23
+ as?: BlockElement | undefined;
24
+ }
25
25
 
26
26
  /**
27
27
  * Full-width vertical region that paints the current surface colour. Use to break a page into stacked
@@ -36,9 +36,9 @@ export interface PanelProps extends ColorVariants, PaddingVariants, StatusVarian
36
36
  * @example <Panel padding="xlarge" color="primary"><Title>Welcome</Title></Panel>
37
37
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/Panel
38
38
  */
39
- export function Panel({ children, ...props }: PanelProps): ReactElement {
39
+ export function Panel({ as: Element = "section", children, ...props }: PanelProps): ReactElement {
40
40
  return (
41
- <div
41
+ <Element
42
42
  className={getClass(
43
43
  PANEL_CLASS, //
44
44
  getStatusClass(props),
@@ -48,6 +48,6 @@ export function Panel({ children, ...props }: PanelProps): ReactElement {
48
48
  )}
49
49
  >
50
50
  {children}
51
- </div>
51
+ </Element>
52
52
  );
53
53
  }
@@ -1,5 +1,7 @@
1
1
  @import "../style/layers.css";
2
2
  @import "../style/Space.module.css";
3
+ @import "../style/Color.module.css";
4
+ @import "../style/Typography.module.css";
3
5
 
4
6
  @layer components {
5
7
  .paragraph,
@@ -1,13 +1,11 @@
1
1
  @import "../style/layers.css";
2
2
  @import "../style/Color.module.css";
3
3
  @import "../style/Duration.module.css";
4
- @import "../style/Font.module.css";
5
4
  @import "../style/Radius.module.css";
6
- @import "../style/Size.module.css";
7
5
  @import "../style/Space.module.css";
8
6
  @import "../style/Stroke.module.css";
9
- @import "../style/Tint.module.css";
10
- @import "../style/Weight.module.css";
7
+ @import "../style/Width.module.css";
8
+ @import "../style/Typography.module.css";
11
9
 
12
10
  @layer components {
13
11
  .preformatted,
@@ -4,19 +4,18 @@ import { type SpaceVariants } from "../style/Space.js";
4
4
  import { type TypographyVariants } from "../style/Typography.js";
5
5
  import { type WidthVariants } from "../style/Width.js";
6
6
  import type { OptionalChildProps } from "../util/props.js";
7
- /**
8
- * Semantic element names a `Section` may render as via its `as` prop.
9
- *
10
- * @see https://dhoulb.github.io/shelving/ui/block/Section/SectionElement
11
- */
12
- export type SectionElement = "section" | "header" | "footer" | "nav" | "aside" | "figure";
7
+ import type { BlockElement } from "./Block.js";
13
8
  /**
14
9
  * Props for `Section` and its semantic siblings — colour, space, typography, and width variants plus an optional `as` element override.
15
10
  *
16
11
  * @see https://dhoulb.github.io/shelving/ui/block/Section/SectionProps
17
12
  */
18
13
  export interface SectionProps extends ColorVariants, SpaceVariants, TypographyVariants, WidthVariants, OptionalChildProps {
19
- as?: SectionElement | undefined;
14
+ /**
15
+ * Element this `<Section>` renders as, e.g. "header" to output a "<header>"
16
+ * @default "section"
17
+ */
18
+ as?: BlockElement | undefined;
20
19
  }
21
20
  /**
22
21
  * Get the combined `className` string for a section from its styling variants.
@@ -30,7 +29,7 @@ export interface SectionProps extends ColorVariants, SpaceVariants, TypographyVa
30
29
  */
31
30
  export declare function getSectionClass(variants: SectionProps): string;
32
31
  /**
33
- * `<section>` block with block-level spacing.
32
+ * `<section>` block with section-level spacing.
34
33
  * - Pass `as` to render a different semantic element.
35
34
  *
36
35
  * @kind component
@@ -38,49 +37,41 @@ export declare function getSectionClass(variants: SectionProps): string;
38
37
  * @example <Section><Heading>About</Heading></Section>
39
38
  * @see https://dhoulb.github.io/shelving/ui/block/Section/Section
40
39
  */
41
- export declare function Section(props: SectionProps): ReactElement;
40
+ export declare function Section({ as: Element, children, ...variants }: SectionProps): ReactElement;
42
41
  /**
43
- * `<header>` block with block-level spacing.
42
+ * `<header>` block with section-level spacing.
44
43
  *
45
44
  * @kind component
46
45
  * @returns Rendered `<header>` element.
47
46
  * @example <Header><Title>Welcome</Title></Header>
48
47
  * @see https://dhoulb.github.io/shelving/ui/block/Section/Header
49
48
  */
50
- export declare function Header(props: SectionProps): ReactElement;
49
+ export declare function Header({ as: Element, children, ...variants }: SectionProps): ReactElement;
51
50
  /**
52
- * `<footer>` block with block-level spacing.
51
+ * `<footer>` block with section-level spacing.
53
52
  *
54
53
  * @kind component
55
54
  * @returns Rendered `<footer>` element.
56
55
  * @example <Footer><Small>© 2026</Small></Footer>
57
56
  * @see https://dhoulb.github.io/shelving/ui/block/Section/Footer
58
57
  */
59
- export declare function Footer(props: SectionProps): ReactElement;
60
- /**
61
- * `<nav>` block with block-level spacing.
62
- *
63
- * @kind component
64
- * @returns Rendered `<nav>` element.
65
- * @example <Nav><Link href="/">Home</Link></Nav>
66
- * @see https://dhoulb.github.io/shelving/ui/block/Section/Nav
67
- */
68
- export declare function Nav(props: SectionProps): ReactElement;
58
+ export declare function Footer({ as: Element, children, ...variants }: SectionProps): ReactElement;
69
59
  /**
70
- * `<aside>` block with block-level spacing.
60
+ * `<article>` block with section-level spacing.
61
+ * - Use a `<Card>` instead to display boxed content.
71
62
  *
72
63
  * @kind component
73
- * @returns Rendered `<aside>` element.
74
- * @example <Aside width="narrow"><Paragraph>Sidebar</Paragraph></Aside>
75
- * @see https://dhoulb.github.io/shelving/ui/block/Section/Aside
64
+ * @returns Rendered `<article>` element.
65
+ * @see https://dhoulb.github.io/shelving/ui/block/Section/Article
76
66
  */
77
- export declare function Aside(props: SectionProps): ReactElement;
67
+ export declare function Article({ as: Element, children, ...variants }: SectionProps): ReactElement;
78
68
  /**
79
- * `<figure>` block with block-level spacing. Pair with `<Caption>` for `<figcaption>` content.
69
+ * `<figure>` block with section-level spacing.
70
+ * - Pair with `<Caption>` for `<figcaption>` content.
80
71
  *
81
72
  * @kind component
82
73
  * @returns Rendered `<figure>` element.
83
74
  * @example <Figure><Image src="/cat.jpg" /><Caption>A cat</Caption></Figure>
84
75
  * @see https://dhoulb.github.io/shelving/ui/block/Section/Figure
85
76
  */
86
- export declare function Figure(props: SectionProps): ReactElement;
77
+ export declare function Figure({ as: Element, children, ...variants }: SectionProps): ReactElement;