@chassis-ui/docs 0.6.1 → 0.6.3
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 +14 -0
- package/README.md +47 -20
- package/package.json +1 -1
- package/src/components/shortcodes/Example.astro +2 -1
- package/src/components/shortcodes/Icon.astro +3 -1
- package/src/components/shortcodes/ResizableExample.astro +2 -1
- package/src/integration.ts +6 -0
- package/src/js/example-mode.js +3 -2
- package/src/js/search.js +68 -14
- package/src/layouts/footer/Footer.astro +3 -1
- package/src/layouts/head/Favicons.astro +7 -5
- package/src/layouts/head/Head.astro +9 -2
- package/src/layouts/head/Social.astro +5 -2
- package/src/layouts/head/Stylesheet.astro +3 -2
- package/src/layouts/header/Navigation.astro +2 -2
- package/src/libs/docs-path.ts +20 -0
- package/src/libs/icon.ts +4 -4
- package/src/libs/schema.ts +10 -0
- package/src/site.ts +9 -1
- package/virtual.d.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @chassis-ui/docs
|
|
2
2
|
|
|
3
|
+
## 0.6.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The home page of a site under a path gets the home form of the title, `title · subtitle`. The layouts took the page at `/` as the home page, so a site with `baseURL` `https://chassis-ui.com/css/` never got it: its home page is at `/css/`. The home page is now the page at the path of `baseURL`. The title of such a home page changes with this release, and the `title` prop of its layout is used for the social tags only. Keep `title · subtitle` within 70 characters where the site validates its HTML.
|
|
8
|
+
- The color mode script is inline in the head and runs before the first paint. It was a module, which runs after the document is parsed, so a page in dark mode showed light first.
|
|
9
|
+
- Five fixes of the search dialog. The page scrolls again after a search result on the current page is opened: the dialog closed without removing the scroll lock of the body. The input has the focus when the dialog is opened with the button, so typing works at once. The keyboard shortcuts open the dialog as the button does: the page behind it no longer scrolls, and a click outside closes it. A click or Enter on a loading placeholder no longer closes the dialog and saves an empty visit. Escape closes the dialog in Safari when the focus is in the empty input.
|
|
10
|
+
|
|
11
|
+
## 0.6.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Add the `staticPath` key of `config.yml`: the URL path that the pages load the static files from, `/static` by default. A site that is served under a path of another host, such as `chassis-ui.com/css`, sets it to `/css/static` and rewrites that path to `/static`, so that its files no longer depend on the `Referer` header to be found. The layouts, the `Icon` shortcode and the scripts of the package use it, and `getStaticPath()` of `@chassis-ui/docs/site` returns a URL under it for the site's own files. See [A site under a prefix](https://github.com/chassis-ui/website/tree/main/packages/docs#a-site-under-a-prefix) in the README.
|
|
16
|
+
|
|
3
17
|
## 0.6.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -106,23 +106,24 @@ repo: 'https://github.com/chassis-ui/css'
|
|
|
106
106
|
currentVersion: '0.5.2'
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
| Key | Required | Meaning
|
|
110
|
-
| --------------------------------------------- | -------- |
|
|
111
|
-
| `title`, `subtitle`, `description`, `authors` | yes | Used in the page title and the meta tags
|
|
112
|
-
| `baseURL` | yes | Canonical URL of the site
|
|
113
|
-
| `docsPath` | yes | URL path of the docs pages. Starts with `/`
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
109
|
+
| Key | Required | Meaning |
|
|
110
|
+
| --------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `title`, `subtitle`, `description`, `authors` | yes | Used in the page title and the meta tags |
|
|
112
|
+
| `baseURL` | yes | Canonical URL of the site. The page at its path is the home page: `/css/` for `https://chassis-ui.com/css/` |
|
|
113
|
+
| `docsPath` | yes | URL path of the docs pages. Starts with `/` |
|
|
114
|
+
| `staticPath` | no | URL path that the pages load the static files from. Default `/static`. See [A site under a prefix](#a-site-under-a-prefix) |
|
|
115
|
+
| `repo` | yes | URL of the repository |
|
|
116
|
+
| `currentVersion` | yes | Version that the site documents. Links to source files use the tag `v<currentVersion>` |
|
|
117
|
+
| `githubOrg` | no | GitHub organisation name, not a URL. The header links to it |
|
|
118
|
+
| `figmaHandle` | no | Figma Community handle. The header links to it |
|
|
119
|
+
| `xUsername` | no | X handle. Used in the social meta tags |
|
|
120
|
+
| `analytics.googleId` | no | Google Analytics ID. In production builds, loaded after the visitor accepts in a consent banner |
|
|
121
|
+
| `anchors.min`, `anchors.max` | no | Heading levels that get an anchor link. Default 2 to 5 |
|
|
122
|
+
| `toc.min`, `toc.max` | no | Heading levels in the table of contents. Default 2 to 6 |
|
|
123
|
+
| `sourceDir` | no | Directory that `file` props are relative to, from the site's root, e.g. `"../css"`. Default `"."` |
|
|
124
|
+
| `sourcePath` | no | The same directory from the root of the repository, e.g. `"packages/css"` |
|
|
125
|
+
| `sitePath` | no | The site's root from the root of the repository, e.g. `"packages/site"` |
|
|
126
|
+
| `siteBranch` | no | The branch that "View on GitHub" links to. Default `main` |
|
|
126
127
|
|
|
127
128
|
`file` props are those of `<ScssDocs>`, `<ScssDocsSimple>`, `<JsDocs>` and `<Code filePath>`.
|
|
128
129
|
|
|
@@ -181,7 +182,7 @@ export const collections = {
|
|
|
181
182
|
|
|
182
183
|
### 4. Provide the static files
|
|
183
184
|
|
|
184
|
-
The layouts link to files under `/static/`, which the site puts into its `public` directory:
|
|
185
|
+
The layouts link to files under `/static/`, which the site puts into `static/` of its `public` directory:
|
|
185
186
|
|
|
186
187
|
| URL | From |
|
|
187
188
|
| -------------------------------------------------------------------- | ----------------------------------------- |
|
|
@@ -195,6 +196,27 @@ These helpers find the folders, whether `node_modules` is in the site's root or
|
|
|
195
196
|
|
|
196
197
|
A site that type-checks with `astro check` declares the `@chassis-ui/css` module, which has no types: `declare module '@chassis-ui/css'`.
|
|
197
198
|
|
|
199
|
+
#### A site under a prefix
|
|
200
|
+
|
|
201
|
+
A site that is served under a path of another host, such as `chassis-ui.com/css`, cannot use `/static/` there: that path belongs to the host's own site. It loads its static files under a path of its own instead:
|
|
202
|
+
|
|
203
|
+
```yaml
|
|
204
|
+
# config.yml
|
|
205
|
+
staticPath: '/css/static'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The layouts, the `Icon` shortcode and the scripts of the package then link `/css/static/css/chassis.min.css` and so on. The files do not move: they stay in `static/` of the `public` directory. Two rewrites make the path answer:
|
|
209
|
+
|
|
210
|
+
- on the host, from `/css/static/*` to `/static/*` of the site's deployment.
|
|
211
|
+
- in the site's own `vercel.json`, from `/css/static/(.*)` to `/static/$1`, so that the deployment also works when it is opened directly.
|
|
212
|
+
|
|
213
|
+
Two kinds of URL are the site's own to move:
|
|
214
|
+
|
|
215
|
+
- URLs that the site writes itself, in its pages and components. `getStaticPath('images/hero.png')` of `@chassis-ui/docs/site` returns the URL under `staticPath`.
|
|
216
|
+
- The files that Astro builds, when the site puts them under `static/` with `build.assets`. Astro's `build.assetsPrefix` adds the prefix to them, `'/css'` here.
|
|
217
|
+
|
|
218
|
+
A social image named in the frontmatter as `static/images/…` is read from the `public` directory and linked under `staticPath`.
|
|
219
|
+
|
|
198
220
|
### 5. Use the layouts
|
|
199
221
|
|
|
200
222
|
```astro
|
|
@@ -207,6 +229,8 @@ import SingleLayout from '@chassis-ui/docs/layouts/SingleLayout.astro'
|
|
|
207
229
|
</SingleLayout>
|
|
208
230
|
```
|
|
209
231
|
|
|
232
|
+
The `<title>` of a page is its `title` and the `title` of `config.yml`: `About · Chassis UI`. The home page, which is the page at the path of `baseURL`, gets `title` and `subtitle` of `config.yml` instead: `Chassis UI · Design Systems that Scale & Sync`. Its own `title` is used for the social tags only. html-validate reports a `<title>` of more than 70 characters, so keep the two within that.
|
|
233
|
+
|
|
210
234
|
## Shortcodes
|
|
211
235
|
|
|
212
236
|
Every component in `@chassis-ui/docs/shortcodes/` is available in MDX files without an import. So is every component in the site's `src/components/shortcodes/`. A component of the site replaces the package's component of the same name.
|
|
@@ -233,6 +257,7 @@ chassisDocs({
|
|
|
233
257
|
| `getCallout(name)` | An entry of the `callouts` collection |
|
|
234
258
|
| `getSiteRoot()`, `getSiteFsPath(file)` | Absolute path of the site's root, or of a file in it |
|
|
235
259
|
| `getPublicFsPath(file)` | Absolute path of a file in the `public` directory |
|
|
260
|
+
| `getStaticPath(file)` | URL path of a static file, under `staticPath` of the config |
|
|
236
261
|
| `getSourceFsPath(file)` | Absolute path of a file that a `file` prop names |
|
|
237
262
|
| `getSourceUrl(file)` | URL of a source file on GitHub, at the tag of the current version |
|
|
238
263
|
| `getSiteFileUrl(filePath)` | URL of a file of the site on GitHub, on the site's branch |
|
|
@@ -343,7 +368,7 @@ These import paths are the public API.
|
|
|
343
368
|
|
|
344
369
|
No other path resolves: `exports` in `package.json` lists these and nothing else. The layouts and components are listed by name, so the folders inside `layouts/` and `components/` are not reachable.
|
|
345
370
|
|
|
346
|
-
The integration provides
|
|
371
|
+
The integration provides five modules: `virtual:chassis-docs/config`, `virtual:chassis-docs/sidebar`, `virtual:chassis-docs/paths`, `virtual:chassis-docs/static` and `virtual:chassis-docs/styles`. Use the functions of `@chassis-ui/docs/site` instead of importing them.
|
|
347
372
|
|
|
348
373
|
## Compatibility
|
|
349
374
|
|
|
@@ -351,6 +376,8 @@ The integration provides four modules: `virtual:chassis-docs/config`, `virtual:c
|
|
|
351
376
|
|
|
352
377
|
| `@chassis-ui/docs` | Requires Astro | Requires `@chassis-ui/css` | Requires Node | Built with |
|
|
353
378
|
| ------------------ | -------------------------- | -------------------------- | ------------- | ------------------------------------ |
|
|
379
|
+
| 0.6.3 | `^7.0.0` | `^0.5.0-0` | `>=22.12.0` | Astro 7.3.5, css 0.5.2, tokens 0.6.0 |
|
|
380
|
+
| 0.6.2 | `^7.0.0` | `^0.5.0-0` | `>=22.12.0` | Astro 7.3.5, css 0.5.2, tokens 0.6.0 |
|
|
354
381
|
| 0.6.1 | `^7.0.0` | `^0.5.0-0` | `>=22.12.0` | Astro 7.3.5, css 0.5.2, tokens 0.6.0 |
|
|
355
382
|
| 0.6.0 | `^7.0.0` | `^0.5.0-0` | `>=22.12.0` | Astro 7.3.5, css 0.5.2, tokens 0.5.3 |
|
|
356
383
|
| 0.5.1 | `^7.0.0` | `^0.5.0-0` | `>=22.12.0` | Astro 7.3.5, css 0.5.2, tokens 0.5.3 |
|
|
@@ -378,7 +405,7 @@ A change is breaking when it changes something that this README documents:
|
|
|
378
405
|
- the keys of `config.yml`, and the schemas of the sidebar and the content collections
|
|
379
406
|
- the props and slots of the layouts, components and shortcodes
|
|
380
407
|
- the `[[config:]]` and `[[docsref:]]` syntax
|
|
381
|
-
- the URLs under `/static/` that the layouts link to
|
|
408
|
+
- the URLs under `staticPath`, `/static/` by default, that the layouts link to
|
|
382
409
|
- the supported versions of Node, Astro and `@chassis-ui/css`
|
|
383
410
|
|
|
384
411
|
Everything else may change in a patch release: the rendered HTML, class names, the content of the styles, and every file that is not reachable through a documented import path.
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { replacePlaceholdersInHtml } from '../../libs/placeholder'
|
|
|
3
3
|
import { replaceIconsInHtml } from '../../libs/icon'
|
|
4
4
|
import Code from './Code.astro'
|
|
5
5
|
import Icon from './Icon.astro'
|
|
6
|
+
import { getStaticPath } from '../../site'
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
/**
|
|
@@ -69,7 +70,7 @@ const {
|
|
|
69
70
|
|
|
70
71
|
let markup = Array.isArray(code) ? code.join('\n') : code
|
|
71
72
|
markup = replacePlaceholdersInHtml(markup)
|
|
72
|
-
markup = replaceIconsInHtml(markup)
|
|
73
|
+
markup = replaceIconsInHtml(markup, getStaticPath())
|
|
73
74
|
markup = markup
|
|
74
75
|
.split('\n')
|
|
75
76
|
.map((line) => line.trimEnd())
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { getStaticPath } from '../../site'
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Icon
|
|
4
6
|
*
|
|
@@ -29,7 +31,7 @@ const {
|
|
|
29
31
|
size = 24,
|
|
30
32
|
font = false,
|
|
31
33
|
title,
|
|
32
|
-
sprite = '
|
|
34
|
+
sprite = getStaticPath('icons/chassis-icons.svg'),
|
|
33
35
|
...rest
|
|
34
36
|
} = Astro.props
|
|
35
37
|
---
|
|
@@ -3,6 +3,7 @@ import { replacePlaceholdersInHtml } from '../../libs/placeholder'
|
|
|
3
3
|
import { replaceIconsInHtml } from '../../libs/icon'
|
|
4
4
|
import Code from './Code.astro'
|
|
5
5
|
import Icon from './Icon.astro'
|
|
6
|
+
import { getStaticPath } from '../../site'
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
/**
|
|
@@ -67,7 +68,7 @@ const {
|
|
|
67
68
|
|
|
68
69
|
let markup = Array.isArray(code) ? code.join('\n') : code
|
|
69
70
|
markup = replacePlaceholdersInHtml(markup)
|
|
70
|
-
markup = replaceIconsInHtml(markup)
|
|
71
|
+
markup = replaceIconsInHtml(markup, getStaticPath())
|
|
71
72
|
|
|
72
73
|
// The resizable container needs `overflow: hidden` for the CSS `resize` handle
|
|
73
74
|
// to work, which clips popovers like menus/dropdowns when they open. Render them
|
package/src/integration.ts
CHANGED
|
@@ -90,6 +90,10 @@ declare module 'virtual:chassis-docs/paths' {
|
|
|
90
90
|
export const packageRoot: string
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
declare module 'virtual:chassis-docs/static' {
|
|
94
|
+
export const staticPath: string
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
declare module 'virtual:chassis-docs/styles' {}
|
|
94
98
|
`
|
|
95
99
|
|
|
@@ -234,6 +238,8 @@ export function chassisDocs<TSchema extends z.ZodType = typeof configSchema>(
|
|
|
234
238
|
paths: Object.entries(paths)
|
|
235
239
|
.map(([name, value]) => `export const ${name} = ${JSON.stringify(value)}`)
|
|
236
240
|
.join('\n'),
|
|
241
|
+
// A config that was not loaded with `loadConfig()` may lack the default.
|
|
242
|
+
static: `export const staticPath = ${JSON.stringify(config.staticPath ?? '/static')}`,
|
|
237
243
|
styles: styles.map((file) => `import ${JSON.stringify(file)}`).join('\n')
|
|
238
244
|
})
|
|
239
245
|
]
|
package/src/js/example-mode.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tooltip } from '@chassis-ui/css'
|
|
2
|
+
import { staticPath } from 'virtual:chassis-docs/static'
|
|
2
3
|
|
|
3
4
|
// Helper to detect the user's preferred theme
|
|
4
5
|
function getPreferredTheme() {
|
|
@@ -43,7 +44,7 @@ function toggleTheme(button) {
|
|
|
43
44
|
const iconUse = button.querySelector('use')
|
|
44
45
|
if (iconUse) {
|
|
45
46
|
const iconHref = newTheme === 'dark' ? 'sun-solid' : 'moon-solid'
|
|
46
|
-
iconUse.setAttribute('href',
|
|
47
|
+
iconUse.setAttribute('href', `${staticPath}/icons/chassis-icons.svg#${iconHref}`)
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -64,7 +65,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
64
65
|
const example = button.closest('.cxd-example-snippet')?.querySelector('.cxd-example')
|
|
65
66
|
const currentTheme = example?.getAttribute('data-cx-theme') || preferredTheme
|
|
66
67
|
const iconHref = currentTheme === 'dark' ? 'sun-solid' : 'moon-solid'
|
|
67
|
-
iconUse.setAttribute('href',
|
|
68
|
+
iconUse.setAttribute('href', `${staticPath}/icons/chassis-icons.svg#${iconHref}`)
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
// Add event listener if not already set through onclick
|
package/src/js/search.js
CHANGED
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
// is needed here.
|
|
14
14
|
|
|
15
15
|
import { getInstanceManager } from '@pagefind/component-ui'
|
|
16
|
+
import { staticPath } from 'virtual:chassis-docs/static'
|
|
16
17
|
|
|
17
18
|
const DIALOG_SELECTOR = '#cxdSearchDialog'
|
|
19
|
+
// The rows that are links. The loading skeleton reuses the class on a `<div>`,
|
|
20
|
+
// which must not be followed, focused or saved as a visit.
|
|
21
|
+
const ITEM_SELECTOR = 'a.cxd-search-item'
|
|
18
22
|
const SUB_RESULTS_LIMIT = 3
|
|
19
23
|
// How long a search may take before we show the loading skeleton. Most queries
|
|
20
24
|
// resolve faster, so the previous results stay on-screen instead of flashing.
|
|
@@ -233,7 +237,7 @@ const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) =>
|
|
|
233
237
|
const renderItem = ({ url, titleHtml, excerptHtml = '', icon, sub = false }) => `
|
|
234
238
|
<a class="cxd-search-item${sub ? ' cxd-search-subitem' : ''}" href="${escapeHtml(url)}">
|
|
235
239
|
<svg class="icon cxd-search-item-icon" width="16" height="16" aria-hidden="true">
|
|
236
|
-
<use href="/
|
|
240
|
+
<use href="${staticPath}/icons/chassis-icons.svg#${icon}"></use>
|
|
237
241
|
</svg>
|
|
238
242
|
<div class="cxd-search-item-body">
|
|
239
243
|
<div class="cxd-search-item-title">${titleHtml}</div>
|
|
@@ -293,12 +297,21 @@ class CxdSearchInput extends HTMLElement {
|
|
|
293
297
|
return
|
|
294
298
|
}
|
|
295
299
|
|
|
300
|
+
// Safari keeps Escape for the search field, even an empty one, and the
|
|
301
|
+
// dialog never gets its cancel event. With text in the field the key clears
|
|
302
|
+
// it, as in every browser; without text it closes the dialog here.
|
|
303
|
+
if (event.key === 'Escape' && !this.input.value) {
|
|
304
|
+
event.preventDefault()
|
|
305
|
+
closeSearchDialog(this.closest('dialog'))
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
|
|
296
309
|
// Enter follows the first result link.
|
|
297
310
|
// Always preventDefault so the form never implicit-submits.
|
|
298
311
|
if (event.key === 'Enter') {
|
|
299
312
|
event.preventDefault()
|
|
300
313
|
const dialogEl = this.closest('dialog')
|
|
301
|
-
dialogEl?.querySelector(
|
|
314
|
+
dialogEl?.querySelector(ITEM_SELECTOR)?.click()
|
|
302
315
|
}
|
|
303
316
|
}
|
|
304
317
|
|
|
@@ -338,11 +351,11 @@ class CxdSearchResults extends HTMLElement {
|
|
|
338
351
|
}
|
|
339
352
|
|
|
340
353
|
_getLinks() {
|
|
341
|
-
return [...this.querySelectorAll(
|
|
354
|
+
return [...this.querySelectorAll(ITEM_SELECTOR)]
|
|
342
355
|
}
|
|
343
356
|
|
|
344
357
|
_onKeydown(event) {
|
|
345
|
-
const link = event.target.closest(
|
|
358
|
+
const link = event.target.closest(ITEM_SELECTOR)
|
|
346
359
|
if (!link) return
|
|
347
360
|
|
|
348
361
|
const links = this._getLinks()
|
|
@@ -616,6 +629,29 @@ const isEditableTarget = (target) => {
|
|
|
616
629
|
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'
|
|
617
630
|
}
|
|
618
631
|
|
|
632
|
+
const TRIGGER_SELECTOR = `[data-cx-toggle="dialog"][data-cx-target="${DIALOG_SELECTOR}"]`
|
|
633
|
+
|
|
634
|
+
// Puts the caret in the input once the dialog is open. The chassis Dialog
|
|
635
|
+
// focuses the dialog element itself when it opens, so a key typed right after a
|
|
636
|
+
// click on the trigger went nowhere.
|
|
637
|
+
const focusSearchInput = (dialogEl) => {
|
|
638
|
+
requestAnimationFrame(() => {
|
|
639
|
+
if (!dialogEl.open) return
|
|
640
|
+
dialogEl.querySelector('cxd-search-input')?.focus()
|
|
641
|
+
if (!instance.searchTerm) {
|
|
642
|
+
dialogEl.querySelector('cxd-search-results')?._renderEmpty?.()
|
|
643
|
+
}
|
|
644
|
+
})
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const registerTriggerHandler = () => {
|
|
648
|
+
document.addEventListener('click', (event) => {
|
|
649
|
+
if (!event.target.closest(TRIGGER_SELECTOR)) return
|
|
650
|
+
const dialogEl = document.querySelector(DIALOG_SELECTOR)
|
|
651
|
+
if (dialogEl) focusSearchInput(dialogEl)
|
|
652
|
+
})
|
|
653
|
+
}
|
|
654
|
+
|
|
619
655
|
const openSearchDialog = () => {
|
|
620
656
|
const dialogEl = document.querySelector(DIALOG_SELECTOR)
|
|
621
657
|
if (!dialogEl) return
|
|
@@ -625,15 +661,24 @@ const openSearchDialog = () => {
|
|
|
625
661
|
return
|
|
626
662
|
}
|
|
627
663
|
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
664
|
+
// Open through the trigger, so that a shortcut does what the button does: the
|
|
665
|
+
// chassis Dialog locks the scroll of the body, closes on a click on the
|
|
666
|
+
// backdrop and gives the focus back. Without the chassis bundle the click does
|
|
667
|
+
// nothing, and the native showModal() opens the dialog.
|
|
668
|
+
document.querySelector(TRIGGER_SELECTOR)?.click()
|
|
669
|
+
if (!dialogEl.open) {
|
|
670
|
+
dialogEl.showModal()
|
|
671
|
+
focusSearchInput(dialogEl)
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Closes through the dismiss button, so that the chassis Dialog animates the
|
|
676
|
+
// dialog out, unlocks the scroll of the body and gives the focus back. Without
|
|
677
|
+
// the chassis bundle the click does nothing, and the native close() closes it.
|
|
678
|
+
const closeSearchDialog = (dialogEl) => {
|
|
679
|
+
if (!dialogEl?.open) return
|
|
680
|
+
dialogEl.querySelector('[data-cx-dismiss="dialog"]')?.click()
|
|
681
|
+
if (dialogEl.open && !dialogEl.classList.contains('hiding')) dialogEl.close()
|
|
637
682
|
}
|
|
638
683
|
|
|
639
684
|
const registerGlobalShortcuts = () => {
|
|
@@ -682,7 +727,7 @@ const setupDialogResetOnClose = () => {
|
|
|
682
727
|
|
|
683
728
|
const registerResultLinkHandler = () => {
|
|
684
729
|
document.addEventListener('click', (event) => {
|
|
685
|
-
const link = event.target.closest(
|
|
730
|
+
const link = event.target.closest(ITEM_SELECTOR)
|
|
686
731
|
if (!link) return
|
|
687
732
|
const dialogEl = link.closest('dialog')
|
|
688
733
|
if (!dialogEl?.matches(DIALOG_SELECTOR)) return
|
|
@@ -696,6 +741,14 @@ const registerResultLinkHandler = () => {
|
|
|
696
741
|
})
|
|
697
742
|
|
|
698
743
|
dialogEl.close()
|
|
744
|
+
|
|
745
|
+
// The trigger opens the dialog through the chassis Dialog, which locks the
|
|
746
|
+
// scroll of the body with this class and unlocks it in its own hide(). The
|
|
747
|
+
// native close() above skips that, so the class stays: when the result is on
|
|
748
|
+
// the current page, nothing reloads and the page can no longer scroll.
|
|
749
|
+
if (!document.querySelector('dialog[open]:modal')) {
|
|
750
|
+
document.body.classList.remove('dialog-open')
|
|
751
|
+
}
|
|
699
752
|
})
|
|
700
753
|
}
|
|
701
754
|
|
|
@@ -703,6 +756,7 @@ const registerResultLinkHandler = () => {
|
|
|
703
756
|
|
|
704
757
|
defineSearchCustomElements()
|
|
705
758
|
registerGlobalShortcuts()
|
|
759
|
+
registerTriggerHandler()
|
|
706
760
|
registerResultLinkHandler()
|
|
707
761
|
|
|
708
762
|
if (document.readyState === 'loading') {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Site footer with logo, license information, and navigation links.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import { getStaticPath } from '../../site'
|
|
7
9
|
---
|
|
8
10
|
|
|
9
11
|
<footer class="cxd-footer py-6xl">
|
|
@@ -15,7 +17,7 @@
|
|
|
15
17
|
href="/"
|
|
16
18
|
aria-label="Chassis"
|
|
17
19
|
>
|
|
18
|
-
<img src=
|
|
20
|
+
<img src={getStaticPath('images/site-logo.svg')} alt="Chassis UI" height="32" />
|
|
19
21
|
</a>
|
|
20
22
|
<p class="mb-xs">
|
|
21
23
|
This project is licensed under the MIT License, which allows for free use, modification,
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Favicon and app icon link tags for various platforms.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import { getStaticPath } from '../../site'
|
|
7
9
|
---
|
|
8
10
|
|
|
9
|
-
<link rel="apple-touch-icon" href={'
|
|
10
|
-
<link rel="icon" href={'
|
|
11
|
-
<link rel="icon" href={'
|
|
12
|
-
<link rel="manifest" href={'
|
|
13
|
-
<link rel="icon" href={'
|
|
11
|
+
<link rel="apple-touch-icon" href={getStaticPath('images/apple-touch-icon.png')} sizes="180x180" />
|
|
12
|
+
<link rel="icon" href={getStaticPath('images/favicon-32x32.png')} sizes="32x32" type="image/png" />
|
|
13
|
+
<link rel="icon" href={getStaticPath('images/favicon-16x16.png')} sizes="16x16" type="image/png" />
|
|
14
|
+
<link rel="manifest" href={getStaticPath('images/manifest.json')} />
|
|
15
|
+
<link rel="icon" href={getStaticPath('images/favicon.png')} />
|
|
14
16
|
<meta name="theme-color" content="#00A4CC" />
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { getConfig } from '../../site'
|
|
9
|
+
import { isHomePath } from '../../libs/docs-path'
|
|
9
10
|
import type { Layout } from '../../libs/layout'
|
|
10
11
|
import Stylesheet from './Stylesheet.astro'
|
|
11
12
|
import Favicons from './Favicons.astro'
|
|
12
13
|
import Social from './Social.astro'
|
|
14
|
+
import colorModes from '../../js/color-modes.js?raw'
|
|
13
15
|
import 'virtual:chassis-docs/styles'
|
|
14
16
|
|
|
15
17
|
interface Props {
|
|
@@ -29,7 +31,8 @@ const { description, layout, robots, thumbnail, title } = Astro.props
|
|
|
29
31
|
|
|
30
32
|
const canonicalUrl = new URL(Astro.url.pathname, Astro.site)
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
// The home page of a site under a path, such as `/css/`, is not at `/`
|
|
35
|
+
const isHome = isHomePath(getConfig().baseURL, Astro.url.pathname)
|
|
33
36
|
const pageTitle = isHome
|
|
34
37
|
? `${getConfig().title} · ${getConfig().subtitle}`
|
|
35
38
|
: `${title} · ${getConfig().title}`
|
|
@@ -60,7 +63,11 @@ const pageTitle = isHome
|
|
|
60
63
|
|
|
61
64
|
{robots && <meta name="robots" content={robots} />}
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
<!--
|
|
67
|
+
Inline and not a module, so that it sets the theme before the first paint. A module runs
|
|
68
|
+
after the document is parsed, and a page in dark mode would show light first
|
|
69
|
+
-->
|
|
70
|
+
<script is:inline set:html={colorModes} />
|
|
64
71
|
|
|
65
72
|
<Stylesheet />
|
|
66
73
|
<slot name="styles" />
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { getRemoteImageSize, getStaticImageSize } from '../../libs/image'
|
|
9
9
|
import type { Layout } from '../../libs/layout'
|
|
10
|
-
import { getConfig, getPublicFsPath } from '../../site'
|
|
10
|
+
import { getConfig, getPublicFsPath, getStaticPath } from '../../site'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
/** Page description */
|
|
@@ -22,7 +22,10 @@ interface Props {
|
|
|
22
22
|
|
|
23
23
|
const { description, layout, thumbnail, title } = Astro.props
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// A file in `static/` of the `public` directory is read from there, and linked under
|
|
26
|
+
// `staticPath` of the config.
|
|
27
|
+
const staticFile = thumbnail.match(/^\/?static\/(.+)$/)?.[1]
|
|
28
|
+
const socialImageUrl = new URL(staticFile ? getStaticPath(staticFile) : thumbnail, Astro.site)
|
|
26
29
|
const isRemoteThumbnail = /^https?:\/\//i.test(thumbnail)
|
|
27
30
|
const socialImageSize = isRemoteThumbnail
|
|
28
31
|
? await getRemoteImageSize(thumbnail)
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { HTMLAttributes } from 'astro/types'
|
|
9
|
+
import { getStaticPath } from '../../site'
|
|
9
10
|
|
|
10
11
|
function getChassisCssProps() {
|
|
11
|
-
let cxCssLinkHref = '
|
|
12
|
+
let cxCssLinkHref = getStaticPath('css/chassis')
|
|
12
13
|
|
|
13
14
|
if (import.meta.env.PROD) {
|
|
14
15
|
cxCssLinkHref = `${cxCssLinkHref}.min`
|
|
@@ -26,4 +27,4 @@ function getChassisCssProps() {
|
|
|
26
27
|
---
|
|
27
28
|
|
|
28
29
|
<link {...getChassisCssProps()} />
|
|
29
|
-
<link rel="stylesheet" href={
|
|
30
|
+
<link rel="stylesheet" href={getStaticPath('icons/chassis-icons.css')} />
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Layout } from '../../libs/layout'
|
|
9
|
-
import { getConfig } from '../../site'
|
|
9
|
+
import { getConfig, getStaticPath } from '../../site'
|
|
10
10
|
import NavLink from '../../components/NavLink.astro'
|
|
11
11
|
import ThemeToggler from '../../components/ThemeToggler.astro'
|
|
12
12
|
import SearchTrigger from './SearchTrigger.astro'
|
|
@@ -55,7 +55,7 @@ const isActive = (path: string) => {
|
|
|
55
55
|
{layout !== 'docs' && <div class="lg:d-none" style="width: 4.25rem;" />}
|
|
56
56
|
|
|
57
57
|
<a class="navbar-brand m-0 py-sm lg:me-md" href="/" aria-label="Chassis">
|
|
58
|
-
<img src=
|
|
58
|
+
<img src={getStaticPath('images/site-logo.svg')} alt="Chassis UI" width="123" height="32" />
|
|
59
59
|
</a>
|
|
60
60
|
|
|
61
61
|
<div class="d-flex">
|
package/src/libs/docs-path.ts
CHANGED
|
@@ -10,3 +10,23 @@ export function joinDocsPath(docsPath: string, inputPath: string): string {
|
|
|
10
10
|
|
|
11
11
|
return page ? `${base}/${page}` : base
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/** Joins the static path of the config and a file: `('/css/static', 'css/chassis.css')`. */
|
|
15
|
+
export function joinStaticPath(staticPath: string, file = ''): string {
|
|
16
|
+
const name = file.replace(/^\/+/, '')
|
|
17
|
+
|
|
18
|
+
return name ? `${staticPath}/${name}` : staticPath
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether a page is the home page of the site: the page at the path of `baseURL`. That is
|
|
23
|
+
* `/` for `https://chassis-ui.com` and `/css/` for `https://chassis-ui.com/css/`.
|
|
24
|
+
*
|
|
25
|
+
* @param baseURL The `baseURL` of the site's `config.yml`.
|
|
26
|
+
* @param pathname URL path of the page, with or without a trailing slash.
|
|
27
|
+
*/
|
|
28
|
+
export function isHomePath(baseURL: string, pathname: string): boolean {
|
|
29
|
+
const trim = (value: string) => value.replace(/\/+$/, '')
|
|
30
|
+
|
|
31
|
+
return trim(pathname) === trim(new URL(baseURL).pathname)
|
|
32
|
+
}
|
package/src/libs/icon.ts
CHANGED
|
@@ -20,7 +20,7 @@ interface IconOptions {
|
|
|
20
20
|
* actually invoked — they need to be expanded to their rendered markup ahead of time.
|
|
21
21
|
* @see src/components/shortcodes/Icon.astro
|
|
22
22
|
*/
|
|
23
|
-
export function replaceIconsInHtml(html: string) {
|
|
23
|
+
export function replaceIconsInHtml(html: string, staticPath = '/static') {
|
|
24
24
|
return html.replace(iconRegex, (match) => {
|
|
25
25
|
const document = htmlparser2.parseDocument(match, { xmlMode: true })
|
|
26
26
|
const iconElement = document.firstChild
|
|
@@ -34,11 +34,11 @@ export function replaceIconsInHtml(html: string) {
|
|
|
34
34
|
throw new Error('Invalid icon element.')
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
return renderIconToString(getOptionsWithDefaults(iconElement.attribs))
|
|
37
|
+
return renderIconToString(getOptionsWithDefaults(iconElement.attribs, staticPath))
|
|
38
38
|
})
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function getOptionsWithDefaults(attribs: Record<string, string
|
|
41
|
+
function getOptionsWithDefaults(attribs: Record<string, string>, staticPath: string): IconOptions {
|
|
42
42
|
if (!attribs.name) {
|
|
43
43
|
throw new Error('Icon element is missing the required "name" attribute.')
|
|
44
44
|
}
|
|
@@ -48,7 +48,7 @@ function getOptionsWithDefaults(attribs: Record<string, string>): IconOptions {
|
|
|
48
48
|
class: attribs.class,
|
|
49
49
|
size: attribs.size ? Number(attribs.size.replace(/[{}]/g, '')) : 24,
|
|
50
50
|
font: attribs.font !== undefined && attribs.font !== '{false}',
|
|
51
|
-
sprite: attribs.sprite ||
|
|
51
|
+
sprite: attribs.sprite || `${staticPath}/icons/chassis-icons.svg`,
|
|
52
52
|
title: attribs.title
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/libs/schema.ts
CHANGED
|
@@ -70,6 +70,16 @@ export const configSchema = z.strictObject({
|
|
|
70
70
|
sourceDir: z.string().default('.'),
|
|
71
71
|
/** The same directory from the root of the repository, e.g. `"packages/css"`. */
|
|
72
72
|
sourcePath: z.string().optional(),
|
|
73
|
+
/**
|
|
74
|
+
* URL path that the pages load the static files from, e.g. `"/css/static"`. The files stay
|
|
75
|
+
* in `static/` of the `public` directory: a site under a prefix of another host sets this
|
|
76
|
+
* and rewrites the path to `/static`.
|
|
77
|
+
*/
|
|
78
|
+
staticPath: z
|
|
79
|
+
.string()
|
|
80
|
+
.regex(/^\/(.*[^/])?$/, 'Expected a URL path that starts with "/" and has no trailing slash')
|
|
81
|
+
.refine((value) => value !== '/', 'Expected a path below the root, such as "/static"')
|
|
82
|
+
.default('/static'),
|
|
73
83
|
subtitle: z.string(),
|
|
74
84
|
title: z.string(),
|
|
75
85
|
/** Heading levels that the table of contents lists. */
|
package/src/site.ts
CHANGED
|
@@ -9,7 +9,7 @@ import sidebar from 'virtual:chassis-docs/sidebar'
|
|
|
9
9
|
import * as paths from 'virtual:chassis-docs/paths'
|
|
10
10
|
import { getCollection, getEntry, type render } from 'astro:content'
|
|
11
11
|
import { loadData } from './libs/config'
|
|
12
|
-
import { joinDocsPath } from './libs/docs-path'
|
|
12
|
+
import { joinDocsPath, joinStaticPath } from './libs/docs-path'
|
|
13
13
|
import { getNodeModulesFsPaths, resolvePackageFilePath } from './libs/paths'
|
|
14
14
|
import { replaceConfigInText, replaceDocsrefInText } from './libs/remark'
|
|
15
15
|
import type { ChassisConfig, DocsPage, Sidebar } from './libs/schema'
|
|
@@ -35,6 +35,14 @@ export function getDocsPath(inputPath: string): string {
|
|
|
35
35
|
return joinDocsPath(config.docsPath, inputPath)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* URL path of a static file, under `staticPath` of the config:
|
|
40
|
+
* `getStaticPath('images/site-logo.svg')` is `/static/images/site-logo.svg` by default.
|
|
41
|
+
*/
|
|
42
|
+
export function getStaticPath(file = ''): string {
|
|
43
|
+
return joinStaticPath(config.staticPath ?? '/static', file)
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
/** Absolute path of the site's root: the directory that holds `astro.config.ts`. */
|
|
39
47
|
export function getSiteRoot(): string {
|
|
40
48
|
return paths.root
|
package/virtual.d.ts
CHANGED
|
@@ -22,4 +22,9 @@ declare module 'virtual:chassis-docs/paths' {
|
|
|
22
22
|
export const packageRoot: string
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
declare module 'virtual:chassis-docs/static' {
|
|
26
|
+
/** `staticPath` of the config, for scripts that run in the browser. */
|
|
27
|
+
export const staticPath: string
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
declare module 'virtual:chassis-docs/styles' {}
|