Compare commits
3 Commits
4ba02b5933
...
ff665e1d26
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff665e1d26 | ||
|
|
949c7c1b48 | ||
|
|
90899c0b3b |
@@ -5,73 +5,12 @@ import type { Category } from '$shared/store/createFilterStore';
|
|||||||
*/
|
*/
|
||||||
export type FontCategory = 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace';
|
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
|
* Font provider
|
||||||
*/
|
*/
|
||||||
export type FontProvider = 'google' | 'fontshare';
|
export type FontProvider = 'google' | 'fontshare';
|
||||||
|
|
||||||
export const FONT_PROVIDERS: Category[] = [
|
|
||||||
{
|
|
||||||
id: 'google',
|
|
||||||
name: 'Google Fonts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'fontshare',
|
|
||||||
name: 'Fontshare',
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Font subset
|
* Font subset
|
||||||
*/
|
*/
|
||||||
export type FontSubset = 'latin' | 'latin-ext' | 'cyrillic' | 'greek' | 'arabic' | 'devanagari';
|
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;
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { FONT_CATEGORIES } from './model/state';
|
import { FONT_CATEGORIES } from './model/state';
|
||||||
import { categoryFilterStore } from './store/categoryFilterStore';
|
import { categoryFilterStore } from './store/categoryFilterStore';
|
||||||
import CategoryFilter from './ui/CategoryFilter.svelte';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CategoryFilter,
|
|
||||||
categoryFilterStore,
|
categoryFilterStore,
|
||||||
FONT_CATEGORIES,
|
FONT_CATEGORIES,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import type { FilterModel } from '$shared/store/createFilterStore';
|
import type {
|
||||||
|
Category,
|
||||||
|
FilterModel,
|
||||||
|
} from '$shared/store/createFilterStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model of state for CategoryFilter
|
* Model of state for CategoryFilter
|
||||||
*/
|
*/
|
||||||
export type CategoryFilterModel = FilterModel;
|
export type CategoryFilterModel = FilterModel;
|
||||||
|
|
||||||
export const FONT_CATEGORIES = [
|
export const FONT_CATEGORIES: Category[] = [
|
||||||
{
|
{
|
||||||
id: 'serif',
|
id: 'serif',
|
||||||
name: 'Serif',
|
name: 'Serif',
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { FONT_CATEGORIES } from '$entities/Font/model/font';
|
|
||||||
import { createFilterStore } from '$shared/store/createFilterStore';
|
import { createFilterStore } from '$shared/store/createFilterStore';
|
||||||
import type { CategoryFilterModel } from '../model/state';
|
import {
|
||||||
|
type CategoryFilterModel,
|
||||||
|
FONT_CATEGORIES,
|
||||||
|
} from '../model/state';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial state for CategoryFilter
|
* Initial state for CategoryFilter
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
7
src/features/ProvidersFilter/index.ts
Normal file
7
src/features/ProvidersFilter/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { FONT_PROVIDERS } from './model/state';
|
||||||
|
import { providersFilterStore } from './store/providersFilterStore';
|
||||||
|
|
||||||
|
export {
|
||||||
|
FONT_PROVIDERS,
|
||||||
|
providersFilterStore,
|
||||||
|
};
|
||||||
20
src/features/ProvidersFilter/model/state.ts
Normal file
20
src/features/ProvidersFilter/model/state.ts
Normal 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;
|
||||||
18
src/features/ProvidersFilter/store/providersFilterStore.ts
Normal file
18
src/features/ProvidersFilter/store/providersFilterStore.ts
Normal 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);
|
||||||
7
src/features/SubsetsFilter/index.ts
Normal file
7
src/features/SubsetsFilter/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { FONT_SUBSETS } from './model/state';
|
||||||
|
import { subsetsFilterStore } from './store/subsetsFilterStore';
|
||||||
|
|
||||||
|
export {
|
||||||
|
FONT_SUBSETS,
|
||||||
|
subsetsFilterStore,
|
||||||
|
};
|
||||||
36
src/features/SubsetsFilter/model/state.ts
Normal file
36
src/features/SubsetsFilter/model/state.ts
Normal 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;
|
||||||
18
src/features/SubsetsFilter/store/subsetsFilterStore.ts
Normal file
18
src/features/SubsetsFilter/store/subsetsFilterStore.ts
Normal 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);
|
||||||
@@ -77,6 +77,12 @@ export interface FilterStore<T extends FilterModel> extends Writable<T> {
|
|||||||
* @param category - Category to deselect
|
* @param category - Category to deselect
|
||||||
*/
|
*/
|
||||||
deselectCategory: (categoryId: string) => void;
|
deselectCategory: (categoryId: string) => void;
|
||||||
|
/**
|
||||||
|
* Toggle a category.
|
||||||
|
*
|
||||||
|
* @param categoryId - Category ID
|
||||||
|
*/
|
||||||
|
toggleCategory: (categoryId: string) => void;
|
||||||
/**
|
/**
|
||||||
* Select all categories.
|
* 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
|
* Select all categories
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
class="border-t"
|
class="border-t"
|
||||||
>
|
>
|
||||||
<div class="px-4 py-3">
|
<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 -->
|
<!-- Each item: checkbox + label with interactive hover/focus states -->
|
||||||
<!-- Keyed by category.id for efficient DOM updates -->
|
<!-- Keyed by category.id for efficient DOM updates -->
|
||||||
{#each categories as category (category.id)}
|
{#each categories as category (category.id)}
|
||||||
@@ -129,7 +129,7 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id={category.id}
|
id={category.id}
|
||||||
checked={category.selected}
|
checked={category.selected}
|
||||||
onchange={() => onCategoryToggle(category.id)}
|
onclick={() => onCategoryToggle(category.id)}
|
||||||
class="
|
class="
|
||||||
shrink-0 cursor-pointer transition-all duration-150 ease-out
|
shrink-0 cursor-pointer transition-all duration-150 ease-out
|
||||||
data-[state=checked]:scale-100
|
data-[state=checked]:scale-100
|
||||||
|
|||||||
@@ -1,10 +1,31 @@
|
|||||||
<script lang="ts">
|
<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 * 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>
|
</script>
|
||||||
|
|
||||||
<Sidebar.Root>
|
<Sidebar.Root>
|
||||||
<Sidebar.Content>
|
<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.Content>
|
||||||
</Sidebar.Root>
|
</Sidebar.Root>
|
||||||
|
|||||||
Reference in New Issue
Block a user