{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "resizable-ratio",
  "title": "Ratio resizable",
  "description": "Styled resizable: the Ratio set. 5 decorative resizable variations (PillResizable, InlineResizable, BadgeResizable, TooltipResizable, CornerResizable) 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/resizable-ratio.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Ratio resizable family: 5 self-contained split panels (NO panel library, NO\n   radix). The theme idea: while dragging, a live percentage label appears on or\n   beside the handle. The value comes from the pct the hook tracks; it is formatted\n   as Math.round(pct) + \"%\" (deterministic, no Date.now / Math.random). The\n   separator uses role=\"separator\" + aria-orientation + aria-valuenow/min/max,\n   tabIndex=0. Color comes ONLY from semantic tokens; alpha via color-mix or the\n   Tailwind /NN. The motion is gated on useReducedMotion() (in reduced the label\n   appears without animation; the drag itself is always instant). */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\ntype Orientation = \"horizontal\" | \"vertical\"\n\nconst height: Record<StyledSize, string> = {\n  sm: \"h-40\",\n  md: \"h-56\",\n  lg: \"h-72\",\n  xl: \"h-96\",\n}\n\nconst MIN = 12\nconst MAX = 88\nconst clamp = (n: number) => Math.min(MAX, Math.max(MIN, n))\n\nconst focusRing =\n  \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\n/* Ortak bolme mantigi: pct state + pointer/klavye tutamac handler'lari.\n   horizontal = sol/sag panel, dikey ayirici (yatay suruklenir).\n   vertical   = ust/alt panel, yatay ayirici (dikey suruklenir). */\nfunction useResizable(orientation: Orientation, initial = 50) {\n  const [pct, setPct] = React.useState(initial)\n  const containerRef = React.useRef<HTMLDivElement>(null)\n  const [dragging, setDragging] = React.useState(false)\n\n  const applyFromPoint = React.useCallback(\n    (clientX: number, clientY: number) => {\n      const el = containerRef.current\n      if (!el) return\n      const rect = el.getBoundingClientRect()\n      const raw =\n        orientation === \"horizontal\"\n          ? ((clientX - rect.left) / rect.width) * 100\n          : ((clientY - rect.top) / rect.height) * 100\n      setPct(clamp(raw))\n    },\n    [orientation]\n  )\n\n  const onPointerDown = React.useCallback(\n    (e: React.PointerEvent<HTMLDivElement>) => {\n      e.preventDefault()\n      e.currentTarget.setPointerCapture(e.pointerId)\n      setDragging(true)\n    },\n    []\n  )\n\n  const onPointerMove = React.useCallback(\n    (e: React.PointerEvent<HTMLDivElement>) => {\n      if (!e.currentTarget.hasPointerCapture(e.pointerId)) return\n      applyFromPoint(e.clientX, e.clientY)\n    },\n    [applyFromPoint]\n  )\n\n  const onPointerUp = React.useCallback(\n    (e: React.PointerEvent<HTMLDivElement>) => {\n      if (e.currentTarget.hasPointerCapture(e.pointerId))\n        e.currentTarget.releasePointerCapture(e.pointerId)\n      setDragging(false)\n    },\n    []\n  )\n\n  const onKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLDivElement>) => {\n      const dec = orientation === \"horizontal\" ? \"ArrowLeft\" : \"ArrowUp\"\n      const inc = orientation === \"horizontal\" ? \"ArrowRight\" : \"ArrowDown\"\n      if (e.key === dec) {\n        e.preventDefault()\n        setPct((p) => clamp(p - (e.shiftKey ? 10 : 2)))\n      } else if (e.key === inc) {\n        e.preventDefault()\n        setPct((p) => clamp(p + (e.shiftKey ? 10 : 2)))\n      } else if (e.key === \"Home\") {\n        e.preventDefault()\n        setPct(MIN)\n      } else if (e.key === \"End\") {\n        e.preventDefault()\n        setPct(MAX)\n      }\n    },\n    [orientation]\n  )\n\n  const separatorProps = {\n    role: \"separator\" as const,\n    \"aria-orientation\": (orientation === \"horizontal\" ? \"vertical\" : \"horizontal\") as\n      | \"vertical\"\n      | \"horizontal\",\n    \"aria-valuenow\": Math.round(pct),\n    \"aria-valuemin\": MIN,\n    \"aria-valuemax\": MAX,\n    tabIndex: 0,\n    onPointerDown,\n    onPointerMove,\n    onPointerUp,\n    onKeyDown,\n  }\n\n  return { pct, containerRef, dragging, separatorProps }\n}\n\ninterface ResizableProps {\n  className?: string\n  size?: StyledSize\n  start?: React.ReactNode\n  end?: React.ReactNode\n}\n\n/* Varsayilan panel: token yuzeyli, ortalanmis etiket. */\nfunction Pane({\n  label,\n  tone = \"bg-surface-2\",\n  children,\n}: {\n  label?: string\n  tone?: string\n  children?: React.ReactNode\n}) {\n  return (\n    <div\n      className={cn(\n        \"flex h-full w-full items-center justify-center overflow-hidden p-4 text-sm font-medium text-foreground\",\n        tone\n      )}\n    >\n      {children ?? <span className=\"text-muted-foreground\">{label}</span>}\n    </div>\n  )\n}\n\nconst shell =\n  \"flex w-full select-none overflow-hidden rounded-xl border border-border\"\n\nconst fmt = (pct: number) => `${Math.round(pct)}%`\n\n/* PillResizable: tutamac ortasinda, suruklerken beliren primary yuvarlak yuzde etiketi. */\nexport function PillResizable({ className, size = \"md\", start, end }: ResizableProps) {\n  const { pct, containerRef, dragging, separatorProps } = useResizable(\"horizontal\")\n  const reduce = useReducedMotion()\n  return (\n    <div data-slot=\"styled-resizable\" ref={containerRef} className={cn(shell, height[size], className)}>\n      <div style={{ width: `${pct}%` }} className=\"h-full\">\n        {start ?? <Pane label=\"Panel bir\" tone=\"bg-surface-2\" />}\n      </div>\n      <div\n        {...separatorProps}\n        className={cn(\n          \"relative flex h-full w-1 shrink-0 cursor-col-resize items-center justify-center transition-colors\",\n          focusRing,\n          \"after:absolute after:inset-y-0 after:-inset-x-1.5 after:content-['']\",\n          dragging ? \"bg-primary\" : \"bg-border hover:bg-primary/60\"\n        )}\n      >\n        <AnimatePresence>\n          {dragging ? (\n            <motion.span\n              className=\"pointer-events-none z-10 rounded-full bg-primary px-2 py-0.5 text-[0.7rem] font-semibold tabular-nums text-primary-foreground shadow-sm\"\n              initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.8 }}\n              animate={reduce ? { opacity: 1 } : { opacity: 1, scale: 1 }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.8 }}\n              transition={{ duration: 0.14, ease: \"easeOut\" }}\n            >\n              {fmt(pct)}\n            </motion.span>\n          ) : null}\n        </AnimatePresence>\n      </div>\n      <div style={{ width: `${100 - pct}%` }} className=\"h-full\">\n        {end ?? <Pane label=\"Panel iki\" tone=\"bg-surface-3\" />}\n      </div>\n    </div>\n  )\n}\n\n/* InlineResizable: a small percentage text embedded on the separator that appears\n   while dragging. */\nexport function InlineResizable({ className, size = \"md\", start, end }: ResizableProps) {\n  const { pct, containerRef, dragging, separatorProps } = useResizable(\"horizontal\")\n  const reduce = useReducedMotion()\n  return (\n    <div data-slot=\"styled-resizable\" ref={containerRef} className={cn(shell, height[size], className)}>\n      <div style={{ width: `${pct}%` }} className=\"h-full\">\n        {start ?? <Pane label=\"Panel bir\" tone=\"bg-surface-2\" />}\n      </div>\n      <div\n        {...separatorProps}\n        className={cn(\n          \"relative flex h-full w-1.5 shrink-0 cursor-col-resize items-start justify-center bg-border transition-colors\",\n          focusRing,\n          \"after:absolute after:inset-y-0 after:-inset-x-1 after:content-['']\",\n          dragging ? \"bg-primary/60\" : \"hover:bg-primary/40\"\n        )}\n      >\n        <AnimatePresence>\n          {dragging ? (\n            <motion.span\n              className=\"pointer-events-none z-10 mt-2 text-[0.65rem] font-semibold tabular-nums text-primary [writing-mode:vertical-rl]\"\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              transition={{ duration: reduce ? 0 : 0.14 }}\n            >\n              {fmt(pct)}\n            </motion.span>\n          ) : null}\n        </AnimatePresence>\n      </div>\n      <div style={{ width: `${100 - pct}%` }} className=\"h-full\">\n        {end ?? <Pane label=\"Panel iki\" tone=\"bg-surface-3\" />}\n      </div>\n    </div>\n  )\n}\n\n/* BadgeResizable: ayiricinin ust kenarinda, suruklerken beliren yuzde rozeti. */\nexport function BadgeResizable({ className, size = \"md\", start, end }: ResizableProps) {\n  const { pct, containerRef, dragging, separatorProps } = useResizable(\"horizontal\")\n  const reduce = useReducedMotion()\n  return (\n    <div data-slot=\"styled-resizable\" ref={containerRef} className={cn(shell, height[size], className)}>\n      <div style={{ width: `${pct}%` }} className=\"h-full\">\n        {start ?? <Pane label=\"Panel bir\" tone=\"bg-surface-2\" />}\n      </div>\n      <div\n        {...separatorProps}\n        className={cn(\n          \"relative flex h-full w-px shrink-0 cursor-col-resize justify-center transition-colors\",\n          focusRing,\n          \"after:absolute after:inset-y-0 after:-inset-x-1.5 after:content-['']\",\n          dragging ? \"bg-primary\" : \"bg-border hover:bg-primary/60\"\n        )}\n      >\n        <AnimatePresence>\n          {dragging ? (\n            <motion.span\n              className=\"pointer-events-none absolute top-1.5 z-10 rounded-md border border-border bg-background px-1.5 py-0.5 text-[0.65rem] font-semibold tabular-nums text-foreground shadow-sm\"\n              initial={reduce ? { opacity: 0 } : { opacity: 0, y: -4 }}\n              animate={reduce ? { opacity: 1 } : { opacity: 1, y: 0 }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, y: -4 }}\n              transition={{ duration: 0.14, ease: \"easeOut\" }}\n            >\n              {fmt(pct)}\n            </motion.span>\n          ) : null}\n        </AnimatePresence>\n      </div>\n      <div style={{ width: `${100 - pct}%` }} className=\"h-full\">\n        {end ?? <Pane label=\"Panel iki\" tone=\"bg-surface-3\" />}\n      </div>\n    </div>\n  )\n}\n\n/* TooltipResizable: tutamacin ustunde, ok isaretli baloncuk seklinde yuzde ipucu. */\nexport function TooltipResizable({ className, size = \"md\", start, end }: ResizableProps) {\n  const { pct, containerRef, dragging, separatorProps } = useResizable(\"horizontal\")\n  const reduce = useReducedMotion()\n  return (\n    <div data-slot=\"styled-resizable\" ref={containerRef} className={cn(shell, height[size], className)}>\n      <div style={{ width: `${pct}%` }} className=\"h-full\">\n        {start ?? <Pane label=\"Panel bir\" tone=\"bg-surface-2\" />}\n      </div>\n      <div\n        {...separatorProps}\n        className={cn(\n          \"relative flex h-full w-1 shrink-0 cursor-col-resize items-center justify-center transition-colors\",\n          focusRing,\n          \"after:absolute after:inset-y-0 after:-inset-x-1.5 after:content-['']\",\n          dragging ? \"bg-primary\" : \"bg-border hover:bg-primary/60\"\n        )}\n      >\n        <AnimatePresence>\n          {dragging ? (\n            <motion.span\n              className=\"pointer-events-none absolute top-2 z-10 flex flex-col items-center\"\n              initial={reduce ? { opacity: 0 } : { opacity: 0, y: -4 }}\n              animate={reduce ? { opacity: 1 } : { opacity: 1, y: 0 }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, y: -4 }}\n              transition={{ duration: 0.14, ease: \"easeOut\" }}\n            >\n              <span className=\"rounded-md bg-foreground px-2 py-0.5 text-[0.7rem] font-semibold tabular-nums text-background shadow-md\">\n                {fmt(pct)}\n              </span>\n              <span className=\"-mt-px size-2 rotate-45 bg-foreground\" />\n            </motion.span>\n          ) : null}\n        </AnimatePresence>\n      </div>\n      <div style={{ width: `${100 - pct}%` }} className=\"h-full\">\n        {end ?? <Pane label=\"Panel iki\" tone=\"bg-surface-3\" />}\n      </div>\n    </div>\n  )\n}\n\n/* CornerResizable: yuzde, kabin sag ust kosesinde sabit bir okuyucu olarak gosterilir. */\nexport function CornerResizable({ className, size = \"md\", start, end }: ResizableProps) {\n  const { pct, containerRef, dragging, separatorProps } = useResizable(\"horizontal\")\n  const reduce = useReducedMotion()\n  return (\n    <div data-slot=\"styled-resizable\" ref={containerRef} className={cn(shell, \"relative\", height[size], className)}>\n      <div style={{ width: `${pct}%` }} className=\"h-full\">\n        {start ?? <Pane label=\"Panel bir\" tone=\"bg-surface-2\" />}\n      </div>\n      <div\n        {...separatorProps}\n        className={cn(\n          \"relative h-full w-px shrink-0 cursor-col-resize transition-colors\",\n          focusRing,\n          \"after:absolute after:inset-y-0 after:-inset-x-1.5 after:content-['']\",\n          dragging ? \"bg-primary\" : \"bg-border hover:bg-primary/60\"\n        )}\n      />\n      <div style={{ width: `${100 - pct}%` }} className=\"h-full\">\n        {end ?? <Pane label=\"Panel iki\" tone=\"bg-surface-3\" />}\n      </div>\n      <AnimatePresence>\n        {dragging ? (\n          <motion.span\n            className=\"pointer-events-none absolute right-2 top-2 z-10 rounded-md border border-border bg-background/90 px-2 py-0.5 text-[0.7rem] font-semibold tabular-nums text-foreground shadow-sm supports-[backdrop-filter]:backdrop-blur-sm\"\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: reduce ? 0 : 0.14 }}\n          >\n            {fmt(pct)}\n          </motion.span>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/resizable-ratio.tsx"
    }
  ],
  "categories": [
    "styled",
    "resizable"
  ],
  "type": "registry:component"
}