chore(shared/ui): enhance stories with cases, controls and documentation
All checks were successful
Workflow / build (pull_request) Successful in 52s

This commit is contained in:
Ilia Mashkov
2026-01-18 20:55:36 +03:00
parent e7f4304391
commit c0eed67618
5 changed files with 295 additions and 13 deletions

View File

@@ -8,14 +8,28 @@ const { Story } = defineMeta({
tags: ['autodocs'], tags: ['autodocs'],
parameters: { parameters: {
docs: { docs: {
description: {
component:
'A collapsible property filter with checkboxes. Displays selected count as a badge and supports reduced motion for accessibility. Open by default for immediate visibility and interaction.',
},
story: { inline: false }, // Render stories in iframe for state isolation story: { inline: false }, // Render stories in iframe for state isolation
}, },
}, },
argTypes: {
displayedLabel: {
control: 'text',
description: 'Label for this filter group (e.g., "Properties", "Tags")',
},
filter: {
control: 'object',
description: 'Filter entity managing properties and selection state',
},
},
}); });
</script> </script>
<script lang="ts"> <script lang="ts">
const filter = createFilter({ const defaultFilter = createFilter({
properties: [{ properties: [{
id: 'cats', id: 'cats',
name: 'Cats', name: 'Cats',
@@ -30,8 +44,30 @@ const filter = createFilter({
value: 'birds', value: 'birds',
}], }],
}); });
const selectedFilter = createFilter({
properties: [{
id: 'rice',
name: 'Rice',
value: 'rice',
selected: true,
}, {
id: 'beans',
name: 'Beans',
value: 'beans',
selected: true,
}, {
id: 'potatoes',
name: 'Potatoes',
value: 'potatoes',
selected: true,
}],
});
</script> </script>
<Story name="Default"> <Story name="Default">
<CheckboxFilter {filter} displayedLabel="Zoo" /> <CheckboxFilter filter={defaultFilter} displayedLabel="Zoo" />
</Story>
<Story name="Selected">
<CheckboxFilter filter={selectedFilter} displayedLabel="Shopping list" />
</Story> </Story>

View File

@@ -5,12 +5,35 @@ import ComboControl from './ComboControl.svelte';
const { Story } = defineMeta({ const { Story } = defineMeta({
title: 'Shared/ComboControl', title: 'Shared/ComboControl',
component: ComboControl,
tags: ['autodocs'], tags: ['autodocs'],
parameters: { parameters: {
docs: { docs: {
description: {
component:
'Provides multiple ways to change a numeric value via decrease/increase buttons, slider, and direct input. All three methods are synchronized, giving users flexibility based on precision needs.',
},
story: { inline: false }, // Render stories in iframe for state isolation story: { inline: false }, // Render stories in iframe for state isolation
}, },
}, },
argTypes: {
control: {
control: 'object',
description: 'TypographyControl instance managing the value and bounds',
},
decreaseLabel: {
control: 'text',
description: 'Accessibility label for the decrease button',
},
increaseLabel: {
control: 'text',
description: 'Accessibility label for the increase button',
},
controlLabel: {
control: 'text',
description: 'Accessibility label for the control button (opens popover)',
},
},
}); });
</script> </script>
@@ -19,20 +42,58 @@ const defaultControl = createTypographyControl({ value: 77, min: 0, max: 100, st
const atMinimumControl = createTypographyControl({ value: 0, min: 0, max: 100, step: 1 }); const atMinimumControl = createTypographyControl({ value: 0, min: 0, max: 100, step: 1 });
const atMaximumControl = createTypographyControl({ value: 100, min: 0, max: 100, step: 1 }); const atMaximumControl = createTypographyControl({ value: 100, min: 0, max: 100, step: 1 });
const withFloatControl = createTypographyControl({ value: 77.5, min: 0, max: 100, step: 0.1 }); const withFloatControl = createTypographyControl({ value: 77.5, min: 0, max: 100, step: 0.1 });
const customLabelsControl = createTypographyControl({ value: 50, min: 0, max: 100, step: 1 });
</script> </script>
<Story name="Default"> <Story
name="Default"
args={{
control: defaultControl,
}}
>
<ComboControl control={defaultControl} /> <ComboControl control={defaultControl} />
</Story> </Story>
<Story name="At Minimum"> <Story
name="At Minimum"
args={{
control: atMinimumControl,
}}
>
<ComboControl control={atMinimumControl} /> <ComboControl control={atMinimumControl} />
</Story> </Story>
<Story name="At Maximum"> <Story
name="At Maximum"
args={{
control: atMaximumControl,
}}
>
<ComboControl control={atMaximumControl} /> <ComboControl control={atMaximumControl} />
</Story> </Story>
<Story name="With Float"> <Story
name="With Float"
args={{
control: withFloatControl,
}}
>
<ComboControl control={withFloatControl} /> <ComboControl control={withFloatControl} />
</Story> </Story>
<Story
name="Custom Labels"
args={{
control: customLabelsControl,
decreaseLabel: 'Decrease font size',
increaseLabel: 'Increase font size',
controlLabel: 'Open font size controls',
}}
>
<ComboControl
control={customLabelsControl}
decreaseLabel="Decrease font size"
increaseLabel="Increase font size"
controlLabel="Open font size controls"
/>
</Story>

View File

@@ -4,19 +4,104 @@ import ContentEditable from './ContentEditable.svelte';
const { Story } = defineMeta({ const { Story } = defineMeta({
title: 'Shared/ContentEditable', title: 'Shared/ContentEditable',
component: ContentEditable,
tags: ['autodocs'], tags: ['autodocs'],
parameters: { parameters: {
docs: { docs: {
description: {
component:
'A contenteditable div with custom font and text properties. Allows inline text editing with support for font size, line height, and letter spacing. The text is two-way bindable for form use cases.',
},
story: { inline: false }, // Render stories in iframe for state isolation story: { inline: false }, // Render stories in iframe for state isolation
}, },
}, },
argTypes: {
text: {
control: 'text',
description: 'Visible text content (two-way bindable)',
},
fontSize: {
control: { type: 'number', min: 8, max: 200 },
description: 'Font size in pixels',
},
lineHeight: {
control: { type: 'number', min: 0.8, max: 3, step: 0.1 },
description: 'Line height multiplier',
},
letterSpacing: {
control: { type: 'number', min: -0.5, max: 1, step: 0.05 },
description: 'Letter spacing in em units',
},
},
}); });
</script> </script>
<script lang="ts"> <script lang="ts">
let value = $state('Here we can type and edit the content. Try it!'); 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.');
let spacedValue = $state('Wide letter spacing.');
let longValue = $state(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
);
</script> </script>
<Story name="Default"> <Story
name="Default"
args={{
text: value,
fontSize: 48,
lineHeight: 1.2,
letterSpacing: 0,
}}
>
<ContentEditable bind:text={value} /> <ContentEditable bind:text={value} />
</Story> </Story>
<Story
name="Small Font"
args={{
text: smallValue,
fontSize: 16,
lineHeight: 1.5,
letterSpacing: 0,
}}
>
<ContentEditable bind:text={smallValue} />
</Story>
<Story
name="Large Font"
args={{
text: largeValue,
fontSize: 72,
lineHeight: 1.1,
letterSpacing: 0,
}}
>
<ContentEditable bind:text={largeValue} />
</Story>
<Story
name="Wide Letter Spacing"
args={{
text: spacedValue,
fontSize: 32,
lineHeight: 1.3,
letterSpacing: 0.3,
}}
>
<ContentEditable bind:text={spacedValue} />
</Story>
<Story
name="Long Text"
args={{
text: longValue,
fontSize: 24,
lineHeight: 1.6,
letterSpacing: 0,
}}
>
<ContentEditable bind:text={longValue} />
</Story>

View File

@@ -4,23 +4,79 @@ import SearchBar from './SearchBar.svelte';
const { Story } = defineMeta({ const { Story } = defineMeta({
title: 'Shared/SearchBar', title: 'Shared/SearchBar',
component: SearchBar,
tags: ['autodocs'], tags: ['autodocs'],
parameters: { parameters: {
docs: { docs: {
description: {
component:
'Search input with popover dropdown for results/suggestions. Features keyboard navigation (ArrowDown/Up/Enter) and auto-focus prevention on popover open. The input field serves as the popover trigger.',
},
story: { inline: false }, // Render stories in iframe for state isolation story: { inline: false }, // Render stories in iframe for state isolation
}, },
}, },
argTypes: {
value: {
control: 'text',
description: 'Current search value (two-way bindable)',
},
placeholder: {
control: 'text',
description: 'Placeholder text for the input',
},
label: {
control: 'text',
description: 'Optional label displayed above the input',
},
},
}); });
</script> </script>
<script lang="ts"> <script lang="ts">
let value = $state(''); let defaultSearchValue = $state('');
let withLabelValue = $state('');
let noChildrenValue = $state('');
</script> </script>
<Story name="Default"> <Story
<SearchBar bind:value={value} placeholder="Type here..."> name="Default"
args={{
value: defaultSearchValue,
placeholder: 'Type here...',
}}
>
<SearchBar bind:value={defaultSearchValue} placeholder="Type here...">
Here will be the search result Here will be the search result
<br /> <br />
Popover closes only when the user clicks outside the search bar or presses the Escape key. Popover closes only when the user clicks outside the search bar or presses the Escape key.
</SearchBar> </SearchBar>
</Story> </Story>
<Story
name="With Label"
args={{
value: withLabelValue,
placeholder: 'Search products...',
label: 'Search',
}}
>
<SearchBar bind:value={withLabelValue} placeholder="Search products..." label="Search">
<div class="p-4">
<p class="text-sm text-muted-foreground">No results found</p>
</div>
</SearchBar>
</Story>
<Story
name="Minimal Content"
args={{
value: noChildrenValue,
placeholder: 'Quick search...',
}}
>
<SearchBar bind:value={noChildrenValue} placeholder="Quick search...">
<div class="p-4 text-center text-sm text-muted-foreground">
Start typing to see results
</div>
</SearchBar>
</Story>

View File

@@ -7,18 +7,62 @@ const { Story } = defineMeta({
tags: ['autodocs'], tags: ['autodocs'],
parameters: { parameters: {
docs: { docs: {
description: {
component:
'High-performance virtualized list for large datasets. Only renders visible items plus an overscan buffer for optimal performance. Supports keyboard navigation (ArrowUp/Down, Home, End) and fixed or dynamic item heights.',
},
story: { inline: false }, // Render stories in iframe for state isolation story: { inline: false }, // Render stories in iframe for state isolation
}, },
}, },
argTypes: {
items: {
description: 'Array of items to render',
},
itemHeight: {
control: 'number',
description: 'Height of each item in pixels',
},
overscan: {
control: 'number',
description: 'Number of extra items to render above and below the visible area',
},
class: {
description: 'CSS class to apply to the root element',
},
onVisibleItemsChange: {
description: 'Callback invoked when the visible items change',
},
},
}); });
</script> </script>
<script lang="ts"> <script lang="ts">
const items = Array.from({ length: 10000 }, (_, i) => `${i + 1}) I will not waste chalk.`); const smallDataSet = Array.from({ length: 20 }, (_, i) => `${i + 1}) I will not waste chalk.`);
const largeDataSet = Array.from(
{ length: 10000 },
(_, i) => `${i + 1}) I will not skateboard in the halls.`,
);
const emptyDataSet: string[] = [];
</script> </script>
<Story name="Default"> <Story name="Small Dataset">
<VirtualList items={items} itemHeight={40}> <VirtualList items={smallDataSet} itemHeight={40}>
{#snippet children({ item })}
<div class="p-2 m-0.5 rounded-sm hover:bg-accent">{item}</div>
{/snippet}
</VirtualList>
</Story>
<Story name="Large Dataset">
<VirtualList items={largeDataSet} itemHeight={40}>
{#snippet children({ item })}
<div class="p-2 m-0.5 rounded-sm hover:bg-accent">{item}</div>
{/snippet}
</VirtualList>
</Story>
<Story name="Empty Dataset">
<VirtualList items={emptyDataSet} itemHeight={40}>
{#snippet children({ item })} {#snippet children({ item })}
<div class="p-2 m-0.5 rounded-sm hover:bg-accent">{item}</div> <div class="p-2 m-0.5 rounded-sm hover:bg-accent">{item}</div>
{/snippet} {/snippet}