|
|
|
@@ -0,0 +1,107 @@
|
|
|
|
|
<!--
|
|
|
|
|
Component: FontSearch
|
|
|
|
|
|
|
|
|
|
Combines search input with font list display
|
|
|
|
|
-->
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import { fontshareStore } from '$entities/Font';
|
|
|
|
|
import {
|
|
|
|
|
FilterControls,
|
|
|
|
|
Filters,
|
|
|
|
|
SuggestedFonts,
|
|
|
|
|
filterManager,
|
|
|
|
|
mapManagerToParams,
|
|
|
|
|
} from '$features/GetFonts';
|
|
|
|
|
import { springySlideFade } from '$shared/lib';
|
|
|
|
|
import { Button } from '$shared/shadcn/ui/button';
|
|
|
|
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
|
|
|
|
import { SearchBar } from '$shared/ui';
|
|
|
|
|
import FunnelIcon from '@lucide/svelte/icons/funnel';
|
|
|
|
|
import { onMount } from 'svelte';
|
|
|
|
|
import { cubicOut } from 'svelte/easing';
|
|
|
|
|
import {
|
|
|
|
|
Tween,
|
|
|
|
|
prefersReducedMotion,
|
|
|
|
|
} from 'svelte/motion';
|
|
|
|
|
import { type SlideParams } from 'svelte/transition';
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
showFilters?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let { showFilters = $bindable(false) }: Props = $props();
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
/**
|
|
|
|
|
* The Pairing:
|
|
|
|
|
* We "plug" this manager into the global store.
|
|
|
|
|
* addBinding returns a function that removes this binding when the component unmounts.
|
|
|
|
|
*/
|
|
|
|
|
const unbind = fontshareStore.addBinding(() => mapManagerToParams(filterManager));
|
|
|
|
|
|
|
|
|
|
return unbind;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const transform = new Tween(
|
|
|
|
|
{ scale: 1, rotate: 0 },
|
|
|
|
|
{ duration: 250, easing: cubicOut },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const slideConfig = $derived<SlideParams>({
|
|
|
|
|
duration: prefersReducedMotion.current ? 0 : 200,
|
|
|
|
|
easing: cubicOut,
|
|
|
|
|
axis: 'y',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function toggleFilters() {
|
|
|
|
|
showFilters = !showFilters;
|
|
|
|
|
|
|
|
|
|
transform.set({ scale: 0.9, rotate: 2 }).then(() => {
|
|
|
|
|
transform.set({ scale: 1, rotate: 0 });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<div class="flex flex-col gap-2 relative">
|
|
|
|
|
<SearchBar
|
|
|
|
|
id="font-search"
|
|
|
|
|
class="w-full"
|
|
|
|
|
placeholder="Search fonts by name..."
|
|
|
|
|
bind:value={filterManager.queryValue}
|
|
|
|
|
>
|
|
|
|
|
<SuggestedFonts />
|
|
|
|
|
</SearchBar>
|
|
|
|
|
|
|
|
|
|
<div class="absolute right-5 top-10 translate-y-[-50%] pl-5 border-l-2">
|
|
|
|
|
<div style:transform="scale({transform.current.scale}) rotate({transform.current.rotate}deg)">
|
|
|
|
|
<Button
|
|
|
|
|
class={cn(
|
|
|
|
|
'cursor-pointer will-change-transform hover:bg-inherit hover:*:stroke-indigo-500',
|
|
|
|
|
showFilters ? 'hover:*:stroke-indigo-500/80' : 'hover:*:stroke-indigo-500',
|
|
|
|
|
)}
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onclick={toggleFilters}
|
|
|
|
|
>
|
|
|
|
|
<FunnelIcon
|
|
|
|
|
class={cn(
|
|
|
|
|
'size-8 stroke-indigo-600/50 transition-all duration-150',
|
|
|
|
|
showFilters ? 'stroke-indigo-600' : 'stroke-indigo-600/50',
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if showFilters}
|
|
|
|
|
<div
|
|
|
|
|
transition:springySlideFade|local={slideConfig}
|
|
|
|
|
class="will-change-[height,opacity] contain-layout overflow-hidden"
|
|
|
|
|
>
|
|
|
|
|
<div class="grid gap-1 grid-cols-[repeat(auto-fit,minmax(8em,14em))]">
|
|
|
|
|
<Filters />
|
|
|
|
|
<FilterControls class="ml-auto py-1" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|