{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider",
  "title": "Slider",
  "description": "continuous/stepped · `tone` · `accessibilityLabel` names it",
  "dependencies": [
    "react-native-gesture-handler@~2.32.0"
  ],
  "registryDependencies": [
    "@kit-native/lib-haptics",
    "@kit-native/lib-utils"
  ],
  "files": [
    {
      "path": "components/kit/slider.tsx",
      "content": "import { haptic } from '@/lib/haptics';\nimport { cn } from '@/lib/utils';\nimport * as React from 'react';\nimport { type LayoutChangeEvent, View } from 'react-native';\nimport { Gesture, GestureDetector } from 'react-native-gesture-handler';\n\ntype Props = {\n  value: number;\n  onChange: (next: number) => void;\n  min?: number;\n  max?: number;\n  /** 0 = continuous. */\n  step?: number;\n  tone?: 'primary' | 'destructive' | 'warning';\n  disabled?: boolean;\n  onChangeComplete?: (final: number) => void;\n  /** What it sets, e.g. \"Daily goal\". A slider has no visible name of its own. */\n  accessibilityLabel: string;\n  className?: string;\n};\n\nconst THUMB = 28;\n\n/**\n * Continuous or stepped slider on a native pan gesture, so horizontal drags are claimed\n * by the slider instead of the navigator's swipe-back or a parent ScrollView.\n */\nexport function Slider({ value, onChange, min = 0, max = 1, step = 0, tone = 'primary', disabled, onChangeComplete, accessibilityLabel, className }: Props) {\n  const [width, setWidth] = React.useState(0);\n  const range = max - min;\n  const usableW = Math.max(0, width - THUMB);\n  const clamp = (v: number) => Math.max(min, Math.min(max, v));\n  const snap = (v: number) => (step > 0 ? Math.round(v / step) * step : v);\n  const ratio = range === 0 ? 0 : (clamp(value) - min) / range;\n  const thumbX = ratio * usableW;\n\n  const latest = React.useRef({ value, min, max, range, usableW, step, onChange, onChangeComplete, startX: 0 });\n  React.useEffect(() => {\n    latest.current = { ...latest.current, value, min, max, range, usableW, step, onChange, onChangeComplete };\n  });\n\n  // Gesture callbacks run at event time; the ref reads inside are safe.\n  /* eslint-disable react-hooks/refs */\n  const pan = React.useMemo(\n    () =>\n      Gesture.Pan()\n        .enabled(!disabled)\n        .runOnJS(true)\n        .activeOffsetX([-4, 4])\n        .failOffsetY([-12, 12])\n        .onBegin((e) => {\n          const L = latest.current;\n          // Jump to the touch point, then drag from there.\n          if (L.usableW <= 0) return;\n          const x = Math.max(0, Math.min(e.x - THUMB / 2, L.usableW));\n          L.startX = x;\n          const raw = L.min + (x / L.usableW) * L.range;\n          const next = Math.max(L.min, Math.min(L.max, L.step > 0 ? Math.round(raw / L.step) * L.step : raw));\n          if (next !== L.value) L.onChange(next);\n        })\n        .onUpdate((e) => {\n          const L = latest.current;\n          if (L.usableW <= 0) return;\n          const x = Math.max(0, Math.min(L.startX + e.translationX, L.usableW));\n          const raw = L.min + (x / L.usableW) * L.range;\n          const next = Math.max(L.min, Math.min(L.max, L.step > 0 ? Math.round(raw / L.step) * L.step : raw));\n          if (next !== L.value) {\n            if (L.step > 0) haptic('selection');\n            L.onChange(next);\n          }\n        })\n        .onFinalize(() => {\n          const L = latest.current;\n          if (L.onChangeComplete) L.onChangeComplete(L.value);\n          else haptic('selection');\n        }),\n    [disabled]\n  );  /* eslint-enable react-hooks/refs */\n\n\n  const adjustBy = (dir: 1 | -1) => {\n    if (disabled) return;\n    const inc = step > 0 ? step : range / 20;\n    const next = clamp(snap(value + dir * inc));\n    if (next !== value) {\n      onChange(next);\n      onChangeComplete?.(next);\n    }\n  };\n\n  const fill = tone === 'destructive' ? 'bg-destructive' : tone === 'warning' ? 'bg-warning' : 'bg-primary';\n  const ring = tone === 'destructive' ? 'border-destructive' : tone === 'warning' ? 'border-warning' : 'border-primary';\n\n  return (\n    <GestureDetector gesture={pan}>\n      <View\n        onLayout={(e: LayoutChangeEvent) => setWidth(e.nativeEvent.layout.width)}\n        accessibilityRole=\"adjustable\"\n        aria-label={accessibilityLabel}\n        aria-disabled={disabled}\n        aria-valuemin={0}\n        aria-valuemax={100}\n        aria-valuenow={Math.round(ratio * 100)}\n        aria-valuetext={`${clamp(value)}`}\n        accessibilityActions={[{ name: 'increment' }, { name: 'decrement' }]}\n        onAccessibilityAction={(e) => (e.nativeEvent.actionName === 'increment' ? adjustBy(1) : adjustBy(-1))}\n        className={cn('h-12 justify-center', disabled && 'opacity-50', className)}>\n        <View style={{ height: THUMB }} className=\"justify-center\">\n          <View className=\"bg-muted absolute left-0 right-0 h-1.5 rounded-full\" />\n          <View className={cn('absolute left-0 h-1.5 rounded-full', fill)} style={{ width: thumbX + THUMB / 2 }} />\n          <View className={cn('bg-card absolute rounded-full border-2 shadow-sm shadow-black/25', ring)} style={{ left: thumbX, width: THUMB, height: THUMB }} />\n        </View>\n      </View>\n    </GestureDetector>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/kit/slider.tsx"
    }
  ],
  "meta": {
    "exports": [
      "Slider"
    ],
    "api": "<Slider value onChange min max step? tone? onChangeComplete? accessibilityLabel />"
  },
  "docs": "Run `npx expo install --fix` afterwards so Expo SDK packages match your SDK. Needs the kit theme (`@kit-native/theme`) and `KitThemeProvider` from `@kit-native/lib-theme-context` at the root.",
  "categories": [
    "inputs"
  ],
  "type": "registry:component"
}