{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider-vertical",
  "title": "Vertical slider",
  "description": "Styled slider: the Vertical set. 5 decorative slider variations (LineVSlider, ThickVSlider, TicksVSlider, FillVSlider, CompactVSlider) 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-vertical.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 vertical family: 5 vertical sliders. The value increases from bottom to\n   top. A self-sufficient structure: a vertical track div + a fill div filling from\n   the bottom + a thumb button. Pointer dragging (clientY within the track rect,\n   with capture on the track) AND keyboard (Up/Down arrows, Shift for a large step,\n   Home/End). Color comes ONLY from tokens, via alpha color-mix. The thumb spring is\n   disabled under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst trackWidth: Record<StyledSize, string> = {\n  sm: \"w-1\",\n  md: \"w-1.5\",\n  lg: \"w-2.5\",\n  xl: \"w-3.5\",\n}\nconst thickTrackWidth: Record<StyledSize, string> = {\n  sm: \"w-2.5\",\n  md: \"w-3.5\",\n  lg: \"w-5\",\n  xl: \"w-6\",\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 thickThumbSize: Record<StyledSize, string> = {\n  sm: \"size-5\",\n  md: \"size-6\",\n  lg: \"size-7\",\n  xl: \"size-9\",\n}\nconst rootHeight: Record<StyledSize, string> = {\n  sm: \"h-32\",\n  md: \"h-40\",\n  lg: \"h-48\",\n  xl: \"h-56\",\n}\nconst compactHeight: Record<StyledSize, string> = {\n  sm: \"h-20\",\n  md: \"h-24\",\n  lg: \"h-28\",\n  xl: \"h-32\",\n}\n\nconst rootBase =\n  \"relative flex touch-none select-none flex-col items-center justify-center outline-none\"\n/* Basparmak 24px altinda: gorunmez dokunma alani genisletmesi zorunlu. */\nconst thumbBase =\n  \"absolute left-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 VSliderProps = {\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/* Ortak dikey slider davranisi. `pct` alttan olculen doluluk yuzdesi. */\nfunction useVSlider(props: VSliderProps) {\n  const {\n    value,\n    defaultValue = 45,\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  /* Dikeyde ust kenar max, alt kenar min. */\n  const fromClientY = React.useCallback(\n    (clientY: number) => {\n      const el = trackRef.current\n      if (!el) return current\n      const rect = el.getBoundingClientRect()\n      const ratio = rect.height > 0 ? (rect.bottom - clientY) / rect.height : 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(fromClientY(e.clientY))\n    },\n    [commit, fromClientY]\n  )\n\n  /* Pressing the thumb does not jump the value, it only starts the drag. */\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(fromClientY(e.clientY))\n    },\n    [commit, dragging, fromClientY]\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 === \"ArrowDown\" || e.key === \"ArrowLeft\") next = current - delta\n      else if (e.key === \"ArrowUp\" || e.key === \"ArrowRight\") 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    step,\n    onPointerDown,\n    onThumbPointerDown,\n    onPointerMove,\n    onPointerUp,\n    onKeyDown,\n  }\n}\n\ntype VState = ReturnType<typeof useVSlider>\n\n/* Ortak thumb: rol, aria ve klavye tek yerde. */\nfunction VThumb({\n  s,\n  reduce,\n  className,\n  sizeClass,\n  label,\n}: {\n  s: VState\n  reduce: boolean | null\n  className?: string\n  sizeClass: string\n  label: string\n}) {\n  return (\n    <motion.button\n      type=\"button\"\n      data-slot=\"styled-slider-thumb\"\n      role=\"slider\"\n      aria-label={label}\n      aria-valuenow={Math.round(s.current)}\n      aria-valuemin={s.min}\n      aria-valuemax={s.max}\n      aria-orientation=\"vertical\"\n      tabIndex={0}\n      onPointerDown={s.onThumbPointerDown}\n      onKeyDown={s.onKeyDown}\n      className={cn(thumbBase, sizeClass, className)}\n      style={{ bottom: `${s.pct}%` }}\n      animate={{ scale: !reduce && s.dragging ? 1.18 : 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/* LineV: ince dikey iz, sade basparmak. */\nexport function LineVSlider({ className, size = \"md\", ...rest }: VSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useVSlider(rest)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      className={cn(rootBase, rootHeight[size], \"w-6\", className)}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative h-full cursor-pointer rounded-full bg-secondary\",\n          trackWidth[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-x-0 bottom-0 rounded-full bg-primary\"\n          style={{ height: `${s.pct}%` }}\n        />\n        <VThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={thumbSize[size]}\n          label=\"Value\"\n          className=\"border-2 border-background bg-primary shadow-sm\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* ThickV: kalin yuvarlatilmis dikey iz + iri basparmak. */\nexport function ThickVSlider({ className, size = \"md\", ...rest }: VSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useVSlider(rest)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      className={cn(rootBase, rootHeight[size], \"w-10\", className)}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative h-full cursor-pointer rounded-full bg-secondary\",\n          thickTrackWidth[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-x-0 bottom-0 rounded-full bg-primary\"\n          style={{ height: `${s.pct}%` }}\n        />\n        <VThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={thickThumbSize[size]}\n          label=\"Value\"\n          className=\"border-4 border-background bg-primary shadow-md\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* TicksV: izin sagina hizalanmis adim cizgileri, 10'luk adima kenetli. */\nexport function TicksVSlider({\n  className,\n  size = \"md\",\n  step = 10,\n  ...rest\n}: VSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useVSlider({ step, ...rest })\n  const ticks = 11\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      className={cn(rootBase, rootHeight[size], \"w-10\", className)}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative h-full cursor-pointer rounded-full bg-secondary\",\n          trackWidth[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-x-0 bottom-0 rounded-full bg-primary\"\n          style={{ height: `${s.pct}%` }}\n        />\n        <div className=\"pointer-events-none absolute inset-0\" aria-hidden=\"true\">\n          {Array.from({ length: ticks }).map((_, i) => (\n            <span\n              key={i}\n              className=\"absolute left-full h-px w-1.5 translate-x-1 translate-y-1/2 rounded-full bg-border\"\n              style={{ bottom: `${(i / (ticks - 1)) * 100}%` }}\n            />\n          ))}\n        </div>\n        <VThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={thumbSize[size]}\n          label=\"Value\"\n          className=\"border-2 border-background bg-primary shadow-sm\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* FillV: dolum cok tonlu token gradyani, iz yumusak token yuzeyi. */\nexport function FillVSlider({ className, size = \"md\", ...rest }: VSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useVSlider(rest)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      className={cn(rootBase, rootHeight[size], \"w-10\", className)}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative h-full cursor-pointer rounded-full bg-secondary\",\n          thickTrackWidth[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-x-0 bottom-0 rounded-full [background-image:linear-gradient(0deg,var(--info),var(--brand),var(--success))]\"\n          style={{ height: `${s.pct}%` }}\n        />\n        <VThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={thumbSize[size]}\n          label=\"Value\"\n          className=\"border-2 border-background bg-primary shadow-md\"\n        />\n      </div>\n    </div>\n  )\n}\n\n/* CompactV: short, with a small thumb, for tight layouts. */\nexport function CompactVSlider({ className, size = \"md\", ...rest }: VSliderProps) {\n  const reduce = useReducedMotion()\n  const s = useVSlider(rest)\n  return (\n    <div\n      data-slot=\"styled-slider\"\n      className={cn(rootBase, compactHeight[size], \"w-6\", className)}\n    >\n      <div\n        ref={s.trackRef}\n        onPointerDown={s.onPointerDown}\n        onPointerMove={s.onPointerMove}\n        onPointerUp={s.onPointerUp}\n        className={cn(\n          \"relative h-full cursor-pointer rounded-full border border-border bg-background\",\n          trackWidth[size]\n        )}\n      >\n        <div\n          className=\"absolute inset-x-0 bottom-0 rounded-full bg-foreground\"\n          style={{ height: `${s.pct}%` }}\n        />\n        <VThumb\n          s={s}\n          reduce={reduce}\n          sizeClass={thumbSize[size]}\n          label=\"Value\"\n          className=\"border border-border bg-background shadow-sm\"\n        />\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/slider-vertical.tsx"
    }
  ],
  "categories": [
    "styled",
    "slider"
  ],
  "type": "registry:component"
}