diff --git a/docs/app/components/content/examples/select-menu/SelectMenuItemsBadgeExample.vue b/docs/app/components/content/examples/select-menu/SelectMenuItemsBadgeExample.vue new file mode 100644 index 0000000000..05af212f76 --- /dev/null +++ b/docs/app/components/content/examples/select-menu/SelectMenuItemsBadgeExample.vue @@ -0,0 +1,46 @@ + + + diff --git a/docs/app/components/content/examples/select/SelectItemsBadgeExample.vue b/docs/app/components/content/examples/select/SelectItemsBadgeExample.vue new file mode 100644 index 0000000000..bdb2eb52ff --- /dev/null +++ b/docs/app/components/content/examples/select/SelectItemsBadgeExample.vue @@ -0,0 +1,50 @@ + + + diff --git a/docs/content/docs/2.components/select-menu.md b/docs/content/docs/2.components/select-menu.md index 02c13cb6c0..fb09814be2 100644 --- a/docs/content/docs/2.components/select-menu.md +++ b/docs/content/docs/2.components/select-menu.md @@ -795,6 +795,21 @@ name: 'select-menu-items-avatar-example' You can also use the `#leading` slot to display the selected avatar. :: +### With badge in items + +You can use the `badge` property to display a [Badge](/docs/components/badge) inside the items. + +::component-example +--- +collapse: true +name: 'select-menu-items-badge-example' +--- +:: + +::tip +You can also use the `#leading` slot to display the selected badge. +:: + ### With chip in items You can use the `chip` property to display a [Chip](/docs/components/chip) inside the items. diff --git a/docs/content/docs/2.components/select.md b/docs/content/docs/2.components/select.md index 44818dc4ac..b5ead79595 100644 --- a/docs/content/docs/2.components/select.md +++ b/docs/content/docs/2.components/select.md @@ -676,6 +676,21 @@ In this example, the avatar is computed from the `value` property of the selecte You can also use the `#leading` slot to display the selected avatar. :: +### With badge in items + +You can use the `badge` property to display a [Badge](/docs/components/badge) inside the items. + +::component-example +--- +collapse: true +name: 'select-items-badge-example' +--- +:: + +::tip +You can also use the `#leading` slot to display the selected badge. +:: + ### With chip in items You can use the `chip` property to display a [Chip](/docs/components/chip) inside the items. diff --git a/playgrounds/nuxt/app/pages/components/select-menu.vue b/playgrounds/nuxt/app/pages/components/select-menu.vue index 237daa1d94..8aae11dff9 100644 --- a/playgrounds/nuxt/app/pages/components/select-menu.vue +++ b/playgrounds/nuxt/app/pages/components/select-menu.vue @@ -18,6 +18,10 @@ 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]] satisfies SelectMenuItem[][] +const itemsBadges = [ + [{ label: 'Fruits', type: 'label' }, ...fruits.map(f => ({ label: f, badge: Math.floor(Math.random() * 10) + 1 }))], + [{ label: 'Vegetables', type: 'label' }, ...vegetables.map(f => ({ label: f, badge: Math.floor(Math.random() * 10) + 1 }))] +] satisfies SelectMenuItem[][] const statuses = [{ label: 'Backlog', @@ -80,6 +84,14 @@ const valueMultiple = ref([fruits[0]!, vegetables[0]!]) v-bind="props" clear /> + diff --git a/playgrounds/nuxt/app/pages/components/select.vue b/playgrounds/nuxt/app/pages/components/select.vue index fd59891f2c..b5df2ffc94 100644 --- a/playgrounds/nuxt/app/pages/components/select.vue +++ b/playgrounds/nuxt/app/pages/components/select.vue @@ -16,7 +16,11 @@ const attrs = reactive({ const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'] const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek'] -const items = [[{ label: 'Fruits', type: 'label' as const }, ...fruits], [{ label: 'Vegetables', type: 'label' as const }, ...vegetables]] +const items = [[{ label: 'Fruits', type: 'label' as const }, ...fruits], [{ label: 'Vegetables', type: 'label' as const }, ...vegetables]] satisfies SelectItem[][] +const itemsBadges = [ + [{ label: 'Fruits', type: 'label' }, ...fruits.map(f => ({ label: f, badge: Math.floor(Math.random() * 10) + 1 }))], + [{ label: 'Vegetables', type: 'label' }, ...vegetables.map(f => ({ label: f, badge: Math.floor(Math.random() * 10) + 1 }))] +] satisfies SelectItem[][] const statuses = [{ label: 'Backlog', @@ -76,6 +80,7 @@ const valueMultiple = ref([fruits[0]!, vegetables[0]!]) + diff --git a/src/runtime/components/Select.vue b/src/runtime/components/Select.vue index b26aed2048..b46f75cbfa 100644 --- a/src/runtime/components/Select.vue +++ b/src/runtime/components/Select.vue @@ -4,7 +4,7 @@ import type { VNode } from 'vue' import type { AppConfig } from '@nuxt/schema' import theme from '#build/ui/select' import type { UseComponentIconsProps } from '../composables/useComponentIcons' -import type { AvatarProps, ChipProps, IconProps, InputProps } from '../types' +import type { AvatarProps, BadgeProps, ChipProps, IconProps, InputProps } from '../types' import type { ModelModifiers, ApplyModifiers } from '../types/input' import type { ButtonHTMLAttributes } from '../types/html' import type { AcceptableValue, ArrayOrNested, GetItemKeys, GetModelValue, NestedItem, EmitsToProps } from '../types/utils' @@ -23,6 +23,11 @@ export type SelectItem = SelectValue | { icon?: IconProps['name'] avatar?: AvatarProps chip?: ChipProps + /** + * Display a badge on the item. + * `{ color: 'neutral', variant: 'soft', size: 'sm' }`{lang="ts-type"} + */ + badge?: string | number | BadgeProps /** * The item type. * @defaultValue 'item' @@ -32,7 +37,7 @@ export type SelectItem = SelectValue | { disabled?: boolean onSelect?: (e: Event) => void class?: any - ui?: Pick + ui?: Pick [key: string]: any } @@ -380,6 +385,15 @@ defineExpose({ {{ isSelectItem(item) ? get(item, props.labelKey as string) : item }} + diff --git a/src/runtime/components/SelectMenu.vue b/src/runtime/components/SelectMenu.vue index 052f1ff8fc..98473ec1ed 100644 --- a/src/runtime/components/SelectMenu.vue +++ b/src/runtime/components/SelectMenu.vue @@ -4,7 +4,7 @@ import type { VNode } from 'vue' import type { AppConfig } from '@nuxt/schema' import theme from '#build/ui/select-menu' import type { UseComponentIconsProps } from '../composables/useComponentIcons' -import type { AvatarProps, ButtonProps, ChipProps, IconProps, InputProps, LinkPropsKeys } from '../types' +import type { AvatarProps, ButtonProps, ChipProps, IconProps, InputProps, LinkPropsKeys, BadgeProps } from '../types' import type { ModelModifiers, ApplyModifiers } from '../types/input' import type { ButtonHTMLAttributes } from '../types/html' import type { AcceptableValue, ArrayOrNested, GetItemKeys, GetItemValue, GetModelValue, NestedItem, EmitsToProps } from '../types/utils' @@ -23,6 +23,11 @@ export type SelectMenuItem = SelectMenuValue | { icon?: IconProps['name'] avatar?: AvatarProps chip?: ChipProps + /** + * Display a badge on the item. + * `{ color: 'neutral', variant: 'soft', size: 'sm' }`{lang="ts-type"} + */ + badge?: string | number | BadgeProps /** * The item type. * @defaultValue 'item' @@ -31,7 +36,7 @@ export type SelectMenuItem = SelectMenuValue | { disabled?: boolean onSelect?: (e: Event) => void class?: any - ui?: Pick + ui?: Pick [key: string]: any } @@ -543,6 +548,15 @@ defineExpose({ {{ isSelectItem(item) ? get(item, props.labelKey as string) : item }} + diff --git a/src/theme/select.ts b/src/theme/select.ts index 8c914f76ca..aeef7e99cf 100644 --- a/src/theme/select.ts +++ b/src/theme/select.ts @@ -26,8 +26,10 @@ export default (options: Required) => { itemTrailing: 'ms-auto inline-flex gap-1.5 items-center', itemTrailingIcon: 'shrink-0', itemWrapper: 'flex-1 flex flex-col min-w-0', - itemLabel: 'truncate', - itemDescription: 'truncate text-muted' + itemLabel: 'truncate flex items-center gap-1', + itemDescription: 'truncate text-muted', + itemBadge: 'shrink-0', + itemBadgeSize: 'sm' }, variants: { ...fieldGroupVariant, diff --git a/test/components/Select.spec.ts b/test/components/Select.spec.ts index 77b0399ba4..af8def3d11 100644 --- a/test/components/Select.spec.ts +++ b/test/components/Select.spec.ts @@ -37,12 +37,20 @@ describe('Select', () => { const itemsWithDescription = [...items.map(item => ({ ...item, description: 'Description' }))] + const itemsWithBadge = [ + ...items.map((item, i) => ({ + ...item, + badge: i % 2 === 0 ? 'Badge' : { color: 'primary', variant: 'solid', size: 'sm', label: 'Badge' } + })) + ] + const props = { open: true, portal: false, items } renderEach(Select, [ // Props ['with items', { props }], ['with items with description', { props: { ...props, items: itemsWithDescription } }], + ['with items with badge', { props: { ...props, items: itemsWithBadge } }], ['with modelValue', { props: { ...props, modelValue: items[0]?.value } }], ['with defaultValue', { props: { ...props, defaultValue: items[0]?.value } }], ['with valueKey', { props: { ...props, valueKey: 'label', defaultValue: 'Backlog' } }], diff --git a/test/components/SelectMenu.spec.ts b/test/components/SelectMenu.spec.ts index f66272b680..7eed4b50e3 100644 --- a/test/components/SelectMenu.spec.ts +++ b/test/components/SelectMenu.spec.ts @@ -37,12 +37,20 @@ describe('SelectMenu', () => { const itemsWithDescription = [...items.map(item => ({ ...item, description: 'Description' }))] + const itemsWithBadge = [ + ...items.map((item, i) => ({ + ...item, + badge: i % 2 === 0 ? 'Badge' : { color: 'primary', variant: 'solid', size: 'sm', label: 'Badge' } + })) + ] + const props = { open: true, portal: false, items } renderEach(SelectMenu, [ // Props ['with items', { props }], ['with items with description', { props: { ...props, items: itemsWithDescription } }], + ['with items with badge', { props: { ...props, items: itemsWithBadge } }], ['with modelValue', { props: { ...props, modelValue: items[0] } }], ['with defaultValue', { props: { ...props, defaultValue: items[0] } }], ['with valueKey', { props: { ...props, valueKey: 'label', defaultValue: 'Backlog' } }], diff --git a/test/components/__snapshots__/Select-vue.spec.ts.snap b/test/components/__snapshots__/Select-vue.spec.ts.snap index fb49734728..36ee199b1c 100644 --- a/test/components/__snapshots__/Select-vue.spec.ts.snap +++ b/test/components/__snapshots__/Select-vue.spec.ts.snap @@ -12,19 +12,19 @@ exports[`Select > renders with ariaLabel correctly 1`] = `