Skip to content

Commit 29079d4

Browse files
authored
fix: Initial sizes should be able to be set to a ratio
1 parent 4e7d1dc commit 29079d4

File tree

6 files changed

+82
-57
lines changed

6 files changed

+82
-57
lines changed

README.md

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
44
<!-- ALL-CONTRIBUTORS-BADGE:END -->
55

6-
This is intended to be **the** simple, reliable, configurable, and elegant solution to having collapsible panes in your react application.
6+
This is intended to be **the** simple, reliable, configurable, and elegant solution to having collapsible panes in your React application.
77

88

99

@@ -46,19 +46,21 @@ Here's a basic example:
4646
</SplitPane>
4747
```
4848

49-
This will split the children. The children can be any valid react child. If a child is `null` it will be excluded from being split or displayed.
49+
This will split the children. The children can be any valid React child. If a child is `null` it will be excluded from being split or displayed.
5050

51-
Notice that there is no limit to the number of divs you have inside here. The library will split them all accordingly.
51+
**Note!** :eyes: There is no limit to the number of divs you have as children. The `SplitPane` will split them all accordingly.
5252

5353
## Styling the Resizer 💅
5454

55-
By default there is a 1px divider that starts out `silver` and transitions to `dimgrey` ([css colors](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value)) on hover.
55+
By default there is a 1px divider that starts out `rgba(120, 120, 120, 0.3)` and transitions to `rgba(120, 120, 120, 0.6)` on hover. Having a grey color with an alpha value means that it will look nice on both light and dark backgrounds.
5656

57-
However this is easily replaceable by using the `css` and `hoverCss` options. You do not have to worry about pseudo selectors, transitions, animations or anything. You just have to indicate what the divider should look like **before** and **after**. This is accomplished by having two separate divs, one of which fades out and the other which fades in.
57+
This is easily replaceable with the `css` and `hoverCss` options. No need to worry about pseudo selectors, transitions, animations or anything. You just have to indicate what the divider should look like **before** and **after**. This is accomplished by having two separate divs, one of which fades out and the other which fades in.
5858

59-
The sizer also has a grabbable surface that spans the length of the split and has a default grabbable surface of `1rem`. Thsiis changeable by the `grabberSize` option which can be set to any valid CSS size value for `width` or `height`.
59+
**Note** 🚨 The css props must be valid `React.CSSProperties` objects.
6060

61-
**Note!** 🚨 As per default react CSS, a number will be interpreted as a `px` value.
61+
The sizer also has a grabbable surface that spans the height (or length) of the split and has a default grabbable surface of `1rem`. This is changeable by the `grabberSize` option which can be set to any valid CSS size value for `width` or `height`.
62+
63+
**Note!** :eyes: As per default React CSS, a number will be interpreted as a `px` value.
6264

6365

6466
Here's an example:
@@ -83,20 +85,22 @@ Here's an example:
8385
</SplitPane>
8486
```
8587

86-
**Note** the css props must be valid `React.CSSProperties` objects.
8788

8889
## Using a Collapse Button 🤹‍♀️
8990

91+
This is the killer feature of this library :eyes:
92+
9093
It's a common UX need to want to collapse the left or initial panel to give more room for another part of a site or app. This is easily accomplished by including several `CollapseOptions` as a prop to the `SplitPane`.
9194

92-
* `beforeToggleButton` - the element displayed as the collapse button **before** the panel is collapsed. This is a purely aesthetic component.
93-
* `afterToggleButton` - the element displayed as the collapse button **after** the panel is collapsed. This is a purely aesthetic component.
94-
* `overlayCss` - the css applied to a div positioned on top of the content.
95+
* `beforeToggleButton` - the element displayed as the collapse button **before** the panel is collapsed. This is an purely aesthetic component.
96+
* `afterToggleButton` - the element displayed as the collapse button **after** the panel is collapsed. This is an purely aesthetic component.
9597
* `buttonTransition` - the animation applied to the button appear/disappear. Possible options are `zoom`, `grow`, or `fade`
9698
* `buttonTransitionTimeout` - the time (in millisecons) that the animation for the appear/disappear of the button will take place
9799
* `buttonPositionOffset` - a positive or negative number that will either add or subtract the flex-basis (starting at 100) of an invisible div before or after the button. e.g. 50 would make the "before" 150 and the "after" 50
100+
* `collapseDirection` - `'left' | 'right' | 'up' | 'down'` - this is used to indicate the direction that it should collapse. By default collapsing happens left and up for the vertical and horizontal splits respectively. Valid values for a vertical split are `left` or `right` and valid values for a horizontal split are `up` or `down`
98101
* `collapseSize` - the size of the collapsed panel after it has been collapsed
99102
* `collapseTransitionTimeout` - the duration within the collapse animation will take place
103+
* `overlayCss` - the css applied to a div positioned on top of the content.
100104

