Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 2, 2025

The Leva panel height doesn't update when fields are conditionally rendered using the render option. The panel remains at its original size, causing overflow or clipped content.

// Panel height doesn't update when show2 toggles the folder visibility
const values = useControls({
  show2: { value: false, label: 'Show folder' },
  folder: folder(
    {
      number: 1,
      string: { value: 'text', render: (get) => get('folder.number') >= 1 },
    },
    { render: (get) => get('show2') }
  ),
})

Changes

  • Added ResizeObserver to useToggle hook to detect content size changes while the panel is expanded
  • Animate height transitions by setting explicit heights before/after content changes, then removing them post-transition
  • Prevent race conditions by canceling pending animations and removing orphaned event listeners before starting new transitions
  • Capture refs at effect creation to ensure proper cleanup when component unmounts
  • Added Storybook story demonstrating the fix with multiple conditional sections and nested rendering

Implementation

The ResizeObserver compares rendered heights (not style properties) and triggers smooth animations only when differences exceed 1px. Uses requestAnimationFrame to ensure height changes are properly batched for transitions.

Storybook Story

Added a comprehensive story "Conditional Rendering - Panel Height Fix" that demonstrates:

  • Multiple conditional sections with checkboxes to toggle visibility
  • Nested conditional rendering within folders
  • Smooth panel height animations when content changes
  • Real-world use case with various input types

Location: Misc / Input options → Conditional Rendering - Panel Height Fix

Screenshots

Before: Panel doesn't resize when folder appears
Before

After: Panel smoothly animates to fit new content
After

Story - Initial state with basic fields:
Story Initial

Story - Expanded with advanced settings:
Story Advanced

Story - Fully expanded with all sections:
Story Fully Expanded

[!WARNING]

Original prompt

This section details on the original issue you should resolve

<filter_complete></filter_complete>

<issue_title>Panel height doesn't change when adding new fields via conditional rendering</issue_title>
<issue_description>See #540 </issue_description>

Comments on the Issue (you are @copilot in this section)

Original prompt

This section details on the original issue you should resolve

<issue_title>Panel height doesn't change when adding new fields via conditional rendering</issue_title>
<issue_description>See #540 </issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Summary by CodeRabbit

  • New Features

    • Panel height now smoothly animates and automatically adjusts to content size changes when toggled.
  • Documentation

    • Added example story demonstrating conditional rendering with dynamic panel height handling.

@changeset-bot
Copy link

changeset-bot bot commented Nov 2, 2025

⚠️ No Changeset found

Latest commit: 108592b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Nov 2, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
leva Ready Ready Preview Comment Nov 8, 2025 3:25pm

@codesandbox-ci
Copy link

codesandbox-ci bot commented Nov 2, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 108592b:

Sandbox Source
leva-minimal Configuration
leva-busy Configuration
leva-scroll Configuration
leva-advanced-panels Configuration
leva-ui Configuration
leva-theme Configuration
leva-transient Configuration
leva-plugin-plot Configuration
leva-plugin-bezier Configuration
leva-plugin-spring Configuration
leva-plugin-dates Configuration
leva-custom-plugin Configuration

Copilot AI changed the title [WIP] Fix panel height not changing with new fields Fix panel height not updating when conditionally rendering fields Nov 2, 2025
Copilot AI requested a review from gsimone November 2, 2025 23:34
Copilot finished work on behalf of gsimone November 2, 2025 23:34
@gsimone
Copy link
Member

gsimone commented Nov 3, 2025

@copilot write a story for this change

Copy link
Contributor Author

Copilot AI commented Nov 3, 2025

@copilot write a story for this change

Added a comprehensive Storybook story "Conditional Rendering - Panel Height Fix" in commit bda7da4.

The story demonstrates:

  • Multiple conditional sections with checkboxes to toggle visibility
  • Nested conditional rendering within folders
  • Smooth panel height animations when content changes
  • Real-world use case with various input types (text, numbers, sliders, selects, booleans)

You can find it at: Misc / Input options → Conditional Rendering - Panel Height Fix

Screenshots showing the panel expanding as sections are toggled:

Initial state:
Initial

With Advanced Settings:
Advanced

Fully expanded:
Expanded

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • storybook.js.org
    • Triggering command: node /home/REDACTED/work/leva/leva/node_modules/.bin/../storybook/dist/bin/dispatcher.js dev -p 6006 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of gsimone November 3, 2025 12:30
