feature/sidebar #8

Merged
ilia merged 50 commits from feature/sidebar into main 2026-01-03 10:56:23 +00:00
Showing only changes of commit 873a020959 - Show all commits

View File

@@ -0,0 +1,189 @@
import { derived, Readable, Writable, writable } from 'svelte/store';
export interface Category {
/**
* Category identifier
*/
id: string;
/**
* Category name
*/
name: string;
/**
* Category selected state
*/
selected: boolean;
}
export interface FilterModel {
/**
* Search query
*/
searchQuery?: string;
/**
* Categories
*/
categories: Category[];
}
/**
* Model for reusable filter store with search support and category selection
*/
export interface FilterStore<T extends FilterModel> extends Writable<T> {
/**
* Get the store.
* @returns Readable store with filter data
*/
getStore: () => Readable<T>;
/**
* Get the selected categories.
* @returns Readable store with selected categories
*/
getSelectedCategories: () => Readable<Category[]>;
/**
* Get the filtered categories.
* @returns Readable store with filtered categories
*/
getFilteredCategories: () => Readable<Category[]>;
/**
* Update the search query filter.
*
* @param searchQuery - Search text (undefined to clear)
*/
setSearchQuery: (searchQuery: string | undefined) => void;
/**
* Clear the search query filter.
*/
clearSearchQuery: () => void;
/**
* Select a category.
*
* @param category - Category to select
*/
selectCategory: (categoryId: string) => void;
/**
* Deselect a category.
*
* @param category - Category to deselect
*/
deselectCategory: (categoryId: string) => void;
/**
* Select all categories.
*/
selectAllCategories: () => void;
/**
* Deselect all categories.
*/
deselectAllCategories: () => void;
}
/**
* Create a filter store.
* @param initialState - Initial state of the filter store
* @returns FilterStore<T>
*/
export function createFilterStore<T extends FilterModel>(
initialState: T,
): FilterStore<T> {
const { subscribe, set, update } = writable<T>(initialState);
return {
/*
* Expose subscribe, set, and update from Writable.
* This makes FilterStore compatible with Writable interface.
*/
subscribe,
set,
update,
/**
* Get the current state of the filter store.
*/
getStore: () => {
return {
subscribe,
};
},
/**
* Get the selected categories.
*/
getSelectedCategories: () => {
return derived({ subscribe }, $store => {
return $store.categories.filter(category => category.selected);
});
},
/**
* Get the filtered categories.
*/
getFilteredCategories: () => {
return derived({ subscribe }, $store => {
return $store.categories.filter(category =>
category.name.includes($store.searchQuery || '')
);
});
},
/**
* Update the search query filter.
*
* @param searchQuery - Search text (undefined to clear)
*/
setSearchQuery: (searchQuery: string | undefined) => {
update(state => ({
...state,
searchQuery: searchQuery || undefined,
}));
},
/**
* Clear the search query filter.
*/
clearSearchQuery: () => {
update(state => ({
...state,
searchQuery: undefined,
}));
},
/**
* Select a category.
*
* @param categoryId - Category ID
*/
selectCategory: (categoryId: string) => {
update(state => ({
...state,
categories: state.categories.map(c =>
c.id === categoryId ? { ...c, selected: true } : c
),
}));
},
/**
* Deselect a category.
*
* @param categoryId - Category ID
*/
deselectCategory: (categoryId: string) => {
update(state => ({
...state,
categories: state.categories.map(c =>
c.id === categoryId ? { ...c, selected: false } : c
),
}));
},
/**
* Select all categories
*/
selectAllCategories: () => {
update(state => ({
...state,
categories: state.categories.map(c => ({ ...c, selected: true })),
}));
},
/**
* Deselect all categories
*/
deselectAllCategories: () => {
update(state => ({
...state,
categories: state.categories.map(c => ({ ...c, selected: false })),
}));
},
};
}