diff --git a/CHANGELOG.md b/CHANGELOG.md index 81cae77..e94be23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to `@neynar/ui` will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.2] - 2025-01-20 + +### Fixed +- **ResizablePanel**: Fixed "Panel constraints not found" error when using `collapsed` prop on initial render. Now uses proper timing with retry logic to ensure panel is registered before collapse/expand. + +--- + ## [1.2.1] - 2025-01-20 ### Added diff --git a/package.json b/package.json index 27670a2..4d9853f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@neynar/ui", - "version": "1.2.1", + "version": "1.2.2", "license": "MIT", "author": "Neynar Inc.", "description": "AI-first React component library for coding agents. LLM-optimized docs, sensible defaults, zero config. Built on shadcn patterns, Base UI, and Tailwind CSS v4.", diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx index aed49cc..77117d8 100644 --- a/src/components/ui/resizable.tsx +++ b/src/components/ui/resizable.tsx @@ -77,7 +77,7 @@ function ResizablePanel({ const elementRef = React.useRef(null); const animatedRef = React.useRef(animated); const durationRef = React.useRef(duration); - const isFirstRender = React.useRef(true); + const prevCollapsedRef = React.useRef(undefined); // Keep the refs in sync with prop changes React.useEffect(() => { @@ -115,27 +115,36 @@ function ResizablePanel({ React.useEffect(() => { if (collapsed === undefined) return; - // On first render, defer to allow panel registration - if (isFirstRender.current) { - isFirstRender.current = false; - // Use requestAnimationFrame to ensure panel is registered - requestAnimationFrame(() => { + const isFirstSync = prevCollapsedRef.current === undefined; + const hasChanged = collapsed !== prevCollapsedRef.current; + prevCollapsedRef.current = collapsed; + + if (!hasChanged && !isFirstSync) return; + + // Helper to safely call collapse/expand with retry logic + const syncState = () => { + try { + const isCurrentlyCollapsed = panelRef.current?.isCollapsed() ?? false; + if (collapsed === isCurrentlyCollapsed) return; + if (collapsed) { - panelRef.current?.collapse(); + if (isFirstSync) { + // First sync: no animation + panelRef.current?.collapse(); + } else { + withTransition(() => panelRef.current?.collapse()); + } + } else { + withTransition(() => panelRef.current?.expand()); } - }); - return; - } - - // After first render, check current state before acting - const isCurrentlyCollapsed = panelRef.current?.isCollapsed() ?? false; - if (collapsed === isCurrentlyCollapsed) return; - - if (collapsed) { - withTransition(() => panelRef.current?.collapse()); - } else { - withTransition(() => panelRef.current?.expand()); - } + } catch { + // Panel not ready yet, retry after a tick + setTimeout(syncState, 0); + } + }; + + // Use setTimeout(0) to ensure we're after the panel group's layout effects + setTimeout(syncState, 0); }, [collapsed, withTransition]); return (