101105
Here's an example using a `Button` element imported from elsewhere.
102106

@@ -120,6 +124,9 @@ Here's an example using a `Button` element imported from elsewhere.
120124
</SplitPane>
121125
```
122126

127+
**Note!** :eyes: When collapsing a panel, the `minSize` value is used to freeze the width of the collapsed panel to its minimum size and hides the rest of the content. This allows for a smooth collapse animation and is something to keep in mind. Until the animation reaches the min size it will shrink the panel as normal. Try it out for yourself!
128+
129+
123130
## Hooks and Saving State 🌩
124131

125132
The component manages its own state while resizing however also allows an initial state as well as callbacks to save state changes.
@@ -128,7 +135,7 @@ These callbacks are in the `hooks` prop:
128135
```ts
129136
onDragStarted?: () => void;
130137
onChange?: (sizes: number[]) => void;
131-
onDragFinished?: (sizes: number[]) => void;
138+
onSaveSizes?: (sizes: number[]) => void;
132139
onCollapse?: (collapsedSizes: Nullable<number>[]) => void;
133140
```
134141
* `onDragStarted` fires as soon as you click down on a resizer and begin moving
@@ -143,14 +150,16 @@ initialSizes?: number[];
143150
minSizes?: number | number[];
144151
collapsedSizes?: Nullable<number>[];
145152
```
146-
* `initialSizes` is the default flex-basis that's given to the panes
153+
* `initialSizes` is the default flex-basis that's given to the panes. This can be a simple ratio if it's the first time the render will happen and there's no saved sizes. e.g. `[1, 2, 1]` would make the second panel twice as big as its siblings. If you're saving state this should be the saved size value on a second render.
147154
* `minSizes` is either (1) a minimum size that's given to **all** the panes, or (2) an array of minimum sizes that's given to each pane in order. Any missing sizes in the array will be assumed default.
148155
* `collapsedSizes` an array of nullable numbers. This keeps track of a pane's size before it was collapsed. If not collapsed it's null. This will determine which panels are collapsed and what to do when they're uncollapsed.
149156

157+
Typically if this is a controlled component you would have state variables for `initialSizes` and `collapsedSizes` and have callbacks on `onSaveSizes` and `onCollapse` that would save the two data points and pass them back into the `SplitPane` on a remount. The `minSizes` would typically never change.
158+
150159

151160
## RTL Support ( Arabic, Hebrew, Farsi ) 🕋
152161

153-
This library easily supports LTR languages by providing a `direction` prop. This is only necessary if you're using RTL.
162+
This library easily supports RTL languages by providing a `direction` prop. This is only necessary if you're using RTL and can be left out.
154163

155164
**Note!** 🚨 the `direction` is _only_ applicable if the split is `vertical`
156165

@@ -192,4 +201,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
192201
<!-- prettier-ignore-end -->
193202
<!-- ALL-CONTRIBUTORS-LIST:END -->
194203

195-
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
204+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

src/components/Resizer/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ import styled from 'styled-components';
1414
import { useTransition } from './hooks/useTransition';
1515

1616
const ButtonPositionOffset = styled.div`
17-
flex-grow: 1;
18-
flex-shrink: 1;
17+
flex: 1 1 auto;
1918
`;
2019

2120
const defaultResizerOptions: Required<ResizerOptions> = {
2221
grabberSize: '1rem',
23-
css: { backgroundColor: 'silver' },
24-
hoverCss: { backgroundColor: 'grey' },
22+
css: { backgroundColor: 'rgba(120, 120, 120, 0.3)' },
23+
hoverCss: { backgroundColor: 'rgba(120, 120, 120, 0.6)' },
2524
};
2625

