{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "splitter",
  "title": "Splitter",
  "description": "Resizable panels with minimum, maximum and collapsible sizes, keyboard separators that report their size, and drag that reverses in right-to-left layouts.",
  "files": [
    {
      "path": "registry/tealmac/splitter.tsx",
      "content": "\"use client\"\n\n/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex --\n   The WAI-ARIA window splitter pattern: a separator with aria-valuenow is a focusable widget with keyboard handlers. */\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface SplitterPanelConfig {\n  id: string\n  content: React.ReactNode\n  /** Initial size in percent. Unset panels share what is left equally. */\n  defaultSize?: number\n  /** Minimum size in percent. Default 10. */\n  min?: number\n  /** Maximum size in percent. Default 100. */\n  max?: number\n  /** Can be collapsed to 0 by a double click or Enter on the handle before it. */\n  collapsible?: boolean\n  /** Accessible name of the panel region. */\n  label?: string\n}\n\nexport interface SplitterProps extends Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  \"onChange\"\n> {\n  panels: SplitterPanelConfig[]\n  orientation?: \"horizontal\" | \"vertical\"\n  /** Controlled sizes in percent, one per panel, summing to 100. */\n  sizes?: number[]\n  onSizesChange?: (sizes: number[]) => void\n}\n\nconst clamp = (n: number, lo: number, hi: number) =>\n  Math.min(hi, Math.max(lo, n))\n\nfunction initialSizes(panels: SplitterPanelConfig[]) {\n  const fixed = panels.reduce((a, p) => a + (p.defaultSize ?? 0), 0)\n  const free = panels.filter((p) => p.defaultSize == null).length\n  const each = free ? Math.max(0, 100 - fixed) / free : 0\n  const raw = panels.map((p) => p.defaultSize ?? each)\n  const sum = raw.reduce((a, b) => a + b, 0) || 1\n  return raw.map((n) => (n / sum) * 100)\n}\n\n/**\n * Resizable panels with minimum, maximum and collapsible sizes. Every handle\n * is a focusable `separator` that reports the size of the panel before it:\n * arrow keys move it by 2%, Shift by 10%, Home and End jump to the limits and\n * Enter collapses or restores a collapsible panel. Pointer drag works with the\n * mouse, touch and pen, and reverses correctly in right-to-left layouts.\n */\nexport function Splitter({\n  panels,\n  orientation = \"horizontal\",\n  sizes: sizesProp,\n  onSizesChange,\n  className,\n  ...props\n}: SplitterProps) {\n  const [inner, setInner] = React.useState(() => initialSizes(panels))\n  const sizes = sizesProp ?? inner\n  const ref = React.useRef<HTMLDivElement>(null)\n  const restore = React.useRef<Record<number, number>>({})\n  const vertical = orientation === \"vertical\"\n  const baseId = React.useId()\n\n  const apply = (next: number[]) => {\n    if (sizesProp === undefined) setInner(next)\n    onSizesChange?.(next)\n  }\n\n  /** Move the boundary after panel `i` so panel `i` becomes `target` percent. */\n  const moveTo = (i: number, target: number) => {\n    const a = panels[i]\n    const b = panels[i + 1]\n    const total = sizes[i] + sizes[i + 1]\n    const min = a.min ?? 10\n    if (a.collapsible && target < min) {\n      target =\n        sizes[i] === 0 ? (target > 0 ? min : 0) : target <= min / 2 ? 0 : min\n    }\n    const aMin = a.collapsible && target === 0 ? 0 : min\n    const lo = Math.max(aMin, total - (b.max ?? 100), 0)\n    const hi = Math.min(a.max ?? 100, total - (b.min ?? 10), total)\n    const next = [...sizes]\n    next[i] = clamp(target, Math.min(lo, hi), Math.max(lo, hi))\n    next[i + 1] = total - next[i]\n    apply(next)\n  }\n\n  const onKeyDown = (i: number, e: React.KeyboardEvent) => {\n    const step = e.shiftKey ? 10 : 2\n    const rtl =\n      !vertical && ref.current\n        ? getComputedStyle(ref.current).direction === \"rtl\"\n        : false\n    const dec = vertical ? \"ArrowUp\" : rtl ? \"ArrowRight\" : \"ArrowLeft\"\n    const inc = vertical ? \"ArrowDown\" : rtl ? \"ArrowLeft\" : \"ArrowRight\"\n    const a = panels[i]\n    if (e.key === inc) moveTo(i, sizes[i] + step)\n    else if (e.key === dec) moveTo(i, sizes[i] - step)\n    else if (e.key === \"Home\") moveTo(i, 0)\n    else if (e.key === \"End\") moveTo(i, 100)\n    else if (e.key === \"Enter\") {\n      if (!a.collapsible) return\n      if (sizes[i] === 0) moveTo(i, restore.current[i] ?? a.defaultSize ?? 30)\n      else {\n        restore.current[i] = sizes[i]\n        moveTo(i, 0)\n      }\n    } else return\n    e.preventDefault()\n  }\n\n  const onPointerDown = (i: number, e: React.PointerEvent<HTMLDivElement>) => {\n    const box = ref.current\n    if (!box) return\n    e.preventDefault()\n    const handle = e.currentTarget\n    handle.setPointerCapture(e.pointerId)\n    const rect = box.getBoundingClientRect()\n    const length = vertical ? rect.height : rect.width\n    const rtl = !vertical && getComputedStyle(box).direction === \"rtl\"\n    const start = vertical ? e.clientY : e.clientX\n    const startSize = sizes[i]\n    const move = (ev: PointerEvent) => {\n      const pos = vertical ? ev.clientY : ev.clientX\n      const delta = ((pos - start) / length) * 100 * (rtl ? -1 : 1)\n      moveTo(i, startSize + delta)\n    }\n    const up = () => {\n      handle.removeEventListener(\"pointermove\", move)\n      handle.removeEventListener(\"pointerup\", up)\n      handle.removeEventListener(\"pointercancel\", up)\n    }\n    handle.addEventListener(\"pointermove\", move)\n    handle.addEventListener(\"pointerup\", up)\n    handle.addEventListener(\"pointercancel\", up)\n  }\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex h-full w-full overflow-hidden rounded-lg border\",\n        vertical && \"flex-col\",\n        className\n      )}\n      {...props}\n    >\n      {panels.map((p, i) => {\n        const prev = panels[i - 1]\n        return (\n          <React.Fragment key={p.id}>\n            {i > 0 && prev ? (\n              <div\n                role=\"separator\"\n                tabIndex={0}\n                aria-orientation={vertical ? \"horizontal\" : \"vertical\"}\n                aria-controls={`${baseId}-${prev.id}`}\n                aria-valuenow={Math.round(sizes[i - 1])}\n                aria-valuemin={prev.collapsible ? 0 : (prev.min ?? 10)}\n                aria-valuemax={prev.max ?? 100}\n                aria-label={`Resize ${prev.label ?? prev.id}`}\n                onKeyDown={(e) => onKeyDown(i - 1, e)}\n                onPointerDown={(e) => onPointerDown(i - 1, e)}\n                onDoubleClick={() => {\n                  if (!prev.collapsible) return\n                  if (sizes[i - 1] === 0)\n                    moveTo(\n                      i - 1,\n                      restore.current[i - 1] ?? prev.defaultSize ?? 30\n                    )\n                  else {\n                    restore.current[i - 1] = sizes[i - 1]\n                    moveTo(i - 1, 0)\n                  }\n                }}\n                className={cn(\n                  \"bg-border hover:bg-ring focus-visible:bg-ring relative shrink-0 touch-none outline-none motion-reduce:transition-none\",\n                  vertical\n                    ? \"h-px cursor-row-resize after:absolute after:inset-x-0 after:-inset-y-1.5\"\n                    : \"w-px cursor-col-resize after:absolute after:-inset-x-1.5 after:inset-y-0\"\n                )}\n              />\n            ) : null}\n            <div\n              id={`${baseId}-${p.id}`}\n              role=\"region\"\n              aria-label={p.label ?? p.id}\n              hidden={sizes[i] === 0}\n              className=\"min-h-0 min-w-0 overflow-auto\"\n              style={{ flexBasis: `${sizes[i]}%`, flexGrow: 0, flexShrink: 0 }}\n            >\n              {p.content}\n            </div>\n          </React.Fragment>\n        )\n      })}\n    </div>\n  )\n}\n\nexport default Splitter\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}