feat(TypographyControls): drasticaly reduce animations, keep only the container functional

This commit is contained in:
Ilia Mashkov
2026-02-15 23:07:23 +03:00
parent 72334a3d05
commit 99966d2de9

View File

@@ -1,198 +1,51 @@
<!-- <!--
Component: TypographyControls Component: TypographyControls
Wrapper for the controls of the slider. Controls for text input and typography settings (size, weight, height).
- Input to change the text Simplified version for static positioning in settings mode.
- Three combo controls with inputs and sliders for font-weight, font-size, and line-height
--> -->
<script lang="ts"> <script lang="ts">
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { cn } from '$shared/shadcn/utils/shadcn-utils';
import { import {
ComboControlV2, ComboControlV2,
ExpandableWrapper,
Input, Input,
} from '$shared/ui'; } from '$shared/ui';
import { comparisonStore } from '$widgets/ComparisonSlider/model'; import { comparisonStore } from '$widgets/ComparisonSlider/model';
import AArrowUP from '@lucide/svelte/icons/a-arrow-up';
import { type Orientation } from 'bits-ui';
import { untrack } from 'svelte';
import { Spring } from 'svelte/motion';
import { fade } from 'svelte/transition';
interface Props {
/**
* Ref
*/
wrapper?: HTMLDivElement | null;
/**
* Slider position
*/
sliderPos: number;
/**
* Whether slider is being dragged
*/
isDragging: boolean;
/** */
isActive?: boolean;
/**
* Container width
*/
containerWidth: number;
/**
* Reduced animations flag
*/
staticPosition?: boolean;
}
let {
sliderPos,
isDragging,
isActive = $bindable(false),
wrapper = $bindable(null),
containerWidth = 0,
staticPosition = false,
}: Props = $props();
const typography = $derived(comparisonStore.typography); const typography = $derived(comparisonStore.typography);
const panelWidth = $derived(wrapper?.clientWidth ?? 0);
const margin = 24;
let side = $state<'left' | 'right'>('left');
// Unified active state for the entire wrapper
let timeoutId = $state<ReturnType<typeof setTimeout> | null>(null);
const xSpring = new Spring(0, {
stiffness: 0.14, // Lower is slower
damping: 0.5, // Settle
});
const rotateSpring = new Spring(0, {
stiffness: 0.12,
damping: 0.55,
});
function handleInputFocus() {
isActive = true;
}
// Movement Logic
$effect(() => {
if (containerWidth === 0 || panelWidth === 0 || staticPosition) {
return;
}
const sliderX = (sliderPos / 100) * containerWidth;
const buffer = 40;
const leftTrigger = margin + panelWidth + buffer;
const rightTrigger = containerWidth - (margin + panelWidth + buffer);
if (side === 'left' && sliderX < leftTrigger) {
side = 'right';
} else if (side === 'right' && sliderX > rightTrigger) {
side = 'left';
}
});
$effect(() => {
// Trigger only when side changes
const currentSide = side;
untrack(() => {
if (containerWidth > 0 && panelWidth > 0) {
const targetX = currentSide === 'right'
? containerWidth - panelWidth - margin * 2
: 0;
// On side change set the position and the rotation
xSpring.target = targetX;
rotateSpring.target = currentSide === 'right' ? 3.5 : -3.5;
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
rotateSpring.target = 0;
}, 600);
}
});
return () => {
if (timeoutId) clearTimeout(timeoutId);
};
});
</script> </script>
{#snippet InputComponent(className: string)} <!-- Text input -->
<Input <Input
class={className} bind:value={comparisonStore.text}
bind:value={comparisonStore.text} size="sm"
disabled={isDragging} label="Text"
onfocusin={handleInputFocus} placeholder="The quick brown fox..."
placeholder="The quick brown fox..." class="w-full px-3 py-2 h-10 rounded-lg border border-border-muted bg-background-60 backdrop-blur-sm mr-4"
/> />
{/snippet}
{#snippet Controls(className: string, orientation: Orientation)} <!-- Typography controls -->
{#if typography.weightControl && typography.sizeControl && typography.heightControl} {#if typography.weightControl && typography.sizeControl && typography.heightControl}
<div class={className}> <div class="flex flex-col gap-1.5 mt-1.5">
<ComboControlV2 control={typography.weightControl} {orientation} reduced /> <ComboControlV2
<ComboControlV2 control={typography.sizeControl} {orientation} reduced /> control={typography.weightControl}
<ComboControlV2 control={typography.heightControl} {orientation} reduced /> orientation="horizontal"
</div> class="sm:py-0"
{/if} showScale={false}
{/snippet} reduced
/>
<div <ComboControlV2
class="z-50 will-change-transform" control={typography.sizeControl}
style:transform=" orientation="horizontal"
translateX({xSpring.current}px) class="sm:py-0"
rotateZ({rotateSpring.current}deg) showScale={false}
" reduced
in:fade={{ duration: 300, delay: 300 }} />
out:fade={{ duration: 300, delay: 300 }} <ComboControlV2
> control={typography.heightControl}
{#if staticPosition} orientation="horizontal"
<div class="flex flex-col gap-6"> class="sm:py-0"
{@render InputComponent?.('p-6')} showScale={false}
{@render Controls?.('flex flex-col justify-between items-center-safe gap-6', 'horizontal')} reduced
</div> />
{:else} </div>
<ExpandableWrapper {/if}
bind:element={wrapper}
bind:expanded={isActive}
disabled={isDragging}
aria-label="Font controls"
rotation={side === 'right' ? 'counterclockwise' : 'clockwise'}
class={cn(
'transition-opacity flex items-top gap-1.5',
panelWidth === 0 ? 'opacity-0' : 'opacity-100',
)}
containerClassName={cn(!isActive && 'p-2 sm:p-0')}
>
{#snippet badge()}
<div
class={cn(
'animate-nudge relative transition-all',
side === 'left' ? 'order-2' : 'order-0',
isActive ? 'opacity-0' : 'opacity-100',
isDragging && 'opacity-80 grayscale-[0.2]',
)}
>
<AArrowUP class={cn('size-3', 'stroke-indigo-600')} />
</div>
{/snippet}
{#snippet visibleContent()}
{@render InputComponent(cn(
'pl-1 sm:pl-3 pr-1 sm:pr-3',
'h-6 sm:h-8 md:h-10',
'rounded-lg',
isActive
? 'h-7 sm:h-8 text-[0.825rem]'
: 'bg-transparent shadow-none border-none p-0 h-auto text-sm sm:text-base font-medium focus-visible:ring-0 text-slate-900/50',
))}
{/snippet}
{#snippet hiddenContent()}
{@render Controls?.('flex flex-row justify-between items-center-safe gap-2 sm:gap-0 h-64', 'vertical')}
{/snippet}
</ExpandableWrapper>
{/if}
</div>