From 36716468d99406bad363a6d38962192c8f858d9f Mon Sep 17 00:00:00 2001 From: zernonia Date: Tue, 5 Sep 2023 23:45:14 +0800 Subject: [PATCH] add registry --- apps/www/package.json | 6 +- apps/www/scripts/build-registry.ts | 423 +++++++++ .../src/lib/registry/{default => }/colors.ts | 0 apps/www/src/lib/registry/registry.ts | 864 ++++++++++++++++++ apps/www/src/lib/registry/schema.ts | 17 + apps/www/src/lib/registry/styles.ts | 12 + .../registry/{default/theme.ts => themes.ts} | 0 package.json | 3 +- pnpm-lock.yaml | 12 +- 9 files changed, 1332 insertions(+), 5 deletions(-) create mode 100644 apps/www/scripts/build-registry.ts rename apps/www/src/lib/registry/{default => }/colors.ts (100%) create mode 100644 apps/www/src/lib/registry/registry.ts create mode 100644 apps/www/src/lib/registry/schema.ts create mode 100644 apps/www/src/lib/registry/styles.ts rename apps/www/src/lib/registry/{default/theme.ts => themes.ts} (100%) diff --git a/apps/www/package.json b/apps/www/package.json index 3e9f8a9d..351caeff 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -8,7 +8,8 @@ "scripts": { "dev": "vitepress dev", "build": "vitepress build", - "preview": "vitepress preview" + "preview": "vitepress preview", + "build:registry": "ts-node --esm --project ./tsconfig.json ./scripts/build-registry.ts" }, "dependencies": { "@morev/vue-transitions": "^2.3.6", @@ -32,12 +33,15 @@ "@iconify-json/tabler": "^1.1.89", "@iconify/json": "^2.2.108", "@iconify/vue": "^4.1.1", + "@types/lodash.template": "^4.5.1", "@types/node": "^20.5.7", "@vitejs/plugin-vue": "^4.1.0", "@vue/compiler-core": "^3.3.4", "autoprefixer": "^10.4.14", + "lodash.template": "^4.5.0", "postcss": "^8.4.24", "radix-vue": "^0.1.30", + "rimraf": "^5.0.1", "tailwind-merge": "^1.14.0", "tailwindcss": "^3.3.3", "typescript": "^5.0.2", diff --git a/apps/www/scripts/build-registry.ts b/apps/www/scripts/build-registry.ts new file mode 100644 index 00000000..00dfaf74 --- /dev/null +++ b/apps/www/scripts/build-registry.ts @@ -0,0 +1,423 @@ +import fs from 'node:fs' +import path, { basename } from 'node:path' +import template from 'lodash.template' +import { rimraf } from 'rimraf' + +import { colorMapping, colors } from '../src/lib/registry/colors' +import { registry } from '../src/lib/registry/registry' +import { registrySchema } from '../src/lib/registry/schema' +import { styles } from '../src/lib/registry/styles' +import { themes } from '../src/lib/registry/themes' + +const REGISTRY_PATH = path.join(process.cwd(), 'src/public/registry') + +const result = registrySchema.safeParse(registry) + +if (!result.success) { + console.error(result.error) + process.exit(1) +} + +// ---------------------------------------------------------------------------- +// Build __registry__/index.tsx. +// ---------------------------------------------------------------------------- +let index = `// @ts-nocheck +// This file is autogenerated by scripts/build-registry.ts +// Do not edit this file directly. +import * as React from "react" + +export const Index: Record = { +` + +for (const style of styles) { + index += ` "${style.name}": {` + + // Build style index. + for (const item of result.data) { + // if (item.type === "components:ui") { + // continue + // } + + const resolveFiles = item.files.map( + file => `registry/${style.name}/${file}`, + ) + + const type = item.type.split(':')[1] + index += ` + "${item.name}": { + name: "${item.name}", + type: "${item.type}", + registryDependencies: ${JSON.stringify(item.registryDependencies)}, + component: React.lazy(() => import("@/registry/${style.name}/${type}/${ + item.name + }")), + files: [${resolveFiles.map(file => `"${file}"`)}], + },` + } + + index += ` + },` +} + +index += ` +} +` + +// Write style index. +rimraf.sync(path.join(process.cwd(), '__registry__/index.tsx')) +fs.writeFileSync(path.join(process.cwd(), '__registry__/index.tsx'), index) + +// ---------------------------------------------------------------------------- +// Build registry/styles/[style]/[name].json. +// ---------------------------------------------------------------------------- +for (const style of styles) { + const targetPath = path.join(REGISTRY_PATH, 'styles', style.name) + + // Create directory if it doesn't exist. + if (!fs.existsSync(targetPath)) + fs.mkdirSync(targetPath, { recursive: true }) + + for (const item of result.data) { + if (item.type !== 'components:ui') + continue + + const files = item.files?.map((file) => { + const content = fs.readFileSync( + path.join(process.cwd(), 'registry', style.name, file), + 'utf8', + ) + + return { + name: basename(file), + content, + } + }) + + const payload = { + ...item, + files, + } + + fs.writeFileSync( + path.join(targetPath, `${item.name}.json`), + JSON.stringify(payload, null, 2), + 'utf8', + ) + } +} + +// ---------------------------------------------------------------------------- +// Build registry/styles/index.json. +// ---------------------------------------------------------------------------- +const stylesJson = JSON.stringify(styles, null, 2) +fs.writeFileSync( + path.join(REGISTRY_PATH, 'styles/index.json'), + stylesJson, + 'utf8', +) + +// ---------------------------------------------------------------------------- +// Build registry/index.json. +// ---------------------------------------------------------------------------- +const names = result.data.filter(item => item.type === 'components:ui') +const registryJson = JSON.stringify(names, null, 2) +rimraf.sync(path.join(REGISTRY_PATH, 'index.json')) +fs.writeFileSync(path.join(REGISTRY_PATH, 'index.json'), registryJson, 'utf8') + +// ---------------------------------------------------------------------------- +// Build registry/colors/index.json. +// ---------------------------------------------------------------------------- +const colorsTargetPath = path.join(REGISTRY_PATH, 'colors') +rimraf.sync(colorsTargetPath) +if (!fs.existsSync(colorsTargetPath)) + fs.mkdirSync(colorsTargetPath, { recursive: true }) + +const colorsData: Record = {} +for (const [color, value] of Object.entries(colors)) { + if (typeof value === 'string') { + colorsData[color] = value + continue + } + + if (Array.isArray(value)) { + colorsData[color] = value.map(item => ({ + ...item, + rgbChannel: item.rgb.replace(/^rgb\((\d+),(\d+),(\d+)\)$/, '$1 $2 $3'), + hslChannel: item.hsl.replace( + /^hsl\(([\d.]+),([\d.]+%),([\d.]+%)\)$/, + '$1 $2 $3', + ), + })) + continue + } + + if (typeof value === 'object') { + colorsData[color] = { + ...value, + rgbChannel: value.rgb.replace(/^rgb\((\d+),(\d+),(\d+)\)$/, '$1 $2 $3'), + hslChannel: value.hsl.replace( + /^hsl\(([\d.]+),([\d.]+%),([\d.]+%)\)$/, + '$1 $2 $3', + ), + } + continue + } +} + +fs.writeFileSync( + path.join(colorsTargetPath, 'index.json'), + JSON.stringify(colorsData, null, 2), + 'utf8', +) + +// ---------------------------------------------------------------------------- +// Build registry/colors/[base].json. +// ---------------------------------------------------------------------------- +export const BASE_STYLES = `@tailwind base; +@tailwind components; +@tailwind utilities; +` + +export const BASE_STYLES_WITH_VARIABLES = `@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: <%- colors.light["background"] %>; + --foreground: <%- colors.light["foreground"] %>; + + --card: <%- colors.light["card"] %>; + --card-foreground: <%- colors.light["card-foreground"] %>; + + --popover: <%- colors.light["popover"] %>; + --popover-foreground: <%- colors.light["popover-foreground"] %>; + + --primary: <%- colors.light["primary"] %>; + --primary-foreground: <%- colors.light["primary-foreground"] %>; + + --secondary: <%- colors.light["secondary"] %>; + --secondary-foreground: <%- colors.light["secondary-foreground"] %>; + + --muted: <%- colors.light["muted"] %>; + --muted-foreground: <%- colors.light["muted-foreground"] %>; + + --accent: <%- colors.light["accent"] %>; + --accent-foreground: <%- colors.light["accent-foreground"] %>; + + --destructive: <%- colors.light["destructive"] %>; + --destructive-foreground: <%- colors.light["destructive-foreground"] %>; + + --border: <%- colors.light["border"] %>; + --input: <%- colors.light["input"] %>; + --ring: <%- colors.light["ring"] %>; + + --radius: 0.5rem; + } + + .dark { + --background: <%- colors.dark["background"] %>; + --foreground: <%- colors.dark["foreground"] %>; + + --card: <%- colors.dark["card"] %>; + --card-foreground: <%- colors.dark["card-foreground"] %>; + + --popover: <%- colors.dark["popover"] %>; + --popover-foreground: <%- colors.dark["popover-foreground"] %>; + + --primary: <%- colors.dark["primary"] %>; + --primary-foreground: <%- colors.dark["primary-foreground"] %>; + + --secondary: <%- colors.dark["secondary"] %>; + --secondary-foreground: <%- colors.dark["secondary-foreground"] %>; + + --muted: <%- colors.dark["muted"] %>; + --muted-foreground: <%- colors.dark["muted-foreground"] %>; + + --accent: <%- colors.dark["accent"] %>; + --accent-foreground: <%- colors.dark["accent-foreground"] %>; + + --destructive: <%- colors.dark["destructive"] %>; + --destructive-foreground: <%- colors.dark["destructive-foreground"] %>; + + --border: <%- colors.dark["border"] %>; + --input: <%- colors.dark["input"] %>; + --ring: <%- colors.dark["ring"] %>; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +}` + +for (const baseColor of ['slate', 'gray', 'zinc', 'neutral', 'stone']) { + const base: Record = { + inlineColors: {}, + cssVars: {}, + } + for (const [mode, values] of Object.entries(colorMapping)) { + base.inlineColors[mode] = {} + base.cssVars[mode] = {} + for (const [key, value] of Object.entries(values)) { + if (typeof value === 'string') { + const resolvedColor = value.replace(/{{base}}-/g, `${baseColor}-`) + base.inlineColors[mode][key] = resolvedColor + + const [resolvedBase, scale] = resolvedColor.split('-') + const color = scale + ? colorsData[resolvedBase].find( + item => item.scale === Number.parseInt(scale), + ) + : colorsData[resolvedBase] + if (color) + base.cssVars[mode][key] = color.hslChannel + } + } + } + + // Build css vars. + base.inlineColorsTemplate = template(BASE_STYLES)({}) + base.cssVarsTemplate = template(BASE_STYLES_WITH_VARIABLES)({ + colors: base.cssVars, + }) + + fs.writeFileSync( + path.join(REGISTRY_PATH, `colors/${baseColor}.json`), + JSON.stringify(base, null, 2), + 'utf8', + ) +} + +// ---------------------------------------------------------------------------- +// Build registry/themes.css +// ---------------------------------------------------------------------------- +export const THEME_STYLES_WITH_VARIABLES = ` + .theme-<%- theme %> { + --background: <%- colors.light["background"] %>; + --foreground: <%- colors.light["foreground"] %>; + + --muted: <%- colors.light["muted"] %>; + --muted-foreground: <%- colors.light["muted-foreground"] %>; + + --popover: <%- colors.light["popover"] %>; + --popover-foreground: <%- colors.light["popover-foreground"] %>; + + --card: <%- colors.light["card"] %>; + --card-foreground: <%- colors.light["card-foreground"] %>; + + --border: <%- colors.light["border"] %>; + --input: <%- colors.light["input"] %>; + + --primary: <%- colors.light["primary"] %>; + --primary-foreground: <%- colors.light["primary-foreground"] %>; + + --secondary: <%- colors.light["secondary"] %>; + --secondary-foreground: <%- colors.light["secondary-foreground"] %>; + + --accent: <%- colors.light["accent"] %>; + --accent-foreground: <%- colors.light["accent-foreground"] %>; + + --destructive: <%- colors.light["destructive"] %>; + --destructive-foreground: <%- colors.light["destructive-foreground"] %>; + + --ring: <%- colors.light["ring"] %>; + + --radius: <%- colors.light["radius"] %>; + } + + .dark .theme-<%- theme %> { + --background: <%- colors.dark["background"] %>; + --foreground: <%- colors.dark["foreground"] %>; + + --muted: <%- colors.dark["muted"] %>; + --muted-foreground: <%- colors.dark["muted-foreground"] %>; + + --popover: <%- colors.dark["popover"] %>; + --popover-foreground: <%- colors.dark["popover-foreground"] %>; + + --card: <%- colors.dark["card"] %>; + --card-foreground: <%- colors.dark["card-foreground"] %>; + + --border: <%- colors.dark["border"] %>; + --input: <%- colors.dark["input"] %>; + + --primary: <%- colors.dark["primary"] %>; + --primary-foreground: <%- colors.dark["primary-foreground"] %>; + + --secondary: <%- colors.dark["secondary"] %>; + --secondary-foreground: <%- colors.dark["secondary-foreground"] %>; + + --accent: <%- colors.dark["accent"] %>; + --accent-foreground: <%- colors.dark["accent-foreground"] %>; + + --destructive: <%- colors.dark["destructive"] %>; + --destructive-foreground: <%- colors.dark["destructive-foreground"] %>; + + --ring: <%- colors.dark["ring"] %>; + }` + +const themeCSS = [] +for (const theme of themes) { + themeCSS.push( + template(THEME_STYLES_WITH_VARIABLES)({ + colors: theme.cssVars, + theme: theme.name, + }), + ) +} + +fs.writeFileSync( + path.join(REGISTRY_PATH, 'themes.css'), + themeCSS.join('\n'), + 'utf8', +) + +// ---------------------------------------------------------------------------- +// Build registry/themes/[theme].json +// ---------------------------------------------------------------------------- +rimraf.sync(path.join(REGISTRY_PATH, 'themes')) +for (const baseColor of ['slate', 'gray', 'zinc', 'neutral', 'stone']) { + const payload = { + name: baseColor, + label: baseColor.charAt(0).toUpperCase() + baseColor.slice(1), + cssVars: {}, + } + + for (const [mode, values] of Object.entries(colorMapping)) { + payload.cssVars[mode] = {} + for (const [key, value] of Object.entries(values)) { + if (typeof value === 'string') { + const resolvedColor = value.replace(/{{base}}-/g, `${baseColor}-`) + payload.cssVars[mode][key] = resolvedColor + + const [resolvedBase, scale] = resolvedColor.split('-') + const color = scale + ? colorsData[resolvedBase].find( + item => item.scale === Number.parseInt(scale), + ) + : colorsData[resolvedBase] + if (color) + payload.cssVars[mode][key] = color.hslChannel + } + } + } + + const targetPath = path.join(REGISTRY_PATH, 'themes') + + // Create directory if it doesn't exist. + if (!fs.existsSync(targetPath)) + fs.mkdirSync(targetPath, { recursive: true }) + + fs.writeFileSync( + path.join(targetPath, `${payload.name}.json`), + JSON.stringify(payload, null, 2), + 'utf8', + ) +} + +console.log('✅ Done!') diff --git a/apps/www/src/lib/registry/default/colors.ts b/apps/www/src/lib/registry/colors.ts similarity index 100% rename from apps/www/src/lib/registry/default/colors.ts rename to apps/www/src/lib/registry/colors.ts diff --git a/apps/www/src/lib/registry/registry.ts b/apps/www/src/lib/registry/registry.ts new file mode 100644 index 00000000..7710b257 --- /dev/null +++ b/apps/www/src/lib/registry/registry.ts @@ -0,0 +1,864 @@ +import type { Registry } from './schema' + +const ui: Registry = [ + { + name: 'accordion', + type: 'components:ui', + dependencies: ['@radix-ui/react-accordion'], + files: ['ui/accordion.tsx'], + }, + { + name: 'alert', + type: 'components:ui', + files: ['ui/alert.tsx'], + }, + { + name: 'alert-dialog', + type: 'components:ui', + dependencies: ['@radix-ui/react-alert-dialog'], + registryDependencies: ['button'], + files: ['ui/alert-dialog.tsx'], + }, + { + name: 'aspect-ratio', + type: 'components:ui', + dependencies: ['@radix-ui/react-aspect-ratio'], + files: ['ui/aspect-ratio.tsx'], + }, + { + name: 'avatar', + type: 'components:ui', + dependencies: ['@radix-ui/react-avatar'], + files: ['ui/avatar.tsx'], + }, + { + name: 'badge', + type: 'components:ui', + files: ['ui/badge.tsx'], + }, + { + name: 'button', + type: 'components:ui', + dependencies: ['@radix-ui/react-slot'], + files: ['ui/button.tsx'], + }, + { + name: 'calendar', + type: 'components:ui', + dependencies: ['react-day-picker', 'date-fns'], + registryDependencies: ['button'], + files: ['ui/calendar.tsx'], + }, + { + name: 'card', + type: 'components:ui', + files: ['ui/card.tsx'], + }, + { + name: 'checkbox', + type: 'components:ui', + dependencies: ['@radix-ui/react-checkbox'], + files: ['ui/checkbox.tsx'], + }, + { + name: 'collapsible', + type: 'components:ui', + dependencies: ['@radix-ui/react-collapsible'], + files: ['ui/collapsible.tsx'], + }, + { + name: 'command', + type: 'components:ui', + dependencies: ['cmdk'], + registryDependencies: ['dialog'], + files: ['ui/command.tsx'], + }, + { + name: 'context-menu', + type: 'components:ui', + dependencies: ['@radix-ui/react-context-menu'], + files: ['ui/context-menu.tsx'], + }, + { + name: 'dialog', + type: 'components:ui', + dependencies: ['@radix-ui/react-dialog'], + files: ['ui/dialog.tsx'], + }, + { + name: 'dropdown-menu', + type: 'components:ui', + dependencies: ['@radix-ui/react-dropdown-menu'], + files: ['ui/dropdown-menu.tsx'], + }, + { + name: 'form', + type: 'components:ui', + dependencies: [ + '@radix-ui/react-label', + '@radix-ui/react-slot', + '@hookform/resolvers', + 'zod', + 'react-hook-form', + ], + registryDependencies: ['button', 'label'], + files: ['ui/form.tsx'], + }, + { + name: 'hover-card', + type: 'components:ui', + dependencies: ['@radix-ui/react-hover-card'], + files: ['ui/hover-card.tsx'], + }, + { + name: 'input', + type: 'components:ui', + files: ['ui/input.tsx'], + }, + { + name: 'label', + type: 'components:ui', + dependencies: ['@radix-ui/react-label'], + files: ['ui/label.tsx'], + }, + { + name: 'menubar', + type: 'components:ui', + dependencies: ['@radix-ui/react-menubar'], + files: ['ui/menubar.tsx'], + }, + { + name: 'navigation-menu', + type: 'components:ui', + dependencies: ['@radix-ui/react-navigation-menu'], + files: ['ui/navigation-menu.tsx'], + }, + { + name: 'popover', + type: 'components:ui', + dependencies: ['@radix-ui/react-popover'], + files: ['ui/popover.tsx'], + }, + { + name: 'progress', + type: 'components:ui', + dependencies: ['@radix-ui/react-progress'], + files: ['ui/progress.tsx'], + }, + { + name: 'radio-group', + type: 'components:ui', + dependencies: ['@radix-ui/react-radio-group'], + files: ['ui/radio-group.tsx'], + }, + { + name: 'scroll-area', + type: 'components:ui', + dependencies: ['@radix-ui/react-scroll-area'], + files: ['ui/scroll-area.tsx'], + }, + { + name: 'select', + type: 'components:ui', + dependencies: ['@radix-ui/react-select'], + files: ['ui/select.tsx'], + }, + { + name: 'separator', + type: 'components:ui', + dependencies: ['@radix-ui/react-separator'], + files: ['ui/separator.tsx'], + }, + { + name: 'sheet', + type: 'components:ui', + dependencies: ['@radix-ui/react-dialog'], + files: ['ui/sheet.tsx'], + }, + { + name: 'skeleton', + type: 'components:ui', + files: ['ui/skeleton.tsx'], + }, + { + name: 'slider', + type: 'components:ui', + dependencies: ['@radix-ui/react-slider'], + files: ['ui/slider.tsx'], + }, + { + name: 'switch', + type: 'components:ui', + dependencies: ['@radix-ui/react-switch'], + files: ['ui/switch.tsx'], + }, + { + name: 'table', + type: 'components:ui', + files: ['ui/table.tsx'], + }, + { + name: 'tabs', + type: 'components:ui', + dependencies: ['@radix-ui/react-tabs'], + files: ['ui/tabs.tsx'], + }, + { + name: 'textarea', + type: 'components:ui', + files: ['ui/textarea.tsx'], + }, + { + name: 'toast', + type: 'components:ui', + dependencies: ['@radix-ui/react-toast'], + files: ['ui/toast.tsx', 'ui/use-toast.ts', 'ui/toaster.tsx'], + }, + { + name: 'toggle', + type: 'components:ui', + dependencies: ['@radix-ui/react-toggle'], + files: ['ui/toggle.tsx'], + }, + { + name: 'tooltip', + type: 'components:ui', + dependencies: ['@radix-ui/react-tooltip'], + files: ['ui/tooltip.tsx'], + }, +] + +const example: Registry = [ + { + name: 'accordion-demo', + type: 'components:example', + registryDependencies: ['accordion'], + files: ['example/accordion-demo.tsx'], + }, + { + name: 'alert-demo', + type: 'components:example', + registryDependencies: ['alert'], + files: ['example/alert-demo.tsx'], + }, + { + name: 'alert-destructive', + type: 'components:example', + registryDependencies: ['alert'], + files: ['example/alert-destructive.tsx'], + }, + { + name: 'alert-dialog-demo', + type: 'components:example', + registryDependencies: ['alert-dialog', 'button'], + files: ['example/alert-dialog-demo.tsx'], + }, + { + name: 'aspect-ratio-demo', + type: 'components:example', + registryDependencies: ['aspect-ratio'], + files: ['example/aspect-ratio-demo.tsx'], + }, + { + name: 'avatar-demo', + type: 'components:example', + registryDependencies: ['avatar'], + files: ['example/avatar-demo.tsx'], + }, + { + name: 'badge-demo', + type: 'components:example', + registryDependencies: ['badge'], + files: ['example/badge-demo.tsx'], + }, + { + name: 'badge-destructive', + type: 'components:example', + registryDependencies: ['badge'], + files: ['example/badge-destructive.tsx'], + }, + { + name: 'badge-outline', + type: 'components:example', + registryDependencies: ['badge'], + files: ['example/badge-outline.tsx'], + }, + { + name: 'badge-secondary', + type: 'components:example', + registryDependencies: ['badge'], + files: ['example/badge-secondary.tsx'], + }, + { + name: 'button-demo', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-demo.tsx'], + }, + { + name: 'button-secondary', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-secondary.tsx'], + }, + { + name: 'button-destructive', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-destructive.tsx'], + }, + { + name: 'button-outline', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-outline.tsx'], + }, + { + name: 'button-ghost', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-ghost.tsx'], + }, + { + name: 'button-link', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-link.tsx'], + }, + { + name: 'button-with-icon', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-with-icon.tsx'], + }, + { + name: 'button-loading', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-loading.tsx'], + }, + { + name: 'button-icon', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-icon.tsx'], + }, + { + name: 'button-as-child', + type: 'components:example', + registryDependencies: ['button'], + files: ['example/button-as-child.tsx'], + }, + { + name: 'calendar-demo', + type: 'components:example', + registryDependencies: ['calendar'], + files: ['example/calendar-demo.tsx'], + }, + { + name: 'calendar-form', + type: 'components:example', + registryDependencies: ['calendar', 'form', 'popover'], + files: ['example/calendar-form.tsx'], + }, + { + name: 'card-demo', + type: 'components:example', + registryDependencies: ['card', 'button', 'switch'], + files: ['example/card-demo.tsx'], + }, + { + name: 'card-with-form', + type: 'components:example', + registryDependencies: ['button', 'card', 'input', 'label', 'select'], + files: ['example/card-with-form.tsx'], + }, + { + name: 'checkbox-demo', + type: 'components:example', + registryDependencies: ['checkbox'], + files: ['example/checkbox-demo.tsx'], + }, + { + name: 'checkbox-disabled', + type: 'components:example', + registryDependencies: ['checkbox'], + files: ['example/checkbox-disabled.tsx'], + }, + { + name: 'checkbox-form-multiple', + type: 'components:example', + registryDependencies: ['checkbox', 'form'], + files: ['example/checkbox-form-multiple.tsx'], + }, + { + name: 'checkbox-form-single', + type: 'components:example', + registryDependencies: ['checkbox', 'form'], + files: ['example/checkbox-form-single.tsx'], + }, + { + name: 'checkbox-with-text', + type: 'components:example', + registryDependencies: ['checkbox'], + files: ['example/checkbox-with-text.tsx'], + }, + { + name: 'collapsible-demo', + type: 'components:example', + registryDependencies: ['collapsible'], + files: ['example/collapsible-demo.tsx'], + }, + { + name: 'combobox-demo', + type: 'components:example', + registryDependencies: ['command'], + files: ['example/combobox-demo.tsx'], + }, + { + name: 'combobox-dropdown-menu', + type: 'components:example', + registryDependencies: ['command', 'dropdown-menu', 'button'], + files: ['example/combobox-dropdown-menu.tsx'], + }, + { + name: 'combobox-form', + type: 'components:example', + registryDependencies: ['command', 'form'], + files: ['example/combobox-form.tsx'], + }, + { + name: 'combobox-popover', + type: 'components:example', + registryDependencies: ['combobox', 'popover'], + files: ['example/combobox-popover.tsx'], + }, + { + name: 'command-demo', + type: 'components:example', + registryDependencies: ['command'], + files: ['example/command-demo.tsx'], + }, + { + name: 'command-dialog', + type: 'components:example', + registryDependencies: ['command', 'dialog'], + files: ['example/command-dialog.tsx'], + }, + { + name: 'context-menu-demo', + type: 'components:example', + registryDependencies: ['context-menu'], + files: ['example/context-menu-demo.tsx'], + }, + { + name: 'data-table-demo', + type: 'components:example', + registryDependencies: ['data-table'], + files: ['example/data-table-demo.tsx'], + }, + { + name: 'date-picker-demo', + type: 'components:example', + registryDependencies: ['button', 'calendar', 'popover'], + files: ['example/date-picker-demo.tsx'], + dependencies: ['date-fns'], + }, + { + name: 'date-picker-form', + type: 'components:example', + registryDependencies: ['button', 'calendar', 'form', 'popover'], + files: ['example/date-picker-form.tsx'], + dependencies: ['date-fns'], + }, + { + name: 'date-picker-with-presets', + type: 'components:example', + registryDependencies: ['button', 'calendar', 'popover', 'select'], + files: ['example/date-picker-with-presets.tsx'], + dependencies: ['date-fns'], + }, + { + name: 'date-picker-with-range', + type: 'components:example', + registryDependencies: ['button', 'calendar', 'popover'], + files: ['example/date-picker-with-range.tsx'], + dependencies: ['date-fns'], + }, + { + name: 'dialog-demo', + type: 'components:example', + registryDependencies: ['dialog'], + files: ['example/dialog-demo.tsx'], + }, + { + name: 'dropdown-menu-demo', + type: 'components:example', + registryDependencies: ['dropdown-menu'], + files: ['example/dropdown-menu-demo.tsx'], + }, + { + name: 'dropdown-menu-checkboxes', + type: 'components:example', + registryDependencies: ['dropdown-menu', 'checkbox'], + files: ['example/dropdown-menu-checkboxes.tsx'], + }, + { + name: 'dropdown-menu-radio-group', + type: 'components:example', + registryDependencies: ['dropdown-menu', 'radio-group'], + files: ['example/dropdown-menu-radio-group.tsx'], + }, + { + name: 'hover-card-demo', + type: 'components:example', + registryDependencies: ['hover-card'], + files: ['example/hover-card-demo.tsx'], + }, + { + name: 'input-demo', + type: 'components:example', + registryDependencies: ['input'], + files: ['example/input-demo.tsx'], + }, + { + name: 'input-disabled', + type: 'components:example', + registryDependencies: ['input'], + files: ['example/input-disabled.tsx'], + }, + { + name: 'input-file', + type: 'components:example', + registryDependencies: ['input'], + files: ['example/input-file.tsx'], + }, + { + name: 'input-form', + type: 'components:example', + registryDependencies: ['input', 'button', 'form'], + files: ['example/input-form.tsx'], + }, + { + name: 'input-with-button', + type: 'components:example', + registryDependencies: ['input', 'button'], + files: ['example/input-with-button.tsx'], + }, + { + name: 'input-with-label', + type: 'components:example', + registryDependencies: ['input', 'button', 'label'], + files: ['example/input-with-label.tsx'], + }, + { + name: 'input-with-text', + type: 'components:example', + registryDependencies: ['input', 'button', 'label'], + files: ['example/input-with-text.tsx'], + }, + { + name: 'label-demo', + type: 'components:example', + registryDependencies: ['label'], + files: ['example/label-demo.tsx'], + }, + { + name: 'menubar-demo', + type: 'components:example', + registryDependencies: ['menubar'], + files: ['example/menubar-demo.tsx'], + }, + { + name: 'navigation-menu-demo', + type: 'components:example', + registryDependencies: ['navigation-menu'], + files: ['example/navigation-menu-demo.tsx'], + }, + { + name: 'popover-demo', + type: 'components:example', + registryDependencies: ['popover'], + files: ['example/popover-demo.tsx'], + }, + { + name: 'progress-demo', + type: 'components:example', + registryDependencies: ['progress'], + files: ['example/progress-demo.tsx'], + }, + { + name: 'radio-group-demo', + type: 'components:example', + registryDependencies: ['radio-group'], + files: ['example/radio-group-demo.tsx'], + }, + { + name: 'radio-group-form', + type: 'components:example', + registryDependencies: ['radio-group', 'form'], + files: ['example/radio-group-form.tsx'], + }, + { + name: 'scroll-area-demo', + type: 'components:example', + registryDependencies: ['scroll-area'], + files: ['example/scroll-area-demo.tsx'], + }, + { + name: 'select-demo', + type: 'components:example', + registryDependencies: ['select'], + files: ['example/select-demo.tsx'], + }, + { + name: 'select-form', + type: 'components:example', + registryDependencies: ['select'], + files: ['example/select-form.tsx'], + }, + { + name: 'separator-demo', + type: 'components:example', + registryDependencies: ['separator'], + files: ['example/separator-demo.tsx'], + }, + { + name: 'sheet-demo', + type: 'components:example', + registryDependencies: ['sheet'], + files: ['example/sheet-demo.tsx'], + }, + { + name: 'sheet-side', + type: 'components:example', + registryDependencies: ['sheet'], + files: ['example/sheet-side.tsx'], + }, + { + name: 'skeleton-demo', + type: 'components:example', + registryDependencies: ['skeleton'], + files: ['example/skeleton-demo.tsx'], + }, + { + name: 'slider-demo', + type: 'components:example', + registryDependencies: ['slider'], + files: ['example/slider-demo.tsx'], + }, + { + name: 'switch-demo', + type: 'components:example', + registryDependencies: ['switch'], + files: ['example/switch-demo.tsx'], + }, + { + name: 'switch-form', + type: 'components:example', + registryDependencies: ['switch', 'form'], + files: ['example/switch-form.tsx'], + }, + { + name: 'table-demo', + type: 'components:example', + registryDependencies: ['table'], + files: ['example/table-demo.tsx'], + }, + { + name: 'tabs-demo', + type: 'components:example', + registryDependencies: ['tabs'], + files: ['example/tabs-demo.tsx'], + }, + { + name: 'textarea-demo', + type: 'components:example', + registryDependencies: ['textarea'], + files: ['example/textarea-demo.tsx'], + }, + { + name: 'textarea-disabled', + type: 'components:example', + registryDependencies: ['textarea'], + files: ['example/textarea-disabled.tsx'], + }, + { + name: 'textarea-form', + type: 'components:example', + registryDependencies: ['textarea', 'form'], + files: ['example/textarea-form.tsx'], + }, + { + name: 'textarea-with-button', + type: 'components:example', + registryDependencies: ['textarea', 'button'], + files: ['example/textarea-with-button.tsx'], + }, + { + name: 'textarea-with-label', + type: 'components:example', + registryDependencies: ['textarea', 'label'], + files: ['example/textarea-with-label.tsx'], + }, + { + name: 'textarea-with-text', + type: 'components:example', + registryDependencies: ['textarea', 'label'], + files: ['example/textarea-with-text.tsx'], + }, + { + name: 'toast-demo', + type: 'components:example', + registryDependencies: ['toast'], + files: ['example/toast-demo.tsx'], + }, + { + name: 'toast-destructive', + type: 'components:example', + registryDependencies: ['toast'], + files: ['example/toast-destructive.tsx'], + }, + { + name: 'toast-simple', + type: 'components:example', + registryDependencies: ['toast'], + files: ['example/toast-simple.tsx'], + }, + { + name: 'toast-with-action', + type: 'components:example', + registryDependencies: ['toast'], + files: ['example/toast-with-action.tsx'], + }, + { + name: 'toast-with-title', + type: 'components:example', + registryDependencies: ['toast'], + files: ['example/toast-with-title.tsx'], + }, + { + name: 'toggle-demo', + type: 'components:example', + registryDependencies: ['toggle'], + files: ['example/toggle-demo.tsx'], + }, + { + name: 'toggle-disabled', + type: 'components:example', + registryDependencies: ['toggle'], + files: ['example/toggle-disabled.tsx'], + }, + { + name: 'toggle-lg', + type: 'components:example', + registryDependencies: ['toggle'], + files: ['example/toggle-lg.tsx'], + }, + { + name: 'toggle-outline', + type: 'components:example', + registryDependencies: ['toggle'], + files: ['example/toggle-outline.tsx'], + }, + { + name: 'toggle-sm', + type: 'components:example', + registryDependencies: ['toggle'], + files: ['example/toggle-sm.tsx'], + }, + { + name: 'toggle-with-text', + type: 'components:example', + registryDependencies: ['toggle'], + files: ['example/toggle-with-text.tsx'], + }, + { + name: 'tooltip-demo', + type: 'components:example', + registryDependencies: ['tooltip'], + files: ['example/tooltip-demo.tsx'], + }, + { + name: 'typography-blockquote', + type: 'components:example', + files: ['example/typography-blockquote.tsx'], + }, + { + name: 'typography-demo', + type: 'components:example', + files: ['example/typography-demo.tsx'], + }, + { + name: 'typography-h1', + type: 'components:example', + files: ['example/typography-h1.tsx'], + }, + { + name: 'typography-h2', + type: 'components:example', + files: ['example/typography-h2.tsx'], + }, + { + name: 'typography-h3', + type: 'components:example', + files: ['example/typography-h3.tsx'], + }, + { + name: 'typography-h4', + type: 'components:example', + files: ['example/typography-h4.tsx'], + }, + { + name: 'typography-inline-code', + type: 'components:example', + files: ['example/typography-inline-code.tsx'], + }, + { + name: 'typography-large', + type: 'components:example', + files: ['example/typography-large.tsx'], + }, + { + name: 'typography-lead', + type: 'components:example', + files: ['example/typography-lead.tsx'], + }, + { + name: 'typography-list', + type: 'components:example', + files: ['example/typography-list.tsx'], + }, + { + name: 'typography-muted', + type: 'components:example', + files: ['example/typography-muted.tsx'], + }, + { + name: 'typography-p', + type: 'components:example', + files: ['example/typography-p.tsx'], + }, + { + name: 'typography-small', + type: 'components:example', + files: ['example/typography-small.tsx'], + }, + { + name: 'typography-table', + type: 'components:example', + files: ['example/typography-table.tsx'], + }, + { + name: 'mode-toggle', + type: 'components:example', + files: ['example/mode-toggle.tsx'], + }, + { + name: 'cards', + type: 'components:example', + files: ['example/cards/cards.tsx'], + }, +] + +export const registry: Registry = [...ui, ...example] diff --git a/apps/www/src/lib/registry/schema.ts b/apps/www/src/lib/registry/schema.ts new file mode 100644 index 00000000..36b22454 --- /dev/null +++ b/apps/www/src/lib/registry/schema.ts @@ -0,0 +1,17 @@ +import * as z from 'zod' + +export const registrySchema = z.array( + z.object({ + name: z.string(), + dependencies: z.array(z.string()).optional(), + registryDependencies: z.array(z.string()).optional(), + files: z.array(z.string()), + type: z.enum([ + 'components:ui', + 'components:component', + 'components:example', + ]), + }), +) + +export type Registry = z.infer diff --git a/apps/www/src/lib/registry/styles.ts b/apps/www/src/lib/registry/styles.ts new file mode 100644 index 00000000..4387500a --- /dev/null +++ b/apps/www/src/lib/registry/styles.ts @@ -0,0 +1,12 @@ +export const styles = [ + { + name: 'default', + label: 'Default', + }, + { + name: 'new-york', + label: 'New York', + }, +] as const + +export type Style = (typeof styles)[number] diff --git a/apps/www/src/lib/registry/default/theme.ts b/apps/www/src/lib/registry/themes.ts similarity index 100% rename from apps/www/src/lib/registry/default/theme.ts rename to apps/www/src/lib/registry/themes.ts diff --git a/package.json b/package.json index b580fe44..232adba9 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "lint": "eslint . --ignore-path .gitignore", "lint:fix": "eslint . --fix --ignore-path .gitignore", "dev:cli": "pnpm --filter shadcn-vue dev", - "build:cli": "pnpm --filter shadcn-vue build" + "build:cli": "pnpm --filter shadcn-vue build", + "build:registry": "pnpm --filter=www build:registry" }, "devDependencies": { "@antfu/eslint-config": "^0.39.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 707141ed..3355092f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: '@iconify/vue': specifier: ^4.1.1 version: 4.1.1(vue@3.3.4) + '@types/lodash.template': + specifier: ^4.5.1 + version: 4.5.1 '@types/node': specifier: ^20.5.7 version: 20.5.7 @@ -111,12 +114,18 @@ importers: autoprefixer: specifier: ^10.4.14 version: 10.4.14(postcss@8.4.24) + lodash.template: + specifier: ^4.5.0 + version: 4.5.0 postcss: specifier: ^8.4.24 version: 8.4.24 radix-vue: specifier: ^0.1.30 version: 0.1.30(vue@3.3.4) + rimraf: + specifier: ^5.0.1 + version: 5.0.1 tailwind-merge: specifier: ^1.14.0 version: 1.14.0 @@ -5550,7 +5559,6 @@ packages: /lodash._reinterpolate@3.0.0: resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} - dev: false /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -5593,13 +5601,11 @@ packages: dependencies: lodash._reinterpolate: 3.0.0 lodash.templatesettings: 4.2.0 - dev: false /lodash.templatesettings@4.2.0: resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} dependencies: lodash._reinterpolate: 3.0.0 - dev: false /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}