chore(playground): compodium setup

This commit is contained in:
Romain Hamel
2025-03-26 11:40:23 +01:00
parent f68061975c
commit 15fe0039f0
47 changed files with 1077 additions and 152 deletions

View File

@@ -1,123 +1,5 @@
<script setup lang="ts">
import { splitByCase, upperFirst } from 'scule'
import { useColorMode } from '#imports'
const router = useRouter()
const appConfig = useAppConfig()
const colorMode = useColorMode()
const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const components = [
'accordion',
'alert',
'avatar',
'badge',
'breadcrumb',
'button',
'button-group',
'card',
'calendar',
'carousel',
'checkbox',
'chip',
'collapsible',
'color-picker',
'context-menu',
'command-palette',
'drawer',
'dropdown-menu',
'form',
'form-field',
'input',
'input-menu',
'input-number',
'kbd',
'link',
'modal',
'navigation-menu',
'pagination',
'pin-input',
'popover',
'progress',
'radio-group',
'select',
'select-menu',
'separator',
'shortcuts',
'skeleton',
'slideover',
'slider',
'stepper',
'switch',
'tabs',
'table',
'textarea',
'toast',
'tooltip',
'tree'
]
const items = components.map(component => ({ label: upperName(component), to: `/components/${component}` }))
function upperName(name: string) {
return splitByCase(name).map(p => upperFirst(p)).join('')
}
const isCommandPaletteOpen = ref(false)
function onSelect(item: any) {
router.push(item.to)
}
defineShortcuts({
meta_k: () => isCommandPaletteOpen.value = true
})
</script>
<template>
<template v-if="!$route.path.startsWith('/__nuxt_ui__')">
<UApp :toaster="appConfig.toaster">
<div class="h-screen w-screen overflow-hidden flex flex-col lg:flex-row min-h-0 bg-(--ui-bg)" data-vaul-drawer-wrapper>
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" />
<div class="fixed top-15 lg:top-3 right-4 flex items-center gap-2">
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
:aria-label="`Switch to ${isDark ? 'light' : 'dark'} mode`"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="size-8" />
</template>
</ClientOnly>
</div>
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-14 px-4">
<NuxtPage />
</div>
<UModal v-model:open="isCommandPaletteOpen" class="sm:h-96">
<template #content>
<UCommandPalette placeholder="Search a component..." :groups="[{ id: 'items', items }]" :fuse="{ resultLimit: 100 }" @update:model-value="onSelect" @update:open="value => isCommandPaletteOpen = value" />
</template>
</UModal>
</div>
</UApp>
</template>
<template v-else>
<UApp>
<NuxtPage />
</template>
</UApp>
</template>

View File

@@ -0,0 +1,118 @@
<script setup lang="ts">
import { splitByCase, upperFirst } from 'scule'
import { useColorMode } from '#imports'
const router = useRouter()
const appConfig = useAppConfig()
const colorMode = useColorMode()
const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const components = [
'accordion',
'alert',
'avatar',
'badge',
'breadcrumb',
'button',
'button-group',
'card',
'calendar',
'carousel',
'checkbox',
'chip',
'collapsible',
'color-picker',
'context-menu',
'command-palette',
'drawer',
'dropdown-menu',
'form',
'form-field',
'input',
'input-menu',
'input-number',
'kbd',
'link',
'modal',
'navigation-menu',
'pagination',
'pin-input',
'popover',
'progress',
'radio-group',
'select',
'select-menu',
'separator',
'shortcuts',
'skeleton',
'slideover',
'slider',
'stepper',
'switch',
'tabs',
'table',
'textarea',
'toast',
'tooltip',
'tree'
]
const items = components.map(component => ({ label: upperName(component), to: `/components/${component}` }))
function upperName(name: string) {
return splitByCase(name).map(p => upperFirst(p)).join('')
}
const isCommandPaletteOpen = ref(false)
function onSelect(item: any) {
router.push(item.to)
}
defineShortcuts({
meta_k: () => isCommandPaletteOpen.value = true
})
</script>
<template>
<UApp :toaster="appConfig.toaster">
<div class="h-screen w-screen overflow-hidden flex flex-col lg:flex-row min-h-0 bg-(--ui-bg)" data-vaul-drawer-wrapper>
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" />
<div class="fixed top-15 lg:top-3 right-4 flex items-center gap-2">
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
:aria-label="`Switch to ${isDark ? 'light' : 'dark'} mode`"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="size-8" />
</template>
</ClientOnly>
</div>
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-14 px-4">
<NuxtPage />
</div>
<UModal v-model:open="isCommandPaletteOpen" class="sm:h-96">
<template #content>
<UCommandPalette placeholder="Search a component..." :groups="[{ id: 'items', items }]" :fuse="{ resultLimit: 100 }" @update:model-value="onSelect" @update:open="value => isCommandPaletteOpen = value" />
</template>
</UModal>
</div>
</UApp>
</template>

