@chassis-ui/docs 0.1.10 → 0.3.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 (79) hide show
  1. package/README.md +16 -16
  2. package/index.ts +6 -1
  3. package/package.json +20 -8
  4. package/src/components/DocsSidebar.astro +63 -23
  5. package/src/components/FeatureCard.astro +8 -8
  6. package/src/components/NavLink.astro +1 -1
  7. package/src/components/TableOfContents.astro +8 -7
  8. package/src/components/ThemeToggler.astro +16 -37
  9. package/src/components/shortcodes/Blockquote.astro +31 -0
  10. package/src/components/shortcodes/CSSOnly.astro +22 -0
  11. package/src/components/shortcodes/Callout.astro +6 -2
  12. package/src/components/shortcodes/Code.astro +289 -0
  13. package/src/components/shortcodes/DeprecatedIn.astro +18 -0
  14. package/src/components/shortcodes/Example.astro +132 -0
  15. package/src/components/shortcodes/Icon.astro +57 -0
  16. package/src/components/shortcodes/InFigma.astro +26 -0
  17. package/src/components/shortcodes/JsDocs.astro +64 -0
  18. package/src/components/shortcodes/Placeholder.astro +28 -0
  19. package/src/components/shortcodes/ResizableExample.astro +126 -0
  20. package/src/components/shortcodes/ScssDocs.astro +108 -0
  21. package/src/components/shortcodes/ScssDocsSimple.astro +66 -0
  22. package/src/js/color-modes.js +3 -3
  23. package/src/js/example-mode.js +75 -0
  24. package/src/js/search.js +501 -39
  25. package/src/js/theme-color.js +1 -1
  26. package/src/layouts/DocsLayout.astro +18 -21
  27. package/src/layouts/RedirectLayout.astro +1 -0
  28. package/src/layouts/SingleLayout.astro +3 -3
  29. package/src/layouts/footer/Footer.astro +5 -5
  30. package/src/layouts/footer/Scripts.astro +14 -7
  31. package/src/layouts/head/Analytics.astro +12 -6
  32. package/src/layouts/head/Favicons.astro +0 -2
  33. package/src/layouts/head/Head.astro +2 -7
  34. package/src/layouts/head/Stylesheet.astro +18 -11
  35. package/src/layouts/header/Header.astro +2 -5
  36. package/src/layouts/header/Navigation.astro +40 -42
  37. package/src/layouts/header/SearchDialog.astro +74 -0
  38. package/src/layouts/header/SearchTrigger.astro +41 -0
  39. package/src/layouts/header/Skippy.astro +1 -1
  40. package/src/libs/highlight.ts +91 -0
  41. package/src/libs/icon.ts +75 -0
  42. package/src/libs/image.ts +12 -1
  43. package/src/libs/layout.ts +18 -0
  44. package/src/libs/markdown.ts +128 -0
  45. package/src/libs/placeholder.ts +250 -0
  46. package/src/libs/rehype.ts +0 -17
  47. package/src/libs/shortcodes.ts +129 -0
  48. package/src/libs/site.ts +95 -0
  49. package/src/libs/toc.ts +40 -6
  50. package/src/libs/utils.ts +34 -82
  51. package/src/libs/vite.ts +45 -0
  52. package/src/scss/_anchor.scss +20 -16
  53. package/src/scss/_callouts.scss +29 -28
  54. package/src/scss/_content.scss +72 -103
  55. package/src/scss/_footer.scss +18 -19
  56. package/src/scss/_layout.scss +56 -61
  57. package/src/scss/_navbar.scss +19 -126
  58. package/src/scss/_placeholder-img.scss +11 -7
  59. package/src/scss/_scrolling.scss +14 -12
  60. package/src/scss/_search.scss +418 -0
  61. package/src/scss/_shiki.scss +120 -0
  62. package/src/scss/_sidebar.scss +137 -132
  63. package/src/scss/_skippy.scss +6 -4
  64. package/src/scss/_snippets.scss +181 -0
  65. package/src/scss/_toc.scss +97 -93
  66. package/src/scss/_vars.scss +89 -0
  67. package/src/scss/main.scss +75 -81
  68. package/src/js/icon-loader.js +0 -229
  69. package/src/libs/chassis.ts +0 -38
  70. package/src/scss/_brand.scss +0 -59
  71. package/src/scss/_buttons.scss +0 -33
  72. package/src/scss/_clipboard-js.scss +0 -63
  73. package/src/scss/_code.scss +0 -116
  74. package/src/scss/_colors.scss +0 -140
  75. package/src/scss/_docsearch.scss +0 -212
  76. package/src/scss/_masthead.scss +0 -124
  77. package/src/scss/_settings.scss +0 -29
  78. package/src/scss/_syntax.scss +0 -160
  79. package/src/scss/_variables.scss +0 -78
