{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider",
  "title": "Slider",
  "description": "Slider from the Get IT Done design system.",
  "dependencies": [],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/slider.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { useLabels } from '@/components/ui/labels-context';\n\n// The native range input stays the interactive element (keyboard, pointer, forms, assistive tech) but is\n// visually hidden; a styled thumb and fill are drawn from the same value so every theme token applies.\nconst THUMB_REM = 1.5;\n\nconst nativeThumb = [\n  '[&::-webkit-slider-thumb]:pointer-events-auto',\n  '[&::-webkit-slider-thumb]:h-6',\n  '[&::-webkit-slider-thumb]:w-6',\n  '[&::-webkit-slider-thumb]:appearance-none',\n  '[&::-moz-range-thumb]:pointer-events-auto',\n  '[&::-moz-range-thumb]:h-6',\n  '[&::-moz-range-thumb]:w-6',\n  '[&::-moz-range-thumb]:border-0',\n];\n\nconst inputBase = [\n  'absolute',\n  'inset-0',\n  'm-0',\n  'h-6',\n  'w-full',\n  'cursor-pointer',\n  'appearance-none',\n  'bg-transparent',\n  'opacity-0',\n  'disabled:cursor-not-allowed',\n  ...nativeThumb,\n];\n\nconst toPercent = (value: number, min: number, max: number) => (max > min ? ((value - min) / (max - min)) * 100 : 0);\n\n// Left offset that keeps the visual thumb centred where the native thumb sits (it travels width minus thumb width).\nconst thumbLeft = (percent: number) => `calc(${percent}% - ${(percent / 100) * THUMB_REM}rem)`;\nconst thumbCenter = (percent: number) => `calc(${percent}% + ${(0.5 - percent / 100) * THUMB_REM}rem)`;\n\ntype ValueLabel = 'none' | 'bottom' | 'top-floating';\n\ninterface SliderShared {\n  min?: number;\n  max?: number;\n  step?: number;\n  disabled?: boolean;\n  // Where the current value is shown: nowhere, under the track, or in a bubble above the thumb.\n  valueLabel?: ValueLabel;\n  // Text for the value label and `aria-valuetext`, for example (v) => `${v} h`.\n  formatValue?: (value: number) => string;\n  className?: string;\n  name?: string;\n  id?: string;\n  'aria-label'?: string;\n  'aria-labelledby'?: string;\n  'aria-describedby'?: string;\n}\n\nconst Thumb = ({ percent, peer, disabled }: { percent: number; peer: string; disabled?: boolean }) => (\n  <span\n    aria-hidden=\"true\"\n    data-slider-thumb=\"\"\n    style={{ left: thumbLeft(percent) }}\n    className={cn(\n      'pointer-events-none absolute top-0 h-6 w-6 rounded-full border-2 border-solid border-border-brand bg-background shadow-elevation-control transition-shadow duration-150 motion-reduce:transition-none',\n      peer,\n      'peer-hover:shadow-field-hover',\n      disabled && 'border-border-disabled',\n    )}\n  />\n);\n\nconst Bubble = ({ percent, children, floating }: { percent: number; children: React.ReactNode; floating: boolean }) => (\n  <span\n    aria-hidden=\"true\"\n    data-slider-label=\"\"\n    style={{ left: thumbCenter(percent) }}\n    className={cn(\n      'pointer-events-none absolute -translate-x-1/2 whitespace-nowrap tabular-nums',\n      floating\n        ? '-top-11 rounded-control border border-solid border-border-default bg-background px-3 py-2 text-foreground shadow-elevation-raised typography-text-small-medium'\n        : 'top-9 text-foreground typography-label',\n    )}\n  >\n    {children}\n  </span>\n);\n\nconst frame = (valueLabel: ValueLabel) =>\n  cn('relative w-full', valueLabel === 'top-floating' && 'pt-12', valueLabel === 'bottom' && 'pb-8');\n\nexport interface SliderProps extends SliderShared {\n  value?: number;\n  defaultValue?: number;\n  onValueChange?: (value: number) => void;\n}\n\nconst Slider = React.forwardRef<HTMLInputElement, SliderProps>(\n  (\n    {\n      value,\n      defaultValue,\n      onValueChange,\n      min = 0,\n      max = 100,\n      step = 1,\n      disabled,\n      valueLabel = 'none',\n      formatValue,\n      className,\n      ...aria\n    },\n    ref,\n  ) => {\n    const [inner, setInner] = React.useState(defaultValue ?? min);\n    const current = value ?? inner;\n    const percent = toPercent(current, min, max);\n    const format = formatValue ?? ((v: number) => String(v));\n    return (\n      <div className={cn(frame(valueLabel), className)} data-disabled={disabled || undefined}>\n        <div className=\"relative h-6 w-full\">\n          <span\n            aria-hidden=\"true\"\n            className=\"absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-background-neutral-hover\"\n          />\n          <span\n            aria-hidden=\"true\"\n            data-slider-fill=\"\"\n            style={{ width: `${percent}%` }}\n            className={cn(\n              'absolute left-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-background-brand-bold-default',\n              disabled && 'bg-border-disabled',\n            )}\n          />\n          <input\n            ref={ref}\n            type=\"range\"\n            min={min}\n            max={max}\n            step={step}\n            value={current}\n            disabled={disabled}\n            aria-valuetext={format(current)}\n            onChange={(event) => {\n              const next = Number(event.target.value);\n              setInner(next);\n              onValueChange?.(next);\n            }}\n            className={cn('peer', inputBase)}\n            {...aria}\n          />\n          <Thumb\n            percent={percent}\n            peer=\"peer-focus-visible:ring-focus peer-focus-visible:ring-offset-focus peer-focus-visible:ring-offset-background peer-focus-visible:ring-ring-default\"\n            disabled={disabled}\n          />\n        </div>\n        {valueLabel !== 'none' && (\n          <Bubble percent={percent} floating={valueLabel === 'top-floating'}>\n            {format(current)}\n          </Bubble>\n        )}\n      </div>\n    );\n  },\n);\nSlider.displayName = 'Slider';\n\nexport interface RangeSliderProps extends Omit<SliderShared, 'name'> {\n  value?: [number, number];\n  defaultValue?: [number, number];\n  onValueChange?: (value: [number, number]) => void;\n  // Accessible names of the two thumbs. Required because a range exposes two sliders.\n  minLabel?: string;\n  maxLabel?: string;\n  // Form field names for the lower and upper value.\n  minName?: string;\n  maxName?: string;\n}\n\nconst RangeSlider = React.forwardRef<HTMLDivElement, RangeSliderProps>(\n  (\n    {\n      value,\n      defaultValue,\n      onValueChange,\n      min = 0,\n      max = 100,\n      step = 1,\n      disabled,\n      valueLabel = 'none',\n      formatValue,\n      className,\n      minLabel: minLabelProp,\n      maxLabel: maxLabelProp,\n      minName,\n      maxName,\n      id,\n      'aria-label': groupLabel,\n      'aria-labelledby': labelledBy,\n      'aria-describedby': describedBy,\n    },\n    ref,\n  ) => {\n    const l = useLabels();\n    const minLabel = minLabelProp ?? l.minimum;\n    const maxLabel = maxLabelProp ?? l.maximum;\n    const [inner, setInner] = React.useState<[number, number]>(defaultValue ?? [min, max]);\n    const [low, high] = value ?? inner;\n    const lowPercent = toPercent(low, min, max);\n    const highPercent = toPercent(high, min, max);\n    const format = formatValue ?? ((v: number) => String(v));\n    const commit = (next: [number, number]) => {\n      setInner(next);\n      onValueChange?.(next);\n    };\n    const lowOnTop = low > min + (max - min) / 2;\n    return (\n      <div\n        ref={ref}\n        id={id}\n        role=\"group\"\n        aria-label={groupLabel}\n        aria-labelledby={labelledBy}\n        className={cn(frame(valueLabel), className)}\n        data-disabled={disabled || undefined}\n      >\n        <div className=\"pointer-events-none relative h-6 w-full\">\n          <span\n            aria-hidden=\"true\"\n            className=\"absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-background-neutral-hover\"\n          />\n          <span\n            aria-hidden=\"true\"\n            data-slider-fill=\"\"\n            style={{ left: `${lowPercent}%`, width: `${Math.max(highPercent - lowPercent, 0)}%` }}\n            className={cn(\n              'absolute top-1/2 h-2 -translate-y-1/2 bg-background-brand-bold-default',\n              disabled && 'bg-border-disabled',\n            )}\n          />\n          <input\n            type=\"range\"\n            name={minName}\n            min={min}\n            max={max}\n            step={step}\n            value={low}\n            disabled={disabled}\n            aria-label={minLabel}\n            aria-valuetext={format(low)}\n            aria-describedby={describedBy}\n            onChange={(event) => commit([Math.min(Number(event.target.value), high), high])}\n            className={cn('peer/low pointer-events-none', inputBase, lowOnTop ? 'z-10' : 'z-0')}\n          />\n          <Thumb\n            percent={lowPercent}\n            peer=\"peer-focus-visible/low:ring-focus peer-focus-visible/low:ring-offset-focus peer-focus-visible/low:ring-offset-background peer-focus-visible/low:ring-ring-default\"\n            disabled={disabled}\n          />\n          <input\n            type=\"range\"\n            name={maxName}\n            min={min}\n            max={max}\n            step={step}\n            value={high}\n            disabled={disabled}\n            aria-label={maxLabel}\n            aria-valuetext={format(high)}\n            aria-describedby={describedBy}\n            onChange={(event) => commit([low, Math.max(Number(event.target.value), low)])}\n            className={cn('peer/high pointer-events-none', inputBase, lowOnTop ? 'z-0' : 'z-10')}\n          />\n          <Thumb\n            percent={highPercent}\n            peer=\"peer-focus-visible/high:ring-focus peer-focus-visible/high:ring-offset-focus peer-focus-visible/high:ring-offset-background peer-focus-visible/high:ring-ring-default\"\n            disabled={disabled}\n          />\n        </div>\n        {valueLabel !== 'none' && (\n          <>\n            <Bubble percent={lowPercent} floating={valueLabel === 'top-floating'}>\n              {format(low)}\n            </Bubble>\n            <Bubble percent={highPercent} floating={valueLabel === 'top-floating'}>\n              {format(high)}\n            </Bubble>\n          </>\n        )}\n      </div>\n    );\n  },\n);\nRangeSlider.displayName = 'RangeSlider';\n\nexport { RangeSlider, Slider };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}