Skip to content

Commit 48ebdbe

Browse files
tomkpclaude
andauthored
fix: code quality improvements from review (#859)
- Remove unused PaneState type from types and exports - Extract cn() utility for className combination (reduces duplication) - Add comprehensive tests for usePersistence hook (12 new tests) - Add SSR safety check to announce() function - Document Escape key in README keyboard navigation - Move Tailwind/shadcn section to separate TAILWIND.md file - Refactor useResizer to avoid state-during-render pattern (React 19 compatible) Closes #858 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent 7de2f44 commit 48ebdbe

File tree

13 files changed

+465
-134
lines changed

13 files changed

+465
-134
lines changed

README.md

Lines changed: 2 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ The divider is fully keyboard accessible:
200200
- **Shift + Arrow**: Resize by larger step (default: 50px)
201201
- **Home**: Minimize left/top pane
202202
- **End**: Maximize left/top pane
203+
- **Escape**: Restore pane sizes to initial state
203204
- **Tab**: Navigate between dividers
204205

205206
## API Reference
@@ -342,112 +343,7 @@ A subtle single-pixel divider:
342343

343344
## Tailwind CSS & shadcn/ui
344345

345-
React Split Pane works seamlessly with Tailwind CSS and shadcn/ui. The component uses plain CSS and inline styles (no CSS-in-JS), so there are no conflicts with utility-first frameworks.
346-
347-
### Using Tailwind Classes
348-
349-
Apply Tailwind classes directly via `className` props. Skip importing the default stylesheet for full Tailwind control:
350-
351-
```tsx
352-
import { SplitPane, Pane } from 'react-split-pane';
353-
// Don't import 'react-split-pane/styles.css' if using Tailwind
354-
355-
<SplitPane
356-
className="h-screen w-full"
357-
dividerClassName="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 transition-colors"
358-
>
359-
<Pane defaultSize="300px" className="bg-white dark:bg-gray-900 p-4">
360-
<Sidebar />
361-
</Pane>
362-
<Pane className="bg-gray-50 dark:bg-gray-800 p-4">
363-
<MainContent />
364-
</Pane>
365-
</SplitPane>
366-
```
367-
368-
### shadcn/ui Integration
369-
370-
Use shadcn's CSS variables and utilities for consistent theming:
371-
372-
```tsx
373-
import { SplitPane, Pane } from 'react-split-pane';
374-
375-
<SplitPane
376-
className="h-full w-full"
377-
dividerClassName="bg-border hover:bg-accent transition-colors"
378-
>
379-
<Pane defaultSize="280px" className="bg-background border-r">
380-
<Sidebar />
381-
</Pane>
382-
<Pane className="bg-muted/50">
383-
<MainContent />
384-
</Pane>
385-
</SplitPane>
386-
```
387-
388-
### Custom Divider with shadcn
389-
390-
Create a themed divider component using shadcn's `cn` utility:
391-
392-
```tsx
393-
import { cn } from '@/lib/utils';
394-
import type { DividerProps } from 'react-split-pane';
395-
396-
function ThemedDivider({ direction, isDragging, disabled, ...props }: DividerProps) {
397-
return (
398-
<div
399-
className={cn(
400-
'flex items-center justify-center transition-colors',
401-
'bg-border hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring',
402-
direction === 'horizontal'
403-
? 'w-1 cursor-col-resize'
404-
: 'h-1 cursor-row-resize',
405-
isDragging && 'bg-primary',
406-
disabled && 'cursor-not-allowed opacity-50'
407-
)}
408-
{...props}
409-
/>
410-
);
411-
}
412-
413-
<SplitPane divider={ThemedDivider}>
414-
<Pane>Left</Pane>
415-
<Pane>Right</Pane>
416-
</SplitPane>
417-
```
418-
419-
### CSS Variables with Tailwind
420-
421-
Override the default CSS variables in your `globals.css` to match your Tailwind theme:
422-
423-
```css
424-
/* globals.css */
425-
@layer base {
426-
:root {
427-
--split-pane-divider-size: 4px;
428-
--split-pane-divider-color: theme('colors.gray.200');
429-
--split-pane-divider-color-hover: theme('colors.gray.300');
430-
--split-pane-focus-color: theme('colors.blue.500');
431-
}
432-
433-
.dark {
434-
--split-pane-divider-color: theme('colors.gray.700');
435-
--split-pane-divider-color-hover: theme('colors.gray.600');
436-
}
437-
}
438-
```
439-
440-
Or with shadcn/ui CSS variables:
441-
442-
```css
443-
@layer base {
444-
:root {
445-
--split-pane-divider-color: hsl(var(--border));
446-
--split-pane-divider-color-hover: hsl(var(--accent));
447-
--split-pane-focus-color: hsl(var(--ring));
448-
}
449-
}
450-
```
346+
React Split Pane works seamlessly with Tailwind CSS and shadcn/ui. See [TAILWIND.md](./TAILWIND.md) for detailed integration examples including custom dividers and CSS variable overrides.
451347

452348
## Migration from v0.1.x
453349

TAILWIND.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Tailwind CSS & shadcn/ui Integration
2+
3+
React Split Pane works seamlessly with Tailwind CSS and shadcn/ui. The component uses plain CSS and inline styles (no CSS-in-JS), so there are no conflicts with utility-first frameworks.
4+
5+
## Using Tailwind Classes
6+
7+
Apply Tailwind classes directly via `className` props. Skip importing the default stylesheet for full Tailwind control:
8+
9+
```tsx
10+
import { SplitPane, Pane } from 'react-split-pane';
11+
// Don't import 'react-split-pane/styles.css' if using Tailwind
12+
13+
<SplitPane
14+
className="h-screen w-full"
15+
dividerClassName="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 transition-colors"
16+
>
17+
<Pane defaultSize="300px" className="bg-white dark:bg-gray-900 p-4">
18+
<Sidebar />
19+
</Pane>
20+
<Pane className="bg-gray-50 dark:bg-gray-800 p-4">
21+
<MainContent />
22+
</Pane>
23+
</SplitPane>
24+
```
25+
26+
## shadcn/ui Integration
27+
28+
Use shadcn's CSS variables and utilities for consistent theming:
29+
30+
```tsx
31+
import { SplitPane, Pane } from 'react-split-pane';
32+
33+
<SplitPane
34+
className="h-full w-full"
35+
dividerClassName="bg-border hover:bg-accent transition-colors"
36+
>
37+
<Pane defaultSize="280px" className="bg-background border-r">
38+
<Sidebar />
39+
</Pane>
40+
<Pane className="bg-muted/50">
41+
<MainContent />
42+
</Pane>
43+
</SplitPane>
44+
```
45+
46+
## Custom Divider with shadcn
47+
48+
Create a themed divider component using shadcn's `cn` utility:
49+
50+
```tsx
51+
import { cn } from '@/lib/utils';
52+
import type { DividerProps } from 'react-split-pane';
53+
54+
function ThemedDivider({ direction, isDragging, disabled, ...props }: DividerProps) {
55+
return (
56+
<div
57+
className={cn(
58+
'flex items-center justify-center transition-colors',
59+
'bg-border hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring',
60+
direction === 'horizontal'
61+
? 'w-1 cursor-col-resize'
62+
: 'h-1 cursor-row-resize',
63+
isDragging && 'bg-primary',
64+
disabled && 'cursor-not-allowed opacity-50'
65+
)}
66+
{...props}
67+
/>
68+
);
69+
}
70+
71+
<SplitPane divider={ThemedDivider}>
72+
<Pane>Left</Pane>
73+
<Pane>Right</Pane>
74+
</SplitPane>
75+
```
76+
77+
## CSS Variables with Tailwind
78+
79+
Override the default CSS variables in your `globals.css` to match your Tailwind theme:
80+
81+
```css
82+
/* globals.css */
83+
@layer base {
84+
:root {
85+
--split-pane-divider-size: 4px;
86+
--split-pane-divider-color: theme('colors.gray.200');
87+
--split-pane-divider-color-hover: theme('colors.gray.300');
88+
--split-pane-focus-color: theme('colors.blue.500');
89+
}
90+
91+
.dark {
92+
--split-pane-divider-color: theme('colors.gray.700');
93+
--split-pane-divider-color-hover: theme('colors.gray.600');
94+
}
95+
}
96+
```
97+
98+
Or with shadcn/ui CSS variables:
99+
100+
```css
101+
@layer base {
102+
:root {
103+
--split-pane-divider-color: hsl(var(--border));
104+
--split-pane-divider-color-hover: hsl(var(--accent));
105+
--split-pane-focus-color: hsl(var(--ring));
106+
}
107+
}
108+
```

src/components/Divider.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
getDividerLabel,
55
getKeyboardInstructions,
66
} from '../utils/accessibility';
7+
import { cn } from '../utils/classNames';
78

