PronounsPage/components/FilterBar.vue

113 lines
3.9 KiB
Vue

<script setup lang="ts">
import type { Category } from '~/src/classes.ts';
const filter = defineModel<string>();
const filterCategory = defineModel<string>('category');
const props = defineProps<{
categories?: Category[] | undefined;
submitButton?: boolean;
}>();
const emit = defineEmits<{
submitClicked: [];
}>();
const filterInput = useTemplateRef<HTMLInputElement>('filterInput');
const { $translator: translator } = useNuxtApp();
const allCategory: Category = { key: '', text: translator.translate('crud.all'), icon: 'clipboard-list' };
const categoriesWithAllCategory = computed(() => [allCategory, ...(props.categories ?? [])]);
defineExpose({
focus: () => filterInput.value?.focus(),
});
const categoryList = useTemplateRef('categoryList');
const categoryButtonKeydown = (event: KeyboardEvent) => {
if (filterCategory.value === undefined) {
return;
}
const activeIndex = categoriesWithAllCategory.value.map((category) => category.key).indexOf(filterCategory.value);
if (activeIndex === -1) {
return;
}
if ((event.key === 'ArrowUp' || event.key === 'ArrowLeft') &&
activeIndex > 0) {
filterCategory.value = categoriesWithAllCategory.value[activeIndex - 1].key;
(categoryList.value?.children[activeIndex - 1] as HTMLButtonElement | undefined)?.focus();
} else if ((event.key === 'ArrowDown' || event.key === 'ArrowRight') &&
activeIndex < categoriesWithAllCategory.value.length - 1) {
filterCategory.value = categoriesWithAllCategory.value[activeIndex + 1].key;
(categoryList.value?.children[activeIndex + 1] as HTMLButtonElement | undefined)?.focus();
}
};
</script>
<template>
<section class="sticky-top bg-white rounded">
<div class="input-group">
<span class="input-group-text">
<Icon v="filter" />
</span>
<input
ref="filterInput"
v-model="filter"
type="search"
class="form-control border-primary"
:placeholder="$t('crud.filterLong')"
>
<button
v-if="filter"
type="button"
class="btn btn-outline-danger"
:title="$t('crud.resetFilter')"
@click="filter = ''; filterInput?.focus()"
>
<Icon v="times" />
</button>
<button
v-if="submitButton"
type="button"
class="btn btn-success"
@click="emit('submitClicked')"
>
<Icon v="plus-circle" />
<T>nouns.submit.action</T>
</button>
</div>
<div
v-if="categories && categories.length > 0"
ref="categoryList"
class="d-flex flex-wrap mt-1 border border-primary rounded overflow-hidden"
>
<button
v-for="category of categoriesWithAllCategory"
:key="category.text"
type="button"
:class="[
'btn btn-sm btn-wrapped',
filterCategory === category.key ? 'btn-primary' : 'btn-outline-primary',
'flex-grow-1 d-flex justify-content-center align-items-center gap-1 rounded-0',
]"
:tabindex="filterCategory === category.key ? 0 : -1"
@click="filterCategory = category.key"
@keydown="categoryButtonKeydown"
>
<Icon v-if="category.icon" :v="category.icon" />
<Spelling :text="category.text" />
</button>
</div>
</section>
</template>
<style lang="scss" scoped>
.btn-wrapped {
margin-block-start: calc(-1 * var(--bs-btn-border-width));
margin-inline-start: calc(-1 * var(--bs-btn-border-width));
border-block-end: none;
border-inline-end: none;
}
</style>