Compare commits

..

3 Commits

Author SHA1 Message Date
Ilia Mashkov
ff665e1d26 feature: add filters for providers and font subsets
Some checks failed
Lint / Lint Code (push) Has been cancelled
Test / Svelte Checks (push) Has been cancelled
2026-01-02 20:06:35 +03:00
Ilia Mashkov
949c7c1b48 feat: delete unnecessary components 2026-01-02 20:03:20 +03:00
Ilia Mashkov
90899c0b3b fix(CategoryFilter): fix toggle behavior 2026-01-02 17:19:53 +03:00
14 changed files with 159 additions and 91 deletions

View File

@@ -5,73 +5,12 @@ import type { Category } from '$shared/store/createFilterStore';
*/
export type FontCategory = 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace';
export const FONT_CATEGORIES: Category[] = [
{
id: 'sans-serif',
name: 'Sans-serif',
},
{
id: 'serif',
name: 'Serif',
},
{
id: 'display',
name: 'Display',
},
{
id: 'handwriting',
name: 'Handwriting',
},
{
id: 'monospace',
name: 'Monospace',
},
] as const;
/**
* Font provider
*/
export type FontProvider = 'google' | 'fontshare';
export const FONT_PROVIDERS: Category[] = [
{
id: 'google',
name: 'Google Fonts',
},
{
id: 'fontshare',
name: 'Fontshare',
},
] as const;
/**
* Font subset
*/
export type FontSubset = 'latin' | 'latin-ext' | 'cyrillic' | 'greek' | 'arabic' | 'devanagari';
export const FONT_SUBSETS: Category[] = [
{
id: 'latin',
name: 'Latin',
},
{
id: 'latin-ext',
name: 'Latin Extended',
},
{
id: 'cyrillic',
name: 'Cyrillic',
},
{
id: 'greek',
name: 'Greek',
},
{
id: 'arabic',
name: 'Arabic',
},
{
id: 'devanagari',
name: 'Devanagari',
},
] as const;

View File

@@ -1,9 +1,7 @@
import { FONT_CATEGORIES } from './model/state';
import { categoryFilterStore } from './store/categoryFilterStore';
import CategoryFilter from './ui/CategoryFilter.svelte';
export {
CategoryFilter,
categoryFilterStore,
FONT_CATEGORIES,
};

View File

