dashboard/shell/components/FilterPanel.vue

157 lines
4.1 KiB
Vue

<script setup lang="ts">
import { DefineComponent } from 'vue';
import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
/**
* Generic type for the passed components as props
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ComponentType = DefineComponent<any, any, any>;
/**
* A single filter option for a group (e.g., a single checkbox).
*/
type FilterOption = {
/** Value to apply when selected */
value?: string;
/** Optional custom component to render for this option (e.g., link or button) */
component?: ComponentType;
/** Props passed to the custom component */
componentProps?: Record<string, unknown>;
/** Label to show next to the checkbox, or a custom component next to the checkbox */
label?: string | { component: ComponentType; componentProps: Record<string, unknown>; };
};
/**
* A group of filter options (e.g., Repositories, Categories).
*/
type FilterGroup = {
/** Key that maps to the filters object (e.g., 'repos', 'categories') */
key: string;
/** Title for the filter group */
title: string;
/** The available options within this group */
options: FilterOption[];
};
/**
* Props accepted by the filter panel.
*/
const props = defineProps<{
/** Two-way bound filters */
modelValue: Record<string, string[]>;
/** The list of filter groups to display */
filters: FilterGroup[];
}>();
/**
* Event emitted by the component.
*/
const emit = defineEmits<{(e: 'update:modelValue', val: Record<string, string[]>): void;}>();
/**
* Handles updating the selected filters for a given filter group key.
*
* @param key - The key of the filter group being updated (e.g., 'tags').
* @param value - The updated list of selected values (e.g. ['monitoring', 'networking']).
*/
const updateFilter = (key: string, value: string[]) => {
const newValue = { ...props.modelValue, [key]: value };
emit('update:modelValue', newValue);
};
</script>
<template>
<div class="filter-panel">
<div
v-for="filter in filters"
:key="filter.key"
class="filter-panel-filter-group"
data-testid="filter-panel-filter-group"
>
<h4 class="filter-panel-filter-group-title">
{{ filter.title }}
</h4>
<div
v-for="(option, i) in filter.options"
:key="`${filter.key}-${i}`"
class="filter-panel-filter-option"
>
<template v-if="option.component">
<component
:is="option.component"
v-bind="option.componentProps"
data-testid="filter-panel-custom-component"
/>
</template>
<template v-else-if="option.label">
<Checkbox
:key="i"
class="filter-panel-filter-checkbox"
:label="typeof option.label === 'string' ? option.label : undefined"
:value="modelValue[filter.key]"
:value-when-true="option.value"
data-testid="filter-panel-filter-checkbox"
@update:value="updateFilter(filter.key, $event)"
>
<template #label>
<span v-if="typeof option.label === 'string'">{{ option.label }}</span>
<component
:is="option.label.component"
v-else
v-bind="option.label.componentProps"
/>
</template>
</Checkbox>
</template>
</div>
</div>
</div>
</template>
<style lang='scss' scoped>
.filter-panel {
display: flex;
min-width: 250px;
height: max-content;
padding: 16px;
flex-direction: column;
align-items: flex-start;
gap: 24px;
border-radius: 6px;
background: var(--sortable-table-header-bg);
&-filter-group {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
&-title {
margin-bottom: 6px;
font-size: 16px;
font-weight: 600;
line-height: 23px;
}
}
&-filter-option {
width: 100%;
.filter-panel-filter-checkbox {
.checkbox-label span {
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
color: var(--link-text-secondary);
margin-left: 4px;
}
}
}
}
</style>