diff --git a/.gitignore b/.gitignore index 71f383d6..49b8e23e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ packages/node-modules-inspector/src/app/public/fonts packages/node-modules-inspector/src/public/fonts packages/node-modules-inspector/runtime .vite-inspect +storybook-static .ghfs test/e2e/.fixtures test/e2e/.results diff --git a/packages/node-modules-inspector/.storybook/docs-dark.css b/packages/node-modules-inspector/.storybook/docs-dark.css new file mode 100644 index 00000000..759890b7 --- /dev/null +++ b/packages/node-modules-inspector/.storybook/docs-dark.css @@ -0,0 +1,21 @@ +/* When the preview is dark, recolor the autodocs/MDX surfaces to match the app + tokens (Storybook's docs theme is otherwise static light). */ +.dark .sbdocs-wrapper, +.dark .sbdocs.sbdocs-content { + background: #111; +} +.dark .sbdocs-preview, +.dark .docs-story { + background: #111; + border-color: #8882; +} +.dark .sbdocs h1, +.dark .sbdocs h2, +.dark .sbdocs h3, +.dark .sbdocs p, +.dark .sbdocs a { + color: #ccc; +} +.dark .sbdocs h2 { + border-color: #8882; +} diff --git a/packages/node-modules-inspector/.storybook/main.ts b/packages/node-modules-inspector/.storybook/main.ts new file mode 100644 index 00000000..283cf411 --- /dev/null +++ b/packages/node-modules-inspector/.storybook/main.ts @@ -0,0 +1,34 @@ +import type { StorybookConfig } from '@storybook/vue3-vite' +import { fileURLToPath } from 'node:url' +import Vue from '@vitejs/plugin-vue' +import Unocss from 'unocss/vite' +import { mergeConfig } from 'vite' + +const config: StorybookConfig = { + // Stories are co-located next to the app's presentational components; the + // Overview is one MDX page that references the others via doc blocks. + stories: [ + '../src/app/components/**/*.mdx', + '../src/app/components/**/*.stories.@(ts|js)', + ], + addons: ['@storybook/addon-docs'], + framework: { + name: '@storybook/vue3-vite', + // The app + `@antfu/design` ship raw `.vue`; disable Storybook's Vue docgen + // so it doesn't re-parse plugin-vue-compiled output ("missing end tag"). + options: { docgen: false }, + }, + async viteFinal(base) { + return mergeConfig(base, { + // Storybook runs its own Vite (not Nuxt): add plugin-vue to compile SFCs + // and reuse the app's UnoCSS config so tokens/fonts match the app exactly. + plugins: [ + Vue(), + Unocss({ configFile: fileURLToPath(new URL('../src/uno.config.ts', import.meta.url)) }), + ], + optimizeDeps: { exclude: ['@antfu/design'] }, + }) + }, +} + +export default config diff --git a/packages/node-modules-inspector/.storybook/manager.ts b/packages/node-modules-inspector/.storybook/manager.ts new file mode 100644 index 00000000..94b8e7c2 --- /dev/null +++ b/packages/node-modules-inspector/.storybook/manager.ts @@ -0,0 +1,11 @@ +import { GLOBALS_UPDATED } from 'storybook/internal/core-events' +import { addons } from 'storybook/manager-api' +import { themes } from 'storybook/theming' + +// Sync the Storybook manager (chrome) theme with the preview's `theme` global so +// the toolbar toggle flips both, instead of leaving the chrome mismatched. +addons.register('nmi/theme-sync', (api) => { + const apply = (theme?: string): void => api.setOptions({ theme: theme === 'dark' ? themes.dark : themes.light }) + apply(api.getGlobals().theme) + api.getChannel()?.on(GLOBALS_UPDATED, ({ globals }: { globals?: { theme?: string } }) => apply(globals?.theme)) +}) diff --git a/packages/node-modules-inspector/.storybook/preview.ts b/packages/node-modules-inspector/.storybook/preview.ts new file mode 100644 index 00000000..3b4bd8bf --- /dev/null +++ b/packages/node-modules-inspector/.storybook/preview.ts @@ -0,0 +1,50 @@ +import type { Preview } from '@storybook/vue3-vite' +import { GLOBALS_UPDATED } from 'storybook/internal/core-events' +import { addons } from 'storybook/preview-api' +import { h } from 'vue' +import 'virtual:uno.css' +import 'floating-vue/dist/style.css' +import '@antfu/design/styles.css' +import './docs-dark.css' + +// The manager (chrome) and preview (iframe) are separate documents. Toggle the +// preview root's `dark` class straight from the `theme` global so the whole +// surface — docs/MDX pages included, not just decorated stories — follows along. +addons.getChannel().on(GLOBALS_UPDATED, ({ globals }: { globals?: { theme?: string } }) => { + document.documentElement.classList.toggle('dark', globals?.theme === 'dark') +}) + +const preview: Preview = { + parameters: { + layout: 'centered', + controls: { expanded: true }, + options: { + // Overview lands first; the rest fall back to alphabetical. + storySort: { order: ['Overview', 'Display', 'UI'] }, + }, + }, + globalTypes: { + theme: { + description: 'Color scheme', + defaultValue: 'light', + toolbar: { + title: 'Theme', + icon: 'circlehollow', + items: [ + { value: 'light', title: 'Light', icon: 'sun' }, + { value: 'dark', title: 'Dark', icon: 'moon' }, + ], + dynamicTitle: true, + }, + }, + }, + decorators: [ + (story, context) => { + const dark = context.globals.theme === 'dark' + document.documentElement.classList.toggle('dark', dark) + return () => h('div', { class: 'p-8 bg-base color-base font-sans' }, [h(story())]) + }, + ], +} + +export default preview diff --git a/packages/node-modules-inspector/.storybook/shims.d.ts b/packages/node-modules-inspector/.storybook/shims.d.ts new file mode 100644 index 00000000..80bb208f --- /dev/null +++ b/packages/node-modules-inspector/.storybook/shims.d.ts @@ -0,0 +1,2 @@ +declare module 'virtual:uno.css' +declare module '*.css' diff --git a/packages/node-modules-inspector/.storybook/tsconfig.json b/packages/node-modules-inspector/.storybook/tsconfig.json new file mode 100644 index 00000000..f7f8bdb3 --- /dev/null +++ b/packages/node-modules-inspector/.storybook/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "types": ["node"], + "allowImportingTsExtensions": true, + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": [ + "**/*.ts", + "**/*.d.ts" + ] +} diff --git a/packages/node-modules-inspector/package.json b/packages/node-modules-inspector/package.json index 01e3bdd8..6e24ec20 100644 --- a/packages/node-modules-inspector/package.json +++ b/packages/node-modules-inspector/package.json @@ -26,6 +26,9 @@ "skills" ], "scripts": { + "storybook": "storybook dev -p 6006 --no-open", + "storybook:build": "storybook build", + "docs:overview": "node scripts/gen-overview.mjs", "dev": "pnpm run -r stub && (cd src && ROLLDOWN_OPTIONS_VALIDATION=loose nuxi dev)", "stub": "unbuild --stub", "build": "pnpm run wc:prepare && (cd src && ROLLDOWN_OPTIONS_VALIDATION=loose nuxi build) && unbuild", @@ -60,13 +63,18 @@ "valibot": "catalog:deps" }, "devDependencies": { + "@antfu/design": "catalog:dev", + "@storybook/addon-docs": "catalog:storybook", + "@storybook/vue3-vite": "catalog:storybook", "@types/semver": "catalog:types", "@unocss/nuxt": "catalog:bundling", "@valibot/to-json-schema": "catalog:testing", + "@vitejs/plugin-vue": "catalog:frontend", "@vueuse/nuxt": "catalog:bundling", "@webcontainer/api": "catalog:frontend", "@xterm/addon-fit": "catalog:frontend", "@xterm/xterm": "catalog:frontend", + "colorjs.io": "catalog:dev", "d3": "catalog:frontend", "d3-hierarchy": "catalog:frontend", "d3-shape": "catalog:frontend", @@ -75,8 +83,10 @@ "idb-keyval": "catalog:frontend", "modern-screenshot": "catalog:frontend", "nanovis": "catalog:frontend", + "reka-ui": "catalog:frontend", "rollup": "catalog:bundling", "semver": "catalog:deps", + "storybook": "catalog:storybook", "theme-vitesse": "catalog:frontend", "vite-hot-client": "catalog:frontend" } diff --git a/packages/node-modules-inspector/scripts/gen-overview.mjs b/packages/node-modules-inspector/scripts/gen-overview.mjs new file mode 100644 index 00000000..d08c1988 --- /dev/null +++ b/packages/node-modules-inspector/scripts/gen-overview.mjs @@ -0,0 +1,55 @@ +// Regenerates the Storybook Overview page (src/app/components/Overview.mdx) +// from the stories on disk: one linked `### [Name](…)` title + a per +// variation, grouped by the category in each story's `title`. +// Run from the package root: `node scripts/gen-overview.mjs` (pnpm docs:overview) +import { readdirSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' + +const base = 'src/app/components' + +const files = [] +for (const dir of readdirSync(base, { withFileTypes: true })) { + if (!dir.isDirectory()) + continue + for (const f of readdirSync(join(base, dir.name)).filter(f => f.endsWith('.stories.ts')).sort()) { + const name = f.replace('.stories.ts', '') + const src = readFileSync(join(base, dir.name, f), 'utf8') + const title = src.match(/title:\s*'([^']+)'/)?.[1] ?? `${dir.name}/${name}` + const category = title.split('/')[0] + const exps = [...src.matchAll(/export const (\w+)/g)].map(m => m[1]) + files.push({ + dir: dir.name, + name, + category, + exps, + id: `${title.toLowerCase().replaceAll('/', '-')}--docs`, + }) + } +} +files.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name)) + +let out = `import { Canvas, Meta } from '@storybook/addon-docs/blocks'\n` +for (const f of files) out += `import * as ${f.category}${f.name} from './${f.dir}/${f.name}.stories'\n` +out += ` + + +# Node Modules Inspector — Components + +App-specific presentational building blocks, themed end-to-end by the +[\`@antfu/design\`](https://github.com/antfu/design) UnoCSS preset. Generic +primitives (badges, versions, avatars, checkboxes, drawers) come from +\`@antfu/design\` directly and are documented in that package's own Storybook — +only components unique to this app live here. Toggle the theme from the +toolbar to check both light and dark. Each tile is a live story; click a +component name to open its full page. +` +let cur = '' +for (const f of files) { + if (f.category !== cur) { + out += `\n## ${f.category}\n` + cur = f.category + } + out += `\n### [${f.name}](/?path=/docs/${f.id})\n\n${f.exps.map(e => ``).join('\n')}\n` +} +writeFileSync(join(base, 'Overview.mdx'), out) +console.log(`Overview.mdx: ${files.length} components`) diff --git a/packages/node-modules-inspector/src/app/app.vue b/packages/node-modules-inspector/src/app/app.vue index 6410e264..688f7735 100644 --- a/packages/node-modules-inspector/src/app/app.vue +++ b/packages/node-modules-inspector/src/app/app.vue @@ -4,6 +4,7 @@ import Entry from './entries/index' import { setupQuery } from './state/query' import 'floating-vue/dist/style.css' +import '@antfu/design/styles.css' import './styles/global.css' import './composables/dark' diff --git a/packages/node-modules-inspector/src/app/components/Overview.mdx b/packages/node-modules-inspector/src/app/components/Overview.mdx new file mode 100644 index 00000000..f36acf52 --- /dev/null +++ b/packages/node-modules-inspector/src/app/components/Overview.mdx @@ -0,0 +1,30 @@ +import { Canvas, Meta } from '@storybook/addon-docs/blocks' +import * as DisplayClusterBadge from './display/ClusterBadge.stories' +import * as UILogo from './ui/Logo.stories' + + + +# Node Modules Inspector — Components + +App-specific presentational building blocks, themed end-to-end by the +[`@antfu/design`](https://github.com/antfu/design) UnoCSS preset. Generic +primitives (badges, versions, avatars, checkboxes, drawers) come from +`@antfu/design` directly and are documented in that package's own Storybook — +only components unique to this app live here. Toggle the theme from the +toolbar to check both light and dark. Each tile is a live story; click a +component name to open its full page. + +## Display + +### [ClusterBadge](/?path=/docs/display-clusterbadge--docs) + + + + + +## UI + +### [Logo](/?path=/docs/ui-logo--docs) + + + diff --git a/packages/node-modules-inspector/src/app/components/display/AuthorEntry.vue b/packages/node-modules-inspector/src/app/components/display/AuthorEntry.vue index f72467e9..7ce99c66 100644 --- a/packages/node-modules-inspector/src/app/components/display/AuthorEntry.vue +++ b/packages/node-modules-inspector/src/app/components/display/AuthorEntry.vue @@ -1,7 +1,6 @@ diff --git a/packages/node-modules-inspector/src/app/components/display/DurationBadge.vue b/packages/node-modules-inspector/src/app/components/display/DurationBadge.vue index 0d45b514..565775e0 100644 --- a/packages/node-modules-inspector/src/app/components/display/DurationBadge.vue +++ b/packages/node-modules-inspector/src/app/components/display/DurationBadge.vue @@ -1,47 +1,27 @@ diff --git a/packages/node-modules-inspector/src/app/components/display/FileSizeBadge.vue b/packages/node-modules-inspector/src/app/components/display/FileSizeBadge.vue index 9b83500a..c4ef4a52 100644 --- a/packages/node-modules-inspector/src/app/components/display/FileSizeBadge.vue +++ b/packages/node-modules-inspector/src/app/components/display/FileSizeBadge.vue @@ -1,8 +1,8 @@