107 lines
3.9 KiB
Svelte
107 lines
3.9 KiB
Svelte
<!--
|
|
Component: Slider
|
|
Styled bits-ui slider component with single value.
|
|
-->
|
|
<script lang="ts">
|
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
|
import {
|
|
Slider,
|
|
type SliderRootProps,
|
|
} from 'bits-ui';
|
|
|
|
type Props = Omit<SliderRootProps, 'type' | 'onValueChange' | 'onValueCommit'> & {
|
|
/**
|
|
* Slider value, numeric.
|
|
*/
|
|
value: number;
|
|
/**
|
|
* A callback function called when the value changes.
|
|
* @param newValue - number
|
|
*/
|
|
onValueChange?: (newValue: number) => void;
|
|
/**
|
|
* A callback function called when the user stops dragging the thumb and the value is committed.
|
|
* @param newValue - number
|
|
*/
|
|
onValueCommit?: (newValue: number) => void;
|
|
};
|
|
|
|
let { value = $bindable(), orientation = 'horizontal', class: className, ...rest }: Props = $props();
|
|
</script>
|
|
|
|
<Slider.Root
|
|
bind:value={value}
|
|
class={cn(
|
|
'relative flex h-full w-6 touch-none select-none items-center justify-center',
|
|
orientation === 'horizontal' ? 'w-48 h-6' : 'w-6 h-48',
|
|
className,
|
|
)}
|
|
type="single"
|
|
{orientation}
|
|
{...rest}
|
|
>
|
|
{#snippet children(props)}
|
|
<span
|
|
{...props}
|
|
class={cn('relative bg-background-muted rounded-full', orientation === 'horizontal' ? 'w-full h-px' : 'h-full w-px')}
|
|
>
|
|
<!-- Filled range with NO transition -->
|
|
<Slider.Range
|
|
class={cn('absolute bg-foreground rounded-full', orientation === 'horizontal' ? 'h-full' : 'w-full')}
|
|
/>
|
|
|
|
<Slider.Thumb
|
|
index={0}
|
|
class={cn(
|
|
'group/thumb relative block',
|
|
'size-2',
|
|
orientation === 'horizontal' ? '-top-1' : '-left-1',
|
|
'rounded-sm',
|
|
'bg-foreground',
|
|
// Glow shadow
|
|
'shadow-[0_0_6px_rgba(0,0,0,0.4)]',
|
|
// Smooth transitions only for size/position
|
|
'duration-200 ease-out',
|
|
orientation === 'horizontal' ? 'transition-[height,top,left,box-shadow]' : 'transition-[width,top,left,box-shadow]',
|
|
// Hover: bigger glow
|
|
'hover:shadow-[0_0_10px_rgba(0,0,0,0.5)]',
|
|
orientation === 'horizontal' ? 'hover:size-3 hover:-top-[5.5px]' : 'hover:size-3 hover:-left-[5.5px]',
|
|
// Active: smaller glow
|
|
'active:shadow-[0_0_4px_rgba(0,0,0,0.3)]',
|
|
orientation === 'horizontal' ? 'active:h-2.5 active:-top-[4.5px]' : 'active:w-2.5 active:-left-[4.5px]',
|
|
'focus:outline-none',
|
|
'cursor-grab active:cursor-grabbing',
|
|
)}
|
|
>
|
|
<!-- Soft glow on hover -->
|
|
<div
|
|
class="
|
|
absolute inset-0 rounded-sm
|
|
bg-background-20
|
|
opacity-0 group-hover/thumb:opacity-100
|
|
transition-opacity duration-200
|
|
"
|
|
>
|
|
</div>
|
|
|
|
<!-- Value label -->
|
|
<span
|
|
class={cn(
|
|
'absolute',
|
|
orientation === 'horizontal' ? '-top-8 left-1/2 -translate-x-1/2' : 'left-5 top-1/2 -translate-y-1/2',
|
|
'px-1.5 py-0.5 rounded-md',
|
|
'bg-foreground/90 backdrop-blur-sm',
|
|
'font-mono text-[0.625rem] font-medium text-background',
|
|
'opacity-0 group-hover/thumb:opacity-100',
|
|
'transition-all duration-300',
|
|
'pointer-events-none',
|
|
'shadow-sm',
|
|
)}
|
|
>
|
|
{value}
|
|
</span>
|
|
</Slider.Thumb>
|
|
</span>
|
|
{/snippet}
|
|
</Slider.Root>
|