refactor(features, widgets): update ThemeManager, FontSampler, FontSearch, and SampleList
This commit is contained in:
@@ -1,3 +1,21 @@
|
||||
/**
|
||||
* Layout mode manager for SampleList widget
|
||||
*
|
||||
* Manages the display layout (list vs grid) for the sample list widget.
|
||||
* Persists user preference and provides responsive column calculations.
|
||||
*
|
||||
* Layout modes:
|
||||
* - List: Single column, full-width items
|
||||
* - Grid: Multi-column with responsive breakpoints
|
||||
*
|
||||
* Responsive grid columns:
|
||||
* - Mobile (< 640px): 1 column
|
||||
* - Tablet Portrait (640-767px): 1 column
|
||||
* - Tablet (768-1023px): 2 columns
|
||||
* - Desktop (1024-1279px): 3 columns
|
||||
* - Desktop Large (>= 1280px): 4 columns
|
||||
*/
|
||||
|
||||
import { createPersistentStore } from '$shared/lib';
|
||||
import { responsiveManager } from '$shared/lib';
|
||||
|
||||
@@ -16,12 +34,15 @@ const DEFAULT_CONFIG: LayoutConfig = {
|
||||
};
|
||||
|
||||
/**
|
||||
* LayoutManager manages the layout configuration for SampleList widget.
|
||||
* Handles mode switching between list/grid and responsive column calculation.
|
||||
* Layout manager for SampleList widget
|
||||
*
|
||||
* Handles mode switching between list/grid and responsive column
|
||||
* calculation. Persists user preference to localStorage.
|
||||
*/
|
||||
class LayoutManager {
|
||||
// Private reactive state
|
||||
/** Current layout mode */
|
||||
#mode = $state<LayoutMode>(DEFAULT_CONFIG.mode);
|
||||
/** Persistent storage for layout preference */
|
||||
#store = createPersistentStore<LayoutConfig>(STORAGE_KEY, DEFAULT_CONFIG);
|
||||
|
||||
constructor() {
|
||||
@@ -32,31 +53,34 @@ class LayoutManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Current layout mode ('list' or 'grid') */
|
||||
get mode(): LayoutMode {
|
||||
return this.#mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gap between items in pixels
|
||||
* Responsive: 16px on mobile, 24px on tablet+
|
||||
*/
|
||||
get gap(): number {
|
||||
return responsiveManager.isMobile || responsiveManager.isTabletPortrait ? SM_GAP_PX : MD_GAP_PX;
|
||||
}
|
||||
|
||||
/** Whether currently in list mode */
|
||||
get isListMode(): boolean {
|
||||
return this.#mode === 'list';
|
||||
}
|
||||
|
||||
/** Whether currently in grid mode */
|
||||
get isGridMode(): boolean {
|
||||
return this.#mode === 'grid';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current number of columns based on mode and screen size.
|
||||
* Current number of columns based on mode and screen size
|
||||
*
|
||||
* List mode always uses 1 column.
|
||||
* Grid mode uses responsive column counts:
|
||||
* - Mobile: 1 column
|
||||
* - Tablet Portrait: 1 column
|
||||
* - Tablet: 2 columns
|
||||
* - Desktop: 3 columns
|
||||
* - Desktop Large: 4 columns
|
||||
* Grid mode uses responsive column counts.
|
||||
*/
|
||||
get columns(): number {
|
||||
if (this.#mode === 'list') {
|
||||
@@ -81,7 +105,7 @@ class LayoutManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the layout mode.
|
||||
* Set the layout mode
|
||||
* @param mode - The new layout mode ('list' or 'grid')
|
||||
*/
|
||||
setMode(mode: LayoutMode): void {
|
||||
@@ -94,14 +118,14 @@ class LayoutManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle between list and grid modes.
|
||||
* Toggle between list and grid modes
|
||||
*/
|
||||
toggleMode(): void {
|
||||
this.setMode(this.#mode === 'list' ? 'grid' : 'list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to default layout mode.
|
||||
* Reset to default layout mode
|
||||
*/
|
||||
reset(): void {
|
||||
this.#mode = DEFAULT_CONFIG.mode;
|
||||
@@ -109,5 +133,10 @@ class LayoutManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Export a singleton — one instance for the whole app
|
||||
/**
|
||||
* Singleton layout manager instance
|
||||
*/
|
||||
export const layoutManager = new LayoutManager();
|
||||
|
||||
// Export class for testing purposes
|
||||
export { LayoutManager };
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
|
||||
// Helper to flush Svelte effects (they run in microtasks)
|
||||
async function flushEffects() {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
// Storage key used by LayoutManager
|
||||
const STORAGE_KEY = 'glyphdiff:sample-list-layout';
|
||||
|
||||
describe('layoutStore', () => {
|
||||
// Default viewport for most tests (desktop large - >= 1536px)
|
||||
const DEFAULT_WIDTH = 1600;
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear localStorage before each test
|
||||
localStorage.clear();
|
||||
|
||||
// Mock window.innerWidth for responsive manager
|
||||
// Default to desktop large (>= 1536px)
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: DEFAULT_WIDTH,
|
||||
});
|
||||
|
||||
// Trigger a resize event to update responsiveManager
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
|
||||
describe('Initialization', () => {
|
||||
it('should initialize with default list mode when no saved value', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
expect(manager.isListMode).toBe(true);
|
||||
expect(manager.isGridMode).toBe(false);
|
||||
});
|
||||
|
||||
it('should load saved grid mode from localStorage', async () => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ mode: 'grid' }));
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.mode).toBe('grid');
|
||||
expect(manager.isListMode).toBe(false);
|
||||
expect(manager.isGridMode).toBe(true);
|
||||
});
|
||||
|
||||
it('should load saved list mode from localStorage', async () => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ mode: 'list' }));
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
expect(manager.isListMode).toBe(true);
|
||||
expect(manager.isGridMode).toBe(false);
|
||||
});
|
||||
|
||||
it('should default to list mode when localStorage has invalid data', async () => {
|
||||
localStorage.setItem(STORAGE_KEY, 'invalid json');
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
});
|
||||
|
||||
it('should default to list mode when localStorage has empty object', async () => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({}));
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
});
|
||||
});
|
||||
|
||||
describe('columns', () => {
|
||||
it('should return 1 column in list mode regardless of screen size', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('list');
|
||||
|
||||
// At default viewport (1600px - desktop large)
|
||||
expect(manager.columns).toBe(1);
|
||||
});
|
||||
|
||||
describe('grid mode', () => {
|
||||
it('should return 1 column on mobile (< 640px)', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', { value: 320, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
await flushEffects();
|
||||
|
||||
expect(manager.columns).toBe(1);
|
||||
});
|
||||
|
||||
it('should return 1 column on tablet portrait (640-767px)', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', { value: 700, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
await flushEffects();
|
||||
|
||||
expect(manager.columns).toBe(1);
|
||||
});
|
||||
|
||||
it('should return 2 columns on tablet (768-1279px)', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', { value: 900, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
await flushEffects();
|
||||
|
||||
expect(manager.columns).toBe(2);
|
||||
});
|
||||
|
||||
it('should return 3 columns on desktop (1280-1535px)', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', { value: 1400, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
await flushEffects();
|
||||
|
||||
expect(manager.columns).toBe(3);
|
||||
});
|
||||
|
||||
it('should return 4 columns on desktop large (>= 1536px)', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
value: DEFAULT_WIDTH,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
await flushEffects();
|
||||
|
||||
expect(manager.columns).toBe(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gap', () => {
|
||||
it('should return 16px on mobile (< 640px)', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', { value: 320, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.gap).toBe(16);
|
||||
});
|
||||
|
||||
it('should return 16px on tablet portrait (640-767px)', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', { value: 700, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.gap).toBe(16);
|
||||
});
|
||||
|
||||
it('should return 24px on tablet and larger', async () => {
|
||||
Object.defineProperty(window, 'innerWidth', { value: 900, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.gap).toBe(24);
|
||||
|
||||
Object.defineProperty(window, 'innerWidth', { value: 1400, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
expect(manager.gap).toBe(24);
|
||||
|
||||
Object.defineProperty(window, 'innerWidth', { value: DEFAULT_WIDTH, configurable: true, writable: true });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await flushEffects();
|
||||
expect(manager.gap).toBe(24);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMode', () => {
|
||||
it('should change mode from list to grid', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
expect(manager.mode).toBe('list');
|
||||
|
||||
manager.setMode('grid');
|
||||
|
||||
expect(manager.mode).toBe('grid');
|
||||
expect(manager.isListMode).toBe(false);
|
||||
expect(manager.isGridMode).toBe(true);
|
||||
});
|
||||
|
||||
it('should change mode from grid to list', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
expect(manager.mode).toBe('grid');
|
||||
|
||||
manager.setMode('list');
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
expect(manager.isListMode).toBe(true);
|
||||
expect(manager.isGridMode).toBe(false);
|
||||
});
|
||||
|
||||
it('should persist mode to localStorage', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
manager.setMode('grid');
|
||||
await flushEffects();
|
||||
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
expect(stored).toBe(JSON.stringify({ mode: 'grid' }));
|
||||
});
|
||||
|
||||
it('should not do anything if setting the same mode', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
|
||||
// Store the current localStorage value
|
||||
const storedBefore = localStorage.getItem(STORAGE_KEY);
|
||||
|
||||
manager.setMode('grid');
|
||||
|
||||
// Mode should still be grid
|
||||
expect(manager.mode).toBe('grid');
|
||||
// localStorage should have the same value (no re-write)
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBe(storedBefore);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleMode', () => {
|
||||
it('should toggle from list to grid', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
expect(manager.mode).toBe('list');
|
||||
|
||||
manager.toggleMode();
|
||||
|
||||
expect(manager.mode).toBe('grid');
|
||||
});
|
||||
|
||||
it('should toggle from grid to list', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
expect(manager.mode).toBe('grid');
|
||||
|
||||
manager.toggleMode();
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
});
|
||||
|
||||
it('should persist toggled mode to localStorage', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
manager.toggleMode();
|
||||
await flushEffects();
|
||||
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
expect(stored).toBe(JSON.stringify({ mode: 'grid' }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset to default list mode', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
expect(manager.mode).toBe('grid');
|
||||
|
||||
manager.reset();
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
});
|
||||
|
||||
it('should clear localStorage', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
|
||||
// Wait for the effect to write to localStorage
|
||||
await flushEffects();
|
||||
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBe(JSON.stringify({ mode: 'grid' }));
|
||||
|
||||
manager.reset();
|
||||
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBe(null);
|
||||
});
|
||||
|
||||
it('should work when already at default mode', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
expect(manager.mode).toBe('list');
|
||||
|
||||
manager.reset();
|
||||
|
||||
expect(manager.mode).toBe('list');
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isListMode and isGridMode', () => {
|
||||
it('should return correct boolean states for list mode', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.isListMode).toBe(true);
|
||||
expect(manager.isGridMode).toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct boolean states for grid mode', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
manager.setMode('grid');
|
||||
|
||||
expect(manager.isListMode).toBe(false);
|
||||
expect(manager.isGridMode).toBe(true);
|
||||
});
|
||||
|
||||
it('should update boolean states when mode changes', async () => {
|
||||
const { LayoutManager } = await import('./layoutStore.svelte');
|
||||
const manager = new LayoutManager();
|
||||
|
||||
expect(manager.isListMode).toBe(true);
|
||||
expect(manager.isGridMode).toBe(false);
|
||||
|
||||
manager.toggleMode();
|
||||
|
||||
expect(manager.isListMode).toBe(false);
|
||||
expect(manager.isGridMode).toBe(true);
|
||||
|
||||
manager.setMode('list');
|
||||
|
||||
expect(manager.isListMode).toBe(true);
|
||||
expect(manager.isGridMode).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,7 @@
|
||||
<!--
|
||||
Component: LayoutSwitch
|
||||
Toggles between list and grid layout modes
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { ButtonGroup } from '$shared/ui';
|
||||
import { IconButton } from '$shared/ui';
|
||||
@@ -6,6 +10,9 @@ import ListIcon from '@lucide/svelte/icons/stretch-horizontal';
|
||||
import { layoutManager } from '../../model';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* CSS classes
|
||||
*/
|
||||
class?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,73 @@ const { Story } = defineMeta({
|
||||
story: { inline: false },
|
||||
},
|
||||
layout: 'fullscreen',
|
||||
viewport: {
|
||||
viewports: {
|
||||
mobile1: {
|
||||
name: 'iPhone 5/SE',
|
||||
styles: {
|
||||
width: '320px',
|
||||
height: '568px',
|
||||
},
|
||||
},
|
||||
mobile2: {
|
||||
name: 'iPhone 14 Pro Max',
|
||||
styles: {
|
||||
width: '414px',
|
||||
height: '896px',
|
||||
},
|
||||
},
|
||||
tablet: {
|
||||
name: 'iPad (Portrait)',
|
||||
styles: {
|
||||
width: '834px',
|
||||
height: '1112px',
|
||||
},
|
||||
},
|
||||
desktop: {
|
||||
name: 'Desktop (Small)',
|
||||
styles: {
|
||||
width: '1024px',
|
||||
height: '1280px',
|
||||
},
|
||||
},
|
||||
widgetMedium: {
|
||||
name: 'Widget Medium',
|
||||
styles: {
|
||||
width: '768px',
|
||||
height: '800px',
|
||||
},
|
||||
},
|
||||
widgetWide: {
|
||||
name: 'Widget Wide',
|
||||
styles: {
|
||||
width: '1024px',
|
||||
height: '800px',
|
||||
},
|
||||
},
|
||||
widgetExtraWide: {
|
||||
name: 'Widget Extra Wide',
|
||||
styles: {
|
||||
width: '1280px',
|
||||
height: '800px',
|
||||
},
|
||||
},
|
||||
fullWidth: {
|
||||
name: 'Full Width',
|
||||
styles: {
|
||||
width: '100%',
|
||||
height: '800px',
|
||||
},
|
||||
},
|
||||
fullScreen: {
|
||||
name: 'Full Screen',
|
||||
styles: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
// This component uses internal stores, so no direct props to document
|
||||
@@ -22,7 +89,7 @@ const { Story } = defineMeta({
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
<Story name="Default" parameters={{ globals: { viewport: 'fullScreen' } }}>
|
||||
<div class="min-h-screen bg-background">
|
||||
<div class="max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-8 text-center">
|
||||
@@ -34,13 +101,13 @@ const { Story } = defineMeta({
|
||||
</div>
|
||||
</Story>
|
||||
|
||||
<Story name="Full Page">
|
||||
<Story name="Full Page" parameters={{ globals: { viewport: 'fullScreen' } }}>
|
||||
<div class="min-h-screen bg-background">
|
||||
<SampleList />
|
||||
</div>
|
||||
</Story>
|
||||
|
||||
<Story name="With Typography Controls">
|
||||
<Story name="With Typography Controls" parameters={{ globals: { viewport: 'fullScreen' } }}>
|
||||
<div class="min-h-screen bg-background">
|
||||
<div class="max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-8 text-center">
|
||||
@@ -52,7 +119,7 @@ const { Story } = defineMeta({
|
||||
</div>
|
||||
</Story>
|
||||
|
||||
<Story name="Custom Text">
|
||||
<Story name="Custom Text" parameters={{ globals: { viewport: 'fullScreen' } }}>
|
||||
<div class="min-h-screen bg-background">
|
||||
<div class="max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-8 text-center">
|
||||
@@ -64,7 +131,7 @@ const { Story } = defineMeta({
|
||||
</div>
|
||||
</Story>
|
||||
|
||||
<Story name="Pagination Info">
|
||||
<Story name="Pagination Info" parameters={{ globals: { viewport: 'fullScreen' } }}>
|
||||
<div class="min-h-screen bg-background">
|
||||
<div class="max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-8 text-center">
|
||||
@@ -76,7 +143,7 @@ const { Story } = defineMeta({
|
||||
</div>
|
||||
</Story>
|
||||
|
||||
<Story name="Responsive Layout">
|
||||
<Story name="Responsive Layout" parameters={{ globals: { viewport: 'fullScreen' } }}>
|
||||
<div class="min-h-screen bg-background">
|
||||
<div class="max-w-6xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-8 text-center">
|
||||
|
||||
@@ -3,36 +3,57 @@
|
||||
Wraps SampleList with a Section component
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { handleTitleStatusChanged } from '$entities/Breadcrumb';
|
||||
import { NavigationWrapper } from '$entities/Breadcrumb';
|
||||
import { unifiedFontStore } from '$entities/Font';
|
||||
import type { ResponsiveManager } from '$shared/lib';
|
||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||
import { Section } from '$shared/ui';
|
||||
import {
|
||||
type Snippet,
|
||||
getContext,
|
||||
} from 'svelte';
|
||||
Label,
|
||||
Section,
|
||||
} from '$shared/ui';
|
||||
import { getContext } from 'svelte';
|
||||
import { layoutManager } from '../../model';
|
||||
import LayoutSwitch from '../LayoutSwitch/LayoutSwitch.svelte';
|
||||
import SampleList from '../SampleList/SampleList.svelte';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Section index
|
||||
*/
|
||||
index: number;
|
||||
}
|
||||
|
||||
const { index }: Props = $props();
|
||||
|
||||
const responsive = getContext<ResponsiveManager>('responsive');
|
||||
</script>
|
||||
|
||||
<Section
|
||||
class="py-4 sm:py-10 md:py-12 gap-6 sm:gap-x-12 sm:gap-y-8"
|
||||
index={3}
|
||||
id="sample_set"
|
||||
onTitleStatusChange={handleTitleStatusChanged}
|
||||
title="Sample Set"
|
||||
headerTitle="visual_output"
|
||||
headerSubtitle="render_engine:"
|
||||
>
|
||||
{#snippet headerContent()}
|
||||
<LayoutSwitch />
|
||||
{/snippet}
|
||||
<NavigationWrapper index={2} title="Samples">
|
||||
{#snippet content(registerAction)}
|
||||
<Section
|
||||
class="py-4 sm:py-10 md:py-12 gap-6 sm:gap-x-12 sm:gap-y-8"
|
||||
{index}
|
||||
id="sample_set"
|
||||
title="Sample Set"
|
||||
headerTitle="visual_output"
|
||||
headerSubtitle="items_total: {unifiedFontStore.pagination.total ?? 0}"
|
||||
headerAction={registerAction}
|
||||
>
|
||||
{#snippet headerContent()}
|
||||
<div class="flex items-center gap-3 md:gap-4">
|
||||
<div class="hidden md:flex items-center gap-2 mr-4">
|
||||
<Label variant="muted" size="sm">view_mode: </Label>
|
||||
<Label variant="default" size="sm" bold>{layoutManager.mode}</Label>
|
||||
</div>
|
||||
<LayoutSwitch />
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet content({ className })}
|
||||
<div class={cn(className, !responsive.isDesktopLarge && 'col-start-0 col-span-2')}>
|
||||
<SampleList />
|
||||
</div>
|
||||
{#snippet content({ className })}
|
||||
<div class={cn(className, !responsive.isDesktopLarge && 'col-start-0 col-span-2')}>
|
||||
<SampleList />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Section>
|
||||
{/snippet}
|
||||
</Section>
|
||||
</NavigationWrapper>
|
||||
|
||||
Reference in New Issue
Block a user