{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "resizable",
  "type": "registry:ui",
  "title": "Resizable",
  "description": "Resizable panel group for Tile UI.",
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/resizable/resizable.tsx",
      "type": "registry:ui",
      "target": "components/ui/resizable/resizable.tsx",
      "content": "import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';\nimport { computeResizableSizes, getResizableDirectionCursor, getResizableStorageKey, resizableStyleKeys } from '../lib/core';\nimport type { ResizableDirection, ResizablePanelGroupBaseProps } from '../lib/core';\nimport styles from './resizable.module.scss';\n\ninterface ResizableContextValue {\n\tdirection: ResizableDirection;\n\tcontainerRef: React.RefObject<HTMLDivElement | null>;\n\tgetSize: (index: number) => number;\n\tregisterPanel: () => number;\n\tregisterHandle: () => number;\n\tresize: (index: number, delta: number) => void;\n}\n\nconst ResizableContext = createContext<ResizableContextValue | null>(null);\n\nfunction useResizable(): ResizableContextValue {\n\tconst context = useContext(ResizableContext);\n\tif (!context) {\n\t\tthrow new Error('Resizable sub-components must be used within <ResizablePanelGroup>.');\n\t}\n\treturn context;\n}\n\nexport interface ResizablePanelGroupProps extends React.HTMLAttributes<HTMLDivElement>, ResizablePanelGroupBaseProps {}\n\nconst ResizablePanelGroup = React.forwardRef<HTMLDivElement, ResizablePanelGroupProps>(({ className = '', direction = 'horizontal', id, children, ...props }, ref) => {\n\tconst containerRef = useRef<HTMLDivElement | null>(null);\n\tconst panelCountRef = useRef(0);\n\tconst [panelCount, setPanelCount] = useState(0);\n\tconst [sizes, setSizes] = useState<number[]>(() => {\n\t\tif (!id || typeof window === 'undefined') {\n\t\t\treturn [];\n\t\t}\n\t\tconst stored = window.localStorage.getItem(getResizableStorageKey(id));\n\t\tif (!stored) {\n\t\t\treturn [];\n\t\t}\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(stored);\n\t\t\treturn Array.isArray(parsed) ? parsed.map((item: unknown) => Number(item)) : [];\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t});\n\n\tuseEffect(() => {\n\t\tif (!id || sizes.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\twindow.localStorage.setItem(getResizableStorageKey(id), JSON.stringify(sizes));\n\t}, [id, sizes]);\n\n\tconst registerPanel = useCallback(() => {\n\t\tconst index = panelCountRef.current;\n\t\tpanelCountRef.current += 1;\n\t\tsetPanelCount(panelCountRef.current);\n\t\treturn index;\n\t}, []);\n\n\tconst registerHandle = useCallback(() => {\n\t\treturn Math.max(0, panelCountRef.current - 1);\n\t}, []);\n\n\tconst getSize = useCallback(\n\t\t(index: number) => {\n\t\t\treturn sizes[index] ?? 100 / Math.max(panelCount, 1);\n\t\t},\n\t\t[sizes, panelCount],\n\t);\n\n\tconst resize = useCallback((index: number, delta: number) => {\n\t\tsetSizes((prev) => {\n\t\t\tconst total = Math.max(panelCountRef.current, 2);\n\t\t\tconst current = Array.from({ length: total }, (_item, itemIndex) => prev[itemIndex] ?? 100 / total);\n\t\t\treturn computeResizableSizes(current, index, delta);\n\t\t});\n\t}, []);\n\n\tfunction setContainerRef(element: HTMLDivElement | null) {\n\t\tcontainerRef.current = element;\n\t\tif (typeof ref === 'function') {\n\t\t\tref(element);\n\t\t} else if (ref) {\n\t\t\tref.current = element;\n\t\t}\n\t}\n\n\treturn (\n\t\t<ResizableContext.Provider value={{ direction, containerRef, getSize, registerPanel, registerHandle, resize }}>\n\t\t\t<div ref={setContainerRef} data-slot=\"resizable-panel-group\" data-direction={direction} className={`${styles[resizableStyleKeys.group]} ${className}`} {...props}>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</ResizableContext.Provider>\n\t);\n});\nResizablePanelGroup.displayName = 'ResizablePanelGroup';\n\nexport interface ResizablePanelProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst ResizablePanel = React.forwardRef<HTMLDivElement, ResizablePanelProps>(({ className = '', style, children, ...props }, ref) => {\n\tconst { getSize, registerPanel } = useResizable();\n\tconst [index, setIndex] = useState(-1);\n\tconst registeredRef = useRef(false);\n\n\tuseEffect(() => {\n\t\tif (registeredRef.current) {\n\t\t\treturn;\n\t\t}\n\t\tregisteredRef.current = true;\n\t\tsetIndex(registerPanel());\n\t}, [registerPanel]);\n\n\tconst size = index >= 0 ? getSize(index) : undefined;\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\tdata-slot=\"resizable-panel\"\n\t\t\tclassName={`${styles[resizableStyleKeys.panel]} ${className}`}\n\t\t\tstyle={{ ...style, flex: size !== undefined ? `0 0 ${size}%` : undefined }}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t</div>\n\t);\n});\nResizablePanel.displayName = 'ResizablePanel';\n\nexport interface ResizableHandleProps extends React.HTMLAttributes<HTMLDivElement> {\n\twithHandle?: boolean;\n}\n\nconst ResizableHandle = React.forwardRef<HTMLDivElement, ResizableHandleProps>(({ className = '', withHandle = false, ...props }, ref) => {\n\tconst { direction, containerRef, registerHandle, resize } = useResizable();\n\tconst [index, setIndex] = useState(-1);\n\tconst [dragging, setDragging] = useState(false);\n\tconst registeredRef = useRef(false);\n\n\tuseEffect(() => {\n\t\tif (registeredRef.current) {\n\t\t\treturn;\n\t\t}\n\t\tregisteredRef.current = true;\n\t\tsetIndex(registerHandle());\n\t}, [registerHandle]);\n\n\tfunction handlePointerDown(event: React.PointerEvent<HTMLDivElement>) {\n\t\tif (index < 0) {\n\t\t\treturn;\n\t\t}\n\t\tevent.preventDefault();\n\t\tconst container = containerRef.current;\n\t\tif (!container) {\n\t\t\treturn;\n\t\t}\n\t\tconst rect = container.getBoundingClientRect();\n\t\tconst total = direction === 'horizontal' ? rect.width : rect.height;\n\t\tconst startCoord = direction === 'horizontal' ? event.clientX : event.clientY;\n\t\tlet lastDeltaPercent = 0;\n\n\t\tsetDragging(true);\n\t\tdocument.body.style.cursor = getResizableDirectionCursor(direction);\n\n\t\tfunction handlePointerMove(moveEvent: PointerEvent) {\n\t\t\tconst coord = direction === 'horizontal' ? moveEvent.clientX : moveEvent.clientY;\n\t\t\tconst deltaPercent = total > 0 ? ((coord - startCoord) / total) * 100 : 0;\n\t\t\tconst step = deltaPercent - lastDeltaPercent;\n\t\t\tresize(index, step);\n\t\t\tlastDeltaPercent = deltaPercent;\n\t\t}\n\n\t\tfunction handlePointerUp() {\n\t\t\tsetDragging(false);\n\t\t\tdocument.body.style.cursor = '';\n\t\t\twindow.removeEventListener('pointermove', handlePointerMove);\n\t\t\twindow.removeEventListener('pointerup', handlePointerUp);\n\t\t}\n\n\t\twindow.addEventListener('pointermove', handlePointerMove);\n\t\twindow.addEventListener('pointerup', handlePointerUp);\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\trole=\"separator\"\n\t\t\taria-orientation={direction === 'horizontal' ? 'vertical' : 'horizontal'}\n\t\t\tdata-slot=\"resizable-handle\"\n\t\t\tdata-active={dragging}\n\t\t\tonPointerDown={handlePointerDown}\n\t\t\tclassName={`${styles[resizableStyleKeys.handle]} ${className}`}\n\t\t\t{...props}>\n\t\t\t{withHandle && (\n\t\t\t\t<div className={styles[resizableStyleKeys.handleBar]}>\n\t\t\t\t\t<svg\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\twidth=\"16\"\n\t\t\t\t\t\theight=\"16\"\n\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t\t<circle cx=\"9\" cy=\"12\" r=\"1\" />\n\t\t\t\t\t\t<circle cx=\"15\" cy=\"12\" r=\"1\" />\n\t\t\t\t\t\t<circle cx=\"9\" cy=\"5\" r=\"1\" />\n\t\t\t\t\t\t<circle cx=\"15\" cy=\"5\" r=\"1\" />\n\t\t\t\t\t\t<circle cx=\"9\" cy=\"19\" r=\"1\" />\n\t\t\t\t\t\t<circle cx=\"15\" cy=\"19\" r=\"1\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n});\nResizableHandle.displayName = 'ResizableHandle';\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle };\nexport default ResizablePanelGroup;\n"
    },
    {
      "path": "packages/styles/scss/components/resizable.module.scss",
      "type": "registry:file",
      "target": "components/ui/resizable/resizable.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n// 面板组\n.group {\n\tdisplay: flex;\n\twidth: 100%;\n\theight: 100%;\n\toverflow: hidden;\n\n\t&[data-direction='vertical'] {\n\t\tflex-direction: column;\n\t}\n}\n\n// 单个面板\n.panel {\n\tmin-width: 0;\n\tmin-height: 0;\n\toverflow: hidden;\n}\n\n// 拖拽手柄 (分割条)\n.handle {\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n\tbackground-color: var(--tile-resizable-line, $border);\n\ttouch-action: none;\n\toutline: none;\n\n\t&::after {\n\t\tcontent: '';\n\t\tposition: absolute;\n\t\tbackground-color: transparent;\n\t\ttransition: background-color $transition-fast;\n\t}\n\n\t&:hover::after,\n\t&[data-active='true']::after {\n\t\tbackground-color: var(--tile-resizable-active-color, $ring);\n\t}\n\n\t&:focus-visible {\n\t\tbox-shadow: var(--tile-resizable-focus-shadow, 0 0 0 2px $background, 0 0 0 4px $ring);\n\t}\n\n\t.handleBar {\n\t\tz-index: 10;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 0.75rem;\n\t\theight: 1rem;\n\t\tborder: 1px solid var(--tile-resizable-line, $border);\n\t\tborder-radius: $radius-xs;\n\t\tbackground-color: var(--tile-resizable-line, $border);\n\n\t\tsvg {\n\t\t\twidth: 0.625rem;\n\t\t\theight: 0.625rem;\n\t\t}\n\t}\n}\n\n// 水平方向：纵向分割条\n.group[data-direction='horizontal'] > .handle {\n\twidth: 1px;\n\talign-self: stretch;\n\tcursor: col-resize;\n\n\t&::after {\n\t\tinset: 0 -3px;\n\t}\n}\n\n// 垂直方向：横向分割条\n.group[data-direction='vertical'] > .handle {\n\theight: 1px;\n\talign-self: stretch;\n\tcursor: row-resize;\n\n\t&::after {\n\t\tinset: -3px 0;\n\t}\n\n\t.handleBar {\n\t\twidth: 1rem;\n\t\theight: 0.75rem;\n\n\t\tsvg {\n\t\t\ttransform: rotate(90deg);\n\t\t}\n\t}\n}\n"
    },
    {
      "path": "packages/react/src/components/resizable/index.ts",
      "type": "registry:ui",
      "target": "components/ui/resizable/index.ts",
      "content": "export { ResizablePanelGroup, ResizablePanel, ResizableHandle, type ResizablePanelGroupProps, type ResizablePanelProps, type ResizableHandleProps } from './resizable';\n"
    }
  ]
}
