diff --git a/.gitignore b/.gitignore index c95b1dc..ca48d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ __screenshots__ .node-modules-inspector .astro .rolldown + +# Generated type docs +docs/src/data/types/ diff --git "a/.specstory/history/2026-02-21_14-57Z-\347\274\226\345\206\231\351\246\226\351\241\265\346\226\207\346\241\243\350\257\267\346\261\202.md" "b/.specstory/history/2026-02-21_14-57Z-\347\274\226\345\206\231\351\246\226\351\241\265\346\226\207\346\241\243\350\257\267\346\261\202.md" new file mode 100644 index 0000000..1124d60 --- /dev/null +++ "b/.specstory/history/2026-02-21_14-57Z-\347\274\226\345\206\231\351\246\226\351\241\265\346\226\207\346\241\243\350\257\267\346\261\202.md" @@ -0,0 +1,323 @@ + + + + +# 编写首页文档请求 (2026-02-21 14:57Z) + +_**User (2026-02-21 14:57Z)**_ + +编写首页文档请求 + +--- + +_**Agent (model GitHub Copilot)**_ + +Performed 304 file operations: + +Created file: `Hero.astro` +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Edited `Hero.astro` (1 edit) +Created file: `Features.astro` +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Edited `Features.astro` (1 edit) +Created file: `Components.astro` +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Edited `Components.astro` (1 edit) +Created file: `Steps.astro` +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Edited `Steps.astro` (1 edit) +Created file: `CTA.astro` +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) +Edited `CTA.astro` (1 edit) + +--- + diff --git "a/.specstory/history/2026-02-24_03-25Z-\346\236\204\345\273\272\346\226\207\346\241\243\351\241\265\351\235\242\347\232\204\351\243\216\346\240\274\346\214\207\345\257\274.md" "b/.specstory/history/2026-02-24_03-25Z-\346\236\204\345\273\272\346\226\207\346\241\243\351\241\265\351\235\242\347\232\204\351\243\216\346\240\274\346\214\207\345\257\274.md" new file mode 100644 index 0000000..08df5cc --- /dev/null +++ "b/.specstory/history/2026-02-24_03-25Z-\346\236\204\345\273\272\346\226\207\346\241\243\351\241\265\351\235\242\347\232\204\351\243\216\346\240\274\346\214\207\345\257\274.md" @@ -0,0 +1,18 @@ + + + + +# 构建文档页面的风格指导 (2026-02-24 03:25Z) + +_**User (2026-02-24 03:25Z)**_ + +那么接下来你需要怎么构建我们的文档页面,你看看我们在 docs 中的 docs/src/components/landing 的风格,然后帮我构建一下,我们的文档页面 + +--- + +_**Agent (model copilot/claude-opus-4.6)**_ + + + +--- + diff --git "a/.specstory/history/2026-02-24_06-54Z-\344\274\230\345\214\226\347\273\204\344\273\266\346\226\207\346\241\243\344\270\273\351\242\230\344\270\216\351\243\216\346\240\274.md" "b/.specstory/history/2026-02-24_06-54Z-\344\274\230\345\214\226\347\273\204\344\273\266\346\226\207\346\241\243\344\270\273\351\242\230\344\270\216\351\243\216\346\240\274.md" new file mode 100644 index 0000000..80af670 --- /dev/null +++ "b/.specstory/history/2026-02-24_06-54Z-\344\274\230\345\214\226\347\273\204\344\273\266\346\226\207\346\241\243\344\270\273\351\242\230\344\270\216\351\243\216\346\240\274.md" @@ -0,0 +1,22 @@ + + + + +# 优化组件文档主题与风格 (2026-02-24 06:54Z) + +_**User (2026-02-24 06:54Z)**_ + +优化组件文档主题与风格 + +--- + +_**Agent (model GitHub Copilot)**_ + +Performed 3 file operations: + +Edited `astro.config.ts` (1 edit) +Edited `typography.css` (1 edit) +Edited `layout.css` (1 edit) + +--- + diff --git "a/.specstory/history/2026-02-25_13-08Z-\347\273\204\344\273\266\351\242\204\350\247\210\344\270\216\344\273\243\347\240\201\345\261\225\347\244\272\345\212\237\350\203\275\345\256\236\347\216\260.md" "b/.specstory/history/2026-02-25_13-08Z-\347\273\204\344\273\266\351\242\204\350\247\210\344\270\216\344\273\243\347\240\201\345\261\225\347\244\272\345\212\237\350\203\275\345\256\236\347\216\260.md" new file mode 100644 index 0000000..29d5ac2 --- /dev/null +++ "b/.specstory/history/2026-02-25_13-08Z-\347\273\204\344\273\266\351\242\204\350\247\210\344\270\216\344\273\243\347\240\201\345\261\225\347\244\272\345\212\237\350\203\275\345\256\236\347\216\260.md" @@ -0,0 +1,21 @@ + + + + +# 组件预览与代码展示功能实现 (2026-02-25 13:08Z) + +_**User (2026-02-25 13:08Z)**_ + +组件预览与代码展示功能实现 + +--- + +_**Agent (model GitHub Copilot)**_ + +Performed 2 file operations: + +Edited `pnpm-workspace.yaml` (1 edit) +Edited `package.json` (1 edit) + +--- + diff --git "a/.specstory/history/2026-02-25_13-59Z-\345\260\206\345\216\237\347\224\237css\350\275\254\346\215\242\344\270\272unocss.md" "b/.specstory/history/2026-02-25_13-59Z-\345\260\206\345\216\237\347\224\237css\350\275\254\346\215\242\344\270\272unocss.md" new file mode 100644 index 0000000..7577757 --- /dev/null +++ "b/.specstory/history/2026-02-25_13-59Z-\345\260\206\345\216\237\347\224\237css\350\275\254\346\215\242\344\270\272unocss.md" @@ -0,0 +1,95 @@ + + + + +# 将原生CSS转换为UnoCSS (2026-02-25 13:59Z) + +_**User (2026-02-25 14:00Z)**_ + +帮我将 #file:ComponentPreview.astro 中的 原生css都修改成 unocss + +--- + +_**Agent (model copilot/claude-opus-4.6)**_ + + +
+Tool use: **copilot_readFile** • 正在读取 [](file:///Users/elonehoo/Developer/destyler-dev/ui/docs/src/components/ComponentPreview.astro#1-1),行 1 到 220 + + +读取 [](file:///Users/elonehoo/Developer/destyler-dev/ui/docs/src/components/ComponentPreview.astro#1-1),行 1 到 220 + +
+
+ +--- + + +
+Tool use: **copilot_readFile** • 正在读取 [](file:///Users/elonehoo/Developer/destyler-dev/ui/docs/src/utils/shiki.ts) + + +读取 [](file:///Users/elonehoo/Developer/destyler-dev/ui/docs/src/utils/shiki.ts) + +
+
+ +--- + + +
+Tool use: **copilot_findFiles** + +Additional data: + +```json +{ + "vscodeToolId": "copilot_findFiles", + "isHidden": false +} +``` +Result: + +```json +{ + "invocationMessage": "正在搜索与 `**/docs/uno.config.*` 匹配的文件", + "pastTenseMessage": "搜索匹配 `**/docs/uno.config.*` 的文件,1 个匹配项", + "resultDetails": [ + { + "$mid": 1, + "fsPath": "/Users/elonehoo/Developer/destyler-dev/ui/docs/uno.config.ts", + "external": "file:///Users/elonehoo/Developer/destyler-dev/ui/docs/uno.config.ts", + "path": "/Users/elonehoo/Developer/destyler-dev/ui/docs/uno.config.ts", + "scheme": "file" + } + ], + "isConfirmed": { + "type": 1 + }, + "content": "", + "textEditContent": "", + "toolCallResult": null +} +``` +Status: **completed** + + +
+
+ +--- + + + + The user wants me to convert the native CSS in ComponentPreview.astro to UnoCSS utility classes. Let me first read the current file to understand the full CSS and HTML structure, then convert the styles to UnoCSS utility classes applied directly to the HTML elements. + +Let me analyze the current CSS and map it to UnoCSS classes: + +1. Framework visibility - these use `:root[data-framework]` selectors which are conditional on parent attributes. These can't easily be converted to utility classes since they depend on a parent element's data attribute. I'll need to keep these or use a different approach. + +2. Layout styles - these can be converted to utility classes. + +Let me read the file first to get the exact content. + +--- + diff --git "a/.specstory/history/2026-03-02_08-56Z-\345\234\250\346\226\207\346\241\243\344\270\255\345\256\236\347\216\260-componentpreview-\347\232\204\346\226\271\346\263\225.md" "b/.specstory/history/2026-03-02_08-56Z-\345\234\250\346\226\207\346\241\243\344\270\255\345\256\236\347\216\260-componentpreview-\347\232\204\346\226\271\346\263\225.md" new file mode 100644 index 0000000..19f1835 --- /dev/null +++ "b/.specstory/history/2026-03-02_08-56Z-\345\234\250\346\226\207\346\241\243\344\270\255\345\256\236\347\216\260-componentpreview-\347\232\204\346\226\271\346\263\225.md" @@ -0,0 +1,21 @@ + + + + +# 在文档中实现 ComponentPreview 的方法 (2026-03-02 08:56Z) + +_**User (2026-03-02 08:56Z)**_ + +在文档中实现 ComponentPreview 的方法 + +--- + +_**Agent (model GitHub Copilot)**_ + +Performed 2 file operations: + +Edited `pnpm-workspace.yaml` (1 edit) +Edited `package.json` (1 edit) + +--- + diff --git a/docs/astro.config.ts b/docs/astro.config.ts new file mode 100644 index 0000000..8067f8e --- /dev/null +++ b/docs/astro.config.ts @@ -0,0 +1,160 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import react from '@astrojs/react' +import starlight from '@astrojs/starlight' +import vue from '@astrojs/vue' +import { defineConfig } from 'astro/config' +import UnoCSS from 'unocss/astro' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const vueSrc = path.resolve(__dirname, '../packages/vue/src') +const reactSrc = path.resolve(__dirname, '../packages/react/src') + +/** + * Vite plugin that resolves the `~/` alias based on the importing file's + * package context, so Vue source files resolve `~/` → packages/vue/src + * and React source files resolve `~/` → packages/react/src. + */ +function destylerTildeAlias(): any { + return { + name: 'destyler-tilde-alias', + async resolveId(source: string, importer: string | undefined) { + if (!source.startsWith('~/') || !importer) + return null + + let root: string | null = null + if (importer.includes('/packages/vue/')) + root = vueSrc + else if (importer.includes('/packages/react/')) + root = reactSrc + + if (!root) + return null + + const resolved = path.join(root, source.slice(2)) + return this.resolve(resolved, importer, { skipSelf: true }) + }, + } +} + +/** + * Vite plugin that gracefully handles Vue SFC compilation failures for + * example files in the packages directory. When the SFC compiler can't + * resolve cross-package types (namespace types, ~/ alias imports, etc.), + * this plugin replaces the file with a valid placeholder component so + * the build succeeds. The code-display panel still shows the real source. + */ +function tolerantPackageVueSfc() { + return { + name: 'tolerant-package-vue-sfc', + enforce: 'pre' as const, + transform(code: string, id: string) { + if (!id.endsWith('.vue') || !id.includes('/packages/') || !id.includes('/examples/')) + return null + + const scriptMatch = code.match(/]*>([\s\S]*?)<\/script>/) + if (!scriptMatch) + return null + const script = scriptMatch[1] + + // Detect patterns that cause SFC type-resolution failures outside + // the original package build context: + // 1. `extends Namespace.Type` (cross-file type extension) + // 2. `defineEmits` (unresolvable emit type) + // 3. `from '~/'` (alias unavailable in SFC resolver) + const problematic + = /extends\s+\w+\.\w+/.test(script) + || /define(?:Props|Emits)<\w+\.\w+>/.test(script) + || /from\s+['"]~\//.test(script) + + if (!problematic) + return null + + return { + code: [ + '', + '', + ].join('\n'), + map: null, + } + }, + } +} + +// https://astro.build/config +export default defineConfig({ + integrations: [ + UnoCSS({ + injectReset: true, + }), + vue(), + react(), + starlight({ + expressiveCode: { + themes: ['vitesse-dark', 'vitesse-light'], + styleOverrides: { + borderRadius: '0.75rem', + }, + }, + title: 'DESTYLER', + social: [ + { + icon: 'github', + label: 'GitHub', + href: 'https://github.com/destyler/ui', + }, + ], + sidebar: [ + { + label: 'Overview', + items: [ + { label: 'Introduction', slug: 'overview/introduction' }, + ], + }, + { + label: 'Components', + items: [ + { label: 'Aspect Ratio', slug: 'components/aspect-ratio' }, + { label: 'Avatar', slug: 'components/avatar' }, + { label: 'Breadcrumbs', slug: 'components/breadcrumbs' }, + ], + }, + ], + components: { + Header: './src/components/Header.astro', + Sidebar: './src/components/Sidebar.astro', + TableOfContents: './src/components/TableOfContents.astro', + MobileTableOfContents: './src/components/MobileTableOfContents.astro', + MobileMenuToggle: './src/components/MobileMenuToggle.astro', + Pagination: './src/components/Pagination.astro', + }, + customCss: [ + './src/styles/bootstrap.css', + ], + }), + ], + vite: { + plugins: [tolerantPackageVueSfc(), destylerTildeAlias()], + optimizeDeps: { + // Vite 8 beta / esbuild evaluates process.env.NODE_ENV during dep + // pre-bundling. Without an explicit define the condition resolves to + // "production", which causes React to export `jsxDEV = void 0` from + // its CJS production build → "jsxDEV is not a function" at runtime. + esbuildOptions: { + define: { + 'process.env.NODE_ENV': '"development"', + }, + }, + }, + server: { + fs: { + allow: ['..'], + }, + }, + }, +}) diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..789b711 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,35 @@ +{ + "name": "@destyler-ui/docs", + "private": "true", + "packageManager": "pnpm@10.28.0", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "generate:types": "tsx scripts/generate-type-docs.ts vue && tsx scripts/generate-type-docs.ts react", + "generate:types:vue": "tsx scripts/generate-type-docs.ts vue", + "generate:types:react": "tsx scripts/generate-type-docs.ts react" + }, + "dependencies": { + "@destyler/docs": "catalog:docs" + }, + "devDependencies": { + "@astrojs/react": "catalog:docs", + "@astrojs/starlight": "catalog:docs", + "@astrojs/vue": "catalog:docs", + "@iconify-json/tabler": "catalog:icons", + "@types/react": "catalog:types", + "@types/react-dom": "catalog:types", + "@unocss/astro": "catalog:styles", + "@unocss/reset": "catalog:styles", + "astro": "catalog:docs", + "react": "catalog:framework", + "react-dom": "catalog:framework", + "shiki": "catalog:docs", + "typescript": "catalog:types", + "unocss": "catalog:styles", + "vue": "catalog:framework" + } +} diff --git a/docs/public/logos b/docs/public/logos new file mode 120000 index 0000000..30b09c0 --- /dev/null +++ b/docs/public/logos @@ -0,0 +1 @@ +../../public/logos \ No newline at end of file diff --git a/docs/scripts/generate-type-docs.ts b/docs/scripts/generate-type-docs.ts new file mode 100644 index 0000000..d01aeee --- /dev/null +++ b/docs/scripts/generate-type-docs.ts @@ -0,0 +1,434 @@ +/** + * generate-type-docs.ts + * + * Extracts Props and Emits type information from the compiled dist of each + * framework package and writes JSON files consumed by the Astro docs site. + * + * Usage: + * npx tsx docs/scripts/generate-type-docs.ts vue + * npx tsx docs/scripts/generate-type-docs.ts react + * + * Prerequisites: + * The target framework package must be built first (`pnpm vue build` / `pnpm react build`) + * so that `dist/index.d.ts` (or `dist/index.d.mts`) exists. + * + * Output: + * docs/src/data/types//.types.json + */ + +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import ts from 'typescript' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootDir = path.resolve(__dirname, '../..') + +function kebabToPascal(str: string): string { + return str + .split('-') + .map(w => w.charAt(0).toUpperCase() + w.slice(1)) + .join('') +} + +// --------------------------------------------------------------------------- +// Property extraction +// --------------------------------------------------------------------------- + +interface PropInfo { + type: string + isRequired: boolean + defaultValue?: string + description?: string +} + +type PropsMap = Record + +function getSourceFileName(symbol: ts.Symbol): string | undefined { + const declarations = symbol.getDeclarations() + if (!declarations || declarations.length === 0) + return undefined + return declarations[0].getSourceFile().fileName +} + +function shouldIgnoreProperty(property: ts.Symbol): boolean { + const sourceFileName = getSourceFileName(property) + // Skip properties originating from external packages (react, vue, etc.) + // but keep properties from @destyler/* + const isExternal + = sourceFileName?.includes('node_modules') + && !sourceFileName?.includes('@destyler') + const isExcludedByName = ['children', 'ref', 'key'].includes(property.getName()) + return !!(isExternal || isExcludedByName) +} + +function extractProperties( + typeNode: ts.Node, + checker: ts.TypeChecker, +): PropsMap { + const properties: PropsMap = {} + const type = checker.getTypeAtLocation(typeNode) + + for (const property of type.getProperties()) { + if (shouldIgnoreProperty(property)) + continue + + const propertyName = property.getName() + // Skip internal/framework properties + if (propertyName.startsWith('$') || propertyName.startsWith('_')) + continue + + const propType = checker.getTypeOfSymbolAtLocation(property, typeNode) + const nonNullableType = propType.getNonNullableType() + const typeName = checker.typeToString(nonNullableType) + const isRequired = nonNullableType === propType && typeName !== 'any' + + const defaultTag = property + .getJsDocTags() + .find(tag => tag.name === 'default') + const defaultValue = defaultTag?.text?.[0]?.text + + const description = property + .getDocumentationComment(checker) + .map(c => c.text) + .join('\n') || undefined + + properties[propertyName] = { + type: typeName, + isRequired, + ...(defaultValue !== undefined ? { defaultValue } : {}), + ...(description !== undefined ? { description } : {}), + } + } + + // Sort: required first, then alphabetical + return Object.fromEntries( + Object.entries(properties) + .sort(([a], [b]) => a.localeCompare(b)) + .sort(([, a], [, b]) => (a.isRequired === b.isRequired ? 0 : a.isRequired ? -1 : 1)), + ) +} + +// --------------------------------------------------------------------------- +// Main extraction logic +// --------------------------------------------------------------------------- + +interface DataAttrInfo { + name: string + type: string +} + +interface PartTypes { + props?: PropsMap + emits?: PropsMap + dataAttr?: DataAttrInfo[] +} + +type ComponentTypesMap = Record + +function extractComponentTypes( + sourceFile: ts.SourceFile, + checker: ts.TypeChecker, + prefix: string, +): ComponentTypesMap { + const result: ComponentTypesMap = {} + // Match: (Props|Emits) + // e.g. AvatarRootProps, CollapseItemContentProps, DialogRootEmits + const regex = new RegExp(`^${prefix}(\\w+?)(Props|Emits)$`) + + for (const statement of sourceFile.statements) { + if (!ts.isInterfaceDeclaration(statement) && !ts.isTypeAliasDeclaration(statement)) + continue + + const name = statement.name.getText() + const match = name.match(regex) + if (!match) + continue + + const [, partName, kind] = match + + // Skip internal types + if (partName.endsWith('Context')) + continue // Context providers (slots API) + if (partName.endsWith('Base')) + continue // React intermediate types + if (partName === 'RootProvider') + continue // Advanced provider API + + const props = extractProperties(statement, checker) + if (Object.keys(props).length === 0) + continue + + const part = partName || 'Root' + if (!result[part]) + result[part] = {} + + if (kind === 'Props') { + result[part].props = props + } + else if (kind === 'Emits') { + result[part].emits = props + } + } + + return result +} + +// --------------------------------------------------------------------------- +// Data attribute extraction from upstream machine packages +// --------------------------------------------------------------------------- + +/** + * Reads the anatomy.ts file for a component to find the upstream @destyler/* package. + * e.g. avatar -> @destyler/image, collapse -> @destyler/collapse + */ +function resolveUpstreamPackage(componentDir: string): string | null { + const anatomyPath = path.join(componentDir, 'anatomy.ts') + if (!fs.existsSync(anatomyPath)) + return null + const content = fs.readFileSync(anatomyPath, 'utf-8') + const match = content.match(/'@destyler\/([^']+)'/) || content.match(/"@destyler\/([^"]+)"/) + return match ? match[1] : null +} + +/** + * Parses the compiled machine JS (dist/index.mjs) to extract data-* attributes + * for each part from the get{Part}Props methods. + */ +function extractDataAttributes( + pkgDir: string, + upstreamPkg: string, +): Record { + const mjsPath = path.join(pkgDir, 'node_modules', '@destyler', upstreamPkg, 'dist', 'index.mjs') + if (!fs.existsSync(mjsPath)) + return {} + + const code = fs.readFileSync(mjsPath, 'utf-8') + + // Find all get{Part}Props methods + const methodRegex = /get(\w+)Props\b[^{]*\{/g + let m: RegExpExecArray | null = methodRegex.exec(code) + const positions: { name: string, start: number }[] = [] + while (m !== null) { + positions.push({ name: m[1], start: m.index }) + m = methodRegex.exec(code) + } + + const result: Record = {} + + for (const pos of positions) { + // Find the balanced closing brace to extract the method body + let braceCount = 0 + let inMethod = false + let end = pos.start + for (let j = pos.start; j < code.length; j++) { + if (code[j] === '{') { + braceCount++ + inMethod = true + } + if (code[j] === '}') { + braceCount-- + } + if (inMethod && braceCount === 0) { + end = j + break + } + } + const body = code.substring(pos.start, end + 1) + + const attrs: DataAttrInfo[] = [] + + // Check for ...parts.xxx.attrs spread (provides data-scope and data-part) + const partsMatch = body.match(/\.\.\.\s*parts\.(\w+)\.attrs/) + if (partsMatch) { + const partKey = partsMatch[1] + const partName = partKey.replace(/([A-Z])/g, '-$1').toLowerCase() + attrs.push( + { name: 'data-scope', type: `"${upstreamPkg}"` }, + { name: 'data-part', type: `"${partName}"` }, + ) + } + + // Extract explicit data-* attributes + const dataAttrRegex = /"(data-[a-z-]+)":\s*([^\n,}]+)/g + let attrMatch: RegExpExecArray | null = dataAttrRegex.exec(body) + while (attrMatch !== null) { + const attrName = attrMatch[1] + if (attrName === 'data-scope' || attrName === 'data-part') { + attrMatch = dataAttrRegex.exec(body) + continue + } + + let value = attrMatch[2].trim() + value = normalizeDataAttrValue(value) + + attrs.push({ name: attrName, type: value }) + attrMatch = dataAttrRegex.exec(body) + } + + if (attrs.length > 0) { + result[pos.name] = attrs + } + } + + return result +} + +/** + * Convert raw JS expressions into human-readable type descriptions. + */ +function normalizeDataAttrValue(raw: string): string { + // dataAttr(...) → boolean presence attribute + if (raw.startsWith('dataAttr(')) + return '"" | undefined' + + // Ternary with string literals: x ? "a" : "b" + if (raw.includes('?')) { + const strings = [...raw.matchAll(/"([^"]+)"/g)].map(s => s[1]) + if (strings.length > 0) { + return strings.map(s => `"${s}"`).join(' | ') + } + } + + // state.context.orientation → known enum + if (raw.includes('orientation')) + return '"horizontal" | "vertical"' + if (raw.includes('placement')) + return 'Placement' + if (raw.includes('dir')) + return '"ltr" | "rtl"' + + // Fallback: show as string + return 'string' +} + +/** + * Map from anatomy part key (camelCase like "itemTrigger") to + * component part name (PascalCase like "ItemTrigger"). + */ +function anatomyKeyToPartName(key: string): string { + return key.charAt(0).toUpperCase() + key.slice(1) +} + +function getDistFilePath(framework: string): string { + const pkgDir = path.join(rootDir, 'packages', framework) + // Vue uses index.d.ts, React uses index.d.mts + const candidates = ['dist/index.d.ts', 'dist/index.d.mts'] + for (const candidate of candidates) { + const fullPath = path.join(pkgDir, candidate) + if (fs.existsSync(fullPath)) + return fullPath + } + throw new Error( + `No declaration file found for ${framework}. Did you run \`pnpm ${framework} build\` first?`, + ) +} + +function getComponentDirs(framework: string): { name: string, dir: string }[] { + const pkgDir = path.join(rootDir, 'packages', framework) + const dirs: { name: string, dir: string }[] = [] + + for (const subDir of ['src/components', 'src/providers']) { + const fullDir = path.join(pkgDir, subDir) + if (!fs.existsSync(fullDir)) + continue + + for (const entry of fs.readdirSync(fullDir, { withFileTypes: true })) { + if (entry.isDirectory()) { + dirs.push({ name: entry.name, dir: path.join(fullDir, entry.name) }) + } + } + } + + return dirs +} + +async function extractTypesForFramework(framework: string) { + const distFile = getDistFilePath(framework) + const pkgDir = path.join(rootDir, 'packages', framework) + const outDir = path.join(rootDir, 'docs', 'src', 'data', 'types', framework) + + console.log(`Reading declarations from ${path.relative(rootDir, distFile)}`) + + // Create a TypeScript program to parse the dist declaration file + const configPath = path.join(pkgDir, 'tsconfig.json') + const configFile = ts.readConfigFile(configPath, ts.sys.readFile) + const { options } = ts.parseJsonConfigFileContent(configFile.config, ts.sys, pkgDir) + + const program = ts.createProgram([distFile], { + ...options, + noEmit: true, + declaration: false, + emitDeclarationOnly: false, + }) + + const sourceFile = program.getSourceFile(distFile) + if (!sourceFile) { + throw new Error(`Could not parse ${distFile}`) + } + + const checker = program.getTypeChecker() + const componentDirs = getComponentDirs(framework) + + let count = 0 + fs.mkdirSync(outDir, { recursive: true }) + + for (const comp of componentDirs) { + const prefix = kebabToPascal(comp.name) + const types = extractComponentTypes(sourceFile, checker, prefix) + + if (Object.keys(types).length === 0) + continue + + // Extract data attributes from upstream machine package + const upstreamPkg = resolveUpstreamPackage(comp.dir) + if (upstreamPkg) { + const dataAttrs = extractDataAttributes(pkgDir, upstreamPkg) + for (const [anatomyKey, attrs] of Object.entries(dataAttrs)) { + const partName = anatomyKeyToPartName(anatomyKey) + if (types[partName]) { + types[partName].dataAttr = attrs + } + } + } + + const outPath = path.join(outDir, `${comp.name}.types.json`) + fs.writeFileSync(outPath, `${JSON.stringify(types, null, 2)}\n`) + count++ + console.log(` ✓ ${comp.name} (${Object.keys(types).length} parts)`) + } + + console.log(`\nGenerated ${count} type files → ${path.relative(rootDir, outDir)}/`) +} + +// --------------------------------------------------------------------------- +// CLI entry +// --------------------------------------------------------------------------- + +async function main() { + const framework = process.argv[2] + if (!framework) { + console.error('Usage: npx tsx docs/scripts/generate-type-docs.ts ') + console.error(' framework: vue | react') + process.exit(1) + } + + const pkgDir = path.join(rootDir, 'packages', framework) + if (!fs.existsSync(pkgDir)) { + console.error(`Package not found: packages/${framework}`) + process.exit(1) + } + + console.log(`Generating type docs for ${framework}...\n`) + await extractTypesForFramework(framework) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/docs/src/components/ComponentPreview.astro b/docs/src/components/ComponentPreview.astro new file mode 100644 index 0000000..61998d7 --- /dev/null +++ b/docs/src/components/ComponentPreview.astro @@ -0,0 +1,284 @@ +--- +/** + * ComponentPreview — renders a live component example with a "Show Code" toggle. + * + * Usage in MDX: + * import ComponentPreview from '../../../components/ComponentPreview.astro'; + * + * + * Automatically switches between the Vue and React renderings based on the + * global framework selection (data-framework on ). + */ + +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { highlight } from '../utils/shiki' +import ReactExample from './ReactExample.tsx' +import VueExample from './VueExample.vue' + +interface Props { + /** Component name, e.g. "avatar", "collapse" */ + component: string + /** Example file name without extension, default "Basic" */ + example?: string +} + +const { component, example = 'Basic' } = Astro.props + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const workspaceRoot = path.resolve(__dirname, '../../..') + +const vuePath = path.join( + workspaceRoot, + 'packages/vue/src/components', + component, + 'examples', + `${example}.vue`, +) +const reactPath = path.join( + workspaceRoot, + 'packages/react/src/components', + component, + 'examples', + `${example}.tsx`, +) + +let vueCode = '' +let reactCode = '' + +try { + vueCode = fs.readFileSync(vuePath, 'utf-8') +} +catch { + vueCode = `// Vue ${example} example not found for ${component}` +} + +try { + reactCode = fs.readFileSync(reactPath, 'utf-8') +} +catch { + reactCode = `// React ${example} example not found for ${component}` +} + +// Rewrite relative imports to package imports for display in docs +vueCode = vueCode.replace(/from\s+['"]\.\.\/(\.\.\/)*index['"]/g, `from '@destyler-ui/vue'`) +reactCode = reactCode.replace(/from\s+['"]\.\.\/(\.\.\/)*index['"]/g, `from '@destyler-ui/react'`) + +const vueHtml = await highlight(vueCode, 'vue') +const reactHtml = await highlight(reactCode, 'tsx') +--- + +
+ +
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + diff --git a/docs/src/components/ComponentTypes.astro b/docs/src/components/ComponentTypes.astro new file mode 100644 index 0000000..43bb174 --- /dev/null +++ b/docs/src/components/ComponentTypes.astro @@ -0,0 +1,92 @@ +--- +/** + * ComponentTypes — renders full API reference tables for a component. + * + * Reads generated type JSON files and renders Props/Emits tables for each + * component part (Root, Item, Content, etc.), switching between frameworks + * using the same data-fw pattern as FrameworkContent. + * + * Usage in MDX: + * import ComponentTypes from '../../../components/ComponentTypes.astro'; + * + */ + +import fs from 'node:fs' +import path from 'node:path' +import PropsTable from './PropsTable.astro' +import EmitsTable from './EmitsTable.astro' +import DataAttrTable from './DataAttrTable.astro' + +interface DataAttrInfo { + name: string + type: string +} + +interface PartTypes { + props?: Record + emits?: Record + dataAttr?: DataAttrInfo[] +} + +interface Props { + /** Component id (directory name), e.g. "avatar", "collapse" */ + id: string +} + +const { id } = Astro.props + +const dataDir = path.join(process.cwd(), 'src', 'data', 'types') + +function loadTypes(framework: string): Record | null { + const filePath = path.join(dataDir, framework, `${id}.types.json`) + if (!fs.existsSync(filePath)) return null + try { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')) + } + catch { + return null + } +} + +const vueTypes = loadTypes('vue') +const reactTypes = loadTypes('react') + +function sortedEntries(types: Record): [string, PartTypes][] { + return Object.entries(types).sort(([a], [b]) => { + // Root always comes first + if (a === 'Root') return -1 + if (b === 'Root') return 1 + return a.localeCompare(b) + }) +} +--- + +{(vueTypes || reactTypes) && ( +
+ {vueTypes && ( +
+ {sortedEntries(vueTypes).map(([partName, types]) => ( +
+

{partName}

+ {types.props && } + {types.emits && } + {types.dataAttr && types.dataAttr.length > 0 && } +
+ ))} +
+ )} + + {reactTypes && ( +
+ {sortedEntries(reactTypes).map(([partName, types]) => ( +
+

{partName}

+ {types.props && } + {types.emits && } + {types.dataAttr && types.dataAttr.length > 0 && } +
+ ))} +
+ )} +
+)} diff --git a/docs/src/components/DataAttrTable.astro b/docs/src/components/DataAttrTable.astro new file mode 100644 index 0000000..4a139e8 --- /dev/null +++ b/docs/src/components/DataAttrTable.astro @@ -0,0 +1,43 @@ +--- +/** + * DataAttrTable — renders a table of component data attributes. + * + * Props: + * - attrs: Array<{ name: string; type: string }> + */ + +interface DataAttrInfo { + name: string + type: string +} + +interface Props { + attrs: DataAttrInfo[] +} + +const { attrs } = Astro.props + +const thClass = 'text-left font-600 text-xs tracking-wider text-$sl-color-gray-3 px-3 py-2 border-b-2 border-b-solid border-b-$sl-color-hairline whitespace-nowrap' +const tdClass = 'px-3 py-2.5 border-b border-b-solid border-b-$sl-color-hairline align-top' +--- + +{attrs.length > 0 && ( +
+ + + + + + + + + {attrs.map((attr) => ( + + + + + ))} + +
AttributeValue
{attr.name}{attr.type}
+
+)} diff --git a/docs/src/components/EmitsTable.astro b/docs/src/components/EmitsTable.astro new file mode 100644 index 0000000..9c1aab6 --- /dev/null +++ b/docs/src/components/EmitsTable.astro @@ -0,0 +1,49 @@ +--- +/** + * EmitsTable — renders a table of component emits/events. + * + * Props: + * - emits: Record + */ + +interface EmitInfo { + type: string + isRequired: boolean + description?: string +} + +interface Props { + emits: Record | undefined +} + +const { emits } = Astro.props + +const entries = emits ? Object.entries(emits) : [] + +const thClass = 'text-left font-600 text-xs tracking-wider text-$sl-color-gray-3 px-3 py-2 border-b-2 border-b-solid border-b-$sl-color-hairline whitespace-nowrap' +const tdClass = 'px-3 py-2.5 border-b border-b-solid border-b-$sl-color-hairline align-top' +--- + +{entries.length > 0 && ( +
+ + + + + + + + + {entries.map(([name, emit]) => ( + + + + + ))} + +
EmitEvent
{name} + {emit.type} + {emit.description &&

{emit.description}

} +
+
+)} diff --git a/docs/src/components/FrameworkContent.astro b/docs/src/components/FrameworkContent.astro new file mode 100644 index 0000000..028ddf1 --- /dev/null +++ b/docs/src/components/FrameworkContent.astro @@ -0,0 +1,48 @@ +--- +/** + * FrameworkContent — shows/hides its content based on the active framework. + * + * Usage in MDX: + * import { VueContent, ReactContent } from '../../../components/FrameworkContent.astro'; + * + * + * ```vue + * + * ``` + * + * + * + * ```tsx + * export default function App() { ... } + * ``` + * + * + * Props: + * - fw: "vue" | "react" — which framework this content belongs to. + */ + +interface Props { + fw: 'vue' | 'react' +} + +const { fw } = Astro.props +--- + +
+ +
+ + diff --git a/docs/src/components/Header.astro b/docs/src/components/Header.astro new file mode 100644 index 0000000..a637096 --- /dev/null +++ b/docs/src/components/Header.astro @@ -0,0 +1,23 @@ +--- +/** + * Custom Header — composed from sub-components. + * Layout: Logo (left) | Search + Theme + GitHub (right) + */ +import HeaderLogo from './header/HeaderLogo.astro' +import HeaderSearch from './header/HeaderSearch.astro' +import HeaderThemeToggle from './header/HeaderThemeToggle.astro' +import HeaderFramework from './header/HeaderFramework.astro' +import HeaderGitHub from './header/HeaderGitHub.astro' +--- + +
+ + +
+ + + +
+
diff --git a/docs/src/components/MobileMenuToggle.astro b/docs/src/components/MobileMenuToggle.astro new file mode 100644 index 0000000..fb0cd04 --- /dev/null +++ b/docs/src/components/MobileMenuToggle.astro @@ -0,0 +1,56 @@ +--- +/** + * Custom Mobile Menu Toggle — zero-radius, dark-theme-aware button. + * Icons via UnoCSS presetIcons (@iconify-json/tabler). + * Keeps `starlight-menu-button` custom element for PageFrame compat. + */ +--- + + + + + + + + diff --git a/docs/src/components/MobileTableOfContents.astro b/docs/src/components/MobileTableOfContents.astro new file mode 100644 index 0000000..14fa70e --- /dev/null +++ b/docs/src/components/MobileTableOfContents.astro @@ -0,0 +1,214 @@ +--- +/** + * Custom Mobile Table of Contents — fully self‑contained, no Starlight imports. + * Renders a fixed bar below the header with a collapsible list of page headings. + * Uses UnoCSS utilities; keeps the `mobile-starlight-toc` custom element for + * heading‑tracking via IntersectionObserver. + */ + +interface TocItem { + depth: number + slug: string + text: string + children: TocItem[] +} + +function flattenToc(items: TocItem[], out: TocItem[] = []): TocItem[] { + for (const item of items) { + out.push(item) + if (item.children?.length) flattenToc(item.children, out) + } + return out +} + +const { toc } = Astro.locals.starlightRoute +const flatItems = toc ? flattenToc(toc.items) : [] +--- + +{toc && flatItems.length > 0 && ( + + + +)} + + + + diff --git a/docs/src/components/Pagination.astro b/docs/src/components/Pagination.astro new file mode 100644 index 0000000..fc3cedb --- /dev/null +++ b/docs/src/components/Pagination.astro @@ -0,0 +1,60 @@ +--- +/** + * Custom Pagination — replaces Starlight's default prev/next footer. + * Brutalist editorial style with UnoCSS utilities + presetIcons. + */ + +const { dir, pagination } = Astro.locals.starlightRoute; +const { prev, next } = pagination; +const isRtl = dir === 'rtl'; + +const prevIcon = isRtl ? 'i-tabler-chevron-right' : 'i-tabler-chevron-left'; +const nextIcon = isRtl ? 'i-tabler-chevron-left' : 'i-tabler-chevron-right'; +--- + + diff --git a/docs/src/components/PropsTable.astro b/docs/src/components/PropsTable.astro new file mode 100644 index 0000000..23fa30d --- /dev/null +++ b/docs/src/components/PropsTable.astro @@ -0,0 +1,54 @@ +--- +/** + * PropsTable — renders a table of component props. + * + * Props: + * - props: Record + */ + +interface PropInfo { + type: string + isRequired: boolean + defaultValue?: string + description?: string +} + +interface Props { + props: Record +} + +const { props } = Astro.props + +const entries = Object.entries(props) + +const thClass = 'text-left font-600 text-xs tracking-wider text-$sl-color-gray-3 px-3 py-2 border-b-2 border-b-solid border-b-$sl-color-hairline whitespace-nowrap' +const tdClass = 'px-3 py-2.5 border-b border-b-solid border-b-$sl-color-hairline align-top' +--- + +{entries.length > 0 && ( +
+ + + + + + + + + + {entries.map(([name, prop]) => ( + + + + + + ))} + +
PropDefaultType
+ {name}{prop.isRequired ? * : ''} + {prop.defaultValue ? {prop.defaultValue} : } + {prop.type} + {prop.description &&

{prop.description}

} +
+
+)} diff --git a/docs/src/components/ReactExample.tsx b/docs/src/components/ReactExample.tsx new file mode 100644 index 0000000..73f9d97 --- /dev/null +++ b/docs/src/components/ReactExample.tsx @@ -0,0 +1,38 @@ +import type { ComponentType, LazyExoticComponent } from 'react' +import { lazy, Suspense, useMemo } from 'react' + +interface Props { + component: string + example: string +} + +const modules: Record Promise> = import.meta.glob( + '../../../packages/react/src/components/*/examples/*.tsx', +) + +export default function ReactExample({ component, example }: Props) { + const Component: LazyExoticComponent> | null = useMemo(() => { + const key = `../../../packages/react/src/components/${component}/examples/${example}.tsx` + const loader = modules[key] + if (!loader) + return null + return lazy(async () => { + const mod = await loader() + // React examples use named exports matching the filename + const Comp = mod[example] || mod.default + return { default: Comp } + }) + }, [component, example]) + + if (!Component) { + return
React example not found
+ } + + return ( +
+ Loading…
}> + + + + ) +} diff --git a/docs/src/components/Sidebar.astro b/docs/src/components/Sidebar.astro new file mode 100644 index 0000000..d2c025b --- /dev/null +++ b/docs/src/components/Sidebar.astro @@ -0,0 +1,227 @@ +--- +/** + * Custom Sidebar — fully self-contained, no Starlight component imports. + * Uses UnoCSS utilities exclusively (no diff --git a/docs/src/components/TableOfContents.astro b/docs/src/components/TableOfContents.astro new file mode 100644 index 0000000..56a0cb3 --- /dev/null +++ b/docs/src/components/TableOfContents.astro @@ -0,0 +1,244 @@ +--- +/** + * Custom Desktop Table of Contents — fully self‑contained, no Starlight imports. + * Renders a sticky sidebar with a hierarchical list of page headings. + * Uses UnoCSS utilities; keeps the `desktop-starlight-toc` custom element for + * heading‑tracking via IntersectionObserver. + * Indentation is applied based on heading depth. + */ + +interface TocItem { + depth: number + slug: string + text: string + children: TocItem[] +} + +function flattenToc(items: TocItem[], out: TocItem[] = []): TocItem[] { + for (const item of items) { + out.push(item) + if (item.children?.length) flattenToc(item.children, out) + } + return out +} + +const { toc } = Astro.locals.starlightRoute +const flatItems = toc ? flattenToc(toc.items) : [] +--- + +{toc && flatItems.length > 0 && ( + +
+
+

+ On this page +

+
+ + +
+
+)} + + + + diff --git a/docs/src/components/VueExample.vue b/docs/src/components/VueExample.vue new file mode 100644 index 0000000..e140e0c --- /dev/null +++ b/docs/src/components/VueExample.vue @@ -0,0 +1,35 @@ + + + diff --git a/docs/src/components/header/HeaderFramework.astro b/docs/src/components/header/HeaderFramework.astro new file mode 100644 index 0000000..71117ef --- /dev/null +++ b/docs/src/components/header/HeaderFramework.astro @@ -0,0 +1,135 @@ +--- +/** + * Header Framework Switcher — single icon trigger with dropdown selector. + * Persists selection to localStorage and updates [data-framework] on . + */ +--- + +
+ + + + + +
+ + + + diff --git a/docs/src/components/header/HeaderGitHub.astro b/docs/src/components/header/HeaderGitHub.astro new file mode 100644 index 0000000..9033ca4 --- /dev/null +++ b/docs/src/components/header/HeaderGitHub.astro @@ -0,0 +1,15 @@ +--- +/** + * Header GitHub link — icon button linking to the repository. + */ +--- + + + diff --git a/docs/src/components/header/HeaderLogo.astro b/docs/src/components/header/HeaderLogo.astro new file mode 100644 index 0000000..4debc32 --- /dev/null +++ b/docs/src/components/header/HeaderLogo.astro @@ -0,0 +1,27 @@ +--- +/** + * Header Logo — site icon + wordmark. + * Shows dark/light logo variant based on data-theme. + */ +--- + + + + + UI + + + diff --git a/docs/src/components/header/HeaderSearch.astro b/docs/src/components/header/HeaderSearch.astro new file mode 100644 index 0000000..18dd671 --- /dev/null +++ b/docs/src/components/header/HeaderSearch.astro @@ -0,0 +1,53 @@ +--- +/** + * Header Search — custom icon-only trigger, Starlight's built-in search dialog. + */ +import Search from '@astrojs/starlight/components/Search.astro' +--- + + + + diff --git a/docs/src/components/header/HeaderThemeToggle.astro b/docs/src/components/header/HeaderThemeToggle.astro new file mode 100644 index 0000000..f8d16a6 --- /dev/null +++ b/docs/src/components/header/HeaderThemeToggle.astro @@ -0,0 +1,33 @@ +--- +/** + * Header Theme Toggle — sun/moon icon button. + * Toggles data-theme on and persists to localStorage. + */ +--- + + + + + + diff --git a/docs/src/components/landing/CTA.astro b/docs/src/components/landing/CTA.astro new file mode 100644 index 0000000..02cec65 --- /dev/null +++ b/docs/src/components/landing/CTA.astro @@ -0,0 +1,56 @@ +--- +/** + * Final CTA section — inverted colors (light bg on dark page) with + * email-style action or direct link. + */ +--- + +
+
+ + + +

READY?

+

+ Start building +
+ ToDay +

+ +

+ Open-source, MIT licensed, and ready for production. +

+ + +
+
+ +
+
+ +
+

UI

+

MIT License

+
+
+
diff --git a/docs/src/components/landing/Components.astro b/docs/src/components/landing/Components.astro new file mode 100644 index 0000000..97496c6 --- /dev/null +++ b/docs/src/components/landing/Components.astro @@ -0,0 +1,66 @@ +--- +/** + * Components showcase — displays available component categories + * with a magazine-spread typographic layout. + */ +const componentGroups = [ + { + category: "FORM", + items: ["Checkbox", "Radio", "Select", "Switch", "Slider", "Number Input", "OTP Input", "File Upload", "Signature", "Color Picker", "Field", "Fieldset", "Label"], + }, + { + category: "OVERLAY", + items: ["Dialog", "Popover", "Tooltip", "Hover Card", "Toast", "Tour"], + }, + { + category: "LAYOUT", + items: ["Collapse", "Collapsible", "Tabs", "Splitter", "Steps", "Separator", "Scroll Area"], + }, + { + category: "NAVIGATION", + items: ["Breadcrumbs", "Menu", "Navigation Menu", "Pagination", "Tree"], + }, + { + category: "DATA", + items: ["Avatar", "Calendar", "Carousel", "Progress", "Timer", "QR Code", "Toggle", "Toggle Group"], + }, + { + category: "UTILITY", + items: ["Clipboard", "Combobox", "Edit", "Floating Panel", "Presence", "Aspect Ratio"], + }, +]; +--- + +
+
+ +
+

COMPONENT LIBRARY

+

+ Everything you need,
+ nothing you don't +

+
+ + +
+ {componentGroups.map((group) => ( +
+
+ {group.category} + {group.items.length} +
+
+
    + {group.items.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+ ))} +
+
+
diff --git a/docs/src/components/landing/Features.astro b/docs/src/components/landing/Features.astro new file mode 100644 index 0000000..5c2202a --- /dev/null +++ b/docs/src/components/landing/Features.astro @@ -0,0 +1,66 @@ +--- +/** + * Features section — asymmetric grid layout with icon accent bars. + */ +const features = [ + { + title: "State Machine Driven", + description: "Every component is powered by a deterministic state machine, ensuring predictable behavior and eliminating impossible states.", + label: "01", + }, + { + title: "Framework Agnostic Core", + description: "Write your logic once. Ship to Vue and React with idiomatic bindings that feel native to each framework.", + label: "02", + }, + { + title: "Fully Unstyled", + description: "Zero visual opinions. Every component is a behavioral primitive — bring your own design system, CSS framework, or hand-rolled styles.", + label: "03", + }, + { + title: "Accessible by Default", + description: "WAI-ARIA patterns baked into every component. Keyboard navigation, focus management, and screen reader support out of the box.", + label: "04", + }, + { + title: "Composable Architecture", + description: "Anatomy-based API design lets you compose complex interfaces from simple, predictable parts. Slot in, swap out, extend.", + label: "05", + }, + { + title: "TypeScript Native", + description: "Full type coverage with discriminated unions and generics. Your editor knows every prop, event, and slot before you write it.", + label: "06", + }, +]; +--- + +
+
+ +
+

WHY DESTYLER

+

+ Components that
+ respect your craft +

+

+ We handle the complex interaction patterns so you can focus on + what matters — your product's unique experience. +

+
+ + +
+ {features.map((feature) => ( +
+ + {feature.label} +

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
diff --git a/docs/src/components/landing/Hero.astro b/docs/src/components/landing/Hero.astro new file mode 100644 index 0000000..4f4dbcc --- /dev/null +++ b/docs/src/components/landing/Hero.astro @@ -0,0 +1,73 @@ +--- +/** + * Hero section — massive typographic statement with animated underline CTAs. + * Follows the Bold Typography design system: extreme scale contrast, + * deliberate negative space, vermillion accent on dark canvas. + */ +--- + +
+ + + +
+ +

UNSTYLED · ACCESSIBLE · COMPOSABLE

+ + +

+ Fully customizable
+ and accessible
+ UI components +

+ + +

+ Destyler UI simplifies the creation of stunning and responsive web applications with its + comprehensive collection of fully styled and customizable UI components designed for You. +

+ + + + + +
+
+ 45+ + COMPONENTS +
+ +
+ + + + + + + FRAMEWORKS +
+
+
+
diff --git a/docs/src/components/landing/Steps.astro b/docs/src/components/landing/Steps.astro new file mode 100644 index 0000000..cfd5782 --- /dev/null +++ b/docs/src/components/landing/Steps.astro @@ -0,0 +1,138 @@ +--- +import { highlight } from '../../utils/shiki' + +/** + * How It Works — numbered steps layout with staggered alignment. + * Code blocks switch between Vue and React based on [data-framework] on . + */ +const steps = [ + { + number: "01", + title: "Install the package", + description: "Add Destyler to your project with your preferred package manager. Zero config, no build plugins required.", + vue: { code: "pnpm add @destyler-ui/vue", lang: "shellscript" as const }, + react: { code: "pnpm add @destyler-ui/react", lang: "shellscript" as const }, + }, + { + number: "02", + title: "Import components", + description: "Pull in exactly what you need. Every component is tree-shakable through namespace exports or direct imports.", + vue: { code: "import { Collapse } from '@destyler-ui/vue'", lang: "typescript" as const }, + react: { code: "import { Collapse } from '@destyler-ui/react'", lang: "typescript" as const }, + }, + { + number: "03", + title: "Style your way", + description: "Apply your design tokens, Tailwind classes, or CSS-in-JS. The components provide behavior — you provide the look.", + vue: { + code: `\n \n ...\n \n`, + lang: "vue-html" as const, + }, + react: { + code: `\n \n ...\n \n`, + lang: "tsx" as const, + }, + }, +]; + +// Pre-render highlighted HTML for each step +const highlightedSteps = await Promise.all( + steps.map(async (step) => ({ + ...step, + vueHtml: await highlight(step.vue.code, step.vue.lang), + reactHtml: await highlight(step.react.code, step.react.lang), + })) +) +--- + +
+
+ +
+

GETTING STARTED

+

+ Three steps to
+ ship faster +

+
+ + +
+ {highlightedSteps.map((step) => ( +
+ {step.number} +
+

{step.title}

+

{step.description}

+
+
+ +
+
+ +
+
+
+
+ ))} +
+
+
+ + diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts new file mode 100644 index 0000000..0025ea3 --- /dev/null +++ b/docs/src/content.config.ts @@ -0,0 +1,10 @@ +import { docsLoader } from '@astrojs/starlight/loaders' +import { docsSchema } from '@astrojs/starlight/schema' +import { defineCollection } from 'astro:content' + +export const collections = { + docs: defineCollection({ + loader: docsLoader(), + schema: docsSchema(), + }), +} diff --git a/docs/src/content/docs/components/aspect-ratio.mdx b/docs/src/content/docs/components/aspect-ratio.mdx new file mode 100644 index 0000000..4e4b146 --- /dev/null +++ b/docs/src/content/docs/components/aspect-ratio.mdx @@ -0,0 +1,59 @@ +--- +title: Aspect Ratio +description: Displays content within a desired ratio. +--- + +import ComponentPreview from '../../../components/ComponentPreview.astro'; +import ComponentTypes from '../../../components/ComponentTypes.astro'; +import FrameworkContent from '../../../components/FrameworkContent.astro'; + +# Aspect Ratio + +> Displays content within a desired ratio. + + + +## Anatomy + + +```vue + + + + +``` + + + +```tsx + +``` + + +## Examples + +### Basic + + + +### Portrait + + + +### Square + + + +### Root Provider + + + +## API Reference + + diff --git a/docs/src/content/docs/components/avatar.mdx b/docs/src/content/docs/components/avatar.mdx new file mode 100644 index 0000000..a57350e --- /dev/null +++ b/docs/src/content/docs/components/avatar.mdx @@ -0,0 +1,68 @@ +--- +title: Avatar +description: An image element with a fallback for representing the user. +--- + +import ComponentPreview from '../../../components/ComponentPreview.astro'; +import ComponentTypes from '../../../components/ComponentTypes.astro'; +import FrameworkContent from '../../../components/FrameworkContent.astro'; + +# Avatar + +> An image element with a fallback for representing the user. + + + +## Anatomy + + +```vue + + + +``` + + + +```tsx +import { Avatar } from '@destyler-ui/react' + +export default function Basic() { + return ( + + EL + + + ) +} +``` + + +## Examples + +### Basic + +Display a user's profile image with a fallback. + + + +### Events + +Use `onStatusChange` to listen for loading state changes. + + + +### Root Provider + + + +## API Reference + + diff --git a/docs/src/content/docs/components/breadcrumbs.mdx b/docs/src/content/docs/components/breadcrumbs.mdx new file mode 100644 index 0000000..ee69ef6 --- /dev/null +++ b/docs/src/content/docs/components/breadcrumbs.mdx @@ -0,0 +1,83 @@ +--- +title: Breadcrumbs +description: Show hierarchy and navigational context for a user’s location within an application. +--- + +import ComponentPreview from '../../../components/ComponentPreview.astro'; +import ComponentTypes from '../../../components/ComponentTypes.astro'; +import FrameworkContent from '../../../components/FrameworkContent.astro'; + +# Breadcrumbs + +> Show hierarchy and navigational context for a user’s location within an application. + + + +## Anatomy + + +```vue + + + +``` + + + +```tsx +import { Breadcrumbs } from '@destyler-ui/react' + +export function Basic() { + return ( + + + + {breadcrumbs => + breadcrumbs.items.map(item => ( + + {item.label} + {item.href && /} + + ))} + + + + ) +} +``` + + +## Examples + +### Basic + +Display a list of breadcrumb items showing the user's navigation path. + + + +### Context + +Use `Context` to access the breadcrumbs state and customize rendering with icons and custom separators. + + + +### Root Provider + + + +## API Reference + + diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 0000000..5753fb5 --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,19 @@ +--- +title: Destyler UI +description: Build scalable design systems with unstyled, accessible UI components powered by state machines. +template: splash +--- + +import Hero from '../../components/landing/Hero.astro'; +import Features from '../../components/landing/Features.astro'; +import Components from '../../components/landing/Components.astro'; +import Steps from '../../components/landing/Steps.astro'; +import CTA from '../../components/landing/CTA.astro'; + +
+ + + + + +
diff --git a/docs/src/content/docs/overview/introduction.mdx b/docs/src/content/docs/overview/introduction.mdx new file mode 100644 index 0000000..2db34be --- /dev/null +++ b/docs/src/content/docs/overview/introduction.mdx @@ -0,0 +1,38 @@ +--- +title: Introduction +description: Build scalable design systems with unstyled, accessible UI components powered by state machines. +--- + +An open-source UI component library for building high-quality, accessible design systems and web apps using [Destyler](https://destyler.org). + +--- + +## Features + +### Accessible + +Components adhere to the [WAI-ARIA design patterns](https://www.w3.org/WAI/ARIA/apg/) where possible. We handle many of the difficult implementation details related to accessibility, including `aria` and `role` attributes, focus management, and keyboard navigation. + +### Composable + +Destyler UI provides granular access to each component part, so you can wrap them and add your own event listeners, props, and refs. Build complex interfaces from simple, predictable primitives. + +### Unstyled + +Components ship with **zero styles**, giving you complete control over the visual design. Bring your preferred styling solution — vanilla CSS, Tailwind, CSS-in-JS, or anything else. + +### Open Architecture + +Our open component architecture provides granular access to each component part. Wrap them, extend them, and add your own `event listeners` and `props` to create exactly the experience you need. + +### Uncontrolled by Default + +Where applicable, components are uncontrolled by default but can also be controlled. All behavior wiring is handled internally, so you can get up and running smoothly without creating local states. + +### Developer Experience + +One of our main goals is to provide the best possible developer experience. All components provide a **fully-typed API** and share a similar, consistent interface — creating a predictable development workflow. + +### Incremental Adoption + +Each component can be installed individually so you can adopt them incrementally. Components are also versioned independently to further facilitate gradual integration into existing projects. diff --git a/docs/src/env.d.ts b/docs/src/env.d.ts new file mode 100644 index 0000000..3aa7b82 --- /dev/null +++ b/docs/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent, Record, any> + export default component +} diff --git a/docs/src/styles/bootstrap.css b/docs/src/styles/bootstrap.css new file mode 100644 index 0000000..cc3fd6d --- /dev/null +++ b/docs/src/styles/bootstrap.css @@ -0,0 +1,7 @@ +@import url('./components.css'); +@import url('./content.css'); +@import url('./landing.css'); +@import url('./layout.css'); +@import url('./tokens.css'); +@import url('./typography.css'); +@import url('./components/components.css'); diff --git a/docs/src/styles/components.css b/docs/src/styles/components.css new file mode 100644 index 0000000..e78ee7a --- /dev/null +++ b/docs/src/styles/components.css @@ -0,0 +1,331 @@ +/* ============================================================ + COMPONENTS — Buttons, cards, tabs, badges, steps, code blocks, + asides, file tree, link cards + ============================================================ */ + +/* Buttons */ +.sl-markdown-content button, .sl-markdown-content .button, .action { + border-radius: 0 !important; + letter-spacing: 0.1em; + font-weight: 600; + font-size: 0.8125rem; + transition: all 150ms cubic-bezier(0.25, 0, 0, 1); +} + +.action.primary { + background-color: transparent !important; + color: var(--sl-color-accent) !important; + border: none !important; + position: relative; + padding-left: 0 !important; + padding-right: 0 !important; +} + +.action.primary::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background-color: var(--sl-color-accent); + transform: scaleX(1); + transform-origin: left; + transition: transform 150ms cubic-bezier(0.25, 0, 0, 1); +} + +.action.primary:hover::after { + transform: scaleX(1.1); +} + +.action.secondary { + background-color: transparent !important; + color: var(--sl-color-text) !important; + border: 1px solid var(--sl-color-text) !important; +} + +.action.secondary:hover { + background-color: var(--sl-color-text) !important; + color: var(--sl-color-bg) !important; +} + +/* Cards */ +.sl-markdown-content .card, .card { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + background: transparent !important; + box-shadow: none !important; + transition: all 200ms cubic-bezier(0.25, 0, 0, 1); + padding: 1.5rem !important; + position: relative; +} + +.sl-markdown-content .card::before, .card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 2px; + background-color: var(--sl-color-accent); + transition: width 200ms cubic-bezier(0.25, 0, 0, 1); +} + +.sl-markdown-content .card:hover::before, .card:hover::before { + width: 100%; +} + +.sl-markdown-content .card:hover, .card:hover { + border-color: var(--sl-color-gray-4) !important; +} + +.card .title { + font-family: "Inter Tight", "Inter", system-ui, sans-serif !important; + font-size: 1.125rem !important; + font-weight: 700 !important; + letter-spacing: -0.02em !important; + margin-bottom: 0.5rem !important; +} + +.card .body { + font-size: 0.875rem !important; + line-height: 1.6 !important; + color: var(--sl-color-gray-3) !important; +} + +.card .icon { + width: 1.5rem !important; + height: 1.5rem !important; + margin-bottom: 0.75rem !important; + color: var(--sl-color-accent) !important; +} + +.card-grid { + gap: 1.5rem !important; +} + +/* Tabs */ +starlight-tabs { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + overflow: hidden; +} + +.tablist-wrapper { + border-bottom: 1px solid var(--sl-color-hairline) !important; + margin-bottom: 0 !important; + background-color: var(--sl-color-bg-sidebar) !important; +} + +[role="tab"] { + border-radius: 0 !important; + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-weight: 500; + letter-spacing: 0.06em; + padding: 0.75rem 1.5rem !important; + font-size: 0.75rem !important; + color: var(--sl-color-gray-3) !important; + transition: all 150ms cubic-bezier(0.25, 0, 0, 1); + border-bottom: 2px solid transparent !important; +} + +[role="tab"]:hover { + color: var(--sl-color-text) !important; +} + +[role="tab"][aria-selected="true"] { + color: var(--sl-color-accent) !important; + border-bottom: 2px solid var(--sl-color-accent) !important; + background-color: transparent !important; +} + +[role="tabpanel"] { + padding: 1.5rem !important; +} + +/* Code blocks */ +.expressive-code { + border-radius: 0.5rem; + border: 1px solid var(--sl-color-hairline) !important; + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + overflow: hidden; +} + +.expressive-code .frame { + border-radius: 0 !important; +} + +.expressive-code .header { + border-radius: 0 !important; + border-bottom: 1px solid var(--sl-color-hairline) !important; + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-size: 0.6875rem !important; + letter-spacing: 0.08em !important; +} + +.expressive-code pre { + border-radius: 0 !important; + padding: 1.25rem 1.5rem !important; + font-size: 0.8125rem !important; + line-height: 1.7 !important; +} + +/* Copy button */ +.expressive-code .copy button { + border-radius: 0.25rem !important; + transition: all 150ms cubic-bezier(0.25, 0, 0, 1) !important; +} + +.expressive-code .copy button:hover { + border-color: var(--sl-color-accent) !important; + color: var(--sl-color-accent) !important; +} + +/* Asides */ +.starlight-aside { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + border-left: 3px solid var(--sl-color-accent) !important; + background-color: transparent !important; + padding: 1.25rem 1.5rem !important; + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.starlight-aside--note { + border-left-color: var(--sl-color-accent) !important; +} + +.starlight-aside--tip { + border-left-color: #22c55e !important; +} + +.starlight-aside--caution { + border-left-color: #eab308 !important; +} + +.starlight-aside--danger { + border-left-color: #ef4444 !important; +} + +.starlight-aside__title { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + letter-spacing: 0.08em !important; + font-weight: 600 !important; + font-size: 0.6875rem !important; + margin-bottom: 0.5rem !important; +} + +.starlight-aside__content p { + font-size: 0.875rem !important; + line-height: 1.6 !important; +} + +/* Badges */ +.sl-badge { + border-radius: 0 !important; + font-family: "JetBrains Mono", "Fira Code", monospace !important; + letter-spacing: 0.08em !important; + font-weight: 600 !important; + font-size: 0.625rem !important; + border: 1px solid currentColor !important; + background-color: transparent !important; + padding: 0.2rem 0.5rem !important; +} + +/* Steps */ +.sl-steps { + --sl-color-hairline: var(--sl-color-gray-5); + margin-top: 1.5rem !important; + margin-bottom: 2rem !important; +} + +.sl-steps > li { + border-left: 1px solid var(--sl-color-hairline) !important; + padding-bottom: 1.5rem !important; +} + +.sl-steps > li::before { + border-radius: 0 !important; + background-color: var(--sl-color-bg) !important; + border: 1px solid var(--sl-color-hairline) !important; + color: var(--sl-color-gray-3) !important; + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-weight: 600 !important; + font-size: 0.6875rem !important; + transition: all 200ms cubic-bezier(0.25, 0, 0, 1) !important; +} + +.sl-steps > li:hover::before { + border-color: var(--sl-color-accent) !important; + color: var(--sl-color-accent) !important; +} + +/* File Tree */ +.file-tree { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + background-color: transparent !important; + padding: 1.25rem 1.5rem !important; +} + +.file-tree .directory > details > summary { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-weight: 600 !important; + font-size: 0.8125rem !important; +} + +.file-tree .file { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-size: 0.8125rem !important; + color: var(--sl-color-gray-3) !important; +} + +.file-tree .file:hover { + color: var(--sl-color-text) !important; +} + +/* Link Cards */ +.sl-link-card { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + background-color: transparent !important; + transition: all 200ms cubic-bezier(0.25, 0, 0, 1) !important; + position: relative; + overflow: hidden; +} + +.sl-link-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 2px; + background-color: var(--sl-color-accent); + transition: width 200ms cubic-bezier(0.25, 0, 0, 1); +} + +.sl-link-card:hover::before { + width: 100%; +} + +.sl-link-card:hover { + border-color: var(--sl-color-gray-4) !important; + background-color: transparent !important; +} + +.sl-link-card .title { + font-family: "Inter Tight", "Inter", system-ui, sans-serif !important; + font-weight: 600 !important; + letter-spacing: -0.02em !important; + font-size: 0.9375rem !important; +} + +/* Framework content visibility — global rules for data-fw switching */ +:root[data-framework='vue'] .ds-fw-content[data-fw='react'], +:root[data-framework='react'] .ds-fw-content[data-fw='vue'], +:root:not([data-framework]) .ds-fw-content[data-fw='react'] { + display: none; +} diff --git a/docs/src/styles/components/aspect-ratio/aspect-ratio.css b/docs/src/styles/components/aspect-ratio/aspect-ratio.css new file mode 100644 index 0000000..51518d6 --- /dev/null +++ b/docs/src/styles/components/aspect-ratio/aspect-ratio.css @@ -0,0 +1,4 @@ +@import url("./basic.css"); +@import url("./portrait.css"); +@import url("./square.css"); +@import url("./root-provider.css"); diff --git a/docs/src/styles/components/aspect-ratio/basic.css b/docs/src/styles/components/aspect-ratio/basic.css new file mode 100644 index 0000000..2809b71 --- /dev/null +++ b/docs/src/styles/components/aspect-ratio/basic.css @@ -0,0 +1,10 @@ +.aspect-ratio-basic main { + width: 300px; +} + +.aspect-ratio-basic img { + border-radius: 0.375rem; + object-fit: cover; + width: 100%; + height: 100%; +} diff --git a/docs/src/styles/components/aspect-ratio/portrait.css b/docs/src/styles/components/aspect-ratio/portrait.css new file mode 100644 index 0000000..ea9583e --- /dev/null +++ b/docs/src/styles/components/aspect-ratio/portrait.css @@ -0,0 +1,10 @@ +.aspect-ratio-portrait main { + width: 200px; +} + +.aspect-ratio-portrait img { + border-radius: 0.375rem; + object-fit: cover; + width: 100%; + height: 100%; +} diff --git a/docs/src/styles/components/aspect-ratio/root-provider.css b/docs/src/styles/components/aspect-ratio/root-provider.css new file mode 100644 index 0000000..25f75aa --- /dev/null +++ b/docs/src/styles/components/aspect-ratio/root-provider.css @@ -0,0 +1,10 @@ +.aspect-ratio-rootprovider main { + width: 300px; +} + +.aspect-ratio-rootprovider img { + border-radius: 0.375rem; + object-fit: cover; + width: 100%; + height: 100%; +} diff --git a/docs/src/styles/components/aspect-ratio/square.css b/docs/src/styles/components/aspect-ratio/square.css new file mode 100644 index 0000000..3b4ae8e --- /dev/null +++ b/docs/src/styles/components/aspect-ratio/square.css @@ -0,0 +1,15 @@ +.aspect-ratio-square main { + width: 200px; +} + +.aspect-ratio-square .demo-content { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + border-radius: 0.375rem; +} diff --git a/docs/src/styles/components/avatar/avatar.css b/docs/src/styles/components/avatar/avatar.css new file mode 100644 index 0000000..45a014a --- /dev/null +++ b/docs/src/styles/components/avatar/avatar.css @@ -0,0 +1,3 @@ +@import url('./basic.css'); +@import url('./event.css'); +@import url('./rootprovider.css'); diff --git a/docs/src/styles/components/avatar/basic.css b/docs/src/styles/components/avatar/basic.css new file mode 100644 index 0000000..41e6f75 --- /dev/null +++ b/docs/src/styles/components/avatar/basic.css @@ -0,0 +1,30 @@ +.avatar-basic [data-part="root"]{ + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 500; + position: relative; + vertical-align: top; + flex-shrink: 0; + user-select: none; + border-radius: 9999px; + background: var(--demo-neutral-subtle); + color: var(--demo-neutral-fg); + font-size: 1rem; + width: 3rem; + height: 3rem; +} + +.avatar-basic [data-part="image"] { + object-fit: cover; + width: 100%; + height: 100%; + border-radius: inherit; +} + +.avatar-basic [data-part="fallback"] { + border-radius: inherit; + line-height: 1; + font-weight: 500; + font-size: inherit; +} diff --git a/docs/src/styles/components/avatar/event.css b/docs/src/styles/components/avatar/event.css new file mode 100644 index 0000000..4b32500 --- /dev/null +++ b/docs/src/styles/components/avatar/event.css @@ -0,0 +1,52 @@ +.avatar-event .stack { + align-items: center; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.avatar-event output { + display: flex; + max-width: 32rem; + flex-direction: column; + gap: .5rem; + border: 1px solid var(--sl-color-hairline);; + border-radius: 0.375rem; /* 6px */ + padding: .5rem .625rem; + margin-bottom: .5rem; + font-family: monospace; + max-height: 200px; + overflow-y: auto; + font-size: .725rem; +} + +.avatar-event [data-part="root"]{ + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 500; + position: relative; + vertical-align: top; + flex-shrink: 0; + user-select: none; + border-radius: 9999px; + background: var(--demo-neutral-subtle); + color: var(--demo-neutral-fg); + font-size: 1rem; + width: 3rem; + height: 3rem; +} + +.avatar-event [data-part="image"] { + object-fit: cover; + width: 100%; + height: 100%; + border-radius: inherit; +} + +.avatar-event [data-part="fallback"] { + border-radius: inherit; + line-height: 1; + font-weight: 500; + font-size: inherit; +} diff --git a/docs/src/styles/components/avatar/rootprovider.css b/docs/src/styles/components/avatar/rootprovider.css new file mode 100644 index 0000000..25ac975 --- /dev/null +++ b/docs/src/styles/components/avatar/rootprovider.css @@ -0,0 +1,57 @@ +.avatar-rootprovider .stack { + align-items: center; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.avatar-rootprovider button { + display: flex; + max-width: 32rem; + flex-direction: column; + gap: .5rem; + border: 1px solid var(--sl-color-hairline);; + border-radius: 0.375rem !important; /* 6px */ + padding: .5rem .625rem; + margin-bottom: .5rem; + font-family: monospace; + max-height: 200px; + overflow-y: auto; + font-size: .725rem; +} + +.avatar-rootprovider button:hover { + background: var(--sl-color-gray-6); +} + +.avatar-rootprovider [data-part="root"]{ + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 500; + position: relative; + vertical-align: top; + flex-shrink: 0; + user-select: none; + border-radius: 9999px; + background: var(--demo-neutral-subtle); + color: var(--demo-neutral-fg); + font-size: 1rem; + width: 3rem; + height: 3rem; +} + +.avatar-rootprovider [data-part="image"] { + object-fit: cover; + width: 100%; + height: 100%; + border-radius: inherit; +} + +.avatar-rootprovider [data-part="fallback"] { + border-radius: inherit; + line-height: 1; + font-weight: 500; + font-size: inherit; +} + diff --git a/docs/src/styles/components/breadcrumbs/basic.css b/docs/src/styles/components/breadcrumbs/basic.css new file mode 100644 index 0000000..2b25e1c --- /dev/null +++ b/docs/src/styles/components/breadcrumbs/basic.css @@ -0,0 +1,43 @@ +.breadcrumbs-basic [data-part="root"] { + font-size: 0.875rem; +} + +.breadcrumbs-basic [data-part="list"] { + display: flex; + align-items: center; + gap: 0.25rem; + list-style: none; + margin: 0; + padding: 0; +} + +.breadcrumbs-basic [data-part="item"] { + display: inline-flex; + align-items: center; + gap: 0.25rem; +} + +.breadcrumbs-basic [data-part="link"] { + color: var(--demo-neutral-fg-muted); + opacity: 0.7; + text-decoration: none; + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + transition: color 0.15s, background-color 0.15s; +} + +.breadcrumbs-basic [data-part="link"]:hover { + opacity: 1.0; +} + +.breadcrumbs-basic [data-part="link"][data-current="page"] { + color: var(--demo-neutral-fg); + font-weight: 500; + pointer-events: none; +} + +.breadcrumbs-basic [data-part="separator"] { + color: var(--demo-neutral-fg-muted); + user-select: none; + font-size: 0.75rem; +} diff --git a/docs/src/styles/components/breadcrumbs/breadcrumbs.css b/docs/src/styles/components/breadcrumbs/breadcrumbs.css new file mode 100644 index 0000000..71a29f2 --- /dev/null +++ b/docs/src/styles/components/breadcrumbs/breadcrumbs.css @@ -0,0 +1,3 @@ +@import url('./basic.css'); +@import url('./context.css'); +@import url('./rootprovider.css'); diff --git a/docs/src/styles/components/breadcrumbs/context.css b/docs/src/styles/components/breadcrumbs/context.css new file mode 100644 index 0000000..d9c6c5b --- /dev/null +++ b/docs/src/styles/components/breadcrumbs/context.css @@ -0,0 +1,75 @@ +.breadcrumbs-context [data-part="root"] { + font-size: 0.875rem; +} + +.breadcrumbs-context [data-part="list"] { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + list-style: none; + margin: 0; + padding: 0; +} + +.breadcrumbs-context [data-part="list"] output { + display: flex; + max-width: 32rem; + flex-direction: column; + gap: .5rem; + border: 1px solid var(--sl-color-hairline);; + border-radius: 0.375rem; /* 6px */ + padding: .5rem .625rem; + margin-bottom: .5rem; + font-family: monospace; + max-height: 200px; + overflow-y: auto; + font-size: .725rem; +} + +.breadcrumbs-context [data-part="item"] { + display: inline-flex; + align-items: center; + gap: 0.25rem; +} + +.breadcrumbs-context [data-part="link"] { + display: inline-flex; + align-items: center; + gap: 0.25rem; + color: var(--demo-neutral-fg-muted); + opacity: 0.7; + text-decoration: none; + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + transition: color 0.15s, background-color 0.15s; +} + +.breadcrumbs-context [data-part="link"]:hover { + opacity: 1.0; +} + +.breadcrumbs-context [data-part="link"][data-current="page"] { + color: var(--demo-neutral-fg); + font-weight: 500; + pointer-events: none; +} + +.breadcrumbs-context [data-part="separator"] { + display: inline-flex; + align-items: center; + color: var(--demo-neutral-fg-muted); + user-select: none; +} + +.breadcrumbs-context [data-part="separator"] svg { + width: 1rem; + height: 1rem; +} + +.breadcrumbs-context p { + margin: 0.5rem 0 0; + font-size: 0.75rem; + font-family: monospace; + color: var(--demo-neutral-fg-muted); +} diff --git a/docs/src/styles/components/breadcrumbs/rootprovider.css b/docs/src/styles/components/breadcrumbs/rootprovider.css new file mode 100644 index 0000000..1c2724a --- /dev/null +++ b/docs/src/styles/components/breadcrumbs/rootprovider.css @@ -0,0 +1,69 @@ +.breadcrumbs-rootprovider main { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; +} + +.breadcrumbs-rootprovider button { + display: flex; + max-width: 32rem; + flex-direction: column; + gap: .5rem; + border: 1px solid var(--sl-color-hairline);; + border-radius: 0.375rem !important; /* 6px */ + padding: .5rem .625rem; + margin-bottom: .5rem; + font-family: monospace; + max-height: 200px; + overflow-y: auto; + font-size: .725rem; +} + +.breadcrumbs-rootprovider button:hover { + background: var(--sl-color-gray-6); +} + +.breadcrumbs-rootprovider [data-part="root"] { + font-size: 0.875rem; +} + +.breadcrumbs-rootprovider [data-part="list"] { + display: flex; + align-items: center; + gap: 0.25rem; + list-style: none; + margin: 0; + padding: 0; +} + +.breadcrumbs-rootprovider [data-part="item"] { + display: inline-flex; + align-items: center; + gap: 0.25rem; +} + +.breadcrumbs-rootprovider [data-part="link"] { + color: var(--demo-neutral-fg-muted); + opacity: 0.7; + text-decoration: none; + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + transition: color 0.15s, background-color 0.15s; +} + +.breadcrumbs-rootprovider [data-part="link"]:hover { + opacity: 1.0; +} + +.breadcrumbs-rootprovider [data-part="link"][data-current="page"] { + color: var(--demo-neutral-fg); + font-weight: 500; + pointer-events: none; +} + +.breadcrumbs-rootprovider [data-part="separator"] { + color: var(--demo-neutral-fg-muted); + user-select: none; + font-size: 0.75rem; +} diff --git a/docs/src/styles/components/components.css b/docs/src/styles/components/components.css new file mode 100644 index 0000000..7a125c3 --- /dev/null +++ b/docs/src/styles/components/components.css @@ -0,0 +1,4 @@ +@import url("./avatar/avatar.css"); +@import url("./aspect-ratio/aspect-ratio.css"); +@import url("./breadcrumbs/breadcrumbs.css"); +@import url("./token.css"); diff --git a/docs/src/styles/components/token.css b/docs/src/styles/components/token.css new file mode 100644 index 0000000..acca11f --- /dev/null +++ b/docs/src/styles/components/token.css @@ -0,0 +1,99 @@ + +:root { + /* Coral semantic tokens */ + --demo-coral-solid: #eb5e41; + --demo-coral-emphasized: #de5045; + --demo-coral-muted: #ffdbda; + --demo-coral-subtle: #ffe5e4; + --demo-coral-fg: #c9453b; + --demo-coral-contrast: white; + --demo-coral-focus-ring: #ffb2a8; + + --demo-neutral-solid: #8d8d86; + --demo-neutral-emphasized: #82827c; + --demo-neutral-muted: #e2e1de; + --demo-neutral-subtle: #f1f0ef; + --demo-neutral-fg: #21201c; + --demo-neutral-fg-muted: #61605b; + --demo-neutral-contrast: white; + --demo-neutral-focus-ring: #bcbbb5; + + /* Border semantic tokens */ + --demo-border: #e9e8e6; + --demo-border-muted: #f9f9f8; + --demo-border-emphasized: #dad9d6; + + /* Error semantic tokens */ + --demo-error: #ff4444; + + /* Popover */ + --demo-bg-popover: white; + --demo-bg-thumb: white; + --demo-bg-checkbox: white; + + /* Z-index tokens */ + --demo-popover-z-index: 50; + + /* Shadow tokens (soft, modern shadows) */ + --demo-shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --demo-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.08), 0 1px 2px -1px rgb(0 0 0 / 0.08); + --demo-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.06); + --demo-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.08), 0 4px 6px -4px rgb(0 0 0 / 0.04); + --demo-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.08), 0 8px 10px -6px rgb(0 0 0 / 0.04); + --demo-shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.15); + + /* JSON tree view */ + --demo-json-string: #c9453b; + --demo-json-number: #0550ae; + --demo-json-boolean: #b35900; + --demo-json-key: #116329; + --demo-json-accent: #6f42c1; +}; + +:root[data-theme='dark'] { + /* Coral dark mode */ + --demo-coral-solid: #eb5e41; + --demo-coral-emphasized: #ef6b4e; + --demo-coral-muted: #722b25; + --demo-coral-subtle: #55221e; + --demo-coral-fg: #f47a5c; + --demo-coral-contrast: white; + --demo-coral-focus-ring: #e2503f; + + --demo-neutral-solid: #6f6d66; + --demo-neutral-emphasized: #7c7b74; + --demo-neutral-muted: #31312e; + --demo-neutral-subtle: #222221; + --demo-neutral-fg: #eeeeec; + --demo-neutral-fg-muted: #a9a9a3; + --demo-neutral-contrast: white; + --demo-neutral-focus-ring: #62605b; + + /* Border dark mode */ + --demo-border: #2a2a28; + --demo-border-muted: #191918; + --demo-border-emphasized: #3b3a37; + + /* Error dark mode */ + --demo-error: #ff4444; + + /* Popover dark mode */ + --demo-bg-popover: #111110; + --demo-bg-thumb: #111110; + --demo-bg-checkbox: #111110; + + /* Shadow tokens dark mode (slightly higher opacity for visibility) */ + --demo-shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.15); + --demo-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.2), 0 1px 2px -1px rgb(0 0 0 / 0.15); + --demo-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.2), 0 2px 4px -2px rgb(0 0 0 / 0.12); + --demo-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.2), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --demo-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.25), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --demo-shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.35); + + /* JSON tree view dark mode */ + --demo-json-string: #ff7b72; + --demo-json-number: #79c0ff; + --demo-json-boolean: #ffa657; + --demo-json-key: #7ee787; + --demo-json-accent: #d2a8ff; +}; diff --git a/docs/src/styles/content.css b/docs/src/styles/content.css new file mode 100644 index 0000000..7e4edf3 --- /dev/null +++ b/docs/src/styles/content.css @@ -0,0 +1,319 @@ +/* ============================================================ + CONTENT — Markdown element overrides (blockquotes, lists, + tables, images, media, pre/code, inline elements) + ============================================================ */ + +.sl-markdown-content { + max-width: 48rem !important; +} + +.sl-markdown-content hr { + border: 0 !important; + border-top: 1px solid var(--sl-color-hairline) !important; + margin-top: 3rem !important; + margin-bottom: 3rem !important; + position: relative !important; +} + +/* Decorative dot on hr */ +.sl-markdown-content hr::after { + content: ''; + position: absolute; + top: -2px; + left: 0; + width: 4px; + height: 4px; + background-color: var(--sl-color-accent); +} + +/* Blockquotes */ +.sl-markdown-content blockquote { + border-left: 3px solid var(--sl-color-accent) !important; + margin-left: 0 !important; + margin-right: 0 !important; + margin-bottom: 1.5rem !important; + margin-top: 0.5rem !important; + font-style: normal !important; + color: var(--sl-color-gray-3) !important; + background-color: transparent !important; + position: relative !important; +} + +.sl-markdown-content blockquote p { + font-size: 0.9375rem !important; + line-height: 1.65 !important; + margin-bottom: 0 !important; +} + +.sl-markdown-content blockquote p:last-child { + margin-bottom: 0 !important; +} + +/* Lists */ +.sl-markdown-content ul, +.sl-markdown-content ol { + padding-left: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.sl-markdown-content li { + margin-bottom: 0.375rem !important; + font-size: 0.9375rem !important; + line-height: 1.65 !important; + color: var(--sl-color-gray-2) !important; +} + +.sl-markdown-content li::marker { + color: var(--sl-color-accent) !important; +} + +/* Nested lists */ +.sl-markdown-content li > ul, +.sl-markdown-content li > ol { + margin-top: 0.375rem !important; + margin-bottom: 0 !important; +} + +/* Tables */ +.sl-markdown-content table { + width: 100% !important; + border-collapse: collapse !important; + font-size: 0.875rem !important; +} + +.sl-markdown-content th, +.sl-markdown-content td { + padding: 0.75rem 1rem !important; + border: 1px solid var(--sl-color-hairline) !important; + text-align: left !important; +} + +.sl-markdown-content th { + background-color: var(--sl-color-bg-sidebar) !important; + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-weight: 600 !important; + letter-spacing: 0.08em !important; + font-size: 0.6875rem !important; + color: var(--sl-color-gray-3) !important; +} + +.sl-markdown-content td { + color: var(--sl-color-gray-2) !important; +} + +.sl-markdown-content tr:hover td { + background-color: var(--sl-color-bg-sidebar) !important; +} + +/* Images */ +.sl-markdown-content img { + max-width: 100%; + height: auto; +} + +.sl-markdown-content figcaption { + text-align: center !important; + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-size: 0.75rem !important; + color: var(--sl-color-gray-4) !important; + letter-spacing: 0.02em !important; +} + +/* Details/Summary */ +.sl-markdown-content details { + border: 1px solid var(--sl-color-hairline) !important; + padding: 1.25rem !important; + margin-bottom: 1.5rem !important; + transition: border-color 200ms cubic-bezier(0.25, 0, 0, 1) !important; +} + +.sl-markdown-content details:hover { + border-color: var(--sl-color-gray-4) !important; +} + +.sl-markdown-content summary { + font-family: "Inter Tight", "Inter", system-ui, sans-serif !important; + font-weight: 600 !important; + cursor: pointer !important; + outline: none !important; + font-size: 0.9375rem !important; + letter-spacing: -0.02em !important; +} + +.sl-markdown-content details[open] summary { + margin-bottom: 1rem !important; + border-bottom: 1px solid var(--sl-color-hairline) !important; + padding-bottom: 1rem !important; +} + +/* Pre/Code */ +.sl-markdown-content pre { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + padding: 0.25rem 0rem !important; + overflow-x: auto !important; +} + +.sl-markdown-content pre code { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-size: 0.8125rem !important; + background-color: transparent !important; + padding: 0 !important; + border: 0 !important; + line-height: 1.7 !important; +} + +/* Keyboard */ +.sl-markdown-content kbd { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + background-color: var(--sl-color-bg-sidebar) !important; + border: 1px solid var(--sl-color-hairline) !important; + border-bottom-width: 2px !important; + border-radius: 0 !important; + padding: 0.125rem 0.375rem !important; + font-size: 0.75rem !important; + box-shadow: none !important; +} + +/* Mark */ +.sl-markdown-content mark { + background-color: var(--sl-color-accent-low) !important; + color: var(--sl-color-text) !important; + padding: 0.125rem 0.25rem !important; +} + +/* Samp */ +.sl-markdown-content samp { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-size: 0.8125rem !important; + background-color: var(--sl-color-bg-sidebar) !important; + border: 1px solid var(--sl-color-hairline) !important; + border-radius: 0 !important; + padding: 0.125rem 0.375rem !important; +} + +/* Var */ +.sl-markdown-content var { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-size: 0.8125rem !important; + font-style: italic !important; + color: var(--sl-color-accent) !important; +} + +/* Sup/Sub */ +.sl-markdown-content sup, +.sl-markdown-content sub { + font-size: 0.75rem !important; + line-height: 0 !important; + position: relative !important; + vertical-align: baseline !important; +} + +.sl-markdown-content sup { + top: -0.5em !important; +} + +.sl-markdown-content sub { + bottom: -0.25em !important; +} + +/* Media elements */ +.sl-markdown-content iframe { + border: 1px solid var(--sl-color-hairline) !important; + margin-top: 2rem !important; + margin-bottom: 2rem !important; +} + +.sl-markdown-content video { + max-width: 100% !important; + height: auto !important; + margin-top: 2rem !important; + margin-bottom: 2rem !important; + border: 1px solid var(--sl-color-hairline) !important; +} + +.sl-markdown-content audio { + width: 100% !important; + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.sl-markdown-content canvas { + max-width: 100% !important; + height: auto !important; + margin-top: 2rem !important; + margin-bottom: 2rem !important; + border: 1px solid var(--sl-color-hairline) !important; +} + +.sl-markdown-content svg { + max-width: 100% !important; + height: auto !important; +} + +.sl-markdown-content math { + font-size: 1.0625rem !important; + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +/* Inline text elements */ +.sl-markdown-content dfn { + font-style: italic !important; + font-weight: 600 !important; + color: var(--sl-color-accent) !important; +} + +.sl-markdown-content abbr { + text-decoration: underline dotted !important; + text-decoration-color: var(--sl-color-gray-4) !important; + cursor: help !important; +} + +.sl-markdown-content q { + font-style: italic !important; +} + +.sl-markdown-content cite { + font-style: italic !important; +} + +.sl-markdown-content small { + font-size: 0.8125rem !important; +} + +.sl-markdown-content s { + text-decoration: line-through !important; + color: var(--sl-color-gray-4) !important; +} + +.sl-markdown-content u { + text-decoration: underline !important; + text-decoration-color: var(--sl-color-accent) !important; +} + +.sl-markdown-content b, +.sl-markdown-content strong { + font-weight: 700 !important; + color: var(--sl-color-text) !important; +} + +.sl-markdown-content em, +.sl-markdown-content i { + font-style: italic !important; +} + +.sl-markdown-content del { + text-decoration: line-through !important; + color: var(--sl-color-gray-4) !important; +} + +.sl-markdown-content ins { + text-decoration: underline !important; + text-decoration-color: var(--sl-color-accent) !important; +} + +.sl-markdown-content ruby { + font-size: 0.875rem !important; +} + diff --git a/docs/src/styles/landing.css b/docs/src/styles/landing.css new file mode 100644 index 0000000..c97caf5 --- /dev/null +++ b/docs/src/styles/landing.css @@ -0,0 +1,110 @@ +/* ============================================================ + DESTYLER LANDING PAGE — Starlight Structure Overrides + ============================================================ + These rules target Starlight's ancestor containers via :has() + and override default element styles. They CANNOT be expressed + as inline UnoCSS utilities because they scope to parent elements + outside our component tree. + ============================================================ */ + +/* ---- STARLIGHT STRUCTURE OVERRIDES ---- */ + +/* Hide the page title block */ +main:has(.landing-page) > .content-panel:first-child { + display: none !important; +} + +/* Remove Starlight's default hero when our landing page is present */ +.content-panel:has(.landing-page) .hero { + display: none !important; +} + +/* Remove max-width & padding from every ancestor container */ +.sl-markdown-content:has(.landing-page) { + max-width: none !important; + padding: 0 !important; + margin: 0 !important; + overflow: visible !important; +} + +.content-panel:has(.landing-page) { + max-width: none !important; + padding: 0 !important; + margin: 0 !important; + border-top: none !important; + overflow: visible !important; +} + +.content-panel:has(.landing-page) .sl-container { + max-width: none !important; + padding: 0 !important; + margin: 0 !important; + overflow: visible !important; +} + +main:has(.landing-page) { + padding: 0 !important; + max-width: none !important; + overflow: visible !important; +} + +.main-pane:has(.landing-page) { + padding: 0 !important; + width: 100% !important; + max-width: none !important; + overflow: visible !important; +} + +.lg\:sl-flex:has(.landing-page) { + max-width: none !important; +} + +/* Remove Starlight footer on landing */ +main:has(.landing-page) footer:not(.landing-footer) { + display: none !important; +} + +/* ---- LANDING WRAPPER ---- */ + +.landing-page { + width: 100%; + max-width: none !important; + margin: 0 !important; + padding: 0 !important; + overflow: visible !important; +} + +/* ---- STARLIGHT ELEMENT OVERRIDES ---- */ + +main:has(.landing-page) .content-panel + .content-panel { + padding: 0 !important; +} + +.landing-page a { + text-decoration: none !important; +} + +.landing-page a::after { + display: none !important; +} + +.landing-page h1, +.landing-page h2, +.landing-page h3 { + margin-top: 0 !important; + border-bottom: none !important; + padding-bottom: 0 !important; +} + +.landing-page ul { + padding-left: 0 !important; + margin-bottom: 0 !important; +} + +.landing-page li { + margin-bottom: 0 !important; +} + +.landing-page p { + margin-bottom: 0 !important; +} diff --git a/docs/src/styles/layout.css b/docs/src/styles/layout.css new file mode 100644 index 0000000..19912fa --- /dev/null +++ b/docs/src/styles/layout.css @@ -0,0 +1,165 @@ +/* ============================================================ + LAYOUT — Header, sidebar, navigation, footer, mobile menu + ============================================================ */ + +/* Header */ +header.header { + border-bottom: 1px solid var(--sl-color-hairline); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + background-color: color-mix(in srgb, var(--sl-color-bg) 85%, transparent) !important; +} + +/* ---- SIDEBAR ---- */ +.sidebar-pane { + border-right: 1px solid var(--sl-color-hairline); + background-color: var(--sl-color-bg-sidebar) !important; +} + +.sidebar-content { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +/* Site Title */ +.site-title { + font-family: "Inter Tight", "Inter", system-ui, sans-serif !important; + font-weight: 800 !important; + letter-spacing: -0.04em !important; + font-size: 0.9375rem !important; +} + +/* ---- RIGHT SIDEBAR (Table of Contents) ---- */ +.right-sidebar-panel { + border: none !important; + padding-top: 1.5rem !important; +} + +/* ---- PAGE TITLE AREA ---- */ + +/* Override Starlight's page title styling */ +.content-panel:first-child { + padding-top: 2.5rem !important; + padding-bottom: 0 !important; + border-top: none !important; +} + +/* Page header eyebrow */ +.content-panel h1#_top { + position: relative; +} + +/* ---- MAIN CONTENT AREA ---- */ +.content-panel + .content-panel { + border-top: none !important; + padding-top: 1rem !important; +} + +.sl-markdown-content { + padding-top: 0.5rem !important; +} + +/* ---- FOOTER ---- */ +footer { + border-top: 1px solid var(--sl-color-hairline) !important; + margin-top: 0rem !important; + padding-top: 0rem !important; +} + +.meta { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + font-size: 0.6875rem !important; + letter-spacing: 0.08em !important; + color: var(--sl-color-gray-4) !important; + margin-top: 0rem !important; +} + +/* ---- SEARCH ---- */ +.search-button { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + background-color: transparent !important; + transition: border-color 150ms cubic-bezier(0.25, 0, 0, 1) !important; +} + +.search-button:hover { + border-color: var(--sl-color-gray-4) !important; +} + +/* ---- THEME TOGGLE ---- */ +starlight-theme-select select { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + background-color: transparent !important; + letter-spacing: 0.05em !important; + font-weight: 600 !important; + font-size: 0.75rem !important; + transition: border-color 150ms cubic-bezier(0.25, 0, 0, 1) !important; +} + +starlight-theme-select select:hover { + border-color: var(--sl-color-gray-4) !important; +} + +/* ---- LANGUAGE SELECT ---- */ +starlight-language-select select { + border-radius: 0 !important; + border: 1px solid var(--sl-color-hairline) !important; + background-color: transparent !important; + letter-spacing: 0.05em !important; + font-weight: 600 !important; + font-size: 0.75rem !important; + transition: border-color 150ms cubic-bezier(0.25, 0, 0, 1) !important; +} + +starlight-language-select select:hover { + border-color: var(--sl-color-gray-4) !important; +} + +/* Icon */ +.icon { + stroke-width: 1.5px !important; +} + +/* ---- HERO (Starlight default hero — kept for landing) ---- */ +.hero { + padding-top: 5rem !important; + padding-bottom: 5rem !important; + text-align: left !important; + align-items: flex-start !important; +} + +.hero h1 { + font-size: 3.5rem !important; + line-height: 1.1 !important; + letter-spacing: -0.06em !important; +} + +@media (min-width: 768px) { + .hero h1 { + font-size: 4.5rem !important; + } +} + +@media (min-width: 1024px) { + .hero h1 { + font-size: 6rem !important; + } +} + +.hero .tagline { + font-size: 1.25rem !important; + letter-spacing: -0.01em !important; + max-width: 40rem !important; + margin-inline: auto !important; +} + +.hero .actions { + justify-content: flex-start !important; +} + +.hero img { + max-width: 100% !important; + height: auto !important; + margin-top: 2rem !important; +} diff --git a/docs/src/styles/tokens.css b/docs/src/styles/tokens.css new file mode 100644 index 0000000..18a7f08 --- /dev/null +++ b/docs/src/styles/tokens.css @@ -0,0 +1,142 @@ +/* ============================================================ + DESIGN TOKENS — Colors, fonts, shadows, radii + ============================================================ */ + +@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&family=Inter:wght@100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap'); + +:root { + /* Light mode colors */ + --sl-color-white: #FAFAFA; + --sl-color-black: #0A0A0A; + --sl-color-gray-1: #1A1A1A; + --sl-color-gray-2: #262626; + --sl-color-gray-3: #737373; + --sl-color-gray-4: #A3A3A3; + --sl-color-gray-5: #D4D4D4; + --sl-color-gray-6: #E5E5E5; + --sl-color-gray-7: #F5F5F5; + + /* Accent: Vermillion #FF3D00 */ + --sl-color-accent-low: #fff1ed; + --sl-color-accent: #FF3D00; + --sl-color-accent-high: #cc3100; + + /* Typography */ + --sl-font: "Inter Tight", "Inter", system-ui, sans-serif; + --sl-font-mono: "JetBrains Mono", "Fira Code", monospace; + + /* Backgrounds */ + --sl-color-bg: var(--sl-color-white); + --sl-color-bg-nav: var(--sl-color-white); + --sl-color-bg-sidebar: var(--sl-color-gray-7); + --sl-color-bg-inline-code: var(--sl-color-gray-7); + + /* Text */ + --sl-color-text: var(--sl-color-black); + --sl-color-text-accent: var(--sl-color-accent); + --sl-color-text-invert: var(--sl-color-white); + + /* Borders */ + --sl-color-hairline: var(--sl-color-gray-6); + --sl-color-hairline-shade: var(--sl-color-gray-5); + + /* Radius */ + --sl-border-radius: 0px; + + /* Header & Sidebar */ + --sl-nav-height: 3.5rem; + --sl-sidebar-width: 16rem; + + --sl-content-gap-y: 0rem; + + /* Shadows */ + --sl-shadow-sm: none !important; + --sl-shadow-md: none !important; + --sl-shadow-lg: none !important; + + /* Content width */ + --sl-content-width: 48rem; +} + +:root[data-theme='dark'] { + /* Dark mode colors */ + --sl-color-white: #0A0A0A; + --sl-color-black: #FAFAFA; + --sl-color-gray-1: #E5E5E5; + --sl-color-gray-2: #D4D4D4; + --sl-color-gray-3: #A3A3A3; + --sl-color-gray-4: #737373; + --sl-color-gray-5: #262626; + --sl-color-gray-6: #1A1A1A; + --sl-color-gray-7: #0F0F0F; + + /* Accent: Vermillion #FF3D00 */ + --sl-color-accent-low: #1a0800; + --sl-color-accent: #FF3D00; + --sl-color-accent-high: #FF8A66; + + /* Backgrounds */ + --sl-color-bg: var(--sl-color-white); + --sl-color-bg-nav: var(--sl-color-white); + --sl-color-bg-sidebar: var(--sl-color-gray-7); + --sl-color-bg-inline-code: var(--sl-color-gray-6); + + /* Text */ + --sl-color-text: var(--sl-color-black); + --sl-color-text-accent: var(--sl-color-accent); + --sl-color-text-invert: var(--sl-color-white); + + /* Borders */ + --sl-color-hairline: var(--sl-color-gray-6); + --sl-color-hairline-shade: var(--sl-color-gray-5); +} + +/* Noise texture overlay */ +body::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 9999; + opacity: 0.012; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); +} + +/* Custom Scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--sl-color-gray-5); + border-radius: 0; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--sl-color-gray-4); +} + +/* Selection */ +::selection { + background-color: var(--sl-color-accent); + color: white; +} + +/* Focus */ +*:focus-visible { + outline: 2px solid var(--sl-color-accent) !important; + outline-offset: 2px !important; +} + +/* Smooth transitions for theme changes */ +html { + transition: background-color 200ms ease, color 200ms ease; +} diff --git a/docs/src/styles/typography.css b/docs/src/styles/typography.css new file mode 100644 index 0000000..108ba0d --- /dev/null +++ b/docs/src/styles/typography.css @@ -0,0 +1,201 @@ +/* ============================================================ + TYPOGRAPHY — Body, headings, inline text styles + ============================================================ */ + +body { + font-family: "Inter", system-ui, sans-serif; + letter-spacing: -0.01em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +.sl-markdown-content{ + cursor: default; +} + +h1, h2, h3, h4, h5, h6, +.sl-markdown-content h1, +.sl-markdown-content h2, +.sl-markdown-content h3, +.sl-markdown-content h4, +.sl-markdown-content h5, +.sl-markdown-content h6 { + font-family: "Inter Tight", "Inter", system-ui, sans-serif; + letter-spacing: -0.04em; + font-weight: 700; + color: var(--sl-color-text); +} + +/* ---- Hide heading anchor link icons ---- */ +.sl-anchor-icon { + display: none !important; +} + +/* ---- Doc page headings (reasonable sizes) ---- */ + +.sl-markdown-content h1 { + font-size: 2rem; + line-height: 1.15; + letter-spacing: -0.04em; + margin-bottom: 1rem; + font-weight: 800; +} + +@media (min-width: 768px) { + .sl-markdown-content h1 { + font-size: 2.5rem; + } +} + +@media (min-width: 1024px) { + .sl-markdown-content h1 { + font-size: 2.75rem; + } +} + +.sl-markdown-content h2 { + font-size: 1.5rem; + margin-top: 3.5rem; + margin-bottom: 1rem; + padding-bottom: 0.75rem; + border: none !important; + position: relative; +} + +@media (min-width: 768px) { + .sl-markdown-content h2 { + font-size: 1.75rem; + } +} + +.sl-markdown-content h3 { + font-size: 1.25rem; + margin-top: 2.5rem; + margin-bottom: 0.75rem; + position: relative; + padding-left: 1rem; +} + +@media (min-width: 768px) { + .sl-markdown-content h3 { + font-size: 1.375rem; + } +} + +/* Decorative accent bar before h3 */ +.sl-markdown-content h3::before { + content: ''; + position: absolute; + left: 0; + top: 0.15em; + width: 3px; + height: 1em; + background-color: var(--sl-color-accent); +} + +.sl-markdown-content h4 { + font-size: 1.125rem; + margin-top: 2rem; + margin-bottom: 0.5rem; + letter-spacing: 0.02em; + font-weight: 600; +} + +.sl-markdown-content h5 { + font-size: 0.9375rem; + margin-top: 1.5rem; + margin-bottom: 0.5rem; + letter-spacing: 0.05em; + font-weight: 600; + color: var(--sl-color-gray-3); +} + +.sl-markdown-content h6 { + font-size: 0.8125rem; + margin-top: 1.5rem; + margin-bottom: 0.5rem; + letter-spacing: 0.08em; + font-weight: 600; + color: var(--sl-color-gray-4); + font-family: "JetBrains Mono", "Fira Code", monospace; +} + +.sl-markdown-content p { + font-size: 0.9375rem; + line-height: 1.7; + letter-spacing: -0.005em; + color: var(--sl-color-gray-2); +} + +:root[data-theme='dark'] .sl-markdown-content p { + color: var(--sl-color-gray-2); +} + +/* Lead paragraph — first p in the content */ +.sl-markdown-content > p:first-of-type { + font-size: 1.0625rem; + line-height: 1.65; + color: var(--sl-color-gray-3); + letter-spacing: -0.01em; +} + +/* Links */ +.sl-markdown-content a:not(.starlight-aside a) { + color: var(--sl-color-accent); + text-decoration: none; + position: relative; + font-weight: 600; + transition: color 150ms cubic-bezier(0.25, 0, 0, 1); +} + +.sl-markdown-content a:not(.starlight-aside a):hover { + color: var(--sl-color-accent-high); +} + +.sl-markdown-content a:not(.starlight-aside a)::after { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + width: 100%; + height: 1.5px; + background-color: var(--sl-color-accent); + transform: scaleX(0); + transform-origin: left; + transition: transform 200ms cubic-bezier(0.25, 0, 0, 1); +} + +.sl-markdown-content a:not(.starlight-aside a):hover::after { + transform: scaleX(1); +} + +.sl-markdown-content a { + color: var(--sl-color-accent); +} + +.sl-markdown-content a:hover { + color: var(--sl-color-accent-high); +} + +.sl-markdown-content .level-h2 { + margin-top: 0rem !important; + margin-bottom: 2rem; + border: none !important; +} + +.sl-markdown-content .level-h3 { + margin-bottom: 1rem; + border: none !important; +} + +/* Inline code */ +.sl-markdown-content code { + font-family: "JetBrains Mono", "Fira Code", monospace !important; + background-color: var(--sl-color-bg-inline-code) !important; + border-radius: 0.375rem !important; + padding: 0.15rem 0.4rem !important; + font-size: 0.8125rem !important; + border: 1px solid var(--sl-color-hairline) !important; + letter-spacing: -0.01em; +} diff --git a/docs/src/utils/shiki.ts b/docs/src/utils/shiki.ts new file mode 100644 index 0000000..afdc321 --- /dev/null +++ b/docs/src/utils/shiki.ts @@ -0,0 +1,47 @@ +import type { HighlighterCore } from 'shiki/core' +import { createHighlighterCore } from 'shiki/core' +import { createJavaScriptRegexEngine } from 'shiki/engine/javascript' + +let highlighterPromise: Promise | null = null + +function getHighlighter(): Promise { + if (!highlighterPromise) { + highlighterPromise = createHighlighterCore({ + engine: createJavaScriptRegexEngine(), + themes: [ + import('shiki/themes/vitesse-dark.mjs'), + import('shiki/themes/vitesse-light.mjs'), + ], + langs: [ + import('shiki/langs/vue.mjs'), + import('shiki/langs/vue-html.mjs'), + import('shiki/langs/tsx.mjs'), + import('shiki/langs/typescript.mjs'), + import('shiki/langs/shellscript.mjs'), + import('shiki/langs/html.mjs'), + import('shiki/langs/css.mjs'), + import('shiki/langs/json.mjs'), + ], + }) + } + return highlighterPromise +} + +/** + * Highlight code using Shiki with vitesse-dark and vitesse-light dual themes. + * Returns pre-rendered HTML that switches between themes via CSS variable. + * + * - `--shiki-dark` → dark mode colors (vitesse-dark) + * - `--shiki-light` → light mode colors (vitesse-light) + */ +export async function highlight(code: string, lang: string): Promise { + const highlighter = await getHighlighter() + return highlighter.codeToHtml(code, { + lang, + themes: { + dark: 'vitesse-dark', + light: 'vitesse-light', + }, + defaultColor: false, + }) +} diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} diff --git a/docs/uno.config.ts b/docs/uno.config.ts new file mode 100644 index 0000000..f69aaf3 --- /dev/null +++ b/docs/uno.config.ts @@ -0,0 +1,34 @@ +import { + defineConfig, + presetAttributify, + presetIcons, + presetWind3, + transformerDirectives, + transformerVariantGroup, +} from 'unocss' + +export default defineConfig({ + theme: { + fontFamily: { + display: '"Inter Tight", Inter, system-ui, sans-serif', + code: '"JetBrains Mono", "Fira Code", monospace', + }, + }, + presets: [ + presetWind3(), + presetAttributify(), + presetIcons({ + warn: true, + extraProperties: { + 'width': '1.23rem', + 'height': '1.23rem', + 'display': 'inline-block', + 'vertical-align': 'text-bottom', + }, + }), + ], + transformers: [ + transformerVariantGroup(), + transformerDirectives(), + ], +}) diff --git a/eslint.config.mjs b/eslint.config.mjs index f880eae..e69278d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,10 +1,11 @@ -import antfu, { react, vue } from '@antfu/eslint-config' +import antfu, { astro, react, vue } from '@antfu/eslint-config' export default antfu( { pnpm: false, vue: false, react: false, + astro: false, ignores: [ '.specstory/**', ], @@ -22,4 +23,9 @@ export default antfu( 'style/jsx-one-expression-per-line': 'off', }, }, + { + ...astro({ + files: ['docs/**/*.{js,ts,jsx,tsx,astro}'], + }), + }, ) diff --git a/package.json b/package.json index 3a1a9eb..06efc65 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,10 @@ "license": "MIT", "scripts": { "build": "pnpm run --filter \"./packages/**\" -r build", + "docs:dev": "pnpm run --filter @destyler-ui/docs -r dev", + "docs:build": "pnpm run --filter @destyler-ui/docs -r build", + "docs:preview": "pnpm run --filter @destyler-ui/docs -r preview", + "docs:generate:types": "npx tsx docs/scripts/generate-type-docs.ts vue && npx tsx docs/scripts/generate-type-docs.ts react", "lint": "eslint", "lint:fix": "eslint --fix", "release": "bumpp package.json packages/**/package.json --commit --push --tag", @@ -38,6 +42,7 @@ "@vitest/ui": "catalog:test", "bumpp": "catalog:node", "eslint": "catalog:lint", + "eslint-plugin-astro": "catalog:lint", "eslint-plugin-react-hooks": "catalog:lint", "eslint-plugin-react-refresh": "catalog:lint", "eslint-plugin-storybook": "catalog:lint", diff --git a/packages/vue/src/components/aspect-ratio/examples/Basic.vue b/packages/vue/src/components/aspect-ratio/examples/Basic.vue index 3588a82..33090dd 100644 --- a/packages/vue/src/components/aspect-ratio/examples/Basic.vue +++ b/packages/vue/src/components/aspect-ratio/examples/Basic.vue @@ -3,13 +3,10 @@ import { AspectRatio } from '../index'