View File

@@ -1,11 +1,20 @@
<script setup lang="ts">
</script>
<template>
<div class="text-center">
<h1 class="font-semibold mb-1">
Playground
<div class="w-screen h-screen flex flex-col justify-center items-center gap-2">
<h1 class="text-3xl font-bold mb-1">
Nuxt UI Playground
</h1>
<div class="flex items-center justify-center gap-1">
<UKbd value="meta" /> <UKbd value="K" />
<UButton size="lg" to="/__compodium__/devtools" external>
Browse components
</UButton>
<UButton variant="outline" color="neutral" size="lg">
Documentation
</UButton>
</div>
</div>
</template>

View File

@@ -0,0 +1,55 @@
<script setup lang="ts">
defineOptions({
inheritAttrs: false
})
extendCompodiumMeta({
defaultProps: {
items: [{
label: 'Getting Started',
icon: 'i-lucide-info',
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-lucide-download',
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-lucide-pipette',
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-lucide-layout-dashboard',
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-lucide-layers-3',
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',
slot: 'custom' as const,
icon: 'i-lucide-wrench',
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>
<template>
<UCard :ui="{ body: 'p-0 sm:p-0' }">
<UAccordion v-bind="$attrs" class="w-96" :ui="{ trigger: 'px-3.5', body: 'px-3.5' }">
<template #body="{ item }">
<p class="text-(--ui-text-muted)">
{{ item.content }}
</p>
</template>
<template #custom-body="{ item }">
<p class="text-(--ui-text-muted)">
Custom: {{ item.content }}
</p>
</template>
</UAccordion>
</UCard>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
extendCompodiumMeta({
defaultProps: {
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant'],
defaultProps: {
icon: 'i-lucide-terminal',
title: 'Heads up!',
description: 'You can change the primary color in your app config.',
close: true,
actions: [
{
label: 'Action',
onClick() {
console.log('Action clicked')
}
},
{
label: 'Another action',
color: 'warning',
onClick() {
console.log('Another action clicked')
}
},
{
label: 'One more action',
color: 'error',
onClick() {
console.log('One more action clicked')
}
},
{
label: 'And one more',
color: 'info',
icon: 'i-lucide-info',
onClick() {
console.log('And one more clicked')
}
},
{
label: 'Last one',
color: 'neutral',
icon: 'i-lucide-info',
onClick() {
console.log('Last one clicked')
}
}
]
}
})
const multipleActions = (color: string) => [
]
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant'],
defaultProps: {
avatar: { src: 'https://github.com/benjamincanac.png' },
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant'],
defaultProps: {
icon: 'i-lucide-terminal',
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant', 'color'],
defaultProps: {
title: 'Heads up!',
description: 'You can change the primary color in your app config.'
}
})
</script>
<template>
<UAlert />
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
extendCompodiumMeta({
defaultProps: {
src: 'https://github.com/benjamincanac.png'
}
})
</script>
<template>
<UAvatar />
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size'],
defaultProps: {
icon: 'i-lucide-image'
}
})
</script>
<template>
<UAvatar />
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size'],
defaultProps: {
alt: 'Benjamin Canac'
}
})
</script>
<template>
<UAvatar />
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size'],
defaultProps: {
src: 'https://github.com/benjamincanac.png'
}
})
</script>
<template>
<UAvatar />
</template>

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,13 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size']
})
</script>
<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,15 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size']
})
</script>
<template>
<UAvatarGroup>
<UChip inset text="1">
<UAvatar src="https://github.com/benjamincanac.png" alt="Benjamin Canac" />
</UChip>
<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,11 @@
<script setup lang="ts">
extendCompodiumMeta({
defaultProps: {
label: 'Badge'
}
})
</script>
<template>
<UBadge />
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'variant'],
defaultProps: {
label: 'Badge',
avatar: { src: 'https://github.com/benjamincanac.png' }
}
})
</script>
<template>
<UBadge />
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'variant'],
defaultProps: {
label: 'Badge',
icon: 'i-lucide-rocket'
}
})
</script>
<template>
<UBadge />
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'variant'],
defaultProps: {
label: 'Badge',
disabled: true
}
})
</script>
<template>
<UBadge />
</template>

