Closed
Description
Description
Hi,
I think it would be great if the command palette would 'natively' support submenus to enable an easy way to build some more feature-rich command palettes.
I implemented submenus by manually replacing the assigned command groups and saving the 'history' of groups assigned to the command palette so I can go back via a manually injected back button.
The following code is a snippet of my code that doesn't work as I put it there because of some missing code, but it outlines what I did well enough to understand my submenu implementation.
Additional context
<template>
<UModal v-model="model">
<UCommandPalette
ref="commandPaletteRef"
:loading="isDataLoading"
:groups="selectedCommandGroups"
:autoselect="true"
:nullable="false"
@update:model-value="onCommandSelect"
@close="() => (model = false)"
:close-button="{
icon: 'i-heroicons-x-mark-20-solid',
color: 'gray',
variant: 'link',
padded: false,
}"
:empty-state="{
icon: 'i-heroicons-magnifying-glass-20-solid',
label: `We couldn't find any items.`,
queryLabel: `We couldn't find any items with that term. Please try again.`,
}">
</UCommandPalette>
</UModal>
</template>
<script setup lang="ts">
import type { Group, Command } from '#ui/types';
import type { UCommandPalette } from '#build/components';
import { nanoid } from 'nanoid';
const toast = useToast();
const router = useRouter();
const model = defineModel<boolean>();
const commandPaletteStore = useCommandPaletteStore();
const { commandPaletteGroups, isDataLoading, realms } = storeToRefs(commandPaletteStore);
const commandPaletteRef = ref<InstanceType<typeof UCommandPalette>>();
const newEntitySubmenuGroup = computed<Group[]>(() => {
return [
{
key: 'action-new-entity-sub-menu',
label: 'Create new entity: Select a realm',
commands: realms.value.map((realm) => ({
id: `action-new-entity-realm-${realm.workspaceId}`,
label: realm.ownershipType === 'PERSONAL' ? 'My Library' : realm.name,
icon: realm.ownershipType === 'PERSONAL' ? 'i-heroicons-user' : 'i-heroicons-user-group',
click: () => navigateTo(`${commandPaletteStore.getWorkspacePrefix(realm)}/entity/new`),
})),
},
];
});
const actionCommands = computed<Command[]>(() => {
return [
{
id: 'action-new-entity',
label: 'Create new entity',
icon: 'i-heroicons-document',
submenu: newEntitySubmenuGroup.value,
},
];
});
const navigationCommands: Command[] = [
{
id: 'navigation-home',
label: 'Home',
icon: 'i-heroicons-home',
click: () => navigateTo('/'),
},
{
id: 'navigation-settings',
label: 'Settings',
icon: 'i-heroicons-cog',
click: () => navigateTo('/settings'),
},
{
id: 'navigation-back',
label: 'Go back',
icon: 'i-heroicons-arrow-uturn-left',
click: () => router.back(),
},
];
const commandGroups = computed(() => {
return [
...commandPaletteGroups.value,
{
key: 'actions',
label: 'Actions',
commands: actionCommands.value.filter((command) => !command.hidden),
},
{
key: 'navigation',
label: 'Navigation',
commands: navigationCommands,
},
] satisfies Group[];
});
const selectedCommandGroups = ref<Group[]>(commandGroups.value);
const commandPaletteMenuHistory = ref<Group[][]>([commandGroups.value]);
function onCommandSelect(option: Command) {
if (option && option.isSubmenuBackButton) {
commandPaletteMenuHistory.value.pop();
selectedCommandGroups.value = commandPaletteMenuHistory.value[commandPaletteMenuHistory.value.length - 1];
return;
}
if (option && option.submenu) {
const id = nanoid();
const submenu: Group[] = [
{
key: `submenu-navigation-${id}`,
static: true,
commands: [
{
id: `command-back-${id}`,
label: 'Back',
icon: 'i-heroicons-arrow-left',
isSubmenuBackButton: true,
},
],
},
...option.submenu,
];
commandPaletteMenuHistory.value.push(submenu);
selectedCommandGroups.value = submenu;
return;
}
model.value = false;
if (option && option.click) {
option.click();
}
}
onBeforeMount(async () => {
await commandPaletteStore.loadData();
selectedCommandGroups.value = commandGroups.value;
});
</script>