@waveso/docs 0.7.0 → 0.8.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 +137 -0
- package/README.md +47 -5
- package/dist/frontmatter.d.ts +1 -0
- package/dist/frontmatter.js +1 -0
- package/dist/meta.d.ts +2 -0
- package/dist/meta.js +5 -2
- package/dist/next.d.ts +14 -0
- package/dist/next.js +3 -2
- package/dist/react/layout.d.ts +16 -1
- package/dist/react/layout.js +2 -1
- package/dist/react/nav.d.ts +4 -1
- package/dist/react/nav.js +3 -2
- package/dist/react/next-nav.d.ts +4 -1
- package/dist/react/next-nav.js +2 -1
- package/dist/react/sidebar.d.ts +51 -3
- package/dist/react/sidebar.js +103 -17
- package/dist/source.js +9 -4
- package/dist/styles.css +221 -0
- package/dist/types.d.ts +27 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,142 @@
|
|
|
1
1
|
# @waveso/docs
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 98f8041: **The sidebar tells a category from a page at a glance.** A folder on every
|
|
8
|
+
group, a page on every page.
|
|
9
|
+
|
|
10
|
+
Weight and a chevron were the only difference, and that is not enough to scan:
|
|
11
|
+
a `Reference` group sitting directly above an `Internals` page read as one
|
|
12
|
+
undifferentiated column, and a real tree interleaves the two a dozen times. A
|
|
13
|
+
silhouette is read before any word is.
|
|
14
|
+
|
|
15
|
+
Two inline SVG paths, in the same style as the disclosure chevron and the
|
|
16
|
+
external-link mark that were already there. The package still ships no icon
|
|
17
|
+
set and takes no icon dependency — a folder and a page are as generic as the
|
|
18
|
+
chevron beside them.
|
|
19
|
+
|
|
20
|
+
`icons={false}` on `DocsSidebar` turns them off, for a host whose own
|
|
21
|
+
navigation has a different vocabulary and does not want a second one.
|
|
22
|
+
|
|
23
|
+
⚠️ AN EXTERNAL LINK'S MARK MOVED TO THE HEAD OF ITS ROW. It used to sit at the
|
|
24
|
+
far end, which cost twice: the leading slot had to be an empty box to stop the
|
|
25
|
+
column going ragged, and the trailing edge carried two unrelated meanings —
|
|
26
|
+
"opens elsewhere" on one row, "expands" on the next. Leading is what a row _is_;
|
|
27
|
+
trailing is what it _does_. With the mark moved, the only thing at the far end
|
|
28
|
+
of any row is a chevron, which is what makes a group legible from across the
|
|
29
|
+
column, and it leaves that edge free for a status dot or an overflow control
|
|
30
|
+
later. A browser test measures the alignment, because the claim is about a
|
|
31
|
+
column and a column is geometry.
|
|
32
|
+
|
|
33
|
+
The sr-only "(opens in a new tab)" did **not** move with it — the name is still
|
|
34
|
+
read as "GitHub, opens in a new tab" rather than the other way round.
|
|
35
|
+
|
|
36
|
+
⚠️ AND `icons={false}` PUTS THE MARK BACK ON THE TRAILING EDGE. With no column
|
|
37
|
+
to lead, rendering nothing would leave a link that leaves your site looking
|
|
38
|
+
exactly like one that does not. Turning off a decorative column is not consent
|
|
39
|
+
to drop a warning.
|
|
40
|
+
|
|
41
|
+
The markers sit at `opacity: 0.4` and inherit their row's colour rather than
|
|
42
|
+
carrying a grey of their own, so the relationship holds at every weight — a
|
|
43
|
+
bold group title and a muted page title each get a marker a fixed step lighter
|
|
44
|
+
than themselves. Full strength on hover and on the current page.
|
|
45
|
+
|
|
46
|
+
New public class names: `.wave-docs-sidebar__icon` and
|
|
47
|
+
`.wave-docs-sidebar__label`. The label wrapper is the flex hook that lets a row
|
|
48
|
+
put a chevron at its far end, and it is present whether or not there is a
|
|
49
|
+
marker beside it.
|
|
50
|
+
|
|
51
|
+
## Your own icons, by name
|
|
52
|
+
|
|
53
|
+
The three built-ins are defaults, not a set. Content names an icon; the host
|
|
54
|
+
maps the name to a component:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
# content/internals.md
|
|
58
|
+
icon: wrench
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
// content/reference/meta.json
|
|
63
|
+
{ "title": "Reference", "icon": "book" }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<docs.Layout icons={{ wrench: Wrench, book: Book }}>{children}</docs.Layout>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Also on `DocsSidebar` and `DocsNav` for a hand-assembled shell.
|
|
71
|
+
|
|
72
|
+
⚠️ A NAME, NEVER ART THIS PACKAGE SHIPS. Content is authored in YAML and JSON
|
|
73
|
+
and cannot carry a React element, and a docs package mounted inside someone
|
|
74
|
+
else's application must not stand its iconography next to theirs. The bundle
|
|
75
|
+
grows by a lookup, not by an icon set — and never will by one.
|
|
76
|
+
|
|
77
|
+
⚠️ EVERY COMPONENT IN THE MAP MUST BE A CLIENT COMPONENT. `docs.Layout` is a
|
|
78
|
+
Server Component and the tree it hands the map to is not, so React serialises a
|
|
79
|
+
_reference_ to each icon; a server component cannot be one. Icons imported from
|
|
80
|
+
a library already satisfy this. The same boundary `search` documents.
|
|
81
|
+
|
|
82
|
+
A name with no entry in the map falls back to the built-in marker for that
|
|
83
|
+
node's type — a typo in one file leaves a folder where a book should be, not a
|
|
84
|
+
hole in the column.
|
|
85
|
+
|
|
86
|
+
### Patch Changes
|
|
87
|
+
|
|
88
|
+
- 72ad371: The sidebar's disclosure chevron points at the group it belongs to.
|
|
89
|
+
|
|
90
|
+
`.wave-docs-sidebar__group-header` is `justify-content: space-between`, so the
|
|
91
|
+
chevron sits flush against the navigation's inline end with the label at the
|
|
92
|
+
other side of the row — and unrotated it aimed at the panel's border. Worse
|
|
93
|
+
than at nothing: a chevron at the _trailing_ edge of a row is the platform
|
|
94
|
+
idiom for "this takes you somewhere else", so it read as navigation on a
|
|
95
|
+
control that only opens a list in place. A collapsed group now points back at
|
|
96
|
+
its own label; an open one still points down at its children.
|
|
97
|
+
|
|
98
|
+
⚠️ AND IT MIRRORS, WHICH A ROTATION DOES NOT DO ON ITS OWN. `rotate` is
|
|
99
|
+
physical — 180deg is left in every writing mode — while every other property
|
|
100
|
+
placing that row is logical. Under `dir="rtl"` the header mirrors, the chevron
|
|
101
|
+
moves to the inline start and the label lands to its right, so a single
|
|
102
|
+
unmirrored rotation would point it out of the panel on the other side: the same
|
|
103
|
+
defect, reflected.
|
|
104
|
+
|
|
105
|
+
⚠️ THE MIRROR IS `[dir='rtl']`, NOT `:dir(rtl)`, AND THAT IS NOT A STYLE
|
|
106
|
+
PREFERENCE. Next compiles this stylesheet with lightningcss, which downlevels
|
|
107
|
+
`:dir(rtl)` into a hardcoded list of right-to-left _languages_ —
|
|
108
|
+
`:is(:lang(ae), :lang(ar), … :lang(yi))`. Direction is not language, and the
|
|
109
|
+
substitution is wrong in both directions: `<html dir="rtl" lang="en">` gets no
|
|
110
|
+
mirror, `lang="ar" dir="ltr"` gets one it never asked for. It is invisible from
|
|
111
|
+
inside this repo, because the tests inject the source text into a `<style>`
|
|
112
|
+
element where `:dir()` behaves perfectly — it was caught by measuring the built
|
|
113
|
+
site. The stylesheet now uses `:dir()` nowhere, and a test enforces that.
|
|
114
|
+
|
|
115
|
+
## 0.7.1
|
|
116
|
+
|
|
117
|
+
### Patch Changes
|
|
118
|
+
|
|
119
|
+
- 43af6e9: The back-to-top link appears when there is something to go back to.
|
|
120
|
+
|
|
121
|
+
It sat at the foot of the table of contents on every page, including at the top
|
|
122
|
+
of one, offering to return a reader to where they already were. It now fades in
|
|
123
|
+
between 25dvh and 35dvh of scroll and fades back out on the way up.
|
|
124
|
+
|
|
125
|
+
No JavaScript was added to do it. The reveal is a scroll-driven animation, so
|
|
126
|
+
scroll position alone drives it — no listener, no state, no re-render per
|
|
127
|
+
frame, and correct before the component has hydrated. `DocsToc` is the smallest
|
|
128
|
+
client component this package ships and it has not grown by a byte.
|
|
129
|
+
|
|
130
|
+
`visibility` moves with the fade, so the link leaves the tab order while it is
|
|
131
|
+
invisible rather than sitting there as a focus target nobody can see — and it
|
|
132
|
+
rejoins only once it is legible, not at the first pixel of the fade.
|
|
133
|
+
|
|
134
|
+
Where the timeline cannot run the link is simply always present, exactly as it
|
|
135
|
+
was: Firefox has not shipped scroll-driven animations, a page too short to
|
|
136
|
+
scroll leaves the timeline inactive, and so does a host that scrolls an inner
|
|
137
|
+
pane rather than the document. Nothing hides a control on the strength of a
|
|
138
|
+
feature the engine did not run.
|
|
139
|
+
|
|
3
140
|
## 0.7.0
|
|
4
141
|
|
|
5
142
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -69,16 +69,16 @@ Every figure below is a **ceiling**, and `pnpm size` fails the build if the meas
|
|
|
69
69
|
|
|
70
70
|
| | At most |
|
|
71
71
|
| --- | --- |
|
|
72
|
-
| Everything the quick start ships, gzipped |
|
|
72
|
+
| Everything the quick start ships, gzipped | 14 KB |
|
|
73
73
|
| Search dialog and router wiring | 9.3 KB |
|
|
74
|
-
| Navigation: one sidebar, open and closed |
|
|
74
|
+
| Navigation: one sidebar, open and closed | 3 KB |
|
|
75
75
|
| Table of contents | 0.9 KB |
|
|
76
76
|
| Copy-button runtime | 1.1 KB |
|
|
77
77
|
| hast over the wire vs HTML, prose page | 1.20× |
|
|
78
78
|
| hast over the wire vs HTML, code and tables | 1.12× |
|
|
79
79
|
| Highlighting vs no highlighting | 2.00× |
|
|
80
80
|
|
|
81
|
-
The first row is the honest total: a reader who lands on a page of your documentation downloads under
|
|
81
|
+
The first row is the honest total: a reader who lands on a page of your documentation downloads under 14 KB gzipped of JavaScript from this package, and that is the whole of it. No markdown parser and no syntax highlighter reach the browser at all — those run in Node at build time. Drop the search dialog and it is under 4 KB.
|
|
82
82
|
|
|
83
83
|
The one real cost is the middle pair: shipping a tree instead of a string is about 20% more brotli on a prose page, and about 12% on a page with code and tables, where Shiki's token spans dominate both representations equally. That is the price of never handing markup to `dangerouslySetInnerHTML`, and it is the first number a skeptical reviewer should ask for.
|
|
84
84
|
|
|
@@ -202,7 +202,7 @@ Every component takes data as props, and every module that imports from `next/*`
|
|
|
202
202
|
| --- | --- | --- |
|
|
203
203
|
| `DocContent` | `react/doc-content` | Renders a hast tree, inside `.wave-docs-prose`. Server Component |
|
|
204
204
|
| `DocsHero` | `react/hero` | A landing page's header. `title`, `description`, `actions`, `Link`, `externalLabel`. Server Component |
|
|
205
|
-
| `DocsSidebar` | `react/sidebar` | Takes `pathname` as a prop, not from `next/navigation` |
|
|
205
|
+
| `DocsSidebar` | `react/sidebar` | Takes `pathname` as a prop, not from `next/navigation`. `icons` controls the marker column — see [Sidebar icons](#sidebar-icons) |
|
|
206
206
|
| `DocsToc` | `react/toc` | Scrollspy via `IntersectionObserver`. `label`, `topLabel`, `rootMargin`, `className` |
|
|
207
207
|
| `DocsSearch` | `react/next-search` | `SearchDialog`, wired to Next's router. What you want |
|
|
208
208
|
| `DocsLink` | `react/next-link` | `next/link`, adapted — pass it as `Link` when composing by hand |
|
|
@@ -212,7 +212,49 @@ Every component takes data as props, and every module that imports from `next/*`
|
|
|
212
212
|
| `SkipLink` | `react/skip-link` | Targets `docs.Page`'s `<main>`; `DOCS_CONTENT_ID` is that id. `docs.Layout` renders one |
|
|
213
213
|
| `createMarkdownComponents` | `react/markdown-components` | The element → component map. `defaultMarkdownComponents` is the unwired one |
|
|
214
214
|
|
|
215
|
-
`DocsToc`'s `rootMargin` is the `IntersectionObserver` margin that decides how far above the viewport a heading counts as current; the default keeps the highlight on the section you are reading rather than the one about to arrive. `topLabel` is the back-to-top link at the end.
|
|
215
|
+
`DocsToc`'s `rootMargin` is the `IntersectionObserver` margin that decides how far above the viewport a heading counts as current; the default keeps the highlight on the section you are reading rather than the one about to arrive. `topLabel` is the back-to-top link at the end — it fades in once the reader is about a third of a screen down and fades out again on the way back, on a scroll timeline rather than a scroll listener, so the component ships no extra bytes to do it. Where that timeline cannot run — an engine without scroll-driven animations, a page too short to scroll, or a host that scrolls an inner pane rather than the document — the link is simply always there.
|
|
216
|
+
|
|
217
|
+
### Sidebar icons
|
|
218
|
+
|
|
219
|
+
Every row in the sidebar carries a marker at its head: a folder on a group, a page on a page, an arrow on a link that leaves your site. Weight and a chevron were the only difference before, and where categories and pages interleave that is not enough to scan.
|
|
220
|
+
|
|
221
|
+
Three glyphs ship. **No icon set does**, and none ever will — this package is mounted inside applications that already have one, and a second vocabulary beside theirs is worse than none. Your own icons come in by name:
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
# content/reference/index.md
|
|
225
|
+
---
|
|
226
|
+
title: Reference
|
|
227
|
+
icon: book
|
|
228
|
+
---
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
// content/reference/meta.json — for a directory with no index page,
|
|
233
|
+
// and for hand-written links
|
|
234
|
+
{ "title": "Reference", "icon": "book", "pages": [{ "title": "npm", "href": "https://npmjs.com", "icon": "package" }] }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
import { DocsSidebar } from '@waveso/docs/react/sidebar';
|
|
239
|
+
import type { DocNavNode } from '@waveso/docs/types';
|
|
240
|
+
|
|
241
|
+
// Yours: `lucide-react`, your design system, or hand-written. Rendered with no
|
|
242
|
+
// props, in a 1rem box — `currentColor` and `100%` keep it in line with the
|
|
243
|
+
// built-ins and with the row it sits on.
|
|
244
|
+
const Book = () => (
|
|
245
|
+
<svg viewBox="0 0 24 24" width="100%" height="100%" fill="none" stroke="currentColor">
|
|
246
|
+
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5v15Z" />
|
|
247
|
+
</svg>
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
export function Nav({ nav, pathname }: { nav: DocNavNode[]; pathname: string }) {
|
|
251
|
+
return <DocsSidebar nav={nav} pathname={pathname} icons={{ book: Book }} />;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
A name with no entry in the map falls back to the built-in marker for that node's type — a typo in one file leaves a folder where a book should be, not a hole in the column. The component is rendered with no props, in a `1rem` box, and the built-ins use `currentColor`, so anything following those two conventions sits in line with them.
|
|
256
|
+
|
|
257
|
+
`icons={false}` removes the column entirely. The external-link mark moves back to the trailing edge there: turning off a decorative column is not consent to drop a warning that a link leaves your site.
|
|
216
258
|
|
|
217
259
|
The two components the adapter injects take a little more than an `<a>` and an `<img>`. `DocsLinkProps` adds `prefetch` — passed straight to `next/link`, where `false` disables the hover and viewport paths both, so it is a stronger switch in the App Router than the name suggests. `DocsImageProps` carries `src`, `alt`, `width` and `height` — the four `next/image` refuses to render without — and adds `sizes`, `loading`, `decoding` and `fetchPriority`, forwarded to it; markdown carries none of them, so they come from your `imageResolver` or from a `components` override. `decoding` defaults to `async`, and `loading` to `lazy` — except on an image the author marked `eager`, which is usually the page's largest element.
|
|
218
260
|
|
package/dist/frontmatter.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ declare const docFrontmatterSchema: z.ZodObject<{
|
|
|
32
32
|
draft: z.ZodExactOptional<z.ZodBoolean>;
|
|
33
33
|
aliases: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
|
|
34
34
|
order: z.ZodExactOptional<z.ZodNumber>;
|
|
35
|
+
icon: z.ZodExactOptional<z.ZodString>;
|
|
35
36
|
actions: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
|
|
36
37
|
label: z.ZodString;
|
|
37
38
|
href: z.ZodString;
|
package/dist/frontmatter.js
CHANGED
|
@@ -32,6 +32,7 @@ const docFrontmatterSchema = z.object({
|
|
|
32
32
|
draft: z.boolean().exactOptional(),
|
|
33
33
|
aliases: z.array(z.string()).exactOptional(),
|
|
34
34
|
order: z.number().exactOptional(),
|
|
35
|
+
icon: z.string().min(1).exactOptional(),
|
|
35
36
|
actions: z.array(z.object({
|
|
36
37
|
label: z.string().min(1),
|
|
37
38
|
href: z.string().min(1).refine(isSafeHref, "must be a safe URL (no javascript: or data:)"),
|
package/dist/meta.d.ts
CHANGED
|
@@ -10,9 +10,11 @@ import { z } from "zod";
|
|
|
10
10
|
*/
|
|
11
11
|
declare const docsMetaSchema: z.ZodObject<{
|
|
12
12
|
title: z.ZodExactOptional<z.ZodString>;
|
|
13
|
+
icon: z.ZodExactOptional<z.ZodString>;
|
|
13
14
|
pages: z.ZodExactOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
14
15
|
title: z.ZodString;
|
|
15
16
|
href: z.ZodString;
|
|
17
|
+
icon: z.ZodExactOptional<z.ZodString>;
|
|
16
18
|
}, z.core.$strict>]>>>;
|
|
17
19
|
}, z.core.$strict>;
|
|
18
20
|
/**
|
package/dist/meta.js
CHANGED
|
@@ -25,9 +25,11 @@ const REST = "...";
|
|
|
25
25
|
*/
|
|
26
26
|
const docsMetaSchema = z.strictObject({
|
|
27
27
|
title: z.string().exactOptional(),
|
|
28
|
+
icon: z.string().min(1).exactOptional(),
|
|
28
29
|
pages: z.array(z.union([z.string(), z.strictObject({
|
|
29
30
|
title: z.string(),
|
|
30
|
-
href: z.string().refine(isSafeHref, { message: "that is not a scheme this package will put in a link. Use http(s), mailto, tel, sms, ftp, irc, xmpp, news, feed, git or matrix — or a path, which needs no scheme at all." })
|
|
31
|
+
href: z.string().refine(isSafeHref, { message: "that is not a scheme this package will put in a link. Use http(s), mailto, tel, sms, ftp, irc, xmpp, news, feed, git or matrix — or a path, which needs no scheme at all." }),
|
|
32
|
+
icon: z.string().min(1).exactOptional()
|
|
31
33
|
})])).exactOptional()
|
|
32
34
|
});
|
|
33
35
|
/**
|
|
@@ -93,7 +95,8 @@ function orderNavEntries(entries, meta, metaPath, depth) {
|
|
|
93
95
|
type: "link",
|
|
94
96
|
title: page.title,
|
|
95
97
|
href: page.href,
|
|
96
|
-
external: opensInNewTab(page.href)
|
|
98
|
+
external: opensInNewTab(page.href),
|
|
99
|
+
...page.icon !== void 0 ? { icon: page.icon } : {}
|
|
97
100
|
});
|
|
98
101
|
continue;
|
|
99
102
|
}
|
package/dist/next.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { DocsHighlighter, DocsLang, DocsTheme, DocsThemes } from "./highlighter.
|
|
|
3
3
|
import { SerializableSearchOptions } from "./search-options.js";
|
|
4
4
|
import { DocsLabels } from "./react/shell-labels.js";
|
|
5
5
|
import { MarkdownComponents } from "./react/markdown-components.js";
|
|
6
|
+
import { DocsIconMap } from "./react/sidebar.js";
|
|
6
7
|
import { DocsLayoutSearchProps } from "./react/layout.js";
|
|
7
8
|
import { DocsSource } from "./source.js";
|
|
8
9
|
import { ReactNode } from "react";
|
|
@@ -154,6 +155,19 @@ interface DocsLayoutProps {
|
|
|
154
155
|
* mean naming one string cost you the other twenty-one.
|
|
155
156
|
*/
|
|
156
157
|
labels?: DocsLabels | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* The sidebar's marker column: `true` (default), `false`, or your own icons
|
|
160
|
+
* keyed by the `icon` names your content authors in frontmatter and
|
|
161
|
+
* `meta.json`. See `DocsSidebarProps.icons`.
|
|
162
|
+
*
|
|
163
|
+
* ⚠️ EVERY COMPONENT IN THE MAP MUST BE A CLIENT COMPONENT — the same
|
|
164
|
+
* boundary `search` documents at length. This is a Server Component handing
|
|
165
|
+
* props to a Client one, so React serialises a *reference* to a client
|
|
166
|
+
* component and cannot serialise a server one. Icons imported from a library
|
|
167
|
+
* already satisfy this; one defined inline in a server file fails the build
|
|
168
|
+
* at the boundary.
|
|
169
|
+
*/
|
|
170
|
+
icons?: boolean | DocsIconMap | undefined;
|
|
157
171
|
}
|
|
158
172
|
/** Props Next hands a page in the App Router. */
|
|
159
173
|
interface DocsPageProps {
|
package/dist/next.js
CHANGED
|
@@ -468,7 +468,7 @@ function createDocsRoute(options) {
|
|
|
468
468
|
async IndexPage() {
|
|
469
469
|
return renderRoute([]);
|
|
470
470
|
},
|
|
471
|
-
async Layout({ children, search, labels }) {
|
|
471
|
+
async Layout({ children, search, labels, icons }) {
|
|
472
472
|
const { DocsLayoutShell } = await import("./react/layout.js");
|
|
473
473
|
const host = search === true || search === void 0 || search === false ? void 0 : search;
|
|
474
474
|
const requestedOptions = host?.miniSearchOptions ?? options.miniSearchOptions;
|
|
@@ -485,7 +485,8 @@ function createDocsRoute(options) {
|
|
|
485
485
|
nav: await requestScopedSource.nav(),
|
|
486
486
|
searchIndexUrl,
|
|
487
487
|
search: searchProps,
|
|
488
|
-
...shellLabels === void 0 ? {} : { labels: shellLabels }
|
|
488
|
+
...shellLabels === void 0 ? {} : { labels: shellLabels },
|
|
489
|
+
...icons === void 0 ? {} : { icons }
|
|
489
490
|
});
|
|
490
491
|
},
|
|
491
492
|
async generateStaticParams() {
|
package/dist/react/layout.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { DocNavNode } from "../types.js";
|
|
|
2
2
|
import { SerializableSearchOptions } from "../search-options.js";
|
|
3
3
|
import { DocsLabels } from "./shell-labels.js";
|
|
4
4
|
import { DocsSearchProps } from "./next-search.js";
|
|
5
|
+
import { DocsIconMap } from "./sidebar.js";
|
|
5
6
|
import { ReactNode } from "react";
|
|
6
7
|
//#region src/react/layout.d.ts
|
|
7
8
|
/**
|
|
@@ -53,7 +54,21 @@ interface DocsLayoutShellProps {
|
|
|
53
54
|
* passed either. Configuration that could not be configured.
|
|
54
55
|
*/
|
|
55
56
|
labels?: DocsLabels | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* The sidebar's marker column: `true` (default), `false`, or your own icons
|
|
59
|
+
* keyed by the `icon` names your content authors. See
|
|
60
|
+
* {@link DocsSidebarProps.icons}.
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ EVERY COMPONENT IN THE MAP MUST BE A CLIENT COMPONENT. This shell is a
|
|
63
|
+
* Server Component and the tree it hands them to is not, so the map crosses
|
|
64
|
+
* that boundary — React can serialise a *reference* to a client component and
|
|
65
|
+
* cannot serialise a server one. Import your icons from a module carrying
|
|
66
|
+
* `'use client'` (every icon library does) and this is invisible; define one
|
|
67
|
+
* inline in a server file and the build fails at the boundary rather than
|
|
68
|
+
* here.
|
|
69
|
+
*/
|
|
70
|
+
icons?: boolean | DocsIconMap | undefined;
|
|
56
71
|
}
|
|
57
|
-
declare function DocsLayoutShell({ children, nav, searchIndexUrl, search, labels }: DocsLayoutShellProps): ReactNode;
|
|
72
|
+
declare function DocsLayoutShell({ children, nav, searchIndexUrl, search, labels, icons }: DocsLayoutShellProps): ReactNode;
|
|
58
73
|
//#endregion
|
|
59
74
|
export { DocsLayoutSearchProps, DocsLayoutShell, DocsLayoutShellProps };
|
package/dist/react/layout.js
CHANGED
|
@@ -4,7 +4,7 @@ import { DocsNextNav } from "./next-nav.js";
|
|
|
4
4
|
import { SkipLink } from "./skip-link.js";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
//#region src/react/layout.tsx
|
|
7
|
-
function DocsLayoutShell({ children, nav, searchIndexUrl, search = true, labels }) {
|
|
7
|
+
function DocsLayoutShell({ children, nav, searchIndexUrl, search = true, labels, icons }) {
|
|
8
8
|
const text = resolveLabels(labels);
|
|
9
9
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SkipLink, { children: text.skipToContent }), /* @__PURE__ */ jsx("div", {
|
|
10
10
|
className: "wave-docs-shell",
|
|
@@ -18,6 +18,7 @@ function DocsLayoutShell({ children, nav, searchIndexUrl, search = true, labels
|
|
|
18
18
|
...labels?.expandGroup === void 0 ? {} : { expandGroup: labels.expandGroup },
|
|
19
19
|
...labels?.collapseGroup === void 0 ? {} : { collapseGroup: labels.collapseGroup },
|
|
20
20
|
...labels?.externalLink === void 0 ? {} : { externalLink: labels.externalLink },
|
|
21
|
+
...icons === void 0 ? {} : { icons },
|
|
21
22
|
children: search === false ? null : /* @__PURE__ */ jsx(DocsSearch, {
|
|
22
23
|
indexUrl: searchIndexUrl,
|
|
23
24
|
...search === true ? {} : search,
|
package/dist/react/nav.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DocNavNode } from "../types.js";
|
|
2
2
|
import { DocsLinkComponent } from "./markdown-components.js";
|
|
3
|
+
import { DocsIconMap } from "./sidebar.js";
|
|
3
4
|
import { ReactNode } from "react";
|
|
4
5
|
//#region src/react/nav.d.ts
|
|
5
6
|
/** The navigation's `id`, and the trigger's `aria-controls`. */
|
|
@@ -26,7 +27,9 @@ interface DocsNavProps {
|
|
|
26
27
|
expandGroup?: string | undefined;
|
|
27
28
|
collapseGroup?: string | undefined;
|
|
28
29
|
externalLink?: string | undefined;
|
|
30
|
+
/** The marker column. See `DocsSidebarProps.icons`. */
|
|
31
|
+
icons?: boolean | DocsIconMap | undefined;
|
|
29
32
|
}
|
|
30
|
-
declare function DocsNav({ nav, pathname, Link, label, children, closeLabel, openLabel, expandGroup, collapseGroup, externalLink }: DocsNavProps): ReactNode;
|
|
33
|
+
declare function DocsNav({ nav, pathname, Link, label, children, closeLabel, openLabel, expandGroup, collapseGroup, externalLink, icons }: DocsNavProps): ReactNode;
|
|
31
34
|
//#endregion
|
|
32
35
|
export { DOCS_NAV_ID, DocsNav, DocsNavProps };
|
package/dist/react/nav.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
//#region src/react/nav.tsx
|
|
6
6
|
/** The navigation's `id`, and the trigger's `aria-controls`. */
|
|
7
7
|
const DOCS_NAV_ID = "wave-docs-nav";
|
|
8
|
-
function DocsNav({ nav, pathname, Link, label = "Documentation", children, closeLabel = "Close navigation", openLabel = "Open navigation", expandGroup, collapseGroup, externalLink }) {
|
|
8
|
+
function DocsNav({ nav, pathname, Link, label = "Documentation", children, closeLabel = "Close navigation", openLabel = "Open navigation", expandGroup, collapseGroup, externalLink, icons }) {
|
|
9
9
|
const shellRef = useRef(null);
|
|
10
10
|
const navRef = useRef(null);
|
|
11
11
|
const returnFocusRef = useRef(null);
|
|
@@ -98,7 +98,8 @@ function DocsNav({ nav, pathname, Link, label = "Documentation", children, close
|
|
|
98
98
|
Link,
|
|
99
99
|
...expandGroup === void 0 ? {} : { expandGroup },
|
|
100
100
|
...collapseGroup === void 0 ? {} : { collapseGroup },
|
|
101
|
-
...externalLink === void 0 ? {} : { externalLink }
|
|
101
|
+
...externalLink === void 0 ? {} : { externalLink },
|
|
102
|
+
...icons === void 0 ? {} : { icons }
|
|
102
103
|
})]
|
|
103
104
|
}), /* @__PURE__ */ jsx("button", {
|
|
104
105
|
type: "button",
|
package/dist/react/next-nav.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DocNavNode } from "../types.js";
|
|
2
|
+
import { DocsIconMap } from "./sidebar.js";
|
|
2
3
|
import { ReactNode } from "react";
|
|
3
4
|
//#region src/react/next-nav.d.ts
|
|
4
5
|
interface DocsNextNavProps {
|
|
@@ -13,7 +14,9 @@ interface DocsNextNavProps {
|
|
|
13
14
|
expandGroup?: string | undefined;
|
|
14
15
|
collapseGroup?: string | undefined;
|
|
15
16
|
externalLink?: string | undefined;
|
|
17
|
+
/** The marker column. See `DocsSidebarProps.icons`. */
|
|
18
|
+
icons?: boolean | DocsIconMap | undefined;
|
|
16
19
|
}
|
|
17
|
-
declare function DocsNextNav({ nav, label, closeLabel, openLabel, children, expandGroup, collapseGroup, externalLink }: DocsNextNavProps): ReactNode;
|
|
20
|
+
declare function DocsNextNav({ nav, label, closeLabel, openLabel, children, expandGroup, collapseGroup, externalLink, icons }: DocsNextNavProps): ReactNode;
|
|
18
21
|
//#endregion
|
|
19
22
|
export { DocsNextNav, DocsNextNavProps };
|
package/dist/react/next-nav.js
CHANGED
|
@@ -19,7 +19,7 @@ import { usePathname } from "next/navigation";
|
|
|
19
19
|
*/
|
|
20
20
|
/** Module scope: a fresh identity here remounts every nav link on every render. */
|
|
21
21
|
const Link = wrapNextLink(NextLink);
|
|
22
|
-
function DocsNextNav({ nav, label, closeLabel, openLabel, children, expandGroup, collapseGroup, externalLink }) {
|
|
22
|
+
function DocsNextNav({ nav, label, closeLabel, openLabel, children, expandGroup, collapseGroup, externalLink, icons }) {
|
|
23
23
|
return /* @__PURE__ */ jsx(DocsNav, {
|
|
24
24
|
nav,
|
|
25
25
|
pathname: usePathname(),
|
|
@@ -30,6 +30,7 @@ function DocsNextNav({ nav, label, closeLabel, openLabel, children, expandGroup,
|
|
|
30
30
|
...expandGroup === void 0 ? {} : { expandGroup },
|
|
31
31
|
...collapseGroup === void 0 ? {} : { collapseGroup },
|
|
32
32
|
...externalLink === void 0 ? {} : { externalLink },
|
|
33
|
+
...icons === void 0 ? {} : { icons },
|
|
33
34
|
children
|
|
34
35
|
});
|
|
35
36
|
}
|
package/dist/react/sidebar.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { DocNavNode } from "../types.js";
|
|
2
2
|
import { DocsLinkComponent } from "./markdown-components.js";
|
|
3
|
-
import { ReactNode } from "react";
|
|
3
|
+
import { ComponentType, ReactNode } from "react";
|
|
4
4
|
//#region src/react/sidebar.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* `icon` names to components, for `DocsSidebar`'s `icons` prop.
|
|
7
|
+
*
|
|
8
|
+
* The component is rendered with no props: an icon that needs configuration is
|
|
9
|
+
* a closure the host writes, not a contract this package invents. It should
|
|
10
|
+
* draw at the size it is given — the column is `1rem` square and the built-in
|
|
11
|
+
* markers use `currentColor`, so anything following those two conventions sits
|
|
12
|
+
* in line with them.
|
|
13
|
+
*/
|
|
14
|
+
type DocsIconMap = Record<string, ComponentType>;
|
|
5
15
|
interface DocsSidebarProps {
|
|
6
16
|
/** The tree from `@waveso/docs/source`. */
|
|
7
17
|
nav: DocNavNode[];
|
|
@@ -32,6 +42,44 @@ interface DocsSidebarProps {
|
|
|
32
42
|
* The separating space is markup, so this is the sentence and nothing else.
|
|
33
43
|
*/
|
|
34
44
|
externalLink?: string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The marker column. `true` (default), `false`, or your own icons.
|
|
47
|
+
*
|
|
48
|
+
* Weight and a chevron were the only things separating a category from a
|
|
49
|
+
* page, and in a tree where the two interleave — a `Reference` group sitting
|
|
50
|
+
* directly above an `Internals` page — that is not enough to scan. A leading
|
|
51
|
+
* glyph gives the column a shape you read before you read any words.
|
|
52
|
+
*
|
|
53
|
+
* `false` renders no markers at all, for a host whose own navigation has a
|
|
54
|
+
* different vocabulary and does not want a second one. The external-link mark
|
|
55
|
+
* returns to the trailing edge there — turning off a decorative column is not
|
|
56
|
+
* consent to drop a warning.
|
|
57
|
+
*
|
|
58
|
+
* A **map** replaces the defaults with your components, keyed by the `icon`
|
|
59
|
+
* name authored in frontmatter or `meta.json`:
|
|
60
|
+
*
|
|
61
|
+
* ```tsx
|
|
62
|
+
* import { Book, Rocket } from 'lucide-react';
|
|
63
|
+
*
|
|
64
|
+
* <DocsSidebar nav={nav} pathname={pathname}
|
|
65
|
+
* icons={{ book: Book, rocket: Rocket }} />
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* ```yaml
|
|
69
|
+
* # content/reference/index.md
|
|
70
|
+
* icon: book
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ A NAME THE HOST RESOLVES, NEVER ART THIS PACKAGE SHIPS. Content is
|
|
74
|
+
* authored in YAML and JSON and cannot carry a React element, and a docs
|
|
75
|
+
* package mounted inside someone else's application must not put its
|
|
76
|
+
* iconography beside theirs. Three markers ship; everything else is yours.
|
|
77
|
+
*
|
|
78
|
+
* A name with no entry in the map falls back to the built-in marker for that
|
|
79
|
+
* node's type. A typo in one file leaves a folder where a book should be —
|
|
80
|
+
* not a hole in the column.
|
|
81
|
+
*/
|
|
82
|
+
icons?: boolean | DocsIconMap | undefined;
|
|
35
83
|
className?: string | undefined;
|
|
36
84
|
}
|
|
37
85
|
/**
|
|
@@ -76,6 +124,6 @@ interface DocsSidebarProps {
|
|
|
76
124
|
* nothing prefetches locally whatever this says. Do not "fix" it back because
|
|
77
125
|
* the network tab looks the same.
|
|
78
126
|
*/
|
|
79
|
-
declare function DocsSidebar({ nav, pathname, Link, label, expandGroup, collapseGroup, externalLink, className }: DocsSidebarProps): ReactNode;
|
|
127
|
+
declare function DocsSidebar({ nav, pathname, Link, label, expandGroup, collapseGroup, externalLink, icons, className }: DocsSidebarProps): ReactNode;
|
|
80
128
|
//#endregion
|
|
81
|
-
export { DocsSidebar, DocsSidebarProps };
|
|
129
|
+
export { DocsIconMap, DocsSidebar, DocsSidebarProps };
|
package/dist/react/sidebar.js
CHANGED
|
@@ -66,7 +66,8 @@ function containsActive(node, pathname) {
|
|
|
66
66
|
* nothing prefetches locally whatever this says. Do not "fix" it back because
|
|
67
67
|
* the network tab looks the same.
|
|
68
68
|
*/
|
|
69
|
-
function DocsSidebar({ nav, pathname, Link, label = "Docs", expandGroup, collapseGroup, externalLink, className }) {
|
|
69
|
+
function DocsSidebar({ nav, pathname, Link, label = "Docs", expandGroup, collapseGroup, externalLink, icons = true, className }) {
|
|
70
|
+
const iconMap = icons === false ? false : icons === true ? {} : icons;
|
|
70
71
|
const text = {
|
|
71
72
|
expandGroup: expandGroup ?? DEFAULT_SIDEBAR_LABELS.expandGroup,
|
|
72
73
|
collapseGroup: collapseGroup ?? DEFAULT_SIDEBAR_LABELS.collapseGroup,
|
|
@@ -108,6 +109,7 @@ function DocsSidebar({ nav, pathname, Link, label = "Docs", expandGroup, collaps
|
|
|
108
109
|
nodes: nav,
|
|
109
110
|
depth: 0,
|
|
110
111
|
keyPrefix: baseId,
|
|
112
|
+
icons: iconMap,
|
|
111
113
|
pathname,
|
|
112
114
|
Link,
|
|
113
115
|
toggled,
|
|
@@ -157,7 +159,7 @@ function scrollableAncestor(element) {
|
|
|
157
159
|
}
|
|
158
160
|
return null;
|
|
159
161
|
}
|
|
160
|
-
function NavList({ nodes, depth, keyPrefix, pathname, Link, toggled, onToggle, text, id }) {
|
|
162
|
+
function NavList({ nodes, depth, keyPrefix, icons, pathname, Link, toggled, onToggle, text, id }) {
|
|
161
163
|
const holdsActive = nodes.some((node) => (node.type === "page" || node.type === "link" && !node.external) && isActiveHref(pathname, node.href));
|
|
162
164
|
return /* @__PURE__ */ jsx("ul", {
|
|
163
165
|
id,
|
|
@@ -175,31 +177,47 @@ function NavList({ nodes, depth, keyPrefix, pathname, Link, toggled, onToggle, t
|
|
|
175
177
|
}, key);
|
|
176
178
|
case "link": return /* @__PURE__ */ jsx("li", {
|
|
177
179
|
className: "wave-docs-sidebar__item",
|
|
178
|
-
children: /* @__PURE__ */
|
|
180
|
+
children: /* @__PURE__ */ jsxs(NavLink, {
|
|
179
181
|
href: node.href,
|
|
180
182
|
isExternal: node.external,
|
|
181
183
|
isActive: !node.external && isActiveHref(pathname, node.href),
|
|
182
184
|
isNearby: holdsActive,
|
|
183
185
|
Link,
|
|
184
186
|
externalLink: text.externalLink,
|
|
185
|
-
|
|
187
|
+
icons,
|
|
188
|
+
children: [icons === false ? null : /* @__PURE__ */ jsx(NavIcon, {
|
|
189
|
+
type: "external",
|
|
190
|
+
name: node.icon,
|
|
191
|
+
icons
|
|
192
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
193
|
+
className: "wave-docs-sidebar__label",
|
|
194
|
+
children: node.title
|
|
195
|
+
})]
|
|
186
196
|
})
|
|
187
197
|
}, key);
|
|
188
198
|
case "page": return /* @__PURE__ */ jsx("li", {
|
|
189
199
|
className: "wave-docs-sidebar__item",
|
|
190
|
-
children: /* @__PURE__ */
|
|
200
|
+
children: /* @__PURE__ */ jsxs(NavLink, {
|
|
191
201
|
href: node.href,
|
|
192
202
|
isExternal: false,
|
|
193
203
|
isActive: isActiveHref(pathname, node.href),
|
|
194
204
|
isNearby: holdsActive,
|
|
195
205
|
Link,
|
|
196
|
-
children:
|
|
206
|
+
children: [icons === false ? null : /* @__PURE__ */ jsx(NavIcon, {
|
|
207
|
+
type: "file",
|
|
208
|
+
name: node.icon,
|
|
209
|
+
icons
|
|
210
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
211
|
+
className: "wave-docs-sidebar__label",
|
|
212
|
+
children: node.title
|
|
213
|
+
})]
|
|
197
214
|
})
|
|
198
215
|
}, key);
|
|
199
216
|
case "group": return /* @__PURE__ */ jsx(NavGroup, {
|
|
200
217
|
node,
|
|
201
218
|
itemKey: key,
|
|
202
219
|
depth,
|
|
220
|
+
icons,
|
|
203
221
|
pathname,
|
|
204
222
|
Link,
|
|
205
223
|
toggled,
|
|
@@ -211,7 +229,7 @@ function NavList({ nodes, depth, keyPrefix, pathname, Link, toggled, onToggle, t
|
|
|
211
229
|
})
|
|
212
230
|
});
|
|
213
231
|
}
|
|
214
|
-
function NavGroup({ node, itemKey, depth, pathname, Link, toggled, onToggle, text }) {
|
|
232
|
+
function NavGroup({ node, itemKey, depth, icons, pathname, Link, toggled, onToggle, text }) {
|
|
215
233
|
const listId = `${itemKey}-list`;
|
|
216
234
|
const hasActive = containsActive(node, pathname);
|
|
217
235
|
const isOpen = toggled[itemKey] ?? hasActive;
|
|
@@ -227,17 +245,32 @@ function NavGroup({ node, itemKey, depth, pathname, Link, toggled, onToggle, tex
|
|
|
227
245
|
"aria-expanded": isOpen,
|
|
228
246
|
"aria-controls": isOpen ? listId : void 0,
|
|
229
247
|
onClick: () => onToggle(itemKey, !isOpen),
|
|
230
|
-
children: [
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
248
|
+
children: [
|
|
249
|
+
icons === false ? null : /* @__PURE__ */ jsx(NavIcon, {
|
|
250
|
+
type: "folder",
|
|
251
|
+
name: node.icon,
|
|
252
|
+
icons
|
|
253
|
+
}),
|
|
254
|
+
/* @__PURE__ */ jsx("span", {
|
|
255
|
+
className: "wave-docs-sidebar__group-title",
|
|
256
|
+
children: node.title
|
|
257
|
+
}),
|
|
258
|
+
/* @__PURE__ */ jsx(Chevron, { isOpen })
|
|
259
|
+
]
|
|
260
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(NavLink, {
|
|
235
261
|
href: node.href,
|
|
236
262
|
isExternal: false,
|
|
237
263
|
isActive: isGroupActive,
|
|
238
264
|
isNearby: hasActive,
|
|
239
265
|
Link,
|
|
240
|
-
children:
|
|
266
|
+
children: [icons === false ? null : /* @__PURE__ */ jsx(NavIcon, {
|
|
267
|
+
type: "folder",
|
|
268
|
+
name: node.icon,
|
|
269
|
+
icons
|
|
270
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
271
|
+
className: "wave-docs-sidebar__label",
|
|
272
|
+
children: node.title
|
|
273
|
+
})]
|
|
241
274
|
}), /* @__PURE__ */ jsx("button", {
|
|
242
275
|
type: "button",
|
|
243
276
|
className: "wave-docs-sidebar__group-toggle",
|
|
@@ -252,6 +285,7 @@ function NavGroup({ node, itemKey, depth, pathname, Link, toggled, onToggle, tex
|
|
|
252
285
|
nodes: node.children,
|
|
253
286
|
depth: depth + 1,
|
|
254
287
|
keyPrefix: itemKey,
|
|
288
|
+
icons,
|
|
255
289
|
pathname,
|
|
256
290
|
Link,
|
|
257
291
|
toggled,
|
|
@@ -260,7 +294,7 @@ function NavGroup({ node, itemKey, depth, pathname, Link, toggled, onToggle, tex
|
|
|
260
294
|
}) : null]
|
|
261
295
|
});
|
|
262
296
|
}
|
|
263
|
-
function NavLink({ href, isExternal, isActive, isNearby = false, Link, externalLink = DEFAULT_SIDEBAR_LABELS.externalLink, children }) {
|
|
297
|
+
function NavLink({ href, isExternal, isActive, isNearby = false, Link, externalLink = DEFAULT_SIDEBAR_LABELS.externalLink, icons = {}, children }) {
|
|
264
298
|
const className = "wave-docs-sidebar__link";
|
|
265
299
|
if (isExternal) return /* @__PURE__ */ jsxs("a", {
|
|
266
300
|
className,
|
|
@@ -269,7 +303,7 @@ function NavLink({ href, isExternal, isActive, isNearby = false, Link, externalL
|
|
|
269
303
|
rel: "noopener noreferrer",
|
|
270
304
|
children: [
|
|
271
305
|
children,
|
|
272
|
-
/* @__PURE__ */ jsx("svg", {
|
|
306
|
+
icons === false ? /* @__PURE__ */ jsx("svg", {
|
|
273
307
|
className: "wave-docs-sidebar__external",
|
|
274
308
|
"aria-hidden": "true",
|
|
275
309
|
focusable: "false",
|
|
@@ -281,8 +315,8 @@ function NavLink({ href, isExternal, isActive, isNearby = false, Link, externalL
|
|
|
281
315
|
strokeWidth: "2",
|
|
282
316
|
strokeLinecap: "round",
|
|
283
317
|
strokeLinejoin: "round",
|
|
284
|
-
children: /* @__PURE__ */ jsx("path", { d:
|
|
285
|
-
}),
|
|
318
|
+
children: /* @__PURE__ */ jsx("path", { d: NAV_ICON_PATHS.external[0] })
|
|
319
|
+
}) : null,
|
|
286
320
|
/* @__PURE__ */ jsxs("span", {
|
|
287
321
|
className: "wave-docs-sr-only",
|
|
288
322
|
children: [" ", externalLink]
|
|
@@ -303,6 +337,58 @@ function NavLink({ href, isExternal, isActive, isNearby = false, Link, externalL
|
|
|
303
337
|
children
|
|
304
338
|
});
|
|
305
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* The glyph at the head of a row: a folder for a group, a page for a page.
|
|
342
|
+
*
|
|
343
|
+
* Weight and a chevron were the only things telling a category from a page, and
|
|
344
|
+
* where the two interleave — a `Reference` group directly above an `Internals`
|
|
345
|
+
* page — that is not enough to scan a column of twenty. A silhouette is read
|
|
346
|
+
* before any word is.
|
|
347
|
+
*
|
|
348
|
+
* An external link takes the third glyph, in the same leading slot. It used to
|
|
349
|
+
* carry that mark at the *far* end of its row, which cost twice: the leading
|
|
350
|
+
* slot then had to be an empty box to keep the column from going ragged, and
|
|
351
|
+
* the trailing edge held two unrelated meanings — "opens elsewhere" on one row,
|
|
352
|
+
* "expands" on the next. Leading is what a row *is*; trailing is what it
|
|
353
|
+
* *does*. With the mark moved, the only thing at the far end of any row is a
|
|
354
|
+
* chevron, which is what makes a group legible from across the column — and
|
|
355
|
+
* leaves that edge free for a status dot or an overflow control later.
|
|
356
|
+
*
|
|
357
|
+
* ⚠️ THE VISUAL MARK MOVED AND THE ANNOUNCED ONE DID NOT. The sr-only "(opens
|
|
358
|
+
* in a new tab)" stays after the link text, so the name is still read as
|
|
359
|
+
* "GitHub, opens in a new tab" rather than the other way round.
|
|
360
|
+
*
|
|
361
|
+
* Inline SVG, matching `Chevron` and the external mark rather than a font or a
|
|
362
|
+
* dependency — the package ships no icon set, and these two are as generic as
|
|
363
|
+
* the chevron beside them.
|
|
364
|
+
*/
|
|
365
|
+
const NAV_ICON_PATHS = {
|
|
366
|
+
folder: ["M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"],
|
|
367
|
+
file: ["M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", "M14 2v4a2 2 0 0 0 2 2h4"],
|
|
368
|
+
external: ["M14 4h6v6M20 4l-8 8M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"]
|
|
369
|
+
};
|
|
370
|
+
function NavIcon({ type, name, icons }) {
|
|
371
|
+
const Custom = name === void 0 ? void 0 : icons[name];
|
|
372
|
+
if (Custom !== void 0) return /* @__PURE__ */ jsx("span", {
|
|
373
|
+
className: "wave-docs-sidebar__icon",
|
|
374
|
+
"aria-hidden": "true",
|
|
375
|
+
children: /* @__PURE__ */ jsx(Custom, {})
|
|
376
|
+
});
|
|
377
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
378
|
+
className: "wave-docs-sidebar__icon",
|
|
379
|
+
"aria-hidden": "true",
|
|
380
|
+
focusable: "false",
|
|
381
|
+
viewBox: "0 0 24 24",
|
|
382
|
+
width: "16",
|
|
383
|
+
height: "16",
|
|
384
|
+
fill: "none",
|
|
385
|
+
stroke: "currentColor",
|
|
386
|
+
strokeWidth: "2",
|
|
387
|
+
strokeLinecap: "round",
|
|
388
|
+
strokeLinejoin: "round",
|
|
389
|
+
children: NAV_ICON_PATHS[type].map((d) => /* @__PURE__ */ jsx("path", { d }, d))
|
|
390
|
+
});
|
|
391
|
+
}
|
|
306
392
|
function Chevron({ isOpen }) {
|
|
307
393
|
return /* @__PURE__ */ jsx("svg", {
|
|
308
394
|
className: "wave-docs-sidebar__chevron",
|
package/dist/source.js
CHANGED
|
@@ -381,17 +381,20 @@ function buildNav(dir, config) {
|
|
|
381
381
|
const visible = index && isVisibleIn(index.doc, config) ? index : void 0;
|
|
382
382
|
const title = groupTitle(child, visible?.doc);
|
|
383
383
|
const href = visible?.doc.href;
|
|
384
|
+
const icon = child.meta?.icon ?? visible?.doc.frontmatter.icon;
|
|
384
385
|
const group = {
|
|
385
386
|
type: "group",
|
|
386
387
|
title,
|
|
387
388
|
children,
|
|
388
|
-
...href !== void 0 ? { href } : {}
|
|
389
|
+
...href !== void 0 ? { href } : {},
|
|
390
|
+
...icon !== void 0 ? { icon } : {}
|
|
389
391
|
};
|
|
390
392
|
const node = children.length === 0 && visible !== void 0 && href !== void 0 ? {
|
|
391
393
|
type: "page",
|
|
392
394
|
title,
|
|
393
395
|
href,
|
|
394
|
-
slug: visible.doc.slug
|
|
396
|
+
slug: visible.doc.slug,
|
|
397
|
+
...icon !== void 0 ? { icon } : {}
|
|
395
398
|
} : group;
|
|
396
399
|
const order = visible?.doc.frontmatter.order;
|
|
397
400
|
entries.push({
|
|
@@ -403,7 +406,8 @@ function buildNav(dir, config) {
|
|
|
403
406
|
type: "page",
|
|
404
407
|
title: navTitle(visible.doc),
|
|
405
408
|
href,
|
|
406
|
-
slug: visible.doc.slug
|
|
409
|
+
slug: visible.doc.slug,
|
|
410
|
+
...visible.doc.frontmatter.icon !== void 0 ? { icon: visible.doc.frontmatter.icon } : {}
|
|
407
411
|
} } : {},
|
|
408
412
|
...order !== void 0 ? { order } : {}
|
|
409
413
|
});
|
|
@@ -420,7 +424,8 @@ function toPageEntry(page, config) {
|
|
|
420
424
|
type: "page",
|
|
421
425
|
title,
|
|
422
426
|
href: page.doc.href,
|
|
423
|
-
slug: page.doc.slug
|
|
427
|
+
slug: page.doc.slug,
|
|
428
|
+
...page.doc.frontmatter.icon !== void 0 ? { icon: page.doc.frontmatter.icon } : {}
|
|
424
429
|
},
|
|
425
430
|
...order !== void 0 ? { order } : {},
|
|
426
431
|
...isVisibleIn(page.doc, config) ? {} : { hidden: true }
|
package/dist/styles.css
CHANGED
|
@@ -1575,6 +1575,71 @@
|
|
|
1575
1575
|
font-weight: 600;
|
|
1576
1576
|
}
|
|
1577
1577
|
|
|
1578
|
+
/*
|
|
1579
|
+
* The row is `justify-content: space-between`, so the label has to claim the
|
|
1580
|
+
* space or the icon and the text end up at opposite edges with a hole between
|
|
1581
|
+
* them. `min-width: 0` because a long title in a 240px column must be allowed
|
|
1582
|
+
* to shrink; without it the flex base is the text's min-content width and the
|
|
1583
|
+
* row overflows instead of wrapping.
|
|
1584
|
+
*/
|
|
1585
|
+
.wave-docs-sidebar__label,
|
|
1586
|
+
.wave-docs-sidebar__group-title {
|
|
1587
|
+
flex: 1 1 auto;
|
|
1588
|
+
min-width: 0;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/*
|
|
1592
|
+
* The type marker: a folder on a group, a page on a page.
|
|
1593
|
+
*
|
|
1594
|
+
* ⚠️ THE BOX IS DECLARED HERE, NOT LEFT TO THE SVG'S OWN WIDTH, because one
|
|
1595
|
+
* of these has no SVG in it. An external link carries its mark at the far end
|
|
1596
|
+
* of the row and needs no second one, so it gets an empty box — and an empty
|
|
1597
|
+
* `<span>` with no size is a column where three labels start 24px left of
|
|
1598
|
+
* every other label. Ragged is the defect this feature exists to remove.
|
|
1599
|
+
*/
|
|
1600
|
+
.wave-docs-sidebar__icon {
|
|
1601
|
+
flex: none;
|
|
1602
|
+
inline-size: 1rem;
|
|
1603
|
+
block-size: 1rem;
|
|
1604
|
+
/*
|
|
1605
|
+
* ⚠️ `inherit` AND `opacity`, NOT A COLOUR OF ITS OWN. A fixed grey put a
|
|
1606
|
+
* bold group title next to a marker several steps lighter than it and a
|
|
1607
|
+
* muted page title next to one barely lighter, so the column read as two
|
|
1608
|
+
* different treatments. Fading whatever the row already is keeps one
|
|
1609
|
+
* relationship at every weight — and it is the reason the active row's
|
|
1610
|
+
* marker turns accent-blue with its label rather than staying grey.
|
|
1611
|
+
*/
|
|
1612
|
+
color: inherit;
|
|
1613
|
+
opacity: 0.4;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/*
|
|
1617
|
+
* A host's own icon, in the column's box.
|
|
1618
|
+
*
|
|
1619
|
+
* `grid` rather than `block`: the component inside is theirs and may be any
|
|
1620
|
+
* size, and a grid with `place-items: center` centres it in our 1rem square
|
|
1621
|
+
* without needing it to fill one. `> *` caps it at the box so an icon
|
|
1622
|
+
* authored at 24 does not push the row taller than every other row.
|
|
1623
|
+
*/
|
|
1624
|
+
span.wave-docs-sidebar__icon {
|
|
1625
|
+
display: grid;
|
|
1626
|
+
place-items: center;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
.wave-docs-sidebar__icon > * {
|
|
1630
|
+
max-inline-size: 100%;
|
|
1631
|
+
max-block-size: 100%;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/* Full strength the moment the row is the reader's own or under their
|
|
1635
|
+
* pointer. A current page whose marker stayed faded reads as
|
|
1636
|
+
* half-highlighted. */
|
|
1637
|
+
.wave-docs-sidebar__link:hover .wave-docs-sidebar__icon,
|
|
1638
|
+
.wave-docs-sidebar__group-button:hover .wave-docs-sidebar__icon,
|
|
1639
|
+
.wave-docs-sidebar__link[aria-current='page'] .wave-docs-sidebar__icon {
|
|
1640
|
+
opacity: 1;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1578
1643
|
.wave-docs-sidebar__list:not([data-depth='0']) {
|
|
1579
1644
|
margin-inline-start: 0.5rem;
|
|
1580
1645
|
padding-inline-start: 0.5rem;
|
|
@@ -1646,11 +1711,55 @@
|
|
|
1646
1711
|
color: var(--wave-docs-fg);
|
|
1647
1712
|
}
|
|
1648
1713
|
|
|
1714
|
+
/*
|
|
1715
|
+
* The chevron points at the label it belongs to, not away from it.
|
|
1716
|
+
*
|
|
1717
|
+
* `.wave-docs-sidebar__group-header` is `justify-content: space-between`, so
|
|
1718
|
+
* this sits flush against the navigation's inline end with the label at the
|
|
1719
|
+
* other side of the row. The icon is Lucide's `chevron-right`, and unrotated
|
|
1720
|
+
* it therefore aimed at the panel's border — at nothing. Worse than nothing,
|
|
1721
|
+
* in fact: a chevron at the *trailing* edge of a row is the platform idiom
|
|
1722
|
+
* for "this takes you somewhere else", so it read as navigation on a control
|
|
1723
|
+
* that only opens a list in place.
|
|
1724
|
+
*
|
|
1725
|
+
* ⚠️ AND THAT IS WHY THERE IS A MIRROR RULE. `rotate` is physical — `180deg`
|
|
1726
|
+
* is left in every writing mode — while everything else positioning this row
|
|
1727
|
+
* is logical, so the header mirrors under `dir="rtl"` and a lone physical
|
|
1728
|
+
* rotation does not. Mirrored, the chevron moves to the inline start with the
|
|
1729
|
+
* label to its right, and 180deg would then point it out of the panel on the
|
|
1730
|
+
* other side. `0deg` is what "at the label" means there.
|
|
1731
|
+
*
|
|
1732
|
+
* ⚠️ `[dir='rtl']`, AND NOT `:dir(rtl)`, WHICH IS THE SELECTOR FOR THIS JOB
|
|
1733
|
+
* AND DOES NOT SURVIVE THE BUILD. Next compiles this sheet with lightningcss,
|
|
1734
|
+
* which downlevels `:dir(rtl)` into a hardcoded list of right-to-left
|
|
1735
|
+
* *languages*:
|
|
1736
|
+
*
|
|
1737
|
+
* .wave-docs-sidebar__chevron:is(:lang(ae), :lang(ar), … :lang(yi))
|
|
1738
|
+
*
|
|
1739
|
+
* Direction is not language. Measured on the site with `<html dir="rtl"
|
|
1740
|
+
* lang="en">`: `:dir(rtl)` matched the element, the rule was nowhere in the
|
|
1741
|
+
* served stylesheet, and the chevron kept pointing out of the panel. It fails
|
|
1742
|
+
* the other way too — `lang="ar" dir="ltr"` gets the mirror it did not ask
|
|
1743
|
+
* for. The attribute selector is plain CSS 2.1, so no pipeline has an opinion
|
|
1744
|
+
* about it, and it is what `rtl:` compiles to in every design system that
|
|
1745
|
+
* ships one.
|
|
1746
|
+
*/
|
|
1649
1747
|
.wave-docs-sidebar__chevron {
|
|
1650
1748
|
flex: none;
|
|
1651
1749
|
color: var(--wave-docs-fg-subtle);
|
|
1750
|
+
rotate: 180deg;
|
|
1652
1751
|
}
|
|
1653
1752
|
|
|
1753
|
+
[dir='rtl'] .wave-docs-sidebar__chevron {
|
|
1754
|
+
rotate: 0deg;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/*
|
|
1758
|
+
* Open is down in both directions, so this needs no mirror — and it must
|
|
1759
|
+
* come last, because the mirror above matches with the same specificity
|
|
1760
|
+
* (0,2,0 either way) and would otherwise hold a mirrored open group pointing
|
|
1761
|
+
* sideways.
|
|
1762
|
+
*/
|
|
1654
1763
|
.wave-docs-sidebar__chevron[data-open] {
|
|
1655
1764
|
rotate: 90deg;
|
|
1656
1765
|
}
|
|
@@ -1673,6 +1782,35 @@
|
|
|
1673
1782
|
.wave-docs-sidebar__chevron {
|
|
1674
1783
|
transition: rotate 150ms ease-out;
|
|
1675
1784
|
}
|
|
1785
|
+
|
|
1786
|
+
/*
|
|
1787
|
+
* The row and its marker ease together, and the row is half of it.
|
|
1788
|
+
*
|
|
1789
|
+
* ⚠️ THE LINKS HAD NO TRANSITION AT ALL — the TOC's did, which is why this
|
|
1790
|
+
* looked like an oversight only in the sidebar. Fading the marker alone
|
|
1791
|
+
* would have been worse than fading nothing: the glyph would drift up to
|
|
1792
|
+
* full strength while the surface under it snapped, so the two halves of
|
|
1793
|
+
* one hover would visibly disagree.
|
|
1794
|
+
*
|
|
1795
|
+
* `background-color`, not `background`: the shorthand includes
|
|
1796
|
+
* `background-image`, and naming it here would put this rule in the way of
|
|
1797
|
+
* any consumer who paints one.
|
|
1798
|
+
*/
|
|
1799
|
+
.wave-docs-sidebar__link,
|
|
1800
|
+
.wave-docs-sidebar__group-button,
|
|
1801
|
+
.wave-docs-sidebar__group-toggle {
|
|
1802
|
+
transition:
|
|
1803
|
+
background-color 150ms ease-out,
|
|
1804
|
+
color 150ms ease-out;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
/* `color` as well as `opacity`: the marker inherits its row's colour, and
|
|
1808
|
+
* on the current page that is a jump from muted grey to accent blue. */
|
|
1809
|
+
.wave-docs-sidebar__icon {
|
|
1810
|
+
transition:
|
|
1811
|
+
opacity 150ms ease-out,
|
|
1812
|
+
color 150ms ease-out;
|
|
1813
|
+
}
|
|
1676
1814
|
}
|
|
1677
1815
|
}
|
|
1678
1816
|
|
|
@@ -1725,6 +1863,89 @@
|
|
|
1725
1863
|
color: var(--wave-docs-fg);
|
|
1726
1864
|
}
|
|
1727
1865
|
|
|
1866
|
+
/*
|
|
1867
|
+
* ⚠️ IT ONLY APPEARS ONCE THERE IS SOMETHING TO GO BACK TO, AND NOTHING IN
|
|
1868
|
+
* JAVASCRIPT DECIDES THAT.
|
|
1869
|
+
*
|
|
1870
|
+
* A scroll-driven animation, so scroll position alone drives it: no listener,
|
|
1871
|
+
* no state, no re-render on every frame of a scroll, and it is right before
|
|
1872
|
+
* hydration rather than after it. `toc` is the smallest client component this
|
|
1873
|
+
* package ships — 900 bytes, and its budget note exists to keep it that way —
|
|
1874
|
+
* so the alternative was to grow the one bundle that should never grow, in
|
|
1875
|
+
* order to recompute a number the compositor already has.
|
|
1876
|
+
*
|
|
1877
|
+
* ⚠️ `root`, NOT `nearest`. Above 80rem `.wave-docs-layout__toc` is itself a
|
|
1878
|
+
* scroll container (`overflow-y: auto`, so a long column of headings scrolls
|
|
1879
|
+
* in place), and `nearest` resolves to it. The animation would then track how
|
|
1880
|
+
* far the reader had scrolled the *table of contents*, which on almost every
|
|
1881
|
+
* page never scrolls at all — an inactive timeline, and a link that never
|
|
1882
|
+
* appears. Same shape as the `nearest`/`self` trap on the table shadow above,
|
|
1883
|
+
* arrived at from the opposite direction.
|
|
1884
|
+
*
|
|
1885
|
+
* ⚠️ AND THE RULE ABOVE IS THE FALLBACK, WHICH IS WHY NOTHING HERE OVERRIDES
|
|
1886
|
+
* IT. Three cases collapse into one: Firefox does not match this `@supports`
|
|
1887
|
+
* yet; a page too short to scroll leaves the timeline inactive; and so does a
|
|
1888
|
+
* host that scrolls an inner pane rather than the document. In all three the
|
|
1889
|
+
* link is simply always present, exactly as it was before this block existed.
|
|
1890
|
+
* Nothing hides a control on the strength of a feature the engine did not run.
|
|
1891
|
+
*
|
|
1892
|
+
* `dvh` rather than `px`: the link is redundant while the top of the document
|
|
1893
|
+
* is still on screen, and "still on screen" is a fraction of the viewport
|
|
1894
|
+
* rather than a constant. On a 900px window it fades in between 225px and
|
|
1895
|
+
* 315px of scroll. `dvh` because this sheet uses no `vh` anywhere, and the
|
|
1896
|
+
* reason it does not — the retracted mobile URL bar — costs nothing here: the
|
|
1897
|
+
* column this link lives in is `display: none` below 80rem.
|
|
1898
|
+
*
|
|
1899
|
+
* No `prefers-reduced-motion` gate, deliberately. That setting is about things
|
|
1900
|
+
* that move under a reader who did not ask them to; this is opacity, and the
|
|
1901
|
+
* reader's own scrolling is its clock.
|
|
1902
|
+
*/
|
|
1903
|
+
@supports (animation-timeline: scroll()) {
|
|
1904
|
+
.wave-docs-toc__top {
|
|
1905
|
+
animation-timeline: scroll(root block);
|
|
1906
|
+
animation-timing-function: linear;
|
|
1907
|
+
animation-fill-mode: both;
|
|
1908
|
+
animation-name: wave-docs-toc-top-reveal;
|
|
1909
|
+
animation-range: 25dvh 35dvh;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
/*
|
|
1914
|
+
* ⚠️ `visibility` KEEPS A LINK NOBODY CAN SEE OUT OF THE TAB ORDER, AND THE
|
|
1915
|
+
* MIDDLE FRAME IS WHAT DECIDES WHEN IT REJOINS.
|
|
1916
|
+
*
|
|
1917
|
+
* `opacity` alone leaves a fully focusable, fully clickable link sitting
|
|
1918
|
+
* invisibly at the foot of the rail: Tab reaches it and the focus ring is
|
|
1919
|
+
* drawn around nothing.
|
|
1920
|
+
*
|
|
1921
|
+
* `visibility` interpolates as a step with one exception — across an interval
|
|
1922
|
+
* where *either* endpoint is `visible` it is `visible` the whole way. Two
|
|
1923
|
+
* frames would therefore flip it one pixel past the threshold and hand the
|
|
1924
|
+
* link back to the tab order at an opacity of almost zero, which is the same
|
|
1925
|
+
* defect a frame later. Two adjacent `hidden` frames have no `visible`
|
|
1926
|
+
* endpoint between them, the exception does not apply, and the step lands at
|
|
1927
|
+
* 60% — where the link is legible.
|
|
1928
|
+
*
|
|
1929
|
+
* That middle frame sits exactly on the linear ramp, so it changes the fade
|
|
1930
|
+
* by nothing at all. Being the second `hidden` is its whole job.
|
|
1931
|
+
*/
|
|
1932
|
+
@keyframes wave-docs-toc-top-reveal {
|
|
1933
|
+
0% {
|
|
1934
|
+
opacity: 0;
|
|
1935
|
+
visibility: hidden;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
60% {
|
|
1939
|
+
opacity: 0.6;
|
|
1940
|
+
visibility: hidden;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
100% {
|
|
1944
|
+
opacity: 1;
|
|
1945
|
+
visibility: visible;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1728
1949
|
.wave-docs-sidebar__external {
|
|
1729
1950
|
/* Baseline-ish against the label, and never a flex item that grows. */
|
|
1730
1951
|
margin-inline-start: 0.25rem;
|
package/dist/types.d.ts
CHANGED
|
@@ -59,6 +59,20 @@ interface DocFrontmatter {
|
|
|
59
59
|
* Lower sorts first; pages without an order sort last, alphabetically.
|
|
60
60
|
*/
|
|
61
61
|
order?: number | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Sidebar marker for this page, as a name the *consumer* resolves.
|
|
64
|
+
*
|
|
65
|
+
* A name and not a component, because frontmatter is data: it is authored by
|
|
66
|
+
* whoever writes the page, in YAML, and cannot carry a React element. The
|
|
67
|
+
* host maps the name to their own icon via `DocsSidebar`'s `icons` prop, so
|
|
68
|
+
* the art belongs to whichever site is rendering — which is the only shape
|
|
69
|
+
* that serves a package mounted inside someone else's application.
|
|
70
|
+
*
|
|
71
|
+
* An unmapped name falls back to the default page marker rather than
|
|
72
|
+
* rendering nothing: a typo in one file should not knock a hole in the
|
|
73
|
+
* column.
|
|
74
|
+
*/
|
|
75
|
+
icon?: string | undefined;
|
|
62
76
|
/**
|
|
63
77
|
* Calls to action, and the opt-in for the page's hero.
|
|
64
78
|
*
|
|
@@ -122,6 +136,8 @@ interface DocNavPage {
|
|
|
122
136
|
title: string;
|
|
123
137
|
href: string;
|
|
124
138
|
slug: string;
|
|
139
|
+
/** Marker name from the page's frontmatter. See {@link DocFrontmatter.icon}. */
|
|
140
|
+
icon?: string | undefined;
|
|
125
141
|
}
|
|
126
142
|
/**
|
|
127
143
|
* A directory. `href` is present when the directory has an `index.md`, in
|
|
@@ -132,6 +148,8 @@ interface DocNavGroup {
|
|
|
132
148
|
title: string;
|
|
133
149
|
href?: string | undefined;
|
|
134
150
|
children: DocNavNode[];
|
|
151
|
+
/** Marker name from the directory's `meta.json`. */
|
|
152
|
+
icon?: string | undefined;
|
|
135
153
|
}
|
|
136
154
|
/** A non-interactive heading between groups, from `"---Label---"` in meta.json. */
|
|
137
155
|
interface DocNavSeparator {
|
|
@@ -144,6 +162,8 @@ interface DocNavLink {
|
|
|
144
162
|
title: string;
|
|
145
163
|
href: string;
|
|
146
164
|
external: boolean;
|
|
165
|
+
/** Marker name from the `meta.json` entry that declared this link. */
|
|
166
|
+
icon?: string | undefined;
|
|
147
167
|
}
|
|
148
168
|
type DocNavNode = DocNavPage | DocNavGroup | DocNavSeparator | DocNavLink;
|
|
149
169
|
/**
|
|
@@ -164,10 +184,17 @@ type DocNavNode = DocNavPage | DocNavGroup | DocNavSeparator | DocNavLink;
|
|
|
164
184
|
interface DocsMeta {
|
|
165
185
|
/** Directory title, shown as the group heading. Defaults to the dirname. */
|
|
166
186
|
title?: string | undefined;
|
|
187
|
+
/**
|
|
188
|
+
* Sidebar marker for this directory, as a name the consumer resolves. See
|
|
189
|
+
* {@link DocFrontmatter.icon} — the same contract, declared where the
|
|
190
|
+
* directory is described rather than where a page is.
|
|
191
|
+
*/
|
|
192
|
+
icon?: string | undefined;
|
|
167
193
|
/** Ordered entries. Omit to sort by frontmatter `order`, then alphabetically. */
|
|
168
194
|
pages?: Array<string | {
|
|
169
195
|
title: string;
|
|
170
196
|
href: string;
|
|
197
|
+
icon?: string | undefined;
|
|
171
198
|
}> | undefined;
|
|
172
199
|
}
|
|
173
200
|
/**
|
package/package.json
CHANGED