feat(module): devtools integration (#2196)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Romain Hamel
2024-11-05 22:17:56 +01:00
committed by GitHub
parent 7fc6b387b3
commit 701c75a2a8
100 changed files with 2062 additions and 59 deletions

View File

@@ -22,6 +22,9 @@ export const defaultOptions = {
theme: {
colors: undefined,
transitions: true
},
devtools: {
enabled: true
}
}

148
src/devtools/meta.ts Normal file
View File

@@ -0,0 +1,148 @@
import type { ViteDevServer } from 'vite'
import { kebabCase, camelCase } from 'scule'
import defu from 'defu'
import fs from 'node:fs'
import type { Resolver } from '@nuxt/kit'
import type { ComponentMeta } from 'vue-component-meta'
import type { DevtoolsMeta } from '../runtime/composables/extendDevtoolsMeta'
import type { ModuleOptions } from '../module'
export type Component = {
slug: string
label: string
meta?: ComponentMeta & { devtools: DevtoolsMeta<any> }
defaultVariants: Record<string, any>
}
const devtoolsComponentMeta: Record<string, any> = {}
function extractDevtoolsMeta(code: string): string | null {
const match = code.match(/extendDevtoolsMeta(?:<.*?>)?\(/)
if (!match) return null
const startIndex = code.indexOf(match[0]) + match[0].length
let openBraceCount = 0
let closeBraceCount = 0
let endIndex = startIndex
for (let i = startIndex; i < code.length; i++) {
if (code[i] === '{') openBraceCount++
if (code[i] === '}') closeBraceCount++
if (openBraceCount > 0 && openBraceCount === closeBraceCount) {
endIndex = i + 1
break
}
}
// Return only the object inside extendDevtoolsMeta
return code.slice(startIndex, endIndex).trim()
}
// A Plugin to parse additional metadata for the Nuxt UI Devtools.
export function devtoolsMetaPlugin({ resolve, options, templates }: { resolve: Resolver['resolve'], options: ModuleOptions, templates: Record<string, any> }) {
return {
name: 'ui-devtools-component-meta',
enforce: 'pre' as const,
async transform(code: string, id: string) {
if (!id.match(/\/runtime\/components\/\w+.vue/)) return
const fileName = id.split('/')[id.split('/').length - 1]
if (code && fileName) {
const slug = kebabCase(fileName.replace(/\..*/, ''))
const match = extractDevtoolsMeta(code)
if (match) {
const metaObject = new Function(`return ${match}`)()
devtoolsComponentMeta[slug] = { meta: { devtools: { ...metaObject } } }
}
}
return {
code
}
},
configureServer(server: ViteDevServer) {
server.middlewares.use('/__nuxt_ui__/devtools/api/component-meta', async (_req, res) => {
res.setHeader('Content-Type', 'application/json')
try {
const componentMeta = await import('./.component-meta/component-meta')
const meta = defu(
Object.entries(componentMeta.default).reduce((acc, [key, value]: [string, any]) => {
if (!key.startsWith('U')) return acc
const name = key.substring(1)
const slug = kebabCase(name)
const template = templates?.[camelCase(name)]
if (devtoolsComponentMeta[slug] === undefined) {
const path = resolve(`./runtime/components/${name}.vue`)
const code = fs.readFileSync(path, 'utf-8')
const match = extractDevtoolsMeta(code)
if (match) {
const metaObject = new Function(`return ${match}`)()
devtoolsComponentMeta[slug] = { meta: { devtools: { ...metaObject } } }
} else {
devtoolsComponentMeta[slug] = null
}
}
value.meta.props = value.meta.props.map((prop: any) => {
let defaultValue = prop.default
? prop.default
: prop?.tags?.find((tag: any) =>
tag.name === 'defaultValue'
&& !tag.text?.includes('appConfig'))?.text
?? template?.defaultVariants?.[prop.name]
if (typeof defaultValue === 'string') defaultValue = defaultValue?.replaceAll(/["'`]/g, '')
if (defaultValue === 'true') defaultValue = true
if (defaultValue === 'false') defaultValue = false
if (!Number.isNaN(Number.parseInt(defaultValue))) defaultValue = Number.parseInt(defaultValue)
return {
...prop,
default: defaultValue
}
})
const label = key.replace(/^U/, options.prefix ?? 'U')
acc[kebabCase(key.replace(/^U/, ''))] = { ...value, label, slug }
return acc
}, {} as Record<string, any>),
devtoolsComponentMeta
)
res.end(JSON.stringify(meta))
} catch (error) {
console.error(`Failed to fetch component meta`, error)
res.statusCode = 500
res.end(JSON.stringify({ error: 'Failed to fetch component meta' }))
}
})
server.middlewares.use('/__nuxt_ui__/devtools/api/component-example', async (req, res) => {
const query = new URL(req.url!, 'http://localhost').searchParams
const name = query.get('component')
if (!name) {
res.statusCode = 400
res.end(JSON.stringify({ error: 'Component name is required' }))
return
}
try {
const path = resolve(`./devtools/runtime/examples/${name}.vue`)
const source = fs.readFileSync(path, 'utf-8')
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ component: name, source }))
} catch (error) {
console.error(`Failed to read component source for ${name}:`, error)
res.statusCode = 500
res.end(JSON.stringify({ error: 'Failed to read component source' }))
}
})
}
}
}

View File

@@ -0,0 +1,71 @@
<script setup lang="ts">
import { onUnmounted, onMounted, reactive } from 'vue'
import { pascalCase } from 'scule'
import { defineAsyncComponent, useColorMode, useRoute } from '#imports'
const route = useRoute()
const component = route.query?.example
? defineAsyncComponent(() => import(`./examples/${route.query.example}.vue`))
: route.params?.slug && defineAsyncComponent(() => import(`../../runtime/components/${pascalCase(route.params.slug as string)}.vue`))
const state = reactive<{ slots?: any, props?: any }>({})
function onUpdateRenderer(event: Event & { data?: any }) {
state.props = { ...event.data.props }
state.slots = { ...event.data.slots }
}
const colorMode = useColorMode()
function setColorMode(event: Event & { isDark?: boolean }) {
colorMode.preference = event.isDark ? 'dark' : 'light'
}
onMounted(() => {
window.parent.addEventListener('nuxt-ui-devtools:update-renderer', onUpdateRenderer)
window.parent.addEventListener('nuxt-ui-devtools:set-color-mode', setColorMode)
})
onUnmounted(() => {
window.parent.removeEventListener('nuxt-ui-devtools:update-renderer', onUpdateRenderer)
window.parent.removeEventListener('nuxt-ui-devtools:set-color-mode', setColorMode)
})
onMounted(async () => {
const event: Event = new Event('nuxt-ui-devtools:component-loaded')
window.parent.dispatchEvent(event)
})
onMounted(() => {
if (!route.query?.example) return
})
</script>
<template>
<div id="ui-devtools-renderer" class="nuxt-ui-component-renderer">
<UApp :toaster="null">
<component :is="component" v-if="component" v-bind="state.props" :class="state?.slots?.base" :ui="state.slots" />
</UApp>
</div>
</template>
<style>
.nuxt-ui-component-renderer {
position: 'relative';
height: 100vh;
width: 100vw;
padding: 32px;
display: flex;
justify-content: center;
align-items: center;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' transform='scale(3)'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cpath fill='none' stroke='hsla(0, 0%25, 98%25, 1)' stroke-width='.2' d='M10 0v20ZM0 10h20Z'/%3E%3C/svg%3E");
background-size: 40px 40px;
}
.dark .nuxt-ui-component-renderer {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' transform='scale(3)'%3E%3Crect width='100%25' height='100%25' fill='hsl(0, 0%25, 8.5%25)'/%3E%3Cpath fill='none' stroke='hsl(0, 0%25, 11.0%25)' stroke-width='.2' d='M10 0v20ZM0 10h20Z'/%3E%3C/svg%3E");
background-size: 40px 40px;
}
</style>

View File

@@ -0,0 +1,7 @@
<template>
<UAvatarGroup>
<UAvatar src="https://github.com/benjamincanac.png" alt="Benjamin Canac" />
<UAvatar src="https://github.com/romhml.png" alt="Romain Hamel" />
<UAvatar src="https://github.com/noook.png" alt="Neil Richter" />
</UAvatarGroup>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<UButtonGroup>
<UInput placeholder="Search..." />
<UButton color="neutral" variant="outline">
Button
</UButton>
</UButtonGroup>
</template>

View File

@@ -0,0 +1,13 @@
<template>
<div class="flex flex-col gap-4">
<UCard class="w-96">
<template #header>
<div class="bg-[var(--ui-bg-accented)]/40 h-8" />
</template>
<div class="bg-[var(--ui-bg-accented)]/40 h-32" />
<template #footer>
<div class="bg-[var(--ui-bg-accented)]/40 h-8" />
</template>
</UCard>
</div>
</template>

View File

@@ -0,0 +1,13 @@
<template>
<UCarousel
v-slot="{ item }"
class="basis-1/3"
:items="[
'https://picsum.photos/320/320?v=1',
'https://picsum.photos/320/320?v=2',
'https://picsum.photos/320/320?v=3'
]"
>
<img :src="item" class="rounded-lg basis-1/3">
</UCarousel>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UChip>
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" />
</UChip>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<UCollapsible class="w-48">
<UButton label="Open Collapse" block />
<template #content>
<div class="bg-[var(--ui-bg-accented)]/40 h-60" />
</template>
</UCollapsible>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
const groups = [{
id: 'actions',
items: [{
label: 'Add new file',
suffix: 'Create a new file in the current directory or workspace.',
icon: 'i-heroicons-document-plus'
}, {
label: 'Add new folder',
suffix: 'Create a new folder in the current directory or workspace.',
icon: 'i-heroicons-folder-plus',
kbds: ['meta', 'F']
}, {
label: 'Add hashtag',
suffix: 'Add a hashtag to the current item.',
icon: 'i-heroicons-hashtag',
kbds: ['meta', 'H']
}, {
label: 'Add label',
suffix: 'Add a label to the current item.',
icon: 'i-heroicons-tag',
kbds: ['meta', 'L']
}]
}]
</script>
<template>
<UCommandPalette :groups="groups" />
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UContainer>
<div class="bg-[var(--ui-bg-accented)]/40 h-60 aspect-video w-72" />
</UContainer>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UContextMenu>
<div class="bg-[var(--ui-bg-accented)]/40 h-60 w-72" />
</UContextMenu>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<UDrawer>
<UButton label="Open Drawer" />
<template #body>
<div class="size-96" />
</template>
</UDrawer>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UDropdownMenu>
<UButton label="Open Dropdown" />
</UDropdownMenu>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { reactive } from 'vue'
const state = reactive({ email: undefined, password: undefined })
function validate(data: Partial<typeof state>) {
const errors: Array<{ name: string, message: string }> = []
if (!data.email) errors.push({ name: 'email', message: 'Required' })
if (!data.password) errors.push({ name: 'password', message: 'Required' })
return errors
}
</script>
<template>
<UForm
:validate="validate"
:state="state"
class="space-y-4"
>
<UFormField name="email" label="Email">
<UInput v-model="state.email" />
</UFormField>
<UFormField name="password" label="Password">
<UInput v-model="state.password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UFormField>
<UInput />
</UFormField>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<ULink>
Link
</ULink>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<UModal>
<UButton label="Open Modal" />
<template #content>
<div class="h-72" />
</template>
</UModal>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<UPopover>
<UButton label="Open Collapse" />
<template #content>
<div class="bg-[var(--ui-bg-accented)]/40 h-24 w-60" />
</template>
</UPopover>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<USkeleton class="h-32 w-96" />
</template>

