Compare commits

..

29 Commits

Author SHA1 Message Date
Romain Hamel
385cbeec6c refactor(Form): remove state assignment and opt-in to nested forms 2025-04-16 18:10:54 +02:00
Romain Hamel
4d875c03a2 chore: up 2025-04-15 12:48:10 +02:00
Romain Hamel
5aea866057 fix(Form): handle schema output types 2025-04-14 19:51:57 +02:00
Benjamin Canac
d5bcb0da59 docs(content): improve examples 2025-04-14 12:42:07 +02:00
Benjamin Canac
0a3cc5a25d docs(SupportedLanguages): add mapping for kk 2025-04-14 11:20:37 +02:00
Benjamin Canac
52735fdd40 docs(nuxt.config): remove hub.vectorize config 2025-04-14 11:18:31 +02:00
renovate[bot]
59fd4d52e1 chore(deps): update all non-major dependencies (v3) (#3854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 11:04:50 +02:00
Iván Máximiliano, Lo Giudice
8e78eb15c8 fix(Form): loses focus on submit (#3796)
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2025-04-14 11:02:27 +02:00
ElshadBeg
b7fc69baa7 feat(locale): add Uyghur language (#3878) 2025-04-14 10:48:40 +02:00
Altynbek
43153c4e91 feat(locale): add Kazakh language (#3875) 2025-04-14 10:48:14 +02:00
Romain Hamel
d059efca25 feat(unplugin): routing support for inertia (#3845)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-14 10:47:26 +02:00
Benjamin Canac
eea14155aa fix(types): handle ClassValue in ui prop
Resolves #3860
2025-04-12 18:28:44 +02:00
Benjamin Canac
4ba8503c60 chore(deps): update @nuxt/ui-pro 2025-04-12 18:28:02 +02:00
Maxime Pauvert
fdee2522bb feat(Form): export loading state (#3861) 2025-04-12 17:54:20 +02:00
Benjamin Canac
39c861a64b fix(components): refactor types after @nuxt/module-builder upgrade (#3855) 2025-04-12 17:53:03 +02:00
Jeremy Woertink
333b7e4c9b docs(components): add missing children field in items (#3846) 2025-04-11 14:46:11 +02:00
Nikolay Larsen
f42a79b5ef feat(locale): add Tajik language (#3850) 2025-04-11 14:33:52 +02:00
Hugo Richard
50012d4866 docs(components): update og images (#3859) 2025-04-11 14:26:22 +02:00
Benjamin Canac
b558b8c5aa docs(form): display theme
Resolves #3752
2025-04-10 21:55:36 +02:00
Guillaume Chau
3447a062b6 feat(Tabs): add list-leading and list-trailing slots (#3837)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-10 21:50:20 +02:00
Sukka
063a23e738 docs(prettier): load from jsdelivr.net instead of unpkg.com (#3575) 2025-04-10 20:53:45 +02:00
renovate[bot]
da0150a9ec chore(deps): update all non-major dependencies (v3) (#3830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 20:45:39 +02:00
Benjamin Canac
981de8b295 docs: prepare for chat components (#3844) 2025-04-10 19:31:12 +02:00
Sandro Circi
fb4c210b41 chore: simplify theme imports in dev (#3851) 2025-04-10 15:59:04 +02:00
Sandro Circi
8c68af5e3b chore: run test suite on windows (#3479)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-10 11:41:23 +02:00
Hugo Richard
81b46ab880 docs(components): add new og images (#3824) 2025-04-09 18:39:09 +02:00
Benjamin Canac
619b6f2a0e chore(Textarea): put back styles 2025-04-09 17:38:19 +02:00
Benjamin Canac
25913188a7 docs(form): improve joi example 2025-04-09 17:30:04 +02:00
Eugen Istoc
f3098df84a feat(OverlayProvider)!: return an overlay instance from .open() (#3829) 2025-04-09 17:26:12 +02:00
130 changed files with 2375 additions and 1340 deletions

View File

@@ -42,6 +42,8 @@ jobs:
- name: Build application
run: pnpm run docs:build
env:
NODE_OPTIONS: '--max-old-space-size=8192'
- name: Deploy to NuxtHub
uses: nuxt-hub/action@v1

View File

@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
os: [ubuntu-latest, windows-latest] # macos-latest
node: [22]
env:
@@ -65,6 +65,7 @@ jobs:
run: pnpm run dev:vue:build
- name: Publish
if: matrix.os != 'windows-latest'
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
starter-nuxt:

1
.npmrc
View File

@@ -1,4 +1,3 @@
shamefully-hoist=true
auto-install-peers=true
ignore-workspace-root-check=true
shell-emulator=true

View File

@@ -6,8 +6,14 @@ export default defineBuildConfig({
'./src/unplugin',
'./src/vite'
],
replace: {
'process.env.DEV': 'false'
rollup: {
replace: {
delimiters: ['', ''],
values: {
// Used in development to import directly from theme
'const isUiDev = true': 'const isUiDev = false'
}
}
},
hooks: {
'mkdist:entry:options'(ctx, entry, options) {

View File

@@ -31,13 +31,10 @@ const component = ({ name, primitive, pro, prose, content }) => {
? `
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props {
/**
@@ -46,7 +43,7 @@ export interface ${upperName}Props {
*/
as?: any
class?: any
ui?: Partial<typeof ${camelName}.slots>
ui?: ${upperName}['slots']
}
export interface ${upperName}Slots {
@@ -55,12 +52,17 @@ export interface ${upperName}Slots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<${upperName}Props>()
defineSlots<${upperName}Slots>()
const ui = ${camelName}()
const appConfig = useAppConfig() as ${upperName}['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
</script>
<template>
@@ -71,22 +73,16 @@ const ui = ${camelName}()
`
: `
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
type ${upperName}Variants = VariantProps<typeof ${camelName}>
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props extends Pick<${upperName}RootProps> {
class?: any
ui?: Partial<typeof ${camelName}.slots>
ui?: ${upperName}['slots']
}
export interface ${upperName}Emits extends ${upperName}RootEmits {}
@@ -95,16 +91,21 @@ export interface ${upperName}Slots {}
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { ${upperName}Root, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<${upperName}Props>()
const emits = defineEmits<${upperName}Emits>()
const slots = defineSlots<${upperName}Slots>()
const appConfig = useAppConfig() as ${upperName}['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props), emits)
const ui = ${camelName}()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
</script>
<template>

View File

@@ -85,5 +85,5 @@ provide('navigation', mappedNavigation)
</template>
<style>
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 */
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 max-h-[341px] */
</style>

View File

@@ -328,7 +328,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
<template>
<div class="my-5">
<div>
<div class="relative">
<div v-if="options.length" class="flex flex-wrap items-center gap-2.5 border border-(--ui-border-muted) border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
<template v-for="option in options" :key="option.name">
<UFormField

View File

@@ -22,6 +22,7 @@ function getEmojiFlag(locale: string): string {
hi: 'in', // Hindi -> India
hy: 'am', // Armenian -> Armenia
ja: 'jp', // Japanese -> Japan
kk: 'kz', // Kazakh -> Kazakhstan
km: 'kh', // Khmer -> Cambodia
ko: 'kr', // Korean -> South Korea
nb: 'no', // Norwegian Bokmål -> Norway

View File

@@ -11,7 +11,6 @@ const items = shallowRef<AccordionItem[]>([
{
label: 'Colors',
icon: 'i-lucide-swatch-book',
slot: 'colors' as const,
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
},
{

View File

@@ -1,26 +1,35 @@
<script setup lang="ts">
import type { BreadcrumbItem } from '@nuxt/ui'
const items = [{
label: 'Home',
to: '/'
}, {
slot: 'dropdown' as const,
icon: 'i-lucide-ellipsis',
children: [{
label: 'Documentation'
}, {
label: 'Themes'
}, {
label: 'GitHub'
}]
}, {
label: 'Components',
to: '/components'
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}] satisfies BreadcrumbItem[]
const items = [
{
label: 'Home',
to: '/'
},
{
slot: 'dropdown' as const,
icon: 'i-lucide-ellipsis',
children: [
{
label: 'Documentation'
},
{
label: 'Themes'
},
{
label: 'GitHub'
}
]
},
{
label: 'Components',
to: '/components'
},
{
label: 'Breadcrumb',
to: '/components/breadcrumb'
}
] satisfies BreadcrumbItem[]
</script>
<template>

View File

@@ -1,16 +1,20 @@
<script setup lang="ts">
import type { BreadcrumbItem } from '@nuxt/ui'
const items: BreadcrumbItem[] = [{
label: 'Home',
to: '/'
}, {
label: 'Components',
to: '/components'
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}]
const items: BreadcrumbItem[] = [
{
label: 'Home',
to: '/'
},
{
label: 'Components',
to: '/components'
},
{
label: 'Breadcrumb',
to: '/components/breadcrumb'
}
]
</script>
<template>

View File

@@ -1,23 +1,30 @@
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const items: DropdownMenuItem[] = [{
label: 'Team',
icon: 'i-lucide-users'
}, {
label: 'Invite users',
icon: 'i-lucide-user-plus',
children: [{
label: 'Invite by email',
icon: 'i-lucide-send-horizontal'
}, {
label: 'Invite by link',
icon: 'i-lucide-link'
}]
}, {
label: 'New team',
icon: 'i-lucide-plus'
}]
const items: DropdownMenuItem[] = [
{
label: 'Team',
icon: 'i-lucide-users'
},
{
label: 'Invite users',
icon: 'i-lucide-user-plus',
children: [
{
label: 'Invite by email',
icon: 'i-lucide-send-horizontal'
},
{
label: 'Invite by link',
icon: 'i-lucide-link'
}
]
},
{
label: 'New team',
icon: 'i-lucide-plus'
}
]
</script>
<template>

View File

@@ -1,76 +1,79 @@
<script setup lang="ts">
const groups = [{
id: 'settings',
items: [
{
label: 'Profile',
icon: 'i-lucide-user',
kbds: ['meta', 'P']
},
{
label: 'Billing',
icon: 'i-lucide-credit-card',
kbds: ['meta', 'B'],
slot: 'billing' as const
},
{
label: 'Notifications',
icon: 'i-lucide-bell'
},
{
label: 'Security',
icon: 'i-lucide-lock'
}
]
}, {
id: 'users',
label: 'Users',
slot: 'users' as const,
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank'
},
{
label: 'Sylvain Marroufin',
suffix: 'smarroufin',
to: 'https://github.com/smarroufin',
target: '_blank'
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank'
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank'
},
{
label: 'Haytham A. Salama',
suffix: 'Haythamasalama',
to: 'https://github.com/Haythamasalama',
target: '_blank'
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank'
},
{
label: 'Neil Richter',
suffix: 'noook',
to: 'https://github.com/noook',
target: '_blank'
}
]
}]
const groups = [
{
id: 'settings',
items: [
{
label: 'Profile',
icon: 'i-lucide-user',
kbds: ['meta', 'P']
},
{
label: 'Billing',
icon: 'i-lucide-credit-card',
kbds: ['meta', 'B'],
slot: 'billing' as const
},
{
label: 'Notifications',
icon: 'i-lucide-bell'
},
{
label: 'Security',
icon: 'i-lucide-lock'
}
]
},
{
id: 'users',
label: 'Users',
slot: 'users' as const,
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank'
},
{
label: 'Sylvain Marroufin',
suffix: 'smarroufin',
to: 'https://github.com/smarroufin',
target: '_blank'
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank'
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank'
},
{
label: 'Haytham A. Salama',
suffix: 'Haythamasalama',
to: 'https://github.com/Haythamasalama',
target: '_blank'
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank'
},
{
label: 'Neil Richter',
suffix: 'noook',
to: 'https://github.com/noook',
target: '_blank'
}
]
}
]
</script>
<template>

View File

@@ -3,14 +3,18 @@ import type { ContextMenuItem } from '@nuxt/ui'
const loading = ref(true)
const items: ContextMenuItem[] = [{
label: 'Refresh the Page',
slot: 'refresh'
}, {
label: 'Clear Cookies and Refresh'
}, {
label: 'Clear Cache and Refresh'
}]
const items = [
{
label: 'Refresh the Page',
slot: 'refresh' as const
},
{
label: 'Clear Cookies and Refresh'
},
{
label: 'Clear Cache and Refresh'
}
] satisfies ContextMenuItem[]
</script>
<template>

View File

@@ -34,9 +34,10 @@ const schema = z.object({
pin: z.string().regex(/^\d$/).array().length(5)
})
type Schema = z.input<typeof schema>
type Input = z.input<typeof schema>
type Output = z.output<typeof schema>
const state = reactive<Partial<Schema>>({})
const state = reactive<Partial<Input>>({})
const form = useTemplateRef('form')
@@ -47,7 +48,7 @@ const items = [
]
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
async function onSubmit(event: FormSubmitEvent<Output>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}

View File

@@ -15,7 +15,7 @@ const state = reactive({
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof schema>) {
async function onSubmit(event: FormSubmitEvent<typeof state>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}

View File

@@ -39,7 +39,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
</div>
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
<UForm v-if="state.news" :state="state" :schema="nestedSchema" nested>
<UFormField label="Email" name="email">
<UInput v-model="state.email" placeholder="john@lennon.com" />
</UFormField>

View File

@@ -51,7 +51,14 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
<UInput v-model="state.customer" placeholder="Wonka Industries" />
</UFormField>
<UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
<UForm
v-for="item, count in state.items"
:key="count"
:state="item"
:schema="itemSchema"
nested
class="flex gap-2"
>
<UFormField :label="!count ? 'Description' : undefined" name="description">
<UInput v-model="item.description" />
</UFormField>

View File

@@ -1,23 +1,21 @@
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items: NavigationMenuItem[] = [
const items = [
{
label: 'Guide',
icon: 'i-lucide-book-open'
},
{
label: 'Composables',
icon: 'i-lucide-database'
},
{
label: 'Components',
icon: 'i-lucide-box',
slot: 'components'
slot: 'components' as const
}
]
] satisfies NavigationMenuItem[]
</script>
<template>

View File

@@ -1,23 +1,23 @@
<script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
const items = [
{
slot: 'address',
slot: 'address' as const,
title: 'Address',
description: 'Add your address here',
icon: 'i-lucide-house'
}, {
slot: 'shipping',
slot: 'shipping' as const,
title: 'Shipping',
description: 'Set your preferred shipping method',
icon: 'i-lucide-truck'
}, {
slot: 'checkout',
slot: 'checkout' as const,
title: 'Checkout',
description: 'Confirm your order'
}
]
] satisfies StepperItem[]
</script>
<template>

View File

@@ -3,17 +3,14 @@ import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
{
slot: 'address',
title: 'Address',
description: 'Add your address here',
icon: 'i-lucide-house'
}, {
slot: 'shipping',
title: 'Shipping',
description: 'Set your preferred shipping method',
icon: 'i-lucide-truck'
}, {
slot: 'checkout',
title: 'Checkout',
description: 'Confirm your order'
}

View File

@@ -6,21 +6,23 @@ const items = [
label: 'app/',
slot: 'app' as const,
defaultExpanded: true,
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }

View File

@@ -5,22 +5,24 @@ const items: TreeItem[] = [
{
label: 'app/',
value: 'app',
children: [{
label: 'composables/',
value: 'composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
value: 'components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
value: 'composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
value: 'components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }

View File

@@ -5,21 +5,23 @@ const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }

View File

@@ -8,21 +8,23 @@ const items: TreeItem[] = [
onSelect: (e: Event) => {
e.preventDefault()
},
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }

View File

@@ -8,21 +8,23 @@ const items: TreeItem[] = [
onToggle: (e: Event) => {
e.preventDefault()
},
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }

View File

@@ -16,12 +16,12 @@ function handleMessage(message) {
async function handleFormatMessage(message) {
if (!globalThis.prettier) {
await Promise.all([
import('https://unpkg.com/prettier@3.5.2/standalone.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/babel.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/estree.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/html.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/markdown.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/typescript.js')
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/standalone.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/babel.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/estree.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/html.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/markdown.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/typescript.js')
])
}

View File

@@ -32,6 +32,7 @@ Use the `items` prop as an array of objects with the following properties:
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `onSelect?(e: Event): void`{lang="ts-type"}
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
- `children?: ContextMenuItem[] | ContextMenuItem[][]`{lang="ts-type"}
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.

View File

@@ -32,6 +32,7 @@ Use the `items` prop as an array of objects with the following properties:
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `onSelect?(e: Event): void`{lang="ts-type"}
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
- `children?: DropdownMenuItem[] | DropdownMenuItem[][]`{lang="ts-type"}
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.

View File

@@ -32,7 +32,7 @@ It requires two props:
class: 'w-60'
---
::
::component-example{label="Zod"}
---
name: 'form-example-zod'
@@ -206,3 +206,7 @@ This will give you access to the following:
| `dirtyFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields that have been modified by the user. |
| `touchedFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields that the user interacted with. |
| `blurredFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields blurred by the user. |
## Theme
:component-theme

View File

@@ -28,6 +28,7 @@ Use the `items` prop as an array of objects with the following properties:
- `class?: any`{lang="ts-type"}
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `onSelect?(e: Event): void`{lang="ts-type"}
- `children?: NavigationMenuChildItem[]`{lang="ts-type"}
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.

View File

@@ -175,6 +175,10 @@ export default defineNuxtConfig({
}
},
hub: {
ai: true
},
vite: {
plugins: [
yaml()

View File

@@ -3,22 +3,23 @@
"name": "@nuxt/ui-docs",
"type": "module",
"dependencies": {
"@ai-sdk/vue": "^1.2.8",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.35",
"@iconify-json/simple-icons": "^1.2.31",
"@iconify-json/vscode-icons": "^1.2.18",
"@iconify-json/lucide": "^1.2.36",
"@iconify-json/simple-icons": "^1.2.32",
"@iconify-json/vscode-icons": "^1.2.19",
"@nuxt/content": "^3.4.0",
"@nuxt/image": "^1.10.0",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@63da8be",
"@nuxthub/core": "^0.8.23",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@4757a1e",
"@nuxthub/core": "^0.8.24",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^21.1.1",
"@rollup/plugin-yaml": "^4.1.2",
"@vueuse/nuxt": "^13.1.0",
"capture-website": "^4.2.0",
"@vueuse/integrations": "^13.1.0",
"sortablejs": "^1.15.6",
"@vueuse/nuxt": "^13.1.0",
"ai": "^4.3.6",
"capture-website": "^4.2.0",
"joi": "^17.13.3",
"motion-v": "0.13.1",
"nuxt": "^3.16.2",
@@ -27,13 +28,15 @@
"nuxt-og-image": "^5.1.1",
"prettier": "^3.5.3",
"shiki-transformer-color-highlight": "^1.0.0",
"sortablejs": "^1.15.6",
"superstruct": "^2.0.2",
"ufo": "^1.6.1",
"valibot": "^1.0.0",
"workers-ai-provider": "^0.3.0",
"yup": "^1.6.1",
"zod": "^3.24.2"
},
"devDependencies": {
"wrangler": "^4.8.0"
"wrangler": "^4.10.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

22
docs/server/api/chat.ts Normal file
View File

@@ -0,0 +1,22 @@
import { streamText } from 'ai'
import { createWorkersAI } from 'workers-ai-provider'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
// Enable AI Gateway if defined in environment variables
const gateway = process.env.CLOUDFLARE_AI_GATEWAY_ID
? {
id: process.env.CLOUDFLARE_AI_GATEWAY_ID,
cacheTtl: 60 * 60 * 24 // 24 hours
}
: undefined
const workersAI = createWorkersAI({ binding: hubAI(), gateway })
return streamText({
model: workersAI('@cf/meta/llama-3.2-3b-instruct'),
maxTokens: 10000,
system: 'You are a helpful assistant that can answer questions and help.',
messages
}).toDataStreamResponse()
})

View File

@@ -96,13 +96,13 @@
"scripts": {
"build": "nuxt-module-build build",
"prepack": "pnpm build",
"dev": "DEV=true nuxi dev playground",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:vue": "DEV=true vite playground-vue",
"dev:vue": "vite playground-vue",
"dev:vue:build": "vite build playground-vue",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && vite build playground-vue",
"docs": "DEV=true nuxi dev docs",
"docs:build": "NODE_OPTIONS='--max-old-space-size=8192' nuxi build docs",
"docs": "nuxi dev docs",
"docs:build": "nuxi build docs",
"docs:prepare": "nuxt-component-meta docs",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
@@ -113,10 +113,10 @@
},
"dependencies": {
"@iconify/vue": "^4.3.0",
"@internationalized/date": "^3.7.0",
"@internationalized/number": "^3.6.0",
"@internationalized/date": "^3.8.0",
"@internationalized/number": "^3.6.1",
"@nuxt/fonts": "^0.11.1",
"@nuxt/icon": "^1.11.0",
"@nuxt/icon": "^1.12.0",
"@nuxt/kit": "^3.16.2",
"@nuxt/schema": "^3.16.2",
"@nuxtjs/color-mode": "^3.5.2",
@@ -149,16 +149,14 @@
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.3",
"tinyglobby": "^0.2.12",
"unplugin": "^2.2.2",
"unplugin": "^2.3.2",
"unplugin-auto-import": "^19.1.2",
"unplugin-vue-components": "^28.4.1",
"vaul-vue": "^0.4.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
"unplugin-vue-components": "^28.5.0",
"vaul-vue": "^0.4.1"
},
"devDependencies": {
"@nuxt/eslint-config": "^1.3.0",
"@nuxt/module-builder": "^1.0.0",
"@nuxt/module-builder": "^1.0.1",
"@nuxt/test-utils": "^3.17.2",
"@release-it/conventional-changelog": "^10.0.0",
"@vue/test-utils": "^2.4.6",
@@ -172,14 +170,19 @@
"vue-tsc": "^2.2.0"
},
"peerDependencies": {
"@inertiajs/vue3": "^2.0.7",
"joi": "^17.13.0",
"superstruct": "^2.0.0",
"typescript": "^5.6.3",
"valibot": "^1.0.0",
"vue-router": "^4.5.0",
"yup": "^1.6.0",
"zod": "^3.24.0"
},
"peerDependenciesMeta": {
"@inertiajs/vue3": {
"optional": true
},
"joi": {
"optional": true
},
@@ -189,6 +192,9 @@
"superstruct": {
"optional": true
},
"vue-router": {
"optional": true
},
"yup": {
"optional": true
},
@@ -201,7 +207,7 @@
"chokidar": "3.6.0",
"debug": "4.3.7",
"rollup": "4.34.9",
"unplugin": "^2.2.2",
"unplugin": "^2.3.2",
"vue-tsc": "2.2.0"
},
"pnpm": {

View File

@@ -18,7 +18,7 @@
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"typescript": "^5.8.3",
"vite": "^6.2.5",
"vite": "^6.2.6",
"vue-tsc": "^2.2.0"
}
}

View File

@@ -0,0 +1,71 @@
<template>
<UContainer>
<UForm :schema :state @submit="onSubmit">
<UFormField label="A" name="a">
<UInput v-model="state.a" />
</UFormField>
<UFormField label="B" name="b">
<UInput v-model="state.b" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
{{ output }}
</UContainer>
</template>
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import * as v from 'valibot'
const _schemaStringFiltered = v.pipe(v.string(), v.trim())
const schema = v.object({
a: v.string(),
b: v.union([
v.pipe(
v.array(_schemaStringFiltered),
v.filterItems((item, index, array) => (array.indexOf(item) === index || item !== ''))
),
v.pipe(
v.string(),
v.trim(),
v.transform(
(item) => {
if (item === '') return undefined
return item
.split(',')
.map(val => val.trim())
.filter(val => val !== '')
}
)
)
])
})
const state = reactive<{
a: string
b: string
}>({
a: 'hello, world',
b: 'hello, world'
})
const output = reactive<{
a: string
b?: string[]
}>({
a: '',
b: []
})
function onSubmit(event: FormSubmitEvent<v.InferOutput<typeof schema>>) {
console.log('typeof `a`:', typeof event.data.a) // should be string
console.log('typeof `b`:', typeof event.data.b) // should be object (array of strings)
output.a = event.data.a
output.b = event.data.b
}
</script>

View File

@@ -59,6 +59,14 @@ const items = [{
<template #custom="{ item }">
<span class="text-(--ui-text-muted)">Custom: {{ item.content }}</span>
</template>
<template #list-trailing>
<UButton
icon="lucide:refresh-cw"
variant="soft"
class="ml-2"
/>
</template>
</UTabs>
</div>
</div>

View File

@@ -8,10 +8,10 @@
"generate": "nuxi generate"
},
"dependencies": {
"@iconify-json/lucide": "^1.2.35",
"@iconify-json/simple-icons": "^1.2.31",
"@iconify-json/lucide": "^1.2.36",
"@iconify-json/simple-icons": "^1.2.32",
"@nuxt/ui": "latest",
"@nuxthub/core": "^0.8.23",
"@nuxthub/core": "^0.8.24",
"nuxt": "^3.16.2",
"zod": "^3.24.2"
}

939
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,9 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
const overrides = globSync('**/*.vue', { cwd: join(runtimeDir, 'vue/components') })
const overrideNames = new Set(overrides.map(c => `${options.prefix}${c.replace(/\.vue$/, '')}`))
const inertiaOverrides = globSync('**/*.vue', { cwd: join(runtimeDir, 'inertia/components') })
const inertiaOverrideNames = new Set(inertiaOverrides.map(c => `${options.prefix}${c.replace(/\.vue$/, '')}`))
const pluginOptions = defu(options.components, <ComponentsOptions>{
dts: options.dts ?? true,
exclude: [
@@ -27,6 +30,9 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
],
resolvers: [
(componentName) => {
if (options.inertia && inertiaOverrideNames.has(componentName)) {
return { name: 'default', from: join(runtimeDir, 'inertia/components', `${componentName.slice(options.prefix.length)}.vue`) }
}
if (overrideNames.has(componentName))
return { name: 'default', from: join(runtimeDir, 'vue/components', `${componentName.slice(options.prefix.length)}.vue`) }
if (componentNames.has(componentName))
@@ -55,6 +61,9 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
}
const filename = id.match(/([^/]+)\.vue$/)?.[1]
if (filename && options.inertia && inertiaOverrideNames.has(`${options.prefix}${filename}`)) {
return join(runtimeDir, 'inertia/components', `${filename}.vue`)
}
if (filename && overrideNames.has(`${options.prefix}${filename}`)) {
return join(runtimeDir, 'vue/components', `${filename}.vue`)
}

View File

@@ -3,13 +3,13 @@ import { normalize } from 'pathe'
import { resolvePathSync } from 'mlly'
import MagicString from 'magic-string'
import { runtimeDir } from '../unplugin'
import { runtimeDir, type NuxtUIOptions } from '../unplugin'
/**
* This plugin normalises Nuxt environment (#imports) and `import.meta.client` within the Nuxt UI components.
*/
export default function NuxtEnvironmentPlugin() {
const stubPath = resolvePathSync('../runtime/vue/stubs', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url })
export default function NuxtEnvironmentPlugin(options: NuxtUIOptions) {
const stubPath = resolvePathSync(options.inertia ? '../runtime/inertia/stubs' : '../runtime/vue/stubs', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url })
return {
name: 'nuxt:ui',

View File

@@ -2,14 +2,10 @@
<script lang="ts">
import type { AccordionRootProps, AccordionRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/accordion'
import { tv } from '../utils/tv'
import type { DynamicSlots } from '../types/utils'
import type { DynamicSlots, ComponentConfig } from '../types/utils'
const appConfigAccordion = _appConfig as AppConfig & { ui: { accordion: Partial<typeof theme> } }
const accordion = tv({ extend: tv(theme), ...(appConfigAccordion.ui?.accordion || {}) })
type Accordion = ComponentConfig<typeof theme, AppConfig, 'accordion'>
export interface AccordionItem {
label?: string
@@ -48,7 +44,7 @@ export interface AccordionProps<T extends AccordionItem = AccordionItem> extends
*/
labelKey?: string
class?: any
ui?: Partial<typeof accordion.slots>
ui?: Accordion['slots']
}
export interface AccordionEmits extends AccordionRootEmits {}
@@ -71,6 +67,7 @@ import { AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, Accord
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { get } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
const props = withDefaults(defineProps<AccordionProps<T>>(), {
@@ -82,10 +79,11 @@ const props = withDefaults(defineProps<AccordionProps<T>>(), {
const emits = defineEmits<AccordionEmits>()
const slots = defineSlots<AccordionSlots<T>>()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Accordion['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type', 'unmountOnHide'), emits)
const ui = computed(() => accordion({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.accordion || {}) })({
disabled: props.disabled
}))
</script>

View File

@@ -1,16 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/alert'
import { tv } from '../utils/tv'
import type { AvatarProps, ButtonProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigAlert = _appConfig as AppConfig & { ui: { alert: Partial<typeof theme> } }
const alert = tv({ extend: tv(theme), ...(appConfigAlert.ui?.alert || {}) })
type AlertVariants = VariantProps<typeof alert>
type Alert = ComponentConfig<typeof theme, AppConfig, 'alert'>
export interface AlertProps {
/**
@@ -28,16 +22,16 @@ export interface AlertProps {
/**
* @defaultValue 'primary'
*/
color?: AlertVariants['color']
color?: Alert['variants']['color']
/**
* @defaultValue 'solid'
*/
variant?: AlertVariants['variant']
variant?: Alert['variants']['variant']
/**
* The orientation between the content and the actions.
* @defaultValue 'vertical'
*/
orientation?: AlertVariants['orientation']
orientation?: Alert['variants']['orientation']
/**
* Display a list of actions:
* - under the title and description when orientation is `vertical`
@@ -59,7 +53,7 @@ export interface AlertProps {
*/
closeIcon?: string
class?: any
ui?: Partial<typeof alert.slots>
ui?: Alert['slots']
}
export interface AlertEmits {
@@ -71,7 +65,7 @@ export interface AlertSlots {
title(props?: {}): any
description(props?: {}): any
actions(props?: {}): any
close(props: { ui: ReturnType<typeof alert> }): any
close(props: { ui: { [K in keyof Required<Alert['slots']>]: (props?: Record<string, any>) => string } }): any
}
</script>
@@ -80,6 +74,7 @@ import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UButton from './Button.vue'
@@ -91,9 +86,9 @@ const emits = defineEmits<AlertEmits>()
const slots = defineSlots<AlertSlots>()
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Alert['AppConfig']
const ui = computed(() => alert({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.alert || {}) })({
color: props.color,
variant: props.variant,
orientation: props.orientation,

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/avatar'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigAvatar = _appConfig as AppConfig & { ui: { avatar: Partial<typeof theme> } }
const avatar = tv({ extend: tv(theme), ...(appConfigAvatar.ui?.avatar || {}) })
type AvatarVariants = VariantProps<typeof avatar>
type Avatar = ComponentConfig<typeof theme, AppConfig, 'avatar'>
export interface AvatarProps {
/**
@@ -27,10 +21,10 @@ export interface AvatarProps {
/**
* @defaultValue 'md'
*/
size?: AvatarVariants['size']
size?: Avatar['variants']['size']
class?: any
style?: any
ui?: Partial<typeof avatar.slots>
ui?: Avatar['slots']
}
export interface AvatarSlots {
@@ -41,8 +35,10 @@ export interface AvatarSlots {
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { Primitive, Slot } from 'reka-ui'
import { useAppConfig } from '#imports'
import ImageComponent from '#build/ui-image-component'
import { useAvatarGroup } from '../composables/useAvatarGroup'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
defineOptions({ inheritAttrs: false })
@@ -51,10 +47,11 @@ const props = withDefaults(defineProps<AvatarProps>(), { as: 'span' })
const fallback = computed(() => props.text || (props.alt || '').split(' ').map(word => word.charAt(0)).join('').substring(0, 2))
const appConfig = useAppConfig() as Avatar['AppConfig']
const { size } = useAvatarGroup(props)
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => avatar({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.avatar || {}) })({
size: size.value
}))

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/avatar-group'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigAvatarGroup = _appConfig as AppConfig & { ui: { avatarGroup: Partial<typeof theme> } }
const avatarGroup = tv({ extend: tv(theme), ...(appConfigAvatarGroup.ui?.avatarGroup || {}) })
type AvatarGroupVariants = VariantProps<typeof avatarGroup>
type AvatarGroup = ComponentConfig<typeof theme, AppConfig, 'avatarGroup'>
export interface AvatarGroupProps {
/**
@@ -20,13 +14,13 @@ export interface AvatarGroupProps {
/**
* @defaultValue 'md'
*/
size?: AvatarGroupVariants['size']
size?: AvatarGroup['variants']['size']
/**
* The maximum number of avatars to display.
*/
max?: number | string
class?: any
ui?: Partial<typeof avatarGroup.slots>
ui?: AvatarGroup['slots']
}
export interface AvatarGroupSlots {
@@ -37,13 +31,17 @@ export interface AvatarGroupSlots {
<script setup lang="ts">
import { computed, provide } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { avatarGroupInjectionKey } from '../composables/useAvatarGroup'
import { tv } from '../utils/tv'
import UAvatar from './Avatar.vue'
const props = defineProps<AvatarGroupProps>()
const slots = defineSlots<AvatarGroupSlots>()
const ui = computed(() => avatarGroup({
const appConfig = useAppConfig() as AvatarGroup['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.avatarGroup || {}) })({
size: props.size
}))

View File

@@ -1,17 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/badge'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigBadge = _appConfig as AppConfig & { ui: { badge: Partial<typeof theme> } }
const badge = tv({ extend: tv(theme), ...(appConfigBadge.ui?.badge || {}) })
type BadgeVariants = VariantProps<typeof badge>
type Badge = ComponentConfig<typeof theme, AppConfig, 'badge'>
export interface BadgeProps extends Omit<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
/**
@@ -23,17 +17,17 @@ export interface BadgeProps extends Omit<UseComponentIconsProps, 'loading' | 'lo
/**
* @defaultValue 'primary'
*/
color?: BadgeVariants['color']
color?: Badge['variants']['color']
/**
* @defaultValue 'solid'
*/
variant?: BadgeVariants['variant']
variant?: Badge['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: BadgeVariants['size']
size?: Badge['variants']['size']
class?: any
ui?: Partial<typeof badge.slots>
ui?: Badge['slots']
}
export interface BadgeSlots {
@@ -46,8 +40,10 @@ export interface BadgeSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -56,10 +52,11 @@ const props = withDefaults(defineProps<BadgeProps>(), {
})
defineSlots<BadgeSlots>()
const appConfig = useAppConfig() as Badge['AppConfig']
const { orientation, size: buttonGroupSize } = useButtonGroup<BadgeProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
const ui = computed(() => badge({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.badge || {}) })({
color: props.color,
variant: props.variant,
size: buttonGroupSize.value || props.size,

View File

@@ -1,15 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/breadcrumb'
import { tv } from '../utils/tv'
import type { AvatarProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString } from '../types/utils'
import type { DynamicSlots, ComponentConfig } from '../types/utils'
const appConfigBreadcrumb = _appConfig as AppConfig & { ui: { breadcrumb: Partial<typeof theme> } }
const breadcrumb = tv({ extend: tv(theme), ...(appConfigBreadcrumb.ui?.breadcrumb || {}) })
type Breadcrumb = ComponentConfig<typeof theme, AppConfig, 'breadcrumb'>
export interface BreadcrumbItem extends Omit<LinkProps, 'raw' | 'custom'> {
label?: string
@@ -41,7 +37,7 @@ export interface BreadcrumbProps<T extends BreadcrumbItem = BreadcrumbItem> {
*/
labelKey?: string
class?: any
ui?: PartialString<typeof breadcrumb.slots>
ui?: Breadcrumb['slots']
}
type SlotProps<T extends BreadcrumbItem> = (props: { item: T, index: number, active?: boolean }) => any
@@ -62,6 +58,7 @@ import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { get } from '../utils'
import { tv } from '../utils/tv'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -73,13 +70,14 @@ const props = withDefaults(defineProps<BreadcrumbProps<T>>(), {
labelKey: 'label'
})
const slots = defineSlots<BreadcrumbSlots<T>>()
const { dir } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Breadcrumb['AppConfig']
const separatorIcon = computed(() => props.separatorIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronLeft : appConfig.ui.icons.chevronRight))
// eslint-disable-next-line vue/no-dupe-keys
const ui = breadcrumb()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.breadcrumb || {}) })())
</script>
<template>

View File

@@ -1,36 +1,29 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/button'
import type { LinkProps } from './Link.vue'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigButton = _appConfig as AppConfig & { ui: { button: Partial<typeof theme> } }
const button = tv({ extend: tv(theme), ...(appConfigButton.ui?.button || {}) })
type ButtonVariants = VariantProps<typeof button>
type Button = ComponentConfig<typeof theme, AppConfig, 'button'>
export interface ButtonProps extends UseComponentIconsProps, Omit<LinkProps, 'raw' | 'custom'> {
label?: string
/**
* @defaultValue 'primary'
*/
color?: ButtonVariants['color']
activeColor?: ButtonVariants['color']
color?: Button['variants']['color']
activeColor?: Button['variants']['color']
/**
* @defaultValue 'solid'
*/
variant?: ButtonVariants['variant']
activeVariant?: ButtonVariants['variant']
variant?: Button['variants']['variant']
activeVariant?: Button['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: ButtonVariants['size']
size?: Button['variants']['size']
/** Render the button with equal padding on all sides. */
square?: boolean
/** Render the button full width. */
@@ -39,7 +32,7 @@ export interface ButtonProps extends UseComponentIconsProps, Omit<LinkProps, 'ra
loadingAuto?: boolean
onClick?: ((event: MouseEvent) => void | Promise<void>) | Array<((event: MouseEvent) => void | Promise<void>)>
class?: any
ui?: PartialString<typeof button.slots>
ui?: Button['slots']
}
export interface ButtonSlots {
@@ -51,11 +44,14 @@ export interface ButtonSlots {
<script setup lang="ts">
import { type Ref, computed, ref, inject } from 'vue'
import { defu } from 'defu'
import { useForwardProps } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useButtonGroup } from '../composables/useButtonGroup'
import { formLoadingInjectionKey } from '../composables/useFormField'
import { omit } from '../utils'
import { tv } from '../utils/tv'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -69,10 +65,11 @@ const props = withDefaults(defineProps<ButtonProps>(), {
})
const slots = defineSlots<ButtonSlots>()
const linkProps = useForwardProps(pickLinkProps(props))
const appConfig = useAppConfig() as Button['AppConfig']
const { orientation, size: buttonSize } = useButtonGroup<ButtonProps>(props)
const linkProps = useForwardProps(pickLinkProps(props))
const loadingAutoState = ref(false)
const formLoading = inject<Ref<boolean> | undefined>(formLoadingInjectionKey, undefined)
@@ -95,17 +92,19 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
)
const ui = computed(() => tv({
extend: button,
variants: {
active: {
true: {
base: props.activeClass
},
false: {
base: props.inactiveClass
extend: tv(theme),
...defu({
variants: {
active: {
true: {
base: props.activeClass
},
false: {
base: props.inactiveClass
}
}
}
}
}, appConfig.ui?.button || {})
})({
color: props.color,
variant: props.variant,

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/button-group'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigButtonGroup = _appConfig as AppConfig & { ui: { buttonGroup: Partial<typeof theme> } }
const buttonGroup = tv({ extend: tv(theme), ...(appConfigButtonGroup.ui?.buttonGroup) })
type ButtonGroupVariants = VariantProps<typeof buttonGroup>
type ButtonGroup = ComponentConfig<typeof theme, AppConfig, 'buttonGroup'>
export interface ButtonGroupProps {
/**
@@ -20,13 +14,14 @@ export interface ButtonGroupProps {
/**
* @defaultValue 'md'
*/
size?: ButtonGroupVariants['size']
size?: ButtonGroup['variants']['size']
/**
* The orientation the buttons are laid out.
* @defaultValue 'horizontal'
*/
orientation?: ButtonGroupVariants['orientation']
orientation?: ButtonGroup['variants']['orientation']
class?: any
ui?: ButtonGroup['slots']
}
export interface ButtonGroupSlots {
@@ -37,13 +32,20 @@ export interface ButtonGroupSlots {
<script setup lang="ts">
import { provide, computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { buttonGroupInjectionKey } from '../composables/useButtonGroup'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<ButtonGroupProps>(), {
orientation: 'horizontal'
})
defineSlots<ButtonGroupSlots>()
const appConfig = useAppConfig() as ButtonGroup['AppConfig']
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.buttonGroup || {}) }))
provide(buttonGroupInjectionKey, computed(() => ({
orientation: props.orientation,
size: props.size
@@ -51,7 +53,7 @@ provide(buttonGroupInjectionKey, computed(() => ({
</script>
<template>
<Primitive :as="as" :class="buttonGroup({ orientation, class: props.class })">
<Primitive :as="as" :class="ui({ orientation, class: props.class })">
<slot />
</Primitive>
</template>

View File

@@ -1,19 +1,12 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootProps, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui'
import type { DateValue } from '@internationalized/date'
import type { AppConfig } from '@nuxt/schema'
import type { ButtonProps } from '../types'
import _appConfig from '#build/app.config'
import theme from '#build/ui/calendar'
import { tv } from '../utils/tv'
import type { PartialString } from '../types/utils'
import type { ButtonProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigCalendar = _appConfig as AppConfig & { ui: { calendar: Partial<typeof theme> } }
const calendar = tv({ extend: tv(theme), ...(appConfigCalendar.ui?.calendar || {}) })
type CalendarVariants = VariantProps<typeof calendar>
type Calendar = ComponentConfig<typeof theme, AppConfig, 'calendar'>
type CalendarDefaultValue<R extends boolean = false, M extends boolean = false> = R extends true
? DateRange
@@ -82,11 +75,11 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
/**
* @defaultValue 'primary'
*/
color?: CalendarVariants['color']
color?: Calendar['variants']['color']
/**
* @defaultValue 'md'
*/
size?: CalendarVariants['size']
size?: Calendar['variants']['size']
/** Whether or not a range of dates can be selected */
range?: R & boolean
/** Whether or not multiple dates can be selected */
@@ -98,7 +91,7 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
defaultValue?: CalendarDefaultValue<R, M>
modelValue?: CalendarModelValue<R, M>
class?: any
ui?: PartialString<typeof calendar.slots>
ui?: Calendar['slots']
}
export interface CalendarEmits<R extends boolean, M extends boolean> extends Omit<CalendarRootEmits & RangeCalendarRootEmits, 'update:modelValue'> {
@@ -119,6 +112,7 @@ import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
import { reactiveOmit } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<CalendarProps<R, M>>(), {
@@ -129,8 +123,8 @@ const props = withDefaults(defineProps<CalendarProps<R, M>>(), {
const emits = defineEmits<CalendarEmits<R, M>>()
defineSlots<CalendarSlots>()
const appConfig = useAppConfig()
const { code: locale, dir, t } = useLocale()
const appConfig = useAppConfig() as Calendar['AppConfig']
const rootProps = useForwardPropsEmits(reactiveOmit(props, 'range', 'modelValue', 'defaultValue', 'color', 'size', 'monthControls', 'yearControls', 'class', 'ui'), emits)
@@ -139,7 +133,7 @@ const nextMonthIcon = computed(() => props.nextMonthIcon || (dir.value === 'rtl'
const prevYearIcon = computed(() => props.prevYearIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleRight : appConfig.ui.icons.chevronDoubleLeft))
const prevMonthIcon = computed(() => props.prevMonthIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronRight : appConfig.ui.icons.chevronLeft))
const ui = computed(() => calendar({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.calendar || {}) })({
color: props.color,
size: props.size
}))

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/card'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigCard = _appConfig as AppConfig & { ui: { card: Partial<typeof theme> } }
const card = tv({ extend: tv(theme), ...(appConfigCard.ui?.card || {}) })
type CardVariants = VariantProps<typeof card>
type Card = ComponentConfig<typeof theme, AppConfig, 'card'>
export interface CardProps {
/**
@@ -20,9 +14,9 @@ export interface CardProps {
/**
* @defaultValue 'outline'
*/
variant?: CardVariants['variant']
variant?: Card['variants']['variant']
class?: any
ui?: Partial<typeof card.slots>
ui?: Card['slots']
}
export interface CardSlots {
@@ -35,11 +29,17 @@ export interface CardSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<CardProps>()
const slots = defineSlots<CardSlots>()
const ui = computed(() => card({ variant: props.variant }))
const appConfig = useAppConfig() as Card['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.card || {}) })({
variant: props.variant
}))
</script>
<template>

View File

@@ -1,6 +1,5 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import type { AcceptableValue } from 'reka-ui'
import type { EmblaCarouselType, EmblaOptionsType, EmblaPluginType } from 'embla-carousel'
@@ -10,17 +9,11 @@ import type { AutoHeightOptionsType } from 'embla-carousel-auto-height'
import type { ClassNamesOptionsType } from 'embla-carousel-class-names'
import type { FadeOptionsType } from 'embla-carousel-fade'
import type { WheelGesturesPluginOptions } from 'embla-carousel-wheel-gestures'
import _appConfig from '#build/app.config'
import theme from '#build/ui/carousel'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigCarousel = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
const carousel = tv({ extend: tv(theme), ...(appConfigCarousel.ui?.carousel || {}) })
type CarouselVariants = VariantProps<typeof carousel>
type Carousel = ComponentConfig<typeof theme, AppConfig, 'carousel'>
export type CarouselItem = AcceptableValue
@@ -66,7 +59,7 @@ export interface CarouselProps<T extends CarouselItem = CarouselItem> extends Om
* The orientation of the carousel.
* @defaultValue 'horizontal'
*/
orientation?: CarouselVariants['orientation']
orientation?: Carousel['variants']['orientation']
items?: T[]
/**
* Enable Autoplay plugin
@@ -99,7 +92,7 @@ export interface CarouselProps<T extends CarouselItem = CarouselItem> extends Om
*/
wheelGestures?: boolean | WheelGesturesPluginOptions
class?: any
ui?: PartialString<typeof carousel.slots>
ui?: Carousel['slots']
}
export type CarouselSlots<T extends CarouselItem = CarouselItem> = {
@@ -115,6 +108,7 @@ import { Primitive, useForwardProps } from 'reka-ui'
import { reactivePick, computedAsync } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<CarouselProps<T>>(), {
@@ -148,14 +142,15 @@ const props = withDefaults(defineProps<CarouselProps<T>>(), {
})
defineSlots<CarouselSlots<T>>()
const appConfig = useAppConfig()
const { dir, t } = useLocale()
const appConfig = useAppConfig() as Carousel['AppConfig']
const rootProps = useForwardProps(reactivePick(props, 'active', 'align', 'breakpoints', 'containScroll', 'dragFree', 'dragThreshold', 'duration', 'inViewThreshold', 'loop', 'skipSnaps', 'slidesToScroll', 'startIndex', 'watchDrag', 'watchResize', 'watchSlides', 'watchFocus'))
const prevIcon = computed(() => props.prevIcon || (dir.value === 'rtl' ? appConfig.ui.icons.arrowRight : appConfig.ui.icons.arrowLeft))
const nextIcon = computed(() => props.nextIcon || (dir.value === 'rtl' ? appConfig.ui.icons.arrowLeft : appConfig.ui.icons.arrowRight))
const ui = computed(() => carousel({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.carousel || {}) })({
orientation: props.orientation
}))

View File

@@ -1,16 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { CheckboxRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/checkbox'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigCheckbox = _appConfig as AppConfig & { ui: { checkbox: Partial<typeof theme> } }
const checkbox = tv({ extend: tv(theme), ...(appConfigCheckbox.ui?.checkbox || {}) })
type CheckboxVariants = VariantProps<typeof checkbox>
type Checkbox = ComponentConfig<typeof theme, AppConfig, 'checkbox'>
export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'required' | 'name' | 'value' | 'id' | 'defaultValue'> {
/**
@@ -23,11 +17,11 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
/**
* @defaultValue 'primary'
*/
color?: CheckboxVariants['color']
color?: Checkbox['variants']['color']
/**
* @defaultValue 'md'
*/
size?: CheckboxVariants['size']
size?: Checkbox['variants']['size']
/**
* The icon displayed when checked.
* @defaultValue appConfig.ui.icons.check
@@ -41,7 +35,7 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
*/
indeterminateIcon?: string
class?: any
ui?: Partial<typeof checkbox.slots>
ui?: Checkbox['slots']
}
export type CheckboxEmits = {
@@ -60,6 +54,7 @@ import { Primitive, CheckboxRoot, CheckboxIndicator, Label, useForwardProps } fr
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
defineOptions({ inheritAttrs: false })
@@ -70,13 +65,14 @@ const emits = defineEmits<CheckboxEmits>()
const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined })
const appConfig = useAppConfig() as Checkbox['AppConfig']
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
const appConfig = useAppConfig()
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<CheckboxProps>(props)
const id = _id.value ?? useId()
const ui = computed(() => checkbox({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })({
size: size.value,
color: color.value,
required: props.required,

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/chip'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigChip = _appConfig as AppConfig & { ui: { chip: Partial<typeof theme> } }
const chip = tv({ extend: tv(theme), ...(appConfigChip.ui?.chip || {}) })
type ChipVariants = VariantProps<typeof chip>
type Chip = ComponentConfig<typeof theme, AppConfig, 'chip'>
export interface ChipProps {
/**
@@ -22,22 +16,22 @@ export interface ChipProps {
/**
* @defaultValue 'primary'
*/
color?: ChipVariants['color']
color?: Chip['variants']['color']
/**
* @defaultValue 'md'
*/
size?: ChipVariants['size']
size?: Chip['variants']['size']
/**
* The position of the chip.
* @defaultValue 'top-right'
*/
position?: ChipVariants['position']
position?: Chip['variants']['position']
/** When `true`, keep the chip inside the component for rounded elements. */
inset?: boolean
/** When `true`, render the chip relatively to the parent. */
standalone?: boolean
class?: any
ui?: Partial<typeof chip.slots>
ui?: Chip['slots']
}
export interface ChipEmits {
@@ -53,7 +47,9 @@ export interface ChipSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive, Slot } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useAvatarGroup } from '../composables/useAvatarGroup'
import { tv } from '../utils/tv'
defineOptions({ inheritAttrs: false })
@@ -66,8 +62,9 @@ defineSlots<ChipSlots>()
const show = defineModel<boolean>('show', { default: true })
const { size } = useAvatarGroup(props)
const appConfig = useAppConfig() as Chip['AppConfig']
const ui = computed(() => chip({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.chip || {}) })({
color: props.color,
size: size.value,
position: props.position,

View File

@@ -1,13 +1,10 @@
<script lang="ts">
import type { CollapsibleRootProps, CollapsibleRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/collapsible'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigCollapsible = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof theme> } }
const collapsible = tv({ extend: tv(theme), ...(appConfigCollapsible.ui?.collapsible || {}) })
type Collapsible = ComponentConfig<typeof theme, AppConfig, 'collapsible'>
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled' | 'unmountOnHide'> {
/**
@@ -16,7 +13,7 @@ export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpe
*/
as?: any
class?: any
ui?: Partial<typeof collapsible.slots>
ui?: Collapsible['slots']
}
export interface CollapsibleEmits extends CollapsibleRootEmits {}
@@ -28,8 +25,11 @@ export interface CollapsibleSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<CollapsibleProps>(), {
unmountOnHide: true
@@ -37,10 +37,12 @@ const props = withDefaults(defineProps<CollapsibleProps>(), {
const emits = defineEmits<CollapsibleEmits>()
const slots = defineSlots<CollapsibleSlots>()
const appConfig = useAppConfig() as Collapsible['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled', 'unmountOnHide'), emits)
// eslint-disable-next-line vue/no-dupe-keys
const ui = collapsible()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.collapsible || {}) })())
</script>
<template>

View File

@@ -1,18 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/color-picker'
import { tv } from '../utils/tv'
import type { HSLObject } from 'colortranslator'
import type { ComponentConfig } from '../types/utils'
const appConfigColorPicker = _appConfig as AppConfig & { ui: { colorPicker: Partial<typeof theme> } }
const colorPicker = tv({ extend: tv(theme), ...(appConfigColorPicker.ui?.colorPicker || {}) })
type ColorPickerVariants = VariantProps<typeof colorPicker>
type ColorPicker = ComponentConfig<typeof theme, AppConfig, 'colorPicker'>
type HSVColor = {
h: number
@@ -67,9 +61,9 @@ export type ColorPickerProps = {
/**
* @defaultValue 'md'
*/
size?: ColorPickerVariants['size']
size?: ColorPicker['variants']['size']
class?: any
ui?: Partial<typeof colorPicker.slots>
ui?: ColorPicker['slots']
}
</script>
@@ -80,6 +74,8 @@ import { Primitive } from 'reka-ui'
import { useEventListener, useElementBounding, watchThrottled, watchPausable } from '@vueuse/core'
import { isClient } from '@vueuse/shared'
import { ColorTranslator } from 'colortranslator'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<ColorPickerProps>(), {
format: 'hex',
@@ -88,6 +84,12 @@ const props = withDefaults(defineProps<ColorPickerProps>(), {
})
const modelValue = defineModel<string>(undefined)
const appConfig = useAppConfig() as ColorPicker['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.colorPicker || {}) })({
size: props.size
}))
const pickedColor = computed<HSVColor>({
get() {
try {
@@ -258,10 +260,6 @@ const trackThumbStyle = computed(() => ({
backgroundColor: trackThumbColor.value,
top: `${trackThumbPosition.value.y}%`
}))
const ui = computed(() => colorPicker({
size: props.size
}))
</script>
<template>

View File

@@ -4,16 +4,12 @@ import type { ListboxRootProps, ListboxRootEmits } from 'reka-ui'
import type { FuseResult } from 'fuse.js'
import type { AppConfig } from '@nuxt/schema'
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import _appConfig from '#build/app.config'
import theme from '#build/ui/command-palette'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps, LinkProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigCommandPalette = _appConfig as AppConfig & { ui: { commandPalette: Partial<typeof theme> } }
const commandPalette = tv({ extend: tv(theme), ...(appConfigCommandPalette.ui?.commandPalette || {}) })
type CommandPalette = ComponentConfig<typeof theme, AppConfig, 'commandPalette'>
export interface CommandPaletteItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
prefix?: string
@@ -115,7 +111,7 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
*/
labelKey?: string
class?: any
ui?: PartialString<typeof commandPalette.slots>
ui?: CommandPalette['slots']
}
export type CommandPaletteEmits<T> = ListboxRootEmits<T> & {
@@ -126,7 +122,7 @@ type SlotProps<T> = (props: { item: T, index: number }) => any
export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?: string }> = {
'empty'(props: { searchTerm?: string }): any
'close'(props: { ui: ReturnType<typeof commandPalette> }): any
'close'(props: { ui: { [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string } }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
@@ -144,6 +140,7 @@ import { useFuse } from '@vueuse/integrations/useFuse'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { omit, get } from '../utils'
import { tv } from '../utils/tv'
import { highlight } from '../utils/fuse'
import { pickLinkProps } from '../utils/link'
import UIcon from './Icon.vue'
@@ -166,13 +163,13 @@ const slots = defineSlots<CommandPaletteSlots<G, T>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as CommandPalette['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'highlightOnHover'), emits)
const inputProps = useForwardProps(reactivePick(props, 'loading', 'loadingIcon'))
// eslint-disable-next-line vue/no-dupe-keys
const ui = commandPalette()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.commandPalette || {}) })())
const fuse = computed(() => defu({}, props.fuse, {
fuseOptions: {

View File

@@ -1,12 +1,9 @@
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/container'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigContainer = _appConfig as AppConfig & { ui: { container: Partial<typeof theme> } }
const container = tv({ extend: tv(theme), ...(appConfigContainer.ui?.container || {}) })
type Container = ComponentConfig<typeof theme, AppConfig, 'container'>
export interface ContainerProps {
/**
@@ -23,14 +20,21 @@ export interface ContainerSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<ContainerProps>()
defineSlots<ContainerSlots>()
const appConfig = useAppConfig() as Container['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.container || {}) }))
</script>
<template>
<Primitive :as="as" :class="container({ class: props.class })">
<Primitive :as="as" :class="ui({ class: props.class })">
<slot />
</Primitive>
</template>

View File

@@ -1,26 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps, ContextMenuContentEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/context-menu'
import { tv } from '../utils/tv'
import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type {
ArrayOrNested,
DynamicSlots,
MergeTypes,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
const contextMenu = tv({ extend: tv(theme), ...(appConfigContextMenu.ui?.contextMenu || {}) })
type ContextMenuVariants = VariantProps<typeof contextMenu>
type ContextMenu = ComponentConfig<typeof theme, AppConfig, 'contextMenu'>
export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
label?: string
@@ -28,7 +14,7 @@ export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custo
* @IconifyIcon
*/
icon?: string
color?: ContextMenuVariants['color']
color?: ContextMenu['variants']['color']
avatar?: AvatarProps
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ContextMenuContentEmits>>
kbds?: KbdProps['value'][] | KbdProps[]
@@ -53,7 +39,7 @@ export interface ContextMenuProps<T extends ArrayOrNested<ContextMenuItem> = Arr
/**
* @defaultValue 'md'
*/
size?: ContextMenuVariants['size']
size?: ContextMenu['variants']['size']
items?: T
/**
* The icon displayed when an item is checked.
@@ -88,7 +74,7 @@ export interface ContextMenuProps<T extends ArrayOrNested<ContextMenuItem> = Arr
labelKey?: keyof NestedItem<T>
disabled?: boolean
class?: any
ui?: PartialString<typeof contextMenu.slots>
ui?: ContextMenu['slots']
}
export interface ContextMenuEmits extends ContextMenuRootEmits {}
@@ -112,7 +98,9 @@ export type ContextMenuSlots<
import { computed, toRef } from 'vue'
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { omit } from '../utils'
import { tv } from '../utils/tv'
import UContextMenuContent from './ContextMenuContent.vue'
const props = withDefaults(defineProps<ContextMenuProps<T>>(), {
@@ -124,12 +112,13 @@ const props = withDefaults(defineProps<ContextMenuProps<T>>(), {
const emits = defineEmits<ContextMenuEmits>()
const slots = defineSlots<ContextMenuSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
const appConfig = useAppConfig() as ContextMenu['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
const contentProps = toRef(() => props.content)
const proxySlots = omit(slots, ['default'])
const ui = computed(() => contextMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.contextMenu || {}) })({
size: props.size
}))
</script>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import type { ContextMenuContentProps as RekaContextMenuContentProps, ContextMenuContentEmits as RekaContextMenuContentEmits } from 'reka-ui'
import theme from '#build/ui/context-menu'
import { tv } from '../utils/tv'
import type { AppConfig } from '@nuxt/schema'
import type theme from '#build/ui/context-menu'
import type { AvatarProps, ContextMenuItem, ContextMenuSlots, KbdProps } from '../types'
import type { ArrayOrNested, NestedItem } from '../types/utils'
import type { ArrayOrNested, NestedItem, ComponentConfig } from '../types/utils'
const _contextMenu = tv(theme)()
type ContextMenu = ComponentConfig<typeof theme, AppConfig, 'contextMenu'>
interface ContextMenuContentProps<T extends ArrayOrNested<ContextMenuItem>> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
items?: T
@@ -25,8 +25,8 @@ interface ContextMenuContentProps<T extends ArrayOrNested<ContextMenuItem>> exte
*/
externalIcon?: boolean | string
class?: any
ui: typeof _contextMenu
uiOverride?: any
ui: { [K in keyof Required<ContextMenu['slots']>]: (props?: Record<string, any>) => string }
uiOverride?: ContextMenu['slots']
}
interface ContextMenuContentEmits extends RekaContextMenuContentEmits {}
@@ -53,8 +53,9 @@ const props = defineProps<ContextMenuContentProps<T>>()
const emits = defineEmits<ContextMenuContentEmits>()
const slots = defineSlots<ContextMenuSlots<T>>()
const appConfig = useAppConfig()
const { dir } = useLocale()
const appConfig = useAppConfig()
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
const proxySlots = omit(slots, ['default'])

View File

@@ -2,14 +2,10 @@
import type { DrawerRootProps, DrawerRootEmits } from 'vaul-vue'
import type { DialogContentProps, DialogContentEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/drawer'
import { tv } from '../utils/tv'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigDrawer = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
const drawer = tv({ extend: tv(theme), ...(appConfigDrawer.ui?.drawer || {}) })
type Drawer = ComponentConfig<typeof theme, AppConfig, 'drawer'>
export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'shouldScaleBackground' | 'setBackgroundColorOnScale' | 'scrollLockTimeout' | 'fixed' | 'dismissible' | 'modal' | 'open' | 'defaultOpen' | 'nested' | 'direction' | 'noBodyStyles' | 'handleOnly' | 'preventScrollRestoration' | 'snapPoints'> {
/**
@@ -42,7 +38,7 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
*/
portal?: boolean
class?: any
ui?: Partial<typeof drawer.slots>
ui?: Drawer['slots']
}
export interface DrawerEmits extends DrawerRootEmits {}
@@ -63,6 +59,8 @@ import { computed, toRef } from 'vue'
import { useForwardPropsEmits } from 'reka-ui'
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<DrawerProps>(), {
direction: 'bottom',
@@ -75,13 +73,15 @@ const props = withDefaults(defineProps<DrawerProps>(), {
const emits = defineEmits<DrawerEmits>()
const slots = defineSlots<DrawerSlots>()
const appConfig = useAppConfig() as Drawer['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'shouldScaleBackground', 'setBackgroundColorOnScale', 'scrollLockTimeout', 'fixed', 'dismissible', 'modal', 'open', 'defaultOpen', 'nested', 'direction', 'noBodyStyles', 'handleOnly', 'preventScrollRestoration', 'snapPoints'), emits)
const contentProps = toRef(() => props.content)
const contentEvents = {
closeAutoFocus: (e: Event) => e.preventDefault()
}
const ui = computed(() => drawer({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}) })({
direction: props.direction,
inset: props.inset
}))

View File

@@ -1,26 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuContentEmits, DropdownMenuArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/dropdown-menu'
import { tv } from '../utils/tv'
import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type {
ArrayOrNested,
DynamicSlots,
MergeTypes,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
const dropdownMenu = tv({ extend: tv(theme), ...(appConfigDropdownMenu.ui?.dropdownMenu || {}) })
type DropdownMenuVariants = VariantProps<typeof dropdownMenu>
type DropdownMenu = ComponentConfig<typeof theme, AppConfig, 'dropdownMenu'>
export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
label?: string
@@ -28,7 +14,7 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
* @IconifyIcon
*/
icon?: string
color?: DropdownMenuVariants['color']
color?: DropdownMenu['variants']['color']
avatar?: AvatarProps
content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DropdownMenuContentEmits>>
kbds?: KbdProps['value'][] | KbdProps[]
@@ -53,7 +39,7 @@ export interface DropdownMenuProps<T extends ArrayOrNested<DropdownMenuItem> = A
/**
* @defaultValue 'md'
*/
size?: DropdownMenuVariants['size']
size?: DropdownMenu['variants']['size']
items?: T
/**
* The icon displayed when an item is checked.
@@ -96,7 +82,7 @@ export interface DropdownMenuProps<T extends ArrayOrNested<DropdownMenuItem> = A
labelKey?: keyof NestedItem<T>
disabled?: boolean
class?: any
ui?: PartialString<typeof dropdownMenu.slots>
ui?: DropdownMenu['slots']
}
export interface DropdownMenuEmits extends DropdownMenuRootEmits {}
@@ -121,7 +107,9 @@ import { computed, toRef } from 'vue'
import { defu } from 'defu'
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { omit } from '../utils'
import { tv } from '../utils/tv'
import UDropdownMenuContent from './DropdownMenuContent.vue'
const props = withDefaults(defineProps<DropdownMenuProps<T>>(), {
@@ -133,12 +121,14 @@ const props = withDefaults(defineProps<DropdownMenuProps<T>>(), {
const emits = defineEmits<DropdownMenuEmits>()
const slots = defineSlots<DropdownMenuSlots<T>>()
const appConfig = useAppConfig() as DropdownMenu['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'modal'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as DropdownMenuContentProps)
const arrowProps = toRef(() => props.arrow as DropdownMenuArrowProps)
const proxySlots = omit(slots, ['default'])
const ui = computed(() => dropdownMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.dropdownMenu || {}) })({
size: props.size
}))
</script>

View File

@@ -1,12 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { DropdownMenuContentProps as RekaDropdownMenuContentProps, DropdownMenuContentEmits as RekaDropdownMenuContentEmits } from 'reka-ui'
import theme from '#build/ui/dropdown-menu'
import { tv } from '../utils/tv'
import type { AppConfig } from '@nuxt/schema'
import type theme from '#build/ui/dropdown-menu'
import type { KbdProps, AvatarProps, DropdownMenuItem, DropdownMenuSlots } from '../types'
import type { ArrayOrNested, NestedItem } from '../types/utils'
import type { ArrayOrNested, NestedItem, ComponentConfig } from '../types/utils'
const _dropdownMenu = tv(theme)()
type DropdownMenu = ComponentConfig<typeof theme, AppConfig, 'dropdownMenu'>
interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuItem>> extends Omit<RekaDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
items?: T
@@ -26,8 +26,8 @@ interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuItem>> ex
*/
externalIcon?: boolean | string
class?: any
ui: typeof _dropdownMenu
uiOverride?: any
ui: { [K in keyof Required<DropdownMenu['slots']>]: (props?: Record<string, any>) => string }
uiOverride?: DropdownMenu['slots']
}
interface DropdownMenuContentEmits extends RekaDropdownMenuContentEmits {}
@@ -59,8 +59,9 @@ const props = defineProps<DropdownMenuContentProps<T>>()
const emits = defineEmits<DropdownMenuContentEmits>()
const slots = defineSlots<DropdownMenuContentSlots<T>>()
const appConfig = useAppConfig()
const { dir } = useLocale()
const appConfig = useAppConfig()
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
const proxySlots = omit(slots, ['default'])

View File

@@ -1,27 +1,25 @@
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/form'
import { tv } from '../utils/tv'
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
import type { DeepReadonly } from 'vue'
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/form'
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
import type { ComponentConfig } from '../types/utils'
const appConfigForm = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
type FormConfig = ComponentConfig<typeof theme, AppConfig, 'form'>
const form = tv({ extend: tv(theme), ...(appConfigForm.ui?.form || {}) })
export interface FormProps<T extends object> {
export interface FormProps<I extends object, O extends object = I> {
id?: string | number
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
schema?: FormSchema<T>
schema?: FormSchema<I, O>
/** An object representing the current state of the form. */
state: Partial<T>
state: Partial<I>
/**
* Custom validation function to validate the form state.
* @param state - The current state of the form.
* @returns A promise that resolves to an array of FormError objects, or an array of FormError objects directly.
*/
validate?: (state: Partial<T>) => Promise<FormError[]> | FormError[]
validate?: (state: Partial<I>) => Promise<FormError[]> | FormError[]
/**
* The list of input events that trigger the form validation.
* @defaultValue `['blur', 'change', 'input']`
@@ -34,17 +32,23 @@ export interface FormProps<T extends object> {
* @defaultValue `300`
*/
validateOnInputDelay?: number
/**
* If true, schema transformations will be applied to the state on submit.
* If true and nested in another form, this form will attach to its parent and validate at the same time.
*/
nested?: boolean
/**
* When `true`, all form elements will be disabled on `@submit` event.
* This will cause any focused input elements to lose their focus state.
* @defaultValue `true`
*/
transform?: boolean
loadingAuto?: boolean
class?: any
onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
onSubmit?: ((event: FormSubmitEvent<O>) => void | Promise<void>) | (() => void | Promise<void>)
}
export interface FormEmits<T extends object> {
(e: 'submit', payload: FormSubmitEvent<T>): void
export interface FormEmits<I extends object, O extends object = I> {
(e: 'submit', payload: FormSubmitEvent<O>): void
(e: 'error', payload: FormErrorEvent): void
}
@@ -53,27 +57,33 @@ export interface FormSlots {
}
</script>
<script lang="ts" setup generic="T extends object">
<script lang="ts" setup generic="I extends object, O extends object = I">
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
import { useEventBus } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from '../composables/useFormField'
import { tv } from '../utils/tv'
import { validateSchema } from '../utils/form'
import { FormValidationException } from '../types/form'
const props = withDefaults(defineProps<FormProps<T>>(), {
const props = withDefaults(defineProps<FormProps<I, O>>(), {
validateOn() {
return ['input', 'blur', 'change'] as FormInputEvents[]
},
validateOnInputDelay: 300,
transform: true
loadingAuto: true
})
const emits = defineEmits<FormEmits<T>>()
const emits = defineEmits<FormEmits<I, O>>()
defineSlots<FormSlots>()
const appConfig = useAppConfig() as FormConfig['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.form || {}) }))
const formId = props.id ?? useId() as string
const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
const bus = useEventBus<FormEvent<I>>(`form-${formId}`)
const parentBus = inject(
formBusInjectionKey,
undefined
@@ -116,14 +126,14 @@ onUnmounted(() => {
})
onMounted(async () => {
if (parentBus) {
if (props.nested && parentBus) {
await nextTick()
parentBus.emit({ type: 'attach', validate: _validate, formId })
}
})
onUnmounted(() => {
if (parentBus) {
if (props.nested && parentBus) {
parentBus.emit({ type: 'detach', formId })
}
})
@@ -131,12 +141,12 @@ onUnmounted(() => {
const errors = ref<FormErrorWithId[]>([])
provide('form-errors', errors)
const inputs = ref<{ [P in keyof T]?: { id?: string, pattern?: RegExp } }>({})
const inputs = ref<{ [P in keyof I]?: { id?: string, pattern?: RegExp } }>({})
provide(formInputsInjectionKey, inputs as any)
const dirtyFields = new Set<keyof T>()
const touchedFields = new Set<keyof T>()
const blurredFields = new Set<keyof T>()
const dirtyFields = new Set<keyof I>()
const touchedFields = new Set<keyof I>()
const blurredFields = new Set<keyof I>()
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
return errs.map(err => ({
@@ -145,7 +155,7 @@ function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
}))
}
const transformedState = ref<T | null>(null)
const transformedState = ref<I | null>(null)
async function getErrors(): Promise<FormErrorWithId[]> {
let errs = props.validate ? (await props.validate(props.state)) ?? [] : []
@@ -162,8 +172,8 @@ async function getErrors(): Promise<FormErrorWithId[]> {
return resolveErrorIds(errs)
}
async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof T)[]
async function _validate(opts: { name?: keyof I | (keyof I)[], silent?: boolean, nested?: boolean } = { silent: false, nested: true }): Promise<O | false> {
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof I)[]
const nestedValidatePromises = !names && opts.nested
? Array.from(nestedForms.value.values()).map(
@@ -199,23 +209,19 @@ async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean,
throw new FormValidationException(formId, errors.value, childErrors)
}
if (opts.transform) {
Object.assign(props.state, transformedState.value)
}
return props.state as T
return transformedState.value
}
const loading = ref(false)
provide(formLoadingInjectionKey, readonly(loading))
async function onSubmitWrapper(payload: Event) {
loading.value = true
loading.value = props.loadingAuto && true
const event = payload as FormSubmitEvent<any>
try {
event.data = await _validate({ nested: true, transform: props.transform })
event.data = await _validate({ nested: true })
await props.onSubmit?.(event)
dirtyFields.clear()
} catch (error) {
@@ -241,11 +247,11 @@ provide(formOptionsInjectionKey, computed(() => ({
validateOnInputDelay: props.validateOnInputDelay
})))
defineExpose<Form<T>>({
defineExpose<Form<I, O>>({
validate: _validate,
errors,
setErrors(errs: FormError[], name?: keyof T) {
setErrors(errs: FormError[], name?: keyof I) {
if (name) {
errors.value = errors.value
.filter(error => error.name !== name)
@@ -259,7 +265,7 @@ defineExpose<Form<T>>({
await onSubmitWrapper(new Event('submit'))
},
getErrors(name?: keyof T) {
getErrors(name?: keyof I) {
if (name) {
return errors.value.filter(err => err.name === name)
}
@@ -275,11 +281,12 @@ defineExpose<Form<T>>({
},
disabled,
loading,
dirty: computed(() => !!dirtyFields.size),
dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof T>>,
blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof T>>,
touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof T>>
dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof I>>,
blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof I>>,
touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof I>>
})
</script>
@@ -287,7 +294,7 @@ defineExpose<Form<T>>({
<component
:is="parentBus ? 'div' : 'form'"
:id="formId"
:class="form({ class: props.class })"
:class="ui({ class: props.class })"
@submit.prevent="onSubmitWrapper"
>
<slot :errors="errors" />

View File

@@ -1,15 +1,9 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/form-field'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigFormField = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
const formField = tv({ extend: tv(theme), ...(appConfigFormField.ui?.formField || {}) })
type FormFieldVariants = VariantProps<typeof formField>
type FormField = ComponentConfig<typeof theme, AppConfig, 'formField'>
export interface FormFieldProps {
/**
@@ -29,7 +23,7 @@ export interface FormFieldProps {
/**
* @defaultValue 'md'
*/
size?: FormFieldVariants['size']
size?: FormField['variants']['size']
required?: boolean
/** If true, validation on input will be active immediately instead of waiting for a blur event. */
eagerValidation?: boolean
@@ -39,7 +33,7 @@ export interface FormFieldProps {
*/
validateOnInputDelay?: number
class?: any
ui?: Partial<typeof formField.slots>
ui?: FormField['slots']
}
export interface FormFieldSlots {
@@ -55,13 +49,17 @@ export interface FormFieldSlots {
<script setup lang="ts">
import { computed, ref, inject, provide, type Ref, useId } from 'vue'
import { Primitive, Label } from 'reka-ui'
import { useAppConfig } from '#imports'
import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
import { tv } from '../utils/tv'
import type { FormError, FormFieldInjectedOptions } from '../types/form'
const props = defineProps<FormFieldProps>()
const slots = defineSlots<FormFieldSlots>()
const ui = computed(() => formField({
const appConfig = useAppConfig() as FormField['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })({
size: props.size,
required: props.required
}))

View File

@@ -1,19 +1,12 @@
<script lang="ts">
import type { InputHTMLAttributes } from 'vue'
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/input'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigInput = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
const input = tv({ extend: tv(theme), ...(appConfigInput.ui?.input || {}) })
type InputVariants = VariantProps<typeof input>
type Input = ComponentConfig<typeof theme, AppConfig, 'input'>
export interface InputProps extends UseComponentIconsProps {
/**
@@ -29,15 +22,15 @@ export interface InputProps extends UseComponentIconsProps {
/**
* @defaultValue 'primary'
*/
color?: InputVariants['color']
color?: Input['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: InputVariants['variant']
variant?: Input['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: InputVariants['size']
size?: Input['variants']['size']
required?: boolean
autocomplete?: InputHTMLAttributes['autocomplete']
autofocus?: boolean
@@ -46,7 +39,7 @@ export interface InputProps extends UseComponentIconsProps {
/** Highlight the ring color like a focus state. */
highlight?: boolean
class?: any
ui?: PartialString<typeof input.slots>
ui?: Input['slots']
}
export interface InputEmits {
@@ -65,10 +58,12 @@ export interface InputSlots {
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { looseToNumber } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
@@ -84,14 +79,15 @@ const slots = defineSlots<InputSlots>()
const [modelValue, modelModifiers] = defineModel<string | number | null>()
const appConfig = useAppConfig() as Input['AppConfig']
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => input({
type: props.type as InputVariants['type'],
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })({
type: props.type as Input['variants']['type'],
color: color.value,
variant: props.variant,
size: inputSize?.value,

View File

@@ -1,27 +1,13 @@
<script lang="ts">
import type { InputHTMLAttributes } from 'vue'
import type { VariantProps } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/input-menu'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type {
AcceptableValue,
ArrayOrNested,
GetItemKeys,
GetModelValue,
GetModelValueEmits,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { AcceptableValue, ArrayOrNested, GetItemKeys, GetModelValue, GetModelValueEmits, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.ui?.inputMenu || {}) })
type InputMenu = ComponentConfig<typeof theme, AppConfig, 'inputMenu'>
interface _InputMenuItem {
label?: string
@@ -42,8 +28,6 @@ interface _InputMenuItem {
}
export type InputMenuItem = _InputMenuItem | AcceptableValue | boolean
type InputMenuVariants = VariantProps<typeof inputMenu>
export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'resetSearchTermOnSelect' | 'highlightOnHover'>, UseComponentIconsProps {
/**
* The element or component this component should render as.
@@ -57,15 +41,15 @@ export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOr
/**
* @defaultValue 'primary'
*/
color?: InputMenuVariants['color']
color?: InputMenu['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: InputMenuVariants['variant']
variant?: InputMenu['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: InputMenuVariants['size']
size?: InputMenu['variants']['size']
required?: boolean
autofocus?: boolean
autofocusDelay?: number
@@ -138,7 +122,7 @@ export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOr
*/
ignoreFilter?: boolean
class?: any
ui?: PartialString<typeof inputMenu.slots>
ui?: InputMenu['slots']
}
export type InputMenuEmits<A extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Pick<ComboboxRootEmits, 'update:open'> & {
@@ -161,8 +145,16 @@ export interface InputMenuSlots<
M extends boolean = false,
T extends NestedItem<A> = NestedItem<A>
> {
'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof inputMenu> }): any
'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof inputMenu> }): any
'leading'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<InputMenu['slots']>]: (props?: Record<string, any>) => string }
}): any
'trailing'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<InputMenu['slots']>]: (props?: Record<string, any>) => string }
}): any
'empty'(props: { searchTerm?: string }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
@@ -186,6 +178,7 @@ import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { useLocale } from '../composables/useLocale'
import { compare, get, isArrayOfArray } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UChip from './Chip.vue'
@@ -206,7 +199,7 @@ const slots = defineSlots<InputMenuSlots<T, VK, M>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as InputMenu['AppConfig']
const { contains } = useFilter({ sensitivity: 'base' })
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'required', 'multiple', 'resetSearchTermOnBlur', 'resetSearchTermOnSelect', 'highlightOnHover', 'ignoreFilter'), emits)
@@ -221,7 +214,7 @@ const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
const ui = computed(() => inputMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu || {}) })({
color: color.value,
variant: props.variant,
size: inputSize?.value,

View File

@@ -1,18 +1,11 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { NumberFieldRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/input-number'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigInputNumber = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
const inputNumber = tv({ extend: tv(theme), ...(appConfigInputNumber.ui?.inputNumber || {}) })
type InputNumberVariants = VariantProps<typeof inputNumber>
type InputNumber = ComponentConfig<typeof theme, AppConfig, 'inputNumber'>
export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue' | 'defaultValue' | 'min' | 'max' | 'step' | 'stepSnapping' | 'disabled' | 'required' | 'id' | 'name' | 'formatOptions' | 'disableWheelChange'> {
/**
@@ -22,9 +15,9 @@ export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue
as?: any
/** The placeholder text when the input is empty. */
placeholder?: string
color?: InputNumberVariants['color']
variant?: InputNumberVariants['variant']
size?: InputNumberVariants['size']
color?: InputNumber['variants']['color']
variant?: InputNumber['variants']['variant']
size?: InputNumber['variants']['size']
/** Highlight the ring color like a focus state. */
highlight?: boolean
/**
@@ -62,7 +55,7 @@ export interface InputNumberProps extends Pick<NumberFieldRootProps, 'modelValue
*/
locale?: string
class?: any
ui?: PartialString<typeof inputNumber.slots>
ui?: InputNumber['slots']
}
export interface InputNumberEmits {
@@ -84,6 +77,7 @@ import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
defineOptions({ inheritAttrs: false })
@@ -94,15 +88,16 @@ const props = withDefaults(defineProps<InputNumberProps>(), {
const emits = defineEmits<InputNumberEmits>()
defineSlots<InputNumberSlots>()
const appConfig = useAppConfig() as InputNumber['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'stepSnapping', 'formatOptions', 'disableWheelChange'), emits)
const appConfig = useAppConfig()
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size, name, highlight, disabled, ariaAttrs } = useFormField<InputNumberProps>(props)
const { t, code: codeLocale } = useLocale()
const locale = computed(() => props.locale || codeLocale.value)
const ui = computed(() => inputNumber({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputNumber || {}) })({
color: color.value,
variant: props.variant,
size: size.value,

View File

@@ -1,16 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/kbd'
import type { KbdKey } from '../composables/useKbd'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigKbd = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
const kbd = tv({ extend: tv(theme), ...(appConfigKbd.ui?.kbd || {}) })
type KbdVariants = VariantProps<typeof kbd>
type Kbd = ComponentConfig<typeof theme, AppConfig, 'kbd'>
export interface KbdProps {
/**
@@ -22,11 +16,11 @@ export interface KbdProps {
/**
* @defaultValue 'outline'
*/
variant?: KbdVariants['variant']
variant?: Kbd['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: KbdVariants['size']
size?: Kbd['variants']['size']
class?: any
}
@@ -36,8 +30,11 @@ export interface KbdSlots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useKbd } from '../composables/useKbd'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<KbdProps>(), {
as: 'kbd'
@@ -45,10 +42,13 @@ const props = withDefaults(defineProps<KbdProps>(), {
defineSlots<KbdSlots>()
const { getKbdKey } = useKbd()
const appConfig = useAppConfig() as Kbd['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.kbd || {}) }))
</script>
<template>
<Primitive :as="as" :class="kbd({ variant, size, class: props.class })">
<Primitive :as="as" :class="ui({ variant, size, class: props.class })">
<slot>
{{ getKbdKey(value) }}
</slot>

View File

@@ -1,10 +1,11 @@
<script lang="ts">
import type { ButtonHTMLAttributes } from 'vue'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import type { RouterLinkProps, RouteLocationRaw } from 'vue-router'
import theme from '#build/ui/link'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
type Link = ComponentConfig<typeof theme, AppConfig, 'link'>
interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
/**
@@ -52,10 +53,6 @@ interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
noPrefetch?: boolean
}
const appConfigLink = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
const link = tv({ extend: tv(theme), ...(appConfigLink.ui?.link || {}) })
export interface LinkProps extends NuxtLinkProps {
/**
* The element or component this component should render as when not a link.
@@ -91,10 +88,12 @@ export interface LinkSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { defu } from 'defu'
import { isEqual, diff } from 'ohash/utils'
import { useForwardProps } from 'reka-ui'
import { reactiveOmit } from '@vueuse/core'
import { useRoute } from '#imports'
import { useRoute, useAppConfig } from '#imports'
import { tv } from '../utils/tv'
import ULinkBase from './LinkBase.vue'
defineOptions({ inheritAttrs: false })
@@ -110,16 +109,20 @@ const props = withDefaults(defineProps<LinkProps>(), {
defineSlots<LinkSlots>()
const route = useRoute()
const appConfig = useAppConfig() as Link['AppConfig']
const nuxtLinkProps = useForwardProps(reactiveOmit(props, 'as', 'type', 'disabled', 'active', 'exact', 'exactQuery', 'exactHash', 'activeClass', 'inactiveClass', 'raw', 'class'))
const ui = computed(() => tv({
extend: link,
variants: {
active: {
true: props.activeClass,
false: props.inactiveClass
extend: tv(theme),
...defu({
variants: {
active: {
true: props.activeClass,
false: props.inactiveClass
}
}
}
}, appConfig.ui?.link || {})
}))
function isPartiallyEqual(item1: any, item2: any) {

View File

@@ -1,15 +1,11 @@
<script lang="ts">
import type { DialogRootProps, DialogRootEmits, DialogContentProps, DialogContentEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/modal'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigModal = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
const modal = tv({ extend: tv(theme), ...(appConfigModal.ui?.modal || {}) })
type Modal = ComponentConfig<typeof theme, AppConfig, 'modal'>
export interface ModalProps extends DialogRootProps {
title?: string
@@ -54,7 +50,7 @@ export interface ModalProps extends DialogRootProps {
*/
dismissible?: boolean
class?: any
ui?: Partial<typeof modal.slots>
ui?: Modal['slots']
}
export interface ModalEmits extends DialogRootEmits {
@@ -67,7 +63,7 @@ export interface ModalSlots {
header(props?: {}): any
title(props?: {}): any
description(props?: {}): any
close(props: { ui: ReturnType<typeof modal> }): any
close(props: { ui: { [K in keyof Required<Modal['slots']>]: (props?: Record<string, any>) => string } }): any
body(props?: {}): any
footer(props?: {}): any
}
@@ -79,6 +75,7 @@ import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent,
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<ModalProps>(), {
@@ -93,7 +90,7 @@ const emits = defineEmits<ModalEmits>()
const slots = defineSlots<ModalSlots>()
const { t } = useLocale()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Modal['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content)
@@ -114,7 +111,7 @@ const contentEvents = computed(() => {
return events
})
const ui = computed(() => modal({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {}) })({
transition: props.transition,
fullscreen: props.fullscreen
}))

View File

@@ -1,24 +1,12 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, NavigationMenuContentEmits, CollapsibleRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/navigation-menu'
import { tv } from '../utils/tv'
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
import type {
ArrayOrNested,
DynamicSlots,
MergeTypes,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigNavigationMenu = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
const navigationMenu = tv({ extend: tv(theme), ...(appConfigNavigationMenu.ui?.navigationMenu || {}) })
type NavigationMenu = ComponentConfig<typeof theme, AppConfig, 'navigationMenu'>
export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'type'> {
/** Description is only used when `orientation` is `horizontal`. */
@@ -55,8 +43,6 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
[key: string]: any
}
type NavigationMenuVariants = VariantProps<typeof navigationMenu>
export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
/**
* The element or component this component should render as.
@@ -80,11 +66,11 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
/**
* @defaultValue 'primary'
*/
color?: NavigationMenuVariants['color']
color?: NavigationMenu['variants']['color']
/**
* @defaultValue 'pill'
*/
variant?: NavigationMenuVariants['variant']
variant?: NavigationMenu['variants']['variant']
/**
* The orientation of the menu.
* @defaultValue 'horizontal'
@@ -101,7 +87,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
/**
* @defaultValue 'primary'
*/
highlightColor?: NavigationMenuVariants['highlightColor']
highlightColor?: NavigationMenu['variants']['highlightColor']
/** The content of the menu. */
content?: Omit<NavigationMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<NavigationMenuContentEmits>>
/**
@@ -109,7 +95,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
* Only works when `orientation` is `horizontal`.
* @defaultValue 'horizontal'
*/
contentOrientation?: NavigationMenuVariants['contentOrientation']
contentOrientation?: NavigationMenu['variants']['contentOrientation']
/**
* Display an arrow alongside the menu.
* @defaultValue false
@@ -121,7 +107,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
*/
labelKey?: keyof NestedItem<T>
class?: any
ui?: PartialString<typeof navigationMenu.slots>
ui?: NavigationMenu['slots']
}
export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
@@ -147,6 +133,7 @@ import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationM
import { createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { get, isArrayOfArray } from '../utils'
import { tv } from '../utils/tv'
import { pickLinkProps } from '../utils/link'
import ULinkBase from './LinkBase.vue'
import ULink from './Link.vue'
@@ -166,6 +153,8 @@ const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
const emits = defineEmits<NavigationMenuEmits>()
const slots = defineSlots<NavigationMenuSlots<T>>()
const appConfig = useAppConfig() as NavigationMenu['AppConfig']
const rootProps = useForwardPropsEmits(computed(() => ({
as: props.as,
modelValue: props.modelValue,
@@ -178,15 +167,18 @@ const rootProps = useForwardPropsEmits(computed(() => ({
disablePointerLeaveClose: props.disablePointerLeaveClose,
unmountOnHide: props.unmountOnHide
})), emits)
const contentProps = toRef(() => props.content)
const appConfig = useAppConfig()
const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, active?: boolean }>()
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, level?: number }>()
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, level?: number }>({
props: {
item: Object,
index: Number,
level: Number
}
})
const ui = computed(() => navigationMenu({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.navigationMenu || {}) })({
orientation: props.orientation,
contentOrientation: props.contentOrientation,
collapsed: props.collapsed,

View File

@@ -22,7 +22,7 @@ const onClose = (id: symbol, value: any) => {
v-for="overlay in mountedOverlays"
:key="overlay.id"
v-bind="overlay.props"
v-model:open="overlay.modelValue"
v-model:open="overlay.isOpen"
@close="(value:any) => onClose(overlay.id, value)"
@after:leave="onAfterLeave(overlay.id)"
/>

View File

@@ -1,14 +1,11 @@
<script lang="ts">
import type { PaginationRootProps, PaginationRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/pagination'
import { tv } from '../utils/tv'
import type { ButtonProps } from '../types'
import type { ComponentConfig } from '../types/utils'
const appConfigPagination = _appConfig as AppConfig & { ui: { pagination: Partial<typeof theme> } }
const pagination = tv({ extend: tv(theme), ...(appConfigPagination.ui?.pagination || {}) })
type Pagination = ComponentConfig<typeof theme, AppConfig, 'pagination'>
export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'>> {
/**
@@ -79,7 +76,7 @@ export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defa
*/
to?: (page: number) => ButtonProps['to']
class?: any
ui?: Partial<typeof pagination.slots>
ui?: Pagination['slots']
}
export interface PaginationEmits extends PaginationRootEmits {}
@@ -110,6 +107,7 @@ import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, Pa
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'
const props = withDefaults(defineProps<PaginationProps>(), {
@@ -127,8 +125,9 @@ const props = withDefaults(defineProps<PaginationProps>(), {
const emits = defineEmits<PaginationEmits>()
const slots = defineSlots<PaginationSlots>()
const appConfig = useAppConfig()
const { dir } = useLocale()
const appConfig = useAppConfig() as Pagination['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultPage', 'disabled', 'itemsPerPage', 'page', 'showEdges', 'siblingCount', 'total'), emits)
const firstIcon = computed(() => props.firstIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleRight : appConfig.ui.icons.chevronDoubleLeft))
@@ -137,7 +136,7 @@ const nextIcon = computed(() => props.nextIcon || (dir.value === 'rtl' ? appConf
const lastIcon = computed(() => props.lastIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleLeft : appConfig.ui.icons.chevronDoubleRight))
// eslint-disable-next-line vue/no-dupe-keys
const ui = pagination()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.pagination || {}) })())
</script>
<template>

View File

@@ -1,18 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { PinInputRootEmits, PinInputRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/pin-input'
import { tv } from '../utils/tv'
import type { PartialString } from '../types/utils'
import type { ComponentConfig } from '../types/utils'
const appConfigPinInput = _appConfig as AppConfig & { ui: { pinInput: Partial<typeof theme> } }
const pinInput = tv({ extend: tv(theme), ...(appConfigPinInput.ui?.pinInput || {}) })
type PinInputVariants = VariantProps<typeof pinInput>
type PinInput = ComponentConfig<typeof theme, AppConfig, 'pinInput'>
export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' | 'disabled' | 'id' | 'mask' | 'modelValue' | 'name' | 'otp' | 'placeholder' | 'required' | 'type'> {
/**
@@ -23,15 +16,15 @@ export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' |
/**
* @defaultValue 'primary'
*/
color?: PinInputVariants['color']
color?: PinInput['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: PinInputVariants['variant']
variant?: PinInput['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: PinInputVariants['size']
size?: PinInput['variants']['size']
/**
* The number of input fields.
* @defaultValue 5
@@ -41,7 +34,7 @@ export interface PinInputProps extends Pick<PinInputRootProps, 'defaultValue' |
autofocusDelay?: number
highlight?: boolean
class?: any
ui?: PartialString<typeof pinInput.slots>
ui?: PinInput['slots']
}
export type PinInputEmits = PinInputRootEmits & {
@@ -56,8 +49,10 @@ import type { ComponentPublicInstance } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { PinInputInput, PinInputRoot, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { looseToNumber } from '../utils'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<PinInputProps>(), {
type: 'text',
@@ -66,10 +61,13 @@ const props = withDefaults(defineProps<PinInputProps>(), {
})
const emits = defineEmits<PinInputEmits>()
const appConfig = useAppConfig() as PinInput['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultValue', 'disabled', 'id', 'mask', 'modelValue', 'name', 'otp', 'placeholder', 'required', 'type'), emits)
const { emitFormInput, emitFormFocus, emitFormChange, emitFormBlur, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<PinInputProps>(props)
const ui = computed(() => pinInput({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.pinInput || {}) })({
color: color.value,
variant: props.variant,
size: size.value,

View File

@@ -1,14 +1,10 @@
<script lang="ts">
import type { PopoverRootProps, HoverCardRootProps, PopoverRootEmits, PopoverContentProps, PopoverContentEmits, PopoverArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/popover'
import { tv } from '../utils/tv'
import type { EmitsToProps } from '../types/utils'
import type { EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigPopover = _appConfig as AppConfig & { ui: { popover: Partial<typeof theme> } }
const popover = tv({ extend: tv(theme), ...(appConfigPopover.ui?.popover || {}) })
type Popover = ComponentConfig<typeof theme, AppConfig, 'popover'>
export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps, 'openDelay' | 'closeDelay'> {
/**
@@ -37,7 +33,7 @@ export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps,
*/
dismissible?: boolean
class?: any
ui?: Partial<typeof popover.slots>
ui?: Popover['slots']
}
export interface PopoverEmits extends PopoverRootEmits {}
@@ -54,6 +50,8 @@ import { defu } from 'defu'
import { useForwardPropsEmits } from 'reka-ui'
import { Popover, HoverCard } from 'reka-ui/namespaced'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<PopoverProps>(), {
portal: true,
@@ -65,6 +63,8 @@ const props = withDefaults(defineProps<PopoverProps>(), {
const emits = defineEmits<PopoverEmits>()
const slots = defineSlots<PopoverSlots>()
const appConfig = useAppConfig() as Popover['AppConfig']
const pick = props.mode === 'hover' ? reactivePick(props, 'defaultOpen', 'open', 'openDelay', 'closeDelay') : reactivePick(props, 'defaultOpen', 'open', 'modal')
const rootProps = useForwardPropsEmits(pick, emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as PopoverContentProps)
@@ -82,7 +82,7 @@ const contentEvents = computed(() => {
const arrowProps = toRef(() => props.arrow as PopoverArrowProps)
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => popover({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.popover || {}) })({
side: contentProps.value.side
}))

View File

@@ -1,17 +1,11 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ProgressRootProps, ProgressRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/progress'
import { tv } from '../utils/tv'
import type { ComponentConfig } from '../types/utils'
const appConfigProgress = _appConfig as AppConfig & { ui: { progress: Partial<typeof theme> } }
const progress = tv({ extend: tv(theme), ...(appConfigProgress.ui?.progress || {}) })
type ProgressVariants = VariantProps<typeof progress>
type Progress = ComponentConfig<typeof theme, AppConfig, 'progress'>
export interface ProgressProps extends Pick<ProgressRootProps, 'getValueLabel' | 'modelValue'> {
/**
@@ -28,23 +22,23 @@ export interface ProgressProps extends Pick<ProgressRootProps, 'getValueLabel' |
/**
* @defaultValue 'md'
*/
size?: ProgressVariants['size']
size?: Progress['variants']['size']
/**
* @defaultValue 'primary'
*/
color?: ProgressVariants['color']
color?: Progress['variants']['color']
/**
* The orientation of the progress bar.
* @defaultValue 'horizontal'
*/
orientation?: ProgressVariants['orientation']
orientation?: Progress['variants']['orientation']
/**
* The animation of the progress bar.
* @defaultValue 'carousel'
*/
animation?: ProgressVariants['animation']
animation?: Progress['variants']['animation']
class?: any
ui?: Partial<typeof progress.slots>
ui?: Progress['slots']
}
export interface ProgressEmits extends ProgressRootEmits {}
@@ -61,7 +55,9 @@ export type ProgressSlots = {
import { computed } from 'vue'
import { Primitive, ProgressRoot, ProgressIndicator, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<ProgressProps>(), {
inverted: false,
@@ -72,6 +68,7 @@ const emits = defineEmits<ProgressEmits>()
const slots = defineSlots<ProgressSlots>()
const { dir } = useLocale()
const appConfig = useAppConfig() as Progress['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'getValueLabel', 'modelValue'), emits)
@@ -160,7 +157,7 @@ function stepVariant(index: number | string) {
return 'other'
}
const ui = computed(() => progress({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.progress || {}) })({
animation: props.animation,
size: props.size,
color: props.color,

View File

@@ -1,17 +1,10 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { RadioGroupRootProps, RadioGroupRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/radio-group'
import { tv } from '../utils/tv'
import type { AcceptableValue } from '../types/utils'
import type { AcceptableValue, ComponentConfig } from '../types/utils'
const appConfigRadioGroup = _appConfig as AppConfig & { ui: { radioGroup: Partial<typeof theme> } }
const radioGroup = tv({ extend: tv(theme), ...(appConfigRadioGroup.ui?.radioGroup || {}) })
type RadioGroupVariants = VariantProps<typeof radioGroup>
type RadioGroup = ComponentConfig<typeof theme, AppConfig, 'radioGroup'>
export type RadioGroupValue = AcceptableValue
export type RadioGroupItem = {
@@ -48,15 +41,15 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
/**
* @defaultValue 'md'
*/
size?: RadioGroupVariants['size']
size?: RadioGroup['variants']['size']
/**
* @defaultValue 'list'
*/
variant?: RadioGroupVariants['variant']
variant?: RadioGroup['variants']['variant']
/**
* @defaultValue 'primary'
*/
color?: RadioGroupVariants['color']
color?: RadioGroup['variants']['color']
/**
* The orientation the radio buttons are laid out.
* @defaultValue 'vertical'
@@ -66,9 +59,9 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
* Position of the indicator.
* @defaultValue 'start'
*/
indicator?: RadioGroupVariants['indicator']
indicator?: RadioGroup['variants']['indicator']
class?: any
ui?: Partial<typeof radioGroup.slots>
ui?: RadioGroup['slots']
}
export type RadioGroupEmits = RadioGroupRootEmits & {
@@ -88,8 +81,10 @@ export interface RadioGroupSlots<T extends RadioGroupItem = RadioGroupItem> {
import { computed, useId } from 'vue'
import { RadioGroupRoot, RadioGroupItem, RadioGroupIndicator, Label, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
import { get } from '../utils'
import { tv } from '../utils/tv'
const props = withDefaults(defineProps<RadioGroupProps<T>>(), {
valueKey: 'value',
@@ -100,12 +95,14 @@ const props = withDefaults(defineProps<RadioGroupProps<T>>(), {
const emits = defineEmits<RadioGroupEmits>()
const slots = defineSlots<RadioGroupSlots<T>>()
const appConfig = useAppConfig() as RadioGroup['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emits)
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled, ariaAttrs } = useFormField<RadioGroupProps<T>>(props, { bind: false })
const id = _id.value ?? useId()
const ui = computed(() => radioGroup({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.radioGroup || {}) })({
size: size.value,
color: color.value,
disabled: disabled.value,

View File

@@ -1,27 +1,12 @@
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectContentEmits, SelectArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/select'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type {
AcceptableValue,
ArrayOrNested,
GetItemKeys,
GetItemValue,
GetModelValue,
GetModelValueEmits,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
import type { AcceptableValue, ArrayOrNested, GetItemKeys, GetItemValue, GetModelValue, GetModelValueEmits, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
const appConfigSelect = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
const select = tv({ extend: tv(theme), ...(appConfigSelect.ui?.select || {}) })
type Select = ComponentConfig<typeof theme, AppConfig, 'select'>
interface SelectItemBase {
label?: string
@@ -43,8 +28,6 @@ interface SelectItemBase {
}
export type SelectItem = SelectItemBase | AcceptableValue | boolean
type SelectVariants = VariantProps<typeof select>
export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false> extends Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'>, UseComponentIconsProps {
id?: string
/** The placeholder text when the select is empty. */
@@ -52,15 +35,15 @@ export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested
/**
* @defaultValue 'primary'
*/
color?: SelectVariants['color']
color?: Select['variants']['color']
/**
* @defaultValue 'outline'
*/
variant?: SelectVariants['variant']
variant?: Select['variants']['variant']
/**
* @defaultValue 'md'
*/
size?: SelectVariants['size']
size?: Select['variants']['size']
/**
* The icon displayed to open the menu.
* @defaultValue appConfig.ui.icons.chevronDown
@@ -108,7 +91,7 @@ export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested
/** Highlight the ring color like a focus state. */
highlight?: boolean
class?: any
ui?: PartialString<typeof select.slots>
ui?: Select['slots']
}
export type SelectEmits<A extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Omit<SelectRootEmits, 'update:modelValue'> & {
@@ -125,9 +108,20 @@ export interface SelectSlots<
M extends boolean = false,
T extends NestedItem<A> = NestedItem<A>
> {
'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof select> }): any
'default'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean }): any
'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof select> }): any
'leading'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<Select['slots']>]: (props?: Record<string, any>) => string }
}): any
'default'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
}): any
'trailing'(props: {
modelValue?: GetModelValue<A, VK, M>
open: boolean
ui: { [K in keyof Required<Select['slots']>]: (props?: Record<string, any>) => string }
}): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
@@ -145,6 +139,7 @@ import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { compare, get, isArrayOfArray } from '../utils'
import { tv } from '../utils/tv'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UChip from './Chip.vue'
@@ -159,7 +154,8 @@ const props = withDefaults(defineProps<SelectProps<T, VK, M>>(), {
const emits = defineEmits<SelectEmits<T, VK, M>>()
const slots = defineSlots<SelectSlots<T, VK, M>>()
const appConfig = useAppConfig()
const appConfig = useAppConfig() as Select['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
const arrowProps = toRef(() => props.arrow as SelectArrowProps)
@@ -170,7 +166,7 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const ui = computed(() => select({
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.select || {}) })({
color: color.value,
variant: props.variant,
size: selectSize?.value,

Some files were not shown because too many files have changed in this diff Show More