{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider-minimal",
  "title": "Minimal slider",
  "description": "Styled slider: the Minimal set. 5 decorative slider variations (HairSlider, BareSlider, GhostSlider, ThinSlider, DotSlider) 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-minimal.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Slider minimal family: 5 plain, low-emphasis sliders. A self-sufficient structure: a track div plus a fill div plus a thumb button. Pointer dragging (clientX against the track rect, with capture on the track) AND keyboard (arrows, Shift+arrow for a large step, Home/End). Because the visuals are thin, the thumb carries an invisible touch area. Colour comes ONLY from tokens, alpha via color-mix. Motion is off under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst hairTrackHeight: Record<StyledSize, string> = {\n  sm: \"h-px\",\n  md: \"h-px\",\n  lg: \"h-0.5\",\n  xl: \"h-0.5\",\n}\nconst thinTrackHeight: Record<StyledSize, string> = {\n  sm: \"h-0.5\",\n  md: \"h-1\",\n  lg: \"h-1\",\n  xl: \"h-1.5\",\n}\nconst smallThumbSize: Record<StyledSize, string> = {\n  sm: \"size-2.5\",\n  md: \"size-3\",\n  lg: \"size-3.5\",\n  xl: \"size-4\",\n}\nconst thumbSize: Record<StyledSize, string> = {\n  sm: \"size-3.5\",\n  md: \"size-4\",\n  lg: \"size-5\",\n  xl: \"size-6\",\n}\n\nconst rootBase =\n  \"relative flex w-56 max-w-full touch-none select-none items-center outline-none\"\n/* Basparmak 24px altinda: gorunmez dokunma alani genisletmesi zorunlu. */\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 after:content-[''] focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nexport type MinimalSliderProps = {\n  className?: string\n  size?: StyledSize\n  min?: number\n  max?: number\n  step?: number\n  value?: number\n  defaultValue?: number\n  onValueChange?: (v: number) => void\n}\n\nfunction clamp(v: number, min: number, max: number) {\n  return Math.max(min, Math.min(max, v))\n}\n\n/* Shared horizontal slider behaviour: controlled/uncontrolled value, pointer,\n   keyboard. */\nfunction useSlider(props: MinimalSliderProps) {\n  const {\n    value,\n    defaultValue = 50,\n    onValueChange,\n    min = 0,\n    max = 100,\n    step = 1,\n  } = 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      trackRef.current?.setPointerCapture?.(e.pointerId)\n      setDragging(true)\n      commit(fromClientX(e.clientX))\n    },\n    [commit, fromClientX]\n  )\n\n  const onThumbPointerDown = React.useCallback((e: React.PointerEvent) => {\n    e.preventDefault()\n    e.stopPropagation()\n    trackRef.current?.setPointerCapture?.(e.pointerId)\n    setDragging(true)\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((e: React.PointerEvent) => {\n    trackRef.current?.releasePointerCapture?.(e.pointerId)\n    setDragging(false)\n  }, [])\n\n  const onKeyDown = React.useCallback(\n    (e: React.KeyboardEvent) => {\n      const delta = e.shiftKey ? step * 10 : step\n      let next = current\n      if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") next = current - delta\n      else if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") next = current + delta\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    onThumbPointerDown,\n    onPointerMove,\n    onPointerUp,\n    onKeyDown,\n  }\n}\n\ntype MinimalState = ReturnType<typeof useSlider>\n\n/* Ortak thumb: rol, aria, klavye ve yay tek yerde. */\nfunction MinimalThumb({\n  s,\n  reduce,\n  sizeClass,\n  className,\n}: {\n  s: MinimalState\n  reduce: boolean | null\n  sizeClass: string\n  className?: string\n}) {\n  return (\n    <motion.button\n      type=\"button\"\n      data-slot=\"styled-slider-thumb\"\n      role=\"slider\"\n      aria-label=\"Value\"\n      aria-valuenow={Math.round(s.current)}\n      aria-valuemin={s.min}\n      aria-valuemax={s.max}\n      aria-orientation=\"horizontal\"\n      tabIndex={0}\n      onPointerDown={s.onThumbPointerDown}\n      onKeyDown={s.onKeyDown}\n      className={cn(thumbBase, sizeClass, className)}\n      style={{ left: `${s.pct}%` }}\n      animate={{ scale: !reduce && s.dragging ? 1.25 : 1 }}\n      transition={\n        reduce\n          ? { duration: 0 }\n          : { type: \"spring\" as const, stiffness: 500, damping: 30, mass: 0.6 }\n      }\n    />\n  )\n}\n\n/* Hair: sac teli inceliginde iz, kucuk sade basparmak. */\nexport function HairSlider({ className, size = \"md\", ...rest }: MinimalSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider(rest)\n  return (\n    <div data-slot=\"styled-slider\" className={cn(rootBase, \"min-h-6\", className)}>\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative w-full cursor-pointer rounded-full bg-border\",\n          hairTrackHeight[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-y-0 left-0 rounded-full bg-foreground\"\n          style={{ width: `${s.pct}%` }}\n        />\n        <MinimalThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={smallThumbSize[size]}\n          className=\"bg-foreground\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* Bare: no fill, only the track and a hollow ring thumb. */\nexport function BareSlider({ className, size = \"md\", ...rest }: MinimalSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider(rest)\n  return (\n    <div data-slot=\"styled-slider\" className={cn(rootBase, \"min-h-6\", className)}>\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative w-full cursor-pointer rounded-full bg-border\",\n          thinTrackHeight[size]\n        )}\n      >\n        <MinimalThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={thumbSize[size]}\n          className=\"border-2 border-foreground bg-background\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* Ghost: a very low-intensity track, with the thumb separated only by a token\n   shadow. */\nexport function GhostSlider({ className, size = \"md\", ...rest }: MinimalSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider(rest)\n  return (\n    <div data-slot=\"styled-slider\" className={cn(rootBase, \"min-h-6\", className)}>\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative w-full cursor-pointer rounded-full [background-color:color-mix(in_oklab,var(--foreground)_8%,transparent)]\",\n          thinTrackHeight[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-y-0 left-0 rounded-full [background-color:color-mix(in_oklab,var(--foreground)_35%,transparent)]\"\n          style={{ width: `${s.pct}%` }}\n        />\n        <MinimalThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={smallThumbSize[size]}\n          className=\"bg-background shadow-md ring-1 ring-border\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* Thin: ince iz, dar dikdortgen kolcak basparmak. */\nexport function ThinSlider({ className, size = \"md\", ...rest }: MinimalSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider(rest)\n  return (\n    <div data-slot=\"styled-slider\" className={cn(rootBase, \"min-h-6\", className)}>\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative w-full cursor-pointer rounded-full bg-secondary\",\n          thinTrackHeight[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-y-0 left-0 rounded-full bg-foreground\"\n          style={{ width: `${s.pct}%` }}\n        />\n        <MinimalThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={cn(smallThumbSize[size], \"w-1 rounded-sm\")}\n          className=\"h-4 bg-foreground\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* Dot: dolum yok, tek notr nokta ve nokta izi. */\nexport function DotSlider({ className, size = \"md\", ...rest }: MinimalSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useSlider(rest)\n  const dots = 9\n  return (\n    <div data-slot=\"styled-slider\" className={cn(rootBase, \"min-h-6\", className)}>\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\"relative w-full cursor-pointer\", thinTrackHeight[size])}\n      >\n        <div className=\"pointer-events-none absolute inset-0\" aria-hidden=\"true\">\n          {Array.from({ length: dots }).map((_, i) => (\n            <span\n              key={i}\n              className=\"absolute top-1/2 size-1 -translate-x-1/2 -translate-y-1/2 rounded-full bg-border\"\n              style={{ left: `${(i / (dots - 1)) * 100}%` }}\n            />\n          ))}\n        </div>\n        <MinimalThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={smallThumbSize[size]}\n          className=\"bg-foreground\"\n        />\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/slider-minimal.tsx"
    }
  ],
  "categories": [
    "styled",
    "slider"
  ],
  "type": "registry:component"
}