Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/runtime/components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export type SelectEmits<A extends ArrayOrNested<SelectItem>, VK extends GetItemK
} & GetModelValueEmits<A, VK, M>

type SlotProps<T extends SelectItem> = (props: { item: T, index: number }) => any
type SlotGroupLabelProps<T extends SelectItem> = (props: { item: T, index: number, groupIndex: number }) => any

export interface SelectSlots<
A extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>,
Expand All @@ -126,6 +127,8 @@ export interface SelectSlots<
open: boolean
ui: { [K in keyof Required<Select['slots']>]: (props?: Record<string, any>) => string }
}): any
'group-label': SlotGroupLabelProps<T>
'separator': SlotProps<T>
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
Expand Down Expand Up @@ -196,7 +199,7 @@ const groups = computed<SelectItem[][]>(() =>
// eslint-disable-next-line vue/no-dupe-keys
const items = computed(() => groups.value.flatMap(group => group) as T[])

function displayValue(value: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string | undefined {
function displayValue(value?: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string | undefined {
if (props.multiple && Array.isArray(value)) {
const values = value.map(v => displayValue(v)).filter(Boolean)
return values?.length ? values.join(', ') : undefined
Expand Down Expand Up @@ -297,11 +300,14 @@ defineExpose({
<div role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
<SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<SelectLabel v-if="isSelectItem(item) && item.type === 'label'" :class="ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })">
{{ get(item, props.labelKey as string) }}
</SelectLabel>

<SelectSeparator v-else-if="isSelectItem(item) && item.type === 'separator'" :class="ui.separator({ class: [props.ui?.separator, item.ui?.separator, item.class] })" />
<slot v-if="isSelectItem(item) && item.type === 'label'" name="group-label" :item="(item as NestedItem<T>)" :index="index" :group-index="groupIndex">
<SelectLabel :class="ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })">
{{ get(item, props.labelKey as string) }}
</SelectLabel>
</slot>
<slot v-else-if="isSelectItem(item) && item.type === 'separator'" name="separator" :item="(item as NestedItem<T>)" :index="index">
<SelectSeparator :class="ui.separator({ class: [props.ui?.separator, item.ui?.separator, item.class] })" />
</slot>

<SelectItem
v-else
Expand Down
18 changes: 12 additions & 6 deletions src/runtime/components/SelectMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export type SelectMenuEmits<A extends ArrayOrNested<SelectMenuItem>, VK extends
} & GetModelValueEmits<A, VK, M>

type SlotProps<T extends SelectMenuItem> = (props: { item: T, index: number }) => any
type SlotGroupLabelProps<T extends SelectMenuItem> = (props: { item: T, index: number, groupIndex: number }) => any

export interface SelectMenuSlots<
A extends ArrayOrNested<SelectMenuItem> = ArrayOrNested<SelectMenuItem>,
Expand All @@ -156,6 +157,8 @@ export interface SelectMenuSlots<
ui: { [K in keyof Required<SelectMenu['slots']>]: (props?: Record<string, any>) => string }
}): any
'empty'(props: { searchTerm?: string }): any
'group-label': SlotGroupLabelProps<T>
'separator': SlotProps<T>
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
Expand Down Expand Up @@ -446,12 +449,15 @@ defineExpose({

<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="isSelectItem(item) && item.type === 'label'" :class="ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })">
{{ get(item, props.labelKey as string) }}
</ComboboxLabel>

<ComboboxSeparator v-else-if="isSelectItem(item) && item.type === 'separator'" :class="ui.separator({ class: [props.ui?.separator, item.ui?.separator, item.class] })" />

<slot v-if="isSelectItem(item) && item.type === 'label'" name="group-label" :item="(item as NestedItem<T>)" :index="index" :group-index="groupIndex">
<ComboboxLabel :class="ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })">
{{ get(item, props.labelKey as string) }}
</ComboboxLabel>
</slot>

<slot v-else-if="isSelectItem(item) && item.type === 'separator'" name="separator" :item="(item as NestedItem<T>)" :index="index">
<ComboboxSeparator :class="ui.separator({ class: [props.ui?.separator, item.ui?.separator, item.class] })" />
</slot>
<ComboboxItem
v-else
:class="ui.item({ class: [props.ui?.item, isSelectItem(item) && item.ui?.item, isSelectItem(item) && item.class] })"
Expand Down
9 changes: 8 additions & 1 deletion test/components/Select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ describe('Select', () => {
const variants = Object.keys(theme.variants.variant) as any

const items = [{
type: 'label',
label: 'Status'
}, {
label: 'Backlog',
value: 'backlog',
icon: 'i-lucide-circle-help'
Expand All @@ -27,6 +30,8 @@ describe('Select', () => {
label: 'Done',
value: 'done',
icon: 'i-lucide-circle-check'
}, {
type: 'separator'
}, {
label: 'Canceled',
value: 'canceled',
Expand Down Expand Up @@ -77,7 +82,9 @@ describe('Select', () => {
['with item slot', { props, slots: { item: () => 'Item slot' } }],
['with item-leading slot', { props, slots: { 'item-leading': () => 'Item leading slot' } }],
['with item-label slot', { props, slots: { 'item-label': () => 'Item label slot' } }],
['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }]
['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }],
['with label slot', { props, slots: { 'group-label': () => 'Group label slot' } }],
['with separator slot', { props, slots: { separator: () => 'Separator slot' } }]
])('renders %s correctly', async (nameOrHtml: string, options: { props?: SelectProps, slots?: Partial<SelectSlots> }) => {
const html = await ComponentRender(nameOrHtml, options, Select)
expect(html).toMatchSnapshot()
Expand Down
4 changes: 3 additions & 1 deletion test/components/SelectMenu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ describe('SelectMenu', () => {
['with item-leading slot', { props, slots: { 'item-leading': () => 'Item leading slot' } }],
['with item-label slot', { props, slots: { 'item-label': () => 'Item label slot' } }],
['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }],
['with create-item-label slot', { props: { ...props, searchTerm: 'New value', createItem: true }, slots: { 'create-item-label': () => 'Create item slot' } }]
['with create-item-label slot', { props: { ...props, searchTerm: 'New value', createItem: true }, slots: { 'create-item-label': () => 'Create item slot' } }],
['with label slot', { props, slots: { 'group-label': () => 'Group label slot' } }],
['with separator slot', { props, slots: { separator: () => 'Separator slot' } }]
])('renders %s correctly', async (nameOrHtml: string, options: { props?: SelectMenuProps, slots?: Partial<SelectMenuSlots> }) => {
const html = await ComponentRender(nameOrHtml, options, SelectMenu)
expect(html).toMatchSnapshot()
Expand Down
Loading