Refactor/reacrhitecture to fsd+ #49
@@ -1,2 +1,2 @@
|
|||||||
export { layoutManager } from './stores';
|
export { getLayoutManager } from './stores';
|
||||||
export type { LayoutMode } from './stores';
|
export type { LayoutMode } from './stores';
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { layoutManager } from './layoutStore/layoutStore.svelte';
|
export { getLayoutManager } from './layoutStore/layoutStore.svelte';
|
||||||
export type { LayoutMode } from './layoutStore/layoutStore.svelte';
|
export type { LayoutMode } from './layoutStore/layoutStore.svelte';
|
||||||
|
|||||||
@@ -146,10 +146,20 @@ class LayoutManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _layoutManager: LayoutManager | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton layout manager instance
|
* App-wide layout manager, created on first access. Lazy so its persisted
|
||||||
|
* layout preference isn't read at module load.
|
||||||
*/
|
*/
|
||||||
export const layoutManager = new LayoutManager();
|
export function getLayoutManager(): LayoutManager {
|
||||||
|
return (_layoutManager ??= new LayoutManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
// test-only reset, so specs don't share persisted layout state
|
||||||
|
export function __resetLayoutManager() {
|
||||||
|
_layoutManager = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Export class for testing purposes
|
// Export class for testing purposes
|
||||||
export { LayoutManager };
|
export { LayoutManager };
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ButtonGroup } from '$shared/ui';
|
|||||||
import { IconButton } from '$shared/ui';
|
import { IconButton } from '$shared/ui';
|
||||||
import GridIcon from '@lucide/svelte/icons/layout-grid';
|
import GridIcon from '@lucide/svelte/icons/layout-grid';
|
||||||
import ListIcon from '@lucide/svelte/icons/stretch-horizontal';
|
import ListIcon from '@lucide/svelte/icons/stretch-horizontal';
|
||||||
import { layoutManager } from '../../model';
|
import { getLayoutManager } from '../../model';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/**
|
/**
|
||||||
@@ -18,18 +18,21 @@ interface Props {
|
|||||||
|
|
||||||
const { class: className }: Props = $props();
|
const { class: className }: Props = $props();
|
||||||
|
|
||||||
|
const layoutManager = getLayoutManager();
|
||||||
|
const mode = $derived(layoutManager.mode);
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
layoutManager.toggleMode();
|
layoutManager.toggleMode();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ButtonGroup class={className}>
|
<ButtonGroup class={className}>
|
||||||
<IconButton active={layoutManager.mode === 'list'} onclick={handleClick}>
|
<IconButton active={mode === 'list'} onclick={handleClick}>
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<ListIcon class="size-4" />
|
<ListIcon class="size-4" />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton active={layoutManager.mode === 'grid'} onclick={handleClick}>
|
<IconButton active={mode === 'grid'} onclick={handleClick}>
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<GridIcon class="size-4" />
|
<GridIcon class="size-4" />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -4,14 +4,23 @@ import {
|
|||||||
screen,
|
screen,
|
||||||
waitFor,
|
waitFor,
|
||||||
} from '@testing-library/svelte';
|
} from '@testing-library/svelte';
|
||||||
import { layoutManager } from '../../model';
|
import { afterEach } from 'vitest';
|
||||||
|
import { getLayoutManager } from '../../model';
|
||||||
|
import { __resetLayoutManager } from '../../model/stores/layoutStore/layoutStore.svelte';
|
||||||
import LayoutSwitch from './LayoutSwitch.svelte';
|
import LayoutSwitch from './LayoutSwitch.svelte';
|
||||||
|
|
||||||
describe('LayoutSwitch', () => {
|
describe('LayoutSwitch', () => {
|
||||||
|
let layoutManager: ReturnType<typeof getLayoutManager>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
layoutManager = getLayoutManager();
|
||||||
layoutManager.reset();
|
layoutManager.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
__resetLayoutManager();
|
||||||
|
});
|
||||||
|
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
it('renders two icon buttons', () => {
|
it('renders two icon buttons', () => {
|
||||||
render(LayoutSwitch);
|
render(LayoutSwitch);
|
||||||
|
|||||||
@@ -20,11 +20,15 @@ import {
|
|||||||
import { FontSampler } from '$features/DisplayFont';
|
import { FontSampler } from '$features/DisplayFont';
|
||||||
import { throttle } from '$shared/lib/utils';
|
import { throttle } from '$shared/lib/utils';
|
||||||
import { Skeleton } from '$shared/ui';
|
import { Skeleton } from '$shared/ui';
|
||||||
import { layoutManager } from '../../model';
|
import { getLayoutManager } from '../../model';
|
||||||
|
|
||||||
const fontCatalog = getFontCatalog();
|
const fontCatalog = getFontCatalog();
|
||||||
const typographySettingsStore = getTypographySettingsStore();
|
const typographySettingsStore = getTypographySettingsStore();
|
||||||
const fontLifecycleManager = getFontLifecycleManager();
|
const fontLifecycleManager = getFontLifecycleManager();
|
||||||
|
const layoutManager = getLayoutManager();
|
||||||
|
|
||||||
|
const columns = $derived(layoutManager.columns);
|
||||||
|
const gap = $derived(layoutManager.gap);
|
||||||
|
|
||||||
// FontSampler chrome heights — derived from Tailwind classes in FontSampler.svelte.
|
// FontSampler chrome heights — derived from Tailwind classes in FontSampler.svelte.
|
||||||
// Header: py-3 (12+12px padding) + ~32px content row ≈ 56px.
|
// Header: py-3 (12+12px padding) + ~32px content row ≈ 56px.
|
||||||
@@ -114,8 +118,8 @@ const fontRowHeight = $derived.by(() =>
|
|||||||
itemHeight={fontRowHeight}
|
itemHeight={fontRowHeight}
|
||||||
useWindowScroll={true}
|
useWindowScroll={true}
|
||||||
weight={typographySettingsStore.weight}
|
weight={typographySettingsStore.weight}
|
||||||
columns={layoutManager.columns}
|
{columns}
|
||||||
gap={layoutManager.gap}
|
{gap}
|
||||||
{skeleton}
|
{skeleton}
|
||||||
>
|
>
|
||||||
{#snippet children({ item: font, index })}
|
{#snippet children({ item: font, index })}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
} from '$shared/ui';
|
} from '$shared/ui';
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import { layoutManager } from '../../model';
|
import { getLayoutManager } from '../../model';
|
||||||
import LayoutSwitch from '../LayoutSwitch/LayoutSwitch.svelte';
|
import LayoutSwitch from '../LayoutSwitch/LayoutSwitch.svelte';
|
||||||
import SampleList from '../SampleList/SampleList.svelte';
|
import SampleList from '../SampleList/SampleList.svelte';
|
||||||
|
|
||||||
@@ -29,6 +29,9 @@ const responsive = getContext<ResponsiveManager>('responsive');
|
|||||||
|
|
||||||
const fontCatalog = getFontCatalog();
|
const fontCatalog = getFontCatalog();
|
||||||
const total = $derived<number>(fontCatalog?.pagination?.total);
|
const total = $derived<number>(fontCatalog?.pagination?.total);
|
||||||
|
|
||||||
|
const layoutManager = getLayoutManager();
|
||||||
|
const mode = $derived(layoutManager.mode);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NavigationWrapper index={2} title="Samples">
|
<NavigationWrapper index={2} title="Samples">
|
||||||
@@ -46,7 +49,7 @@ const total = $derived<number>(fontCatalog?.pagination?.total);
|
|||||||
<div class="flex items-center gap-3 md:gap-4">
|
<div class="flex items-center gap-3 md:gap-4">
|
||||||
<div class="hidden md:flex items-center gap-2 mr-4">
|
<div class="hidden md:flex items-center gap-2 mr-4">
|
||||||
<Label variant="muted" size="sm">view_mode: </Label>
|
<Label variant="muted" size="sm">view_mode: </Label>
|
||||||
<Label variant="default" size="sm" bold>{layoutManager.mode}</Label>
|
<Label variant="default" size="sm" bold>{mode}</Label>
|
||||||
</div>
|
</div>
|
||||||
<LayoutSwitch />
|
<LayoutSwitch />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user