Skip to content
Merged
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
3 changes: 3 additions & 0 deletions 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 @@ -157,6 +158,7 @@ defineShortcuts({
<template>
<Navbar>
<USwitch v-model="virtualize" label="Virtualize" />
<USwitch v-model="preserveGroupOrder" label="Preserve order" />

<UModal v-model:open="open">
<UButton label="Open modal" color="neutral" variant="outline" />
Expand Down Expand Up @@ -195,6 +197,7 @@ defineShortcuts({
}
}"
multiple
:preserve-group-order="preserveGroupOrder"
class="sm:max-h-96"
@update:model-value="onSelect"
>
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 @@ -159,6 +159,12 @@ export interface CommandPaletteProps<G extends CommandPaletteGroup<T> = CommandP
* @defaultValue 'description'
*/
descriptionKey?: 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 @@ -210,6 +216,7 @@ const props = withDefaults(defineProps<CommandPaletteProps<G, T>>(), {
descriptionKey: 'description',
autofocus: true,
back: true,
preserveGroupOrder: false,
virtualize: false
})
const emits = defineEmits<CommandPaletteEmits<T>>()
Expand Down Expand Up @@ -304,6 +311,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)
if (!group) {
Expand Down
4 changes: 3 additions & 1 deletion test/components/CommandPalette.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,11 @@ describe('CommandPalette', () => {
// Props
['with groups', { props }],
['with groups with description', { props: { groups: groupsWithDescription } }],
['without data', {}],
['without groups', {}],
['with modelValue', { props: { ...props, modelValue: groups[2]?.items[0] } }],
['with defaultValue', { props: { ...props, defaultValue: groups[2]?.items[0] } }],
['with searchTerm', { props: { ...props, searchTerm: 'f' } }],
['with searchTerm and preserveGroupOrder', { props: { ...props, searchTerm: 'f', preserveGroupOrder: true } }],
['with labelKey', { props: { ...props, labelKey: 'icon' } }],
['with descriptionKey', { props: { groups: groupsWithDescription, descriptionKey: 'label' } }],
['with placeholder', { props: { ...props, placeholder: 'Search...' } }],
Expand Down
60 changes: 59 additions & 1 deletion test/components/__snapshots__/CommandPalette-vue.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,64 @@ exports[`CommandPalette > renders with placeholder correctly 1`] = `
</div>"
`;

exports[`CommandPalette > renders with searchTerm and 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="f"><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-start 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="flex-1 flex flex-col text-start min-w-0"><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>
<!--v-if--></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 uppercase 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 uppercase 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-start 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="flex-1 flex flex-col text-start min-w-0"><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>
<!--v-if--></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 uppercase 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 uppercase h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">F</kbd></span>
<!--v-if--></span>
</button>
</div>
<div role="group" aria-labelledby="reka-listbox-group-v-3" class="p-1 isolate">
<div id="reka-listbox-group-v-3" class="p-1.5 text-xs font-semibold text-highlighted">Labels</div><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-start 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="flex-1 flex flex-col text-start min-w-0"><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>
<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><!--v-if--></span>
</button>
</div>
</div>
</div>
<!--v-if-->
<!--v-if-->
</div>"
`;

exports[`CommandPalette > renders with searchTerm 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="f"><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">
<div id="reka-listbox-group-v-0" class="p-1.5 text-xs font-semibold text-highlighted">Labels</div><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-start 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="flex-1 flex flex-col text-start min-w-0"><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>
<!--v-if--></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-2" class="p-1 isolate">
<!--v-if--><button type="button" data-reka-collection-item="" id="reka-listbox-item-v-3" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-start 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="flex-1 flex flex-col text-start min-w-0"><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>
<!--v-if--></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 uppercase 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 uppercase 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-4" role="option" tabindex="-1" aria-selected="false" data-state="unchecked" class="group relative w-full flex items-start 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="flex-1 flex flex-col text-start min-w-0"><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>
<!--v-if--></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 uppercase 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 uppercase h-5 min-w-[20px] text-[11px] ring ring-inset ring-accented text-default bg-default">F</kbd></span>
<!--v-if--></span>
</button>
</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 Expand Up @@ -1116,7 +1174,7 @@ exports[`CommandPalette > renders with virtualize correctly 1`] = `
</div>"
`;

exports[`CommandPalette > renders without data correctly 1`] = `
exports[`CommandPalette > renders without groups 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-->
Expand Down
Loading
Loading