mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 21:33:02 +01:00
Co-authored-by: Matt Kilgore <tankerkiller125@users.noreply.github.com> Co-authored-by: Tonya <tonya@tokia.dev>
117 lines
3.1 KiB
Vue
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>
|