@@ -7,9 +7,9 @@
7
7
  ---
8
8
 
9
9
  <footer class="cxd-footer py-6xlarge">
10
- <div class="container-2xlarge fg-subtle">
10
+ <div class="container 2xlarge fg-subtle">
11
11
  <div class="row">
12
- <div class="col-medium-10 col-large-5 mb-medium">
12
+ <div class="medium:col-10 large:col-5 mb-medium">
13
13
  <a
14
14
  class="d-inline-flex align-items-center mb-xsmall text-body-emphasis text-decoration-none"
15
15
  href="/"
@@ -22,7 +22,7 @@
22
22
  and distribution of the software.
23
23
  </p>
24
24
  </div>
25
- <div class="col-small-4 col-large-2 offset-large-1 mb-medium">
25
+ <div class="small:col-4 large:col-2 large:offset-1 mb-medium">
26
26
  <p class="h6 mb-xsmall">Docs</p>
27
27
  <ul class="nav">
28
28
  <li><a href="/tokens">Design Tokens</a></li>
@@ -32,7 +32,7 @@
32
32
  <li><a href="/icons">Icon Generator</a></li>
33
33
  </ul>
34
34
  </div>
35
- <div class="col-small-4 col-large-2 mb-medium">
35
+ <div class="small:col-4 large:col-2 mb-medium">
36
36
  <p class="h6 mb-xsmall">Github</p>
37
37
  <ul class="nav">
38
38
  <li><a href="https://github.com/chassis-ui/tokens">Chassis Tokens</a></li>
@@ -42,7 +42,7 @@
42
42
  <li><a href="https://github.com/chassis-ui/icons">Chassis Icons</a></li>
43
43
  </ul>
44
44
  </div>
45
- <div class="col-small-4 col-large-2 mb-medium">
45
+ <div class="small:col-4 large:col-2 mb-medium">
46
46
  <p class="h6 mb-xsmall">Project</p>
47
47
  <ul class="nav">
48
48
  <li><a href="/about">About</a></li>
@@ -5,8 +5,8 @@
5
5
  * JavaScript imports for site functionality.
6
6
  */
7
7
 
8
- import { getChassisJsProps } from '../../libs/chassis'
9
8
  import type { Layout } from '../../libs/layout'
9
+ import type { HTMLAttributes } from 'astro/types'
10
10
 