89
const DEFAULT_CLASSNAME = 'split-pane-divider';
910

@@ -79,14 +80,12 @@ export function Divider(props: DividerProps) {
7980
...style,
8081
};
8182

82-
const combinedClassName = [
83+
const combinedClassName = cn(
8384
DEFAULT_CLASSNAME,
8485
direction,
8586
isDragging && 'dragging',
86-
className,
87-
]
88-
.filter(Boolean)
89-
.join(' ');
87+
className
88+
);
9089

9190
const label = getDividerLabel(index, direction);
9291
const instructions = getKeyboardInstructions(direction);

src/components/Pane.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { CSSProperties } from 'react';
22
import { forwardRef } from 'react';
33
import type { PaneProps } from '../types';
4+
import { cn } from '../utils/classNames';
45

56
const DEFAULT_CLASSNAME = 'split-pane-pane';
67

@@ -54,9 +55,7 @@ export const Pane = forwardRef<HTMLDivElement, PaneProps>(
5455
...style,
5556
};
5657

57-
const combinedClassName = [DEFAULT_CLASSNAME, className]
58-
.filter(Boolean)
59-
.join(' ');
58+
const combinedClassName = cn(DEFAULT_CLASSNAME, className);
6059

6160
return (
6261
<div

src/components/SplitPane.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Divider } from './Divider';
1313
import { useResizer } from '../hooks/useResizer';
1414
import { useKeyboardResize } from '../hooks/useKeyboardResize';
1515
import { convertToPixels, distributeSizes } from '../utils/calculations';
16+
import { cn } from '../utils/classNames';
1617

