diff --git a/README.md b/README.md index 6e336301..77006153 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [View the Demo](https://strml.github.io/react-resizable/examples/1.html) -A simple widget that can be resized via a handle. +A simple widget that can be resized via one or more handles. You can either use the `` element directly, or use the much simpler `` element. @@ -59,6 +59,7 @@ These props apply to both `` and ``. onResizeStop?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any, onResizeStart?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any, onResize?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any, - draggableOpts?: ?Object + draggableOpts?: ?Object, + resizeHandles?: ?Array<'s' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'> = ['se'] }; ``` diff --git a/css/styles.css b/css/styles.css index ae64b220..8fe11a9e 100644 --- a/css/styles.css +++ b/css/styles.css @@ -5,13 +5,61 @@ position: absolute; width: 20px; height: 20px; - bottom: 0; - right: 0; - background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+'); - background-position: bottom right; - padding: 0 3px 3px 0; background-repeat: no-repeat; background-origin: content-box; box-sizing: border-box; + background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+'); + background-position: bottom right; + padding: 0 3px 3px 0; +} +.react-resizable-handle-sw { + bottom: 0; + left: 0; + cursor: sw-resize; + transform: rotate(90deg); +} +.react-resizable-handle-se { + bottom: 0; + right: 0; cursor: se-resize; } +.react-resizable-handle-nw { + top: 0; + left: 0; + cursor: nw-resize; + transform: rotate(180deg); +} +.react-resizable-handle-ne { + top: 0; + right: 0; + cursor: ne-resize; + transform: rotate(270deg); +} +.react-resizable-handle-w, +.react-resizable-handle-e { + top: 50%; + margin-top: -10px; + cursor: ew-resize; +} +.react-resizable-handle-w { + left: 0; + transform: rotate(135deg); +} +.react-resizable-handle-e { + right: 0; + transform: rotate(315deg); +} +.react-resizable-handle-n, +.react-resizable-handle-s { + left: 50%; + margin-left: -10px; + cursor: ns-resize; +} +.react-resizable-handle-n { + top: 0; + transform: rotate(225deg); +} +.react-resizable-handle-s { + bottom: 0; + transform: rotate(45deg); +} \ No newline at end of file diff --git a/lib/Resizable.js b/lib/Resizable.js index c3e9918a..c8184f06 100644 --- a/lib/Resizable.js +++ b/lib/Resizable.js @@ -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 ResizeHandle = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'; type State = { resizing: boolean, width: number, height: number, @@ -19,14 +20,15 @@ type DragCallbackData = { }; export type ResizeCallbackData = { node: HTMLElement, - size: {width: number, height: number} + size: {width: number, height: number}, + handle: ResizeHandle }; export type Props = { children: ReactElement, className?: ?string, width: number, height: number, - handle: ReactElement, + handle: ReactElement | (resizeHandle: ResizeHandle) => ReactElement, handleSize: [number, number], lockAspectRatio: boolean, axis: Axis, @@ -35,7 +37,8 @@ export type Props = { onResizeStop?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any, onResizeStart?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any, onResize?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any, - draggableOpts?: ?Object + draggableOpts?: ?Object, + resizeHandles?: ?ResizeHandle[] }; export default class Resizable extends React.Component { @@ -61,6 +64,18 @@ export default class Resizable extends React.Component { // If you change this, be sure to update your css handleSize: PropTypes.array, + // Defines which resize handles should be rendered (default: 'se') + // Allows for any combination of: + // 's' - South handle (bottom-center) + // 'w' - West handle (left-center) + // 'e' - East handle (right-center) + // 'n' - North handle (top-center) + // 'sw' - Southwest handle (bottom-left) + // 'nw' - Northwest handle (top-left) + // 'se' - Southeast handle (bottom-right) + // 'ne' - Northeast handle (top-center) + resizeHandles: PropTypes.arrayOf(PropTypes.oneOf(['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne'])), + // If true, will only allow width/height to move in lockstep lockAspectRatio: PropTypes.bool, @@ -89,7 +104,8 @@ export default class Resizable extends React.Component { lockAspectRatio: false, axis: 'both', minConstraints: [20, 20], - maxConstraints: [Infinity, Infinity] + maxConstraints: [Infinity, Infinity], + resizeHandles: ['se'] }; state: State = { @@ -161,12 +177,20 @@ export default class Resizable extends React.Component { * @param {String} handlerName Handler name to wrap. * @return {Function} Handler function. */ - resizeHandler(handlerName: string): Function { + resizeHandler(handlerName: string, axis: ResizeHandle): 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 canDragX = (this.props.axis === 'both' || this.props.axis === 'x') && ['n', 's'].indexOf(axis) === -1; + const canDragY = (this.props.axis === 'both' || this.props.axis === 'y') && ['e', 'w'].indexOf(axis) === -1; + + // reverse delta if using top or left drag handles + if (canDragX && axis[axis.length - 1] === 'w') { + deltaX = -deltaX; + } + if (canDragY && axis[0] === 'n') { + deltaY = -deltaY; + } // Update w/h let width = this.state.width + (canDragX ? deltaX : 0); @@ -195,18 +219,29 @@ export default class Resizable extends React.Component { 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}, handle: axis})); } else { this.setState(newState); } }; } + renderResizeHandle(resizeHandle: ResizeHandle): ReactNode { + const {handle} = this.props; + if (handle) { + if (typeof handle === 'function') { + return handle(resizeHandle); + } + return handle; + } + return ; + } + render(): ReactNode { // eslint-disable-next-line no-unused-vars - const {children, draggableOpts, width, height, handle, handleSize, + const {children, draggableOpts, width, height, handleSize, lockAspectRatio, axis, minConstraints, maxConstraints, onResize, - onResizeStop, onResizeStart, ...p} = this.props; + onResizeStop, onResizeStart, resizeHandles, ...p} = this.props; const className = p.className ? `${p.className} react-resizable`: @@ -215,21 +250,23 @@ export default class Resizable extends React.Component { // 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 - // A draggable handle. + // One or more draggable handles. return cloneElement(children, { ...p, className, children: [ children.props.children, - - {handle || } - + resizeHandles.map(h => ( + + {this.renderResizeHandle(h)} + + )) ] }); } diff --git a/lib/ResizableBox.js b/lib/ResizableBox.js index 0a11dbe5..f4b7b81c 100644 --- a/lib/ResizableBox.js +++ b/lib/ResizableBox.js @@ -48,8 +48,8 @@ export default class ResizableBox extends React.Component // Basic wrapper around a Resizable instance. // If you use Resizable directly, you are responsible for updating the child component // with a new width and height. - const {handle, handleSize, onResize, onResizeStart, onResizeStop, draggableOpts, - minConstraints, maxConstraints, lockAspectRatio, axis, width, height, ...props} = this.props; + const {handle, handleSize, onResize, onResizeStart, onResizeStop, draggableOpts, minConstraints, + maxConstraints, lockAspectRatio, axis, width, height, resizeHandles, ...props} = this.props; return ( maxConstraints={maxConstraints} lockAspectRatio={lockAspectRatio} axis={axis} + resizeHandles={resizeHandles} >
diff --git a/test/TestLayout.js b/test/TestLayout.js index 566ffaf2..2fc2aea7 100644 --- a/test/TestLayout.js +++ b/test/TestLayout.js @@ -2,6 +2,7 @@ import React from 'react'; import Resizable from '../lib/Resizable'; import ResizableBox from '../lib/ResizableBox'; import 'style-loader!css-loader!../css/styles.css'; +import 'style-loader!css-loader!./test.css'; export default class TestLayout extends React.Component<{}, {width: number, height: number}> { state = {width: 200, height: 200}; @@ -10,7 +11,8 @@ export default class TestLayout extends React.Component<{}, {width: number, heig this.setState({width: 200, height: 200}); }; - onResize = (event, {element, size}) => { + onResize = (event, {element, size, handle}) => { + console.log(handle); this.setState({width: size.width, height: size.height}); }; @@ -19,14 +21,31 @@ export default class TestLayout extends React.Component<{}, {width: number, heig
- +
- {"Raw use of element. 200x200, no constraints."} + {"Raw use of element. 200x200, all Resize Handles."}
{", same as above."} + } + handleSize={[8, 8]}> + {" with custom handle in SE corner."} + + } + handleSize={[8, 8]} + resizeHandles={['sw', 'se', 'nw', 'ne', 'w', 'e', 'n', 's']}> + {" with custom handles in all locations."} + Resizable box that snaps to even intervals of 25px. diff --git a/test/test.css b/test/test.css new file mode 100644 index 00000000..d062b272 --- /dev/null +++ b/test/test.css @@ -0,0 +1,55 @@ +.custom-box { + overflow: visible; +} +.custom-handle { + position: absolute; + width: 8px; + height: 8px; + background-color: #1153aa; + opacity: 0.75; + border-radius: 4px; +} +.custom-handle-sw { + bottom: -4px; + left: -4px; + cursor: sw-resize; +} +.custom-handle-se { + bottom: -4px; + right: -4px; + cursor: se-resize; +} +.custom-handle-nw { + top: -4px; + left: -4px; + cursor: nw-resize; +} +.custom-handle-ne { + top: -4px; + right: -4px; + cursor: ne-resize; +} +.custom-handle-w, +.custom-handle-e { + top: 50%; + margin-top: -4px; + cursor: ew-resize; +} +.custom-handle-w { + left: -4px; +} +.custom-handle-e { + right: -4px; +} +.custom-handle-n, +.custom-handle-s { + left: 50%; + margin-left: -4px; + cursor: ns-resize; +} +.custom-handle-n { + top: -4px; +} +.custom-handle-s { + bottom: -4px; +} \ No newline at end of file