Files
homebox/frontend/components/global/QuickMenu/Input.vue
2025-01-26 12:43:45 +00:00

117 lines
3.1 KiB
Vue

<template>
<Combobox v-model="selectedAction" :nullable="true">
<ComboboxInput
ref="inputBox"
class="input input-bordered mt-2 w-full"
@input="inputValue = $event.target.value"
></ComboboxInput>
<ComboboxOptions
class="card dropdown-content absolute max-h-48 w-full overflow-y-scroll rounded-lg border border-base-300 bg-base-100"
:unmount="false"
>
<ComboboxOption
v-for="(action, idx) in filteredActions"
:key="idx"
v-slot="{ active }"
:value="action"
as="template"
>
<button
class="flex w-full rounded-lg px-3 py-1.5 text-left transition-colors"
:class="{ 'bg-primary text-primary-content': active }"
>
{{ action.text }}
<kbd
v-if="action.shortcut"
class="kbd kbd-sm ml-auto"
:class="{ 'border-primary-content bg-primary': active }"
>
{{ action.shortcut }}
</kbd>
</button>
</ComboboxOption>
<div
v-if="filteredActions.length == 0"
class="w-full rounded-lg p-3 text-left transition-colors hover:bg-base-300"
>
No actions found.
</div>
</ComboboxOptions>
<ComboboxButton ref="inputBoxButton"></ComboboxButton>
</Combobox>
</template>
<script setup lang="ts">
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, ComboboxButton } from "@headlessui/vue";
type ExposedProps = {
focused: boolean;
revealActions: () => void;
};
type QuickMenuAction = {
text: string;
action: () => void;
// A character that invokes this action instantly if pressed
shortcut?: string;
};
const props = defineProps({
modelValue: {
type: Object as PropType<QuickMenuAction>,
required: false,
default: undefined,
},
actions: {
type: Array as PropType<QuickMenuAction[]>,
required: true,
},
});
const emit = defineEmits(["update:modelValue", "actionSelected"]);
const selectedAction = ref(null);
const inputValue = ref("");
const inputBox = ref();
const inputBoxButton = ref();
const { focused: inputBoxFocused } = useFocus(inputBox);
const revealActions = () => {
unrefElement(inputBoxButton).click();
};
watch(inputBoxFocused, val => {
if (val) revealActions();
else inputValue.value = "";
});
watch(inputValue, (val, oldVal) => {
if (!oldVal) {
const action = props.actions?.find(v => v.shortcut === val);
if (action) {
emit("actionSelected", action);
inputBoxFocused.value = false;
}
}
});
watch(selectedAction, val => {
if (val) {
emit("actionSelected", val);
selectedAction.value = null;
}
});
const filteredActions = computed(() => {
const searchTerm = inputValue.value.toLowerCase();
return (props.actions || []).filter(action => {
return action.text.toLowerCase().includes(searchTerm) || action.shortcut?.includes(searchTerm);
});
});
defineExpose({ focused: inputBoxFocused, revealActions });
export type { QuickMenuAction, ExposedProps };
</script>