@waveso/docs 0.7.1 → 0.9.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 +226 -0
- package/README.md +54 -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/search-dialog.d.ts +24 -1
- package/dist/react/search-dialog.js +104 -16
- package/dist/react/sidebar.d.ts +51 -3
- package/dist/react/sidebar.js +126 -19
- package/dist/source.js +9 -4
- package/dist/styles.css +297 -13
- package/dist/types.d.ts +27 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,231 @@
|
|
|
1
1
|
# @waveso/docs
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 216f2df: **The search dialog says how to drive it.** A magnifier on the trigger and in
|
|
8
|
+
the input, and a footer carrying `↑` `↓` Select · `↵` Open · `Esc` Close.
|
|
9
|
+
|
|
10
|
+
⚠️ AND IT DELETES A BUTTON THAT SAID `Close` IN HARDCODED ENGLISH. Every other
|
|
11
|
+
user-facing string in this package had been lifted to a prop; that one was
|
|
12
|
+
missed, in the one dialog a reader cannot leave without it — so a Portuguese
|
|
13
|
+
site rendered a Portuguese dialog with an English way out. It is `closeLabel`
|
|
14
|
+
now, alongside `selectLabel` and `openLabel`.
|
|
15
|
+
|
|
16
|
+
The key-caps beside them are glyphs and stay untranslated: an arrow is an
|
|
17
|
+
arrow, and `Esc` is `Esc` on a Portuguese keyboard. The props are the verbs,
|
|
18
|
+
which are not.
|
|
19
|
+
|
|
20
|
+
⚠️ THE DISMISS CONTROL IS A BUTTON, NOT A THIRD HINT. Under `(hover: none) and
|
|
21
|
+
(pointer: coarse)` the two hints are hidden — on the same reasoning as the
|
|
22
|
+
trigger's `⌘K`, that an instruction to press a key is one a reader on a phone
|
|
23
|
+
cannot follow — and that leaves this as the only pointer route out of the
|
|
24
|
+
dialog. It is not hidden with them.
|
|
25
|
+
|
|
26
|
+
⚠️ IT ALSO MOVED PAST THE RESULTS. The button it replaces sat in the input row,
|
|
27
|
+
so one Tab from the query landed on _Close_ rather than on the first result —
|
|
28
|
+
past every answer the reader had just asked for. Document order is tab order
|
|
29
|
+
here, and a test now holds it.
|
|
30
|
+
|
|
31
|
+
The hints are `aria-hidden`: they describe the pointer-free path through a
|
|
32
|
+
listbox that a screen reader already exposes through `role`,
|
|
33
|
+
`aria-activedescendant` and `aria-posinset`, so announcing them adds two lines
|
|
34
|
+
of symbols and no information. The magnifiers are hidden for the reason the
|
|
35
|
+
trigger's own label is pinned — named from content, that button once announced
|
|
36
|
+
as "Search Ctrl K".
|
|
37
|
+
|
|
38
|
+
Published sizes rise with it: the quick start's total 14 → 14.3 KB, the search
|
|
39
|
+
dialog 9.3 → 9.5 KB. Budgets raised in `size-budget.json` with the reason.
|
|
40
|
+
|
|
41
|
+
⚠️ AND THE TRIGGER NOW SHARES A COLUMN WITH THE TREE. It sits directly above
|
|
42
|
+
the navigation, so its magnifier is the first thing in the same column as every
|
|
43
|
+
folder and page marker below it and its label starts the same column as every
|
|
44
|
+
title — and both were out, measured at 1280px by 3px and 11px. The trigger was
|
|
45
|
+
spaced as a standalone control: 10px of inline padding against the rows' 8px,
|
|
46
|
+
and a 16px gap against their 8px. It carries a 1px border the rows do not, so
|
|
47
|
+
the fix is `calc(0.5rem - 1px)` rather than `0.5rem` — matching the number
|
|
48
|
+
instead of the content edge leaves it 1px out and looks fixed in a screenshot.
|
|
49
|
+
A browser test measures both columns against the tree's.
|
|
50
|
+
|
|
51
|
+
## The trigger's shortcut is plain text again
|
|
52
|
+
|
|
53
|
+
`⌘K` was a bordered chip inside a bordered, filled control — a chip on a chip,
|
|
54
|
+
sharing its fill, for a hint nobody clicks. It kept that border only because
|
|
55
|
+
the footer's key-caps were added to the same rule; the two are separate now,
|
|
56
|
+
and a test holds them apart. There was never a `background` on either: the
|
|
57
|
+
trigger's own `--wave-docs-bg-subtle` showed through, which is what made the
|
|
58
|
+
border read as a filled shape.
|
|
59
|
+
|
|
60
|
+
⚠️ AND THE `⌘` IS ITS OWN ELEMENT, BECAUSE CSS CANNOT SELECT A CHARACTER.
|
|
61
|
+
Measured in the shipped mono stack at 12px, the glyph carries 6.39px of ink
|
|
62
|
+
against the `K`'s 8.75px — a third short of the letter beside it, in one string
|
|
63
|
+
at one size. `1.45em` on the symbol brings it to 9.26px — a hair
|
|
64
|
+
taller than the letter, which is what makes the two read as one mark. `Ctrl` is
|
|
65
|
+
gated out of that rule by an attribute: it is a word set in the same face as
|
|
66
|
+
the `K`, and scaling it makes the hint shout.
|
|
67
|
+
|
|
68
|
+
⚠️ AND SIZE ALONE LEAVES IT FLOATING. `⌘` is drawn around the font's
|
|
69
|
+
mathematical axis rather than standing on the baseline like a capital, so at
|
|
70
|
+
that size its ink centre sits 2.24px above the `K`'s while inline layout aligns
|
|
71
|
+
the two by baseline. `vertical-align: -0.13em` drops it. Measured off a render
|
|
72
|
+
at 8x: both ink boxes centre on the same pixel, with the symbol 5% the taller.
|
|
73
|
+
|
|
74
|
+
New public class names: `.wave-docs-search-glyph`, `.wave-docs-search-footer`,
|
|
75
|
+
`.wave-docs-search-hint`, `.wave-docs-search-kbd`, `.wave-docs-search-trigger-mod`.
|
|
76
|
+
`.wave-docs-search-close` survives, restyled — it is a footer control now, not
|
|
77
|
+
a bordered button in the input row.
|
|
78
|
+
|
|
79
|
+
### Patch Changes
|
|
80
|
+
|
|
81
|
+
- 5647418: Sidebar groups stop collapsing behind the reader.
|
|
82
|
+
|
|
83
|
+
Expand three sections, click a page, and two of them shut. Or: open a section,
|
|
84
|
+
read a page in it, open another section, read a page in _that_ — and the first
|
|
85
|
+
one closes. Both reported from real use, and both the same defect.
|
|
86
|
+
|
|
87
|
+
⚠️ THE CAUSE WAS `setToggled({})` ON EVERY NAVIGATION. The reader's own state
|
|
88
|
+
and the route's default share one map — `toggled[key] ?? hasActive` — so
|
|
89
|
+
clearing it does not "reset the tree to its default" in any useful sense. The
|
|
90
|
+
default is _open only what holds the current page_, so wiping the map collapses
|
|
91
|
+
everything the reader had deliberately opened.
|
|
92
|
+
|
|
93
|
+
Navigation now opens whatever holds the page just arrived at, and closes
|
|
94
|
+
nothing. A group closes when the reader closes it.
|
|
95
|
+
|
|
96
|
+
⚠️ AND IT RECORDS `true` RATHER THAN DELETING THE KEY, WHICH IS THE HALF THAT
|
|
97
|
+
IS EASY TO GET WRONG. Deleting also reopens the group — it falls back to
|
|
98
|
+
`hasActive` — and looks correct for exactly one navigation. Read a page in one
|
|
99
|
+
section, then a page in another, and the first section has no entry left and no
|
|
100
|
+
longer holds the route, so it shuts. That is the second report, reproduced by
|
|
101
|
+
the obvious fix.
|
|
102
|
+
|
|
103
|
+
The state is also seeded from the first route rather than starting empty, so a
|
|
104
|
+
group open at first paint is open by _record_ rather than by inference. Without
|
|
105
|
+
it, landing on a deep page from a search result and clicking away collapses the
|
|
106
|
+
section you arrived in, while one you had opened by hand would have stayed.
|
|
107
|
+
|
|
108
|
+
Kept from the old reset: a group collapsed an hour ago must not hide the page
|
|
109
|
+
just navigated to. It is reopened explicitly.
|
|
110
|
+
|
|
111
|
+
It costs 60 gzipped bytes on the sidebar bundle — a walk that names the groups
|
|
112
|
+
holding the current route. The line it replaces cost nothing, which is the
|
|
113
|
+
point: the cheapest possible reset was also the one that threw away everything
|
|
114
|
+
the reader had opened. Published sizes rise with it: the quick start's total
|
|
115
|
+
14.3 → 14.5 KB, the navigation 3 → 3.1 KB.
|
|
116
|
+
|
|
117
|
+
## 0.8.0
|
|
118
|
+
|
|
119
|
+
### Minor Changes
|
|
120
|
+
|
|
121
|
+
- 98f8041: **The sidebar tells a category from a page at a glance.** A folder on every
|
|
122
|
+
group, a page on every page.
|
|
123
|
+
|
|
124
|
+
Weight and a chevron were the only difference, and that is not enough to scan:
|
|
125
|
+
a `Reference` group sitting directly above an `Internals` page read as one
|
|
126
|
+
undifferentiated column, and a real tree interleaves the two a dozen times. A
|
|
127
|
+
silhouette is read before any word is.
|
|
128
|
+
|
|
129
|
+
Two inline SVG paths, in the same style as the disclosure chevron and the
|
|
130
|
+
external-link mark that were already there. The package still ships no icon
|
|
131
|
+
set and takes no icon dependency — a folder and a page are as generic as the
|
|
132
|
+
chevron beside them.
|
|
133
|
+
|
|
134
|
+
`icons={false}` on `DocsSidebar` turns them off, for a host whose own
|
|
135
|
+
navigation has a different vocabulary and does not want a second one.
|
|
136
|
+
|
|
137
|
+
⚠️ AN EXTERNAL LINK'S MARK MOVED TO THE HEAD OF ITS ROW. It used to sit at the
|
|
138
|
+
far end, which cost twice: the leading slot had to be an empty box to stop the
|
|
139
|
+
column going ragged, and the trailing edge carried two unrelated meanings —
|
|
140
|
+
"opens elsewhere" on one row, "expands" on the next. Leading is what a row _is_;
|
|
141
|
+
trailing is what it _does_. With the mark moved, the only thing at the far end
|
|
142
|
+
of any row is a chevron, which is what makes a group legible from across the
|
|
143
|
+
column, and it leaves that edge free for a status dot or an overflow control
|
|
144
|
+
later. A browser test measures the alignment, because the claim is about a
|
|
145
|
+
column and a column is geometry.
|
|
146
|
+
|
|
147
|
+
The sr-only "(opens in a new tab)" did **not** move with it — the name is still
|
|
148
|
+
read as "GitHub, opens in a new tab" rather than the other way round.
|
|
149
|
+
|
|
150
|
+
⚠️ AND `icons={false}` PUTS THE MARK BACK ON THE TRAILING EDGE. With no column
|
|
151
|
+
to lead, rendering nothing would leave a link that leaves your site looking
|
|
152
|
+
exactly like one that does not. Turning off a decorative column is not consent
|
|
153
|
+
to drop a warning.
|
|
154
|
+
|
|
155
|
+
The markers sit at `opacity: 0.4` and inherit their row's colour rather than
|
|
156
|
+
carrying a grey of their own, so the relationship holds at every weight — a
|
|
157
|
+
bold group title and a muted page title each get a marker a fixed step lighter
|
|
158
|
+
than themselves. Full strength on hover and on the current page.
|
|
159
|
+
|
|
160
|
+
New public class names: `.wave-docs-sidebar__icon` and
|
|
161
|
+
`.wave-docs-sidebar__label`. The label wrapper is the flex hook that lets a row
|
|
162
|
+
put a chevron at its far end, and it is present whether or not there is a
|
|
163
|
+
marker beside it.
|
|
164
|
+
|
|
165
|
+
## Your own icons, by name
|
|
166
|
+
|
|
167
|
+
The three built-ins are defaults, not a set. Content names an icon; the host
|
|
168
|
+
maps the name to a component:
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
# content/internals.md
|
|
172
|
+
icon: wrench
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
// content/reference/meta.json
|
|
177
|
+
{ "title": "Reference", "icon": "book" }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
<docs.Layout icons={{ wrench: Wrench, book: Book }}>{children}</docs.Layout>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Also on `DocsSidebar` and `DocsNav` for a hand-assembled shell.
|
|
185
|
+
|
|
186
|
+
⚠️ A NAME, NEVER ART THIS PACKAGE SHIPS. Content is authored in YAML and JSON
|
|
187
|
+
and cannot carry a React element, and a docs package mounted inside someone
|
|
188
|
+
else's application must not stand its iconography next to theirs. The bundle
|
|
189
|
+
grows by a lookup, not by an icon set — and never will by one.
|
|
190
|
+
|
|
191
|
+
⚠️ EVERY COMPONENT IN THE MAP MUST BE A CLIENT COMPONENT. `docs.Layout` is a
|
|
192
|
+
Server Component and the tree it hands the map to is not, so React serialises a
|
|
193
|
+
_reference_ to each icon; a server component cannot be one. Icons imported from
|
|
194
|
+
a library already satisfy this. The same boundary `search` documents.
|
|
195
|
+
|
|
196
|
+
A name with no entry in the map falls back to the built-in marker for that
|
|
197
|
+
node's type — a typo in one file leaves a folder where a book should be, not a
|
|
198
|
+
hole in the column.
|
|
199
|
+
|
|
200
|
+
### Patch Changes
|
|
201
|
+
|
|
202
|
+
- 72ad371: The sidebar's disclosure chevron points at the group it belongs to.
|
|
203
|
+
|
|
204
|
+
`.wave-docs-sidebar__group-header` is `justify-content: space-between`, so the
|
|
205
|
+
chevron sits flush against the navigation's inline end with the label at the
|
|
206
|
+
other side of the row — and unrotated it aimed at the panel's border. Worse
|
|
207
|
+
than at nothing: a chevron at the _trailing_ edge of a row is the platform
|
|
208
|
+
idiom for "this takes you somewhere else", so it read as navigation on a
|
|
209
|
+
control that only opens a list in place. A collapsed group now points back at
|
|
210
|
+
its own label; an open one still points down at its children.
|
|
211
|
+
|
|
212
|
+
⚠️ AND IT MIRRORS, WHICH A ROTATION DOES NOT DO ON ITS OWN. `rotate` is
|
|
213
|
+
physical — 180deg is left in every writing mode — while every other property
|
|
214
|
+
placing that row is logical. Under `dir="rtl"` the header mirrors, the chevron
|
|
215
|
+
moves to the inline start and the label lands to its right, so a single
|
|
216
|
+
unmirrored rotation would point it out of the panel on the other side: the same
|
|
217
|
+
defect, reflected.
|
|
218
|
+
|
|
219
|
+
⚠️ THE MIRROR IS `[dir='rtl']`, NOT `:dir(rtl)`, AND THAT IS NOT A STYLE
|
|
220
|
+
PREFERENCE. Next compiles this stylesheet with lightningcss, which downlevels
|
|
221
|
+
`:dir(rtl)` into a hardcoded list of right-to-left _languages_ —
|
|
222
|
+
`:is(:lang(ae), :lang(ar), … :lang(yi))`. Direction is not language, and the
|
|
223
|
+
substitution is wrong in both directions: `<html dir="rtl" lang="en">` gets no
|
|
224
|
+
mirror, `lang="ar" dir="ltr"` gets one it never asked for. It is invisible from
|
|
225
|
+
inside this repo, because the tests inject the source text into a `<style>`
|
|
226
|
+
element where `:dir()` behaves perfectly — it was caught by measuring the built
|
|
227
|
+
site. The stylesheet now uses `:dir()` nowhere, and a test enforces that.
|
|
228
|
+
|
|
3
229
|
## 0.7.1
|
|
4
230
|
|
|
5
231
|
### Patch 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 |
|
|
73
|
-
| Search dialog and router wiring | 9.
|
|
74
|
-
| Navigation: one sidebar, open and closed |
|
|
72
|
+
| Everything the quick start ships, gzipped | 14.5 KB |
|
|
73
|
+
| Search dialog and router wiring | 9.5 KB |
|
|
74
|
+
| Navigation: one sidebar, open and closed | 3.1 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.5 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 |
|
|
@@ -214,6 +214,48 @@ Every component takes data as props, and every module that imports from `next/*`
|
|
|
214
214
|
|
|
215
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
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.
|
|
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
|
|
|
219
261
|
### Layout
|
|
@@ -713,10 +755,17 @@ If your site sets Next's own `basePath` config, prefix `indexUrl` yourself: Next
|
|
|
713
755
|
| `loadingLabel` | `string` | `'Loading the search index…'` | While the index is fetched |
|
|
714
756
|
| `errorLabel` | `string` | `'Search is unavailable right now. Try reloading the page.'` | When it cannot be |
|
|
715
757
|
| `emptyLabel` | `string` | `'No results for “{query}”.'` | No matches. `{query}` is what was typed |
|
|
758
|
+
| `selectLabel` | `string` | `'Select'` | Footer hint beside `↑` `↓` |
|
|
759
|
+
| `openLabel` | `string` | `'Open'` | Footer hint beside `↵` |
|
|
760
|
+
| `closeLabel` | `string` | `'Close'` | The footer's dismiss button, beside `Esc` |
|
|
716
761
|
| `resultCountLabels` | `Partial<Record<Intl.LDMLPluralRule, string>>` | `{ one: '{count} result', other: '{count} results' }` | The live region, by plural category |
|
|
717
762
|
| `locale` | `string` | `<html lang>`, then `'en'` | Language tag for those plural rules |
|
|
718
763
|
| `miniSearchOptions` | `Partial<Options<SearchRecord>>` | — | See [Tuning](#tuning) |
|
|
719
764
|
|
|
765
|
+
The dialog's footer carries the three keyboard hints and the dismiss control. The key-caps beside them — `↑` `↓` `↵` `Esc` — are glyphs and are not translatable; the three props above are the verbs, which are.
|
|
766
|
+
|
|
767
|
+
Under `(hover: none) and (pointer: coarse)` the two hints are hidden, on the same reasoning as the trigger's `⌘K`: an instruction to press a key is one a reader on a phone cannot follow. `closeLabel`'s button is deliberately not hidden with them — on exactly those devices it is the only pointer route out of the dialog.
|
|
768
|
+
|
|
720
769
|
**`pageSize` replaced `maxResults` in 0.4.0**, and the meaning changed with the name: `maxResults` was a hard ceiling of 8 that made results unreachable on a six-page site, and the live region announced the slice as though it were the total. `pageSize` is a window — every match is reachable by scrolling, and the count announced is the real one.
|
|
721
770
|
|
|
722
771
|
`resultCountLabels` is keyed by plural category rather than being a singular and a plural, because most languages are not English: Polish takes four forms and Arabic six. `Intl.PluralRules` picks, and a category you do not list falls back to `other`.
|
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
|
}
|
|
@@ -95,6 +95,29 @@ interface SearchDialogProps {
|
|
|
95
95
|
* `{query}` is replaced with what the reader typed.
|
|
96
96
|
*/
|
|
97
97
|
emptyLabel?: string | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* The arrow-keys hint in the footer. Defaults to `'Select'`.
|
|
100
|
+
*
|
|
101
|
+
* The key-caps beside it are glyphs and are not translatable — an arrow is an
|
|
102
|
+
* arrow, and `Esc` is `Esc` on a Portuguese keyboard. These props are the
|
|
103
|
+
* verbs, which are not.
|
|
104
|
+
*/
|
|
105
|
+
selectLabel?: string | undefined;
|
|
106
|
+
/** The Enter hint in the footer. Defaults to `'Open'`. */
|
|
107
|
+
openLabel?: string | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* The footer's dismiss control. Defaults to `'Close'`.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ THIS REPLACES A BUTTON THAT SAID `Close` IN HARDCODED ENGLISH — the one
|
|
112
|
+
* string in this package that was never lifted to a prop, in the one dialog a
|
|
113
|
+
* reader cannot leave without it.
|
|
114
|
+
*
|
|
115
|
+
* It names a real button rather than a third hint: under `pointer: coarse`
|
|
116
|
+
* the two hints beside it are hidden, because an instruction to press Esc is
|
|
117
|
+
* one a reader on a phone cannot follow — and that leaves this as the only
|
|
118
|
+
* pointer route out of the dialog.
|
|
119
|
+
*/
|
|
120
|
+
closeLabel?: string | undefined;
|
|
98
121
|
/**
|
|
99
122
|
* The live region's announcement, by plural category. `{count}` is the total.
|
|
100
123
|
*
|
|
@@ -151,6 +174,6 @@ interface SearchDialogProps {
|
|
|
151
174
|
* portalled to `document.body`, so a navbar's stacking context cannot trap
|
|
152
175
|
* it behind the page.
|
|
153
176
|
*/
|
|
154
|
-
declare function SearchDialog({ indexUrl, navigate, Link, triggerLabel, placeholder, dialogLabel, pageSize, minQueryLength, debounceMs, className, miniSearchOptions, hintLabel, shortQueryLabel, loadingLabel, errorLabel, emptyLabel, resultCountLabels, locale }: SearchDialogProps): ReactNode;
|
|
177
|
+
declare function SearchDialog({ indexUrl, navigate, Link, triggerLabel, placeholder, dialogLabel, pageSize, minQueryLength, debounceMs, className, miniSearchOptions, hintLabel, shortQueryLabel, loadingLabel, errorLabel, emptyLabel, selectLabel, openLabel, closeLabel, resultCountLabels, locale }: SearchDialogProps): ReactNode;
|
|
155
178
|
//#endregion
|
|
156
179
|
export { SearchDialog, SearchDialogProps };
|