View File

@@ -2,12 +2,11 @@
extendCompodiumMeta({
combo: ['variant', 'color'],
defaultProps: {
label: 'Click me!',
disabled: true
label: 'Badge'
}
})
</script>
<template>
<UButton />
<UBadge />
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
extendCompodiumMeta({
defaultProps: {
items: [{
label: 'Home',
to: '/'
}, {
slot: 'dropdown' as const,
icon: 'i-lucide-ellipsis',
children: [{
label: 'Documentation'
}, {
label: 'Themes'
}, {
label: 'GitHub'
}]
}, {
label: 'Components',
disabled: true
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}]
}
})
</script>
<template>
<UBreadcrumb />
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant', 'size'],
combo: ['size', 'variant'],
defaultProps: {
avatar: { src: 'https://github.com/benjamincanac.png' }
}

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant', 'size'],
combo: ['size', 'variant'],
defaultProps: {
label: 'Click me!',
icon: 'i-lucide-rocket'

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant', 'size'],
combo: ['size', 'variant'],
defaultProps: {
label: 'Click me!',
loading: true

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'variant'],
defaultProps: {
label: 'Click me!'
}
})
</script>
<template>
<UButton />
</template>

View File

@@ -0,0 +1,55 @@
<script setup lang="ts">
defineOptions({
inheritAttrs: false
})
</script>
<template>
<div class="flex flex-col gap-4">
<UButtonGroup v-bind="$attrs">
<UButton>Button</UButton>
</UButtonGroup>
<UButtonGroup v-bind="$attrs">
<UInput placeholder="Search..." />
</UButtonGroup>
<UButtonGroup v-bind="$attrs">
<UButton color="neutral" variant="outline">
Button
</UButton>
<UButton color="neutral" variant="subtle">
Button
</UButton>
<UButton color="neutral" variant="outline">
Button
</UButton>
</UButtonGroup>
<UButtonGroup v-bind="$attrs" orientation="vertical">
<UButton color="neutral" variant="outline">
Button
</UButton>
<UInput placeholder="Search..." />
</UButtonGroup>
<UButtonGroup v-bind="$attrs">
<UButton color="neutral" variant="outline">
Button
</UButton>
<UInput placeholder="Search..." />
</UButtonGroup>
<UButtonGroup v-bind="$attrs">
<UInput placeholder="Search..." />
<UButton color="neutral" variant="outline">
Button
</UButton>
</UButtonGroup>
<UButtonGroup v-bind="$attrs">
<UBadge color="neutral" variant="outline" size="lg" label="https://" />
<UInput color="neutral" variant="outline" placeholder="www.example.com" />
</UButtonGroup>
</div>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'orientation']
})
</script>
<template>
<UButtonGroup v-bind="$attrs">
<UButton color="neutral" variant="outline">
Button
</UButton>
<UInput placeholder="Search..." />
</UButtonGroup>
</template>

View File

