Skip to content

Conversation

@kotAPI
Copy link
Collaborator

@kotAPI kotAPI commented Sep 5, 2025

Summary

  • refactor Splitter.Root, Panel, Handle and wrapper to use React.forwardRef
  • add tests for ref forwarding

Testing

  • npm test
  • npm run build:rollup (fails: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory)

https://chatgpt.com/codex/tasks/task_e_68ba8070cc4c83318da638a5fffd6722

Summary by CodeRabbit

  • New Features

    • Splitter now renders a real container with subcomponents (Root, Panel, Handle) for easier composition.
    • Supports ref forwarding across Root, Panel, and Handle for direct DOM access.
    • Accepts standard div attributes (e.g., className, style, aria-*) for broader customization.
    • Improved debugging via clearer component display names.
  • Refactor

    • Unified component API to standard HTML div props and forwardRef patterns for consistency.
  • Tests

    • Added tests validating ref forwarding to all Splitter elements.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 5, 2025

Walkthrough

Refactors Splitter components to use React.forwardRef, broaden props to standard div attributes, and expose static subcomponents on the main Splitter export. Implements ref forwarding for Root, Panel, and Handle, adds displayName assignments, merges styles/props, and adds a test verifying refs are forwarded to DOM elements.

Changes

Cohort / File(s) Summary of changes
Splitter core export
src/components/ui/Splitter/Splitter.tsx
Converts Splitter to a forwardRef div component, updates SplitterProps to ComponentPropsWithoutRef<'div'>, assigns displayName, and attaches static subcomponents: Root, Panel, Handle.
Splitter fragments (forwardRef + div props)
src/components/ui/Splitter/fragments/SplitterRoot.tsx, src/components/ui/Splitter/fragments/SplitterPanel.tsx, src/components/ui/Splitter/fragments/SplitterHandle.tsx
Each component refactored to React.forwardRef with div ref, props extended to ComponentPropsWithoutRef<'div'>, style and rest props spread to root div, displayName set. Root merges internal and forwarded refs; Panel computes merged style; Handle cleans unused code.
Tests
src/components/ui/Splitter/tests/Splitter.test.tsx
Adds a test to assert refs for Root, Panel, and Handle resolve to HTMLDivElement instances.

Sequence Diagram(s)

sequenceDiagram
  actor App
  participant Splitter as Splitter
  participant Root as Splitter.Root (div)
  participant Panel as Splitter.Panel (div)
  participant Handle as Splitter.Handle (div)

  App->>Splitter: Render with children
  Splitter->>Root: Render, forward ref
  Splitter->>Panel: Render, forward ref
  Splitter->>Handle: Render, forward ref
  Root-->>App: rootRef.current = HTMLDivElement
  Panel-->>App: panelRef.current = HTMLDivElement
  Handle-->>App: handleRef.current = HTMLDivElement
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit taps the DOM with glee,
“Refs, arise! Now point to me.”
Root, Panel, Handle—click by click,
Forwarded paths, precise and slick.
With tidy names and props that flow,
Our Splitter’s ready—on we go! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kotapi/refactor-splitter-component-with-forwardref-o555vb

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
src/components/ui/Splitter/tests/Splitter.test.tsx (2)

212-235: Good ref-forwarding coverage; tighten assertions to match actual nodes.

Consider also asserting the refs point to the exact rendered elements (not just instances). This makes regressions easier to catch.

Apply this diff to extend the test:

         expect(rootRef.current).toBeInstanceOf(HTMLDivElement);
         expect(panelRef.current).toBeInstanceOf(HTMLDivElement);
         expect(handleRef.current).toBeInstanceOf(HTMLDivElement);
+        // Stronger equality checks
+        const panel0Parent = screen.getByTestId('panel-0').parentElement as HTMLDivElement;
+        expect(panelRef.current).toBe(panel0Parent);
+        const handle = screen.getByRole('separator') as HTMLDivElement;
+        expect(handleRef.current).toBe(handle);

1-4: Rollup OOM during build: practical mitigations.

  • Externalize peer deps in Rollup (react, react-dom, clsx) to shrink bundle graph.
  • Disable or limit sourcemaps for production builds.
  • Switch to @rollup/plugin-typescript with transpileOnly or use esbuild plugin for ts/tsx.
  • Run build with NODE_OPTIONS="--max-old-space-size=4096".
  • Ensure tree-shaking enabled and avoid bundling tests/stories via include/exclude globs.
