* Update AttachControl.vue * Update ButtonControl.vue * Update CheckControl.vue * Update CodeControl.vue * Update DataControl.vue * Update ImageControl.vue * Update LinkControl.vue * Update RatingControl.vue * Update SelectControl.vue * Update SignatureControl.vue * Update TableControl.vue * Update TextControl.vue * Update TextEditorControl.vue * Update Section.vue * Update Column.vue * Update Tabs.vue * Update Field.vue * Update Sidebar.vue * Update AddFieldButton.vue * Update AddFieldButton.vue * Update Section.vue * Update WorkflowBuilder.vue * Update Autocomplete.vue * Update EditableInput.vue * Update AttachControl.vue * Update ButtonControl.vue * Update CheckControl.vue * Update CodeControl.vue * Update DataControl.vue * Update ImageControl.vue * Update LinkControl.vue * Update RatingControl.vue * Update SelectControl.vue * Update SignatureControl.vue * Update TextControl.vue * Update TextEditorControl.vue * Update Field.vue * Update EditableInput.vue * Update TableControl.vue * Update Column.vue * fix: variable in translatable string * fix: add context for row number label * fix: translate labels in workflow builder * style: formatting --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> Co-authored-by: Ankush Menat <ankush@frappe.io>
167 lines
3.1 KiB
Vue
167 lines
3.1 KiB
Vue
<template>
|
|
<Combobox v-model="selectedValue" nullable>
|
|
<ComboboxOptions class="combo-box-options" static>
|
|
<div class="search-box">
|
|
<ComboboxInput
|
|
ref="search"
|
|
class="search-input form-control"
|
|
type="text"
|
|
@change="(e) => (query = e.target.value)"
|
|
:value="query"
|
|
:placeholder="props.placeholder"
|
|
autocomplete="off"
|
|
@click.stop
|
|
/>
|
|
<button class="clear-button btn btn-sm" @click="clear_search">
|
|
<div v-html="frappe.utils.icon('close', 'sm')" />
|
|
</button>
|
|
</div>
|
|
<div class="combo-box-items">
|
|
<ComboboxOption
|
|
as="template"
|
|
v-for="(field, i) in sortedOptions"
|
|
:key="i"
|
|
:value="field"
|
|
v-slot="{ active }"
|
|
>
|
|
<li :class="['combo-box-option', active ? 'active' : '']">
|
|
{{ __(field.label) }}
|
|
</li>
|
|
</ComboboxOption>
|
|
</div>
|
|
</ComboboxOptions>
|
|
</Combobox>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption } from "@headlessui/vue";
|
|
import { computed, ref, useAttrs, watch, nextTick } from "vue";
|
|
|
|
const props = defineProps({
|
|
options: {
|
|
type: Array,
|
|
default: [],
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
modelValue: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
show: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(["update:modelValue", "update:show", "change"]);
|
|
const attrs = useAttrs();
|
|
|
|
const query = ref(null);
|
|
const search = ref(null);
|
|
|
|
const showOptions = computed({
|
|
get() {
|
|
return props.show;
|
|
},
|
|
set(val) {
|
|
emit("update:show", val);
|
|
},
|
|
});
|
|
|
|
const selectedValue = computed({
|
|
get() {
|
|
return attrs.value;
|
|
},
|
|
set(val) {
|
|
query.value = "";
|
|
if (val) {
|
|
showOptions.value = false;
|
|
}
|
|
emit("change", val);
|
|
},
|
|
});
|
|
|
|
const filteredOptions = computed(() => {
|
|
if (!query.value) return props.options;
|
|
return props.options.filter((option) => {
|
|
return option.label.toLocaleLowerCase().includes(query.value.toLocaleLowerCase());
|
|
});
|
|
});
|
|
|
|
const sortedOptions = computed(() => {
|
|
return filteredOptions.value.sort((a, b) => {
|
|
return a.label.localeCompare(b.label);
|
|
});
|
|
});
|
|
|
|
function clear_search() {
|
|
selectedValue.value = "";
|
|
search.value.el.focus();
|
|
}
|
|
|
|
watch(showOptions, (val) => {
|
|
if (val) {
|
|
nextTick(() => {
|
|
search.value.el.focus();
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.combo-box {
|
|
z-index: 100;
|
|
}
|
|
|
|
.combo-box-options {
|
|
width: 100%;
|
|
background-color: var(--fg-color);
|
|
border-radius: var(--border-radius-lg);
|
|
box-shadow: var(--shadow-2xl);
|
|
padding: 0;
|
|
border: 1px solid var(--subtle-accent);
|
|
}
|
|
|
|
.combo-box-option {
|
|
font-size: small;
|
|
text-align: left;
|
|
border-radius: var(--border-radius-sm);
|
|
padding: 6px 10px;
|
|
width: 100%;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
|
|
&:hover,
|
|
&.active {
|
|
background-color: var(--bg-light-gray);
|
|
}
|
|
}
|
|
|
|
.combo-box-items {
|
|
max-height: 200px;
|
|
padding: 5px;
|
|
padding-top: 0px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.search-box {
|
|
position: relative;
|
|
padding: 6px;
|
|
.clear-button {
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
display: inline-flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
}
|
|
.search-input {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|