Spaces:
Build error
Build error
| import React, { CSSProperties, JSX, useEffect, useRef, useState } from "react"; | |
| import { | |
| VscChevronDown, | |
| VscChevronLeft, | |
| VscChevronRight, | |
| VscChevronUp, | |
| } from "react-icons/vsc"; | |
| import { twMerge } from "tailwind-merge"; | |
| import { IconButton } from "../shared/buttons/icon-button"; | |
| export enum Orientation { | |
| HORIZONTAL = "horizontal", | |
| VERTICAL = "vertical", | |
| } | |
| enum Collapse { | |
| COLLAPSED = "collapsed", | |
| SPLIT = "split", | |
| FILLED = "filled", | |
| } | |
| type ResizablePanelProps = { | |
| firstChild: React.ReactNode; | |
| firstClassName: string | undefined; | |
| secondChild: React.ReactNode; | |
| secondClassName: string | undefined; | |
| className: string | undefined; | |
| orientation: Orientation; | |
| initialSize: number; | |
| }; | |
| export function ResizablePanel({ | |
| firstChild, | |
| firstClassName, | |
| secondChild, | |
| secondClassName, | |
| className, | |
| orientation, | |
| initialSize, | |
| }: ResizablePanelProps): JSX.Element { | |
| const [firstSize, setFirstSize] = useState<number>(initialSize); | |
| const [dividerPosition, setDividerPosition] = useState<number | null>(null); | |
| const firstRef = useRef<HTMLDivElement>(null); | |
| const secondRef = useRef<HTMLDivElement>(null); | |
| const [collapse, setCollapse] = useState<Collapse>(Collapse.SPLIT); | |
| const isHorizontal = orientation === Orientation.HORIZONTAL; | |
| useEffect(() => { | |
| if (dividerPosition == null || !firstRef.current) { | |
| return undefined; | |
| } | |
| const getFirstSizeFromEvent = (e: MouseEvent) => { | |
| const position = isHorizontal ? e.clientX : e.clientY; | |
| return firstSize + position - dividerPosition; | |
| }; | |
| const onMouseMove = (e: MouseEvent) => { | |
| e.preventDefault(); | |
| const newFirstSize = `${getFirstSizeFromEvent(e)}px`; | |
| const { current } = firstRef; | |
| if (current) { | |
| if (isHorizontal) { | |
| current.style.width = newFirstSize; | |
| current.style.minWidth = newFirstSize; | |
| } else { | |
| current.style.height = newFirstSize; | |
| current.style.minHeight = newFirstSize; | |
| } | |
| } | |
| }; | |
| const onMouseUp = (e: MouseEvent) => { | |
| e.preventDefault(); | |
| if (firstRef.current) { | |
| firstRef.current.style.transition = ""; | |
| } | |
| if (secondRef.current) { | |
| secondRef.current.style.transition = ""; | |
| } | |
| setFirstSize(getFirstSizeFromEvent(e)); | |
| setDividerPosition(null); | |
| document.removeEventListener("mousemove", onMouseMove); | |
| document.removeEventListener("mouseup", onMouseUp); | |
| }; | |
| document.addEventListener("mousemove", onMouseMove); | |
| document.addEventListener("mouseup", onMouseUp); | |
| return () => { | |
| document.removeEventListener("mousemove", onMouseMove); | |
| document.removeEventListener("mouseup", onMouseUp); | |
| }; | |
| }, [dividerPosition, firstSize, orientation]); | |
| const onMouseDown = (e: React.MouseEvent) => { | |
| e.preventDefault(); | |
| if (firstRef.current) { | |
| firstRef.current.style.transition = "none"; | |
| } | |
| if (secondRef.current) { | |
| secondRef.current.style.transition = "none"; | |
| } | |
| const position = isHorizontal ? e.clientX : e.clientY; | |
| setDividerPosition(position); | |
| }; | |
| const getStyleForFirst = () => { | |
| const style: CSSProperties = { overflow: "hidden" }; | |
| if (collapse === Collapse.COLLAPSED) { | |
| style.opacity = 0; | |
| style.width = 0; | |
| style.minWidth = 0; | |
| style.height = 0; | |
| style.minHeight = 0; | |
| } else if (collapse === Collapse.SPLIT) { | |
| const firstSizePx = `${firstSize}px`; | |
| if (isHorizontal) { | |
| style.width = firstSizePx; | |
| style.minWidth = firstSizePx; | |
| } else { | |
| style.height = firstSizePx; | |
| style.minHeight = firstSizePx; | |
| } | |
| } else { | |
| style.flexGrow = 1; | |
| } | |
| return style; | |
| }; | |
| const getStyleForSecond = () => { | |
| const style: CSSProperties = { overflow: "hidden" }; | |
| if (collapse === Collapse.FILLED) { | |
| style.opacity = 0; | |
| style.width = 0; | |
| style.minWidth = 0; | |
| style.height = 0; | |
| style.minHeight = 0; | |
| } else if (collapse === Collapse.SPLIT) { | |
| style.flexGrow = 1; | |
| } else { | |
| style.flexGrow = 1; | |
| } | |
| return style; | |
| }; | |
| const onCollapse = () => { | |
| if (collapse === Collapse.SPLIT) { | |
| setCollapse(Collapse.COLLAPSED); | |
| } else { | |
| setCollapse(Collapse.SPLIT); | |
| } | |
| }; | |
| const onExpand = () => { | |
| if (collapse === Collapse.SPLIT) { | |
| setCollapse(Collapse.FILLED); | |
| } else { | |
| setCollapse(Collapse.SPLIT); | |
| } | |
| }; | |
| return ( | |
| <div className={twMerge("flex", !isHorizontal && "flex-col", className)}> | |
| <div | |
| ref={firstRef} | |
| className={twMerge(firstClassName, "transition-all ease-soft-spring")} | |
| style={getStyleForFirst()} | |
| > | |
| {firstChild} | |
| </div> | |
| <div | |
| className={`${isHorizontal ? "cursor-ew-resize w-3 flex-col" : "cursor-ns-resize h-3 flex-row"} shrink-0 flex justify-center items-center`} | |
| onMouseDown={collapse === Collapse.SPLIT ? onMouseDown : undefined} | |
| > | |
| <IconButton | |
| icon={isHorizontal ? <VscChevronLeft /> : <VscChevronUp />} | |
| ariaLabel="Collapse" | |
| onClick={onCollapse} | |
| /> | |
| <IconButton | |
| icon={isHorizontal ? <VscChevronRight /> : <VscChevronDown />} | |
| ariaLabel="Expand" | |
| onClick={onExpand} | |
| /> | |
| </div> | |
| <div | |
| ref={secondRef} | |
| className={twMerge(secondClassName, "transition-all ease-soft-spring")} | |
| style={getStyleForSecond()} | |
| > | |
| {secondChild} | |
| </div> | |
| </div> | |
| ); | |
| } | |