mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-11 02:48:21 +00:00
WIP Multiselect
This commit is contained in:
parent
1c467e90ae
commit
eea54c306d
5 changed files with 161 additions and 2 deletions
148
client/src/components/inputs/Multiselect.vue
Normal file
148
client/src/components/inputs/Multiselect.vue
Normal file
|
@ -0,0 +1,148 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineModel<{
|
||||
options?: Record<any, any>,
|
||||
selection?: any[],
|
||||
search?: string,
|
||||
}>();
|
||||
let {options, selection, search} = $(props);
|
||||
|
||||
options ??= {
|
||||
bt1: "Boring Test Data 1",
|
||||
bt2: "Boring Test Data 2",
|
||||
bt3: "Boring Test Data 3",
|
||||
bt4: "Boring Test Data 4",
|
||||
bt5: "Boring Test Data 5",
|
||||
bt6: "Boring Test Data 6",
|
||||
};
|
||||
selection ??= ["bt1"];
|
||||
search ??= "";
|
||||
|
||||
let open = $ref(false);
|
||||
let navigated = $ref(0);
|
||||
let input: HTMLElement | null = $ref(null);
|
||||
|
||||
const selected = $computed(() => selection?.length || 0);
|
||||
|
||||
function toggle(key: any) {
|
||||
if (selection?.includes(key)) selection?.splice(selection?.indexOf(key), 1);
|
||||
else selection?.push(key);
|
||||
input?.focus();
|
||||
}
|
||||
|
||||
function onInputKeydown(e: KeyboardEvent) {
|
||||
switch (e.key) {
|
||||
case "Backspace":
|
||||
case "Delete":
|
||||
if (!selection || search !== "") break;
|
||||
if (navigated === 0) selection?.pop();
|
||||
else if (navigated > 0) navigated = 0;
|
||||
else {
|
||||
selection?.splice(navigated, 1);
|
||||
navigated = 0;
|
||||
}
|
||||
selection = selection;
|
||||
break;
|
||||
case "ArrowUp":
|
||||
navigated--;
|
||||
if (-navigated > selected) navigated = 0;
|
||||
break;
|
||||
case "ArrowDown":
|
||||
navigated++;
|
||||
if (navigated > Object.entries(options || {}).length) navigated = 0;
|
||||
break;
|
||||
case "Enter":
|
||||
if (!open) open = true;
|
||||
else if (navigated > 0)
|
||||
toggle(Object.entries(options || {})[navigated-1][0]);
|
||||
break;
|
||||
case "Escape":
|
||||
if (navigated !== 0) navigated = 0;
|
||||
else open = false;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div :class="{'multiselect': 1, 'cl-secondary': 1, open}"
|
||||
@focusout="$event => open = ($event.currentTarget as HTMLElement).contains(($event.relatedTarget as HTMLElement))">
|
||||
<div class="head">
|
||||
<div class="selection">
|
||||
<div v-for="(key, index) of selection" :key="key" v-text="(options || {})[key]" @click="() => toggle(key)" :class="{navigated: selected + navigated === index}"/>
|
||||
</div>
|
||||
<div class="searchbar" @focusin="() => open = true">
|
||||
<button class="expand" @click="() => ($refs.input as any).focus()">
|
||||
<i-material-symbols-expand-circle-down-outline/>
|
||||
</button>
|
||||
<input placeholder="Search..." v-model="search" ref="input" @keydown="onInputKeydown"/>
|
||||
</div>
|
||||
</div>
|
||||
<Transition name="fade-fast">
|
||||
<div class="dropdown" v-if="open" tabindex="-1" @focusin="() => open = true">
|
||||
<div v-for="([key, option], index) in Object.entries(options || {})" :key="key" @click="() => toggle(key)" :class="{selected: selection?.includes(key), navigated: navigated === index + 1}">
|
||||
<i-material-symbols-check-box-outline v-if="selection?.includes(key)"/>
|
||||
<i-material-symbols-check-box-outline-blank v-else/>
|
||||
<div v-text="option"/>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
* { border: none; }
|
||||
.selection, .searchbar, .dropdown {
|
||||
border: 1px solid var(--cl-fg);
|
||||
}
|
||||
.selection { border-bottom: none; }
|
||||
.dropdown { border-top: none; }
|
||||
|
||||
.selection, .dropdown {
|
||||
flex-grow: 1;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--cl-bg);
|
||||
}
|
||||
|
||||
.selection {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.selection > div {
|
||||
background-color: var(--cl-bg-el);
|
||||
}
|
||||
|
||||
:is(.selection, .dropdown) > div {
|
||||
flex-flow: row nowrap;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
:is(.selection, .dropdown) > div > div {
|
||||
justify-content: center;
|
||||
}
|
||||
:is(.selection, .dropdown) > div.selected {
|
||||
background-color: var(--cl-bg-sl);
|
||||
}
|
||||
:is(.selection, .dropdown) > div:hover,
|
||||
:is(.selection, .dropdown) > div.navigated {
|
||||
background-color: var(--cl-bg-hl);
|
||||
}
|
||||
|
||||
.searchbar {
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.searchbar > input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.expand > svg { transition: all 0.2s ease-out; }
|
||||
.open .expand > svg { transform: rotate(180deg); }
|
||||
|
||||
div:empty {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -67,7 +67,8 @@ button, .button {
|
|||
.nav-body .button {
|
||||
background-color: var(--cl-bg);
|
||||
}
|
||||
.button:hover, button:hover {
|
||||
.button:hover, button:hover,
|
||||
.button:focus, button:focus {
|
||||
background-color: var(--cl-bg-hl);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
user-select: inherit;
|
||||
color: inherit;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
div, ul, ol, nav {
|
||||
|
|
|
@ -3,4 +3,11 @@
|
|||
}
|
||||
.fade-enter-from, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-fast-enter-active, .fade-fast-leave-active {
|
||||
transition: all 0.1s ease-out !important;
|
||||
}
|
||||
.fade-fast-enter-from, .fade-fast-leave-to {
|
||||
opacity: 0;
|
||||
}
|
|
@ -13,7 +13,7 @@ let options = $ref([{name: 'Accept'}, {name: 'Drop'}, {name: 'Continue'}]);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="overflow-y: auto;">
|
||||
<PageHeader title="Dashboard">
|
||||
<button @click="doShit">Example Buttons</button>
|
||||
</PageHeader>
|
||||
|
@ -29,6 +29,8 @@ let options = $ref([{name: 'Accept'}, {name: 'Drop'}, {name: 'Continue'}]);
|
|||
<pillbar :options="options" name="verdict" ></pillbar>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
<Multiselect/>
|
||||
asd
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue