feat: add missing storybook files and type template arguments properly

This commit is contained in:
Ilia Mashkov
2026-04-17 18:01:24 +03:00
parent 5eb9584797
commit bb65f1c8d6
31 changed files with 1124 additions and 90 deletions

View File

@@ -0,0 +1,65 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import BreadcrumbHeader from './BreadcrumbHeader.svelte';
const { Story } = defineMeta({
title: 'Entities/BreadcrumbHeader',
component: BreadcrumbHeader,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Fixed header that slides in when the user scrolls past tracked page sections. Reads `scrollBreadcrumbsStore.scrolledPastItems` — renders nothing when the list is empty. Requires the `responsive` context provided by `Providers`.',
},
story: { inline: false },
},
layout: 'fullscreen',
},
argTypes: {},
});
</script>
<script>
import Providers from '$shared/lib/storybook/Providers.svelte';
import BreadcrumbHeaderSeeded from './BreadcrumbHeaderSeeded.svelte';
</script>
<Story
name="With Breadcrumbs"
parameters={{
docs: {
description: {
story:
'Three sections are registered with the breadcrumb store. The story scrolls the iframe so the IntersectionObserver marks them as scrolled-past, revealing the fixed header.',
},
},
}}
>
{#snippet template()}
<Providers>
<BreadcrumbHeaderSeeded />
</Providers>
{/snippet}
</Story>
<Story
name="Empty"
parameters={{
docs: {
description: {
story:
'No sections registered — BreadcrumbHeader renders nothing. This is the initial state before the user scrolls past any tracked section.',
},
},
}}
>
{#snippet template()}
<Providers>
<div style="padding: 2rem; color: #888; font-size: 0.875rem;">
BreadcrumbHeader renders nothing when scrolledPastItems is empty.
</div>
<BreadcrumbHeader />
</Providers>
{/snippet}
</Story>

View File

@@ -0,0 +1,49 @@
<script>
import { onMount } from 'svelte';
import { scrollBreadcrumbsStore } from '../../model';
import BreadcrumbHeader from './BreadcrumbHeader.svelte';
const sections = [
{ index: 100, title: 'Introduction' },
{ index: 101, title: 'Typography' },
{ index: 102, title: 'Spacing' },
];
/** @type {HTMLDivElement} */
let container;
onMount(() => {
for (const section of sections) {
const el = /** @type {HTMLElement} */ (container.querySelector(`[data-story-index="${section.index}"]`));
scrollBreadcrumbsStore.add({ index: section.index, title: section.title, element: el }, 96);
}
/*
* Scroll past the sections so IntersectionObserver marks them as
* scrolled-past, making scrolledPastItems non-empty and the header visible.
*/
setTimeout(() => {
window.scrollTo({ top: 2000, behavior: 'instant' });
}, 100);
return () => {
for (const { index } of sections) {
scrollBreadcrumbsStore.remove(index);
}
window.scrollTo({ top: 0, behavior: 'instant' });
};
});
</script>
<div bind:this={container} style="height: 2400px; padding-top: 900px;">
{#each sections as section}
<div
data-story-index={section.index}
style="height: 400px; padding: 2rem; background: #f5f5f5; margin-bottom: 1rem;"
>
{section.title} — scroll up to see the breadcrumb header
</div>
{/each}
</div>
<BreadcrumbHeader />

View File

@@ -0,0 +1,109 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import NavigationWrapper from './NavigationWrapper.svelte';
const { Story } = defineMeta({
title: 'Entities/NavigationWrapper',
component: NavigationWrapper,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Thin wrapper that registers an HTML section with `scrollBreadcrumbsStore` via a Svelte use-directive action. Has no visual output of its own — renders `{@render content(registerBreadcrumb)}` where `registerBreadcrumb` is the action to attach with `use:`. On destroy the section is automatically removed from the store.',
},
story: { inline: false },
},
layout: 'fullscreen',
},
argTypes: {
index: {
control: { type: 'number', min: 0 },
description: 'Unique index used for ordering in the breadcrumb trail',
},
title: {
control: 'text',
description: 'Display title shown in the breadcrumb header',
},
offset: {
control: { type: 'number', min: 0 },
description: 'Scroll offset in pixels to account for sticky headers',
},
},
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story
name="Single Section"
args={{ index: 0, title: 'Introduction', offset: 96 }}
parameters={{
docs: {
description: {
story:
'A single section registered with NavigationWrapper. The `content` snippet receives the `register` action and applies it via `use:register`.',
},
},
}}
>
{#snippet template(args: ComponentProps<typeof NavigationWrapper>)}
<NavigationWrapper {...args}>
{#snippet content(register)}
<section use:register style="padding: 2rem; background: #f5f5f5; min-height: 200px;">
<p style="font-size: 0.875rem; color: #555;">
Section registered as <strong>{args.title}</strong> at index {args.index}. Scroll past this
section to see it appear in the breadcrumb header.
</p>
</section>
{/snippet}
</NavigationWrapper>
{/snippet}
</Story>
<Story
name="Multiple Sections"
parameters={{
docs: {
description: {
story:
'Three sequential sections each wrapped in NavigationWrapper with distinct indices and titles. Demonstrates how the breadcrumb trail builds as the user scrolls.',
},
},
}}
>
{#snippet template()}
<div style="display: flex; flex-direction: column; gap: 0;">
<NavigationWrapper index={0} title="Introduction" offset={96}>
{#snippet content(register)}
<section use:register style="padding: 2rem; background: #f5f5f5; min-height: 300px;">
<h2 style="margin: 0 0 0.5rem;">Introduction</h2>
<p style="font-size: 0.875rem; color: #555;">
Registered as section 0. Scroll down to build the breadcrumb trail.
</p>
</section>
{/snippet}
</NavigationWrapper>
<NavigationWrapper index={1} title="Typography" offset={96}>
{#snippet content(register)}
<section use:register style="padding: 2rem; background: #ebebeb; min-height: 300px;">
<h2 style="margin: 0 0 0.5rem;">Typography</h2>
<p style="font-size: 0.875rem; color: #555;">Registered as section 1.</p>
</section>
{/snippet}
</NavigationWrapper>
<NavigationWrapper index={2} title="Spacing" offset={96}>
{#snippet content(register)}
<section use:register style="padding: 2rem; background: #e0e0e0; min-height: 300px;">
<h2 style="margin: 0 0 0.5rem;">Spacing</h2>
<p style="font-size: 0.875rem; color: #555;">Registered as section 2.</p>
</section>
{/snippet}
</NavigationWrapper>
</div>
{/snippet}
</Story>

View File

@@ -0,0 +1,91 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import FontApplicator from './FontApplicator.svelte';
const { Story } = defineMeta({
title: 'Entities/FontApplicator',
component: FontApplicator,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Loads a font and applies it to children. Shows blur/scale loading state until font is ready, then reveals with a smooth transition.',
},
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
weight: { control: 'number' },
},
});
</script>
<script lang="ts">
import { mockUnifiedFont } from '$entities/Font/lib/mocks';
import type { ComponentProps } from 'svelte';
const fontUnknown = mockUnifiedFont({ id: 'nonexistent-font-xk92z', name: 'Nonexistent Font Xk92z' });
const fontArial = mockUnifiedFont({ id: 'arial', name: 'Arial' });
const fontArialBold = mockUnifiedFont({ id: 'arial-bold', name: 'Arial' });
</script>
<Story
name="Loading State"
parameters={{
docs: {
description: {
story:
'Font that has never been loaded by appliedFontsManager. The component renders in its pending state: blurred, scaled down, and semi-transparent.',
},
},
}}
args={{ font: fontUnknown, weight: 400 }}
>
{#snippet template(args: ComponentProps<typeof FontApplicator>)}
<FontApplicator {...args}>
<p class="text-xl">The quick brown fox jumps over the lazy dog</p>
</FontApplicator>
{/snippet}
</Story>
<Story
name="Loaded State"
parameters={{
docs: {
description: {
story:
'Uses Arial, a system font available in all browsers. Because appliedFontsManager has not loaded it via FontFace, the manager status may remain pending — meaning the blur/scale state may still show. In a real app the manager would load the font and transition to the revealed state.',
},
},
}}
args={{ font: fontArial, weight: 400 }}
>
{#snippet template(args: ComponentProps<typeof FontApplicator>)}
<FontApplicator {...args}>
<p class="text-xl">The quick brown fox jumps over the lazy dog</p>
</FontApplicator>
{/snippet}
</Story>
<Story
name="Custom Weight"
parameters={{
docs: {
description: {
story:
'Demonstrates passing a custom weight (700). The weight is forwarded to appliedFontsManager for font resolution; visually identical to the loaded state story until the manager confirms the font.',
},
},
}}
args={{ font: fontArialBold, weight: 700 }}
>
{#snippet template(args: ComponentProps<typeof FontApplicator>)}
<FontApplicator {...args}>
<p class="text-xl">The quick brown fox jumps over the lazy dog</p>
</FontApplicator>
{/snippet}
</Story>

View File

@@ -0,0 +1,114 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import FontVirtualList from './FontVirtualList.svelte';
const { Story } = defineMeta({
title: 'Entities/FontVirtualList',
component: FontVirtualList,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Virtualized font list backed by the `fontStore` singleton. Handles font loading registration (pin/touch) for visible items and triggers infinite scroll pagination via `fontStore.nextPage()`. Because the component reads directly from the `fontStore` singleton, stories render against a live (but empty/loading) store — no font data will appear unless the API is reachable from the Storybook host.',
},
story: { inline: false },
},
layout: 'padded',
},
argTypes: {
weight: { control: 'number', description: 'Font weight applied to visible fonts' },
itemHeight: { control: 'number', description: 'Height of each list item in pixels' },
},
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story
name="Loading Skeleton"
parameters={{
docs: {
description: {
story:
'Skeleton state shown while `fontStore.fonts` is empty and `fontStore.isLoading` is true. In a real session the skeleton fades out once the first page loads.',
},
},
}}
args={{ weight: 400, itemHeight: 72 }}
>
{#snippet template(args: ComponentProps<typeof FontVirtualList>)}
<div class="h-[400px] w-full">
<FontVirtualList {...args}>
{#snippet skeleton()}
<div class="flex flex-col gap-2 p-4">
{#each Array(6) as _}
<div class="h-16 animate-pulse rounded bg-neutral-200"></div>
{/each}
</div>
{/snippet}
{#snippet children({ item })}
<div class="border-b border-neutral-100 p-3">{item.name}</div>
{/snippet}
</FontVirtualList>
</div>
{/snippet}
</Story>
<Story
name="Empty State"
parameters={{
docs: {
description: {
story:
'No `skeleton` snippet provided. When `fontStore.fonts` is empty the underlying VirtualList renders its empty state directly.',
},
},
}}
args={{ weight: 400, itemHeight: 72 }}
>
{#snippet template(args: ComponentProps<typeof FontVirtualList>)}
<div class="h-[400px] w-full">
<FontVirtualList {...args}>
{#snippet children({ item })}
<div class="border-b border-neutral-100 p-3">{item.name}</div>
{/snippet}
</FontVirtualList>
</div>
{/snippet}
</Story>
<Story
name="With Item Renderer"
parameters={{
docs: {
description: {
story:
'Demonstrates how to configure a `children` snippet for item rendering. The list will be empty because `fontStore` is not populated in Storybook, but the template shows the expected slot shape: `{ item: UnifiedFont }`.',
},
},
}}
args={{ weight: 400, itemHeight: 80 }}
>
{#snippet template(args: ComponentProps<typeof FontVirtualList>)}
<div class="h-[400px] w-full">
<FontVirtualList {...args}>
{#snippet skeleton()}
<div class="flex flex-col gap-2 p-4">
{#each Array(6) as _}
<div class="h-16 animate-pulse rounded bg-neutral-200"></div>
{/each}
</div>
{/snippet}
{#snippet children({ item })}
<div class="flex items-center justify-between border-b border-neutral-100 px-4 py-3">
<span class="text-sm font-medium">{item.name}</span>
<span class="text-xs text-neutral-400">{item.category}</span>
</div>
{/snippet}
</FontVirtualList>
</div>
{/snippet}
</Story>

View File

@@ -35,6 +35,7 @@ const { Story } = defineMeta({
<script lang="ts">
import type { UnifiedFont } from '$entities/Font';
import type { ComponentProps } from 'svelte';
// Mock fonts for testing
const mockArial: UnifiedFont = {
@@ -88,7 +89,7 @@ const mockGeorgia: UnifiedFont = {
index: 0,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof FontSampler>)}
<Providers>
<div class="max-w-2xl mx-auto">
<FontSampler {...args} />
@@ -105,7 +106,7 @@ const mockGeorgia: UnifiedFont = {
index: 1,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof FontSampler>)}
<Providers>
<div class="max-w-2xl mx-auto">
<FontSampler {...args} />

View File

@@ -0,0 +1,26 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Filters from './Filters.svelte';
const { Story } = defineMeta({
title: 'Features/Filters',
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Renders the full list of filter groups managed by filterManager. Each group maps to a collapsible FilterGroup with checkboxes. No props — reads directly from the filterManager singleton.',
},
story: { inline: false },
},
layout: 'padded',
},
argTypes: {},
});
</script>
<Story name="Default">
{#snippet template()}
<Filters />
{/snippet}
</Story>

View File

@@ -0,0 +1,39 @@
<script module>
import Providers from '$shared/lib/storybook/Providers.svelte';
import { defineMeta } from '@storybook/addon-svelte-csf';
import FilterControls from './FilterControls.svelte';
const { Story } = defineMeta({
title: 'Features/FilterControls',
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Sort options and Reset_Filters button rendered below the filter list. Reads sort state from sortStore and dispatches resets via filterManager. Requires responsive context — wrap with Providers.',
},
story: { inline: false },
},
layout: 'padded',
},
argTypes: {},
});
</script>
<Story name="Default">
{#snippet template()}
<Providers>
<FilterControls />
</Providers>
{/snippet}
</Story>
<Story name="Mobile layout">
{#snippet template()}
<Providers initialWidth={375}>
<div style="width: 375px;">
<FilterControls />
</div>
</Providers>
{/snippet}
</Story>

View File

@@ -0,0 +1,54 @@
<script module>
import Providers from '$shared/lib/storybook/Providers.svelte';
import { defineMeta } from '@storybook/addon-svelte-csf';
import TypographyMenu from './TypographyMenu.svelte';
const { Story } = defineMeta({
title: 'Features/TypographyMenu',
component: TypographyMenu,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Floating typography controls. Mobile/tablet: settings button that opens a popover. Desktop: inline bar with combo controls.',
},
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
hidden: { control: 'boolean' },
},
});
</script>
<Story name="Desktop">
{#snippet template()}
<Providers>
<div class="relative h-20 flex items-end justify-center p-4">
<TypographyMenu />
</div>
</Providers>
{/snippet}
</Story>
<Story name="Mobile">
{#snippet template()}
<Providers initialWidth={375}>
<div class="relative h-20 flex items-end justify-end p-4">
<TypographyMenu />
</div>
</Providers>
{/snippet}
</Story>
<Story name="Hidden">
{#snippet template()}
<Providers>
<div class="relative h-20 flex items-end justify-center p-4">
<TypographyMenu hidden={true} />
</div>
</Providers>
{/snippet}
</Story>

View File

@@ -45,6 +45,7 @@ const { Story } = defineMeta({
<script lang="ts">
import XIcon from '@lucide/svelte/icons/x';
import type { ComponentProps } from 'svelte';
import ButtonGroup from './ButtonGroup.svelte';
</script>
@@ -52,7 +53,7 @@ import ButtonGroup from './ButtonGroup.svelte';
name="Default/Basic"
parameters={{ docs: { description: { story: 'Standard text button at all sizes' } } }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Button>)}
<ButtonGroup>
<Button {...args} size="xs">xs</Button>
<Button {...args} size="sm">sm</Button>
@@ -67,7 +68,7 @@ import ButtonGroup from './ButtonGroup.svelte';
name="Default/With Icon"
args={{ variant: 'secondary', size: 'md', iconPosition: 'left', active: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Button>)}
<Button {...args}>
{#snippet icon()}
<XIcon />
@@ -81,7 +82,7 @@ import ButtonGroup from './ButtonGroup.svelte';
name="Primary"
args={{ variant: 'primary', size: 'md', iconPosition: 'left', active: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Button>)}
<Button {...args}>Primary</Button>
{/snippet}
</Story>
@@ -90,7 +91,7 @@ import ButtonGroup from './ButtonGroup.svelte';
name="Secondary"
args={{ variant: 'secondary', size: 'md', iconPosition: 'left', active: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Button>)}
<Button {...args}>Secondary</Button>
{/snippet}
</Story>
@@ -99,7 +100,7 @@ import ButtonGroup from './ButtonGroup.svelte';
name="Icon"
args={{ variant: 'icon', size: 'md', iconPosition: 'left', active: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Button>)}
<Button {...args}>
{#snippet icon()}
<XIcon />
@@ -112,7 +113,7 @@ import ButtonGroup from './ButtonGroup.svelte';
name="Ghost"
args={{ variant: 'ghost', size: 'md', iconPosition: 'left', active: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Button>)}
<Button {...args}>
Ghost
</Button>

View File

@@ -27,10 +27,11 @@ const { Story } = defineMeta({
<script lang="ts">
import { Button } from '$shared/ui/Button';
import type { ComponentProps } from 'svelte';
</script>
<Story name="Default">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ButtonGroup>)}
<ButtonGroup {...args}>
<Button variant="tertiary">Option 1</Button>
<Button variant="tertiary">Option 2</Button>
@@ -40,7 +41,7 @@ import { Button } from '$shared/ui/Button';
</Story>
<Story name="Horizontal">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ButtonGroup>)}
<ButtonGroup {...args}>
<Button variant="tertiary">Day</Button>
<Button variant="tertiary" active>Week</Button>
@@ -51,7 +52,7 @@ import { Button } from '$shared/ui/Button';
</Story>
<Story name="Vertical">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ButtonGroup>)}
<ButtonGroup {...args} class="flex-col">
<Button variant="tertiary">Top</Button>
<Button variant="tertiary" active>Middle</Button>
@@ -61,7 +62,7 @@ import { Button } from '$shared/ui/Button';
</Story>
<Story name="With Icons">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ButtonGroup>)}
<ButtonGroup {...args}>
<Button variant="tertiary">Grid</Button>
<Button variant="tertiary" active>List</Button>
@@ -78,7 +79,7 @@ import { Button } from '$shared/ui/Button';
},
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ButtonGroup>)}
<div class="p-8 bg-background text-foreground">
<h3 class="mb-4 text-sm font-medium">Dark Mode</h3>
<ButtonGroup {...args}>

View File

@@ -43,13 +43,14 @@ const { Story } = defineMeta({
import MoonIcon from '@lucide/svelte/icons/moon';
import SearchIcon from '@lucide/svelte/icons/search';
import TrashIcon from '@lucide/svelte/icons/trash-2';
import type { ComponentProps } from 'svelte';
</script>
<Story
name="Default"
args={{ variant: 'icon', size: 'md', active: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof IconButton>)}
<div class="flex items-center gap-4">
<IconButton {...args}>
{#snippet icon()}
@@ -74,7 +75,7 @@ import TrashIcon from '@lucide/svelte/icons/trash-2';
name="Variants"
args={{ size: 'md', active: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof IconButton>)}
<div class="flex items-center gap-4">
<IconButton {...args} variant="icon">
{#snippet icon()}
@@ -99,7 +100,7 @@ import TrashIcon from '@lucide/svelte/icons/trash-2';
name="Active State"
args={{ size: 'md', animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof IconButton>)}
<div class="flex items-center gap-4">
<IconButton {...args} active={false} variant="icon">
{#snippet icon()}
@@ -123,7 +124,7 @@ import TrashIcon from '@lucide/svelte/icons/trash-2';
},
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof IconButton>)}
<div class="p-8 bg-background text-foreground">
<h3 class="mb-4 text-sm font-medium">Dark Mode</h3>
<div class="flex items-center gap-4">

View File

@@ -44,6 +44,7 @@ const { Story } = defineMeta({
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
let selected = $state(false);
</script>
@@ -51,7 +52,7 @@ let selected = $state(false);
name="Default"
args={{ variant: 'tertiary', size: 'md', selected: false, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ToggleButton>)}
<ToggleButton {...args}>Toggle Me</ToggleButton>
{/snippet}
</Story>
@@ -60,7 +61,7 @@ let selected = $state(false);
name="Selected/Unselected"
args={{ variant: 'tertiary', size: 'md', animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ToggleButton>)}
<div class="flex items-center gap-4">
<ToggleButton {...args} selected={false}>
Unselected
@@ -76,7 +77,7 @@ let selected = $state(false);
name="Variants"
args={{ size: 'md', selected: true, animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ToggleButton>)}
<div class="flex items-center gap-4">
<ToggleButton {...args} variant="primary">
Primary
@@ -101,9 +102,15 @@ let selected = $state(false);
name="Interactive"
args={{ variant: 'tertiary', size: 'md', animate: true }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ToggleButton>)}
<div class="flex items-center gap-4">
<ToggleButton {...args} selected={selected} onclick={() => selected = !selected}>
<ToggleButton
{...args}
selected={selected}
onclick={() => {
selected = !selected;
}}
>
Click to toggle
</ToggleButton>
<span class="text-sm text-muted-foreground">Currently: {selected ? 'selected' : 'unselected'}</span>
@@ -119,7 +126,7 @@ let selected = $state(false);
},
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ToggleButton>)}
<div class="p-8 bg-background text-foreground">
<h3 class="mb-4 text-sm font-medium">Dark Mode</h3>
<div class="flex items-center gap-4">

View File

@@ -30,6 +30,7 @@ const { Story } = defineMeta({
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
const horizontalControl = createTypographyControl({ min: 0, max: 100, step: 1, value: 50 });
</script>
@@ -40,7 +41,7 @@ const horizontalControl = createTypographyControl({ min: 0, max: 100, step: 1, v
label: 'Size',
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ComboControl>)}
<ComboControl {...args} />
{/snippet}
</Story>

View File

@@ -37,6 +37,8 @@ const { Story } = defineMeta({
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
let value = $state('Here we can type and edit the content. Try it!');
let smallValue = $state('Small font size for compact text.');
let largeValue = $state('Large font size for emphasis.');
@@ -55,7 +57,7 @@ let longValue = $state(
letterSpacing: 0,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ContentEditable>)}
<ContentEditable {...args} />
{/snippet}
</Story>
@@ -69,7 +71,7 @@ let longValue = $state(
letterSpacing: 0,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ContentEditable>)}
<ContentEditable {...args} />
{/snippet}
</Story>
@@ -83,7 +85,7 @@ let longValue = $state(
letterSpacing: 0,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ContentEditable>)}
<ContentEditable {...args} />
{/snippet}
</Story>
@@ -97,7 +99,7 @@ let longValue = $state(
letterSpacing: 0.3,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ContentEditable>)}
<ContentEditable {...args} />
{/snippet}
</Story>
@@ -111,7 +113,7 @@ let longValue = $state(
letterSpacing: 0,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof ContentEditable>)}
<ContentEditable {...args} />
{/snippet}
</Story>

View File

@@ -0,0 +1,53 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import ControlGroup from './ControlGroup.svelte';
const { Story } = defineMeta({
title: 'Shared/ControlGroup',
component: ControlGroup,
tags: ['autodocs'],
parameters: {
docs: {
description: { component: 'Labelled section for grouping related sidebar controls, with a bottom border.' },
story: { inline: false },
},
layout: 'padded',
},
argTypes: {
label: {
control: 'text',
description: 'Uppercase label shown above the control content',
},
class: {
control: 'text',
description: 'Additional CSS classes',
},
},
});
</script>
<Story name="Default/Basic">
{#snippet template()}
<div class="w-64">
<ControlGroup label="Font Size">
<div class="text-sm text-neutral-500">Control content here</div>
</ControlGroup>
</div>
{/snippet}
</Story>
<Story name="With form control">
{#snippet template()}
<div class="w-64">
<ControlGroup label="Weight">
<div class="flex gap-2">
<button class="px-3 py-1 text-xs border border-neutral-300 rounded">100</button>
<button class="px-3 py-1 text-xs border border-neutral-300 rounded">400</button>
<button class="px-3 py-1 text-xs border border-neutral-300 rounded bg-neutral-900 text-white">
700
</button>
</div>
</ControlGroup>
</div>
{/snippet}
</Story>

View File

@@ -29,6 +29,7 @@ const { Story } = defineMeta({
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
const defaultFilter = createFilter({
properties: [{
id: 'cats',
@@ -64,14 +65,20 @@ const selectedFilter = createFilter({
});
</script>
<Story name="Default">
{#snippet template(args)}
<FilterGroup filter={defaultFilter} displayedLabel="Zoo" {...args} />
<Story
name="Default"
args={{ filter: defaultFilter, displayedLabel: 'Zoo' }}
>
{#snippet template(args: ComponentProps<typeof FilterGroup>)}
<FilterGroup {...args} />
{/snippet}
</Story>
<Story name="Selected">
{#snippet template(args)}
<FilterGroup filter={selectedFilter} displayedLabel="Shopping list" {...args} />
<Story
name="Selected"
args={{ filter: selectedFilter, displayedLabel: 'Shopping list' }}
>
{#snippet template(args: ComponentProps<typeof FilterGroup>)}
<FilterGroup {...args} />
{/snippet}
</Story>

View File

@@ -16,8 +16,12 @@ const { Story } = defineMeta({
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story name="Default">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Footnote>)}
<Footnote {...args}>
Footnote
</Footnote>
@@ -25,7 +29,7 @@ const { Story } = defineMeta({
</Story>
<Story name="With custom render">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Footnote>)}
<Footnote {...args}>
{#snippet render({ class: className })}
<span class={className}>Footnote</span>

View File

@@ -61,42 +61,43 @@ const { Story } = defineMeta({
<script lang="ts">
import SearchIcon from '@lucide/svelte/icons/search';
import ClearIcon from '@lucide/svelte/icons/x';
import type { ComponentProps } from 'svelte';
let value = $state('');
const placeholder = 'Enter text';
</script>
<!-- Default Story (Left Aligned) -->
<Story name="Default" args={{ placeholder, value }}>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Input>)}
<Input {...args} />
{/snippet}
</Story>
<Story name="All sizes" args={{ value }}>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Input>)}
<div class="flex flex-col gap-4">
<Input size="sm" placeholder="Size sm" {...args} />
<Input size="md" placeholder="Size md" {...args} />
<Input size="lg" placeholder="Size lg" {...args} />
<Input size="xl" placeholder="Size xl" {...args} />
<Input {...args} size="sm" placeholder="Size sm" />
<Input {...args} size="md" placeholder="Size md" />
<Input {...args} size="lg" placeholder="Size lg" />
<Input {...args} size="xl" placeholder="Size xl" />
</div>
{/snippet}
</Story>
<Story name="Underlined" args={{ placeholder, value }}>
{#snippet template(args)}
<Input variant="underline" {...args} />
<Story name="Underlined" args={{ placeholder, value, variant: 'underline' }}>
{#snippet template(args: ComponentProps<typeof Input>)}
<Input {...args} />
{/snippet}
</Story>
<Story name="Filled" args={{ placeholder, value }}>
{#snippet template(args)}
<Input variant="filled" {...args} />
<Story name="Filled" args={{ placeholder, value, variant: 'filled' }}>
{#snippet template(args: ComponentProps<typeof Input>)}
<Input {...args} />
{/snippet}
</Story>
<Story name="With icon on the right" args={{ placeholder, value }}>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Input>)}
<Input {...args}>
{#snippet rightIcon()}
<SearchIcon />
@@ -106,7 +107,7 @@ const placeholder = 'Enter text';
</Story>
<Story name="With icon on the left" args={{ placeholder, value }}>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Input>)}
<Input {...args}>
{#snippet leftIcon()}
<SearchIcon />
@@ -115,9 +116,9 @@ const placeholder = 'Enter text';
{/snippet}
</Story>
<Story name="With clear button" args={{ placeholder, value }}>
{#snippet template(args)}
<Input showClearButton {...args}>
<Story name="With clear button" args={{ placeholder, value, showClearButton: true }}>
{#snippet template(args: ComponentProps<typeof Input>)}
<Input {...args}>
{#snippet rightIcon()}
<ClearIcon />
{/snippet}

View File

@@ -47,13 +47,14 @@ const { Story } = defineMeta({
import AlertTriangleIcon from '@lucide/svelte/icons/alert-triangle';
import CheckIcon from '@lucide/svelte/icons/check';
import CircleIcon from '@lucide/svelte/icons/circle';
import type { ComponentProps } from 'svelte';
</script>
<Story
name="Default/Basic"
parameters={{ docs: { description: { story: 'Standard label with default styling' } } }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Default Label</Label>
{/snippet}
</Story>
@@ -72,7 +73,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Default variant"
args={{ variant: 'default', size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Default Label</Label>
{/snippet}
</Story>
@@ -81,7 +82,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Accent variant"
args={{ variant: 'accent', size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Accent Label</Label>
{/snippet}
</Story>
@@ -90,7 +91,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Muted variant"
args={{ variant: 'muted', size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Muted Label</Label>
{/snippet}
</Story>
@@ -99,7 +100,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Success variant"
args={{ variant: 'success', size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Success Label</Label>
{/snippet}
</Story>
@@ -108,7 +109,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Warning variant"
args={{ variant: 'warning', size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Warning Label</Label>
{/snippet}
</Story>
@@ -117,7 +118,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Error variant"
args={{ variant: 'error', size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Error Label</Label>
{/snippet}
</Story>
@@ -139,7 +140,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Uppercase"
args={{ uppercase: true, size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Uppercase Label</Label>
{/snippet}
</Story>
@@ -148,7 +149,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Lowercase"
args={{ uppercase: false, size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>lowercase label</Label>
{/snippet}
</Story>
@@ -157,7 +158,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="Bold"
args={{ bold: true, size: 'sm' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>Bold Label</Label>
{/snippet}
</Story>
@@ -166,7 +167,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="With icon (left)"
args={{ variant: 'default', size: 'sm', iconPosition: 'left' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>
{#snippet icon()}
<CircleIcon size={10} />
@@ -180,7 +181,7 @@ import CircleIcon from '@lucide/svelte/icons/circle';
name="With icon (right)"
args={{ variant: 'default', size: 'sm', iconPosition: 'right' }}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Label>)}
<Label {...args}>
Label with icon
{#snippet icon()}

View File

@@ -28,8 +28,12 @@ const { Story } = defineMeta({
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story name="Default">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Loader>)}
<Loader {...args} />
{/snippet}
</Story>

View File

@@ -16,8 +16,12 @@ const { Story } = defineMeta({
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story name="Default">
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Logo>)}
<Logo {...args} />
{/snippet}
</Story>

View File

@@ -0,0 +1,91 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import PerspectivePlan from './PerspectivePlan.svelte';
const { Story } = defineMeta({
title: 'Shared/PerspectivePlan',
component: PerspectivePlan,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Wrapper applying perspective 3D transform based on a PerspectiveManager spring. Used for front/back stacking in comparison views.',
},
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
region: {
control: 'select',
options: ['full', 'left', 'right'],
},
regionWidth: {
control: 'number',
},
},
});
</script>
<script>
import { createPerspectiveManager } from '$shared/lib';
const frontManager = createPerspectiveManager({ depthStep: 100, scaleStep: 0.5, blurStep: 4 });
const backManager = createPerspectiveManager({ depthStep: 100, scaleStep: 0.5, blurStep: 4 });
backManager.setBack();
const leftManager = createPerspectiveManager({ depthStep: 100, scaleStep: 0.5, blurStep: 4 });
</script>
<Story name="Front State">
{#snippet template()}
<div style="width: 300px; height: 200px; perspective: 800px; position: relative;">
<PerspectivePlan manager={frontManager}>
{#snippet children({ className })}
<div
class={className}
style="width: 300px; height: 200px; background: #1e1e2e; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #cdd6f4; font-family: sans-serif;"
>
Front — fully visible
</div>
{/snippet}
</PerspectivePlan>
</div>
{/snippet}
</Story>
<Story name="Back State">
{#snippet template()}
<div style="width: 300px; height: 200px; perspective: 800px; position: relative;">
<PerspectivePlan manager={backManager}>
{#snippet children({ className })}
<div
class={className}
style="width: 300px; height: 200px; background: #1e1e2e; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #cdd6f4; font-family: sans-serif;"
>
Back — blurred and scaled down
</div>
{/snippet}
</PerspectivePlan>
</div>
{/snippet}
</Story>
<Story name="Left Region">
{#snippet template()}
<div style="width: 300px; height: 200px; perspective: 800px; position: relative;">
<PerspectivePlan manager={leftManager} region="left" regionWidth={50}>
{#snippet children({ className })}
<div
class={className}
style="width: 100%; height: 100%; background: #313244; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #cdd6f4; font-family: sans-serif;"
>
Left half
</div>
{/snippet}
</PerspectivePlan>
</div>
{/snippet}
</Story>

View File

@@ -28,6 +28,8 @@ const { Story } = defineMeta({
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
let defaultSearchValue = $state('');
</script>
@@ -36,9 +38,10 @@ let defaultSearchValue = $state('');
args={{
value: defaultSearchValue,
placeholder: 'Type here...',
variant: 'filled',
}}
>
{#snippet template(args)}
<SearchBar variant="filled" {...args} />
{#snippet template(args: ComponentProps<typeof SearchBar>)}
<SearchBar {...args} />
{/snippet}
</Story>

View File

@@ -0,0 +1,45 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import SectionSeparator from './SectionSeparator.svelte';
const { Story } = defineMeta({
title: 'Shared/SectionSeparator',
component: SectionSeparator,
tags: ['autodocs'],
parameters: {
docs: {
description: { component: 'Full-width horizontal rule for separating page sections.' },
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
class: {
control: 'text',
description: 'Additional CSS classes to merge onto the element',
},
},
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story name="Default/Basic" args={{}}>
{#snippet template(args: ComponentProps<typeof SectionSeparator>)}
<div class="w-96">
<SectionSeparator {...args} />
</div>
{/snippet}
</Story>
<Story name="Custom class" args={{ class: 'my-4' }}>
{#snippet template(args: ComponentProps<typeof SectionSeparator>)}
<div class="w-96">
<p class="text-sm text-neutral-500">Above</p>
<SectionSeparator {...args} />
<p class="text-sm text-neutral-500">Below</p>
</div>
{/snippet}
</Story>

View File

@@ -0,0 +1,45 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import SectionTitle from './SectionTitle.svelte';
const { Story } = defineMeta({
title: 'Shared/SectionTitle',
component: SectionTitle,
tags: ['autodocs'],
parameters: {
docs: {
description: { component: 'Large responsive heading for named page sections.' },
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
text: {
control: 'text',
description: 'Heading text; renders nothing when omitted',
},
},
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story name="Default/Basic" args={{ text: 'Browse Fonts' }}>
{#snippet template(args: ComponentProps<typeof SectionTitle>)}
<SectionTitle {...args} />
{/snippet}
</Story>
<Story name="Long title" args={{ text: 'Explore Typefaces From Around the World' }}>
{#snippet template(args: ComponentProps<typeof SectionTitle>)}
<SectionTitle {...args} />
{/snippet}
</Story>
<Story name="Empty" args={{}}>
{#snippet template(args: ComponentProps<typeof SectionTitle>)}
<SectionTitle {...args} />
{/snippet}
</Story>

View File

@@ -0,0 +1,102 @@
<script module>
import { Providers } from '$shared/lib/storybook';
import { defineMeta } from '@storybook/addon-svelte-csf';
import SidebarContainer from './SidebarContainer.svelte';
const { Story } = defineMeta({
title: 'Shared/SidebarContainer',
component: SidebarContainer,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Responsive sidebar container. On desktop it collapses to zero width with a CSS transition. On mobile it renders as a slide-in drawer over a backdrop overlay. The `sidebar` prop is a snippet that receives `{ onClose }` so the sidebar content can dismiss itself.',
},
story: { inline: false },
},
layout: 'fullscreen',
},
argTypes: {
isOpen: {
control: 'boolean',
description: 'Whether the sidebar is open (bindable)',
},
class: {
control: 'text',
description: 'Additional CSS classes applied to the desktop wrapper element',
},
},
});
</script>
<Story name="Desktop Open">
{#snippet template()}
<Providers>
<div class="h-64 flex relative overflow-hidden">
<SidebarContainer isOpen={true}>
{#snippet sidebar({ onClose })}
<div class="w-80 h-full bg-white p-4 border-r border-neutral-200 flex flex-col gap-3">
<button
onclick={onClose}
class="self-start text-sm text-neutral-500 hover:text-neutral-900"
>
Close
</button>
<p class="text-sm text-neutral-700">Sidebar Content</p>
</div>
{/snippet}
</SidebarContainer>
<div class="flex-1 p-4 bg-neutral-50 text-sm text-neutral-500">Main content</div>
</div>
</Providers>
{/snippet}
</Story>
<Story name="Desktop Closed">
{#snippet template()}
<Providers>
<div class="h-64 flex relative overflow-hidden">
<SidebarContainer isOpen={false}>
{#snippet sidebar({ onClose })}
<div class="w-80 h-full bg-white p-4 border-r border-neutral-200 flex flex-col gap-3">
<button
onclick={onClose}
class="self-start text-sm text-neutral-500 hover:text-neutral-900"
>
Close
</button>
<p class="text-sm text-neutral-700">Sidebar Content</p>
</div>
{/snippet}
</SidebarContainer>
<div class="flex-1 p-4 bg-neutral-50 text-sm text-neutral-500">
Main content — sidebar is collapsed to zero width
</div>
</div>
</Providers>
{/snippet}
</Story>
<Story name="Mobile Open">
{#snippet template()}
<Providers initialWidth={375}>
<div class="h-64 relative overflow-hidden">
<SidebarContainer isOpen={true}>
{#snippet sidebar({ onClose })}
<div class="w-80 h-full bg-white p-4 flex flex-col gap-3">
<button
onclick={onClose}
class="self-start text-sm text-neutral-500 hover:text-neutral-900"
>
Close
</button>
<p class="text-sm text-neutral-700">Sidebar Content</p>
</div>
{/snippet}
</SidebarContainer>
<div class="p-4 bg-neutral-50 text-sm text-neutral-500">Main content</div>
</div>
</Providers>
{/snippet}
</Story>

View File

@@ -23,13 +23,17 @@ const { Story } = defineMeta({
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story
name="Default"
args={{
animate: true,
}}
>
{#snippet template(args)}
{#snippet template(args: ComponentProps<typeof Skeleton>)}
<div class="flex flex-col gap-4 p-4 w-full">
<div class="flex flex-col gap-2 p-4 border rounded-xl border-border-subtle bg-background-40">
<div class="flex items-center justify-between mb-4">

View File

@@ -37,13 +37,23 @@ const { Story } = defineMeta({
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
let value = $state(50);
let valueLow = $state(25);
let valueHigh = $state(75);
</script>
<Story name="Horizontal" args={{ orientation: 'horizontal', min: 0, max: 100, step: 1, value }}>
{#snippet template(args)}
<Story
name="Horizontal"
args={{
orientation: 'horizontal',
min: 0,
max: 100,
step: 1,
value,
}}
>
{#snippet template(args: ComponentProps<typeof Slider>)}
<div class="p-8">
<Slider {...args} />
<p class="mt-4 text-sm text-muted-foreground">Value: {args.value}</p>
@@ -54,8 +64,17 @@ let valueHigh = $state(75);
{/snippet}
</Story>
<Story name="Vertical" args={{ orientation: 'vertical', min: 0, max: 100, step: 1, value }}>
{#snippet template(args)}
<Story
name="Vertical"
args={{
orientation: 'vertical',
min: 0,
max: 100,
step: 1,
value,
}}
>
{#snippet template(args: ComponentProps<typeof Slider>)}
<div class="p-8 flex items-center gap-8 h-72">
<Slider {...args} />
<div>
@@ -66,8 +85,17 @@ let valueHigh = $state(75);
{/snippet}
</Story>
<Story name="With Label" args={{ orientation: 'horizontal', min: 0, max: 100, step: 1, value }}>
{#snippet template(args)}
<Story
name="With Label"
args={{
orientation: 'horizontal',
min: 0,
max: 100,
step: 1,
value,
}}
>
{#snippet template(args: ComponentProps<typeof Slider>)}
<div class="p-8">
<Slider {...args} />
<p class="mt-4 text-sm text-muted-foreground">Slider with inline label</p>
@@ -75,8 +103,17 @@ let valueHigh = $state(75);
{/snippet}
</Story>
<Story name="Interactive States" args={{ orientation: 'horizontal', min: 0, max: 100, step: 1, value: 50 }}>
{#snippet template(args)}
<Story
name="Interactive States"
args={{
orientation: 'horizontal',
min: 0,
max: 100,
step: 1,
value: 50,
}}
>
{#snippet template(args: ComponentProps<typeof Slider>)}
<div class="p-8 space-y-8">
<div>
<p class="text-sm font-medium mb-2">Thumb: 45° rotated square</p>
@@ -103,8 +140,17 @@ let valueHigh = $state(75);
{/snippet}
</Story>
<Story name="Step Sizes" args={{ orientation: 'horizontal', min: 0, max: 100, step: 1, value }}>
{#snippet template(args)}
<Story
name="Step Sizes"
args={{
orientation: 'horizontal',
min: 0,
max: 100,
step: 1,
value,
}}
>
{#snippet template(args: ComponentProps<typeof Slider>)}
<div class="p-8 space-y-6">
<div>
<p class="text-sm font-medium mb-2">Step: 1 (default)</p>

View File

@@ -0,0 +1,52 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import StatGroup from './StatGroup.svelte';
const { Story } = defineMeta({
title: 'Shared/StatGroup',
component: StatGroup,
tags: ['autodocs'],
parameters: {
docs: {
description: { component: 'Horizontal row of Stat items with automatic separators between them.' },
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
stats: {
control: 'object',
description: 'Array of stat items with label, value, and optional variant',
},
class: {
control: 'text',
description: 'Additional CSS classes',
},
},
});
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
</script>
<Story name="Default/Basic" args={{ stats: [{ label: 'Size', value: '16px' }, { label: 'Weight', value: 400 }] }}>
{#snippet template(args: ComponentProps<typeof StatGroup>)}
<StatGroup {...args} />
{/snippet}
</Story>
<Story
name="Three stats"
args={{ stats: [{ label: 'Size', value: '24px' }, { label: 'Weight', value: 700 }, { label: 'Line height', value: 1.5 }] }}
>
{#snippet template(args: ComponentProps<typeof StatGroup>)}
<StatGroup {...args} />
{/snippet}
</Story>
<Story name="Single stat" args={{ stats: [{ label: 'Style', value: 'Italic' }] }}>
{#snippet template(args: ComponentProps<typeof StatGroup>)}
<StatGroup {...args} />
{/snippet}
</Story>

View File

@@ -37,6 +37,8 @@ const { Story } = defineMeta({
</script>
<script lang="ts">
import type { ComponentProps } from 'svelte';
const smallDataSet = Array.from({ length: 20 }, (_, i) => `${i + 1}) I will not waste chalk.`);
const mediumDataSet = Array.from(
{ length: 200 },
@@ -45,10 +47,13 @@ const mediumDataSet = Array.from(
const emptyDataSet: string[] = [];
</script>
<Story name="Small Dataset">
{#snippet template(args)}
<Story
name="Small Dataset"
args={{ items: smallDataSet, itemHeight: 40 }}
>
{#snippet template(args: ComponentProps<typeof VirtualList>)}
<div class="h-[400px]">
<VirtualList items={smallDataSet} itemHeight={40} {...args}>
<VirtualList {...args}>
{#snippet children({ item })}
<div class="p-2 m-0.5 rounded-sm hover:bg-accent">{item}</div>
{/snippet}
@@ -57,10 +62,13 @@ const emptyDataSet: string[] = [];
{/snippet}
</Story>
<Story name="Medium Dataset (200 items)">
{#snippet template(args)}
<Story
name="Medium Dataset (200 items)"
args={{ items: mediumDataSet, itemHeight: 40 }}
>
{#snippet template(args: ComponentProps<typeof VirtualList>)}
<div class="h-[400px]">
<VirtualList items={mediumDataSet} itemHeight={40} {...args}>
<VirtualList {...args}>
{#snippet children({ item })}
<div class="p-2 m-0.5 rounded-sm hover:bg-accent">{item}</div>
{/snippet}
@@ -69,9 +77,12 @@ const emptyDataSet: string[] = [];
{/snippet}
</Story>
<Story name="Empty Dataset">
{#snippet template(args)}
<VirtualList items={emptyDataSet} itemHeight={40} {...args}>
<Story
name="Empty Dataset"
args={{ items: emptyDataSet, itemHeight: 40 }}
>
{#snippet template(args: ComponentProps<typeof VirtualList>)}
<VirtualList {...args}>
{#snippet children({ item })}
<div class="p-2 m-0.5 rounded-sm hover:bg-accent">{item}</div>
{/snippet}