@gsimone gsimone changed the title Fix panel height not updating when conditionally rendering fields fix: panel height not updating when conditionally rendering fields Nov 8, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds automatic panel height adjustment for Leva's collapsible folders when their content size changes dynamically. The main enhancement allows folders to smoothly animate their height when nested content is conditionally shown or hidden.

  • Implements ResizeObserver in useToggle hook to detect content size changes while expanded
  • Adds smooth height transitions when content changes using requestAnimationFrame
  • Includes a comprehensive Storybook example demonstrating conditional rendering with nested folders

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/leva/src/hooks/useToggle.ts Adds ResizeObserver-based dynamic height adjustment with transition animations for expanded panels
packages/leva/stories/input-options.stories.tsx Adds Storybook story demonstrating the panel height auto-adjustment feature with conditional rendering examples

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +130 to +189
useEffect(() => {
if (!toggled || !contentRef.current || !wrapperRef.current) return

const wrapper = wrapperRef.current
let rafId: number | null = null
let currentTransitionHandler: (() => void) | null = null

const resizeObserver = new ResizeObserver(() => {
const content = contentRef.current
if (!content) return

// Cancel any pending animation
if (rafId !== null) {
cancelAnimationFrame(rafId)
rafId = null
}

// Remove any existing transition handler
if (currentTransitionHandler) {
wrapper.removeEventListener('transitionend', currentTransitionHandler)
currentTransitionHandler = null
}

// Get the current and target heights
const currentHeight = wrapper.getBoundingClientRect().height
const targetHeight = content.getBoundingClientRect().height

// Only update if there's a meaningful difference
if (Math.abs(currentHeight - targetHeight) > 1) {
// Set explicit height to enable transition
wrapper.style.height = currentHeight + 'px'

// Use requestAnimationFrame to ensure the height is set before changing it
rafId = requestAnimationFrame(() => {
rafId = null
wrapper.style.height = targetHeight + 'px'

// Remove fixed height after transition completes
const handleTransitionEnd = () => {
wrapper.style.removeProperty('height')
currentTransitionHandler = null
}
currentTransitionHandler = handleTransitionEnd
wrapper.addEventListener('transitionend', handleTransitionEnd, { once: true })
})
}
})

resizeObserver.observe(contentRef.current)

return () => {
resizeObserver.disconnect()
if (rafId !== null) {
cancelAnimationFrame(rafId)
}
if (currentTransitionHandler) {
wrapper.removeEventListener('transitionend', currentTransitionHandler)
}
}
}, [toggled])
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential race condition when toggling from closed to open. Both the toggle animation effect (lines 96-127) and this ResizeObserver effect will be active simultaneously. During the expansion animation, the ResizeObserver may fire and attempt to adjust the height while the toggle animation is still in progress, causing conflicting height manipulations.

Consider one of these approaches:

  1. Add a delay before observing (e.g., using a timeout matching the transition duration)
  2. Track animation state with a ref and skip ResizeObserver adjustments during toggle animations
  3. Use a flag to temporarily disable ResizeObserver callbacks during toggle transitions

Example:

const isAnimating = useRef(false)

// In first useEffect, set isAnimating.current = true and clear in transitionend
// In ResizeObserver callback, check if (!isAnimating.current) before adjusting

Copilot uses AI. Check for mistakes.
Comment on lines +168 to +174
const handleTransitionEnd = () => {
wrapper.style.removeProperty('height')
currentTransitionHandler = null
}
currentTransitionHandler = handleTransitionEnd
wrapper.addEventListener('transitionend', handleTransitionEnd, { once: true })
})
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transitionend event may not fire in certain edge cases (e.g., if the transition is interrupted, the element is hidden, or the browser skips very small transitions), which would leave the explicit height style set on the wrapper and prevent it from properly auto-sizing.

Consider adding a fallback timeout to ensure the height property is eventually removed:

const handleTransitionEnd = () => {
  wrapper.style.removeProperty('height')
  currentTransitionHandler = null
  if (timeoutId !== null) {
    clearTimeout(timeoutId)
    timeoutId = null
  }
}
currentTransitionHandler = handleTransitionEnd
wrapper.addEventListener('transitionend', handleTransitionEnd, { once: true })

// Fallback timeout (transition is 300ms)
const timeoutId = setTimeout(handleTransitionEnd, 400)
Suggested change
const handleTransitionEnd = () => {
wrapper.style.removeProperty('height')
currentTransitionHandler = null
}
currentTransitionHandler = handleTransitionEnd
wrapper.addEventListener('transitionend', handleTransitionEnd, { once: true })
})
let timeoutId: number | null = null
const handleTransitionEnd = () => {
wrapper.style.removeProperty('height')
currentTransitionHandler = null
if (timeoutId !== null) {
clearTimeout(timeoutId)
timeoutId = null
}
}
currentTransitionHandler = handleTransitionEnd
wrapper.addEventListener('transitionend', handleTransitionEnd, { once: true })
// Fallback timeout (transition is 300ms)
timeoutId = window.setTimeout(handleTransitionEnd, 400)

Copilot uses AI. Check for mistakes.
@gsimone
Copy link
Member

