feat(Section): component redesign

This commit is contained in:
Ilia Mashkov
2026-02-25 10:04:25 +03:00
parent 629dd15628
commit ea858dfdda

View File

@@ -11,7 +11,9 @@ import {
type FlyParams, type FlyParams,
fly, fly,
} from 'svelte/transition'; } from 'svelte/transition';
import { Footnote } from '..';
import SectionHeader from './SectionHeader/SectionHeader.svelte';
import SectionTitle from './SectionTitle/SectionTitle.svelte';
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'title'> { interface Props extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
/** /**
@@ -23,17 +25,15 @@ interface Props extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
*/ */
class?: string; class?: string;
/** /**
* Snippet for a title itself * Title of the section
*/ */
title?: Snippet<[{ className?: string }]>; title: string;
/**
* Snippet for a title icon
*/
icon?: Snippet<[{ className?: string }]>;
/** /**
* Snippet for a title description * Snippet for a title description
*/ */
description?: Snippet<[{ className?: string }]>; description?: Snippet<[{ className?: string }]>;
headerTitle?: string;
headerSubtitle?: string;
/** /**
* Index of the section * Index of the section
*/ */
@@ -57,32 +57,20 @@ interface Props extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
* Snippet for the section content * Snippet for the section content
*/ */
content?: Snippet<[{ className?: string }]>; 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 { const {
class: className, class: className,
title, title,
icon, headerTitle,
headerSubtitle,
description, description,
index = 0, index = 0,
onTitleStatusChange, onTitleStatusChange,
id, id,
content, content,
stickyTitle = false,
stickyOffset = '0px',
}: Props = $props(); }: Props = $props();
let titleContainer = $state<HTMLElement>();
const flyParams: FlyParams = { const flyParams: FlyParams = {
y: 0, y: 0,
x: -50, x: -50,
@@ -90,90 +78,19 @@ const flyParams: FlyParams = {
easing: cubicOut, easing: cubicOut,
opacity: 0.2, 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);
};
});
</script> </script>
<section <section
{id} {id}
class={cn( class="w-full max-w-7xl mx-auto px-4 md:px-6 pb-32 md:pb-48"
'col-span-2 grid grid-cols-subgrid',
stickyTitle ? 'gap-x-6 sm:gap-x-8 md:gap-x-10 lg:gap-x-12' : 'grid-rows-[max-content_1fr]',
className,
)}
in:fly={flyParams} in:fly={flyParams}
out:fly={flyParams} out:fly={flyParams}
> >
<div <div>
bind:this={titleContainer} {#if headerTitle}
class={cn( <SectionHeader title={headerTitle} subtitle={headerSubtitle} index={index} />
'flex flex-col gap-2 sm:gap-3',
stickyTitle && 'self-start',
)}
style:position={stickyTitle ? 'sticky' : undefined}
style:top={stickyTitle ? stickyOffset : undefined}
>
<div class="flex items-center gap-2 sm:gap-3">
{#if icon}
{@render icon({
className: 'size-3 sm:size-4 stroke-foreground stroke-1 opacity-60',
})}
<div class="w-px h-2.5 sm:h-3 bg-border-subtle"></div>
{/if}
{#if description}
<Footnote>
{#snippet render({ class: className })}
{@render description({ className })}
{/snippet}
</Footnote>
{:else if typeof index === 'number'}
<Footnote>
Component_{String(index).padStart(3, '0')}
</Footnote>
{/if}
</div>
{#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} {/if}
<SectionTitle text={title} />
</div> </div>
{@render content?.({})}
{@render content?.({
className: stickyTitle
? 'row-start-2 col-start-2'
: 'row-start-2 col-start-2',
})}
</section> </section>