refactor(createFilterStore): move from store pattern to svelte 5 runes usage
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
export { categoryFilterStore } from './model/stores/categoryFilterStore';
|
export {
|
||||||
export { providersFilterStore } from './model/stores/providersFilterStore';
|
createFilterManager,
|
||||||
export { subsetsFilterStore } from './model/stores/subsetsFilterStore';
|
type FilterManager,
|
||||||
|
} from './lib/filterManager/filterManager.svelte';
|
||||||
export { clearAllFilters } from './model/services/clearAllFilters/clearAllFilters';
|
export {
|
||||||
|
FONT_CATEGORIES,
|
||||||
|
FONT_PROVIDERS,
|
||||||
|
FONT_SUBSETS,
|
||||||
|
} from './model/const/const';
|
||||||
|
export type { FilterGroupConfig } from './model/const/types/common';
|
||||||
|
export { filterManager } from './model/state/manager.svelte';
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
type Filter,
|
||||||
|
createFilter,
|
||||||
|
} from '$shared/lib/utils';
|
||||||
|
import type { FilterGroupConfig } from '../../model/const/types/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a filter manager instance.
|
||||||
|
*/
|
||||||
|
export function createFilterManager(configs: FilterGroupConfig[]) {
|
||||||
|
// Create filter instances upfront
|
||||||
|
const groups = $state(
|
||||||
|
configs.map(config => ({
|
||||||
|
id: config.id,
|
||||||
|
label: config.label,
|
||||||
|
instance: createFilter({ properties: config.properties }),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Derived: any selection across all groups
|
||||||
|
const hasAnySelection = $derived(
|
||||||
|
groups.some(group => group.instance.selectedProperties.length > 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Derived: total count across all groups
|
||||||
|
const totalSelectedCount = $derived(
|
||||||
|
groups.reduce(
|
||||||
|
(acc, group) => acc + group.instance.selectedProperties.length,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Direct array reference (reactive)
|
||||||
|
get groups() {
|
||||||
|
return groups;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Derived values
|
||||||
|
get hasAnySelection() {
|
||||||
|
return hasAnySelection;
|
||||||
|
},
|
||||||
|
get totalSelectedCount() {
|
||||||
|
return totalSelectedCount;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Global action
|
||||||
|
deselectAllGlobal: () => {
|
||||||
|
groups.forEach(group => group.instance.deselectAll());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper to get group by id
|
||||||
|
getGroup: (id: string) => {
|
||||||
|
return groups.find(g => g.id === id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FilterManager = ReturnType<typeof createFilterManager>;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Property } from '$shared/lib/store/createFilterStore/createFilterStore';
|
import type { Property } from '$shared/lib/store';
|
||||||
|
|
||||||
export const FONT_CATEGORIES: Property[] = [
|
export const FONT_CATEGORIES: Property[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
7
src/features/FilterFonts/model/const/types/common.ts
Normal file
7
src/features/FilterFonts/model/const/types/common.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { Property } from '$shared/lib/store';
|
||||||
|
|
||||||
|
export interface FilterGroupConfig {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
properties: Property[];
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { categoryFilterStore } from '../../stores/categoryFilterStore';
|
|
||||||
import { providersFilterStore } from '../../stores/providersFilterStore';
|
|
||||||
import { subsetsFilterStore } from '../../stores/subsetsFilterStore';
|
|
||||||
|
|
||||||
export function clearAllFilters() {
|
|
||||||
categoryFilterStore.deselectAllProperties();
|
|
||||||
providersFilterStore.deselectAllProperties();
|
|
||||||
subsetsFilterStore.deselectAllProperties();
|
|
||||||
}
|
|
||||||
27
src/features/FilterFonts/model/state/manager.svelte.ts
Normal file
27
src/features/FilterFonts/model/state/manager.svelte.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { createFilterManager } from '../../lib/filterManager/filterManager.svelte';
|
||||||
|
import {
|
||||||
|
FONT_CATEGORIES,
|
||||||
|
FONT_PROVIDERS,
|
||||||
|
FONT_SUBSETS,
|
||||||
|
} from '../const/const';
|
||||||
|
import type { FilterGroupConfig } from '../const/types/common';
|
||||||
|
|
||||||
|
const filtersData: FilterGroupConfig[] = [
|
||||||
|
{
|
||||||
|
id: 'providers',
|
||||||
|
label: 'Font provider',
|
||||||
|
properties: FONT_PROVIDERS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'subsets',
|
||||||
|
label: 'Font subset',
|
||||||
|
properties: FONT_SUBSETS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'categories',
|
||||||
|
label: 'Font category',
|
||||||
|
properties: FONT_CATEGORIES,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const filterManager = createFilterManager(filtersData);
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import {
|
|
||||||
type FilterModel,
|
|
||||||
createFilterStore,
|
|
||||||
} from '$shared/lib/store/createFilterStore/createFilterStore';
|
|
||||||
import { FONT_CATEGORIES } from '../const/const';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial state for CategoryFilter
|
|
||||||
*/
|
|
||||||
export const initialState: FilterModel = {
|
|
||||||
searchQuery: '',
|
|
||||||
properties: FONT_CATEGORIES,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CategoryFilter store
|
|
||||||
*/
|
|
||||||
export const categoryFilterStore = createFilterStore(initialState);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import {
|
|
||||||
type FilterModel,
|
|
||||||
createFilterStore,
|
|
||||||
} from '$shared/lib/store/createFilterStore/createFilterStore';
|
|
||||||
import { FONT_PROVIDERS } from '../const/const';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial state for ProvidersFilter
|
|
||||||
*/
|
|
||||||
export const initialState: FilterModel = {
|
|
||||||
searchQuery: '',
|
|
||||||
properties: FONT_PROVIDERS,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ProvidersFilter store
|
|
||||||
*/
|
|
||||||
export const providersFilterStore = createFilterStore(initialState);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import {
|
|
||||||
type FilterModel,
|
|
||||||
createFilterStore,
|
|
||||||
} from '$shared/lib/store/createFilterStore/createFilterStore';
|
|
||||||
import { FONT_SUBSETS } from '../const/const';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial state for SubsetsFilter
|
|
||||||
*/
|
|
||||||
const initialState: FilterModel = {
|
|
||||||
searchQuery: '',
|
|
||||||
properties: FONT_SUBSETS,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SubsetsFilter store
|
|
||||||
*/
|
|
||||||
export const subsetsFilterStore = createFilterStore(initialState);
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
import {
|
|
||||||
type Readable,
|
|
||||||
type Writable,
|
|
||||||
derived,
|
|
||||||
writable,
|
|
||||||
} from 'svelte/store';
|
|
||||||
|
|
||||||
export interface Property {
|
|
||||||
/**
|
|
||||||
* Property identifier
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
/**
|
|
||||||
* Property name
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* Property selected state
|
|
||||||
*/
|
|
||||||
selected?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FilterModel {
|
|
||||||
/**
|
|
||||||
* Search query
|
|
||||||
*/
|
|
||||||
searchQuery?: string;
|
|
||||||
/**
|
|
||||||
* Properties
|
|
||||||
*/
|
|
||||||
properties: Property[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for reusable filter store with search support and property selection
|
|
||||||
*/
|
|
||||||
export interface FilterStore<T extends FilterModel> extends Writable<T> {
|
|
||||||
/**
|
|
||||||
* Get the store.
|
|
||||||
* @returns Readable store with filter data
|
|
||||||
*/
|
|
||||||
getStore: () => Readable<T>;
|
|
||||||
/**
|
|
||||||
* Get all properties.
|
|
||||||
* @returns Readable store with properties
|
|
||||||
*/
|
|
||||||
getAllProperties: () => Readable<Property[]>;
|
|
||||||
/**
|
|
||||||
* Get the selected properties.
|
|
||||||
* @returns Readable store with selected properties
|
|
||||||
*/
|
|
||||||
getSelectedProperties: () => Readable<Property[]>;
|
|
||||||
/**
|
|
||||||
* Get the filtered properties.
|
|
||||||
* @returns Readable store with filtered properties
|
|
||||||
*/
|
|
||||||
getFilteredProperties: () => Readable<Property[]>;
|
|
||||||
/**
|
|
||||||
* 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 property.
|
|
||||||
*
|
|
||||||
* @param property - Property to select
|
|
||||||
*/
|
|
||||||
selectProperty: (propertyId: string) => void;
|
|
||||||
/**
|
|
||||||
* Deselect a property.
|
|
||||||
*
|
|
||||||
* @param property - Property to deselect
|
|
||||||
*/
|
|
||||||
deselectProperty: (propertyId: string) => void;
|
|
||||||
/**
|
|
||||||
* Toggle a property.
|
|
||||||
*
|
|
||||||
* @param propertyId - Property ID
|
|
||||||
*/
|
|
||||||
toggleProperty: (propertyId: string) => void;
|
|
||||||
/**
|
|
||||||
* Select all properties.
|
|
||||||
*/
|
|
||||||
selectAllProperties: () => void;
|
|
||||||
/**
|
|
||||||
* Deselect all properties.
|
|
||||||
*/
|
|
||||||
deselectAllProperties: () => 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 filtered properties.
|
|
||||||
*/
|
|
||||||
getAllProperties: () => {
|
|
||||||
return derived({ subscribe }, $store => {
|
|
||||||
return $store.properties;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get the selected properties.
|
|
||||||
*/
|
|
||||||
getSelectedProperties: () => {
|
|
||||||
return derived({ subscribe }, $store => {
|
|
||||||
return $store.properties.filter(property => property.selected);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get the filtered properties.
|
|
||||||
*/
|
|
||||||
getFilteredProperties: () => {
|
|
||||||
return derived({ subscribe }, $store => {
|
|
||||||
return $store.properties.filter(property =>
|
|
||||||
property.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 property.
|
|
||||||
*
|
|
||||||
* @param propertyId - Property ID
|
|
||||||
*/
|
|
||||||
selectProperty: (propertyId: string) => {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
properties: state.properties.map(c =>
|
|
||||||
c.id === propertyId ? { ...c, selected: true } : c
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Deselect a property.
|
|
||||||
*
|
|
||||||
* @param propertyId - Property ID
|
|
||||||
*/
|
|
||||||
deselectProperty: (propertyId: string) => {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
properties: state.properties.map(c =>
|
|
||||||
c.id === propertyId ? { ...c, selected: false } : c
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Toggle a property.
|
|
||||||
*
|
|
||||||
* @param propertyId - Property ID
|
|
||||||
*/
|
|
||||||
toggleProperty: (propertyId: string) => {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
properties: state.properties.map(c =>
|
|
||||||
c.id === propertyId ? { ...c, selected: !c.selected } : c
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Select all properties
|
|
||||||
*/
|
|
||||||
selectAllProperties: () => {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
properties: state.properties.map(c => ({ ...c, selected: true })),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Deselect all properties
|
|
||||||
*/
|
|
||||||
deselectAllProperties: () => {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
properties: state.properties.map(c => ({ ...c, selected: false })),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
120
src/shared/lib/utils/filter/createFilter/createFilter.svelte.ts
Normal file
120
src/shared/lib/utils/filter/createFilter/createFilter.svelte.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
|
|
||||||
|
export interface Property {
|
||||||
|
/**
|
||||||
|
* Property identifier
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Property name
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Property selected state
|
||||||
|
*/
|
||||||
|
selected?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterModel {
|
||||||
|
/**
|
||||||
|
* Search query
|
||||||
|
*/
|
||||||
|
searchQuery?: string;
|
||||||
|
/**
|
||||||
|
* Properties
|
||||||
|
*/
|
||||||
|
properties: Property[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a filter store.
|
||||||
|
* @param initialState - Initial state of the filter store
|
||||||
|
*/
|
||||||
|
export function createFilter<T extends FilterModel>(
|
||||||
|
initialState: T,
|
||||||
|
) {
|
||||||
|
let properties = $state(
|
||||||
|
initialState.properties.map(p => ({
|
||||||
|
...p,
|
||||||
|
selected: p.selected ?? false,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedProperties = $derived.by(() => {
|
||||||
|
const _ = properties;
|
||||||
|
return properties.filter(p => p.selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCount = $derived.by(() => {
|
||||||
|
const _ = properties;
|
||||||
|
return selectedProperties.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get all properties.
|
||||||
|
*/
|
||||||
|
get properties() {
|
||||||
|
return properties;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get selected properties.
|
||||||
|
*/
|
||||||
|
get selectedProperties() {
|
||||||
|
return selectedProperties;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get selected count.
|
||||||
|
*/
|
||||||
|
get selectedCount() {
|
||||||
|
return selectedCount;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Toggle property selection.
|
||||||
|
*/
|
||||||
|
toggleProperty: (id: string) => {
|
||||||
|
properties = properties.map(p => ({
|
||||||
|
...p,
|
||||||
|
selected: p.id === id ? !p.selected : p.selected,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Select property.
|
||||||
|
*/
|
||||||
|
selectProperty(id: string) {
|
||||||
|
properties = properties.map(p => ({
|
||||||
|
...p,
|
||||||
|
selected: p.id === id ? true : p.selected,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Deselect property.
|
||||||
|
*/
|
||||||
|
deselectProperty(id: string) {
|
||||||
|
properties = properties.map(p => ({
|
||||||
|
...p,
|
||||||
|
selected: p.id === id ? false : p.selected,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Select all properties.
|
||||||
|
*/
|
||||||
|
selectAll: () => {
|
||||||
|
properties = properties.map(p => ({
|
||||||
|
...p,
|
||||||
|
selected: true,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Deselect all properties.
|
||||||
|
*/
|
||||||
|
deselectAll: () => {
|
||||||
|
properties = properties.map(p => ({
|
||||||
|
...p,
|
||||||
|
selected: false,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Filter = ReturnType<typeof createFilter>;
|
||||||
@@ -7,4 +7,11 @@ export type {
|
|||||||
QueryParams,
|
QueryParams,
|
||||||
QueryParamValue,
|
QueryParamValue,
|
||||||
} from './buildQueryString';
|
} from './buildQueryString';
|
||||||
export { createVirtualizer } from './createVirtualizer/createVirtualizer';
|
export {
|
||||||
|
createVirtualizer,
|
||||||
|
type Virtualizer,
|
||||||
|
} from './createVirtualizer/createVirtualizer.svelte';
|
||||||
|
export {
|
||||||
|
createFilter,
|
||||||
|
type Filter,
|
||||||
|
} from './filter/createFilter/createFilter.svelte';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Property } from '$shared/lib/store';
|
import type { Filter } from '$shared/lib/utils';
|
||||||
import { Badge } from '$shared/shadcn/ui/badge';
|
import { Badge } from '$shared/shadcn/ui/badge';
|
||||||
import { buttonVariants } from '$shared/shadcn/ui/button';
|
import { buttonVariants } from '$shared/shadcn/ui/button';
|
||||||
import { Checkbox } from '$shared/shadcn/ui/checkbox';
|
import { Checkbox } from '$shared/shadcn/ui/checkbox';
|
||||||
@@ -8,6 +8,7 @@ import { Label } from '$shared/shadcn/ui/label';
|
|||||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { cubicOut } from 'svelte/easing';
|
import { cubicOut } from 'svelte/easing';
|
||||||
|
import type { FormEventHandler } from 'svelte/elements';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,13 +27,11 @@ import { slide } from 'svelte/transition';
|
|||||||
interface PropertyFilterProps {
|
interface PropertyFilterProps {
|
||||||
/** Label for this filter group (e.g., "Properties", "Tags") */
|
/** Label for this filter group (e.g., "Properties", "Tags") */
|
||||||
displayedLabel: string;
|
displayedLabel: string;
|
||||||
/** Array of properties with their selection states */
|
/** Filter entity */
|
||||||
properties: Property[];
|
filter: Filter;
|
||||||
/** Callback when a property checkbox is toggled */
|
|
||||||
onPropertyToggle: (id: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { displayedLabel, properties, onPropertyToggle }: PropertyFilterProps = $props();
|
const { displayedLabel, filter }: PropertyFilterProps = $props();
|
||||||
|
|
||||||
// Toggle state - defaults to open for better discoverability
|
// Toggle state - defaults to open for better discoverability
|
||||||
let isOpen = $state(true);
|
let isOpen = $state(true);
|
||||||
@@ -63,8 +62,10 @@ const slideConfig = $derived({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Derived for reactive updates when properties change - avoids recomputing on every render
|
// Derived for reactive updates when properties change - avoids recomputing on every render
|
||||||
const selectedCount = $derived(properties.filter(c => c.selected).length);
|
const selectedCount = $derived(filter.selectedCount);
|
||||||
const hasSelection = $derived(selectedCount > 0);
|
const hasSelection = $derived(selectedCount > 0);
|
||||||
|
|
||||||
|
$inspect(filter.properties).with(console.trace);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Collapsible card wrapper with subtle hover state for affordance -->
|
<!-- Collapsible card wrapper with subtle hover state for affordance -->
|
||||||
@@ -114,7 +115,7 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
<div class="flex flex-col gap-0.5">
|
<div class="flex flex-col gap-0.5">
|
||||||
<!-- Each item: checkbox + label with interactive hover/focus states -->
|
<!-- Each item: checkbox + label with interactive hover/focus states -->
|
||||||
<!-- Keyed by property.id for efficient DOM updates -->
|
<!-- Keyed by property.id for efficient DOM updates -->
|
||||||
{#each properties as property (property.id)}
|
{#each filter.properties as property (property.id)}
|
||||||
<Label
|
<Label
|
||||||
for={property.id}
|
for={property.id}
|
||||||
class="
|
class="
|
||||||
@@ -129,8 +130,7 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
-->
|
-->
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={property.id}
|
id={property.id}
|
||||||
checked={property.selected}
|
bind:checked={property.selected}
|
||||||
onclick={() => onPropertyToggle(property.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
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
* Exports all shared UI components and their types
|
* Exports all shared UI components and their types
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import CheckboxFilter from './CheckboxFilter/CheckboxFilter.svelte';
|
||||||
|
import ComboControl from './ComboControl/ComboControl.stories.svelte';
|
||||||
import VirtualList from './VirtualList/VirtualList.svelte';
|
import VirtualList from './VirtualList/VirtualList.svelte';
|
||||||
|
|
||||||
export { VirtualList };
|
export {
|
||||||
|
CheckboxFilter,
|
||||||
|
ComboControl,
|
||||||
|
VirtualList,
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,12 +10,16 @@
|
|||||||
* Buttons are equally sized (flex-1) for balanced layout. Note:
|
* Buttons are equally sized (flex-1) for balanced layout. Note:
|
||||||
* Functionality not yet implemented - wire up to filter stores.
|
* Functionality not yet implemented - wire up to filter stores.
|
||||||
*/
|
*/
|
||||||
import { clearAllFilters } from '$features/FilterFonts';
|
import { filterManager } from '$features/FilterFonts';
|
||||||
import { Button } from '$shared/shadcn/ui/button';
|
import { Button } from '$shared/shadcn/ui/button';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<Button variant="outline" class="flex-1 cursor-pointer" onclick={clearAllFilters}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
class="flex-1 cursor-pointer"
|
||||||
|
onclick={filterManager.deselectAllGlobal}
|
||||||
|
>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="flex-1 cursor-pointer">
|
<Button class="flex-1 cursor-pointer">
|
||||||
|
|||||||
@@ -12,31 +12,15 @@
|
|||||||
* Uses $derived for reactive access to filter states, ensuring UI updates
|
* Uses $derived for reactive access to filter states, ensuring UI updates
|
||||||
* when selections change through any means (sidebar, programmatically, etc.).
|
* when selections change through any means (sidebar, programmatically, etc.).
|
||||||
*/
|
*/
|
||||||
import { categoryFilterStore } from '$features/FilterFonts';
|
import { filterManager } from '$features/FilterFonts';
|
||||||
import { providersFilterStore } from '$features/FilterFonts';
|
import { CheckboxFilter } from '$shared/ui';
|
||||||
import { subsetsFilterStore } from '$features/FilterFonts';
|
|
||||||
import CheckboxFilter from '$shared/ui/CheckboxFilter/CheckboxFilter.svelte';
|
|
||||||
|
|
||||||
/** Reactive properties from providers filter store */
|
$inspect(filterManager.groups).with(console.trace);
|
||||||
const { properties: providers } = $derived($providersFilterStore);
|
|
||||||
/** Reactive properties from subsets filter store */
|
|
||||||
const { properties: subsets } = $derived($subsetsFilterStore);
|
|
||||||
/** Reactive properties from categories filter store */
|
|
||||||
const { properties: categories } = $derived($categoryFilterStore);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#each filterManager.groups as group (group.id)}
|
||||||
<CheckboxFilter
|
<CheckboxFilter
|
||||||
displayedLabel="Font provider"
|
displayedLabel={group.label}
|
||||||
properties={providers}
|
filter={group.instance}
|
||||||
onPropertyToggle={providersFilterStore.toggleProperty}
|
|
||||||
/>
|
|
||||||
<CheckboxFilter
|
|
||||||
displayedLabel="Font subset"
|
|
||||||
properties={subsets}
|
|
||||||
onPropertyToggle={subsetsFilterStore.toggleProperty}
|
|
||||||
/>
|
|
||||||
<CheckboxFilter
|
|
||||||
displayedLabel="Font category"
|
|
||||||
properties={categories}
|
|
||||||
onPropertyToggle={categoryFilterStore.toggleProperty}
|
|
||||||
/>
|
/>
|
||||||
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user