mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-31 04:07:56 +01:00
playground: migrate to nuxt compatibility 4
This commit is contained in:
11
playground/app/app.config.ts
Normal file
11
playground/app/app.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export default defineAppConfig({
|
||||
toaster: {
|
||||
position: 'bottom-right' as const,
|
||||
expand: true,
|
||||
duration: 5000
|
||||
},
|
||||
ui: {
|
||||
primary: 'sky',
|
||||
gray: 'cool'
|
||||
}
|
||||
})
|
||||
82
playground/app/app.vue
Normal file
82
playground/app/app.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { splitByCase, upperFirst } from 'scule'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const router = useRouter()
|
||||
|
||||
const components = [
|
||||
'accordion',
|
||||
'alert',
|
||||
'avatar',
|
||||
'badge',
|
||||
'breadcrumb',
|
||||
'button',
|
||||
'button-group',
|
||||
'card',
|
||||
'checkbox',
|
||||
'chip',
|
||||
'collapsible',
|
||||
'context-menu',
|
||||
'command-palette',
|
||||
'drawer',
|
||||
'dropdown-menu',
|
||||
'form',
|
||||
'form-field',
|
||||
'input',
|
||||
'input-menu',
|
||||
'kbd',
|
||||
'link',
|
||||
'modal',
|
||||
'navigation-menu',
|
||||
'pagination',
|
||||
'popover',
|
||||
'progress',
|
||||
'radio-group',
|
||||
'select',
|
||||
'select-menu',
|
||||
'separator',
|
||||
'shortcuts',
|
||||
'skeleton',
|
||||
'slideover',
|
||||
'slider',
|
||||
'switch',
|
||||
'tabs',
|
||||
'textarea',
|
||||
'toast',
|
||||
'tooltip'
|
||||
]
|
||||
|
||||
const items = components.map(component => ({ label: upperName(component), to: `/${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 min-h-0 bg-white dark:bg-gray-900" vaul-drawer-wrapper>
|
||||
<UNavigationMenu :items="items" orientation="vertical" class="border-r border-gray-200 dark:border-gray-800 overflow-y-auto w-48 p-4" />
|
||||
|
||||
<div class="flex-1 flex flex-col items-center justify-around overflow-y-auto w-full py-12 px-4">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</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>
|
||||
</UApp>
|
||||
</template>
|
||||
107
playground/app/components/FormElementsExample.vue
Normal file
107
playground/app/components/FormElementsExample.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent, Form } from '#ui/types/form'
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string().min(10),
|
||||
inputMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
inputMenuMultiple: z.any().refine(values => !!values?.find((option: any) => option.value === 'option-2'), {
|
||||
message: 'Include Option 2'
|
||||
}),
|
||||
textarea: z.string().min(10),
|
||||
select: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
selectMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
selectMenuMultiple: z.any().refine(values => !!values?.find((option: any) => option.value === 'option-2'), {
|
||||
message: 'Include Option 2'
|
||||
}),
|
||||
switch: z.boolean().refine(value => value === true, {
|
||||
message: 'Toggle me'
|
||||
}),
|
||||
checkbox: z.boolean().refine(value => value === true, {
|
||||
message: 'Check me'
|
||||
}),
|
||||
radioGroup: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
slider: z.number().max(20, { message: 'Must be less than 20' })
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive<Partial<Schema>>({})
|
||||
const form = ref<Form<Schema>>()
|
||||
|
||||
const items = [
|
||||
{ label: 'Option 1', value: 'option-1' },
|
||||
{ label: 'Option 2', value: 'option-2' },
|
||||
{ label: 'Option 3', value: 'option-3' }
|
||||
]
|
||||
|
||||
function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :state="state" :schema="schema" class="gap-4 flex flex-col w-60" @submit="onSubmit">
|
||||
<UFormField label="Input" name="input">
|
||||
<UInput v-model="state.input" placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Textarea" name="textarea">
|
||||
<UTextarea v-model="state.textarea" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="select" label="Select">
|
||||
<USelect v-model="state.select" class="w-44" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="selectMenu" label="Select Menu">
|
||||
<USelectMenu v-model="state.selectMenu" class="w-44" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="selectMenuMultiple" label="Select Menu (Multiple)">
|
||||
<USelectMenu v-model="state.selectMenuMultiple" class="w-44" multiple :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="inputMenu" label="Input Menu">
|
||||
<UInputMenu v-model="state.inputMenu" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="inputMenuMultiple" label="Input Menu (Multiple)">
|
||||
<UInputMenu v-model="state.inputMenuMultiple" multiple :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="checkbox">
|
||||
<UCheckbox v-model="state.checkbox" label="Check me" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="radioGroup">
|
||||
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="switch">
|
||||
<USwitch v-model="state.switch" label="Switch me" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="slider" label="Slider">
|
||||
<USlider v-model="state.slider" />
|
||||
</UFormField>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<UButton color="gray" type="submit" :disabled="form?.disabled">
|
||||
Submit
|
||||
</UButton>
|
||||
|
||||
<UButton variant="outline" :disabled="form?.disabled" @click="form?.clear()">
|
||||
Clear
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</template>
|
||||
65
playground/app/components/FormNestedExample.vue
Normal file
65
playground/app/components/FormNestedExample.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '#ui/types/form'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().min(2),
|
||||
password: z.string().min(8)
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const nestedSchema = z.object({
|
||||
phone: z.string().length(10)
|
||||
})
|
||||
|
||||
type NestedSchema = z.output<typeof nestedSchema>
|
||||
|
||||
const state = reactive<Partial<Schema & { nested: Partial<NestedSchema> }>>({
|
||||
nested: {}
|
||||
})
|
||||
|
||||
const checked = ref(false)
|
||||
|
||||
function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
console.log('Success', event.data)
|
||||
}
|
||||
|
||||
function onError(event: any) {
|
||||
console.log('Error', event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:state="state"
|
||||
:schema="schema"
|
||||
class="gap-4 flex flex-col w-60"
|
||||
@submit="(event) => onSubmit(event)"
|
||||
@error="(event) => onError(event)"
|
||||
>
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<div>
|
||||
<UCheckbox v-model="checked" name="check" label="Check me" @change="state.nested = {}" />
|
||||
</div>
|
||||
|
||||
<UForm v-if="checked && state.nested" :state="state.nested" :schema="nestedSchema">
|
||||
<UFormField label="Phone" name="phone">
|
||||
<UInput v-model="state.nested.phone" />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
|
||||
<div>
|
||||
<UButton color="gray" type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</template>
|
||||
85
playground/app/components/FormNestedListExample.vue
Normal file
85
playground/app/components/FormNestedListExample.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '#ui/types/form'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().min(2),
|
||||
password: z.string().min(8)
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const itemSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
price: z.string().min(1)
|
||||
})
|
||||
|
||||
type ItemSchema = z.output<typeof itemSchema>
|
||||
|
||||
const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({})
|
||||
|
||||
function addItem() {
|
||||
if (!state.items) {
|
||||
state.items = []
|
||||
}
|
||||
state.items.push({})
|
||||
}
|
||||
|
||||
function removeItem() {
|
||||
if (state.items) {
|
||||
state.items.pop()
|
||||
}
|
||||
}
|
||||
const formItemRef = ref()
|
||||
|
||||
function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
console.log('Success', event.data)
|
||||
}
|
||||
|
||||
function onError(event: any) {
|
||||
console.log('Error', event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="formItemRef"
|
||||
:state="state"
|
||||
:schema="schema"
|
||||
class="gap-4 flex flex-col w-60"
|
||||
@submit="onSubmit"
|
||||
@error="onError"
|
||||
>
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
|
||||
<UFormField label="Name" name="name">
|
||||
<UInput v-model="item.name" />
|
||||
</UFormField>
|
||||
<UFormField label="Price" name="price">
|
||||
<UInput v-model="item.price" />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<UButton color="black" @click="addItem()">
|
||||
Add Item
|
||||
</UButton>
|
||||
|
||||
<UButton color="black" variant="ghost" @click="removeItem()">
|
||||
Remove Item
|
||||
</UButton>
|
||||
</div>
|
||||
<div>
|
||||
<UButton color="gray" type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</template>
|
||||
18
playground/app/components/ModalProgrammaticExample.vue
Normal file
18
playground/app/components/ModalProgrammaticExample.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
const modal = useModal()
|
||||
|
||||
defineProps<{
|
||||
count: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal title="This modal was opened programmatically">
|
||||
<template #footer>
|
||||
<div class="flex w-full justify-between">
|
||||
<p>Count: {{ count }}</p>
|
||||
<UButton color="gray" label="Close" @click="modal.close()" />
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
21
playground/app/components/Placeholder.vue
Normal file
21
playground/app/components/Placeholder.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded border border-dashed border-gray-400 dark:border-gray-500 opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-gray-900/10 dark:stroke-white/10" fill="none">
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
|
||||
x="0"
|
||||
y="0"
|
||||
width="10"
|
||||
height="10"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M-3 13 15-5M-5 5l18-18M-1 21 17 3" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect stroke="none" fill="url(#pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e)" width="100%" height="100%" />
|
||||
</svg>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
18
playground/app/components/SlideoverProgrammaticExample.vue
Normal file
18
playground/app/components/SlideoverProgrammaticExample.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
const slideover = useSlideover()
|
||||
|
||||
defineProps<{
|
||||
count: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USlideover title="This slideover was opened programmatically">
|
||||
<template #footer>
|
||||
<div class="flex w-full justify-between">
|
||||
<p>Count: {{ count }}</p>
|
||||
<UButton color="gray" label="Close" @click="slideover.close()" />
|
||||
</div>
|
||||
</template>
|
||||
</USlideover>
|
||||
</template>
|
||||
39
playground/app/pages/accordion.vue
Normal file
39
playground/app/pages/accordion.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
const 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',
|
||||
slot: 'custom' as const,
|
||||
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>
|
||||
|
||||
<template>
|
||||
<UCard :ui="{ body: 'p-0 sm:p-0' }">
|
||||
<UAccordion :items="items" class="w-96" :ui="{ trigger: 'px-3.5', content: 'px-3.5' }">
|
||||
<template #custom="{ item }">
|
||||
<span class="text-gray-500 dark:text-gray-400">Custom: {{ item.content }}</span>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</UCard>
|
||||
</template>
|
||||
43
playground/app/pages/alert.vue
Normal file
43
playground/app/pages/alert.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/alert'
|
||||
|
||||
const variants = Object.keys(theme.variants.variant)
|
||||
|
||||
const actions = (variant?: string) => [{
|
||||
label: 'Action',
|
||||
color: (variant === 'solid' ? 'black' as const : 'primary' as const),
|
||||
click() {
|
||||
console.log('Action clicked')
|
||||
}
|
||||
}]
|
||||
|
||||
const data = {
|
||||
title: 'Heads up!',
|
||||
description: 'You can add components to your app using the cli.',
|
||||
icon: 'i-heroicons-command-line',
|
||||
actions: actions(),
|
||||
close: true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 flex-1">
|
||||
<UAlert :title="data.title" />
|
||||
<UAlert :title="data.title" :icon="data.icon" />
|
||||
<UAlert :title="data.title" :icon="data.icon" :close="data.close" />
|
||||
<UAlert :title="data.title" :icon="data.icon" :close="data.close" :actions="data.actions" />
|
||||
<UAlert :title="data.title" :icon="data.icon" :close="data.close" :description="data.description" />
|
||||
<UAlert :title="data.title" :avatar="{ src: 'https://avatars.githubusercontent.com/u/739984?v=4' }" :close="data.close" :description="data.description" />
|
||||
<UAlert v-bind="data" color="white" />
|
||||
<UAlert v-bind="data" color="gray" />
|
||||
<UAlert v-bind="data" color="black" />
|
||||
<UAlert
|
||||
v-for="variant in variants"
|
||||
:key="variant"
|
||||
v-bind="data"
|
||||
:actions="actions(variant)"
|
||||
color="primary"
|
||||
:variant="(variant as any)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
39
playground/app/pages/avatar.vue
Normal file
39
playground/app/pages/avatar.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/avatar'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 items-center">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<UAvatar v-for="size in sizes" :key="size" src="https://avatars.githubusercontent.com/u/739984?v=4" alt="Benjamin Canac" :size="(size as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<UAvatar v-for="size in sizes" :key="size" icon="i-heroicons-photo" :size="(size as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<UAvatar v-for="size in sizes" :key="size" alt="Benjamin Canac" :size="(size as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<UAvatar v-for="size in sizes" :key="size" :text="size" :size="(size as any)" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<UAvatarGroup v-for="size in sizes" :key="size" :text="size" :size="(size as any)" :max="2">
|
||||
<UChip inset text="1">
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" alt="benjamincanac" />
|
||||
</UChip>
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Atinux" />
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/7547335?v=4" alt="smarroufin" />
|
||||
</UAvatarGroup>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<UAvatarGroup v-for="size in sizes" :key="size" :text="size" :size="(size as any)" :max="4">
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" alt="benjamincanac" />
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Atinux" />
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/7547335?v=4" alt="smarroufin" />
|
||||
</UAvatarGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
31
playground/app/pages/badge.vue
Normal file
31
playground/app/pages/badge.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/badge'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
const variants = Object.keys(theme.variants.variant)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge class="font-bold">
|
||||
Badge
|
||||
</UBadge>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge v-for="variant in variants" :key="variant" label="Badge" :variant="(variant as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge label="Badge" color="white" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge label="Badge" color="gray" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge label="Badge" color="black" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-[-56px]">
|
||||
<UBadge v-for="size in sizes" :key="size" label="Badge" :size="(size as any)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
32
playground/app/pages/breadcrumb.vue
Normal file
32
playground/app/pages/breadcrumb.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
const items = [{
|
||||
label: 'Home',
|
||||
to: '/'
|
||||
}, {
|
||||
slot: 'dropdown' as const,
|
||||
icon: 'i-heroicons-ellipsis-horizontal',
|
||||
children: [{
|
||||
label: 'Documentation'
|
||||
}, {
|
||||
label: 'Themes'
|
||||
}, {
|
||||
label: 'GitHub'
|
||||
}]
|
||||
}, {
|
||||
label: 'Components',
|
||||
disabled: true
|
||||
}, {
|
||||
label: 'Breadcrumb',
|
||||
to: '/breadcrumb'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBreadcrumb :items="items">
|
||||
<template #dropdown="{ item }">
|
||||
<UDropdownMenu :items="item.children">
|
||||
<UButton :icon="item.icon" color="gray" variant="link" class="p-0" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
</UBreadcrumb>
|
||||
</template>
|
||||
57
playground/app/pages/button-group.vue
Normal file
57
playground/app/pages/button-group.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/button'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<div class="flex flex-col gap-4 -ml-[110px]">
|
||||
<UButtonGroup>
|
||||
<UButton> Click me! </UButton>
|
||||
</UButtonGroup>
|
||||
|
||||
<UButtonGroup>
|
||||
<UInput />
|
||||
</UButtonGroup>
|
||||
|
||||
<UButtonGroup>
|
||||
<UButton> Click me! </UButton>
|
||||
<UButton color="white">
|
||||
Click me!
|
||||
</UButton>
|
||||
<UButton> Click me! </UButton>
|
||||
</UButtonGroup>
|
||||
|
||||
<UButtonGroup orientation="vertical">
|
||||
<UButton color="white">
|
||||
Click me!
|
||||
</UButton>
|
||||
<UInput />
|
||||
</UButtonGroup>
|
||||
|
||||
<UButtonGroup>
|
||||
<UButton color="white">
|
||||
Click me!
|
||||
</UButton>
|
||||
<UInput />
|
||||
</UButtonGroup>
|
||||
|
||||
<UButtonGroup>
|
||||
<UInput />
|
||||
<UButton color="white">
|
||||
Click me!
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center justify-center">
|
||||
<UButtonGroup v-for="size in sizes" :key="size" :size="(size as any)">
|
||||
<UInput />
|
||||
<UButton color="white">
|
||||
Click me!
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
72
playground/app/pages/button.vue
Normal file
72
playground/app/pages/button.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/button'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
const variants = Object.keys(theme.variants.variant)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton class="font-bold">
|
||||
Button
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton disabled>
|
||||
Disabled
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton loading>
|
||||
Loading
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton loading trailing>
|
||||
Loading
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton v-for="variant in variants" :key="variant" icon="i-heroicons-rocket-launch" label="Button" :variant="(variant as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton icon="i-heroicons-rocket-launch" label="Button" color="white" />
|
||||
<UButton icon="i-heroicons-rocket-launch" label="Button" color="white" variant="ghost" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton icon="i-heroicons-rocket-launch" label="Button" color="gray" />
|
||||
<UButton icon="i-heroicons-rocket-launch" label="Button" color="gray" variant="ghost" />
|
||||
<UButton icon="i-heroicons-rocket-launch" label="Button" color="gray" variant="link" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton icon="i-heroicons-rocket-launch" label="Button" color="black" />
|
||||
<UButton icon="i-heroicons-rocket-launch" label="Button" color="black" variant="link" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-[-129px]">
|
||||
<UButton v-for="size in sizes" :key="size" label="Button" :size="(size as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-[-171px]">
|
||||
<UButton v-for="size in sizes" :key="size" icon="i-heroicons-rocket-launch" label="Button" :size="(size as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-[-159px]">
|
||||
<UButton
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
icon="i-heroicons-rocket-launch"
|
||||
label="Square"
|
||||
square
|
||||
:size="(size as any)"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-[-68px]">
|
||||
<UButton v-for="size in sizes" :key="size" icon="i-heroicons-rocket-launch" :size="(size as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton icon="i-heroicons-rocket-launch" trailing-icon="i-heroicons-chevron-down-20-solid" label="Block" block />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton icon="i-heroicons-cloud-arrow-down" label="Button" class="group" :ui="{ leadingIcon: 'group-hover:animate-pulse' }" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
15
playground/app/pages/card.vue
Normal file
15
playground/app/pages/card.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<UCard class="w-96">
|
||||
<template #header>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-32" />
|
||||
|
||||
<template #footer>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
27
playground/app/pages/checkbox.vue
Normal file
27
playground/app/pages/checkbox.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/checkbox'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const checked = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<UCheckbox v-model="checked" label="Model" />
|
||||
<UCheckbox label="Default value" :default-value="true" />
|
||||
<UCheckbox label="Indeterminate" indeterminate />
|
||||
<UCheckbox label="Required" required />
|
||||
<UCheckbox label="Disabled" disabled />
|
||||
<UCheckbox label="Black" color="black" :default-value="true" />
|
||||
<UCheckbox label="Icon" color="red" icon="i-heroicons-heart-solid" :model-value="true" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mr-[-11px]">
|
||||
<UCheckbox v-for="size in sizes" :key="size" label="Check me" :size="(size as any)" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mr-[-96px]">
|
||||
<UCheckbox v-for="size in sizes" :key="size" label="Check me" description="This is a description" :size="(size as any)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
38
playground/app/pages/chip.vue
Normal file
38
playground/app/pages/chip.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/chip'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
const positions = Object.keys(theme.variants.position)
|
||||
|
||||
const items = [{
|
||||
name: 'messages',
|
||||
icon: 'i-heroicons-chat-bubble-oval-left',
|
||||
count: 3
|
||||
}, {
|
||||
name: 'notifications',
|
||||
icon: 'i-heroicons-bell',
|
||||
count: 0
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<UChip v-for="position in positions" :key="position" :position="(position as any)">
|
||||
<UButton icon="i-heroicons-inbox" color="gray" />
|
||||
</UChip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<UChip v-for="{ name, icon, count } in items" :key="name" :text="count" :show="count > 0" size="lg">
|
||||
<UButton :icon="icon" color="gray" />
|
||||
</UChip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<UChip v-for="size in sizes" :key="size" :size="(size as any)" inset text="1">
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" />
|
||||
</UChip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
23
playground/app/pages/collapsible.vue
Normal file
23
playground/app/pages/collapsible.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
const appConfig = useAppConfig()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1">
|
||||
<UCollapsible class="space-y-2 w-48">
|
||||
<UButton
|
||||
class="group"
|
||||
icon="i-heroicons-light-bulb"
|
||||
:trailing-icon="appConfig.ui.icons.chevronDown"
|
||||
color="gray"
|
||||
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>
|
||||
</div>
|
||||
</template>
|
||||
145
playground/app/pages/command-palette.vue
Normal file
145
playground/app/pages/command-palette.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<script setup lang="ts">
|
||||
// import { createReusableTemplate, refDebounced } from '@vueuse/core'
|
||||
import { createReusableTemplate } from '@vueuse/core'
|
||||
import type { User } from '~/types'
|
||||
|
||||
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
|
||||
const toast = useToast()
|
||||
|
||||
const open = ref(false)
|
||||
const searchTerm = ref('')
|
||||
// const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
const selected = ref([])
|
||||
|
||||
const { data: users, pending } = 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 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-heroicons-document-plus',
|
||||
select: (e: Event) => {
|
||||
e?.preventDefault()
|
||||
toast.add({ title: 'New file added!' })
|
||||
},
|
||||
kbds: ['meta', 'N']
|
||||
}, {
|
||||
label: 'Add new folder',
|
||||
suffix: 'Create a new folder in the current directory or workspace.',
|
||||
icon: 'i-heroicons-folder-plus',
|
||||
select: (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-heroicons-hashtag',
|
||||
select: (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-heroicons-tag',
|
||||
select: (e: Event) => {
|
||||
e?.preventDefault()
|
||||
toast.add({ title: 'Label added!' })
|
||||
},
|
||||
kbds: ['meta', 'L']
|
||||
}]
|
||||
}])
|
||||
|
||||
const labels = [{
|
||||
label: 'bug',
|
||||
chip: {
|
||||
color: 'red' as const
|
||||
}
|
||||
}, {
|
||||
label: 'feature',
|
||||
chip: {
|
||||
color: 'green' as const
|
||||
}
|
||||
}, {
|
||||
label: 'enhancement',
|
||||
chip: {
|
||||
color: 'blue' as const
|
||||
}
|
||||
}]
|
||||
const label = ref()
|
||||
|
||||
// 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>
|
||||
<DefineTemplate>
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
v-model:search-term="searchTerm"
|
||||
:loading="pending"
|
||||
:groups="groups"
|
||||
:fuse="{
|
||||
fuseOptions: {
|
||||
includeMatches: true
|
||||
}
|
||||
}"
|
||||
multiple
|
||||
class="sm:max-h-80"
|
||||
@update:model-value="onSelect"
|
||||
/>
|
||||
</DefineTemplate>
|
||||
|
||||
<div class="flex-1 flex flex-col gap-12 w-full max-w-lg">
|
||||
<div class="flex items-center justify-between gap-2 mt-[58px]">
|
||||
<UModal v-model:open="open">
|
||||
<UButton label="Open modal" color="gray" />
|
||||
|
||||
<template #content>
|
||||
<ReuseTemplate :close="true" @close="open = false" />
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<UDrawer should-scale-background>
|
||||
<UButton label="Open drawer" color="gray" />
|
||||
|
||||
<template #content>
|
||||
<ReuseTemplate class="border-t border-gray-200 dark:border-gray-800" />
|
||||
</template>
|
||||
</UDrawer>
|
||||
|
||||
<UPopover :content="{ side: 'right', align: 'start' }">
|
||||
<UButton label="Select label (popover)" color="gray" />
|
||||
|
||||
<template #content>
|
||||
<UCommandPalette v-model="label" placeholder="Search labels..." :groups="[{ id: 'labels', items: labels }]" :ui="{ input: '[&>input]:h-9' }" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
|
||||
<UCard :ui="{ body: '!p-0' }">
|
||||
<ReuseTemplate />
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
78
playground/app/pages/context-menu.vue
Normal file
78
playground/app/pages/context-menu.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
[{
|
||||
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'],
|
||||
select() {
|
||||
console.log('Show Sidebar clicked')
|
||||
}
|
||||
}, {
|
||||
label: 'Show Toolbar',
|
||||
kbds: ['shift', 'meta', 'D'],
|
||||
select() {
|
||||
console.log('Show Toolbar clicked')
|
||||
}
|
||||
}, {
|
||||
label: 'Collapse Pinned Tabs',
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Refresh the Page'
|
||||
}, {
|
||||
label: 'Clear Cookies and Refresh'
|
||||
}, {
|
||||
label: 'Clear Cache and Refresh'
|
||||
}, {
|
||||
type: 'separator' as const
|
||||
}, {
|
||||
label: 'Developer',
|
||||
children: [[{
|
||||
label: 'View Source',
|
||||
kbds: ['option', 'meta', 'U'],
|
||||
select() {
|
||||
console.log('View Source clicked')
|
||||
}
|
||||
}, {
|
||||
label: 'Developer Tools',
|
||||
kbds: ['option', 'meta', 'I'],
|
||||
select() {
|
||||
console.log('Developer Tools clicked')
|
||||
}
|
||||
}], [{
|
||||
label: 'Inspect Elements',
|
||||
kbds: ['option', 'meta', 'C'],
|
||||
select() {
|
||||
console.log('Inspect Elements clicked')
|
||||
}
|
||||
}], [{
|
||||
label: 'JavaScript Console',
|
||||
kbds: ['option', 'meta', 'J'],
|
||||
select() {
|
||||
console.log('JavaScript Console clicked')
|
||||
}
|
||||
}]]
|
||||
}]
|
||||
]
|
||||
|
||||
defineShortcuts(extractShortcuts(items))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" class="min-w-48">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-gray-300 dark:border-gray-700 text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</template>
|
||||
28
playground/app/pages/drawer.vue
Normal file
28
playground/app/pages/drawer.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
const open = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<UDrawer v-model:open="open" title="Drawer with v-model" description="This is useful to control the state yourself.">
|
||||
<UButton color="white" label="Open with v-model" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-48 w-full" />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<UButton label="Submit" color="black" class="justify-center" />
|
||||
<UButton label="Cancel" color="white" class="justify-center" @click="open = false" />
|
||||
</template>
|
||||
</UDrawer>
|
||||
|
||||
<UDrawer should-scale-background title="Drawer with `should-scale-background`" description="You need to add the `vaul-drawer-wrapper` directive to your content to make it work.">
|
||||
<UButton color="white" label="Open with scale" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-screen w-full" />
|
||||
</template>
|
||||
</UDrawer>
|
||||
</div>
|
||||
</template>
|
||||
128
playground/app/pages/dropdown-menu.vue
Normal file
128
playground/app/pages/dropdown-menu.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
[{
|
||||
label: 'My account',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
},
|
||||
type: 'label' as const
|
||||
}],
|
||||
[{
|
||||
label: 'Profile',
|
||||
icon: 'i-heroicons-user',
|
||||
slot: 'custom' as const,
|
||||
select(e: Event) {
|
||||
e.preventDefault()
|
||||
console.log('Profile clicked')
|
||||
}
|
||||
}, {
|
||||
label: 'Billing',
|
||||
icon: 'i-heroicons-credit-card',
|
||||
kbds: ['meta', 'b'],
|
||||
select() {
|
||||
console.log('Billing clicked')
|
||||
}
|
||||
}, {
|
||||
label: 'Settings',
|
||||
icon: 'i-heroicons-cog',
|
||||
kbds: ['?'],
|
||||
select() {
|
||||
console.log('Settings clicked')
|
||||
}
|
||||
}], [{
|
||||
label: 'Team',
|
||||
icon: 'i-heroicons-users'
|
||||
}, {
|
||||
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'],
|
||||
select(e: Event) {
|
||||
e?.preventDefault()
|
||||
console.log('Invite by link clicked')
|
||||
}
|
||||
}], [{
|
||||
label: 'More',
|
||||
icon: 'i-heroicons-plus-circle',
|
||||
children: [{
|
||||
label: 'Import from Slack',
|
||||
icon: 'i-simple-icons-slack',
|
||||
to: 'https://slack.com',
|
||||
target: '_blank',
|
||||
select(e: Event) {
|
||||
e.preventDefault()
|
||||
console.log('Import from Slack clicked')
|
||||
}
|
||||
}, {
|
||||
label: 'Import from Trello',
|
||||
icon: 'i-simple-icons-trello',
|
||||
select(e: Event) {
|
||||
e.preventDefault()
|
||||
console.log('Import from Trello clicked')
|
||||
}
|
||||
}, {
|
||||
label: 'Import from Asana',
|
||||
icon: 'i-simple-icons-asana',
|
||||
select(e: Event) {
|
||||
e.preventDefault()
|
||||
console.log('Import from Asana clicked')
|
||||
}
|
||||
}]
|
||||
}]]
|
||||
}, {
|
||||
label: 'New team',
|
||||
icon: 'i-heroicons-plus',
|
||||
kbds: ['meta', 'n'],
|
||||
select() {
|
||||
console.log('New team clicked')
|
||||
}
|
||||
}], [{
|
||||
label: 'GitHub',
|
||||
icon: 'i-simple-icons-github',
|
||||
to: 'https://github.com/nuxt/ui',
|
||||
target: '_blank',
|
||||
select(e: Event) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}, {
|
||||
label: 'Support',
|
||||
icon: 'i-heroicons-lifebuoy',
|
||||
to: '/dropdown-menu'
|
||||
}, {
|
||||
type: 'separator' as const
|
||||
}, {
|
||||
label: 'Keyboard Shortcuts',
|
||||
icon: 'i-heroicons-key'
|
||||
}, {
|
||||
label: 'API',
|
||||
icon: 'i-heroicons-cube',
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Logout',
|
||||
icon: 'i-heroicons-arrow-right-start-on-rectangle',
|
||||
kbds: ['shift', 'meta', 'q'],
|
||||
select() {
|
||||
console.log('Logout clicked')
|
||||
}
|
||||
}]
|
||||
]
|
||||
|
||||
defineShortcuts(extractShortcuts(items))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1">
|
||||
<UDropdownMenu :items="items" arrow :content="{ side: 'bottom', align: 'start' }" class="min-w-48">
|
||||
<UButton label="Open" color="white" icon="i-heroicons-user" />
|
||||
|
||||
<template #custom-trailing>
|
||||
<UIcon name="i-heroicons-check-badge" class="shrink-0 size-5 text-primary-500 dark:text-primary-400" />
|
||||
</template>
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
</template>
|
||||
49
playground/app/pages/form-field.vue
Normal file
49
playground/app/pages/form-field.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/form-field'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
const feedbacks = [
|
||||
{ description: 'This is a description' },
|
||||
{ error: 'This is an error' },
|
||||
{ hint: 'This is a hint' },
|
||||
{ help: 'Help! I need somebody!' },
|
||||
{ required: true }
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 ml-[-38px]">
|
||||
<div v-for="(feedback, count) in feedbacks" :key="count" class="flex items-center">
|
||||
<UFormField v-bind="feedback" label="Email" name="email">
|
||||
<UInput placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<UFormField
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:size="(size as any)"
|
||||
label="Email"
|
||||
name="email"
|
||||
>
|
||||
<UInput placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<UFormField
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:size="(size as any)"
|
||||
label="Email"
|
||||
description="This is a description"
|
||||
name="email"
|
||||
>
|
||||
<UInput placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
85
playground/app/pages/form.vue
Normal file
85
playground/app/pages/form.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '#ui/types/form'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8),
|
||||
tos: z.literal(true)
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive<Partial<Schema>>({})
|
||||
const state2 = reactive<Partial<Schema>>({})
|
||||
|
||||
function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex gap-4">
|
||||
<UForm
|
||||
:state="state"
|
||||
:schema="schema"
|
||||
class="gap-4 flex flex-col w-60"
|
||||
@submit="(event) => onSubmit(event)"
|
||||
>
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="tos">
|
||||
<UCheckbox v-model="state.tos" label="I accept the terms and conditions" />
|
||||
</UFormField>
|
||||
|
||||
<div>
|
||||
<UButton color="gray" type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
|
||||
<UForm
|
||||
:state="state2"
|
||||
:schema="schema"
|
||||
class="gap-4 flex flex-col w-60"
|
||||
:validate-on-input-delay="2000"
|
||||
@submit="(event) => onSubmit(event)"
|
||||
>
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state2.email" placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField
|
||||
label="Password"
|
||||
name="password"
|
||||
:validate-on-input-delay="50"
|
||||
eager-validation
|
||||
>
|
||||
<UInput v-model="state2.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<div>
|
||||
<UButton color="gray" type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<FormNestedExample />
|
||||
<FormNestedListExample />
|
||||
<FormElementsExample />
|
||||
<FormElementsExample disabled />
|
||||
</div>
|
||||
<div class="flex gap-4" />
|
||||
</div>
|
||||
</template>
|
||||
11
playground/app/pages/index.vue
Normal file
11
playground/app/pages/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="text-center">
|
||||
<h1 class="font-semibold mb-1">
|
||||
Playground
|
||||
</h1>
|
||||
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<UKbd value="meta" /> <UKbd value="K" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
119
playground/app/pages/input-menu.vue
Normal file
119
playground/app/pages/input-menu.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
import type { User } from '~/types'
|
||||
import theme from '#build/ui/input-menu'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
|
||||
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
|
||||
|
||||
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
|
||||
const selectedItems = ref([fruits[0], vegetables[0]])
|
||||
|
||||
const statuses = [{
|
||||
label: 'Backlog',
|
||||
icon: 'i-heroicons-question-mark-circle'
|
||||
}, {
|
||||
label: 'Todo',
|
||||
icon: 'i-heroicons-plus-circle'
|
||||
}, {
|
||||
label: 'In Progress',
|
||||
icon: 'i-heroicons-arrow-up-circle'
|
||||
}, {
|
||||
label: 'Done',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
}, {
|
||||
label: 'Canceled',
|
||||
icon: 'i-heroicons-x-circle'
|
||||
}]
|
||||
|
||||
const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, pending } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: User[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
},
|
||||
lazy: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 w-60">
|
||||
<UInputMenu :items="items" autofocus />
|
||||
<UInputMenu :items="items" placeholder="Search..." color="gray" />
|
||||
<UInputMenu :items="items" placeholder="Search..." color="primary" />
|
||||
<UInputMenu :items="items" placeholder="Search..." variant="none" />
|
||||
<UInputMenu :items="items" placeholder="Disabled" disabled />
|
||||
<UInputMenu :items="items" placeholder="Required" required />
|
||||
<UInputMenu v-model="selectedItems" :items="items" placeholder="Multiple" multiple />
|
||||
<UInputMenu :items="items" loading placeholder="Search..." />
|
||||
<UInputMenu :items="statuses" placeholder="Search status..." icon="i-heroicons-magnifying-glass" trailing-icon="i-heroicons-chevron-up-down-20-solid">
|
||||
<template #leading="{ modelValue }">
|
||||
<UIcon v-if="modelValue?.icon" :name="modelValue.icon" class="size-5" />
|
||||
</template>
|
||||
</UInputMenu>
|
||||
<UInputMenu
|
||||
v-model:search-term="searchTerm"
|
||||
:items="users || []"
|
||||
:loading="pending"
|
||||
:filter="false"
|
||||
icon="i-heroicons-user"
|
||||
placeholder="Search users..."
|
||||
>
|
||||
<template #leading="{ modelValue }">
|
||||
<UAvatar v-if="modelValue?.avatar" size="2xs" v-bind="modelValue.avatar" />
|
||||
</template>
|
||||
</UInputMenu>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInputMenu
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInputMenu
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInputMenu
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
trailing
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInputMenu
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
:model-value="[fruits[0]]"
|
||||
multiple
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
55
playground/app/pages/input.vue
Normal file
55
playground/app/pages/input.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/input'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 w-60">
|
||||
<UInput autofocus />
|
||||
<UInput placeholder="Search..." color="gray" />
|
||||
<UInput placeholder="Search..." color="primary" />
|
||||
<UInput placeholder="Search..." variant="none" />
|
||||
<UInput placeholder="Disabled" disabled />
|
||||
<UInput placeholder="Required" required />
|
||||
<UInput file="i-heroicons-calculator" type="number" :model-value="10" />
|
||||
<UInput icon="i-heroicons-folder" type="file" />
|
||||
<UInput icon="i-heroicons-calendar" type="date" :model-value="new Date().toISOString().substring(0, 10)" />
|
||||
<UInput icon="i-heroicons-lock-closed" type="password" model-value="password" />
|
||||
<UInput loading placeholder="Search..." />
|
||||
<UInput loading trailing placeholder="Search..." />
|
||||
<UInput loading icon="i-heroicons-magnifying-glass" trailing-icon="i-heroicons-chevron-down" placeholder="Search..." />
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInput
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInput
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UInput
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
trailing
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
27
playground/app/pages/kbd.vue
Normal file
27
playground/app/pages/kbd.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/kbd'
|
||||
import { kbdKeysMap } from '#ui/composables/useKbd'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
const kbdKeys = Object.keys(kbdKeysMap)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<UKbd value="meta" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<UKbd value="meta" color="gray" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<UKbd value="meta" color="black" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1 ml-[-216px]">
|
||||
<UKbd v-for="(kdbKey, index) in kbdKeys" :key="index" :value="kdbKey" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1 ml-[-22px]">
|
||||
<UKbd v-for="size in sizes" :key="size" value="meta" :size="(size as any)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
55
playground/app/pages/link.vue
Normal file
55
playground/app/pages/link.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex flex-col items-start gap-2 text-sm">
|
||||
<ULink raw>
|
||||
Button raw
|
||||
</ULink>
|
||||
|
||||
<ULink active>
|
||||
Button active
|
||||
</ULink>
|
||||
<ULink active class="font-medium" active-class="text-gray-900 dark:text-white">
|
||||
Button active with class
|
||||
</ULink>
|
||||
<ULink active disabled>
|
||||
Button active disabled
|
||||
</ULink>
|
||||
|
||||
<ULink>
|
||||
Button inactive
|
||||
</ULink>
|
||||
<ULink class="font-medium" inactive-class="hover:text-primary-500 dark:hover:text-primary-400">
|
||||
Button inactive with class
|
||||
</ULink>
|
||||
<ULink disabled>
|
||||
Button inactive disabled
|
||||
</ULink>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start gap-2 text-sm">
|
||||
<ULink to="/link" raw>
|
||||
Link raw
|
||||
</ULink>
|
||||
|
||||
<ULink to="/link">
|
||||
Link active
|
||||
</ULink>
|
||||
<ULink to="/link" class="font-medium" active-class="text-gray-900 dark:text-white">
|
||||
Link active with class
|
||||
</ULink>
|
||||
<ULink to="/link" disabled>
|
||||
Link active disabled
|
||||
</ULink>
|
||||
|
||||
<ULink to="/button">
|
||||
Link inactive
|
||||
</ULink>
|
||||
<ULink to="/button" class="font-medium" inactive-class="hover:text-primary-500 dark:hover:text-primary-400">
|
||||
Link inactive with class
|
||||
</ULink>
|
||||
<ULink to="/button" disabled>
|
||||
Link inactive disabled
|
||||
</ULink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
67
playground/app/pages/modal.vue
Normal file
67
playground/app/pages/modal.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { LazyModalProgrammaticExample } from '#components'
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
const modal = useModal()
|
||||
const count = ref(0)
|
||||
const openModal = () => {
|
||||
count.value++
|
||||
modal.open(LazyModalProgrammaticExample, {
|
||||
description: 'And you can even provide a description !',
|
||||
count: count.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<UModal title="First modal">
|
||||
<UButton color="white" label="Open with nested" />
|
||||
|
||||
<template #footer>
|
||||
<UModal title="Second modal">
|
||||
<UButton label="Open second" />
|
||||
</UModal>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<UModal v-model:open="open" title="Modal with v-model" description="This can be useful to control the state of the modal yourself." />
|
||||
|
||||
<UButton label="Open with v-model" color="gray" @click="open = true" />
|
||||
|
||||
<UModal title="Modal without overlay" description="This modal has `overlay: false` prop." :overlay="false">
|
||||
<UButton label="Open without overlay" color="white" />
|
||||
</UModal>
|
||||
|
||||
<UModal title="Modal without modal & overlay" description="This modal has `modal: false` and `overlay: false` to interact with outside content." :overlay="false" :modal="false">
|
||||
<UButton label="Open without modal" color="gray" />
|
||||
</UModal>
|
||||
|
||||
<UModal title="Modal without transition" description="This modal has `transition: false` prop." :transition="false">
|
||||
<UButton label="Open without transition" color="white" />
|
||||
</UModal>
|
||||
|
||||
<UModal title="Modal without portal" description="This modal has `portal: false` prop." :portal="false">
|
||||
<UButton label="Open without portal" color="gray" />
|
||||
</UModal>
|
||||
|
||||
<UModal title="Modal fullscreen" description="This modal has `fullscreen: true` prop." fullscreen>
|
||||
<UButton label="Open fullscreen" color="white" />
|
||||
</UModal>
|
||||
|
||||
<UModal title="Modal prevent close" description="This modal has `prevent-close: true` prop so it won't close when clicking outside." prevent-close>
|
||||
<UButton label="Open unclosable" color="gray" />
|
||||
</UModal>
|
||||
|
||||
<UModal title="Modal without close button" description="This modal has `close: false` prop." :close="false">
|
||||
<UButton label="Open without close button" color="white" />
|
||||
</UModal>
|
||||
|
||||
<UModal title="Modal with custom close button" description="The `close` prop inherits from the Button props." :close="{ color: 'primary', variant: 'solid', size: 'xs' }" :ui="{ close: 'top-3.5 rounded-full' }">
|
||||
<UButton label="Open with custom close button" color="gray" />
|
||||
</UModal>
|
||||
|
||||
<UButton label="Open programmatically" color="white" @click="openModal" />
|
||||
</div>
|
||||
</template>
|
||||
107
playground/app/pages/navigation-menu.vue
Normal file
107
playground/app/pages/navigation-menu.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/navigation-menu'
|
||||
|
||||
const colors = Object.keys(theme.variants.color)
|
||||
const variants = Object.keys(theme.variants.variant)
|
||||
const orientations = Object.keys(theme.variants.orientation)
|
||||
|
||||
const color = ref(theme.defaultVariants.color)
|
||||
const highlightColor = ref()
|
||||
const variant = ref(theme.defaultVariants.variant)
|
||||
const orientation = ref('horizontal' as const)
|
||||
const highlight = ref(true)
|
||||
|
||||
const items = [
|
||||
[{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open',
|
||||
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: 'Components',
|
||||
icon: 'i-heroicons-cube-transparent',
|
||||
active: true,
|
||||
children: [{
|
||||
label: 'Link',
|
||||
icon: 'i-heroicons-document',
|
||||
description: 'Use NuxtLink with superpowers.',
|
||||
to: '/link'
|
||||
}, {
|
||||
label: 'Modal',
|
||||
icon: 'i-heroicons-document',
|
||||
description: 'Display a modal within your application.',
|
||||
to: '/modal'
|
||||
}, {
|
||||
label: 'NavigationMenu',
|
||||
icon: 'i-heroicons-document',
|
||||
description: 'Display a list of links.',
|
||||
to: '/navigation-menu'
|
||||
}, {
|
||||
label: 'Pagination',
|
||||
icon: 'i-heroicons-document',
|
||||
description: 'Display a list of pages.',
|
||||
to: '/pagination'
|
||||
}, {
|
||||
label: 'Popover',
|
||||
icon: 'i-heroicons-document',
|
||||
description: 'Display a non-modal dialog that floats around a trigger element.',
|
||||
to: '/popover'
|
||||
}, {
|
||||
label: 'Progress',
|
||||
icon: 'i-heroicons-document',
|
||||
description: 'Show a horizontal bar to indicate task progression.',
|
||||
to: '/progress'
|
||||
}]
|
||||
}, {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-12">
|
||||
<div class="flex items-center gap-2">
|
||||
<USelect v-model="color" :items="colors" placeholder="Color" />
|
||||
<USelect v-model="variant" :items="variants" placeholder="Variant" />
|
||||
<USelect v-model="orientation" :items="orientations" placeholder="Orientation" />
|
||||
<USwitch v-model="highlight" label="Highlight" />
|
||||
<USelect v-model="highlightColor" :items="colors" placeholder="Highlight color" />
|
||||
</div>
|
||||
|
||||
<UNavigationMenu
|
||||
:items="items"
|
||||
:color="color"
|
||||
:variant="variant"
|
||||
:orientation="orientation"
|
||||
:highlight="highlight"
|
||||
:highlight-color="highlightColor"
|
||||
arrow
|
||||
:class="highlight ? [
|
||||
'border-gray-200 dark:border-gray-800',
|
||||
orientation === 'vertical' as any ? 'border-l' : 'border-b'
|
||||
] : undefined"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
17
playground/app/pages/pagination.vue
Normal file
17
playground/app/pages/pagination.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
const page = ref(5)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<p>With buttons (default)</p>
|
||||
<UPagination v-model:page="page" :total="100" :sibling-count="1" show-edges />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<p>With links</p>
|
||||
<UPagination v-model:page="page" :total="100" :to="page => ({ query: { page } })" :sibling-count="1" show-edges />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
92
playground/app/pages/popover.vue
Normal file
92
playground/app/pages/popover.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
const open = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
function send() {
|
||||
loading.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
open.value = false
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-center">
|
||||
<div>
|
||||
<UPopover arrow :content="{ side: 'top' }">
|
||||
<UButton label="Click me top" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="w-48 h-16" />
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<div class="flex items-center gap-2 my-2">
|
||||
<UPopover arrow :content="{ side: 'left' }">
|
||||
<UButton label="Click me top" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="w-48 h-16" />
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<UPopover arrow :content="{ side: 'right' }">
|
||||
<UButton label="Click me top" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="w-48 h-16" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
|
||||
<UPopover v-model:open="open" arrow>
|
||||
<UButton label="Click me bottom" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="flex justify-center gap-2 p-4 w-48">
|
||||
<UButton label="Close" color="gray" @click="open = false" />
|
||||
<UButton label="Send" color="black" trailing-icon="i-heroicons-paper-airplane" :loading="loading" @click="send" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
|
||||
<div class="mt-24">
|
||||
<UPopover mode="hover" arrow :content="{ side: 'top' }">
|
||||
<UButton label="Hover me top" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="w-48 h-16" />
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<div class="flex items-center gap-2 my-2">
|
||||
<UPopover mode="hover" arrow :content="{ side: 'left' }">
|
||||
<UButton label="Hover me left" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="w-48 h-16" />
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<UPopover mode="hover" arrow :content="{ side: 'right' }">
|
||||
<UButton label="Hover me right" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="w-48 h-16" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
|
||||
<UPopover mode="hover" arrow>
|
||||
<UButton label="Hover me bottom" color="white" />
|
||||
|
||||
<template #content>
|
||||
<div class="w-48 h-16" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
69
playground/app/pages/progress.vue
Normal file
69
playground/app/pages/progress.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import theme from '#build/ui/progress'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const value1 = ref(0)
|
||||
const value2 = ref(0)
|
||||
const max = ['Waiting...', 'Cloning...', 'Migrating...', 'Deploying...', 'Done!']
|
||||
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
if (value1.value === 100) {
|
||||
value1.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
value1.value += 25
|
||||
}, 1000)
|
||||
|
||||
setInterval(() => {
|
||||
if (value2.value === 4) {
|
||||
value2.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
value2.value += 1
|
||||
}, 1000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-8 items-center">
|
||||
<div class="flex flex-col gap-4 w-48">
|
||||
<UProgress />
|
||||
<UProgress color="black" />
|
||||
<UProgress color="red" />
|
||||
<UProgress animation="carousel-inverse" />
|
||||
<UProgress animation="swing" />
|
||||
<UProgress animation="elastic" />
|
||||
<UProgress v-model="value2" :max="max" status />
|
||||
<UProgress v-model="value2" :max="max" status inverted />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<UProgress v-for="size in sizes" :key="size" v-model="value1" :size="(size as any)" class="w-48" />
|
||||
</div>
|
||||
|
||||
<div class="h-48 flex items-center gap-8">
|
||||
<UProgress orientation="vertical" />
|
||||
<UProgress orientation="vertical" animation="carousel-inverse" />
|
||||
<UProgress orientation="vertical" animation="swing" />
|
||||
<UProgress orientation="vertical" animation="elastic" />
|
||||
<UProgress v-model="value2" orientation="vertical" :max="max" status class="w-48 justify-start" />
|
||||
<UProgress
|
||||
v-model="value2"
|
||||
orientation="vertical"
|
||||
:max="max"
|
||||
status
|
||||
inverted
|
||||
class="w-48 justify-start"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="h-48 flex items-center gap-8">
|
||||
<UProgress v-for="size in sizes" :key="size" v-model="value1" orientation="vertical" :size="(size as any)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
61
playground/app/pages/radio-group.vue
Normal file
61
playground/app/pages/radio-group.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/radio-group'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const literalOptions = [
|
||||
'Option 1',
|
||||
'Option 2',
|
||||
'Option 3'
|
||||
]
|
||||
const items = [
|
||||
{ value: '1', label: 'Option 1' },
|
||||
{ value: '2', label: 'Option 2' },
|
||||
{ value: '3', label: 'Option 3' }
|
||||
]
|
||||
|
||||
const itemsWithDescription = [
|
||||
{ value: '1', label: 'Option 1', description: 'Description 1' },
|
||||
{ value: '2', label: 'Option 2', description: 'Description 2' },
|
||||
{ value: '3', label: 'Option 3', description: 'Description 3' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 ml-[100px]">
|
||||
<URadioGroup :items="items" default-value="1" />
|
||||
<URadioGroup :items="literalOptions" />
|
||||
<URadioGroup :items="items" label="Disabled" disabled />
|
||||
<URadioGroup :items="items" color="black" default-value="1" />
|
||||
<URadioGroup :items="items" color="red" default-value="2" />
|
||||
<URadioGroup :items="items" orientation="horizontal" class="ml-[-91px]" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 ml-[34px]">
|
||||
<URadioGroup v-for="size in sizes" :key="size" :size="(size as any)" :items="items" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 ml-[74px]">
|
||||
<URadioGroup v-for="size in sizes" :key="size" :size="(size as any)" :items="itemsWithDescription" />
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<URadioGroup :items="items" legend="Legend" />
|
||||
<URadioGroup :items="items" legend="Legend" required />
|
||||
<URadioGroup :items="items">
|
||||
<template #legend>
|
||||
<span class="italic font-bold">
|
||||
With slots
|
||||
</span>
|
||||
</template>
|
||||
<template #label="{ item }">
|
||||
<span class="italic">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</template>
|
||||
</URadioGroup>
|
||||
</div>
|
||||
<URadioGroup :items="items" legend="Legend" orientation="horizontal" required />
|
||||
</div>
|
||||
</template>
|
||||
112
playground/app/pages/select-menu.vue
Normal file
112
playground/app/pages/select-menu.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
import theme from '#build/ui/select-menu'
|
||||
import type { User } from '~/types'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
|
||||
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
|
||||
|
||||
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
|
||||
const selectedItems = ref([fruits[0], vegetables[0]])
|
||||
|
||||
const statuses = [{
|
||||
label: 'Backlog',
|
||||
value: 'backlog',
|
||||
icon: 'i-heroicons-question-mark-circle'
|
||||
}, {
|
||||
label: 'Todo',
|
||||
value: 'todo',
|
||||
icon: 'i-heroicons-plus-circle'
|
||||
}, {
|
||||
label: 'In Progress',
|
||||
value: 'in_progress',
|
||||
icon: 'i-heroicons-arrow-up-circle'
|
||||
}, {
|
||||
label: 'Done',
|
||||
value: 'done',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
}, {
|
||||
label: 'Canceled',
|
||||
value: 'canceled',
|
||||
icon: 'i-heroicons-x-circle'
|
||||
}]
|
||||
|
||||
const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, pending } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: User[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
},
|
||||
lazy: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 w-60">
|
||||
<USelectMenu :items="items" />
|
||||
<USelectMenu :items="items" placeholder="Search..." color="gray" />
|
||||
<USelectMenu :items="items" placeholder="Search..." color="primary" />
|
||||
<USelectMenu :items="items" placeholder="Search..." variant="none" />
|
||||
<USelectMenu :items="items" placeholder="Disabled" disabled />
|
||||
<USelectMenu :items="items" placeholder="Required" required />
|
||||
<USelectMenu v-model="selectedItems" :items="items" placeholder="Multiple" multiple />
|
||||
<USelectMenu :items="items" loading placeholder="Search..." />
|
||||
<USelectMenu :items="statuses" placeholder="Search status..." icon="i-heroicons-magnifying-glass" trailing-icon="i-heroicons-chevron-up-down-20-solid">
|
||||
<template #leading="{ modelValue }">
|
||||
<UIcon v-if="modelValue" :name="modelValue.icon" class="size-5" />
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<USelectMenu
|
||||
v-model:search-term="searchTerm"
|
||||
:items="users || []"
|
||||
:loading="pending"
|
||||
:filter="false"
|
||||
icon="i-heroicons-user"
|
||||
placeholder="Search users..."
|
||||
@update:open="searchTerm = ''"
|
||||
>
|
||||
<template #leading="{ modelValue }">
|
||||
<UAvatar v-if="modelValue?.avatar" size="2xs" v-bind="modelValue.avatar" />
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USelectMenu
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USelectMenu
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USelectMenu
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
trailing
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
89
playground/app/pages/select.vue
Normal file
89
playground/app/pages/select.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/select'
|
||||
import type { User } from '~/types'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
|
||||
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
|
||||
|
||||
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
|
||||
|
||||
const statuses = [{
|
||||
label: 'Backlog',
|
||||
value: 'backlog',
|
||||
icon: 'i-heroicons-question-mark-circle'
|
||||
}, {
|
||||
label: 'Todo',
|
||||
value: 'todo',
|
||||
icon: 'i-heroicons-plus-circle'
|
||||
}, {
|
||||
label: 'In Progress',
|
||||
value: 'in_progress',
|
||||
icon: 'i-heroicons-arrow-up-circle'
|
||||
}, {
|
||||
label: 'Done',
|
||||
value: 'done',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
}, {
|
||||
label: 'Canceled',
|
||||
value: 'canceled',
|
||||
icon: 'i-heroicons-x-circle'
|
||||
}]
|
||||
|
||||
const { data: users, pending } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
transform: (data: User[]) => {
|
||||
return data?.map(user => ({ label: user.name, value: user.id, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
},
|
||||
lazy: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 w-60">
|
||||
<USelect :items="items" />
|
||||
<USelect :items="items" placeholder="Search..." color="gray" />
|
||||
<USelect :items="items" placeholder="Search..." color="primary" />
|
||||
<USelect :items="items" placeholder="Search..." variant="none" />
|
||||
<USelect :items="items" placeholder="Disabled" disabled />
|
||||
<USelect :items="items" placeholder="Required" required />
|
||||
<USelect :items="items" loading placeholder="Search..." />
|
||||
<USelect :items="statuses" placeholder="Search status..." icon="i-heroicons-magnifying-glass" trailing-icon="i-heroicons-chevron-up-down-20-solid" />
|
||||
<USelect :items="users || []" :loading="pending" icon="i-heroicons-user" placeholder="Search users..." />
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USelect
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USelect
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USelect
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:items="items"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
trailing
|
||||
placeholder="Search..."
|
||||
:size="(size as any)"
|
||||
class="w-60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
38
playground/app/pages/separator.vue
Normal file
38
playground/app/pages/separator.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<p class="font-semibold text-gray-900 dark:text-white">
|
||||
Nuxt UI
|
||||
</p>
|
||||
<p>An open-source UI component library.</p>
|
||||
</div>
|
||||
|
||||
<USeparator icon="i-simple-icons-nuxtdotjs" type="dashed" />
|
||||
|
||||
<div class="h-24 flex gap-4 items-center">
|
||||
<div class="flex-1 text-center">
|
||||
Blog
|
||||
</div>
|
||||
|
||||
<USeparator
|
||||
:avatar="{ src: 'https://avatars.githubusercontent.com/u/739984?v=4' }"
|
||||
decorative
|
||||
orientation="vertical"
|
||||
/>
|
||||
|
||||
<div class="flex-1 text-center">
|
||||
Docs
|
||||
</div>
|
||||
|
||||
<USeparator decorative orientation="vertical">
|
||||
<UAvatar size="2xs" src="https://avatars.githubusercontent.com/u/13056429?v=4" />
|
||||
</USeparator>
|
||||
|
||||
<div class="flex-1 text-center">
|
||||
Source
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<USeparator label="As simple as it gets" type="dotted" size="lg" color="primary" />
|
||||
</div>
|
||||
</template>
|
||||
80
playground/app/pages/shortcuts.vue
Normal file
80
playground/app/pages/shortcuts.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="w-full flex flex-col gap-4">
|
||||
<UCard class="flex-1">
|
||||
<template #header>
|
||||
<h3 class="font-bold">
|
||||
Shortcuts
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div>
|
||||
<span>{{ shortcutsState.a.label }} shortcut</span>
|
||||
<UCheckbox v-model="shortcutsState.a.disabled" :label="`Disable ${shortcutsState.a.label}`" />
|
||||
<UCheckbox v-model="shortcutsState.a.usingInput" :label="`Using in inputs ${shortcutsState.a.label}`" />
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ shortcutsState.shift_i.label }} shortcut</span>
|
||||
<UCheckbox v-model="shortcutsState.shift_i.disabled" :label="`Disable ${shortcutsState.shift_i.label}`" />
|
||||
<UCheckbox v-model="shortcutsState.shift_i.usingInput" :label="`Using in inputs ${shortcutsState.shift_i.label}`" />
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ shortcutsState['g-i'].label }} shortcut</span>
|
||||
<UCheckbox v-model="shortcutsState['g-i'].disabled" :label="`Disable ${shortcutsState['g-i'].label}`" />
|
||||
<UCheckbox v-model="shortcutsState['g-i'].usingInput" :label="`Using in inputs ${shortcutsState['g-i'].label}`" />
|
||||
</div>
|
||||
<UInput placeholder="Input to focus" />
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard :ui="{ body: 'h-[200px] overflow-y-auto' }" class="flex-1">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3 class="font-bold">
|
||||
Logs
|
||||
</h3>
|
||||
<UButton icon="i-heroicons-trash" size="sm" color="gray" class="-my-1" @click="logs = []" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p v-for="(log, index) of logs" :key="index">
|
||||
{{ log }}
|
||||
</p>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const logs = ref<string[]>([])
|
||||
const shortcutsState = ref({
|
||||
'a': {
|
||||
label: 'A',
|
||||
disabled: false,
|
||||
usingInput: false
|
||||
},
|
||||
'shift_i': {
|
||||
label: 'Shift+I',
|
||||
disabled: false,
|
||||
usingInput: false
|
||||
},
|
||||
'g-i': {
|
||||
label: 'G->I',
|
||||
disabled: false,
|
||||
usingInput: false
|
||||
}
|
||||
})
|
||||
|
||||
const shortcuts = computed(() => {
|
||||
return Object.entries(shortcutsState.value).reduce<ShortcutsConfig>((acc, [key, { label, disabled, usingInput }]) => {
|
||||
if (disabled) {
|
||||
return acc
|
||||
}
|
||||
acc[key] = {
|
||||
handler: () => { logs.value.unshift(`"${label}" triggered`) },
|
||||
usingInput
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
defineShortcuts(shortcuts)
|
||||
</script>
|
||||
10
playground/app/pages/skeleton.vue
Normal file
10
playground/app/pages/skeleton.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-4">
|
||||
<USkeleton class="h-12 w-12 rounded-full" />
|
||||
|
||||
<div class="space-y-2">
|
||||
<USkeleton class="h-4 w-[250px]" />
|
||||
<USkeleton class="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
123
playground/app/pages/slideover.vue
Normal file
123
playground/app/pages/slideover.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import { LazySlideoverProgrammaticExample } from '#components'
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
const slideover = useSlideover()
|
||||
const count = ref(0)
|
||||
const openSlideover = () => {
|
||||
count.value++
|
||||
slideover.open(LazySlideoverProgrammaticExample, {
|
||||
description: 'And you can even provide a description!',
|
||||
count: count.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<USlideover title="First slideover">
|
||||
<UButton color="white" label="Open with nested" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<USlideover title="Second slideover">
|
||||
<UButton label="Open second" />
|
||||
</USlideover>
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover on left side" description="This slideover has `side: 'left'` prop." side="left">
|
||||
<UButton label="Open on left" color="gray" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover on top side" description="This slideover has `side: 'top'` prop." side="top">
|
||||
<UButton label="Open on top" color="white" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-48 w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover on bottom side" description="This slideover has `side: 'bottom'` prop." side="bottom">
|
||||
<UButton label="Open on bottom" color="gray" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-48 w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover v-model:open="open" title="Slideover with v-model" description="This is useful to control the state yourself.">
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<UButton label="Open with v-model" color="white" @click="open = true" />
|
||||
|
||||
<USlideover title="Slideover without overlay" description="This slideover has `overlay: false` prop." :overlay="false">
|
||||
<UButton label="Open without overlay" color="gray" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover without modal & overlay" description="This slideover has `modal: false` and `overlay: false` to interact with outside content." :overlay="false" :modal="false">
|
||||
<UButton label="Open without modal" color="white" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover without transition" description="This slideover has `transition: false` prop." :transition="false">
|
||||
<UButton label="Open without transition" color="gray" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover without portal" description="This slideover has `portal: false` prop." :portal="false">
|
||||
<UButton label="Open without portal" color="white" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover prevent close" description="This slideover has `prevent-close: true` prop so it won't close when clicking outside." prevent-close>
|
||||
<UButton label="Open unclosable" color="gray" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover without close button" description="This slideover has `close: false` prop." :close="false">
|
||||
<UButton label="Open without close button" color="white" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<USlideover title="Slideover with custom close button" description="The `close` prop inherits from the Button props." :close="{ color: 'primary', variant: 'solid', size: 'xs' }" :ui="{ close: 'top-3.5 rounded-full' }">
|
||||
<UButton label="Open with custom close button" color="gray" />
|
||||
|
||||
<template #body>
|
||||
<Placeholder class="h-full w-full" />
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
<UButton label="Open programmatically" color="white" @click="openSlideover" />
|
||||
</div>
|
||||
</template>
|
||||
38
playground/app/pages/slider.vue
Normal file
38
playground/app/pages/slider.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/slider'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const value = ref(50)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 items-center">
|
||||
<div class="flex flex-col gap-6 w-48">
|
||||
<USlider v-model="value" />
|
||||
<USlider :default-value="100" />
|
||||
<USlider inverted />
|
||||
<USlider disabled />
|
||||
<USlider color="black" :model-value="50" />
|
||||
<USlider color="red" :model-value="50" />
|
||||
<USlider :min="4" :max="12" :step="2" :model-value="6" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6 w-48">
|
||||
<USlider :default-value="[0, 20]" />
|
||||
<USlider :model-value="[0, 20]" />
|
||||
<USlider :model-value="[0, 20, 80]" />
|
||||
<USlider :model-value="[0, 80]" :min-steps-between-thumbs="20" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<USlider v-for="size in sizes" :key="size" v-model="value" :size="(size as any)" class="w-48" />
|
||||
</div>
|
||||
|
||||
<div class="h-48 flex items-center gap-6">
|
||||
<USlider :model-value="[0, 20, 80]" orientation="vertical" />
|
||||
<USlider v-model="value" orientation="vertical" />
|
||||
<USlider :model-value="[0, 80]" :min-steps-between-thumbs="20" orientation="vertical" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
64
playground/app/pages/switch.vue
Normal file
64
playground/app/pages/switch.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/switch'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const checked = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 ml-[-114px]">
|
||||
<USwitch v-model="checked" label="Model" />
|
||||
<USwitch label="Default value" :default-value="true" />
|
||||
<USwitch label="Required" required />
|
||||
<USwitch label="Disabled" disabled />
|
||||
<USwitch color="black" label="Black" :default-value="true" />
|
||||
<USwitch color="red" label="Red" :default-value="true" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4 ml-[-82px]">
|
||||
<USwitch v-for="size in sizes" :key="size" :size="(size as any)" label="Switch me" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4 ml-[-82px]">
|
||||
<USwitch
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:size="(size as any)"
|
||||
label="Switch me"
|
||||
unchecked-icon="i-heroicons-x-mark-20-solid"
|
||||
checked-icon="i-heroicons-check-20-solid"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 ml-[-82px]">
|
||||
<USwitch
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:size="(size as any)"
|
||||
label="Switch me"
|
||||
unchecked-icon="i-heroicons-x-mark-20-solid"
|
||||
checked-icon="i-heroicons-check-20-solid"
|
||||
loading
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USwitch
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:size="(size as any)"
|
||||
label="Switch me"
|
||||
description="This is a description"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<USwitch
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:size="(size as any)"
|
||||
label="Switch me"
|
||||
description="This is a description"
|
||||
unchecked-icon="i-heroicons-x-mark-20-solid"
|
||||
checked-icon="i-heroicons-check-20-solid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
65
playground/app/pages/tabs.vue
Normal file
65
playground/app/pages/tabs.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/tabs'
|
||||
|
||||
const colors = Object.keys(theme.variants.color)
|
||||
const variants = Object.keys(theme.variants.variant)
|
||||
const orientations = Object.keys(theme.variants.orientation)
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
|
||||
const color = ref(theme.defaultVariants.color)
|
||||
const variant = ref(theme.defaultVariants.variant)
|
||||
const orientation = ref('horizontal' as const)
|
||||
const size = ref('md' as const)
|
||||
|
||||
const 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',
|
||||
slot: 'custom' as const
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-12">
|
||||
<div class="flex items-center gap-2">
|
||||
<USelect v-model="color" :items="colors" placeholder="Color" />
|
||||
<USelect v-model="variant" :items="variants" placeholder="Variant" />
|
||||
<USelect v-model="orientation" :items="orientations" placeholder="Orientation" />
|
||||
<USelect v-model="size" :items="sizes" placeholder="Size" />
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<UTabs
|
||||
:color="color"
|
||||
:variant="variant"
|
||||
:orientation="orientation"
|
||||
:size="size"
|
||||
:items="[{ label: 'Monthly' }, { label: 'Yearly' }]"
|
||||
:content="false"
|
||||
/>
|
||||
|
||||
<UTabs
|
||||
:color="color"
|
||||
:variant="variant"
|
||||
:orientation="orientation"
|
||||
:size="size"
|
||||
:items="items"
|
||||
class="w-96"
|
||||
>
|
||||
<template #custom="{ item }">
|
||||
<span class="text-gray-500 dark:text-gray-400">Custom: {{ item.content }}</span>
|
||||
</template>
|
||||
</UTabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
21
playground/app/pages/textarea.vue
Normal file
21
playground/app/pages/textarea.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/textarea'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 w-60">
|
||||
<UTextarea autofocus />
|
||||
<UTextarea placeholder="Search..." color="gray" />
|
||||
<UTextarea placeholder="Search..." color="primary" />
|
||||
<UTextarea placeholder="Search..." variant="none" />
|
||||
<UTextarea placeholder="Search..." disabled />
|
||||
<UTextarea autoresize :maxrows="5" :rows="1" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<UTextarea v-for="size in sizes" :key="size" placeholder="Search..." :size="(size as any)" class="w-60" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
123
playground/app/pages/toast.vue
Normal file
123
playground/app/pages/toast.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import theme from '#build/ui/toaster'
|
||||
|
||||
const positions = Object.keys(theme.variants.position)
|
||||
|
||||
const { toasts, add, update, remove } = useToast()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const count = ref(1)
|
||||
const last = computed(() => toasts.value[toasts.value.length - 1])
|
||||
|
||||
const templates = (id: number) => [{
|
||||
title: 'Toast',
|
||||
description: `This is the toast ${id}`
|
||||
}, {
|
||||
title: `Toast ${id}`
|
||||
}, {
|
||||
description: `This is the toast ${id}`
|
||||
}, {
|
||||
title: 'Toast',
|
||||
description: `This is the toast ${id}`,
|
||||
icon: 'i-heroicons-rocket-launch'
|
||||
}, {
|
||||
title: `Toast ${id}`,
|
||||
icon: 'i-heroicons-rocket-launch'
|
||||
}, {
|
||||
description: `This is the toast ${id}`,
|
||||
icon: 'i-heroicons-rocket-launch'
|
||||
}, {
|
||||
title: 'Toast',
|
||||
description: `This is the toast ${id}`,
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}, {
|
||||
title: 'Toast',
|
||||
description: `This is the toast ${id}`,
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
},
|
||||
actions: [{
|
||||
label: 'Action',
|
||||
click() {
|
||||
console.log(`Toast ${id} action clicked`)
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
title: `Toast ${id}`,
|
||||
icon: 'i-heroicons-rocket-launch',
|
||||
actions: [{
|
||||
label: 'Action 1',
|
||||
color: 'gray' as const,
|
||||
click() {
|
||||
console.log(`Toast ${id} action 1 clicked`)
|
||||
}
|
||||
}, {
|
||||
label: 'Action 2',
|
||||
color: 'black' as const,
|
||||
click() {
|
||||
console.log(`Toast ${id} action 2 clicked`)
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
description: `This is the toast ${id}`,
|
||||
icon: 'i-heroicons-rocket-launch',
|
||||
actions: [{
|
||||
label: 'Action',
|
||||
variant: 'outline' as const,
|
||||
click() {
|
||||
console.log(`Toast ${id} action clicked`)
|
||||
}
|
||||
}]
|
||||
}]
|
||||
|
||||
function addToast() {
|
||||
const id = count.value++
|
||||
|
||||
const template = templates(id)[Math.floor(Math.random() * templates(id).length)]
|
||||
|
||||
add({
|
||||
id,
|
||||
...template,
|
||||
click(toast) {
|
||||
console.log(`Toast ${toast.id} clicked`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateToast() {
|
||||
if (!last.value) {
|
||||
return
|
||||
}
|
||||
|
||||
update(last.value.id, {
|
||||
title: 'Toast updated',
|
||||
description: `This is the updated toast ${count.value++}`
|
||||
})
|
||||
}
|
||||
|
||||
function removeToast() {
|
||||
if (!last.value) {
|
||||
return
|
||||
}
|
||||
|
||||
remove(last.value.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-8">
|
||||
<div class="flex flex-col gap-2">
|
||||
<URadioGroup v-model="appConfig.toaster.position" :items="positions" />
|
||||
<UCheckbox v-model="appConfig.toaster.expand" label="Expand" class="mt-1" />
|
||||
<UInput v-model="appConfig.toaster.duration" label="Duration" type="number" class="mt-1" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton label="Add new" color="gray" @click="addToast" />
|
||||
<UButton label="Update last" color="gray" @click="updateToast" />
|
||||
<UButton label="Remove last" color="gray" @click="removeToast" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
21
playground/app/pages/tooltip.vue
Normal file
21
playground/app/pages/tooltip.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<UTooltip text="Top" :kbds="['meta', 'T']" :content="{ side: 'top' }" arrow>
|
||||
<UAvatar text="T" />
|
||||
</UTooltip>
|
||||
|
||||
<div class="flex items-center gap-2 ml-[-20px]">
|
||||
<UTooltip text="Left" :kbds="['meta', 'L']" :content="{ side: 'left' }" arrow>
|
||||
<UAvatar text="L" />
|
||||
</UTooltip>
|
||||
|
||||
<UTooltip text="Right" :kbds="['meta', 'R']" :content="{ side: 'right' }" arrow>
|
||||
<UAvatar text="R" />
|
||||
</UTooltip>
|
||||
</div>
|
||||
|
||||
<UTooltip text="Bottom" :kbds="['meta', 'B']" arrow>
|
||||
<UAvatar text="B" />
|
||||
</UTooltip>
|
||||
</div>
|
||||
</template>
|
||||
6
playground/app/types/index.d.ts
vendored
Normal file
6
playground/app/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
phone: string
|
||||
}
|
||||
Reference in New Issue
Block a user