{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "resizable",
  "title": "Resizable",
  "author": "andongmin94",
  "description": "Adjustable panel groups with draggable resize handles.",
  "dependencies": [
    "react-resizable-panels@^4.12.2"
  ],
  "files": [
    {
      "path": "src/components/ui/resizable.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { GripVertical } from \"lucide-react\";\nimport * as ResizablePrimitive from \"react-resizable-panels\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype LegacyTagName = keyof HTMLElementTagNameMap;\ntype PointerHitAreaMargins = {\n  coarse: number;\n  fine: number;\n};\n\ntype ResizablePanelGroupProps = Omit<ResizablePrimitive.GroupProps, \"id\" | \"orientation\"> & {\n  autoSaveId?: string | null;\n  direction?: ResizablePrimitive.Orientation;\n  id?: string | number | null;\n  keyboardResizeBy?: number | null;\n  onLayout?: ((layout: number[]) => void) | null;\n  orientation?: ResizablePrimitive.Orientation;\n  storage?: ResizablePrimitive.LayoutStorage;\n  tagName?: LegacyTagName;\n};\n\ntype ResizablePanelProps = Omit<\n  ResizablePrimitive.PanelProps,\n  \"collapsedSize\" | \"defaultSize\" | \"id\" | \"maxSize\" | \"minSize\" | \"onResize\"\n> & {\n  collapsedSize?: string | number;\n  defaultSize?: string | number;\n  id?: string | number | null;\n  maxSize?: string | number;\n  minSize?: string | number;\n  onCollapse?: () => void;\n  onExpand?: () => void;\n  onResize?: (size: number, prevSize: number | undefined) => void;\n  order?: number;\n  tagName?: LegacyTagName;\n};\n\ntype ResizableHandleProps = Omit<ResizablePrimitive.SeparatorProps, \"id\"> & {\n  hitAreaMargins?: PointerHitAreaMargins;\n  id?: string | number | null;\n  onDragging?: (isDragging: boolean) => void;\n  tagName?: LegacyTagName;\n  withHandle?: boolean;\n};\n\nexport type ResizablePanelHandle = {\n  collapse: () => void;\n  expand: (minSize?: number) => void;\n  getId: () => string;\n  getSize: () => number;\n  isCollapsed: () => boolean;\n  isExpanded: () => boolean;\n  resize: (size: number) => void;\n};\n\nconst LEGACY_HIT_AREA_MARGINS = { coarse: 15, fine: 5 } as const;\nconst HANDLE_THICKNESS = 2;\nconst LEGACY_KEYBOARD_RESIZE_BY = 10;\n\nconst defaultStorage: ResizablePrimitive.LayoutStorage = {\n  getItem(name) {\n    try {\n      return typeof window === \"undefined\" ? null : window.localStorage.getItem(name);\n    } catch {\n      return null;\n    }\n  },\n  setItem(name, value) {\n    try {\n      window.localStorage.setItem(name, value);\n    } catch {\n      // Match the legacy adapter's no-op behavior when storage is unavailable.\n    }\n  },\n};\n\nfunction assignRef<T>(ref: React.Ref<T> | undefined, value: T | null) {\n  if (typeof ref === \"function\") {\n    ref(value);\n  } else if (ref) {\n    ref.current = value;\n  }\n}\n\nfunction toPercentSize(value: string | number | undefined) {\n  return typeof value === \"number\" ? `${value}%` : value;\n}\n\nfunction isSamePercentage(left: number, right: number) {\n  return Math.abs(left - right) < 0.0001;\n}\n\nconst ResizablePanel = React.forwardRef<ResizablePanelHandle, ResizablePanelProps>(\n  function ResizablePanel(\n    {\n      collapsedSize,\n      defaultSize,\n      id,\n      maxSize,\n      minSize,\n      onCollapse,\n      onExpand,\n      onResize,\n      order,\n      tagName,\n      elementRef,\n      panelRef,\n      collapsible,\n      ...props\n    },\n    forwardedRef,\n  ) {\n    const panelElementRef = React.useRef<HTMLDivElement | null>(null);\n    const primitivePanelRef = React.useRef<ResizablePrimitive.PanelImperativeHandle | null>(null);\n    const previousCollapsedRef = React.useRef<boolean | undefined>(undefined);\n    const legacyCallbacksRef = React.useRef({\n      collapsedSize,\n      collapsible,\n      onCollapse,\n      onExpand,\n      onResize,\n    });\n    legacyCallbacksRef.current = {\n      collapsedSize,\n      collapsible,\n      onCollapse,\n      onExpand,\n      onResize,\n    };\n\n    const setElementRef = React.useCallback(\n      (element: HTMLDivElement | null) => {\n        panelElementRef.current = element;\n        if (element) {\n          if (order === undefined) {\n            element.style.removeProperty(\"order\");\n          } else {\n            element.style.order = String(order);\n          }\n        }\n        assignRef(elementRef, element);\n      },\n      [elementRef, order],\n    );\n\n    const setPanelRef = React.useCallback(\n      (handle: ResizablePrimitive.PanelImperativeHandle | null) => {\n        primitivePanelRef.current = handle;\n        assignRef(panelRef, handle);\n      },\n      [panelRef],\n    );\n\n    const handleResize = React.useCallback(\n      (\n        panelSize: ResizablePrimitive.PanelSize,\n        _id: string | number | undefined,\n        prevPanelSize: ResizablePrimitive.PanelSize | undefined,\n      ) => {\n        const callbacks = legacyCallbacksRef.current;\n        callbacks.onResize?.(panelSize.asPercentage, prevPanelSize?.asPercentage);\n\n        if (!callbacks.collapsible) return;\n\n        const collapsedPercentage =\n          typeof callbacks.collapsedSize === \"number\"\n            ? callbacks.collapsedSize\n            : callbacks.collapsedSize?.endsWith(\"%\")\n              ? Number.parseFloat(callbacks.collapsedSize)\n              : 0;\n        const isCollapsed =\n          primitivePanelRef.current?.isCollapsed() ??\n          isSamePercentage(panelSize.asPercentage, collapsedPercentage);\n        const wasCollapsed =\n          previousCollapsedRef.current ??\n          (prevPanelSize\n            ? isSamePercentage(prevPanelSize.asPercentage, collapsedPercentage)\n            : undefined);\n\n        if ((wasCollapsed === undefined || wasCollapsed) && !isCollapsed) {\n          callbacks.onExpand?.();\n        }\n        if ((wasCollapsed === undefined || !wasCollapsed) && isCollapsed) {\n          callbacks.onCollapse?.();\n        }\n        previousCollapsedRef.current = isCollapsed;\n      },\n      [],\n    );\n\n    React.useImperativeHandle(\n      forwardedRef,\n      () => ({\n        collapse: () => primitivePanelRef.current?.collapse(),\n        expand: (size) => {\n          const handle = primitivePanelRef.current;\n          if (!handle?.isCollapsed()) return;\n\n          handle.expand();\n          if (size !== undefined && handle.getSize().asPercentage < size) {\n            handle.resize(`${size}%`);\n          }\n        },\n        getId: () => panelElementRef.current?.id ?? String(id ?? \"\"),\n        getSize: () => primitivePanelRef.current?.getSize().asPercentage ?? 0,\n        isCollapsed: () => primitivePanelRef.current?.isCollapsed() ?? false,\n        isExpanded: () => !(primitivePanelRef.current?.isCollapsed() ?? false),\n        resize: (size) => primitivePanelRef.current?.resize(`${size}%`),\n      }),\n      [id],\n    );\n\n    return (\n      <ResizablePrimitive.Panel\n        key={`${tagName ?? \"div\"}:${order ?? \"\"}`}\n        data-slot=\"resizable-panel\"\n        collapsedSize={toPercentSize(collapsedSize)}\n        defaultSize={toPercentSize(defaultSize)}\n        id={id == null ? undefined : String(id)}\n        maxSize={toPercentSize(maxSize)}\n        minSize={toPercentSize(minSize)}\n        collapsible={collapsible}\n        elementRef={setElementRef}\n        panelRef={setPanelRef}\n        onResize={handleResize}\n        {...props}\n      />\n    );\n  },\n);\n\nfunction ResizableHandle({\n  withHandle,\n  className,\n  hitAreaMargins: _hitAreaMargins,\n  id,\n  onDragging,\n  tagName,\n  elementRef,\n  ...props\n}: ResizableHandleProps) {\n  const separatorElementRef = React.useRef<HTMLDivElement | null>(null);\n  const onDraggingRef = React.useRef(onDragging);\n  onDraggingRef.current = onDragging;\n\n  const setElementRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      separatorElementRef.current = element;\n      assignRef(elementRef, element);\n    },\n    [elementRef],\n  );\n\n  React.useEffect(() => {\n    const element = separatorElementRef.current;\n    if (!element) return;\n\n    let wasDragging = element.dataset.separator === \"active\";\n    const observer = new MutationObserver(() => {\n      const isDragging = element.dataset.separator === \"active\";\n      if (isDragging !== wasDragging) {\n        wasDragging = isDragging;\n        onDraggingRef.current?.(isDragging);\n      }\n    });\n    observer.observe(element, { attributeFilter: [\"data-separator\"] });\n\n    return () => {\n      observer.disconnect();\n      if (wasDragging) onDraggingRef.current?.(false);\n    };\n  }, [tagName]);\n\n  return (\n    <ResizablePrimitive.Separator\n      key={tagName}\n      data-slot=\"resizable-handle\"\n      id={id == null ? undefined : String(id)}\n      elementRef={setElementRef}\n      className={cn(\n        \"relative flex w-0.5 items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-black focus-visible:ring-offset-1 aria-[orientation=horizontal]:h-0.5 aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:-translate-y-1/2 aria-[orientation=horizontal]:after:translate-x-0 [&[aria-orientation=horizontal]>div]:rotate-90\",\n        className,\n      )}\n      {...props}\n    >\n      {withHandle && (\n        <div className=\"z-10 flex h-4 w-3 items-center justify-center rounded-base border bg-border\">\n          <GripVertical className=\"size-2.5\" />\n        </div>\n      )}\n    </ResizablePrimitive.Separator>\n  );\n}\n\nfunction getLegacyResizeTargetMinimumSize(children: React.ReactNode) {\n  let target: PointerHitAreaMargins | undefined;\n\n  React.Children.forEach(children, (child) => {\n    if (!React.isValidElement(child)) return;\n    if (child.type === React.Fragment) {\n      const fragmentProps = child.props as { children?: React.ReactNode };\n      const fragmentTarget = getLegacyResizeTargetMinimumSize(fragmentProps.children);\n      if (fragmentTarget) {\n        target = {\n          coarse: Math.max(target?.coarse ?? 0, fragmentTarget.coarse),\n          fine: Math.max(target?.fine ?? 0, fragmentTarget.fine),\n        };\n      }\n      return;\n    }\n    if (child.type !== ResizableHandle) return;\n\n    const handleProps = child.props as ResizableHandleProps;\n    const margins = handleProps.hitAreaMargins ?? LEGACY_HIT_AREA_MARGINS;\n    target = {\n      coarse: Math.max(target?.coarse ?? 0, HANDLE_THICKNESS + margins.coarse * 2),\n      fine: Math.max(target?.fine ?? 0, HANDLE_THICKNESS + margins.fine * 2),\n    };\n  });\n\n  return target;\n}\n\ntype PersistedLayout = ReturnType<typeof ResizablePrimitive.useDefaultLayout>;\ntype ResizablePanelGroupAdapterProps = ResizablePanelGroupProps & {\n  persistedLayout?: PersistedLayout;\n};\n\nfunction ResizablePanelGroupAdapter({\n  autoSaveId: _autoSaveId,\n  children,\n  className,\n  defaultLayout,\n  direction,\n  disabled,\n  elementRef,\n  groupRef,\n  id,\n  keyboardResizeBy,\n  onKeyDownCapture,\n  onLayout,\n  onLayoutChange,\n  onLayoutChanged,\n  orientation = direction ?? \"horizontal\",\n  persistedLayout,\n  resizeTargetMinimumSize,\n  storage: _storage,\n  tagName,\n  ...props\n}: ResizablePanelGroupAdapterProps) {\n  const groupApiRef = React.useRef<ResizablePrimitive.GroupImperativeHandle | null>(null);\n  const groupElementRef = React.useRef<HTMLDivElement | null>(null);\n\n  const setElementRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      groupElementRef.current = element;\n      assignRef(elementRef, element);\n    },\n    [elementRef],\n  );\n\n  const setGroupRef = React.useCallback(\n    (handle: ResizablePrimitive.GroupImperativeHandle | null) => {\n      groupApiRef.current = handle;\n      assignRef(groupRef, handle);\n    },\n    [groupRef],\n  );\n\n  const layoutToArray = React.useCallback((layout: ResizablePrimitive.Layout) => {\n    const panelIds = groupElementRef.current\n      ? Array.from(groupElementRef.current.children)\n          .filter((element) => element.hasAttribute(\"data-panel\"))\n          .map((element) => element.id)\n      : [];\n    return panelIds.length === Object.keys(layout).length\n      ? panelIds.map((panelId) => layout[panelId] ?? 0)\n      : Object.values(layout);\n  }, []);\n\n  const handleLayoutChange = React.useCallback(\n    (layout: ResizablePrimitive.Layout) => {\n      persistedLayout?.onLayoutChange(layout);\n      onLayoutChange?.(layout);\n      onLayout?.(layoutToArray(layout));\n    },\n    [layoutToArray, onLayout, onLayoutChange, persistedLayout],\n  );\n\n  const handleKeyDownCapture = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      onKeyDownCapture?.(event);\n      if (disabled || event.defaultPrevented) return;\n\n      const isHorizontalKey = event.key === \"ArrowLeft\" || event.key === \"ArrowRight\";\n      const isVerticalKey = event.key === \"ArrowUp\" || event.key === \"ArrowDown\";\n      if (\n        (orientation === \"horizontal\" && !isHorizontalKey) ||\n        (orientation === \"vertical\" && !isVerticalKey)\n      ) {\n        return;\n      }\n\n      const target =\n        event.target instanceof Element\n          ? event.target.closest<HTMLElement>(\"[data-separator]\")\n          : null;\n      const groupElement = groupElementRef.current;\n      const groupApi = groupApiRef.current;\n      if (!target || !groupElement || !groupApi || target.parentElement !== groupElement) return;\n\n      const elements = Array.from(groupElement.children);\n      const separatorIndex = elements.indexOf(target);\n      const panelIndex =\n        elements.slice(0, separatorIndex).filter((element) => element.hasAttribute(\"data-panel\"))\n          .length - 1;\n      const panelIds = elements\n        .filter((element) => element.hasAttribute(\"data-panel\"))\n        .map((element) => element.id);\n      const firstPanelId = panelIds[panelIndex];\n      const secondPanelId = panelIds[panelIndex + 1];\n      if (!firstPanelId || !secondPanelId) return;\n\n      const layout = groupApi.getLayout();\n      let delta = event.shiftKey ? 100 : (keyboardResizeBy ?? LEGACY_KEYBOARD_RESIZE_BY);\n      if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") delta *= -1;\n      if (orientation === \"horizontal\" && getComputedStyle(groupElement).direction === \"rtl\") {\n        delta *= -1;\n      }\n\n      event.preventDefault();\n      groupApi.setLayout({\n        ...layout,\n        [firstPanelId]: layout[firstPanelId] + delta,\n        [secondPanelId]: layout[secondPanelId] - delta,\n      });\n    },\n    [disabled, keyboardResizeBy, onKeyDownCapture, orientation],\n  );\n\n  const legacyResizeTargetMinimumSize = getLegacyResizeTargetMinimumSize(children);\n\n  return (\n    <ResizablePrimitive.Group\n      key={tagName}\n      data-slot=\"resizable-panel-group\"\n      id={id == null ? undefined : String(id)}\n      orientation={orientation}\n      disabled={disabled}\n      defaultLayout={defaultLayout ?? persistedLayout?.defaultLayout}\n      elementRef={setElementRef}\n      groupRef={setGroupRef}\n      onKeyDownCapture={handleKeyDownCapture}\n      onLayoutChange={handleLayoutChange}\n      onLayoutChanged={onLayoutChanged}\n      resizeTargetMinimumSize={resizeTargetMinimumSize ?? legacyResizeTargetMinimumSize}\n      className={cn(\"flex h-full w-full font-base aria-[orientation=vertical]:flex-col\", className)}\n      {...props}\n    >\n      {children}\n    </ResizablePrimitive.Group>\n  );\n}\n\nfunction PersistedResizablePanelGroup({\n  autoSaveId,\n  storage,\n  ...props\n}: ResizablePanelGroupProps & { autoSaveId: string }) {\n  const persistedLayout = ResizablePrimitive.useDefaultLayout({\n    id: autoSaveId,\n    storage: storage ?? defaultStorage,\n  });\n\n  return (\n    <ResizablePanelGroupAdapter\n      autoSaveId={autoSaveId}\n      storage={storage}\n      persistedLayout={persistedLayout}\n      {...props}\n    />\n  );\n}\n\nfunction ResizablePanelGroup(props: ResizablePanelGroupProps) {\n  return props.autoSaveId ? (\n    <PersistedResizablePanelGroup {...props} autoSaveId={props.autoSaveId} />\n  ) : (\n    <ResizablePanelGroupAdapter {...props} />\n  );\n}\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle };\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "layout"
  ],
  "type": "registry:ui"
}