shelving 1.242.1 → 1.243.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.
Files changed (45) hide show
  1. package/extract/TypescriptExtractor.js +7 -5
  2. package/package.json +1 -1
  3. package/ui/README.md +1 -1
  4. package/ui/app/App.md +1 -1
  5. package/ui/block/Block.d.ts +1 -1
  6. package/ui/block/Block.js +1 -1
  7. package/ui/block/Block.tsx +1 -1
  8. package/ui/block/Image.d.ts +1 -1
  9. package/ui/block/Image.js +1 -1
  10. package/ui/block/Image.tsx +1 -1
  11. package/ui/block/Panel.d.ts +1 -1
  12. package/ui/block/Panel.js +1 -1
  13. package/ui/block/Panel.md +3 -3
  14. package/ui/block/Panel.tsx +1 -1
  15. package/ui/block/Section.d.ts +1 -1
  16. package/ui/block/Section.js +1 -1
  17. package/ui/block/Section.md +4 -4
  18. package/ui/block/Section.module.css +2 -1
  19. package/ui/block/Section.tsx +1 -1
  20. package/ui/block/Table.js +1 -1
  21. package/ui/block/Table.tsx +1 -1
  22. package/ui/block/Title.md +1 -1
  23. package/ui/docs/DocumentationHomePage.js +1 -1
  24. package/ui/docs/DocumentationHomePage.tsx +2 -2
  25. package/ui/docs/DocumentationPage.d.ts +1 -1
  26. package/ui/docs/DocumentationPage.js +4 -5
  27. package/ui/docs/DocumentationPage.md +1 -1
  28. package/ui/docs/DocumentationPage.test.tsx +26 -0
  29. package/ui/docs/DocumentationPage.tsx +77 -47
  30. package/ui/form/Input.d.ts +2 -2
  31. package/ui/form/Input.tsx +2 -2
  32. package/ui/layout/CenteredLayout.md +1 -1
  33. package/ui/style/Scroll.d.ts +3 -4
  34. package/ui/style/Scroll.js +1 -1
  35. package/ui/style/Scroll.tsx +3 -4
  36. package/ui/style/Width.d.ts +17 -20
  37. package/ui/style/Width.js +6 -6
  38. package/ui/style/Width.module.css +7 -2
  39. package/ui/style/Width.tsx +18 -21
  40. package/ui/style/getWidthClass.md +5 -4
  41. package/ui/tree/TreeIndexPage.js +1 -1
  42. package/ui/tree/TreeIndexPage.tsx +4 -4
  43. package/ui/tree/TreePage.js +1 -1
  44. package/ui/tree/TreePage.tsx +3 -3
  45. package/util/tree.d.ts +2 -0
@@ -56,9 +56,9 @@ function _mergeOverloads(existing, next) {
56
56
  // Keep first content encountered; fill in if `existing` had none.
57
57
  content: a.content ?? b.content,
58
58
  // Append incoming entries, skipping any already present (by field identity), preserving insertion order.
59
- // Identity: signatures/examples/throws by rendered string; params by (name, type, description, optional); returns by (type, description).
59
+ // Identity: signatures/examples/throws by rendered string; params by (name, type, description, optional, default); returns by (type, description).
60
60
  signatures: _concatUnique(a.signatures, b.signatures, s => s),
61
- params: _concatUnique(a.params, b.params, p => `${p.name}\0${p.type}\0${p.description}\0${p.optional}`),
61
+ params: _concatUnique(a.params, b.params, p => `${p.name}\0${p.type}\0${p.description}\0${p.optional}\0${p.default}`),
62
62
  returns: _concatUnique(a.returns, b.returns, r => `${r.type}\0${r.description}`),
63
63
  throws: _concatUnique(a.throws, b.throws, t => `${t.type}\0${t.description}`),
64
64
  examples: _concatUnique(a.examples, b.examples, e => e.description ?? ""),
@@ -260,7 +260,8 @@ function _getParams(statement, source, jsDocParams) {
260
260
  const type = p.type?.getText(source);
261
261
  const optional = !!p.questionToken || !!p.initializer;
262
262
  const description = jsDocParams?.find(d => d.name === name)?.description;
263
- return { name, type, description, optional };
263
+ const def = p.initializer?.getText(source);
264
+ return { name, type, description, optional, default: def };
264
265
  });
265
266
  return params.length ? params : undefined;
266
267
  }
@@ -279,9 +280,10 @@ function _getConstructorParams(statement, source, classJsDocParams) {
279
280
  const optional = !!p.questionToken || !!p.initializer;
280
281
  // Constructor-level `@param` wins over the class-level `@param` on collision.
281
282
  const description = ctorJsDocParams?.find(d => d.name === name)?.description ?? classJsDocParams?.find(d => d.name === name)?.description;
282
- return { name, type, description, optional };
283
+ const def = p.initializer?.getText(source);
284
+ return { name, type, description, optional, default: def };
283
285
  });
284
- params = _concatUnique(params, next, p => `${p.name}\0${p.type}\0${p.description}\0${p.optional}`);
286
+ params = _concatUnique(params, next, p => `${p.name}\0${p.type}\0${p.description}\0${p.optional}\0${p.default}`);
285
287
  }
286
288
  return params?.length ? params : undefined;
287
289
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.242.1",
3
+ "version": "1.243.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
package/ui/README.md CHANGED
@@ -10,7 +10,7 @@ The `ui` module exists so an app never hand-rolls the same form field, card, or
10
10
 
11
11
  A few conventions run through every component:
12
12
 
13
- - **Styling props are for one-off overrides.** Visual options are props on the component — enumerated props for the scales (`color="red"`, `size="large"`, `space="none"`) and boolean props for on/off variants (`<Button strong>`, `<Section narrow>`, `<Flex wrap>`). Each maps to a class in a CSS Module. Reach for them when a component needs to look different in *one place* — the way the docs site tints its accents purple — not as the way to dress a whole app. You never pass `style` or raw `className`.
13
+ - **Styling props are for one-off overrides.** Visual options are props on the component — enumerated props for the scales (`color="red"`, `size="large"`, `space="none"`, `width="narrow"`) and boolean props for on/off variants (`<Button strong>`, `<Title center>`, `<Flex wrap>`). Each maps to a class in a CSS Module. Reach for them when a component needs to look different in *one place* — the way the docs site tints its accents purple — not as the way to dress a whole app. You never pass `style` or raw `className`.
14
14
  - **Composition.** Higher-level components — a `*Page`, a `*Card` — take their identity from library components like [`Card`](/ui/Card), [`Section`](/ui/Section), [`Button`](/ui/Button), and [`Tag`](/ui/Tag) rather than shipping their own styling.
15
15
  - **Sentence case.** Titles, headings, and button labels capitalise only the first word.