11
11
  interface Props {
12
12
  /** Current layout context */
@@ -14,14 +14,21 @@ interface Props {
14
14
  }
15
15
 
16
16
  const { layout } = Astro.props
17
- ---
18
17
 
19
- <script is:inline {...getChassisJsProps()}></script>
18
+ function getChassisJsProps() {
19
+ // __CHASSIS_JS_SRC__ is an optional dev-mode override injected via vite.define by the
20
+ // host project's integration. When set, it replaces the default public-dir bundle path.
21
+ const src =
22
+ (import.meta.env.DEV && (globalThis as any).__CHASSIS_JS_SRC__) ||
23
+ (import.meta.env.PROD ? '/static/js/chassis.bundle.min.js' : '/static/js/chassis.bundle.js')
20
24
 
21
- <script src="../../js/search.js"></script>
22
-
23
- {layout === 'docs' && <script src="../../js/sidebar.js" />}
25
+ const cxJsLinkProps: HTMLAttributes<'script'> = { type: 'module', src }
24
26
 
25
- <script src="../../js/icon-loader.js"></script>
27
+ return cxJsLinkProps
28
+ }
29
+ ---
26
30
 
31
+ <script src="../../js/search.js"></script>
32
+ <script is:inline {...getChassisJsProps()}></script>
33
+ {layout === 'docs' && <script src="../../js/sidebar.js" />}
27
34
  <slot name="scripts" />
@@ -6,18 +6,24 @@
6
6
  */
7
7
 
8
8
  import { getConfig } from '@libs/config'
9
+ const isProd = import.meta.env.PROD
9
10
  const googleId = getConfig().analytics.google_id
10
11
  ---
11
12
 
12
- <!-- Google tag (gtag.js) -->
13
- <script is:inline async src={`https://www.googletagmanager.com/gtag/js?id=${googleId}`}></script>
14
- <script
15
- is:inline
16
- set:html={`
13
+ {
14
+ isProd && (
15
+ <>
16
+ <script is:inline async src={`https://www.googletagmanager.com/gtag/js?id=${googleId}`} />
17
+ <script
18
+ is:inline
19
+ set:html={`
17
20
  window.dataLayer = window.dataLayer || [];
18
21
  function gtag(){dataLayer.push(arguments);}
19
22
  gtag('js', new Date());
20
23
 
21
24
  gtag('config', '${googleId}');
22
25
  `}
23
- />
26
+ />
27
+ </>
28
+ )
29
+ }
@@ -4,8 +4,6 @@
4
4
  *
5
5
  * Favicon and app icon link tags for various platforms.
6
6
  */
7
-
8
- // <meta name="theme-color" content="#00A4CC" />
9
7
  ---
10
8
 
11
9
  <link rel="apple-touch-icon" href={'/static/images/apple-touch-icon.png'} sizes="180x180" />
