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`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -51,19 +51,19 @@ exports[`Select > renders with arrow correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -110,19 +110,19 @@ exports[`Select > renders with class correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -149,19 +149,19 @@ exports[`Select > renders with defaultValue correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -188,19 +188,19 @@ exports[`Select > renders with descriptionKey correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -227,19 +227,19 @@ exports[`Select > renders with disabled correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -273,19 +273,19 @@ exports[`Select > renders with id correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -341,11 +341,11 @@ exports[`Select > renders with item-description slot correctly 1`] = ` - BacklogItem description slot - TodoItem description slot - In ProgressItem description slot - DoneItem description slot - CanceledItem description slot + BacklogItem description slot + TodoItem description slot + In ProgressItem description slot + DoneItem description slot + CanceledItem description slot @@ -370,19 +370,19 @@ exports[`Select > renders with item-label slot correctly 1`] = ` - Item label slot + Item label slot - Item label slot + Item label slot - Item label slot + Item label slot - Item label slot + Item label slot - Item label slot + Item label slot @@ -409,19 +409,19 @@ exports[`Select > renders with item-leading slot correctly 1`] = ` - Item leading slotBacklog + Item leading slotBacklog - Item leading slotTodo + Item leading slotTodo - Item leading slotIn Progress + Item leading slotIn Progress - Item leading slotDone + Item leading slotDone - Item leading slotCanceled + Item leading slotCanceled @@ -448,19 +448,19 @@ exports[`Select > renders with item-trailing slot correctly 1`] = ` - Backlog + Backlog Item trailing slot - Todo + Todo Item trailing slot - In Progress + In Progress Item trailing slot - Done + Done Item trailing slot - Canceled + Canceled Item trailing slot @@ -487,19 +487,63 @@ exports[`Select > renders with items correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled + + + + + + + + + + + +" +`; + +exports[`Select > renders with items with badge correctly 1`] = ` +" + + + + + + + + + + + BacklogBadge + + + + TodoBadge + + + + In ProgressBadge + + + + DoneBadge + + + + CanceledBadge + @@ -526,11 +570,11 @@ exports[`Select > renders with items with description correctly 1`] = ` - BacklogDescription - TodoDescription - In ProgressDescription - DoneDescription - CanceledDescription + BacklogDescription + TodoDescription + In ProgressDescription + DoneDescription + CanceledDescription @@ -555,19 +599,19 @@ exports[`Select > renders with labelKey correctly 1`] = ` - backlog + backlog - todo + todo - in_progress + in_progress - done + done - canceled + canceled @@ -599,19 +643,19 @@ exports[`Select > renders with leading slot correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -682,19 +726,19 @@ exports[`Select > renders with modelValue correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -721,19 +765,19 @@ exports[`Select > renders with multiple and modelValue correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -760,19 +804,19 @@ exports[`Select > renders with multiple correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -799,19 +843,19 @@ exports[`Select > renders with name correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -838,19 +882,19 @@ exports[`Select > renders with neutral variant ghost correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -877,19 +921,19 @@ exports[`Select > renders with neutral variant none correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -916,19 +960,19 @@ exports[`Select > renders with neutral variant outline correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -955,19 +999,19 @@ exports[`Select > renders with neutral variant soft correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -994,19 +1038,19 @@ exports[`Select > renders with neutral variant subtle correctly 1`] = ` - Backlog + Backlog - Todo + Todo - In Progress + In Progress - Done + Done - Canceled + Canceled @@ -1033,19 +1077,19 @@ exports[`Select > renders with placeholder correctly 1`] = ` - Backlog + Backlog - Todo +