@@ -0,0 +1,13 @@
<template>
<UCard class="w-96">
<template #header>
<Placeholder class="h-8" />
</template>
<Placeholder class="h-32" />
<template #footer>
<Placeholder class="h-8" />
</template>
</UCard>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
extendCompodiumMeta({
defaultProps: {
items: [
'https://picsum.photos/320/320?v=1',
'https://picsum.photos/320/320?v=2',
'https://picsum.photos/320/320?v=3'
]
}
})
</script>
<template>
<UCarousel
v-slot="{ item }"
class="w-xs h-xs"
>
<img
:src="item"
class="rounded-lg"
>
</UCarousel>
</template>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['variant', 'size'],
combo: ['size', 'color'],
defaultProps: {
label: 'Click me!',
disabled: true
modelValue: true
}
})
</script>
<template>
<UButton v-bind="$attrs" />
<UCheckbox />
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'color'],
defaultProps: {
label: 'Click me!',
description: 'This is a description',
modelValue: true
}
})
</script>
<template>
<UCheckbox />
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'color'],
defaultProps: {
label: 'Click me!',
icon: 'lucide:heart',
modelValue: true
}
})
</script>
<template>
<UCheckbox />
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size', 'color'],
defaultProps: {
label: 'Click me!',
modelValue: 'indeterminate',
indeterminate: true
}
})
</script>
<template>
<UCheckbox />
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UChip>
<UButton icon="i-lucide-inbox" color="neutral" variant="subtle" />
</UChip>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['position']
})
</script>
<template>
<UChip>
<UButton icon="i-lucide-inbox" color="neutral" variant="subtle" />
</UChip>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
extendCompodiumMeta({
combo: ['size']
})
</script>
<template>
<UChip>
<UAvatar src="https://github.com/benjamincanac.png" />
</UChip>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import { useAppConfig } from '#imports'
const appConfig = useAppConfig()
</script>
<template>
<UCollapsible class="flex flex-col gap-2 w-48">
<UButton
class="group"
icon="i-lucide-lightbulb"
:trailing-icon="appConfig.ui.icons.chevronDown"
color="neutral"
variant="outline"
label="Open"
block
:ui="{ trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200' }"
/>
<template #content>
<Placeholder class="h-96 w-full" />
</template>
</UCollapsible>
</template>

View File

@@ -0,0 +1,104 @@
<script setup lang="ts">
import type { User } from '~/types'
import { ref, computed, useFetch, useToast } from '#imports'
const toast = useToast()
const open = ref(false)
const searchTerm = ref('')
const selected = ref([])
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: User[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
const loading = ref(false)
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || []
}, {
id: 'actions',
items: [{
label: 'Add new file',
suffix: 'Create a new file in the current directory or workspace.',
icon: 'i-lucide-file-plus',
loading: loading.value,
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New file added!' })
loading.value = true
setTimeout(() => {
loading.value = false
}, 2000)
},
kbds: ['meta', 'N']
}, {
label: 'Add new folder',
suffix: 'Create a new folder in the current directory or workspace.',
icon: 'i-lucide-folder-plus',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New folder added!' })
},
kbds: ['meta', 'F']
}, {
label: 'Add hashtag',
suffix: 'Add a hashtag to the current item.',
icon: 'i-lucide-hash',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Hashtag added!' })
},
kbds: ['meta', 'H']
}, {
label: 'Add label',
suffix: 'Add a label to the current item.',
icon: 'i-lucide-tag',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Label added!' })
},
kbds: ['meta', 'L']
}]
}])
// function onSelect(item: typeof groups.value[number]['items'][number]) {
function onSelect(item: any) {
console.log('Selected', item)
}
defineShortcuts({
meta_k: () => open.value = !open.value,
...extractShortcuts(groups.value)
})
</script>
<template>
<UCard :ui="{ body: '!p-0' }">
<UCommandPalette
v-model="selected"
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
:fuse="{
fuseOptions: {
includeMatches: true
}
}"
multiple
class="sm:max-h-80"
@update:model-value="onSelect"
/>
</UCard>
</template>

View File