View File

@@ -0,0 +1,8 @@
<template>
<USlideover>
<UButton label="Open Slideover" />
<template #body>
<div class="size-96" />
</template>
</USlideover>
</template>

View File

@@ -0,0 +1,11 @@
<script setup>
import { useToast } from '#imports'
const toast = useToast()
</script>
<template>
<UToaster>
<UButton label="Open toast" @click="toast.add({ title: 'Heads up!' })" />
</UToaster>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UTooltip>
<div class="bg-[var(--ui-bg-accented)]/40 size-20" />
</UTooltip>
</template>

View File

@@ -1,6 +1,10 @@
import { defu } from 'defu'
import { createResolver, defineNuxtModule, addComponentsDir, addImportsDir, addVitePlugin, addPlugin, installModule, hasNuxtModule } from '@nuxt/kit'
import { addTemplates } from './templates'
import { createResolver, defineNuxtModule, addComponentsDir, addImportsDir, addVitePlugin, addPlugin, installModule, extendPages, hasNuxtModule } from '@nuxt/kit'
import { addTemplates, buildTemplates } from './templates'
import { addCustomTab, startSubprocess } from '@nuxt/devtools-kit'
import sirv from 'sirv'
import { getPort } from 'get-port-please'
import { devtoolsMetaPlugin } from './devtools/meta'
import { defaultOptions, getDefaultUiConfig, resolveColors } from './defaults'
export type * from './runtime/types'
@@ -46,6 +50,17 @@ export interface ModuleOptions {
*/
transitions?: boolean
}
/**
* Configuration for the Nuxt UI devtools.
*/
devtools?: {
/**
* Enable or disable Nuxt UI devtools.
* @defaultValue `true`
*/
enabled?: boolean
}
}
export default defineNuxtModule<ModuleOptions>({
@@ -58,6 +73,7 @@ export default defineNuxtModule<ModuleOptions>({
docs: 'https://ui3.nuxt.dev/getting-started/installation'
},
defaults: defaultOptions,
async setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
@@ -110,5 +126,77 @@ export default defineNuxtModule<ModuleOptions>({
addImportsDir(resolve('./runtime/composables'))
addTemplates(options, nuxt)
if (nuxt.options.dev && nuxt.options.devtools.enabled && options.devtools?.enabled) {
const templates = buildTemplates(options)
nuxt.options.vite = defu(nuxt.options?.vite, { plugins: [devtoolsMetaPlugin({ resolve, templates, options })] })
// Runs UI devtools in a subprocess for local development
if (process.env.NUXT_UI_DEVTOOLS_LOCAL) {
const PORT = await getPort({ port: 42124 })
nuxt.hook('app:resolve', () => {
startSubprocess(
{
command: 'pnpm',
args: ['nuxi', 'dev'],
cwd: './devtools',
stdio: 'pipe',
env: {
PORT: PORT.toString()
}
},
{
id: 'ui:devtools:local',
name: 'Nuxt UI DevTools Local',
icon: 'logos-nuxt-icon'
},
nuxt
)
})
nuxt.hook('vite:extendConfig', (config) => {
config.server ||= {}
config.server.proxy ||= {}
config.server.proxy['/__nuxt_ui__/devtools'] = {
target: `http://localhost:${PORT}`,
changeOrigin: true,
followRedirects: true,
ws: true,
rewriteWsOrigin: true
}
})
} else {
nuxt.hook('vite:serverCreated', async (server) => {
server.middlewares.use('/__nuxt_ui__/devtools', sirv(resolve('../dist/client/devtools'), {
single: true,
dev: true
}))
})
}
nuxt.options.routeRules = defu(nuxt.options.routeRules, { '/__nuxt_ui__/**': { ssr: false } })
extendPages((pages) => {
pages.unshift({
name: 'ui-devtools',
path: '/__nuxt_ui__/components/:slug',
file: resolve('./devtools/runtime/DevtoolsRenderer.vue'),
meta: {
// https://github.com/nuxt/nuxt/pull/29366
// isolate: true
layout: false
}
})
})
addCustomTab({
name: 'nuxt-ui',
title: 'Nuxt UI',
icon: '/__nuxt_ui__/devtools/favicon.svg',
view: {
type: 'iframe',
src: '/__nuxt_ui__/devtools'
}
})
}
}
})

View File

@@ -1,10 +1,10 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { AccordionRootProps, AccordionRootEmits } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/accordion'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { DynamicSlots } from '../types/utils'
const appConfig = _appConfig as AppConfig & { ui: { accordion: Partial<typeof theme> } }
@@ -55,6 +55,36 @@ export type AccordionSlots<T extends { slot?: string }> = {
body: SlotProps<T>
} & DynamicSlots<T, SlotProps<T>>
extendDevtoolsMeta({
defaultProps: {
items: [{
label: 'Getting Started',
icon: 'i-heroicons-information-circle',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
label: 'Installation',
icon: 'i-heroicons-arrow-down-tray',
disabled: true,
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
label: 'Theming',
icon: 'i-heroicons-eye-dropper',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
label: 'Layouts',
icon: 'i-heroicons-rectangle-group',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
label: 'Components',
icon: 'i-heroicons-square-3-stack-3d',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
label: 'Utilities',
icon: 'i-heroicons-wrench-screwdriver',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}]
}
})
</script>
<script setup lang="ts" generic="T extends AccordionItem">

View File

@@ -3,6 +3,7 @@ import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/alert'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, ButtonProps } from '../types'
const appConfig = _appConfig as AppConfig & { ui: { alert: Partial<typeof theme> } }
@@ -56,6 +57,8 @@ export interface AlertSlots {
actions(props?: {}): any
close(props: { ui: any }): any
}
extendDevtoolsMeta<AlertProps>({ defaultProps: { title: 'Heads up!' } })
</script>
<script setup lang="ts">

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import type { ConfigProviderProps, TooltipProviderProps } from 'radix-vue'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ToasterProps } from '../types'
export interface AppProps extends Omit<ConfigProviderProps, 'useId'> {
@@ -10,6 +11,12 @@ export interface AppProps extends Omit<ConfigProviderProps, 'useId'> {
export interface AppSlots {
default(props?: {}): any
}
export default {
name: 'App'
}
extendDevtoolsMeta({ ignore: true })
</script>
<script setup lang="ts">
@@ -34,6 +41,7 @@ const toasterProps = toRef(() => props.toaster)
<UToaster v-if="toaster !== null" v-bind="toasterProps">
<slot />
</UToaster>
<slot v-else />
</TooltipProvider>
<UModalProvider />

View File

@@ -2,6 +2,7 @@
import { tv, type VariantProps } from 'tailwind-variants'
import type { AvatarFallbackProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import _appConfig from '#build/app.config'
import theme from '#build/ui/avatar'
@@ -25,6 +26,8 @@ export interface AvatarProps extends Pick<AvatarFallbackProps, 'delayMs'> {
class?: any
ui?: Partial<typeof avatar.slots>
}
extendDevtoolsMeta<AvatarProps>({ defaultProps: { src: 'https://avatars.githubusercontent.com/u/739984?v=4', alt: 'Benjamin Canac' } })
</script>
<script setup lang="ts">

View File

@@ -3,6 +3,7 @@ import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/avatar-group'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { avatarGroup: Partial<typeof theme> } }
@@ -28,6 +29,8 @@ export interface AvatarGroupProps {
export interface AvatarGroupSlots {
default(props?: {}): any
}
extendDevtoolsMeta({ example: 'AvatarGroupExample' })
</script>
<script setup lang="ts">

View File

@@ -3,6 +3,7 @@ import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/badge'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import type { AvatarProps } from '../types'
@@ -51,6 +52,8 @@ const ui = computed(() => badge({
variant: props.variant,
size: props.size
}))
extendDevtoolsMeta({ defaultProps: { label: 'Badge' } })
</script>
<template>

View File

@@ -1,9 +1,9 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/breadcrumb'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
@@ -49,6 +49,30 @@ export type BreadcrumbSlots<T extends { slot?: string }> = {
'separator'(props?: {}): any
} & DynamicSlots<T, SlotProps<T>>
extendDevtoolsMeta({
defaultProps: {
items: [
{ label: 'Home', to: '/' },
{
slot: 'dropdown',
icon: 'i-heroicons-ellipsis-horizontal',
children: [{
label: 'Documentation'
}, {
label: 'Themes'
}, {
label: 'GitHub'
}]
}, {
label: 'Components',
disabled: true
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}
]
}
})
</script>
<script setup lang="ts" generic="T extends BreadcrumbItem">

View File

@@ -5,9 +5,9 @@ import _appConfig from '#build/app.config'
import theme from '#build/ui/button'
import type { LinkProps } from './Link.vue'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps } from '../types'
import type { PartialString } from '../types/utils'
import { formLoadingInjectionKey } from '../composables/useFormField'
const appConfig = _appConfig as AppConfig & { ui: { button: Partial<typeof theme> } }
@@ -31,6 +31,9 @@ export interface ButtonProps extends UseComponentIconsProps, Omit<LinkProps, 'ra
ui?: PartialString<typeof button.slots>
}
// Injects props to use as default in the devtools playground.
extendDevtoolsMeta<ButtonProps>({ defaultProps: { label: 'Click me!' } })
export interface ButtonSlots {
leading(props?: {}): any
default(props?: {}): any
@@ -43,6 +46,7 @@ import { type Ref, computed, ref, inject } from 'vue'
import { useForwardProps } from 'radix-vue'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useButtonGroup } from '../composables/useButtonGroup'
import { formLoadingInjectionKey } from '../composables/useFormField'
import { omit } from '../utils'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'

View File

@@ -3,6 +3,7 @@ import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/button-group'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { buttonGroup: Partial<typeof theme> } }
@@ -28,6 +29,8 @@ export interface ButtonGroupProps {
export interface ButtonGroupSlots {
default(props?: {}): any
}
extendDevtoolsMeta({ example: 'ButtonGroupExample' })
</script>
<script setup lang="ts">

View File

@@ -3,6 +3,7 @@ import { tv } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/card'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { card: Partial<typeof theme> } }
@@ -23,6 +24,8 @@ export interface CardSlots {
default(props?: {}): any
footer(props?: {}): any
}
extendDevtoolsMeta({ example: 'CardExample' })
</script>
<script setup lang="ts">

View File

@@ -1,4 +1,3 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
@@ -11,6 +10,7 @@ import type { FadeOptionsType } from 'embla-carousel-fade'
import type { WheelGesturesPluginOptions } from 'embla-carousel-wheel-gestures'
import _appConfig from '#build/app.config'
import theme from '#build/ui/carousel'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ButtonProps } from '../types'
import type { AcceptableValue, PartialString } from '../types/utils'
@@ -91,6 +91,7 @@ export type CarouselSlots<T> = {
default(props: { item: T, index: number }): any
}
extendDevtoolsMeta({ example: 'CarouselExample' })
</script>
<script setup lang="ts" generic="T extends AcceptableValue">

View File

@@ -4,6 +4,7 @@ import type { CheckboxRootProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/checkbox'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { checkbox: Partial<typeof theme> } }
@@ -42,6 +43,8 @@ export interface CheckboxSlots {
label(props: { label?: string }): any
description(props: { description?: string }): any
}
extendDevtoolsMeta({ defaultProps: { label: 'Check me!' } })
</script>
<script setup lang="ts">

View File

@@ -3,6 +3,7 @@ import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/chip'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { chip: Partial<typeof theme> } }
@@ -37,6 +38,8 @@ export interface ChipSlots {
default(props?: {}): any
content(props?: {}): any
}
extendDevtoolsMeta({ example: 'ChipExample' })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { CollapsibleRootProps, CollapsibleRootEmits } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/collapsible'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof theme> } }
@@ -25,6 +26,8 @@ export interface CollapsibleSlots {
default(props: { open: boolean }): any
content(props?: {}): any
}
extendDevtoolsMeta({ example: 'CollapsibleExample' })
</script>
<script setup lang="ts">

View File

@@ -1,4 +1,3 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits } from 'radix-vue'
@@ -8,6 +7,7 @@ import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import _appConfig from '#build/app.config'
import theme from '#build/ui/command-palette'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
@@ -114,6 +114,7 @@ export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?:
'item-trailing': SlotProps<T>
} & DynamicSlots<G, SlotProps<T>> & DynamicSlots<T, SlotProps<T>>
extendDevtoolsMeta({ example: 'CommandPaletteExample', ignoreProps: ['groups'] })
</script>
<script setup lang="ts" generic="G extends CommandPaletteGroup<T>, T extends CommandPaletteItem">

View File

@@ -3,6 +3,7 @@ import { tv } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/container'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { container: Partial<typeof theme> } }
@@ -20,6 +21,8 @@ export interface ContainerProps {
export interface ContainerSlots {
default(props?: {}): any
}
extendDevtoolsMeta({ example: 'ContainerExample' })
</script>
<script setup lang="ts">

View File

@@ -1,10 +1,10 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/context-menu'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
@@ -79,6 +79,66 @@ export type ContextMenuSlots<T extends { slot?: string }> = {
'item-trailing': SlotProps<T>
} & DynamicSlots<T, SlotProps<T>>
extendDevtoolsMeta({
example: 'ContextMenuExample',
ignoreProps: ['items'],
defaultProps: {
items: [
[{
label: 'My account',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
}
}],
[{
label: 'Appearance',
children: [{
label: 'System',
icon: 'i-heroicons-computer-desktop'
}, {
label: 'Light',
icon: 'i-heroicons-sun'
}, {
label: 'Dark',
icon: 'i-heroicons-moon'
}]
}],
[{
label: 'Show Sidebar',
kbds: ['meta', 'S']
}, {
label: 'Show Toolbar',
kbds: ['shift', 'meta', 'D']
}, {
label: 'Collapse Pinned Tabs',
disabled: true
}], [{
label: 'Refresh the Page'
}, {
label: 'Clear Cookies and Refresh'
}, {
label: 'Clear Cache and Refresh'
}, {
type: 'separator'
}, {
label: 'Developer',
children: [[{
label: 'View Source',
kbds: ['option', 'meta', 'U']
}, {
label: 'Developer Tools',
kbds: ['option', 'meta', 'I']
}], [{
label: 'Inspect Elements',
kbds: ['option', 'meta', 'C']
}], [{
label: 'JavaScript Console',
kbds: ['option', 'meta', 'J']
}]]
}]
]
}
})
</script>
<script setup lang="ts" generic="T extends ContextMenuItem">

View File

@@ -5,6 +5,7 @@ import type { DialogContentProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/drawer'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
@@ -51,6 +52,8 @@ export interface DrawerSlots {
body(props?: {}): any
footer(props?: {}): any
}
extendDevtoolsMeta({ example: 'DrawerExample' })
</script>
<script setup lang="ts">

View File

@@ -1,10 +1,10 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/dropdown-menu'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
@@ -87,6 +87,54 @@ export type DropdownMenuSlots<T extends { slot?: string }> = {
'item-trailing': SlotProps<T>
} & DynamicSlots<T, SlotProps<T>>
extendDevtoolsMeta({
example: 'DropdownMenuExample',
ignoreProps: ['items'],
defaultProps: {
items: [
[{
label: 'My account',
avatar: {
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
},
type: 'label'
}], [{
label: 'Profile',
icon: 'i-heroicons-user',
slot: 'custom'
}, {
label: 'Billing',
icon: 'i-heroicons-credit-card',
kbds: ['meta', 'b']
}, {
label: 'Settings',
icon: 'i-heroicons-cog',
kbds: ['?']
}], [{
label: 'Invite users',
icon: 'i-heroicons-user-plus',
children: [[{
label: 'Invite by email',
icon: 'i-heroicons-paper-airplane'
}, {
label: 'Invite by link',
icon: 'i-heroicons-link',
kbds: ['meta', 'i']
}]]
}],
[{
label: 'GitHub',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui',
target: '_blank'
}, {
label: 'Support',
icon: 'i-heroicons-lifebuoy',
to: '/components/dropdown-menu'
}]
]
}
})
</script>
<script setup lang="ts" generic="T extends DropdownMenuItem">

View File

@@ -3,6 +3,7 @@ import { tv } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/form'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
const appConfig = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
@@ -29,6 +30,8 @@ export interface FormEmits<T extends object> {
export interface FormSlots {
default(props?: {}): any
}
extendDevtoolsMeta({ example: 'FormExample' })
</script>
<script lang="ts" setup generic="T extends object">

View File

@@ -3,6 +3,7 @@ import { tv, type VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/form-field'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
@@ -33,6 +34,8 @@ export interface FormFieldSlots {
error(props: { error?: string | boolean }): any
default(props: { error?: string | boolean }): any
}
extendDevtoolsMeta({ example: 'FormFieldExample', defaultProps: { label: 'Label' } })
</script>
<script setup lang="ts">

View File

@@ -6,6 +6,7 @@ import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/input-menu'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type { AcceptableValue, ArrayOrWrapped, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
@@ -122,6 +123,8 @@ export interface InputMenuSlots<T> {
'tags-item-text': SlotProps<T>
'tags-item-delete': SlotProps<T>
}
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
</script>
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue> = MaybeArrayOfArray<InputMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">

View File

@@ -4,6 +4,7 @@ import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/kbd'
import type { KbdKey } from '../composables/useKbd'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
@@ -26,6 +27,7 @@ export interface KbdProps {
export interface KbdSlots {
default(props?: {}): any
}
extendDevtoolsMeta({ defaultProps: { value: 'K' } })
</script>
<script setup lang="ts">

View File

@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import type { RouterLinkProps, RouteLocationRaw } from 'vue-router'
import theme from '#build/ui/link'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
/**
@@ -87,6 +88,8 @@ export interface LinkProps extends NuxtLinkProps {
export interface LinkSlots {
default(props: { active: boolean }): any
}
extendDevtoolsMeta({ example: 'LinkExample' })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'radix
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/modal'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ButtonProps } from '../types'
const appConfig = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
@@ -67,6 +68,8 @@ export interface ModalSlots {
body(props?: {}): any
footer(props?: {}): any
}
extendDevtoolsMeta({ example: 'ModalExample' })
</script>
<script setup lang="ts">

View File

@@ -1,10 +1,10 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/navigation-menu'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
import type { DynamicSlots, MaybeArrayOfArray, MaybeArrayOfArrayItem, PartialString } from '../types/utils'
@@ -81,6 +81,44 @@ export type NavigationMenuSlots<T extends { slot?: string }> = {
'item-content': SlotProps<T>
} & DynamicSlots<T, SlotProps<T>>
extendDevtoolsMeta({
ignoreProps: ['items'],
defaultProps: {
items: [
[{
label: 'Documentation',
icon: 'i-heroicons-book-open',
badge: 10,
children: [{
label: 'Introduction',
description: 'Fully styled and customizable components for Nuxt.',
icon: 'i-heroicons-home'
}, {
label: 'Installation',
description: 'Learn how to install and configure Nuxt UI in your application.',
icon: 'i-heroicons-cloud-arrow-down'
}, {
label: 'Theming',
description: 'Learn how to customize the look and feel of the components.',
icon: 'i-heroicons-swatch'
}, {
label: 'Shortcuts',
description: 'Learn how to display and define keyboard shortcuts in your app.',
icon: 'i-heroicons-computer-desktop'
}]
}, {
label: 'GitHub',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui',
target: '_blank'
}, {
label: 'Help',
icon: 'i-heroicons-question-mark-circle',
disabled: true
}]
]
}
})
</script>
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<NavigationMenuItem>">

View File

@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
import type { RouteLocationRaw } from '#vue-router'
import _appConfig from '#build/app.config'
import theme from '#build/ui/pagination'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ButtonProps } from '../types'
const appConfig = _appConfig as AppConfig & { ui: { pagination: Partial<typeof theme> } }
@@ -97,6 +98,8 @@ export interface PaginationSlots {
index: number
}): any
}
extendDevtoolsMeta({ defaultProps: { total: 50 } })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { PopoverRootProps, HoverCardRootProps, PopoverRootEmits, PopoverCon
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/popover'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { popover: Partial<typeof theme> } }
@@ -40,6 +41,8 @@ export interface PopoverSlots {
default(props: { open: boolean }): any
content(props?: {}): any
}
extendDevtoolsMeta({ example: 'PopoverExample' })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { RadioGroupRootProps, RadioGroupRootEmits } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/radio-group'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AcceptableValue } from '../types/utils'
const appConfig = _appConfig as AppConfig & { ui: { radioGroup: Partial<typeof theme> } }
@@ -64,6 +65,8 @@ export interface RadioGroupSlots<T> {
label: SlotProps<T>
description: SlotProps<T>
}
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
</script>
<script setup lang="ts" generic="T extends RadioGroupItem | AcceptableValue">

View File

@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/select'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type { AcceptableValue, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
@@ -95,6 +96,8 @@ export interface SelectSlots<T> {
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
}
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
</script>
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectItem | AcceptableValue> = MaybeArrayOfArray<SelectItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined">

View File

@@ -5,6 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/select-menu'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type { AcceptableValue, ArrayOrWrapped, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
@@ -112,6 +113,8 @@ export interface SelectMenuSlots<T> {
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
}
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
</script>
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectMenuItem | AcceptableValue> = MaybeArrayOfArray<SelectMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">

View File

@@ -3,6 +3,7 @@ import { tv } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/skeleton'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { skeleton: Partial<typeof theme> } }
@@ -16,6 +17,8 @@ export interface SkeletonProps {
as?: any
class?: any
}
extendDevtoolsMeta({ example: 'SkeletonExample' })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'radix
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/slideover'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ButtonProps } from '../types'
const appConfig = _appConfig as AppConfig & { ui: { slideover: Partial<typeof theme> } }
@@ -65,6 +66,8 @@ export interface SlideoverSlots {
body(props?: {}): any
footer(props?: {}): any
}
extendDevtoolsMeta({ example: 'SlideoverExample' })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { SwitchRootProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/switch'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { PartialString } from '../types/utils'
const appConfig = _appConfig as AppConfig & { ui: { switch: Partial<typeof theme> } }
@@ -48,6 +49,8 @@ export interface SwitchSlots {
label(props: { label?: string }): any
description(props: { description?: string }): any
}
extendDevtoolsMeta({ defaultProps: { label: 'Switch me!' } })
</script>
<script setup lang="ts">

View File

@@ -1,10 +1,10 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { TabsRootProps, TabsRootEmits, TabsContentProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/tabs'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
@@ -65,6 +65,23 @@ export type TabsSlots<T extends { slot?: string }> = {
content: SlotProps<T>
} & DynamicSlots<T, SlotProps<T>>
extendDevtoolsMeta({
defaultProps: {
items: [{
label: 'Tab1',
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' },
content: 'This is the content shown for Tab1'
}, {
label: 'Tab2',
icon: 'i-heroicons-user',
content: 'And, this is the content for Tab2'
}, {
label: 'Tab3',
icon: 'i-heroicons-bell',
content: 'Finally, this is the content for Tab3'
}]
}
})
</script>
<script setup lang="ts" generic="T extends TabsItem">

View File

@@ -4,6 +4,7 @@ import type { ToastRootProps, ToastRootEmits } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/toast'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, ButtonProps } from '../types'
const appConfig = _appConfig as AppConfig & { ui: { toast: Partial<typeof theme> } }
@@ -53,6 +54,8 @@ export interface ToastSlots {
actions(props?: {}): any
close(props: { ui: any }): any
}
extendDevtoolsMeta<ToastProps>({ ignore: true })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { ToastProviderProps } from 'radix-vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/toaster'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
const appConfig = _appConfig as AppConfig & { ui: { toaster: Partial<typeof theme> } }
@@ -25,6 +26,12 @@ export interface ToasterProps extends Omit<ToastProviderProps, 'swipeDirection'>
export interface ToasterSlots {
default(props?: {}): any
}
export default {
name: 'Toaster'
}
extendDevtoolsMeta({ example: 'ToasterExample' })
</script>
<script setup lang="ts">

View File

@@ -4,6 +4,7 @@ import type { TooltipRootProps, TooltipRootEmits, TooltipContentProps, TooltipAr
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/tooltip'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { KbdProps } from '../types'
const appConfig = _appConfig as AppConfig & { ui: { tooltip: Partial<typeof theme> } }
@@ -40,6 +41,8 @@ export interface TooltipSlots {
default(props: { open: boolean }): any
content(props?: {}): any
}
extendDevtoolsMeta({ example: 'TooltipExample', defaultProps: { text: 'Hello world!' } })
</script>
<script setup lang="ts">

View File

@@ -0,0 +1,8 @@
export type DevtoolsMeta<T> = {
defaultProps?: T
example?: string
ignore?: boolean
ignoreProps?: string[]
}
export function extendDevtoolsMeta<T>(_meta: DevtoolsMeta<T>) { }

View File

@@ -5,6 +5,13 @@ import type { Nuxt, NuxtTemplate, NuxtTypeTemplate } from '@nuxt/schema'
import type { ModuleOptions } from './module'
import * as theme from './theme'
export function buildTemplates(options: ModuleOptions) {
return Object.entries(theme).reduce((acc, [key, component]) => {
acc[key] = typeof component === 'function' ? component(options as Required<ModuleOptions>) : component
return acc
}, {} as Record<string, any>)
}
export function getTemplates(options: ModuleOptions, uiConfig: Record<string, any>) {
const templates: NuxtTemplate[] = []

View File

@@ -44,7 +44,7 @@ export interface NuxtUIOptions extends Omit<ModuleOptions, 'fonts' | 'colorMode'
export const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
export const NuxtUIPlugin = createUnplugin<NuxtUIOptions | undefined>((_options = {}, meta) => {
const options = defu(_options, { fonts: false }, defaultOptions)
const options = defu(_options, { fonts: false, devtools: { enabled: false } }, defaultOptions)
options.theme = options.theme || {}
options.theme.colors = resolveColors(options.theme.colors)