-
-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4315dad
commit ca33267
Showing
5 changed files
with
171 additions
and
160 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,109 +1,108 @@ | ||
<template> | ||
<Combobox v-model="selectedAction"> | ||
<ComboboxInput ref="inputBox" @input="inputValue = $event.target.value" class="input input-bordered w-full"></ComboboxInput> | ||
<ComboboxOptions | ||
class="card dropdown-content absolute w-full rounded-lg border border-base-300 bg-base-100" | ||
> | ||
<ComboboxOption v-for="(action, idx) in filteredActions" | ||
:key="idx" | ||
:value="action" | ||
as="template" | ||
v-slot="{ active }" | ||
> | ||
<button | ||
class="flex transition-colors w-full text-left rounded-lg px-3 py-1.5" | ||
:class="{ 'bg-primary text-primary-content': active }" | ||
> | ||
{{ action.text }} | ||
<Combobox v-model="selectedAction"> | ||
<ComboboxInput | ||
ref="inputBox" | ||
class="input input-bordered w-full" | ||
@input="inputValue = $event.target.value" | ||
></ComboboxInput> | ||
<ComboboxOptions class="card dropdown-content absolute w-full rounded-lg border border-base-300 bg-base-100"> | ||
<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="ml-auto text-center px-2 border border-base-300 rounded-md" | ||
:class="{ 'border-primary-content': active }" | ||
> | ||
{{action.shortcut}} | ||
</kbd> | ||
</button> | ||
</ComboboxOption> | ||
<div v-if="filteredActions.length == 0" | ||
class="transition-colors w-full text-left rounded-lg p-3 hover:bg-base-300"> | ||
No actions found. | ||
</div> | ||
</ComboboxOptions> | ||
<ComboboxButton ref="inputBoxButton"></ComboboxButton> | ||
</Combobox> | ||
<kbd | ||
v-if="action.shortcut" | ||
class="ml-auto rounded-md border border-base-300 px-2 text-center" | ||
:class="{ 'border-primary-content': 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'; | ||
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, ComboboxButton } from "@headlessui/vue"; | ||
type ExposedProps = { | ||
focused: boolean, | ||
revealActions: () => void, | ||
} | ||
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, | ||
}, | ||
}); | ||
type QuickMenuAction = { | ||
text: string, | ||
action: () => void, | ||
// A character that invokes this action instantly if pressed | ||
shortcut?: string, | ||
}; | ||
const selectedAction = useVModel(props, "modelValue"); | ||
const props = defineProps({ | ||
modelValue: { | ||
type: Object as PropType<QuickMenuAction>, | ||
required: false, | ||
}, | ||
actions: { | ||
type: Array as PropType<QuickMenuAction[]>, | ||
required: true, | ||
}, | ||
}) | ||
const inputValue = ref(""); | ||
const inputBox = ref(); | ||
const inputBoxButton = ref(); | ||
const { focused: inputBoxFocused } = useFocus(inputBox); | ||
const selectedAction = useVModel(props, "modelValue"); | ||
const emit = defineEmits(["update:modelValue", "quickSelect"]); | ||
const inputValue = ref(""); | ||
const inputBox = ref(); | ||
const inputBoxButton = ref(); | ||
const { focused: inputBoxFocused } = useFocus(inputBox); | ||
const revealActions = () => { | ||
unrefElement(inputBoxButton).click(); | ||
}; | ||
const emit = defineEmits(["update:modelValue", "quickSelect"]) | ||
watch(inputBoxFocused, () => { | ||
if (inputBoxFocused.value) revealActions(); | ||
else inputValue.value = ""; | ||
}); | ||
const revealActions = () => { | ||
unrefElement(inputBoxButton).click(); | ||
watch(inputValue, (val, oldVal) => { | ||
if (!oldVal) { | ||
const action = props.actions?.find(v => v.shortcut === val); | ||
if (action) { | ||
emit("quickSelect", action); | ||
} | ||
} | ||
watch(inputBoxFocused, () => { | ||
if (inputBoxFocused.value) | ||
revealActions(); | ||
else | ||
inputValue.value = ""; | ||
}) | ||
}); | ||
watch(inputValue, (val, oldVal) => { | ||
if (!oldVal) { | ||
let action = props.actions?.find(v => v.shortcut == val); | ||
if (action) { | ||
emit("quickSelect", action) | ||
} | ||
} | ||
}) | ||
const filteredActions = computed(() => { | ||
return (props.actions || []).filter(action => { | ||
return ( | ||
action.text.toLowerCase().includes(inputValue.value.toLowerCase()) || | ||
action.shortcut?.includes(inputValue.value.toLowerCase()) | ||
) | ||
}) | ||
}) | ||
const filteredActions = computed(() => { | ||
return (props.actions || []).filter(action => { | ||
return ( | ||
action.text.toLowerCase().includes(inputValue.value.toLowerCase()) || | ||
action.shortcut?.includes(inputValue.value.toLowerCase()) | ||
); | ||
}); | ||
}); | ||
defineExpose({ focused: inputBoxFocused, revealActions }) | ||
defineExpose({ focused: inputBoxFocused, revealActions }); | ||
export type { QuickMenuAction, ExposedProps as QuickMenuInput } | ||
</script> | ||
export type { QuickMenuAction, ExposedProps }; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,70 @@ | ||
<template> | ||
<BaseModal v-model="modal" :show-close-button="false"> | ||
<div class="relative"> | ||
<QuickMenuInput ref="inputBox" v-model="selectedAction" :actions="props.actions || []" @quickSelect="invokeAction"></QuickMenuInput> | ||
<ul v-if=false class="menu rounded-box w-full"> | ||
<li v-for="(action, idx) in (actions || [])" :key="idx"> | ||
<button | ||
@click="invokeAction(action)" | ||
class="transition-colors w-full text-left rounded-btn p-3 hover:bg-neutral hover:text-white"> | ||
<b v-if="action.shortcut">{{action.shortcut}}.</b> | ||
|
||
{{ action.text }} | ||
</button> | ||
</li> | ||
</ul> | ||
<span class="text-base-300">Use number keys to quick select.</span> | ||
</div> | ||
</BaseModal> | ||
<BaseModal v-model="modal" :show-close-button="false"> | ||
<div class="relative"> | ||
<QuickMenuInput | ||
ref="inputBox" | ||
v-model="selectedAction" | ||
:actions="props.actions || []" | ||
@quick-select="invokeAction" | ||
></QuickMenuInput> | ||
<ul v-if="false" class="menu rounded-box w-full"> | ||
<li v-for="(action, idx) in actions || []" :key="idx"> | ||
<button | ||
class="rounded-btn w-full p-3 text-left transition-colors hover:bg-neutral hover:text-white" | ||
@click="invokeAction(action)" | ||
> | ||
<b v-if="action.shortcut">{{ action.shortcut }}.</b> | ||
|
||
{{ action.text }} | ||
</button> | ||
</li> | ||
</ul> | ||
<span class="text-base-300">Use number keys to quick select.</span> | ||
</div> | ||
</BaseModal> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import type { QuickMenuAction, QuickMenuInput } from "./Input.vue" | ||
const props = defineProps({ | ||
modelValue: { | ||
type: Boolean, | ||
required: true, | ||
}, | ||
actions: { | ||
type: Array as PropType<QuickMenuAction[]>, | ||
required: false, | ||
}, | ||
}); | ||
import type { ExposedProps as QuickMenuInputData, QuickMenuAction } from "./Input.vue"; | ||
const props = defineProps({ | ||
modelValue: { | ||
type: Boolean, | ||
required: true, | ||
}, | ||
actions: { | ||
type: Array as PropType<QuickMenuAction[]>, | ||
required: false, | ||
default: () => [], | ||
}, | ||
}); | ||
const modal = useVModel(props, "modelValue"); | ||
const selectedAction = ref<QuickMenuAction>(); | ||
const inputBox = ref<QuickMenuInputData>({ focused: false, revealActions: () => {} }); | ||
const modal = useVModel(props, "modelValue"); | ||
const selectedAction = ref<QuickMenuAction>(); | ||
const inputBox = ref<QuickMenuInput>({ focused: false, revealActions: () => {} }); | ||
const onModalOpen = useTimeoutFn(() => { | ||
inputBox.value.focused = true; | ||
}, 50).start | ||
const onModalOpen = useTimeoutFn(() => { | ||
inputBox.value.focused = true; | ||
}, 50).start; | ||
const onModalClose = () => { | ||
selectedAction.value = undefined | ||
inputBox.value.focused = false | ||
} | ||
const onModalClose = () => { | ||
selectedAction.value = undefined; | ||
inputBox.value.focused = false; | ||
}; | ||
watch(modal, () => (modal.value ? onModalOpen : onModalClose)()) | ||
watch(modal, () => (modal.value ? onModalOpen : onModalClose)()); | ||
onStartTyping(() => { | ||
inputBox.value.focused = true; | ||
}); | ||
onStartTyping(() => { | ||
inputBox.value.focused = true | ||
}) | ||
function invokeAction(action: QuickMenuAction) { | ||
modal.value = false; | ||
useTimeoutFn(action.action, 100).start(); | ||
} | ||
function invokeAction(action: QuickMenuAction) { | ||
modal.value = false; | ||
useTimeoutFn(action.action, 100).start(); | ||
} | ||
watch(selectedAction, (action) => { | ||
if (action) | ||
invokeAction(action) | ||
}) | ||
</script> | ||
watch(selectedAction, action => { | ||
if (action) invokeAction(action); | ||
}); | ||
</script> |
Oops, something went wrong.