2026-02-19 13:58:12 +03:00
< script module >
import { cn } from '$shared/shadcn/utils/shadcn-utils' ;
import { defineMeta } from '@storybook/addon-svelte-csf' ;
import Section from './Section.svelte' ;
const { Story } = defineMeta ({
title : 'Shared/Section' ,
component : Section ,
tags : [ 'autodocs' ],
parameters : {
docs : {
description : {
component :
'Page layout component with optional sticky title feature. Provides a container for page widgets with title, icon, description snippets. The title can remain fixed while scrolling through content.' ,
},
story : { inline : false },
},
layout : 'fullscreen' ,
},
argTypes : {
id : {
control : 'text' ,
description : 'ID of the section' ,
},
index : {
control : 'number' ,
description : 'Index of the section (used for default description)' ,
},
stickyTitle : {
control : 'boolean' ,
description : 'When true, title stays fixed while scrolling through content' ,
},
stickyOffset : {
control : 'text' ,
description : 'Top offset for sticky title (e.g. "60px")' ,
},
class : {
control : 'text' ,
description : 'Additional CSS classes' ,
},
onTitleStatusChange : {
action : 'titleStatusChanged' ,
description : 'Callback when title visibility status changes' ,
},
},
});
</ script >
< script lang = "ts" >
import ListIcon from '@lucide/svelte/icons/list' ;
import SearchIcon from '@lucide/svelte/icons/search' ;
import SettingsIcon from '@lucide/svelte/icons/settings' ;
</ script >
{ # snippet searchIcon ({ className } : { className? : string })}
< SearchIcon class = { className } / >
{ /snippet }
{ # snippet welcomeTitle ({ className } : { className? : string })}
< h2 class = { className } > Welcome</h2 >
{ /snippet }
{ # snippet welcomeContent ({ className } : { className? : string })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted" >
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.
</ p >
</ div >
{ /snippet }
{ # snippet stickyTitle ({ className } : { className? : string })}
< h2 class = { className } > Sticky Title </ h2 >
{ /snippet }
{ # snippet stickyContent ({ className } : { className? : string })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted mb-4" >
This section has a sticky title that stays fixed while you scroll through the content. Try scrolling down to
see the effect.
</ p >
< div class = "space-y-4" >
< p class = "text-text-muted" >
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua.
</ p >
< p class = "text-text-muted" >
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
</ p >
< p class = "text-text-muted" >
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</ p >
< p class = "text-text-muted" >
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollim anim id est
laborum.
</ p >
< p class = "text-text-muted" >
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.
</ p >
</ div >
</ div >
{ /snippet }
{ # snippet searchFontsTitle ({ className } : { className? : string })}
< h2 class = { className } > Search Fonts </ h2 >
{ /snippet }
{ # snippet searchFontsDescription ({ className } : { className? : string })}
< span class = { className } > Find your perfect typeface </ span >
{ /snippet }
{ # snippet searchFontsContent ({ className } : { className? : string })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted" >
Use the search bar to find fonts by name, or use the filters to browse by category, subset, or provider.
</ p >
</ div >
{ /snippet }
{ # snippet longContentTitle ({ className } : { className? : string })}
< h2 class = { className } > Long Content </ h2 >
{ /snippet }
{ # snippet longContent ({ className } : { className? : string })}
< div class = { cn ( className , 'min-w-128' )} >
< div class = "space-y-6" >
< p class = "text-lg text-text-muted" >
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.
</ p >
< div class = "h-64 bg-background-40 rounded-lg flex items-center justify-center" >
< span class = "text-text-muted" > Content block 1</ span >
</ div >
< p class = "text-text-muted" >
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.
</ p >
< div class = "h-64 bg-background-40 rounded-lg flex items-center justify-center" >
< span class = "text-text-muted" > Content block 2</ span >
</ div >
< p class = "text-text-muted" >
The Intersection Observer API is used to detect when the section title scrolls out of view, triggering
the optional onTitleStatusChange callback.
</ p >
< div class = "h-64 bg-background-40 rounded-lg flex items-center justify-center" >
< span class = "text-text-muted" > Content block 3</ span >
</ div >
</ div >
</ div >
{ /snippet }
{ # snippet minimalTitle ({ className } : { className? : string })}
< h2 class = { className } > Minimal Section </ h2 >
{ /snippet }
{ # snippet minimalContent ({ className } : { className? : string })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-text-muted" >
A minimal section without index, icon, or description. Just the essentials.
</ p >
</ div >
{ /snippet }
{ # snippet customTitle ({ className } : { className? : string })}
< h2 class = { className } > Custom Content </ h2 >
{ /snippet }
{ # snippet customDescription ({ className } : { className? : string })}
< span class = { className } > With interactive elements </ span >
{ /snippet }
{ # snippet customContent ({ className } : { className? : string })}
< div class = { cn ( className , 'min-w-128' )} >
< div class = "grid grid-cols-2 gap-4" >
< div class = "p-4 bg-background-40 rounded-lg" >
< h3 class = "font-semibold mb-2" > Card 1</ h3 >
< p class = "text-sm text-text-muted" > Some content here</ p >
</ div >
< div class = "p-4 bg-background-40 rounded-lg" >
< h3 class = "font-semibold mb-2" > Card 2</ h3 >
< p class = "text-sm text-text-muted" > More content here</ p >
</ div >
</ div >
</ div >
{ /snippet }
< Story
name = "Default"
args = {{
title : welcomeTitle ,
content : welcomeContent ,
}}
>
2026-02-22 11:25:02 +03:00
{ # snippet template ( args )}
< div class = "grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12 p-8 max-w-6xl mx-auto" >
< Section index = { 1 } {... args } >
{ # snippet title ({ className })}
< h2 class = { className } > Welcome</h2 >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted" >
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.
</ p >
</ div >
{ /snippet }
</ Section >
</ div >
{ /snippet }
2026-02-19 13:58:12 +03:00
</ Story >
< Story
name = "With Sticky Title"
args = {{
title : stickyTitle ,
content : stickyContent ,
}}
>
2026-02-22 11:25:02 +03:00
{ # snippet template ( args )}
< div class = "h-[200vh]" >
< div class = "grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12 p-8 max-w-6xl mx-auto" >
< Section
id = "sticky-section"
index = { 1 }
stickyTitle= { true }
stickyOffset = "20px"
{... args }
>
{ # snippet title ({ className })}
< h2 class = { className } > Sticky Title </ h2 >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted mb-4" >
This section has a sticky title that stays fixed while you scroll through the content.
Try scrolling down to see the effect.
2026-02-19 13:58:12 +03:00
</ p >
2026-02-22 11:25:02 +03:00
< div class = "space-y-4" >
< p class = "text-text-muted" >
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
</ p >
< p class = "text-text-muted" >
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat.
</ p >
< p class = "text-text-muted" >
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur.
</ p >
< p class = "text-text-muted" >
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollim anim id est laborum.
</ p >
< p class = "text-text-muted" >
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
doloremque laudantium.
</ p >
</ div >
2026-02-19 13:58:12 +03:00
</ div >
2026-02-22 11:25:02 +03:00
{ /snippet }
</ Section >
</ div >
2026-02-19 13:58:12 +03:00
</ div >
2026-02-22 11:25:02 +03:00
{ /snippet }
2026-02-19 13:58:12 +03:00
</ Story >
< Story
name = "With Icon and Description"
args = {{
icon : searchIcon ,
title : searchFontsTitle ,
description : searchFontsDescription ,
content : searchFontsContent ,
}}
>
2026-02-22 11:25:02 +03:00
{ # snippet template ( args )}
< div class = "grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12 p-8 max-w-6xl mx-auto" >
< Section index = { 1 } {... args } >
{ # snippet title ({ className })}
< h2 class = { className } > Search Fonts </ h2 >
{ /snippet }
{ # snippet icon ({ className })}
< SearchIcon class = { className } / >
{ /snippet }
{ # snippet description ({ className })}
< span class = { className } > Find your perfect typeface </ span >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted" >
Use the search bar to find fonts by name, or use the filters to browse by category, subset,
or provider.
</ p >
</ div >
{ /snippet }
</ Section >
</ div >
{ /snippet }
2026-02-19 13:58:12 +03:00
</ Story >
< Story name = "Multiple Sections" tags = {[ '!autodocs' ]} >
2026-02-22 11:25:02 +03:00
{ # snippet template ( args )}
< div class = "grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12 p-8 max-w-6xl mx-auto" >
< Section
id = "section-1"
index = { 1 }
stickyTitle= { true }
{... args }
>
{ # snippet title ({ className })}
< h2 class = { className } > Typography</h2 >
{ /snippet }
{ # snippet icon ({ className })}
< SettingsIcon class = { className } / >
{ /snippet }
{ # snippet description ({ className })}
< span class = { className } > Adjust text appearance </ span >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted" >
Control the size, weight, and line height of your text. These settings apply across the
comparison view.
</ p >
</ div >
{ /snippet }
</ Section >
2026-02-19 13:58:12 +03:00
2026-02-22 11:25:02 +03:00
< Section
id = "section-2"
index = { 2 }
stickyTitle= { true }
{... args }
>
{ # snippet title ({ className })}
< h2 class = { className } > Font Search </ h2 >
{ /snippet }
{ # snippet icon ({ className })}
< SearchIcon class = { className } / >
{ /snippet }
{ # snippet description ({ className })}
< span class = { className } > Browse available typefaces </ span >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted" >
Search through our collection of fonts from Google Fonts and Fontshare. Use filters to
narrow down your selection.
</ p >
</ div >
{ /snippet }
</ Section >
2026-02-19 13:58:12 +03:00
2026-02-22 11:25:02 +03:00
< Section
id = "section-3"
index = { 3 }
stickyTitle= { true }
{... args }
>
{ # snippet title ({ className })}
< h2 class = { className } > Sample List </ h2 >
{ /snippet }
{ # snippet icon ({ className })}
< ListIcon class = { className } / >
{ /snippet }
{ # snippet description ({ className })}
< span class = { className } > Preview font samples </ span >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-lg text-text-muted" >
Browse through font samples with your custom text. The list is virtualized for optimal
performance.
</ p >
</ div >
{ /snippet }
</ Section >
</ div >
{ /snippet }
2026-02-19 13:58:12 +03:00
</ Story >
< Story
name = "With Long Content"
args = {{
title : longContentTitle ,
content : longContent ,
}}
>
2026-02-22 11:25:02 +03:00
{ # snippet template ( args )}
< div class = "grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12 p-8 max-w-6xl mx-auto" >
< Section
index = { 1 }
stickyTitle= { true }
stickyOffset = "0px"
{... args }
>
{ # snippet title ({ className })}
< h2 class = { className } > Long Content </ h2 >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< div class = "space-y-6" >
< p class = "text-lg text-text-muted" >
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.
</ p >
< div class = "h-64 bg-background-40 rounded-lg flex items-center justify-center" >
< span class = "text-text-muted" > Content block 1</ span >
</ div >
< p class = "text-text-muted" >
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.
</ p >
< div class = "h-64 bg-background-40 rounded-lg flex items-center justify-center" >
< span class = "text-text-muted" > Content block 2</ span >
</ div >
< p class = "text-text-muted" >
The Intersection Observer API is used to detect when the section title scrolls out of
view, triggering the optional onTitleStatusChange callback.
</ p >
< div class = "h-64 bg-background-40 rounded-lg flex items-center justify-center" >
< span class = "text-text-muted" > Content block 3</ span >
</ div >
2026-02-19 13:58:12 +03:00
</ div >
</ div >
2026-02-22 11:25:02 +03:00
{ /snippet }
</ Section >
</ div >
{ /snippet }
2026-02-19 13:58:12 +03:00
</ Story >
< Story
name = "Minimal"
args = {{
title : minimalTitle ,
content : minimalContent ,
}}
>
2026-02-22 11:25:02 +03:00
{ # snippet template ( args )}
< div class = "grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12 p-8 max-w-6xl mx-auto" >
< Section {... args }>
{ # snippet title ({ className })}
< h2 class = { className } > Minimal Section </ h2 >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< p class = "text-text-muted" >
A minimal section without index, icon, or description. Just the essentials.
</ p >
</ div >
{ /snippet }
</ Section >
</ div >
{ /snippet }
2026-02-19 13:58:12 +03:00
</ Story >
< Story
name = "Custom Content"
args = {{
title : customTitle ,
description : customDescription ,
content : customContent ,
}}
>
2026-02-22 11:25:02 +03:00
{ # snippet template ( args )}
< div class = "grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12 p-8 max-w-6xl mx-auto" >
< Section index = { 42 } {... args } >
{ # snippet title ({ className })}
< h2 class = { className } > Custom Content </ h2 >
{ /snippet }
{ # snippet description ({ className })}
< span class = { className } > With interactive elements </ span >
{ /snippet }
{ # snippet content ({ className })}
< div class = { cn ( className , 'min-w-128' )} >
< div class = "grid grid-cols-2 gap-4" >
< div class = "p-4 bg-background-40 rounded-lg" >
< h3 class = "font-semibold mb-2" > Card 1</ h3 >
< p class = "text-sm text-text-muted" > Some content here</ p >
</ div >
< div class = "p-4 bg-background-40 rounded-lg" >
< h3 class = "font-semibold mb-2" > Card 2</ h3 >
< p class = "text-sm text-text-muted" > More content here</ p >
</ div >
2026-02-19 13:58:12 +03:00
</ div >
</ div >
2026-02-22 11:25:02 +03:00
{ /snippet }
</ Section >
</ div >
{ /snippet }
2026-02-19 13:58:12 +03:00
</ Story >