├── .babelrc ├── .editorconfig ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── .nojekyll ├── css │ ├── normalize.css │ ├── rc-slider.css │ ├── react-select.css │ └── style.css ├── favicon.ico ├── images │ ├── logo.png │ ├── pattern1.png │ ├── pattern2.png │ ├── pattern3.png │ └── photos │ │ ├── photo01.jpg │ │ ├── photo02.jpg │ │ ├── photo03.jpg │ │ ├── photo04.jpg │ │ ├── photo05.jpg │ │ ├── photo06.jpg │ │ ├── photo07.jpg │ │ ├── photo08.jpg │ │ ├── photo09.jpg │ │ ├── photo10.jpg │ │ ├── photo11.jpg │ │ ├── photo12.jpg │ │ ├── photo13.jpg │ │ ├── photo14.jpg │ │ ├── photo15.jpg │ │ ├── photo16.jpg │ │ ├── photo17.jpg │ │ ├── photo18.jpg │ │ ├── photo19.jpg │ │ ├── photo20.jpg │ │ ├── photo21.jpg │ │ └── photo22.jpg ├── index.html ├── js │ ├── App.js │ ├── Root.js │ ├── components │ │ ├── DemoControl.js │ │ └── Header.js │ ├── index.js │ ├── pages │ │ ├── ChangeSize.js │ │ ├── Home.js │ │ ├── HorizontalFlow.js │ │ └── RealWorld.js │ └── routes.js ├── webpack.config.js └── webpack.config.production.js ├── jest └── setup.js ├── package.json ├── src ├── animations │ ├── easings.js │ ├── request-animation-frame.js │ └── transitions │ │ ├── fade-down.js │ │ ├── fade-up.js │ │ ├── fade.js │ │ ├── flip.js │ │ ├── helix.js │ │ ├── index.js │ │ ├── scale-down.js │ │ └── scale-up.js ├── components │ ├── GridItem.js │ ├── StackGrid.js │ └── __tests__ │ │ ├── GridItem.spec.js │ │ └── StackGrid.spec.js ├── declares │ └── vendor.js.flow ├── index.js ├── types │ └── index.js └── utils │ ├── __tests__ │ └── style-helper.spec.js │ └── style-helper.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "flow", 4 | "es2015", 5 | "react", 6 | "stage-1" 7 | ], 8 | "plugins": [ 9 | "react-hot-loader/babel" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "browser": true, 5 | "node": true, 6 | "jest/globals": true 7 | }, 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 2017, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true 14 | } 15 | }, 16 | "plugins": [ 17 | "jest", 18 | "flowtype" 19 | ], 20 | "extends": [ 21 | "airbnb", 22 | "plugin:jest/recommended", 23 | "plugin:flowtype/recommended" 24 | ], 25 | "rules": { 26 | "no-prototype-builtins": "off", 27 | "comma-dangle": [ 28 | "error", 29 | { 30 | "arrays": "always-multiline", 31 | "objects": "always-multiline", 32 | "imports": "always-multiline", 33 | "exports": "always-multiline", 34 | "functions": "ignore" 35 | } 36 | ], 37 | "function-paren-newline": "off", 38 | "object-curly-newline": "off", 39 | "react/no-find-dom-node": "off", 40 | "react/jsx-filename-extension": "off", 41 | "react/sort-comp": "off", 42 | "react/require-default-props": "off", 43 | "react/no-multi-comp": "off", 44 | "jsx-a11y/href-no-hash": "off", 45 | "jsx-a11y/label-has-for": "off", 46 | "jsx-a11y/img-has-alt": "off" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | ./src/declares 8 | 9 | [options] 10 | esproposal.class_static_fields=enable 11 | esproposal.export_star_as=enable 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /tmp 3 | 4 | # Created by https://www.gitignore.io/api/osx,windows,node 5 | 6 | ### Node ### 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Typescript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | 67 | ### OSX ### 68 | *.DS_Store 69 | .AppleDouble 70 | .LSOverride 71 | 72 | # Icon must end with two \r 73 | Icon 74 | 75 | # Thumbnails 76 | ._* 77 | 78 | # Files that might appear in the root of a volume 79 | .DocumentRevisions-V100 80 | .fseventsd 81 | .Spotlight-V100 82 | .TemporaryItems 83 | .Trashes 84 | .VolumeIcon.icns 85 | .com.apple.timemachine.donotpresent 86 | 87 | # Directories potentially created on remote AFP share 88 | .AppleDB 89 | .AppleDesktop 90 | Network Trash Folder 91 | Temporary Items 92 | .apdisk 93 | 94 | ### Windows ### 95 | # Windows thumbnail cache files 96 | Thumbs.db 97 | ehthumbs.db 98 | ehthumbs_vista.db 99 | 100 | # Folder config file 101 | Desktop.ini 102 | 103 | # Recycle Bin used on file shares 104 | $RECYCLE.BIN/ 105 | 106 | # Windows Installer files 107 | *.cab 108 | *.msi 109 | *.msm 110 | *.msp 111 | 112 | # Windows shortcuts 113 | *.lnk 114 | 115 | # End of https://www.gitignore.io/api/osx,windows,node 116 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | notifications: 5 | email: false 6 | language: node_js 7 | node_js: 8 | - "8" 9 | - "7" 10 | - "6" 11 | sudo: false 12 | cache: 13 | directories: 14 | - node_modules 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.7.1 2 | 3 | * Make transform coordinates round to the nearest integer to avoid blurriness. Thank you [@xaviergonz](https://github.com/xaviergonz) 4 | 5 | 6 | ## 0.7.0 7 | 8 | * Add `itemComponent` props. Thank you [@solomonhawk](https://github.com/solomonhawk) ! [#35](https://github.com/tsuyoshiwada/react-stack-grid/issues/35) 9 | 10 | 11 | 12 | ## 0.6.0 13 | 14 | * Add `rtl` props. Thank you [@azizghuloum](https://github.com/azizghuloum) ! 15 | 16 | 17 | 18 | ## 0.5.0 19 | 20 | * Add `horizontal` props. Thank you [@ilyalesik](https://github.com/ilyalesik) ! 21 | 22 | 23 | 24 | ## 0.4.0 25 | 26 | * Add `onLayout` props. Thanks for [@jarib](https://github.com/jarib) ! 27 | 28 | 29 | 30 | ## 0.3.0 31 | 32 | * Add `gridRef` props. Thanks for [@mquandalle](https://github.com/mquandalle) and [@derwydd](https://github.com/derwydd) ! 33 | 34 | 35 | 36 | ## 0.2.2 37 | 38 | * Fix calculations for percentage columns width [#16](https://github.com/tsuyoshiwada/react-stack-grid/pull/16) 39 | Thanks [@mquandalle](https://github.com/mquandalle)! 40 | * Migrate from react-addons-transition-group to react-transition-group. [#17](https://github.com/tsuyoshiwada/react-stack-grid/issues/17) 41 | * Remove setState warning that occurs after unmounting. 42 | 43 | 44 | 45 | ## 0.2.1 46 | 47 | * Support for React v15.5.x ~ (add `prop-types` package) [#2](https://github.com/tsuyoshiwada/react-stack-grid/issues/12) 48 | * Fix lint and typechecking (flowtype) 49 | * Update devDependencies 50 | 51 | 52 | 53 | ## 0.2.0 54 | 55 | * Add `enableSSR` props. Thanks for [@egorAva](https://github.com/egorAva)! [#7](https://github.com/tsuyoshiwada/react-stack-grid/pull/7) 56 | 57 | 58 | 59 | ## 0.1.1 60 | 61 | * Fix transition animation when sort order changed 62 | * Update demo page (Added shuffle button) 63 | 64 | 65 | 66 | ## 0.1.0 67 | 68 | ### New feature 69 | 70 | * Support column percentage width 71 | 72 | ### Bugfix 73 | 74 | * Change waiting for size calculation of react-sizeme before rendering 75 | * Fix warning when deleted before appear 76 | * Remove propTypes warning when there is no children 77 | 78 | 79 | 80 | ## 0.0.2 81 | 82 | * Fix files field in package.json 83 | 84 | 85 | 86 | ## 0.0.1 87 | 88 | * First release. 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 tsuyoshiwada 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-stack-grid 2 | 3 | [![Build Status](http://img.shields.io/travis/tsuyoshiwada/react-stack-grid.svg?style=flat-square)](https://travis-ci.org/tsuyoshiwada/react-stack-grid) 4 | [![npm version](https://img.shields.io/npm/v/react-stack-grid.svg?style=flat-square)](http://badge.fury.io/js/react-stack-grid) 5 | 6 | 7 | Pinterest like layout components for React.js. 8 | 9 | 10 | 11 | 12 | ## Table of Contents 13 | 14 | * [Live Demo](#live-demo) 15 | * [Install](#install) 16 | * [Quick Example](#quick-example) 17 | * [Props](#props) 18 | * [Instance API](#instance-api) 19 | * [updateLayout(): void](#updatelayout-void) 20 | * [Animations](#animations) 21 | * [Tips](#tips) 22 | * [Performance when using images](#performance-when-using-images) 23 | * [When animation is unnecessary](#when-animation-is-unnecessary) 24 | * [How to manually update layout ?](#how-to-manually-update-layout-) 25 | * [Responsive layout](#responsive-layout) 26 | * [Thanks](#thanks) 27 | * [License](#license) 28 | * [ChangeLog](#changelog) 29 | * [Author](#author) 30 | * [Development](#development) 31 | * [Contribution](#contribution) 32 | 33 | 34 | 35 | 36 | ## Live Demo 37 | 38 | ![Screenshot](https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/images/screenshot.png) 39 | 40 | [https://tsuyoshiwada.github.io/react-stack-grid/](https://tsuyoshiwada.github.io/react-stack-grid/) 41 | 42 | 43 | 44 | 45 | ## Install 46 | 47 | You can install the [react-stack-grid](https://www.npmjs.com/package/react-stack-grid) from [npm](https://www.npmjs.com/). 48 | 49 | ```bash 50 | $ npm install react-stack-grid 51 | ``` 52 | 53 | 54 | 55 | 56 | ## Quick Example 57 | 58 | Following code is simplest usage. 59 | 60 | ```javascript 61 | import React, { Component } from "react"; 62 | import StackGrid from "react-stack-grid"; 63 | 64 | class MyComponent extends Component { 65 | render() { 66 | return ( 67 | 70 |
Item 1
71 |
Item 2
72 |
Item 3
73 |
74 | ); 75 | } 76 | } 77 | ``` 78 | 79 | width of parent is managed by [react-sizeme](https://github.com/ctrlplusb/react-sizeme). 80 | 81 | 82 | 83 | 84 | ## Props 85 | 86 | You can set the following properties. 87 | 88 | | Property | Type | Default | Description | 89 | |:----------------------|:------------------------------------------------------------|:---------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------| 90 | | `className` | `PropTypes.string` | `undefined` | Specify `className` of component. | 91 | | `style` | `PropTypes.object` | `{}` | Original style of component. Following styles are ignored. (`position`, `height`, `transition`) | 92 | | `gridRef` | `PropTypes.func` | `null` | Reference the instance of StackGrid. Unlike ordinary `ref`, it accepts only functions. | 93 | | `component` | `PropTypes.string` | `"div"` | See [ReactTransitionGroup](https://facebook.github.io/react/docs/animation.html#rendering-a-different-component) | 94 | | `itemComponent` | `PropTypes.string` | `"span"` | Specify the component of the grid item. | 95 | | `columnWidth` | `PropTypes.oneOfType([PropTypes.number, PropTypes.string])` | `150` | Specify column width as an number(`px`), or percentage string. (Example `"33.33%"`) | 96 | | `gutterWidth` | `PropTypes.number` | `5` | Specify gutter width as an number. | 97 | | `gutterHeight` | `PropTypes.number` | `5` | Specify gutter height as an number. | 98 | | `duration` | `PropTypes.number` | `480` | Specify duration of animation in ms. | 99 | | `easing` | `PropTypes.string` | `easings.quartOut` | Specify a css valid transition-timing-function string. It can be easily specification by using `easings`. | 100 | | `appearDelay` | `PropTypes.number` | `30` | Specify delay of the initial animation in ms. | 101 | | `appear` | `PropTypes.func` | `fadeUp.appear` | See Animations section. | 102 | | `appeared` | `PropTypes.func` | `fadeUp.appear` | ... | 103 | | `enter` | `PropTypes.func` | `fadeUp.appear` | ... | 104 | | `entered` | `PropTypes.func` | `fadeUp.appear` | ... | 105 | | `leaved` | `PropTypes.func` | `fadeUp.appear` | ... | 106 | | `units` | `PropTypes.func` | `{ length: "px", angle: "deg" }` | ... | 107 | | `monitorImagesLoaded` | `PropTypes.bool` | `false` | If set to `true`, images reading is monitored. use [imagesloaded](https://github.com/desandro/imagesloaded). | 108 | | `vendorPrefix` | `PropTypes.bool` | `false` | If set to `true`, add a vendor prefix to styles add dynamically. | 109 | | `userAgent` | `PropTypes.string` | `undefined` | Specify userAgent for determinig the vendor prefix. See [inline-style-prefixer](https://github.com/rofrischmann/inline-style-prefixer). | 110 | | `enableSSR` | `PropTypes.bool` | `false` | Render component on the server side. [More info](https://github.com/ctrlplusb/react-sizeme#server-side-rendering). | 111 | | `onLayout` | `PropTypes.func` | `null` | It is called at the timing when the layout is confirmed, or at the updated timing. (Called only by client.) | 112 | | `horizontal` | `PropTypes.bool` | `false` | The transposed (horizontal) order of drawing elements. Retains the original order of the items. | 113 | | `rtl` | `PropTypes.bool` | `false` | When true, items are placed right-to-left instead of the default left-to-right. Useful for RTL languages such as Arabic and Hebrew. | 114 | 115 | 116 | ## Instance API 117 | 118 | ### updateLayout(): void 119 | 120 | Update the current layout. 121 | 122 | 123 | 124 | 125 | ## Animations 126 | 127 | The following function must return styles related to animation. 128 | See [ReactTransitionGroup](https://facebook.github.io/react/docs/animation.html#rendering-a-different-component) for details. 129 | 130 | * `appear` 131 | * `appeared` 132 | * `enter` 133 | * `entered` 134 | * `leaved` 135 | 136 | You can use extended syntax for transform's style. For example properties like `translateX` and` scale`. 137 | See [easy-css-transform-builder](https://github.com/tsuyoshiwada/easy-css-transform-builder). 138 | 139 | Each function is given the following arguments. 140 | 141 | * `rect: { top: number; left: number; width: number; height: number; }` 142 | * `containerSize: { width: number; height: number; }` 143 | * `index: number` 144 | 145 | It is easiest to use them because you have several presets. 146 | 147 | * `fade` 148 | * `fadeDown` 149 | * `fadeUp` 150 | * `scaleDown` 151 | * `scaleUp` 152 | * `flip` 153 | * `helix` 154 | 155 | It's an actual use example. 156 | 157 | ```javascript 158 | import StackGrid, { transitions } from "react-stack-grid"; 159 | 160 | const { scaleDown } = transitions; 161 | 162 | class MyComponent extends Component { 163 | render() { 164 | return ( 165 | 173 | ... 174 | 175 | ); 176 | } 177 | } 178 | ``` 179 | 180 | Please try actual demonstration in [live demo](https://tsuyoshiwada.github.io/react-stack-grid/). 181 | 182 | 183 | 184 | 185 | ## Tips 186 | 187 | 188 | ### Performance when using images 189 | 190 | When `true` is specified for `monitorImagesLoaded`, reloading occurs when the image loading is completed. 191 | If you know the size in advance, specify `monitorImagesLoaded` as `false`. 192 | 193 | 194 | ### When animation is unnecessary 195 | 196 | By default animation is enabled. 197 | If it's not necessary, specify `0` for `duration` property. 198 | 199 | ```javascript 200 | 204 | ... 205 | 206 | ``` 207 | 208 | 209 | ### How to manually update layout ? 210 | 211 | If the size of an item is changed by an action such as a click event, there are cases where you want to update the layout manually. 212 | You can manually update the layout by referring to the StackGrid instance with `gridRef` and executing the`updateLayout()` method. 213 | 214 | ```javascript 215 | class MyComponent extends React.Component { 216 | 217 | // When the size of the item is changed... 218 | something = () => { 219 | this.grid.updateLayout(); 220 | }; 221 | 222 | render() { 223 | return ( 224 | this.grid = grid} 226 | > 227 | {/* items ... */} 228 | 229 | ); 230 | } 231 | } 232 | ``` 233 | 234 | 235 | ### Responsive layout 236 | 237 | You can get width using [react-sizeme](https://github.com/ctrlplusb/react-sizeme) and change columnWidth according to width. 238 | This is a solution, but we can respond in other ways! 239 | 240 | ```javascript 241 | import React, { Component } from 'react'; 242 | import sizeMe from 'react-sizeme'; 243 | import StackGrid from 'react-stack-grid'; 244 | 245 | class YourComponent extends Component { 246 | render() { 247 | const { 248 | size: { 249 | width 250 | } 251 | } = this.props; 252 | 253 | return ( 254 | 258 | // Grid items... 259 | 260 | ); 261 | } 262 | } 263 | 264 | export default sizeMe()(YourComponent); 265 | ``` 266 | 267 | 268 | 269 | 270 | 271 | ## Thanks 272 | 273 | * Layout inspired by [Pinterest](https://pinterest.com/). 274 | * API inspired by [dantrain/react-stonecutter](https://github.com/dantrain/react-stonecutter). 275 | 276 | 277 | 278 | 279 | ## License 280 | 281 | Released under the [MIT Licence](https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/master/LICENSE) 282 | 283 | 284 | 285 | 286 | ## ChangeLog 287 | 288 | See [CHANGELOG.md](./CHANGELOG.md) 289 | 290 | 291 | 292 | 293 | ## Author 294 | 295 | [tsuyoshiwada](https://github.com/tsuyoshiwada) 296 | 297 | 298 | 299 | 300 | ## Development 301 | 302 | Initialization of the project. 303 | 304 | ```bash 305 | $ cd /your/project/dir 306 | $ git clone https://github.com/tsuyoshiwada/react-stack-grid.git 307 | ``` 308 | 309 | Install some dependencies. 310 | 311 | ```bash 312 | $ npm install 313 | ``` 314 | 315 | Start the development and can you see demo page (access to the `http://localhost:3000/`). 316 | 317 | ```bash 318 | $ npm start 319 | ``` 320 | 321 | Run lint and testing. 322 | 323 | ```bash 324 | $ npm test 325 | ``` 326 | 327 | Generates build file. 328 | 329 | ```bash 330 | $ npm run build 331 | ``` 332 | 333 | 334 | 335 | ## Contribution 336 | 337 | Thank you for your interest in react-stack-grid.js. 338 | Bugs, feature requests and comments are more than welcome in the [issues](https://github.com/tsuyoshiwada/react-stack-grid/issues). 339 | 340 | **Before you open a PR:** 341 | 342 | Be careful to follow the code style of the project. Run `npm test` after your changes and ensure you do not introduce any new errors or warnings. 343 | All new features and changes need documentation. 344 | 345 | Thanks! 346 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | bundle.js 2 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/.nojekyll -------------------------------------------------------------------------------- /docs/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Correct the line height in all browsers. 6 | * 3. Prevent adjustments of font size after orientation changes in 7 | * IE on Windows Phone and in iOS. 8 | */ 9 | 10 | /* Document 11 | ========================================================================== */ 12 | 13 | html { 14 | font-family: sans-serif; /* 1 */ 15 | line-height: 1.15; /* 2 */ 16 | -ms-text-size-adjust: 100%; /* 3 */ 17 | -webkit-text-size-adjust: 100%; /* 3 */ 18 | } 19 | 20 | /* Sections 21 | ========================================================================== */ 22 | 23 | /** 24 | * Remove the margin in all browsers (opinionated). 25 | */ 26 | 27 | body { 28 | margin: 0; 29 | } 30 | 31 | /** 32 | * Add the correct display in IE 9-. 33 | */ 34 | 35 | article, 36 | aside, 37 | footer, 38 | header, 39 | nav, 40 | section { 41 | display: block; 42 | } 43 | 44 | /** 45 | * Correct the font size and margin on `h1` elements within `section` and 46 | * `article` contexts in Chrome, Firefox, and Safari. 47 | */ 48 | 49 | h1 { 50 | font-size: 2em; 51 | margin: 0.67em 0; 52 | } 53 | 54 | /* Grouping content 55 | ========================================================================== */ 56 | 57 | /** 58 | * Add the correct display in IE 9-. 59 | * 1. Add the correct display in IE. 60 | */ 61 | 62 | figcaption, 63 | figure, 64 | main { /* 1 */ 65 | display: block; 66 | } 67 | 68 | /** 69 | * Add the correct margin in IE 8. 70 | */ 71 | 72 | figure { 73 | margin: 1em 40px; 74 | } 75 | 76 | /** 77 | * 1. Add the correct box sizing in Firefox. 78 | * 2. Show the overflow in Edge and IE. 79 | */ 80 | 81 | hr { 82 | box-sizing: content-box; /* 1 */ 83 | height: 0; /* 1 */ 84 | overflow: visible; /* 2 */ 85 | } 86 | 87 | /** 88 | * 1. Correct the inheritance and scaling of font size in all browsers. 89 | * 2. Correct the odd `em` font sizing in all browsers. 90 | */ 91 | 92 | pre { 93 | font-family: monospace, monospace; /* 1 */ 94 | font-size: 1em; /* 2 */ 95 | } 96 | 97 | /* Text-level semantics 98 | ========================================================================== */ 99 | 100 | /** 101 | * 1. Remove the gray background on active links in IE 10. 102 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 103 | */ 104 | 105 | a { 106 | background-color: transparent; /* 1 */ 107 | -webkit-text-decoration-skip: objects; /* 2 */ 108 | } 109 | 110 | /** 111 | * Remove the outline on focused links when they are also active or hovered 112 | * in all browsers (opinionated). 113 | */ 114 | 115 | a:active, 116 | a:hover { 117 | outline-width: 0; 118 | } 119 | 120 | /** 121 | * 1. Remove the bottom border in Firefox 39-. 122 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 123 | */ 124 | 125 | abbr[title] { 126 | border-bottom: none; /* 1 */ 127 | text-decoration: underline; /* 2 */ 128 | text-decoration: underline dotted; /* 2 */ 129 | } 130 | 131 | /** 132 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: inherit; 138 | } 139 | 140 | /** 141 | * Add the correct font weight in Chrome, Edge, and Safari. 142 | */ 143 | 144 | b, 145 | strong { 146 | font-weight: bolder; 147 | } 148 | 149 | /** 150 | * 1. Correct the inheritance and scaling of font size in all browsers. 151 | * 2. Correct the odd `em` font sizing in all browsers. 152 | */ 153 | 154 | code, 155 | kbd, 156 | samp { 157 | font-family: monospace, monospace; /* 1 */ 158 | font-size: 1em; /* 2 */ 159 | } 160 | 161 | /** 162 | * Add the correct font style in Android 4.3-. 163 | */ 164 | 165 | dfn { 166 | font-style: italic; 167 | } 168 | 169 | /** 170 | * Add the correct background and color in IE 9-. 171 | */ 172 | 173 | mark { 174 | background-color: #ff0; 175 | color: #000; 176 | } 177 | 178 | /** 179 | * Add the correct font size in all browsers. 180 | */ 181 | 182 | small { 183 | font-size: 80%; 184 | } 185 | 186 | /** 187 | * Prevent `sub` and `sup` elements from affecting the line height in 188 | * all browsers. 189 | */ 190 | 191 | sub, 192 | sup { 193 | font-size: 75%; 194 | line-height: 0; 195 | position: relative; 196 | vertical-align: baseline; 197 | } 198 | 199 | sub { 200 | bottom: -0.25em; 201 | } 202 | 203 | sup { 204 | top: -0.5em; 205 | } 206 | 207 | /* Embedded content 208 | ========================================================================== */ 209 | 210 | /** 211 | * Add the correct display in IE 9-. 212 | */ 213 | 214 | audio, 215 | video { 216 | display: inline-block; 217 | } 218 | 219 | /** 220 | * Add the correct display in iOS 4-7. 221 | */ 222 | 223 | audio:not([controls]) { 224 | display: none; 225 | height: 0; 226 | } 227 | 228 | /** 229 | * Remove the border on images inside links in IE 10-. 230 | */ 231 | 232 | img { 233 | border-style: none; 234 | } 235 | 236 | /** 237 | * Hide the overflow in IE. 238 | */ 239 | 240 | svg:not(:root) { 241 | overflow: hidden; 242 | } 243 | 244 | /* Forms 245 | ========================================================================== */ 246 | 247 | /** 248 | * 1. Change the font styles in all browsers (opinionated). 249 | * 2. Remove the margin in Firefox and Safari. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | font-family: sans-serif; /* 1 */ 258 | font-size: 100%; /* 1 */ 259 | line-height: 1.15; /* 1 */ 260 | margin: 0; /* 2 */ 261 | } 262 | 263 | /** 264 | * Show the overflow in IE. 265 | * 1. Show the overflow in Edge. 266 | */ 267 | 268 | button, 269 | input { /* 1 */ 270 | overflow: visible; 271 | } 272 | 273 | /** 274 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 275 | * 1. Remove the inheritance of text transform in Firefox. 276 | */ 277 | 278 | button, 279 | select { /* 1 */ 280 | text-transform: none; 281 | } 282 | 283 | /** 284 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 285 | * controls in Android 4. 286 | * 2. Correct the inability to style clickable types in iOS and Safari. 287 | */ 288 | 289 | button, 290 | html [type="button"], /* 1 */ 291 | [type="reset"], 292 | [type="submit"] { 293 | -webkit-appearance: button; /* 2 */ 294 | } 295 | 296 | /** 297 | * Remove the inner border and padding in Firefox. 298 | */ 299 | 300 | button::-moz-focus-inner, 301 | [type="button"]::-moz-focus-inner, 302 | [type="reset"]::-moz-focus-inner, 303 | [type="submit"]::-moz-focus-inner { 304 | border-style: none; 305 | padding: 0; 306 | } 307 | 308 | /** 309 | * Restore the focus styles unset by the previous rule. 310 | */ 311 | 312 | button:-moz-focusring, 313 | [type="button"]:-moz-focusring, 314 | [type="reset"]:-moz-focusring, 315 | [type="submit"]:-moz-focusring { 316 | outline: 1px dotted ButtonText; 317 | } 318 | 319 | /** 320 | * Change the border, margin, and padding in all browsers (opinionated). 321 | */ 322 | 323 | fieldset { 324 | border: 1px solid #c0c0c0; 325 | margin: 0 2px; 326 | padding: 0.35em 0.625em 0.75em; 327 | } 328 | 329 | /** 330 | * 1. Correct the text wrapping in Edge and IE. 331 | * 2. Correct the color inheritance from `fieldset` elements in IE. 332 | * 3. Remove the padding so developers are not caught out when they zero out 333 | * `fieldset` elements in all browsers. 334 | */ 335 | 336 | legend { 337 | box-sizing: border-box; /* 1 */ 338 | color: inherit; /* 2 */ 339 | display: table; /* 1 */ 340 | max-width: 100%; /* 1 */ 341 | padding: 0; /* 3 */ 342 | white-space: normal; /* 1 */ 343 | } 344 | 345 | /** 346 | * 1. Add the correct display in IE 9-. 347 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 348 | */ 349 | 350 | progress { 351 | display: inline-block; /* 1 */ 352 | vertical-align: baseline; /* 2 */ 353 | } 354 | 355 | /** 356 | * Remove the default vertical scrollbar in IE. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; 361 | } 362 | 363 | /** 364 | * 1. Add the correct box sizing in IE 10-. 365 | * 2. Remove the padding in IE 10-. 366 | */ 367 | 368 | [type="checkbox"], 369 | [type="radio"] { 370 | box-sizing: border-box; /* 1 */ 371 | padding: 0; /* 2 */ 372 | } 373 | 374 | /** 375 | * Correct the cursor style of increment and decrement buttons in Chrome. 376 | */ 377 | 378 | [type="number"]::-webkit-inner-spin-button, 379 | [type="number"]::-webkit-outer-spin-button { 380 | height: auto; 381 | } 382 | 383 | /** 384 | * 1. Correct the odd appearance in Chrome and Safari. 385 | * 2. Correct the outline style in Safari. 386 | */ 387 | 388 | [type="search"] { 389 | -webkit-appearance: textfield; /* 1 */ 390 | outline-offset: -2px; /* 2 */ 391 | } 392 | 393 | /** 394 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 395 | */ 396 | 397 | [type="search"]::-webkit-search-cancel-button, 398 | [type="search"]::-webkit-search-decoration { 399 | -webkit-appearance: none; 400 | } 401 | 402 | /** 403 | * 1. Correct the inability to style clickable types in iOS and Safari. 404 | * 2. Change font properties to `inherit` in Safari. 405 | */ 406 | 407 | ::-webkit-file-upload-button { 408 | -webkit-appearance: button; /* 1 */ 409 | font: inherit; /* 2 */ 410 | } 411 | 412 | /* Interactive 413 | ========================================================================== */ 414 | 415 | /* 416 | * Add the correct display in IE 9-. 417 | * 1. Add the correct display in Edge, IE, and Firefox. 418 | */ 419 | 420 | details, /* 1 */ 421 | menu { 422 | display: block; 423 | } 424 | 425 | /* 426 | * Add the correct display in all browsers. 427 | */ 428 | 429 | summary { 430 | display: list-item; 431 | } 432 | 433 | /* Scripting 434 | ========================================================================== */ 435 | 436 | /** 437 | * Add the correct display in IE 9-. 438 | */ 439 | 440 | canvas { 441 | display: inline-block; 442 | } 443 | 444 | /** 445 | * Add the correct display in IE. 446 | */ 447 | 448 | template { 449 | display: none; 450 | } 451 | 452 | /* Hidden 453 | ========================================================================== */ 454 | 455 | /** 456 | * Add the correct display in IE 10-. 457 | */ 458 | 459 | [hidden] { 460 | display: none; 461 | } 462 | -------------------------------------------------------------------------------- /docs/css/rc-slider.css: -------------------------------------------------------------------------------- 1 | .rc-slider { 2 | position: relative; 3 | height: 14px; 4 | padding: 5px 0; 5 | width: 100%; 6 | border-radius: 6px; 7 | box-sizing: border-box; 8 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 9 | } 10 | .rc-slider * { 11 | box-sizing: border-box; 12 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 13 | } 14 | .rc-slider-rail { 15 | position: absolute; 16 | width: 100%; 17 | background-color: #e9e9e9; 18 | height: 4px; 19 | } 20 | .rc-slider-track { 21 | position: absolute; 22 | left: 0; 23 | height: 4px; 24 | border-radius: 6px; 25 | background-color: #abe2fb; 26 | } 27 | .rc-slider-handle { 28 | position: absolute; 29 | margin-left: -7px; 30 | margin-top: -5px; 31 | width: 14px; 32 | height: 14px; 33 | cursor: pointer; 34 | border-radius: 50%; 35 | border: solid 2px #96dbfa; 36 | background-color: #fff; 37 | } 38 | .rc-slider-handle:hover { 39 | border-color: #57c5f7; 40 | } 41 | .rc-slider-handle-active:active { 42 | border-color: #57c5f7; 43 | box-shadow: 0 0 5px #57c5f7; 44 | } 45 | .rc-slider-mark { 46 | position: absolute; 47 | top: 18px; 48 | left: 0; 49 | width: 100%; 50 | font-size: 12px; 51 | } 52 | .rc-slider-mark-text { 53 | position: absolute; 54 | display: inline-block; 55 | vertical-align: middle; 56 | text-align: center; 57 | cursor: pointer; 58 | color: #999; 59 | } 60 | .rc-slider-mark-text-active { 61 | color: #666; 62 | } 63 | .rc-slider-step { 64 | position: absolute; 65 | width: 100%; 66 | height: 4px; 67 | background: transparent; 68 | } 69 | .rc-slider-dot { 70 | position: absolute; 71 | bottom: -2px; 72 | margin-left: -4px; 73 | width: 8px; 74 | height: 8px; 75 | border: 2px solid #e9e9e9; 76 | background-color: #fff; 77 | cursor: pointer; 78 | border-radius: 50%; 79 | vertical-align: middle; 80 | } 81 | .rc-slider-dot:first-child { 82 | margin-left: -4px; 83 | } 84 | .rc-slider-dot:last-child { 85 | margin-left: -4px; 86 | } 87 | .rc-slider-dot-active { 88 | border-color: #96dbfa; 89 | } 90 | .rc-slider-disabled { 91 | background-color: #e9e9e9; 92 | } 93 | .rc-slider-disabled .rc-slider-track { 94 | background-color: #ccc; 95 | } 96 | .rc-slider-disabled .rc-slider-handle, 97 | .rc-slider-disabled .rc-slider-dot { 98 | border-color: #ccc; 99 | background-color: #fff; 100 | cursor: not-allowed; 101 | } 102 | .rc-slider-disabled .rc-slider-mark-text, 103 | .rc-slider-disabled .rc-slider-dot { 104 | cursor: not-allowed !important; 105 | } 106 | .rc-slider-vertical { 107 | width: 14px; 108 | height: 100%; 109 | padding: 0 5px; 110 | } 111 | .rc-slider-vertical .rc-slider-rail { 112 | height: 100%; 113 | width: 4px; 114 | } 115 | .rc-slider-vertical .rc-slider-track { 116 | left: 5px; 117 | bottom: 0; 118 | width: 4px; 119 | } 120 | .rc-slider-vertical .rc-slider-handle { 121 | margin-left: -5px; 122 | margin-bottom: -7px; 123 | } 124 | .rc-slider-vertical .rc-slider-mark { 125 | top: 0; 126 | left: 18px; 127 | height: 100%; 128 | } 129 | .rc-slider-vertical .rc-slider-step { 130 | height: 100%; 131 | width: 4px; 132 | } 133 | .rc-slider-vertical .rc-slider-dot { 134 | left: 2px; 135 | margin-bottom: -4px; 136 | } 137 | .rc-slider-vertical .rc-slider-dot:first-child { 138 | margin-bottom: -4px; 139 | } 140 | .rc-slider-vertical .rc-slider-dot:last-child { 141 | margin-bottom: -4px; 142 | } 143 | .rc-slider-tooltip-zoom-down-enter, 144 | .rc-slider-tooltip-zoom-down-appear { 145 | -webkit-animation-duration: .3s; 146 | animation-duration: .3s; 147 | -webkit-animation-fill-mode: both; 148 | animation-fill-mode: both; 149 | display: block !important; 150 | -webkit-animation-play-state: paused; 151 | animation-play-state: paused; 152 | } 153 | .rc-slider-tooltip-zoom-down-leave { 154 | -webkit-animation-duration: .3s; 155 | animation-duration: .3s; 156 | -webkit-animation-fill-mode: both; 157 | animation-fill-mode: both; 158 | display: block !important; 159 | -webkit-animation-play-state: paused; 160 | animation-play-state: paused; 161 | } 162 | .rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active, 163 | .rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active { 164 | -webkit-animation-name: rcSliderTooltipZoomDownIn; 165 | animation-name: rcSliderTooltipZoomDownIn; 166 | -webkit-animation-play-state: running; 167 | animation-play-state: running; 168 | } 169 | .rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active { 170 | -webkit-animation-name: rcSliderTooltipZoomDownOut; 171 | animation-name: rcSliderTooltipZoomDownOut; 172 | -webkit-animation-play-state: running; 173 | animation-play-state: running; 174 | } 175 | .rc-slider-tooltip-zoom-down-enter, 176 | .rc-slider-tooltip-zoom-down-appear { 177 | -webkit-transform: scale(0, 0); 178 | transform: scale(0, 0); 179 | -webkit-animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); 180 | animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); 181 | } 182 | .rc-slider-tooltip-zoom-down-leave { 183 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 184 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 185 | } 186 | @-webkit-keyframes rcSliderTooltipZoomDownIn { 187 | 0% { 188 | opacity: 0; 189 | -webkit-transform-origin: 50% 100%; 190 | transform-origin: 50% 100%; 191 | -webkit-transform: scale(0, 0); 192 | transform: scale(0, 0); 193 | } 194 | 100% { 195 | -webkit-transform-origin: 50% 100%; 196 | transform-origin: 50% 100%; 197 | -webkit-transform: scale(1, 1); 198 | transform: scale(1, 1); 199 | } 200 | } 201 | @keyframes rcSliderTooltipZoomDownIn { 202 | 0% { 203 | opacity: 0; 204 | -webkit-transform-origin: 50% 100%; 205 | transform-origin: 50% 100%; 206 | -webkit-transform: scale(0, 0); 207 | transform: scale(0, 0); 208 | } 209 | 100% { 210 | -webkit-transform-origin: 50% 100%; 211 | transform-origin: 50% 100%; 212 | -webkit-transform: scale(1, 1); 213 | transform: scale(1, 1); 214 | } 215 | } 216 | @-webkit-keyframes rcSliderTooltipZoomDownOut { 217 | 0% { 218 | -webkit-transform-origin: 50% 100%; 219 | transform-origin: 50% 100%; 220 | -webkit-transform: scale(1, 1); 221 | transform: scale(1, 1); 222 | } 223 | 100% { 224 | opacity: 0; 225 | -webkit-transform-origin: 50% 100%; 226 | transform-origin: 50% 100%; 227 | -webkit-transform: scale(0, 0); 228 | transform: scale(0, 0); 229 | } 230 | } 231 | @keyframes rcSliderTooltipZoomDownOut { 232 | 0% { 233 | -webkit-transform-origin: 50% 100%; 234 | transform-origin: 50% 100%; 235 | -webkit-transform: scale(1, 1); 236 | transform: scale(1, 1); 237 | } 238 | 100% { 239 | opacity: 0; 240 | -webkit-transform-origin: 50% 100%; 241 | transform-origin: 50% 100%; 242 | -webkit-transform: scale(0, 0); 243 | transform: scale(0, 0); 244 | } 245 | } 246 | .rc-slider-tooltip { 247 | position: absolute; 248 | left: -9999px; 249 | top: -9999px; 250 | visibility: visible; 251 | box-sizing: border-box; 252 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 253 | } 254 | .rc-slider-tooltip * { 255 | box-sizing: border-box; 256 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 257 | } 258 | .rc-slider-tooltip-hidden { 259 | display: none; 260 | } 261 | .rc-slider-tooltip-placement-top { 262 | padding: 4px 0 8px 0; 263 | } 264 | .rc-slider-tooltip-inner { 265 | padding: 6px 2px; 266 | min-width: 24px; 267 | height: 24px; 268 | font-size: 12px; 269 | line-height: 1; 270 | color: #fff; 271 | text-align: center; 272 | text-decoration: none; 273 | background-color: #6c6c6c; 274 | border-radius: 6px; 275 | box-shadow: 0 0 4px #d9d9d9; 276 | } 277 | .rc-slider-tooltip-arrow { 278 | position: absolute; 279 | width: 0; 280 | height: 0; 281 | border-color: transparent; 282 | border-style: solid; 283 | } 284 | .rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow { 285 | bottom: 4px; 286 | left: 50%; 287 | margin-left: -4px; 288 | border-width: 4px 4px 0; 289 | border-top-color: #6c6c6c; 290 | } 291 | -------------------------------------------------------------------------------- /docs/css/react-select.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Select 3 | * ============ 4 | * Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/ 5 | * https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs 6 | * MIT License: https://github.com/JedWatson/react-select 7 | */ 8 | .Select { 9 | position: relative; 10 | } 11 | .Select, 12 | .Select div, 13 | .Select input, 14 | .Select span { 15 | -webkit-box-sizing: border-box; 16 | -moz-box-sizing: border-box; 17 | box-sizing: border-box; 18 | } 19 | .Select.is-disabled > .Select-control { 20 | background-color: #f9f9f9; 21 | } 22 | .Select.is-disabled > .Select-control:hover { 23 | box-shadow: none; 24 | } 25 | .Select.is-disabled .Select-arrow-zone { 26 | cursor: default; 27 | pointer-events: none; 28 | opacity: 0.35; 29 | } 30 | .Select-control { 31 | background-color: #fff; 32 | border-color: #d9d9d9 #ccc #b3b3b3; 33 | border-radius: 4px; 34 | border: 1px solid #ccc; 35 | color: #333; 36 | cursor: default; 37 | display: table; 38 | border-spacing: 0; 39 | border-collapse: separate; 40 | height: 36px; 41 | outline: none; 42 | overflow: hidden; 43 | position: relative; 44 | width: 100%; 45 | } 46 | .Select-control:hover { 47 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); 48 | } 49 | .Select-control .Select-input:focus { 50 | outline: none; 51 | } 52 | .is-searchable.is-open > .Select-control { 53 | cursor: text; 54 | } 55 | .is-open > .Select-control { 56 | border-bottom-right-radius: 0; 57 | border-bottom-left-radius: 0; 58 | background: #fff; 59 | border-color: #b3b3b3 #ccc #d9d9d9; 60 | } 61 | .is-open > .Select-control > .Select-arrow { 62 | border-color: transparent transparent #999; 63 | border-width: 0 5px 5px; 64 | } 65 | .is-searchable.is-focused:not(.is-open) > .Select-control { 66 | cursor: text; 67 | } 68 | .is-focused:not(.is-open) > .Select-control { 69 | border-color: #007eff; 70 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px rgba(0, 126, 255, 0.1); 71 | } 72 | .Select-placeholder, 73 | .Select--single > .Select-control .Select-value { 74 | bottom: 0; 75 | color: #aaa; 76 | left: 0; 77 | line-height: 34px; 78 | padding-left: 10px; 79 | padding-right: 10px; 80 | position: absolute; 81 | right: 0; 82 | top: 0; 83 | max-width: 100%; 84 | overflow: hidden; 85 | text-overflow: ellipsis; 86 | white-space: nowrap; 87 | } 88 | .has-value.Select--single > .Select-control .Select-value .Select-value-label, 89 | .has-value.is-pseudo-focused.Select--single > .Select-control .Select-value .Select-value-label { 90 | color: #333; 91 | } 92 | .has-value.Select--single > .Select-control .Select-value a.Select-value-label, 93 | .has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label { 94 | cursor: pointer; 95 | text-decoration: none; 96 | } 97 | .has-value.Select--single > .Select-control .Select-value a.Select-value-label:hover, 98 | .has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label:hover, 99 | .has-value.Select--single > .Select-control .Select-value a.Select-value-label:focus, 100 | .has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label:focus { 101 | color: #007eff; 102 | outline: none; 103 | text-decoration: underline; 104 | } 105 | .Select-input { 106 | height: 34px; 107 | padding-left: 10px; 108 | padding-right: 10px; 109 | vertical-align: middle; 110 | } 111 | .Select-input > input { 112 | width: 100%; 113 | background: none transparent; 114 | border: 0 none; 115 | box-shadow: none; 116 | cursor: default; 117 | display: inline-block; 118 | font-family: inherit; 119 | font-size: inherit; 120 | margin: 0; 121 | outline: none; 122 | line-height: 14px; 123 | /* For IE 8 compatibility */ 124 | padding: 8px 0 12px; 125 | /* For IE 8 compatibility */ 126 | -webkit-appearance: none; 127 | } 128 | .is-focused .Select-input > input { 129 | cursor: text; 130 | } 131 | .has-value.is-pseudo-focused .Select-input { 132 | opacity: 0; 133 | } 134 | .Select-control:not(.is-searchable) > .Select-input { 135 | outline: none; 136 | } 137 | .Select-loading-zone { 138 | cursor: pointer; 139 | display: table-cell; 140 | position: relative; 141 | text-align: center; 142 | vertical-align: middle; 143 | width: 16px; 144 | } 145 | .Select-loading { 146 | -webkit-animation: Select-animation-spin 400ms infinite linear; 147 | -o-animation: Select-animation-spin 400ms infinite linear; 148 | animation: Select-animation-spin 400ms infinite linear; 149 | width: 16px; 150 | height: 16px; 151 | box-sizing: border-box; 152 | border-radius: 50%; 153 | border: 2px solid #ccc; 154 | border-right-color: #333; 155 | display: inline-block; 156 | position: relative; 157 | vertical-align: middle; 158 | } 159 | .Select-clear-zone { 160 | -webkit-animation: Select-animation-fadeIn 200ms; 161 | -o-animation: Select-animation-fadeIn 200ms; 162 | animation: Select-animation-fadeIn 200ms; 163 | color: #999; 164 | cursor: pointer; 165 | display: table-cell; 166 | position: relative; 167 | text-align: center; 168 | vertical-align: middle; 169 | width: 17px; 170 | } 171 | .Select-clear-zone:hover { 172 | color: #D0021B; 173 | } 174 | .Select-clear { 175 | display: inline-block; 176 | font-size: 18px; 177 | line-height: 1; 178 | } 179 | .Select--multi .Select-clear-zone { 180 | width: 17px; 181 | } 182 | .Select-arrow-zone { 183 | cursor: pointer; 184 | display: table-cell; 185 | position: relative; 186 | text-align: center; 187 | vertical-align: middle; 188 | width: 25px; 189 | padding-right: 5px; 190 | } 191 | .Select-arrow { 192 | border-color: #999 transparent transparent; 193 | border-style: solid; 194 | border-width: 5px 5px 2.5px; 195 | display: inline-block; 196 | height: 0; 197 | width: 0; 198 | } 199 | .is-open .Select-arrow, 200 | .Select-arrow-zone:hover > .Select-arrow { 201 | border-top-color: #666; 202 | } 203 | .Select--multi .Select-multi-value-wrapper { 204 | display: inline-block; 205 | } 206 | .Select .Select-aria-only { 207 | display: inline-block; 208 | height: 1px; 209 | width: 1px; 210 | margin: -1px; 211 | clip: rect(0, 0, 0, 0); 212 | overflow: hidden; 213 | } 214 | @-webkit-keyframes Select-animation-fadeIn { 215 | from { 216 | opacity: 0; 217 | } 218 | to { 219 | opacity: 1; 220 | } 221 | } 222 | @keyframes Select-animation-fadeIn { 223 | from { 224 | opacity: 0; 225 | } 226 | to { 227 | opacity: 1; 228 | } 229 | } 230 | .Select-menu-outer { 231 | border-bottom-right-radius: 4px; 232 | border-bottom-left-radius: 4px; 233 | background-color: #fff; 234 | border: 1px solid #ccc; 235 | border-top-color: #e6e6e6; 236 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); 237 | box-sizing: border-box; 238 | margin-top: -1px; 239 | max-height: 200px; 240 | position: absolute; 241 | top: 100%; 242 | width: 100%; 243 | z-index: 1; 244 | -webkit-overflow-scrolling: touch; 245 | } 246 | .Select-menu { 247 | max-height: 198px; 248 | overflow-y: auto; 249 | } 250 | .Select-option { 251 | box-sizing: border-box; 252 | background-color: #fff; 253 | color: #666666; 254 | cursor: pointer; 255 | display: block; 256 | padding: 8px 10px; 257 | } 258 | .Select-option:last-child { 259 | border-bottom-right-radius: 4px; 260 | border-bottom-left-radius: 4px; 261 | } 262 | .Select-option.is-selected { 263 | background-color: #f5faff; 264 | /* Fallback color for IE 8 */ 265 | background-color: rgba(0, 126, 255, 0.04); 266 | color: #333; 267 | } 268 | .Select-option.is-focused { 269 | background-color: #ebf5ff; 270 | /* Fallback color for IE 8 */ 271 | background-color: rgba(0, 126, 255, 0.08); 272 | color: #333; 273 | } 274 | .Select-option.is-disabled { 275 | color: #cccccc; 276 | cursor: default; 277 | } 278 | .Select-noresults { 279 | box-sizing: border-box; 280 | color: #999999; 281 | cursor: default; 282 | display: block; 283 | padding: 8px 10px; 284 | } 285 | .Select--multi .Select-input { 286 | vertical-align: middle; 287 | margin-left: 10px; 288 | padding: 0; 289 | } 290 | .Select--multi.has-value .Select-input { 291 | margin-left: 5px; 292 | } 293 | .Select--multi .Select-value { 294 | background-color: #ebf5ff; 295 | /* Fallback color for IE 8 */ 296 | background-color: rgba(0, 126, 255, 0.08); 297 | border-radius: 2px; 298 | border: 1px solid #c2e0ff; 299 | /* Fallback color for IE 8 */ 300 | border: 1px solid rgba(0, 126, 255, 0.24); 301 | color: #007eff; 302 | display: inline-block; 303 | font-size: 0.9em; 304 | line-height: 1.4; 305 | margin-left: 5px; 306 | margin-top: 5px; 307 | vertical-align: top; 308 | } 309 | .Select--multi .Select-value-icon, 310 | .Select--multi .Select-value-label { 311 | display: inline-block; 312 | vertical-align: middle; 313 | } 314 | .Select--multi .Select-value-label { 315 | border-bottom-right-radius: 2px; 316 | border-top-right-radius: 2px; 317 | cursor: default; 318 | padding: 2px 5px; 319 | } 320 | .Select--multi a.Select-value-label { 321 | color: #007eff; 322 | cursor: pointer; 323 | text-decoration: none; 324 | } 325 | .Select--multi a.Select-value-label:hover { 326 | text-decoration: underline; 327 | } 328 | .Select--multi .Select-value-icon { 329 | cursor: pointer; 330 | border-bottom-left-radius: 2px; 331 | border-top-left-radius: 2px; 332 | border-right: 1px solid #c2e0ff; 333 | /* Fallback color for IE 8 */ 334 | border-right: 1px solid rgba(0, 126, 255, 0.24); 335 | padding: 1px 5px 3px; 336 | } 337 | .Select--multi .Select-value-icon:hover, 338 | .Select--multi .Select-value-icon:focus { 339 | background-color: #d8eafd; 340 | /* Fallback color for IE 8 */ 341 | background-color: rgba(0, 113, 230, 0.08); 342 | color: #0071e6; 343 | } 344 | .Select--multi .Select-value-icon:active { 345 | background-color: #c2e0ff; 346 | /* Fallback color for IE 8 */ 347 | background-color: rgba(0, 126, 255, 0.24); 348 | } 349 | .Select--multi.is-disabled .Select-value { 350 | background-color: #fcfcfc; 351 | border: 1px solid #e3e3e3; 352 | color: #333; 353 | } 354 | .Select--multi.is-disabled .Select-value-icon { 355 | cursor: not-allowed; 356 | border-right: 1px solid #e3e3e3; 357 | } 358 | .Select--multi.is-disabled .Select-value-icon:hover, 359 | .Select--multi.is-disabled .Select-value-icon:focus, 360 | .Select--multi.is-disabled .Select-value-icon:active { 361 | background-color: #fcfcfc; 362 | } 363 | @keyframes Select-animation-spin { 364 | to { 365 | transform: rotate(1turn); 366 | } 367 | } 368 | @-webkit-keyframes Select-animation-spin { 369 | to { 370 | -webkit-transform: rotate(1turn); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | html { 4 | color: #454649; 5 | font-size: 16px; 6 | font-family: 'Questrial', sans-serif; 7 | line-height: 1.7; 8 | } 9 | 10 | body { 11 | min-width: 320px; 12 | } 13 | 14 | img { 15 | max-width: 100%; 16 | height: auto; 17 | vertical-align: top; 18 | } 19 | 20 | a { 21 | color: #454649; 22 | transition: color 280ms ease-out; 23 | text-decoration: none; 24 | } 25 | 26 | a:hover { 27 | color: #9fa2ab; 28 | } 29 | 30 | button:focus { 31 | outline: none; 32 | } 33 | 34 | ::selection { 35 | background-color: rgba(50, 95, 92, .4); 36 | } 37 | 38 | ::-moz-selection { 39 | background-color: rgba(50, 95, 92, .4); 40 | } 41 | 42 | ::-webkit-scrollbar { 43 | width: 8px; 44 | background: #efefef; 45 | z-index: 10; 46 | } 47 | 48 | ::-webkit-scrollbar:horizontal { 49 | height: 6px; 50 | } 51 | 52 | ::-webkit-scrollbar-track { 53 | border-radius: 2px; 54 | } 55 | 56 | ::-webkit-scrollbar-thumb { 57 | border-radius: 2px; 58 | background: #ccc; 59 | } 60 | 61 | 62 | /* Cutomize */ 63 | .rc-slider-handle { 64 | margin-top: -6px; 65 | border: none; 66 | background: #fff; 67 | } 68 | 69 | .rc-slider-track { 70 | background: #fff; 71 | } 72 | 73 | .rc-slider-step { 74 | height: 2px; 75 | } 76 | 77 | .rc-slider-rail { 78 | height: 2px; 79 | background: #b1960a; 80 | } 81 | 82 | 83 | /* Header */ 84 | .header { 85 | } 86 | 87 | .header h1 { 88 | position: fixed; 89 | top: 30px; 90 | left: 30px; 91 | z-index: 1000; 92 | width: 180px; 93 | margin: 0; 94 | } 95 | 96 | .header nav { 97 | position: fixed; 98 | top: 30px; 99 | left: 210px; 100 | z-index: 1000; 101 | } 102 | 103 | .header ul { 104 | margin: 0; 105 | padding: 0; 106 | list-style: none; 107 | font-size: 0; 108 | } 109 | 110 | .header li { 111 | display: inline-block; 112 | margin: 0 0 0 2em; 113 | font-size: 1rem; 114 | } 115 | 116 | .header .is-active { 117 | color: #ecc60b; 118 | } 119 | 120 | 121 | /* Button */ 122 | .btn { 123 | display: inline-block; 124 | padding: 0 2em; 125 | background: #fff; 126 | border: none; 127 | color: #ebc60b; 128 | font-size: 1rem; 129 | font-weight: bold; 130 | text-align: center; 131 | line-height: 50px; 132 | white-space: nowrap; 133 | vertical-align: middle; 134 | cursor: pointer; 135 | transition: all 280ms ease-out; 136 | -webkit-user-select: none; 137 | user-select: none; 138 | } 139 | 140 | .btn:hover { 141 | background: #e0bc09; 142 | color: #fff; 143 | } 144 | 145 | 146 | /* Demo control */ 147 | .demo-control { 148 | display: flex; 149 | position: fixed; 150 | right: 0; 151 | bottom: 0; 152 | left: 0; 153 | z-index: 1000; 154 | padding: 15px; 155 | overflow-x: auto; 156 | overflow-y: hidden; 157 | -webkit-overflow-scrolling : touch; 158 | background: #ebc60b; 159 | color: #fff; 160 | } 161 | 162 | .demo-control > div { 163 | padding-right: 15px; 164 | } 165 | 166 | .demo-control label { 167 | display: block; 168 | font-size: 12px; 169 | } 170 | 171 | .demo-control .rc-slider { 172 | width: 140px; 173 | margin: 10px 0 0; 174 | } 175 | 176 | .demo-control .btn { 177 | font-size: 12px; 178 | } 179 | 180 | .demo-control select { 181 | display: block; 182 | min-width: 140px; 183 | height: 35px; 184 | } 185 | 186 | 187 | /* Content */ 188 | .content { 189 | padding: 120px 30px; 190 | } 191 | 192 | 193 | /* Item */ 194 | .item { 195 | display: block; 196 | width: 100%; 197 | } 198 | 199 | .item--gray { 200 | background: #b8bbbe; 201 | } 202 | 203 | .item--gray-light { 204 | background: #dfe0df; 205 | } 206 | 207 | .item--gray-dark { 208 | background: #47484a; 209 | } 210 | 211 | .item--yellow { 212 | background: #e1c531; 213 | } 214 | 215 | .item--pink { 216 | background: #d59c90; 217 | } 218 | 219 | .item--purple { 220 | background: #c9cae3; 221 | } 222 | 223 | .item--pattern1 { 224 | background: url("../images/pattern1.png") repeat 0 0; 225 | background-size: 45px; 226 | } 227 | 228 | .item--pattern2 { 229 | background: url("../images/pattern2.png") repeat 0 0; 230 | background-size: 66px; 231 | } 232 | 233 | .item--pattern3 { 234 | background: url("../images/pattern3.png") repeat 0 0; 235 | background-size: 45px; 236 | } 237 | 238 | 239 | /* Image */ 240 | .image { 241 | display: block; 242 | margin: 0; 243 | } 244 | 245 | .image figcaption { 246 | display: block; 247 | margin: .2em 0 0; 248 | font-weight: normal; 249 | } 250 | 251 | 252 | @media (max-width: 580px) { 253 | .header h1 { 254 | top: 15px; 255 | left: 15px; 256 | width: 120px; 257 | } 258 | 259 | .header nav { 260 | top: 15px; 261 | left: 135px; 262 | } 263 | 264 | .header li { 265 | display: block; 266 | margin-left: 1em; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/favicon.ico -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/pattern1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/pattern1.png -------------------------------------------------------------------------------- /docs/images/pattern2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/pattern2.png -------------------------------------------------------------------------------- /docs/images/pattern3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/pattern3.png -------------------------------------------------------------------------------- /docs/images/photos/photo01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo01.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo02.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo03.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo04.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo05.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo06.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo07.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo08.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo09.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo10.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo11.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo12.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo13.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo14.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo15.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo16.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo17.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo18.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo19.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo20.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo21.jpg -------------------------------------------------------------------------------- /docs/images/photos/photo22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsuyoshiwada/react-stack-grid/13349baf5e12738359facfc126240e3633855752/docs/images/photos/photo22.jpg -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | React Stack Grid 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/js/App.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | import Header from './components/Header'; 4 | 5 | const App = ({ children }) => ( 6 |
7 |
8 |
9 | {children} 10 |
11 |
12 | ); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /docs/js/Root.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | import React from 'react'; 4 | import { Router, hashHistory } from 'react-router'; 5 | 6 | const Root = ({ routes }) => ( 7 | 8 | {routes()} 9 | 10 | ); 11 | 12 | export default Root; 13 | -------------------------------------------------------------------------------- /docs/js/components/DemoControl.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | import React, { Component } from 'react'; 4 | import Slider from 'rc-slider'; 5 | import { easings, transitions } from '../../../src/'; 6 | 7 | const selectEasingOptions = Object.keys(easings).map(k => ({ 8 | label: k, 9 | value: easings[k], 10 | })); 11 | 12 | const selectTransitionOptions = Object.keys(transitions).map(k => ({ 13 | label: k, 14 | value: k, 15 | })); 16 | 17 | 18 | export default class DemoControl extends Component { 19 | handleShuffle = () => { 20 | this.props.onShuffle(); 21 | } 22 | 23 | handlePrepend = () => { 24 | this.props.onPrepend(); 25 | } 26 | 27 | handleAppend = () => { 28 | this.props.onAppend(); 29 | } 30 | 31 | handleMultipleAppend = () => { 32 | this.props.onMultipleAppend(); 33 | } 34 | 35 | handleDurationChange = (value) => { 36 | this.props.onDurationChange(value); 37 | } 38 | 39 | handleColumnWidthChange = (value) => { 40 | this.props.onColumnWidthChange(value); 41 | } 42 | 43 | handleGutterChange = (value) => { 44 | this.props.onGutterChange(value); 45 | } 46 | 47 | handleEasingChange = (e) => { 48 | this.props.onEasingChange(e.target.value); 49 | } 50 | 51 | handleTransitionChange = (e) => { 52 | this.props.onTransitionChange(e.target.value); 53 | } 54 | 55 | handleRTLChange = (e) => { 56 | this.props.onRTLChange(e.target.checked); 57 | } 58 | 59 | render() { 60 | const { 61 | duration, 62 | columnWidth, 63 | gutter, 64 | easing, 65 | transition, 66 | rtl, 67 | } = this.props; 68 | 69 | return ( 70 |
71 |
72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 | 81 |
82 | 83 |
84 | 85 |
86 | 87 |
88 | 89 |
90 | 91 |
92 | 93 | 99 |
100 | 101 |
102 | 103 | 109 |
110 | 111 |
112 | 113 | 119 |
120 | 121 |
122 | 123 |
124 | 125 | 130 |
131 | 132 |
133 | 134 | 139 |
140 | 141 |
142 | 143 | 144 |
145 |
146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /docs/js/components/Header.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | /* eslint-disable jsx-a11y/anchor-is-valid */ 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | import React from 'react'; 5 | import { IndexLink, Link } from 'react-router'; 6 | 7 | const Header = () => ( 8 |
9 |

React Stack Grid

10 | 19 |
20 | ); 21 | 22 | export default Header; 23 | -------------------------------------------------------------------------------- /docs/js/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | import { AppContainer } from 'react-hot-loader'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import Root from './Root'; 7 | import routes from './routes'; 8 | 9 | 10 | const render = (nextRoutes) => { 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | }; 18 | 19 | render(routes); 20 | 21 | if (module.hot) { 22 | module.hot.accept('./routes', () => { 23 | const nextRoutes = require('./routes').default; 24 | render(nextRoutes); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /docs/js/pages/ChangeSize.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-return-assign */ 2 | /* eslint-disable react/prop-types */ 3 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 4 | /* eslint-disable jsx-a11y/click-events-have-key-events */ 5 | import React, { Component } from 'react'; 6 | import StackGrid from '../../../src/'; 7 | 8 | 9 | const items = []; 10 | 11 | for (let i = 0; i < 30; i += 1) { 12 | const id = Math.random().toString(36).substr(2, 9); 13 | const height = Math.floor((Math.random() * (300 - 80)) + 80); 14 | 15 | items.push({ 16 | id, 17 | height, 18 | active: false, 19 | actualHeight: height, 20 | }); 21 | } 22 | 23 | 24 | export default class ChangeSize extends Component { 25 | state = { items }; 26 | 27 | changeItemSize = (id) => { 28 | this.setState({ 29 | items: this.state.items.map(o => ( 30 | o.id !== id ? o : { 31 | ...o, 32 | active: !o.active, 33 | height: !o.active ? o.height * 1.5 : o.actualHeight, 34 | } 35 | )), 36 | }, () => { 37 | this.grid.updateLayout(); 38 | }); 39 | }; 40 | 41 | render() { 42 | return ( 43 | this.grid = grid} 45 | columnWidth={120} 46 | > 47 | {this.state.items.map(({ id, active, height }) => ( 48 |
this.changeItemSize(id)} 56 | /> 57 | ))} 58 | 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /docs/js/pages/Home.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | /* eslint-disable react/prop-types */ 3 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 4 | /* eslint-disable jsx-a11y/click-events-have-key-events */ 5 | import React, { Component } from 'react'; 6 | import StackGrid, { transitions, easings } from '../../../src/'; 7 | import DemoControl from '../components/DemoControl'; 8 | 9 | const itemModifier = [ 10 | 'pattern1', 11 | 'pattern2', 12 | 'pattern3', 13 | 'gray', 14 | 'gray-light', 15 | 'gray-dark', 16 | 'yellow', 17 | 'pink', 18 | 'purple', 19 | ]; 20 | 21 | export default class Home extends Component { 22 | constructor(props) { 23 | super(props); 24 | 25 | const items = []; 26 | 27 | for (let i = 0; i < 10; i += 1) { 28 | items.push(this.createItem()); 29 | } 30 | 31 | this.state = { 32 | items, 33 | duration: 480, 34 | columnWidth: 150, 35 | gutter: 5, 36 | easing: easings.quartOut, 37 | transition: 'fadeDown', 38 | rtl: false, 39 | }; 40 | } 41 | 42 | createItem() { 43 | const id = Math.random().toString(36).substr(2, 9); 44 | const height = Math.floor((Math.random() * (300 - 80)) + 80); 45 | const modifier = itemModifier[Math.floor(Math.random() * itemModifier.length)]; 46 | 47 | return { id, height, modifier }; 48 | } 49 | 50 | shuffleItems = () => { 51 | const newItems = [...this.state.items]; 52 | let i = newItems.length; 53 | 54 | while (i) { 55 | const j = Math.floor(Math.random() * i); 56 | const t = newItems[--i]; // eslint-disable-line no-plusplus 57 | newItems[i] = newItems[j]; 58 | newItems[j] = t; 59 | } 60 | 61 | this.setState({ items: newItems }); 62 | } 63 | 64 | prependItem = () => { 65 | this.setState({ 66 | items: [this.createItem(), ...this.state.items], 67 | }); 68 | } 69 | 70 | appendItem = () => { 71 | this.setState({ 72 | items: [...this.state.items, this.createItem()], 73 | }); 74 | } 75 | 76 | multipleAppendItem = () => { 77 | const newItems = []; 78 | 79 | for (let i = 0; i < 5; i += 1) { 80 | newItems.push(this.createItem()); 81 | } 82 | 83 | this.setState({ 84 | items: [...this.state.items, ...newItems], 85 | }); 86 | } 87 | 88 | removeItem = (id) => { 89 | this.setState({ 90 | items: this.state.items.filter(o => o.id !== id), 91 | }); 92 | } 93 | 94 | handleDurationChange = (duration) => { 95 | this.setState({ duration }); 96 | } 97 | 98 | handleColumnWidthChange = (columnWidth) => { 99 | this.setState({ columnWidth }); 100 | } 101 | 102 | handleGutterChange = (gutter) => { 103 | this.setState({ gutter }); 104 | } 105 | 106 | handleEasingChange = (easing) => { 107 | this.setState({ easing }); 108 | } 109 | 110 | handleTransitionChange = (transition) => { 111 | this.setState({ transition }); 112 | } 113 | 114 | handleRTLChange = (rtl) => { 115 | this.setState({ rtl }); 116 | } 117 | 118 | render() { 119 | const { 120 | items, 121 | duration, 122 | columnWidth, 123 | gutter, 124 | easing, 125 | transition: transitionSelect, 126 | rtl, 127 | } = this.state; 128 | 129 | const transition = transitions[transitionSelect]; 130 | return ( 131 |
132 | 150 | 151 | { 164 | console.log('[DEMO] `onLayout()` has been called.'); // eslint-disable-line 165 | }} 166 | > 167 | {items.map(item => 168 | (
this.removeItem(item.id)} 173 | />) 174 | )} 175 | 176 |
177 | ); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /docs/js/pages/HorizontalFlow.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | /* eslint-disable react/prop-types */ 3 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 4 | /* eslint-disable jsx-a11y/click-events-have-key-events */ 5 | import React, { Component } from 'react'; 6 | import StackGrid, { transitions, easings } from '../../../src/'; 7 | import DemoControl from '../components/DemoControl'; 8 | 9 | const itemModifier = [ 10 | 'pattern1', 11 | 'pattern2', 12 | 'pattern3', 13 | 'gray', 14 | 'gray-light', 15 | 'gray-dark', 16 | 'yellow', 17 | 'pink', 18 | 'purple', 19 | ]; 20 | 21 | export default class HorizontalFlow extends Component { 22 | constructor(props) { 23 | super(props); 24 | 25 | const items = []; 26 | 27 | for (let i = 0; i < 10; i += 1) { 28 | items.push(this.createItem()); 29 | } 30 | 31 | this.state = { 32 | items, 33 | duration: 480, 34 | columnWidth: 150, 35 | gutter: 5, 36 | easing: easings.quartOut, 37 | transition: 'fadeDown', 38 | }; 39 | } 40 | 41 | createItem() { 42 | const id = Math.random().toString(36).substr(2, 9); 43 | const height = Math.floor((Math.random() * (300 - 80)) + 80); 44 | const modifier = itemModifier[Math.floor(Math.random() * itemModifier.length)]; 45 | 46 | return { id, height, modifier }; 47 | } 48 | 49 | shuffleItems = () => { 50 | const newItems = [...this.state.items]; 51 | let i = newItems.length; 52 | 53 | while (i) { 54 | const j = Math.floor(Math.random() * i); 55 | const t = newItems[--i]; // eslint-disable-line no-plusplus 56 | newItems[i] = newItems[j]; 57 | newItems[j] = t; 58 | } 59 | 60 | this.setState({ items: newItems }); 61 | } 62 | 63 | prependItem = () => { 64 | this.setState({ 65 | items: [this.createItem(), ...this.state.items], 66 | }); 67 | } 68 | 69 | appendItem = () => { 70 | this.setState({ 71 | items: [...this.state.items, this.createItem()], 72 | }); 73 | } 74 | 75 | multipleAppendItem = () => { 76 | const newItems = []; 77 | 78 | for (let i = 0; i < 5; i += 1) { 79 | newItems.push(this.createItem()); 80 | } 81 | 82 | this.setState({ 83 | items: [...this.state.items, ...newItems], 84 | }); 85 | } 86 | 87 | removeItem = (id) => { 88 | this.setState({ 89 | items: this.state.items.filter(o => o.id !== id), 90 | }); 91 | } 92 | 93 | handleDurationChange = (duration) => { 94 | this.setState({ duration }); 95 | } 96 | 97 | handleColumnWidthChange = (columnWidth) => { 98 | this.setState({ columnWidth }); 99 | } 100 | 101 | handleGutterChange = (gutter) => { 102 | this.setState({ gutter }); 103 | } 104 | 105 | handleEasingChange = (easing) => { 106 | this.setState({ easing }); 107 | } 108 | 109 | handleTransitionChange = (transition) => { 110 | this.setState({ transition }); 111 | } 112 | 113 | render() { 114 | const { 115 | items, 116 | duration, 117 | columnWidth, 118 | gutter, 119 | easing, 120 | transition: transitionSelect, 121 | } = this.state; 122 | 123 | const transition = transitions[transitionSelect]; 124 | 125 | return ( 126 |
127 | 143 | 144 | { 157 | console.log('[DEMO] `onLayout()` has been called.'); // eslint-disable-line 158 | }} 159 | > 160 | {items.map(item => 161 | (
this.removeItem(item.id)} 166 | />) 167 | )} 168 | 169 |
170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /docs/js/pages/RealWorld.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | import StackGrid, { transitions, easings } from '../../../src/'; 4 | 5 | 6 | const transition = transitions.scaleDown; 7 | 8 | const images = [ 9 | { src: './images/photos/photo01.jpg', label: 'Sample image 1' }, 10 | { src: './images/photos/photo02.jpg', label: 'Sample image 2' }, 11 | { src: './images/photos/photo03.jpg', label: 'Sample image 3' }, 12 | { src: './images/photos/photo04.jpg', label: 'Sample image 4' }, 13 | { src: './images/photos/photo05.jpg', label: 'Sample image 5' }, 14 | { src: './images/photos/photo06.jpg', label: 'Sample image 6' }, 15 | { src: './images/photos/photo07.jpg', label: 'Sample image 7' }, 16 | { src: './images/photos/photo08.jpg', label: 'Sample image 8' }, 17 | { src: './images/photos/photo09.jpg', label: 'Sample image 9' }, 18 | { src: './images/photos/photo10.jpg', label: 'Sample image 10' }, 19 | { src: './images/photos/photo11.jpg', label: 'Sample image 11' }, 20 | { src: './images/photos/photo12.jpg', label: 'Sample image 12' }, 21 | { src: './images/photos/photo13.jpg', label: 'Sample image 13' }, 22 | { src: './images/photos/photo14.jpg', label: 'Sample image 14' }, 23 | { src: './images/photos/photo15.jpg', label: 'Sample image 15' }, 24 | { src: './images/photos/photo16.jpg', label: 'Sample image 16' }, 25 | { src: './images/photos/photo17.jpg', label: 'Sample image 17' }, 26 | { src: './images/photos/photo18.jpg', label: 'Sample image 18' }, 27 | { src: './images/photos/photo19.jpg', label: 'Sample image 19' }, 28 | { src: './images/photos/photo20.jpg', label: 'Sample image 20' }, 29 | { src: './images/photos/photo21.jpg', label: 'Sample image 21' }, 30 | { src: './images/photos/photo22.jpg', label: 'Sample image 22' }, 31 | ]; 32 | 33 | 34 | const RealWorld = () => ( 35 | 49 | {images.map(obj => ( 50 |
54 | {obj.label} 55 |
{obj.label}
56 |
57 | ))} 58 |
59 | ); 60 | 61 | export default RealWorld; 62 | -------------------------------------------------------------------------------- /docs/js/routes.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import React from 'react'; 3 | import { Route, IndexRoute } from 'react-router'; 4 | import App from './App'; 5 | import Home from './pages/Home'; 6 | import ChangeSize from './pages/ChangeSize'; 7 | import RealWorld from './pages/RealWorld'; 8 | import HorizontalFlow from './pages/HorizontalFlow'; 9 | 10 | const routes = () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | 19 | export default routes; 20 | -------------------------------------------------------------------------------- /docs/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | 'react-hot-loader/patch', 7 | 'webpack-dev-server/client?http://localhost:3000', 8 | 'webpack/hot/only-dev-server', 9 | path.resolve(__dirname, 'js/index.js'), 10 | ], 11 | 12 | output: { 13 | filename: 'bundle.js', 14 | path: __dirname, 15 | publicPath: '/', 16 | }, 17 | 18 | devtool: 'inline-source-map', 19 | 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.jsx?$/, 24 | use: [ 25 | 'react-hot-loader/webpack', 26 | 'babel-loader', 27 | ], 28 | exclude: /node_modules/, 29 | }, 30 | ], 31 | }, 32 | 33 | plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NamedModulesPlugin(), 36 | new webpack.NoEmitOnErrorsPlugin(), 37 | ], 38 | 39 | devServer: { 40 | contentBase: __dirname, 41 | host: '0.0.0.0', 42 | port: 3000, 43 | historyApiFallback: true, 44 | hot: true, 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /docs/webpack.config.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | path.resolve(__dirname, 'js/index.js'), 7 | ], 8 | 9 | output: { 10 | filename: 'bundle.js', 11 | path: __dirname, 12 | publicPath: '/', 13 | }, 14 | 15 | devtool: false, 16 | 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.jsx?$/, 21 | use: [ 22 | 'babel-loader', 23 | ], 24 | exclude: /node_modules/, 25 | }, 26 | ], 27 | }, 28 | 29 | plugins: [ 30 | new webpack.NamedModulesPlugin(), 31 | new webpack.LoaderOptionsPlugin({ 32 | minimize: true, 33 | debug: false, 34 | }), 35 | new webpack.DefinePlugin({ 36 | 'process.env.NODE_ENV': JSON.stringify('production'), 37 | }), 38 | new webpack.optimize.UglifyJsPlugin({ 39 | beautify: false, 40 | mangle: { 41 | screw_ie8: true, 42 | keep_fnames: true, 43 | }, 44 | compress: { 45 | screw_ie8: true, 46 | }, 47 | comments: false, 48 | }), 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /jest/setup.js: -------------------------------------------------------------------------------- 1 | global.window.requestAnimationFrame = callback => ( 2 | setTimeout(callback, 1) 3 | ); 4 | 5 | global.window.cancelAnimationFrame = () => {}; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-stack-grid", 3 | "version": "0.7.1", 4 | "description": "Pinterest like layout components for React.js", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "start": "npm run docs", 8 | "clean": "rimraf lib", 9 | "build": "babel src/ -d lib/ --ignore \"**/__tests__/*\"", 10 | "test": "npm run test:lint && npm run test:typecheck && npm run test:unit", 11 | "test:lint": "eslint \"src/**/*.js\" \"docs/js/**/*.js\"", 12 | "test:typecheck": "flow", 13 | "test:unit": "jest", 14 | "test:watch": "npm run test:unit -- --watch", 15 | "docs": "webpack-dev-server --config docs/webpack.config.js", 16 | "docs:build": "webpack -p --config docs/webpack.config.production.js", 17 | "docs:deploy": "npm run docs:build && gh-pages -d docs", 18 | "prebuild": "npm run clean", 19 | "prepublish": "npm run build" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/tsuyoshiwada/react-stack-grid.git" 24 | }, 25 | "keywords": [ 26 | "react", 27 | "react-component", 28 | "layout", 29 | "grid", 30 | "pinterest", 31 | "masonry" 32 | ], 33 | "author": "tsuyoshiwada", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/tsuyoshiwada/react-stack-grid/issues" 37 | }, 38 | "homepage": "https://github.com/tsuyoshiwada/react-stack-grid#readme", 39 | "files": [ 40 | "lib", 41 | "test" 42 | ], 43 | "browserify": { 44 | "transform": [ 45 | "babelify" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "babel-cli": "^6.18.0", 50 | "babel-eslint": "^8.0.3", 51 | "babel-jest": "^21.2.0", 52 | "babel-loader": "^7.1.1", 53 | "babel-preset-es2015": "^6.18.0", 54 | "babel-preset-flow": "^6.23.0", 55 | "babel-preset-power-assert": "^1.0.0", 56 | "babel-preset-react": "^6.16.0", 57 | "babel-preset-stage-1": "^6.16.0", 58 | "babel-register": "^6.18.0", 59 | "enzyme": "^2.9.1", 60 | "eslint": "^4.2.0", 61 | "eslint-config-airbnb": "^16.1.0", 62 | "eslint-plugin-babel": "^4.0.0", 63 | "eslint-plugin-flowtype": "^2.34.0", 64 | "eslint-plugin-flowtype-errors": "^3.3.0", 65 | "eslint-plugin-import": "^2.3.0", 66 | "eslint-plugin-jest": "^21.4.2", 67 | "eslint-plugin-jsx-a11y": "^6.0.2", 68 | "eslint-plugin-react": "^7.0.1", 69 | "flow-bin": "^0.50.0", 70 | "gh-pages": "^1.0.0", 71 | "jest": "^21.2.1", 72 | "rc-slider": "^8.2.0", 73 | "react": "^15.5.4", 74 | "react-addons-test-utils": "^15.6.0", 75 | "react-dom": "^15.5.4", 76 | "react-hot-loader": "^3.1.3", 77 | "react-router": "^3.0.0", 78 | "react-test-renderer": "^15.6.1", 79 | "rimraf": "^2.6.1", 80 | "sinon": "^2.3.2", 81 | "webpack": "^3.3.0", 82 | "webpack-dev-server": "^2.5.1" 83 | }, 84 | "peerDependencies": { 85 | "react": ">=15.3.0", 86 | "react-dom": ">=15.3.0" 87 | }, 88 | "dependencies": { 89 | "easy-css-transform-builder": "^0.0.2", 90 | "exenv": "^1.2.1", 91 | "imagesloaded": "^4.1.1", 92 | "inline-style-prefixer": "^3.0.6", 93 | "invariant": "^2.2.2", 94 | "prop-types": "^15.5.10", 95 | "react-sizeme": "^2.2.0", 96 | "react-transition-group": "^1.2.0", 97 | "shallowequal": "^1.0.1" 98 | }, 99 | "jest": { 100 | "testMatch": [ 101 | "**/?(*.)spec.js" 102 | ], 103 | "testPathIgnorePatterns": [ 104 | "/node_modules/", 105 | "/lib/" 106 | ], 107 | "setupFiles": [ 108 | "./jest/setup.js" 109 | ] 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/animations/easings.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // http://easings.net/ 3 | export const linear = 'linear'; 4 | export const easeIn = 'ease-in'; 5 | export const easeOut = 'ease-out'; 6 | export const easeInOut = 'ease-in-out'; 7 | export const sineIn = 'cubic-bezier(0.47, 0, 0.745, 0.715)'; 8 | export const sineOut = 'cubic-bezier(0.39, 0.575, 0.565, 1)'; 9 | export const sineInOut = 'cubic-bezier(0.445, 0.05, 0.55, 0.95)'; 10 | export const quadIn = 'cubic-bezier(0.55, 0.085, 0.68, 0.53)'; 11 | export const quadOut = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'; 12 | export const quadInOut = 'cubic-bezier(0.455, 0.03, 0.515, 0.955)'; 13 | export const cubicIn = 'cubic-bezier(0.55, 0.055, 0.675, 0.19)'; 14 | export const cubicOut = 'cubic-bezier(0.215, 0.61, 0.355, 1)'; 15 | export const cubicInOut = 'cubic-bezier(0.645, 0.045, 0.355, 1)'; 16 | export const quartIn = 'cubic-bezier(0.895, 0.03, 0.685, 0.22)'; 17 | export const quartOut = 'cubic-bezier(0.165, 0.84, 0.44, 1)'; 18 | export const quartInOut = 'cubic-bezier(0.77, 0, 0.175, 1)'; 19 | export const quintIn = 'cubic-bezier(0.755, 0.05, 0.855, 0.06)'; 20 | export const quintOut = 'cubic-bezier(0.23, 1, 0.32, 1)'; 21 | export const quintInOut = 'cubic-bezier(0.86, 0, 0.07, 1)'; 22 | export const expoIn = 'cubic-bezier(0.95, 0.05, 0.795, 0.035)'; 23 | export const expoOut = 'cubic-bezier(0.19, 1, 0.22, 1)'; 24 | export const expoInOut = 'cubic-bezier(1, 0, 0, 1)'; 25 | export const circIn = 'cubic-bezier(0.6, 0.04, 0.98, 0.335)'; 26 | export const circOut = 'cubic-bezier(0.075, 0.82, 0.165, 1)'; 27 | export const circInOut = 'cubic-bezier(0.785, 0.135, 0.15, 0.86)'; 28 | export const backIn = 'cubic-bezier(0.6, -0.28, 0.735, 0.045)'; 29 | export const backOut = 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'; 30 | export const backInOut = 'cubic-bezier(0.68, -0.55, 0.265, 1.55)'; 31 | -------------------------------------------------------------------------------- /src/animations/request-animation-frame.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import ExecutionEnvironment from 'exenv'; 3 | 4 | const vendors = ['ms', 'moz', 'webkit']; 5 | let tmpRaf = null; 6 | let tmpCaf = null; 7 | 8 | if (ExecutionEnvironment.canUseDOM) { 9 | tmpRaf = window.requestAnimationFrame; 10 | tmpCaf = window.cancelAnimationFrame; 11 | 12 | // eslint-disable-next-line no-plusplus 13 | for (let x = 0; x < vendors.length && !tmpRaf; ++x) { 14 | tmpRaf = window[`${vendors[x]}RequestAnimationFrame`]; 15 | tmpCaf = window[`${vendors[x]}CancelAnimationFrame`] || 16 | window[`${vendors[x]}CancelRequestAnimationFrame`]; 17 | } 18 | } else { 19 | tmpRaf = (callback: Function): number => callback(); 20 | tmpCaf = (id: number): void => {}; // eslint-disable-line no-unused-vars 21 | } 22 | 23 | export const raf = tmpRaf; 24 | export const caf = tmpCaf; 25 | -------------------------------------------------------------------------------- /src/animations/transitions/fade-down.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Rect } from '../../types'; 3 | 4 | export const appear = (rect: Rect) => ({ 5 | translateY: rect.top - 10, 6 | opacity: 0, 7 | }); 8 | 9 | export const appeared = () => ({ opacity: 1 }); 10 | 11 | export const enter = appeared; 12 | 13 | export const entered = appeared; 14 | 15 | export const leaved = (rect: Rect) => ({ 16 | translateY: rect.top + 10, 17 | opacity: 0, 18 | }); 19 | -------------------------------------------------------------------------------- /src/animations/transitions/fade-up.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Rect } from '../../types/'; 3 | 4 | export const appear = (rect: Rect) => ({ 5 | translateY: rect.top + 10, 6 | opacity: 0, 7 | }); 8 | 9 | export const appeared = () => ({ opacity: 1 }); 10 | 11 | export const enter = appeared; 12 | 13 | export const entered = appeared; 14 | 15 | export const leaved = appear; 16 | -------------------------------------------------------------------------------- /src/animations/transitions/fade.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const appear = () => ({ opacity: 0 }); 3 | 4 | export const appeared = () => ({ opacity: 1 }); 5 | 6 | export const enter = appear; 7 | 8 | export const entered = appeared; 9 | 10 | export const leaved = appear; 11 | -------------------------------------------------------------------------------- /src/animations/transitions/flip.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const common = { 3 | perspective: 1000, 4 | transformStyle: 'preserve-3d', 5 | backfaceVisibility: 'hidden', 6 | }; 7 | 8 | export const appear = () => ({ 9 | ...common, 10 | rotateX: -180, 11 | opacity: 0, 12 | }); 13 | 14 | export const appeared = () => ({ 15 | ...common, 16 | rotateX: 0, 17 | opacity: 1, 18 | }); 19 | 20 | export const enter = appeared; 21 | 22 | export const entered = appeared; 23 | 24 | export const leaved = appear; 25 | -------------------------------------------------------------------------------- /src/animations/transitions/helix.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Rect } from '../../types/'; 3 | 4 | const common = { 5 | transformStyle: 'preserve-3d', 6 | backfaceVisibility: 'hidden', 7 | }; 8 | 9 | export const appear = (rect: Rect) => ({ 10 | ...common, 11 | perspective: rect.height, 12 | rotateY: -180, 13 | opacity: 0, 14 | }); 15 | 16 | export const appeared = () => ({ 17 | ...common, 18 | perspective: 0, 19 | rotateY: 0, 20 | opacity: 1, 21 | }); 22 | 23 | export const enter = appeared; 24 | 25 | export const entered = appeared; 26 | 27 | export const leaved = (rect: Rect) => ({ 28 | ...common, 29 | perspective: rect.height, 30 | rotateY: 180, 31 | opacity: 0, 32 | }); 33 | -------------------------------------------------------------------------------- /src/animations/transitions/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export * as fade from './fade'; 3 | export * as fadeDown from './fade-down'; 4 | export * as fadeUp from './fade-up'; 5 | export * as scaleDown from './scale-down'; 6 | export * as scaleUp from './scale-up'; 7 | export * as flip from './flip'; 8 | export * as helix from './helix'; 9 | -------------------------------------------------------------------------------- /src/animations/transitions/scale-down.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const appear = () => ({ 3 | scale: 1.1, 4 | opacity: 0, 5 | }); 6 | 7 | export const appeared = () => ({ 8 | scale: 1, 9 | opacity: 1, 10 | }); 11 | 12 | export const enter = appear; 13 | 14 | export const entered = appeared; 15 | 16 | export const leaved = () => ({ 17 | scale: 0.95, 18 | opacity: 0, 19 | }); 20 | -------------------------------------------------------------------------------- /src/animations/transitions/scale-up.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const appear = () => ({ 3 | scale: 0.9, 4 | opacity: 0, 5 | }); 6 | 7 | export const appeared = () => ({ 8 | scale: 1, 9 | opacity: 1, 10 | }); 11 | 12 | export const enter = appear; 13 | 14 | export const entered = appeared; 15 | 16 | export const leaved = () => ({ 17 | scale: 1.05, 18 | opacity: 0, 19 | }); 20 | -------------------------------------------------------------------------------- /src/components/GridItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import shallowequal from 'shallowequal'; 5 | import { transition, buildStyles } from '../utils/style-helper'; 6 | import { raf } from '../animations/request-animation-frame'; 7 | 8 | import type { Units, Rect } from '../types/'; 9 | 10 | type Props = { 11 | itemKey: string; 12 | index: number; 13 | component: string, 14 | rect: Rect; 15 | containerSize: { 16 | width: number; 17 | height: number; 18 | actualWidth: number; 19 | }; 20 | duration: number; 21 | easing: string; 22 | appearDelay: number; 23 | appear: Function; 24 | appeared: Function; 25 | enter: Function; 26 | entered: Function; 27 | leaved: Function; 28 | units: Units; 29 | vendorPrefix: boolean; 30 | userAgent: ?string; 31 | onMounted: Function; 32 | onUnmount: Function; 33 | rtl: boolean; 34 | }; 35 | 36 | type State = Object; 37 | 38 | const getTransitionStyles = (type: string, props: Props): Object => { 39 | const { rect, containerSize, index } = props; 40 | 41 | return props[type](rect, containerSize, index); 42 | }; 43 | 44 | const getPositionStyles = (rect: Rect, zIndex: number, rtl: boolean): Object => ({ 45 | translateX: `${rtl ? -Math.round(rect.left) : Math.round(rect.left)}px`, 46 | translateY: `${Math.round(rect.top)}px`, 47 | zIndex, 48 | }); 49 | 50 | 51 | export default class GridItem extends Component { 52 | props: Props; 53 | state: State; 54 | node: ?HTMLElement; 55 | mounted: boolean; 56 | appearTimer: ?number; 57 | 58 | static propTypes = { 59 | itemKey: PropTypes.string, 60 | index: PropTypes.number, 61 | component: PropTypes.string, 62 | rect: PropTypes.shape({ 63 | top: PropTypes.number, 64 | left: PropTypes.number, 65 | width: PropTypes.number, 66 | height: PropTypes.number, 67 | }), 68 | containerSize: PropTypes.shape({ 69 | width: PropTypes.number, 70 | height: PropTypes.number, 71 | actualWidth: PropTypes.number, 72 | }), 73 | duration: PropTypes.number, 74 | easing: PropTypes.string, 75 | appearDelay: PropTypes.number, 76 | appear: PropTypes.func, 77 | appeared: PropTypes.func, 78 | enter: PropTypes.func, 79 | entered: PropTypes.func, 80 | leaved: PropTypes.func, 81 | units: PropTypes.shape({ 82 | length: PropTypes.string, 83 | angle: PropTypes.string, 84 | }), 85 | vendorPrefix: PropTypes.bool, 86 | userAgent: PropTypes.string, 87 | onMounted: PropTypes.func, 88 | onUnmount: PropTypes.func, 89 | rtl: PropTypes.bool, 90 | }; 91 | 92 | constructor(props: Props) { 93 | super(props); 94 | 95 | this.mounted = false; 96 | this.appearTimer = null; 97 | this.node = null; 98 | 99 | this.state = { 100 | ...getPositionStyles(props.rect, 1, props.rtl), 101 | ...getTransitionStyles('appear', props), 102 | }; 103 | } 104 | 105 | componentDidMount() { 106 | this.mounted = true; 107 | this.props.onMounted(this); 108 | } 109 | 110 | componentWillUnmount() { 111 | this.mounted = false; 112 | clearTimeout(this.appearTimer); 113 | this.appearTimer = null; 114 | this.props.onUnmount(this); 115 | } 116 | 117 | componentWillReceiveProps(nextProps: Props) { 118 | if (!shallowequal(nextProps, this.props)) { 119 | raf(() => { 120 | this.setStateIfNeeded({ 121 | ...this.state, 122 | ...getPositionStyles(nextProps.rect, 2, nextProps.rtl), 123 | }); 124 | }); 125 | } 126 | } 127 | 128 | shouldComponentUpdate(nextProps: Props, nextState: State) { 129 | return ( 130 | !shallowequal(nextProps, this.props) || 131 | !shallowequal(nextState, this.state) 132 | ); 133 | } 134 | 135 | componentWillAppear(callback: Function) { 136 | this.appearTimer = setTimeout(callback, this.props.appearDelay * this.props.index); 137 | } 138 | 139 | componentDidAppear() { 140 | this.setAppearedStyles(); 141 | } 142 | 143 | componentWillEnter(callback: Function) { 144 | this.setEnterStyles(); 145 | this.forceUpdate(callback); 146 | } 147 | 148 | componentDidEnter() { 149 | this.setEnteredStyles(); 150 | } 151 | 152 | componentWillLeave(callback: Function) { 153 | this.setLeaveStyles(); 154 | setTimeout(callback, this.props.duration); 155 | } 156 | 157 | setStateIfNeeded(state: Object) { 158 | if (this.mounted) { 159 | this.setState(state); 160 | } 161 | } 162 | 163 | setAppearedStyles() { 164 | this.setStateIfNeeded({ 165 | ...this.state, 166 | ...getTransitionStyles('appeared', this.props), 167 | ...getPositionStyles(this.props.rect, 1, this.props.rtl), 168 | }); 169 | } 170 | 171 | setEnterStyles() { 172 | this.setStateIfNeeded({ 173 | ...this.state, 174 | ...getPositionStyles(this.props.rect, 2, this.props.rtl), 175 | ...getTransitionStyles('enter', this.props), 176 | }); 177 | } 178 | 179 | setEnteredStyles() { 180 | this.setStateIfNeeded({ 181 | ...this.state, 182 | ...getTransitionStyles('entered', this.props), 183 | ...getPositionStyles(this.props.rect, 1, this.props.rtl), 184 | }); 185 | } 186 | 187 | setLeaveStyles() { 188 | this.setStateIfNeeded({ 189 | ...this.state, 190 | ...getPositionStyles(this.props.rect, 2, this.props.rtl), 191 | ...getTransitionStyles('leaved', this.props), 192 | }); 193 | } 194 | 195 | render() { 196 | const { 197 | /* eslint-disable no-unused-vars */ 198 | index, 199 | component: Element, 200 | containerSize, 201 | appearDelay, 202 | appear, 203 | appeared, 204 | enter, 205 | entered, 206 | leaved, 207 | onMounted, 208 | onUnmount, 209 | itemKey, 210 | /* eslint-enable no-unused-vars */ 211 | rect, 212 | duration, 213 | easing, 214 | units, 215 | vendorPrefix, 216 | userAgent, 217 | rtl, 218 | ...rest 219 | } = this.props; 220 | 221 | const style = buildStyles({ 222 | ...this.state, 223 | display: 'block', 224 | position: 'absolute', 225 | top: 0, 226 | ...(rtl ? { right: 0 } : { left: 0 }), 227 | width: rect.width, 228 | transition: transition(['opacity', 'transform'], duration, easing), 229 | }, units, vendorPrefix, userAgent); 230 | 231 | /* eslint-disable no-return-assign */ 232 | return ( 233 | this.node = node} 236 | style={style} 237 | /> 238 | ); 239 | /* eslint-enable no-return-assign */ 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/components/StackGrid.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component, isValidElement } from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import PropTypes from 'prop-types'; 5 | import TransitionGroup from 'react-transition-group/TransitionGroup'; 6 | import sizeMe from 'react-sizeme'; 7 | import shallowequal from 'shallowequal'; 8 | import ExecutionEnvironment from 'exenv'; 9 | import invariant from 'invariant'; 10 | import GridItem from './GridItem'; 11 | import { transition } from '../utils/style-helper'; 12 | import { raf } from '../animations/request-animation-frame'; 13 | import * as easings from '../animations/easings'; 14 | import * as transitions from '../animations/transitions/'; 15 | 16 | import type { Units } from '../types/'; 17 | 18 | const imagesLoaded = ExecutionEnvironment.canUseDOM ? require('imagesloaded') : null; 19 | 20 | 21 | const isNumber = (v: any): boolean => typeof v === 'number' && isFinite(v); // eslint-disable-line no-restricted-globals 22 | const isPercentageNumber = (v: any): boolean => typeof v === 'string' && /^\d+(\.\d+)?%$/.test(v); 23 | 24 | // eslint-disable-next-line arrow-parens 25 | const createArray = (v: T, l: number): T[] => { 26 | const array = []; 27 | for (let i = 0; i < l; i += 1) array.push(v); 28 | return array; 29 | }; 30 | 31 | /* eslint-disable consistent-return */ 32 | const getColumnLengthAndWidth = ( 33 | width: number, 34 | value: number | string, 35 | gutter: number 36 | ): [number, number] => { 37 | if (isNumber(value)) { 38 | const columnWidth = parseFloat(value); 39 | 40 | return [ 41 | Math.floor((width - (((width / columnWidth) - 1) * gutter)) / columnWidth), 42 | columnWidth, 43 | ]; 44 | } else if (isPercentageNumber(value)) { 45 | const columnPercentage = parseFloat(value) / 100; 46 | const maxColumn = Math.floor(1 / columnPercentage); 47 | const columnWidth = (width - (gutter * (maxColumn - 1))) / maxColumn; 48 | 49 | return [ 50 | maxColumn, 51 | columnWidth, 52 | ]; 53 | } 54 | 55 | invariant(false, 'Should be columnWidth is a number or percentage string.'); 56 | }; 57 | /* eslint-enable consistent-return */ 58 | 59 | 60 | type Props = { 61 | children: React$Element; 62 | className?: string; 63 | style: Object; 64 | gridRef?: Function; 65 | component: string; 66 | itemComponent: string; 67 | columnWidth: number | string; 68 | gutterWidth: number; 69 | gutterHeight: number; 70 | duration: number; 71 | easing: string; 72 | appearDelay: number; 73 | appear: Function; 74 | appeared: Function; 75 | enter: Function; 76 | entered: Function; 77 | leaved: Function; 78 | units: Units; 79 | monitorImagesLoaded: boolean; 80 | vendorPrefix: boolean; 81 | userAgent: ?string; 82 | enableSSR: boolean; 83 | onLayout: Function; 84 | horizontal: boolean; 85 | rtl: boolean; 86 | }; 87 | 88 | type InlineState = { 89 | rects: Array<{ 90 | top: number; 91 | left: number; 92 | width: number; 93 | height: number; 94 | }>; 95 | actualWidth: number; 96 | height: number; 97 | columnWidth: number; 98 | }; 99 | 100 | type InlineProps = Props & { 101 | refCallback: Function; 102 | size: { 103 | width: number; 104 | height: number; 105 | } 106 | }; 107 | 108 | /* eslint-disable react/no-unused-prop-types */ 109 | const propTypes = { 110 | children: PropTypes.node, 111 | className: PropTypes.string, 112 | style: PropTypes.object, // eslint-disable-line react/forbid-prop-types 113 | gridRef: PropTypes.func, 114 | component: PropTypes.string, 115 | itemComponent: PropTypes.string, 116 | columnWidth: PropTypes.oneOfType([ 117 | PropTypes.number, 118 | PropTypes.string, 119 | ]).isRequired, 120 | gutterWidth: PropTypes.number, 121 | gutterHeight: PropTypes.number, 122 | duration: PropTypes.number, 123 | easing: PropTypes.string, 124 | appearDelay: PropTypes.number, 125 | appear: PropTypes.func, 126 | appeared: PropTypes.func, 127 | enter: PropTypes.func, 128 | entered: PropTypes.func, 129 | leaved: PropTypes.func, 130 | units: PropTypes.shape({ 131 | length: PropTypes.string, 132 | angle: PropTypes.string, 133 | }), 134 | monitorImagesLoaded: PropTypes.bool, 135 | vendorPrefix: PropTypes.bool, 136 | userAgent: PropTypes.string, 137 | enableSSR: PropTypes.bool, 138 | onLayout: PropTypes.func, 139 | horizontal: PropTypes.bool, 140 | rtl: PropTypes.bool, 141 | }; 142 | /* eslint-enable react/no-unused-prop-types */ 143 | 144 | export class GridInline extends Component { 145 | props: InlineProps; 146 | state: InlineState; 147 | items: { [key: string]: GridItem; }; 148 | imgLoad: Object; 149 | mounted: boolean; 150 | 151 | static propTypes = { 152 | ...propTypes, 153 | size: PropTypes.shape({ 154 | width: PropTypes.number, 155 | height: PropTypes.number, 156 | }), 157 | }; 158 | 159 | constructor(props: InlineProps) { 160 | super(props); 161 | 162 | this.items = {}; 163 | this.imgLoad = {}; 164 | this.mounted = false; 165 | this.state = this.doLayout(props); 166 | } 167 | 168 | componentDidMount() { 169 | this.mounted = true; 170 | this.updateLayout(this.props); 171 | } 172 | 173 | componentWillReceiveProps(nextProps: InlineProps) { 174 | if (!shallowequal(nextProps, this.props)) { 175 | this.updateLayout(nextProps); 176 | } 177 | } 178 | 179 | shouldComponentUpdate(nextProps: InlineProps, nextState: InlineState) { 180 | return ( 181 | !shallowequal(nextProps, this.props) || 182 | !shallowequal(nextState, this.state) 183 | ); 184 | } 185 | 186 | componentWillUnmount() { 187 | this.mounted = false; 188 | } 189 | 190 | setStateIfNeeded(state: Object) { 191 | if (this.mounted) { 192 | this.setState(state); 193 | } 194 | } 195 | 196 | getItemHeight(item: any): number { 197 | if (item.key && this.items.hasOwnProperty(item.key)) { 198 | const component = this.items[item.key]; 199 | const el = (ReactDOM.findDOMNode(component): any); 200 | const candidate = [el.scrollHeight, el.clientHeight, el.offsetHeight, 0].filter(isNumber); 201 | 202 | return Math.max(...candidate); 203 | } 204 | 205 | return 0; 206 | } 207 | 208 | 209 | doLayout(props: InlineProps): InlineState { 210 | if (!ExecutionEnvironment.canUseDOM) { 211 | return this.doLayoutForSSR(props); 212 | } 213 | 214 | const results = this.doLayoutForClient(props); 215 | 216 | if (this.mounted && typeof this.props.onLayout === 'function') { 217 | this.props.onLayout(); 218 | } 219 | 220 | return results; 221 | } 222 | 223 | doLayoutForClient(props: InlineProps): InlineState { 224 | const { 225 | size: { width: containerWidth }, 226 | columnWidth: rawColumnWidth, 227 | gutterWidth, 228 | gutterHeight, 229 | horizontal, 230 | } = props; 231 | 232 | const childArray = React.Children.toArray(props.children); 233 | const [maxColumn, columnWidth] = getColumnLengthAndWidth( 234 | containerWidth, 235 | rawColumnWidth, 236 | gutterWidth 237 | ); 238 | const columnHeights = createArray(0, maxColumn); 239 | 240 | let rects; 241 | if (!horizontal) { 242 | rects = childArray.map((child) => { 243 | const column = columnHeights.indexOf(Math.min(...columnHeights)); 244 | const height = this.getItemHeight(child); 245 | const left = (column * columnWidth) + (column * gutterWidth); 246 | const top = columnHeights[column]; 247 | 248 | columnHeights[column] += Math.round(height) + gutterHeight; 249 | 250 | return { top, left, width: columnWidth, height }; 251 | }); 252 | } else { 253 | const sumHeights = childArray.reduce( 254 | (sum, child) => sum + Math.round(this.getItemHeight(child)) + gutterHeight, 0); 255 | const maxHeight = sumHeights / maxColumn; 256 | 257 | let currentColumn = 0; 258 | rects = childArray.map((child) => { 259 | const column = currentColumn >= maxColumn - 1 ? maxColumn - 1 : currentColumn; 260 | const height = this.getItemHeight(child); 261 | const left = (column * columnWidth) + (column * gutterWidth); 262 | const top = columnHeights[column]; 263 | 264 | columnHeights[column] += Math.round(height) + gutterHeight; 265 | if (columnHeights[column] >= maxHeight) { 266 | currentColumn += 1; 267 | } 268 | 269 | return { top, left, width: columnWidth, height }; 270 | }); 271 | } 272 | 273 | const width = (maxColumn * columnWidth) + ((maxColumn - 1) * gutterWidth); 274 | const height = Math.max(...columnHeights) - gutterHeight; 275 | const finalRects = rects.map(o => ({ 276 | ...o, 277 | left: o.left + ((containerWidth - width) / 2), 278 | })); 279 | 280 | return { rects: finalRects, actualWidth: width, height, columnWidth }; 281 | } 282 | 283 | // eslint-disable-next-line class-methods-use-this 284 | doLayoutForSSR(props: InlineProps): InlineState { 285 | return { 286 | rects: React.Children.toArray(props.children).map(() => ({ 287 | top: 0, left: 0, width: 0, height: 0, 288 | })), 289 | actualWidth: 0, 290 | height: 0, 291 | columnWidth: 0, 292 | }; 293 | } 294 | 295 | updateLayout(props: ?InlineProps): void { 296 | if (!props) { 297 | this.setStateIfNeeded(this.doLayout(this.props)); 298 | } else { 299 | this.setStateIfNeeded(this.doLayout(props)); 300 | } 301 | } 302 | 303 | handleItemMounted = (item: GridItem) => { 304 | const { itemKey: key } = item.props; 305 | this.items[key] = item; 306 | 307 | if (this.props.monitorImagesLoaded && typeof imagesLoaded === 'function') { 308 | const node = ReactDOM.findDOMNode(item); 309 | const imgLoad = imagesLoaded(node); 310 | 311 | imgLoad.once('always', () => raf(() => { 312 | this.updateLayout(this.props); 313 | })); 314 | 315 | this.imgLoad[key] = imgLoad; 316 | } 317 | 318 | this.updateLayout(this.props); 319 | } 320 | 321 | handleItemUnmount = (item: GridItem) => { 322 | const { itemKey: key } = item.props; 323 | 324 | if (this.items.hasOwnProperty(key)) { 325 | delete this.items[key]; 326 | } 327 | 328 | if (this.imgLoad.hasOwnProperty(key)) { 329 | this.imgLoad[key].off('always'); 330 | delete this.imgLoad[key]; 331 | } 332 | } 333 | 334 | handleRef = () => { 335 | this.props.refCallback(this); 336 | }; 337 | 338 | render() { 339 | const { 340 | /* eslint-disable no-unused-vars */ 341 | gutterWidth, 342 | gutterHeight, 343 | columnWidth: rawColumnWidth, 344 | monitorImagesLoaded, 345 | enableSSR, 346 | onLayout, 347 | horizontal, 348 | rtl, 349 | refCallback, 350 | /* eslint-enable no-unused-vars */ 351 | className, 352 | style, 353 | size, 354 | component, 355 | itemComponent, 356 | children, 357 | ...rest 358 | } = this.props; 359 | 360 | const { rects, actualWidth, height } = this.state; 361 | const containerSize = { 362 | actualWidth, 363 | width: size.width == null ? 0 : size.width, 364 | height, 365 | }; 366 | const validChildren = React.Children 367 | .toArray(children) 368 | .filter(child => isValidElement(child)); 369 | 370 | 371 | /* eslint-disable no-return-assign */ 372 | return ( 373 | 384 | {validChildren.map((child, i) => ( 385 | 397 | {child} 398 | 399 | ))} 400 | 401 | ); 402 | /* eslint-enable no-return-assign */ 403 | } 404 | } 405 | 406 | const SizeAwareGridInline = sizeMe({ 407 | monitorWidth: true, 408 | monitorHeight: false, 409 | })(GridInline); 410 | 411 | 412 | export default class StackGrid extends Component { 413 | static propTypes = propTypes; 414 | 415 | static defaultProps = { 416 | style: {}, 417 | gridRef: null, 418 | component: 'div', 419 | itemComponent: 'span', 420 | columnWidth: 150, 421 | gutterWidth: 5, 422 | gutterHeight: 5, 423 | duration: 480, 424 | easing: easings.quartOut, 425 | appearDelay: 30, 426 | appear: transitions.fadeUp.appear, 427 | appeared: transitions.fadeUp.appeared, 428 | enter: transitions.fadeUp.enter, 429 | entered: transitions.fadeUp.entered, 430 | leaved: transitions.fadeUp.leaved, 431 | units: { length: 'px', angle: 'deg' }, 432 | monitorImagesLoaded: false, 433 | vendorPrefix: true, 434 | userAgent: null, 435 | enableSSR: false, 436 | onLayout: null, 437 | horizontal: false, 438 | rtl: false, 439 | }; 440 | 441 | props: Props; 442 | grid: GridInline; 443 | 444 | updateLayout() { 445 | this.grid.updateLayout(); 446 | } 447 | 448 | handleRef = (grid: GridInline) => { 449 | this.grid = grid; 450 | 451 | if (typeof this.props.gridRef === 'function') { 452 | this.props.gridRef(this); 453 | } 454 | }; 455 | 456 | render() { 457 | const { 458 | enableSSR, 459 | gridRef, 460 | ...rest 461 | } = this.props; 462 | 463 | sizeMe.enableSSRBehaviour = enableSSR; 464 | 465 | return ( 466 | 470 | ); 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /src/components/__tests__/GridItem.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | import React from 'react'; 3 | import { mount } from 'enzyme'; 4 | import { easings, transitions } from '../../'; 5 | import GridItem from '../GridItem'; 6 | 7 | 8 | const mockProps = { 9 | component: 'span', 10 | rect: { 11 | top: 0, 12 | left: 0, 13 | width: 0, 14 | height: 0, 15 | }, 16 | containerSize: { 17 | width: 960, 18 | height: 0, 19 | }, 20 | index: 0, 21 | easing: easings.quartOut, 22 | appearDelay: 30, 23 | appear: transitions.fadeUp.appear, 24 | appeared: transitions.fadeUp.appeared, 25 | enter: transitions.fadeUp.enter, 26 | entered: transitions.fadeUp.entered, 27 | leaved: transitions.fadeUp.leaved, 28 | units: { length: 'px', angle: 'deg' }, 29 | vendorPrefix: true, 30 | userAgent: null, 31 | onMounted: () => {}, 32 | onUnmount: () => {}, 33 | }; 34 | 35 | let clock = null; 36 | 37 | 38 | describe('', () => { 39 | beforeEach(() => { 40 | clock = sinon.useFakeTimers(); 41 | }); 42 | 43 | afterEach(() => { 44 | clock.restore(); 45 | }); 46 | 47 | 48 | test('Should be change component', () => { 49 | const wrapper = mount( 50 | 54 | ); 55 | 56 | expect(wrapper.find('li')).toHaveLength(1); 57 | }); 58 | 59 | 60 | test('Should be call handleMounted/handleUnmount', () => { 61 | const handleMounted = sinon.spy(); 62 | const handleUnmount = sinon.spy(); 63 | 64 | expect(handleMounted.called).toBe(false); 65 | 66 | const wrapper = mount( 67 | 72 | ); 73 | 74 | expect(handleMounted.called).toBe(true); 75 | 76 | wrapper.unmount(); 77 | expect(handleUnmount.called).toBe(true); 78 | }); 79 | 80 | 81 | test('Should be call transition style function', () => { 82 | const spyFunctions = { 83 | appear: sinon.spy(), 84 | appeared: sinon.spy(), 85 | enter: sinon.spy(), 86 | entered: sinon.spy(), 87 | leaved: sinon.spy(), 88 | }; 89 | 90 | const wrapper = mount( 91 | 96 | Item 97 | 98 | ); 99 | 100 | const gridItem = wrapper.instance(); 101 | const noop = () => {}; 102 | 103 | expect(spyFunctions.appear.called).toBe(true); 104 | 105 | gridItem.componentDidAppear(() => {}); 106 | clock.tick(300); 107 | expect(spyFunctions.appeared.called).toBe(true); 108 | 109 | clock.tick(300); 110 | gridItem.componentWillEnter(noop); 111 | expect(spyFunctions.enter.called).toBe(true); 112 | 113 | clock.tick(300); 114 | gridItem.componentDidEnter(); 115 | expect(spyFunctions.entered.called).toBe(true); 116 | 117 | clock.tick(300); 118 | gridItem.componentWillLeave(noop); 119 | expect(spyFunctions.leaved.called).toBe(true); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /src/components/__tests__/StackGrid.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | import StackGrid, { easings, transitions } from '../../'; 4 | import { GridInline } from '../StackGrid'; 5 | 6 | 7 | const mockProps = { 8 | size: { 9 | width: 960, 10 | height: 0, 11 | }, 12 | style: {}, 13 | refCallback: () => {}, 14 | component: 'div', 15 | itemComponent: 'span', 16 | columnWidth: 150, 17 | gutterWidth: 5, 18 | gutterHeight: 5, 19 | duration: 480, 20 | easing: easings.quartOut, 21 | appearDelay: 30, 22 | appear: transitions.fadeUp.appear, 23 | appeared: transitions.fadeUp.appeared, 24 | enter: transitions.fadeUp.enter, 25 | entered: transitions.fadeUp.entered, 26 | leaved: transitions.fadeUp.leaved, 27 | units: { length: 'px', angle: 'deg' }, 28 | monitorImagesLoaded: false, 29 | vendorPrefix: true, 30 | userAgent: null, 31 | onLayout: () => {}, 32 | }; 33 | 34 | 35 | describe(' (GridInline)', () => { 36 | test('Should not be render children', () => { 37 | const wrapper = mount( 38 | 39 | ); 40 | 41 | expect(wrapper.children()).toHaveLength(0); 42 | }); 43 | 44 | 45 | test('Should be pass the base props', () => { 46 | const wrapper = mount( 47 | 54 |
A
55 |
56 | ); 57 | 58 | expect(wrapper.props().className).toBe('rsg-grid'); 59 | expect(wrapper.props().style).toEqual({ 60 | width: 960, 61 | background: '#fff', 62 | }); 63 | }); 64 | 65 | 66 | test('Should be render with specify component', () => { 67 | const wrapper = mount( 68 | 74 |
A
75 |
B
76 |
C
77 |
78 | ); 79 | 80 | expect(wrapper.find('ul.rsg-grid')).toHaveLength(1); 81 | expect(wrapper.find('li')).toHaveLength(3); 82 | }); 83 | 84 | 85 | test('Should be render children', () => { 86 | const wrapper = mount( 87 | 88 |
ITEM 1
89 |
ITEM 2
90 |
ITEM 3
91 |
92 | ); 93 | 94 | expect(wrapper.find('div.item')).toHaveLength(3); 95 | }); 96 | 97 | 98 | test('Should be get grid ref', () => { 99 | const callback = jest.fn(); 100 | const wrapper = mount( 101 | 105 |
Foo
106 |
107 | ); 108 | 109 | expect(callback.mock.calls).toHaveLength(1); 110 | expect(callback.mock.calls[0]).toEqual([ 111 | wrapper.instance(), 112 | ]); 113 | }); 114 | 115 | 116 | test('Should be call onLayout', () => { 117 | const callback = jest.fn(); 118 | const wrapper = mount( 119 | 123 |
Foo
124 |
125 | ); 126 | 127 | expect(callback.mock.calls).toHaveLength(1); 128 | 129 | wrapper.setProps({ 130 | ...mockProps, 131 | size: { width: 300, height: 0 }, 132 | }); 133 | 134 | expect(callback.mock.calls).toHaveLength(2); 135 | }); 136 | 137 | test('Should be horizontal', () => { 138 | const testMockProps = Object.assign({}, mockProps, { 139 | size: { 140 | width: 320, 141 | height: 0, 142 | }, 143 | horizontal: true, 144 | }); 145 | 146 | const wrapper = mount( 147 | 148 |
ITEM 1
149 |
ITEM 2
150 |
ITEM 3
151 |
ITEM 4
152 |
153 | ); 154 | 155 | expect(wrapper.find('span').at(0).prop('style').transform).toBe('translateX(8px) translateY(10px)'); 156 | expect(wrapper.find('span').at(1).prop('style').transform).toBe('translateX(8px) translateY(15px)'); 157 | expect(wrapper.find('span').at(2).prop('style').transform).toBe('translateX(163px) translateY(10px)'); 158 | expect(wrapper.find('span').at(3).prop('style').transform).toBe('translateX(163px) translateY(15px)'); 159 | }); 160 | 161 | test('Should be horizontal RTL', () => { 162 | const testMockProps = Object.assign({}, mockProps, { 163 | size: { 164 | width: 320, 165 | height: 0, 166 | }, 167 | rtl: true, 168 | horizontal: true, 169 | }); 170 | 171 | const wrapper = mount( 172 | 173 |
ITEM 1
174 |
ITEM 2
175 |
ITEM 3
176 |
ITEM 4
177 |
178 | ); 179 | expect(wrapper.find('span').at(0).prop('style').right).toBe(0); 180 | expect(wrapper.find('span').at(1).prop('style').right).toBe(0); 181 | expect(wrapper.find('span').at(2).prop('style').right).toBe(0); 182 | expect(wrapper.find('span').at(3).prop('style').right).toBe(0); 183 | 184 | expect(wrapper.find('span').at(0).prop('style').transform).toBe('translateX(-8px) translateY(10px)'); 185 | expect(wrapper.find('span').at(1).prop('style').transform).toBe('translateX(-8px) translateY(15px)'); 186 | expect(wrapper.find('span').at(2).prop('style').transform).toBe('translateX(-163px) translateY(10px)'); 187 | expect(wrapper.find('span').at(3).prop('style').transform).toBe('translateX(-163px) translateY(15px)'); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /src/declares/vendor.js.flow: -------------------------------------------------------------------------------- 1 | declare module "prop-types" { 2 | declare export default any; 3 | } 4 | 5 | declare module "imagesloaded" { 6 | declare export default any; 7 | } 8 | 9 | declare module "react-transition-group/TransitionGroup" { 10 | declare export default any; 11 | } 12 | 13 | declare module "inline-style-prefixer" { 14 | declare export default any; 15 | } 16 | 17 | declare module "shallowequal" { 18 | declare export default any; 19 | } 20 | 21 | declare module "react-sizeme" { 22 | declare export default any; 23 | } 24 | 25 | declare module "throttle-debounce" { 26 | declare export function debounce(): Function; 27 | } 28 | 29 | declare module "exenv" { 30 | declare export default any; 31 | } 32 | 33 | declare module "invariant" { 34 | declare export default any; 35 | } 36 | 37 | declare module "easy-css-transform-builder" { 38 | declare export function createCSSTransformBuilder(units: any): Function; 39 | declare export var properties: Array; 40 | } 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import StackGrid from './components/StackGrid'; 3 | 4 | export * as easings from './animations/easings'; 5 | export * as transitions from './animations/transitions/'; 6 | 7 | export default StackGrid; 8 | -------------------------------------------------------------------------------- /src/types/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type Units = { 3 | length: string; 4 | angle: string; 5 | }; 6 | 7 | export type Rect = { 8 | top: number; 9 | left: number; 10 | width: number; 11 | height: number; 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/__tests__/style-helper.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { transition, buildStyles } from '../style-helper'; 3 | 4 | 5 | describe('style-helper', () => { 6 | test('Should be build a transition string', () => { 7 | expect(transition(['opacity'], 1000, 'ease-in')).toBe('opacity 1000ms ease-in'); 8 | 9 | const easing = 'cubic-bezier(0.215, 0.61, 0.355, 1)'; 10 | expect(transition(['transform', 'opacity', 'background'], 200, easing)).toBe([ 11 | `transform 200ms ${easing}`, 12 | `opacity 200ms ${easing}`, 13 | `background 200ms ${easing}`, 14 | ].join(',')); 15 | }); 16 | 17 | 18 | test('Should be build a style object', () => { 19 | const ios8 = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A365 Safari/600.1.4'; 20 | const units = { 21 | length: 'px', 22 | angle: 'deg', 23 | }; 24 | 25 | expect(buildStyles({ 26 | opacity: 1, 27 | fontSize: 16, 28 | }, units, false)).toEqual({ 29 | opacity: 1, 30 | fontSize: 16, 31 | }); 32 | 33 | expect(buildStyles({ 34 | opacity: 1, 35 | translateX: 900, 36 | translateY: 200, 37 | scale: 0.8, 38 | perspective: 1000, 39 | }, units, false)).toEqual({ 40 | opacity: 1, 41 | perspective: 1000, 42 | transform: 'translateX(900px) translateY(200px) scale(0.8) perspective(1000px)', 43 | }); 44 | 45 | expect(buildStyles({ 46 | opacity: 1, 47 | translateX: 900, 48 | translateY: 200, 49 | scale: 0.8, 50 | perspective: 1000, 51 | }, units, true, ios8)).toEqual({ 52 | opacity: 1, 53 | WebkitPerspective: 1000, 54 | WebkitTransform: 'translateX(900px) translateY(200px) scale(0.8) perspective(1000px)', 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/utils/style-helper.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import Prefixer from 'inline-style-prefixer'; 3 | import { createCSSTransformBuilder, properties } from 'easy-css-transform-builder'; 4 | 5 | export type Units = { 6 | length: string; 7 | angle: string; 8 | }; 9 | 10 | 11 | const isTransformProp = v => properties.indexOf(v) > -1; 12 | 13 | export const transition = (props: Array, duration: number, easing: string) => ( 14 | props.map(prop => 15 | `${prop} ${duration}ms ${easing}` 16 | ).join(',') 17 | ); 18 | 19 | 20 | export const buildStyles = ( 21 | styles: Object, 22 | units: Units, 23 | vendorPrefix: boolean, 24 | userAgent: ?string 25 | ) => { 26 | const builder = createCSSTransformBuilder(units); 27 | const finalStyles = {}; 28 | const transformStyles = {}; 29 | 30 | Object.keys(styles).forEach((key) => { 31 | const value = styles[key]; 32 | 33 | if (isTransformProp(key)) { 34 | transformStyles[key] = value; 35 | 36 | if (key === 'perspective') { 37 | finalStyles[key] = value; 38 | } 39 | } else { 40 | finalStyles[key] = value; 41 | } 42 | }); 43 | 44 | const transform = builder(transformStyles, units); 45 | if (transform !== '') { 46 | finalStyles.transform = transform; 47 | } 48 | 49 | if (vendorPrefix) { 50 | const prefixer = new Prefixer({ userAgent }); 51 | return prefixer.prefix(finalStyles); 52 | } 53 | 54 | return finalStyles; 55 | }; 56 | --------------------------------------------------------------------------------