Add custom select component

The selection of locations, events, roles, shifts and users using the
native <select> element makes for awkward and difficult interactions.
Add an alternative select control that fixes the issues with the poor
handling and navigation of the control when having many options.

The custom select component can handle the selection of either one or
many entity from a ClientMap of entiteis with a name. Typing into the
text box searches the entities by name, arrow keys can navigate and
enter confirms the chosen entity by toggling it's presence in the
selection.
This commit is contained in:
Hornwitser 2025-06-27 18:20:24 +02:00
parent 3f9f218ed0
commit d49ed38185
3 changed files with 363 additions and 0 deletions

View file

@ -0,0 +1,64 @@
<template>
<div
class="select"
@focusin="dropdownComponent?.focusin()"
@focusout="dropdownComponent?.focusout()"
@keyup.esc="dropdownComponent?.esc()"
>
<div class="selected">
<div
v-for="id of selectedIds"
:key="id"
class="item"
>
{{ entities.get(id)?.name ?? id }}
<button
@click="selectedIds.delete(id)"
>x</button>
</div>
</div>
<SelectDropdown
ref="dropdown"
v-model="selectedIds"
:multi="true"
:entities
/>
</div>
</template>
<script lang="ts" setup>
import type { ApiEntity } from '~/shared/types/api';
import type { Id } from '~/shared/types/common';
import type { ClientEntity } from '~/utils/client-entity';
const selectedIds = defineModel<Set<Id>>({ required: true });
defineProps<{
entities: ClientMap<ClientEntity<ApiEntity> & { name?: string }>,
}>();
const dropdownComponent = useTemplateRef("dropdown");
</script>
<style scoped>
.select {
display: flex;
flex-wrap: wrap;
position: relative;
}
.selected {
display: flex;
flex-wrap: wrap;
gap: 0.2rem;
}
.item {
background-color: color-mix(in srgb, Canvas, CanvasText 10%);
border-radius: 0.3rem;
padding-inline: 0.2rem;
padding-block: 0.1rem;
margin-inline-end: 0.2rem;
white-space: pre-wrap;
}
.item button {
line-height: 0.7;
}
</style>