diff options
Diffstat (limited to 'viewer/src/components/LdDropdown.vue')
-rw-r--r-- | viewer/src/components/LdDropdown.vue | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/viewer/src/components/LdDropdown.vue b/viewer/src/components/LdDropdown.vue new file mode 100644 index 0000000..2ab3252 --- /dev/null +++ b/viewer/src/components/LdDropdown.vue | |||
@@ -0,0 +1,92 @@ | |||
1 | <template> | ||
2 | <div style="position:relative;"> | ||
3 | <Transition name="fade"> | ||
4 | <div | ||
5 | v-if="model" | ||
6 | ref="dropdown" | ||
7 | class="scrollbar" | ||
8 | :class="$style.dropdown" | ||
9 | v-bind="attrs" | ||
10 | > | ||
11 | <div | ||
12 | v-for="(option,idx) in props.list" | ||
13 | :key="listKey ? option[props.listKey] : idx" | ||
14 | :tabindex="props.tabindexRoot + idx" | ||
15 | @click="emit('select', option)" | ||
16 | @keypress.enter.space="emit('select', option)" | ||
17 | > | ||
18 | <slot | ||
19 | name="option" | ||
20 | :option="option" | ||
21 | /> | ||
22 | </div> | ||
23 | <slot | ||
24 | v-if="!props.list.length" | ||
25 | name="empty" | ||
26 | /> | ||
27 | </div> | ||
28 | </Transition> | ||
29 | </div> | ||
30 | </template> | ||
31 | |||
32 | <script setup lang="ts"> | ||
33 | import { onClickOutside, onKeyStroke, useVModel } from '@vueuse/core'; | ||
34 | import { ref, useAttrs, watch } from 'vue'; | ||
35 | |||
36 | const props = defineProps({ | ||
37 | modelValue: { type: Boolean, required: true }, | ||
38 | // Vue 3 currently won't allow generics in props | ||
39 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
40 | list: { type: Array<any>, required: true }, | ||
41 | listKey: { type: [String, Number], default: null }, | ||
42 | tabindexRoot: { type: Number, required: true }, | ||
43 | }); | ||
44 | const emit = defineEmits(['update:modelValue', 'select', 'opening', 'closing']); | ||
45 | const model = useVModel(props, 'modelValue', emit); | ||
46 | |||
47 | const attrs = useAttrs(); | ||
48 | |||
49 | const dropdown = ref(); | ||
50 | |||
51 | watch(() => model.value, value => { | ||
52 | if (value) emit('opening'); | ||
53 | else emit('closing'); | ||
54 | }); | ||
55 | |||
56 | onClickOutside(dropdown, closeDropdown); | ||
57 | onKeyStroke('Escape', closeDropdown); | ||
58 | |||
59 | function closeDropdown() { | ||
60 | setTimeout(() => (model.value = false)); | ||
61 | } | ||
62 | </script> | ||
63 | <script lang="ts"> | ||
64 | export default { | ||
65 | inheritAttrs: false, | ||
66 | }; | ||
67 | </script> | ||
68 | |||
69 | <style lang="scss" module> | ||
70 | @import "~@/assets/scss/theme"; | ||
71 | |||
72 | .dropdown { | ||
73 | position: absolute; | ||
74 | left: 0; | ||
75 | z-index: 99; | ||
76 | width: $layout-left; | ||
77 | color: $input-color; | ||
78 | background-color: $dropdown-item-color; | ||
79 | padding: 4px 0px; | ||
80 | > div { | ||
81 | padding: 4px 0; | ||
82 | margin: 2px; // For the focus border | ||
83 | cursor: pointer; | ||
84 | &:hover { | ||
85 | background-color: $dropdown-item-hover-color; | ||
86 | } | ||
87 | &:focus { | ||
88 | outline: solid 1px $button-active-color; | ||
89 | } | ||
90 | } | ||
91 | } | ||
92 | </style> | ||