@@ -0,0 +1,121 @@
<script setup lang="ts">
import { ref, computed, useFetch, useToast } from '#imports'
import type { User } from '~/types'
const toast = useToast()
const searchTerm = ref('')
const selected = ref([])
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
// params: { q: searchTermDebounced },
transform: (data: User[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
const loading = ref(false)
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || []
}, {
id: 'actions',
items: [{
label: 'Add new file',
suffix: 'Create a new file in the current directory or workspace.',
icon: 'i-lucide-file-plus',
loading: loading.value,
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New file added!' })
loading.value = true
setTimeout(() => {
loading.value = false
}, 2000)
},
kbds: ['meta', 'N']
}, {
label: 'Add new folder',
suffix: 'Create a new folder in the current directory or workspace.',
icon: 'i-lucide-folder-plus',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New folder added!' })
},
kbds: ['meta', 'F']
}, {
label: 'Add hashtag',
suffix: 'Add a hashtag to the current item.',
icon: 'i-lucide-hash',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Hashtag added!' })
},
kbds: ['meta', 'H']
}, {
label: 'Add label',
suffix: 'Add a label to the current item.',
icon: 'i-lucide-tag',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Label added!' })
},
kbds: ['meta', 'L']
}]
}])
const labels = [{
label: 'bug',
chip: {
color: 'error' as const
}
}, {
label: 'feature',
chip: {
color: 'success' as const
}
}, {
label: 'enhancement',
chip: {
color: 'info' as const
}
}]
// function onSelect(item: typeof groups.value[number]['items'][number]) {
function onSelect(item: any) {
console.log('Selected', item)
}
</script>
<template>
<UDrawer should-scale-background>
<UButton label="Open in drawer" color="neutral" variant="outline" />
<template #content>
<UCommandPalette
v-model="selected"
v-model:search-term="searchTerm"
v-bind="$attrs"
:loading="status === 'pending'"
:groups="groups"
:fuse="{
fuseOptions: {
includeMatches: true
}
}"
multiple
class="sm:max-h-80 border-t border-(--ui-border) mt-4"
@update:model-value="onSelect"
/>
</template>
</UDrawer>
</template>

View File

@@ -0,0 +1,107 @@
<script setup lang="ts">
import type { User } from '~/types'
import { ref, computed, useFetch, useToast } from '#imports'
const toast = useToast()
const open = ref(false)
const searchTerm = ref('')
const selected = ref([])
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
// params: { q: searchTermDebounced },
transform: (data: User[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
const loading = ref(false)
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || []
}, {
id: 'actions',
items: [{
label: 'Add new file',
suffix: 'Create a new file in the current directory or workspace.',
icon: 'i-lucide-file-plus',
loading: loading.value,
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New file added!' })
loading.value = true
setTimeout(() => {
loading.value = false
}, 2000)
},
kbds: ['meta', 'N']
}, {
label: 'Add new folder',
suffix: 'Create a new folder in the current directory or workspace.',
icon: 'i-lucide-folder-plus',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New folder added!' })
},
kbds: ['meta', 'F']
}, {
label: 'Add hashtag',
suffix: 'Add a hashtag to the current item.',
icon: 'i-lucide-hash',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Hashtag added!' })
},
kbds: ['meta', 'H']
}, {
label: 'Add label',
suffix: 'Add a label to the current item.',
icon: 'i-lucide-tag',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Label added!' })
},
kbds: ['meta', 'L']
}]
}])
// function onSelect(item: typeof groups.value[number]['items'][number]) {
function onSelect(item: any) {
console.log('Selected', item)
}
</script>
<template>
<UModal v-model:open="open">
<UButton label="Open in modal" color="neutral" variant="outline" />
<template #content>
<UCommandPalette
v-bind="$attrs"
v-model="selected"
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
:fuse="{
fuseOptions: {
includeMatches: true
}
}"
multiple
close
class="sm:max-h-80"
@update:open="open = $event"
@update:model-value="onSelect"
/>
</template>
</UModal>
</template>

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import { ref } from '#imports'
const labels = [{
label: 'bug',
chip: {
color: 'error' as const
}
}, {
label: 'feature',
chip: {
color: 'success' as const
}
}, {
label: 'enhancement',
chip: {
color: 'info' as const
}
}]
const label = ref()
</script>
<template>
<UPopover :content="{ side: 'right', align: 'start' }">
<UButton label="Select label (popover)" color="neutral" variant="outline" />
<template #content>
<UCommandPalette v-model="label" placeholder="Search labels..." :groups="[{ id: 'labels', items: labels }]" :ui="{ input: '[&>input]:h-9' }" />
</template>
</UPopover>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<UApp>
<div class="flex justify-center items-center w-content min-h-screen py-16 px-8">
<slot />
</div>
</UApp>
</template>