16
16
  - **Theme with CSS.** An app-wide custom look is a CSS file, not a wall of props. Write a `theme.css` that overrides the base design-token variables (and, where needed, per-component hooks) at `:root`, and import it after the library styles. The recommended workflow is to spend time tuning those variables to match your design — see [Theming](#theming) below.
package/ui/app/App.md CHANGED
@@ -15,7 +15,7 @@ function HelloApp() {
15
15
  return (
16
16
  <App app="My app">
17
17
  <CenteredLayout>
18
- <Section narrow>
18
+ <Section width="narrow">
19
19
  <Title>Hello</Title>
20
20
  <Paragraph>Welcome to the app.</Paragraph>
21
21
  </Section>
@@ -34,7 +34,7 @@ export declare function getBlockClass(variants: BlockProps): string;
34
34
  * - Pass `as` to render a different semantic element (`section`, `header`, `footer`, `nav`, `aside`, `figure`).
35
35
  *
36
36
  * @example <Block><Paragraph>Hello</Paragraph></Block>
37
- * @example <Block as="aside" narrow><Paragraph>Sidebar</Paragraph></Block>
37
+ * @example <Block as="aside" width="narrow"><Paragraph>Sidebar</Paragraph></Block>
38
38
  * @see https://dhoulb.github.io/shelving/ui/block/Block/Block
39
39
  */
40
40
  export declare function Block({ as: Component, children, ...props }: BlockProps): ReactElement;
package/ui/block/Block.js CHANGED
@@ -25,7 +25,7 @@ export function getBlockClass(variants) {
25
25
  * - Pass `as` to render a different semantic element (`section`, `header`, `footer`, `nav`, `aside`, `figure`).
26
26
  *
27
27
  * @example <Block><Paragraph>Hello</Paragraph></Block>
28
- * @example <Block as="aside" narrow><Paragraph>Sidebar</Paragraph></Block>
28
+ * @example <Block as="aside" width="narrow"><Paragraph>Sidebar</Paragraph></Block>
29
29
  * @see https://dhoulb.github.io/shelving/ui/block/Block/Block
30
30
  */
31
31
  export function Block({ as: Component = "div", children, ...props }) {
@@ -52,7 +52,7 @@ export function getBlockClass(variants: BlockProps): string {
52
52
  * - Pass `as` to render a different semantic element (`section`, `header`, `footer`, `nav`, `aside`, `figure`).
53
53
  *
54
54
  * @example <Block><Paragraph>Hello</Paragraph></Block>
55
- * @example <Block as="aside" narrow><Paragraph>Sidebar</Paragraph></Block>
55
+ * @example <Block as="aside" width="narrow"><Paragraph>Sidebar</Paragraph></Block>
56
56
  * @see https://dhoulb.github.io/shelving/ui/block/Block/Block
57
57
  */
58
58
  export function Block({ as: Component = "div", children, ...props }: BlockProps): ReactElement {
@@ -15,7 +15,7 @@ export interface ImageProps extends SpaceVariants, WidthVariants {
15
15
  *
16
16
  * @param props The image `src`, optional `alt` text, plus space and width variants.
17
17
  * @returns Rendered `<img>` element.
18
- * @example <Image src="/logo.png" alt="Logo" width="medium" />
18
+ * @example <Image src="/logo.png" alt="Logo" width="narrow" />
19
19
  * @see https://dhoulb.github.io/shelving/ui/block/Image/Image
20
20
  */
21
21
  export declare function Image({ src, alt, ...variants }: ImageProps): ReactElement;
package/ui/block/Image.js CHANGED
@@ -9,7 +9,7 @@ const IMAGE_CLASS = getModuleClass(styles, "image");
9
9
  *
10
10
  * @param props The image `src`, optional `alt` text, plus space and width variants.
11
11
  * @returns Rendered `<img>` element.
12
- * @example <Image src="/logo.png" alt="Logo" width="medium" />
12
+ * @example <Image src="/logo.png" alt="Logo" width="narrow" />
13
13
  * @see https://dhoulb.github.io/shelving/ui/block/Image/Image
14
14
  */
15
15
  export function Image({ src, alt, ...variants }) {
@@ -21,7 +21,7 @@ export interface ImageProps extends SpaceVariants, WidthVariants {
21
21
  *
22
22
  * @param props The image `src`, optional `alt` text, plus space and width variants.
23
23
  * @returns Rendered `<img>` element.
24
- * @example <Image src="/logo.png" alt="Logo" width="medium" />
24
+ * @example <Image src="/logo.png" alt="Logo" width="narrow" />
25
25
  * @see https://dhoulb.github.io/shelving/ui/block/Image/Image
26
26
  */
27
27
  export function Image({ src, alt, ...variants }: ImageProps): ReactElement {
@@ -27,7 +27,7 @@ export interface PanelProps extends ColorVariants, PaddingVariants, StatusVarian
27
27
  * @kind component
28
28
  * @param props Colour, padding, status, and typography variants plus `children`.
29
29
  * @returns Rendered full-width panel region.
30
- * @example <Panel><Block narrow><Title>Welcome</Title></Block></Panel>
30
+ * @example <Panel><Block width="narrow"><Title>Welcome</Title></Block></Panel>
31
31
  * @example <Panel padding="xlarge" color="primary"><Title>Welcome</Title></Panel>
32
32
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/Panel
33
33
  */
package/ui/block/Panel.js CHANGED
@@ -16,7 +16,7 @@ const PANEL_CLASS = getModuleClass(PANEL_CSS, "panel");
16
16
  * @kind component
17
17
  * @param props Colour, padding, status, and typography variants plus `children`.
18
18
  * @returns Rendered full-width panel region.
19
- * @example <Panel><Block narrow><Title>Welcome</Title></Block></Panel>
19
+ * @example <Panel><Block width="narrow"><Title>Welcome</Title></Block></Panel>
20
20
  * @example <Panel padding="xlarge" color="primary"><Title>Welcome</Title></Panel>
21
21
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/Panel
22
22
  */
package/ui/block/Panel.md CHANGED
@@ -4,7 +4,7 @@ A full-width vertical region that paints the current surface colour. Use panels
4
4
 
5
5
  **Things to know:**
6
6
 
7
- - A panel always spans the full width of its container. To constrain the content inside, compose a [`Block`](/ui/Block) `narrow` (or `wide`) within it.
7
+ - A panel always spans the full width of its container. To constrain the content inside, compose a [`Block`](/ui/Block) `width="narrow"` (or `width="wide"`) within it.
8
8
  - Block margin is always zero so panels stack flush; control the vertical breathing room with the `padding` variant (`<Panel padding="large">`, `<Panel padding="none">`). Inline padding is fixed.
9
9
  - `color=` / `status=` move the tint anchor for the whole panel scope, so the surface, border, and text re-derive together and cascade into nested content.
10
10
  - The top and bottom borders are dropped on the first and last panel so the page doesn't gain stray edge lines.
@@ -17,12 +17,12 @@ A full-width vertical region that paints the current surface colour. Use panels
17
17
  import { Panel, Block, Title, Paragraph } from "shelving/ui";
18
18
 
19
19
  <Panel as="header" color="primary">
20
- <Block narrow>
20
+ <Block width="narrow">
21
21
  <Title>Welcome</Title>
22
22
  </Block>
23
23
  </Panel>
24
24
  <Panel padding="large">
25
- <Block narrow>
25
+ <Block width="narrow">
26
26
  <Paragraph>Each panel is a full-width band; the inner block constrains the content.</Paragraph>
27
27
  </Block>
28
28
  </Panel>
@@ -33,7 +33,7 @@ export interface PanelProps extends ColorVariants, PaddingVariants, StatusVarian
33
33
  * @kind component
34
34
  * @param props Colour, padding, status, and typography variants plus `children`.
35
35
  * @returns Rendered full-width panel region.
36
- * @example <Panel><Block narrow><Title>Welcome</Title></Block></Panel>
36
+ * @example <Panel><Block width="narrow"><Title>Welcome</Title></Block></Panel>
37
37
  * @example <Panel padding="xlarge" color="primary"><Title>Welcome</Title></Panel>
38
38
  * @see https://dhoulb.github.io/shelving/ui/block/Panel/Panel
39
39
  */
@@ -76,7 +76,7 @@ export declare function Nav(props: SectionProps): ReactElement;
76
76
  * @kind component
77
77
  * @param props Colour, space, typography, and width variants plus optional `as` override and `children`.
78
78
  * @returns Rendered `<aside>` element.
79
- * @example <Aside narrow><Paragraph>Sidebar</Paragraph></Aside>
79
+ * @example <Aside width="narrow"><Paragraph>Sidebar</Paragraph></Aside>
80
80
  * @see https://dhoulb.github.io/shelving/ui/block/Section/Aside
81
81
  */
82
82
  export declare function Aside(props: SectionProps): ReactElement;
@@ -77,7 +77,7 @@ export function Nav(props) {
77
77
  * @kind component
78
78
  * @param props Colour, space, typography, and width variants plus optional `as` override and `children`.
79
79
  * @returns Rendered `<aside>` element.
80
- * @example <Aside narrow><Paragraph>Sidebar</Paragraph></Aside>
80
+ * @example <Aside width="narrow"><Paragraph>Sidebar</Paragraph></Aside>
81
81
  * @see https://dhoulb.github.io/shelving/ui/block/Section/Aside
82
82
  */
83
83
  export function Aside(props) {
@@ -6,7 +6,7 @@ A landmark content region — renders a `<section>` with block-level spacing and
6
6
 
7
7
  - Pick the component whose HTML element matches the semantic meaning rather than reaching for a generic [`Block`](/ui/Block). `<Section>` is a `<section>`, `<Nav>` a `<nav>`, `<Figure>` a `<figure>`, and so on.
8
8
  - Every section centres its content and caps the line length so text never touches the viewport edges. Nested sections relax that cap so they can fill their parent.
9
- - Pass `narrow` / `wide` / `full` to constrain or unconstrain the width, and the usual `color` / `space` / typography variants to retint and respace.
9
+ - Sections default to the `--width-normal` content width, so most of the time you set no width at all. Pass `width="narrow"` / `"wide"` / `"full"` (or `"fit"`) to change that, and the usual `color` / `space` / typography variants to retint and respace.
10
10
  - Pair [`Figure`](/ui/Section) with [`Caption`](/ui/Caption) for a `<figure>` / `<figcaption>` pair.
11
11
 
12
12
  ## Usage
@@ -16,7 +16,7 @@ A landmark content region — renders a `<section>` with block-level spacing and
16
16
  ```tsx
17
17
  import { Section, Heading, Definitions } from "shelving/ui";
18
18
 
19
- <Section narrow>
19
+ <Section width="narrow">
20
20
  <Heading>Account details</Heading>
21
21
  <Definitions>
22
22
  <dt>Name</dt><dd>Alice Smith</dd>
@@ -42,11 +42,11 @@ import { Header, Nav, Footer } from "shelving/ui";
42
42
 
43
43
  | Variable | Styles | Default |
44
44
  |---|---|---|
45
- | `--section-width` | Content width | `100%` |
45
+ | `--section-width` | Content width | `var(--width-normal)` (55rem) |
46
46
  | `--section-indent` | Inline gutter kept on each side so text doesn't touch the edges | `var(--space-normal)` (16px) |
47
47
  | `--section-space` | Outer block margin (top + bottom) | `var(--space-section)` (2rem) |
48
48
 
49
- **Global tokens it reads:** [`--space-normal`](/ui/getSpaceClass) and [`--space-section`](/ui/getSpaceClass). The `narrow` / `wide` / `full` width variants come from the shared [`ui`](/ui) styling system.
49
+ **Global tokens it reads:** [`--space-normal`](/ui/getSpaceClass), [`--space-section`](/ui/getSpaceClass), and [`--width-normal`](/ui/getWidthClass) (the default content width). The `width` variant (`narrow` / `normal` / `wide` / `full` / `fit`) comes from the shared [`ui`](/ui) styling system.
50
50
 
51
51
  ## See also
52
52
 
@@ -1,5 +1,6 @@
1
1
  @import "../style/layers.css";
2
2
  @import "../style/Space.module.css";
3
+ @import "../style/Width.module.css";
3
4
 
4
5
  @layer components {
5
6
  .prose :where(section, article, aside, nav, header, footer, figure),
@@ -9,7 +10,7 @@
9
10
  display: block;
10
11
  box-sizing: border-box;
11
12
  max-inline-size: calc(100% - (var(--section-indent, var(--space-normal)) * 2)); /* Stop text touching the sides. */
12
- inline-size: var(--section-width, 100%);
13
+ inline-size: var(--section-width, var(--width-normal)); /* Default to the normal content width; override with the `width` variant. */
13
14
  margin-inline: auto;
14
15
  margin-block: var(--section-space, var(--space-section));
15
16
 
@@ -105,7 +105,7 @@ export function Nav(props: SectionProps): ReactElement {
105
105
  * @kind component
106
106
  * @param props Colour, space, typography, and width variants plus optional `as` override and `children`.
107
107
  * @returns Rendered `<aside>` element.
108
- * @example <Aside narrow><Paragraph>Sidebar</Paragraph></Aside>
108
+ * @example <Aside width="narrow"><Paragraph>Sidebar</Paragraph></Aside>
109
109
  * @see https://dhoulb.github.io/shelving/ui/block/Section/Aside
110
110
  */
111
111
  export function Aside(props: SectionProps): ReactElement {
package/ui/block/Table.js CHANGED
@@ -4,7 +4,7 @@ import { getSpaceClass } from "../style/Space.js";
4
4
  import { getTypographyClass } from "../style/Typography.js";
5
5
  import { getClass, getModuleClass } from "../util/css.js";
6
6
  import TABLE_CSS from "./Table.module.css";
7
- const TABLE_CLASS = getModuleClass(TABLE_CSS, "divider");
7
+ const TABLE_CLASS = getModuleClass(TABLE_CSS, "table");
8
8
  /**
9
9
  * Table block — rendered as `<table>`.
10
10
  * - Wrap in a `<Scroll horizontal>` if the table may exceed the container width on small screens.
@@ -6,7 +6,7 @@ import { getClass, getModuleClass } from "../util/css.js";
6
6
  import type { ChildProps } from "../util/props.js";
7
7
  import TABLE_CSS from "./Table.module.css";
8
8
 
9
- const TABLE_CLASS = getModuleClass(TABLE_CSS, "divider");
9
+ const TABLE_CLASS = getModuleClass(TABLE_CSS, "table");
10
10
 
11
11
  /**
12
12
  * Props for `Table` — colour, space, and typography variants plus `children`.
package/ui/block/Title.md CHANGED
@@ -26,7 +26,7 @@ import { Title, Paragraph } from "shelving/ui";
26
26
  import { Panel, Block, Title } from "shelving/ui";
27
27
 
28
28
  <Panel as="header" color="primary">
29
- <Block narrow>
29
+ <Block width="narrow">
30
30
  <Title>Welcome</Title>
31
31
  </Block>
32
32
  </Panel>
@@ -20,5 +20,5 @@ import { TreeCards } from "../tree/TreeCards.js";
20
20
  * @see https://dhoulb.github.io/shelving/ui/docs/DocumentationHomePage/DocumentationHomePage
21
21
  */
22
22
  export function DocumentationHomePage({ title, name, description, content, children }) {
23
- return (_jsx(Page, { title: title ?? name, description: description, children: _jsxs(Block, { color: "red", children: [_jsx(Panel, { padding: "5x", children: _jsx(Title, { center: true, children: title ?? name }) }), content && (_jsx(Section, { wide: true, children: _jsx(Prose, { children: _jsx(Markup, { children: content }) }) })), _jsx(Section, { wide: true, children: _jsx(TreeCards, { children: children }) })] }) }));
23
+ return (_jsx(Page, { title: title ?? name, description: description, children: _jsxs(Block, { color: "red", children: [_jsx(Panel, { padding: "5x", children: _jsx(Title, { center: true, children: title ?? name }) }), content && (_jsx(Section, { children: _jsx(Prose, { children: _jsx(Markup, { children: content }) }) })), _jsx(Section, { children: _jsx(TreeCards, { children: children }) })] }) }));
24
24
  }
@@ -29,13 +29,13 @@ export function DocumentationHomePage({ title, name, description, content, child
29
29
  <Title center>{title ?? name}</Title>
30
30
  </Panel>
31
31
  {content && (
32
- <Section wide>
32
+ <Section>
33
33
  <Prose>
34
34
  <Markup>{content}</Markup>
35
35
  </Prose>
36
36
  </Section>
37
37
  )}
38
- <Section wide>
38
+ <Section>
39
39
  <TreeCards>{children}</TreeCards>
40
40
  </Section>
41
41
  </Block>
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from "react";
1
+ import type { ReactNode } from "react";
2
2
  import type { DocumentationElementProps } from "../../util/tree.js";
3
3
  /**
4
4
  * Page renderer for a `tree-documentation` element — the full detail page for a documented symbol.
@@ -1,19 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment } from "react";
3
2
  import { walkElements } from "../../util/element.js";
4
3
  import { Block } from "../block/Block.js";
5
- import { Definitions } from "../block/Definitions.js";
6
4
  import { Heading } from "../block/Heading.js";
7
- import { Label } from "../block/Label.js";
8
5
  import { Panel } from "../block/Panel.js";
9
6
  import { Preformatted } from "../block/Preformatted.js";
10
7
  import { Prose } from "../block/Prose.js";
11
8
  import { Header, Section } from "../block/Section.js";
9
+ import { Table } from "../block/Table.js";
12
10
  import { Title } from "../block/Title.js";
13
11
  import { Code } from "../inline/Code.js";
14
12
  import { Markup } from "../misc/Markup.js";
15
13
  import { Page } from "../page/Page.js";
16
14
  import { Row } from "../style/Flex.js";
15
+ import { Scroll } from "../style/Scroll.js";
17
16
  import { TreeBreadcrumbs } from "../tree/TreeBreadcrumbs.js";
18
17
  import { TreeCards } from "../tree/TreeCards.js";
19
18
  import { DocumentationButtons } from "./DocumentationButtons.js";
@@ -37,7 +36,7 @@ const KIND_SECTIONS = {
37
36
  function _renderSections(elements) {
38
37
  return Object.entries(KIND_SECTIONS).map(([kind, label]) => {
39
38
  const group = elements.filter(el => el.props.kind === kind);
40
- return group.length ? (_jsxs(Section, { wide: true, children: [_jsx(Heading, { children: label }), _jsx(TreeCards, { children: group })] }, kind)) : null;
39
+ return group.length ? (_jsxs(Section, { children: [_jsx(Heading, { children: label }), _jsx(TreeCards, { children: group })] }, kind)) : null;
41
40
  });
42
41
  }
43
42
  /**
@@ -69,5 +68,5 @@ function DocumentationChildren({ elements }) {
69
68
  * @see https://dhoulb.github.io/shelving/ui/docs/DocumentationPage/DocumentationPage
70
69
  */
71
70
  export function DocumentationPage({ title, name, kind = "unknown", description, content, signatures, params, returns, throws, examples, children, ...props }) {
72
- return (_jsx(Page, { title: title ?? name, description: description, children: _jsxs(Block, { color: getDocumentationKindColor(kind), children: [_jsx(Panel, { children: _jsxs(Header, { wide: true, children: [_jsx(TreeBreadcrumbs, {}), _jsx(Title, { children: _jsxs(Row, { left: true, wrap: true, children: [title ?? name, kind && _jsx(DocumentationKind, { kind: kind, size: "normal" })] }) }), _jsx(DocumentationButtons, { ...props })] }) }), signatures?.length || params?.length || returns?.length || throws?.length ? (_jsxs(Section, { wide: true, children: [_jsx(DocumentationSignatures, { signatures: signatures }), params?.length && (_jsxs(Section, { wide: true, children: [_jsx(Label, { children: "Parameters" }), _jsx(Definitions, { children: params.map(({ name, type = DEFAULT_TYPE, description = "", optional }) => (_jsxs(Fragment, { children: [_jsx("dt", { children: _jsxs(Code, { size: "normal", children: [name, optional ? "?" : "", ": ", type] }) }), _jsx("dd", { children: description })] }, `${name}-${type}-${description}`))) })] })), returns?.length && (_jsxs(Section, { wide: true, children: [_jsx(Label, { children: "Returns" }), _jsx(Definitions, { children: returns.map(({ type = DEFAULT_TYPE, description = "" }) => (_jsxs(Fragment, { children: [_jsx("dt", { children: _jsx(Code, { size: "normal", children: type }) }), _jsx("dd", { children: description })] }, `${type}-${description}`))) })] })), throws?.length && (_jsxs(Section, { wide: true, children: [_jsx(Label, { children: "Throws" }), _jsx(Definitions, { children: throws.map(({ type = DEFAULT_TYPE, description = "" }) => (_jsxs(Fragment, { children: [_jsx("dt", { children: _jsx(Code, { size: "normal", children: type }) }), _jsx("dd", { children: description })] }, `${type}-${description}`))) })] }))] })) : null, content && (_jsx(Section, { wide: true, children: _jsx(Prose, { children: _jsx(Markup, { children: content }) }) })), examples?.length && (_jsxs(Section, { wide: true, children: [_jsx(Heading, { children: "Examples" }), examples.map(({ description }) => (_jsx(Preformatted, { children: description }, description)))] })), _jsx(DocumentationChildren, { elements: children })] }) }));
71
+ return (_jsx(Page, { title: title ?? name, description: description, children: _jsxs(Block, { color: getDocumentationKindColor(kind), children: [_jsx(Panel, { children: _jsxs(Header, { children: [_jsx(TreeBreadcrumbs, {}), _jsx(Title, { children: _jsxs(Row, { left: true, wrap: true, children: [title ?? name, kind && _jsx(DocumentationKind, { kind: kind, size: "normal" })] }) }), _jsx(DocumentationButtons, { ...props })] }) }), signatures?.length || params?.length || returns?.length || throws?.length ? (_jsxs(Section, { children: [_jsx(DocumentationSignatures, { signatures: signatures }), params?.length && (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Parameter" }), _jsx("th", { children: "Type" }), _jsx("th", { children: "Default" }), _jsx("th", { children: "Description" })] }) }), _jsx("tbody", { children: params.map(({ name, type = DEFAULT_TYPE, description = "", default: def }) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx(Code, { size: "normal", children: name }) }), _jsx("td", { children: _jsx(Code, { size: "normal", children: type }) }), _jsx("td", { children: def ? _jsx(Code, { size: "normal", children: def }) : "-" }), _jsx("td", { children: description })] }, `${name}-${type}-${description}`))) })] }) }) })), returns?.length && (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Return" }), _jsx("th", { children: "Description" })] }) }), _jsx("tbody", { children: returns.map(({ type = DEFAULT_TYPE, description = "" }) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx(Code, { size: "normal", children: type }) }), _jsx("td", { children: description })] }, `${type}-${description}`))) })] }) }) })), throws?.length && (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Throws" }), _jsx("th", { children: "Description" })] }) }), _jsx("tbody", { children: throws.map(({ type = DEFAULT_TYPE, description = "" }) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx(Code, { size: "normal", children: type }) }), _jsx("td", { children: description })] }, `${type}-${description}`))) })] }) }) }))] })) : null, content && (_jsx(Section, { children: _jsx(Prose, { children: _jsx(Markup, { children: content }) }) })), examples?.length && (_jsxs(Section, { children: [_jsx(Heading, { children: "Examples" }), examples.map(({ description }) => (_jsx(Preformatted, { children: description }, description)))] })), _jsx(DocumentationChildren, { elements: children })] }) }));
73
72
  }
@@ -6,7 +6,7 @@ The full detail page for a documented symbol — the default renderer for `tree-
6
6
 
7
7
  - Above the title it renders an ancestor trail via [`TreeBreadcrumbs`](/ui/TreeBreadcrumbs), so it needs a [`TreeProvider`](/ui/TreeProvider) above it to resolve the ancestors.
8
8
  - The title carries a [`DocumentationKind`](/ui/DocumentationKind) tag, and below it [`DocumentationButtons`](/ui/DocumentationButtons) lists the symbol's relations (`member of`, `extends`, `implements`) as links.
9
- - It renders the signature(s) via [`DocumentationSignatures`](/ui/DocumentationSignatures) (one block per overload, each carrying the symbol's name), then prose content, then conditional `Parameters` / `Returns` / `Throws` / `Examples` sections — each only appears when it has entries.
9
+ - It renders the signature(s) via [`DocumentationSignatures`](/ui/DocumentationSignatures) (one block per overload, each carrying the symbol's name), then prose content, then conditional `Parameters` / `Returns` / `Throws` / `Examples` sections — each only appears when it has entries. Parameters render as a [`Table`](/ui/Table) (`Parameter` / `Type` / `Default` / `Description` columns, a `-` standing in where a parameter has no default); Returns and Throws render as two-column tables (`Return` / `Throws` plus `Description`), with the type in the first column.
10
10
  - Child symbols follow, grouped by `kind` into card sections (`Components`, `Functions`, `Classes`, `Interfaces`, `Types`, `Constants`, `Methods`, `Properties`) rendered as [`DocumentationCard`](/ui/DocumentationCard)s inside a [`TreeCards`](/ui/TreeCards) listing. A new documented kind needs an entry in `KIND_SECTIONS` here (and a colour in [`DocumentationKind`](/ui/DocumentationKind)).
11
11
 
12
12
  ## Usage
@@ -42,6 +42,32 @@ describe("DocumentationPage", () => {
42
42
  expect(html).not.toContain("Interfaces");
43
43
  });
44
44
 
45
+ test("renders parameters as a table with a default column, hyphenating params without a default", () => {
46
+ const html = render(
47
+ <DocumentationPage
48
+ path="/makeThing"
49
+ name="makeThing"
50
+ kind="function"
51
+ params={[
52
+ { name: "name", type: "string", description: "The name." },
53
+ { name: "required", type: "boolean", description: "Whether required.", optional: true, default: "false" },
54
+ ]}
55
+ returns={[{ type: "Thing", description: "The new thing." }]}
56
+ />,
57
+ "./makeThing",
58
+ );
59
+ // Parameters table headers — name, type, default, description in separate columns.
60
+ expect(html).toContain("<th>Parameter</th>");
61
+ expect(html).toContain("<th>Type</th>");
62
+ expect(html).toContain("<th>Default</th>");
63
+ // A param with a default renders it; one without gets a hyphen.
64
+ expect(html).toContain("false");
65
+ expect(html).toContain("<td>-</td>");
66
+ // Returns table headers.
67
+ expect(html).toContain("<th>Return</th>");
68
+ expect(html).toContain("The new thing.");
69
+ });
70
+
45
71
  test("groups static members into their own sections, before instance sections", () => {
46
72
  const html = render(
47
73
  <DocumentationPage path="/Color" name="Color" kind="class">
@@ -1,19 +1,19 @@
1
- import { Fragment, type ReactNode } from "react";
1
+ import type { ReactNode } from "react";
2
2
  import { walkElements } from "../../util/element.js";
3
3
  import type { DocumentationElementProps, TreeElement, TreeElements } from "../../util/tree.js";
4
4
  import { Block } from "../block/Block.js";
5
- import { Definitions } from "../block/Definitions.js";
6
5
  import { Heading } from "../block/Heading.js";
7
- import { Label } from "../block/Label.js";
8
6
  import { Panel } from "../block/Panel.js";
9
7
  import { Preformatted } from "../block/Preformatted.js";
10
8
  import { Prose } from "../block/Prose.js";
11
9
  import { Header, Section } from "../block/Section.js";
10
+ import { Table } from "../block/Table.js";
12
11
  import { Title } from "../block/Title.js";
13
12
  import { Code } from "../inline/Code.js";
14
13
  import { Markup } from "../misc/Markup.js";
15
14
  import { Page } from "../page/Page.js";
16
15
  import { Row } from "../style/Flex.js";
16
+ import { Scroll } from "../style/Scroll.js";
17
17
  import { TreeBreadcrumbs } from "../tree/TreeBreadcrumbs.js";
18
18
  import { TreeCards } from "../tree/TreeCards.js";
19
19
  import { DocumentationButtons } from "./DocumentationButtons.js";
@@ -41,7 +41,7 @@ function _renderSections(elements: readonly TreeElement[]): ReactNode {
41
41
  return Object.entries(KIND_SECTIONS).map(([kind, label]) => {
42
42
  const group = elements.filter(el => (el.props as DocumentationElementProps).kind === kind);
43
43
  return group.length ? (
44
- <Section wide key={kind}>
44
+ <Section key={kind}>
45
45
  <Heading>{label}</Heading>
46
46
  <TreeCards>{group}</TreeCards>
47
47
  </Section>
@@ -97,7 +97,7 @@ export function DocumentationPage({
97
97
  <Page title={title ?? name} description={description}>
98
98
  <Block color={getDocumentationKindColor(kind)}>
99
99
  <Panel>
100
- <Header wide>
100
+ <Header>
101
101
  <TreeBreadcrumbs />
102
102
  <Title>
103
103
  <Row left wrap>
@@ -109,67 +109,97 @@ export function DocumentationPage({
109
109
  </Header>
110
110
  </Panel>
111
111
  {signatures?.length || params?.length || returns?.length || throws?.length ? (
112
- <Section wide>
112
+ <Section>
113
113
  <DocumentationSignatures signatures={signatures} />
114
114
  {params?.length && (
115
- <Section wide>
116
- <Label>Parameters</Label>
117
- <Definitions>
118
- {params.map(({ name, type = DEFAULT_TYPE, description = "", optional }) => (
119
- <Fragment key={`${name}-${type}-${description}`}>
120
- <dt>
121
- <Code size="normal">
122
- {name}
123
- {optional ? "?" : ""}: {type}
124
- </Code>
125
- </dt>
126
- <dd>{description}</dd>
127
- </Fragment>
128
- ))}
129
- </Definitions>
115
+ <Section>
116
+ <Scroll horizontal>
117
+ <Table>
118
+ <thead>
119
+ <tr>
120
+ <th>Parameter</th>
121
+ <th>Type</th>
122
+ <th>Default</th>
123
+ <th>Description</th>
124
+ </tr>
125
+ </thead>
126
+ <tbody>
127
+ {params.map(({ name, type = DEFAULT_TYPE, description = "", default: def }) => (
128
+ <tr key={`${name}-${type}-${description}`}>
129
+ <td>
130
+ <Code size="normal">{name}</Code>
131
+ </td>
132
+ <td>
133
+ <Code size="normal">{type}</Code>
134
+ </td>
135
+ <td>{def ? <Code size="normal">{def}</Code> : "-"}</td>
136
+ <td>{description}</td>
137
+ </tr>
138
+ ))}
139
+ </tbody>
140
+ </Table>
141
+ </Scroll>
130
142
  </Section>
131
143
  )}
132
144
  {returns?.length && (
133
- <Section wide>
134
- <Label>Returns</Label>
135
- <Definitions>
136
- {returns.map(({ type = DEFAULT_TYPE, description = "" }) => (
137
- <Fragment key={`${type}-${description}`}>
138
- <dt>
139
- <Code size="normal">{type}</Code>
140
- </dt>
141
- <dd>{description}</dd>
142
- </Fragment>
143
- ))}
144
- </Definitions>
145
+ <Section>
146
+ <Scroll horizontal>
147
+ <Table>
148
+ <thead>
149
+ <tr>
150
+ <th>Return</th>
151
+ <th>Description</th>
152
+ </tr>
153
+ </thead>
154
+ <tbody>
155
+ {returns.map(({ type = DEFAULT_TYPE, description = "" }) => (
156
+ <tr key={`${type}-${description}`}>
157
+ <td>
158
+ <Code size="normal">{type}</Code>
159
+ </td>
160
+ <td>{description}</td>
161
+ </tr>
162
+ ))}
163
+ </tbody>
164
+ </Table>
165
+ </Scroll>
145
166
  </Section>
146
167
  )}
147
168
  {throws?.length && (
148
- <Section wide>
149
- <Label>Throws</Label>
150
- <Definitions>
151
- {throws.map(({ type = DEFAULT_TYPE, description = "" }) => (
152
- <Fragment key={`${type}-${description}`}>
153
- <dt>
154
- <Code size="normal">{type}</Code>
155
- </dt>
156
- <dd>{description}</dd>
157
- </Fragment>
158
- ))}
159
- </Definitions>
169
+ <Section>
170
+ <Scroll horizontal>
171
+ <Table>
172
+ <thead>
173
+ <tr>
174
+ <th>Throws</th>
175
+ <th>Description</th>
176
+ </tr>
177
+ </thead>
178
+ <tbody>
179
+ {throws.map(({ type = DEFAULT_TYPE, description = "" }) => (
180
+ <tr key={`${type}-${description}`}>
181
+ <td>
182
+ <Code size="normal">{type}</Code>
183
+ </td>
184
+ <td>{description}</td>
185
+ </tr>
186
+ ))}
187
+ </tbody>
188
+ </Table>
189
+ </Scroll>
160
190
  </Section>
161
191
  )}
162
192
  </Section>
163
193
  ) : null}
164
194
  {content && (
165
- <Section wide>
195
+ <Section>
166
196
  <Prose>
167
197
  <Markup>{content}</Markup>
168
198
  </Prose>
169
199
  </Section>
170
200
  )}
171
201
  {examples?.length && (
172
- <Section wide>
202
+ <Section>
173
203
  <Heading>Examples</Heading>
174
204
  {examples.map(({ description }) => (
175
205
  <Preformatted key={description}>{description}</Preformatted>
@@ -2,8 +2,8 @@ import type { ReactElement } from "react";
2
2
  import { type WidthVariants } from "../style/Width.js";
3
3
  import type { ChildProps } from "../util/props.js";
4
4
  /**
5
- * Styling variants shared by every form input — currently the width variants (`narrow`, `wide`, `full`, `fit`).
6
- * - Extends `WidthVariants` so any input can be sized (e.g. `<CheckboxInput fit>` to shrink to its content).
5
+ * Styling variants shared by every form input — currently the `width` variant (`width="narrow"`, `"normal"`, `"wide"`, `"full"`, `"fit"`).
6
+ * - Extends `WidthVariants` so any input can be sized (e.g. `<CheckboxInput width="fit">` to shrink to its content).
7
7
  * - Designed to grow: new cross-cutting input styling props (e.g. spacing) should be added here so every input picks them up consistently.
8
8
  *
9
9
  * @see https://dhoulb.github.io/shelving/ui/form/Input/InputVariants
package/ui/form/Input.tsx CHANGED
@@ -7,8 +7,8 @@ import type { ChildProps } from "../util/props.js";
7
7
  import INPUT_CSS from "./Input.module.css";
8
8
 
9
9
  /**
10
- * Styling variants shared by every form input — currently the width variants (`narrow`, `wide`, `full`, `fit`).
11
- * - Extends `WidthVariants` so any input can be sized (e.g. `<CheckboxInput fit>` to shrink to its content).
10
+ * Styling variants shared by every form input — currently the `width` variant (`width="narrow"`, `"normal"`, `"wide"`, `"full"`, `"fit"`).
11
+ * - Extends `WidthVariants` so any input can be sized (e.g. `<CheckboxInput width="fit">` to shrink to its content).
12
12
  * - Designed to grow: new cross-cutting input styling props (e.g. spacing) should be added here so every input picks them up consistently.
13
13
  *
14
14
  * @see https://dhoulb.github.io/shelving/ui/form/Input/InputVariants
@@ -15,7 +15,7 @@ import { CenteredLayout, Section } from "shelving/ui";
15
15
  function LoginPage() {
16
16
  return (
17
17
  <CenteredLayout>
18
- <Section narrow>
18
+ <Section width="narrow">
19
19
  <LoginForm/>
20
20
  </Section>
21
21
  </CenteredLayout>
@@ -1,6 +1,5 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { ChildProps } from "../util/index.js";
3
- import type { WidthVariants } from "./Width.js";
4
3
  /**
5
4
  * Variant props selecting which scroll axes are enabled on an element.
6
5
  *
@@ -22,16 +21,16 @@ export interface ScrollProps {
22
21
  */
23
22
  export declare function getScrollClass({ horizontal, vertical }: ScrollProps): string;
24
23
  /**
25
- * Props for the `Scroll` component — children plus scroll-axis and width variants.
24
+ * Props for the `Scroll` component — children plus scroll-axis variants.
26
25
  *
27
26
  * @see https://dhoulb.github.io/shelving/ui/style/Scroll/ScrollComponentProps
28
27
  */
29
- export interface ScrollComponentProps extends ChildProps, ScrollProps, WidthVariants {
28
+ export interface ScrollComponentProps extends ChildProps, ScrollProps {
30
29
  }
31
30
  /**
32
31
  * Wrap children in a scrollable container with horizontal and/or vertical scrolling enabled.
33
32
  *
34
- * @param props Children plus scroll-axis and width variant props.
33
+ * @param props Children plus scroll-axis variant props.
35
34
  * @returns A `<div>` element with the computed scroll class.
36
35
  * @example <Scroll horizontal>{wideContent}</Scroll>
37
36
  * @see https://dhoulb.github.io/shelving/ui/style/Scroll/Scroll
@@ -18,7 +18,7 @@ export function getScrollClass({ horizontal, vertical }) {
18
18
  /**
19
19
  * Wrap children in a scrollable container with horizontal and/or vertical scrolling enabled.
20
20
  *
21
- * @param props Children plus scroll-axis and width variant props.
21
+ * @param props Children plus scroll-axis variant props.
22
22
  * @returns A `<div>` element with the computed scroll class.
23
23
  * @example <Scroll horizontal>{wideContent}</Scroll>
24
24
  * @see https://dhoulb.github.io/shelving/ui/style/Scroll/Scroll
@@ -2,7 +2,6 @@ import type { ReactElement } from "react";
2
2
  import { getClass, getModuleClass } from "../util/css.js";
3
3
  import type { ChildProps } from "../util/index.js";
4
4
  import SCROLLABLE_CSS from "./Scroll.module.css";
5
- import type { WidthVariants } from "./Width.js";
6
5
 
7
6
  const SCROLL_HORIZONTAL_CLASS = getModuleClass(SCROLLABLE_CSS, "horizontal");
8
7
 
@@ -36,16 +35,16 @@ export function getScrollClass({ horizontal, vertical }: ScrollProps): string {
36
35
  }
37
36
 
38
37
  /**
39
- * Props for the `Scroll` component — children plus scroll-axis and width variants.
38
+ * Props for the `Scroll` component — children plus scroll-axis variants.
40
39
  *
41
40
  * @see https://dhoulb.github.io/shelving/ui/style/Scroll/ScrollComponentProps
42
41
  */
43
- export interface ScrollComponentProps extends ChildProps, ScrollProps, WidthVariants {}
42
+ export interface ScrollComponentProps extends ChildProps, ScrollProps {}
44
43
 
45
44
  /**
46
45
  * Wrap children in a scrollable container with horizontal and/or vertical scrolling enabled.
47
46
  *
48
- * @param props Children plus scroll-axis and width variant props.
47
+ * @param props Children plus scroll-axis variant props.
49
48
  * @returns A `<div>` element with the computed scroll class.
50
49
  * @example <Scroll horizontal>{wideContent}</Scroll>
51
50
  * @see https://dhoulb.github.io/shelving/ui/style/Scroll/Scroll
@@ -1,30 +1,27 @@
1
1
  /**
2
- * Variant props that constrain (or unconstrain) a block-level component's max-width, e.g. `narrow`.
2
+ * Enumerated max-inline-size constraints selectable via the `width` variant prop.
3
+ * - `narrow` / `normal` / `wide` — fixed max-widths from the `--width-*` tokens.
4
+ * - `full` — take the full available width, removing any default max-width constraint.
5
+ * - `fit` — shrink to fit the content's intrinsic width (`fit-content`).
3
6
  *
4
- * @see https://dhoulb.github.io/shelving/ui/style/Width/WidthVariants
7
+ * @see https://dhoulb.github.io/shelving/ui/style/Width/UIWidth
5
8
  */
6
- export interface WidthVariants {
7
- /** Constrain to narrow max-width (`--width-narrow`). */
8
- narrow?: boolean | undefined;
9
- /** Constrain to wide max-width (`--width-wide`). */
10
- wide?: boolean | undefined;
11
- /** Take the full available width — removes any default max-width constraint. */
12
- full?: boolean | undefined;
13
- /** Shrink to fit the content's intrinsic width (`fit-content`). */
14
- fit?: boolean | undefined;
15
- }
9
+ export type UIWidth = "narrow" | "normal" | "wide" | "full" | "fit";
16
10
  /**
17
- * Enumerated width modifier names the boolean keys of `WidthVariants`.
11
+ * Variant props that constrain (or unconstrain) a block-level component's max-width, e.g. `width="narrow"`.
18
12
  *
19
- * @see https://dhoulb.github.io/shelving/ui/style/Width/UIWidth
13
+ * @see https://dhoulb.github.io/shelving/ui/style/Width/WidthVariants
20
14
  */
21
- export type UIWidth = keyof WidthVariants;
15
+ export interface WidthVariants {
16
+ /** Max-inline-size constraint of the element. */
17
+ width?: UIWidth | undefined;
18
+ }
22
19
  /**
23
- * Get the max-width class for a component from its width variant props.
20
+ * Get the max-width class for a component from its `width` variant prop.
24
21
  *
25
- * @param width Width variant props selecting the max-width constraint.
26
- * @returns The width class string, or `undefined` when no width variant is set.
27
- * @example getWidthClass({ narrow: true }) // "narrow"
22
+ * @param variants Variant props containing the optional `width` constraint.
23
+ * @returns The width class string, or `undefined` when no `width` is set.
24
+ * @example getWidthClass({ width: "narrow" }) // "narrow"
28
25
  * @see https://dhoulb.github.io/shelving/ui/style/Width/getWidthClass
29
26
  */
30
- export declare function getWidthClass(width: WidthVariants): string | undefined;
27
+ export declare function getWidthClass({ width }: WidthVariants): string | undefined;
package/ui/style/Width.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { getModuleClass } from "../util/css.js";
2
2
  import WIDTH_CSS from "./Width.module.css";
3
3
  /**
4
- * Get the max-width class for a component from its width variant props.
4
+ * Get the max-width class for a component from its `width` variant prop.
5
5
  *
6
- * @param width Width variant props selecting the max-width constraint.
7
- * @returns The width class string, or `undefined` when no width variant is set.
8
- * @example getWidthClass({ narrow: true }) // "narrow"
6
+ * @param variants Variant props containing the optional `width` constraint.
7
+ * @returns The width class string, or `undefined` when no `width` is set.
8
+ * @example getWidthClass({ width: "narrow" }) // "narrow"
9
9
  * @see https://dhoulb.github.io/shelving/ui/style/Width/getWidthClass
10
10
  */
11
- export function getWidthClass(width) {
12
- return getModuleClass(WIDTH_CSS, width);
11
+ export function getWidthClass({ width }) {
12
+ return width && getModuleClass(WIDTH_CSS, width);
13
13
  }
@@ -3,11 +3,13 @@
3
3
  @layer defaults {
4
4
  :root {
5
5
  /* Semantic widths */
6
- --width-narrow: 22.5rem;
7
- --width-wide: 33.75rem;
6
+ --width-narrow: 35rem;
7
+ --width-normal: 55rem;
8
+ --width-wide: 75rem;
8
9
  }
9
10
 
10
11
  .narrow,
12
+ .normal,
11
13
  .wide,
12
14
  .full,
13
15
  .fit {
@@ -22,6 +24,9 @@
22
24
  .narrow {
23
25
  inline-size: var(--width-narrow);
24
26
  }
27
+ .normal {
28
+ inline-size: var(--width-normal);
29
+ }
25
30
  .wide {
26
31
  inline-size: var(--width-wide);
27
32
  }
@@ -2,36 +2,33 @@ import { getModuleClass } from "../util/css.js";
2
2
  import WIDTH_CSS from "./Width.module.css";
3
3
 
4
4
  /**
5
- * Variant props that constrain (or unconstrain) a block-level component's max-width, e.g. `narrow`.
5
+ * Enumerated max-inline-size constraints selectable via the `width` variant prop.
6
+ * - `narrow` / `normal` / `wide` — fixed max-widths from the `--width-*` tokens.
7
+ * - `full` — take the full available width, removing any default max-width constraint.
8
+ * - `fit` — shrink to fit the content's intrinsic width (`fit-content`).
6
9
  *
7
- * @see https://dhoulb.github.io/shelving/ui/style/Width/WidthVariants
10
+ * @see https://dhoulb.github.io/shelving/ui/style/Width/UIWidth
8
11
  */
9
- export interface WidthVariants {
10
- /** Constrain to narrow max-width (`--width-narrow`). */
11
- narrow?: boolean | undefined;
12
- /** Constrain to wide max-width (`--width-wide`). */
13
- wide?: boolean | undefined;
14
- /** Take the full available width — removes any default max-width constraint. */
15
- full?: boolean | undefined;
16
- /** Shrink to fit the content's intrinsic width (`fit-content`). */
17
- fit?: boolean | undefined;
18
- }
12
+ export type UIWidth = "narrow" | "normal" | "wide" | "full" | "fit";
19
13
 
20
14
  /**
21
- * Enumerated width modifier names the boolean keys of `WidthVariants`.
15
+ * Variant props that constrain (or unconstrain) a block-level component's max-width, e.g. `width="narrow"`.
22
16
  *
23
- * @see https://dhoulb.github.io/shelving/ui/style/Width/UIWidth
17
+ * @see https://dhoulb.github.io/shelving/ui/style/Width/WidthVariants
24
18
  */
25
- export type UIWidth = keyof WidthVariants;
19
+ export interface WidthVariants {
20
+ /** Max-inline-size constraint of the element. */
21
+ width?: UIWidth | undefined;
22
+ }
26
23
 
27
24
  /**
28
- * Get the max-width class for a component from its width variant props.
25
+ * Get the max-width class for a component from its `width` variant prop.
29
26
  *
30
- * @param width Width variant props selecting the max-width constraint.
31
- * @returns The width class string, or `undefined` when no width variant is set.
32
- * @example getWidthClass({ narrow: true }) // "narrow"
27
+ * @param variants Variant props containing the optional `width` constraint.
28
+ * @returns The width class string, or `undefined` when no `width` is set.
29
+ * @example getWidthClass({ width: "narrow" }) // "narrow"
33
30
  * @see https://dhoulb.github.io/shelving/ui/style/Width/getWidthClass
34
31
  */
35
- export function getWidthClass(width: WidthVariants): string | undefined {
36
- return getModuleClass(WIDTH_CSS, width);
32
+ export function getWidthClass({ width }: WidthVariants): string | undefined {
33
+ return width && getModuleClass(WIDTH_CSS, width);
37
34
  }
@@ -1,8 +1,8 @@
1
1
  # getWidthClass
2
2
 
3
- The width variant props (`narrow`, `wide`, `full`, `fit`) constrain a block-level component's max-inline-size — `<Section narrow>`, `<Card wide>`. They're **overrides** for one-off layout; for an app-wide change, retune the width variables below in a theme file.
3
+ The `width` variant prop (`"narrow"`, `"normal"`, `"wide"`, `"full"`, `"fit"`) constrains a block-level component's max-inline-size — `<Card width="narrow">`, `<Block width="wide">`. 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` constraint, so most pages never set `width` at all.
4
4
 
5
- `getWidthClass({ narrow, wide, full })` maps the boolean props to a width class. The `narrow` and `wide` constraints read the variables below.
5
+ `getWidthClass({ width })` maps the prop to a width class. The `narrow`, `normal`, and `wide` constraints read the variables below; `full` takes the full available width and `fit` shrinks to the content.
6
6
 
7
7
  ## Theme variables
8
8
 
@@ -10,8 +10,9 @@ The following `:root` variables are defined by this module and can be overridden
10
10
 
11
11
  | Variable | Default | Used for |
12
12
  |---|---|---|
13
- | `--width-narrow` | `22.5rem` | The `narrow` constraint — single-column forms, dialogs. |
14
- | `--width-wide` | `33.75rem` | The `wide` constraint — readable prose measure. |
13
+ | `--width-narrow` | `35rem` | The `narrow` constraint — single-column forms, dialogs. |
14
+ | `--width-normal` | `55rem` | The `normal` constraint — standard content column. |
15
+ | `--width-wide` | `75rem` | The `wide` constraint — readable prose measure. |
15
16
 
16
17
  ## See also
17
18
 
@@ -51,5 +51,5 @@ export function TreeIndexPage() {
51
51
  const filter = selected.length ? { kind: selected } : undefined;
52
52
  // Each element's `key` is its unique canonical path (stamped by `flattenTree()`), so this flat cross-tree listing reconciles correctly.
53
53
  const cards = root ? searchTree(root, trimmed, { limit: trimmed ? 20 : INDEX_LIMIT, filter }) : [];
54
- return (_jsxs(Page, { title: INDEX_TITLE, description: INDEX_DESCRIPTION, children: [_jsx(Header, { wide: true, children: _jsx(Title, { children: INDEX_TITLE }) }), _jsxs(Section, { wide: true, children: [_jsx(TextInput, { name: "search", title: "Search", placeholder: "Search\u2026", value: query, onValue: v => setQuery(v ?? "") }), !!kinds.length && (_jsx(Row, { left: true, wrap: true, children: kinds.map(kind => (_jsx(CheckboxInput, { name: kind, fit: true, value: selected.includes(kind), onValue: () => setSelected(s => toggleArrayItem(s, kind)), children: _jsx(DocumentationKind, { kind: kind }) }, kind))) }))] }), _jsx(Section, { wide: true, children: _jsx(TreeCards, { children: cards }) })] }));
54
+ return (_jsxs(Page, { title: INDEX_TITLE, description: INDEX_DESCRIPTION, children: [_jsx(Header, { children: _jsx(Title, { children: INDEX_TITLE }) }), _jsxs(Section, { children: [_jsx(TextInput, { name: "search", title: "Search", placeholder: "Search\u2026", value: query, onValue: v => setQuery(v ?? "") }), !!kinds.length && (_jsx(Row, { left: true, wrap: true, children: kinds.map(kind => (_jsx(CheckboxInput, { name: kind, width: "fit", value: selected.includes(kind), onValue: () => setSelected(s => toggleArrayItem(s, kind)), children: _jsx(DocumentationKind, { kind: kind }) }, kind))) }))] }), _jsx(Section, { children: _jsx(TreeCards, { children: cards }) })] }));
55
55
  }
@@ -64,10 +64,10 @@ export function TreeIndexPage(): ReactNode {
64
64
 
65
65
  return (
66
66
  <Page title={INDEX_TITLE} description={INDEX_DESCRIPTION}>
67
- <Header wide>
67
+ <Header>
68
68
  <Title>{INDEX_TITLE}</Title>
69
69
  </Header>
70
- <Section wide>
70
+ <Section>
71
71
  <TextInput name="search" title="Search" placeholder="Search…" value={query} onValue={v => setQuery(v ?? "")} />
72
72
  {!!kinds.length && (
73
73
  <Row left wrap>
@@ -75,7 +75,7 @@ export function TreeIndexPage(): ReactNode {
75
75
  <CheckboxInput
76
76
  key={kind}
77
77
  name={kind}
78
- fit
78
+ width="fit"
79
79
  value={selected.includes(kind)}
80
80
  onValue={() => setSelected(s => toggleArrayItem(s, kind))}
81
81
  >
@@ -85,7 +85,7 @@ export function TreeIndexPage(): ReactNode {
85
85
  </Row>
86
86
  )}
87
87
  </Section>
88
- <Section wide>
88
+ <Section>
89
89
  <TreeCards>{cards}</TreeCards>
90
90
  </Section>
91
91
  </Page>
@@ -17,5 +17,5 @@ import { TreeCards } from "./TreeCards.js";
17
17
  * @see https://dhoulb.github.io/shelving/ui/tree/TreePage/TreePage
18
18
  */
19
19
  export function TreePage({ title, name, description, content, children }) {
20
- return (_jsxs(Page, { title: title ?? name, description: description, children: [_jsx(Header, { wide: true, children: _jsx(Title, { children: title ?? name }) }), _jsx(Section, { wide: true, children: content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })) }), _jsx(Section, { wide: true, children: _jsx(TreeCards, { children: children }) })] }));
20
+ return (_jsxs(Page, { title: title ?? name, description: description, children: [_jsx(Header, { children: _jsx(Title, { children: title ?? name }) }), _jsx(Section, { children: content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })) }), _jsx(Section, { children: _jsx(TreeCards, { children: children }) })] }));
21
21
  }
@@ -21,17 +21,17 @@ import { TreeCards } from "./TreeCards.js";
21
21
  export function TreePage({ title, name, description, content, children }: TreeElementProps): ReactNode {
22
22
  return (
23
23
  <Page title={title ?? name} description={description}>
24
- <Header wide>
24
+ <Header>
25
25
  <Title>{title ?? name}</Title>
26
26
  </Header>
27
- <Section wide>
27
+ <Section>
28
28
  {content && (
29
29
  <Prose>
30
30
  <Markup>{content}</Markup>
31
31
  </Prose>
32
32
  )}
33
33
  </Section>
34
- <Section wide>
34
+ <Section>
35
35
  <TreeCards>{children}</TreeCards>
36
36
  </Section>
37
37
  </Page>
package/util/tree.d.ts CHANGED
@@ -77,6 +77,8 @@ export interface DocumentationParam {
77
77
  readonly type?: string | undefined;
78
78
  readonly description?: string | undefined;
79
79
  readonly optional?: boolean | undefined;
80
+ /** Default-value expression from the parameter's initializer (e.g. `"false"`, `"{}"`), or `undefined` when the parameter has none. */
81
+ readonly default?: string | undefined;
80
82
  }
81
83
  /**
82
84
  * A single `@returns` entry — multiple allowed to document union return types separately.