├── .gitignore ├── LICENSE ├── README.md ├── build ├── index.js └── index.js.map ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.demo.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo.png ├── logo.svg └── manifest.json ├── scripts ├── build.js ├── demo.js ├── start.js └── test.js ├── src ├── demo │ ├── App.css │ ├── App.js │ ├── index.js │ ├── logo.png │ └── registerServiceWorker.js ├── lib │ ├── components │ │ ├── Layout │ │ │ ├── HorizontalLayout.jsx │ │ │ ├── Layout.jsx │ │ │ ├── Panel.jsx │ │ │ ├── Separator.jsx │ │ │ ├── Spacer.jsx │ │ │ ├── VerticalLayout.jsx │ │ │ └── index.js │ │ └── View │ │ │ ├── View.jsx │ │ │ └── index.js │ └── index.js └── version.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # production 7 | /demo 8 | 9 | # testing 10 | /coverage 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Eric Ros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stable Release](https://img.shields.io/npm/v/nice-react-layout.svg)](https://npm.im/nice-react-layout) 2 | ![last commit](https://img.shields.io/github/last-commit/google/skia.svg) 3 | ![license](https://img.shields.io/github/license/mashape/apistatus.svg) 4 | [![DeepScan grade](https://deepscan.io/api/projects/2670/branches/18301/badge/grade.svg)](https://deepscan.io/dashboard#view=project&pid=2670&bid=18301) 5 | 6 | # ![logo](public/logo.png) Nice React Layout 7 | 8 | A set of React components to create complex flexbox-based layouts without knowing what flexbox is. 9 | [https://ekros.github.io/nice-react-layout/](https://ekros.github.io/nice-react-layout/) 10 | 11 | ## Installation 12 | 13 | ```sh 14 | yarn add nice-react-layout 15 | ``` 16 | 17 | ## Motivation 18 | 19 | The aim of this project is to have a reduced set of components to create flexbox-based layouts abstracting the knowledge needed to understand how flexbox works. This library is very useful for web apps with lots of panels. 20 | 21 | ## Features 22 | 23 | - Easy to learn: Just combine layouts and panels. 24 | - Create collapsible sidebars with ease. 25 | - Automagically colorize panels with random colors to speed-up prototyping. 26 | - Resizable panels. Just add separators to your layouts. 27 | - Swap panels position using Drag and drop! 28 | 29 | ![Simple example](https://i.postimg.cc/W3gMfVCN/preview.gif) 30 | 31 | ![Drag and Drop](https://i.postimg.cc/Xvh8zQv8/cats-demo.gif) 32 | 33 | ## Basic Usage 34 | 35 | Components can be imported using ES6 modules syntax: 36 | 37 | ```javascript 38 | import { 39 | HorizontalLayout, 40 | VerticalLayout, 41 | Panel, 42 | Separator, 43 | Spacer, 44 | View 45 | } from "nice-react-layout"; 46 | ``` 47 | 48 | Creating a simple layout is as easy as this: 49 | 50 | ```javascript 51 | 52 | 53 | 54 | 55 | ``` 56 | 57 | It renders an horizontal layout with two panels of the same size (they have proportion=1 by default). Thanks to the 'mockup' prop it paints every panel with a random color, easing the layout prototyping process. Layouts get all the available space by default (in the parent element). If you want your layout to fill the viewport you can use the component. Like this: 58 | 59 | ```javascript 60 | 61 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 |
69 | In both horizontal and vertical layouts add the prop 'proportion' with the proportional part it takes from the available space. This example creates a typical sidebar + content layout: 70 | 71 | ```javascript 72 | 73 | 74 | 75 | 76 | ``` 77 | 78 |
79 | Do you want to add a separator between both panels? Use the Separator component: 80 | 81 | ```javascript 82 | 83 | 84 | 85 | 86 | 87 | ``` 88 | 89 |
90 | You can nest layouts. Let's add a vertical layout, with its own Separator, inside the right panel: 91 | 92 | ```javascript 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ``` 105 | 106 | To enable drag-and-drop feature, use the `draggable` and `droppable` props: 107 | 108 | ```javascript 109 | 110 | 111 | 112 | 113 | ``` 114 | 115 | For a complete example with every feature, see the src/demo/App.js file. Or just run `yarn start`. 116 | 117 | ## Components 118 | 119 | ### View 120 | 121 | The top-level component. It gets all the available viewport space. Use it if you want your layout to fill the viewport or pass width / height props. 122 | 123 | ##### Props 124 | 125 | | Prop | Description | 126 | | ------ | ------------------------------ | 127 | | width | view width (100vw by default) | 128 | | height | view height (100vh by default) | 129 | 130 | ### HorizontalLayout 131 | 132 | It creates an horizontal layout. This is shorthand component for: 133 | 134 | ```javascript 135 | 136 | ``` 137 | 138 | ### VerticalLayout 139 | 140 | It creates vertical layout layout. This is a shorthand component for: 141 | 142 | ```javascript 143 | 144 | ``` 145 | 146 | ### Layout 147 | 148 | Creates a layout. 149 | 150 | ##### Props 151 | 152 | | Prop | Description | 153 | | ------------ | ------------------------------------------- | 154 | | className | Top-level element class name | 155 | | collapseSize | Collapsible panels size when collapsed | 156 | | customCss | Custom layout CSS object | 157 | | mockup | Render in mockup mode | 158 | | onResize | Returns (layout, collapsedPanels) on resize | 159 | | orientation | 'vertical' or 'horizontal' | 160 | | reverse | Render layout in reverse order | 161 | 162 | ### Panel 163 | 164 | Here is where your content goes. 165 | If you are familiar with flexbox, this is like a "flex item" with a flex value of 1 by default. If not, don't worry, you don't need to know that :) 166 | 167 | ##### Props 168 | 169 | | Prop | Description | 170 | | ------------------------------ | ---------------------------------------------------------------------------------- | 171 | | centered | Center panel content | 172 | | className | Top-level element class name | 173 | | collapsible | The panel can be collapsed | 174 | | collapsed | Is the panel collapsed? | 175 | | collapseButtonClass | Adds a class to the collapse button | 176 | | collapseButtonContent | A String or element | 177 | | collapseButtonCollapsedContent | A String or element | 178 | | collapseButtonStyle | Inject inline CSS to the collapse button | 179 | | collapsePanel | Called when collapse button is clicked | 180 | | collapseSwitch | Custom collapse element (renders button if not provided) | 181 | | columns | Number of columns (uses CSS multiple columns) | 182 | | customCss | Custom panel CSS object (injects it as an inline style) | 183 | | draggable | Enable dragging | 184 | | droppable | Other panels can be dropped here | 185 | | minHeight | Minimum panel height | 186 | | minWidth | Minimum panel width | 187 | | mockup | Render in mockup mode | 188 | | orientation | 'vertical' or 'horizontal' | 189 | | proportion | Proportion it uses from the available space (default = 1) | 190 | | reverse | Render layout in reverse order | 191 | | showSize | Show panel size while dragging adjacent separators | 192 | | sidebar | Don't do much by it self. It is a requirement for sidebar props like 'collapsible' | 193 | 194 | ### Separator 195 | 196 | It separates panels and allows them to be resized. This is optional. 197 | 198 | ##### Props 199 | 200 | | Prop | Description | 201 | | ---------------------- | ----------------------------------------------------------- | 202 | | customCss | Custom separator CSS object (injects it as an inline style) | 203 | | defaultDblClickPos | Position where the separator goes when double-clicked | 204 | | disabled | Is disabled? | 205 | | onSeparatorDoubleClick | Action called when the separator is double-clicked | 206 | | onSeparatorMouseDown | Action called when the mouse is over the separator | 207 | 208 | ### Spacer 209 | 210 | It renders a blank space. Useful when you need to leave spaces between panels. 211 | 212 | ##### Props 213 | 214 | | Prop | Description | 215 | | ---- | ------------------------ | 216 | | size | Separator size in pixels | 217 | 218 | --- 219 | 220 | Like this project? ★ us on Github :) 221 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){if("object"===typeof exports&&"object"===typeof module)module.exports=t(require("react"),require("react-dom"));else if("function"===typeof define&&define.amd)define(["react","react-dom"],t);else{var n="object"===typeof exports?t(require("react"),require("react-dom")):t(e.react,e["react-dom"]);for(var r in n)("object"===typeof exports?exports:e)[r]=n[r]}}(this,function(e,t){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=7)}([function(t,n){t.exports=e},function(e,t,n){e.exports=n(10)()},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==typeof t&&"function"!==typeof t?e:t}function o(e,t){if("function"!==typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var a=n(0),l=n.n(a),u=n(1),c=n.n(u),s=n(12),f=n.n(s),d=function(){function e(e,t){for(var n=0;n=s?b=s:"horizontal"===r&&b>=c&&(b=c);var x=0,w=0,S="vertical"===r?s-v-y:c-g-y;m.forEach(function(e){x+=e});for(var O=b*x/S,E=0;E<=d;E++)w+=m[E];for(var j=O/w,k=0;k<=d;k++)m[k]=m[k]*j;e.setState({draggingSeparator:!0,layout:m,isBusyOnDragging:!0}),setTimeout(function(){e.setState({isBusyOnDragging:!1})},o)}},this.handleSeparatorMouseUp=function(){document.removeEventListener("mouseup",e.handleSeparatorMouseUp),document.removeEventListener("mousemove",e.handleSeparatorMouseMove),e.setState({draggingSeparator:!1,draggingSeparatorIndex:void 0})},this.startDraggingPanel=function(t){e.setState({draggingPanelIndex:t})},this.stopDraggingPanel=function(){var t=e.state,n=t.layoutOrdering,r=t.draggingPanelIndex,i=t.draggingPanelOverIndex;if(n&&null!==r&&void 0!==r&&null!==i&&void 0!==i){var o=p(n,r,i);e.setState({draggingPanelIndex:void 0,draggingPanelOverIndex:void 0,layoutOrdering:o})}else e.setState({draggingPanelIndex:void 0,draggingPanelOverIndex:void 0})}};t.a=h},function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"===typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==typeof t&&"function"!==typeof t?e:t}function o(e,t){if("function"!==typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var a=n(0),l=n.n(a),u=n(1),c=n.n(u),s=n(16),f=(n.n(s),function(){function e(e,t){for(var n=0;n0?t.style.transform="rotateZ(10deg)":e.clientX-n.lastDragX<0?t.style.transform="rotateZ(-10deg)":e.clientY-n.lastDragY>0?t.style.transform="rotateZ(-10deg)":e.clientY-n.lastDragY<0?t.style.transform="rotateZ(10deg)":t.style.transform="rotateZ(0deg)",n.lastDragX=e.clientX,n.lastDragY=e.clientY);var r=n.props;(0,r.draggingOver)(r.layoutIndex)}},n.startDragging=function(e){var t=n.panel.current.cloneNode(!0);t.id="panel-dragging-ghost",t.style.position="fixed",t.style.opacity=.5,t.style.width=n.panel.current.getBoundingClientRect().width+"px",t.style.height=n.panel.current.getBoundingClientRect().height+"px",t.style.transition="transform 0.2s",t.style.transformOrigin="0% 0%",t.style.zIndex=10,document.body.appendChild(t),document.addEventListener("mouseup",n.stopDragging);var r=n.props,i=r.layoutIndex;(0,r.startDragging)(i)},n.stopDragging=function(e){var t=document.getElementById("panel-dragging-ghost");t&&document.body.removeChild(t),t=document.getElementById("panel-dragging-ghost"),t&&document.body.removeChild(t),document.removeEventListener("mouseup",n.stopDragging),n.props.stopDragging()},n.toggleCollapse=function(){var e=n.props;(0,e.collapsePanel)(e.layoutIndex)},n.panel=l.a.createRef(),n.lastDragX,n.lastDragY,n}return o(t,e),f(t,[{key:"componentDidMount",value:function(){var e=this;this.props.draggable&&setTimeout(function(){e.panel.current.addEventListener("mousedown",e.startDragging)},400),this.props.droppable&&setTimeout(function(){e.panel.current.addEventListener("mousemove",e.draggingOver)},500)}},{key:"componentWillUnmount",value:function(){this.props.draggable&&(this.panel.current.removeEventListener("mousedown",this.startDragging),this.panel.current.removeEventListener("mouseup",this.cancelDragging)),this.props.droppable&&this.panel.current.removeEventListener("mousemove",this.draggingOver)}},{key:"render",value:function(){var e=this,t=this.props,n=t.centered,r=t.children,i=t.className,o=t.customCss,a=t.collapsed,u=t.collapsible,c=t.collapseButtonClass,f=t.collapseButtonContent,d=t.collapseButtonCollapsedContent,p=t.collapseSize,h=t.collapseButtonStyle,g=t.collapseSwitch,v=t.columns,y=t.draggingPanelIndex,m=t.draggingSeparator,b=t.flex,x=t.height,w=t.isDraggingOver,S=t.minHeight,O=t.minWidth,E=t.mockupStyle,j=t.order,k=t.showSize,z=t.orientation,P=t.render,_=t.sidebar,D=t.width,C={sidebarActions:{height:"20px"},centered:{display:"flex",justifyContent:"center",alignItems:"center"},horizontalPanel:{position:"relative",borderRight:_?"1px solid #445161":"none",cursor:m?"col-resize":"default",flex:null!==b&&void 0!==b?b:this.calculatePanelFlex(),minWidth:_&&u&&a?p:O,overflowX:"auto",overflowY:"auto",width:D||"auto"},draggingPanel:{cursor:"grab"},isDraggingOver:{filter:"brightness(120%)"},panelSize:{position:"absolute",background:"rgba(255, 255, 255, 0.5)",borderRadius:"4px",color:"#222222",fontSize:"11px",right:"5px",bottom:"5px",width:"90px",height:"15px",textAlign:"center"},verticalPanel:{position:"relative",borderRight:_?"1px solid #445161":"none",cursor:m?"row-resize":"default",flex:null!==b&&void 0!==b?b:this.calculatePanelFlex(),height:x||"auto",minHeight:_&&u&&a?p:S,overflowX:"hidden",overflowY:"auto"},collapsedPanel:{position:"relative",boxShadow:"1px 0px 4px black",flex:0}};return l.a.createElement(s.SizeMe,{monitorHeight:!0,refreshRate:200},function(t){var s=t.size;return l.a.createElement("div",{ref:e.panel,style:Object.assign({},{order:j,transition:m?"none":"flex 0.3s"},"vertical"===z?C.verticalPanel:C.horizontalPanel,n?C.centered:null,v?{columnCount:v}:null,o,a?C.collapsedPanel:null,E,w?C.isDraggingOver:null,null!==y&&void 0!==y?C.draggingPanel:null),className:i},u?l.a.createElement("div",{style:Object.assign({},C.sidebarActions,o&&o.sidebarActions?o.sidebarActions:null)},g||l.a.createElement("button",{style:h||{float:"right"},onClick:e.toggleCollapse,className:c},a?d:f)):null,P?P({size:s}):r,m&&k?l.a.createElement("div",{style:C.panelSize},s?Math.floor(s.width)+" x "+Math.floor(s.height):null):null)})}}]),t}(l.a.PureComponent);d.propTypes={id:c.a.string,centered:c.a.bool,children:c.a.node,className:c.a.string,customCss:c.a.object,draggable:c.a.bool,draggingOver:c.a.func,draggingPanelIndex:c.a.number,draggingSeparator:c.a.bool,droppable:c.a.bool,collapsed:c.a.bool,collapsible:c.a.bool,collapseButtonClass:c.a.string,collapseSize:c.a.string,collapseButtonStyle:c.a.object,collapseButtonContent:c.a.oneOfType([c.a.string,c.a.element]),collapseButtonCollapsedContent:c.a.oneOfType([c.a.string,c.a.element]),collapsePanel:c.a.func,collapseSwitch:c.a.element,columns:c.a.number,flex:c.a.oneOfType([c.a.string,c.a.number]),isDraggingOver:c.a.bool,layoutIndex:c.a.number,minHeight:c.a.number,minWidth:c.a.number,mockupStyle:c.a.object,order:c.a.number,proportion:c.a.number,render:c.a.func,startDragging:c.a.func,stopDragging:c.a.func,showSize:c.a.bool,sidebar:c.a.bool},d.defaultProps={id:"panel",centered:!1,className:"",collapseSize:"30px",collapseButtonContent:"Collapse",collapseButtonCollapsedContent:"Extend",columns:void 0,draggable:!1,droppable:!1,isDraggingOver:!1,proportion:1,render:void 0,showSize:!1},t.a=d},function(e,t,n){"use strict";(e.exports={}).forEach=function(e,t){for(var n=0;n4?e:void 0}())},r.isLegacyOpera=function(){return!!window.opera}},function(e,t,n){e.exports=n(8)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(9),i=n(35);n.d(t,"Layout",function(){return r.c}),n.d(t,"HorizontalLayout",function(){return r.b}),n.d(t,"VerticalLayout",function(){return r.h}),n.d(t,"Panel",function(){return r.d}),n.d(t,"Separator",function(){return r.e}),n.d(t,"Spacer",function(){return r.f}),n.d(t,"FormLayout",function(){return r.FormLayout}),n.d(t,"TableLayout",function(){return r.TableLayout}),n.d(t,"View",function(){return i.a})},function(e,t,n){"use strict";var r=n(2),i=n(14),o=n(15),a=n(4),l=n(33),u=n(34);n.d(t,"c",function(){return r.a}),n.d(t,"b",function(){return i.a}),n.d(t,"h",function(){return o.a}),n.d(t,"d",function(){return a.a}),n.d(t,"e",function(){return l.a}),n.d(t,"f",function(){return u.a})},function(e,t,n){"use strict";function r(){}var i=n(11);e.exports=function(){function e(e,t,n,r,o,a){if(a!==i){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t};return n.checkPropTypes=r,n.PropTypes=n,n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t,n){(function(e,n){var r,i;!function(){var o="object"==typeof self&&self.self===self&&self||"object"==typeof e&&e.global===e&&e||this||{},a=o._,l=Array.prototype,u=Object.prototype,c="undefined"!==typeof Symbol?Symbol.prototype:null,s=l.push,f=l.slice,d=u.toString,p=u.hasOwnProperty,h=Array.isArray,g=Object.keys,v=Object.create,y=function(){},m=function(e){return e instanceof m?e:this instanceof m?void(this._wrapped=e):new m(e)};"undefined"==typeof t||t.nodeType?o._=m:("undefined"!=typeof n&&!n.nodeType&&n.exports&&(t=n.exports=m),t._=m),m.VERSION="1.9.1";var b,x=function(e,t,n){if(void 0===t)return e;switch(null==n?3:n){case 1:return function(n){return e.call(t,n)};case 3:return function(n,r,i){return e.call(t,n,r,i)};case 4:return function(n,r,i,o){return e.call(t,n,r,i,o)}}return function(){return e.apply(t,arguments)}},w=function(e,t,n){return m.iteratee!==b?m.iteratee(e,t):null==e?m.identity:m.isFunction(e)?x(e,t,n):m.isObject(e)&&!m.isArray(e)?m.matcher(e):m.property(e)};m.iteratee=b=function(e,t){return w(e,t,1/0)};var S=function(e,t){return t=null==t?e.length-1:+t,function(){for(var n=Math.max(arguments.length-t,0),r=Array(n),i=0;i=0&&t<=z};m.each=m.forEach=function(e,t,n){t=x(t,n);var r,i;if(_(e))for(r=0,i=e.length;r0?0:a-1;for(i||(r=t[o?o[l]:l],l+=e);l>=0&&l=3;return t(e,x(n,i,4),r,o)}};m.reduce=m.foldl=m.inject=D(1),m.reduceRight=m.foldr=D(-1),m.find=m.detect=function(e,t,n){var r=_(e)?m.findIndex:m.findKey,i=r(e,t,n);if(void 0!==i&&-1!==i)return e[i]},m.filter=m.select=function(e,t,n){var r=[];return t=w(t,n),m.each(e,function(e,n,i){t(e,n,i)&&r.push(e)}),r},m.reject=function(e,t,n){return m.filter(e,m.negate(w(t)),n)},m.every=m.all=function(e,t,n){t=w(t,n);for(var r=!_(e)&&m.keys(e),i=(r||e).length,o=0;o=0},m.invoke=S(function(e,t,n){var r,i;return m.isFunction(t)?i=t:m.isArray(t)&&(r=t.slice(0,-1),t=t[t.length-1]),m.map(e,function(e){var o=i;if(!o){if(r&&r.length&&(e=k(e,r)),null==e)return;o=e[t]}return null==o?o:o.apply(e,n)})}),m.pluck=function(e,t){return m.map(e,m.property(t))},m.where=function(e,t){return m.filter(e,m.matcher(t))},m.findWhere=function(e,t){return m.find(e,m.matcher(t))},m.max=function(e,t,n){var r,i,o=-1/0,a=-1/0;if(null==t||"number"==typeof t&&"object"!=typeof e[0]&&null!=e){e=_(e)?e:m.values(e);for(var l=0,u=e.length;lo&&(o=r)}else t=w(t,n),m.each(e,function(e,n,r){((i=t(e,n,r))>a||i===-1/0&&o===-1/0)&&(o=e,a=i)});return o},m.min=function(e,t,n){var r,i,o=1/0,a=1/0;if(null==t||"number"==typeof t&&"object"!=typeof e[0]&&null!=e){e=_(e)?e:m.values(e);for(var l=0,u=e.length;lr||void 0===n)return 1;if(n0?0:i-1;o>=0&&o0?a=o>=0?o:Math.max(o+l,a):l=o>=0?Math.min(o+1,l):o+l+1;else if(n&&o&&l)return o=n(r,i),r[o]===i?o:-1;if(i!==i)return o=t(f.call(r,a,l),m.isNaN),o>=0?o+a:-1;for(o=e>0?a:l-1;o>=0&&ot?(r&&(clearTimeout(r),r=null),l=c,a=e.apply(i,o),r||(i=o=null)):r||!1===n.trailing||(r=setTimeout(u,s)),a};return c.cancel=function(){clearTimeout(r),l=0,r=i=o=null},c},m.debounce=function(e,t,n){var r,i,o=function(t,n){r=null,n&&(i=e.apply(t,n))},a=S(function(a){if(r&&clearTimeout(r),n){var l=!r;r=setTimeout(o,t),l&&(i=e.apply(this,a))}else r=m.delay(o,t,this,a);return i});return a.cancel=function(){clearTimeout(r),r=null},a},m.wrap=function(e,t){return m.partial(t,e)},m.negate=function(e){return function(){return!e.apply(this,arguments)}},m.compose=function(){var e=arguments,t=e.length-1;return function(){for(var n=t,r=e[t].apply(this,arguments);n--;)r=e[n].call(this,r);return r}},m.after=function(e,t){return function(){if(--e<1)return t.apply(this,arguments)}},m.before=function(e,t){var n;return function(){return--e>0&&(n=t.apply(this,arguments)),e<=1&&(t=null),n}},m.once=m.partial(m.before,2),m.restArguments=S;var L=!{toString:null}.propertyIsEnumerable("toString"),R=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],H=function(e,t){var n=R.length,r=e.constructor,i=m.isFunction(r)&&r.prototype||u,o="constructor";for(j(e,o)&&!m.contains(t,o)&&t.push(o);n--;)(o=R[n])in e&&e[o]!==i[o]&&!m.contains(t,o)&&t.push(o)};m.keys=function(e){if(!m.isObject(e))return[];if(g)return g(e);var t=[];for(var n in e)j(e,n)&&t.push(n);return L&&H(e,t),t},m.allKeys=function(e){if(!m.isObject(e))return[];var t=[];for(var n in e)t.push(n);return L&&H(e,t),t},m.values=function(e){for(var t=m.keys(e),n=t.length,r=Array(n),i=0;i1&&(r=x(r,t[1])),t=m.allKeys(e)):(r=W,t=I(t,!1,!1),e=Object(e));for(var i=0,o=t.length;i1&&(n=t[1])):(t=m.map(I(t,!1,!1),String),r=function(e,n){return!m.contains(t,n)}),m.pick(e,r,n)}),m.defaults=B(m.allKeys,!0),m.create=function(e,t){var n=O(e);return t&&m.extendOwn(n,t),n},m.clone=function(e){return m.isObject(e)?m.isArray(e)?e.slice():m.extend({},e):e},m.tap=function(e,t){return t(e),e},m.isMatch=function(e,t){var n=m.keys(t),r=n.length;if(null==e)return!r;for(var i=Object(e),o=0;o":">",'"':""","'":"'","`":"`"},U=m.invert(X),$=function(e){var t=function(t){return e[t]},n="(?:"+m.keys(e).join("|")+")",r=RegExp(n),i=RegExp(n,"g");return function(e){return e=null==e?"":""+e,r.test(e)?e.replace(i,t):e}};m.escape=$(X),m.unescape=$(U),m.result=function(e,t,n){m.isArray(t)||(t=[t]);var r=t.length;if(!r)return m.isFunction(n)?n.call(e):n;for(var i=0;i/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var V=/(.)^/,Z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},G=/\\|'|\r|\n|\u2028|\u2029/g,J=function(e){return"\\"+Z[e]};m.template=function(e,t,n){!t&&n&&(t=n),t=m.defaults({},t,m.templateSettings);var r=RegExp([(t.escape||V).source,(t.interpolate||V).source,(t.evaluate||V).source].join("|")+"|$","g"),i=0,o="__p+='";e.replace(r,function(t,n,r,a,l){return o+=e.slice(i,l).replace(G,J),i=l+t.length,n?o+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":r?o+="'+\n((__t=("+r+"))==null?'':__t)+\n'":a&&(o+="';\n"+a+"\n__p+='"),t}),o+="';\n",t.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";var a;try{a=new Function(t.variable||"obj","_",o)}catch(e){throw e.source=o,e}var l=function(e){return a.call(this,e,m)};return l.source="function("+(t.variable||"obj")+"){\n"+o+"}",l},m.chain=function(e){var t=m(e);return t._chain=!0,t};var Q=function(e,t){return e._chain?m(t).chain():t};m.mixin=function(e){return m.each(m.functions(e),function(t){var n=m[t]=e[t];m.prototype[t]=function(){var e=[this._wrapped];return s.apply(e,arguments),Q(this,n.apply(m,e))}}),m},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=l[e];m.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),"shift"!==e&&"splice"!==e||0!==n.length||delete n[0],Q(this,n)}}),m.each(["concat","join","slice"],function(e){var t=l[e];m.prototype[e]=function(){return Q(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return String(this._wrapped)},r=[],void 0!==(i=function(){return m}.apply(t,r))&&(n.exports=i)}()}).call(t,n(3),n(13)(e))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t,n){"use strict";function r(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}var i=n(0),o=n.n(i),a=n(2),l=function(e){var t=r(e,[]);return o.a.createElement(a.a,Object.assign({},t,{orientation:"horizontal"}))};t.a=l},function(e,t,n){"use strict";function r(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}var i=n(0),o=n.n(i),a=n(2),l=function(e){var t=r(e,[]);return o.a.createElement(a.a,Object.assign({},t,{orientation:"vertical"}))};t.a=l},function(e,t,n){"use strict";function r(e){return e&&"object"===typeof e&&"default"in e?e.default:e}function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"scroll";return y[e]||(y[e]=u({strategy:e})),y[e]}function o(e){return e.displayName||e.name||"Component"}function a(e){var t=e.className,n=e.style,r={};return t||n?(t&&(r.className=t),n&&(r.style=n)):r.style={width:"100%",height:"100%"},s.createElement("div",r)}function l(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:E,t=e.monitorWidth,n=void 0===t?E.monitorWidth:t,r=e.monitorHeight,a=void 0===r?E.monitorHeight:r,u=e.monitorPosition,c=void 0===u?E.monitorPosition:u,v=e.refreshRate,y=void 0===v?E.refreshRate:v,S=e.refreshMode,j=void 0===S?E.refreshMode:S,z=e.noPlaceholder,P=void 0===z?E.noPlaceholder:z,_=e.resizeDetectorStrategy,D=void 0===_?E.resizeDetectorStrategy:_;p(n||a||c,'You have to monitor at least one of the width, height, or position when using "sizeMe"'),p(y>=16,"It is highly recommended that you don't put your refreshRate lower than 16 as this may cause layout thrashing."),p("throttle"===j||"debounce"===j,'The refreshMode should have a value of "throttle" or "debounce"');var C="throttle"===j?h:g;return function(e){var t=k(e),r=function(e){function r(){var e,t,i,o;m(this,r);for(var l=arguments.length,u=Array(l),s=0;s=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n},O=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==typeof t&&"function"!==typeof t?e:t},E={monitorWidth:!0,monitorHeight:!1,monitorPosition:!1,refreshRate:16,refreshMode:"throttle",noPlaceholder:!1,resizeDetectorStrategy:"scroll"},j=function(e){function t(){return m(this,t),O(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return w(t,e),b(t,[{key:"render",value:function(){return c.Children.only(this.props.children)}}]),t}(c.Component);j.displayName="SizeMeReferenceWrapper",j.propTypes={children:f.element.isRequired},a.displayName="SizeMePlaceholder",a.propTypes={className:f.string,style:f.object};var k=function(e){function t(t){var n=t.explicitRef,r=t.className,i=t.style,o=t.size,l=t.disablePlaceholder,u=(t.onSize,S(t,["explicitRef","className","style","size","disablePlaceholder","onSize"])),c=null==o||null==o.width&&null==o.height&&null==o.position,f=c&&!l,d={className:r,style:i};null!=o&&(d.size=o);var p=f?s.createElement(a,{className:r,style:i}):s.createElement(e,x({},d,u));return s.createElement(j,{ref:n},p)}return t.displayName="SizeMeRenderer("+o(e)+")",t.propTypes={explicitRef:f.func.isRequired,className:f.string,style:f.object,size:f.shape({width:f.number,height:f.number}),disablePlaceholder:f.bool,onSize:f.func},t};l.enableSSRBehaviour=!1,l.noPlaceholders=!1;var z=function(e){function t(e){m(this,t);var n=O(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));P.call(n);var r=(e.children,e.render,S(e,["children","render"]));return n.createComponent(r),n.state={size:{width:void 0,height:void 0}},n}return w(t,e),b(t,[{key:"componentWillReceiveProps",value:function(e){var t=this.props,n=(t.children,t.render,S(t,["children","render"])),r=(e.children,e.render,S(e,["children","render"]));v(n,r)||this.createComponent(r)}},{key:"render",value:function(){var e=this.SizeAware,t=this.props.children||this.props.render;return s.createElement(e,{onSize:this.onSize},t({size:this.state.size}))}}]),t}(c.Component);z.propTypes={children:f.func,render:f.func},z.defaultProps={children:void 0,render:void 0};var P=function(){var e=this;this.createComponent=function(t){e.SizeAware=l(t)(function(e){return e.children})},this.onSize=function(t){return e.setState({size:t})}};l.SizeMe=z,l.withSize=l,e.exports=l},function(e,t,n){"use strict";function r(e){return Array.isArray(e)||void 0!==e.length}function i(e){if(Array.isArray(e))return e;var t=[];return l(e,function(e){t.push(e)}),t}function o(e){return e&&1===e.nodeType}function a(e,t,n){var r=e[t];return void 0!==r&&null!==r||void 0===n?r:n}var l=n(5).forEach,u=n(18),c=n(19),s=n(20),f=n(21),d=n(22),p=n(6),h=n(23),g=n(25),v=n(26),y=n(27);e.exports=function(e){function t(e,t,n){function u(e){var t=j.get(e);l(t,function(t){t(e)})}function c(e,t,n){j.add(t,n),e&&n(t)}if(n||(n=t,t=e,e={}),!t)throw new Error("At least one element required.");if(!n)throw new Error("Listener required.");if(o(t))t=[t];else{if(!r(t))return w.error("Invalid arguments. Must be a DOM element or a collection of DOM elements.");t=i(t)}var s=0,f=a(e,"callOnAdd",O.callOnAdd),d=a(e,"onReady",function(){}),p=a(e,"debug",O.debug);l(t,function(e){g.getState(e)||(g.initState(e),m.set(e));var r=m.get(e);if(p&&w.log("Attaching listener to element",r,e),!k.isDetectable(e))return p&&w.log(r,"Not detectable."),k.isBusy(e)?(p&&w.log(r,"System busy making it detectable"),c(f,e,n),_[r]=_[r]||[],void _[r].push(function(){++s===t.length&&d()})):(p&&w.log(r,"Making detectable..."),k.markBusy(e,!0),E.makeDetectable({debug:p},e,function(e){if(p&&w.log(r,"onElementDetectable"),g.getState(e)){k.markAsDetectable(e),k.markBusy(e,!1),E.addListener(e,u),c(f,e,n);var i=g.getState(e);if(i&&i.startSize){var o=e.offsetWidth,a=e.offsetHeight;i.startSize.width===o&&i.startSize.height===a||u(e)}_[r]&&l(_[r],function(e){e()})}else p&&w.log(r,"Element uninstalled before being detectable.");delete _[r],++s===t.length&&d()}));p&&w.log(r,"Already detecable, adding listener."),c(f,e,n),s++}),s===t.length&&d()}function n(e){if(!e)return w.error("At least one element is required.");if(o(e))e=[e];else{if(!r(e))return w.error("Invalid arguments. Must be a DOM element or a collection of DOM elements.");e=i(e)}l(e,function(e){j.removeAllListeners(e),E.uninstall(e),g.cleanState(e)})}e=e||{};var m;if(e.idHandler)m={get:function(t){return e.idHandler.get(t,!0)},set:e.idHandler.set};else{var b=s(),x=f({idGenerator:b,stateHandler:g});m=x}var w=e.reporter;if(!w){w=d(!1===w)}var S=a(e,"batchProcessor",h({reporter:w})),O={};O.callOnAdd=!!a(e,"callOnAdd",!0),O.debug=!!a(e,"debug",!1);var E,j=c(m),k=u({stateHandler:g}),z=a(e,"strategy","object"),P={reporter:w,batchProcessor:S,stateHandler:g,idHandler:m};if("scroll"===z&&(p.isLegacyOpera()?(w.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy."),z="object"):p.isIE(9)&&(w.warn("Scroll strategy is not supported on IE9. Changing to object strategy."),z="object")),"scroll"===z)E=y(P);else{if("object"!==z)throw new Error("Invalid strategy name: "+z);E=v(P)}var _={};return{listenTo:t,removeListener:j.removeListener,removeAllListeners:j.removeAllListeners,uninstall:n}}},function(e,t,n){"use strict";e.exports=function(e){function t(e){var t=o(e);return t&&!!t.isDetectable}function n(e){o(e).isDetectable=!0}function r(e){return!!o(e).busy}function i(e,t){o(e).busy=!!t}var o=e.stateHandler.getState;return{isDetectable:t,markAsDetectable:n,isBusy:r,markBusy:i}}},function(e,t,n){"use strict";e.exports=function(e){function t(t){var n=e.get(t);return void 0===n?[]:o[n]||[]}function n(t,n){var r=e.get(t);o[r]||(o[r]=[]),o[r].push(n)}function r(e,n){for(var r=t(e),i=0,o=r.length;io?o=e:e div::-webkit-scrollbar { display: none; }\n\n",i+="."+r+" { -webkit-animation-duration: 0.1s; animation-duration: 0.1s; -webkit-animation-name: "+n+"; animation-name: "+n+"; }\n",i+="@-webkit-keyframes "+n+" { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }\n",i+="@keyframes "+n+" { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }",function(t,n){n=n||function(e){document.head.appendChild(e)};var r=document.createElement("style");r.innerHTML=t,r.id=e,n(r)}(i)}}("erd_scroll_detection_scrollbar_style",g),{makeDetectable:u,addListener:l,uninstall:c}}},function(e,n){e.exports=t},function(e,t,n){"use strict";var r=function(e,t,n,r,i,o,a,l){if(!e){var u;if(void 0===t)u=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,i,o,a,l],s=0;u=new Error(t.replace(/%s/g,function(){return c[s++]})),u.name="Invariant Violation"}throw u.framesToPop=1,u}};e.exports=r},function(e,t,n){(function(t){function n(e,t,n){function r(t){var n=g,r=v;return g=v=void 0,E=t,m=e.apply(r,n)}function o(e){return E=e,b=setTimeout(s,t),j?r(e):m}function a(e){var n=e-x,r=e-E,i=t-n;return k?S(i,y-r):i}function c(e){var n=e-x,r=e-E;return void 0===x||n>=t||n<0||k&&r>=y}function s(){var e=O();if(c(e))return f(e);b=setTimeout(s,a(e))}function f(e){return b=void 0,z&&g?r(e):(g=v=void 0,m)}function d(){void 0!==b&&clearTimeout(b),E=0,g=x=v=b=void 0}function p(){return void 0===b?m:f(O())}function h(){var e=O(),n=c(e);if(g=arguments,v=this,x=e,n){if(void 0===b)return o(x);if(k)return b=setTimeout(s,t),r(x)}return void 0===b&&(b=setTimeout(s,t)),m}var g,v,y,m,b,x,E=0,j=!1,k=!1,z=!0;if("function"!=typeof e)throw new TypeError(u);return t=l(t)||0,i(n)&&(j=!!n.leading,k="maxWait"in n,y=k?w(l(n.maxWait)||0,t):y,z="trailing"in n?!!n.trailing:z),h.cancel=d,h.flush=p,h}function r(e,t,r){var o=!0,a=!0;if("function"!=typeof e)throw new TypeError(u);return i(r)&&(o="leading"in r?!!r.leading:o,a="trailing"in r?!!r.trailing:a),n(e,t,{leading:o,maxWait:t,trailing:a})}function i(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function o(e){return!!e&&"object"==typeof e}function a(e){return"symbol"==typeof e||o(e)&&x.call(e)==s}function l(e){if("number"==typeof e)return e;if(a(e))return c;if(i(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=i(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(f,"");var n=p.test(e);return n||h.test(e)?g(e.slice(2),n?2:8):d.test(e)?c:+e}var u="Expected a function",c=NaN,s="[object Symbol]",f=/^\s+|\s+$/g,d=/^[-+]0x[0-9a-f]+$/i,p=/^0b[01]+$/i,h=/^0o[0-7]+$/i,g=parseInt,v="object"==typeof t&&t&&t.Object===Object&&t,y="object"==typeof self&&self&&self.Object===Object&&self,m=v||y||Function("return this")(),b=Object.prototype,x=b.toString,w=Math.max,S=Math.min,O=function(){return m.Date.now()};e.exports=r}).call(t,n(3))},function(e,t,n){(function(t){function n(e,t,n){function i(t){var n=g,r=v;return g=v=void 0,E=t,m=e.apply(r,n)}function o(e){return E=e,b=setTimeout(s,t),j?i(e):m}function u(e){var n=e-O,r=e-E,i=t-n;return k?w(i,y-r):i}function c(e){var n=e-O,r=e-E;return void 0===O||n>=t||n<0||k&&r>=y}function s(){var e=S();if(c(e))return f(e);b=setTimeout(s,u(e))}function f(e){return b=void 0,z&&g?i(e):(g=v=void 0,m)}function d(){void 0!==b&&clearTimeout(b),E=0,g=O=v=b=void 0}function p(){return void 0===b?m:f(S())}function h(){var e=S(),n=c(e);if(g=arguments,v=this,O=e,n){if(void 0===b)return o(O);if(k)return b=setTimeout(s,t),i(O)}return void 0===b&&(b=setTimeout(s,t)),m}var g,v,y,m,b,O,E=0,j=!1,k=!1,z=!0;if("function"!=typeof e)throw new TypeError(l);return t=a(t)||0,r(n)&&(j=!!n.leading,k="maxWait"in n,y=k?x(a(n.maxWait)||0,t):y,z="trailing"in n?!!n.trailing:z),h.cancel=d,h.flush=p,h}function r(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function i(e){return!!e&&"object"==typeof e}function o(e){return"symbol"==typeof e||i(e)&&b.call(e)==c}function a(e){if("number"==typeof e)return e;if(o(e))return u;if(r(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=r(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(s,"");var n=d.test(e);return n||p.test(e)?h(e.slice(2),n?2:8):f.test(e)?u:+e}var l="Expected a function",u=NaN,c="[object Symbol]",s=/^\s+|\s+$/g,f=/^[-+]0x[0-9a-f]+$/i,d=/^0b[01]+$/i,p=/^0o[0-7]+$/i,h=parseInt,g="object"==typeof t&&t&&t.Object===Object&&t,v="object"==typeof self&&self&&self.Object===Object&&self,y=g||v||Function("return this")(),m=Object.prototype,b=m.toString,x=Math.max,w=Math.min,S=function(){return y.Date.now()};e.exports=n}).call(t,n(3))},function(e,t){e.exports=function(e,t,n,r){var i=n?n.call(r,e,t):void 0;if(void 0!==i)return!!i;if(e===t)return!0;if("object"!==typeof e||!e||"object"!==typeof t||!t)return!1;var o=Object.keys(e),a=Object.keys(t);if(o.length!==a.length)return!1;for(var l=Object.prototype.hasOwnProperty.bind(t),u=0;u { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | PUBLIC_URL: publicUrl, 77 | } 78 | ); 79 | // Stringify all values so we can feed into Webpack DefinePlugin 80 | const stringified = { 81 | 'process.env': Object.keys(raw).reduce((env, key) => { 82 | env[key] = JSON.stringify(raw[key]); 83 | return env; 84 | }, {}), 85 | }; 86 | 87 | return { raw, stringified }; 88 | } 89 | 90 | module.exports = getClientEnvironment; 91 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekros/nice-react-layout/4d208b018fa4d6806a91f4a1f42eb1e94f806be1/public/logo.png -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 29 | 36 | 40 | 44 | 51 | 58 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const chalk = require('chalk'); 18 | const fs = require('fs-extra'); 19 | const webpack = require('webpack'); 20 | const config = require('../config/webpack.config.prod'); 21 | const paths = require('../config/paths'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 24 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 25 | const printBuildError = require('react-dev-utils/printBuildError'); 26 | 27 | const measureFileSizesBeforeBuild = 28 | FileSizeReporter.measureFileSizesBeforeBuild; 29 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 30 | 31 | // These sizes are pretty large. We'll warn for bundles exceeding them. 32 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 33 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 34 | 35 | // Warn and crash if required files are missing 36 | if (!checkRequiredFiles([paths.appLibIndexJs])) { // CRL: Updated with library index file 37 | process.exit(1); 38 | } 39 | 40 | // First, read the current file sizes in build directory. 41 | // This lets us display how much they changed later. 42 | measureFileSizesBeforeBuild(paths.appBuild) 43 | .then(previousFileSizes => { 44 | // Remove all content but keep the directory so that 45 | // if you're in it, you don't end up in Trash 46 | fs.emptyDirSync(paths.appBuild); 47 | 48 | // Start the webpack build 49 | return build(previousFileSizes); 50 | }) 51 | .then( 52 | ({ stats, previousFileSizes, warnings }) => { 53 | if (warnings.length) { 54 | console.log(chalk.yellow('Compiled with warnings.\n')); 55 | console.log(warnings.join('\n\n')); 56 | console.log( 57 | '\nSearch for the ' + 58 | chalk.underline(chalk.yellow('keywords')) + 59 | ' to learn more about each warning.' 60 | ); 61 | console.log( 62 | 'To ignore, add ' + 63 | chalk.cyan('// eslint-disable-next-line') + 64 | ' to the line before.\n' 65 | ); 66 | } else { 67 | console.log(chalk.green('Compiled successfully.\n')); 68 | } 69 | 70 | console.log('File sizes after gzip:\n'); 71 | printFileSizesAfterBuild( 72 | stats, 73 | previousFileSizes, 74 | paths.appBuild, 75 | WARN_AFTER_BUNDLE_GZIP_SIZE, 76 | WARN_AFTER_CHUNK_GZIP_SIZE 77 | ); 78 | console.log(); 79 | }, 80 | err => { 81 | console.log(chalk.red('Failed to compile.\n')); 82 | printBuildError(err); 83 | process.exit(1); 84 | } 85 | ); 86 | 87 | // Create the production build and print the deployment instructions. 88 | function build(previousFileSizes) { 89 | console.log('Creating an optimized production build...'); 90 | 91 | let compiler = webpack(config); 92 | return new Promise((resolve, reject) => { 93 | compiler.run((err, stats) => { 94 | if (err) { 95 | return reject(err); 96 | } 97 | const messages = formatWebpackMessages(stats.toJson({}, true)); 98 | if (messages.errors.length) { 99 | // Only keep the first error. Others are often indicative 100 | // of the same problem, but confuse the reader with noise. 101 | if (messages.errors.length > 1) { 102 | messages.errors.length = 1; 103 | } 104 | return reject(new Error(messages.errors.join('\n\n'))); 105 | } 106 | if ( 107 | process.env.CI && 108 | (typeof process.env.CI !== 'string' || 109 | process.env.CI.toLowerCase() !== 'false') && 110 | messages.warnings.length 111 | ) { 112 | console.log( 113 | chalk.yellow( 114 | '\nTreating warnings as errors because process.env.CI = true.\n' + 115 | 'Most CI servers set it automatically.\n' 116 | ) 117 | ); 118 | return reject(new Error(messages.warnings.join('\n\n'))); 119 | } 120 | return resolve({ 121 | stats, 122 | previousFileSizes, 123 | warnings: messages.warnings, 124 | }); 125 | }); 126 | }); 127 | } 128 | -------------------------------------------------------------------------------- /scripts/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const chalk = require('chalk'); 18 | const fs = require('fs-extra'); 19 | const webpack = require('webpack'); 20 | const config = require('../config/webpack.config.demo'); 21 | const paths = require('../config/paths'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 24 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 25 | const printBuildError = require('react-dev-utils/printBuildError'); 26 | 27 | const measureFileSizesBeforeBuild = 28 | FileSizeReporter.measureFileSizesBeforeBuild; 29 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 30 | 31 | // These sizes are pretty large. We'll warn for bundles exceeding them. 32 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 33 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 34 | 35 | // Warn and crash if required files are missing 36 | if (!checkRequiredFiles([paths.appDemoIndexJs])) { // CRL: Updated with library index file 37 | process.exit(1); 38 | } 39 | 40 | // First, read the current file sizes in build directory. 41 | // This lets us display how much they changed later. 42 | measureFileSizesBeforeBuild(paths.appDemoBuild) 43 | .then(previousFileSizes => { 44 | // Remove all content but keep the directory so that 45 | // if you're in it, you don't end up in Trash 46 | fs.emptyDirSync(paths.appDemoBuild); 47 | 48 | // Start the webpack build 49 | return build(previousFileSizes); 50 | }) 51 | .then( 52 | ({ stats, previousFileSizes, warnings }) => { 53 | if (warnings.length) { 54 | console.log(chalk.yellow('Compiled with warnings.\n')); 55 | console.log(warnings.join('\n\n')); 56 | console.log( 57 | '\nSearch for the ' + 58 | chalk.underline(chalk.yellow('keywords')) + 59 | ' to learn more about each warning.' 60 | ); 61 | console.log( 62 | 'To ignore, add ' + 63 | chalk.cyan('// eslint-disable-next-line') + 64 | ' to the line before.\n' 65 | ); 66 | } else { 67 | console.log(chalk.green('Compiled successfully.\n')); 68 | } 69 | 70 | console.log('File sizes after gzip:\n'); 71 | printFileSizesAfterBuild( 72 | stats, 73 | previousFileSizes, 74 | paths.appDemoBuild, 75 | WARN_AFTER_BUNDLE_GZIP_SIZE, 76 | WARN_AFTER_CHUNK_GZIP_SIZE 77 | ); 78 | console.log(); 79 | }, 80 | err => { 81 | console.log(chalk.red('Failed to compile.\n')); 82 | printBuildError(err); 83 | process.exit(1); 84 | } 85 | ); 86 | 87 | // Create the production build and print the deployment instructions. 88 | function build(previousFileSizes) { 89 | console.log('Creating a build of the demo app...'); 90 | 91 | let compiler = webpack(config); 92 | return new Promise((resolve, reject) => { 93 | compiler.run((err, stats) => { 94 | if (err) { 95 | return reject(err); 96 | } 97 | const messages = formatWebpackMessages(stats.toJson({}, true)); 98 | if (messages.errors.length) { 99 | // Only keep the first error. Others are often indicative 100 | // of the same problem, but confuse the reader with noise. 101 | if (messages.errors.length > 1) { 102 | messages.errors.length = 1; 103 | } 104 | return reject(new Error(messages.errors.join('\n\n'))); 105 | } 106 | if ( 107 | process.env.CI && 108 | (typeof process.env.CI !== 'string' || 109 | process.env.CI.toLowerCase() !== 'false') && 110 | messages.warnings.length 111 | ) { 112 | console.log( 113 | chalk.yellow( 114 | '\nTreating warnings as errors because process.env.CI = true.\n' + 115 | 'Most CI servers set it automatically.\n' 116 | ) 117 | ); 118 | return reject(new Error(messages.warnings.join('\n\n'))); 119 | } 120 | return resolve({ 121 | stats, 122 | previousFileSizes, 123 | warnings: messages.warnings, 124 | }); 125 | }); 126 | }); 127 | } 128 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const fs = require('fs'); 18 | const chalk = require('chalk'); 19 | const webpack = require('webpack'); 20 | const WebpackDevServer = require('webpack-dev-server'); 21 | const clearConsole = require('react-dev-utils/clearConsole'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const { 24 | choosePort, 25 | createCompiler, 26 | prepareProxy, 27 | prepareUrls, 28 | } = require('react-dev-utils/WebpackDevServerUtils'); 29 | const openBrowser = require('react-dev-utils/openBrowser'); 30 | const paths = require('../config/paths'); 31 | const config = require('../config/webpack.config.dev'); 32 | const createDevServerConfig = require('../config/webpackDevServer.config'); 33 | 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | const isInteractive = process.stdout.isTTY; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // Tools like Cloud9 rely on this. 43 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 44 | const HOST = process.env.HOST || '0.0.0.0'; 45 | 46 | // We attempt to use the default port but if it is busy, we offer the user to 47 | // run on a different port. `detect()` Promise resolves to the next free port. 48 | choosePort(HOST, DEFAULT_PORT) 49 | .then(port => { 50 | if (port == null) { 51 | // We have not found a port. 52 | return; 53 | } 54 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 55 | const appName = require(paths.appPackageJson).name; 56 | const urls = prepareUrls(protocol, HOST, port); 57 | // Create a webpack compiler that is configured with custom messages. 58 | const compiler = createCompiler(webpack, config, appName, urls, useYarn); 59 | // Load proxy config 60 | const proxySetting = require(paths.appPackageJson).proxy; 61 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 62 | // Serve webpack assets generated by the compiler over a web sever. 63 | const serverConfig = createDevServerConfig( 64 | proxyConfig, 65 | urls.lanUrlForConfig 66 | ); 67 | const devServer = new WebpackDevServer(compiler, serverConfig); 68 | // Launch WebpackDevServer. 69 | devServer.listen(port, HOST, err => { 70 | if (err) { 71 | return console.log(err); 72 | } 73 | if (isInteractive) { 74 | clearConsole(); 75 | } 76 | console.log(chalk.cyan('Starting the development server...\n')); 77 | openBrowser(urls.localUrlForBrowser); 78 | }); 79 | 80 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 81 | process.on(sig, function() { 82 | devServer.close(); 83 | process.exit(); 84 | }); 85 | }); 86 | }) 87 | .catch(err => { 88 | if (err && err.message) { 89 | console.log(err.message); 90 | } 91 | process.exit(1); 92 | }); 93 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/demo/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | justify-content: center; 4 | margin: 0; 5 | } 6 | 7 | li > a { 8 | color: gray; 9 | text-decoration: none; 10 | } 11 | 12 | li > a.is-active { 13 | color: black; 14 | font-weight: bold; 15 | } 16 | 17 | li > a:hover { 18 | color: #222222; 19 | } 20 | -------------------------------------------------------------------------------- /src/demo/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import ScrollspyNav from "react-scrollspy-nav"; 3 | import { 4 | HorizontalLayout, 5 | VerticalLayout, 6 | Panel, 7 | Separator 8 | } from "../lib/components/Layout"; 9 | import "./App.css"; 10 | import version from "../version.json"; 11 | 12 | const mockupContent = ( 13 |

