feature/project-redesign #28
@@ -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
|
||||||
|
variant="ghost"
|
||||||
|
active={sort === option}
|
||||||
|
onclick={() => onSortChange?.(option)}
|
||||||
|
class="text-xs font-bold uppercase tracking-wide font-primary"
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</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
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group flex flex-1 cursor-pointer gap-1"
|
onclick={handleReset}
|
||||||
onclick={handleClick}
|
class="
|
||||||
|
group
|
||||||
|
text-[0.5625rem] md:text-[0.625rem] font-mono font-bold uppercase tracking-widest
|
||||||
|
text-neutral-400
|
||||||
|
"
|
||||||
|
iconPosition="left"
|
||||||
>
|
>
|
||||||
<Rotate class="size-4 group-hover:-rotate-180 transition-transform duration-300" />
|
{#snippet icon()}
|
||||||
Reset
|
<RefreshCwIcon class="size-4 transition-transform duration-300 group-hover:rotate-180" />
|
||||||
|
{/snippet}
|
||||||
|
Reset_Filters
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user