{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider",
  "type": "registry:component",
  "title": "Slider",
  "description": "Selects one value from a range. A fine track sits inside a 44px hit area.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/vlak-lib.json",
    "https://vlak.dev/r/field.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/slider.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { useFieldControl } from \"./field\";\n\nexport interface SliderProps\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"value\" | \"defaultValue\" | \"onChange\"> {\n  value?: number;\n  defaultValue?: number;\n  min?: number;\n  max?: number;\n  step?: number;\n  onValueChange?: (value: number) => void;\n}\n\nconst styles = stylex.create({\n  slider: {\n    position: \"relative\",\n    height: {\n      default: 2,\n      [mq.phone]: \"0.25rem\",\n    },\n    backgroundColor: vlak.divider,\n    borderRadius: {\n      default: 1,\n      [mq.phone]: 1,\n    },\n    marginBlock: {\n      default: \"1.3125rem\",\n      [mq.phone]: \"1.25rem\",\n    },\n    marginInline: 0,\n  },\n  fill: {\n    position: \"absolute\",\n    insetInlineStart: 0,\n    top: 0,\n    bottom: 0,\n    backgroundColor: vlak.ink,\n    borderRadius: {\n      default: 1,\n      [mq.phone]: 1,\n    },\n    pointerEvents: \"none\",\n  },\n  thumb: {\n    position: \"absolute\",\n    top: \"50%\",\n    transform: \"translate(-50%, -50%)\",\n    width: {\n      default: \"0.875rem\",\n      [mq.phone]: \"1.375rem\",\n    },\n    height: {\n      default: \"0.875rem\",\n      [mq.phone]: \"1.375rem\",\n    },\n    borderRadius: {\n      default: \"50%\",\n      [mq.phone]: \"50%\",\n    },\n    backgroundColor: vlak.paper,\n    borderWidth: 1.5,\n    borderStyle: \"solid\",\n    borderColor: vlak.ink,\n    boxSizing: \"border-box\",\n    pointerEvents: \"none\",\n  },\n  /** Keyboard focus on the hidden range paints a ring on the thumb. */\n  thumbFocused: {\n    outlineWidth: 2,\n    outlineStyle: \"solid\",\n    outlineColor: vlak.ink,\n    outlineOffset: 2,\n  },\n  range: {\n    position: \"absolute\",\n    insetInlineStart: 0,\n    insetInlineEnd: 0,\n    // The hit area is 44px; the visible track stays quiet.\n    top: {\n      default: \"-1.3125rem\",\n      [mq.phone]: \"-1.25rem\",\n    },\n    bottom: {\n      default: \"-1.3125rem\",\n      [mq.phone]: \"-1.25rem\",\n    },\n    width: \"100%\",\n    height: \"auto\",\n    opacity: 0,\n    cursor: \"pointer\",\n    margin: 0,\n  },\n});\n\nfunction isFocusVisible(el: HTMLElement): boolean {\n  try {\n    return el.matches(\":focus-visible\");\n  } catch {\n    return true;\n  }\n}\n\n/** A native range input drives the ink track. */\nexport const Slider = React.forwardRef<HTMLInputElement, SliderProps>(function Slider(\n  { value, defaultValue = 50, min = 0, max = 100, step = 1, onValueChange, className, style, onFocus, onBlur, ...props },\n  ref,\n) {\n  const [inner, setInner] = React.useState(defaultValue);\n  const [focused, setFocused] = React.useState(false);\n  const isControlled = value !== undefined;\n  const current = isControlled ? value : inner;\n  const pct = max === min ? 0 : ((current - min) / (max - min)) * 100;\n  const field = useFieldControl(props);\n  const sx = rs([\"rs-slider\", className], styles.slider);\n  const fill = rs([\"rs-slider-fill\"], styles.fill);\n  const thumb = rs([\"rs-slider-thumb\", focused && \"rs-slider-thumb-focused\"], styles.thumb, focused && styles.thumbFocused);\n  const range = rs([\"rs-slider-range\"], styles.range);\n  return (\n    <div className={sx.className} style={{ ...sx.style, ...style }}>\n      <span className={fill.className} style={{ ...fill.style, width: `${pct}%` }} />\n      <input\n        ref={ref}\n        type=\"range\"\n        min={min}\n        max={max}\n        step={step}\n        value={current}\n        className={range.className}\n        style={range.style}\n        onChange={(e) => {\n          const next = Number(e.target.value);\n          if (!isControlled) setInner(next);\n          onValueChange?.(next);\n        }}\n        {...props}\n        aria-describedby={field[\"aria-describedby\"]}\n        aria-invalid={field[\"aria-invalid\"]}\n        onFocus={(e) => {\n          onFocus?.(e);\n          setFocused(isFocusVisible(e.currentTarget));\n        }}\n        onBlur={(e) => {\n          onBlur?.(e);\n          setFocused(false);\n        }}\n      />\n      <span className={thumb.className} style={{ ...thumb.style, left: `${pct}%` }} />\n    </div>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/slider.tsx"
    },
    {
      "path": "vlak/styles/slider.css",
      "content": "/* ── slider: generated from packages/react/src/components/slider.tsx ── */\n.rs-slider{position:relative;height:2px;background-color:var(--divider);border-radius:1px;margin-block:1.3125rem;margin-inline:0}\n@media (max-width: 640px){.rs-slider{height:0.25rem;border-radius:1px;margin-block:1.25rem}}\n.rs-slider-fill{position:absolute;inset-inline-start:0;top:0;bottom:0;background-color:var(--text);border-radius:1px;pointer-events:none}\n@media (max-width: 640px){.rs-slider-fill{border-radius:1px}}\n.rs-slider-thumb{position:absolute;top:50%;transform:translate(-50%, -50%);width:0.875rem;height:0.875rem;border-radius:50%;background-color:var(--bg);border-width:1.5px;border-style:solid;border-color:var(--text);box-sizing:border-box;pointer-events:none}\n@media (max-width: 640px){.rs-slider-thumb{width:1.375rem;height:1.375rem;border-radius:50%}}\n.rs-slider-thumb-focused{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:2px}\n.rs-slider-range{position:absolute;inset-inline-start:0;inset-inline-end:0;top:-1.3125rem;bottom:-1.3125rem;width:100%;height:auto;opacity:0;cursor:pointer;margin:0}\n@media (max-width: 640px){.rs-slider-range{top:-1.25rem;bottom:-1.25rem}}\n",
      "type": "registry:file",
      "target": "styles/vlak/slider.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "forms",
      "classes": [
        "rs-slider",
        "rs-slider-fill",
        "rs-slider-thumb",
        "rs-slider-thumb-focused",
        "rs-slider-range"
      ],
      "snippet": "<div class=\"rs-slider\"><span class=\"rs-slider-fill\" style=\"width:62%\"></span><span class=\"rs-slider-thumb\" style=\"left:62%\"></span></div>",
      "cssOnly": false,
      "registryDependencies": [
        "vlak-lib",
        "field"
      ],
      "aliases": [
        "Slider",
        "Range",
        "Range input"
      ],
      "example": "import { useState } from \"react\";\nimport { Slider } from \"@noorddev/vlak-react\";\n\nconst [volume, setVolume] = useState(62);\n\n<Slider value={volume} onValueChange={setVolume} min={0} max={100} step={1} aria-label=\"Volume\" />",
      "usage": {
        "use": [
          "A number inside a known range where the position matters more than the digits.",
          "Volume, opacity, zoom, and similar live values."
        ],
        "avoid": [
          "Exact values; use Input type=\"number\".",
          "Ranges with two thumbs; the component has one."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Moves focus to the slider"
        },
        {
          "keys": "Arrow right, Arrow up",
          "does": "Increases by step"
        },
        {
          "keys": "Arrow left, Arrow down",
          "does": "Decreases by step"
        },
        {
          "keys": "Home, End",
          "does": "Jumps to min or max"
        }
      ],
      "a11y": [
        "A native <input type=\"range\"> drives the ink track; the platform exposes value, min, and max.",
        "It has no visible text; pass aria-label or aria-labelledby.",
        "The visible thumb sits on a 44px hit area and shows a focus ring on :focus-visible."
      ]
    }
  }
}