2726
export interface ResizerProps {

src/components/SplitPane/helpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ export const convertCollapseSizesToIndices = (sizes?: Nullable<number>[]) =>
103103
sizes?.reduce((prev, cur, idx) => (cur !== null ? [...prev, idx] : [...prev]), [] as number[]) ??
104104
[];
105105

106+
export const addArray = (arr: number[]) => arr.reduce((prev, cur) => prev + cur, 0);
107+
106108
export const debounce = (callback: (...args: any[]) => void, wait = 250) => {
107109
let timer: any;
108110
return (...args: any[]) => {
Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getMinSize, moveSizes } from '../../helpers';
1+
import { addArray, getMinSize, moveSizes } from '../../helpers';
22
import React, { useCallback } from 'react';
33

44
export function useRecalculateSizes({
@@ -18,40 +18,46 @@ export function useRecalculateSizes({
1818
setMovedSizes: React.Dispatch<React.SetStateAction<number[]>>;
1919
setSizes: React.Dispatch<React.SetStateAction<number[]>>;
2020
}) {
21-
return useCallback(() => {
22-
const curSizes = getCurrentPaneSizes();
23-
const adjustedSizes = curSizes.map((size, idx) => {
24-
if (collapsedIndices.includes(idx)) {
25-
return collapsedSize;
26-
}
27-
if (collapsedIndices.includes(idx - 1)) {
28-
return size + curSizes[idx - 1] - collapsedSize;
29-
}
30-
return size;
31-
});
32-
curSizes.forEach((_size, idx) => {
33-
const offset = curSizes[idx] - getMinSize(idx, originalMinSizes);
34-
// if offset is negative this means the min size is greater and we need to move this guy
35-
if (offset < 0) {
36-
moveSizes({
37-
collapsedIndices,
38-
collapsedSize,
39-
sizes: adjustedSizes,
40-
index: idx,
41-
offset: -offset,
42-
minSizes,
43-
});
44-
}
45-
});
46-
setMovedSizes(adjustedSizes);
47-
setSizes(adjustedSizes);
48-
}, [
49-
collapsedIndices,
50-
collapsedSize,
51-
getCurrentPaneSizes,
52-
minSizes,
53-
originalMinSizes,
54-
setMovedSizes,
55-
setSizes,
56-
]);
21+
return useCallback(
22+
(initialSizes?: number[]) => {
23+
const curSizes = getCurrentPaneSizes();
24+
const ratio =
25+
initialSizes && initialSizes.length > 0 ? addArray(curSizes) / addArray(initialSizes) : 1;
26+
const initialRatioSizes = initialSizes ? initialSizes.map(size => size * ratio) : curSizes;
27+
const adjustedSizes = initialRatioSizes.map((size, idx) => {
28+
if (collapsedIndices.includes(idx)) {
29+
return collapsedSize;
30+
}
31+
if (collapsedIndices.includes(idx - 1)) {
32+
return size + curSizes[idx - 1] - collapsedSize;
33+
}
34+
return size;
35+
});
36+
curSizes.forEach((_size, idx) => {
37+
const offset = curSizes[idx] - getMinSize(idx, originalMinSizes);
38+
// if offset is negative this means the min size is greater and we need to move this guy
39+
if (offset < 0) {
40+
moveSizes({
41+
collapsedIndices,
42+
collapsedSize,
43+
sizes: adjustedSizes,
44+
index: idx,
45+
offset: -offset,
46+
minSizes,
47+
});
48+
}
49+
});
50+
setMovedSizes(adjustedSizes);
51+
setSizes(adjustedSizes);
52+
},
53+
[
54+
collapsedIndices,
55+
collapsedSize,
56+
getCurrentPaneSizes,
57+
minSizes,
58+
originalMinSizes,
59+
setMovedSizes,
60+
setSizes,
61+
]
62+
);
5763
}

src/components/SplitPane/hooks/useSplitPaneResize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export function useSplitPaneResize(options: SplitPaneResizeOptions): SplitPaneRe
158158
const resetSizes = useMemo(() => debounce(() => recalculateSizes(), 50), [recalculateSizes]);
159159
window.addEventListener('resize', () => resetSizes());
160160
useEffect(
161-
() => recalculateSizes(),
161+
() => recalculateSizes(initialSizes),
162162
// eslint-disable-next-line react-hooks/exhaustive-deps
163163
[]
164164
);

stories/InitialState.stories.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,13 @@ storiesOf('Initial States', module)
5151
</VerticalSplitPane>
5252
);
5353
})
54+
.add('Initial Sizes as Flex-Basis proportions', () => {
55+
return (
56+
<SplitPane split="vertical" initialSizes={[1, 2, 1]}>
57+
<div>This is a div</div>
58+
<div>This is a second div</div>
59+
<div>This is a third div</div>
60+
</SplitPane>
61+
);
62+
})
5463
.addDecorator(withKnobs);

0 commit comments

Comments
 (0)