@chassis-ui/docs 0.2.0 → 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 (74) 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 +3 -3
  6. package/src/components/TableOfContents.astro +8 -7
  7. package/src/components/ThemeToggler.astro +15 -36
  8. package/src/components/shortcodes/Blockquote.astro +1 -1
  9. package/src/components/shortcodes/CSSOnly.astro +6 -2
  10. package/src/components/shortcodes/Callout.astro +6 -2
  11. package/src/components/shortcodes/Code.astro +289 -0
  12. package/src/components/shortcodes/DeprecatedIn.astro +5 -2
  13. package/src/components/shortcodes/Example.astro +132 -0
  14. package/src/components/shortcodes/Icon.astro +57 -0
  15. package/src/components/shortcodes/InFigma.astro +7 -2
  16. package/src/components/shortcodes/JsDocs.astro +64 -0
  17. package/src/components/shortcodes/Placeholder.astro +28 -0
  18. package/src/components/shortcodes/ResizableExample.astro +126 -0
  19. package/src/components/shortcodes/ScssDocs.astro +108 -0
  20. package/src/components/shortcodes/ScssDocsSimple.astro +66 -0
  21. package/src/js/color-modes.js +3 -3
  22. package/src/js/example-mode.js +75 -0
  23. package/src/js/search.js +501 -39
  24. package/src/js/theme-color.js +1 -1
  25. package/src/layouts/DocsLayout.astro +11 -14
  26. package/src/layouts/RedirectLayout.astro +1 -0
  27. package/src/layouts/footer/Scripts.astro +14 -9
  28. package/src/layouts/head/Analytics.astro +12 -6
  29. package/src/layouts/head/Favicons.astro +0 -2
  30. package/src/layouts/head/Head.astro +2 -7
  31. package/src/layouts/head/Stylesheet.astro +18 -11
  32. package/src/layouts/header/Header.astro +2 -5
  33. package/src/layouts/header/Navigation.astro +31 -33
  34. package/src/layouts/header/SearchDialog.astro +74 -0
  35. package/src/layouts/header/SearchTrigger.astro +41 -0
  36. package/src/libs/highlight.ts +91 -0
  37. package/src/libs/icon.ts +75 -0
  38. package/src/libs/image.ts +12 -1
  39. package/src/libs/layout.ts +18 -0
  40. package/src/libs/markdown.ts +128 -0
  41. package/src/libs/placeholder.ts +250 -0
  42. package/src/libs/rehype.ts +0 -17
  43. package/src/libs/shortcodes.ts +129 -0
  44. package/src/libs/site.ts +95 -0
  45. package/src/libs/toc.ts +40 -6
  46. package/src/libs/utils.ts +34 -82
  47. package/src/libs/vite.ts +45 -0
  48. package/src/scss/_anchor.scss +20 -16
  49. package/src/scss/_callouts.scss +29 -28
  50. package/src/scss/_content.scss +72 -103
  51. package/src/scss/_footer.scss +18 -19
  52. package/src/scss/_layout.scss +56 -63
  53. package/src/scss/_navbar.scss +19 -123
  54. package/src/scss/_placeholder-img.scss +11 -7
  55. package/src/scss/_scrolling.scss +14 -12
  56. package/src/scss/_search.scss +418 -0
  57. package/src/scss/_shiki.scss +120 -0
  58. package/src/scss/_sidebar.scss +137 -132
  59. package/src/scss/_skippy.scss +6 -4
  60. package/src/scss/_snippets.scss +181 -0
  61. package/src/scss/_toc.scss +97 -93
  62. package/src/scss/_vars.scss +89 -0
  63. package/src/scss/main.scss +75 -80
  64. package/src/js/icon-loader.js +0 -229
  65. package/src/libs/chassis.ts +0 -38
  66. package/src/scss/_brand.scss +0 -59
  67. package/src/scss/_buttons.scss +0 -33
  68. package/src/scss/_clipboard-js.scss +0 -63
  69. package/src/scss/_code.scss +0 -116
  70. package/src/scss/_colors.scss +0 -140
  71. package/src/scss/_docsearch.scss +0 -212
  72. package/src/scss/_settings.scss +0 -29
  73. package/src/scss/_syntax.scss +0 -160
  74. package/src/scss/_variables.scss +0 -73