src/components/ui/Splitter/fragments/SplitterHandle.tsx (2)

16-17: Remove or surface “active” state.

isActive is computed but unused. Either remove it or expose it for styling/testing.

Apply this diff to surface as a data attribute:

-    const isActive = isDragging && activeHandleIndex === index;
+    const isActive = isDragging && activeHandleIndex === index;
@@
-            className={clsx(`${rootClass}-handle`, className)}
+            className={clsx(`${rootClass}-handle`, className)}
+            data-active={isActive || undefined}

6-9: Consider dropping unused prop from the public API.

customRootClass isn’t used. Keeping unused public props is confusing and adds maintenance overhead.

Apply this diff:

-export interface SplitterHandleProps extends React.ComponentPropsWithoutRef<'div'> {
+export interface SplitterHandleProps extends React.ComponentPropsWithoutRef<'div'> {
   index: number;
-  customRootClass?: string;
 }
src/components/ui/Splitter/fragments/SplitterPanel.tsx (1)

6-11: Public props not used here—verify or trim.

customRootClass, minSize, and maxSize aren’t referenced in this component. If Root consumes them via context, fine; otherwise consider removing to keep API lean.

Would you like me to scan the repo to confirm these props are actually used by SplitterRoot or elsewhere?

src/components/ui/Splitter/Splitter.tsx (1)

38-41: Consider removing or conditionalizing the console warning.

While the warning is helpful during development, it will appear in production builds. Consider either:

  1. Removing it entirely if direct usage is truly unsupported
  2. Wrapping it in a development-only check
  3. Throwing an error instead if this is a critical usage error
 const Splitter = React.forwardRef<React.ElementRef<'div'>, SplitterProps>((props, ref) => {
-    console.warn('Direct usage of Splitter is not supported. Please use Splitter.Root, Splitter.Panel, etc. instead.');
+    if (process.env.NODE_ENV !== 'production') {
+        console.warn('Direct usage of Splitter is not supported. Please use Splitter.Root, Splitter.Panel, etc. instead.');
+    }
     return <div ref={ref} {...props} />;
 }) as SplitterComponent;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9ce1e84 and e5b9565.

📒 Files selected for processing (5)
  • src/components/ui/Splitter/Splitter.tsx (1 hunks)
  • src/components/ui/Splitter/fragments/SplitterHandle.tsx (1 hunks)
  • src/components/ui/Splitter/fragments/SplitterPanel.tsx (1 hunks)
  • src/components/ui/Splitter/fragments/SplitterRoot.tsx (3 hunks)
  • src/components/ui/Splitter/tests/Splitter.test.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/ui/Splitter/fragments/SplitterPanel.tsx (1)
src/components/ui/Splitter/fragments/SplitterRoot.tsx (1)
  • useSplitter (17-23)
src/components/ui/Splitter/fragments/SplitterHandle.tsx (1)
src/components/ui/Splitter/fragments/SplitterRoot.tsx (1)
  • useSplitter (17-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (9)
src/components/ui/Splitter/fragments/SplitterPanel.tsx (1)

13-16: Ref-forwarding and prop spreading look good.

Signature, ref wiring, style merging, and displayName assignment are correct.

Also applies to: 29-38

src/components/ui/Splitter/Splitter.tsx (2)

28-29: LGTM! Clean type definition for forwardRef props.

The use of React.ComponentPropsWithoutRef<'div'> properly extends all standard div attributes while excluding the ref, which will be handled separately by forwardRef.


30-36: Well-structured compound component type definition.

The type correctly combines the ForwardRefExoticComponent with static members for the compound component pattern. This provides excellent TypeScript support for the Splitter.Root, Splitter.Panel, and Splitter.Handle API.

src/components/ui/Splitter/fragments/SplitterRoot.tsx (6)

7-14: Clean interface extension with ComponentPropsWithoutRef.

The interface properly extends div props, allowing all standard HTML div attributes to be passed through, which improves component flexibility and reusability.


27-41: Proper forwardRef implementation with correct type annotations.

The forwardRef setup correctly uses React.ElementRef<'div'> for the ref type and properly destructures props including the new style prop.


49-56: Robust ref merging implementation.

The mergedRef callback correctly handles both function refs and object refs, ensuring that both the internal containerRef and the forwarded ref receive the DOM node. This is essential for maintaining internal functionality while exposing the ref to parent components.


308-317: Excellent prop spreading and style merging.

The implementation correctly:

  1. Spreads the remaining props first
  2. Then applies the ref and className
  3. Properly merges the incoming style prop with the component's required styles

This order ensures that the component's essential styles are preserved while allowing customization.


325-325: Good practice: displayName for debugging.

Setting the displayName helps with debugging in React DevTools and error messages.


43-43: No changes needed—null-check guards ensure safe non‐null usage
The if (!containerRef.current) return; at line 125 prevents containerRef.current from ever being null when using the non-null assertions on lines 141–142.

'use client';
import React from 'react';
import { customClassSwitcher } from '~/core';
import { clsx } from 'clsx';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect clsx import.

clsx has a default export. Named import will break at runtime.

Apply this diff:

-import { clsx } from 'clsx';
+import clsx from 'clsx';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { clsx } from 'clsx';
import clsx from 'clsx';
🤖 Prompt for AI Agents
In src/components/ui/Splitter/fragments/SplitterHandle.tsx around line 3, the
code imports clsx using a named import which is incorrect because clsx uses a
default export; replace the named import with a default import (e.g., import
clsx from 'clsx') so runtime errors are avoided, and ensure any existing usages
of clsx in the file remain unchanged.

Comment on lines +20 to 31
{...props}
ref={forwardedRef}
className={clsx(`${rootClass}-handle`, className)}
role="separator"
aria-orientation={orientation}
aria-label={ariaLabel || `${orientation} resize handle`}
tabIndex={0}
onMouseDown={(e) => startDrag(index, e)}
onTouchStart={(e) => startDrag(index, e)}
onKeyDown={(e) => handleKeyDown(index, e)}

style={style}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Don’t overwrite consumer event handlers; compose them with internal ones.

Current {...props} order causes any passed onMouseDown/onTouchStart/onKeyDown to be silently overridden. Compose them so analytics and side-effects still fire.

Apply this diff:

->(({ index, className, 'aria-label': ariaLabel, style, ...props }, forwardedRef) => {
+>(({ index, className, 'aria-label': ariaLabel, style, onMouseDown: userOnMouseDown, onTouchStart: userOnTouchStart, onKeyDown: userOnKeyDown, ...props }, forwardedRef) => {
@@
-        <div
-            {...props}
-            ref={forwardedRef}
+        <div
+            {...props}
+            ref={forwardedRef}
             className={clsx(`${rootClass}-handle`, className)}
             role="separator"
             aria-orientation={orientation}
             aria-label={ariaLabel || `${orientation} resize handle`}
             tabIndex={0}
-            onMouseDown={(e) => startDrag(index, e)}
-            onTouchStart={(e) => startDrag(index, e)}
-            onKeyDown={(e) => handleKeyDown(index, e)}
+            onMouseDown={(e) => {
+                userOnMouseDown?.(e);
+                startDrag(index, e);
+            }}
+            onTouchStart={(e) => {
+                userOnTouchStart?.(e);
+                startDrag(index, e);
+            }}
+            onKeyDown={(e) => {
+                userOnKeyDown?.(e);
+                handleKeyDown(index, e);
+            }}
             style={style}
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{...props}
ref={forwardedRef}
className={clsx(`${rootClass}-handle`, className)}
role="separator"
aria-orientation={orientation}
aria-label={ariaLabel || `${orientation} resize handle`}
tabIndex={0}
onMouseDown={(e) => startDrag(index, e)}
onTouchStart={(e) => startDrag(index, e)}
onKeyDown={(e) => handleKeyDown(index, e)}
style={style}
/>
// File: src/components/ui/Splitter/fragments/SplitterHandle.tsx
// Lines: 20-31
(({
index,
className,
'aria-label': ariaLabel,
style,
onMouseDown: userOnMouseDown,
onTouchStart: userOnTouchStart,
onKeyDown: userOnKeyDown,
...props
}, forwardedRef) => {
return (
<div
{...props}
ref={forwardedRef}
className={clsx(`${rootClass}-handle`, className)}
role="separator"
aria-orientation={orientation}
aria-label={ariaLabel || `${orientation} resize handle`}
tabIndex={0}
onMouseDown={(e) => {
userOnMouseDown?.(e);
startDrag(index, e);
}}
onTouchStart={(e) => {
userOnTouchStart?.(e);
startDrag(index, e);
}}
onKeyDown={(e) => {
userOnKeyDown?.(e);
handleKeyDown(index, e);
}}
style={style}
/>
);
})
🤖 Prompt for AI Agents
In src/components/ui/Splitter/fragments/SplitterHandle.tsx around lines 20–31,
the spread {...props} followed by internal onMouseDown/onTouchStart/onKeyDown
handlers silently overrides consumer handlers; fix by composing the handlers
instead: read any existing props.onMouseDown/onTouchStart/onKeyDown into local
constants and create composed handlers that call the consumer handler (if
present) and then the internal startDrag/handleKeyDown (or the other order if
you prefer consumer first), passing the original event through and preserving
return values/stopPropagation behavior; replace the hardwired
onMouseDown/onTouchStart/onKeyDown props with these composed functions so
consumer analytics/side-effects still run.

import React, { ReactNode } from 'react';
import { customClassSwitcher } from '~/core';
import React from 'react';
import { clsx } from 'clsx';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect clsx import.

Use default import to avoid runtime failure.

Apply this diff:

-import { clsx } from 'clsx';
+import clsx from 'clsx';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { clsx } from 'clsx';
import clsx from 'clsx';
🤖 Prompt for AI Agents
In src/components/ui/Splitter/fragments/SplitterPanel.tsx around line 3, the
file currently imports clsx as a named import which causes a runtime failure;
change the import to use the default export from 'clsx' (replace the named
import with a default import) so existing usages of clsx work at runtime.

Comment on lines +19 to +27
const panelStyle = {
flexBasis: `${sizes[index]}%`,
flexGrow: 0,
flexShrink: 0,
overflow: 'auto',
minWidth: orientation === 'horizontal' ? 0 : undefined,
minHeight: orientation === 'vertical' ? 0 : undefined
};
minHeight: orientation === 'vertical' ? 0 : undefined,
...style
} as React.CSSProperties;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against undefined sizes[index] to avoid “undefined%”.

If sizes[index] is missing, flexBasis becomes "undefined%". Guard and only set when defined.

Apply this diff:

-    const panelStyle = {
-        flexBasis: `${sizes[index]}%`,
+    const basis = sizes[index];
+    const panelStyle: React.CSSProperties = {
+        ...(typeof basis === 'number' ? { flexBasis: `${basis}%` } : {}),
         flexGrow: 0,
         flexShrink: 0,
         overflow: 'auto',
         minWidth: orientation === 'horizontal' ? 0 : undefined,
-        minHeight: orientation === 'vertical' ? 0 : undefined,
-        ...style
-    } as React.CSSProperties;
+        minHeight: orientation === 'vertical' ? 0 : undefined,
+        ...style
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const panelStyle = {
flexBasis: `${sizes[index]}%`,
flexGrow: 0,
flexShrink: 0,
overflow: 'auto',
minWidth: orientation === 'horizontal' ? 0 : undefined,
minHeight: orientation === 'vertical' ? 0 : undefined
};
minHeight: orientation === 'vertical' ? 0 : undefined,
...style
} as React.CSSProperties;
const basis = sizes[index];
const panelStyle: React.CSSProperties = {
...(typeof basis === 'number' ? { flexBasis: `${basis}%` } : {}),
flexGrow: 0,
flexShrink: 0,
overflow: 'auto',
minWidth: orientation === 'horizontal' ? 0 : undefined,
minHeight: orientation === 'vertical' ? 0 : undefined,
...style
};
🤖 Prompt for AI Agents
In src/components/ui/Splitter/fragments/SplitterPanel.tsx around lines 19 to 27,
the panelStyle builds flexBasis using `${sizes[index]}%` which yields
"undefined%" when sizes[index] is missing; change the logic to only set
flexBasis when sizes[index] is a defined number (e.g., compute a variable like
const flexBasis = typeof sizes[index] === 'number' ? `${sizes[index]}%` :
undefined and include flexBasis in the style object conditionally) so that
flexBasis is omitted when the size is undefined, preserving types as
React.CSSProperties.

@kotAPI kotAPI linked an issue Sep 5, 2025 that may be closed by this pull request
@kotAPI kotAPI merged commit 434ec37 into main Sep 5, 2025
7 checks passed
@kotAPI kotAPI deleted the kotapi/refactor-splitter-component-with-forwardref-o555vb branch September 5, 2025 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Splitter [forwardRef refactor]

2 participants