shadimage

Slider

A range slider component with tooltip and inline output options

registry/preskok/ui/preskok-ui/slider.tsx

"use client"

import { use } from "react"
import { twMerge } from "cn"
import {
  Slider as PrimitiveSlider,
  SliderOutput as PrimitiveSliderOutput,
  SliderThumb as PrimitiveSliderThumb,
  SliderTrack as PrimitiveSliderTrack,
  SliderStateContext,
  type SliderProps,
} from "react-aria-components/Slider"

import { cx } from "@/lib/primitive"

export function SliderGroup({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      className="flex items-center gap-x-3 *:data-[slot=icon]:size-5"
      {...props}
    />
  )
}

export function Slider({ className, ...props }: SliderProps) {
  return (
    <PrimitiveSlider
      data-slot="control"
      className={cx(
        "group relative flex touch-none flex-col select-none disabled:opacity-50",
        "orientation-horizontal:w-full orientation-horizontal:min-w-fit orientation-horizontal:gap-y-2",
        "orientation-vertical:h-full orientation-vertical:min-h-fit orientation-vertical:w-1.5 orientation-vertical:items-center orientation-vertical:gap-y-2",
        className
      )}
      {...props}
    />
  )
}

export function SliderOutput({
  className,
  ...props
}: React.ComponentProps<typeof PrimitiveSliderOutput>) {
  return (
    <PrimitiveSliderOutput
      className={cx("text-base/6 font-medium sm:text-sm/6", className)}
      {...props}
    />
  )
}

export function SliderThumb({
  className,
  ...props
}: React.ComponentProps<typeof PrimitiveSliderThumb>) {
  return (
    <PrimitiveSliderThumb
      className={cx(
        "border-foreground/10 top-[50%] left-[50%] size-[1.25rem] rounded-full border bg-white ring-black outline-hidden transition-[width,height]",
        className
      )}
      {...props}
    />
  )
}

export function SliderTrack({
  className,
  children,
  ...props
}: React.ComponentProps<typeof PrimitiveSliderTrack>) {
  return (
    <PrimitiveSliderTrack
      className={cx(
        "bg-(--slider-track-background,var(--color-secondary))",
        "group/track relative cursor-default rounded-full",
        "group-orientation-horizontal:h-1.5 group-orientation-horizontal:w-full group-orientation-vertical:w-1.5 group-orientation-vertical:flex-1 grow",
        "disabled:cursor-default disabled:opacity-60",
        className
      )}
      {...props}
    >
      {(values) => (
        <>
          {typeof children === "function"
            ? children(values)
            : (children ?? (
                <>
                  <SliderFill />
                  <SliderThumb />
                </>
              ))}
        </>
      )}
    </PrimitiveSliderTrack>
  )
}

export function SliderFill({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) {
  const state = use(SliderStateContext)
  const { orientation, getThumbPercent, values } = state || {}

  const getStyle = () => {
    const percent0 = getThumbPercent ? getThumbPercent(0) * 100 : 0
    const percent1 = getThumbPercent ? getThumbPercent(1) * 100 : 0

    if (values?.length === 1) {
      return orientation === "horizontal"
        ? { width: `${percent0}%` }
        : { height: `${percent0}%` }
    }

    return orientation === "horizontal"
      ? {
          left: `${percent0}%`,
          width: `${Math.abs(percent0 - percent1)}%`,
        }
      : {
          bottom: `${percent0}%`,
          height: `${Math.abs(percent0 - percent1)}%`,
        }
  }

  return (
    <div
      {...props}
      style={getStyle()}
      className={twMerge(
        "group-orientation-horizontal/top-0 bg-primary group-orientation-vertical/track:bottom-0 group-orientation-horizontal/track:h-full group-orientation-vertical/track:w-full pointer-events-none absolute rounded-full group-disabled/track:opacity-60",
        className
      )}
    />
  )
}
Interaction
carouseltooltip

DependenciesBundle weight · light

react-aria-componentscn@^0.2.4