feature/ux-improvements #26
@@ -0,0 +1,202 @@
|
||||
<!--
|
||||
Component: FontList
|
||||
A scrollable list of fonts with dual selection buttons for fontA and fontB.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import {
|
||||
FontVirtualList,
|
||||
type UnifiedFont,
|
||||
} from '$entities/Font';
|
||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||
import { comparisonStore } from '$widgets/ComparisonSlider/model';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { draw } from 'svelte/transition';
|
||||
|
||||
const fontA = $derived(comparisonStore.fontA);
|
||||
const fontB = $derived(comparisonStore.fontB);
|
||||
const typography = $derived(comparisonStore.typography);
|
||||
|
||||
/**
|
||||
* Select a font as fontA (right slot - compare_to)
|
||||
*/
|
||||
function selectFontA(font: UnifiedFont) {
|
||||
comparisonStore.fontA = font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a font as fontB (left slot - compare_from)
|
||||
*/
|
||||
function selectFontB(font: UnifiedFont) {
|
||||
comparisonStore.fontB = font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a font is selected as fontA
|
||||
*/
|
||||
function isFontA(font: UnifiedFont): boolean {
|
||||
return fontA?.id === font.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a font is selected as fontB
|
||||
*/
|
||||
function isFontB(font: UnifiedFont): boolean {
|
||||
return fontB?.id === font.id;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#snippet rightBrackets(className?: string)}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={cn(
|
||||
'lucide lucide-focus-icon lucide-focus right-0 top-0 absolute',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<path
|
||||
transition:draw={{ duration: 300, delay: 150, easing: cubicOut }}
|
||||
d="M17 3h2a2 2 0 0 1 2 2v2"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={cn(
|
||||
'lucide lucide-focus-icon lucide-focus right-0 bottom-0 absolute',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<path
|
||||
transition:draw={{ duration: 300, delay: 150, easing: cubicOut }}
|
||||
d="M21 17v2a2 2 0 0 1-2 2h-2"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
{#snippet leftBrackets(className?: string)}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={cn(
|
||||
'lucide lucide-focus-icon lucide-focus left-0 top-0 absolute',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<path
|
||||
transition:draw={{ duration: 300, delay: 150, easing: cubicOut }}
|
||||
d="M3 7V5a2 2 0 0 1 2-2h2"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={cn(
|
||||
'lucide lucide-focus-icon lucide-focus left-0 bottom-0 absolute',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<path
|
||||
transition:draw={{ duration: 300, delay: 150, easing: cubicOut }}
|
||||
d="M7 21H5a2 2 0 0 1-2-2v-2"
|
||||
/>
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
{#snippet brackets(renderLeft?: boolean, renderRight?: boolean, className?: string)}
|
||||
{#if renderLeft}
|
||||
{@render leftBrackets(className)}
|
||||
{/if}
|
||||
{#if renderRight}
|
||||
{@render rightBrackets(className)}
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div class="flex flex-col h-full min-h-0 bg-transparent">
|
||||
<div class="flex-1 min-h-0">
|
||||
<FontVirtualList
|
||||
weight={typography.weight}
|
||||
itemHeight={36}
|
||||
class="bg-transparent"
|
||||
>
|
||||
{#snippet children({ item: font })}
|
||||
{@const isSelectedA = isFontA(font)}
|
||||
{@const isSelectedB = isFontB(font)}
|
||||
{@const isEither = isSelectedA || isSelectedB}
|
||||
{@const isBoth = isSelectedA && isSelectedB}
|
||||
{@const handleSelectFontA = () => selectFontA(font)}
|
||||
{@const handleSelectFontB = () => selectFontB(font)}
|
||||
|
||||
<div class="group relative flex w-auto h-[36px] border-b border-black/[0.03] overflow-hidden mr-4 lg:mr-6">
|
||||
<div
|
||||
class={cn(
|
||||
'absolute inset-0 flex items-center justify-center z-20 pointer-events-none transition-all duration-500 cubic-bezier-out',
|
||||
isSelectedB && !isBoth && '-translate-x-1/4',
|
||||
isSelectedA && !isBoth && 'translate-x-1/4',
|
||||
isBoth && 'translate-x-0',
|
||||
)}
|
||||
>
|
||||
<div class="relative flex items-center px-6">
|
||||
<span
|
||||
class={cn(
|
||||
'font-mono text-[10px] sm:text-[11px] uppercase tracking-tighter select-none transition-all duration-300',
|
||||
isEither
|
||||
? 'opacity-100 font-bold'
|
||||
: 'opacity-30 group-hover:opacity-100',
|
||||
isSelectedB && 'text-indigo-500',
|
||||
isSelectedA && 'text-normal-950',
|
||||
isBoth && 'text-indigo-600',
|
||||
)}
|
||||
>
|
||||
--- {font.name} ---
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onclick={handleSelectFontB}
|
||||
class="flex-1 relative flex items-center justify-between transition-all duration-200 cursor-pointer hover:bg-indigo-500/[0.03]"
|
||||
>
|
||||
{@render brackets(isSelectedB, isSelectedB && !isBoth, 'stroke-1 size-7 stroke-indigo-600')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onclick={handleSelectFontA}
|
||||
class="flex-1 relative flex items-center justify-end transition-all duration-200 cursor-pointer hover:bg-black/[0.02]"
|
||||
>
|
||||
{@render brackets(isSelectedA && !isBoth, isSelectedA, 'stroke-1 size-7 stroke-normal-950')}
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</FontVirtualList>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user