From f645fdbce71834082c88132be36057886fe4798d Mon Sep 17 00:00:00 2001 From: Guanghui Li Date: Tue, 2 Dec 2025 00:54:37 -0600 Subject: [PATCH] feat: support conditional rendering --- .changeset/lucky-rooms-dream.md | 5 + apps/docs/src/App.tsx | 2 + apps/docs/src/pages/SandboxPage.tsx | 44 +++++++ apps/docs/src/pages/index.ts | 1 + package.json | 2 +- .../src/components/FlexyPanel/FlexyPanel.tsx | 6 +- .../FlexyPanelGroup/FlexyPanelGroup.tsx | 13 +-- .../FlexyPanelHandle/FlexyPanelHandle.tsx | 108 +++++++++--------- .../src/contexts/FlexyPanelContext.ts | 6 - .../src/hooks/usePanelDrag.ts | 39 ++++--- .../src/utils/findAdjacentPanels.ts | 36 ++++++ .../react-flexy-panels/src/utils/index.ts | 3 +- pnpm-lock.yaml | 10 +- 13 files changed, 176 insertions(+), 99 deletions(-) create mode 100644 .changeset/lucky-rooms-dream.md create mode 100644 apps/docs/src/pages/SandboxPage.tsx create mode 100644 packages/react-flexy-panels/src/utils/findAdjacentPanels.ts diff --git a/.changeset/lucky-rooms-dream.md b/.changeset/lucky-rooms-dream.md new file mode 100644 index 0000000..c3b3b96 --- /dev/null +++ b/.changeset/lucky-rooms-dream.md @@ -0,0 +1,5 @@ +--- +"react-flexy-panels": minor +--- + +support conditional rendering diff --git a/apps/docs/src/App.tsx b/apps/docs/src/App.tsx index c972641..fcf3cfb 100644 --- a/apps/docs/src/App.tsx +++ b/apps/docs/src/App.tsx @@ -6,6 +6,7 @@ import { ExamplesPage, StylingGuidePage, HomePage, + SandboxPage, } from "./pages"; const App = () => { @@ -17,6 +18,7 @@ const App = () => { } /> } /> } /> + } /> ); diff --git a/apps/docs/src/pages/SandboxPage.tsx b/apps/docs/src/pages/SandboxPage.tsx new file mode 100644 index 0000000..e19cc03 --- /dev/null +++ b/apps/docs/src/pages/SandboxPage.tsx @@ -0,0 +1,44 @@ +import { FlexyPanel, FlexyPanelGroup } from "react-flexy-panels"; +import { FlexyHandle } from "../components"; +import { Button } from "@rlx-widgets/button"; +import { useState } from "react"; + +export const SandboxPage = () => { + const [panel2Open, setPanel2Open] = useState(true); + return ( +
+
+ + +
+ Panel 1 +
+
+ {panel2Open && ( + <> + + +
+ Panel 2 +
+
+ + )} + + +
+ Panel 3 +
+
+
+
+
+ +
+
+ ); +}; + +export default SandboxPage; diff --git a/apps/docs/src/pages/index.ts b/apps/docs/src/pages/index.ts index caf71fd..a69ef8a 100644 --- a/apps/docs/src/pages/index.ts +++ b/apps/docs/src/pages/index.ts @@ -3,3 +3,4 @@ export * from "./GettingStartedPage"; export * from "./ApiReferencePage"; export * from "./ExamplesPage"; export * from "./StylingGuidePage"; +export * from "./SandboxPage"; \ No newline at end of file diff --git a/package.json b/package.json index f882183..cb29c8b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@nx/react": "22.1.1", "@nx/vite": "22.1.1", "@nx/web": "22.1.1", - "@rlx-ui/mcp": "^0.0.6", + "@rlx-ui/mcp": "^0.0.12", "@swc-node/register": "~1.9.1", "@swc/cli": "~0.6.0", "@swc/core": "~1.5.7", diff --git a/packages/react-flexy-panels/src/components/FlexyPanel/FlexyPanel.tsx b/packages/react-flexy-panels/src/components/FlexyPanel/FlexyPanel.tsx index 24e3f41..3056af6 100644 --- a/packages/react-flexy-panels/src/components/FlexyPanel/FlexyPanel.tsx +++ b/packages/react-flexy-panels/src/components/FlexyPanel/FlexyPanel.tsx @@ -13,7 +13,6 @@ import { attachSetSizeFunction } from "../../utils/attachSetSizeFunction"; export const FlexyPanel = forwardRef( ({ children, defaultSize, defaultSizeUnit = "%", style, ...props }, ref) => { - const { addPanelRef } = useFlexyPanelsContext(); const size: CSSProperties = defaultSize === "auto" ? { flex: "auto" } @@ -27,9 +26,8 @@ export const FlexyPanel = forwardRef( const setRef = useCallback( (node: HTMLDivElement | null) => { - // Call addPanelRef to register the element + // Attach setSize function to panel element if (node) { - addPanelRef(node); attachSetSizeFunction(node); } // Forward the ref if provided @@ -40,7 +38,7 @@ export const FlexyPanel = forwardRef( currentRef.current = node as FlexyPanelRef; } }, - [addPanelRef] + [] ); return ( diff --git a/packages/react-flexy-panels/src/components/FlexyPanelGroup/FlexyPanelGroup.tsx b/packages/react-flexy-panels/src/components/FlexyPanelGroup/FlexyPanelGroup.tsx index eee44f6..79308c7 100644 --- a/packages/react-flexy-panels/src/components/FlexyPanelGroup/FlexyPanelGroup.tsx +++ b/packages/react-flexy-panels/src/components/FlexyPanelGroup/FlexyPanelGroup.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useMemo, useState } from "react"; +import { useMemo } from "react"; import { FlexyPanelsContext } from "../../contexts"; import { FlexyPanelGroupProps } from "./types"; @@ -10,16 +10,7 @@ export const FlexyPanelGroup = ({ style, ...props }: FlexyPanelGroupProps) => { - const [panelRefs, setPanelRefs] = useState>([]); - - const addPanelRef = useCallback((ref: HTMLDivElement) => { - setPanelRefs((prev) => [...prev, ref]); - }, []); - - const value = useMemo( - () => ({ direction, panelRefs, addPanelRef }), - [direction, panelRefs, addPanelRef] - ); + const value = useMemo(() => ({ direction }), [direction]); return (
( - ({ onMouseDown, onTouchStart, ...props }, ref) => { - const id = useId(); - const { direction, panelRefs, addPanelRef } = useFlexyPanelsContext(); - const { handleMouseDown: handleDragMouseDown, handleTouchStart: handleDragTouchStart } = usePanelDrag({ - direction, - panelRefs, - handleId: id, - }); +export const FlexyPanelHandle = forwardRef< + HTMLDivElement, + FlexyPanelHandleProps +>(({ onMouseDown, onTouchStart, ...props }, ref) => { + const id = useId(); + const { direction } = useFlexyPanelsContext(); + const { + handleMouseDown: handleDragMouseDown, + handleTouchStart: handleDragTouchStart, + } = usePanelDrag({ + direction, + handleId: id, + }); - const handleMouseDown = useCallback( - (e: React.MouseEvent) => { - handleDragMouseDown(e); - onMouseDown?.(e); - }, - [handleDragMouseDown, onMouseDown] - ); + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + handleDragMouseDown(e); + onMouseDown?.(e); + }, + [handleDragMouseDown, onMouseDown] + ); - const handleTouchStartEvent = useCallback( - (e: React.TouchEvent) => { - handleDragTouchStart(e); - onTouchStart?.(e); - }, - [handleDragTouchStart, onTouchStart] - ); + const handleTouchStartEvent = useCallback( + (e: React.TouchEvent) => { + handleDragTouchStart(e); + onTouchStart?.(e); + }, + [handleDragTouchStart, onTouchStart] + ); - // Store the forwarded ref in a ref to avoid dependency issues - const forwardedRef = useRef(ref); - useEffect(() => { - forwardedRef.current = ref; - }, [ref]); + // Store the forwarded ref in a ref to avoid dependency issues + const forwardedRef = useRef(ref); + useEffect(() => { + forwardedRef.current = ref; + }, [ref]); - const setRef = useCallback( - (node: HTMLDivElement | null) => { - // Call addPanelRef to register the element - if (node) { - addPanelRef(node); - } - // Forward the ref if provided - const currentRef = forwardedRef.current; - if (typeof currentRef === "function") { - currentRef(node); - } else if (currentRef) { - currentRef.current = node; - } - }, - [addPanelRef] - ); + const setRef = useCallback((node: HTMLDivElement | null) => { + // Forward the ref if provided + const currentRef = forwardedRef.current; + if (typeof currentRef === "function") { + currentRef(node); + } else if (currentRef) { + currentRef.current = node; + } + }, []); - return ( -
- ); - } -); + return ( +
+ ); +}); FlexyPanelHandle.displayName = "FlexyPanelHandle"; diff --git a/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts b/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts index 8e0d100..d7c89e0 100644 --- a/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts +++ b/packages/react-flexy-panels/src/contexts/FlexyPanelContext.ts @@ -3,12 +3,6 @@ import { Direction } from "../types"; export const FlexyPanelsContext = createContext<{ direction: Direction; - panelRefs: Array; - addPanelRef: (ref: HTMLDivElement) => void; }>({ direction: "horizontal", - panelRefs: [], - addPanelRef: () => { - return; - }, }); diff --git a/packages/react-flexy-panels/src/hooks/usePanelDrag.ts b/packages/react-flexy-panels/src/hooks/usePanelDrag.ts index e948d35..fc578d2 100644 --- a/packages/react-flexy-panels/src/hooks/usePanelDrag.ts +++ b/packages/react-flexy-panels/src/hooks/usePanelDrag.ts @@ -1,36 +1,37 @@ import { Direction } from "../types"; -import { updatePanelSizes } from "../utils"; +import { findAdjacentPanels, updatePanelSizes } from "../utils"; import { useCallback, useEffect, useRef, useState } from "react"; type UsePanelDragOptions = { direction: Direction; - panelRefs: Array; handleId: string; }; /** * Custom hook to handle panel dragging logic */ -export const usePanelDrag = ({ - direction, - panelRefs, - handleId, -}: UsePanelDragOptions) => { +export const usePanelDrag = ({ direction, handleId }: UsePanelDragOptions) => { const [isDragging, setIsDragging] = useState(false); const dragStartRef = useRef(0); + const handleElementRef = useRef(null); const updateDragPosition = useCallback( (clientX: number, clientY: number) => { const dragCurrent = direction === "horizontal" ? clientX : clientY; const dragDelta = dragCurrent - dragStartRef.current; - const handleIndex = panelRefs.findIndex((ref) => ref.id === handleId); - if (handleIndex === -1) { + // Find handle element if not cached + if (!handleElementRef.current) { + handleElementRef.current = document.getElementById(handleId); + } + + if (!handleElementRef.current) { return; } - const panel1 = panelRefs[handleIndex - 1]; - const panel2 = panelRefs[handleIndex + 1]; + // Find adjacent panels using DOM traversal + const { panel1, panel2 } = findAdjacentPanels(handleElementRef.current); + if (!panel1 || !panel2) { return; } @@ -43,7 +44,7 @@ export const usePanelDrag = ({ }); dragStartRef.current = dragCurrent; }, - [direction, handleId, panelRefs] + [direction, handleId] ); const onDrag = useCallback( @@ -71,6 +72,8 @@ export const usePanelDrag = ({ const handleMouseDown = useCallback( (e: React.MouseEvent) => { + // Cache handle element reference on drag start + handleElementRef.current = e.currentTarget; setIsDragging(true); dragStartRef.current = direction === "horizontal" ? e.clientX : e.clientY; }, @@ -80,9 +83,12 @@ export const usePanelDrag = ({ const handleTouchStart = useCallback( (e: React.TouchEvent) => { if (e.touches.length > 0) { + // Cache handle element reference on drag start + handleElementRef.current = e.currentTarget; setIsDragging(true); const touch = e.touches[0]; - dragStartRef.current = direction === "horizontal" ? touch.clientX : touch.clientY; + dragStartRef.current = + direction === "horizontal" ? touch.clientX : touch.clientY; } }, [direction] @@ -102,7 +108,7 @@ export const usePanelDrag = ({ const originalUserSelect = document.body.style.userSelect; // Disable text selection during drag document.body.style.userSelect = "none"; - + document.addEventListener("mousemove", onDrag); document.addEventListener("mouseup", handleMouseUp); document.addEventListener("touchmove", onTouchDrag, { passive: false }); @@ -112,7 +118,10 @@ export const usePanelDrag = ({ return () => { // Restore original user-select value document.body.style.userSelect = originalUserSelect; - + + // Clear handle element reference on drag end + handleElementRef.current = null; + document.removeEventListener("mousemove", onDrag); document.removeEventListener("mouseup", handleMouseUp); document.removeEventListener("touchmove", onTouchDrag); diff --git a/packages/react-flexy-panels/src/utils/findAdjacentPanels.ts b/packages/react-flexy-panels/src/utils/findAdjacentPanels.ts new file mode 100644 index 0000000..0a74d7f --- /dev/null +++ b/packages/react-flexy-panels/src/utils/findAdjacentPanels.ts @@ -0,0 +1,36 @@ +/** + * Finds adjacent panels to a handle element using DOM traversal + */ +export function findAdjacentPanels(handleElement: HTMLElement): { + panel1: HTMLDivElement | null; + panel2: HTMLDivElement | null; +} { + // Helper to check if an element is a panel (has data-unit attribute) + const isPanel = (el: Element | null): el is HTMLDivElement => { + return el instanceof HTMLDivElement && el.hasAttribute("data-unit"); + }; + + // Find previous sibling panel + let panel1: HTMLDivElement | null = null; + let current: Element | null = handleElement.previousElementSibling; + while (current && !panel1) { + if (isPanel(current)) { + panel1 = current; + } else { + current = current.previousElementSibling; + } + } + + // Find next sibling panel + let panel2: HTMLDivElement | null = null; + current = handleElement.nextElementSibling; + while (current && !panel2) { + if (isPanel(current)) { + panel2 = current; + } else { + current = current.nextElementSibling; + } + } + + return { panel1, panel2 }; +} diff --git a/packages/react-flexy-panels/src/utils/index.ts b/packages/react-flexy-panels/src/utils/index.ts index 8a218ad..1561491 100644 --- a/packages/react-flexy-panels/src/utils/index.ts +++ b/packages/react-flexy-panels/src/utils/index.ts @@ -1,2 +1,3 @@ +export * from "./attachSetSizeFunction"; +export * from "./findAdjacentPanels"; export * from "./panelSize"; - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3741dc4..90a750d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,8 +121,8 @@ importers: specifier: 22.1.1 version: 22.1.1(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.1.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(verdaccio@6.2.1(encoding@0.1.13)(typanion@3.14.0)) '@rlx-ui/mcp': - specifier: ^0.0.6 - version: 0.0.6 + specifier: ^0.0.12 + version: 0.0.12 '@swc-node/register': specifier: ~1.9.1 version: 1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.9.3) @@ -2153,8 +2153,8 @@ packages: react: '>=18.0.0' react-dom: '>=18.0.0' - '@rlx-ui/mcp@0.0.6': - resolution: {integrity: sha512-VnjH49uMcTWEIy1DDVqE7SP1hlO3bEXecgQprV0ZSaZ8IudRFgsPjBjE8eimp3Vvg87cQtD2eBhdpZY94mHBog==} + '@rlx-ui/mcp@0.0.12': + resolution: {integrity: sha512-ppIarxt8tlmIS6pX9z10GOIzBgiNCspGrd/XrYB5MFY8Risn20WUBqLGKu54x+wqKBVq+ysMHWvHfqPtquLaAQ==} hasBin: true '@rlx-widgets/base@0.0.3': @@ -10902,7 +10902,7 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - '@rlx-ui/mcp@0.0.6': + '@rlx-ui/mcp@0.0.12': dependencies: '@modelcontextprotocol/sdk': 1.22.0 zod: 3.25.76