@waveso/docs 0.10.0 → 0.11.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/CHANGELOG.md +380 -0
- package/README.md +77 -0
- package/dist/code-frame.d.ts +28 -1
- package/dist/code-frame.js +36 -1
- package/dist/frontmatter.d.ts +5 -0
- package/dist/frontmatter.js +5 -0
- package/dist/nav-order.d.ts +14 -1
- package/dist/nav-order.js +15 -1
- package/dist/next.js +26 -1
- package/dist/plugins/rehype-code-frame.js +41 -11
- package/dist/react/explore.d.ts +53 -0
- package/dist/react/explore.js +86 -0
- package/dist/react/markdown-components.js +14 -0
- package/dist/react/shell-labels.d.ts +8 -1
- package/dist/react/shell-labels.js +1 -0
- package/dist/styles.css +688 -130
- package/dist/types.d.ts +31 -1
- package/package.json +5 -1
package/dist/next.js
CHANGED
|
@@ -3,12 +3,13 @@ import { docsError } from "./docs-error.js";
|
|
|
3
3
|
import { DOCS_CONTENT_ID } from "./docs-content-id.js";
|
|
4
4
|
import { describeSuggestion } from "./link-suggestion.js";
|
|
5
5
|
import { mapPooled } from "./map-pooled.js";
|
|
6
|
-
import { neighbours } from "./nav-order.js";
|
|
6
|
+
import { neighbours, readingOrder, stepTitle } from "./nav-order.js";
|
|
7
7
|
import { findFunctionValuedOptions } from "./search-options.js";
|
|
8
8
|
import { createMarkdownComponents } from "./react/markdown-components.js";
|
|
9
9
|
import { DocContent } from "./react/doc-content.js";
|
|
10
10
|
import { DocsHero } from "./react/hero.js";
|
|
11
11
|
import { DocsPager } from "./react/pager.js";
|
|
12
|
+
import { DocsExplore } from "./react/explore.js";
|
|
12
13
|
import { DocsToc } from "./react/toc.js";
|
|
13
14
|
import { wrapNextLink } from "./react/link-adapter.js";
|
|
14
15
|
import { createDocsRenderer } from "./render.js";
|
|
@@ -429,6 +430,25 @@ function createDocsRoute(options) {
|
|
|
429
430
|
etag: `"${createHash("sha1").update(json).digest("hex")}"`
|
|
430
431
|
} });
|
|
431
432
|
};
|
|
433
|
+
/**
|
|
434
|
+
* A page's `next` rows with their link text filled in.
|
|
435
|
+
*
|
|
436
|
+
* ⚠️ THE ERROR IS THE POINT. Falling back to the href renders a URL where a
|
|
437
|
+
* sentence should be, on a page that builds cleanly — so an unresolvable row
|
|
438
|
+
* stops the build and names both fixes.
|
|
439
|
+
*/
|
|
440
|
+
async function resolveExplore(doc) {
|
|
441
|
+
const stops = readingOrder(await requestScopedSource.nav());
|
|
442
|
+
return (doc.frontmatter.explore ?? []).map((step) => {
|
|
443
|
+
const title = stepTitle(stops, step);
|
|
444
|
+
if (title === void 0) throw docsError("invalid-frontmatter", `${doc.href}: the "explore" entry pointing at "${step.href}" has no title, and no page in the navigation owns that route. Point it at a page in the tree, or give the entry its own \`title\` — which is what an external link or a page kept out of the navigation needs.`);
|
|
445
|
+
return {
|
|
446
|
+
question: step.question,
|
|
447
|
+
href: step.href,
|
|
448
|
+
title
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
}
|
|
432
452
|
async function renderRoute(segments) {
|
|
433
453
|
const doc = await getPage(segments);
|
|
434
454
|
if (doc === void 0) return (await loadNotFound())();
|
|
@@ -450,6 +470,11 @@ function createDocsRoute(options) {
|
|
|
450
470
|
...options.components
|
|
451
471
|
},
|
|
452
472
|
...copyLabels === void 0 ? {} : { labels: copyLabels }
|
|
473
|
+
}), (doc.frontmatter.explore?.length ?? 0) === 0 ? null : createElement(DocsExplore, {
|
|
474
|
+
links: await resolveExplore(doc),
|
|
475
|
+
Link: link,
|
|
476
|
+
...routeLabels?.explore === void 0 ? {} : { heading: routeLabels.explore },
|
|
477
|
+
...routeLabels?.externalLink === void 0 ? {} : { externalLabel: routeLabels.externalLink }
|
|
453
478
|
}), options.pager === false ? null : createElement(DocsPager, {
|
|
454
479
|
...neighbours(await requestScopedSource.nav(), doc.href),
|
|
455
480
|
Link: link,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CODE_COPY_ATTRIBUTE, CODE_FRAME_ATTRIBUTE } from "../code-frame.js";
|
|
1
|
+
import { CODE_COPY_ATTRIBUTE, CODE_FRAME_ATTRIBUTE, CODE_ICON_PATHS, CODE_ICON_STATES } from "../code-frame.js";
|
|
2
2
|
import { parseCodeMeta } from "../code-meta.js";
|
|
3
3
|
import { CONTINUE, SKIP, visit } from "unist-util-visit";
|
|
4
4
|
//#region src/plugins/rehype-code-frame.ts
|
|
@@ -28,12 +28,17 @@ const rehypeCodeFrame = (options = {}) => {
|
|
|
28
28
|
value: title
|
|
29
29
|
}]
|
|
30
30
|
});
|
|
31
|
-
children.push(copyButton(title === void 0 ? copyLabel : copyFromLabel.replace("{title}", title)),
|
|
31
|
+
children.push(copyButton(title === void 0 ? copyLabel : copyFromLabel.replace("{title}", title)), {
|
|
32
|
+
type: "element",
|
|
33
|
+
tagName: "div",
|
|
34
|
+
properties: { className: ["wave-docs-panel__body", "wave-docs-code__body"] },
|
|
35
|
+
children: [node]
|
|
36
|
+
});
|
|
32
37
|
parent.children[index] = {
|
|
33
38
|
type: "element",
|
|
34
39
|
tagName: "figure",
|
|
35
40
|
properties: {
|
|
36
|
-
className: ["wave-docs-code"],
|
|
41
|
+
className: ["wave-docs-panel", "wave-docs-code"],
|
|
37
42
|
[CODE_FRAME_ATTRIBUTE]: "",
|
|
38
43
|
...language === void 0 ? {} : { "data-lang": language }
|
|
39
44
|
},
|
|
@@ -60,15 +65,40 @@ function copyButton(label) {
|
|
|
60
65
|
[CODE_COPY_ATTRIBUTE]: "",
|
|
61
66
|
"aria-label": label
|
|
62
67
|
},
|
|
63
|
-
children: [
|
|
68
|
+
children: CODE_ICON_STATES.map(([state, name]) => stateIcon(state, name))
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One Lucide glyph, as hast.
|
|
73
|
+
*
|
|
74
|
+
* Decoration, so it is out of the accessibility tree — the button reads as its
|
|
75
|
+
* `aria-label` — and out of the search index by the same rule that drops the
|
|
76
|
+
* heading anchor icons.
|
|
77
|
+
*/
|
|
78
|
+
function stateIcon(state, name) {
|
|
79
|
+
return {
|
|
80
|
+
type: "element",
|
|
81
|
+
tagName: "svg",
|
|
82
|
+
properties: {
|
|
83
|
+
className: ["wave-docs-code__copy-icon"],
|
|
84
|
+
"data-state": state,
|
|
85
|
+
"aria-hidden": "true",
|
|
86
|
+
focusable: "false",
|
|
87
|
+
viewBox: "0 0 24 24",
|
|
88
|
+
width: "16",
|
|
89
|
+
height: "16",
|
|
90
|
+
fill: "none",
|
|
91
|
+
stroke: "currentColor",
|
|
92
|
+
strokeWidth: "2",
|
|
93
|
+
strokeLinecap: "round",
|
|
94
|
+
strokeLinejoin: "round"
|
|
95
|
+
},
|
|
96
|
+
children: CODE_ICON_PATHS[name].map((d) => ({
|
|
64
97
|
type: "element",
|
|
65
|
-
tagName: "
|
|
66
|
-
properties: {
|
|
67
|
-
children: [
|
|
68
|
-
|
|
69
|
-
value: "⧉"
|
|
70
|
-
}]
|
|
71
|
-
}]
|
|
98
|
+
tagName: "path",
|
|
99
|
+
properties: { d },
|
|
100
|
+
children: []
|
|
101
|
+
}))
|
|
72
102
|
};
|
|
73
103
|
}
|
|
74
104
|
/** `code.data.meta` — read, never written. */
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DocsLinkComponent } from "./markdown-components.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/react/explore.d.ts
|
|
4
|
+
/** One row: a question, and the page that answers it. */
|
|
5
|
+
interface DocsExploreLink {
|
|
6
|
+
question: string;
|
|
7
|
+
href: string;
|
|
8
|
+
/** Resolved by the caller — from the navigation, or from the frontmatter. */
|
|
9
|
+
title: string;
|
|
10
|
+
}
|
|
11
|
+
interface DocsExploreProps {
|
|
12
|
+
links: DocsExploreLink[];
|
|
13
|
+
/** The block's heading. Defaults to `'Where to go next'`. */
|
|
14
|
+
heading?: string | undefined;
|
|
15
|
+
/** Client-side router link, e.g. `next/link`. Falls back to `<a>`. */
|
|
16
|
+
Link?: DocsLinkComponent | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Screen-reader suffix on a row whose answer is off-site. Defaults to
|
|
19
|
+
* `'(opens in a new tab)'`.
|
|
20
|
+
*/
|
|
21
|
+
externalLabel?: string | undefined;
|
|
22
|
+
className?: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* "Where to go next" — a question per row, and the page that answers it.
|
|
26
|
+
*
|
|
27
|
+
* The sidebar is a structure and this is a router: it says *why* a reader would
|
|
28
|
+
* go somewhere, which no tree of titles can. A page opts in by declaring
|
|
29
|
+
* `explore` in its frontmatter, so the same component is a landing page's
|
|
30
|
+
* onboarding and an ordinary page's footnote.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ `explore`, NOT `next` — AND NOT `steps` EITHER.
|
|
33
|
+
*
|
|
34
|
+
* `next` is unusable in this package: `src/next.ts` is the Next.js adapter, so
|
|
35
|
+
* two files one directory apart would carry the same name for entirely
|
|
36
|
+
* different things, and `doc.frontmatter.next` would read like a routing hook.
|
|
37
|
+
*
|
|
38
|
+
* `steps` is wrong for a second reason: these rows are a *branch*, not a
|
|
39
|
+
* sequence. A reader picks one and ignores the rest; none of them is first. A
|
|
40
|
+
* numbered "1 → 2 → 3" component is a real and separate thing worth building,
|
|
41
|
+
* and `steps` is the name it will need.
|
|
42
|
+
*
|
|
43
|
+
* ⚠️ A LIST, NOT A TABLE — which is what the markdown this replaces had to be.
|
|
44
|
+
* A screen reader announces "table, 2 columns, 7 rows" for what is a list of
|
|
45
|
+
* links with descriptions, and asks the reader to navigate it by cell. Two
|
|
46
|
+
* columns of sentence-length questions are also cramped on a phone, where this
|
|
47
|
+
* stacks instead.
|
|
48
|
+
*
|
|
49
|
+
* No client JavaScript: a heading and a list of links, rendered on the server.
|
|
50
|
+
*/
|
|
51
|
+
declare function DocsExplore({ links, heading, Link, externalLabel, className }: DocsExploreProps): ReactNode;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { DocsExplore, DocsExploreLink, DocsExploreProps };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { opensInNewTab } from "../safe-href.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/react/explore.tsx
|
|
4
|
+
/**
|
|
5
|
+
* "Where to go next" — a question per row, and the page that answers it.
|
|
6
|
+
*
|
|
7
|
+
* The sidebar is a structure and this is a router: it says *why* a reader would
|
|
8
|
+
* go somewhere, which no tree of titles can. A page opts in by declaring
|
|
9
|
+
* `explore` in its frontmatter, so the same component is a landing page's
|
|
10
|
+
* onboarding and an ordinary page's footnote.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ `explore`, NOT `next` — AND NOT `steps` EITHER.
|
|
13
|
+
*
|
|
14
|
+
* `next` is unusable in this package: `src/next.ts` is the Next.js adapter, so
|
|
15
|
+
* two files one directory apart would carry the same name for entirely
|
|
16
|
+
* different things, and `doc.frontmatter.next` would read like a routing hook.
|
|
17
|
+
*
|
|
18
|
+
* `steps` is wrong for a second reason: these rows are a *branch*, not a
|
|
19
|
+
* sequence. A reader picks one and ignores the rest; none of them is first. A
|
|
20
|
+
* numbered "1 → 2 → 3" component is a real and separate thing worth building,
|
|
21
|
+
* and `steps` is the name it will need.
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ A LIST, NOT A TABLE — which is what the markdown this replaces had to be.
|
|
24
|
+
* A screen reader announces "table, 2 columns, 7 rows" for what is a list of
|
|
25
|
+
* links with descriptions, and asks the reader to navigate it by cell. Two
|
|
26
|
+
* columns of sentence-length questions are also cramped on a phone, where this
|
|
27
|
+
* stacks instead.
|
|
28
|
+
*
|
|
29
|
+
* No client JavaScript: a heading and a list of links, rendered on the server.
|
|
30
|
+
*/
|
|
31
|
+
function DocsExplore({ links, heading = "Where to go next", Link, externalLabel = "(opens in a new tab)", className }) {
|
|
32
|
+
if (links.length === 0) return null;
|
|
33
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
34
|
+
className: [
|
|
35
|
+
"wave-docs-panel",
|
|
36
|
+
"wave-docs-explore",
|
|
37
|
+
className
|
|
38
|
+
].filter(Boolean).join(" "),
|
|
39
|
+
"aria-labelledby": "wave-docs-explore-heading",
|
|
40
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
41
|
+
className: "wave-docs-panel__header",
|
|
42
|
+
children: /* @__PURE__ */ jsx("h2", {
|
|
43
|
+
className: "wave-docs-panel__title",
|
|
44
|
+
id: "wave-docs-explore-heading",
|
|
45
|
+
children: heading
|
|
46
|
+
})
|
|
47
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
48
|
+
className: "wave-docs-panel__body wave-docs-explore__list",
|
|
49
|
+
children: links.map((row) => /* @__PURE__ */ jsxs("li", {
|
|
50
|
+
className: "wave-docs-explore__item",
|
|
51
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
52
|
+
className: "wave-docs-explore__question",
|
|
53
|
+
children: row.question
|
|
54
|
+
}), /* @__PURE__ */ jsx(ExploreAnswer, {
|
|
55
|
+
row,
|
|
56
|
+
Link,
|
|
57
|
+
externalLabel
|
|
58
|
+
})]
|
|
59
|
+
}, row.href))
|
|
60
|
+
})]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function ExploreAnswer({ row, Link, externalLabel }) {
|
|
64
|
+
const className = "wave-docs-explore__answer";
|
|
65
|
+
if (opensInNewTab(row.href)) return /* @__PURE__ */ jsxs("a", {
|
|
66
|
+
className,
|
|
67
|
+
href: row.href,
|
|
68
|
+
target: "_blank",
|
|
69
|
+
rel: "noopener noreferrer",
|
|
70
|
+
children: [row.title, /* @__PURE__ */ jsxs("span", {
|
|
71
|
+
className: "wave-docs-sr-only",
|
|
72
|
+
children: [" ", externalLabel]
|
|
73
|
+
})]
|
|
74
|
+
});
|
|
75
|
+
return Link === void 0 ? /* @__PURE__ */ jsx("a", {
|
|
76
|
+
className,
|
|
77
|
+
href: row.href,
|
|
78
|
+
children: row.title
|
|
79
|
+
}) : /* @__PURE__ */ jsx(Link, {
|
|
80
|
+
className,
|
|
81
|
+
href: row.href,
|
|
82
|
+
children: row.title
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { DocsExplore };
|
|
@@ -110,6 +110,20 @@ function createImage(Image) {
|
|
|
110
110
|
* "no tabindex on non-interactive elements", not a violation of it. A labelled
|
|
111
111
|
* `<section>` is a `region` landmark, so the tab stop announces itself instead
|
|
112
112
|
* of being a mystery stop in the tab order.
|
|
113
|
+
*
|
|
114
|
+
* ⚠️ ONE FRAME, AND IT WORE `.wave-docs-panel` FOR A WHILE.
|
|
115
|
+
*
|
|
116
|
+
* The panel — an outer frame, a header band, an inset card — exists to separate
|
|
117
|
+
* *chrome* from *content*. "Where to go next" and a code frame both have chrome
|
|
118
|
+
* to put in that band: a title, a language, a copy button. A table's header row
|
|
119
|
+
* is not chrome, it is data, and setting the body into a card away from its own
|
|
120
|
+
* header cost three vertical rules down each side, stopped the row dividers
|
|
121
|
+
* short of the box, and narrowed the reading width — on the densest element on
|
|
122
|
+
* a page, for nothing gained. Full-width dividers are what let an eye track a
|
|
123
|
+
* row across.
|
|
124
|
+
*
|
|
125
|
+
* It keeps the panel's outer radius, so a table and a code block still read as
|
|
126
|
+
* two of one family without the table pretending to have chrome it has not got.
|
|
113
127
|
*/
|
|
114
128
|
function createTable(label) {
|
|
115
129
|
return function MarkdownTable({ className, ...rest }) {
|
|
@@ -76,6 +76,13 @@ interface DocsLabels {
|
|
|
76
76
|
* The direction, not the destination: the page's own title is the name, and
|
|
77
77
|
* these two words are what say which way it lies.
|
|
78
78
|
*/
|
|
79
|
+
/**
|
|
80
|
+
* The "where to go next" block's heading. Defaults to `'Where to go next'`.
|
|
81
|
+
*
|
|
82
|
+
* A page opts into that block with `explore` in its frontmatter; this is the one
|
|
83
|
+
* string the package supplies for it, and the questions are the author's.
|
|
84
|
+
*/
|
|
85
|
+
explore?: string | undefined;
|
|
79
86
|
previousPage?: string | undefined;
|
|
80
87
|
/** The same for the next page. Defaults to `'Next'`. */
|
|
81
88
|
nextPage?: string | undefined;
|
|
@@ -154,7 +161,7 @@ interface DocsLabels {
|
|
|
154
161
|
* failure being guarded against is a key that is declared, documented and never
|
|
155
162
|
* read, which type-checks perfectly.
|
|
156
163
|
*/
|
|
157
|
-
declare const DOCS_LABEL_KEYS: readonly ["nav", "openNav", "closeNav", "skipToContent", "expandGroup", "collapseGroup", "toc", "backToTop", "externalLink", "table", "calloutNote", "calloutTip", "calloutImportant", "calloutWarning", "calloutCaution", "youtubeTitle", "youtubePlay", "youtubeHide", "copyCode", "copyCodeFrom", "previousPage", "nextPage", "pagination", "copied", "copyFailed"];
|
|
164
|
+
declare const DOCS_LABEL_KEYS: readonly ["nav", "openNav", "closeNav", "skipToContent", "expandGroup", "collapseGroup", "toc", "backToTop", "externalLink", "table", "calloutNote", "calloutTip", "calloutImportant", "calloutWarning", "calloutCaution", "youtubeTitle", "youtubePlay", "youtubeHide", "copyCode", "copyCodeFrom", "explore", "previousPage", "nextPage", "pagination", "copied", "copyFailed"];
|
|
158
165
|
/** The four the shell renders itself, resolved centrally. */
|
|
159
166
|
type ShellLabelKey = 'nav' | 'openNav' | 'closeNav' | 'skipToContent';
|
|
160
167
|
/**
|