feat(TypographyControls): drasticaly reduce animations, keep only the container functional
This commit is contained in:
@@ -1,198 +1,51 @@
|
||||
<!--
|
||||
Component: TypographyControls
|
||||
Wrapper for the controls of the slider.
|
||||
- Input to change the text
|
||||
- Three combo controls with inputs and sliders for font-weight, font-size, and line-height
|
||||
Controls for text input and typography settings (size, weight, height).
|
||||
Simplified version for static positioning in settings mode.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||
import {
|
||||
ComboControlV2,
|
||||
ExpandableWrapper,
|
||||
Input,
|
||||
} from '$shared/ui';
|
||||
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 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>
|
||||
|
||||
{#snippet InputComponent(className: string)}
|
||||
<Input
|
||||
class={className}
|
||||
bind:value={comparisonStore.text}
|
||||
disabled={isDragging}
|
||||
onfocusin={handleInputFocus}
|
||||
placeholder="The quick brown fox..."
|
||||
/>
|
||||
{/snippet}
|
||||
<!-- Text input -->
|
||||
<Input
|
||||
bind:value={comparisonStore.text}
|
||||
size="sm"
|
||||
label="Text"
|
||||
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 Controls(className: string, orientation: Orientation)}
|
||||
{#if typography.weightControl && typography.sizeControl && typography.heightControl}
|
||||
<div class={className}>
|
||||
<ComboControlV2 control={typography.weightControl} {orientation} reduced />
|
||||
<ComboControlV2 control={typography.sizeControl} {orientation} reduced />
|
||||
<ComboControlV2 control={typography.heightControl} {orientation} reduced />
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div
|
||||
class="z-50 will-change-transform"
|
||||
style:transform="
|
||||
translateX({xSpring.current}px)
|
||||
rotateZ({rotateSpring.current}deg)
|
||||
"
|
||||
in:fade={{ duration: 300, delay: 300 }}
|
||||
out:fade={{ duration: 300, delay: 300 }}
|
||||
>
|
||||
{#if staticPosition}
|
||||
<div class="flex flex-col gap-6">
|
||||
{@render InputComponent?.('p-6')}
|
||||
{@render Controls?.('flex flex-col justify-between items-center-safe gap-6', 'horizontal')}
|
||||
</div>
|
||||
{:else}
|
||||
<ExpandableWrapper
|
||||
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>
|
||||
<!-- Typography controls -->
|
||||
{#if typography.weightControl && typography.sizeControl && typography.heightControl}
|
||||
<div class="flex flex-col gap-1.5 mt-1.5">
|
||||
<ComboControlV2
|
||||
control={typography.weightControl}
|
||||
orientation="horizontal"
|
||||
class="sm:py-0"
|
||||
showScale={false}
|
||||
reduced
|
||||
/>
|
||||
<ComboControlV2
|
||||
control={typography.sizeControl}
|
||||
orientation="horizontal"
|
||||
class="sm:py-0"
|
||||
showScale={false}
|
||||
reduced
|
||||
/>
|
||||
<ComboControlV2
|
||||
control={typography.heightControl}
|
||||
orientation="horizontal"
|
||||
class="sm:py-0"
|
||||
showScale={false}
|
||||
reduced
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user