mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-05 12:07:22 -04:00
113 lines
3.9 KiB
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>
|