feature/fetch-fonts #14

Merged
ilia merged 76 commits from feature/fetch-fonts into main 2026-01-14 11:01:44 +00:00
6 changed files with 52 additions and 25 deletions
Showing only changes of commit 1990860717 - Show all commits

View File

@@ -4,7 +4,7 @@ import type { FilterGroupConfig } from '../../model/const/types/common';
/** /**
* Create a filter manager instance. * Create a filter manager instance.
*/ */
export function createFilterManager(configs: FilterGroupConfig[]) { export function createFilterManager<TValue extends string>(configs: FilterGroupConfig<TValue>[]) {
// Create filter instances upfront // Create filter instances upfront
const groups = $state( const groups = $state(
configs.map(config => ({ configs.map(config => ({

View File

@@ -1,70 +1,90 @@
import type {
FontCategory,
FontProvider,
FontSubset,
} from '$entities/Font';
import type { Property } from '$shared/lib'; import type { Property } from '$shared/lib';
export const FONT_CATEGORIES: Property[] = [ export const FONT_CATEGORIES: Property<FontCategory>[] = [
{ {
id: 'serif', id: 'serif',
name: 'Serif', name: 'Serif',
value: 'serif',
}, },
{ {
id: 'sans-serif', id: 'sans-serif',
name: 'Sans-serif', name: 'Sans-serif',
value: 'sans-serif',
}, },
{ {
id: 'display', id: 'display',
name: 'Display', name: 'Display',
value: 'display',
}, },
{ {
id: 'handwriting', id: 'handwriting',
name: 'Handwriting', name: 'Handwriting',
value: 'handwriting',
}, },
{ {
id: 'monospace', id: 'monospace',
name: 'Monospace', name: 'Monospace',
value: 'monospace',
}, },
{ {
id: 'script', id: 'script',
name: 'Script', name: 'Script',
value: 'script',
}, },
{ {
id: 'slab', id: 'slab',
name: 'Slab', name: 'Slab',
value: 'slab',
}, },
] as const; ] as const;
export const FONT_PROVIDERS: Property[] = [ export const FONT_PROVIDERS: Property<FontProvider>[] = [
{ {
id: 'google', id: 'google',
name: 'Google Fonts', name: 'Google Fonts',
value: 'google',
}, },
{ {
id: 'fontshare', id: 'fontshare',
name: 'Fontshare', name: 'Fontshare',
value: 'fontshare',
}, },
] as const; ] as const;
export const FONT_SUBSETS: Property[] = [ export const FONT_SUBSETS: Property<FontSubset>[] = [
{ {
id: 'latin', id: 'latin',
name: 'Latin', name: 'Latin',
value: 'latin',
}, },
{ {
id: 'latin-ext', id: 'latin-ext',
name: 'Latin Extended', name: 'Latin Extended',
value: 'latin-ext',
}, },
{ {
id: 'cyrillic', id: 'cyrillic',
name: 'Cyrillic', name: 'Cyrillic',
value: 'cyrillic',
}, },
{ {
id: 'greek', id: 'greek',
name: 'Greek', name: 'Greek',
value: 'greek',
}, },
{ {
id: 'arabic', id: 'arabic',
name: 'Arabic', name: 'Arabic',
value: 'arabic',
}, },
{ {
id: 'devanagari', id: 'devanagari',
name: 'Devanagari', name: 'Devanagari',
value: 'devanagari',
}, },
] as const; ] as const;

View File

@@ -1,7 +1,7 @@
import type { Property } from '$shared/lib'; import type { Property } from '$shared/lib';
export interface FilterGroupConfig { export interface FilterGroupConfig<TValue extends string> {
id: string; id: string;
label: string; label: string;
properties: Property[]; properties: Property<TValue>[];
} }

View File

@@ -1,4 +1,4 @@
export interface Property { export interface Property<TValue extends string> {
/** /**
* Property identifier * Property identifier
*/ */
@@ -7,13 +7,17 @@ export interface Property {
* Property name * Property name
*/ */
name: string; name: string;
/**
* Property value
*/
value: TValue;
/** /**
* Property selected state * Property selected state
*/ */
selected?: boolean; selected?: boolean;
} }
export interface FilterModel { export interface FilterModel<TValue extends string> {
/** /**
* Search query * Search query
*/ */
@@ -21,15 +25,15 @@ export interface FilterModel {
/** /**
* Properties * Properties
*/ */
properties: Property[]; properties: Property<TValue>[];
} }
/** /**
* Create a filter store. * Create a filter store.
* @param initialState - Initial state of the filter store * @param initialState - Initial state of the filter store
*/ */
export function createFilter<T extends FilterModel>( export function createFilter<TValue extends string>(
initialState: T, initialState: FilterModel<TValue>,
) { ) {
let properties = $state( let properties = $state(
initialState.properties.map(p => ({ initialState.properties.map(p => ({

View File

@@ -22,6 +22,7 @@ describe('createFilter - Filter Logic', () => {
return Array.from({ length: count }, (_, i) => ({ return Array.from({ length: count }, (_, i) => ({
id: `prop-${i}`, id: `prop-${i}`,
name: `Property ${i}`, name: `Property ${i}`,
value: `Value ${i}`,
selected: selectedIndices.includes(i), selected: selectedIndices.includes(i),
})); }));
} }

View File

@@ -33,7 +33,7 @@ describe('CheckboxFilter Component', () => {
/** /**
* Helper function to create a filter for testing * Helper function to create a filter for testing
*/ */
function createTestFilter(properties: Property[]) { function createTestFilter<T extends string>(properties: Property<T>[]) {
return createFilter({ properties }); return createFilter({ properties });
} }
@@ -44,6 +44,7 @@ describe('CheckboxFilter Component', () => {
return Array.from({ length: count }, (_, i) => ({ return Array.from({ length: count }, (_, i) => ({
id: `prop-${i}`, id: `prop-${i}`,
name: `Property ${i}`, name: `Property ${i}`,
value: `Value ${i}`,
selected: selectedIndices.includes(i), selected: selectedIndices.includes(i),
})); }));
} }
@@ -437,10 +438,11 @@ describe('CheckboxFilter Component', () => {
describe('Edge Cases', () => { describe('Edge Cases', () => {
it('handles long property names', () => { it('handles long property names', () => {
const properties: Property[] = [ const properties: Property<string>[] = [
{ {
id: '1', id: '1',
name: 'This is a very long property name that might wrap to multiple lines', name: 'This is a very long property name that might wrap to multiple lines',
value: '1',
selected: false, selected: false,
}, },
]; ];
@@ -458,10 +460,10 @@ describe('CheckboxFilter Component', () => {
}); });
it('handles special characters in property names', () => { it('handles special characters in property names', () => {
const properties: Property[] = [ const properties: Property<string>[] = [
{ id: '1', name: 'Café & Restaurant', selected: true }, { id: '1', name: 'Café & Restaurant', value: '1', selected: true },
{ id: '2', name: '100% Organic', selected: false }, { id: '2', name: '100% Organic', value: '2', selected: false },
{ id: '3', name: '(Special) <Characters>', selected: false }, { id: '3', name: '(Special) <Characters>', value: '3', selected: false },
]; ];
const filter = createTestFilter(properties); const filter = createTestFilter(properties);
render(CheckboxFilter, { render(CheckboxFilter, {
@@ -475,8 +477,8 @@ describe('CheckboxFilter Component', () => {
}); });
it('handles single property filter', () => { it('handles single property filter', () => {
const properties: Property[] = [ const properties: Property<string>[] = [
{ id: '1', name: 'Only One', selected: true }, { id: '1', name: 'Only One', value: '1', selected: true },
]; ];
const filter = createTestFilter(properties); const filter = createTestFilter(properties);
render(CheckboxFilter, { render(CheckboxFilter, {
@@ -527,12 +529,12 @@ describe('CheckboxFilter Component', () => {
describe('Component Integration', () => { describe('Component Integration', () => {
it('works correctly with real filter data', async () => { it('works correctly with real filter data', async () => {
const realProperties: Property[] = [ const realProperties: Property<string>[] = [
{ id: 'sans-serif', name: 'Sans-serif', selected: true }, { id: 'sans-serif', name: 'Sans-serif', value: 'sans-serif', selected: true },
{ id: 'serif', name: 'Serif', selected: false }, { id: 'serif', name: 'Serif', value: 'serif', selected: false },
{ id: 'display', name: 'Display', selected: false }, { id: 'display', name: 'Display', value: 'display', selected: false },
{ id: 'handwriting', name: 'Handwriting', selected: true }, { id: 'handwriting', name: 'Handwriting', value: 'handwriting', selected: true },
{ id: 'monospace', name: 'Monospace', selected: false }, { id: 'monospace', name: 'Monospace', value: 'monospace', selected: false },
]; ];
const filter = createTestFilter(realProperties); const filter = createTestFilter(realProperties);
render(CheckboxFilter, { render(CheckboxFilter, {