From 0c8b8e989f2e78502786a22902f4ab7d2e4e6489 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 22 Feb 2026 11:25:02 +0300 Subject: [PATCH 01/88] chore: rewrite existing shared/ui stories using snippet template pattern --- .../CheckboxFilter.stories.svelte | 8 +- .../ComboControl/ComboControl.stories.svelte | 31 +- .../ComboControlV2.stories.svelte | 24 +- .../ContentEditable.stories.svelte | 20 +- .../ExpandableWrapper.stories.svelte | 6 +- .../ui/Footnote/Footnote.stories.svelte | 20 +- .../ui/IconButton/IconButton.stories.svelte | 28 +- src/shared/ui/Input/Input.stories.svelte | 28 +- src/shared/ui/Loader/Loader.stories.svelte | 4 +- src/shared/ui/Logo/Logo.stories.svelte | 4 +- .../ui/SearchBar/SearchBar.stories.svelte | 4 +- src/shared/ui/Section/Section.stories.svelte | 460 +++++++++--------- .../ui/Skeleton/Skeleton.stories.svelte | 16 +- src/shared/ui/Slider/Slider.stories.svelte | 12 +- .../ui/VirtualList/VirtualList.stories.svelte | 42 +- 15 files changed, 403 insertions(+), 304 deletions(-) diff --git a/src/shared/ui/CheckboxFilter/CheckboxFilter.stories.svelte b/src/shared/ui/CheckboxFilter/CheckboxFilter.stories.svelte index 2d0df2f..08df45a 100644 --- a/src/shared/ui/CheckboxFilter/CheckboxFilter.stories.svelte +++ b/src/shared/ui/CheckboxFilter/CheckboxFilter.stories.svelte @@ -65,9 +65,13 @@ const selectedFilter = createFilter({ - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/ComboControl/ComboControl.stories.svelte b/src/shared/ui/ComboControl/ComboControl.stories.svelte index c697da4..8adc9e2 100644 --- a/src/shared/ui/ComboControl/ComboControl.stories.svelte +++ b/src/shared/ui/ComboControl/ComboControl.stories.svelte @@ -51,7 +51,9 @@ const customLabelsControl = createTypographyControl({ value: 50, min: 0, max: 10 control: defaultControl, }} > - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/ComboControlV2/ComboControlV2.stories.svelte b/src/shared/ui/ComboControlV2/ComboControlV2.stories.svelte index 232fadc..0c04937 100644 --- a/src/shared/ui/ComboControlV2/ComboControlV2.stories.svelte +++ b/src/shared/ui/ComboControlV2/ComboControlV2.stories.svelte @@ -52,7 +52,9 @@ const largeRangeControl = createTypographyControl({ min: 0, max: 1000, step: 10, label: 'Size', }} > - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/ContentEditable/ContentEditable.stories.svelte b/src/shared/ui/ContentEditable/ContentEditable.stories.svelte index c839f86..da3c1db 100644 --- a/src/shared/ui/ContentEditable/ContentEditable.stories.svelte +++ b/src/shared/ui/ContentEditable/ContentEditable.stories.svelte @@ -55,7 +55,9 @@ let longValue = $state( letterSpacing: 0, }} > - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/ExpandableWrapper/ExpandableWrapper.stories.svelte b/src/shared/ui/ExpandableWrapper/ExpandableWrapper.stories.svelte index aacfa84..3514d84 100644 --- a/src/shared/ui/ExpandableWrapper/ExpandableWrapper.stories.svelte +++ b/src/shared/ui/ExpandableWrapper/ExpandableWrapper.stories.svelte @@ -59,7 +59,7 @@ const { Story } = defineMeta({ {/* @ts-ignore */ null} - {#snippet children(args)} + {#snippet template(args)}
- {#snippet children(args)} + {#snippet template(args)}
- {#snippet children(args)} + {#snippet template(args)}
- - Footnote - + {#snippet template(args)} + + Footnote + + {/snippet} - - {#snippet render({ class: className })} - Footnote - {/snippet} - + {#snippet template(args)} + + {#snippet render({ class: className })} + Footnote + {/snippet} + + {/snippet} diff --git a/src/shared/ui/IconButton/IconButton.stories.svelte b/src/shared/ui/IconButton/IconButton.stories.svelte index b485dde..b1e8b30 100644 --- a/src/shared/ui/IconButton/IconButton.stories.svelte +++ b/src/shared/ui/IconButton/IconButton.stories.svelte @@ -77,11 +77,13 @@ import XIcon from '@lucide/svelte/icons/x'; icon: chevronRightIcon, }} > - console.log('Default clicked')}> - {#snippet icon({ className })} - - {/snippet} - + {#snippet template(args)} + console.log('Default clicked')} {...args}> + {#snippet icon({ className })} + + {/snippet} + + {/snippet} -
- - {#snippet icon({ className })} - - {/snippet} - -
+ {#snippet template(args)} +
+ + {#snippet icon({ className })} + + {/snippet} + +
+ {/snippet}
diff --git a/src/shared/ui/Input/Input.stories.svelte b/src/shared/ui/Input/Input.stories.svelte index ed7fbd1..6d9f9fb 100644 --- a/src/shared/ui/Input/Input.stories.svelte +++ b/src/shared/ui/Input/Input.stories.svelte @@ -50,33 +50,47 @@ const placeholder = 'Enter text'; - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/Loader/Loader.stories.svelte b/src/shared/ui/Loader/Loader.stories.svelte index 37739e3..26fc0b2 100644 --- a/src/shared/ui/Loader/Loader.stories.svelte +++ b/src/shared/ui/Loader/Loader.stories.svelte @@ -29,5 +29,7 @@ const { Story } = defineMeta({ - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/Logo/Logo.stories.svelte b/src/shared/ui/Logo/Logo.stories.svelte index 28ecda6..1e94cf0 100644 --- a/src/shared/ui/Logo/Logo.stories.svelte +++ b/src/shared/ui/Logo/Logo.stories.svelte @@ -17,5 +17,7 @@ const { Story } = defineMeta({ - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/SearchBar/SearchBar.stories.svelte b/src/shared/ui/SearchBar/SearchBar.stories.svelte index ab5f839..254f162 100644 --- a/src/shared/ui/SearchBar/SearchBar.stories.svelte +++ b/src/shared/ui/SearchBar/SearchBar.stories.svelte @@ -39,5 +39,7 @@ let defaultSearchValue = $state(''); placeholder: 'Type here...', }} > - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/Section/Section.stories.svelte b/src/shared/ui/Section/Section.stories.svelte index 9e58126..50b9596 100644 --- a/src/shared/ui/Section/Section.stories.svelte +++ b/src/shared/ui/Section/Section.stories.svelte @@ -192,21 +192,23 @@ import SettingsIcon from '@lucide/svelte/icons/settings'; content: welcomeContent, }} > -
-
- {#snippet title({ className })} -

Welcome

- {/snippet} - {#snippet content({ className })} -
-

- This is the default section layout with a title and content area. The section uses a 2-column - grid layout with the title on the left and content on the right. -

-
- {/snippet} -
-
+ {#snippet template(args)} +
+
+ {#snippet title({ className })} +

Welcome

+ {/snippet} + {#snippet content({ className })} +
+

+ This is the default section layout with a title and content area. The section uses a + 2-column grid layout with the title on the left and content on the right. +

+
+ {/snippet} +
+
+ {/snippet} -
-
-
- {#snippet title({ className })} -

Sticky Title

- {/snippet} - {#snippet content({ className })} -
-

- This section has a sticky title that stays fixed while you scroll through the content. Try - scrolling down to see the effect. -

-
-

- 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 nisi ut aliquip ex ea - commodo consequat. -

-

- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur. -

-

- Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt - mollim anim id est laborum. -

-

- Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque - laudantium. + {#snippet template(args)} +

+
+
+ {#snippet title({ className })} +

Sticky Title

+ {/snippet} + {#snippet content({ className })} +
+

+ This section has a sticky title that stays fixed while you scroll through the content. + Try scrolling down to see the effect.

+
+

+ 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 nisi ut aliquip + ex ea commodo consequat. +

+

+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. +

+

+ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollim anim id est laborum. +

+

+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium + doloremque laudantium. +

+
-
- {/snippet} -
+ {/snippet} + +
-
+ {/snippet} -
-
- {#snippet title({ className })} -

Search Fonts

- {/snippet} - {#snippet icon({ className })} - - {/snippet} - {#snippet description({ className })} - Find your perfect typeface - {/snippet} - {#snippet content({ className })} -
-

- Use the search bar to find fonts by name, or use the filters to browse by category, subset, or - provider. -

-
- {/snippet} -
-
+ {#snippet template(args)} +
+
+ {#snippet title({ className })} +

Search Fonts

+ {/snippet} + {#snippet icon({ className })} + + {/snippet} + {#snippet description({ className })} + Find your perfect typeface + {/snippet} + {#snippet content({ className })} +
+

+ Use the search bar to find fonts by name, or use the filters to browse by category, subset, + or provider. +

+
+ {/snippet} +
+
+ {/snippet}
-
-
- {#snippet title({ className })} -

Typography

- {/snippet} - {#snippet icon({ className })} - - {/snippet} - {#snippet description({ className })} - Adjust text appearance - {/snippet} - {#snippet content({ className })} -
-

- Control the size, weight, and line height of your text. These settings apply across the - comparison view. -

-
- {/snippet} -
+ {#snippet template(args)} +
+
+ {#snippet title({ className })} +

Typography

+ {/snippet} + {#snippet icon({ className })} + + {/snippet} + {#snippet description({ className })} + Adjust text appearance + {/snippet} + {#snippet content({ className })} +
+

+ Control the size, weight, and line height of your text. These settings apply across the + comparison view. +

+
+ {/snippet} +
-
- {#snippet title({ className })} -

Font Search

- {/snippet} - {#snippet icon({ className })} - - {/snippet} - {#snippet description({ className })} - Browse available typefaces - {/snippet} - {#snippet content({ className })} -
-

- Search through our collection of fonts from Google Fonts and Fontshare. Use filters to narrow - down your selection. -

-
- {/snippet} -
+
+ {#snippet title({ className })} +

Font Search

+ {/snippet} + {#snippet icon({ className })} + + {/snippet} + {#snippet description({ className })} + Browse available typefaces + {/snippet} + {#snippet content({ className })} +
+

+ Search through our collection of fonts from Google Fonts and Fontshare. Use filters to + narrow down your selection. +

+
+ {/snippet} +
-
- {#snippet title({ className })} -

Sample List

- {/snippet} - {#snippet icon({ className })} - - {/snippet} - {#snippet description({ className })} - Preview font samples - {/snippet} - {#snippet content({ className })} -
-

- Browse through font samples with your custom text. The list is virtualized for optimal - performance. -

-
- {/snippet} -
-
+
+ {#snippet title({ className })} +

Sample List

+ {/snippet} + {#snippet icon({ className })} + + {/snippet} + {#snippet description({ className })} + Preview font samples + {/snippet} + {#snippet content({ className })} +
+

+ Browse through font samples with your custom text. The list is virtualized for optimal + performance. +

+
+ {/snippet} +
+
+ {/snippet}
-
-
- {#snippet title({ className })} -

Long Content

- {/snippet} - {#snippet content({ className })} -
-
-

- This section demonstrates how the sticky title behaves with longer content. As you scroll - through this content, the title remains visible at the top of the viewport. -

-
- Content block 1 -
-

- The sticky position is achieved using CSS position: sticky with a configurable top offset. - This is useful for long sections where you want to maintain context while scrolling. -

-
- Content block 2 -
-

- The Intersection Observer API is used to detect when the section title scrolls out of view, - triggering the optional onTitleStatusChange callback. -

-
- Content block 3 + {#snippet template(args)} +
+
+ {#snippet title({ className })} +

Long Content

+ {/snippet} + {#snippet content({ className })} +
+
+

+ This section demonstrates how the sticky title behaves with longer content. As you + scroll through this content, the title remains visible at the top of the viewport. +

+
+ Content block 1 +
+

+ The sticky position is achieved using CSS position: sticky with a configurable top + offset. This is useful for long sections where you want to maintain context while + scrolling. +

+
+ Content block 2 +
+

+ The Intersection Observer API is used to detect when the section title scrolls out of + view, triggering the optional onTitleStatusChange callback. +

+
+ Content block 3 +
-
- {/snippet} -
-
+ {/snippet} + +
+ {/snippet} -
-
- {#snippet title({ className })} -

Minimal Section

- {/snippet} - {#snippet content({ className })} -
-

- A minimal section without index, icon, or description. Just the essentials. -

-
- {/snippet} -
-
+ {#snippet template(args)} +
+
+ {#snippet title({ className })} +

Minimal Section

+ {/snippet} + {#snippet content({ className })} +
+

+ A minimal section without index, icon, or description. Just the essentials. +

+
+ {/snippet} +
+
+ {/snippet}
-
-
- {#snippet title({ className })} -

Custom Content

- {/snippet} - {#snippet description({ className })} - With interactive elements - {/snippet} - {#snippet content({ className })} -
-
-
-

Card 1

-

Some content here

-
-
-

Card 2

-

More content here

+ {#snippet template(args)} +
+
+ {#snippet title({ className })} +

Custom Content

+ {/snippet} + {#snippet description({ className })} + With interactive elements + {/snippet} + {#snippet content({ className })} +
+
+
+

Card 1

+

Some content here

+
+
+

Card 2

+

More content here

+
-
- {/snippet} -
-
+ {/snippet} + +
+ {/snippet}
diff --git a/src/shared/ui/Skeleton/Skeleton.stories.svelte b/src/shared/ui/Skeleton/Skeleton.stories.svelte index 24c3635..15c2cef 100644 --- a/src/shared/ui/Skeleton/Skeleton.stories.svelte +++ b/src/shared/ui/Skeleton/Skeleton.stories.svelte @@ -29,13 +29,15 @@ const { Story } = defineMeta({ animate: true, }} > -
-
-
- - + {#snippet template(args)} +
+
+
+ + +
+
-
-
+ {/snippet} diff --git a/src/shared/ui/Slider/Slider.stories.svelte b/src/shared/ui/Slider/Slider.stories.svelte index 3495c3f..062fa7f 100644 --- a/src/shared/ui/Slider/Slider.stories.svelte +++ b/src/shared/ui/Slider/Slider.stories.svelte @@ -44,13 +44,19 @@ let value = $state(50); - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} - + {#snippet template(args)} + + {/snippet} diff --git a/src/shared/ui/VirtualList/VirtualList.stories.svelte b/src/shared/ui/VirtualList/VirtualList.stories.svelte index cb835d6..de7f6a1 100644 --- a/src/shared/ui/VirtualList/VirtualList.stories.svelte +++ b/src/shared/ui/VirtualList/VirtualList.stories.svelte @@ -46,29 +46,35 @@ const emptyDataSet: string[] = []; -
- - {#snippet children({ item })} -
{item}
- {/snippet} -
-
+ {#snippet template(args)} +
+ + {#snippet children({ item })} +
{item}
+ {/snippet} +
+
+ {/snippet}
-
- + {#snippet template(args)} +
+ + {#snippet children({ item })} +
{item}
+ {/snippet} +
+
+ {/snippet} + + + + {#snippet template(args)} + {#snippet children({ item })}
{item}
{/snippet}
-
-
- - - - {#snippet children({ item })} -
{item}
- {/snippet} -
+ {/snippet}
-- 2.49.1 From 12222634d33c7d2f28b9a153b7c384fddccb2bf2 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 22 Feb 2026 11:25:33 +0300 Subject: [PATCH 02/88] feat(MicroLabel): create MicroLabel ui component --- .../ui/MicroLabel/MicroLabel.stories.svelte | 67 +++++++++++++++++++ src/shared/ui/MicroLabel/MicroLabel.svelte | 29 ++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/shared/ui/MicroLabel/MicroLabel.stories.svelte create mode 100644 src/shared/ui/MicroLabel/MicroLabel.svelte diff --git a/src/shared/ui/MicroLabel/MicroLabel.stories.svelte b/src/shared/ui/MicroLabel/MicroLabel.stories.svelte new file mode 100644 index 0000000..5662ac0 --- /dev/null +++ b/src/shared/ui/MicroLabel/MicroLabel.stories.svelte @@ -0,0 +1,67 @@ + + + + {#snippet template(args)} + Label + {/snippet} + + + + {#snippet template(args)} +
+
+ Muted Label +

Supporting text goes here

+
+
+ Accent Label +

Supporting text goes here

+
+
+ Subtle Label +

Supporting text goes here

+
+
+ {/snippet} +
+ + + {#snippet template(args)} +
+
+ Status + Active +
+
+ Category + Typography +
+
+ {/snippet} +
diff --git a/src/shared/ui/MicroLabel/MicroLabel.svelte b/src/shared/ui/MicroLabel/MicroLabel.svelte new file mode 100644 index 0000000..0a1c5a9 --- /dev/null +++ b/src/shared/ui/MicroLabel/MicroLabel.svelte @@ -0,0 +1,29 @@ + + + + + + -- 2.49.1 From 7f2fcb1797e2ec6a74d5ff4b8b00d29347782c1c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 22 Feb 2026 11:25:53 +0300 Subject: [PATCH 03/88] feat(DotIndicator): create DotIndicator ui component --- .../DotIndicator/DotIndicator.stories.svelte | 189 ++++++++++++++++++ .../ui/DotIndicator/DotIndicator.svelte | 60 ++++++ 2 files changed, 249 insertions(+) create mode 100644 src/shared/ui/DotIndicator/DotIndicator.stories.svelte create mode 100644 src/shared/ui/DotIndicator/DotIndicator.svelte diff --git a/src/shared/ui/DotIndicator/DotIndicator.stories.svelte b/src/shared/ui/DotIndicator/DotIndicator.stories.svelte new file mode 100644 index 0000000..5d60fcb --- /dev/null +++ b/src/shared/ui/DotIndicator/DotIndicator.stories.svelte @@ -0,0 +1,189 @@ + + + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} +
+
+ + Online +
+
+ + Offline +
+
+ + Busy +
+
+ + Away +
+
+ + Success +
+
+ + Warning +
+
+ + Error +
+
+ {/snippet} +
+ + + {#snippet template(args)} +
+
+ + Small +
+
+ + Medium +
+
+ + Large +
+
+ {/snippet} +
+ + + {#snippet template(args)} +
+
+ + Online +
+
+ + Busy +
+
+ + Away +
+
+ {/snippet} +
+ + + {#snippet template(args)} +
+

Sizes

+
+
+ + Small +
+
+ + Medium +
+
+ + Large +
+
+ +

Status Variants

+
+
+ + Online +
+
+ + Offline +
+
+ + Busy +
+
+ + Away +
+
+ +

Feedback Variants

+
+
+ + Success +
+
+ + Warning +
+
+ + Error +
+
+ +

With Pulse Animation

+
+
+ + Online +
+
+ + Busy +
+
+
+ {/snippet} +
diff --git a/src/shared/ui/DotIndicator/DotIndicator.svelte b/src/shared/ui/DotIndicator/DotIndicator.svelte new file mode 100644 index 0000000..b62c4d2 --- /dev/null +++ b/src/shared/ui/DotIndicator/DotIndicator.svelte @@ -0,0 +1,60 @@ + + + + -- 2.49.1 From acd656ddd137c9fd160c8d481a52b1c3e6bd9753 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 22 Feb 2026 11:26:11 +0300 Subject: [PATCH 04/88] feat(Badge): create Badge ui component --- src/shared/ui/Badge/Badge.stories.svelte | 76 ++++++++++++++++++++++++ src/shared/ui/Badge/Badge.svelte | 40 +++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/shared/ui/Badge/Badge.stories.svelte create mode 100644 src/shared/ui/Badge/Badge.svelte diff --git a/src/shared/ui/Badge/Badge.stories.svelte b/src/shared/ui/Badge/Badge.stories.svelte new file mode 100644 index 0000000..f9e1d90 --- /dev/null +++ b/src/shared/ui/Badge/Badge.stories.svelte @@ -0,0 +1,76 @@ + + + + + + {#snippet template(args)} + Default + {/snippet} + + + + {#snippet template(args)} +
+ Default + Success + Warning + Error + Info +
+ {/snippet} +
+ + + {#snippet template(args)} +
+ Small + Medium +
+ {/snippet} +
+ + + {#snippet template(args)} +
+ {#each ['default', 'success', 'warning', 'error', 'info'] as variant} +
+ {variant} sm + {variant} md +
+ {/each} +
+ {/snippet} +
diff --git a/src/shared/ui/Badge/Badge.svelte b/src/shared/ui/Badge/Badge.svelte new file mode 100644 index 0000000..a559a87 --- /dev/null +++ b/src/shared/ui/Badge/Badge.svelte @@ -0,0 +1,40 @@ + + + + + + -- 2.49.1 From 10437a2bf3fdd9ca32ff09bbc807b0f9a4800b4a Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 17:56:55 +0300 Subject: [PATCH 05/88] feat: new css variables for light and dark theme --- src/app/styles/app.css | 311 +++++++++++++++++++++++++++++------------ 1 file changed, 222 insertions(+), 89 deletions(-) diff --git a/src/app/styles/app.css b/src/app/styles/app.css index f312953..e908072 100644 --- a/src/app/styles/app.css +++ b/src/app/styles/app.css @@ -2,117 +2,261 @@ @import "tw-animate-css"; -@custom-variant dark (&:is(.dark *)); - :root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.141 0.005 285.823); - --card: oklch(1 0 0); - --card-foreground: oklch(0.141 0.005 285.823); + /* Base font size */ + --font-size: 16px; + + /* GLYPHDIFF Swiss Design System */ + /* Primary Colors */ + --swiss-beige: #f3f0e9; + --swiss-red: #ff3b30; + --swiss-black: #1a1a1a; + --swiss-white: #ffffff; + + /* Neutral Grays */ + --neutral-50: #fafafa; + --neutral-100: #f5f5f5; + --neutral-200: #e5e5e5; + --neutral-300: #d4d4d4; + --neutral-400: #a3a3a3; + --neutral-500: #737373; + --neutral-600: #525252; + --neutral-700: #404040; + --neutral-800: #262626; + --neutral-900: #171717; + + /* Dark Mode Backgrounds */ + --dark-bg: #121212; + --dark-card: #1e1e1e; + --dark-border: rgba(255, 255, 255, 0.1); + + /* Light Mode Backgrounds */ + --light-bg: #f3f0e9; + --light-card: #ffffff; + --light-border: rgba(0, 0, 0, 0.05); + + /* Semantic Colors */ + --color-brand: var(--swiss-red); + --color-surface: var(--swiss-beige); + --color-paper: var(--swiss-white); + + /* Base Tailwind Colors (for compatibility) */ + --background: #ffffff; + --foreground: oklch(0.145 0 0); + --card: #ffffff; + --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.21 0.006 285.885); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.705 0.015 286.067); + --popover-foreground: oklch(0.145 0 0); + --primary: #030213; + --primary-foreground: oklch(1 0 0); + --secondary: oklch(0.95 0.0058 264.53); + --secondary-foreground: #030213; + --muted: #ececf0; + --muted-foreground: #717182; + --accent: #e9ebef; + --accent-foreground: #030213; + --destructive: #d4183d; + --destructive-foreground: #ffffff; + --border: rgba(0, 0, 0, 0.1); + --input: transparent; + --input-background: #f3f3f5; + --switch-background: #cbced4; + --font-weight-medium: 500; + --font-weight-normal: 400; + --ring: oklch(0.708 0 0); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); + --radius: 0rem; --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: #030213; --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.705 0.015 286.067); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); - --background-20: oklch(1 0 0 / 20%); - --background-40: oklch(1 0 0 / 40%); - --background-60: oklch(1 0 0 / 60%); - --background-80: oklch(1 0 0 / 80%); - --background-95: oklch(1 0 0 / 95%); - --background-subtle: oklch(0.98 0 0); - --background-muted: oklch(0.97 0.002 286.375); + /* Spacing Scale (rem-based) */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 0.75rem; + --space-lg: 1rem; + --space-xl: 1.5rem; + --space-2xl: 2rem; + --space-3xl: 3rem; + --space-4xl: 4rem; - --text-muted: oklch(0.552 0.016 285.938); - --text-subtle: oklch(0.705 0.015 286.067); - --text-soft: oklch(0.5 0.01 286); + /* Typography Scale */ + --text-2xs: 0.625rem; + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 1.875rem; + --text-4xl: 2.25rem; + --text-5xl: 3rem; + --text-6xl: 3.75rem; + --text-7xl: 4.5rem; + --text-8xl: 6rem; - --border-subtle: oklch(0.95 0.003 286.32); - --border-muted: oklch(0.92 0.004 286.32); - --border-soft: oklch(0.88 0.005 286.32); - - --gradient-from: oklch(0.98 0.002 286.32); - --gradient-via: oklch(1 0 0); - --gradient-to: oklch(0.98 0.002 286.32); - - --font-mono: 'Major Mono Display'; + /* Comparison Font Sizes */ + --comparison-font-mobile: 3rem; + --comparison-font-tablet: 4.5rem; + --comparison-font-desktop: 6rem; } .dark { - --background: oklch(0.141 0.005 285.823); + --color-surface: var(--dark-bg); + --color-paper: var(--dark-card); + + --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); - --card: oklch(0.21 0.006 285.885); + --card: oklch(0.145 0 0); --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.21 0.006 285.885); + --popover: oklch(0.145 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.92 0.004 286.32); - --primary-foreground: oklch(0.21 0.006 285.885); - --secondary: oklch(0.274 0.006 286.033); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.552 0.016 285.938); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --font-weight-medium: 500; + --font-weight-normal: 400; --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.21 0.006 285.885); + --sidebar: oklch(0.205 0 0); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent: oklch(0.269 0 0); --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.552 0.016 285.938); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} - --background-20: oklch(0.21 0.006 285.885 / 20%); - --background-40: oklch(0.21 0.006 285.885 / 40%); - --background-60: oklch(0.21 0.006 285.885 / 60%); - --background-80: oklch(0.21 0.006 285.885 / 80%); - --background-95: oklch(0.21 0.006 285.885 / 95%); - --background-subtle: oklch(0.18 0.005 285.823); - --background-muted: oklch(0.274 0.006 286.033); +@theme { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-input-background: var(--input-background); + --color-switch-background: var(--switch-background); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: 0rem; + --radius-md: 0rem; + --radius-lg: 0rem; + --radius-xl: 0rem; + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); - --text-muted: oklch(0.705 0.015 286.067); - --text-subtle: oklch(0.552 0.016 285.938); - --text-soft: oklch(0.8 0.01 286); + --color-swiss-beige: var(--swiss-beige); + --color-swiss-red: var(--swiss-red); + --color-swiss-black: var(--swiss-black); + --color-swiss-white: var(--swiss-white); + --color-brand: var(--color-brand); + --color-surface: var(--color-surface); + --color-paper: var(--color-paper); + --color-dark-bg: var(--dark-bg); + --color-dark-card: var(--dark-card); +} - --border-subtle: oklch(1 0 0 / 8%); - --border-muted: oklch(1 0 0 / 10%); - --border-soft: oklch(1 0 0 / 15%); +@layer base { + * { + @apply border-border outline-ring/50; + } - --gradient-from: oklch(0.25 0.005 285.885); - --gradient-via: oklch(0.21 0.006 285.885); - --gradient-to: oklch(0.25 0.005 285.885); + body { + @apply bg-background text-foreground; + font-family: "Karla", system-ui, -apple-system, "Segoe UI", Inter, Roboto, Arial, sans-serif; + font-optical-sizing: auto; + } + + html { + font-size: var(--font-size); + } + + h1 { + font-size: var(--text-2xl); + font-weight: var(--font-weight-medium); + line-height: 1.5; + } + + h2 { + font-size: var(--text-xl); + font-weight: var(--font-weight-medium); + line-height: 1.5; + } + + h3 { + font-size: var(--text-lg); + font-weight: var(--font-weight-medium); + line-height: 1.5; + } + + h4 { + font-size: var(--text-base); + font-weight: var(--font-weight-medium); + line-height: 1.5; + } + + label { + font-size: var(--text-base); + font-weight: var(--font-weight-medium); + line-height: 1.5; + } + + button { + font-size: var(--text-base); + font-weight: var(--font-weight-medium); + line-height: 1.5; + } + + input { + font-size: var(--text-base); + font-weight: var(--font-weight-normal); + line-height: 1.5; + } } @theme inline { @@ -171,17 +315,6 @@ --font-sans: 'Karla', system-ui, -apple-system, 'Segoe UI', Inter, Roboto, Arial, sans-serif; } -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - font-family: "Karla", system-ui, -apple-system, "Segoe UI", Inter, Roboto, Arial, sans-serif; - font-optical-sizing: auto; - } -} - /* Global utility - useful across your app */ @media (prefers-reduced-motion: reduce) { * { -- 2.49.1 From 2ee49b7cbd2c50171204e875bc47470cb13601bd Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 17:57:40 +0300 Subject: [PATCH 06/88] feat(Slider): component redesign with complete storybook coverage --- src/shared/ui/Slider/Slider.stories.svelte | 97 +++++++- src/shared/ui/Slider/Slider.svelte | 243 +++++++++++---------- 2 files changed, 212 insertions(+), 128 deletions(-) diff --git a/src/shared/ui/Slider/Slider.stories.svelte b/src/shared/ui/Slider/Slider.stories.svelte index 062fa7f..c0fa835 100644 --- a/src/shared/ui/Slider/Slider.stories.svelte +++ b/src/shared/ui/Slider/Slider.stories.svelte @@ -9,7 +9,8 @@ const { Story } = defineMeta({ parameters: { docs: { description: { - component: 'Styled bits-ui slider component for selecting a value within a range.', + component: + 'Styled bits-ui slider component with red accent (#ff3b30). Thumb is a 45° rotated square with hover/active scale animations.', }, story: { inline: false }, // Render stories in iframe for state isolation }, @@ -31,32 +32,110 @@ const { Story } = defineMeta({ control: 'number', description: 'Step size for value increments', }, - label: { - control: 'text', - description: 'Optional label displayed inline on the track', - }, }, }); {#snippet template(args)} - +
+ +

Value: {args.value}

+

+ Hover over thumb to see scale effect, click and drag to interact +

+
{/snippet}
{#snippet template(args)} - +
+ +
+

Value: {args.value}

+

Vertical orientation with same red accent

+
+
{/snippet}
- + {#snippet template(args)} - +
+ +

Slider with inline label

+
+ {/snippet} +
+ + + {#snippet template(args)} +
+ +

Value: {args.value}

+

Dark mode: track uses neutral-800

+
+ {/snippet} +
+ + + {#snippet template(args)} +
+
+

Thumb: 45° rotated square

+ +
+
+

Hover State (scale-125)

+ +
+
+

Different Values

+
+ + + +
+
+
+

Focus State (ring-2 ring-[#ff3b30]/20)

+

Tab to the thumb to see focus ring

+ +
+
+ {/snippet} +
+ + + {#snippet template(args)} +
+
+

Step: 1 (default)

+ +
+
+

Step: 10

+ +
+
+

Step: 25

+ +
+
{/snippet}
diff --git a/src/shared/ui/Slider/Slider.svelte b/src/shared/ui/Slider/Slider.svelte index 20e68bb..c0d02b5 100644 --- a/src/shared/ui/Slider/Slider.svelte +++ b/src/shared/ui/Slider/Slider.svelte @@ -1,137 +1,142 @@ - - {#snippet children(props)} - {#if label && orientation === 'horizontal'} - - {label} - - {/if} - - +{#if isVertical} +
+ + {format(value)} + - -
onValueChange?.(v))} + class=" + relative flex flex-col items-center select-none touch-none + w-5 h-full grow cursor-row-resize + disabled:opacity-50 disabled:cursor-not-allowed + " + > + {#snippet children({ thumbItems })} + -
- - - {value} + -
+ + {#each thumbItems as thumb (thumb)} + + {/each} + {/snippet} + +
+{:else} +
+ onValueChange?.(v))} + class=" + relative flex items-center select-none touch-none + w-full h-5 cursor-col-resize + disabled:opacity-50 disabled:cursor-not-allowed + " + > + {#snippet children({ thumbItems })} + + + + + {#each thumbItems as thumb (thumb)} + + {/each} + {/snippet} + + + + + {format(value)} - {/snippet} - +
+{/if} -- 2.49.1 From 3e8e8a70c7cd5ae55686b83cee5267e93d50e277 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 17:58:00 +0300 Subject: [PATCH 07/88] feat(Input): component redesign with complete storybook coverage --- src/shared/ui/Input/Input.stories.svelte | 160 ++++++++-------- src/shared/ui/Input/Input.svelte | 227 +++++++++++++++-------- 2 files changed, 234 insertions(+), 153 deletions(-) diff --git a/src/shared/ui/Input/Input.stories.svelte b/src/shared/ui/Input/Input.stories.svelte index 6d9f9fb..4a58da8 100644 --- a/src/shared/ui/Input/Input.stories.svelte +++ b/src/shared/ui/Input/Input.stories.svelte @@ -8,13 +8,39 @@ const { Story } = defineMeta({ parameters: { docs: { description: { - component: 'Styled input component with size and variant options', + component: 'Input component', }, - story: { inline: false }, // Render stories in iframe for state isolation + story: { inline: false }, }, layout: 'centered', }, argTypes: { + variant: { + control: 'select', + options: ['default', 'underline', 'filled'], + description: 'Input variant', + defaultValue: 'default', + }, + size: { + control: 'select', + options: ['sm', 'md', 'lg', 'xl'], + description: 'Input size', + defaultValue: 'md', + }, + error: { + control: 'boolean', + description: 'Input error state', + defaultValue: false, + }, + helperText: { + control: 'text', + description: 'Input helper text', + }, + showClearButton: { + control: 'boolean', + description: 'Show clear button', + defaultValue: false, + }, placeholder: { control: 'text', description: "input's placeholder", @@ -23,90 +49,78 @@ const { Story } = defineMeta({ control: 'text', description: "input's value", }, - variant: { - control: 'select', - options: ['default', 'ghost'], - description: 'Visual style variant', - }, - size: { - control: 'select', - options: ['sm', 'md', 'lg'], - description: 'Size variant', + fullWidth: { + control: 'boolean', + description: 'Input fullWidth', + defaultValue: false, }, }, }); - - + + {#snippet template(args)} - + {/snippet} - - + {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - -
-
- Small - +
+ + + +
-
- Medium - -
-
- Large - -
-
+ {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {#snippet rightIcon()} + + {/snippet} + + {/snippet} + + + + {#snippet template(args)} + + {#snippet leftIcon()} + + {/snippet} + + {/snippet} + + + + {#snippet template(args)} + + {#snippet rightIcon()} + + {/snippet} + + {/snippet} diff --git a/src/shared/ui/Input/Input.svelte b/src/shared/ui/Input/Input.svelte index 818331e..0bf83fa 100644 --- a/src/shared/ui/Input/Input.svelte +++ b/src/shared/ui/Input/Input.svelte @@ -1,90 +1,157 @@ - - - +
+
+ + {#if leftIcon} +
+ {@render leftIcon(size)} +
+ {/if} + + + + + + {#if hasRightSlot} +
+ {#if showClear} + + {/if} + + {#if rightIcon} +
+ {@render rightIcon(size)} +
+ {/if} +
+ {/if} +
+ + + {#if helperText} + + {helperText} + + {/if} +
-- 2.49.1 From d36ab3c993b27a5c57cb451e19439efddb3edd07 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 17:58:56 +0300 Subject: [PATCH 08/88] feat(Button): shared button component with different sizes and variants --- src/shared/ui/Button/Button.stories.svelte | 120 +++++++++++++ src/shared/ui/Button/Button.svelte | 185 +++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 src/shared/ui/Button/Button.stories.svelte create mode 100644 src/shared/ui/Button/Button.svelte diff --git a/src/shared/ui/Button/Button.stories.svelte b/src/shared/ui/Button/Button.stories.svelte new file mode 100644 index 0000000..990177c --- /dev/null +++ b/src/shared/ui/Button/Button.stories.svelte @@ -0,0 +1,120 @@ + + + + + + {#snippet template(args)} + + + + + + + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + diff --git a/src/shared/ui/Button/Button.svelte b/src/shared/ui/Button/Button.svelte new file mode 100644 index 0000000..ec1c5c8 --- /dev/null +++ b/src/shared/ui/Button/Button.svelte @@ -0,0 +1,185 @@ + + + + -- 2.49.1 From 12d57c59c1d13c5eeb87c28a5b2f0847e79325bc Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 17:59:18 +0300 Subject: [PATCH 09/88] feat(Label): component redesign with complete storybook coverage --- src/shared/ui/Label/Label.stories.svelte | 213 +++++++++++++++++++++++ src/shared/ui/Label/Label.svelte | 73 +++++--- 2 files changed, 257 insertions(+), 29 deletions(-) create mode 100644 src/shared/ui/Label/Label.stories.svelte diff --git a/src/shared/ui/Label/Label.stories.svelte b/src/shared/ui/Label/Label.stories.svelte new file mode 100644 index 0000000..a72556e --- /dev/null +++ b/src/shared/ui/Label/Label.stories.svelte @@ -0,0 +1,213 @@ + + + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template()} +
+ + + +
+ {/snippet} +
+ + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template()} +
+ + + + + + +
+ {/snippet} +
+ + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + diff --git a/src/shared/ui/Label/Label.svelte b/src/shared/ui/Label/Label.svelte index 6265b3a..1e6a97e 100644 --- a/src/shared/ui/Label/Label.svelte +++ b/src/shared/ui/Label/Label.svelte @@ -1,45 +1,60 @@ + -
- {#if align !== 'left'} -
+ {#if icon && iconPosition === 'left'} + {@render icon()} {/if} -
- {text} -
- {#if align !== 'right'} -
+ + {#if children} + {@render children()} {/if} -
+ + {#if icon && iconPosition === 'right'} + {@render icon()} + {/if} + -- 2.49.1 From 83f2bdcdda0069b96cb898085b4dcb3cd8893af4 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:00:01 +0300 Subject: [PATCH 10/88] feat(TechText): component for technical text --- .../ui/TechText/TechText.stories.svelte | 97 +++++++++++++++++++ src/shared/ui/TechText/TechText.svelte | 39 ++++++++ 2 files changed, 136 insertions(+) create mode 100644 src/shared/ui/TechText/TechText.stories.svelte create mode 100644 src/shared/ui/TechText/TechText.svelte diff --git a/src/shared/ui/TechText/TechText.stories.svelte b/src/shared/ui/TechText/TechText.stories.svelte new file mode 100644 index 0000000..015f7c4 --- /dev/null +++ b/src/shared/ui/TechText/TechText.stories.svelte @@ -0,0 +1,97 @@ + + + + {#snippet template()} + 0x1F4A9 + {/snippet} + + + + {#snippet template()} + 0x1F4A9 + {/snippet} + + + + {#snippet template()} + 0x1F4A9 + {/snippet} + + + + {#snippet template()} + 0x1F4A9 + {/snippet} + + + + {#snippet template()} + 0x1F4A9 + {/snippet} + + + + {#snippet template()} + 0x1F4A9 + {/snippet} + + + + {#snippet template()} +
+ XS: font-family-16px + SM: font-family-16px + MD: font-family-16px +
+ {/snippet} +
diff --git a/src/shared/ui/TechText/TechText.svelte b/src/shared/ui/TechText/TechText.svelte new file mode 100644 index 0000000..63f6895 --- /dev/null +++ b/src/shared/ui/TechText/TechText.svelte @@ -0,0 +1,39 @@ + + + + + {#if children}{@render children()}{/if} + -- 2.49.1 From eac47fb99d3415cba83836ff35e7a6ae24fc1a8f Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:00:43 +0300 Subject: [PATCH 11/88] feat(Stat): Label wrapper for statistics --- src/shared/ui/Stat/Stat.stories.svelte | 136 +++++++++++++++++++++++++ src/shared/ui/Stat/Stat.svelte | 34 +++++++ 2 files changed, 170 insertions(+) create mode 100644 src/shared/ui/Stat/Stat.stories.svelte create mode 100644 src/shared/ui/Stat/Stat.svelte diff --git a/src/shared/ui/Stat/Stat.stories.svelte b/src/shared/ui/Stat/Stat.stories.svelte new file mode 100644 index 0000000..2c10a02 --- /dev/null +++ b/src/shared/ui/Stat/Stat.stories.svelte @@ -0,0 +1,136 @@ + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} +
+ + +
+ {/snippet} +
+ + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + diff --git a/src/shared/ui/Stat/Stat.svelte b/src/shared/ui/Stat/Stat.svelte new file mode 100644 index 0000000..7a3fd31 --- /dev/null +++ b/src/shared/ui/Stat/Stat.svelte @@ -0,0 +1,34 @@ + + + +
+ + +
+ +{#if separator} +
+{/if} -- 2.49.1 From cec166182c67537e5a1d8d616c02db4c820bf305 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:01:06 +0300 Subject: [PATCH 12/88] feat(Metric): Label wrapper for metrics --- src/shared/ui/Metric/Metric.stories.svelte | 139 +++++++++++++++++++++ src/shared/ui/Metric/Metric.svelte | 47 +++++++ 2 files changed, 186 insertions(+) create mode 100644 src/shared/ui/Metric/Metric.stories.svelte create mode 100644 src/shared/ui/Metric/Metric.svelte diff --git a/src/shared/ui/Metric/Metric.stories.svelte b/src/shared/ui/Metric/Metric.stories.svelte new file mode 100644 index 0000000..c792088 --- /dev/null +++ b/src/shared/ui/Metric/Metric.stories.svelte @@ -0,0 +1,139 @@ + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} +
+ + + + + + +
+ {/snippet} +
+ + + {#snippet template()} +
+ + + + + + +
+ {/snippet} +
diff --git a/src/shared/ui/Metric/Metric.svelte b/src/shared/ui/Metric/Metric.svelte new file mode 100644 index 0000000..a160c0a --- /dev/null +++ b/src/shared/ui/Metric/Metric.svelte @@ -0,0 +1,47 @@ + + + +
+
+ + {value} + + + {#if unit} + + {/if} +
+ + +
-- 2.49.1 From 089dc73abe12572c718595815eb1677b13df3b19 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:01:48 +0300 Subject: [PATCH 13/88] feat(StatusIndicator): Label wrapper for status display --- .../StatusIndicator.stories.svelte | 80 +++++++++++++++++++ .../ui/StatusIndicator/StatusIndicator.svelte | 45 +++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/shared/ui/StatusIndicator/StatusIndicator.stories.svelte create mode 100644 src/shared/ui/StatusIndicator/StatusIndicator.svelte diff --git a/src/shared/ui/StatusIndicator/StatusIndicator.stories.svelte b/src/shared/ui/StatusIndicator/StatusIndicator.stories.svelte new file mode 100644 index 0000000..95ecb23 --- /dev/null +++ b/src/shared/ui/StatusIndicator/StatusIndicator.stories.svelte @@ -0,0 +1,80 @@ + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + diff --git a/src/shared/ui/StatusIndicator/StatusIndicator.svelte b/src/shared/ui/StatusIndicator/StatusIndicator.svelte new file mode 100644 index 0000000..3b32a14 --- /dev/null +++ b/src/shared/ui/StatusIndicator/StatusIndicator.svelte @@ -0,0 +1,45 @@ + + + +
+ + + {#if label} + + {/if} +
-- 2.49.1 From 8617f2c117bd8b85b08625ff813eba73cad86968 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:02:24 +0300 Subject: [PATCH 14/88] feat(SectionHeader): Header for page sections --- .../SectionHeader.stories.svelte | 68 +++++++++++++++++++ .../ui/SectionHeader/SectionHeader.svelte | 43 ++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/shared/ui/SectionHeader/SectionHeader.stories.svelte create mode 100644 src/shared/ui/SectionHeader/SectionHeader.svelte diff --git a/src/shared/ui/SectionHeader/SectionHeader.stories.svelte b/src/shared/ui/SectionHeader/SectionHeader.stories.svelte new file mode 100644 index 0000000..bc7563c --- /dev/null +++ b/src/shared/ui/SectionHeader/SectionHeader.stories.svelte @@ -0,0 +1,68 @@ + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + + + + {#snippet template()} + + {/snippet} + diff --git a/src/shared/ui/SectionHeader/SectionHeader.svelte b/src/shared/ui/SectionHeader/SectionHeader.svelte new file mode 100644 index 0000000..4ed5091 --- /dev/null +++ b/src/shared/ui/SectionHeader/SectionHeader.svelte @@ -0,0 +1,43 @@ + + + +
+
+ {#if pulse} + + {/if} + + +
+ + {#if subtitle} + + + {/if} +
-- 2.49.1 From 043db46eaf74f4cea5f9f1ff745e9a4d74238105 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:02:52 +0300 Subject: [PATCH 15/88] feat(Divider): universal divider --- src/shared/ui/Divider/Divider.stories.svelte | 53 ++++++++++++++++++++ src/shared/ui/Divider/Divider.svelte | 26 ++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/shared/ui/Divider/Divider.stories.svelte create mode 100644 src/shared/ui/Divider/Divider.svelte diff --git a/src/shared/ui/Divider/Divider.stories.svelte b/src/shared/ui/Divider/Divider.stories.svelte new file mode 100644 index 0000000..86eed19 --- /dev/null +++ b/src/shared/ui/Divider/Divider.stories.svelte @@ -0,0 +1,53 @@ + + + + {#snippet template()} +
+
Content above divider
+ +
Content below divider
+
+ {/snippet} +
+ + + {#snippet template()} +
+
Left content
+ +
Right content
+
+ {/snippet} +
diff --git a/src/shared/ui/Divider/Divider.svelte b/src/shared/ui/Divider/Divider.svelte new file mode 100644 index 0000000..6f71638 --- /dev/null +++ b/src/shared/ui/Divider/Divider.svelte @@ -0,0 +1,26 @@ + + + +
+
-- 2.49.1 From 7cb9ae9edea9e5ab09cda41a4b275efc7b646e94 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:03:40 +0300 Subject: [PATCH 16/88] feat(StatGroup): wrapper around Stat to group a list of stats --- src/shared/ui/Stat/StatGroup.svelte | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/shared/ui/Stat/StatGroup.svelte diff --git a/src/shared/ui/Stat/StatGroup.svelte b/src/shared/ui/Stat/StatGroup.svelte new file mode 100644 index 0000000..8809790 --- /dev/null +++ b/src/shared/ui/Stat/StatGroup.svelte @@ -0,0 +1,32 @@ + + + +
+ {#each stats as stat, i} + + {/each} +
-- 2.49.1 From cd5abea56c339d84be67c87adf504f5cc905ae72 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:04:15 +0300 Subject: [PATCH 17/88] feat(ButtonGroup): container for buttons --- src/shared/ui/Button/ButtonGroup.svelte | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/shared/ui/Button/ButtonGroup.svelte diff --git a/src/shared/ui/Button/ButtonGroup.svelte b/src/shared/ui/Button/ButtonGroup.svelte new file mode 100644 index 0000000..c00eb02 --- /dev/null +++ b/src/shared/ui/Button/ButtonGroup.svelte @@ -0,0 +1,33 @@ + + + +
+ {#if children} + {@render children()} + {/if} +
-- 2.49.1 From 98101217dbef38e02859de58c368055360c747bb Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Feb 2026 18:06:24 +0300 Subject: [PATCH 18/88] feat(Button): wrapper arround Button to create square buttons with icons --- src/shared/ui/Button/IconButton.svelte | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/shared/ui/Button/IconButton.svelte diff --git a/src/shared/ui/Button/IconButton.svelte b/src/shared/ui/Button/IconButton.svelte new file mode 100644 index 0000000..5f60386 --- /dev/null +++ b/src/shared/ui/Button/IconButton.svelte @@ -0,0 +1,37 @@ + + + + + + +
+ {#snippet child({ props })} - + + {#if displayLabel} + + {displayLabel} + + {/if} + + + + {formattedValue()} + + {/snippet} - -
- - -
+ + + +
+
- {#if !reduced} - - {#snippet icon({ className })} - - {/snippet} - - {/if} - - - {#if controlLabel} - - {controlLabel} - - {/if} - + + +
+{/if} diff --git a/src/shared/ui/ComboControlV2/ComboControlV2.stories.svelte b/src/shared/ui/ComboControlV2/ComboControlV2.stories.svelte deleted file mode 100644 index 0c04937..0000000 --- a/src/shared/ui/ComboControlV2/ComboControlV2.stories.svelte +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - - - - {#snippet template(args)} - - {/snippet} - diff --git a/src/shared/ui/ComboControlV2/ComboControlV2.svelte b/src/shared/ui/ComboControlV2/ComboControlV2.svelte deleted file mode 100644 index efbbbc9..0000000 --- a/src/shared/ui/ComboControlV2/ComboControlV2.svelte +++ /dev/null @@ -1,231 +0,0 @@ - - - -{#snippet ComboControl()} -
-
- {#if showScale} -
- {#each Array(5) as _, i} -
- - {calculateScale(i)} - -
-
-
- {/each} -
- {/if} - - -
- - {#if !reduced} - - {/if} -
-{/snippet} - -{#if reduced} - {@render ComboControl()} -{:else} - - - - - {#snippet icon({ className })} - - {/snippet} - - - - {#snippet child({ props })} - - {/snippet} - - - {@render ComboControl()} - - - - - {#snippet icon({ className })} - - {/snippet} - - - - {#if controlLabel} - - {controlLabel} - - {/if} - -{/if} -- 2.49.1 From 2a65cedd0a413769895319e1b8dea68059d3035f Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 09:56:59 +0300 Subject: [PATCH 21/88] chore: replace font-name with variable --- src/shared/ui/Input/Input.svelte | 4 ++-- src/shared/ui/Label/Label.svelte | 2 +- src/shared/ui/Metric/Metric.svelte | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/ui/Input/Input.svelte b/src/shared/ui/Input/Input.svelte index 0bf83fa..9265da3 100644 --- a/src/shared/ui/Input/Input.svelte +++ b/src/shared/ui/Input/Input.svelte @@ -90,7 +90,7 @@ const cfg = $derived(sizeConfig[size]); const styles = $derived(variantConfig[variant]); const inputClasses = $derived(cn( - "font-['Space_Grotesk'] rounded-none outline-none transition-all duration-200", + 'font-primary rounded-none outline-none transition-all duration-200', 'text-neutral-900 dark:text-neutral-100', 'placeholder:text-neutral-400 dark:placeholder:text-neutral-600', 'disabled:opacity-50 disabled:cursor-not-allowed', @@ -147,7 +147,7 @@ const inputClasses = $derived(cn( {#if helperText} diff --git a/src/shared/ui/Label/Label.svelte b/src/shared/ui/Label/Label.svelte index 1e6a97e..a19947f 100644 --- a/src/shared/ui/Label/Label.svelte +++ b/src/shared/ui/Label/Label.svelte @@ -37,7 +37,7 @@ let { -- 2.49.1 From 8d571042d8f1bae14491952b69600d3869ede9ac Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 09:57:51 +0300 Subject: [PATCH 22/88] feat(Section): move SectionHeader component to Section ui shared component --- .../ui/{ => Section}/SectionHeader/SectionHeader.stories.svelte | 0 src/shared/ui/{ => Section}/SectionHeader/SectionHeader.svelte | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/shared/ui/{ => Section}/SectionHeader/SectionHeader.stories.svelte (100%) rename src/shared/ui/{ => Section}/SectionHeader/SectionHeader.svelte (100%) diff --git a/src/shared/ui/SectionHeader/SectionHeader.stories.svelte b/src/shared/ui/Section/SectionHeader/SectionHeader.stories.svelte similarity index 100% rename from src/shared/ui/SectionHeader/SectionHeader.stories.svelte rename to src/shared/ui/Section/SectionHeader/SectionHeader.stories.svelte diff --git a/src/shared/ui/SectionHeader/SectionHeader.svelte b/src/shared/ui/Section/SectionHeader/SectionHeader.svelte similarity index 100% rename from src/shared/ui/SectionHeader/SectionHeader.svelte rename to src/shared/ui/Section/SectionHeader/SectionHeader.svelte -- 2.49.1 From bd480f9592687436d012eec635fd9f260f5769a7 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 09:58:14 +0300 Subject: [PATCH 23/88] feat(Section): add SectionTitle component --- .../ui/Section/SectionTitle/SectionTitle.svelte | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/shared/ui/Section/SectionTitle/SectionTitle.svelte diff --git a/src/shared/ui/Section/SectionTitle/SectionTitle.svelte b/src/shared/ui/Section/SectionTitle/SectionTitle.svelte new file mode 100644 index 0000000..1afc08c --- /dev/null +++ b/src/shared/ui/Section/SectionTitle/SectionTitle.svelte @@ -0,0 +1,16 @@ + + +{#if text} +

+ {text} +

+{/if} -- 2.49.1 From d9925da96f55456d35b5276db83dbc9d81bde5b6 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 09:58:24 +0300 Subject: [PATCH 24/88] feat(Section): add SectionSeparator component --- .../SectionSeparator/SectionSeparator.svelte | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/shared/ui/Section/SectionSeparator/SectionSeparator.svelte diff --git a/src/shared/ui/Section/SectionSeparator/SectionSeparator.svelte b/src/shared/ui/Section/SectionSeparator/SectionSeparator.svelte new file mode 100644 index 0000000..241b3d9 --- /dev/null +++ b/src/shared/ui/Section/SectionSeparator/SectionSeparator.svelte @@ -0,0 +1,14 @@ + + + +
-- 2.49.1 From e125b2c7950bf654470e72d91f2b29adc3b0ef7f Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 09:59:19 +0300 Subject: [PATCH 25/88] feat(FontSampler): redesign component, remuve unused code, add stories --- .../ui/FontSampler/FontSampler.stories.svelte | 116 ++++++++++++++++++ .../ui/FontSearch/FontSearch.svelte | 3 +- 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/features/DisplayFont/ui/FontSampler/FontSampler.stories.svelte diff --git a/src/features/DisplayFont/ui/FontSampler/FontSampler.stories.svelte b/src/features/DisplayFont/ui/FontSampler/FontSampler.stories.svelte new file mode 100644 index 0000000..0238a83 --- /dev/null +++ b/src/features/DisplayFont/ui/FontSampler/FontSampler.stories.svelte @@ -0,0 +1,116 @@ + + + + + + {#snippet template(args)} + +
+ +
+
+ {/snippet} +
+ + {#snippet template(args)} + +
+ +
+
+ {/snippet} +
diff --git a/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte b/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte index feb3882..5adbffc 100644 --- a/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte +++ b/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte @@ -80,10 +80,9 @@ function toggleFilters() {
- {#snippet icon({ className })} + {#snippet icon()} -- 2.49.1 From b891f4c64bd7d351640effb9a8236471b294d853 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 09:59:52 +0300 Subject: [PATCH 26/88] chore(Button): add separate files for Button types and export --- src/shared/ui/Button/index.ts | 4 ++++ src/shared/ui/Button/types.ts | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 src/shared/ui/Button/index.ts create mode 100644 src/shared/ui/Button/types.ts diff --git a/src/shared/ui/Button/index.ts b/src/shared/ui/Button/index.ts new file mode 100644 index 0000000..a182c06 --- /dev/null +++ b/src/shared/ui/Button/index.ts @@ -0,0 +1,4 @@ +export { default as Button } from './Button.svelte'; +export { default as ButtonGroup } from './ButtonGroup.svelte'; +export { default as IconButton } from './IconButton.svelte'; +export { default as ToggleButton } from './ToggleButton.svelte'; diff --git a/src/shared/ui/Button/types.ts b/src/shared/ui/Button/types.ts new file mode 100644 index 0000000..0c7ecd6 --- /dev/null +++ b/src/shared/ui/Button/types.ts @@ -0,0 +1,3 @@ +export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'outline' | 'icon'; +export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +export type IconPosition = 'left' | 'right'; -- 2.49.1 From f134a343bef1b198478a7005b68ea98d801c97f7 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 10:00:18 +0300 Subject: [PATCH 27/88] chore(Import): add separate files for Import types --- src/shared/ui/Input/types.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/shared/ui/Input/types.ts diff --git a/src/shared/ui/Input/types.ts b/src/shared/ui/Input/types.ts new file mode 100644 index 0000000..9947652 --- /dev/null +++ b/src/shared/ui/Input/types.ts @@ -0,0 +1,9 @@ +export type InputVariant = 'default' | 'underline' | 'filled'; +export type InputSize = 'sm' | 'md' | 'lg' | 'xl'; +/** Convenience map for consumers sizing icons to match the input. */ +export const inputIconSize: Record = { + sm: 14, + md: 16, + lg: 18, + xl: 20, +}; -- 2.49.1 From 121eab54d9905171227341c15322c5c3c9fa936b Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 10:00:36 +0300 Subject: [PATCH 28/88] chore(Label): add separate file for Label types --- src/shared/ui/Label/config.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/shared/ui/Label/config.ts diff --git a/src/shared/ui/Label/config.ts b/src/shared/ui/Label/config.ts new file mode 100644 index 0000000..bdd4c55 --- /dev/null +++ b/src/shared/ui/Label/config.ts @@ -0,0 +1,29 @@ +/** + * Shared config. + * Import from here in each component to keep maps DRY. + */ + +export type LabelVariant = + | 'default' + | 'accent' + | 'muted' + | 'success' + | 'warning' + | 'error'; + +export type LabelSize = 'xs' | 'sm' | 'md'; + +export const labelSizeConfig: Record = { + xs: 'text-[0.5rem]', + sm: 'text-[0.5625rem] md:text-[0.625rem]', + md: 'text-[0.625rem] md:text-[0.6875rem]', +}; + +export const labelVariantConfig: Record = { + default: 'text-neutral-900 dark:text-neutral-100', + accent: 'text-brand', + muted: 'text-neutral-400 dark:text-neutral-500', + success: 'text-green-600 dark:text-green-400', + warning: 'text-yellow-600 dark:text-yellow-400', + error: 'text-brand', +}; -- 2.49.1 From 7aa1ddd40533cdf617e81370280950f5500be76d Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 10:01:00 +0300 Subject: [PATCH 29/88] chore: replace font-name with variable --- src/shared/ui/TechText/TechText.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/ui/TechText/TechText.svelte b/src/shared/ui/TechText/TechText.svelte index 63f6895..04dacbe 100644 --- a/src/shared/ui/TechText/TechText.svelte +++ b/src/shared/ui/TechText/TechText.svelte @@ -29,7 +29,7 @@ let { Date: Wed, 25 Feb 2026 10:01:26 +0300 Subject: [PATCH 30/88] chore: replace font-name with variable --- src/shared/ui/Slider/Slider.svelte | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/shared/ui/Slider/Slider.svelte b/src/shared/ui/Slider/Slider.svelte index c0d02b5..4fc4a37 100644 --- a/src/shared/ui/Slider/Slider.svelte +++ b/src/shared/ui/Slider/Slider.svelte @@ -4,7 +4,10 @@ Swiss design: 1px track, diamond thumb (rotate-45), brand accent. --> -
-
- -
- -
+ + {#snippet leftIcon(size)} + + {/snippet} + -- 2.49.1 From ea858dfdda228ee4842b05b5446ac84064136978 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 25 Feb 2026 10:04:25 +0300 Subject: [PATCH 34/88] feat(Section): component redesign --- src/shared/ui/Section/Section.svelte | 113 ++++----------------------- 1 file changed, 15 insertions(+), 98 deletions(-) diff --git a/src/shared/ui/Section/Section.svelte b/src/shared/ui/Section/Section.svelte index 9d0c7fd..2648797 100644 --- a/src/shared/ui/Section/Section.svelte +++ b/src/shared/ui/Section/Section.svelte @@ -11,7 +11,9 @@ import { type FlyParams, fly, } from 'svelte/transition'; -import { Footnote } from '..'; + +import SectionHeader from './SectionHeader/SectionHeader.svelte'; +import SectionTitle from './SectionTitle/SectionTitle.svelte'; interface Props extends Omit, 'title'> { /** @@ -23,17 +25,15 @@ interface Props extends Omit, 'title'> { */ class?: string; /** - * Snippet for a title itself + * Title of the section */ - title?: Snippet<[{ className?: string }]>; - /** - * Snippet for a title icon - */ - icon?: Snippet<[{ className?: string }]>; + title: string; /** * Snippet for a title description */ description?: Snippet<[{ className?: string }]>; + headerTitle?: string; + headerSubtitle?: string; /** * Index of the section */ @@ -57,32 +57,20 @@ interface Props extends Omit, 'title'> { * Snippet for the section content */ content?: Snippet<[{ className?: string }]>; - /** - * When true, the title stays fixed in view while - * scrolling through the section content. - */ - stickyTitle?: boolean; - /** - * Top offset for sticky title (e.g. header height). - * @default '0px' - */ - stickyOffset?: string; } const { class: className, title, - icon, + headerTitle, + headerSubtitle, description, index = 0, onTitleStatusChange, id, content, - stickyTitle = false, - stickyOffset = '0px', }: Props = $props(); -let titleContainer = $state(); const flyParams: FlyParams = { y: 0, x: -50, @@ -90,90 +78,19 @@ const flyParams: FlyParams = { easing: cubicOut, opacity: 0.2, }; - -// Track if the user has actually scrolled away from view -let isScrolledPast = $state(false); - -$effect(() => { - if (!titleContainer) { - return; - } - let cleanup: ((index: number) => void) | undefined; - const observer = new IntersectionObserver( - entries => { - const entry = entries[0]; - const isPast = !entry.isIntersecting && entry.boundingClientRect.top < 0; - - if (isPast !== isScrolledPast) { - isScrolledPast = isPast; - cleanup = onTitleStatusChange?.(index, isPast, title, id); - } - }, - { - // Set threshold to 0 to trigger exactly when the last pixel leaves - threshold: 0, - }, - ); - - observer.observe(titleContainer); - return () => { - observer.disconnect(); - cleanup?.(index); - }; -});
-
-
- {#if icon} - {@render icon({ - className: 'size-3 sm:size-4 stroke-foreground stroke-1 opacity-60', -})} -
- {/if} - - {#if description} - - {#snippet render({ class: className })} - {@render description({ className })} - {/snippet} - - {:else if typeof index === 'number'} - - Component_{String(index).padStart(3, '0')} - - {/if} -
- - {#if title} - {@render title({ - className: - 'text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-semibold tracking-tighter text-foreground leading-[0.9]', -})} +
+ {#if headerTitle} + {/if} +
- - {@render content?.({ - className: stickyTitle - ? 'row-start-2 col-start-2' - : 'row-start-2 col-start-2', -})} + {@render content?.({})}
-- 2.49.1 From 4f4afaebdf7103e647a1065d5987b879d6234e5f Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:20:12 +0300 Subject: [PATCH 35/88] fix(app.css): delete unused theme, move font variables, fix dark theme behavior --- src/app/styles/app.css | 66 +++++------------------------------------- 1 file changed, 7 insertions(+), 59 deletions(-) diff --git a/src/app/styles/app.css b/src/app/styles/app.css index 1baa541..e981773 100644 --- a/src/app/styles/app.css +++ b/src/app/styles/app.css @@ -1,7 +1,8 @@ @import "tailwindcss"; - @import "tw-animate-css"; +@variant dark (&:where(.dark, .dark *)); + :root { /* Base font size */ --font-size: 16px; @@ -199,6 +200,11 @@ --color-paper: var(--color-paper); --color-dark-bg: var(--dark-bg); --color-dark-card: var(--dark-card); + + --font-logo: 'Syne', system-ui, -apple-system, 'Segoe UI', Inter, Roboto, Arial, sans-serif; + --font-mono: 'Space Mono', monospace; + --font-primary: 'Space Grotesk', system-ui, -apple-system, 'Segoe UI', Inter, Roboto, Arial, sans-serif; + --font-secondary: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; } @layer base { @@ -259,64 +265,6 @@ } } -@theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); - --color-background-20: var(--background-20); - --color-background-40: var(--background-40); - --color-background-60: var(--background-60); - --color-background-80: var(--background-80); - --color-background-95: var(--background-95); - --color-background-subtle: var(--background-subtle); - --color-background-muted: var(--background-muted); - --color-text-muted: var(--text-muted); - --color-text-subtle: var(--text-subtle); - --color-text-soft: var(--text-soft); - --color-border-subtle: var(--border-subtle); - --color-border-muted: var(--border-muted); - --color-border-soft: var(--border-soft); - --color-gradient-from: var(--gradient-from); - --color-gradient-via: var(--gradient-via); - --color-gradient-to: var(--gradient-to); - --font-logo: 'Syne', system-ui, -apple-system, 'Segoe UI', Inter, Roboto, Arial, sans-serif; - --font-mono: 'Space Mono', monospace; - --font-primary: 'Space Grotesk', system-ui, -apple-system, 'Segoe UI', Inter, Roboto, Arial, sans-serif; - --font-secondary: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; -} - /* Global utility - useful across your app */ @media (prefers-reduced-motion: reduce) { * { -- 2.49.1 From c4daf47628abe447fbe4ca883f23ae400edbadbd Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:21:44 +0300 Subject: [PATCH 36/88] feat(ThemeManager): create ThemeManager that uses persistent storage to store preferred user theme --- .../store/ThemeManager/ThemeManager.svelte.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/features/ChangeAppTheme/model/store/ThemeManager/ThemeManager.svelte.ts diff --git a/src/features/ChangeAppTheme/model/store/ThemeManager/ThemeManager.svelte.ts b/src/features/ChangeAppTheme/model/store/ThemeManager/ThemeManager.svelte.ts new file mode 100644 index 0000000..d551017 --- /dev/null +++ b/src/features/ChangeAppTheme/model/store/ThemeManager/ThemeManager.svelte.ts @@ -0,0 +1,99 @@ +import { createPersistentStore } from '$shared/lib'; + +type Theme = 'light' | 'dark'; +type ThemeSource = 'system' | 'user'; + +class ThemeManager { + // Private reactive state + #theme = $state('light'); + #source = $state('system'); + #mediaQuery: MediaQueryList | null = null; + #store = createPersistentStore('glyphdiff:theme', null); + #systemChangeHandler = this.#onSystemChange.bind(this); + + constructor() { + // Derive initial values from stored preference or OS + const stored = this.#store.value; + if (stored === 'dark' || stored === 'light') { + this.#theme = stored; + this.#source = 'user'; + } else { + this.#theme = this.#getSystemTheme(); + this.#source = 'system'; + } + } + + get value(): Theme { + return this.#theme; + } + + get source(): ThemeSource { + return this.#source; + } + + get isDark(): boolean { + return this.#theme === 'dark'; + } + + get isUserControlled(): boolean { + return this.#source === 'user'; + } + + /** Call once in root onMount */ + init(): void { + this.#applyToDom(this.#theme); + this.#mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + this.#mediaQuery.addEventListener('change', this.#systemChangeHandler); + } + + /** Call in root onDestroy */ + destroy(): void { + this.#mediaQuery?.removeEventListener('change', this.#systemChangeHandler); + this.#mediaQuery = null; + } + + setTheme(theme: Theme): void { + this.#source = 'user'; + this.#theme = theme; + this.#store.value = theme; + this.#applyToDom(theme); + } + + toggle(): void { + this.setTheme(this.value === 'dark' ? 'light' : 'dark'); + } + + /** Hand control back to OS */ + resetToSystem(): void { + this.#store.clear(); + this.#theme = this.#getSystemTheme(); + this.#source = 'system'; + this.#applyToDom(this.#theme); + } + + // ------------------------- + // Private helpers + // ------------------------- + + #getSystemTheme(): Theme { + if (typeof window === 'undefined') { + return 'light'; + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + #applyToDom(theme: Theme): void { + document.documentElement.classList.toggle('dark', theme === 'dark'); + } + + #onSystemChange(e: MediaQueryListEvent): void { + if (this.#source === 'system') { + this.#theme = e.matches ? 'dark' : 'light'; + this.#applyToDom(this.#theme); + } + } +} + +// Export a singleton — one instance for the whole app +export const themeManager = new ThemeManager(); -- 2.49.1 From 7b8b41021cc596b4091f4523cd3aaccdd9169c6e Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:22:37 +0300 Subject: [PATCH 37/88] feat(ThemeSwitch): create ThemeSwitch component that uses ThemeMager toggle to switch theme --- src/features/ChangeAppTheme/index.ts | 2 + src/features/ChangeAppTheme/model/index.ts | 1 + .../ui/ThemeSwitch/ThemeSwitch.stories.svelte | 59 +++++++++++++++++++ .../ui/ThemeSwitch/ThemeSwitch.svelte | 26 ++++++++ src/features/ChangeAppTheme/ui/index.ts | 1 + 5 files changed, 89 insertions(+) create mode 100644 src/features/ChangeAppTheme/index.ts create mode 100644 src/features/ChangeAppTheme/model/index.ts create mode 100644 src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.stories.svelte create mode 100644 src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.svelte create mode 100644 src/features/ChangeAppTheme/ui/index.ts diff --git a/src/features/ChangeAppTheme/index.ts b/src/features/ChangeAppTheme/index.ts new file mode 100644 index 0000000..80b33de --- /dev/null +++ b/src/features/ChangeAppTheme/index.ts @@ -0,0 +1,2 @@ +export * from './model'; +export * from './ui'; diff --git a/src/features/ChangeAppTheme/model/index.ts b/src/features/ChangeAppTheme/model/index.ts new file mode 100644 index 0000000..cbc18dd --- /dev/null +++ b/src/features/ChangeAppTheme/model/index.ts @@ -0,0 +1 @@ +export { themeManager } from './store/ThemeManager/ThemeManager.svelte'; diff --git a/src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.stories.svelte b/src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.stories.svelte new file mode 100644 index 0000000..e3334f7 --- /dev/null +++ b/src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.stories.svelte @@ -0,0 +1,59 @@ + + + + + +
+ +
+ Theme: {currentTheme} + {#if themeSource === 'user'} + (user preference) + {:else} + (system preference) + {/if} +
+
+
diff --git a/src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.svelte b/src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.svelte new file mode 100644 index 0000000..74823dc --- /dev/null +++ b/src/features/ChangeAppTheme/ui/ThemeSwitch/ThemeSwitch.svelte @@ -0,0 +1,26 @@ + + + + themeManager.toggle()} size={responsive.isMobile ? 'sm' : 'md'} title="Toggle theme"> + {#snippet icon()} + {#if theme === 'light'} + + {:else} + + {/if} + {/snippet} + diff --git a/src/features/ChangeAppTheme/ui/index.ts b/src/features/ChangeAppTheme/ui/index.ts new file mode 100644 index 0000000..14e9d6c --- /dev/null +++ b/src/features/ChangeAppTheme/ui/index.ts @@ -0,0 +1 @@ +export { default as ThemeSwitch } from './ThemeSwitch/ThemeSwitch.svelte'; -- 2.49.1 From 661f3f0ae3fd72222c261d9ca68b990a77bea741 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:23:31 +0300 Subject: [PATCH 38/88] feat(Layout): add ThemeManager support --- src/app/ui/Layout.svelte | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/app/ui/Layout.svelte b/src/app/ui/Layout.svelte index a267d56..1bfbcac 100644 --- a/src/app/ui/Layout.svelte +++ b/src/app/ui/Layout.svelte @@ -11,12 +11,18 @@ * - Footer area (currently empty, reserved for future use) */ import { BreadcrumbHeader } from '$entities/Breadcrumb'; +import { + ThemeSwitch, + themeManager, +} from '$features/ChangeAppTheme'; import GD from '$shared/assets/GD.svg'; import { ResponsiveProvider } from '$shared/lib'; import { ScrollArea } from '$shared/shadcn/ui/scroll-area'; import { Provider as TooltipProvider } from '$shared/shadcn/ui/tooltip'; +import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { type Snippet, + onDestroy, onMount, } from 'svelte'; @@ -26,6 +32,7 @@ interface Props { let { children }: Props = $props(); let fontsReady = $state(false); +const theme = $derived(themeManager.value); /** * Sets fontsReady flag to true when font for the page logo is loaded. @@ -49,6 +56,9 @@ onMount(async () => { } fontsReady = true; }); + +onMount(() => themeManager.init()); +onDestroy(() => themeManager.destroy()); @@ -88,9 +98,16 @@ onMount(async () => { -
+
+
-- 2.49.1 From bf79cbb26febfe5e322b860af64d54ede53629d5 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:24:16 +0300 Subject: [PATCH 39/88] feat(storybook): create ThemeDecorator to support themeManager logic in storybook --- .storybook/ThemeDecorator.svelte | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .storybook/ThemeDecorator.svelte diff --git a/.storybook/ThemeDecorator.svelte b/.storybook/ThemeDecorator.svelte new file mode 100644 index 0000000..0df30cb --- /dev/null +++ b/.storybook/ThemeDecorator.svelte @@ -0,0 +1,30 @@ + + + +{@render children()} -- 2.49.1 From f8f295e5a0eaa0ffb33a8fa5f3af976b4d2c97e3 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:25:16 +0300 Subject: [PATCH 40/88] feat(Button): add tertiary variant and change ghost variant styles --- src/shared/ui/Button/Button.svelte | 31 ++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/shared/ui/Button/Button.svelte b/src/shared/ui/Button/Button.svelte index ec1c5c8..04d9c19 100644 --- a/src/shared/ui/Button/Button.svelte +++ b/src/shared/ui/Button/Button.svelte @@ -89,10 +89,9 @@ const variantStyles: Record = { 'bg-transparent', 'text-neutral-500 dark:text-neutral-400', 'border border-transparent', - 'hover:bg-paper dark:hover:bg-paper', - 'hover:text-swiss-black dark:hover:text-neutral-200', - 'hover:border-black/5 dark:hover:border-white/5', - 'active:bg-surface dark:active:bg-neutral-700', + 'hover:bg-transparent dark:hover:bg-transparent', + 'hover:text-brand dark:hover:text-brand', + 'active:bg-transparent dark:active:bg-transparent', 'disabled:text-neutral-400 dark:disabled:text-neutral-600', 'disabled:cursor-not-allowed', ), @@ -107,6 +106,23 @@ const variantStyles: Record = { 'disabled:text-neutral-400 dark:disabled:text-neutral-600', 'disabled:cursor-not-allowed', ), + tertiary: cn( + // Font override — must come after base in cn() to win via tailwind-merge + 'font-secondary font-medium normal-case tracking-normal', + // Inactive state + 'bg-transparent', + 'text-neutral-400 dark:text-neutral-400', + 'border border-transparent', + // Hover (inactive) — semi-transparent lift, no bg-paper token + 'hover:bg-white/50 dark:hover:bg-[#1e1e1e]/50', + 'hover:text-neutral-900 dark:hover:text-neutral-100', + 'hover:border-black/5 dark:hover:border-white/10', + // Press + 'active:bg-white/70 dark:active:bg-[#1e1e1e]/70', + // Disabled + 'disabled:text-neutral-300 dark:disabled:text-neutral-600', + 'disabled:cursor-not-allowed', + ), }; // ── Size styles ─────────────────────────────────────────────────────────────── @@ -130,7 +146,9 @@ const iconSizeStyles: Record = { // ── Active state overrides (per variant) ───────────────────────────────────── const activeStyles: Partial> = { secondary: 'bg-paper dark:bg-paper shadow-sm border-black/20 dark:border-white/20', - ghost: 'bg-paper dark:bg-paper text-swiss-black dark:text-neutral-200', + tertiary: + 'bg-paper dark:bg-[#1e1e1e] border-black/10 dark:border-white/10 shadow-sm text-neutral-900 dark:text-neutral-100', + ghost: 'bg-transparent dark:bg-transparent text-brnad dark:text-brand', outline: 'bg-surface dark:bg-paper border-brand', icon: 'bg-paper dark:bg-paper text-brand border-black/5 dark:border-white/10', }; @@ -138,11 +156,12 @@ const activeStyles: Partial> = { const classes = $derived(cn( // Base 'inline-flex items-center justify-center', - 'font-["Space_Grotesk"] font-bold tracking-tight uppercase', + 'font-primary font-bold tracking-tight uppercase', 'rounded-none', 'transition-all duration-200', 'select-none', 'outline-none', + 'cursor-pointer', 'focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2', 'focus-visible:ring-offset-surface dark:focus-visible:ring-offset-dark-bg', 'disabled:cursor-not-allowed disabled:pointer-events-none', -- 2.49.1 From 0ca5115d10a12bf62be6cef86e534fea9663f7aa Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:25:25 +0300 Subject: [PATCH 41/88] feat(Button): add tertiary variant --- src/shared/ui/Button/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/ui/Button/types.ts b/src/shared/ui/Button/types.ts index 0c7ecd6..2d42e35 100644 --- a/src/shared/ui/Button/types.ts +++ b/src/shared/ui/Button/types.ts @@ -1,3 +1,3 @@ -export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'outline' | 'icon'; +export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'outline' | 'icon'; export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; export type IconPosition = 'left' | 'right'; -- 2.49.1 From 38f4243739623632c8dd230036092688bb23a33c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:38:19 +0300 Subject: [PATCH 42/88] feat(FilterGroup): refactor CheckboxFilter component to FilterGroup --- .../ui/CheckboxFilter/CheckboxFilter.svelte | 137 ------------------ .../FilterGroup.stories.svelte} | 8 +- src/shared/ui/FilterGroup/FilterGroup.svelte | 71 +++++++++ .../FilterGroup.svelte.test.ts} | 56 +++---- 4 files changed, 103 insertions(+), 169 deletions(-) delete mode 100644 src/shared/ui/CheckboxFilter/CheckboxFilter.svelte rename src/shared/ui/{CheckboxFilter/CheckboxFilter.stories.svelte => FilterGroup/FilterGroup.stories.svelte} (87%) create mode 100644 src/shared/ui/FilterGroup/FilterGroup.svelte rename src/shared/ui/{CheckboxFilter/CheckboxFilter.svelte.test.ts => FilterGroup/FilterGroup.svelte.test.ts} (94%) diff --git a/src/shared/ui/CheckboxFilter/CheckboxFilter.svelte b/src/shared/ui/CheckboxFilter/CheckboxFilter.svelte deleted file mode 100644 index bf14acd..0000000 --- a/src/shared/ui/CheckboxFilter/CheckboxFilter.svelte +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - -
- -

{displayedLabel}

- - - {#if hasSelection} - - {selectedCount} - - {/if} - - -
- -
-
-
- - - {#if isOpen} -
-
-
- - - {#each filter.properties as property (property.id)} - - {/each} -
-
-
- {/if} -
diff --git a/src/shared/ui/CheckboxFilter/CheckboxFilter.stories.svelte b/src/shared/ui/FilterGroup/FilterGroup.stories.svelte similarity index 87% rename from src/shared/ui/CheckboxFilter/CheckboxFilter.stories.svelte rename to src/shared/ui/FilterGroup/FilterGroup.stories.svelte index 08df45a..d9d8ada 100644 --- a/src/shared/ui/CheckboxFilter/CheckboxFilter.stories.svelte +++ b/src/shared/ui/FilterGroup/FilterGroup.stories.svelte @@ -1,10 +1,10 @@ + +{#snippet icon()} + + + +{/snippet} + +
+ + +
+ {#each filter.properties as property (property.id)} + + {/each} +
+
diff --git a/src/shared/ui/CheckboxFilter/CheckboxFilter.svelte.test.ts b/src/shared/ui/FilterGroup/FilterGroup.svelte.test.ts similarity index 94% rename from src/shared/ui/CheckboxFilter/CheckboxFilter.svelte.test.ts rename to src/shared/ui/FilterGroup/FilterGroup.svelte.test.ts index 7e52cfb..085b646 100644 --- a/src/shared/ui/CheckboxFilter/CheckboxFilter.svelte.test.ts +++ b/src/shared/ui/FilterGroup/FilterGroup.svelte.test.ts @@ -13,10 +13,10 @@ import { expect, it, } from 'vitest'; -import CheckboxFilter from './CheckboxFilter.svelte'; +import FilterGroup from './FilterGroup.svelte'; /** - * Test Suite for CheckboxFilter Component + * Test Suite for FilterGroup Component * * This suite tests the actual Svelte component rendering, interactions, and behavior * using a real browser environment (Playwright) via @vitest/browser-playwright. @@ -29,7 +29,7 @@ import CheckboxFilter from './CheckboxFilter.svelte'; * not as . */ -describe('CheckboxFilter Component', () => { +describe('FilterGroup Component', () => { /** * Helper function to create a filter for testing */ @@ -52,7 +52,7 @@ describe('CheckboxFilter Component', () => { describe('Rendering', () => { it('displays the label', () => { const filter = createTestFilter(createMockProperties(3)); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test Label', filter, }); @@ -63,7 +63,7 @@ describe('CheckboxFilter Component', () => { it('renders all properties as checkboxes with labels', () => { const properties = createMockProperties(3); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -77,7 +77,7 @@ describe('CheckboxFilter Component', () => { it('shows selected count badge when items are selected', () => { const properties = createMockProperties(3, [0, 2]); // Select 2 items const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -88,7 +88,7 @@ describe('CheckboxFilter Component', () => { it('hides badge when no items selected', () => { const properties = createMockProperties(3); const filter = createTestFilter(properties); - const { container } = render(CheckboxFilter, { + const { container } = render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -100,7 +100,7 @@ describe('CheckboxFilter Component', () => { it('renders with no properties', () => { const filter = createTestFilter([]); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Empty Filter', filter, }); @@ -113,7 +113,7 @@ describe('CheckboxFilter Component', () => { it('checkboxes reflect initial selected state', async () => { const properties = createMockProperties(3, [0, 2]); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -132,7 +132,7 @@ describe('CheckboxFilter Component', () => { it('clicking checkbox toggles property.selected state', async () => { const properties = createMockProperties(3, [0]); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -164,7 +164,7 @@ describe('CheckboxFilter Component', () => { it('label styling changes based on selection state', async () => { const properties = createMockProperties(2, [0]); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -192,7 +192,7 @@ describe('CheckboxFilter Component', () => { it('multiple checkboxes can be toggled independently', async () => { const properties = createMockProperties(3); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -223,7 +223,7 @@ describe('CheckboxFilter Component', () => { describe('Collapsible Behavior', () => { it('is open by default', () => { const filter = createTestFilter(createMockProperties(2)); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -235,7 +235,7 @@ describe('CheckboxFilter Component', () => { it('clicking trigger toggles open/close state', async () => { const filter = createTestFilter(createMockProperties(2)); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -263,7 +263,7 @@ describe('CheckboxFilter Component', () => { it('chevron icon rotates based on open state', async () => { const filter = createTestFilter(createMockProperties(2)); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -297,7 +297,7 @@ describe('CheckboxFilter Component', () => { it('badge shows correct count based on filter.selectedCount', async () => { const properties = createMockProperties(5, [0, 2, 4]); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -318,7 +318,7 @@ describe('CheckboxFilter Component', () => { it('badge visibility changes with hasSelection (selectedCount > 0)', async () => { const properties = createMockProperties(2, [0]); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -347,7 +347,7 @@ describe('CheckboxFilter Component', () => { it('badge shows count correctly when all items are selected', () => { const properties = createMockProperties(5, [0, 1, 2, 3, 4]); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -359,7 +359,7 @@ describe('CheckboxFilter Component', () => { describe('Accessibility', () => { it('provides proper ARIA labels on buttons', () => { const filter = createTestFilter(createMockProperties(2)); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test Label', filter, }); @@ -372,7 +372,7 @@ describe('CheckboxFilter Component', () => { it('labels are properly associated with checkboxes', async () => { const properties = createMockProperties(3); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -391,7 +391,7 @@ describe('CheckboxFilter Component', () => { it('checkboxes have proper role', async () => { const filter = createTestFilter(createMockProperties(2)); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -406,7 +406,7 @@ describe('CheckboxFilter Component', () => { it('labels are clickable and toggle associated checkboxes', async () => { const properties = createMockProperties(2); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -447,7 +447,7 @@ describe('CheckboxFilter Component', () => { }, ]; const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -466,7 +466,7 @@ describe('CheckboxFilter Component', () => { { id: '3', name: '(Special) ', value: '3', selected: false }, ]; const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -481,7 +481,7 @@ describe('CheckboxFilter Component', () => { { id: '1', name: 'Only One', value: '1', selected: true }, ]; const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Single', filter, }); @@ -493,7 +493,7 @@ describe('CheckboxFilter Component', () => { it('handles very large number of properties', async () => { const properties = createMockProperties(50, [0, 25, 49]); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Large List', filter, }); @@ -506,7 +506,7 @@ describe('CheckboxFilter Component', () => { it('updates badge when filter is manipulated externally', async () => { const properties = createMockProperties(3); const filter = createTestFilter(properties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Test', filter, }); @@ -537,7 +537,7 @@ describe('CheckboxFilter Component', () => { { id: 'monospace', name: 'Monospace', value: 'monospace', selected: false }, ]; const filter = createTestFilter(realProperties); - render(CheckboxFilter, { + render(FilterGroup, { displayedLabel: 'Font Category', filter, }); -- 2.49.1 From 248ca7d8181c7aca7a0713ff456de330998fd08c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:39:20 +0300 Subject: [PATCH 43/88] fix(SearchBar): change component variant according to redesign --- src/shared/ui/SearchBar/SearchBar.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/ui/SearchBar/SearchBar.svelte b/src/shared/ui/SearchBar/SearchBar.svelte index 9f4c655..3c565e4 100644 --- a/src/shared/ui/SearchBar/SearchBar.svelte +++ b/src/shared/ui/SearchBar/SearchBar.svelte @@ -20,8 +20,8 @@ let { }: Props = $props(); - - {#snippet leftIcon(size)} + + {#snippet rightIcon(size)} {/snippet} -- 2.49.1 From 9af81c3f176eed032c05a7d874df1c40fc4f9a3c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:40:09 +0300 Subject: [PATCH 44/88] feat(FontSearch): refactor component to align it with new design --- .../ui/FontSearch/FontSearch.svelte | 64 ++++++------------- 1 file changed, 21 insertions(+), 43 deletions(-) diff --git a/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte b/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte index 5adbffc..07e0be6 100644 --- a/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte +++ b/src/widgets/FontSearch/ui/FontSearch/FontSearch.svelte @@ -13,6 +13,7 @@ import { import { springySlideFade } from '$shared/lib'; import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { + Button, Footnote, IconButton, SearchBar, @@ -66,62 +67,39 @@ function toggleFilters() { } -
-
+
+
-
-
-
-
- - {#snippet icon()} - - {/snippet} - -
-
-
+
{#if showFilters}
-
-
-
-
- - filter_params - -
- -
- -
- -
- -
+
+
+ +
{/if}
-- 2.49.1 From 3a9bd0c465bcdd538ebb52d1344f1a7d286eecd7 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:40:37 +0300 Subject: [PATCH 45/88] chore: fix imports --- src/features/GetFonts/ui/Filters/Filters.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/GetFonts/ui/Filters/Filters.svelte b/src/features/GetFonts/ui/Filters/Filters.svelte index 3210cd5..5c7749c 100644 --- a/src/features/GetFonts/ui/Filters/Filters.svelte +++ b/src/features/GetFonts/ui/Filters/Filters.svelte @@ -1,14 +1,14 @@ {#each filterManager.groups as group (group.id)} - -- 2.49.1 From e85f6639ff19a9e888956c5c5aa4c677bfe464e2 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:41:05 +0300 Subject: [PATCH 46/88] feat(FilterControls): refactor component to align it with new design --- .../ui/FiltersControl/FilterControls.svelte | 88 ++++++++++++++----- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/src/features/GetFonts/ui/FiltersControl/FilterControls.svelte b/src/features/GetFonts/ui/FiltersControl/FilterControls.svelte index d50e84c..40a1074 100644 --- a/src/features/GetFonts/ui/FiltersControl/FilterControls.svelte +++ b/src/features/GetFonts/ui/FiltersControl/FilterControls.svelte @@ -1,46 +1,86 @@
+ +
+ + +
+ {#each SORT_OPTIONS as option} + + + {/each} +
+
+ + +
-- 2.49.1 From 9983be650ab00924668838c0b5ac16e06ff0697c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:41:58 +0300 Subject: [PATCH 47/88] feat(TypographyMenu): refactor component to align it with new design --- .../SetupFont/ui/TypographyMenu.svelte | 137 ++++++------------ 1 file changed, 48 insertions(+), 89 deletions(-) diff --git a/src/features/SetupFont/ui/TypographyMenu.svelte b/src/features/SetupFont/ui/TypographyMenu.svelte index 9d8d6ba..db82f82 100644 --- a/src/features/SetupFont/ui/TypographyMenu.svelte +++ b/src/features/SetupFont/ui/TypographyMenu.svelte @@ -1,25 +1,16 @@ -- 2.49.1 From 12718593e34cefae90a6a40956e30db73c447a0d Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:42:18 +0300 Subject: [PATCH 48/88] feat(FontSampler): refactor component to align it with new design --- .../ui/FontSampler/FontSampler.svelte | 161 +++++++++++------- 1 file changed, 102 insertions(+), 59 deletions(-) diff --git a/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte b/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte index 0e43da1..92daa6d 100644 --- a/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte +++ b/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte @@ -1,6 +1,7 @@
-
-
- - typeface_{String(index).padStart(3, '0')} - -
-
+ +
+ +
+ + {String(index + 1).padStart(2, '0')} + + +
+ + {font.name} -
+ + + {#if fontType} + + {/if}
- + +
-
+ +
-
- - SZ:{fontSize}PX - - - - WGT:{fontWeight} - - - - LH:{lineHeight?.toFixed(2)} - - - - LTR:{letterSpacing} - + +
+ {#each stats as stat, i} + + {stat.label}:{stat.value} + + {#if i < stats.length - 1} + + {/if} + {/each} +
+ + +
-- 2.49.1 From d516a383e181daa5a7a65e06507fa20ab76b3cb5 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:43:21 +0300 Subject: [PATCH 49/88] chore(IconButon): delete unusual code --- .../ui/IconButton/IconButton.stories.svelte | 105 ------------------ src/shared/ui/IconButton/IconButton.svelte | 52 --------- 2 files changed, 157 deletions(-) delete mode 100644 src/shared/ui/IconButton/IconButton.stories.svelte delete mode 100644 src/shared/ui/IconButton/IconButton.svelte diff --git a/src/shared/ui/IconButton/IconButton.stories.svelte b/src/shared/ui/IconButton/IconButton.stories.svelte deleted file mode 100644 index b1e8b30..0000000 --- a/src/shared/ui/IconButton/IconButton.stories.svelte +++ /dev/null @@ -1,105 +0,0 @@ - - - - -{#snippet chevronRightIcon({ className }: { className: string })} - -{/snippet} - -{#snippet chevronLeftIcon({ className }: { className: string })} - -{/snippet} - -{#snippet plusIcon({ className }: { className: string })} - -{/snippet} - -{#snippet minusIcon({ className }: { className: string })} - -{/snippet} - -{#snippet settingsIcon({ className }: { className: string })} - -{/snippet} - -{#snippet xIcon({ className }: { className: string })} - -{/snippet} - - - {#snippet template(args)} - console.log('Default clicked')} {...args}> - {#snippet icon({ className })} - - {/snippet} - - {/snippet} - - - - {#snippet template(args)} -
- - {#snippet icon({ className })} - - {/snippet} - -
- {/snippet} -
diff --git a/src/shared/ui/IconButton/IconButton.svelte b/src/shared/ui/IconButton/IconButton.svelte deleted file mode 100644 index bb2b2b6..0000000 --- a/src/shared/ui/IconButton/IconButton.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - - - -- 2.49.1 From fbf6f3dcb4b6d5322f1b59c2a7303528e595bc03 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:44:57 +0300 Subject: [PATCH 50/88] feat(SampleList): create a component that wraps SampleList with Section --- .../SampleListSection.svelte | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/widgets/SampleList/ui/SampleListSection/SampleListSection.svelte diff --git a/src/widgets/SampleList/ui/SampleListSection/SampleListSection.svelte b/src/widgets/SampleList/ui/SampleListSection/SampleListSection.svelte new file mode 100644 index 0000000..c8c6d34 --- /dev/null +++ b/src/widgets/SampleList/ui/SampleListSection/SampleListSection.svelte @@ -0,0 +1,33 @@ + + + +
+ {#snippet content({ className })} +
+ +
+ {/snippet} +
-- 2.49.1 From 338f4e106cb683a612243679a62ff0919cdd7f86 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:45:07 +0300 Subject: [PATCH 51/88] feat(FontSearch): create a component that wraps SampleList with Section --- .../FontSearchSection.svelte | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/widgets/FontSearch/ui/FontSearchSection/FontSearchSection.svelte diff --git a/src/widgets/FontSearch/ui/FontSearchSection/FontSearchSection.svelte b/src/widgets/FontSearch/ui/FontSearchSection/FontSearchSection.svelte new file mode 100644 index 0000000..217f579 --- /dev/null +++ b/src/widgets/FontSearch/ui/FontSearchSection/FontSearchSection.svelte @@ -0,0 +1,31 @@ + + + +
+ {#snippet content({ className })} +
+ +
+ {/snippet} +
-- 2.49.1 From 1b0451faffcf2dd14a2741e17c4b98a70c6beb0f Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:46:52 +0300 Subject: [PATCH 52/88] chore: delete unused code --- .../Font/ui/FontListItem/FontListItem.svelte | 39 ------------------- src/shared/ui/Section/types.ts | 17 ++++++++ 2 files changed, 17 insertions(+), 39 deletions(-) delete mode 100644 src/entities/Font/ui/FontListItem/FontListItem.svelte create mode 100644 src/shared/ui/Section/types.ts diff --git a/src/entities/Font/ui/FontListItem/FontListItem.svelte b/src/entities/Font/ui/FontListItem/FontListItem.svelte deleted file mode 100644 index 9ea82c1..0000000 --- a/src/entities/Font/ui/FontListItem/FontListItem.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - -
- {@render children?.(font)} -
diff --git a/src/shared/ui/Section/types.ts b/src/shared/ui/Section/types.ts new file mode 100644 index 0000000..8a99739 --- /dev/null +++ b/src/shared/ui/Section/types.ts @@ -0,0 +1,17 @@ +import type { Snippet } from 'svelte'; + +/** + * Type for callback function to notify when the title visibility status changes + * + * @param index - Index of the section + * @param isPast - Whether the section is past the current scroll position + * @param title - Snippet for a title itself + * @param id - ID of the section + * @returns Cleanup callback + */ +export type TitleStatusChangeHandler = ( + index: number, + isPast: boolean, + title?: Snippet<[{ className?: string }]>, + id?: string, +) => () => void; -- 2.49.1 From 9f84769fba1c6e77e2f1b1f6113ee5e89209a3f9 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:48:14 +0300 Subject: [PATCH 53/88] chore: add/delete imports/exports --- src/entities/Breadcrumb/index.ts | 5 ++++- src/entities/Breadcrumb/model/index.ts | 1 + .../Breadcrumb/model/services/index.ts | 1 + src/entities/Font/index.ts | 1 - src/entities/Font/ui/index.ts | 2 -- src/shared/ui/Input/index.ts | 14 +------------ src/shared/ui/index.ts | 20 +++++++++++-------- src/widgets/FontSearch/index.ts | 5 ++++- src/widgets/FontSearch/ui/index.ts | 5 ++--- src/widgets/SampleList/index.ts | 5 ++++- src/widgets/SampleList/ui/index.ts | 5 ++--- src/widgets/index.ts | 4 ++++ 12 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 src/entities/Breadcrumb/model/services/index.ts diff --git a/src/entities/Breadcrumb/index.ts b/src/entities/Breadcrumb/index.ts index 18a6254..4fdbd0b 100644 --- a/src/entities/Breadcrumb/index.ts +++ b/src/entities/Breadcrumb/index.ts @@ -1,2 +1,5 @@ -export { scrollBreadcrumbsStore } from './model'; +export { + handleTitleStatusChanged, + scrollBreadcrumbsStore, +} from './model'; export { BreadcrumbHeader } from './ui'; diff --git a/src/entities/Breadcrumb/model/index.ts b/src/entities/Breadcrumb/model/index.ts index f177f5c..634e557 100644 --- a/src/entities/Breadcrumb/model/index.ts +++ b/src/entities/Breadcrumb/model/index.ts @@ -1 +1,2 @@ +export * from './services'; export * from './store/scrollBreadcrumbsStore.svelte'; diff --git a/src/entities/Breadcrumb/model/services/index.ts b/src/entities/Breadcrumb/model/services/index.ts new file mode 100644 index 0000000..81f8f56 --- /dev/null +++ b/src/entities/Breadcrumb/model/services/index.ts @@ -0,0 +1 @@ +export { handleTitleStatusChanged } from './handleTitleStatusChanged/handleTitleStatusChanged'; diff --git a/src/entities/Font/index.ts b/src/entities/Font/index.ts index 4c3340d..f073adf 100644 --- a/src/entities/Font/index.ts +++ b/src/entities/Font/index.ts @@ -131,6 +131,5 @@ export { // UI elements export { FontApplicator, - FontListItem, FontVirtualList, } from './ui'; diff --git a/src/entities/Font/ui/index.ts b/src/entities/Font/ui/index.ts index 2ed85bc..7dae687 100644 --- a/src/entities/Font/ui/index.ts +++ b/src/entities/Font/ui/index.ts @@ -1,9 +1,7 @@ import FontApplicator from './FontApplicator/FontApplicator.svelte'; -import FontListItem from './FontListItem/FontListItem.svelte'; import FontVirtualList from './FontVirtualList/FontVirtualList.svelte'; export { FontApplicator, - FontListItem, FontVirtualList, }; diff --git a/src/shared/ui/Input/index.ts b/src/shared/ui/Input/index.ts index 68249c7..db1dab6 100644 --- a/src/shared/ui/Input/index.ts +++ b/src/shared/ui/Input/index.ts @@ -1,13 +1 @@ -import type { ComponentProps } from 'svelte'; -import Input from './Input.svelte'; - -type InputProps = ComponentProps; -type InputSize = InputProps['size']; -type InputVariant = InputProps['variant']; - -export { - Input, - type InputProps, - type InputSize, - type InputVariant, -}; +export { default as Input } from './Input.svelte'; diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 4d7ba05..934734c 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -1,16 +1,17 @@ -export { default as CheckboxFilter } from './CheckboxFilter/CheckboxFilter.svelte'; +export { + Button, + ButtonGroup, + IconButton, + ToggleButton, +} from './Button'; export { default as ComboControl } from './ComboControl/ComboControl.svelte'; -export { default as ComboControlV2 } from './ComboControlV2/ComboControlV2.svelte'; export { default as ContentEditable } from './ContentEditable/ContentEditable.svelte'; export { default as Drawer } from './Drawer/Drawer.svelte'; export { default as ExpandableWrapper } from './ExpandableWrapper/ExpandableWrapper.svelte'; +export { default as FilterGroup } from './FilterGroup/FilterGroup.svelte'; export { default as Footnote } from './Footnote/Footnote.svelte'; -export { default as IconButton } from './IconButton/IconButton.svelte'; -export { - Input, - type InputSize, - type InputVariant, -} from './Input'; +export { default as GridBackground } from './GridBackground/GridBackground.svelte'; +export { default as Input } from './Input/Input.svelte'; export { default as Label } from './Label/Label.svelte'; export { default as Loader } from './Loader/Loader.svelte'; export { default as Logo } from './Logo/Logo.svelte'; @@ -20,4 +21,7 @@ export { default as Section } from './Section/Section.svelte'; export { default as SidebarMenu } from './SidebarMenu/SidebarMenu.svelte'; export { default as Skeleton } from './Skeleton/Skeleton.svelte'; export { default as Slider } from './Slider/Slider.svelte'; +export { default as Stat } from './Stat/Stat.svelte'; export { default as VirtualList } from './VirtualList/VirtualList.svelte'; + +export type { TitleStatusChangeHandler } from './Section/types'; diff --git a/src/widgets/FontSearch/index.ts b/src/widgets/FontSearch/index.ts index e9369b4..3092866 100644 --- a/src/widgets/FontSearch/index.ts +++ b/src/widgets/FontSearch/index.ts @@ -1 +1,4 @@ -export { FontSearch } from './ui'; +export { + FontSearch, + FontSearchSection, +} from './ui'; diff --git a/src/widgets/FontSearch/ui/index.ts b/src/widgets/FontSearch/ui/index.ts index e71451a..28259c6 100644 --- a/src/widgets/FontSearch/ui/index.ts +++ b/src/widgets/FontSearch/ui/index.ts @@ -1,3 +1,2 @@ -import FontSearch from './FontSearch/FontSearch.svelte'; - -export { FontSearch }; +export { default as FontSearch } from './FontSearch/FontSearch.svelte'; +export { default as FontSearchSection } from './FontSearchSection/FontSearchSection.svelte'; diff --git a/src/widgets/SampleList/index.ts b/src/widgets/SampleList/index.ts index fac592d..e1b3cf0 100644 --- a/src/widgets/SampleList/index.ts +++ b/src/widgets/SampleList/index.ts @@ -1 +1,4 @@ -export { SampleList } from './ui'; +export { + SampleList, + SampleListSection, +} from './ui'; diff --git a/src/widgets/SampleList/ui/index.ts b/src/widgets/SampleList/ui/index.ts index d73a19d..6773855 100644 --- a/src/widgets/SampleList/ui/index.ts +++ b/src/widgets/SampleList/ui/index.ts @@ -1,3 +1,2 @@ -import SampleList from './SampleList/SampleList.svelte'; - -export { SampleList }; +export { default as SampleList } from './SampleList/SampleList.svelte'; +export { default as SampleListSection } from './SampleListSection/SampleListSection.svelte'; diff --git a/src/widgets/index.ts b/src/widgets/index.ts index 9719c1a..13a31c1 100644 --- a/src/widgets/index.ts +++ b/src/widgets/index.ts @@ -1,2 +1,6 @@ export { ComparisonSlider } from './ComparisonSlider'; export { FontSearch } from './FontSearch'; +export { + SampleList, + SampleListSection, +} from './SampleList'; -- 2.49.1 From 8fa376ef946317441f27a408067eecf101131684 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:49:13 +0300 Subject: [PATCH 54/88] feat(handleTitleStatusChanged): create reusable handler for sections title status management --- .../handleTitleStatusChanged.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/entities/Breadcrumb/model/services/handleTitleStatusChanged/handleTitleStatusChanged.ts diff --git a/src/entities/Breadcrumb/model/services/handleTitleStatusChanged/handleTitleStatusChanged.ts b/src/entities/Breadcrumb/model/services/handleTitleStatusChanged/handleTitleStatusChanged.ts new file mode 100644 index 0000000..7e53688 --- /dev/null +++ b/src/entities/Breadcrumb/model/services/handleTitleStatusChanged/handleTitleStatusChanged.ts @@ -0,0 +1,29 @@ +import type { TitleStatusChangeHandler } from '$shared/ui'; +import type { Snippet } from 'svelte'; +import { scrollBreadcrumbsStore } from '../../store/scrollBreadcrumbsStore.svelte'; + +/** + * Updates the breadcrumb store when the title visibility status changes. + * + * @param index - Index of the section + * @param isPast - Whether the section is past the current scroll position + * @param title - Snippet for a title itself + * @param id - ID of the section + * @returns Cleanup callback + */ +export const handleTitleStatusChanged: TitleStatusChangeHandler = ( + index: number, + isPast: boolean, + title?: Snippet<[{ className?: string }]>, + id?: string, +) => { + if (isPast && title) { + scrollBreadcrumbsStore.add({ index, title, id }); + } else { + scrollBreadcrumbsStore.remove(index); + } + + return () => { + scrollBreadcrumbsStore.remove(index); + }; +}; -- 2.49.1 From 44bbac469510a0b3da362a5ff95811849b70f7cb Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 12:50:16 +0300 Subject: [PATCH 55/88] feat(Section) add headerContent snippet --- src/shared/ui/Section/Section.svelte | 33 +++++++++++++--------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/shared/ui/Section/Section.svelte b/src/shared/ui/Section/Section.svelte index 2648797..3b3f3d3 100644 --- a/src/shared/ui/Section/Section.svelte +++ b/src/shared/ui/Section/Section.svelte @@ -14,6 +14,7 @@ import { import SectionHeader from './SectionHeader/SectionHeader.svelte'; import SectionTitle from './SectionTitle/SectionTitle.svelte'; +import type { TitleStatusChangeHandler } from './types'; interface Props extends Omit, 'title'> { /** @@ -40,23 +41,16 @@ interface Props extends Omit, 'title'> { index?: number; /** * Callback function to notify when the title visibility status changes - * - * @param index - Index of the section - * @param isPast - Whether the section is past the current scroll position - * @param title - Snippet for a title itself - * @param id - ID of the section - * @returns Cleanup callback */ - onTitleStatusChange?: ( - index: number, - isPast: boolean, - title?: Snippet<[{ className?: string }]>, - id?: string, - ) => () => void; + onTitleStatusChange?: TitleStatusChangeHandler; /** * Snippet for the section content */ content?: Snippet<[{ className?: string }]>; + /** + * Snippet for the section header content + */ + headerContent?: Snippet<[{ className?: string }]>; } const { @@ -64,11 +58,11 @@ const { title, headerTitle, headerSubtitle, - description, index = 0, onTitleStatusChange, id, content, + headerContent, }: Props = $props(); const flyParams: FlyParams = { @@ -86,11 +80,14 @@ const flyParams: FlyParams = { in:fly={flyParams} out:fly={flyParams} > -
- {#if headerTitle} - - {/if} - +
+
+ {#if headerTitle} + + {/if} + +
+ {@render headerContent?.({})}
{@render content?.({})} -- 2.49.1 From fb6cd495d38e470dbd2be750f2047d881fa02754 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 13:00:03 +0300 Subject: [PATCH 56/88] feat(VirtualList): add different layout support --- src/shared/ui/VirtualList/VirtualList.svelte | 186 +++++++++++++------ 1 file changed, 127 insertions(+), 59 deletions(-) diff --git a/src/shared/ui/VirtualList/VirtualList.svelte b/src/shared/ui/VirtualList/VirtualList.svelte index 82c5a85..93d2e9d 100644 --- a/src/shared/ui/VirtualList/VirtualList.svelte +++ b/src/shared/ui/VirtualList/VirtualList.svelte @@ -51,6 +51,16 @@ interface Props { * (follows shadcn convention for className prop) */ class?: string; + /** + * Number of columns for grid layout. + * @default 1 + */ + columns?: number; + /** + * Gap between items in pixels. + * @default 0 + */ + gap?: number; /** * An optional callback that will be called for each new set of loaded items * @param items - Loaded items @@ -131,18 +141,26 @@ let { children, useWindowScroll = false, isLoading = false, + columns = 1, + gap = 0, }: Props = $props(); // Reference to the scroll container element for attaching the virtualizer let viewportRef = $state(null); +// Calculate row-based counts for grid layout +const rowCount = $derived(Math.ceil(items.length / columns)); +const totalRows = $derived(Math.ceil(total / columns)); + // Use items.length for count to keep existing item positions stable // But calculate a separate totalSize for scrollbar that accounts for unloaded items const virtualizer = createVirtualizer(() => ({ // Only virtualize loaded items - this keeps positions stable when new items load - count: items.length, + count: rowCount, data: items, - estimateSize: typeof itemHeight === 'function' ? itemHeight : () => itemHeight, + estimateSize: typeof itemHeight === 'function' + ? (index: number) => itemHeight(index) + gap + : () => itemHeight + gap, overscan, useWindowScroll, })); @@ -150,25 +168,26 @@ const virtualizer = createVirtualizer(() => ({ // Calculate total size including unloaded items for proper scrollbar sizing // Use estimateSize() for items that haven't been loaded yet const estimatedTotalSize = $derived.by(() => { - if (total === items.length) { + if (totalRows === rowCount) { // No unloaded items, use virtualizer's totalSize return virtualizer.totalSize; } - // Start with the virtualized (loaded) items size + // Start with the virtualized (loaded) rows size const loadedSize = virtualizer.totalSize; - // Add estimated size for unloaded items - const unloadedCount = total - items.length; - if (unloadedCount <= 0) return loadedSize; + // Add estimated size for unloaded rows + const unloadedRows = totalRows - rowCount; + if (unloadedRows <= 0) return loadedSize; - // Estimate the size of unloaded items - // Get the average size of loaded items, or use the estimateSize function - const estimateFn = typeof itemHeight === 'function' ? itemHeight : () => itemHeight; + // Estimate the size of unloaded rows + const estimateFn = typeof itemHeight === 'function' + ? (index: number) => itemHeight(index * columns) + gap + : () => itemHeight + gap; - // Use estimateSize for unloaded items (index from items.length to total - 1) + // Use estimateSize for unloaded rows (index from rowCount to totalRows - 1) let unloadedSize = 0; - for (let i = items.length; i < total; i++) { + for (let i = rowCount; i < totalRows; i++) { unloadedSize += estimateFn(i); } @@ -191,8 +210,31 @@ const throttledNearBottom = throttle((lastVisibleIndex: number) => { onNearBottom?.(lastVisibleIndex); }, 200); // 200ms debounce +// Calculate top/bottom padding for spacer elements +const topPad = $derived( + virtualizer.items.length > 0 ? virtualizer.items[0].start - gap : 0, +); +const botPad = $derived( + virtualizer.items.length > 0 + ? Math.max( + 0, + estimatedTotalSize + - (virtualizer.items[virtualizer.items.length - 1].end + gap), + ) + : estimatedTotalSize, +); + $effect(() => { - const visibleItems = virtualizer.items.map(item => items[item.index]); + // Expand row indices to item indices + const visibleItemIndices: number[] = []; + for (const row of virtualizer.items) { + const startItemIndex = row.index * columns; + const endItemIndex = Math.min(startItemIndex + columns, items.length); + for (let i = startItemIndex; i < endItemIndex; i++) { + visibleItemIndices.push(i); + } + } + const visibleItems = visibleItemIndices.map(index => items[index]); throttledVisibleChange(visibleItems); }); @@ -200,41 +242,87 @@ $effect(() => { // Trigger onNearBottom when user scrolls near the end of loaded items (within 5 items) // Only trigger if container has sufficient height to avoid false positives if (virtualizer.items.length > 0 && onNearBottom && virtualizer.containerHeight > 100) { - const lastVisibleItem = virtualizer.items[virtualizer.items.length - 1]; + const lastVisibleRow = virtualizer.items[virtualizer.items.length - 1]; + // Convert row index to last item index in that row + const lastVisibleItemIndex = Math.min( + (lastVisibleRow.index + 1) * columns - 1, + items.length - 1, + ); // Compare against loaded items length, not total - const itemsRemaining = items.length - lastVisibleItem.index; + const itemsRemaining = items.length - lastVisibleItemIndex; if (itemsRemaining <= 5) { - throttledNearBottom(lastVisibleItem.index); + throttledNearBottom(lastVisibleItemIndex); } } }); +{#snippet content()} +
+ +
+
+ + {#each virtualizer.items as row (row.key)} + {#if row.index < rowCount} + {@const startItemIndex = row.index * columns} + {@const endItemIndex = Math.min(startItemIndex + columns, items.length)} + {#each Array.from({ length: endItemIndex - startItemIndex }) as _, colIndex (startItemIndex + colIndex)} + {@const itemIndex = startItemIndex + colIndex} + {#if colIndex === 0} +
+ {#if itemIndex < items.length} + {@render children({ + item: items[itemIndex], + index: itemIndex, + isFullyVisible: row.isFullyVisible, + isPartiallyVisible: row.isPartiallyVisible, + proximity: row.proximity, +})} + {/if} +
+ {:else} +
+ {#if itemIndex < items.length} + {@render children({ + item: items[itemIndex], + index: itemIndex, + isFullyVisible: row.isFullyVisible, + isPartiallyVisible: row.isPartiallyVisible, + proximity: row.proximity, +})} + {/if} +
+ {/if} + {/each} + {/if} + {/each} + + +
+
+
+{/snippet} + {#if useWindowScroll}
-
- {#each virtualizer.items as item (item.key)} -
- {#if item.index < items.length} - {@render children({ - // TODO: Fix indentation rule for this case - item: items[item.index], - index: item.index, - isFullyVisible: item.isFullyVisible, - isPartiallyVisible: item.isPartiallyVisible, - proximity: item.proximity, -})} - {/if} -
- {/each} -
+ {@render content()}
{:else}
{ className, )} > -
- {#each virtualizer.items as item (item.key)} -
- {#if item.index < items.length} - {@render children({ - // TODO: Fix indentation rule for this case - item: items[item.index], - index: item.index, - isFullyVisible: item.isFullyVisible, - isPartiallyVisible: item.isPartiallyVisible, - proximity: item.proximity, -})} - {/if} -
- {/each} -
+ {@render content()}
{/if} -- 2.49.1 From 3a813b019b73ba554b043f649d5500a13020aed7 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 13:00:58 +0300 Subject: [PATCH 57/88] chore: rename --- src/entities/Font/model/types/common.ts | 4 ++-- src/shared/lib/helpers/createFilter/createFilter.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/entities/Font/model/types/common.ts b/src/entities/Font/model/types/common.ts index bd6536e..0e225cc 100644 --- a/src/entities/Font/model/types/common.ts +++ b/src/entities/Font/model/types/common.ts @@ -30,8 +30,8 @@ export interface FontFilters { subsets: FontSubset[]; } -export type CheckboxFilter = 'providers' | 'categories' | 'subsets'; -export type FilterType = CheckboxFilter | 'searchQuery'; +export type FilterGroup = 'providers' | 'categories' | 'subsets'; +export type FilterType = FilterGroup | 'searchQuery'; /** * Standard font weights diff --git a/src/shared/lib/helpers/createFilter/createFilter.test.ts b/src/shared/lib/helpers/createFilter/createFilter.test.ts index ee979bd..8e77f46 100644 --- a/src/shared/lib/helpers/createFilter/createFilter.test.ts +++ b/src/shared/lib/helpers/createFilter/createFilter.test.ts @@ -9,7 +9,7 @@ import { * Test Suite for createFilter Helper Function * * This suite tests the Filter logic and state management. - * Component rendering tests are in CheckboxFilter.svelte.test.ts + * Component rendering tests are in FilterGroup.svelte.test.ts */ describe('createFilter - Filter Logic', () => { -- 2.49.1 From 80feda41a3df302aa387e013efbd0cd83758bcb5 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 18:35:40 +0300 Subject: [PATCH 58/88] feat(createResponsiveManager): rewrote ifs to switch case --- .../createResponsiveManager.svelte.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/shared/lib/helpers/createResponsiveManager/createResponsiveManager.svelte.ts b/src/shared/lib/helpers/createResponsiveManager/createResponsiveManager.svelte.ts index 1332c22..6556732 100644 --- a/src/shared/lib/helpers/createResponsiveManager/createResponsiveManager.svelte.ts +++ b/src/shared/lib/helpers/createResponsiveManager/createResponsiveManager.svelte.ts @@ -146,12 +146,20 @@ export function createResponsiveManager(customBreakpoints?: Partial */ const currentBreakpoint = $derived( (() => { - if (isMobile) return 'mobile'; - if (isTabletPortrait) return 'tabletPortrait'; - if (isTablet) return 'tablet'; - if (isDesktop) return 'desktop'; - if (isDesktopLarge) return 'desktopLarge'; - return 'xs'; // Fallback for very small screens + switch (true) { + case isMobile: + return 'mobile'; + case isTabletPortrait: + return 'tabletPortrait'; + case isTablet: + return 'tablet'; + case isDesktop: + return 'desktop'; + case isDesktopLarge: + return 'desktopLarge'; + default: + return 'xs'; // Fallback for very small screens + } })(), ); -- 2.49.1 From f0aa89097e594e3ac77db63f7390948daf5a833c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 27 Feb 2026 18:39:09 +0300 Subject: [PATCH 59/88] feat(TypographyMenu): rewrite from hidden class to if based rendering --- .../SetupFont/ui/TypographyMenu.svelte | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/features/SetupFont/ui/TypographyMenu.svelte b/src/features/SetupFont/ui/TypographyMenu.svelte index db82f82..3150ada 100644 --- a/src/features/SetupFont/ui/TypographyMenu.svelte +++ b/src/features/SetupFont/ui/TypographyMenu.svelte @@ -10,6 +10,7 @@ import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { ComboControl } from '$shared/ui'; import Settings2Icon from '@lucide/svelte/icons/settings-2'; import { getContext } from 'svelte'; +import { cubicOut } from 'svelte/easing'; import { fly } from 'svelte/transition'; import { MULTIPLIER_L, @@ -47,47 +48,48 @@ $effect(() => { } }); - -