{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider-styled",
  "title": "Slider essentials",
  "description": "Styled slider: the essentials set. 5 decorative slider variations (GradientSlider, GlowSlider, TicksSlider, BubbleSlider, ThickSlider) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/slider-styled.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useMotionValue, useReducedMotion, useSpring } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Slider family: 5 decorative single-thumb sliders. A custom accessible structure\n   (a native input[type=range] thumb cannot take tokens): a track div + a fill div +\n   a thumb button. Pointer dragging (clientX within the track/thumb rect) AND\n   keyboard (ArrowLeft/Right). Color comes ONLY from tokens, via alpha color-mix.\n   The thumb spring is disabled under reduced-motion. Controlled (value) or\n   uncontrolled (defaultValue). */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst trackHeight: Record<StyledSize, string> = {\n  sm: \"h-1\",\n  md: \"h-1.5\",\n  lg: \"h-2.5\",\n  xl: \"h-3.5\",\n}\nconst thumbSize: Record<StyledSize, string> = {\n  sm: \"size-3.5\",\n  md: \"size-4\",\n  lg: \"size-5\",\n  xl: \"size-6\",\n}\nconst thickTrackHeight: Record<StyledSize, string> = {\n  sm: \"h-2.5\",\n  md: \"h-3.5\",\n  lg: \"h-5\",\n  xl: \"h-6\",\n}\nconst thickThumbSize: Record<StyledSize, string> = {\n  sm: \"size-5\",\n  md: \"size-6\",\n  lg: \"size-7\",\n  xl: \"size-9\",\n}\n\nconst rootBase =\n  \"relative flex w-56 max-w-full touch-none select-none items-center outline-none\"\n/* The thumb's visual box stays under 24px (sm 14px ... xl 24px), so per AGENTS.md R48 it carries an invisible hit-area extension: after:-inset-1.5. The extra area only grows the touch target, the visual size does not change. The same pattern as the base ui/slider.tsx. */\nconst thumbBase =\n  \"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full outline-none after:absolute after:-inset-1.5 focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\ntype SliderProps = {\n  className?: string\n  size?: StyledSize\n  value?: number\n  defaultValue?: number\n  onValueChange?: (v: number) => void\n  min?: number\n  max?: number\n}\n\nfunction clamp(v: number, min: number, max: number) {\n  return Math.max(min, Math.min(max, v))\n}\n\n/* Shared slider behaviour: controlled/uncontrolled value, pointer dragging, keyboard. `pct` is the fill percentage between 0 and 100. */\nfunction useSlider(props: SliderProps, step = 1) {\n  const { value, defaultValue = 40, onValueChange, min = 0, max = 100 } = props\n  const trackRef = React.useRef<HTMLDivElement>(null)\n  const [internal, setInternal] = React.useState(clamp(defaultValue, min, max))\n  const [dragging, setDragging] = React.useState(false)\n  const current = value !== undefined ? clamp(value, min, max) : internal\n\n  const commit = React.useCallback(\n    (next: number) => {\n      const c = clamp(Math.round(next / step) * step, min, max)\n      if (value === undefined) setInternal(c)\n      onValueChange?.(c)\n    },\n    [max, min, onValueChange, step, value]\n  )\n\n  const fromClientX = React.useCallback(\n    (clientX: number) => {\n      const el = trackRef.current\n      if (!el) return current\n      const rect = el.getBoundingClientRect()\n      const ratio = rect.width > 0 ? (clientX - rect.left) / rect.width : 0\n      return min + clamp(ratio, 0, 1) * (max - min)\n    },\n    [current, max, min]\n  )\n\n  const onPointerDown = React.useCallback(\n    (e: React.PointerEvent) => {\n      e.preventDefault()\n      ;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)\n      setDragging(true)\n      commit(fromClientX(e.clientX))\n    },\n    [commit, fromClientX]\n  )\n\n  const onPointerMove = React.useCallback(\n    (e: React.PointerEvent) => {\n      if (!dragging) return\n      commit(fromClientX(e.clientX))\n    },\n    [commit, dragging, fromClientX]\n  )\n\n  const onPointerUp = React.useCallback(\n    (e: React.PointerEvent) => {\n      ;(e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId)\n      setDragging(false)\n    },\n    []\n  )\n\n  const onKeyDown = React.useCallback(\n    (e: React.KeyboardEvent) => {\n      let next = current\n      if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") next = current - step\n      else if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") next = current + step\n      else if (e.key === \"Home\") next = min\n      else if (e.key === \"End\") next = max\n      else return\n      e.preventDefault()\n      commit(next)\n    },\n    [commit, current, max, min, step]\n  )\n\n  const pct = max > min ? ((current - min) / (max - min)) * 100 : 0\n\n  return {\n    trackRef,\n    current,\n    pct,\n    dragging,\n    min,\n    max,\n    onPointerDown,\n    onPointerMove,\n    onPointerUp,\n    onKeyDown,\n  }\n}\n\n/* The shared position value that follows the thumb with a spring (instant under reduced-motion). */\nfunction useThumbSpring(pct: number, reduce: boolean | null) {\n  const raw = useMotionValue(pct)\n  const spring = useSpring(raw, { stiffness: 500, damping: 40, mass: 0.6 })\n  React.useEffect(() => {\n    raw.set(pct)\n  }, [pct, raw])\n  return reduce ? pct : spring\n}\n\ntype RootHandlers = ReturnType<typeof useSlider>\n\nfunction rootAria(s: RootHandlers) {\n  return {\n    role: \"slider\" as const,\n    \"aria-valuenow\": Math.round(s.current),\n    \"aria-valuemin\": s.min,\n    \"aria-valuemax\": s.max,\n    \"aria-orientation\": \"horizontal\" as const,\n  }\n}\n\n/* Gradient: dolum cok tonlu token gradyan. */\nexport function GradientSlider({ className, size = \"md\", ...rest }: SliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider({ size, ...rest })\n  const left = useThumbSpring(s.pct, reduce)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      {...rootAria(s)}\n      className={cn(rootBase, className)}\n      onPointerMove={s.onPointerMove}\n      onPointerUp={s.onPointerUp}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        className={cn(\"relative w-full cursor-pointer overflow-hidden rounded-full bg-secondary\", trackHeight[size])}\n      >\n        <div\n          className=\"absolute inset-y-0 left-0 rounded-full [background-image:linear-gradient(90deg,var(--info),var(--brand),var(--success))]\"\n          style={{ width: `${s.pct}%` }}\n        />\n      </div>\n      <motion.button\n        type=\"button\"\n        data-slot=\"styled-slider-thumb\"\n        onPointerDown={s.onPointerDown}\n        onKeyDown={s.onKeyDown}\n        className={cn(thumbBase, thumbSize[size], \"border-2 border-background bg-primary shadow-sm\")}\n        style={{ left: typeof left === \"number\" ? `${left}%` : (left as unknown as string) }}\n      />\n    </div>\n  )\n}\n\n/* Glow: basparmakta token isima, surukleme sirasinda siddetlenir. */\nexport function GlowSlider({ className, size = \"md\", ...rest }: SliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider({ size, ...rest })\n  const left = useThumbSpring(s.pct, reduce)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      {...rootAria(s)}\n      className={cn(rootBase, className)}\n      onPointerMove={s.onPointerMove}\n      onPointerUp={s.onPointerUp}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        className={cn(\"relative w-full cursor-pointer overflow-hidden rounded-full bg-secondary\", trackHeight[size])}\n      >\n        <div className=\"absolute inset-y-0 left-0 rounded-full bg-info\" style={{ width: `${s.pct}%` }} />\n      </div>\n      <motion.button\n        type=\"button\"\n        data-slot=\"styled-slider-thumb\"\n        onPointerDown={s.onPointerDown}\n        onKeyDown={s.onKeyDown}\n        className={cn(\n          thumbBase,\n          thumbSize[size],\n          \"border-2 border-background bg-info transition-shadow\",\n          s.dragging ? \"shadow-[0_0_16px_3px_var(--info)]\" : \"shadow-[0_0_8px_1px_var(--info)]\"\n        )}\n        style={{ left: typeof left === \"number\" ? `${left}%` : (left as unknown as string) }}\n      />\n    </div>\n  )\n}\n\n/* Ticks: step lines along the track; snapping (step) is optional. */\nexport function TicksSlider({ className, size = \"md\", ...rest }: SliderProps) {\n  const reduce = useReducedMotion()\n  const step = 10\n  const s = useSlider({ size, ...rest }, step)\n  const left = useThumbSpring(s.pct, reduce)\n  const ticks = 11\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      {...rootAria(s)}\n      className={cn(rootBase, className)}\n      onPointerMove={s.onPointerMove}\n      onPointerUp={s.onPointerUp}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        className={cn(\"relative w-full cursor-pointer rounded-full bg-secondary\", trackHeight[size])}\n      >\n        <div className=\"absolute inset-y-0 left-0 rounded-full bg-primary\" style={{ width: `${s.pct}%` }} />\n        <div className=\"pointer-events-none absolute inset-0\">\n          {Array.from({ length: ticks }).map((_, i) => (\n            <span\n              key={i}\n              className=\"absolute top-1/2 h-1.5 w-px -translate-x-1/2 -translate-y-1/2 rounded-full bg-border\"\n              style={{ left: `${(i / (ticks - 1)) * 100}%` }}\n            />\n          ))}\n        </div>\n      </div>\n      <motion.button\n        type=\"button\"\n        data-slot=\"styled-slider-thumb\"\n        onPointerDown={s.onPointerDown}\n        onKeyDown={s.onKeyDown}\n        className={cn(thumbBase, thumbSize[size], \"border-2 border-background bg-primary shadow-sm\")}\n        style={{ left: typeof left === \"number\" ? `${left}%` : (left as unknown as string) }}\n      />\n    </div>\n  )\n}\n\n/* Bubble: a value bubble appears above the thumb while dragging. */\nexport function BubbleSlider({ className, size = \"md\", ...rest }: SliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider({ size, ...rest })\n  const left = useThumbSpring(s.pct, reduce)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      {...rootAria(s)}\n      className={cn(rootBase, className)}\n      onPointerMove={s.onPointerMove}\n      onPointerUp={s.onPointerUp}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        className={cn(\"relative w-full cursor-pointer overflow-hidden rounded-full bg-secondary\", trackHeight[size])}\n      >\n        <div className=\"absolute inset-y-0 left-0 rounded-full bg-primary\" style={{ width: `${s.pct}%` }} />\n      </div>\n      <motion.button\n        type=\"button\"\n        data-slot=\"styled-slider-thumb\"\n        onPointerDown={s.onPointerDown}\n        onKeyDown={s.onKeyDown}\n        className={cn(thumbBase, thumbSize[size], \"border-2 border-background bg-primary shadow-sm\")}\n        style={{ left: typeof left === \"number\" ? `${left}%` : (left as unknown as string) }}\n      >\n        <motion.span\n          data-slot=\"styled-slider-bubble\"\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute -top-2 left-1/2 -translate-x-1/2 -translate-y-full rounded-md bg-primary px-1.5 py-0.5 text-[10px] font-semibold leading-none text-primary-foreground shadow-sm\"\n          initial={false}\n          animate={reduce ? { opacity: s.dragging ? 1 : 0 } : { opacity: s.dragging ? 1 : 0, y: s.dragging ? 0 : 4 }}\n          transition={reduce ? { duration: 0 } : { duration: 0.15, ease: \"easeOut\" }}\n        >\n          {Math.round(s.current)}\n        </motion.span>\n      </motion.button>\n    </div>\n  )\n}\n\n/* Thick: iri yuvarlatilmis iz + buyuk basparmak. */\nexport function ThickSlider({ className, size = \"md\", ...rest }: SliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider({ size, ...rest })\n  const left = useThumbSpring(s.pct, reduce)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      {...rootAria(s)}\n      className={cn(rootBase, \"min-h-9\", className)}\n      onPointerMove={s.onPointerMove}\n      onPointerUp={s.onPointerUp}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        className={cn(\"relative w-full cursor-pointer overflow-hidden rounded-full bg-secondary\", thickTrackHeight[size])}\n      >\n        <div\n          className=\"absolute inset-y-0 left-0 rounded-full bg-primary\"\n          style={{ width: `${s.pct}%` }}\n        />\n      </div>\n      <motion.button\n        type=\"button\"\n        data-slot=\"styled-slider-thumb\"\n        onPointerDown={s.onPointerDown}\n        onKeyDown={s.onKeyDown}\n        className={cn(thumbBase, thickThumbSize[size], \"border-4 border-background bg-primary shadow-md\")}\n        style={{ left: typeof left === \"number\" ? `${left}%` : (left as unknown as string) }}\n      />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/slider-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "slider"
  ],
  "type": "registry:component"
}