From 355843b054f1267efc36a2ce762772c715ea8d5b Mon Sep 17 00:00:00 2001 From: HoshinoSuzumi Date: Fri, 22 Nov 2024 01:23:09 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20docs(interactive):=20add=20inter?= =?UTF-8?q?active=20props?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/components/content/ComponentPreview.vue | 68 +++++++++++++++++++- docs/composables/useComponentMeta.ts | 39 +++++++++++ docs/content/2.components/button.md | 63 +++++++----------- docs/nuxt.config.ts | 53 +++++++++++---- docs/package.json | 1 + pnpm-lock.yaml | 43 +++++++++++++ 6 files changed, 209 insertions(+), 58 deletions(-) create mode 100644 docs/composables/useComponentMeta.ts diff --git a/docs/components/content/ComponentPreview.vue b/docs/components/content/ComponentPreview.vue index 9fc6523..ca5af5f 100644 --- a/docs/components/content/ComponentPreview.vue +++ b/docs/components/content/ComponentPreview.vue @@ -6,6 +6,7 @@ import FileTypeJavascript from '../icon/VscodeIconsFileTypeJsOfficial.vue' import TablerTerminal from '../icon/TablerTerminal.vue' const route = useRoute() +const appConfig = useAppConfig() const IconComponents = { 'vue': FileTypeVue, @@ -38,9 +39,52 @@ const props = defineProps({ }, }) + const componentName = props.slug || `Ray${upperFirst(camelCase(route.params.slug[route.params.slug.length - 1]))}` +const componentMeta = await fetchComponentMeta(componentName) + const componentProps = reactive({ ...props.props }) +const customizableOptions = (key: string, schema: { kind: string, type: string, schema: [] }) => { + let options: string[] = []; + const invalidTypes = ['string', 'array', 'boolean', 'object', 'number', 'Function'] + const hasInvalidType = schema?.type?.split('|')?.map(item => item.trim()?.replaceAll('"', ''))?.some(type => invalidTypes.includes(type)) + const schemaOptions = Object.values(schema?.schema || {}) + + if (key.toLowerCase().endsWith('color')) { + options = [...appConfig.rayui.colors] + } + + if (key.toLowerCase() === 'size' && schemaOptions?.length) { + const baseSizeOrder = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }; + schemaOptions.sort((a: string, b: string) => { + const [aBase, aNum] = [(a.match(/[a-z]+/i)?.[0].toLowerCase() || 'xs') as keyof typeof baseSizeOrder, parseInt(a.match(/\d+/)?.[0] || '1')]; + const [bBase, bNum] = [(b.match(/[a-z]+/i)?.[0].toLowerCase() || 'xs') as keyof typeof baseSizeOrder, parseInt(b.match(/\d+/)?.[0] || '1')]; + return aBase === bBase + ? (aBase === 'xs' ? bNum - aNum : aNum - bNum) + : baseSizeOrder[aBase] - baseSizeOrder[bBase]; + }); + } + + if (schemaOptions?.length > 0 && schema?.kind === 'enum' && !hasInvalidType) { + options = schemaOptions.filter(option => option !== 'undefined' && typeof option === 'string').map((option: string) => option.replaceAll('"', '')) + } + + return options +} + +const customizableProps = computed(() => Object.keys(componentProps).map(k => { + const prop = componentMeta?.meta?.props?.find((prop: any) => prop.name === k) + const schema = prop?.schema || {} + const options = customizableOptions(k, schema) + return { + name: k, + type: prop?.type, + label: camelCase(k), + options, + } +})) + const code = computed(() => { let code = `\`\`\`html - + diff --git a/docs/composables/useComponentMeta.ts b/docs/composables/useComponentMeta.ts new file mode 100644 index 0000000..a8af2ed --- /dev/null +++ b/docs/composables/useComponentMeta.ts @@ -0,0 +1,39 @@ +interface ComponentMetaState { + [key: string]: any; +} + +const useComponentsMetaState = () => + useState("components-meta", () => ({})); + +export const fetchComponentMeta = async (name: string) => { + const state = useComponentsMetaState(); + + if (state.value[name]?.then) { + await state.value[name]; + return state.value[name]; + } + if (state.value[name]) { + return state.value[name]; + } + + if (import.meta.server) { + const event = useRequestEvent(); + if (event && event.node && event.node.res) { + event.node.res.setHeader( + "x-nitro-prerender", + [ + event.node.res.getHeader("x-nitro-prerender"), + `/api/component-meta/${name}.json`, + ].filter(Boolean) as string[] + ); + } + } + state.value[name] = $fetch(`/api/component-meta/${name}.json`).then( + (meta) => { + state.value[name] = meta; + } + ); + + await state.value[name]; + return state.value[name]; +}; diff --git a/docs/content/2.components/button.md b/docs/content/2.components/button.md index e04eb86..00ee2eb 100644 --- a/docs/content/2.components/button.md +++ b/docs/content/2.components/button.md @@ -10,58 +10,39 @@ Default button style Button :: -### Variants +### Styles -#### soft +Use the `variant` and `color` props to predefined styles and change the color of buttons. ::ComponentPreview --- props: variant: soft ---- -Button -:: - -#### outline - -::ComponentPreview ---- -props: - variant: outline ---- -Button -:: - -#### ghost - -::ComponentPreview ---- -props: - variant: ghost ---- -Button -:: - -#### link - -::ComponentPreview ---- -props: - variant: link ---- -Button -:: - -### Colors - -::ComponentPreview ---- -props: color: violet --- Button :: +### Sizes + +::ComponentPreview +--- +props: + size: sm +--- +Button +:: + +### Block + +::ComponentPreview +--- +props: + block: true +--- +Button +:: + ### Disabled ::ComponentPreview diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index dd5138f..2bd92ca 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -1,48 +1,73 @@ -import defaultTheme from 'tailwindcss/defaultTheme' -import module from '../src/module' +import { createResolver } from "@nuxt/kit"; +import defaultTheme from "tailwindcss/defaultTheme"; +import module from "../src/module"; + +const { resolve } = createResolver(import.meta.url); // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ - modules: ['@nuxt/content', '@nuxt/fonts', '@nuxtjs/color-mode', module], + modules: [ + "@nuxt/content", + "@nuxt/fonts", + "@nuxtjs/color-mode", + module, + "nuxt-component-meta", + ], devtools: { enabled: true }, colorMode: { - preference: 'system', - classSuffix: '', + preference: "system", + classSuffix: "", }, content: { highlight: { - langs: ['postcss', 'mdc', 'html', 'vue', 'ts', 'js', 'bash'], + langs: ["postcss", "mdc", "html", "vue", "ts", "js", "bash"], }, }, mdc: { highlight: { theme: { - light: 'material-theme-lighter', - dark: 'material-theme', + light: "material-theme-lighter", + dark: "material-theme", }, - themes: ['material-theme-lighter', 'material-theme'], + themes: ["material-theme-lighter", "material-theme"], }, }, routeRules: { - '/components': { redirect: '/components/button' }, + "/components": { redirect: "/components/button" }, }, - compatibilityDate: '2024-04-03', + componentMeta: { + exclude: [ + "@nuxt/content", + "@nuxtjs/color-mode", + "@nuxtjs/mdc", + "nuxt/dist", + resolve("./components"), + ], + metaFields: { + type: false, + props: true, + slots: true, + events: false, + exposed: false, + }, + }, + compatibilityDate: "2024-04-03", typescript: { includeWorkspace: true, }, rayui: { globalComponents: true, - safeColors: ['amber', 'emerald', 'red', 'sky', 'violet', 'cyan'], + safeColors: ["amber", "emerald", "red", "sky", "violet", "cyan"], }, tailwindcss: { config: { theme: { extend: { fontFamily: { - sans: ['Rubik', '"Noto Sans SC"', ...defaultTheme.fontFamily.sans], + sans: ["Rubik", '"Noto Sans SC"', ...defaultTheme.fontFamily.sans], }, }, }, }, }, -}) +}); diff --git a/docs/package.json b/docs/package.json index 013a4dc..039be76 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,6 +13,7 @@ "@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/mdc": "^0.9.2", "nuxt": "^3.14.159", + "nuxt-component-meta": "^0.9.0", "nuxt-shiki": "^0.3.0", "rayine-ui": "workspace:rayine-ui", "ufo": "^1.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af03447..627ff77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,6 +87,9 @@ importers: nuxt: specifier: ^3.14.159 version: 3.14.159(@parcel/watcher@2.5.0)(@types/node@22.9.1)(eslint@9.15.0(jiti@2.4.0))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.27.2)(terser@5.36.0)(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.1)(terser@5.36.0))(vue-tsc@2.1.10(typescript@5.6.3)) + nuxt-component-meta: + specifier: ^0.9.0 + version: 0.9.0(magicast@0.3.5)(rollup@4.27.2) nuxt-shiki: specifier: ^0.3.0 version: 0.3.0(magicast@0.3.5)(rollup@4.27.2) @@ -3546,6 +3549,10 @@ packages: engines: {node: ^16.10.0 || >=18.0.0} hasBin: true + nuxt-component-meta@0.9.0: + resolution: {integrity: sha512-Zmuw/PxAeJkIu7EPZEFg0lIwnfpeiDsHog9i1g5d4Z3O5+00R1Vbuk+NC6K7kgpdLsdU2/XBvzc7oQ6BsXuQPg==} + hasBin: true + nuxt-shiki@0.3.0: resolution: {integrity: sha512-edeP230QQmQ02PdquCjE0noM8+RNTrpjJk1pfR1tIJ5gBp1DegzTe4WdFUWXu7sjSiIndfDy2KbQ9SPscGROJw==} @@ -4944,6 +4951,17 @@ packages: vue-bundle-renderer@2.1.1: resolution: {integrity: sha512-+qALLI5cQncuetYOXp4yScwYvqh8c6SMXee3B+M7oTZxOgtESP0l4j/fXdEJoZ+EdMxkGWIj+aSEyjXkOdmd7g==} + vue-component-meta@2.1.10: + resolution: {integrity: sha512-YEFSau36lLCJNvoM6eynAcq891Y6HKIEdEk3PCzCyNVySeYJAXgE/9iCYqQzLtBJlKg/bBpImz8VbUZsh4N/7Q==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vue-component-type-helpers@2.1.10: + resolution: {integrity: sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==} + vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} @@ -9509,6 +9527,20 @@ snapshots: nuxi@3.15.0: {} + nuxt-component-meta@0.9.0(magicast@0.3.5)(rollup@4.27.2): + dependencies: + '@nuxt/kit': 3.14.159(magicast@0.3.5)(rollup@4.27.2) + citty: 0.1.6 + mlly: 1.7.3 + scule: 1.3.0 + typescript: 5.6.3 + ufo: 1.5.4 + vue-component-meta: 2.1.10(typescript@5.6.3) + transitivePeerDependencies: + - magicast + - rollup + - supports-color + nuxt-shiki@0.3.0(magicast@0.3.5)(rollup@4.27.2): dependencies: '@nuxt/kit': 3.14.159(magicast@0.3.5)(rollup@4.27.2) @@ -11380,6 +11412,17 @@ snapshots: dependencies: ufo: 1.5.4 + vue-component-meta@2.1.10(typescript@5.6.3): + dependencies: + '@volar/typescript': 2.4.10 + '@vue/language-core': 2.1.10(typescript@5.6.3) + path-browserify: 1.0.1 + vue-component-type-helpers: 2.1.10 + optionalDependencies: + typescript: 5.6.3 + + vue-component-type-helpers@2.1.10: {} + vue-demi@0.14.10(vue@3.5.13(typescript@5.6.3)): dependencies: vue: 3.5.13(typescript@5.6.3)