View File

@@ -21,6 +21,7 @@ export default defineNuxtConfig({
compodium: {
includeLibraryCollections: false,
ignore: ['**/App.vue', '**/*Example.vue', '**/Placeholder.vue', '**/*Content.vue'],
extras: {
colors: {
neutral: 'slate'

View File

@@ -8,12 +8,14 @@
"generate": "nuxi generate"
},
"dependencies": {
"@compodium/nuxt": "0.1.0-beta.7",
"@iconify-json/lucide": "^1.2.32",
"@iconify-json/simple-icons": "^1.2.29",
"@nuxt/ui": "latest",
"@nuxthub/core": "^0.8.18",
"nuxt": "^3.16.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@compodium/nuxt": "https://pkg.pr.new/romhml/compodium/@compodium/nuxt@185ece2"
}
}

42
pnpm-lock.yaml generated
View File

@@ -304,9 +304,6 @@ importers:
playground:
dependencies:
'@compodium/nuxt':
specifier: 0.1.0-beta.7
version: 0.1.0-beta.7(magicast@0.3.5)(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))
'@iconify-json/lucide':
specifier: ^1.2.32
version: 1.2.32
@@ -325,6 +322,10 @@ importers:
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@compodium/nuxt':
specifier: https://pkg.pr.new/romhml/compodium/@compodium/nuxt@185ece2
version: https://pkg.pr.new/romhml/compodium/@compodium/nuxt@185ece2(magicast@0.3.5)(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))
playground-vue:
dependencies:
@@ -552,8 +553,9 @@ packages:
'@cloudflare/workers-types@4.20250321.0':
resolution: {integrity: sha512-jPwtZJC7tVFOwFazuwq96be8haTnY9qik8hJ+oLFi50d9LTWPPrnrNHC4OxZmJTEcPIAy0y1WFZHe8C/b7xFXQ==}
'@compodium/core@0.1.0-beta.7':
resolution: {integrity: sha512-QFR+nlc7x0oP4j10eSOwH8TM1/mEIGseo7qrtPEVq35X0F7bAPXSQAZnyNOEZtcAc9PETkZ+j8l508K90orWdw==}
'@compodium/core@https://pkg.pr.new/romhml/compodium/@compodium/core@185ece23f49ec55deb12b041a017f31f7ba25ade':
resolution: {tarball: https://pkg.pr.new/romhml/compodium/@compodium/core@185ece23f49ec55deb12b041a017f31f7ba25ade}
version: 0.0.0-185ece23f49ec55deb12b041a017f31f7ba25ade
peerDependencies:
vite: '>=3.2.11'
vue: '>=3.5.13'
@@ -561,19 +563,22 @@ packages:
vite:
optional: true
'@compodium/examples@0.1.0-beta.7':
resolution: {integrity: sha512-EbyD4fW3ZCel9sYlB/ecZ9tWYyjduBRRNhU4NeL+hgyYi7gInrPMkx8PVyYrp2A6ckjJCmDZtkVCRrBQ/f8u4Q==}
'@compodium/examples@https://pkg.pr.new/romhml/compodium/@compodium/examples@185ece23f49ec55deb12b041a017f31f7ba25ade':
resolution: {tarball: https://pkg.pr.new/romhml/compodium/@compodium/examples@185ece23f49ec55deb12b041a017f31f7ba25ade}
version: 0.0.0-185ece23f49ec55deb12b041a017f31f7ba25ade
'@compodium/meta@0.1.0-beta.7':
resolution: {integrity: sha512-i1xm4wQ1YwPgLiiM22In+y94oxJgd5/CgkXFJK9rnq7jO+uGi7rbbyA/fr+7BXnpIgE9eqPESewI7YYyzuQ1Pw==}
'@compodium/meta@https://pkg.pr.new/romhml/compodium/@compodium/meta@185ece23f49ec55deb12b041a017f31f7ba25ade':
resolution: {tarball: https://pkg.pr.new/romhml/compodium/@compodium/meta@185ece23f49ec55deb12b041a017f31f7ba25ade}
version: 0.0.0-185ece23f49ec55deb12b041a017f31f7ba25ade
peerDependencies:
typescript: 5.6.3
peerDependenciesMeta:
typescript:
optional: true
'@compodium/nuxt@0.1.0-beta.7':
resolution: {integrity: sha512-TVACdtzWRwfXJzG+T8+Fal8ecdeo0AIc9jcslslMSjKDqTRK6mgCMnfDr8dM5IX1VD2U46euKR/ZpnTqg2/a2w==}
'@compodium/nuxt@https://pkg.pr.new/romhml/compodium/@compodium/nuxt@185ece2':
resolution: {tarball: https://pkg.pr.new/romhml/compodium/@compodium/nuxt@185ece2}
version: 0.0.0-185ece23f49ec55deb12b041a017f31f7ba25ade
'@conventional-changelog/git-client@1.0.1':
resolution: {integrity: sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==}
@@ -7548,10 +7553,10 @@ snapshots:
'@cloudflare/workers-types@4.20250321.0': {}
'@compodium/core@0.1.0-beta.7(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))':
'@compodium/core@https://pkg.pr.new/romhml/compodium/@compodium/core@185ece23f49ec55deb12b041a017f31f7ba25ade(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))':
dependencies:
'@compodium/examples': 0.1.0-beta.7
'@compodium/meta': 0.1.0-beta.7(typescript@5.6.3)
'@compodium/examples': https://pkg.pr.new/romhml/compodium/@compodium/examples@185ece23f49ec55deb12b041a017f31f7ba25ade
'@compodium/meta': https://pkg.pr.new/romhml/compodium/@compodium/meta@185ece23f49ec55deb12b041a017f31f7ba25ade(typescript@5.6.3)
'@vueuse/core': 13.0.0(vue@3.5.13(typescript@5.6.3))
chokidar: 3.6.0
hookable: 5.5.3
@@ -7569,12 +7574,12 @@ snapshots:
transitivePeerDependencies:
- typescript
'@compodium/examples@0.1.0-beta.7':
'@compodium/examples@https://pkg.pr.new/romhml/compodium/@compodium/examples@185ece23f49ec55deb12b041a017f31f7ba25ade':
dependencies:
pathe: 2.0.3
scule: 1.3.0
'@compodium/meta@0.1.0-beta.7(typescript@5.6.3)':
'@compodium/meta@https://pkg.pr.new/romhml/compodium/@compodium/meta@185ece23f49ec55deb12b041a017f31f7ba25ade(typescript@5.6.3)':
dependencies:
'@volar/typescript': 2.4.12
'@vue/language-core': 2.2.8(typescript@5.6.3)
@@ -7584,12 +7589,13 @@ snapshots:
optionalDependencies:
typescript: 5.6.3
'@compodium/nuxt@0.1.0-beta.7(magicast@0.3.5)(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))':
'@compodium/nuxt@https://pkg.pr.new/romhml/compodium/@compodium/nuxt@185ece2(magicast@0.3.5)(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))':
dependencies:
'@compodium/core': 0.1.0-beta.7(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))
'@compodium/core': https://pkg.pr.new/romhml/compodium/@compodium/core@185ece23f49ec55deb12b041a017f31f7ba25ade(typescript@5.6.3)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3))
'@nuxt/devtools-kit': 2.3.1(magicast@0.3.5)(vite@6.2.3(@types/node@22.13.12)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.0))
'@nuxt/kit': 3.16.1(magicast@0.3.5)
consola: 3.4.2
defu: 6.1.4
ufo: 1.5.4
transitivePeerDependencies:
- magicast