14 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy 15 | nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut 16 | wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit 17 | lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure 18 | dolor in hendrerit in vulputate velit esse molestie consequat, vel illum 19 | dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio 20 | dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te 21 | feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option 22 | congue nihil imperdiet doming id quod mazim placerat facer possim assum. 23 |

24 | ); 25 | 26 | const catUrls = [ 27 | `http://placekitten.com/${300 + Math.floor(Math.random() * 5)}/${200 + 28 | Math.floor(Math.random() * 5)}`, 29 | `http://placekitten.com/${300 + Math.floor(Math.random() * 5)}/${200 + 30 | Math.floor(Math.random() * 5)}`, 31 | `http://placekitten.com/${300 + Math.floor(Math.random() * 5)}/${200 + 32 | Math.floor(Math.random() * 5)}`, 33 | `http://placekitten.com/${300 + Math.floor(Math.random() * 5)}/${200 + 34 | Math.floor(Math.random() * 5)}`, 35 | `http://placekitten.com/${300 + Math.floor(Math.random() * 5)}/${200 + 36 | Math.floor(Math.random() * 5)}`, 37 | `http://placekitten.com/${300 + Math.floor(Math.random() * 5)}/${200 + 38 | Math.floor(Math.random() * 5)}` 39 | ]; 40 | 41 | const catPanel = (proportion, imageIndex) => ( 42 |
} 55 | /> 56 | ); 57 | 58 | const Header = ({ backgroundColor, message, niceReactLayoutProps }) => ( 59 |
{message}
60 | ); 61 | 62 | const CustomPanel = ({ children, niceReactLayoutProps: { mockupStyle } }) => ( 63 |
69 | {children} 70 |
71 | ); 72 | 73 | class App extends Component { 74 | constructor(props) { 75 | super(props); 76 | this.state = { 77 | dynamicPanels: [ 78 | { 79 | proportion: 1 80 | }, 81 | { proportion: 2 } 82 | ] 83 | }; 84 | } 85 | addSmallPanel = () => { 86 | this.setState({ 87 | dynamicPanels: this.state.dynamicPanels.concat({ proportion: 1 }) 88 | }); 89 | }; 90 | 91 | addBigPanel = () => { 92 | this.setState({ 93 | dynamicPanels: this.state.dynamicPanels.concat({ proportion: 2 }) 94 | }); 95 | }; 96 | 97 | removePanel = () => { 98 | this.setState({ 99 | dynamicPanels: this.state.dynamicPanels.slice( 100 | 0, 101 | this.state.dynamicPanels.length - 1 102 | ) 103 | }); 104 | }; 105 | render() { 106 | const { dynamicPanels } = this.state; 107 | const styles = { 108 | collapseButton: { 109 | background: "azure", 110 | border: 0, 111 | float: "right", 112 | outline: "none" 113 | }, 114 | example: { 115 | width: "600px", 116 | height: "400px", 117 | border: "1px solid gray", 118 | borderRadius: "4px", 119 | margin: "10px 0px" 120 | }, 121 | github: { 122 | width: "32px", 123 | height: "32px" 124 | }, 125 | app: { 126 | display: "flex", 127 | alignItems: "flex-start" 128 | }, 129 | main: { 130 | display: "flex", 131 | flexDirection: "column", 132 | alignItems: "center", 133 | marginTop: "20px" 134 | }, 135 | navList: { 136 | listStyle: "none" 137 | }, 138 | scrollSpy: { 139 | position: "sticky", 140 | right: "0px", 141 | top: "0px", 142 | width: "225px", 143 | height: "300px" 144 | }, 145 | tableTop: { 146 | width: "100%", 147 | height: "100%", 148 | background: "rgba(0, 0, 0, 0.5)", 149 | textAlign: "center" 150 | }, 151 | tableBottom: { 152 | width: "100%", 153 | height: "100%", 154 | background: "rgba(0, 0, 0, 0.2)", 155 | textAlign: "center" 156 | } 157 | }; 158 | return ( 159 |
160 |
161 | 180 | 217 | 218 |
219 |
220 |
227 |
228 | 229 |

230 | nice-react-layout 231 | {version.number} 232 |

233 | 237 | github 242 | 243 |

244 | A set of React components to create complex flexbox-based 245 | layouts without knowing what flexbox is. 246 |

247 |
248 |
249 |
250 |

Basic example

251 |
252 |                 {`
253 |   
254 |     
255 |     Lorem ipsum...
256 |   
257 |                   `}
258 |               
259 |
260 | 261 | 262 | 263 | {mockupContent} 264 | {mockupContent} 265 | {mockupContent} 266 | 267 | 268 |
269 |
270 |
271 |
272 |

Nested layouts

273 |
274 |                 {`
275 |   
276 |     
277 |     
278 |       
279 |         
280 |         
281 |       
282 |     
283 |   
284 |                     `}
285 |               
286 |
287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 |
297 |
298 |
299 |
300 |

Separator

301 |
Horizontal
302 |
303 |                 {`
304 |   
305 |     
306 |     
307 |       
308 |         
309 |         
310 |         
311 |         
312 |       
313 |     
314 |   
315 |                       `}
316 |               
317 |
318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 |
330 |
Vertical
331 |
332 |                 {`
333 |   
334 |     
335 |     
336 |       
337 |         
338 |         
339 |         
340 |         
341 |       
342 |     
343 |   
344 |                         `}
345 |               
346 |
347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 |
359 |
360 |
361 |
362 |

Panels with size (drag separators to see it)

363 |
364 |                 {`
365 |   
366 |     
367 |     
368 |       
369 |         
370 |           
371 |             
372 |             
373 |             
374 |           
375 |         
376 |         
377 |         
378 |         
379 |       
380 |     
381 |   
382 |                           `}
383 |               
384 |
385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 |
403 |
404 |
405 |
406 |

Collapsible sidebar

407 |
408 |                 {`
409 |   
410 |     
411 |     
412 |       
413 |         
423 |         
424 |         
425 |       
426 |     
427 |   
428 |                             `}
429 |               
430 |
431 | 432 | 433 | 434 | 435 | 445 | 446 | 447 | 448 | 449 | 450 |
451 |
452 |
453 |
454 |

Multiple collapsible panels

455 |
456 |                 {`
457 |   
458 |     
459 |     
460 |       
461 |         
471 |         
481 |         
491 |       
492 |     
493 |   
494 |                               `}
495 |               
496 |
497 | 498 | 499 | 500 | 501 | 511 | 521 | 531 | 532 | 533 | 534 |
535 |
536 |
537 |
538 |

Multi-column panel

539 | Note you can pass a custom style using the customCss prop 540 |
541 |                 {`
542 |   
543 |     
544 |     
545 |       
546 |         
547 |         
548 |         
553 |             Lorem ipsum...
554 |           
555 |         
556 |       
557 |     
558 |                                   `}
559 |               
560 |
561 | 562 | 563 | 564 | 565 | 566 | 567 | 574 | {mockupContent} 575 | 576 | 577 | 578 | 579 |
580 |
581 |
582 |
583 |

Panels with render prop

584 | In this example, we use the panel size to change its background 585 |
586 |                 {`
587 |   
588 |     
589 |     
590 |       
591 |         
592 |           
593 |              (
595 |                 
607 | Resize me 608 |
609 | )} 610 | /> 611 | 612 | 613 |
614 |
615 | 616 | 617 |
618 |
619 |
620 | `} 621 |
622 |
623 | 624 | 625 | 626 | 627 | 628 | 629 | ( 631 |
643 | Resize me 644 |
645 | )} 646 | /> 647 | 648 | 649 |
650 |
651 | 652 | 653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |

Drag and drop panels

661 | Note: only works on panels in the same layout 662 |
663 |                 {`
664 |   
671 |      (
684 |         
685 | )} 686 | /> 687 | ... 688 | 689 | `} 690 |
691 |
692 | Images provided by{" "} 693 | 698 | placekitten 699 | 700 |
701 |
702 | 709 | {catPanel(0, 0)} 710 | {catPanel(0, 1)} 711 | {catPanel(0, 2)} 712 | {catPanel(0, 3)} 713 | {catPanel(0, 4)} 714 | {catPanel(0, 5)} 715 | 716 |
717 |
718 |
719 |
720 |

Drag and drop - 2

721 |
722 |                 {`
723 |   
731 |     
732 |       Drag and drop!
733 |     
734 |     
735 |       Drag and drop!
736 |     
737 |     
738 |       Only drag
739 |     
740 |     
741 |       Only drop
742 |     
743 |   
744 |                                             `}
745 |               
746 |
747 | 755 | 756 | Drag and drop! 757 | 758 | 759 | Drag and drop! 760 | 761 | 762 | Only drag 763 | 764 | 765 | Only drop 766 | 767 | 768 |
769 |
770 |
771 |

Dynamic panels

772 | 773 | 774 | 775 |
776 | 784 | {dynamicPanels && 785 | dynamicPanels.map((p, index) => ( 786 | 792 | ))} 793 | 794 |
795 |
796 |
797 |

798 | Custom panel components (EXPERIMENTAL) 799 |

800 |

801 | Instead of using the Panel component, you can use your own 802 | component (Header and CustomPanel are 803 | user-created) 804 |

805 |

806 | It will receive a niceReactLayoutProps prop with (almost) all 807 | you need to make it work as a normal panel. 808 |

809 |
810 |                 {`
811 |   
812 |     
816 | 817 | 818 | 828 | I'm a custom panel and I'm getting the mockup 829 | background color as a prop. 830 | 831 | 832 | 833 | `} 834 |
835 |
836 | 837 |
841 | 842 | 843 | 853 | 854 | I'm a custom panel and I'm getting the mockup background 855 | color as a prop. 856 | 857 | 858 | 859 | 860 |
861 |
862 |
863 |
864 |
865 | ); 866 | } 867 | } 868 | 869 | export default App; 870 | -------------------------------------------------------------------------------- /src/demo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import registerServiceWorker from './registerServiceWorker'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | registerServiceWorker(); 8 | -------------------------------------------------------------------------------- /src/demo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekros/nice-react-layout/4d208b018fa4d6806a91f4a1f42eb1e94f806be1/src/demo/logo.png -------------------------------------------------------------------------------- /src/demo/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | } else { 39 | // Is not local host. Just register service worker 40 | registerValidSW(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/lib/components/Layout/HorizontalLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Layout from "./Layout.jsx"; 3 | 4 | const HorizontalLayout = ({ ...props }) => { 5 | return ; 6 | }; 7 | 8 | export default HorizontalLayout; 9 | -------------------------------------------------------------------------------- /src/lib/components/Layout/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import reject from "lodash/reject"; 4 | 5 | // swap array indexes. Work only for flat arrays 6 | const swapArrayIndexes = (arr, index1, index2) => { 7 | let a = arr.slice(0); 8 | const buff = a[index1]; 9 | a[index1] = a[index2]; 10 | a[index2] = buff; 11 | return a; 12 | }; 13 | 14 | export default class Layout extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | this.layout = React.createRef(); 18 | this.mockupColors = [ 19 | "#ffcccc", 20 | "#ccffff", 21 | "#ffe4cc", 22 | "#ccceff", 23 | "#fffbcc", 24 | "#ecccff", 25 | "#d6ffcc", 26 | "#ffccf2", 27 | "#f5ffcc", 28 | "#ccd6ff", 29 | "#ffdbcc", 30 | "#ccf0ff", 31 | "#ffe9cc", 32 | "#d8ccff", 33 | "#fffecc", 34 | "#f3ccff", 35 | "#ccffcd", 36 | "#ffcce9", 37 | "#eaffcc", 38 | "#dbccff" 39 | ]; 40 | let layoutInfo = Layout.calculateLayout(props); 41 | this.state = { 42 | collapsedPanels: [], // array of indexes 43 | draggingPanelIndex: undefined, 44 | draggingPanelOverIndex: undefined, 45 | draggingSeparator: false, 46 | draggingSeparatorIndex: undefined, 47 | isBusyOnDragging: false, // sidebar dragging throttle 48 | ...layoutInfo 49 | }; 50 | } 51 | 52 | static calculateLayout(props) { 53 | let initialLayout = []; 54 | let initialOrdering = []; 55 | let totalFixedWidth = 0; 56 | let totalFixedHeight = 0; 57 | let totalSpacerSize = 0; 58 | React.Children.map(props.children, (c, index) => { 59 | const { id } = c && c.props; 60 | if (id === "panel") { 61 | initialOrdering.push(index); 62 | if (c.props.fixed) { 63 | initialLayout.push(0); 64 | if (c.props.fixedWidth) { 65 | totalFixedWidth += c.props.fixedWidth; 66 | } else if (c.props.fixedHeight) { 67 | totalFixedHeight += c.props.fixedHeight; 68 | } 69 | } else { 70 | initialLayout.push(c.props.proportion); 71 | } 72 | } else if (id === "spacer") { 73 | totalSpacerSize += c.props.size; 74 | } 75 | }); 76 | return { 77 | layout: initialLayout, 78 | layoutOrdering: initialOrdering, 79 | totalFixedWidth, 80 | totalFixedHeight, 81 | totalSpacerSize 82 | }; 83 | } 84 | 85 | componentDidUpdate(prevProps, prevState, snapshot) { 86 | let layoutInfo = Layout.calculateLayout(this.props); 87 | if (layoutInfo.layout.length !== prevState.layout.length) { 88 | this.setState({ ...layoutInfo }); 89 | } 90 | } 91 | 92 | static propTypes = { 93 | children: PropTypes.arrayOf(PropTypes.element), 94 | className: PropTypes.string, 95 | customCss: PropTypes.object, 96 | collapseSize: PropTypes.string, 97 | mockup: PropTypes.bool, 98 | orientation: PropTypes.string, 99 | reverse: PropTypes.bool, 100 | onResize: PropTypes.func 101 | }; 102 | 103 | static defaultProps = { 104 | className: "", 105 | mockup: false, 106 | orientation: "horizontal", 107 | reverse: false, 108 | separatorsRefreshInterval: 0, 109 | onResize: null 110 | }; 111 | 112 | collapsePanel = layoutIndex => { 113 | const { collapsedPanels } = this.state; 114 | if (!collapsedPanels.includes(layoutIndex)) { 115 | this.setState({ collapsedPanels: collapsedPanels.concat([layoutIndex]) }); 116 | } else { 117 | this.setState({ 118 | collapsedPanels: reject(collapsedPanels, p => p === layoutIndex) 119 | }); 120 | } 121 | }; 122 | 123 | draggingOver = layoutIndex => { 124 | this.setState({ draggingPanelOverIndex: layoutIndex }); 125 | }; 126 | 127 | onSeparatorDoubleClick = (draggingSeparatorIndex, defaultDblClickPos) => { 128 | this.setState({ draggingSeparatorIndex }, () => { 129 | this.handleSeparatorMouseMove( 130 | this.props.orientation === "vertical" 131 | ? { pageY: defaultDblClickPos } 132 | : { pageX: defaultDblClickPos } 133 | ); 134 | this.handleSeparatorMouseUp(); 135 | }); 136 | }; 137 | onSeparatorMouseDown = draggingSeparatorIndex => { 138 | document.addEventListener("mouseup", this.handleSeparatorMouseUp); 139 | document.addEventListener("mousemove", this.handleSeparatorMouseMove); 140 | if (this.props.onResize) { 141 | this.props.onResize(this.state.layout, this.state.collapsedPanels); 142 | } 143 | this.setState({ draggingSeparatorIndex }); 144 | }; 145 | handleSeparatorMouseMove = e => { 146 | const { orientation, reverse, separatorsRefreshInterval } = this.props; 147 | const rect = this.layout.current.getBoundingClientRect(); 148 | const { top, left, width, height } = rect; 149 | const { 150 | draggingSeparatorIndex, 151 | isBusyOnDragging, 152 | layout, 153 | totalFixedWidth, 154 | totalFixedHeight, 155 | totalSpacerSize 156 | } = this.state; 157 | const newLayout = layout.slice(0); 158 | if (!isBusyOnDragging) { 159 | let separatorPos; 160 | if (reverse) { 161 | separatorPos = 162 | orientation === "vertical" 163 | ? height - (e.clientY - top) 164 | : width - (e.clientX - left); 165 | } else { 166 | separatorPos = 167 | orientation === "vertical" ? e.clientY - top : e.clientX - left; 168 | } 169 | 170 | // separator pos limits 171 | if (separatorPos <= 0) { 172 | separatorPos = 1; 173 | } else { 174 | if (orientation === "vertical" && separatorPos >= height) { 175 | separatorPos = height; 176 | } else if (orientation === "horizontal" && separatorPos >= width) { 177 | separatorPos = width; 178 | } 179 | } 180 | 181 | let flexUnitsSum = 0; 182 | let currentFlexValue = 0; 183 | const layoutSize = 184 | orientation === "vertical" 185 | ? height - totalFixedHeight - totalSpacerSize 186 | : width - totalFixedWidth - totalSpacerSize; 187 | newLayout.forEach(panel => { 188 | flexUnitsSum += panel; 189 | }); 190 | const newFlexValue = (separatorPos * flexUnitsSum) / layoutSize; 191 | for (let i = 0; i <= draggingSeparatorIndex; i++) { 192 | currentFlexValue += newLayout[i]; 193 | } 194 | const relation = newFlexValue / currentFlexValue; 195 | for (let i = 0; i <= draggingSeparatorIndex; i++) { 196 | newLayout[i] = newLayout[i] * relation; 197 | } 198 | this.setState({ 199 | draggingSeparator: true, 200 | layout: newLayout, 201 | isBusyOnDragging: true 202 | }); 203 | setTimeout(() => { 204 | this.setState({ isBusyOnDragging: false }); 205 | }, separatorsRefreshInterval); 206 | } 207 | }; 208 | handleSeparatorMouseUp = () => { 209 | document.removeEventListener("mouseup", this.handleSeparatorMouseUp); 210 | document.removeEventListener("mousemove", this.handleSeparatorMouseMove); 211 | this.setState({ 212 | draggingSeparator: false, 213 | draggingSeparatorIndex: undefined 214 | }); 215 | }; 216 | 217 | startDraggingPanel = panelIndex => { 218 | this.setState({ draggingPanelIndex: panelIndex }); 219 | }; 220 | 221 | stopDraggingPanel = () => { 222 | // swap panels 223 | const { 224 | layoutOrdering, 225 | draggingPanelIndex, 226 | draggingPanelOverIndex 227 | } = this.state; 228 | if ( 229 | layoutOrdering && 230 | draggingPanelIndex !== null && 231 | draggingPanelIndex !== undefined && 232 | draggingPanelOverIndex !== null && 233 | draggingPanelOverIndex !== undefined 234 | ) { 235 | const newOrder = swapArrayIndexes( 236 | layoutOrdering, 237 | draggingPanelIndex, 238 | draggingPanelOverIndex 239 | ); 240 | this.setState({ 241 | draggingPanelIndex: undefined, 242 | draggingPanelOverIndex: undefined, 243 | layoutOrdering: newOrder 244 | }); 245 | } else { 246 | this.setState({ 247 | draggingPanelIndex: undefined, 248 | draggingPanelOverIndex: undefined 249 | }); 250 | } 251 | }; 252 | 253 | render() { 254 | const { 255 | children, 256 | className, 257 | customCss, 258 | collapseSize, 259 | mockup, 260 | orientation, 261 | reverse 262 | } = this.props; 263 | const { 264 | collapsedPanels, 265 | draggingPanelIndex, 266 | draggingPanelOverIndex, 267 | draggingSeparator, 268 | layout, 269 | layoutOrdering 270 | } = this.state; 271 | const styles = { 272 | horizontalLayout: { 273 | cursor: draggingSeparator ? "col-resize" : "default", 274 | display: "flex", 275 | flexDirection: reverse ? "row-reverse" : "row", 276 | height: "100%" 277 | }, 278 | verticalLayout: { 279 | cursor: draggingSeparator ? "row-resize" : "default", 280 | display: "flex", 281 | flexDirection: reverse ? "column-reverse" : "column", 282 | height: "100%" 283 | } 284 | }; 285 | let panelIndex = 0; 286 | const childrenWithProps = React.Children.map(children, (c, index) => { 287 | const { id } = c && c.props; 288 | let child; 289 | if (id === "separator") { 290 | child = React.cloneElement(c, { 291 | disabled: 292 | (index - 1 === 0 && children[index - 1].props.fixed) || 293 | (index + 1 === children.length - 1 && 294 | children[index + 1].props.fixed) || 295 | (collapsedPanels.includes(index - 1) || 296 | collapsedPanels.includes(index + 1)), 297 | draggingSeparator, 298 | onSeparatorDoubleClick: this.onSeparatorDoubleClick, 299 | onSeparatorMouseDown: this.onSeparatorMouseDown, 300 | orientation, 301 | layoutIndex: panelIndex - 1 302 | }); 303 | } else if (id === "panel") { 304 | if (orientation === "vertical") { 305 | child = React.cloneElement(c, { 306 | collapseSize, 307 | collapsed: collapsedPanels.includes(panelIndex), 308 | collapsePanel: this.collapsePanel, 309 | draggingOver: this.draggingOver, 310 | draggingPanelIndex, 311 | draggingSeparator, 312 | flex: c.props.fixed ? "none" : layout[panelIndex], 313 | height: c.props.fixedHeight, 314 | isDraggingOver: panelIndex === draggingPanelOverIndex, 315 | layoutIndex: panelIndex, 316 | mockupStyle: mockup 317 | ? { background: this.mockupColors[index] } 318 | : null, 319 | order: layoutOrdering[index], 320 | orientation, 321 | startDragging: this.startDraggingPanel, 322 | stopDragging: this.stopDraggingPanel 323 | }); 324 | } else { 325 | child = React.cloneElement(c, { 326 | collapseSize, 327 | collapsed: collapsedPanels.includes(panelIndex), 328 | collapsePanel: this.collapsePanel, 329 | draggingOver: this.draggingOver, 330 | draggingPanelIndex, 331 | draggingSeparator, 332 | width: c.props.fixedWidth, 333 | flex: c.props.fixed ? "none" : layout[panelIndex], 334 | isDraggingOver: panelIndex === draggingPanelOverIndex, 335 | layoutIndex: panelIndex, 336 | mockupStyle: mockup 337 | ? { 338 | background: this.mockupColors[ 339 | this.mockupColors.length - index 340 | ] 341 | } 342 | : null, 343 | order: layoutOrdering[index], 344 | orientation, 345 | startDragging: this.startDraggingPanel, 346 | stopDragging: this.stopDraggingPanel 347 | }); 348 | } 349 | panelIndex += 1; 350 | } else if (id === "spacer") { 351 | child = React.cloneElement(c, { 352 | orientation 353 | }); 354 | } else { 355 | if (orientation === "vertical") { 356 | child = React.cloneElement(c, { 357 | niceReactLayoutProps: { 358 | layoutIndex: panelIndex, 359 | mockupStyle: mockup 360 | ? { 361 | background: this.mockupColors[ 362 | this.mockupColors.length - index 363 | ] 364 | } 365 | : null, 366 | orientation 367 | } 368 | }); 369 | } else { 370 | child = React.cloneElement(c, { 371 | niceReactLayoutProps: { 372 | layoutIndex: panelIndex, 373 | mockupStyle: mockup 374 | ? { 375 | background: this.mockupColors[ 376 | this.mockupColors.length - index 377 | ] 378 | } 379 | : null, 380 | orientation 381 | } 382 | }); 383 | } 384 | } 385 | return child; 386 | }); 387 | return ( 388 |
399 | {childrenWithProps} 400 |
401 | ); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/lib/components/Layout/Panel.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { SizeMe } from "react-sizeme"; 4 | 5 | export default class Panel extends React.PureComponent { 6 | constructor(props) { 7 | super(props); 8 | this.panel = React.createRef(); 9 | this.lastDragX; 10 | this.lastDragY; 11 | } 12 | 13 | static propTypes = { 14 | id: PropTypes.string, // internal use only 15 | centered: PropTypes.bool, 16 | children: PropTypes.node, 17 | className: PropTypes.string, 18 | customCss: PropTypes.object, 19 | draggable: PropTypes.bool, 20 | draggingOver: PropTypes.func, 21 | draggingPanelIndex: PropTypes.number, 22 | draggingSeparator: PropTypes.bool, 23 | droppable: PropTypes.bool, 24 | collapsed: PropTypes.bool, 25 | collapsible: PropTypes.bool, 26 | collapseButtonClass: PropTypes.string, 27 | collapseSize: PropTypes.string, 28 | collapseButtonStyle: PropTypes.object, 29 | collapseButtonContent: PropTypes.oneOfType([ 30 | PropTypes.string, 31 | PropTypes.element 32 | ]), 33 | collapseButtonCollapsedContent: PropTypes.oneOfType([ 34 | PropTypes.string, 35 | PropTypes.element 36 | ]), 37 | collapsePanel: PropTypes.func, 38 | collapseSwitch: PropTypes.element, 39 | columns: PropTypes.number, 40 | // contentAlign: PropTypes.oneOf([ 41 | // "center", 42 | // "top", 43 | // "right", 44 | // "bottom", 45 | // "left", 46 | // "top right", 47 | // "bottom right", 48 | // "bottom left", 49 | // "top left" 50 | // ]), 51 | flex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 52 | isDraggingOver: PropTypes.bool, 53 | layoutIndex: PropTypes.number, 54 | minHeight: PropTypes.number, 55 | minWidth: PropTypes.number, 56 | mockupStyle: PropTypes.object, 57 | order: PropTypes.number, 58 | proportion: PropTypes.number, 59 | render: PropTypes.func, 60 | startDragging: PropTypes.func, 61 | stopDragging: PropTypes.func, 62 | showSize: PropTypes.bool, 63 | sidebar: PropTypes.bool 64 | }; 65 | 66 | static defaultProps = { 67 | id: "panel", 68 | centered: false, 69 | className: "", 70 | collapseSize: "30px", 71 | collapseButtonContent: "Collapse", 72 | collapseButtonCollapsedContent: "Extend", 73 | columns: undefined, 74 | draggable: false, 75 | droppable: false, 76 | isDraggingOver: false, 77 | proportion: 1, 78 | render: undefined, 79 | showSize: false 80 | }; 81 | 82 | componentDidMount() { 83 | if (this.props.draggable) { 84 | setTimeout(() => { 85 | this.panel.current.addEventListener("mousedown", this.startDragging); 86 | }, 400); 87 | } 88 | if (this.props.droppable) { 89 | setTimeout(() => { 90 | this.panel.current.addEventListener("mousemove", this.draggingOver); 91 | }, 500); 92 | } 93 | } 94 | 95 | componentWillUnmount() { 96 | if (this.props.draggable) { 97 | this.panel.current.removeEventListener("mousedown", this.startDragging); 98 | this.panel.current.removeEventListener("mouseup", this.cancelDragging); 99 | } 100 | if (this.props.droppable) { 101 | this.panel.current.removeEventListener("mousemove", this.draggingOver); 102 | } 103 | } 104 | 105 | calculatePanelFlex = () => { 106 | const { sidebar, collapsed, collapsible, collapseSize } = this.props; 107 | let flex; 108 | if (sidebar && collapsible) { 109 | if (collapsed) { 110 | flex = collapseSize; 111 | } else { 112 | flex = this.calculatePanelLength(); 113 | } 114 | } else { 115 | flex = this.calculatePanelLength(); 116 | } 117 | return flex; 118 | }; 119 | 120 | calculatePanelLength = () => this.props.proportion; 121 | 122 | // cancelDragging = ev => { 123 | // let ghost = document.getElementById("panel-dragging-ghost"); 124 | // if (ghost) { 125 | // document.body.removeChild(ghost); 126 | // } 127 | // // WORKAROUND: is there a second ghost?? 128 | // ghost = document.getElementById("panel-dragging-ghost"); 129 | // if (ghost) { 130 | // document.body.removeChild(ghost); 131 | // } 132 | // 133 | // document.removeEventListener("mouseup", this.cancelDragging); 134 | // // document.removeEventListener("dragend", this.stopDragging); 135 | // this.props.stopDragging(); 136 | // }; 137 | 138 | draggingOver = ev => { 139 | if ( 140 | this.props.draggingPanelIndex !== null && 141 | this.props.draggingPanelIndex !== undefined 142 | ) { 143 | const dragEl = document.getElementById("panel-dragging-ghost"); 144 | if (dragEl) { 145 | dragEl.style.top = `${ev.clientY + 10}px`; 146 | dragEl.style.left = `${ev.clientX + 10}px`; 147 | if (ev.clientX - this.lastDragX > 0) { 148 | dragEl.style.transform = "rotateZ(10deg)"; 149 | } else if (ev.clientX - this.lastDragX < 0) { 150 | dragEl.style.transform = "rotateZ(-10deg)"; 151 | } else if (ev.clientY - this.lastDragY > 0) { 152 | dragEl.style.transform = "rotateZ(-10deg)"; 153 | } else if (ev.clientY - this.lastDragY < 0) { 154 | dragEl.style.transform = "rotateZ(10deg)"; 155 | } else { 156 | dragEl.style.transform = "rotateZ(0deg)"; 157 | } 158 | this.lastDragX = ev.clientX; 159 | this.lastDragY = ev.clientY; 160 | } 161 | const { draggingOver, layoutIndex } = this.props; 162 | draggingOver(layoutIndex); 163 | } 164 | }; 165 | 166 | // onMouseMove = ev => { 167 | // const dragEl = document.getElementById("panel-dragging-ghost"); 168 | // if (dragEl) { 169 | // dragEl.style.top = `${ev.clientY + 10}px`; 170 | // dragEl.style.left = `${ev.clientX + 10}px`; 171 | // } 172 | // }; 173 | 174 | startDragging = ev => { 175 | // clone the panel element 176 | const panelClone = this.panel.current.cloneNode(true); 177 | panelClone.id = "panel-dragging-ghost"; 178 | panelClone.style.position = "fixed"; 179 | panelClone.style.opacity = 0.5; 180 | panelClone.style.width = `${ 181 | this.panel.current.getBoundingClientRect().width 182 | }px`; 183 | panelClone.style.height = `${ 184 | this.panel.current.getBoundingClientRect().height 185 | }px`; 186 | panelClone.style.transition = "transform 0.2s"; 187 | panelClone.style.transformOrigin = "0% 0%"; 188 | panelClone.style.zIndex = 10; 189 | document.body.appendChild(panelClone); 190 | 191 | document.addEventListener("mouseup", this.stopDragging); 192 | const { layoutIndex, startDragging } = this.props; 193 | startDragging(layoutIndex); 194 | }; 195 | 196 | stopDragging = ev => { 197 | let ghost = document.getElementById("panel-dragging-ghost"); 198 | if (ghost) { 199 | document.body.removeChild(ghost); 200 | } 201 | // WORKAROUND: is there a second ghost?? 202 | ghost = document.getElementById("panel-dragging-ghost"); 203 | if (ghost) { 204 | document.body.removeChild(ghost); 205 | } 206 | 207 | document.removeEventListener("mouseup", this.stopDragging); 208 | this.props.stopDragging(); 209 | }; 210 | 211 | toggleCollapse = () => { 212 | const { collapsePanel, layoutIndex } = this.props; 213 | collapsePanel(layoutIndex); 214 | }; 215 | 216 | render() { 217 | const { 218 | centered, 219 | children, 220 | className, 221 | customCss, 222 | collapsed, 223 | collapsible, 224 | collapseButtonClass, 225 | collapseButtonContent, 226 | collapseButtonCollapsedContent, 227 | collapseSize, 228 | collapseButtonStyle, 229 | collapseSwitch, 230 | columns, 231 | draggingPanelIndex, 232 | draggingSeparator, 233 | flex, 234 | height, 235 | isDraggingOver, 236 | minHeight, 237 | minWidth, 238 | mockupStyle, 239 | order, 240 | showSize, 241 | orientation, 242 | render, 243 | sidebar, 244 | width 245 | } = this.props; 246 | const styles = { 247 | sidebarActions: { 248 | height: "20px" 249 | }, 250 | centered: { 251 | display: "flex", 252 | justifyContent: "center", 253 | alignItems: "center" 254 | }, 255 | horizontalPanel: { 256 | position: "relative", 257 | borderRight: sidebar ? "1px solid #445161" : "none", 258 | cursor: draggingSeparator ? "col-resize" : "default", 259 | flex: 260 | flex !== null && flex !== undefined 261 | ? flex 262 | : this.calculatePanelFlex(), // TODO: remove local calculation??? 263 | minWidth: sidebar && collapsible && collapsed ? collapseSize : minWidth, 264 | overflowX: "auto", 265 | overflowY: "auto", 266 | width: width || "auto" 267 | }, 268 | draggingPanel: { 269 | cursor: "grab" 270 | }, 271 | isDraggingOver: { 272 | filter: "brightness(120%)" 273 | }, 274 | panelSize: { 275 | position: "absolute", 276 | background: "rgba(255, 255, 255, 0.5)", 277 | borderRadius: "4px", 278 | color: "#222222", 279 | fontSize: "11px", 280 | right: "5px", 281 | bottom: "5px", 282 | width: "90px", 283 | height: "15px", 284 | textAlign: "center" 285 | }, 286 | verticalPanel: { 287 | position: "relative", 288 | borderRight: sidebar ? "1px solid #445161" : "none", 289 | cursor: draggingSeparator ? "row-resize" : "default", 290 | flex: 291 | flex !== null && flex !== undefined 292 | ? flex 293 | : this.calculatePanelFlex(), 294 | height: height || "auto", 295 | minHeight: 296 | sidebar && collapsible && collapsed ? collapseSize : minHeight, 297 | overflowX: "hidden", 298 | overflowY: "auto" 299 | }, 300 | collapsedPanel: { 301 | position: "relative", 302 | boxShadow: "1px 0px 4px black", 303 | flex: 0 304 | } 305 | }; 306 | return ( 307 | 308 | {({ size }) => ( 309 |
332 | {collapsible ? ( 333 |
342 | {collapseSwitch || ( 343 | 352 | )} 353 |
354 | ) : null} 355 | {render ? render({ size }) : children} 356 | {draggingSeparator && showSize ? ( 357 |
358 | {size 359 | ? `${Math.floor(size.width)} x ${Math.floor(size.height)}` 360 | : null} 361 |
362 | ) : null} 363 |
364 | )} 365 |
366 | ); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/lib/components/Layout/Separator.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const Separator = ({ 5 | defaultDblClickPos, 6 | disabled, 7 | layoutIndex, 8 | onSeparatorDoubleClick, 9 | onSeparatorMouseDown, 10 | orientation, 11 | customCss, 12 | }) => { 13 | const styles = { 14 | horizontalSeparator: { 15 | display: "inline-block", 16 | width: "100%", 17 | height: "3px", 18 | background: "hsl(0, 0%, 80%)", 19 | cursor: disabled ? "not-allowed" : "row-resize", 20 | }, 21 | verticalSeparator: { 22 | display: "inline-block", 23 | width: "3px", 24 | height: "100%", 25 | background: "hsl(0, 0%, 80%)", 26 | cursor: disabled ? "not-allowed" : "col-resize", 27 | }, 28 | }; 29 | return ( 30 |
onSeparatorMouseDown(layoutIndex)} 39 | onDoubleClick={ 40 | disabled 41 | ? null 42 | : () => onSeparatorDoubleClick(layoutIndex, defaultDblClickPos) 43 | } 44 | /> 45 | ); 46 | }; 47 | 48 | Separator.propTypes = { 49 | id: PropTypes.string, // internal use only 50 | defaultDblClickPos: PropTypes.number, 51 | disabled: PropTypes.bool, 52 | layoutIndex: PropTypes.number, 53 | onSeparatorDoubleClick: PropTypes.func, 54 | onSeparatorMouseDown: PropTypes.func, 55 | orientation: PropTypes.string, 56 | customCss: PropTypes.object, 57 | }; 58 | 59 | Separator.defaultProps = { 60 | id: "separator" 61 | }; 62 | 63 | export default Separator; 64 | -------------------------------------------------------------------------------- /src/lib/components/Layout/Spacer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Panel from './Panel.jsx'; 4 | 5 | const Spacer = ({ orientation, size }) => { 6 | return ( 7 | 13 | ); 14 | }; 15 | 16 | Spacer.propTypes = { 17 | name: PropTypes.string, 18 | orientation: PropTypes.string, 19 | size: PropTypes.number, 20 | }; 21 | 22 | Spacer.defaultProps = { 23 | name: "spacer", 24 | }; 25 | 26 | export default Spacer; 27 | -------------------------------------------------------------------------------- /src/lib/components/Layout/VerticalLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Layout from "./Layout.jsx"; 3 | 4 | const VerticalLayout = ({ ...props }) => { 5 | return ; 6 | }; 7 | 8 | export default VerticalLayout; 9 | -------------------------------------------------------------------------------- /src/lib/components/Layout/index.js: -------------------------------------------------------------------------------- 1 | import Layout from "./Layout.jsx"; 2 | import HorizontalLayout from "./HorizontalLayout.jsx"; 3 | import VerticalLayout from "./VerticalLayout.jsx"; 4 | import Panel from "./Panel.jsx"; 5 | import Separator from "./Separator.jsx"; 6 | import Spacer from "./Spacer.jsx"; 7 | 8 | export { 9 | Layout, 10 | HorizontalLayout, 11 | VerticalLayout, 12 | Panel, 13 | Separator, 14 | Spacer, 15 | }; 16 | -------------------------------------------------------------------------------- /src/lib/components/View/View.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const View = ({ children, width, height }) => { 5 | const styles = { 6 | view: { 7 | width, 8 | height, 9 | } 10 | }; 11 | return ( 12 |
{children}
13 | ); 14 | }; 15 | 16 | View.propTypes = { 17 | children: PropTypes.node, 18 | width: PropTypes.string, 19 | height: PropTypes.string, 20 | }; 21 | 22 | View.defaultProps = { 23 | width: "100vw", 24 | height: "100vh", 25 | }; 26 | 27 | export default View; 28 | -------------------------------------------------------------------------------- /src/lib/components/View/index.js: -------------------------------------------------------------------------------- 1 | import View from "./View.jsx"; 2 | 3 | export { View }; 4 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Layout, 3 | HorizontalLayout, 4 | VerticalLayout, 5 | Panel, 6 | Separator, 7 | Spacer, 8 | FormLayout, 9 | TableLayout 10 | } from "./components/Layout"; 11 | import { View } from "./components/View"; 12 | 13 | export { 14 | Layout, 15 | HorizontalLayout, 16 | VerticalLayout, 17 | Panel, 18 | Separator, 19 | Spacer, 20 | FormLayout, 21 | TableLayout, 22 | View, 23 | }; 24 | -------------------------------------------------------------------------------- /src/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": "0.9.0" 3 | } 4 | --------------------------------------------------------------------------------