feature/ux-improvements #26
83
src/shared/ui/PerspectivePlan/PerspectivePlan.svelte
Normal file
83
src/shared/ui/PerspectivePlan/PerspectivePlan.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<!--
|
||||
Component: PerspectivePlan
|
||||
Wrapper that applies perspective transformations based on back/front state.
|
||||
Style computation moved from manager to component for simpler architecture.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { PerspectiveManager } from '$shared/lib';
|
||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||
import { type Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Perspective manager
|
||||
*/
|
||||
manager: PerspectiveManager;
|
||||
/**
|
||||
* Additional classes
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
* Children
|
||||
*/
|
||||
children: Snippet<[{ className?: string }]>;
|
||||
/**
|
||||
* Constrain plan to a horizontal region
|
||||
* 'left' | 'right' | 'full' (default)
|
||||
*/
|
||||
region?: 'left' | 'right' | 'full';
|
||||
/**
|
||||
* Width percentage when using left/right region (default 50)
|
||||
*/
|
||||
regionWidth?: number;
|
||||
}
|
||||
|
||||
let { manager, children, class: className = '', region = 'full', regionWidth = 50 }: Props = $props();
|
||||
|
||||
const config = $derived(manager.getConfig());
|
||||
|
||||
// Computed style based on spring position (0 = front, 1 = back)
|
||||
const style = $derived.by(() => {
|
||||
const distance = manager.spring.current;
|
||||
const baseX = config.horizontalOffset ?? 0;
|
||||
|
||||
// Back state: blurred, scaled down, pushed back
|
||||
// Front state: fully visible, in focus
|
||||
const scale = 1 - distance * (config.scaleStep ?? 0.5);
|
||||
const blur = distance * (config.blurStep ?? 4);
|
||||
const opacity = Math.max(0, 1 - distance * (config.opacityStep ?? 0.5));
|
||||
const zIndex = 10;
|
||||
const pointerEvents = distance < 0.4 ? 'auto' : ('none' as const);
|
||||
|
||||
return {
|
||||
transform: `translate3d(${baseX}px, 0px, ${-distance * (config.depthStep ?? 100)}px) scale(${scale})`,
|
||||
filter: `blur(${blur}px)`,
|
||||
opacity,
|
||||
pointerEvents,
|
||||
zIndex,
|
||||
};
|
||||
});
|
||||
|
||||
// Calculate horizontal constraints based on region
|
||||
const regionStyleStr = $derived(() => {
|
||||
if (region === 'full') return '';
|
||||
const side = region === 'left' ? 'left' : 'right';
|
||||
return `position: absolute; ${side}: 0; width: ${regionWidth}%; top: 0; bottom: 0;`;
|
||||
});
|
||||
|
||||
// Visibility: front = visible, back = hidden
|
||||
const isVisible = $derived(manager.isFront);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn('will-change-transform', className)}
|
||||
style:transform-style="preserve-3d"
|
||||
style:transform={style?.transform}
|
||||
style:filter={style?.filter}
|
||||
style:opacity={style?.opacity}
|
||||
style:pointer-events={style?.pointerEvents}
|
||||
style:z-index={style?.zIndex}
|
||||
style:custom={regionStyleStr()}
|
||||
>
|
||||
{@render children({ className: isVisible ? 'visible' : 'hidden' })}
|
||||
</div>
|
||||
Reference in New Issue
Block a user