@@ -16,8 +16,6 @@ import '@scss/docs.scss'
16
16
  interface Props {
17
17
  /** Page description for meta tags */
18
18
  description: string
19
- /** Text direction (for RTL languages) */
20
- direction?: 'rtl'
21
19
  /** Current layout context */
22
20
  layout: Layout
23
21
  /** Robots meta tag value */
@@ -28,7 +26,7 @@ interface Props {
28
26
  title: string
29
27
  }
30
28
 
31
- const { description, direction, layout, robots, thumbnail, title } = Astro.props
29
+ const { description, layout, robots, thumbnail, title } = Astro.props
32
30
 
33
31
  const canonicalUrl = new URL(Astro.url.pathname, Astro.site)
34
32
 
@@ -45,19 +43,16 @@ const pageTitle = isHome
45
43
 
46
44
  <meta name="author" content={getConfig().authors} />
47
45
  <meta name="generator" content={Astro.generator} />
48
- <meta name="docsearch:language" content="en" />
49
46
 
50
47
  <link rel="canonical" href={canonicalUrl} />
51
48
 
52
- <link rel="preconnect" href={`https://${getConfig().algolia.app_id}-dsn.algolia.net`} crossorigin />
53
-
54
49
  <title>{pageTitle}</title>
55
50
 
56
51
  {robots && <meta name="robots" content={robots} />}
57
52
 
58
53
  <script src="../../js/color-modes.js"></script>
59
54
 
60
- <Stylesheet direction={direction} layout={layout} />
55
+ <Stylesheet />
61
56
  <slot name="styles" />
62
57
  <Favicons />
63
58
  <Social description={description} layout={layout} thumbnail={thumbnail} title={title} />
@@ -5,18 +5,25 @@
5
5
  * Stylesheet link tags for external CSS resources.
6
6
  */
7
7
 
8
- import { getChassisCssProps } from '../../libs/chassis'
9
- import type { Layout } from '../../libs/layout'
8
+ import type { HTMLAttributes } from 'astro/types'
10
9
 
11
- interface Props {
12
- /** Text direction (for RTL languages) */
13
- direction?: 'rtl'
14
- /** Current layout context */
15
- layout: Layout
16
- }
10
+ function getChassisCssProps() {
11
+ let cxCssLinkHref = '/static/css/chassis'
12
+
13
+ if (import.meta.env.PROD) {
14
+ cxCssLinkHref = `${cxCssLinkHref}.min`
15
+ }
16
+
17
+ cxCssLinkHref = `${cxCssLinkHref}.css`
17
18
 
18
- const { direction } = Astro.props
19
+ const cxCssLinkProps: HTMLAttributes<'link'> = {
20
+ href: cxCssLinkHref,
21
+ rel: 'stylesheet'
22
+ }
23
+
24
+ return cxCssLinkProps
25
+ }
19
26
  ---
20
27
 
21
- <link {...getChassisCssProps(direction)} />
22
- <link rel="stylesheet" href={`/static/icons/chassis-icons.min.css`} />
28
+ <link {...getChassisCssProps()} />
29
+ <link rel="stylesheet" href={`/static/icons/chassis-icons.css`} />
@@ -5,23 +5,20 @@
5
5
  * Site header with navigation and page title.
6
6
  */
7
7
 
8
- import type { CollectionEntry } from 'astro:content'
9
8
  import type { Layout } from '../../libs/layout'
10
9
  import Skippy from './Skippy.astro'
11
10
  import Navigation from './Navigation.astro'
12
11
 
13
12
  interface Props {
14
- /** Version when the feature was added */
15
- addedIn?: CollectionEntry<'docs'>['data']['added']
16
13
  /** Current layout context */
17
14
  layout: Layout
18
15
  /** Page title */
19
16
  title: string
20
17
  }
21
18
 
22
- const { layout, title } = Astro.props
19
+ const { layout } = Astro.props
23
20
  ---
24
21
 
25
22
  <Skippy layout={layout} />
26
23
 
27
- <Navigation layout={layout} title={title} />
24
+ <Navigation layout={layout} />
@@ -9,12 +9,13 @@ import { getConfig } from '@libs/config'
9
9
  import type { Layout } from '../../libs/layout'
10
10
  import NavLink from '../../components/NavLink.astro'
11
11
  import ThemeToggler from '../../components/ThemeToggler.astro'
12
+ import SearchTrigger from './SearchTrigger.astro'
13
+ import SearchDialog from './SearchDialog.astro'
14
+ import Icon from '../../components/shortcodes/Icon.astro'
12
15
 
13
16
  interface Props {
14
17
  /** Current layout context */
15
18
  layout: Layout
16
- /** Page title for the navbar */
17
- title: string
18
19
  }
19
20
 
20
21
  const { layout } = Astro.props
@@ -31,111 +32,108 @@ const isActive = (path: string) => {
31
32
  }
32
33
  ---
33
34
 
34
- <header class="navbar navbar-expand-large cxd-navbar sticky-top">
35
- <nav class="container-2xlarge" aria-label="Main navigation">
35
+ <header class="navbar translucent large:navbar-expand cxd-navbar sticky-top">
36
+ <nav class="container 2xlarge" aria-label="Site navigation">
36
37
  {
37
38
  layout === 'docs' && (
38
39
  <div class="cxd-navbar-toggle">
39
40
  <button
40
- class="navbar-toggler p-xsmall2"
41
+ class="button icon-only navbar-toggler"
41
42
  type="button"
42
- data-cx-toggle="offcanvas"
43
+ data-cx-toggle="drawer"
43
44
  data-cx-target="#cxdSidebar"
44
45
  aria-controls="cxdSidebar"
45
46
  aria-label="Toggle docs navigation"
46
47
  >
47
- <svg class="icon icon-large" aria-hidden="true">
48
- <use href="#bars-solid" />
49
- </svg>
50
- <span class="d-none fs-6 pe-2xsmall">Browse</span>
48
+ <Icon name="bars-solid" class="icon-large" />
49
+ <span class="d-none large:d-inline fs-6 pe-2xsmall">Browse</span>
51
50
  </button>
52
51
  </div>
53
52
  )
54
53
  }
55
- {layout !== 'docs' && <div class="d-large-none" style="width: 4.25rem;" />}
54
+ {layout !== 'docs' && <div class="large:d-none" style="width: 4.25rem;" />}
56
55
 
57
- <a class="navbar-brand me-large-medium" href="/" aria-label="Chassis">
56
+ <a class="navbar-brand m-0 py-small large:me-medium" href="/" aria-label="Chassis">
58
57
  <img src="/static/images/site-logo.svg" alt="Chassis UI" width="123" height="32" />
59
58
  </a>
60
59
 
61
60
  <div class="d-flex">
62
- <div class="cxd-search" id="docsearch"></div>
61
+ <div class="cxd-search"><SearchTrigger placeholder="Search docs…" /></div>
63
62
 
64
63
  <button
65
- class="navbar-toggler d-flex d-large-none order-3 p-xsmall"
64
+ class="button icon-only navbar-toggler"
66
65
  type="button"
67
- data-cx-toggle="offcanvas"
66
+ data-cx-toggle="drawer"
68
67
  data-cx-target="#cxdNavbar"
69
68
  aria-controls="cxdNavbar"
70
69
  aria-label="Toggle navigation"
71
70
  >
72
- <svg class="icon" aria-hidden="true"><use xlink:href="#ellipsis-h-solid"></use></svg>
71
+ <Icon name="ellipsis-h-solid" />
73
72
  </button>
74
73
  </div>
75
74
 
76
- <div
77
- class="offcanvas-large offcanvas-end flex-grow-1"
78
- role="dialog"
79
- tabindex="-1"
75
+ <dialog
76
+ class="max-large:drawer drawer-end flex-grow-1"
80
77
  id="cxdNavbar"
81
78
  aria-labelledby="cxdNavbarOffcanvasLabel"
82
79
  >
83
- <div class="offcanvas-header px-large pb-0">
84
- <h5 class="offcanvas-title text-white" id="cxdNavbarOffcanvasLabel">Chassis</h5>
80
+ <div class="drawer-header">
81
+ <h2 class="drawer-title" id="cxdNavbarOffcanvasLabel">Chassis</h2>
85
82
  <button
86
83
  type="button"
87
84
  class="close-button close-button-white"
88
- data-cx-dismiss="offcanvas"
85
+ data-cx-dismiss="drawer"
89
86
  aria-label="Close"
90
87
  data-cx-target="#cxdNavbar"></button>
91
88
  </div>
92
89
 
93
- <div class="offcanvas-body p-large pt-0 p-large-0">
94
- <hr class="d-large-none" />
95
- <ul class="navbar-nav flex-row flex-wrap cxd-navbar-nav mx-0 mx-large-medium">
90
+ <hr class="large:d-none m-0" />
91
+ <div class="drawer-body large:p-0">
92
+ <ul class="navbar-nav flex-row flex-wrap cxd-navbar-nav mx-0 large:mx-medium">
96
93
  <NavLink active={isActive('/tokens')} href="/tokens">Tokens</NavLink>
97
94
  <NavLink active={isActive('/figma')} href="/figma">Figma</NavLink>
98
95
  <NavLink active={isActive('/css')} href="/css">CSS</NavLink>
99
96
  <NavLink active={isActive('/assets')} href="/assets">Assets</NavLink>
100
97
  <NavLink active={isActive('/icons')} href="/icons">Icons</NavLink>
101
- <li class="nav-item col-12 col-large-auto d-large-flex">
102
- <div class="vr d-none d-large-flex my-auto mx-large-2"></div>
103
- <hr class="d-large-none" />
98
+ <li class="nav-item col-12 large:col-auto large:d-flex">
99
+ <div class="vr d-none large:d-flex my-auto large:mx-small"></div>
100
+ <hr class="large:d-none" />
104
101
  </li>
105
102
  <NavLink active={isActive('/examples')} href="/examples">Examples</NavLink>
106
103
  </ul>
107
104
 
108
- <hr class="d-large-none" />
105
+ <hr class="large:d-none m-0" />
109
106
 
110
- <ul class="navbar-nav flex-row flex-wrap ms-medium-auto align-items-center">
107
+ <ul class="navbar-nav flex-row flex-wrap large:ms-auto align-items-center">
111
108
  <NavLink
112
- class="nav-link px-0 px-large-2"
109
+ class="px-0 large:px-small"
113
110
  href={getConfig().github_org}
114
111
  target="_blank"
115
112
  rel="noopener"
116
113
  >
117
- <svg class="icon"><title>GitHub</title><use href="#github-brand"></use></svg>
118
- <small class="d-large-none ms-xsmall">GitHub</small>
114
+ <Icon name="github-brand" title="GitHub" />
115
+ <small class="large:d-none ms-xsmall">GitHub</small>
119
116
  </NavLink>
120
117
  <NavLink
121
- class="nav-link px-0 px-large-2"
118
+ class="px-0 large:px-small"
122
119
  href={`https://x.com/${getConfig().x}`}
123
120
  target="_blank"
124
121
  rel="noopener"
125
122
  >
126
- <svg class="icon"><title>X</title><use href="#x-twitter-brand"></use></svg>
127
- <small class="d-large-none ms-xsmall">X</small>
123
+ <Icon name="x-twitter-brand" title="X" />
124
+ <small class="large:d-none ms-xsmall">X</small>
128
125
  </NavLink>
129
- <li class="nav-item col-12 col-large-auto">
130
- <div class="vr d-none d-large-flex h-100 mx-large-2"></div>
131
- <hr class="d-large-none" />
126
+ <li class="nav-item col-12 large:col-auto">
127
+ <div class="vr d-none large:d-flex h-100 large:mx-2xsmall"></div>
128
+ <hr class="large:d-none" />
132
129
  </li>
133
130
 
134
- <li class="nav-item dropdown">
131
+ <li class="nav-item">
135
132
  <ThemeToggler layout={layout} />
136
133
  </li>
137
134
  </ul>
138
135
  </div>
139
- </div>
136
+ </dialog>
140
137
  </nav>
141
138
  </header>
139
+ <SearchDialog placeholder="Search docs…" />
@@ -0,0 +1,74 @@
1
+ ---
2
+ /**
3
+ * Search Dialog
4
+ *
5
+ * Modal dialog containing the Pagefind search input and results. Opened by
6
+ * `SearchTrigger` or the ⌘K / Ctrl+K / "/" keyboard shortcuts defined in
7
+ * `src/js/search.js`. Uses native `<dialog>` with `showModal()`.
8
+ */
9
+
10
+ import Icon from '../../components/shortcodes/Icon.astro'
11
+
12
+ interface Props {
13
+ /** HTML `id` of the dialog element. Must match the `target` in `SearchTrigger`. */
14
+ id?: string
15
+ /** Placeholder text for the search input and the visually-hidden label. */
16
+ placeholder?: string
17
+ }
18
+
19
+ const { id = 'cxdSearchDialog', placeholder = 'Search docs…' } = Astro.props
20
+ ---
21
+
22
+ <dialog
23
+ class="modal dialog large cxd-search-dialog scrollable"
24
+ id={id}
25
+ aria-labelledby={`${id}-label`}
26
+ >
27
+ <search class="modal-header cxd-search-form">
28
+ <label class="visually-hidden" id={`${id}-label`} for={`${id}-input`}>
29
+ {placeholder}
30
+ </label>
31
+ <cxd-search-input>
32
+ <div class="form-input">
33
+ <div class="input-help">
34
+ <Icon name="search-solid" />
35
+ </div>
36
+ <input
37
+ id={`${id}-input`}
38
+ type="search"
39
+ class="ghost-input cxd-search-input"
40
+ placeholder={placeholder}
41
+ autocomplete="off"
42
+ spellcheck="false"
43
+ enterkeyhint="search"
44
+ />
45
+ </div>
46
+ </cxd-search-input>
47
+ <button
48
+ type="button"
49
+ class="close-button"
50
+ aria-label="Close"
51
+ data-cx-dismiss="dialog"
52
+ data-cx-target={`#${id}`}>×</button
53
+ >
54
+ </search>
55
+
56
+ <div class="modal-body cxd-search-body">
57
+ <cxd-search-results></cxd-search-results>
58
+ </div>
59
+ <div class="modal-footer cxd-search-footer">
60
+ <ul class="list horizontal cxd-search-shortcuts-list">
61
+ <li class="list-item">
62
+ <kbd class="cxd-search-shortcut-key font-2xsmall">⮐</kbd>
63
+ <span class="cxd-search-shortcut-label">to select</span>
64
+ </li><li class="list-item">
65
+ <kbd class="cxd-search-shortcut-key font-small">↑</kbd>
66
+ <kbd class="cxd-search-shortcut-key font-small">↓</kbd>
67
+ <span class="cxd-search-shortcut-label">to navigate</span>
68
+ </li><li class="list-item">
69
+ <kbd class="cxd-search-shortcut-key font-xsmall">ESC</kbd>
70
+ <span class="cxd-search-shortcut-label">to close</span>
71
+ </li>
72
+ </ul>
73
+ </div>
74
+ </dialog>
@@ -0,0 +1,41 @@
1
+ ---
2
+ /**
3
+ * Search Trigger
4
+ *
5
+ * Button that opens the `SearchDialog`. Displays a platform-aware keyboard
6
+ * shortcut hint (⌘K on Mac, Ctrl K elsewhere) that `search.js` fills in after
7
+ * detecting the platform — rendered hidden until then so non-JS visitors never
8
+ * see a misleading hint.
9
+ */
10
+
11
+ import Icon from '../../components/shortcodes/Icon.astro'
12
+
13
+ interface Props {
14
+ /** Additional CSS classes to apply to the trigger button. */
15
+ class?: string
16
+ /** Accessible label and visible placeholder text inside the trigger. */
17
+ placeholder?: string
18
+ /** CSS selector for the dialog to open. Must match `SearchDialog`'s `id`. */
19
+ target?: string
20
+ }
21
+
22
+ const { class: className, placeholder = 'Search docs…', target = '#cxdSearchDialog' } = Astro.props
23
+ ---
24
+
25
+ <button
26
+ type="button"
27
+ class:list={['cxd-search-trigger', className]}
28
+ data-cx-toggle="dialog"
29
+ data-cx-target={target}
30
+ aria-haspopup="dialog"
31
+ aria-label={placeholder}
32
+ >
33
+ <Icon name="search-solid" size={16} />
34
+ <span class="cxd-search-trigger-label">{placeholder}</span>
35
+ {
36
+ /* Shortcut hint is empty + hidden until search.js detects the platform and
37
+ fills in either ⌘K (Mac) or Ctrl K (other). Without JS the shortcut
38
+ itself doesn't work, so showing nothing is more honest than guessing. */
39
+ }
40
+ <span class="cxd-search-trigger-shortcut" hidden></span>
41
+ </button>
@@ -16,7 +16,7 @@ const { layout } = Astro.props
16
16
  ---
17
17
 
18
18
  <div class="skippy visually-hidden-focusable overflow-hidden">
19
- <div class="container-xlarge">
19
+ <div class="container xlarge">
20
20
  <a class="d-inline-flex p-xsmall m-xsmall" href="#content">Skip to main content</a>
21
21
  {
22
22
  layout === 'docs' && (
@@ -0,0 +1,91 @@
1
+ import { codeToHtml, type ShikiTransformer } from 'shiki'
2
+ import { DOCS_SHIKI_THEMES } from './markdown'
3
+
4
+ /**
5
+ * Shiki transformer that renames the `shiki` CSS class on `<pre>` elements to
6
+ * `astro-code` so that Astro's built-in syntax highlighting styles apply.
7
+ */
8
+ const classTransformer: ShikiTransformer = {
9
+ name: 'class-name-transformer',
10
+ pre(node) {
11
+ if (typeof node.properties.class === 'string') {
12
+ node.properties.class = node.properties.class.replace(/shiki/g, 'astro-code')
13
+ }
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Shiki transformer that wraps non-comment lines in a `<span class="line">`
19
+ * element. Applied only to shell languages (bash, sh, powershell) where line
20
+ * wrapping is needed for the copy-button to correctly extract individual commands.
21
+ */
22
+ const lineWrapperTransformer: ShikiTransformer = {
23
+ name: 'line-wrapper',
24
+ line(node) {
25
+ const hasOnlyComments = node.children.every(
26
+ (child) =>
27
+ child.type === 'element' &&
28
+ child.properties?.class &&
29
+ Array.isArray(child.properties.class) &&
30
+ child.properties.class.some((cls) => typeof cls === 'string' && cls.includes('comment'))
31
+ )
32
+
33
+ if (!hasOnlyComments) {
34
+ node.properties = node.properties || {}
35
+ const existing = node.properties.class ?? ''
36
+ const classes = typeof existing === 'string' ? existing.split(' ') : []
37
+ if (!classes.includes('line')) classes.push('line')
38
+ node.properties.class = classes.join(' ').trim()
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Replaces any remaining `shiki` / `shiki-themes` class strings in the
45
+ * serialized HTML that the transformer may have missed (e.g. in inline styles
46
+ * converted by `transformerStyleToClass`).
47
+ */
48
+ function replaceShikiClasses(html: string): string {
49
+ return html
50
+ .replace(/class=(["'])shiki(\s+)/g, 'class=$1astro-code$2')
51
+ .replace(/class=(["'])shiki(["'])/g, 'class=$1astro-code$2')
52
+ .replace(/shiki-themes/g, 'astro-code-themes')
53
+ }
54
+
55
+ /**
56
+ * Highlights a code string using Shiki with the GitHub light/dark dual theme.
57
+ *
58
+ * Inline styles are converted to CSS classes via `transformerStyleToClass` so
59
+ * that a single `<style>` block can be emitted per page rather than repeating
60
+ * styles on every token. The returned `css` string must be injected into the
61
+ * page (e.g. inside a `<style>` tag) for the highlighted output to render correctly.
62
+ *
63
+ * For shell languages (`bash`, `sh`, `powershell`) each non-comment line is
64
+ * additionally wrapped in `<span class="line">` to support per-line copy.
65
+ *
66
+ * @param code - Source code to highlight.
67
+ * @param lang - Shiki language identifier (e.g. `"html"`, `"ts"`, `"bash"`).
68
+ * @param extraTransformers - Additional Shiki transformers to apply before the built-ins.
69
+ * @returns An object with `html` (the highlighted markup) and `css` (the extracted styles).
70
+ */
71
+ export async function highlightCode(
72
+ code: string,
73
+ lang: string,
74
+ extraTransformers: ShikiTransformer[] = []
75
+ ): Promise<{ html: string }> {
76
+ const shouldWrapLines = ['bash', 'sh', 'powershell'].includes(lang)
77
+
78
+ const transformers: ShikiTransformer[] = [...extraTransformers, classTransformer]
79
+ if (shouldWrapLines) {
80
+ transformers.push(lineWrapperTransformer)
81
+ }
82
+
83
+ const highlighted = await codeToHtml(code, {
84
+ lang,
85
+ themes: DOCS_SHIKI_THEMES,
86
+ defaultColor: false,
87
+ transformers
88
+ })
89
+
90
+ return { html: replaceShikiClasses(highlighted) }
91
+ }