@@ -1,11 +1,14 @@
import type { FilterModel } from '$shared/store/createFilterStore';
import type {
Category,
FilterModel,
} from '$shared/store/createFilterStore';
/**
* Model of state for CategoryFilter
*/
export type CategoryFilterModel = FilterModel;
export const FONT_CATEGORIES = [
export const FONT_CATEGORIES: Category[] = [
{
id: 'serif',
name: 'Serif',

View File

@@ -1,6 +1,8 @@
import { FONT_CATEGORIES } from '$entities/Font/model/font';
import { createFilterStore } from '$shared/store/createFilterStore';
import type { CategoryFilterModel } from '../model/state';
import {
type CategoryFilterModel,
FONT_CATEGORIES,
} from '../model/state';
/**
* Initial state for CategoryFilter

View File

@@ -1,20 +0,0 @@
<script lang="ts">
import CheckboxFilter from '$shared/ui/CheckboxFilter/CheckboxFilter.svelte';
import { categoryFilterStore } from '../store/categoryFilterStore';
const { categories } = $derived($categoryFilterStore);
function didCategoryToggle(categoryId: string) {
if (categories?.find(category => category.id === categoryId)) {
categoryFilterStore.deselectCategory(categoryId);
} else {
categoryFilterStore.selectCategory(categoryId);
}
}
</script>
<CheckboxFilter
filterName="Font category"
categories={categories}
onCategoryToggle={didCategoryToggle}
/>

View File

@@ -0,0 +1,7 @@
import { FONT_PROVIDERS } from './model/state';
import { providersFilterStore } from './store/providersFilterStore';
export {
FONT_PROVIDERS,
providersFilterStore,
};

View File

@@ -0,0 +1,20 @@
import type {
Category,
FilterModel,
} from '$shared/store/createFilterStore';
/**
* Model of state for ProvidersFilter
*/
export type ProvidersFilterModel = FilterModel;
export const FONT_PROVIDERS: Category[] = [
{
id: 'google',
name: 'Google Fonts',
},
{
id: 'fontshare',
name: 'Fontshare',
},
] as const;

View File

@@ -0,0 +1,18 @@
import { createFilterStore } from '$shared/store/createFilterStore';
import {
FONT_PROVIDERS,
type ProvidersFilterModel,
} from '../model/state';
/**
* Initial state for ProvidersFilter
*/
export const initialState: ProvidersFilterModel = {
searchQuery: '',
categories: FONT_PROVIDERS,
};
/**
* ProvidersFilter store
*/
export const providersFilterStore = createFilterStore(initialState);

View File

@@ -0,0 +1,7 @@
import { FONT_SUBSETS } from './model/state';
import { subsetsFilterStore } from './store/subsetsFilterStore';
export {
FONT_SUBSETS,
subsetsFilterStore,
};

View File

@@ -0,0 +1,36 @@
import type {
Category,
FilterModel,
} from '$shared/store/createFilterStore';
/**
* Model of state for SubsetsFilter
*/
export type SubsetsFilterModel = FilterModel;
export const FONT_SUBSETS: Category[] = [
{
id: 'latin',
name: 'Latin',
},
{
id: 'latin-ext',
name: 'Latin Extended',
},
{
id: 'cyrillic',
name: 'Cyrillic',
},
{
id: 'greek',
name: 'Greek',
},
{
id: 'arabic',
name: 'Arabic',
},
{
id: 'devanagari',
name: 'Devanagari',
},
] as const;

View File

@@ -0,0 +1,18 @@
import { createFilterStore } from '$shared/store/createFilterStore';
import {
FONT_SUBSETS,
type SubsetsFilterModel,
} from '../model/state';
/**
* Initial state for SubsetsFilter
*/
export const initialState: SubsetsFilterModel = {
searchQuery: '',
categories: FONT_SUBSETS,
};
/**
* SubsetsFilter store
*/
export const subsetsFilterStore = createFilterStore(initialState);

View File

@@ -77,6 +77,12 @@ export interface FilterStore<T extends FilterModel> extends Writable<T> {
* @param category - Category to deselect
*/
deselectCategory: (categoryId: string) => void;
/**
* Toggle a category.
*
* @param categoryId - Category ID
*/
toggleCategory: (categoryId: string) => void;
/**
* Select all categories.
*/
@@ -185,6 +191,19 @@ export function createFilterStore<T extends FilterModel>(
),
}));
},
/**
* Toggle a category.
*
* @param categoryId - Category ID
*/
toggleCategory: (categoryId: string) => {
update(state => ({
...state,
categories: state.categories.map(c =>
c.id === categoryId ? { ...c, selected: !c.selected } : c
),
}));
},
/**
* Select all categories
*/

View File

@@ -110,7 +110,7 @@ const hasSelection = $derived(selectedCount > 0);
class="border-t"
>
<div class="px-4 py-3">
<div class="flex flex-col gap-2.5">
<div class="flex flex-col gap-1.5">
<!-- Each item: checkbox + label with interactive hover/focus states -->
<!-- Keyed by category.id for efficient DOM updates -->
{#each categories as category (category.id)}
@@ -129,7 +129,7 @@ const hasSelection = $derived(selectedCount > 0);
<Checkbox
id={category.id}
checked={category.selected}
onchange={() => onCategoryToggle(category.id)}
onclick={() => onCategoryToggle(category.id)}
class="
shrink-0 cursor-pointer transition-all duration-150 ease-out
data-[state=checked]:scale-100

View File

@@ -1,10 +1,31 @@
<script lang="ts">
import { CategoryFilter } from '$features/CategoryFilter';
import { categoryFilterStore } from '$features/CategoryFilter';
import { providersFilterStore } from '$features/ProvidersFilter';
import { subsetsFilterStore } from '$features/SubsetsFilter';
import * as Sidebar from '$shared/shadcn/ui/sidebar/index';
import CheckboxFilter from '$shared/ui/CheckboxFilter/CheckboxFilter.svelte';
const { categories: providers } = $derived($providersFilterStore);
const { categories: subsets } = $derived($subsetsFilterStore);
const { categories } = $derived($categoryFilterStore);
</script>
<Sidebar.Root>
<Sidebar.Content>
<CategoryFilter />
<CheckboxFilter
filterName="Font provider"
categories={providers}
onCategoryToggle={providersFilterStore.toggleCategory}
/>
<CheckboxFilter
filterName="Font subset"
categories={subsets}
onCategoryToggle={subsetsFilterStore.toggleCategory}
/>
<CheckboxFilter
filterName="Font category"
categories={categories}
onCategoryToggle={categoryFilterStore.toggleCategory}
/>
</Sidebar.Content>
</Sidebar.Root>