docpensieve 0.4.0-beta.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/docpensieve.js +5 -2
  2. package/package.json +5 -5
  3. package/src/commands/build.js +9 -1
  4. package/src/commands/dev.js +9 -1
  5. package/src/commands/init.js +23 -3
  6. package/starter/01-guide/01-installation.md +2 -3
  7. package/starter/01-guide/02-first-site.md +7 -1
  8. package/starter/01-guide/03-writing-pages.md +4 -0
  9. package/starter/01-guide/04-versions.md +4 -0
  10. package/starter/01-guide/05-themes.mdx +47 -13
  11. package/starter/01-guide/07-migrate-from-0-3.md +125 -0
  12. package/starter/01-guide/08-navigation.mdx +138 -0
  13. package/starter/01-guide/09-when-it-breaks.md +176 -0
  14. package/starter/01-guide/10-languages.md +160 -0
  15. package/starter/02-components/01-card.mdx +2 -2
  16. package/starter/02-components/02-columns.mdx +3 -0
  17. package/starter/02-components/03-time-timer.mdx +4 -0
  18. package/starter/02-components/04-tooltip.mdx +40 -0
  19. package/starter/02-components/05-tree.mdx +4 -0
  20. package/starter/02-components/06-scroll-to-top.mdx +3 -0
  21. package/starter/02-components/07-skill.mdx +30 -0
  22. package/starter/02-components/08-logo-icon.mdx +24 -0
  23. package/starter/02-components/09-for-theme.mdx +3 -0
  24. package/starter/02-components/11-menu.mdx +129 -0
  25. package/starter/02-components/12-admonition.mdx +154 -0
  26. package/starter/03-reference/02-configuration.md +96 -31
  27. package/starter/03-reference/03-frontmatter.md +3 -0
  28. package/starter/03-reference/04-theme.md +80 -62
  29. package/starter/03-reference/05-api.md +169 -12
  30. package/starter/03-reference/index.mdx +3 -0
  31. package/starter/04-architecture.md +3 -0
  32. package/starter/05-whats-new.md +76 -12
  33. package/starter/examples.css +15 -0
  34. package/starter/01-guide/07-migrate-to-beta.md +0 -24
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { createRequire } from 'node:module';
10
10
 
11
- import { DocPensieveError, NotImplementedError } from '@docpensieve/shared';
11
+ import { DOCUMENTATION_URL, DocPensieveError, NotImplementedError } from '@docpensieve/shared';
12
12
  import { Command } from 'commander';
13
13
  import chalk from 'chalk';
14
14
 
@@ -21,7 +21,10 @@ const program = new Command();
21
21
  program
22
22
  .name('docpensieve')
23
23
  .description('Static documentation site generator')
24
- .version(version, '-v, --version');
24
+ .version(version, '-v, --version')
25
+ // Someone typing --help is usually stuck: the list of commands alone sends
26
+ // them back where they started.
27
+ .addHelpText('after', `\nDocumentation: ${DOCUMENTATION_URL}`);
25
28
 
26
29
  program
27
30
  .command('init')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docpensieve",
3
- "version": "0.4.0-beta.1",
3
+ "version": "0.4.0",
4
4
  "description": "DocPensieve command-line interface (init, build, check, dev, serve)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,10 +24,10 @@
24
24
  "types"
25
25
  ],
