2026-01-18 14:59:00 +03:00
|
|
|
<!--
|
|
|
|
|
Component: FontListItem
|
|
|
|
|
Displays a font item with a checkbox and its characteristics in badges.
|
|
|
|
|
-->
|
2026-01-18 12:59:12 +03:00
|
|
|
<script lang="ts">
|
|
|
|
|
import { Badge } from '$shared/shadcn/ui/badge';
|
2026-01-22 15:41:55 +03:00
|
|
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
|
|
|
|
import { Spring } from 'svelte/motion';
|
2026-01-18 14:59:00 +03:00
|
|
|
import {
|
|
|
|
|
type UnifiedFont,
|
|
|
|
|
selectedFontsStore,
|
|
|
|
|
} from '../../model';
|
2026-01-18 12:59:12 +03:00
|
|
|
import FontApplicator from '../FontApplicator/FontApplicator.svelte';
|
|
|
|
|
|
|
|
|
|
interface Props {
|
2026-01-18 15:55:07 +03:00
|
|
|
/**
|
|
|
|
|
* Object with information about font
|
|
|
|
|
*/
|
2026-01-18 12:59:12 +03:00
|
|
|
font: UnifiedFont;
|
2026-01-22 15:41:55 +03:00
|
|
|
/**
|
|
|
|
|
* Is element fully visible
|
|
|
|
|
*/
|
|
|
|
|
isVisible: boolean;
|
|
|
|
|
/**
|
|
|
|
|
* From 0 to 1
|
|
|
|
|
*/
|
|
|
|
|
proximity: number;
|
2026-01-18 12:59:12 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-22 15:41:55 +03:00
|
|
|
const { font, isVisible, proximity }: Props = $props();
|
|
|
|
|
|
|
|
|
|
let selected = $state(selectedFontsStore.has(font.id));
|
|
|
|
|
let timeoutId = $state<NodeJS.Timeout | null>(null);
|
|
|
|
|
|
|
|
|
|
// Create a spring for smooth scale animation
|
|
|
|
|
const scale = new Spring(1, {
|
|
|
|
|
stiffness: 0.3,
|
|
|
|
|
damping: 0.7,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Springs react to the virtualizer's computed state
|
|
|
|
|
const bloom = new Spring(0, {
|
|
|
|
|
stiffness: 0.15,
|
|
|
|
|
damping: 0.6,
|
|
|
|
|
});
|
2026-01-18 12:59:12 +03:00
|
|
|
|
2026-01-22 15:41:55 +03:00
|
|
|
// Sync spring to proximity for a "Lens" effect
|
|
|
|
|
$effect(() => {
|
|
|
|
|
bloom.target = isVisible ? 1 : 0;
|
|
|
|
|
});
|
2026-01-18 14:59:00 +03:00
|
|
|
|
2026-01-22 15:41:55 +03:00
|
|
|
$effect(() => {
|
|
|
|
|
selected = selectedFontsStore.has(font.id);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
if (timeoutId) {
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function handleClick() {
|
|
|
|
|
animateSelection();
|
|
|
|
|
selected ? selectedFontsStore.removeOne(font.id) : selectedFontsStore.addOne(font);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function animateSelection() {
|
|
|
|
|
scale.target = 0.98;
|
|
|
|
|
|
|
|
|
|
timeoutId = setTimeout(() => {
|
|
|
|
|
scale.target = 1;
|
|
|
|
|
}, 150);
|
|
|
|
|
}
|
2026-01-18 12:59:12 +03:00
|
|
|
</script>
|
|
|
|
|
|
2026-01-22 15:41:55 +03:00
|
|
|
<div
|
|
|
|
|
class={cn('pb-1 will-change-transform')}
|
|
|
|
|
style:opacity={bloom.current}
|
|
|
|
|
style:transform="
|
|
|
|
|
scale({0.92 + (bloom.current * 0.08)})
|
|
|
|
|
translateY({(1 - bloom.current) * 10}px)
|
|
|
|
|
"
|
|
|
|
|
>
|
|
|
|
|
<div style:transform={`scale(${scale.current})`}>
|
|
|
|
|
<div
|
|
|
|
|
class={cn(
|
|
|
|
|
'w-full hover:bg-accent/50 flex items-start gap-3 rounded-lg border border-transparent p-3',
|
|
|
|
|
'active:transition-transform active:duration-150',
|
|
|
|
|
'border dark:border-slate-800',
|
|
|
|
|
'bg-white/10 border-white/20',
|
|
|
|
|
isVisible && 'bg-white/40 border-white/40',
|
|
|
|
|
selected && 'ring-2 ring-indigo-600 ring-inset bg-indigo-50/50 hover:bg-indigo-50',
|
|
|
|
|
)}
|
|
|
|
|
role="button"
|
|
|
|
|
tabindex="0"
|
|
|
|
|
onmousedown={(e => {
|
|
|
|
|
// Prevent browser focus-jump
|
|
|
|
|
if (e.currentTarget === document.activeElement) return;
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
handleClick();
|
|
|
|
|
})}
|
|
|
|
|
onkeydown={(e => {
|
|
|
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
handleClick();
|
|
|
|
|
}
|
|
|
|
|
})}
|
|
|
|
|
>
|
|
|
|
|
<div class="w-full">
|
|
|
|
|
<div class="flex flex-row gap-1 w-full items-center justify-between">
|
|
|
|
|
<div class="flex flex-col gap-1 transition-all duration-150 ease-out">
|
|
|
|
|
<div class="flex flex-row gap-1">
|
|
|
|
|
<Badge variant="outline" class="text-[0.5rem]">
|
|
|
|
|
{font.provider}
|
|
|
|
|
</Badge>
|
|
|
|
|
<Badge variant="outline" class="text-[0.5rem]">
|
|
|
|
|
{font.category}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
<FontApplicator
|
|
|
|
|
id={font.id}
|
|
|
|
|
className="text-2xl"
|
|
|
|
|
name={font.name}
|
|
|
|
|
>
|
|
|
|
|
{font.name}
|
|
|
|
|
</FontApplicator>
|
2026-01-18 12:59:12 +03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-22 15:41:55 +03:00
|
|
|
</div>
|
2026-01-18 12:59:12 +03:00
|
|
|
</div>
|