@@ -0,0 +1,126 @@
1
+ ---
2
+ import { replacePlaceholdersInHtml } from '../../libs/placeholder'
3
+ import { replaceIconsInHtml } from '../../libs/icon'
4
+ import Code from './Code.astro'
5
+ import Icon from './Icon.astro'
6
+
7
+ interface Props {
8
+ /**
9
+ * Defines if extra JS snippet should be added to StackBlitz or not.
10
+ * @default false
11
+ */
12
+ addStackblitzJs?: boolean
13
+ /**
14
+ * The example code.
15
+ */
16
+ code: string | string[]
17
+ /**
18
+ * Custom markup to display in the source code section, different from the preview code.
19
+ * If provided, this will be used instead of the `code` prop for the source display.
20
+ */
21
+ customMarkup?: string | string[]
22
+ /**
23
+ * The CSS class(es) to be added to the resizable container.
24
+ */
25
+ class?: string
26
+ /**
27
+ * Alias for class prop.
28
+ */
29
+ containerClass?: string
30
+ /**
31
+ * The preview wrapping `div` element ID.
32
+ */
33
+ id?: string
34
+ /**
35
+ * Initial width of the resizable container.
36
+ * @default '100%'
37
+ */
38
+ initialWidth?: string
39
+ /**
40
+ * Minimum width of the resizable container.
41
+ * @default '200px'
42
+ */
43
+ minWidth?: string
44
+ /**
45
+ * Whether to show the code markup below.
46
+ * @default true
47
+ */
48
+ showMarkup?: boolean
49
+ /**
50
+ * If true, displays line numbers in the code block.
51
+ * @default false
52
+ */
53
+ lineNumbers?: boolean
54
+ }
55
+
56
+ const {
57
+ code,
58
+ class: className,
59
+ customMarkup,
60
+ containerClass,
61
+ id,
62
+ initialWidth = '100%',
63
+ minWidth = '200px',
64
+ showMarkup = true,
65
+ lineNumbers = false
66
+ } = Astro.props
67
+
68
+ let markup = Array.isArray(code) ? code.join('\n') : code
69
+ markup = replacePlaceholdersInHtml(markup)
70
+ markup = replaceIconsInHtml(markup)
71
+
72
+ // The resizable container needs `overflow: hidden` for the CSS `resize` handle
73
+ // to work, which clips popovers like menus/dropdowns when they open. Render them
74
+ // with a fixed positioning strategy in the live preview only so they can spill
75
+ // outside the container. The displayed markup below is left untouched.
76
+ const previewMarkup = markup.replace(
77
+ /data-cx-toggle="(menu|dropdown)"(?![^>]*\bdata-cx-strategy=)/g,
78
+ 'data-cx-toggle="$1" data-cx-strategy="fixed"'
79
+ )
80
+
81
+ let sourceMarkup = customMarkup
82
+ ? Array.isArray(customMarkup)
83
+ ? customMarkup.join('\n')
84
+ : customMarkup
85
+ : markup
86
+
87
+ const simplifiedMarkup = sourceMarkup
88
+ .replace(
89
+ /<svg.*class="cxd-placeholder-img(?:-large)?(?: *?cxd-placeholder-img-large)? ?(.*?)".*?<\/svg>/g,
90
+ (_match, classes) => `<img src="..."${classes ? ` class="${classes}"` : ''} alt="...">`
91
+ )
92
+ .replace(
93
+ /<img.*class="cxd-placeholder-img(?:-large)?(?: *?cxd-placeholder-img-large)? ?(.*?)".*?>/g,
94
+ (_match, classes) => `<img src="..."${classes ? ` class="${classes}"` : ''} alt="...">`
95
+ )
96
+ .replace(/(<svg class="icon\b[^"]*")\s+width="\d+"\s+height="\d+"/g, '$1')
97
+ .replace(/href="\/static\/icons\/chassis-icons\.svg#/g, 'href="#')
98
+ ---
99
+
100
+ <div class="cxd-example-snippet cxd-code-snippet" data-pagefind-ignore>
101
+ <div id={id} class:list={['cxd-example m-0 border-0 context', className]}>
102
+ <div class="cxd-mode">
103
+ <button
104
+ type="button"
105
+ class="button-mode"
106
+ title="Toggle color mode"
107
+ aria-label="Toggle color mode"
108
+ data-cx-toggle="tooltip"
109
+ onclick="toggleTheme(this)"
110
+ >
111
+ <Icon name="swatchbook-solid" />
112
+ </button>
113
+ </div>
114
+ <div
115
+ class:list={['cxd-resizable-container', containerClass]}
116
+ style={`width: ${initialWidth}; min-width: ${minWidth};`}
117
+ >
118
+ <Fragment set:html={previewMarkup} />
119
+ </div>
120
+ </div>
121
+ {
122
+ showMarkup && (
123
+ <Code code={simplifiedMarkup} lang="html" nestedInExample={true} lineNumbers={lineNumbers} />
124
+ )
125
+ }
126
+ </div>
@@ -0,0 +1,108 @@
1
+ ---
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import * as sass from 'sass'
5
+ import Code from './Code.astro'
6
+
7
+ // Prints everything between `// scss-docs-start "name"` and `// scss-docs-end "name"`
8
+ // comments in the docs. When compile={true}, the snippet is compiled via the Sass
9
+ // JS API so @include mixin output is shown as real CSS. Default is raw SCSS.
10
+
11
+ interface Props {
12
+ /**
13
+ * Reference name used to find the content to display within the content of the `file` prop.
14
+ */
15
+ name: string
16
+ /**
17
+ * File path that contains the content to display relative to the root of the repository.
18
+ */
19
+ file: string
20
+ /**
21
+ * When true, compiles the SCSS snippet via the Sass JS API and displays
22
+ * the resulting CSS output (useful when the block contains @include mixin calls).
23
+ * Defaults to false — raw SCSS is shown as-is.
24
+ */
25
+ compile?: boolean
26
+ }
27
+
28
+ const { name, file, compile = false } = Astro.props
29
+
30
+ if (!name || !file) {
31
+ throw new Error(
32
+ `Missing required parameter(s) for the '<ScssDocs />' component, expected both 'name' and 'file' but got 'name: ${name}' and 'file: ${file}'.`
33
+ )
34
+ }
35
+
36
+ let content: string
37
+ let lang: string = 'scss'
38
+
39
+ try {
40
+ const fileContent = fs.readFileSync(file, 'utf8')
41
+
42
+ const matches = fileContent.match(
43
+ new RegExp(`// scss-docs-start ${name}\n((?:.|\n)*)// scss-docs-end ${name}`, 'm')
44
+ )
45
+
46
+ if (!matches || !matches[1]) {
47
+ throw new Error(
48
+ `Failed to find the content named '${name}', make sure that '// scss-docs-start ${name}' and '// scss-docs-end ${name}' are defined.`
49
+ )
50
+ }
51
+
52
+ const rawContent = matches[1]
53
+
54
+ if (compile) {
55
+ // Derive the SCSS load path from the file argument (e.g. "scss/_foo.scss" → "scss/")
56
+ const scssLoadPath = path.resolve(process.cwd(), file.split('/')[0])
57
+
58
+ const scssInput = [
59
+ '@use "config" as *;',
60
+ '@use "functions" as *;',
61
+ '@use "tokens" as *;',
62
+ '@use "maps" as *;',
63
+ '@use "mixins" as *;',
64
+ '',
65
+ '.__cxd_docs_sentinel__ {',
66
+ rawContent,
67
+ '}'
68
+ ].join('\n')
69
+
70
+ const result = await sass.compileStringAsync(scssInput, {
71
+ loadPaths: [scssLoadPath, path.resolve(process.cwd(), 'scss/vendor')],
72
+ // Match the same deprecation silencing used in astro.config.ts / vite scss options
73
+ silenceDeprecations: ['import', 'global-builtin', 'color-functions', 'if-function'] as any
74
+ })
75
+
76
+ // Extract the body between the sentinel braces.
77
+ // Greedy [\s\S]* ensures we capture multi-property output including mixin expansions.
78
+ const sentinelMatch = result.css.match(/\.__cxd_docs_sentinel__\s*\{([\s\S]*)\}/)
79
+ content = sentinelMatch ? sentinelMatch[1] : result.css
80
+ // Apply the same cx- prefix to CSS custom properties as the build's css:prefix step does.
81
+ content = content.replace(/\.__cxd_docs_sentinel__\s*/g, '')
82
+ content = content.replace(/--(?!cx-|cxd-|pf-|shell-|shiki-)([a-zA-Z][a-zA-Z0-9-]*)/g, '--cx-$1')
83
+ } else {
84
+ // Raw SCSS mode — Sass variable declarations, shown as-is.
85
+ content = rawContent.replaceAll(' !default', '')
86
+ }
87
+
88
+ // Normalise indentation by removing the common leading-space prefix.
89
+ const lines = content.split('\n')
90
+ const spaceCounts = lines
91
+ .filter((line) => line.trim().length > 0)
92
+ .map((line) => (line.match(/^ */) || [''])[0].length)
93
+ const minSpaces = spaceCounts.length ? Math.min(...spaceCounts) : 0
94
+ content = lines
95
+ .map((line) => line.slice(minSpaces))
96
+ .join('\n')
97
+ .trim()
98
+ } catch (error) {
99
+ throw new Error(
100
+ `Failed to find the content to render in the '<ScssDocs />' component at '${file}'.`,
101
+ {
102
+ cause: error
103
+ }
104
+ )
105
+ }
106
+ ---
107
+
108
+ <Code containerClass="cxd-example-snippet cxd-file-ref" code={content} lang={lang} file={file} />
@@ -0,0 +1,66 @@
1
+ ---
2
+ import fs from 'node:fs'
3
+ import Code from './Code.astro'
4
+
5
+ // Prints everything between `// scss-docs-start "name"` and `// scss-docs-end "name"`
6
+ // comments in the docs.
7
+
8
+ interface Props {
9
+ /**
10
+ * Reference name used to find the content to display within the content of the `file` prop.
11
+ */
12
+ name: string
13
+ /**
14
+ * File path that contains the content to display relative to the root of the repository.
15
+ */
16
+ file: string
17
+ }
18
+
19
+ const { name, file } = Astro.props
20
+
21
+ let lang: string = 'scss'
22
+
23
+ if (!name || !file) {
24
+ throw new Error(
25
+ `Missing required parameter(s) for the '<ScssDocs />' component, expected both 'name' and 'file' but got 'name: ${name}' and 'file: ${file}'.`
26
+ )
27
+ }
28
+
29
+ let content: string
30
+
31
+ try {
32
+ const fileContent = fs.readFileSync(file, 'utf8')
33
+
34
+ const matches = fileContent.match(
35
+ new RegExp(`// scss-docs-start ${name}\n((?:.|\n)*)// scss-docs-end ${name}`, 'm')
36
+ )
37
+
38
+ if (!matches || !matches[1]) {
39
+ throw new Error(
40
+ `Failed to find the content named '${name}', make sure that '// scss-docs-start ${name}' and '// scss-docs-end ${name}' are defined.`
41
+ )
42
+ }
43
+
44
+ content = matches[1].replaceAll(' !default', '')
45
+
46
+ // Fix the identation by removing extra spaces at the beginning of each line
47
+ const lines = content.split('\n')
48
+ const spaceCounts = lines
49
+ .filter((line) => line.trim().length > 0)
50
+ .map((line) => (line.match(/^ */) || [''])[0].length)
51
+ const minSpaces = spaceCounts.length ? Math.min(...spaceCounts) : 0
52
+ content = lines
53
+ .map((line) => line.slice(minSpaces))
54
+ .join('\n')
55
+ .trimEnd()
56
+ } catch (error) {
57
+ throw new Error(
58
+ `Failed to find the content to render in the '<ScssDocs />' component at '${file}'.`,
59
+ {
60
+ cause: error
61
+ }
62
+ )
63
+ }
64
+ ---
65
+
66
+ <Code containerClass="cxd-example-snippet cxd-file-ref" code={content} lang={lang} file={file} />
@@ -7,7 +7,7 @@
7
7
  ;(() => {
8
8
  'use strict'
9
9
 
10
- const getStoredTheme = () => localStorage.getItem('theme')
10
+ const getStoredTheme = () => localStorage.getItem('theme') || 'auto'
11
11
  const setStoredTheme = (theme) => localStorage.setItem('theme', theme)
12
12
 
13
13
  const getPreferredTheme = () => {
@@ -45,11 +45,11 @@
45
45
  const svgOfActiveButton = buttonToActive.querySelector('svg use').getAttribute('href')
46
46
 
47
47
  document.querySelectorAll('[data-cx-theme-value]').forEach((element) => {
48
- element.classList.remove('active')
48
+ element.classList.remove('selected')
49
49
  element.setAttribute('aria-pressed', 'false')
50
50
  })
51
51
 
52
- buttonToActive.classList.add('active')
52
+ buttonToActive.classList.add('selected')
53
53
  buttonToActive.setAttribute('aria-pressed', 'true')
54
54
  activeThemeIcon.setAttribute('href', svgOfActiveButton)
55
55
  const themeSwitcherLabel = `${themeSwitcherText.textContent} (${buttonToActive.dataset.cxThemeValue})`
@@ -0,0 +1,75 @@
1
+ import { Tooltip } from '@chassis-ui/css'
2
+
3
+ // Helper to detect the user's preferred theme
4
+ function getPreferredTheme() {
5
+ // First check localStorage
6
+ const storedTheme = localStorage.getItem('theme')
7
+ if (storedTheme && storedTheme !== 'auto') {
8
+ return storedTheme
9
+ }
10
+
11
+ // Then check system preference
12
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
13
+ }
14
+
15
+ // Direct theme toggle function that's globally accessible
16
+ function toggleTheme(button) {
17
+ const example = button.closest('.cxd-example-snippet').querySelector('.cxd-example')
18
+ if (!example) return
19
+
20
+ // Get current theme - try from element, then use detection logic if not set
21
+ let currentTheme = example.getAttribute('data-cx-theme')
22
+ if (!currentTheme) {
23
+ // If element doesn't have theme set, use preferred theme
24
+ currentTheme = getPreferredTheme()
25
+ }
26
+
27
+ // Toggle to the opposite
28
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
29
+
30
+ // Get system preferred theme
31
+ const preferredTheme = getPreferredTheme()
32
+
33
+ // If the new theme matches the preferred system theme, remove the attribute
34
+ // to let it naturally follow system preference
35
+ if (newTheme === preferredTheme) {
36
+ example.removeAttribute('data-cx-theme')
37
+ } else {
38
+ // Otherwise, explicitly set the theme
39
+ example.setAttribute('data-cx-theme', newTheme)
40
+ }
41
+
42
+ // Update the icon to match the theme
43
+ const iconUse = button.querySelector('use')
44
+ if (iconUse) {
45
+ const iconHref = newTheme === 'dark' ? 'sun-solid' : 'moon-solid'
46
+ iconUse.setAttribute('href', `/static/icons/chassis-icons.svg#${iconHref}`)
47
+ }
48
+ }
49
+
50
+ // Expose globally for inline onclick="toggleTheme(this)" handlers in Example.astro
51
+ window.toggleTheme = toggleTheme
52
+
53
+ // Initialize theme icons for all buttons based on current theme
54
+ document.addEventListener('DOMContentLoaded', () => {
55
+ const preferredTheme = getPreferredTheme()
56
+
57
+ document.querySelectorAll('.button-mode').forEach((button) => {
58
+ // Initialize tooltip
59
+ Tooltip.getOrCreateInstance(button)
60
+
61
+ // Set initial icon based on theme
62
+ const iconUse = button.querySelector('use')
63
+ if (iconUse) {
64
+ const example = button.closest('.cxd-example-snippet')?.querySelector('.cxd-example')
65
+ const currentTheme = example?.getAttribute('data-cx-theme') || preferredTheme
66
+ const iconHref = currentTheme === 'dark' ? 'sun-solid' : 'moon-solid'
67
+ iconUse.setAttribute('href', `/static/icons/chassis-icons.svg#${iconHref}`)
68
+ }
69
+
70
+ // Add event listener if not already set through onclick
71
+ if (!button.hasAttribute('onclick')) {
72
+ button.addEventListener('click', () => toggleTheme(button))
73
+ }
74
+ })
75
+ })