Skip to content

feat: Vertical and horizontal resize handles supported #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions css/styles.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.react-resizable {
position: relative;
}

.react-resizable-handle {
position: absolute;
width: 20px;
Expand All @@ -15,3 +16,17 @@
box-sizing: border-box;
cursor: se-resize;
}

.react-resizable-handle-x {
right: 0;
bottom: 50%;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCA0LjIgNiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+');
cursor: ew-resize;
}

.react-resizable-handle-y {
right: 50%;
bottom: 0;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDYgNC4yIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+');
cursor: ns-resize;
}
60 changes: 45 additions & 15 deletions lib/Resizable.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import cloneElement from './cloneElement';
import type {Element as ReactElement, Node as ReactNode} from 'react';

type Axis = 'both' | 'x' | 'y' | 'none';
type Resizer = Axis | 'all';
type State = {
resizing: boolean,
width: number, height: number,
Expand All @@ -19,7 +20,8 @@ type DragCallbackData = {
};
export type ResizeCallbackData = {
node: HTMLElement,
size: {width: number, height: number}
size: {width: number, height: number},
axis: Axis,
};
export type Props = {
children: ReactElement<any>,
Expand All @@ -29,6 +31,7 @@ export type Props = {
handleSize: [number, number],
lockAspectRatio: boolean,
axis: Axis,
resizer: Resizer,
minConstraints: [number, number],
maxConstraints: [number, number],
onResizeStop?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any,
Expand Down Expand Up @@ -67,6 +70,14 @@ export default class Resizable extends React.Component<Props, State> {
// 'none' - disables resizing altogether
axis: PropTypes.oneOf(['both', 'x', 'y', 'none']),

// Restricts resize handle.
// 'all' -show all resizer if that axis is resizable
// 'both' - show bottom right resizer if that axis is resizable
// 'x' - only show resizer on x axis if resizable
// 'y' - only show resizer on y axis if resizable
// 'none' - disables all resizer
resizer: PropTypes.oneOf([ 'both', 'x', 'y', 'none' ]),

// Min/max size
minConstraints: PropTypes.arrayOf(PropTypes.number),
maxConstraints: PropTypes.arrayOf(PropTypes.number),
Expand All @@ -84,6 +95,7 @@ export default class Resizable extends React.Component<Props, State> {
handleSize: [20, 20],
lockAspectRatio: false,
axis: 'both',
resizer: 'both',
minConstraints: [20, 20],
maxConstraints: [Infinity, Infinity]
};
Expand Down Expand Up @@ -157,12 +169,13 @@ export default class Resizable extends React.Component<Props, State> {
* @param {String} handlerName Handler name to wrap.
* @return {Function} Handler function.
*/
resizeHandler(handlerName: string): Function {
resizeHandler(handlerName: string, resizeAxis: Axis): Function {
return (e: SyntheticEvent<> | MouseEvent, {node, deltaX, deltaY}: DragCallbackData) => {

// Axis restrictions
const canDragX = this.props.axis === 'both' || this.props.axis === 'x';
const canDragY = this.props.axis === 'both' || this.props.axis === 'y';
const axis = resizeAxis || this.props.axis;
const canDragX = axis === 'both' || axis === 'x';
const canDragY = axis === 'both' || axis === 'y';

// Update w/h
let width = this.state.width + (canDragX ? deltaX : 0);
Expand Down Expand Up @@ -191,23 +204,48 @@ export default class Resizable extends React.Component<Props, State> {
const hasCb = typeof this.props[handlerName] === 'function';
if (hasCb) {
if (typeof e.persist === 'function') e.persist();
this.setState(newState, () => this.props[handlerName](e, {node, size: {width, height}}));
this.setState(newState, () => this.props[handlerName](e, {node, size: {width, height}, axis }));
} else {
this.setState(newState);
}
};
}


renderAxis(axis: Axis): ReactNode {
const { draggableOpts } = this.props;
return <DraggableCore
{...draggableOpts}
key={`resizableHandle-${axis}`}
onStop={this.resizeHandler('onResizeStop', axis)}
onStart={this.resizeHandler('onResizeStart', axis)}
onDrag={this.resizeHandler('onResize', axis)}
>
<span className={`react-resizable-handle ${axis === 'both' ? '' : 'react-resizable-handle-' + axis}`} />
</DraggableCore>;
}

render(): ReactNode {
// eslint-disable-next-line no-unused-vars
const {children, draggableOpts, width, height, handleSize,
lockAspectRatio, axis, minConstraints, maxConstraints, onResize,
lockAspectRatio, resizer, axis, minConstraints, maxConstraints, onResize,
onResizeStop, onResizeStart, ...p} = this.props;

const className = p.className ?
`${p.className} react-resizable`:
'react-resizable';

const draggableCores = [];

['both', 'x', 'y'].forEach(axisType => {
if (
(axis === 'both' || axisType === axis) &&
(resizer === 'all' || axisType === resizer)
) {
draggableCores.push(this.renderAxis(axisType));
}
});

// What we're doing here is getting the child of this element, and cloning it with this element's props.
// We are then defining its children as:
// Its original children (resizable's child's children), and
Expand All @@ -217,15 +255,7 @@ export default class Resizable extends React.Component<Props, State> {
className,
children: [
children.props.children,
<DraggableCore
{...draggableOpts}
key="resizableHandle"
onStop={this.resizeHandler('onResizeStop')}
onStart={this.resizeHandler('onResizeStart')}
onDrag={this.resizeHandler('onResize')}
>
<span className="react-resizable-handle" />
</DraggableCore>
...draggableCores,
]
});
}
Expand Down
4 changes: 3 additions & 1 deletion lib/ResizableBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export default class ResizableBox extends React.Component<ResizableProps, State>
// If you use Resizable directly, you are responsible for updating the child component
// with a new width and height.
const {handleSize, onResize, onResizeStart, onResizeStop, draggableOpts,
minConstraints, maxConstraints, lockAspectRatio, axis, width, height, ...props} = this.props;
minConstraints, maxConstraints, lockAspectRatio, axis, resizer,
width, height, ...props} = this.props;
return (
<Resizable
handleSize={handleSize}
Expand All @@ -63,6 +64,7 @@ export default class ResizableBox extends React.Component<ResizableProps, State>
maxConstraints={maxConstraints}
lockAspectRatio={lockAspectRatio}
axis={axis}
resizer={resizer}
>
<div style={{width: this.state.width + 'px', height: this.state.height + 'px'}} {...props} />
</Resizable>
Expand Down
13 changes: 13 additions & 0 deletions test/TestLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,28 @@ export default class TestLayout extends React.Component<{}, {width: number, heig
<ResizableBox className="box" width={200} height={200} axis="x">
<span className="text">Only resizable by "x" axis.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} axis="x" resizer="x">
<span className="text">Only resizable by "x" axis with x resizer.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} axis="y">
<span className="text">Only resizable by "y" axis.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} axis="y" resizer="y">
<span className="text">Only resizable by "y" axis with x resizer.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} axis="both">
<span className="text">Resizable ("both" axis).</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} axis="both" resizer="both">
<span className="text">Resizable ("both" axis with "both" resizer).</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} axis="both" resizer="all">
<span className="text">Resizable ("both" axis with "all" resizer).</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} axis="none">
<span className="text">Not resizable ("none" axis).</span>
</ResizableBox>

</div>
</div>
);
Expand Down