Compare commits

..

13 Commits

Author SHA1 Message Date
Romain Hamel
3a56de3be0 fix(form): provide typings for $el 2024-11-08 12:18:30 +01:00
Benjamin Canac
503f701c7e fix(InputMenu/SelectMenu): multiple not working with generic boolean casting
Resolves #2541
2024-11-08 10:59:13 +01:00
Benjamin Canac
d9822db6e8 chore(InputMenu/Select/SelectMenu): consistent types 2024-11-08 10:27:05 +01:00
Benjamin Canac
0ceafe1d54 fix(InputMenu/SelectMenu): look in items only with value-attribute
Resolves #2464
2024-11-08 10:26:44 +01:00
Benjamin Canac
f943f88fcc fix(InputMenu/SelectMenu): use isEqual from ohash 2024-11-08 10:19:02 +01:00
Benjamin Canac
e831813aa3 chore(git): ignore vue playground files 2024-11-07 22:35:50 +01:00
Benjamin Canac
37a359701f fix(module): remove fast-deep-equal in favor of custom isEqual 2024-11-07 21:56:56 +01:00
Benjamin Canac
557e0c92a4 docs(deps): revert @nuxt/content to 3.0.0-alpha.5 2024-11-07 21:54:42 +01:00
Benjamin Canac
7f6db45f1e feat(css): add --ui-bg-muted / --ui-border-muted variables
Those are used in Nuxt UI Pro
2024-11-07 21:43:49 +01:00
Benjamin Canac
a64a7104c5 docs(app): update links 2024-11-07 17:46:44 +01:00
Benjamin Canac
40fc8f3718 docs(app): remove icons from breadcrumb 2024-11-07 16:33:19 +01:00
Benjamin Canac
8059d540e3 docs(app): improve links 2024-11-07 16:25:01 +01:00
Benjamin Canac
42fce998e4 docs: remove markdown from descriptions 2024-11-07 15:21:38 +01:00
23 changed files with 134 additions and 106 deletions

2
.gitignore vendored
View File

@@ -28,3 +28,5 @@ logs
playground-vue/auto-imports.d.ts
playground-vue/components.d.ts
playground-vue/tsconfig.app.tsbuildinfo
playground-vue/tsconfig.node.tsbuildinfo

View File

@@ -20,33 +20,31 @@ const searchTerm = ref('')
// useTrackEvent('Search', { props: { query: `${query} - ${searchTerm.value?.commandPaletteRef.results.length} results` } })
// }, 500))
const links = computed(() => {
return [{
label: 'Docs',
icon: 'i-lucide-book-open',
to: '/getting-started',
active: route.path.startsWith('/getting-started') || route.path.startsWith('/components')
}, ...(navigation.value?.find(item => item.path === '/pro')
? [{
label: 'Pro',
icon: 'i-lucide-layers-3',
to: '/pro',
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
}, {
label: 'Pricing',
icon: 'i-lucide-credit-card',
to: '/pro/pricing'
}, {
label: 'Templates',
icon: 'i-lucide-monitor',
to: '/pro/templates'
}]
: []), {
label: 'Releases',
icon: 'i-lucide-rocket',
to: '/releases'
}].filter(Boolean)
})
const links = computed(() => [{
label: 'Docs',
icon: 'i-lucide-square-play',
to: '/getting-started',
active: route.path.startsWith('/getting-started')
}, {
label: 'Components',
icon: 'i-lucide-square-code',
to: '/components',
active: route.path.startsWith('/components')
}, {
label: 'Roadmap',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
label: 'Figma',
icon: 'i-lucide-figma',
to: 'https://www.figma.com/community/file/1288455405058138934',
target: '_blank'
}, {
label: 'Releases',
icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases',
target: '_blank'
}].filter(Boolean))
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)

View File

