feat(module)!: migrate to reka-ui (#2448)

This commit is contained in:
Benjamin Canac
2024-12-03 16:11:32 +01:00
committed by GitHub
parent c440c91a29
commit 81ac076219
229 changed files with 13487 additions and 13466 deletions

View File

@@ -7,7 +7,7 @@
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Radix Vue](https://www.radix-vue.com/), [Tailwind CSS v4](https://tailwindcss.com/docs/v4-beta), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS v4](https://tailwindcss.com/docs/v4-beta), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
> [!NOTE]
> You are on the `v3` development branch, check out the [dev branch](https://github.com/nuxt/ui) for Nuxt UI v2.
@@ -99,7 +99,7 @@ Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/inst
- [nuxt/icon](https://github.com/nuxt/icon)
- [nuxt/fonts](https://github.com/nuxt/fonts)
- [nuxt-modules/color-mode](https://github.com/nuxt-modules/color-mode)
- [radix-vue/radix-vue](https://github.com/radix-vue/radix-vue)
- [unovue/reka-ui](https://github.com/unovue/reka-ui)
- [tailwindlabs/tailwindcss](https://github.com/tailwindlabs/tailwindcss)
- [vueuse/vueuse](https://github.com/vueuse/vueuse)

View File

@@ -55,7 +55,7 @@ export interface ${upperName}Slots {
</script>
<script setup lang="ts">
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
const props = withDefaults(defineProps<${upperName}Props>(), { as: 'div' })
defineSlots<${upperName}Slots>()
@@ -72,7 +72,7 @@ const ui = ${camelName}()
: `
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'radix-vue'
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}'
@@ -94,7 +94,7 @@ export interface ${upperName}Slots {}
</script>
<script setup lang="ts">
import { ${upperName}Root, useForwardPropsEmits } from 'radix-vue'
import { ${upperName}Root, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
const props = defineProps<${upperName}Props>()
@@ -149,7 +149,7 @@ import ComponentRender from '../${content ? '../' : ''}component-render'
describe('${upperName}', () => {
it.each([
// Props
['with as', { props: { as: 'div' } }],
['with as', { props: { as: 'section' } }],
['with class', { props: { class: '' } }],
['with ui', { props: { ui: {} } }],
// Slots
@@ -175,8 +175,8 @@ links: ${pro
? ''
: `
- label: ${upperName}
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/${kebabName}.html`}
icon: i-custom-reka-ui
to: https://www.reka-ui.com/components/${kebabName}.html`}
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/${pro ? 'ui-pro' : 'ui'}/tree/v3/src/runtime/components/${upperName}.vue

View File

@@ -84,7 +84,7 @@ provide('navigation', mappedNavigation)
<NuxtLoadingIndicator color="#FFF" />
<template v-if="!route.path.startsWith('/examples')">
<Banner />
<!-- <Banner /> -->
<Header :links="links" />
</template>

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -7,7 +7,7 @@ const { frameworks } = useSharedData()
v-slot="{ open }"
:modal="false"
:items="frameworks"
:ui="{ content: 'w-(--radix-dropdown-menu-trigger-width)' }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width)' }"
>
<UButton
color="neutral"

View File

@@ -30,7 +30,7 @@ defineShortcuts({
v-slot="{ open }"
:modal="false"
:items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: 'v2.19', to: 'https://ui.nuxt.com' }]"
:ui="{ content: 'w-(--radix-dropdown-menu-trigger-width) min-w-0' }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
size="xs"
>
<UButton

View File

@@ -280,7 +280,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
container: 'mt-0'
}"
>
<USelectMenu
<USelect
v-if="option.items?.length"
:model-value="getComponentProp(option.name)"
:items="option.items"
@@ -288,7 +288,6 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
color="neutral"
variant="soft"
class="rounded-[var(--ui-radius)] rounded-l-none min-w-12"
:search-input="false"
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
:ui="{ itemLeadingChip: 'size-2' }"
@update:model-value="setComponentProp(option.name, $event)"
@@ -303,7 +302,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
class="size-2"
/>
</template>
</USelectMenu>
</USelect>
<UInput
v-else
:type="option.type?.includes('number') ? 'number' : 'text'"

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Slot } from 'radix-vue'
import { Slot } from 'reka-ui'
</script>
<template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
import type { Matcher } from 'radix-vue/date'
import type { Matcher } from 'reka-ui/date'
const modelValue = shallowRef({
start: new CalendarDate(2022, 1, 1),

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
import type { Matcher } from 'radix-vue/date'
import type { Matcher } from 'reka-ui/date'
const modelValue = shallowRef({
start: new CalendarDate(2022, 1, 1),

View File

@@ -14,7 +14,7 @@ const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
filter: false
ignoreFilter: true
}])
</script>

View File

@@ -11,8 +11,8 @@ const items = [
level: 2
},
{
id: '/getting-started#radix-vue-3',
label: 'Radix Vue',
id: '/getting-started#reka-ui-radix-vue',
label: 'Reka UI',
level: 3
},
{

View File

@@ -72,7 +72,7 @@ const users = [
}
]
const searchTerm = ref('')
const searchTerm = ref('B')
function onSelect() {
searchTerm.value = ''

View File

@@ -13,7 +13,7 @@ const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
filter: false
ignoreFilter: true
}])
</script>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
function onCreate(item: string) {
items.value.push(item)
value.value = item
}
</script>
<template>
<UInputMenu
v-model="value"
create-item
:items="items"
class="w-48"
@create="onCreate"
/>
</template>

View File

@@ -16,7 +16,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UInputMenu
:items="users || []"
:loading="status === 'pending'"
:filter="['label', 'email']"
:filter-fields="['label', 'email']"
icon="i-lucide-user"
placeholder="Select user"
class="w-80"

View File

@@ -20,7 +20,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
v-model:search-term="searchTerm"
:items="users || []"
:loading="status === 'pending'"
:filter="false"
ignore-filter
icon="i-lucide-user"
placeholder="Select user"
>

View File

@@ -13,7 +13,7 @@ const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
filter: false
ignoreFilter: true
}])
</script>

View File

@@ -61,7 +61,7 @@ const items = [
:items="items"
class="justify-center"
:ui="{
viewport: 'sm:w-[var(--radix-navigation-menu-viewport-width)]',
viewport: 'sm:w-[var(--reka-navigation-menu-viewport-width)]',
childList: 'sm:w-96',
childLinkDescription: 'text-balance line-clamp-2'
}"

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
function onCreate(item: string) {
items.value.push(item)
value.value = item
}
</script>
<template>
<USelectMenu
v-model="value"
create-item
:items="items"
class="w-48"
@create="onCreate"
/>
</template>

View File

@@ -16,7 +16,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<USelectMenu
:items="users || []"
:loading="status === 'pending'"
:filter="['label', 'email']"
:filter-fields="['label', 'email']"
icon="i-lucide-user"
placeholder="Select user"
class="w-80"

View File

@@ -20,7 +20,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
v-model:search-term="searchTerm"
:items="users || []"
:loading="status === 'pending'"
:filter="false"
ignore-filter
icon="i-lucide-user"
placeholder="Select user"
class="w-48"

View File

@@ -26,7 +26,7 @@ function getUserAvatar(value: string) {
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="getUserAvatar(modelValue)"
v-bind="getUserAvatar(modelValue as string)"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>

View File

@@ -34,7 +34,7 @@ function getChip(value: string) {
<template #leading="{ modelValue, ui }">
<UChip
v-if="modelValue"
v-bind="getChip(modelValue)"
v-bind="getChip(modelValue as string)"
inset
standalone
:size="ui.itemLeadingChipSize()"

View File

@@ -143,14 +143,13 @@ const data = ref<Payment[]>([{
const columns: TableColumn<Payment>[] = [{
id: 'select',
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsAllPageRowsSelected(),
'indeterminate': table.getIsSomePageRowsSelected(),
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
}),
enableSorting: false,

View File

@@ -48,14 +48,13 @@ const data = ref<Payment[]>([{
const columns: TableColumn<Payment>[] = [{
id: 'select',
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsAllPageRowsSelected(),
'indeterminate': table.getIsSomePageRowsSelected(),
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
})
}, {

View File

@@ -91,7 +91,7 @@ provide('navigation', mappedNavigation)
<UApp>
<NuxtLoadingIndicator color="#FFF" />
<Banner />
<!-- <Banner /> -->
<Header :links="links" />

View File

@@ -8,12 +8,12 @@ We're thrilled to introduce this major update to our UI library, bringing signif
## What's New in v3?
### Radix Vue
### Reka UI (Radix Vue)
We've transitioned from [Headless UI](https://headlessui.com/) to [Radix Vue](https://www.radix-vue.com/) as our core component foundation. This shift brings several key advantages:
We've transitioned from [Headless UI](https://headlessui.com/) to [Reka UI](https://reka-ui.com/) as our core component foundation. This shift brings several key advantages:
- **Extensive Component Library**: With 55+ primitives, Radix Vue significantly expands our component offerings.
- **Active Development**: Radix Vue's growing popularity ensures ongoing improvements and updates.
- **Extensive Component Library**: With 55+ primitives, Reka UI significantly expands our component offerings.
- **Active Development**: Reka UI's growing popularity ensures ongoing improvements and updates.
- **Enhanced Accessibility**: Built-in accessibility features align with our commitment to inclusive design.
- **Vue 3 Optimization**: Seamless integration with Vue 3 and the Composition API.
@@ -103,7 +103,7 @@ Key points to consider:
::
::accordion-item{label="How does Nuxt UI v3 handle accessibility?"}
Nuxt UI v3 enhances accessibility through Radix Vue integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Radix Vue's accessibility documentation](https://www.radix-vue.com/overview/accessibility.html).
Nuxt UI v3 enhances accessibility through Reka UI integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Reka UI's accessibility documentation](https://reka-ui.com/docs/overview/accessibility).
::
::accordion-item{label="What is the testing approach for Nuxt UI v3?"}

View File

@@ -18,7 +18,7 @@ const toast = useToast()
- When removing a toast, there's a 200ms delay before it's actually removed from the state, allowing for exit animations.
::warning
Make sure to wrap your app with the [`App`](/components/app) component which uses our [`Toaster`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/Toaster.vue) component which uses the [`ToastProvider`](https://www.radix-vue.com/components/toast.html#provider) component from Radix Vue.
Make sure to wrap your app with the [`App`](/components/app) component which uses our [`Toaster`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/Toaster.vue) component which uses the [`ToastProvider`](https://reka-ui.com/docs/components/toast#provider) component from Reka UI.
::
::tip{to="/components/toast"}

View File

@@ -9,13 +9,13 @@ links:
## Usage
This component implements Radix Vue [ConfigProvider](https://www.radix-vue.com/utilities/config-provider.html) to provide global configuration to all components:
This component implements Reka UI [ConfigProvider](https://reka-ui.com/docs/utilities/config-provider) to provide global configuration to all components:
- Enables all primitives to inherit global reading direction.
- Enables changing the behavior of scroll body when setting body lock.
- Much more controls to prevent layout shifts.
It's also using [ToastProvider](https://www.radix-vue.com/components/toast.html#provider) and [TooltipProvider](https://www.radix-vue.com/components/tooltip.html#provider) to provide global toasts and tooltips, as well as programmatic modals and slideovers.
It's also using [ToastProvider](https://reka-ui.com/docs/components/toast#provider) and [TooltipProvider](https://reka-ui.com/docs/components/tooltip#provider) to provide global toasts and tooltips, as well as programmatic modals and slideovers.
Use it as at the root of your app:

View File

@@ -2,8 +2,8 @@
description: A stacked set of collapsible panels.
links:
- label: Accordion
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/accordion.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/accordion
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Accordion.vue
@@ -104,6 +104,38 @@ props:
---
::
### Unmount
Use the `unmount-on-hide` prop to prevent the content from being unmounted when the accordion is collapsed. Defaults to `true`.
::component-code
---
ignore:
- items
external:
- items
hide:
- class
props:
class: 'px-4'
unmountOnHide: false
items:
- label: 'Icons'
icon: 'i-lucide-smile'
content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
- label: 'Colors'
icon: 'i-lucide-swatch-book'
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
- label: 'Components'
icon: 'i-lucide-box'
content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
---
::
::note
You can inspect the DOM to see each item's content being rendered.
::
### Disabled
Use the `disabled` property to disable the Accordion.

View File

@@ -2,8 +2,8 @@
description: An img element with fallback and Nuxt Image support.
links:
- label: Avatar
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/avatar.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/avatar
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Avatar.vue

View File

@@ -3,8 +3,8 @@ title: Calendar
description: A calendar component for selecting single dates, multiple dates or date ranges.
links:
- label: Calendar
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/calendar.html
icon: i-custom-reka-ui
to: https://www.reka-ui.com/components/calendar.html
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Calendar.vue

View File

@@ -2,8 +2,8 @@
description: An input element to toggle between checked and unchecked states.
links:
- label: Checkbox
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/checkbox.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/checkbox
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Checkbox.vue
@@ -37,12 +37,14 @@ props:
### Indeterminate
Use the `indeterminate` prop to set the Checkbox to an [indeterminate state](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes).
Use the `indeterminate` value in the `v-model` directive or `default-value` prop to set the Checkbox to an [indeterminate state](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes).
::component-code
---
ignore:
- defaultValue
props:
indeterminate: true
defaultValue: 'indeterminate'
---
::
@@ -52,8 +54,10 @@ Use the `indeterminate-icon` prop to customize the indeterminate icon. Defaults
::component-code
---
ignore:
- defaultValue
props:
indeterminate: true
defaultValue: 'indeterminate'
indeterminateIcon: 'i-lucide-plus'
---
::

View File

@@ -2,8 +2,8 @@
description: A collapsible element to toggle visibility of its content.
links:
- label: Collapsible
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/collapsible.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/collapsible
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Collapsible.vue
@@ -38,6 +38,38 @@ slots:
:placeholder{class="h-48"}
::
### Unmount
Use the `unmount-on-hide` prop to prevent the content from being unmounted when the Collapsible is collapsed. Defaults to `true`.
::component-code
---
prettier: true
ignore:
- class
props:
unmountOnHide: false
class: 'flex flex-col gap-2 w-48'
slots:
default: |
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-down" block />
content: |
<Placeholder class="h-48" />
---
:u-button{label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-down" block}
#content
:placeholder{class="h-48"}
::
::note
You can inspect the DOM to see the content being rendered.
::
### Disabled
Use the `disabled` prop to disable the Collapsible.

View File

@@ -6,8 +6,8 @@ links:
to: https://fusejs.io/
target: _blank
- label: Combobox
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/combobox.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/combobox
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/CommandPalette.vue
@@ -29,7 +29,7 @@ The CommandPalette component filters groups and ranks matching commands by relev
- `label?: string`{lang="ts-type"}
- `slot?: string`{lang="ts-type"}
- `items?: CommandPaletteItem[]`{lang="ts-type"}
- [`filter?: boolean`{lang="ts-type"}](#without-internal-search)
- [`ignoreFilter?: boolean`{lang="ts-type"}](#with-ignore-filter)
- [`postFilter?: (searchTerm: string, items: T[]) => T[]`{lang="ts-type"}](#with-post-filtered-items)
- `highlightedIcon?: string`{lang="ts-type"}
@@ -478,14 +478,14 @@ class: '!p-0'
---
::
### Without internal search
### With ignore filter
You can set the `filter` field to `false` on a group to disable the internal search and use your own search logic.
You can set the `ignoreFilter` field to `true` on a group to disable the internal search and use your own search logic.
::component-example
---
collapse: true
name: 'command-palette-filter-example'
name: 'command-palette-ignore-filter-example'
class: '!p-0'
---
::

View File

@@ -3,8 +3,8 @@ title: ContextMenu
description: A menu to display actions when right-clicking on an element.
links:
- label: ContextMenu
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/context-menu.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/context-menu
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/ContextMenu.vue

View File

@@ -2,8 +2,8 @@
description: A drawer that smoothly slides in & out of the screen.
links:
- label: Drawer
icon: i-custom-radix-vue
to: https://github.com/radix-vue/vaul-vue
icon: i-custom-reka-ui
to: https://github.com/unovue/vaul-vue
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Drawer.vue

View File

@@ -3,8 +3,8 @@ title: DropdownMenu
description: A menu to display actions when clicking on an element.
links:
- label: DropdownMenu
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/dropdown-menu.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/dropdown-menu
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/DropdownMenu.vue

View File

@@ -3,8 +3,8 @@ title: InputMenu
description: An autocomplete input with real-time suggestions.
links:
- label: Combobox
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/combobox.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/combobox
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/InputMenu.vue
@@ -15,7 +15,7 @@ links:
Use the `v-model` directive to control the value of the InputMenu or the `default-value` prop to set the initial value when you do not need to control its state.
::tip
Use this over an [`Input`](/components/input) to take advantage of Radix Vue's [`Combobox`](https://www.radix-vue.com/components/combobox.html) component that offers autocomplete capabilities.
Use this over an [`Input`](/components/input) to take advantage of Reka UI's [`Combobox`](https://reka-ui.com/docs/components/combobox) component that offers autocomplete capabilities.
::
::note
@@ -222,42 +222,6 @@ props:
---
::
### Create Item
Use the `create-item` prop to allow user input.
::component-code
---
prettier: true
ignore:
- modelValue
- items
external:
- items
- modelValue
items:
createItem:
- true
- 'always'
props:
modelValue: 'Backlog'
items:
- Backlog
- Todo
- In Progress
- Done
createItem: true
---
::
::note
The create option shows when no match is found by default. Set it to `always` to show it even when similar values exist.
::
::tip{to="#emits"}
Use the `@create` event to handle the creation of the item. You will receive the event and the item as arguments.
::
### Content
Use the `content` prop to control how the InputMenu content is rendered, like its `align` or `side` for example.
@@ -282,7 +246,7 @@ items:
- top
- bottom
props:
modelValue: Backlog
modelValue: 'Backlog'
content:
align: center
side: bottom
@@ -310,7 +274,7 @@ external:
- items
- modelValue
props:
modelValue: Backlog
modelValue: 'Backlog'
arrow: true
items:
- Backlog
@@ -734,6 +698,25 @@ name: 'input-menu-icon-example'
---
::
### With create item
Use the `create-item` prop to enable users to add custom values that aren't in the predefined options.
::component-example
---
collapse: true
name: 'input-menu-create-item-example'
---
::
::note
The create option shows when no match is found by default. Set it to `always` to show it even when similar values exist.
::
::tip{to="#emits"}
Use the `@create` event to handle the creation of the item. You will receive the event and the item as arguments.
::
### With fetched items
You can fetch items from an API and use them in the InputMenu.
@@ -745,14 +728,14 @@ name: 'input-menu-fetch-example'
---
::
### Without internal search
### With ignore filter
Set the `filter` prop to `false` to disable the internal search and use your own search logic.
Set the `ignore-filter` prop to `true` to disable the internal search and use your own search logic.
::component-example
---
collapse: true
name: 'input-menu-filter-example'
name: 'input-menu-ignore-filter-example'
---
::
@@ -760,9 +743,9 @@ name: 'input-menu-filter-example'
This example uses [`refDebounced`](https://vueuse.org/shared/refDebounced/#refdebounced) to debounce the API calls.
::
### With custom search
### With filter fields
Use the `filter` prop with an array of fields to filter on. Defaults to `[labelKey]`.
Use the `filter-fields` prop with an array of fields to filter on. Defaults to `[labelKey]`.
::component-example
---

View File

@@ -3,8 +3,8 @@ title: InputNumber
description: Input numerical values with a customizable range.
links:
- label: Number Field
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/number-field
icon: i-custom-reka-ui
to: https://www.reka-ui.com/components/input-number
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/InputNumber.vue

View File

@@ -2,8 +2,8 @@
description: A dialog window that can be used to display a message or request user input.
links:
- label: Dialog
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/dialog.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/dialog
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Modal.vue

View File

@@ -3,8 +3,8 @@ title: NavigationMenu
description: A list of links that can be displayed horizontally or vertically.
links:
- label: NavigationMenu
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/navigation-menu.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/navigation-menu
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/NavigationMenu.vue
@@ -249,7 +249,7 @@ external:
- items
props:
highlight: true
highlightColor: ''
highlightColor: 'primary'
orientation: 'horizontal'
items:
- - label: Guide
@@ -538,6 +538,98 @@ props:
The arrow is animated to follow the active item.
::
### Unmount
Use the `unmount-on-hide` prop to control the content unmounting behavior. Defaults to `true`.
::component-code
---
collapse: true
ignore:
- items
- arrow
- class
external:
- items
props:
unmountOnHide: false
items:
- label: Guide
icon: i-lucide-book-open
to: /getting-started
children:
- label: Introduction
description: Fully styled and customizable components for Nuxt.
icon: i-lucide-house
- label: Installation
description: Learn how to install and configure Nuxt UI in your application.
icon: i-lucide-cloud-download
- label: 'Icons'
icon: 'i-lucide-smile'
description: 'You have nothing to do, @nuxt/icon will handle it automatically.'
- label: 'Colors'
icon: 'i-lucide-swatch-book'
description: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
- label: 'Theme'
icon: 'i-lucide-cog'
description: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
- label: Composables
icon: i-lucide-database
to: /composables
children:
- label: defineShortcuts
icon: i-lucide-file-text
description: Define shortcuts for your application.
to: /composables/define-shortcuts
- label: useModal
icon: i-lucide-file-text
description: Display a modal within your application.
to: /composables/use-modal
- label: useSlideover
icon: i-lucide-file-text
description: Display a slideover within your application.
to: /composables/use-slideover
- label: useToast
icon: i-lucide-file-text
description: Display a toast within your application.
to: /composables/use-toast
- label: Components
icon: i-lucide-box
to: /components
active: true
children:
- label: Link
icon: i-lucide-file-text
description: Use NuxtLink with superpowers.
to: /components/link
- label: Modal
icon: i-lucide-file-text
description: Display a modal within your application.
to: /components/modal
- label: NavigationMenu
icon: i-lucide-file-text
description: Display a list of links.
to: /components/navigation-menu
- label: Pagination
icon: i-lucide-file-text
description: Display a list of pages.
to: /components/pagination
- label: Popover
icon: i-lucide-file-text
description: Display a non-modal dialog that floats around a trigger element.
to: /components/popover
- label: Progress
icon: i-lucide-file-text
description: Show a horizontal bar to indicate task progression.
to: /components/progress
class: 'justify-center'
---
::
::note
You can inspect the DOM to see each item's content being rendered.
::
## Examples
### Control active item
@@ -592,7 +684,7 @@ name: 'navigation-menu-content-slot-example'
::
::note
In this example, we add the `sm:w-[var(--radix-navigation-menu-viewport-width)]` class on the `viewport` to have a dynamic width. This requires to set a width on the content's first child.
In this example, we add the `sm:w-[var(--reka-navigation-menu-viewport-width)]` class on the `viewport` to have a dynamic width. This requires to set a width on the content's first child.
::
## API

View File

@@ -2,8 +2,8 @@
description: A list of buttons or links to navigate through pages.
links:
- label: Pagination
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/pagination.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/pagination
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Pagination.vue

View File

@@ -1,9 +1,12 @@
---
description: A non-modal dialog that floats around a trigger element.
links:
- label: HoverCard
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/hover-card
- label: Popover
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/popover.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/popover
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Popover.vue
@@ -64,7 +67,7 @@ slots:
::
::note
When using the `hover` mode, the Radix Vue [`HoverCard`](https://www.radix-vue.com/components/hover-card.html) component is used instead of the [`Popover`](https://www.radix-vue.com/components/popover.html).
When using the `hover` mode, the Reka UI [`HoverCard`](https://reka-ui.com/docs/components/hover-card) component is used instead of the [`Popover`](https://reka-ui.com/docs/components/popover).
::
### Delay

View File

@@ -2,8 +2,8 @@
description: An indicator showing the progress of a task.
links:
- label: Progress
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/progress.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/progress
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Progress.vue

View File

@@ -3,8 +3,8 @@ title: RadioGroup
description: A set of radio buttons to select a single option from a list.
links:
- label: RadioGroup
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/radio-group.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/radio-group
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/RadioGroup.vue

View File

@@ -3,8 +3,8 @@ title: SelectMenu
description: An advanced searchable select element.
links:
- label: Combobox
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/combobox.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/combobox
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/SelectMenu.vue
@@ -15,7 +15,7 @@ links:
Use the `v-model` directive to control the value of the SelectMenu or the `default-value` prop to set the initial value when you do not need to control its state.
::tip
Use this over a [`Select`](/components/select) to take advantage of Radix Vue's [`Combobox`](https://www.radix-vue.com/components/combobox.html) component that offers search capabilities and multiple selection.
Use this over a [`Select`](/components/select) to take advantage of Reka UI's [`Combobox`](https://reka-ui.com/docs/components/combobox) component that offers search capabilities and multiple selection.
::
::note
@@ -239,44 +239,6 @@ props:
You can set the `search-input` prop to `false` to hide the search input.
::
### Create Item
Use the `create-item` prop to allow user input.
::component-code
---
prettier: true
ignore:
- modelValue
- items
- class
external:
- items
- modelValue
items:
createItem:
- true
- 'always'
props:
modelValue: 'Backlog'
createItem: true
items:
- Backlog
- Todo
- In Progress
- Done
class: 'w-48'
---
::
::note
The create option shows when no match is found by default. Set it to `always` to show it even when similar values exist.
::
::tip{to="#emits"}
Use the `@create` event to handle the creation of the item. You will receive the event and the item as arguments.
::
### Content
Use the `content` prop to control how the SelectMenu content is rendered, like its `align` or `side` for example.
@@ -302,7 +264,7 @@ items:
- top
- bottom
props:
modelValue: Backlog
modelValue: 'Backlog'
content:
align: center
side: bottom
@@ -332,7 +294,7 @@ external:
- items
- modelValue
props:
modelValue: Backlog
modelValue: 'Backlog'
arrow: true
items:
- Backlog
@@ -769,6 +731,25 @@ name: 'select-menu-icon-example'
---
::
### With create item
Use the `create-item` prop to enable users to add custom values that aren't in the predefined options.
::component-example
---
collapse: true
name: 'select-menu-create-item-example'
---
::
::note
The create option shows when no match is found by default. Set it to `always` to show it even when similar values exist.
::
::tip{to="#emits"}
Use the `@create` event to handle the creation of the item. You will receive the event and the item as arguments.
::
### With fetched items
You can fetch items from an API and use them in the SelectMenu.
@@ -780,14 +761,14 @@ name: 'select-menu-fetch-example'
---
::
### Without internal search
### With ignore filter
Set the `filter` prop to `false` to disable the internal search and use your own search logic.
Set the `ignore-filter` prop to `true` to disable the internal search and use your own search logic.
::component-example
---
collapse: true
name: 'select-menu-filter-example'
name: 'select-menu-ignore-filter-example'
---
::
@@ -795,9 +776,9 @@ name: 'select-menu-filter-example'
This example uses [`refDebounced`](https://vueuse.org/shared/refDebounced/#refdebounced) to debounce the API calls.
::
### With custom search
### With filter fields
Use the `filter` prop with an array of fields to filter on. Defaults to `[labelKey]`.
Use the `filter-fields` prop with an array of fields to filter on. Defaults to `[labelKey]`.
::component-example
---

View File

@@ -2,8 +2,8 @@
description: A select element to choose from a list of options.
links:
- label: Select
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/select.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/select
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Select.vue
@@ -135,6 +135,39 @@ props:
---
::
### Multiple
Use the `multiple` prop to allow multiple selections, the selected items will be separated by a comma in the trigger.
::component-code
---
prettier: true
ignore:
- modelValue
- items
- multiple
- class
external:
- items
- modelValue
props:
modelValue:
- Backlog
- Todo
multiple: true
items:
- Backlog
- Todo
- In Progress
- Done
class: 'w-48'
---
::
::caution
Ensure to pass an array to the `default-value` prop or the `v-model` directive.
::
### Placeholder
Use the `placeholder` prop to set a placeholder text.
@@ -160,11 +193,7 @@ props:
### Content
Use the `content` prop to control how the Select content is rendered, like its `align`, `side` or `position` for example.
::warning
The `content.align`, `content.side`, etc. properties only apply when `content.position` is set to `popper`.
::
Use the `content` prop to control how the Select content is rendered, like its `align` or `side` for example.
::component-code
---
@@ -177,9 +206,6 @@ external:
- items
- modelValue
items:
content.position:
- 'item-aligned'
- 'popper'
content.align:
- start
- center
@@ -190,9 +216,8 @@ items:
- top
- bottom
props:
modelValue: 'Todo'
modelValue: 'Backlog'
content:
position: 'item-aligned'
align: center
side: bottom
sideOffset: 8
@@ -205,11 +230,6 @@ props:
---
::
::note{to="https://www.radix-vue.com/components/select.html#change-the-positioning-mode"}
Read more about the `content.position` prop in the **Radix Vue** documentation.
::
<!--
### Arrow
Use the `arrow` prop to display an arrow on the Select.
@@ -226,7 +246,7 @@ external:
- items
- modelValue
props:
modelValue: 'Todo'
modelValue: 'Backlog'
arrow: true
items:
- Backlog
@@ -237,8 +257,6 @@ props:
---
::
-->
### Color
Use the `color` prop to change the ring color when the Select is focused.

View File

@@ -2,8 +2,8 @@
description: Separates content horizontally or vertically.
links:
- label: Separator
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/separator.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/separator
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Separator.vue

View File

@@ -2,8 +2,8 @@
description: A dialog that slides in from any side of the screen.
links:
- label: Dialog
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/dialog.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/dialog
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Slideover.vue

View File

@@ -2,8 +2,8 @@
description: An input to select a numeric value within a range.
links:
- label: Slider
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/slider.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/slider
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Slider.vue

View File

@@ -2,8 +2,8 @@
description: A control that toggles between two states.
links:
- label: Switch
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/switch.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/switch
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Switch.vue

View File

@@ -2,8 +2,8 @@
description: A set of tab panels that are displayed one at a time.
links:
- label: Tabs
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/tabs.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/tabs
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Tabs.vue
@@ -44,9 +44,7 @@ props:
### Content
Use the `content` prop to control how the Tabs are rendered.
You can set it to `false` to prevent the Tabs from rendering any content and act as a toggle.
Set the `content` prop to `false` to turn the Tabs into a toggle-only control without displaying any content. Defaults to `true`.
::component-code
---
@@ -69,20 +67,20 @@ props:
---
::
You can also choose to only render the content of the active tab by setting `content.forceMount` to `false`.
### Unmount
Use the `unmount-on-hide` prop to prevent the content from being unmounted when the Tabs is collapsed. Defaults to `true`.
::component-code
---
prettier: true
ignore:
- content.forceMount
- content
- items
- class
external:
- items
props:
content:
forceMount: false
unmountOnHide: false
items:
- label: Account
icon: 'i-lucide-user'
@@ -95,7 +93,7 @@ props:
::
::note
You can inspect the DOM to see that the content of the inactive tab is not rendered.
You can inspect the DOM to see each item's content being rendered.
::
### Color

View File

@@ -2,8 +2,8 @@
description: A succinct message to provide information or feedback to the user.
links:
- label: Toast
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/toast.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/toast
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Toast.vue
@@ -14,7 +14,7 @@ links:
Use the [useToast](/composables/use-toast) composable to display a toast in your application.
::warning
Make sure to wrap your app with the [`App`](/components/app) component which uses our [`Toaster`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/Toaster.vue) component which uses the [`ToastProvider`](https://www.radix-vue.com/components/toast.html#provider) component from Radix Vue.
Make sure to wrap your app with the [`App`](/components/app) component which uses our [`Toaster`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/Toaster.vue) component which uses the [`ToastProvider`](https://reka-ui.com/docs/components/toast#provider) component from Reka UI.
::
::tip{to="/components/app#props"}

View File

@@ -2,8 +2,8 @@
description: A popup that reveals information when hovering over an element.
links:
- label: Tooltip
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/tooltip.html
icon: i-custom-reka-ui
to: https://reka-ui.com/docs/components/tooltip
- label: GitHub
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Tooltip.vue
@@ -14,7 +14,7 @@ links:
Use a [Button](/components/button) or any other component in the default slot of the Tooltip.
::warning
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`TooltipProvider`](https://www.radix-vue.com/components/tooltip.html#provider) component from Radix Vue.
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`TooltipProvider`](https://reka-ui.com/docs/components/tooltip#provider) component from Reka UI.
::
::tip{to="/components/app#props"}

View File

@@ -10,7 +10,7 @@
"@nuxt/content": "https://pkg.pr.new/@nuxt/content@c5b1a4f",
"@nuxt/image": "^1.8.1",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@3cbc7f0",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@4fc0904",
"@nuxthub/core": "^0.8.7",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^21.0.2",

View File

@@ -103,8 +103,8 @@
"magic-string": "^0.30.14",
"mlly": "^1.7.3",
"ohash": "^1.1.4",
"reka-ui": "https://pkg.pr.new/reka-ui@8238615",
"pathe": "^1.1.2",
"radix-vue": "^1.9.10",
"scule": "^1.3.0",
"sirv": "^3.0.0",
"tailwind-variants": "^0.3.0",

View File

@@ -30,6 +30,6 @@ export default defineConfig({
],
optimizeDeps: {
// prevents reloading page when navigating between components
include: ['radix-vue/namespaced', 'vaul-vue', 'embla-carousel-vue', 'embla-carousel-autoplay', 'embla-carousel-auto-scroll', 'embla-carousel-auto-height', 'embla-carousel-class-names', 'embla-carousel-fade', 'embla-carousel-wheel-gestures']
include: ['reka-ui/namespaced', 'vaul-vue', 'embla-carousel-vue', 'embla-carousel-autoplay', 'embla-carousel-auto-scroll', 'embla-carousel-auto-height', 'embla-carousel-class-names', 'embla-carousel-fade', 'embla-carousel-wheel-gestures']
}
})

View File

@@ -14,7 +14,7 @@ const checked = ref(true)
<UCheckbox label="Error" color="error" :model-value="true" />
<UCheckbox label="Icon" icon="i-lucide-heart" :model-value="true" />
<UCheckbox label="Default value" :default-value="true" />
<UCheckbox label="Indeterminate" indeterminate />
<UCheckbox label="Indeterminate" default-value="indeterminate" />
<UCheckbox label="Required" required />
<UCheckbox label="Disabled" disabled />
</div>

View File

@@ -130,7 +130,7 @@ defineShortcuts({
<UButton label="Open modal" color="neutral" variant="outline" />
<template #content>
<ReuseTemplate :close="true" @close="open = false" />
<ReuseTemplate :close="true" @update:open="open = $event" />
</template>
</UModal>
@@ -138,7 +138,7 @@ defineShortcuts({
<UButton label="Open drawer" color="neutral" variant="outline" />
<template #content>
<ReuseTemplate class="border-t border-[var(--ui-border)]" />
<ReuseTemplate class="border-t border-[var(--ui-border)] mt-4" />
</template>
</UDrawer>

View File

@@ -83,7 +83,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<div class="flex flex-col gap-4 w-48">
<UInputMenu :items="items" placeholder="Disabled" disabled />
<UInputMenu :items="items" placeholder="Required" required />
<UInputMenu v-model="selectedItems" :items="items" placeholder="Multiple" multiple color="neutral" />
<UInputMenu v-model="selectedItems" :items="items" placeholder="Multiple" multiple />
<UInputMenu :items="items" loading placeholder="Search..." />
</div>
<div class="flex items-center gap-4">
@@ -119,7 +119,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
v-model:search-term="searchTerm"
:items="users || []"
:loading="status === 'pending'"
:filter="false"
ignore-filter
icon="i-lucide-user"
placeholder="Search users..."
:size="size"

View File

@@ -124,7 +124,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
v-model:search-term="searchTerm"
:items="users || []"
:loading="status === 'pending'"
:filter="false"
ignore-filter
icon="i-lucide-user"
placeholder="Search users..."
:size="size"

View File

@@ -10,6 +10,7 @@ const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
const statuses = [{
label: 'Backlog',
@@ -40,7 +41,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
lazy: true
})
function getStatusIcon(value: string): string {
function getStatusIcon(value: string) {
return statuses.find(status => status.value === value)?.icon || 'i-lucide-user'
}
@@ -52,7 +53,7 @@ function getUserAvatar(value: string) {
<template>
<div class="flex flex-col items-center gap-4">
<div class="flex flex-col gap-4 w-48">
<USelect :items="items" placeholder="Search..." />
<USelect :items="items" placeholder="Search..." default-value="Apple" />
</div>
<div class="flex items-center gap-2">
<USelect
@@ -90,6 +91,7 @@ function getUserAvatar(value: string) {
<div class="flex flex-col gap-4 w-48">
<USelect :items="items" placeholder="Disabled" disabled />
<USelect :items="items" placeholder="Required" required />
<USelect v-model="selectedItems" :items="items" placeholder="Multiple" multiple />
<USelect :items="items" loading placeholder="Search..." />
</div>
<div class="flex items-center gap-4">
@@ -114,7 +116,7 @@ function getUserAvatar(value: string) {
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UIcon v-if="modelValue" :name="getStatusIcon(modelValue)" :class="ui.leadingIcon()" />
<UIcon v-if="modelValue" :name="getStatusIcon(modelValue as string)" :class="ui.leadingIcon()" />
</template>
</USelect>
</div>
@@ -130,7 +132,7 @@ function getUserAvatar(value: string) {
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar v-if="modelValue" :size="ui.itemLeadingAvatarSize()" v-bind="getUserAvatar(modelValue)" />
<UAvatar v-if="modelValue" :size="ui.itemLeadingAvatarSize()" v-bind="getUserAvatar(modelValue as string)" />
</template>
</USelect>
</div>

View File

@@ -145,14 +145,13 @@ const data = ref<Payment[]>([{
const columns: TableColumn<Payment>[] = [{
id: 'select',
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsAllPageRowsSelected(),
'indeterminate': table.getIsSomePageRowsSelected(),
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
}),
enableSorting: false,

View File

@@ -3,8 +3,8 @@ import { createResolver } from '@nuxt/kit'
const { resolve } = createResolver(import.meta.url)
export default defineNuxtConfig({
modules: ['../src/module'],
devtools: { enabled: true },
ui: {

2047
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { AccordionRootProps, AccordionRootEmits } from 'radix-vue'
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'
@@ -22,7 +22,7 @@ export interface AccordionItem {
disabled?: boolean
}
export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible' | 'defaultValue' | 'modelValue' | 'type' | 'disabled'> {
export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible' | 'defaultValue' | 'modelValue' | 'type' | 'disabled' | 'unmountOnHide'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -89,7 +89,7 @@ extendDevtoolsMeta({
<script setup lang="ts" generic="T extends AccordionItem">
import { computed } from 'vue'
import { AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'radix-vue'
import { AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { get } from '../utils'
@@ -104,7 +104,7 @@ const emits = defineEmits<AccordionEmits>()
const slots = defineSlots<AccordionSlots<T>>()
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type'), emits)
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'collapsible', 'defaultValue', 'disabled', 'modelValue', 'type', 'unmountOnHide'), emits)
const ui = computed(() => accordion({
disabled: props.disabled

View File

@@ -64,7 +64,7 @@ extendDevtoolsMeta<AlertProps>({ defaultProps: { title: 'Heads up!' } })
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import UIcon from './Icon.vue'
@@ -75,8 +75,8 @@ const props = defineProps<AlertProps>()
const emits = defineEmits<AlertEmits>()
const slots = defineSlots<AlertSlots>()
const appConfig = useAppConfig()
const { t } = useLocale()
const appConfig = useAppConfig()
const multiline = computed(() => !!props.title && !!props.description)

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import type { ConfigProviderProps, TooltipProviderProps } from 'radix-vue'
import type { ConfigProviderProps, TooltipProviderProps } from 'reka-ui'
import { localeContextInjectionKey } from '../composables/useLocale'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ToasterProps, Locale } from '../types'
export interface AppProps extends Omit<ConfigProviderProps, 'useId' | 'dir'> {
export interface AppProps extends Omit<ConfigProviderProps, 'useId' | 'dir' | 'locale'> {
tooltip?: TooltipProviderProps
toaster?: ToasterProps | null
locale?: Locale
@@ -23,7 +23,7 @@ extendDevtoolsMeta({ ignore: true })
<script setup lang="ts">
import { toRef, useId, provide } from 'vue'
import { ConfigProvider, TooltipProvider, useForwardProps } from 'radix-vue'
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import UToaster from './Toaster.vue'
import UModalProvider from './ModalProvider.vue'
@@ -41,7 +41,7 @@ provide(localeContextInjectionKey, locale)
</script>
<template>
<ConfigProvider :use-id="() => (useId() as string)" :dir="locale?.dir" v-bind="configProviderProps">
<ConfigProvider :use-id="() => (useId() as string)" :dir="locale?.dir" :locale="locale?.code" v-bind="configProviderProps">
<TooltipProvider v-bind="tooltipProps">
<UToaster v-if="toaster !== null" v-bind="toasterProps">
<slot />

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { AvatarFallbackProps } from 'radix-vue'
import type { AvatarFallbackProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import _appConfig from '#build/app.config'
@@ -32,7 +32,7 @@ extendDevtoolsMeta<AvatarProps>({ defaultProps: { src: 'https://avatars.githubus
<script setup lang="ts">
import { computed } from 'vue'
import { AvatarRoot, AvatarImage, AvatarFallback, useForwardProps } from 'radix-vue'
import { AvatarRoot, AvatarImage, AvatarFallback, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAvatarGroup } from '../composables/useAvatarGroup'
import UIcon from './Icon.vue'

View File

@@ -35,7 +35,7 @@ extendDevtoolsMeta({ example: 'AvatarGroupExample' })
<script setup lang="ts">
import { computed, provide } from 'vue'
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
import { avatarGroupInjectionKey } from '../composables/useAvatarGroup'
import UAvatar from './Avatar.vue'

View File

@@ -36,7 +36,7 @@ export interface BadgeSlots {
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
import { useComponentIcons } from '../composables/useComponentIcons'
import UIcon from './Icon.vue'

View File

@@ -77,7 +77,7 @@ extendDevtoolsMeta({
<script setup lang="ts" generic="T extends BreadcrumbItem">
import { computed } from 'vue'
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
import { get } from '../utils'

View File

@@ -43,7 +43,7 @@ export interface ButtonSlots {
<script setup lang="ts">
import { type Ref, computed, ref, inject } from 'vue'
import { useForwardProps } from 'radix-vue'
import { useForwardProps } from 'reka-ui'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useButtonGroup } from '../composables/useButtonGroup'
import { formLoadingInjectionKey } from '../composables/useFormField'

View File

@@ -35,7 +35,7 @@ extendDevtoolsMeta({ example: 'ButtonGroupExample' })
<script setup lang="ts">
import { provide, computed } from 'vue'
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
import { buttonGroupInjectionKey } from '../composables/useButtonGroup'
const props = withDefaults(defineProps<ButtonGroupProps>(), {

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'radix-vue'
import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui'
import type { DateValue } from '@internationalized/date'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
@@ -73,8 +73,8 @@ export interface CalendarSlots {
<script setup lang="ts" generic="R extends boolean = false, M extends boolean = false">
import { computed } from 'vue'
import { useForwardPropsEmits } from 'radix-vue'
import { Calendar as SingleCalendar, RangeCalendar } from 'radix-vue/namespaced'
import { useForwardPropsEmits } from 'reka-ui'
import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
import { reactiveOmit } from '@vueuse/core'
import { useLocale } from '../composables/useLocale'
import UButton from './Button.vue'

View File

@@ -29,7 +29,7 @@ extendDevtoolsMeta({ example: 'CardExample' })
</script>
<script setup lang="ts">
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
const props = defineProps<CardProps>()
const slots = defineSlots<CardSlots>()

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { tv, 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'
import type { AutoplayOptionsType } from 'embla-carousel-autoplay'
import type { AutoScrollOptionsType } from 'embla-carousel-auto-scroll'
@@ -12,7 +13,7 @@ import _appConfig from '#build/app.config'
import theme from '#build/ui/carousel'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ButtonProps } from '../types'
import type { AcceptableValue, PartialString } from '../types/utils'
import type { PartialString } from '../types/utils'
const appConfig = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
@@ -21,6 +22,11 @@ const carousel = tv({ extend: tv(theme), ...(appConfig.ui?.carousel || {}) })
type CarouselVariants = VariantProps<typeof carousel>
export interface CarouselProps<T> extends Omit<EmblaOptionsType, 'axis' | 'container' | 'slides' | 'direction'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
*/
as?: any
/**
* Configure the prev button when arrows are enabled.
* @defaultValue { size: 'md', color: 'neutral', variant: 'link' }
@@ -97,7 +103,7 @@ extendDevtoolsMeta({ example: 'CarouselExample' })
<script setup lang="ts" generic="T extends AcceptableValue">
import { computed, ref, watch, onMounted } from 'vue'
import useEmblaCarousel from 'embla-carousel-vue'
import { useForwardProps } from 'radix-vue'
import { Primitive, useForwardProps } from 'reka-ui'
import { reactivePick, computedAsync } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
@@ -254,7 +260,8 @@ defineExpose({
</script>
<template>
<div
<Primitive
:as="as"
role="region"
aria-roledescription="carousel"
tabindex="0"
@@ -311,5 +318,5 @@ defineExpose({
</template>
</div>
</div>
</div>
</Primitive>
</template>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { CheckboxRootProps } from 'radix-vue'
import type { CheckboxRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/checkbox'
@@ -12,7 +12,12 @@ const checkbox = tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })
type CheckboxVariants = VariantProps<typeof checkbox>
export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'required' | 'name' | 'value' | 'id'> {
export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'required' | 'name' | 'value' | 'id' | 'defaultValue'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
*/
as?: any
label?: string
description?: string
color?: CheckboxVariants['color']
@@ -22,21 +27,17 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
* @defaultValue appConfig.ui.icons.check
*/
icon?: string
indeterminate?: boolean
/**
* The icon displayed when the checkbox is indeterminate.
* @defaultValue appConfig.ui.icons.minus
*/
indeterminateIcon?: string
/** The checked state of the checkbox when it is initially rendered. Use when you do not need to control its checked state. */
defaultValue?: boolean
class?: any
ui?: Partial<typeof checkbox.slots>
}
export interface CheckboxEmits {
(e: 'update:modelValue', payload: boolean): void
(e: 'change', payload: Event): void
export type CheckboxEmits = {
change: [payload: Event]
}
export interface CheckboxSlots {
@@ -49,7 +50,7 @@ extendDevtoolsMeta({ defaultProps: { label: 'Check me!' } })
<script setup lang="ts">
import { computed, useId } from 'vue'
import { CheckboxRoot, CheckboxIndicator, Label, useForwardProps } from 'radix-vue'
import { Primitive, CheckboxRoot, CheckboxIndicator, Label, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useFormField } from '../composables/useFormField'
@@ -59,29 +60,20 @@ const props = defineProps<CheckboxProps>()
const slots = defineSlots<CheckboxSlots>()
const emits = defineEmits<CheckboxEmits>()
const modelValue = defineModel<boolean | undefined>({ default: undefined })
const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined })
const rootProps = useForwardProps(reactivePick(props, 'required', 'value'))
const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
const appConfig = useAppConfig()
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<CheckboxProps>(props)
const id = _id.value ?? useId()
const checked = computed({
get() {
return props.indeterminate ? 'indeterminate' : modelValue.value
},
set(value) {
modelValue.value = value === 'indeterminate' ? undefined : value
}
})
const ui = computed(() => checkbox({
size: size.value,
color: color.value,
required: props.required,
disabled: disabled.value,
checked: (modelValue.value ?? props.defaultValue) || props.indeterminate
checked: Boolean(modelValue.value ?? props.defaultValue)
}))
function onUpdate(value: any) {
@@ -93,23 +85,25 @@ function onUpdate(value: any) {
}
</script>
<!-- eslint-disable vue/no-template-shadow -->
<template>
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<div :class="ui.container({ class: props.ui?.container })">
<CheckboxRoot
:id="id"
v-model:checked="checked"
:default-checked="defaultValue"
v-bind="rootProps"
v-model="modelValue"
:name="name"
:disabled="disabled"
:class="ui.base({ class: props.ui?.base })"
@update:checked="onUpdate"
@update:model-value="onUpdate"
>
<CheckboxIndicator as-child>
<UIcon v-if="indeterminate" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon({ class: props.ui?.icon })" />
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon({ class: props.ui?.icon })" />
</CheckboxIndicator>
<template #default="{ modelValue }">
<CheckboxIndicator as-child>
<UIcon v-if="modelValue === 'indeterminate'" :name="indeterminateIcon || appConfig.ui.icons.minus" :class="ui.icon({ class: props.ui?.icon })" />
<UIcon v-else :name="icon || appConfig.ui.icons.check" :class="ui.icon({ class: props.ui?.icon })" />
</CheckboxIndicator>
</template>
</CheckboxRoot>
</div>
@@ -125,5 +119,5 @@ function onUpdate(value: any) {
</slot>
</p>
</div>
</div>
</Primitive>
</template>

View File

@@ -44,7 +44,7 @@ extendDevtoolsMeta({ example: 'ChipExample' })
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive, Slot } from 'radix-vue'
import { Primitive, Slot } from 'reka-ui'
import { useAvatarGroup } from '../composables/useAvatarGroup'
defineOptions({ inheritAttrs: false })

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { CollapsibleRootProps, CollapsibleRootEmits } from 'radix-vue'
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'
@@ -10,7 +10,7 @@ const appConfig = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof
const collapsible = tv({ extend: tv(theme), ...(appConfig.ui?.collapsible || {}) })
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled'> {
export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled' | 'unmountOnHide'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -31,14 +31,14 @@ extendDevtoolsMeta({ example: 'CollapsibleExample' })
</script>
<script setup lang="ts">
import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, useForwardPropsEmits } from 'radix-vue'
import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
const props = defineProps<CollapsibleProps>()
const emits = defineEmits<CollapsibleEmits>()
const slots = defineSlots<CollapsibleSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled'), emits)
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultOpen', 'open', 'disabled', 'unmountOnHide'), emits)
// eslint-disable-next-line vue/no-dupe-keys
const ui = collapsible()

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits } from 'radix-vue'
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'
@@ -37,17 +37,17 @@ export interface CommandPaletteGroup<T> {
items?: T[]
/**
* Whether to filter group items with [useFuse](https://vueuse.org/integrations/useFuse).
* When `false`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
* @defaultValue true
* When `true`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
* @defaultValue false
*/
filter?: boolean
ignoreFilter?: boolean
/** Filter group items after the search happened. */
postFilter?: (searchTerm: string, items: T[]) => T[]
/** The icon displayed when an item is highlighted. */
highlightedIcon?: string
}
export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'selectedValue' | 'resetSearchTermOnBlur'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'highlightOnHover'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -102,7 +102,9 @@ export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'mult
ui?: PartialString<typeof commandPalette.slots>
}
export type CommandPaletteEmits<T> = ComboboxRootEmits<T>
export type CommandPaletteEmits<T> = ListboxRootEmits<T> & {
'update:open': [value: boolean]
}
type SlotProps<T> = (props: { item: T, index: number }) => any
@@ -120,7 +122,7 @@ extendDevtoolsMeta({ example: 'CommandPaletteExample', ignoreProps: ['groups'] }
<script setup lang="ts" generic="G extends CommandPaletteGroup<T>, T extends CommandPaletteItem">
import { computed } from 'vue'
import { ComboboxRoot, ComboboxInput, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxViewport, ComboboxGroup, ComboboxLabel, ComboboxItem, ComboboxItemIndicator, useForwardProps, useForwardPropsEmits } from 'radix-vue'
import { ListboxRoot, ListboxFilter, ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxItem, ListboxItemIndicator, useForwardProps, useForwardPropsEmits } from 'reka-ui'
import { defu } from 'defu'
import { reactivePick } from '@vueuse/core'
import { useFuse } from '@vueuse/integrations/useFuse'
@@ -145,9 +147,10 @@ const slots = defineSlots<CommandPaletteSlots<G, T>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const appConfig = useAppConfig()
const { t } = useLocale()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'selectedValue', 'resetSearchTermOnBlur'), emits)
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'highlightOnHover'), emits)
const inputProps = useForwardProps(reactivePick(props, 'loading', 'loadingIcon', 'placeholder'))
// eslint-disable-next-line vue/no-dupe-keys
@@ -169,7 +172,7 @@ const items = computed(() => props.groups?.filter((group) => {
return false
}
if (group.filter === false) {
if (group.ignoreFilter) {
return false
}
@@ -217,7 +220,7 @@ const groups = computed(() => {
return getGroupWithItems(group, items)
}).filter(group => !!group)
const nonFuseGroups = props.groups?.filter(group => group.filter === false && group.items?.length).map((group) => {
const nonFuseGroups = props.groups?.filter(group => group.ignoreFilter && group.items?.length).map((group) => {
return getGroupWithItems(group, group.items || [])
}) || []
@@ -230,8 +233,8 @@ const groups = computed(() => {
<!-- eslint-disable vue/no-v-html -->
<template>
<ComboboxRoot v-bind="rootProps" v-model:search-term="searchTerm" open :class="ui.root({ class: [props.class, props.ui?.root] })">
<ComboboxInput as-child>
<ListboxRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<ListboxFilter v-model="searchTerm" as-child>
<UInput
variant="none"
autofocus
@@ -256,72 +259,70 @@ const groups = computed(() => {
</slot>
</template>
</UInput>
</ComboboxInput>
</ListboxFilter>
<ComboboxPortal disabled>
<ComboboxContent :class="ui.content({ class: props.ui?.content })" :dismissable="false">
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? t('commandPalette.noMatch', { searchTerm }) : t('commandPalette.noData') }}
</slot>
</ComboboxEmpty>
<ListboxContent :class="ui.content({ class: props.ui?.content })">
<div v-if="groups?.length" :class="ui.viewport({ class: props.ui?.viewport })">
<ListboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<ListboxGroupLabel v-if="get(group, props.labelKey as string)" :class="ui.label({ class: props.ui?.label })">
{{ get(group, props.labelKey as string) }}
</ListboxGroupLabel>
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<ComboboxLabel v-if="get(group, props.labelKey as string)" :class="ui.label({ class: props.ui?.label })">
{{ get(group, props.labelKey as string) }}
</ComboboxLabel>
<ListboxItem
v-for="(item, index) in group.items"
:key="`group-${groupIndex}-${index}`"
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
:disabled="item.disabled"
:class="ui.item({ class: props.ui?.item, active: item.active })"
@select="item.onSelect"
>
<slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
<slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, loading: true })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, active: item.active })" />
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar, active: item.active })" />
<UChip
v-else-if="item.chip"
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip, active: item.active })"
/>
</slot>
<ComboboxItem
v-for="(item, index) in group.items"
:key="`group-${groupIndex}-${index}`"
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
:disabled="item.disabled"
:class="ui.item({ class: props.ui?.item, active: item.active })"
@select="item.onSelect"
>
<slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
<slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, loading: true })" />
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, active: item.active })" />
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar, active: item.active })" />
<UChip
v-else-if="item.chip"
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
inset
standalone
v-bind="item.chip"
:class="ui.itemLeadingChip({ class: props.ui?.itemLeadingChip, active: item.active })"
/>
<span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel, active: item.active })">
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase, active: item.active })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix, active: item.active })" v-html="item.suffixHtml || item.suffix" />
</slot>
</span>
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
<slot :name="item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`" :item="item" :index="index">
<span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: props.ui?.itemTrailingKbds })">
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((props.ui?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
</span>
<UIcon v-else-if="group.highlightedIcon" :name="group.highlightedIcon" :class="ui.itemTrailingHighlightedIcon({ class: props.ui?.itemTrailingHighlightedIcon })" />
</slot>
<span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel, active: item.active })">
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
<ListboxItemIndicator as-child>
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
</ListboxItemIndicator>
</span>
</slot>
</ListboxItem>
</ListboxGroup>
</div>
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase, active: item.active })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix, active: item.active })" v-html="item.suffixHtml || item.suffix" />
</slot>
</span>
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
<slot :name="item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`" :item="item" :index="index">
<span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: props.ui?.itemTrailingKbds })">
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((props.ui?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
</span>
<UIcon v-else-if="group.highlightedIcon" :name="group.highlightedIcon" :class="ui.itemTrailingHighlightedIcon({ class: props.ui?.itemTrailingHighlightedIcon })" />
</slot>
<ComboboxItemIndicator as-child>
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
</ComboboxItemIndicator>
</span>
</slot>
</ComboboxItem>
</ComboboxGroup>
</ComboboxViewport>
</ComboboxContent>
</ComboboxPortal>
</ComboboxRoot>
<div v-else :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? t('commandPalette.noMatch', { searchTerm }) : t('commandPalette.noData') }}
</slot>
</div>
</ListboxContent>
</ListboxRoot>
</template>

View File

@@ -26,7 +26,7 @@ extendDevtoolsMeta({ example: 'ContainerExample' })
</script>
<script setup lang="ts">
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
const props = defineProps<ContainerProps>()
defineSlots<ContainerSlots>()

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'radix-vue'
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/context-menu'
@@ -143,7 +143,7 @@ extendDevtoolsMeta({
<script setup lang="ts" generic="T extends ContextMenuItem">
import { computed, toRef } from 'vue'
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'radix-vue'
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { omit } from '../utils'
import UContextMenuContent from './ContextMenuContent.vue'

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { ContextMenuContentProps as RadixContextMenuContentProps, ContextMenuContentEmits as RadixContextMenuContentEmits } from 'radix-vue'
import type { ContextMenuContentProps as RekaContextMenuContentProps, ContextMenuContentEmits as RekaContextMenuContentEmits } from 'reka-ui'
import theme from '#build/ui/context-menu'
import type { KbdProps, AvatarProps, ContextMenuItem, ContextMenuSlots } from '../types'
const _contextMenu = tv(theme)()
interface ContextMenuContentProps<T> extends Omit<RadixContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
interface ContextMenuContentProps<T> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
items?: T[] | T[][]
portal?: boolean
sub?: boolean
@@ -18,13 +18,13 @@ interface ContextMenuContentProps<T> extends Omit<RadixContextMenuContentProps,
uiOverride?: any
}
interface ContextMenuContentEmits extends RadixContextMenuContentEmits {}
interface ContextMenuContentEmits extends RekaContextMenuContentEmits {}
</script>
<script setup lang="ts" generic="T extends ContextMenuItem">
import { computed } from 'vue'
import { ContextMenu } from 'radix-vue/namespaced'
import { useForwardPropsEmits } from 'radix-vue'
import { ContextMenu } from 'reka-ui/namespaced'
import { useForwardPropsEmits } from 'reka-ui'
import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { omit, get } from '../utils'

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { DrawerRootProps, DrawerRootEmits } from 'vaul-vue'
import type { DialogContentProps } from 'radix-vue'
import type { DialogContentProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/drawer'
@@ -58,7 +58,7 @@ extendDevtoolsMeta({ example: 'DrawerExample' })
<script setup lang="ts">
import { computed, toRef } from 'vue'
import { useForwardPropsEmits } from 'radix-vue'
import { useForwardPropsEmits } from 'reka-ui'
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription } from 'vaul-vue'
import { reactivePick } from '@vueuse/core'

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'radix-vue'
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/dropdown-menu'
@@ -140,7 +140,7 @@ extendDevtoolsMeta({
<script setup lang="ts" generic="T extends DropdownMenuItem">
import { computed, toRef } from 'vue'
import { defu } from 'defu'
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'radix-vue'
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { omit } from '../utils'
import UDropdownMenuContent from './DropdownMenuContent.vue'

View File

@@ -1,13 +1,13 @@
<!-- eslint-disable vue/block-tag-newline -->
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { DropdownMenuContentProps as RadixDropdownMenuContentProps, DropdownMenuContentEmits as RadixDropdownMenuContentEmits } from 'radix-vue'
import type { DropdownMenuContentProps as RekaDropdownMenuContentProps, DropdownMenuContentEmits as RekaDropdownMenuContentEmits } from 'reka-ui'
import theme from '#build/ui/dropdown-menu'
import type { KbdProps, AvatarProps, DropdownMenuItem, DropdownMenuSlots } from '../types'
const _dropdownMenu = tv(theme)()
interface DropdownMenuContentProps<T> extends Omit<RadixDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
interface DropdownMenuContentProps<T> extends Omit<RekaDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
items?: T[] | T[][]
portal?: boolean
sub?: boolean
@@ -19,7 +19,7 @@ interface DropdownMenuContentProps<T> extends Omit<RadixDropdownMenuContentProps
uiOverride?: any
}
interface DropdownMenuContentEmits extends RadixDropdownMenuContentEmits {}
interface DropdownMenuContentEmits extends RekaDropdownMenuContentEmits {}
type DropdownMenuContentSlots<T extends { slot?: string }> = Omit<DropdownMenuSlots<T>, 'default'> & {
default(props?: {}): any
@@ -29,8 +29,8 @@ type DropdownMenuContentSlots<T extends { slot?: string }> = Omit<DropdownMenuSl
<script setup lang="ts" generic="T extends DropdownMenuItem">
import { computed } from 'vue'
import { DropdownMenu } from 'radix-vue/namespaced'
import { useForwardPropsEmits } from 'radix-vue'
import { DropdownMenu } from 'reka-ui/namespaced'
import { useForwardPropsEmits } from 'reka-ui'
import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { omit, get } from '../utils'

View File

@@ -12,6 +12,11 @@ const formField = tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })
type FormFieldVariants = VariantProps<typeof formField>
export interface FormFieldProps {
/**
* The element or component this component should render as.
* @defaultValue 'div'
*/
as?: any
/** The name of the FormField. Also used to match form errors. */
name?: string
/** A regular expression to match form error names. */
@@ -43,7 +48,7 @@ extendDevtoolsMeta({ example: 'FormFieldExample', defaultProps: { label: 'Label'
<script setup lang="ts">
import { computed, ref, inject, provide, type Ref, useId } from 'vue'
import { Label } from 'radix-vue'
import { Primitive, Label } from 'reka-ui'
import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
import type { FormError, FormFieldInjectedOptions } from '../types/form'
@@ -74,7 +79,7 @@ provide(formFieldInjectionKey, computed(() => ({
</script>
<template>
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
<div v-if="label || !!slots.label" :class="ui.labelWrapper({ class: props.ui?.labelWrapper })">
<Label :for="id" :class="ui.label({ class: props.ui?.label })">
@@ -110,5 +115,5 @@ provide(formFieldInjectionKey, computed(() => ({
</slot>
</p>
</div>
</div>
</Primitive>
</template>

View File

@@ -13,7 +13,7 @@ export interface IconProps {
</script>
<script setup lang="ts">
import { useForwardProps } from 'radix-vue'
import { useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
const props = defineProps<IconProps>()

View File

@@ -15,6 +15,11 @@ const input = tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })
type InputVariants = VariantProps<typeof input>
export interface InputProps extends UseComponentIconsProps {
/**
* The element or component this component should render as.
* @defaultValue 'div'
*/
as?: any
id?: string
name?: string
type?: InputHTMLAttributes['type']
@@ -49,6 +54,7 @@ export interface InputSlots {
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Primitive } from 'reka-ui'
import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
@@ -147,7 +153,7 @@ onMounted(() => {
</script>
<template>
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<input
:id="id"
ref="inputRef"
@@ -179,5 +185,5 @@ onMounted(() => {
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</span>
</div>
</Primitive>
</template>

View File

@@ -1,14 +1,14 @@
<script lang="ts">
import type { InputHTMLAttributes } from 'vue'
import { tv, type VariantProps } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps } from 'radix-vue'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } 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 { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { AvatarProps, ChipProps, InputProps } from '../types'
import type { AcceptableValue, ArrayOrWrapped, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
const appConfig = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
@@ -30,7 +30,7 @@ export interface InputMenuItem {
type InputMenuVariants = VariantProps<typeof inputMenu>
export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue> = MaybeArrayOfArray<InputMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'defaultValue' | 'selectedValue' | 'open' | 'defaultOpen' | 'searchTerm' | 'disabled' | 'name' | 'resetSearchTermOnBlur'>, UseComponentIconsProps {
export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -77,13 +77,6 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
* @defaultValue true
*/
portal?: boolean
/**
* Whether to filter items or not, can be an array of fields to filter. Defaults to `[labelKey]`.
* When `false`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
* `['label']`{lang="ts-type"}
* @defaultValue true
*/
filter?: boolean | string[]
/**
* When `items` is an array of objects, select the field to use as the value instead of the object itself.
* @defaultValue undefined
@@ -95,33 +88,45 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
*/
labelKey?: V
items?: I
/** The value of the InputMenu when initially rendered. Use when you do not need to control the state of the InputMenu. */
defaultValue?: SelectModelValue<T, V, M>
/** The controlled value of the InputMenu. Can be binded-with with `v-model`. */
modelValue?: SelectModelValue<T, V, M>
/** Whether multiple options can be selected or not. */
multiple?: M & boolean
/** Highlight the ring color like a focus state. */
highlight?: boolean
/**
* Determines if custom user input that does not exist in options can be added.
* @defaultValue false
*/
createItem?: boolean | 'always' | { placement?: 'top' | 'bottom', when?: 'empty' | 'always' }
createItem?: boolean | 'always' | { position?: 'top' | 'bottom', when?: 'empty' | 'always' }
/**
* Fields to filter items by.
* @defaultValue [labelKey]
*/
filterFields?: string[]
/**
* When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
* @defaultValue false
*/
ignoreFilter?: boolean
class?: any
ui?: PartialString<typeof inputMenu.slots>
/** The controlled value of the Combobox. Can be binded-with with `v-model`. */
modelValue?: SelectModelValue<T, V, M>
/** Whether multiple options can be selected or not. */
multiple?: M & boolean
}
export type InputMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
change: [payload: Event]
blur: [payload: FocusEvent]
focus: [payload: FocusEvent]
create: [payload: Event, item: T]
create: [item: string]
} & SelectModelValueEmits<T, V, M>
type SlotProps<T> = (props: { item: T, index: number }) => any
export interface InputMenuSlots<T> {
'leading'(props: { modelValue: T, open: boolean, ui: any }): any
'trailing'(props: { modelValue: T, open: boolean, ui: any }): any
export interface InputMenuSlots<T, M extends boolean> {
'leading'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
'trailing'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
'empty'(props: { searchTerm?: string }): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
@@ -129,24 +134,23 @@ export interface InputMenuSlots<T> {
'item-trailing': SlotProps<T>
'tags-item-text': SlotProps<T>
'tags-item-delete': SlotProps<T>
'create-item-label'(props: { item: T }): any
'create-item-label'(props: { item: string }): any
}
extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
</script>
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue> = MaybeArrayOfArray<InputMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
import { computed, ref, toRef, onMounted, toRaw } from 'vue'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits } from 'radix-vue'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
import { defu } from 'defu'
import { isEqual } from 'ohash'
import { reactivePick, createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useButtonGroup } from '../composables/useButtonGroup'
import { useComponentIcons } from '../composables/useComponentIcons'
import { useFormField } from '../composables/useFormField'
import { useLocale } from '../composables/useLocale'
import { get, escapeRegExp } from '../utils'
import { get, compare } from '../utils'
import UIcon from './Icon.vue'
import UAvatar from './Avatar.vue'
import UChip from './Chip.vue'
@@ -157,17 +161,18 @@ const props = withDefaults(defineProps<InputMenuProps<T, I, V, M>>(), {
type: 'text',
autofocusDelay: 0,
portal: true,
filter: true,
labelKey: 'label' as never
})
const emits = defineEmits<InputMenuEmits<T, V, M>>()
const slots = defineSlots<InputMenuSlots<T>>()
const slots = defineSlots<InputMenuSlots<T, M>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const appConfig = useAppConfig()
const { t } = useLocale()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
const appConfig = useAppConfig()
const { contains } = useFilter({ sensitivity: 'base' })
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur', 'highlightOnHover', 'ignoreFilter'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
@@ -175,10 +180,10 @@ const { emitFormBlur, emitFormChange, emitFormInput, size: formGroupSize, color,
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
const ui = computed(() => inputMenu({
color: color.value,
variant: props.variant,
@@ -196,69 +201,49 @@ function displayValue(value: T): string {
return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
}
const item = items.value.find(item => isEqual(get(item as Record<string, any>, props.valueKey as string), value))
const item = items.value.find(item => compare(typeof item === 'object' ? get(item, props.valueKey as string) : item, value))
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
}
function filterFunction(
inputItems: ArrayOrWrapped<T> = items.value as ArrayOrWrapped<T>,
filterSearchTerm: string = searchTerm.value,
comparator = (item: any, term: string) => String(item).search(new RegExp(term, 'i')) !== -1
): ArrayOrWrapped<T> {
if (props.filter === false) {
return inputItems
}
const fields = Array.isArray(props.filter) ? props.filter : [props.labelKey]
const escapedSearchTerm = escapeRegExp(filterSearchTerm ?? '')
return inputItems.filter((item) => {
if (typeof item !== 'object') {
return comparator(item, escapedSearchTerm)
}
return fields.some((field) => {
const child = get(item, field as string)
return child !== null && child !== undefined && comparator(child, escapedSearchTerm)
})
}) as ArrayOrWrapped<T>
}
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as InputMenuItem[][] : [])
// eslint-disable-next-line vue/no-dupe-keys
const items = computed(() => groups.value.flatMap(group => group) as T[])
const creatable = computed(() => {
if (!props.createItem) {
const filteredGroups = computed(() => {
if (props.ignoreFilter || !searchTerm.value) {
return groups.value
}
const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
return groups.value.map(items => items.filter((item) => {
if (typeof item !== 'object') {
return contains(item, searchTerm.value)
}
if (item.type && ['label', 'separator'].includes(item.type)) {
return true
}
return fields.some(field => contains(get(item, field), searchTerm.value))
})).filter(group => group.filter(item => !item.type || !['label', 'separator'].includes(item.type)).length > 0)
})
const filteredItems = computed(() => filteredGroups.value.flatMap(group => group) as T[])
const createItem = computed(() => {
if (!props.createItem || !searchTerm.value) {
return false
}
const isModelValueCustom = props.modelValue && filterFunction((props.multiple && Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue]) as ArrayOrWrapped<T>, searchTerm.value, (item, term) => String(item) === term).length === 1
if (isModelValueCustom) {
return false
}
const filteredItems = filterFunction()
const newItem = searchTerm.value && {
item: props.valueKey ? { [props.valueKey]: searchTerm.value, [props.labelKey ?? 'label']: searchTerm.value } : searchTerm.value,
position: ((typeof props.createItem === 'object' && props.createItem.placement) || 'bottom') as 'top' | 'bottom'
}
const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as T : searchTerm.value
if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
return (filteredItems.length === 1 && filterFunction(filteredItems, searchTerm.value, (item, term) => String(item) === term).length === 1) ? false : newItem
return !filteredItems.value.find(item => compare(item, newItem, props.valueKey))
}
return filteredItems.length > 0 ? false : newItem
return !filteredItems.value.length
})
const rootItems = computed(() => [
...(creatable.value && creatable.value.position === 'top' ? [creatable.value.item] : []),
...filterFunction(),
...(creatable.value && creatable.value.position === 'bottom' ? [creatable.value.item] : [])
] as ArrayOrWrapped<T>)
const createItemPosition = computed(() => typeof props.createItem === 'object' ? props.createItem.position : 'bottom')
const inputRef = ref<InstanceType<typeof ComboboxInput> | null>(null)
@@ -310,17 +295,18 @@ defineExpose({
})
</script>
<!-- eslint-disable vue/no-template-shadow -->
<template>
<DefineCreateItemTemplate>
<ComboboxGroup v-if="creatable" :class="ui.group({ class: props.ui?.group })">
<ComboboxGroup :class="ui.group({ class: props.ui?.group })">
<ComboboxItem
:class="ui.item({ class: props.ui?.item })"
:value="valueKey && typeof creatable.item === 'object' ? get(creatable.item, props.valueKey as string) : creatable.item"
@select="e => emits('create', e, (creatable as any).item as T)"
:value="searchTerm"
@select.prevent="emits('create', searchTerm)"
>
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<slot name="create-item-label" :item="(creatable.item as T)">
{{ t('inputMenu.create', { label: typeof creatable.item === 'object' ? get(creatable.item, props.labelKey as string) : creatable.item }) }}
<slot name="create-item-label" :item="searchTerm">
{{ t('inputMenu.create', { label: searchTerm }) }}
</slot>
</span>
</ComboboxItem>
@@ -331,13 +317,11 @@ defineExpose({
:id="id"
v-slot="{ modelValue, open }"
v-bind="rootProps"
v-model:search-term="searchTerm"
:name="name"
:disabled="disabled"
:display-value="displayValue"
:filter-function="() => rootItems"
:class="ui.root({ class: [props.class, props.ui?.root] })"
:as-child="!!multiple"
ignore-filter
@update:model-value="onUpdate"
@update:open="onUpdateOpen"
@keydown.enter="$event.preventDefault()"
@@ -345,7 +329,7 @@ defineExpose({
<ComboboxAnchor :as-child="!multiple" :class="ui.base({ class: props.ui?.base })">
<TagsInputRoot
v-if="multiple"
v-slot="{ modelValue: tags }: { modelValue: AcceptableValue[] }"
v-slot="{ modelValue: tags }"
:model-value="(modelValue as string[])"
:disabled="disabled"
delimiter=""
@@ -367,7 +351,7 @@ defineExpose({
</TagsInputItemDelete>
</TagsInputItem>
<ComboboxInput as-child>
<ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
<TagsInputInput
ref="inputRef"
v-bind="$attrs"
@@ -382,24 +366,25 @@ defineExpose({
<ComboboxInput
v-else
ref="inputRef"
v-model="searchTerm"
:display-value="displayValue"
v-bind="$attrs"
:type="type"
:placeholder="placeholder"
:required="required"
:class="ui.base({ class: props.ui?.base })"
@blur="onBlur"
@focus="onFocus"
/>
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
<slot name="leading" :model-value="(modelValue as T)" :open="open" :ui="ui">
<slot name="leading" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
<UAvatar v-else-if="!!avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
</slot>
</span>
<ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
<slot name="trailing" :model-value="(modelValue as T)" :open="open" :ui="ui">
<slot name="trailing" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
</slot>
</ComboboxTrigger>
@@ -414,9 +399,9 @@ defineExpose({
</ComboboxEmpty>
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
<ReuseCreateItemTemplate v-if="creatable && creatable.position === 'top'" />
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
{{ get(item, props.labelKey as string) }}
@@ -463,7 +448,7 @@ defineExpose({
</template>
</ComboboxGroup>
<ReuseCreateItemTemplate v-if="creatable && creatable.position === 'bottom'" />
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
</ComboboxViewport>
<ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { NumberFieldRootProps } from 'radix-vue'
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'
@@ -75,7 +75,7 @@ export interface InputNumberSlots {
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'
import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'radix-vue'
import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useFormField } from '../composables/useFormField'
import { useLocale } from '../composables/useLocale'

View File

@@ -31,7 +31,7 @@ extendDevtoolsMeta({ defaultProps: { value: 'K' } })
</script>
<script setup lang="ts">
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
import { useKbd } from '../composables/useKbd'
const props = withDefaults(defineProps<KbdProps>(), {

View File

@@ -95,7 +95,7 @@ extendDevtoolsMeta({ example: 'LinkExample' })
<script setup lang="ts">
import { computed } from 'vue'
import { isEqual, diff } from 'ohash'
import { useForwardProps } from 'radix-vue'
import { useForwardProps } from 'reka-ui'
import { reactiveOmit } from '@vueuse/core'
import { useRoute } from '#imports'
import ULinkBase from './LinkBase.vue'

View File

@@ -13,7 +13,7 @@ export interface LinkBaseProps {
</script>
<script setup lang="ts">
import { Primitive } from 'radix-vue'
import { Primitive } from 'reka-ui'
const props = withDefaults(defineProps<LinkBaseProps>(), {
as: 'button',

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'radix-vue'
import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/modal'
@@ -74,7 +74,7 @@ extendDevtoolsMeta({ example: 'ModalExample' })
<script setup lang="ts">
import { computed, toRef } from 'vue'
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'radix-vue'
import { DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'
@@ -90,6 +90,9 @@ const props = withDefaults(defineProps<ModalProps>(), {
const emits = defineEmits<ModalEmits>()
const slots = defineSlots<ModalSlots>()
const { t } = useLocale()
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content)
const contentEvents = computed(() => {
@@ -110,9 +113,6 @@ const contentEvents = computed(() => {
}
})
const appConfig = useAppConfig()
const { t } = useLocale()
const ui = computed(() => modal({
transition: props.transition,
fullscreen: props.fullscreen

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, CollapsibleRootProps } from 'radix-vue'
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, CollapsibleRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/navigation-menu'
@@ -31,7 +31,7 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'raw' | 'custom'>, P
type NavigationMenuVariants = VariantProps<typeof navigationMenu>
export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'modelValue' | 'skipDelayDuration'> {
export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -122,8 +122,8 @@ extendDevtoolsMeta({
</script>
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<NavigationMenuItem>">
import { computed, reactive, toRef } from 'vue'
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'radix-vue'
import { computed, toRef } from 'vue'
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'reka-ui'
import { createReusableTemplate } from '@vueuse/core'
import { get } from '../utils'
import { pickLinkProps } from '../utils/link'
@@ -137,19 +137,24 @@ import UCollapsible from './Collapsible.vue'
const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
orientation: 'horizontal',
delayDuration: 0,
labelKey: 'label'
labelKey: 'label',
unmountOnHide: true
})
const emits = defineEmits<NavigationMenuEmits>()
const slots = defineSlots<NavigationMenuSlots<T>>()
const rootProps = useForwardPropsEmits(reactive({
const rootProps = useForwardPropsEmits(computed(() => ({
as: props.as,
modelValue: props.modelValue,
defaultValue: props.defaultValue,
delayDuration: props.delayDuration,
skipDelayDuration: props.skipDelayDuration,
orientation: props.orientation
}), emits)
orientation: props.orientation,
disableClickTrigger: props.disableClickTrigger,
disableHoverTrigger: props.disableHoverTrigger,
disablePointerLeaveClose: props.disablePointerLeaveClose,
unmountOnHide: props.unmountOnHide
})), emits)
const contentProps = toRef(() => props.content)
@@ -199,7 +204,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
</slot>
</DefineItemTemplate>
<NavigationMenuRoot v-bind="rootProps" :data-orientation="orientation" :class="ui.root({ class: [props.class, props.ui?.root] })">
<NavigationMenuRoot v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })">
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
<NavigationMenuList :class="ui.list({ class: props.ui?.list })">
<component
@@ -209,6 +214,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
as="li"
:value="item.value || String(index)"
:default-open="item.defaultOpen"
:unmount-on-hide="(item.children?.length && orientation === 'vertical') ? unmountOnHide : undefined"
:open="item.open"
:class="ui.item({ class: props.ui?.item })"
>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { tv } from 'tailwind-variants'
import type { PaginationRootProps, PaginationRootEmits } from 'radix-vue'
import type { PaginationRootProps, PaginationRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import type { RouteLocationRaw } from '#vue-router'
import _appConfig from '#build/app.config'
@@ -12,7 +12,7 @@ const appConfig = _appConfig as AppConfig & { ui: { pagination: Partial<typeof t
const pagination = tv({ extend: tv(theme), ...(appConfig.ui?.pagination || {}) })
export interface PaginationProps extends Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'> {
export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defaultPage' | 'disabled' | 'itemsPerPage' | 'page' | 'showEdges' | 'siblingCount' | 'total'>> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -104,7 +104,7 @@ extendDevtoolsMeta({ defaultProps: { total: 50 } })
<script setup lang="ts">
import { computed } from 'vue'
import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, PaginationPrev, PaginationEllipsis, PaginationNext, PaginationLast, useForwardPropsEmits } from 'radix-vue'
import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, PaginationPrev, PaginationEllipsis, PaginationNext, PaginationLast, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useLocale } from '../composables/useLocale'

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