feat(FilterControls): refactor component to align it with new design

This commit is contained in:
Ilia Mashkov
2026-02-27 12:41:05 +03:00
parent 3a9bd0c465
commit e85f6639ff

View File

@@ -1,46 +1,86 @@
<!-- <!--
Component: FiltersControl Component: FilterControls
Renders a group of action buttons for filter operations. Sort options + Reset_Filters button.
- Reset: Clears all active filters (outline variant for secondary action) Sits below the filter list, separated by a top border.
--> -->
<script lang="ts"> <script lang="ts">
import { Button } from '$shared/shadcn/ui/button';
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { cn } from '$shared/shadcn/utils/shadcn-utils';
import Rotate from '@lucide/svelte/icons/rotate-ccw'; import { Button } from '$shared/ui';
import { cubicOut } from 'svelte/easing'; import { Label } from '$shared/ui';
import { Tween } from 'svelte/motion'; import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
import { filterManager } from '../../model'; import { filterManager } from '../../model';
type SortOption = 'Name' | 'Popularity' | 'Newest';
const SORT_OPTIONS: SortOption[] = ['Name', 'Popularity', 'Newest'];
interface Props { interface Props {
sort?: SortOption;
onSortChange?: (v: SortOption) => void;
class?: string; class?: string;
} }
const { class: className }: Props = $props(); const {
sort = 'Popularity',
onSortChange,
class: className,
}: Props = $props();
const transform = new Tween( function handleReset() {
{ scale: 1, rotate: 0 },
{ duration: 150, easing: cubicOut },
);
function handleClick() {
filterManager.deselectAllGlobal(); filterManager.deselectAllGlobal();
transform.set({ scale: 0.98, rotate: 1 }).then(() => {
transform.set({ scale: 1, rotate: 0 });
});
} }
</script> </script>
<div <div
class={cn('flex flex-row gap-2', className)} class={cn(
style:transform="scale({transform.current.scale}) rotate({transform.current.rotate}deg)" 'flex flex-col md:flex-row justify-between items-start md:items-center',
'gap-4 md:gap-6',
'pt-6 mt-6 md:pt-8 md:mt-8',
'border-t border-foreground/5 dark:border-white/10',
className,
)}
> >
<!-- Left: Sort By label + options -->
<div class="flex flex-col md:flex-row items-start md:items-center gap-3 md:gap-8 w-full md:w-auto">
<Label variant="muted" size="sm">Sort By:</Label>
<div class="flex gap-3 md:gap-4">
{#each SORT_OPTIONS as option}
<!--
Ghost button with red-only hover (no bg lift).
active prop turns text [#ff3b30] for the selected sort.
class overrides: Space_Grotesk bold tracking-wide, no padding bg.
-->
<Button <Button
variant="ghost" variant="ghost"
class="group flex flex-1 cursor-pointer gap-1" active={sort === option}
onclick={handleClick} onclick={() => onSortChange?.(option)}
class="text-xs font-bold uppercase tracking-wide font-primary"
> >
<Rotate class="size-4 group-hover:-rotate-180 transition-transform duration-300" /> {option}
Reset </Button>
{/each}
</div>
</div>
<!-- Right: Reset_Filters -->
<!--
Bare ghost, red hover. Space_Mono to match Swiss technical text pattern.
Icon uses CSS group-hover for the spin — no Tween needed.
-->
<Button
variant="ghost"
onclick={handleReset}
class="
group
text-[0.5625rem] md:text-[0.625rem] font-mono font-bold uppercase tracking-widest
text-neutral-400
"
iconPosition="left"
>
{#snippet icon()}
<RefreshCwIcon class="size-4 transition-transform duration-300 group-hover:rotate-180" />
{/snippet}
Reset_Filters
</Button> </Button>
</div> </div>