shelving 1.246.1 → 1.248.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extract/TypescriptExtractor.d.ts +3 -1
- package/extract/TypescriptExtractor.js +84 -7
- package/package.json +1 -1
- package/ui/block/Table.md +2 -0
- package/ui/block/TableCell.d.ts +23 -0
- package/ui/block/TableCell.js +18 -0
- package/ui/block/TableCell.md +35 -0
- package/ui/block/TableCell.tsx +27 -0
- package/ui/block/TableHeader.d.ts +23 -0
- package/ui/block/TableHeader.js +18 -0
- package/ui/block/TableHeader.md +43 -0
- package/ui/block/TableHeader.tsx +27 -0
- package/ui/block/index.d.ts +2 -0
- package/ui/block/index.js +2 -0
- package/ui/block/index.ts +2 -0
- package/ui/docs/DocumentationPage.d.ts +6 -3
- package/ui/docs/DocumentationPage.js +20 -3
- package/ui/docs/DocumentationPage.md +1 -1
- package/ui/docs/DocumentationPage.test.tsx +1 -1
- package/ui/docs/DocumentationPage.tsx +95 -33
- package/ui/style/Width.d.ts +18 -11
- package/ui/style/Width.js +9 -6
- package/ui/style/Width.module.css +107 -11
- package/ui/style/Width.tsx +53 -12
- package/ui/style/getWidthClass.md +11 -2
- package/ui/tree/TreeButton.d.ts +2 -2
- package/ui/tree/TreeButton.js +4 -4
- package/ui/tree/TreeButton.test.tsx +11 -0
- package/ui/tree/TreeButton.tsx +4 -4
- package/ui/tree/TreeContext.d.ts +14 -0
- package/ui/tree/TreeContext.js +20 -0
- package/ui/tree/TreeContext.tsx +20 -0
- package/ui/tree/TreeLink.d.ts +26 -0
- package/ui/tree/TreeLink.js +23 -0
- package/ui/tree/TreeLink.tsx +36 -0
- package/ui/tree/index.d.ts +1 -0
- package/ui/tree/index.js +1 -0
- package/ui/tree/index.ts +1 -0
- package/util/tree.d.ts +12 -2
|
@@ -9,7 +9,9 @@ import { FileExtractor } from "./FileExtractor.js";
|
|
|
9
9
|
* - A `@kind` tag in a symbol's JSDoc overrides the inferred kind — e.g. `@kind component` relabels a React component (otherwise a `function`) so the docs site groups and colours it as a component. The override also drops the trailing `()` from the title, since a non-function kind reads as a bare name.
|
|
10
10
|
* - Sets `description` (a plain-text summary from the first JSDoc paragraph) on every `tree-documentation` child.
|
|
11
11
|
* - Sets `title` on every `tree-documentation` child — `name()` for functions, `Class.name()` for methods, `Class.name` for properties, bare `name` for other kinds.
|
|
12
|
-
* - Records relational metadata as raw strings for render-time linking: `class` (owning class), `readonly`, `extends
|
|
12
|
+
* - Records relational metadata as raw strings for render-time linking: `class` (owning class), `readonly`, `extends` / `implements` (full heritage type text including generic arguments, e.g. `AbstractStore<string>` or `Omit<StringSchemaOptions, "value">`), and `types` (the type names a `type` alias's body references, e.g. `OtherType` in `string | OtherType`).
|
|
13
|
+
* - Records a structured `properties` list for interfaces and object-literal `type` aliases — each member's name, type, optionality, `@default`, and description — so an options-bag parameter can be flattened into its fields at render time.
|
|
14
|
+
* - Pretty-prints object-literal signatures (interfaces and object-literal `type` aliases) as multi-line `{ … }` blocks, one member per line; other type bodies (`string | null`, mapped types, …) are emitted verbatim.
|
|
13
15
|
* - Members declared with the `override` or `declare` modifier are skipped — the base class already documents overrides, and `declare` members are ambient type-only re-declarations rather than new API.
|
|
14
16
|
* - Keys are the raw declared `name` (case-preserving) so case-distinct exports like `Collection` and `COLLECTION` stay separate.
|
|
15
17
|
* - The file element itself has no `title` — a TS source file has no confident title source; renderers fall back to `name`.
|
|
@@ -10,7 +10,9 @@ import { extractMarkdownProps } from "./MarkupExtractor.js";
|
|
|
10
10
|
* - A `@kind` tag in a symbol's JSDoc overrides the inferred kind — e.g. `@kind component` relabels a React component (otherwise a `function`) so the docs site groups and colours it as a component. The override also drops the trailing `()` from the title, since a non-function kind reads as a bare name.
|
|
11
11
|
* - Sets `description` (a plain-text summary from the first JSDoc paragraph) on every `tree-documentation` child.
|
|
12
12
|
* - Sets `title` on every `tree-documentation` child — `name()` for functions, `Class.name()` for methods, `Class.name` for properties, bare `name` for other kinds.
|
|
13
|
-
* - Records relational metadata as raw strings for render-time linking: `class` (owning class), `readonly`, `extends
|
|
13
|
+
* - Records relational metadata as raw strings for render-time linking: `class` (owning class), `readonly`, `extends` / `implements` (full heritage type text including generic arguments, e.g. `AbstractStore<string>` or `Omit<StringSchemaOptions, "value">`), and `types` (the type names a `type` alias's body references, e.g. `OtherType` in `string | OtherType`).
|
|
14
|
+
* - Records a structured `properties` list for interfaces and object-literal `type` aliases — each member's name, type, optionality, `@default`, and description — so an options-bag parameter can be flattened into its fields at render time.
|
|
15
|
+
* - Pretty-prints object-literal signatures (interfaces and object-literal `type` aliases) as multi-line `{ … }` blocks, one member per line; other type bodies (`string | null`, mapped types, …) are emitted verbatim.
|
|
14
16
|
* - Members declared with the `override` or `declare` modifier are skipped — the base class already documents overrides, and `declare` members are ambient type-only re-declarations rather than new API.
|
|
15
17
|
* - Keys are the raw declared `name` (case-preserving) so case-distinct exports like `Collection` and `COLLECTION` stay separate.
|
|
16
18
|
* - The file element itself has no `title` — a TS source file has no confident title source; renderers fall back to `name`.
|
|
@@ -114,6 +116,9 @@ function _extractStatement(statement, source) {
|
|
|
114
116
|
const examples = jsDoc?.examples;
|
|
115
117
|
// Heritage (`extends` / `implements`) is only meaningful for classes and interfaces.
|
|
116
118
|
const heritage = _getHeritage(statement, source);
|
|
119
|
+
// Referenced type names (type aliases) and structured property lists (interfaces / object-literal types) — both resolved to links at render time.
|
|
120
|
+
const types = _getReferencedTypes(statement, source);
|
|
121
|
+
const properties = _getProperties(statement, source);
|
|
117
122
|
const children = _getClassMembers(statement, source, name);
|
|
118
123
|
return {
|
|
119
124
|
type: "tree-documentation",
|
|
@@ -132,18 +137,21 @@ function _extractStatement(statement, source) {
|
|
|
132
137
|
examples,
|
|
133
138
|
extends: heritage?.extends,
|
|
134
139
|
implements: heritage?.implements,
|
|
140
|
+
types,
|
|
141
|
+
properties,
|
|
135
142
|
children,
|
|
136
143
|
},
|
|
137
144
|
};
|
|
138
145
|
}
|
|
139
|
-
/** Extract the `extends` (single base type) and `implements` (interface list)
|
|
146
|
+
/** Extract the `extends` (single base type) and `implements` (interface list) heritage from a class or interface declaration, as full type text including any generic arguments (e.g. `AbstractStore<string>`, `Omit<StringSchemaOptions, "value">`). */
|
|
140
147
|
function _getHeritage(statement, source) {
|
|
141
148
|
if (!ts.isClassDeclaration(statement) && !ts.isInterfaceDeclaration(statement))
|
|
142
149
|
return;
|
|
143
150
|
let extendsName;
|
|
144
151
|
const implementsNames = [];
|
|
145
152
|
for (const clause of statement.heritageClauses ?? []) {
|
|
146
|
-
|
|
153
|
+
// Full text — keep generic arguments (`Foo<T>`) and wrappers (`Omit<…>`) intact; render-time lookup trims them to the bare name to resolve a link.
|
|
154
|
+
const names = clause.types.map(t => t.getText(source));
|
|
147
155
|
// `extends` keeps the first base type; an interface extending several still surfaces its primary base.
|
|
148
156
|
if (clause.token === ts.SyntaxKind.ExtendsKeyword)
|
|
149
157
|
extendsName ??= names[0];
|
|
@@ -154,6 +162,62 @@ function _getHeritage(statement, source) {
|
|
|
154
162
|
return;
|
|
155
163
|
return { extends: extendsName, implements: implementsNames.length ? implementsNames : undefined };
|
|
156
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Collect the type names a `type` alias's body references (e.g. `OtherType` in `type X = string | OtherType`), for render-time linking.
|
|
167
|
+
* - Walks the whole type expression so names nested inside generics, unions, arrays, etc. are all caught.
|
|
168
|
+
* - Primitive keyword types (`string`, `number`, …) aren't type references so they're naturally excluded; the alias's own generic parameters are filtered out explicitly.
|
|
169
|
+
* - Order-preserving and de-duplicated. Unresolved names (builtins like `Record`, externals) simply stay as plain text at render time.
|
|
170
|
+
*/
|
|
171
|
+
function _getReferencedTypes(statement, source) {
|
|
172
|
+
if (!ts.isTypeAliasDeclaration(statement))
|
|
173
|
+
return;
|
|
174
|
+
const generics = new Set(statement.typeParameters?.map(t => t.name.text) ?? []);
|
|
175
|
+
const names = [];
|
|
176
|
+
const seen = new Set();
|
|
177
|
+
const visit = (node) => {
|
|
178
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
179
|
+
const name = node.typeName.getText(source);
|
|
180
|
+
if (!generics.has(name) && !seen.has(name)) {
|
|
181
|
+
seen.add(name);
|
|
182
|
+
names.push(name);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
node.forEachChild(visit);
|
|
186
|
+
};
|
|
187
|
+
visit(statement.type);
|
|
188
|
+
return names.length ? names : undefined;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Extract structured property entries from an interface or object-literal `type` alias, mirroring the `DocumentationParam` shape.
|
|
192
|
+
* - Lets a consumer flatten an options-bag parameter into its individual fields at render time (resolve the param's type in the tree map, then list these).
|
|
193
|
+
* - Skips private `_`-prefixed members. Descriptions and `@default` values come from each member's own JSDoc.
|
|
194
|
+
*/
|
|
195
|
+
function _getProperties(statement, source) {
|
|
196
|
+
const members = ts.isInterfaceDeclaration(statement)
|
|
197
|
+
? statement.members
|
|
198
|
+
: ts.isTypeAliasDeclaration(statement) && ts.isTypeLiteralNode(statement.type)
|
|
199
|
+
? statement.type.members
|
|
200
|
+
: undefined;
|
|
201
|
+
if (!members)
|
|
202
|
+
return;
|
|
203
|
+
const properties = [];
|
|
204
|
+
for (const member of members) {
|
|
205
|
+
if (!ts.isPropertySignature(member))
|
|
206
|
+
continue;
|
|
207
|
+
const name = member.name && ts.isIdentifier(member.name) ? member.name.text : undefined;
|
|
208
|
+
if (!name || name.startsWith("_"))
|
|
209
|
+
continue;
|
|
210
|
+
const jsDoc = _getJSDoc(member, source);
|
|
211
|
+
properties.push({
|
|
212
|
+
name,
|
|
213
|
+
type: member.type?.getText(source),
|
|
214
|
+
description: jsDoc?.description,
|
|
215
|
+
optional: !!member.questionToken,
|
|
216
|
+
default: jsDoc?.default,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return properties.length ? properties : undefined;
|
|
220
|
+
}
|
|
157
221
|
/**
|
|
158
222
|
* Combine the JSDoc leading-description text and any unhandled `@rule` blocks into a single markup content string.
|
|
159
223
|
* - Unhandled rules (anything not `@param`/`@returns`/`@throws`/`@example`/`@see`) are appended after the description, separated by blank lines, with their `@name` preserved.
|
|
@@ -211,12 +275,13 @@ function _getSignatures(statement, source, name) {
|
|
|
211
275
|
return _getConstructorSignatures(statement, source, name);
|
|
212
276
|
}
|
|
213
277
|
if (ts.isInterfaceDeclaration(statement)) {
|
|
214
|
-
// Emit `{ member; member }` — the same shape a `type` object body produces, distinguished only by the `kind` badge.
|
|
215
|
-
|
|
216
|
-
return [members ? `{ ${members} }` : "{}"];
|
|
278
|
+
// Emit a pretty-printed `{ member; member }` block — the same shape a `type` object body produces, distinguished only by the `kind` badge.
|
|
279
|
+
return [_formatObjectSignature(statement.members, source)];
|
|
217
280
|
}
|
|
218
281
|
if (ts.isTypeAliasDeclaration(statement)) {
|
|
219
|
-
//
|
|
282
|
+
// Pretty-print object-literal aliases multi-line like an interface; emit other bodies (`string | null`, mapped types, …) verbatim. The alias name is already the page title.
|
|
283
|
+
if (ts.isTypeLiteralNode(statement.type))
|
|
284
|
+
return [_formatObjectSignature(statement.type.members, source)];
|
|
220
285
|
return [statement.type.getText(source)];
|
|
221
286
|
}
|
|
222
287
|
if (ts.isVariableStatement(statement)) {
|
|
@@ -225,6 +290,13 @@ function _getSignatures(statement, source, name) {
|
|
|
225
290
|
return [`${name}: ${declaration.type.getText(source)}`];
|
|
226
291
|
}
|
|
227
292
|
}
|
|
293
|
+
/** Pretty-print object-type members as a multi-line `{ … }` block — one member per tab-indented line ending in `;` — or `{}` when empty. */
|
|
294
|
+
function _formatObjectSignature(members, source) {
|
|
295
|
+
if (!members.length)
|
|
296
|
+
return "{}";
|
|
297
|
+
const lines = members.map(m => `\t${m.getText(source).replace(/;\s*$/, "").trim()};`);
|
|
298
|
+
return `{\n${lines.join("\n")}\n}`;
|
|
299
|
+
}
|
|
228
300
|
/** Render a class's generic parameter names as `<P, R>` (names only, no constraints), or `""` when non-generic. */
|
|
229
301
|
function _getTypeParamNames(statement, source) {
|
|
230
302
|
const { typeParameters } = statement;
|
|
@@ -438,6 +510,7 @@ function _getJSDoc(node, source) {
|
|
|
438
510
|
return;
|
|
439
511
|
const description = ts.getTextOfJSDocComment(jsDoc.comment)?.trim();
|
|
440
512
|
let kind;
|
|
513
|
+
let def;
|
|
441
514
|
const params = [];
|
|
442
515
|
const returns = [];
|
|
443
516
|
const throws = [];
|
|
@@ -469,6 +542,9 @@ function _getJSDoc(node, source) {
|
|
|
469
542
|
// `@kind <name>` overrides the AST-inferred kind (e.g. `@kind component` for a React component declared as a function).
|
|
470
543
|
if (name === "kind")
|
|
471
544
|
kind = comment?.match(/^[\w-]+/)?.[0];
|
|
545
|
+
// `@default <value>` documents a property's default — surfaced structurally (e.g. on `properties`) rather than rendered into the body.
|
|
546
|
+
else if (name === "default")
|
|
547
|
+
def = comment || undefined;
|
|
472
548
|
else if (name === "example") {
|
|
473
549
|
if (comment)
|
|
474
550
|
examples.push({ description: comment });
|
|
@@ -480,6 +556,7 @@ function _getJSDoc(node, source) {
|
|
|
480
556
|
return {
|
|
481
557
|
description: description || undefined,
|
|
482
558
|
kind,
|
|
559
|
+
default: def,
|
|
483
560
|
params: params.length ? params : undefined,
|
|
484
561
|
returns: returns.length ? returns : undefined,
|
|
485
562
|
throws: throws.length ? throws : undefined,
|
package/package.json
CHANGED
package/ui/block/Table.md
CHANGED
|
@@ -9,6 +9,7 @@ A data table — renders a `<table>`. Compose the usual `<thead>` / `<tbody>` /
|
|
|
9
9
|
- Wrap a wide table in a horizontally scrollable container if it may exceed the content width on small screens.
|
|
10
10
|
- Like the other block components it collapses its outer block margin when it is the first or last child.
|
|
11
11
|
- Spans the full width of its container by default; set the `width` variant (`narrow` / `normal` / `wide` / `full` / `fit`) to constrain it.
|
|
12
|
+
- Size columns with [`<TableHeader>`](/ui/TableHeader) / [`<TableCell>`](/ui/TableCell) and the `width` variant — `width="fit"` hugs the content, and `width="12x" grow` gives a column a hard minimum it can grow past (cells honour `min-width`, so the table scrolls rather than collapsing the column on a narrow screen). A column's width is the widest of its cells.
|
|
12
13
|
- Inside [`Prose`](/ui/Prose) a raw `<table>` picks up the same styling, so Markdown-rendered tables match component ones.
|
|
13
14
|
|
|
14
15
|
## Usage
|
|
@@ -50,6 +51,7 @@ import { Table } from "shelving/ui";
|
|
|
50
51
|
|
|
51
52
|
## See also
|
|
52
53
|
|
|
54
|
+
- [`TableHeader`](/ui/TableHeader) / [`TableCell`](/ui/TableCell) — `<th>` / `<td>` cells that set column widths and typography via variants.
|
|
53
55
|
- [`Definitions`](/ui/Definitions) — term/value pairs when a two-column key/value layout fits better than a grid.
|
|
54
56
|
- [`Prose`](/ui/Prose) — styles raw `<table>` inside longform content.
|
|
55
57
|
- [`ui`](/ui) — the styling system: tint ladder, label tokens, and theming.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { type TypographyVariants } from "../style/Typography.js";
|
|
3
|
+
import { type WidthVariants } from "../style/Width.js";
|
|
4
|
+
import type { ChildProps } from "../util/props.js";
|
|
5
|
+
/**
|
|
6
|
+
* Props for `TableCell` — width and typography variants plus `children`.
|
|
7
|
+
*
|
|
8
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableCell/TableCellProps
|
|
9
|
+
*/
|
|
10
|
+
export interface TableCellProps extends WidthVariants, TypographyVariants, ChildProps {
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Table body cell — rendered as `<td>`.
|
|
14
|
+
* - Sets its column's width via the `width` variant. Unlike a `<col>`, a cell honours `min-width`, so `width="12x" grow` gives the column a hard minimum that fills the remaining space and keeps the table from collapsing the column on a narrow viewport.
|
|
15
|
+
* - A column's width is the widest of its cells, so use this to size a column that has no header to set the width on.
|
|
16
|
+
*
|
|
17
|
+
* @kind component
|
|
18
|
+
* @param props Width and typography variant props plus `children`.
|
|
19
|
+
* @returns Rendered `<td>` element.
|
|
20
|
+
* @example <TableCell width="12x" grow>A longer description that wants a sensible minimum width.</TableCell>
|
|
21
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableCell/TableCell
|
|
22
|
+
*/
|
|
23
|
+
export declare function TableCell({ children, ...props }: TableCellProps): ReactElement;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getTypographyClass } from "../style/Typography.js";
|
|
3
|
+
import { getWidthClass } from "../style/Width.js";
|
|
4
|
+
import { getClass } from "../util/css.js";
|
|
5
|
+
/**
|
|
6
|
+
* Table body cell — rendered as `<td>`.
|
|
7
|
+
* - Sets its column's width via the `width` variant. Unlike a `<col>`, a cell honours `min-width`, so `width="12x" grow` gives the column a hard minimum that fills the remaining space and keeps the table from collapsing the column on a narrow viewport.
|
|
8
|
+
* - A column's width is the widest of its cells, so use this to size a column that has no header to set the width on.
|
|
9
|
+
*
|
|
10
|
+
* @kind component
|
|
11
|
+
* @param props Width and typography variant props plus `children`.
|
|
12
|
+
* @returns Rendered `<td>` element.
|
|
13
|
+
* @example <TableCell width="12x" grow>A longer description that wants a sensible minimum width.</TableCell>
|
|
14
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableCell/TableCell
|
|
15
|
+
*/
|
|
16
|
+
export function TableCell({ children, ...props }) {
|
|
17
|
+
return _jsx("td", { className: getClass(getWidthClass(props), getTypographyClass(props)) || undefined, children: children });
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# TableCell
|
|
2
|
+
|
|
3
|
+
A table body cell — renders a `<td>`. Use it for the body rows of a [`Table`](/ui/Table); it sets the column's width and cell typography.
|
|
4
|
+
|
|
5
|
+
**Things to know:**
|
|
6
|
+
|
|
7
|
+
- Set the column's width with the `width` variant. Because a cell (unlike a `<col>`) honours `min-width`, `width="12x" grow` gives the column a *hard* minimum that fills the remaining space and keeps the table from collapsing the column on a narrow screen — the table scrolls instead. See [`getWidthClass`](/ui/getWidthClass).
|
|
8
|
+
- A column's width is the widest of its cells, so this is how you size a column that has no header to set the width on (e.g. a trailing description column).
|
|
9
|
+
- Carries the same [typography variants](/ui/getTypographyClass) as the prose components (`size`, `weight`, `font`, `tint`, alignment).
|
|
10
|
+
- For a column that does have a header, prefer setting the width once on its [`TableHeader`](/ui/TableHeader).
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Table, TableCell } from "shelving/ui";
|
|
16
|
+
|
|
17
|
+
<Table>
|
|
18
|
+
<tbody>
|
|
19
|
+
<tr>
|
|
20
|
+
<TableCell>name</TableCell>
|
|
21
|
+
<TableCell width="12x" grow>A longer description that should keep a sensible minimum width.</TableCell>
|
|
22
|
+
</tr>
|
|
23
|
+
</tbody>
|
|
24
|
+
</Table>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Styling
|
|
28
|
+
|
|
29
|
+
`TableCell` paints nothing of its own — it composes the [`width`](/ui/getWidthClass) and [`typography`](/ui/getTypographyClass) variants onto a `<td>`, and inherits the cell styling (borders, padding) from the surrounding [`Table`](/ui/Table). It exposes no hooks of its own.
|
|
30
|
+
|
|
31
|
+
## See also
|
|
32
|
+
|
|
33
|
+
- [`TableHeader`](/ui/TableHeader) — the `<th>` equivalent for header cells.
|
|
34
|
+
- [`Table`](/ui/Table) — the table the cells belong to.
|
|
35
|
+
- [`getWidthClass`](/ui/getWidthClass) — the full `width` / `grow` variant reference.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { getTypographyClass, type TypographyVariants } from "../style/Typography.js";
|
|
3
|
+
import { getWidthClass, type WidthVariants } from "../style/Width.js";
|
|
4
|
+
import { getClass } from "../util/css.js";
|
|
5
|
+
import type { ChildProps } from "../util/props.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props for `TableCell` — width and typography variants plus `children`.
|
|
9
|
+
*
|
|
10
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableCell/TableCellProps
|
|
11
|
+
*/
|
|
12
|
+
export interface TableCellProps extends WidthVariants, TypographyVariants, ChildProps {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Table body cell — rendered as `<td>`.
|
|
16
|
+
* - Sets its column's width via the `width` variant. Unlike a `<col>`, a cell honours `min-width`, so `width="12x" grow` gives the column a hard minimum that fills the remaining space and keeps the table from collapsing the column on a narrow viewport.
|
|
17
|
+
* - A column's width is the widest of its cells, so use this to size a column that has no header to set the width on.
|
|
18
|
+
*
|
|
19
|
+
* @kind component
|
|
20
|
+
* @param props Width and typography variant props plus `children`.
|
|
21
|
+
* @returns Rendered `<td>` element.
|
|
22
|
+
* @example <TableCell width="12x" grow>A longer description that wants a sensible minimum width.</TableCell>
|
|
23
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableCell/TableCell
|
|
24
|
+
*/
|
|
25
|
+
export function TableCell({ children, ...props }: TableCellProps): ReactElement {
|
|
26
|
+
return <td className={getClass(getWidthClass(props), getTypographyClass(props)) || undefined}>{children}</td>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { type TypographyVariants } from "../style/Typography.js";
|
|
3
|
+
import { type WidthVariants } from "../style/Width.js";
|
|
4
|
+
import type { ChildProps } from "../util/props.js";
|
|
5
|
+
/**
|
|
6
|
+
* Props for `TableHeader` — width and typography variants plus `children`.
|
|
7
|
+
*
|
|
8
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableHeader/TableHeaderProps
|
|
9
|
+
*/
|
|
10
|
+
export interface TableHeaderProps extends WidthVariants, TypographyVariants, ChildProps {
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Table header cell — rendered as `<th>`.
|
|
14
|
+
* - Sets its column's width via the `width` variant (e.g. `width="fit"` to hug the content). Unlike a `<col>`, a cell honours `min-width`, so `width="12x" grow` gives the column a hard minimum it can grow past.
|
|
15
|
+
* - A column's width is the widest of its cells, so sizing the `<th>` sizes the whole column.
|
|
16
|
+
*
|
|
17
|
+
* @kind component
|
|
18
|
+
* @param props Width and typography variant props plus `children`.
|
|
19
|
+
* @returns Rendered `<th>` element.
|
|
20
|
+
* @example <TableHeader width="fit">Parameter</TableHeader>
|
|
21
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableHeader/TableHeader
|
|
22
|
+
*/
|
|
23
|
+
export declare function TableHeader({ children, ...props }: TableHeaderProps): ReactElement;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getTypographyClass } from "../style/Typography.js";
|
|
3
|
+
import { getWidthClass } from "../style/Width.js";
|
|
4
|
+
import { getClass } from "../util/css.js";
|
|
5
|
+
/**
|
|
6
|
+
* Table header cell — rendered as `<th>`.
|
|
7
|
+
* - Sets its column's width via the `width` variant (e.g. `width="fit"` to hug the content). Unlike a `<col>`, a cell honours `min-width`, so `width="12x" grow` gives the column a hard minimum it can grow past.
|
|
8
|
+
* - A column's width is the widest of its cells, so sizing the `<th>` sizes the whole column.
|
|
9
|
+
*
|
|
10
|
+
* @kind component
|
|
11
|
+
* @param props Width and typography variant props plus `children`.
|
|
12
|
+
* @returns Rendered `<th>` element.
|
|
13
|
+
* @example <TableHeader width="fit">Parameter</TableHeader>
|
|
14
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableHeader/TableHeader
|
|
15
|
+
*/
|
|
16
|
+
export function TableHeader({ children, ...props }) {
|
|
17
|
+
return _jsx("th", { className: getClass(getWidthClass(props), getTypographyClass(props)) || undefined, children: children });
|
|
18
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# TableHeader
|
|
2
|
+
|
|
3
|
+
A table header cell — renders a `<th>`. Use it for the header row of a [`Table`](/ui/Table); it sets the column's width and label typography.
|
|
4
|
+
|
|
5
|
+
**Things to know:**
|
|
6
|
+
|
|
7
|
+
- Set the column's width with the `width` variant — `width="fit"` hugs the content. Because a cell (unlike a `<col>`) honours `min-width`, `width="12x" grow` gives the column a *hard* minimum it can grow past, so the table scrolls rather than collapsing the column on a narrow screen. See [`getWidthClass`](/ui/getWidthClass).
|
|
8
|
+
- A column's width is the widest of its cells, so sizing the `<th>` sizes the whole column — set the width once on the header instead of on every body cell.
|
|
9
|
+
- Carries the same [typography variants](/ui/getTypographyClass) as the prose components (`size`, `weight`, `font`, `tint`, alignment), though the `Table` already styles header cells in the label style by default.
|
|
10
|
+
- For a column that has no header, set the width on its [`TableCell`](/ui/TableCell)s instead.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Table, TableCell, TableHeader } from "shelving/ui";
|
|
16
|
+
|
|
17
|
+
<Table>
|
|
18
|
+
<thead>
|
|
19
|
+
<tr>
|
|
20
|
+
<TableHeader width="fit">Parameter</TableHeader>
|
|
21
|
+
<TableHeader width="fit">Type</TableHeader>
|
|
22
|
+
<TableHeader width="12x" grow>Description</TableHeader>
|
|
23
|
+
</tr>
|
|
24
|
+
</thead>
|
|
25
|
+
<tbody>
|
|
26
|
+
<tr>
|
|
27
|
+
<TableCell>name</TableCell>
|
|
28
|
+
<TableCell>string</TableCell>
|
|
29
|
+
<TableCell>The name of the thing.</TableCell>
|
|
30
|
+
</tr>
|
|
31
|
+
</tbody>
|
|
32
|
+
</Table>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Styling
|
|
36
|
+
|
|
37
|
+
`TableHeader` paints nothing of its own — it composes the [`width`](/ui/getWidthClass) and [`typography`](/ui/getTypographyClass) variants onto a `<th>`, and inherits the header styling (label font, borders, padding) from the surrounding [`Table`](/ui/Table). It exposes no hooks of its own.
|
|
38
|
+
|
|
39
|
+
## See also
|
|
40
|
+
|
|
41
|
+
- [`TableCell`](/ui/TableCell) — the `<td>` equivalent for body cells.
|
|
42
|
+
- [`Table`](/ui/Table) — the table the cells belong to.
|
|
43
|
+
- [`getWidthClass`](/ui/getWidthClass) — the full `width` / `grow` variant reference.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { getTypographyClass, type TypographyVariants } from "../style/Typography.js";
|
|
3
|
+
import { getWidthClass, type WidthVariants } from "../style/Width.js";
|
|
4
|
+
import { getClass } from "../util/css.js";
|
|
5
|
+
import type { ChildProps } from "../util/props.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props for `TableHeader` — width and typography variants plus `children`.
|
|
9
|
+
*
|
|
10
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableHeader/TableHeaderProps
|
|
11
|
+
*/
|
|
12
|
+
export interface TableHeaderProps extends WidthVariants, TypographyVariants, ChildProps {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Table header cell — rendered as `<th>`.
|
|
16
|
+
* - Sets its column's width via the `width` variant (e.g. `width="fit"` to hug the content). Unlike a `<col>`, a cell honours `min-width`, so `width="12x" grow` gives the column a hard minimum it can grow past.
|
|
17
|
+
* - A column's width is the widest of its cells, so sizing the `<th>` sizes the whole column.
|
|
18
|
+
*
|
|
19
|
+
* @kind component
|
|
20
|
+
* @param props Width and typography variant props plus `children`.
|
|
21
|
+
* @returns Rendered `<th>` element.
|
|
22
|
+
* @example <TableHeader width="fit">Parameter</TableHeader>
|
|
23
|
+
* @see https://dhoulb.github.io/shelving/ui/block/TableHeader/TableHeader
|
|
24
|
+
*/
|
|
25
|
+
export function TableHeader({ children, ...props }: TableHeaderProps): ReactElement {
|
|
26
|
+
return <th className={getClass(getWidthClass(props), getTypographyClass(props)) || undefined}>{children}</th>;
|
|
27
|
+
}
|
package/ui/block/index.d.ts
CHANGED
|
@@ -14,5 +14,7 @@ export * from "./Preformatted.js";
|
|
|
14
14
|
export * from "./Prose.js";
|
|
15
15
|
export * from "./Subheading.js";
|
|
16
16
|
export * from "./Table.js";
|
|
17
|
+
export * from "./TableCell.js";
|
|
18
|
+
export * from "./TableHeader.js";
|
|
17
19
|
export * from "./Title.js";
|
|
18
20
|
export * from "./Video.js";
|
package/ui/block/index.js
CHANGED
|
@@ -14,5 +14,7 @@ export * from "./Preformatted.js";
|
|
|
14
14
|
export * from "./Prose.js";
|
|
15
15
|
export * from "./Subheading.js";
|
|
16
16
|
export * from "./Table.js";
|
|
17
|
+
export * from "./TableCell.js";
|
|
18
|
+
export * from "./TableHeader.js";
|
|
17
19
|
export * from "./Title.js";
|
|
18
20
|
export * from "./Video.js";
|
package/ui/block/index.ts
CHANGED
|
@@ -14,5 +14,7 @@ export * from "./Preformatted.js";
|
|
|
14
14
|
export * from "./Prose.js";
|
|
15
15
|
export * from "./Subheading.js";
|
|
16
16
|
export * from "./Table.js";
|
|
17
|
+
export * from "./TableCell.js";
|
|
18
|
+
export * from "./TableHeader.js";
|
|
17
19
|
export * from "./Title.js";
|
|
18
20
|
export * from "./Video.js";
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type
|
|
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.
|
|
5
|
-
* - Renders breadcrumbs, title (with kind + `readonly` tags), relational links (`member of`, `extends`, `implements`), signatures (one per overload), content, parameters, returns, throws, and examples.
|
|
5
|
+
* - Renders breadcrumbs, title (with kind + `readonly` tags), relational links (`member of`, `extends`, `implements`), signatures (one per overload), content, parameters, returns, throws, referenced types, and examples.
|
|
6
|
+
* - In the Parameters / Returns / Throws tables the `Type` column links each type to its documented page via `TreeLink` (exact-match only; compound or builtin types stay plain text), and a row with no hand-written description falls back to the referenced type's own `description`.
|
|
7
|
+
* - An options-bag parameter whose type resolves to a documented interface/object type is flattened into indented child rows (one per property), so readers see the individual fields inline.
|
|
8
|
+
* - A `type` alias's referenced type names render as a linked `Type` table, each row carrying the resolved element's `description` (exact-match only).
|
|
6
9
|
* - Child symbols are grouped by `kind` into card sections (Functions, Classes, Methods, Properties, …), each under its own heading.
|
|
7
10
|
* - All sections are conditional — only render when they have entries.
|
|
8
11
|
*
|
|
@@ -12,4 +15,4 @@ import type { DocumentationElementProps } from "../../util/tree.js";
|
|
|
12
15
|
* @example <DocumentationPage {...element.props} />
|
|
13
16
|
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationPage/DocumentationPage
|
|
14
17
|
*/
|
|
15
|
-
export declare function DocumentationPage({ title, name, kind, description, content, signatures, params, returns, throws, examples, children, ...props }: DocumentationElementProps): ReactNode;
|
|
18
|
+
export declare function DocumentationPage({ title, name, kind, description, content, signatures, params, returns, throws, types, examples, children, ...props }: DocumentationElementProps): ReactNode;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from "react";
|
|
2
3
|
import { walkElements } from "../../util/element.js";
|
|
3
4
|
import { Block } from "../block/Block.js";
|
|
4
5
|
import { Heading } from "../block/Heading.js";
|
|
@@ -7,6 +8,8 @@ import { Preformatted } from "../block/Preformatted.js";
|
|
|
7
8
|
import { Prose } from "../block/Prose.js";
|
|
8
9
|
import { Header, Section } from "../block/Section.js";
|
|
9
10
|
import { Table } from "../block/Table.js";
|
|
11
|
+
import { TableCell } from "../block/TableCell.js";
|
|
12
|
+
import { TableHeader } from "../block/TableHeader.js";
|
|
10
13
|
import { Title } from "../block/Title.js";
|
|
11
14
|
import { Code } from "../inline/Code.js";
|
|
12
15
|
import { Markup } from "../misc/Markup.js";
|
|
@@ -15,10 +18,16 @@ import { Row } from "../style/Flex.js";
|
|
|
15
18
|
import { Scroll } from "../style/Scroll.js";
|
|
16
19
|
import { TreeBreadcrumbs } from "../tree/TreeBreadcrumbs.js";
|
|
17
20
|
import { TreeCards } from "../tree/TreeCards.js";
|
|
21
|
+
import { getTreeElement, useTreeMap } from "../tree/TreeContext.js";
|
|
22
|
+
import { TreeLink } from "../tree/TreeLink.js";
|
|
18
23
|
import { DocumentationButtons } from "./DocumentationButtons.js";
|
|
19
24
|
import { DocumentationKind, getDocumentationKindColor } from "./DocumentationKind.js";
|
|
20
25
|
import { DocumentationSignatures } from "./DocumentationSignatures.js";
|
|
21
26
|
const DEFAULT_TYPE = "unknown";
|
|
27
|
+
/** Resolve a table row's description — the manually-written one, falling back to the referenced type's own `description` from the tree map (exact-match only). */
|
|
28
|
+
function _getRowDescription(map, type, description) {
|
|
29
|
+
return description || getTreeElement(map, type)?.props.description || "";
|
|
30
|
+
}
|
|
22
31
|
/** Documentation `kind`s grouped into card sections, in display order — pluralised, sentence-case headings. */
|
|
23
32
|
const KIND_SECTIONS = {
|
|
24
33
|
component: "Components",
|
|
@@ -57,7 +66,10 @@ function DocumentationChildren({ elements }) {
|
|
|
57
66
|
}
|
|
58
67
|
/**
|
|
59
68
|
* Page renderer for a `tree-documentation` element — the full detail page for a documented symbol.
|
|
60
|
-
* - Renders breadcrumbs, title (with kind + `readonly` tags), relational links (`member of`, `extends`, `implements`), signatures (one per overload), content, parameters, returns, throws, and examples.
|
|
69
|
+
* - Renders breadcrumbs, title (with kind + `readonly` tags), relational links (`member of`, `extends`, `implements`), signatures (one per overload), content, parameters, returns, throws, referenced types, and examples.
|
|
70
|
+
* - In the Parameters / Returns / Throws tables the `Type` column links each type to its documented page via `TreeLink` (exact-match only; compound or builtin types stay plain text), and a row with no hand-written description falls back to the referenced type's own `description`.
|
|
71
|
+
* - An options-bag parameter whose type resolves to a documented interface/object type is flattened into indented child rows (one per property), so readers see the individual fields inline.
|
|
72
|
+
* - A `type` alias's referenced type names render as a linked `Type` table, each row carrying the resolved element's `description` (exact-match only).
|
|
61
73
|
* - Child symbols are grouped by `kind` into card sections (Functions, Classes, Methods, Properties, …), each under its own heading.
|
|
62
74
|
* - All sections are conditional — only render when they have entries.
|
|
63
75
|
*
|
|
@@ -67,6 +79,11 @@ function DocumentationChildren({ elements }) {
|
|
|
67
79
|
* @example <DocumentationPage {...element.props} />
|
|
68
80
|
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationPage/DocumentationPage
|
|
69
81
|
*/
|
|
70
|
-
export function DocumentationPage({ title, name, kind = "unknown", description, content, signatures, params, returns, throws, examples, children, ...props }) {
|
|
71
|
-
|
|
82
|
+
export function DocumentationPage({ title, name, kind = "unknown", description, content, signatures, params, returns, throws, types, examples, children, ...props }) {
|
|
83
|
+
const map = useTreeMap();
|
|
84
|
+
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 || types?.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(TableHeader, { width: "fit", children: "Param" }), _jsx(TableHeader, { width: "fit", children: "Type" }), _jsx(TableHeader, { width: "fit", children: "Default" })] }) }), _jsx("tbody", { children: params.map(({ name, type = DEFAULT_TYPE, description, default: def }) => {
|
|
85
|
+
// An options-bag param whose type resolves to a documented interface/object type is flattened into its individual fields as indented child rows.
|
|
86
|
+
const resolved = getTreeElement(map, type)?.props;
|
|
87
|
+
return (_jsxs(Fragment, { children: [_jsxs("tr", { children: [_jsx(TableCell, { children: _jsx(Code, { children: name }) }), _jsx(TableCell, { children: _jsx(TreeLink, { name: type }) }), _jsx(TableCell, { children: def ? _jsx(Code, { children: def }) : "-" }), _jsx(TableCell, { width: "20x", grow: true, children: description || resolved?.description || "" })] }), resolved?.properties?.map(prop => (_jsxs("tr", { children: [_jsx(TableCell, { children: _jsx(Code, { children: `.${prop.name}` }) }), _jsx(TableCell, { children: _jsx(TreeLink, { name: prop.type ?? DEFAULT_TYPE }) }), _jsx(TableCell, { children: prop.default ? _jsx(Code, { children: prop.default }) : "-" }), _jsx(TableCell, { width: "20x", grow: true, children: _getRowDescription(map, prop.type ?? DEFAULT_TYPE, prop.description) })] }, `${name}.${prop.name}`)))] }, `${name}-${type}`));
|
|
88
|
+
}) })] }) }) })), returns?.length && (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsx("tr", { children: _jsx(TableHeader, { width: "fit", children: "Return" }) }) }), _jsx("tbody", { children: returns.map(({ type = DEFAULT_TYPE, description }) => (_jsxs("tr", { children: [_jsx(TableCell, { children: _jsx(TreeLink, { name: type }) }), _jsx(TableCell, { width: "20x", grow: true, children: _getRowDescription(map, type, description) })] }, `${type}-${description}`))) })] }) }) })), throws?.length && (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsx("tr", { children: _jsx(TableHeader, { width: "fit", children: "Throws" }) }) }), _jsx("tbody", { children: throws.map(({ type = DEFAULT_TYPE, description }) => (_jsxs("tr", { children: [_jsx(TableCell, { children: _jsx(TreeLink, { name: type }) }), _jsx(TableCell, { width: "20x", grow: true, children: _getRowDescription(map, type, description) })] }, `${type}-${description}`))) })] }) }) })), types?.length && (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsx("tr", { children: _jsx(TableHeader, { width: "fit", children: "Type" }) }) }), _jsx("tbody", { children: types.map(type => (_jsxs("tr", { children: [_jsx(TableCell, { children: _jsx(TreeLink, { name: type }) }), _jsx(TableCell, { width: "20x", grow: true, children: _getRowDescription(map, type) })] }, type))) })] }) }) }))] })) : 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 })] }) }));
|
|
72
89
|
}
|
|
@@ -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. Parameters render as a [`Table`](/ui/Table) (`
|
|
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) (`Param` / `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
|
|
@@ -57,7 +57,7 @@ describe("DocumentationPage", () => {
|
|
|
57
57
|
"./makeThing",
|
|
58
58
|
);
|
|
59
59
|
// Parameters table headers — name, type, default, description in separate columns.
|
|
60
|
-
expect(html).toContain("<th>
|
|
60
|
+
expect(html).toContain("<th>Param</th>");
|
|
61
61
|
expect(html).toContain("<th>Type</th>");
|
|
62
62
|
expect(html).toContain("<th>Default</th>");
|
|
63
63
|
// A param with a default renders it; one without gets a hyphen.
|