Skip to content
Draft
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
6 changes: 5 additions & 1 deletion playgrounds/nuxt/app/pages/components/command-palette.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const searchTerm = ref('')
// const searchTermDebounced = refDebounced(searchTerm, 200)
const selected = ref([])
const virtualize = ref(false)
const preserveGroupOrder = ref(false)

const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
// params: { q: searchTermDebounced },
Expand Down Expand Up @@ -155,6 +156,7 @@ defineShortcuts({
<template>
<Navbar>
<USwitch v-model="virtualize" label="Virtualize" />
<USwitch v-model="preserveGroupOrder" label="Preserve group order" />

<UModal v-model:open="open">
<UButton label="Open modal" color="neutral" variant="outline" />
Expand All @@ -176,7 +178,7 @@ defineShortcuts({
<UButton label="Select label (popover)" color="neutral" variant="outline" />

<template #content>
<UCommandPalette v-model="label" placeholder="Search labels..." :groups="[{ id: 'labels', items: labels }]" :ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }" />
<UCommandPalette v-model="label" placeholder="Search labels..." :groups="[{ id: 'labels', items: labels }]" :ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }" :preserve-group-order />
</template>
</UPopover>
</Navbar>
Expand All @@ -193,6 +195,7 @@ defineShortcuts({
}
}"
multiple
:preserve-group-order
class="sm:max-h-96"
@update:model-value="onSelect"
>
Expand Down Expand Up @@ -224,6 +227,7 @@ defineShortcuts({
<UCommandPalette
v-if="virtualize"
virtualize
:preserve-group-order
:fuse="{ resultLimit: 1000 }"
placeholder="Search virtualized items..."
:groups="[{ id: 'items', items: Array(1000).fill(0).map((_, i) => ({ label: `item-${i}`, value: i, icon: 'i-lucide-file' })) }]"
Expand Down
27 changes: 27 additions & 0 deletions src/runtime/components/CommandPalette.vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ export interface CommandPaletteProps<G extends CommandPaletteGroup<T> = CommandP
* @defaultValue 'label'
*/
labelKey?: GetItemKeys<T>
/**
* Whether to preserve the order of groups as defined in the `groups` prop when filtering.
* When `false`, groups will appear based on item matches.
* @defaultValue false
*/
preserveGroupOrder?: boolean
class?: any
ui?: CommandPalette['slots']
}
Expand Down Expand Up @@ -202,6 +208,7 @@ const props = withDefaults(defineProps<CommandPaletteProps<G, T>>(), {
labelKey: 'label',
autofocus: true,
back: true,
preserveGroupOrder: false,
virtualize: false
})
const emits = defineEmits<CommandPaletteEmits<T>>()
Expand Down Expand Up @@ -295,6 +302,26 @@ const filteredGroups = computed(() => {

return acc
}, {} as Record<string, (T & { matches?: FuseResult<T>['matches'] })[]>)
if (props.preserveGroupOrder) {
const processedGroups: Array<ReturnType<typeof getGroupWithItems>> = []

for (const group of groups.value || []) {
if (!group.items?.length) {
continue
}

const items
= group.ignoreFilter
? group.items
: groupsById[group.id]

if (items?.length) {
processedGroups.push(getGroupWithItems(group, items))
}
}

return processedGroups
}

const fuseGroups = Object.entries(groupsById).map(([id, items]) => {
const group = groups.value?.find(group => group.id === id)
Expand Down
1 change: 1 addition & 0 deletions test/components/CommandPalette.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ describe('CommandPalette', () => {
['with close', { props: { ...props, close: true } }],
['with closeIcon', { props: { ...props, close: true, closeIcon: 'i-lucide-trash' } }],
['with virtualize', { props: { ...props, virtualize: true } }],
['with preserveGroupOrder', { props: { ...props, preserveGroupOrder: true } }],
['with as', { props: { ...props, as: 'section' } }],
['with class', { props: { ...props, class: 'divide-accented' } }],
['with ui', { props: { ...props, ui: { input: '[&>input]:h-10' } } }],
Expand Down
37 changes: 37 additions & 0 deletions test/components/__snapshots__/CommandPalette-vue.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,43 @@ exports[`CommandPalette > renders with placeholder correctly 1`] = `
</div>"
`;

exports[`CommandPalette > renders with preserveGroupOrder correctly 1`] = `
"<div dir="ltr" class="flex flex-col min-h-0 min-w-0 divide-y divide-default">
<div class="relative inline-flex items-center [&amp;>input]:h-12"><input type="text" placeholder="Type a command or search…" class="w-full rounded-md border-0 appearance-none placeholder:text-dimmed focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors px-2.5 py-1.5 text-sm gap-1.5 text-highlighted bg-transparent ps-9" autocomplete="off" aria-disabled="false" value=""><span class="absolute inset-y-0 start-0 flex items-center ps-2.5"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 16 16" class="shrink-0 text-dimmed size-5"></svg></span>
<!--v-if-->
</div>
<div class="relative overflow-hidden flex flex-col" role="listbox" aria-orientation="vertical" aria-multiselectable="false" data-orientation="vertical">
<div role="presentation" class="relative scroll-py-1 overflow-y-auto flex-1 focus:outline-none divide-y divide-default">
<div role="group" aria-labelledby="reka-listbox-group-v-0" class="p-1 isolate">
<!--v-if--><button type="button" data-reka-collection-item="" id="reka-listbox-item-v-1" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-highlighted before:bg-elevated"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 16 16" class="shrink-0 size-5 text-default"></svg><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Add new file</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Create a new file in the current directory or workspace.</span></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="hidden lg:inline-flex items-center shrink-0 gap-0.5"><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">Ctrl</kbd><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">N</kbd></span>
<!--v-if--></span>
</button><button type="button" data-reka-collection-item="" id="reka-listbox-item-v-2" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50 transition-colors before:transition-colors"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 16 16" class="shrink-0 size-5 text-dimmed group-data-highlighted:not-group-data-disabled:text-default transition-colors"></svg><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Add new folder</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Create a new folder in the current directory or workspace.</span></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="hidden lg:inline-flex items-center shrink-0 gap-0.5"><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">Ctrl</kbd><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">F</kbd></span>
<!--v-if--></span>
</button><button type="button" disabled="" data-reka-collection-item="" id="reka-listbox-item-v-3" role="option" tabindex="-1" aria-selected="false" data-disabled="" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50 transition-colors before:transition-colors"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 16 16" class="shrink-0 size-5 text-dimmed group-data-highlighted:not-group-data-disabled:text-default transition-colors"></svg><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Add hashtag</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Add a hashtag to the current item.</span></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="hidden lg:inline-flex items-center shrink-0 gap-0.5"><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">Ctrl</kbd><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">H</kbd></span>
<!--v-if--></span>
</button><button type="button" data-reka-collection-item="" id="reka-listbox-item-v-4" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50 transition-colors before:transition-colors"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 16 16" class="shrink-0 size-5 text-dimmed group-data-highlighted:not-group-data-disabled:text-default transition-colors"></svg><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Add label</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">Add a label to the current item.</span></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="hidden lg:inline-flex items-center shrink-0 gap-0.5"><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">Ctrl</kbd><kbd class="inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">L</kbd></span>
<!--v-if--></span>
</button>
</div>
<div role="group" aria-labelledby="reka-listbox-group-v-5" class="p-1 isolate">
<div id="reka-listbox-group-v-5" class="p-1.5 text-xs font-semibold text-highlighted">Labels</div><button type="button" data-reka-collection-item="" id="reka-listbox-item-v-6" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50 transition-colors before:transition-colors">
<div class="relative inline-flex items-center justify-center shrink-0 size-5"><span class="rounded-full ring ring-bg flex items-center justify-center text-inverted font-medium whitespace-nowrap bg-error h-[8px] min-w-[8px] text-[8px] top-0 right-0"></span></div><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">bug</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary"></span></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><!--v-if--></span>
</button><button type="button" data-reka-collection-item="" id="reka-listbox-item-v-7" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50 transition-colors before:transition-colors">
<div class="relative inline-flex items-center justify-center shrink-0 size-5"><span class="rounded-full ring ring-bg flex items-center justify-center text-inverted font-medium whitespace-nowrap bg-success h-[8px] min-w-[8px] text-[8px] top-0 right-0"></span></div><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">feature</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary"></span></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><!--v-if--></span>
</button><button type="button" data-reka-collection-item="" id="reka-listbox-item-v-8" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50 transition-colors before:transition-colors">
<div class="relative inline-flex items-center justify-center shrink-0 size-5"><span class="rounded-full ring ring-bg flex items-center justify-center text-inverted font-medium whitespace-nowrap bg-info h-[8px] min-w-[8px] text-[8px] top-0 right-0"></span></div><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">enhancement</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary"></span></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><!--v-if--></span>
</button>
</div>
<div role="group" aria-labelledby="reka-listbox-group-v-9" class="p-1 isolate">
<div id="reka-listbox-group-v-9" class="p-1.5 text-xs font-semibold text-highlighted">Users</div><a href="https://github.com/benjamincanac" role="option" tabindex="-1" target="_blank" data-reka-collection-item="" id="reka-listbox-item-v-10" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-center gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50 transition-colors before:transition-colors"><span class="inline-flex items-center justify-center select-none rounded-full align-middle bg-elevated size-5 text-[10px] shrink-0"><img src="https://github.com/benjamincanac.png" width="20" height="20" class="h-full w-full rounded-[inherit] object-cover"></span><span class="truncate space-x-1 text-dimmed"><!--v-if--><span class="text-highlighted [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary">benjamincanac</span><span class="text-dimmed [&amp;>mark]:text-inverted [&amp;>mark]:bg-primary"></span></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><!--v-if--></span></a>
</div>
</div>
</div>
<!--v-if-->
<!--v-if-->
</div>"
`;

exports[`CommandPalette > renders with selectedIcon correctly 1`] = `
"<div dir="ltr" class="flex flex-col min-h-0 min-w-0 divide-y divide-default">
<div class="relative inline-flex items-center [&amp;>input]:h-12"><input type="text" placeholder="Type a command or search…" class="w-full rounded-md border-0 appearance-none placeholder:text-dimmed focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors px-2.5 py-1.5 text-sm gap-1.5 text-highlighted bg-transparent ps-9" autocomplete="off" aria-disabled="false" value=""><span class="absolute inset-y-0 start-0 flex items-center ps-2.5"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 16 16" class="shrink-0 text-dimmed size-5"></svg></span>
Expand Down
Loading
Loading