26
26
  "dependencies": {
27
- "@docpensieve/components": "0.4.0-beta.1",
28
- "@docpensieve/core": "0.4.0-beta.1",
29
- "@docpensieve/shared": "0.4.0-beta.1",
30
- "@docpensieve/theme": "0.4.0-beta.1",
27
+ "@docpensieve/components": "0.4.0",
28
+ "@docpensieve/core": "0.4.0",
29
+ "@docpensieve/shared": "0.4.0",
30
+ "@docpensieve/theme": "0.4.0",
31
31
  "chalk": "^6.0.0",
32
32
  "chokidar": "^5.0.0",
33
33
  "commander": "^15.0.0"
@@ -6,7 +6,12 @@
6
6
 
7
7
  import path from 'node:path';
8
8
 
9
- import { componentsCss, createRegistry, setSiteContext } from '@docpensieve/components';
9
+ import {
10
+ componentsCss,
11
+ createRegistry,
12
+ setSiteContext,
13
+ setAdmonitionKinds,
14
+ } from '@docpensieve/components';
10
15
  import { SiteGenerator, loadConfig, resolveVersion } from '@docpensieve/core';
11
16
 
12
17
  import { createTheme } from '../theme.js';
@@ -21,6 +26,9 @@ export async function build(versionSlug, options = {}) {
21
26
  const config = await loadConfig(cwd);
22
27
  const outDir = path.resolve(cwd, options.out ?? config.outDir);
23
28
 
29
+ // The kinds a project declares, beside the ones shipped.
30
+ setAdmonitionKinds(config.admonitions);
31
+
24
32
  const generator = new SiteGenerator(config, {
25
33
  // `globalComponents: false` removes the shipped components: a project that
26
34
  // defines its own thus avoids a name collision. The option was declared
@@ -7,7 +7,12 @@
7
7
  import { existsSync } from 'node:fs';
8
8
  import path from 'node:path';
9
9
 
10
- import { componentsCss, createRegistry, setSiteContext } from '@docpensieve/components';
10
+ import {
11
+ componentsCss,
12
+ createRegistry,
13
+ setSiteContext,
14
+ setAdmonitionKinds,
15
+ } from '@docpensieve/components';
11
16
  import { SiteGenerator, loadConfig } from '@docpensieve/core';
12
17
  import { CONFIG_FILENAME, DocPensieveError, THEME_FOLDER } from '@docpensieve/shared';
13
18
  import chokidar from 'chokidar';
@@ -54,6 +59,9 @@ export async function dev(options = {}) {
54
59
  // The configuration is read again every time: changing it must show
55
60
  // without restarting the command.
56
61
  const current = await loadConfig(cwd);
62
+ // Read again with the rest: a kind added to the configuration must show
63
+ // without restarting the command.
64
+ setAdmonitionKinds(current.admonitions);
57
65
  const generator = new SiteGenerator(current, {
58
66
  // `globalComponents: false` removes the shipped components: a project
59
67
  // that defines its own thus avoids a name collision. The option was
@@ -14,6 +14,7 @@ import { fileURLToPath } from 'node:url';
14
14
  import {
15
15
  CONFIG_FILENAME,
16
16
  CONFIG_FILENAMES,
17
+ DOCUMENTATION_URL,
17
18
  DocPensieveError,
18
19
  THEME_FOLDER,
19
20
  THEME_FRAMEWORKS,
@@ -64,9 +65,6 @@ const NOT_INSTALLED = new Set([
64
65
  /** Starting point of the project's own stylesheet, under the custom theme. */
65
66
  const CUSTOM_CSS = fileURLToPath(new URL('../templates/custom.css', import.meta.url));
66
67
 
67
- /** Where the generated configuration sends readers for every field. */
68
- const DOCUMENTATION_URL = 'https://docpensieve.com/';
69
-
70
68
  /**
71
69
  * Asks a question, with a default value shown between brackets.
72
70
  *
@@ -228,12 +226,16 @@ function renderConfig({ name, theme, siteUrl, version }) {
228
226
  ' // current the version the site root leads to — at most one',
229
227
  ' // prerelease in preparation: a banner on every page, kept out of search',
230
228
  ' // archived no longer maintained: a banner, still indexed',
229
+ ' // translations a folder per language, served under its code',
231
230
  ' versions: [',
232
231
  ' {',
233
232
  ` slug: ${quote(slug)}, // URL segment, and name of the version's branch`,
234
233
  ` name: ${quote(version)}, // label in the version switcher`,
235
234
  ` folder: ${quote(`docs/${slug}`)},`,
236
235
  ' current: true,',
236
+ ' // Pages of this version in another language, served under /fr/.',
237
+ ' // Your own language stays where it is, and keeps its addresses.',
238
+ ` // translations: { fr: ${quote(`docs/${slug}-fr`)} },`,
237
239
  ' },',
238
240
  ' ],',
239
241
  '',
@@ -284,6 +286,20 @@ function renderConfig({ name, theme, siteUrl, version }) {
284
286
  " // on a narrow screen. For instance [{ label: 'Blog', href: '/blog/' }].",
285
287
  ' headerLinks: [],',
286
288
  '',
289
+ ' // Categories of the menu fold, opened on the branch of the page being',
290
+ ' // read. Useful once the documentation is long.',
291
+ ' foldedSidebar: false,',
292
+ '',
293
+ ' // Wording of the shell — the menu, the notices, the search field.',
294
+ ' // English and French ship with the tool and follow `lang`; this field',
295
+ " // corrects a word or adds a language: { de: { search: 'Suchen' } }.",
296
+ ' ui: {},',
297
+ '',
298
+ ' // Kinds of admonition, beside the six shipped — note, info, tip,',
299
+ ' // attention, alert and danger. A kind is a label and a tone, which',
300
+ " // carries its colour: { review: { label: 'Review', tone: 'info' } }.",
301
+ ' admonitions: {},',
302
+ '',
287
303
  ' // The shipped components — Card, Columns, Tooltip… — usable in any .mdx',
288
304
  ' // page without an import. false removes them, to use your own names.',
289
305
  ' globalComponents: true,',
@@ -291,6 +307,10 @@ function renderConfig({ name, theme, siteUrl, version }) {
291
307
  ' // Back-to-top button on every page.',
292
308
  ' scrollToTop: true,',
293
309
  '',
310
+ ' // The header stays at the top of the screen. false lets it scroll away',
311
+ ' // with the page, and gives its height back to the text.',
312
+ ' stickyHeader: true,',
313
+ '',
294
314
  ' // Structured data (JSON-LD) generated from the frontmatter of each page.',
295
315
  ' jsonld: { enabled: true },',
296
316
  '',
@@ -99,9 +99,8 @@ The documentation `init` installed in `99-docpensieve` stays at the version it
99
99
  came with. To refresh it, run `init` in a scratch folder and copy that folder
100
100
  over.
101
101
 
102
- Coming from the 0.3, [Migrate from latest to beta](./migrate-to-beta/) says
103
- what changes on its own and what to check first. To try this beta, install
104
- `docpensieve@beta`, or run `npx docpensieve@beta` alone.
102
+ Coming from the 0.3, [Migrate from 0.3 to 0.4](./migrate-from-0-3/) says what
103
+ changes on its own and what to check first.
105
104
 
106
105
  ## Checking
107
106
 
@@ -72,6 +72,11 @@ actually used in order to emit only those.
72
72
  **The `versions.json` at the root** describes the available versions. It is
73
73
  what lets versions built long ago stay online without ever rebuilding them.
74
74
 
75
+ The folder itself is `dist/`, which `outDir` changes — to `public/`, or to
76
+ whatever your host expects. The build empties the folders it writes and leaves
77
+ the rest of that folder alone, so an output folder shared with something else
78
+ keeps what is not its own.
79
+
75
80
  ## Reading the output back
76
81
 
77
82
  A successful build says nothing of a dead link: nothing in the chain looks at
@@ -94,7 +99,8 @@ online.
94
99
  ```
95
100
 
96
101
  It is the check to run after any change of `baseUrl`, of folder structure or of
97
- URL.
102
+ URL. When something does not add up, [When it breaks](./when-it-breaks/) lists
103
+ the failures this tool produces, by the symptom you see.
98
104
 
99
105
  ## Searching the site
100
106
 
@@ -197,6 +197,10 @@ In an `.mdx` page, the shipped components are used **without an import**:
197
197
  They are rendered at build time: the delivered HTML only holds their result.
198
198
  The list is in [Components](../components/).
199
199
 
200
+ `globalComponents: false` removes them from every page. It is meant for a
201
+ project that would rather bring its own: a page still using one then stops the
202
+ build, naming it.
203
+
200
204
  ## A trap to know
201
205
 
202
206
  The content of a JSX tag **left alone on its own line** becomes a paragraph:
@@ -199,3 +199,7 @@ today's generator or today's sources. Rebuilding it is never necessary, and
199
199
  nothing would guarantee it gives the same result.
200
200
 
201
201
  The sources stay on the working branch. The two never mix.
202
+
203
+ What the day of a release looks like from the outside — the addresses, the
204
+ pipeline, the check that stops a bad publication — is in
205
+ [Deployment](./deployment/).
@@ -26,12 +26,43 @@ styles the site, with no styling dependency.
26
26
  The templates are the same in both cases. Switching values requires touching
27
27
  no page.
28
28
 
29
+ ## The images of the site
30
+
31
+ Three images carry the identity of a project, and none of them is a stylesheet
32
+ matter — they are declared once, from the root of the project:
33
+
34
+ ```js
35
+ logo: 'branding/logo.png',
36
+ favicon: 'branding/favicon.png',
37
+ socialImage: 'branding/social.jpg',
38
+ ```
39
+
40
+ | Field | Where it shows | What to give it |
41
+ | ------------- | ------------------------------ | ------------------------------------------------------------------------------------- |
42
+ | `logo` | In the header, beside the name | Any web image. A detailed logo reads badly at header size: crop the mark alone |
43
+ | `favicon` | In the browser tab | `.ico`, `.png` or `.svg` — nothing else is shown by browsers |
44
+ | `socialImage` | The preview of a shared link | A PNG or JPEG, 1200 × 630 pixels as a rule. Social networks read neither SVG nor AVIF |
45
+
46
+ A version may carry its own `logo` and `favicon`, which is how a beta tells
47
+ itself apart at a glance in the tab.
48
+
49
+ `socialImage` needs `siteUrl`, and the build refuses the pair when it is
50
+ missing rather than publish a preview nobody can fetch:
51
+
52
+ ```
53
+ socialImage needs siteUrl.
54
+ Social networks only read an absolute address: set siteUrl, the public address of the site.
55
+ ```
56
+
57
+ A preview is fetched by another machine, from the outside: a path that works in
58
+ your browser means nothing to it.
59
+
29
60
  ## The principle: slots, not classes
30
61
 
31
62
  The templates **hard-code no class**. They ask the theme for the class of each
32
63
  slot — the header, the menu, a navigation link — and the theme answers.
33
64
 
34
- The twenty-four slots are listed in the [reference](../reference/theme/).
65
+ The fifty slots are listed in the [reference](../reference/theme/).
35
66
 
36
67
  That is what lets a utility styling and a classic styling share the same HTML:
37
68
  one answers `dp-nav-link`, the other a handful of utilities. The template
@@ -43,7 +74,7 @@ they therefore follow the active palette without knowing anything about it.
43
74
 
44
75
  ## Changing the colours
45
76
 
46
- The fifteen tokens are listed in the [reference](../reference/theme/). They are
77
+ The tokens are listed in the [reference](../reference/theme/). They are
47
78
  redefined from the configuration:
48
79
 
49
80
  ```js
@@ -56,19 +87,22 @@ theme: {
56
87
  },
57
88
  ```
58
89
 
59
- | Token | What it sets |
60
- | -------------------------------------- | -------------------------------------------------------------- |
61
- | `--dp-bg`, `--dp-bg-soft` | Backgrounds |
62
- | `--dp-text`, `--dp-text-soft` | Text |
63
- | `--dp-border`, `--dp-rule` | Borders and rules |
64
- | `--dp-accent`, `--dp-accent-soft` | Accent colour |
65
- | `--dp-radius` | Corner rounding |
66
- | `--dp-font`, `--dp-font-mono` | Font families |
67
- | `--dp-content-width` | Reading width. `none` by default: the content fills its column |
68
- | `--dp-sidebar-width`, `--dp-toc-width` | Side columns |
90
+ | Token | What it sets |
91
+ | --------------------------------------------------------------- | -------------------------------------------------------------- |
92
+ | `--dp-bg`, `--dp-bg-soft` | Backgrounds |
93
+ | `--dp-text`, `--dp-text-soft` | Text |
94
+ | `--dp-border`, `--dp-rule` | Borders and rules |
95
+ | `--dp-accent`, `--dp-accent-soft` | Accent colour |
96
+ | `--dp-tip`, `--dp-attention`, `--dp-danger` (and their `-soft`) | Tones of the blocks set apart from the text |
97
+ | `--dp-radius` | Corner rounding |
98
+ | `--dp-font`, `--dp-font-mono` | Font families |
99
+ | `--dp-content-width` | Reading width. `none` by default: the content fills its column |
100
+ | `--dp-sidebar-width`, `--dp-toc-width` | Side columns |
69
101
 
70
102
  A redefined token propagates everywhere: components included, without any of
71
- them having to know.
103
+ them having to know. It sets the **light** palette; the dark one is a set of
104
+ tokens of its own, redefined in the `theme/` folder — see below. A site held
105
+ dark by `darkMode: 'dark'` therefore shows nothing of a token set here.
72
106
 
73
107
  ## Adding CSS
74
108
 
@@ -0,0 +1,125 @@
1
+ ---
2
+ title: Migrate from 0.3 to 0.4
3
+ description: Move a project from the 0.3 to the 0.4 — what changes on its own, and what to turn on.
4
+ tags: [guide, migration]
5
+ ---
6
+
7
+ # Migrate from 0.3 to 0.4
8
+
9
+ A project on the 0.3 builds with the 0.4 as it is: every field the 0.4 adds is
10
+ optional, and leaving them out keeps the site you have.
11
+
12
+ ## Update
13
+
14
+ ```bash
15
+ npm install docpensieve@latest
16
+ ```
17
+
18
+ Through `npx` alone, `npx docpensieve` already runs it.
19
+
20
+ Read your site back once after the update — it is the cheapest check there is:
21
+
22
+ ```bash
23
+ npx docpensieve build
24
+ npx docpensieve check
25
+ ```
26
+
27
+ ## What changes on its own
28
+
29
+ **On a narrow screen, the documentation menu now folds above the content**
30
+ instead of standing open between the header and the text. Nothing to
31
+ configure, and nothing to undo: on a wide screen the menu is unchanged.
32
+
33
+ Nothing else moves. Your pages, your configuration and your theme folder are
34
+ read exactly as before.
35
+
36
+ ## What is worth turning on
37
+
38
+ Each of these is one line of `docpensieve.config.mjs`. None depends on
39
+ another.
40
+
41
+ ### A menu that folds
42
+
43
+ Past twenty or so pages, a menu that shows everything asks the reader to scroll
44
+ past what does not concern them:
45
+
46
+ ```js
47
+ foldedSidebar: true,
48
+ ```
49
+
50
+ The categories fold, and the branch of the page being read opens on its own.
51
+ A category that is also a page keeps its page as the first entry — the handle
52
+ of a fold cannot be a link.
53
+
54
+ ### Links in the header
55
+
56
+ ```js
57
+ headerLinks: [
58
+ { label: 'Blog', href: '/blog/' },
59
+ { label: 'Repository', href: 'https://example.com/repo' },
60
+ ],
61
+ ```
62
+
63
+ A target starts from the root of a version, or is a full address. An entry
64
+ carrying `columns` opens a panel instead of leading anywhere; `href` and
65
+ `columns` together are refused, an entry doing one thing or the other.
66
+
67
+ See [Navigation](./navigation/) for the panel and for what happens on a phone.
68
+
69
+ ### A header that scrolls away
70
+
71
+ ```js
72
+ stickyHeader: false,
73
+ ```
74
+
75
+ The header gives its height back to the text instead of holding to the top of
76
+ the screen. It stays held by default.
77
+
78
+ ### Blocks that stand apart
79
+
80
+ Six kinds ship — `note`, `info`, `tip`, `attention`, `alert`, `danger` — and
81
+ need no configuration:
82
+
83
+ ```mdx
84
+ <Admonition type="attention">Read this before upgrading.</Admonition>
85
+ ```
86
+
87
+ Kinds of your own take a label and a tone:
88
+
89
+ ```js
90
+ admonitions: {
91
+ review: { label: 'To review', tone: 'attention' },
92
+ },
93
+ ```
94
+
95
+ A kind nobody declared stops the build rather than rendering a block with no
96
+ colour and no label. See [Admonition](../components/admonition/).
97
+
98
+ ### Icons from a set
99
+
100
+ Beside a file of your project, `LogoIcon` accepts the name of an icon from a
101
+ collection:
102
+
103
+ ```bash
104
+ npm install --save-dev @iconify-json/simple-icons
105
+ ```
106
+
107
+ ```mdx
108
+ <LogoIcon src="simple-icons:github" label="Repository" />
109
+ ```
110
+
111
+ The set is read at the build and the drawing placed in the page: no request
112
+ leaves your reader's browser. See [LogoIcon](../components/logo-icon/).
113
+
114
+ ## What to check afterwards
115
+
116
+ - **Your own CSS**, if the theme folder styles the header or the menu: the
117
+ folded menu adds `details` and `summary` elements where there were only
118
+ links, and a static header no longer carries `position: sticky`.
119
+ - **A link to a heading**, if you turned the sticky header off: the space kept
120
+ above an anchor goes away with it, which is the point.
121
+ - **`npx docpensieve check`**, which reads the built site back and reports dead
122
+ links and invalid markup.
123
+
124
+ None of this is required. A project that turns none of it on is a 0.3 project
125
+ that happens to be running the 0.4.
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: Navigation
3
+ description: The menu of the documentation, the links of the header, and what becomes of both on a phone.
4
+ tags: [guide, navigation]
5
+
6
+ jsonld:
7
+ type: TechArticle
8
+ breadcrumbs: true
9
+ ---
10
+
11
+ # Navigation
12
+
13
+ A reader finds a page in one of two ways: the menu of the documentation, on the
14
+ left, or the header, at the top. They answer different questions — _where am I
15
+ in this documentation_ and _where else can I go_ — and a site may use one, the
16
+ other, or both.
17
+
18
+ Neither loads a script. Everything here folds and unfolds with native elements.
19
+
20
+ ## The header, held or not
21
+
22
+ The header stays at the top of the screen. To let it scroll away with the page:
23
+
24
+ ```js
25
+ stickyHeader: false,
26
+ ```
27
+
28
+ Held, it keeps the search field and the version switcher within reach. Let go,
29
+ it gives back its height — a third of a phone screen — and anchors stop
30
+ reserving room for it.
31
+
32
+ ## The menu of the documentation
33
+
34
+ It follows the file tree, or the file you describe — that is
35
+ [Writing the menu by hand](./writing-pages/). What changes here is how much of
36
+ it shows at once.
37
+
38
+ ### Folding the categories
39
+
40
+ ```js
41
+ // docpensieve.config.mjs
42
+ foldedSidebar: true,
43
+ ```
44
+
45
+ Every category becomes a fold. **The branch holding the page being read is
46
+ open, the others closed**, so a menu of a hundred entries stops asking the
47
+ reader to scroll past what does not concern them.
48
+
49
+ <Columns>
50
+ <Column span={6}>
51
+ <Card style={{ height: '100%' }}>
52
+ <CardHeader>Unfolded — the default</CardHeader>
53
+ <CardBody>
54
+ Everything is visible at once. Right for a documentation of a few dozen pages, where
55
+ scrolling the menu costs nothing.
56
+ </CardBody>
57
+ </Card>
58
+ </Column>
59
+ <Column span={6}>
60
+ <Card style={{ height: '100%', borderColor: 'var(--dp-accent)' }}>
61
+ <CardHeader>Folded</CardHeader>
62
+ <CardBody>
63
+ Only the branch being read is open. Right once the menu is long enough that its end is out
64
+ of sight.
65
+ </CardBody>
66
+ </Card>
67
+ </Column>
68
+ </Columns>
69
+
70
+ A category that is also a page gains that page as its first entry. The handle
71
+ of a fold cannot be a link as well — a click would mean two things, and the
72
+ page would become unreachable.
73
+
74
+ ## The header
75
+
76
+ `headerLinks` puts links beside the version switcher.
77
+
78
+ ```js
79
+ headerLinks: [{ label: 'Blog', href: '/blog/' }],
80
+ ```
81
+
82
+ A target starts from the root of a version — `/blog/` — or is a full address. A
83
+ relative target is refused: the header is on every page, and `blog/` would mean
84
+ something else on each.
85
+
86
+ ### A section that lives in one version
87
+
88
+ A link may name the version it leads to:
89
+
90
+ ```js
91
+ { label: 'Examples', href: '/examples/', version: 'latest' },
92
+ ```
93
+
94
+ Every version then leads there, which is what keeps a section written in one
95
+ version reachable from all of them. This site does exactly that for its
96
+ examples.
97
+
98
+ ### A panel of links
99
+
100
+ An entry carrying `columns` opens a panel instead of leading anywhere:
101
+
102
+ ```js
103
+ {
104
+ label: 'Product',
105
+ columns: [
106
+ {
107
+ title: 'Guide',
108
+ items: [
109
+ { label: 'Installation', href: '/guide/installation/' },
110
+ { label: 'First site', href: '/guide/first-site/' },
111
+ ],
112
+ },
113
+ { items: [{ label: 'Repository', href: 'https://github.com/me/my-project' }] },
114
+ ],
115
+ }
116
+ ```
117
+
118
+ A column may go without a title. An entry cannot carry both `href` and
119
+ `columns`: it either leads somewhere or opens a panel.
120
+
121
+ The panel is declared here, **not derived from the menu of the documentation**.
122
+ The two are therefore free to show different things — or a site without a
123
+ sidebar can navigate from the header alone.
124
+
125
+ ## On a phone
126
+
127
+ Nothing is left to overflow off the screen.
128
+
129
+ | What | Below 48rem | Below 56rem |
130
+ | -------------------------------------- | ------------------------------------ | ----------------------------------------------- |
131
+ | Version switcher, links, panel, search | Behind the menu button of the header | — |
132
+ | Menu of the documentation | — | Folded above the content, behind its own button |
133
+
134
+ The panel of links unfolds inside the header menu rather than over it, and the
135
+ documentation menu starts closed: open, it would fill the first screen before a
136
+ word is read.
137
+
138
+ Both buttons are native elements. With JavaScript turned off, they still open.