@@ -2,7 +2,7 @@
import type { ContentNavigationItem } from '@nuxt/content'
import type { NavigationMenuItem } from '@nuxt/ui'
defineProps<{
const props = defineProps<{
links: NavigationMenuItem[]
}>()
@@ -10,7 +10,7 @@ const config = useRuntimeConfig().public
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
// const items = computed(() => props.links.map(({ icon, ...link }) => link))
const items = computed(() => props.links.map(({ icon, ...link }) => link))
defineShortcuts({
meta_g: () => {
@@ -29,7 +29,7 @@ defineShortcuts({
</NuxtLink>
</template>
<!-- <UNavigationMenu :items="items" variant="link" /> -->
<UNavigationMenu :items="items" variant="link" />
<template #right>
<ThemePicker />
@@ -51,9 +51,9 @@ defineShortcuts({
</template>
<template #content>
<!-- <UNavigationMenu orientation="vertical" :items="items" class="-ml-2.5" />
<UNavigationMenu orientation="vertical" :items="links" class="-ml-2.5" />
<USeparator type="dashed" class="my-4" /> -->
<USeparator type="dashed" class="my-4" />
<UContentNavigation :navigation="navigation" highlight />
</template>

View File

@@ -13,33 +13,31 @@ const colorMode = useColorMode()
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('content'))
const { data: files } = await useAsyncData('files', () => queryCollectionSearchSections('content', { ignoredTags: ['style'] }))
const links = computed(() => {
return [{
label: 'Docs',
icon: 'i-lucide-book-open',
to: '/getting-started',
active: route.path.startsWith('/getting-started') || route.path.startsWith('/components')
}, ...(navigation.value?.find(item => item.path === '/pro')
? [{
label: 'Pro',
icon: 'i-lucide-layers-3',
to: '/pro',
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
}, {
label: 'Pricing',
icon: 'i-lucide-credit-card',
to: '/pro/pricing'
}, {
label: 'Templates',
icon: 'i-lucide-monitor',
to: '/pro/templates'
}]
: []), {
label: 'Releases',
icon: 'i-lucide-rocket',
to: '/releases'
}].filter(Boolean)
})
const links = computed(() => [{
label: 'Docs',
icon: 'i-lucide-square-play',
to: '/getting-started',
active: route.path.startsWith('/getting-started')
}, {
label: 'Components',
icon: 'i-lucide-square-code',
to: '/components',
active: route.path.startsWith('/components')
}, {
label: 'Roadmap',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
label: 'Figma',
icon: 'i-lucide-figma',
to: 'https://www.figma.com/community/file/1288455405058138934',
target: '_blank'
}, {
label: 'Releases',
icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases',
target: '_blank'
}].filter(Boolean))
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)

View File

@@ -19,14 +19,14 @@ const { data: surround } = await useAsyncData(`${route.path}-surround`, () => qu
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value)))
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value)).map(({ icon, ...link }) => link))
useSeoMeta({
titleTemplate: '%s - Nuxt UI v3',
title: typeof page.value.navigation === 'object' ? page.value.navigation.title : page.value.title,
ogTitle: `${typeof page.value.navigation === 'object' ? page.value.navigation.title : page.value.title} - Nuxt UI v3`,
description: page.value.seo?.description || page.value.description,
ogDescription: page.value.seo?.description || page.value.description
description: page.value.description,
ogDescription: page.value.description
})
defineOgImageComponent('Docs', {
@@ -34,7 +34,7 @@ defineOgImageComponent('Docs', {
})
const communityLinks = computed(() => [{
icon: 'i-lucide-square-pen',
icon: 'i-lucide-file-pen',
label: 'Edit this page',
to: `https://github.com/nuxt/ui/edit/v3/docs/content/${page?.value?.stem}.md`,
target: '_blank'
@@ -43,10 +43,6 @@ const communityLinks = computed(() => [{
label: 'Star on GitHub',
to: 'https://github.com/nuxt/ui',
target: '_blank'
}, {
label: 'Roadmap',
icon: 'i-lucide-map',
to: '/roadmap'
}])
// const resourcesLinks = [{

View File

@@ -1 +1,2 @@
title: Getting Started
icon: i-lucide-square-play

View File

@@ -254,6 +254,8 @@ Nuxt UI provides a comprehensive set of design tokens for the `neutral` color pa
/* Main background color */
--ui-bg: var(--color-white);
/* Subtle background */
--ui-bg-muted: var(--ui-color-neutral-50);
/* Slightly elevated background */
--ui-bg-elevated: var(--ui-color-neutral-100);
/* More prominent background */
@@ -263,6 +265,8 @@ Nuxt UI provides a comprehensive set of design tokens for the `neutral` color pa
/* Default border color */
--ui-border: var(--ui-color-neutral-200);
/* Subtle border */
--ui-border-muted: var(--ui-color-neutral-200);
/* More prominent border */
--ui-border-accented: var(--ui-color-neutral-300);
/* Inverted border color */
@@ -285,6 +289,8 @@ Nuxt UI provides a comprehensive set of design tokens for the `neutral` color pa
/* Main background color */
--ui-bg: var(--ui-color-neutral-900);
/* Subtle background */
--ui-bg-muted: var(--ui-color-neutral-800);
/* Slightly elevated background */
--ui-bg-elevated: var(--ui-color-neutral-800);
/* More prominent background */
@@ -294,6 +300,8 @@ Nuxt UI provides a comprehensive set of design tokens for the `neutral` color pa
/* Default border color */
--ui-border: var(--ui-color-neutral-800);
/* Subtle border */
--ui-border-muted: var(--ui-color-neutral-700);
/* More prominent border */
--ui-border-accented: var(--ui-color-neutral-700);
/* Inverted border color */

View File

@@ -1,8 +1,11 @@
---
title: Icons
description: 'Nuxt UI integrates with `@nuxt/icon` to access over 200,000+ icons from [Iconify](https://iconify.design/).'
seo.description: 'Nuxt UI integrates with `@nuxt/icon` to access over 200,000+ icons from Iconify.'
description: 'Nuxt UI integrates with Nuxt Icon to access over 200,000+ icons from Iconify.'
links:
- label: 'Iconify'
to: https://iconify.design/
target: _blank
icon: i-simple-icons-iconify
- label: 'nuxt/icon'
to: https://github.com/nuxt/icon
target: _blank

View File

@@ -1,6 +1,6 @@
---
title: Fonts
description: 'Nuxt UI integrates with `@nuxt/fonts` to provide plug-and-play font optimization.'
description: 'Nuxt UI integrates with Nuxt Fonts to provide plug-and-play font optimization.'
links:
- label: 'nuxt/fonts'
to: https://github.com/nuxt/fonts

View File

@@ -1,6 +1,6 @@
---
title: Color Mode
description: 'Nuxt UI integrates with `@nuxtjs/color-mode` to allow for easy switching between light and dark themes.'
description: 'Nuxt UI integrates with Nuxt Color Mode to allow for easy switching between light and dark themes.'
links:
- label: 'nuxtjs/color-mode'
to: https://github.com/nuxt-modules/color-mode
@@ -10,7 +10,7 @@ links:
## Usage
Nuxt UI automatically registers the `@nuxtjs/color-mode` module for you, so there's no additional setup required.
Nuxt UI automatically registers the [`@nuxtjs/color-mode`](https://github.com/nuxt-modules/color-mode) module for you, so there's no additional setup required.
You can disable dark mode by setting the `preference` to `light` instead of `system` in your `nuxt.config.ts`.

View File

@@ -0,0 +1,2 @@
title: Composables
icon: i-lucide-square-function

View File

@@ -0,0 +1,2 @@
title: Components
icon: i-lucide-square-code

View File

@@ -1,9 +1,10 @@
---
title: CommandPalette
description: A command palette with full-text search powered by [Fuse.js](https://fusejs.io/) for efficient fuzzy matching.
seo:
description: A command palette with full-text search powered by Fuse.js for efficient fuzzy matching.
description: A command palette with full-text search powered by Fuse.js for efficient fuzzy matching.
links:
- label: Fuse.js
to: https://fusejs.io/
target: _blank
- label: Combobox
icon: i-custom-radix-vue
to: https://www.radix-vue.com/components/combobox.html

View File

@@ -6,7 +6,7 @@
"@iconify-json/lucide": "^1.2.12",
"@iconify-json/simple-icons": "^1.2.11",
"@iconify-json/vscode-icons": "^1.2.2",
"@nuxt/content": "3.0.0-alpha.6",
"@nuxt/content": "3.0.0-alpha.5",
"@nuxt/image": "^1.8.1",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@62862c8",

View File

@@ -89,7 +89,6 @@
"embla-carousel-fade": "^8.3.1",
"embla-carousel-vue": "^8.3.1",
"embla-carousel-wheel-gestures": "^8.0.1",
"fast-deep-equal": "^3.1.3",
"fuse.js": "^7.0.0",
"get-port-please": "^3.1.2",
"knitwork": "^1.1.0",

View File

@@ -30,6 +30,6 @@ export default defineConfig({
],
optimizeDeps: {
// prevents reloading page when navigating between components
include: ['radix-vue/namespaced', 'vaul-vue', 'fast-deep-equal', '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: ['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']
}
})

20
pnpm-lock.yaml generated
View File

@@ -79,9 +79,6 @@ importers:
embla-carousel-wheel-gestures:
specifier: ^8.0.1
version: 8.0.1(embla-carousel@8.3.1)
fast-deep-equal:
specifier: ^3.1.3
version: 3.1.3
fuse.js:
specifier: ^7.0.0
version: 7.0.0
@@ -243,8 +240,8 @@ importers:
specifier: ^1.2.2
version: 1.2.2
'@nuxt/content':
specifier: 3.0.0-alpha.6
version: 3.0.0-alpha.6(magicast@0.3.5)(pg@8.13.1)(rollup@4.24.4)
specifier: 3.0.0-alpha.5
version: 3.0.0-alpha.5(magicast@0.3.5)(pg@8.13.1)(rollup@4.24.4)
'@nuxt/image':
specifier: ^1.8.1
version: 1.8.1(ioredis@5.4.1)(magicast@0.3.5)(rollup@4.24.4)
@@ -1338,8 +1335,8 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@nuxt/content@3.0.0-alpha.6':
resolution: {integrity: sha512-vxn1iMa3YrrnYs3f9DNx9/R/AncK87kFbysNmjH634JBcLC1yCMWqCq4VvasdFx4ko3x5ip+9m9J3z3SjUaPZQ==}
'@nuxt/content@3.0.0-alpha.5':
resolution: {integrity: sha512-q1eNWF+nTVqDzdMZjwBmq2IQTGf2ycf1wcBTZg3jEBlPm0B4T8CP4p3lt3QNZUZpmihmdFbW3aggdZX07WnkGw==}
peerDependencies:
pg: '*'
@@ -5142,6 +5139,9 @@ packages:
pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
pako@2.1.0:
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -7738,13 +7738,12 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
'@nuxt/content@3.0.0-alpha.6(magicast@0.3.5)(pg@8.13.1)(rollup@4.24.4)':
'@nuxt/content@3.0.0-alpha.5(magicast@0.3.5)(pg@8.13.1)(rollup@4.24.4)':
dependencies:
'@nuxt/kit': 3.14.159(magicast@0.3.5)(rollup@4.24.4)
'@nuxtjs/mdc': 0.9.2(magicast@0.3.5)(rollup@4.24.4)
'@sqlite.org/sqlite-wasm': 3.47.0-build1
better-sqlite3: 11.5.0
c12: 2.0.1(magicast@0.3.5)
chokidar: 4.0.1
consola: 3.2.3
defu: 6.1.4
@@ -7762,6 +7761,7 @@ snapshots:
micromark-util-sanitize-uri: 2.0.0
micromatch: 4.0.8
ohash: 1.1.4
pako: 2.1.0
pathe: 1.1.2
pg: 8.13.1
remark-mdc: 3.2.1
@@ -12765,6 +12765,8 @@ snapshots:
pako@0.2.9: {}
pako@2.1.0: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0

View File

@@ -127,8 +127,6 @@ export default defineNuxtModule<ModuleOptions>({
addTemplates(options, nuxt, resolve)
nuxt.options.vite = defu(nuxt.options?.vite, { optimizeDeps: { include: ['fast-deep-equal'] } })
if (nuxt.options.dev && nuxt.options.devtools.enabled && options.devtools?.enabled) {
const templates = buildTemplates(options)
nuxt.options.vite = defu(nuxt.options?.vite, { plugins: [devtoolsMetaPlugin({ resolve, templates, options })] })

View File

@@ -196,7 +196,7 @@ provide(formOptionsInjectionKey, computed(() => ({
validateOnInputDelay: props.validateOnInputDelay
})))
defineExpose<Form<T>>({
defineExpose<{ $el: HTMLFormElement | HTMLDivElement } & Form<T>>({
validate: _validate,
errors,
@@ -230,7 +230,7 @@ defineExpose<Form<T>>({
},
disabled
})
} as { $el: HTMLFormElement | HTMLDivElement } & Form<T>)
</script>
<template>

View File

@@ -92,7 +92,7 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
* When `items` is an array of objects, select the field to use as the label.
* @defaultValue 'label'
*/
labelKey?: keyof T
labelKey?: V
items?: I
/** Highlight the ring color like a focus state. */
highlight?: boolean
@@ -131,7 +131,7 @@ extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3']
import { computed, ref, toRef, onMounted } 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 { defu } from 'defu'
import * as isEqual from 'fast-deep-equal'
import { isEqual } from 'ohash'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useButtonGroup } from '../composables/useButtonGroup'
@@ -149,7 +149,7 @@ const props = withDefaults(defineProps<InputMenuProps<T, I, V, M>>(), {
autofocusDelay: 0,
portal: true,
filter: () => ['label'],
labelKey: 'label' as keyof T
labelKey: 'label' as never
})
const emits = defineEmits<InputMenuEmits<T, V, M>>()
const slots = defineSlots<InputMenuSlots<T>>()
@@ -157,9 +157,11 @@ const slots = defineSlots<InputMenuSlots<T>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'resetSearchTermOnBlur'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, position: 'popper' }) as ComboboxContentProps)
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
// This is a hack due to generic boolean casting (see https://github.com/nuxt/ui/issues/2541)
const multiple = toRef(() => typeof props.multiple === 'string' ? true : props.multiple)
const { emitFormBlur, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
@@ -175,17 +177,21 @@ const ui = computed(() => inputMenu({
highlight: highlight.value,
leading: isLeading.value || !!props.avatar || !!slots.leading,
trailing: isTrailing.value || !!slots.trailing,
multiple: props.multiple,
multiple: multiple.value,
buttonGroup: orientation.value
}))
function displayValue(value: AcceptableValue): string {
const item = items.value.find(item => props.valueKey ? isEqual.default(get(item as Record<string, any>, props.valueKey as string), value) : isEqual.default(item, value))
function displayValue(value: T): string {
if (!props.valueKey) {
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))
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
}
function filterFunction(items: ArrayOrWrapped<AcceptableValue>, searchTerm: string): ArrayOrWrapped<AcceptableValue> {
function filterFunction(items: ArrayOrWrapped<T>, searchTerm: string): ArrayOrWrapped<T> {
if (props.filter === false) {
return items
}
@@ -265,6 +271,7 @@ defineExpose({
v-model:search-term="searchTerm"
:name="name"
:disabled="disabled"
:multiple="multiple"
:display-value="displayValue"
:filter-function="filterFunction"
:class="ui.root({ class: [props.class, props.ui?.root] })"

View File

@@ -70,7 +70,7 @@ export interface SelectProps<T extends MaybeArrayOfArrayItem<I>, I extends Maybe
* When `items` is an array of objects, select the field to use as the label.
* @defaultValue 'label'
*/
labelKey?: SelectItemKey<T>
labelKey?: V
items?: I
/** Highlight the ring color like a focus state. */
highlight?: boolean

View File

@@ -83,7 +83,7 @@ export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends M
* When `items` is an array of objects, select the field to use as the label.
* @defaultValue 'label'
*/
labelKey?: SelectItemKey<T>
labelKey?: V
items?: I
/** Highlight the ring color like a focus state. */
highlight?: boolean
@@ -121,7 +121,7 @@ extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3']
import { computed, toRef } from 'vue'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, useForwardPropsEmits } from 'radix-vue'
import { defu } from 'defu'
import * as isEqual from 'fast-deep-equal'
import { isEqual } from 'ohash'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useButtonGroup } from '../composables/useButtonGroup'
@@ -147,9 +147,11 @@ const slots = defineSlots<SelectMenuSlots<T>>()
const searchTerm = defineModel<string>('searchTerm', { default: '' })
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'resetSearchTermOnBlur'), emits)
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, position: 'popper' }) as ComboboxContentProps)
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
// This is a hack due to generic boolean casting (see https://github.com/nuxt/ui/issues/2541)
const multiple = toRef(() => typeof props.multiple === 'string' ? true : props.multiple)
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
@@ -169,11 +171,15 @@ const ui = computed(() => selectMenu({
}))
function displayValue(value: T | T[]): string {
if (props.multiple && Array.isArray(value)) {
return value.map(v => displayValue(v)).join(', ')
if (multiple.value && Array.isArray(value)) {
return value.map(v => displayValue(v)).filter(Boolean).join(', ')
}
const item = items.value.find(item => props.valueKey ? isEqual.default(get(item as Record<string, any>, props.valueKey as string), value) : isEqual.default(item, value))
if (!props.valueKey) {
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))
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
}
@@ -232,6 +238,7 @@ function onUpdateOpen(value: boolean) {
as-child
:name="name"
:disabled="disabled"
:multiple="multiple"
:display-value="() => searchTerm"
:filter-function="filterFunction"
@update:model-value="onUpdate"

View File

@@ -21,11 +21,13 @@
--ui-text-highlighted: var(--ui-color-neutral-900);
--ui-bg: var(--color-white);
--ui-bg-muted: var(--ui-color-neutral-50);
--ui-bg-elevated: var(--ui-color-neutral-100);
--ui-bg-accented: var(--ui-color-neutral-200);
--ui-bg-inverted: var(--ui-color-neutral-900);
--ui-border: var(--ui-color-neutral-200);
--ui-border-muted: var(--ui-color-neutral-200);
--ui-border-accented: var(--ui-color-neutral-300);
--ui-border-inverted: var(--ui-color-neutral-900);
@@ -42,11 +44,13 @@
--ui-text-highlighted: var(--color-white);
--ui-bg: var(--ui-color-neutral-900);
--ui-bg-muted: var(--ui-color-neutral-800);
--ui-bg-elevated: var(--ui-color-neutral-800);
--ui-bg-accented: var(--ui-color-neutral-700);
--ui-bg-inverted: var(--color-white);
--ui-border: var(--ui-color-neutral-800);
--ui-border-muted: var(--ui-color-neutral-700);
--ui-border-accented: var(--ui-color-neutral-700);
--ui-border-inverted: var(--color-white);
}