1718
const DEFAULT_CLASSNAME = 'split-pane';
1819
const MIN_PANES = 2;
@@ -262,9 +263,7 @@ export function SplitPane(props: SplitPaneProps) {
262263
...style,
263264
};
264265

265-
const containerClassName = [DEFAULT_CLASSNAME, direction, className]
266-
.filter(Boolean)
267-
.join(' ');
266+
const containerClassName = cn(DEFAULT_CLASSNAME, direction, className);
268267

269268
// Render panes and dividers
270269
const renderChildren = () => {

src/hooks/useResizer.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,17 @@ export function useResizer(options: UseResizerOptions): UseResizerResult {
8383
const onResizeEndRef = useRef(onResizeEnd);
8484
onResizeEndRef.current = onResizeEnd;
8585

86-
// Update current sizes when prop sizes change (only when not dragging)
87-
if (
88-
!isDragging &&
89-
sizes !== currentSizes &&
90-
JSON.stringify(sizes) !== JSON.stringify(currentSizes)
91-
) {
92-
setCurrentSizes(sizes);
93-
}
86+
// Sync sizes from props when not dragging (React 19 compatible)
87+
const sizesRef = useRef(sizes);
88+
useEffect(() => {
89+
if (
90+
!isDragging &&
91+
JSON.stringify(sizes) !== JSON.stringify(sizesRef.current)
92+
) {
93+
sizesRef.current = sizes;
94+
setCurrentSizes(sizes);
95+
}
96+
}, [sizes, isDragging]);
9497

9598
// Track mounted state for RAF cleanup
9699
useEffect(() => {

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export type {
99
Direction,
1010
Size,
1111
ResizeEvent,
12-
PaneState,
1312
} from './types';
1413

1514
// Re-export hooks for advanced usage

0 commit comments

Comments
 (0)