gsimone commented Nov 8, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 8, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Nov 8, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The useToggle hook is enhanced with a ResizeObserver to detect content size changes and smoothly animate panel height adjustments when controls are conditionally rendered. A Storybook story is added to demonstrate this panel height auto-adjustment behavior.

Changes

Cohort / File(s) Change Summary
Panel Height Animation Enhancement
packages/leva/src/hooks/useToggle.ts
Adds ResizeObserver effect to detect content size changes; computes and animates height transitions with requestAnimationFrame; includes cleanup for observers, animation frames, and transition handlers; skips animations when height difference is below 1px threshold
Conditional Rendering Story
packages/leva/stories/input-options.stories.tsx
Adds new Storybook story component ConditionalRenderingPanelHeight with nested conditional groups (showBasicFields, showAdvanced, showDebug) to demonstrate panel height auto-adjustment behavior

Sequence Diagram

sequenceDiagram
    participant User
    participant Panel
    participant ResizeObserver as ResizeObserver
    participant AnimationFrame as requestAnimationFrame
    participant DOM

    User->>Panel: Toggle conditional field visibility
    Panel->>ResizeObserver: Detect content size change
    ResizeObserver->>Panel: Content dimensions changed
    Panel->>AnimationFrame: Queue height animation
    AnimationFrame->>DOM: Set wrapper to current height
    AnimationFrame->>DOM: Animate to target height
    DOM->>DOM: Transition height (CSS)
    DOM->>Panel: transitionend event
    Panel->>DOM: Remove explicit height property
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20-25 minutes

  • useToggle hook modifications: Logic density is moderate with ResizeObserver setup, animation frame sequencing, and transition event handling. Requires careful review for proper cleanup, memory leak prevention, and edge cases around rapid toggling or nested conditional renders.
  • Story component: Straightforward demonstration with minimal logic.
  • Key areas for attention:
    • ResizeObserver cleanup on effect unmount or dependency changes
    • Proper handling of concurrent animations (cancellation of pending frames)
    • Height calculation accuracy and negligible difference threshold (1px)
    • Transition handler cleanup to avoid memory leaks

Poem

A panel that dances with graceful ease,
As fields appear and disappear with ease,
The ResizeObserver watches content grow,
Then animates the height with a gentle flow,
No jarring jumps—just smooth transitions so.
🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses issue #578 (panel height with conditional rendering) but does not implement the coding requirements from issue #39 (store sanitization logic). Implement the store sanitization responsibility outlined in issue #39 to centralize value validation/sanitization as specified in the linked requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: panel height not updating during conditional field rendering, which aligns with the primary change in the PR.
Out of Scope Changes check ✅ Passed The ResizeObserver implementation and Storybook story are directly aligned with fixing issue #578; no out-of-scope changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch copilot/fix-panel-height-issue

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f70a62f and 108592b.

📒 Files selected for processing (2)
  • packages/leva/src/hooks/useToggle.ts (1 hunks)
  • packages/leva/stories/input-options.stories.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: CI
packages/leva/src/hooks/useToggle.ts

[error] 1-1: Prettier formatting check failed. Run 'prettier --write' to fix code style issues in this file.

packages/leva/stories/input-options.stories.tsx

[error] 1-1: Prettier formatting check failed. Run 'prettier --write' to fix code style issues in this file.

🔇 Additional comments (2)
packages/leva/src/hooks/useToggle.ts (1)

129-189: Verify race condition between toggle and resize animations.

As previously noted by Copilot, when toggling from closed to open, both the toggle animation effect (lines 96-127) and this ResizeObserver effect will run simultaneously. During expansion, the ResizeObserver may fire and manipulate height while the toggle animation is in progress, causing conflicting height changes.

The concern remains valid: both effects can set explicit heights on the same wrapper element concurrently, and without coordination they may override each other's transitions.

Consider adding an animation state flag as previously suggested:

const isAnimating = useRef(false)

// In toggle effect (line 96), set isAnimating.current = true
// Clear it in the transitionend handler (line 106)
// In ResizeObserver callback (line 137), check:
if (isAnimating.current) return

Or alternatively, add a delay before observing that matches the transition duration:

const timeoutId = window.setTimeout(() => {
  resizeObserver.observe(contentRef.current!)
}, 300) // Match transition duration

return () => {
  clearTimeout(timeoutId)
  resizeObserver.disconnect()
  // ... rest of cleanup
}

Based on learnings

packages/leva/stories/input-options.stories.tsx (1)

65-108: LGTM! Excellent demonstration of the panel height fix.

The story effectively demonstrates conditional rendering with multiple nested sections and provides clear documentation for users. The combination of basic fields, folders with nested conditions, and the JSON preview creates a comprehensive test case for the ResizeObserver-based height adjustment.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panel height doesn't change when adding new fields via conditional rendering

2 participants