├── .npmignore
├── __mocks__
└── fileMock.js
├── test-config
├── shim.js
└── test-setup.js
├── examples
├── shared
│ ├── img
│ │ ├── 1.jpg
│ │ ├── 10.jpg
│ │ ├── 11.jpg
│ │ ├── 2.jpg
│ │ ├── 3.jpg
│ │ ├── 4.jpg
│ │ ├── 5.jpg
│ │ ├── 6.jpg
│ │ ├── 7.jpg
│ │ ├── 8.jpg
│ │ ├── 9.jpg
│ │ ├── ink.png
│ │ ├── canvas.png
│ │ ├── wireframe.png
│ │ ├── Dribbble
│ │ │ ├── 1.png
│ │ │ ├── 10.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ ├── 5.png
│ │ │ ├── 6.png
│ │ │ ├── 7.png
│ │ │ ├── 8.png
│ │ │ └── 9.png
│ │ ├── Dribbble1
│ │ │ ├── 1.jpg
│ │ │ ├── 2.jpg
│ │ │ ├── 3.jpg
│ │ │ ├── 4.jpg
│ │ │ ├── 1_1.jpg
│ │ │ ├── 1_2.jpg
│ │ │ ├── 1_3.jpg
│ │ │ ├── 2_1.jpg
│ │ │ ├── 2_2.jpg
│ │ │ ├── 3_1.jpg
│ │ │ ├── 3_2.jpg
│ │ │ ├── 4_1.jpg
│ │ │ └── 4_2.jpg
│ │ ├── related
│ │ │ ├── ImageGridEffects.jpg
│ │ │ └── GridItemAnimation.jpg
│ │ ├── menu_icon.svg
│ │ └── PageCloud_logo.svg
│ └── favicon
│ │ └── favicon.ico
├── storybooks
│ ├── storyshots.test.js
│ ├── generic.scss
│ ├── basic-stack.js
│ ├── index.js
│ ├── side-stack.js
│ ├── sandbox-utils.js
│ ├── basic-stack-with-title.js
│ ├── loaded-callback.js
│ ├── spread.js
│ └── __snapshots__
│ │ └── storyshots.test.js.snap
└── basic-example
│ ├── index.html
│ ├── index.js
│ ├── stylesheets
│ ├── app.scss
│ └── vendor
│ │ ├── github-light.css
│ │ └── stylesheet.css
│ └── app.js
├── src
├── index.js
├── utils
│ ├── misc.js
│ └── animation-frame.js
├── react-isometric-grid.scss
├── cell.js
├── react-isometric-grid.js
└── isometric-grid.js
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── .eslintrc.json
├── .gitignore
├── .codeclimate.yml
├── .babelrc
├── .prettierrc
├── .travis.yml
├── LICENSE
├── CODE_OF_CONDUCT.md
├── webpack.config.js
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | lib
3 | CODE_OF_CONDUCT.md
4 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/test-config/shim.js:
--------------------------------------------------------------------------------
1 | global.requestAnimationFrame = callback => {
2 | setTimeout(callback, 0);
3 | };
4 |
--------------------------------------------------------------------------------
/examples/shared/img/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/1.jpg
--------------------------------------------------------------------------------
/examples/shared/img/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/10.jpg
--------------------------------------------------------------------------------
/examples/shared/img/11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/11.jpg
--------------------------------------------------------------------------------
/examples/shared/img/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/2.jpg
--------------------------------------------------------------------------------
/examples/shared/img/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/3.jpg
--------------------------------------------------------------------------------
/examples/shared/img/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/4.jpg
--------------------------------------------------------------------------------
/examples/shared/img/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/5.jpg
--------------------------------------------------------------------------------
/examples/shared/img/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/6.jpg
--------------------------------------------------------------------------------
/examples/shared/img/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/7.jpg
--------------------------------------------------------------------------------
/examples/shared/img/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/8.jpg
--------------------------------------------------------------------------------
/examples/shared/img/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/9.jpg
--------------------------------------------------------------------------------
/examples/shared/img/ink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/ink.png
--------------------------------------------------------------------------------
/examples/shared/img/canvas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/canvas.png
--------------------------------------------------------------------------------
/examples/shared/img/wireframe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/wireframe.png
--------------------------------------------------------------------------------
/examples/shared/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/favicon/favicon.ico
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/1.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/10.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/2.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/3.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/4.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/5.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/6.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/7.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/8.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble/9.png
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/1.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/2.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/3.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/4.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/1_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/1_1.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/1_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/1_2.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/1_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/1_3.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/2_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/2_1.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/2_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/2_2.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/3_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/3_1.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/3_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/3_2.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/4_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/4_1.jpg
--------------------------------------------------------------------------------
/examples/shared/img/Dribbble1/4_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/Dribbble1/4_2.jpg
--------------------------------------------------------------------------------
/test-config/test-setup.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/examples/shared/img/related/ImageGridEffects.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/related/ImageGridEffects.jpg
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import ReactIsometricGrid from './react-isometric-grid';
2 | import Cell from './cell';
3 |
4 | export default ReactIsometricGrid;
5 | export { Cell };
6 |
--------------------------------------------------------------------------------
/examples/shared/img/related/GridItemAnimation.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frontend-collective/react-isometric-grid/HEAD/examples/shared/img/related/GridItemAnimation.jpg
--------------------------------------------------------------------------------
/examples/storybooks/storyshots.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import initStoryshots from '@storybook/addon-storyshots';
3 |
4 | initStoryshots();
5 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import '@storybook/addon-options/register';
3 | import '@storybook/addon-notes/register';
4 | import '@storybook/addon-actions/register';
5 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb", "prettier", "prettier/react"],
3 | "env": {
4 | "browser": true,
5 | "jest": true
6 | },
7 | "rules": {
8 | "react/jsx-filename-extension": 0,
9 | "react/prefer-stateless-function": 0
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | yarn.lock
4 | coverage
5 | cc-test-reporter
6 |
7 | # Editor and other tmp files
8 | *.swp
9 | *.un~
10 | *.iml
11 | *.ipr
12 | *.iws
13 | *.sublime-*
14 | .idea/
15 | *.DS_Store
16 |
17 | # Build directories (Will be preserved by npm)
18 | dist
19 | build
20 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | eslint:
3 | enabled: true
4 | checks:
5 | import/extensions:
6 | enabled: false
7 | ratings:
8 | paths:
9 | - src/**
10 | - examples/**
11 | - "**.js"
12 | exclude_paths:
13 | - "**/*.test.js"
14 | - "node_modules/"
15 | - "__mocks__/"
16 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "targets": {
7 | "browsers": ["last 2 versions", "ie >= 9"]
8 | }
9 | }
10 | ],
11 | "react"
12 | ],
13 | "plugins": [
14 | "transform-class-properties",
15 | "transform-object-rest-spread",
16 | "react-hot-loader/babel"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/shared/img/menu_icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/examples/basic-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Isometric Grid
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "es5",
8 | "bracketSpacing": true,
9 | "jsxBracketSameLine": false,
10 | "overrides": [
11 | {
12 | "files": ".prettierrc",
13 | "options": { "parser": "json", "trailingComma": "none" }
14 | },
15 | {
16 | "files": "*.json",
17 | "options": { "trailingComma": "none" }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import { configure } from '@storybook/react';
3 | import { setOptions } from '@storybook/addon-options';
4 |
5 | setOptions({
6 | name: 'React Isometric Grid',
7 | url: 'https://github.com/wuweiweiwu/react-isometric-grid',
8 | });
9 |
10 | function loadStories() {
11 | // eslint-disable-next-line global-require
12 | require('../examples/storybooks');
13 | }
14 |
15 | configure(loadStories, module);
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | env:
5 | global:
6 | - CC_TEST_REPORTER_ID=078550efafc3dbb8faf56159f96a1f6aed1bf6633c08cea85d7ac08a45c781b9
7 | - GIT_COMMITTED_AT=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then git log -1 --pretty=format:%ct; else git log -1 --skip 1 --pretty=format:%ct; fi)
8 | before_script:
9 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
10 | - chmod +x ./cc-test-reporter
11 | script:
12 | - npm test -- --coverage
13 | - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; fi
14 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
2 | // This is just the basic way to add additional webpack configurations.
3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config
4 |
5 | // IMPORTANT
6 | // When you add this file, we won't add the default configurations which is similar
7 | // to "React Create App". This only has babel loader to load JavaScript.
8 |
9 | const mainWebpackConfig = require('../webpack.config');
10 |
11 | module.exports = {
12 | plugins: [
13 | // your custom plugins
14 | ],
15 | module: {
16 | rules: mainWebpackConfig.module.rules,
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/examples/basic-example/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader'; // eslint-disable-line import/no-extraneous-dependencies
4 |
5 | import 'normalize.css';
6 |
7 | import App from './app';
8 |
9 | const rootEl = document.getElementById('app');
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | rootEl
16 | );
17 | /* eslint-disable global-require, import/newline-after-import */
18 | if (module.hot) {
19 | module.hot.accept('./app', () => {
20 | const NextApp = require('./app').default;
21 | ReactDOM.render(
22 |
23 |
24 | ,
25 | rootEl
26 | );
27 | });
28 | }
29 | /* eslint-enable global-require, import/newline-after-import */
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Wei-Wei Wu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/utils/misc.js:
--------------------------------------------------------------------------------
1 | // from: http://stackoverflow.com/a/21913575
2 | export function getComputedTranslateY(obj) {
3 | if (!window.getComputedStyle) {
4 | return 0;
5 | }
6 | const style = getComputedStyle(obj);
7 | const transform =
8 | style.transform || style.webkitTransform || style.mozTransform;
9 | let mat = transform.match(/^matrix3d\((.+)\)$/);
10 | if (mat) {
11 | return parseFloat(mat[1].split(', ')[13]);
12 | }
13 | mat = transform.match(/^matrix\((.+)\)$/);
14 | return mat ? parseFloat(mat[1].split(', ')[5]) : 0;
15 | }
16 |
17 | // some helper functions
18 | export function scrollY() {
19 | return window.pageYOffset || window.document.documentElement.scrollTop;
20 | }
21 |
22 | export function extend(a, b) {
23 | return Object.assign(a, b);
24 | }
25 |
26 | export function getViewportH() {
27 | const client = window.document.documentElement.clientHeight;
28 | const inner = window.innerHeight;
29 |
30 | if (client < inner) {
31 | return inner;
32 | }
33 | return client;
34 | }
35 |
36 | export function isValidColor(color) {
37 | if (color.charAt(0) === '#') {
38 | const hexValue = color.substring(1);
39 | return [3, 4, 6, 8].indexOf(hexValue.length) > -1 && parseInt(hexValue, 16);
40 | }
41 | return /^(rgb|hsl)a?\((\d+%?(deg|rad|grad|turn)?[,\s]+){2,3}[\s]*[\d]+%?\)$/i.test(
42 | color
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/examples/storybooks/generic.scss:
--------------------------------------------------------------------------------
1 | .sourceLink,
2 | .sandboxButton {
3 | position: fixed;
4 | top: 0;
5 | right: 0;
6 | padding: 130px 50px 5px 50px;
7 | font: 10px helvetica, sans-serif;
8 | display: inline-block;
9 | background: rgb(12, 35, 194);
10 | color: #fff;
11 | text-decoration: none;
12 | transform: translate(50%, -50%) rotateZ(45deg);
13 | transition: background 100ms;
14 |
15 | &:hover:not(:active) {
16 | background: rgb(102, 135, 244);
17 | }
18 | }
19 |
20 | .sandboxButton {
21 | top: 30px;
22 | right: 30px;
23 | background: rgb(12, 194, 68);
24 | padding: 130px 100px 5px 100px;
25 | border: none;
26 | cursor: pointer;
27 | outline: none;
28 | &:hover:not(:active) {
29 | background: rgb(128, 242, 137);
30 | }
31 | }
32 |
33 | .component {
34 | opacity: 0;
35 | }
36 |
37 | .grid-loaded {
38 | .component {
39 | opacity: 1;
40 | }
41 | }
42 |
43 | .wrapper {
44 | &::after {
45 | content: '';
46 | position: fixed;
47 | z-index: 1000;
48 | top: 50%;
49 | left: 50%;
50 | width: 70px;
51 | height: 70px;
52 | margin: -35px 0 0 -35px;
53 | pointer-events: none;
54 | border: 3px solid #fff;
55 | border-right-color: #323f5c;
56 | border-radius: 50%;
57 | -webkit-transition: opacity 0.3s;
58 | transition: opacity 0.3s;
59 | -webkit-animation: rotateCircle 0.7s linear infinite forwards;
60 | animation: rotateCircle 0.7s linear infinite forwards;
61 | }
62 | &.grid-loaded::after {
63 | opacity: 0;
64 | }
65 | }
66 |
67 | @-webkit-keyframes rotateCircle {
68 | to {
69 | -webkit-transform: rotate(360deg);
70 | transform: rotate(360deg);
71 | }
72 | }
73 |
74 | @keyframes rotateCircle {
75 | to {
76 | -webkit-transform: rotate(360deg);
77 | transform: rotate(360deg);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/examples/basic-example/stylesheets/app.scss:
--------------------------------------------------------------------------------
1 | @import './vendor/stylesheet';
2 | @import './vendor/github-light';
3 |
4 | .page-header {
5 | padding: 1rem 6rem;
6 | }
7 |
8 | .main-content {
9 | box-sizing: border-box;
10 | // background: linear-gradient(90deg, #58a8fb, #465683);
11 | background: linear-gradient(180deg, white, #58a8fb, white);
12 | }
13 |
14 | .footer {
15 | margin: 0 auto;
16 | box-sizing: border-box;
17 | padding: 2rem 6rem;
18 | max-width: 64rem;
19 | }
20 |
21 | body {
22 | background-color: #fafafa;
23 | font-family: Arial, Helvetica, sans-serif;
24 | }
25 |
26 | /**
27 | * Uncomment to simulate bootstrap environment
28 | */
29 | // html {
30 | // box-sizing: border-box;
31 | // }
32 |
33 | // *, *::before, *::after {
34 | // box-sizing: inherit;
35 | // }
36 |
37 | .component {
38 | opacity: 0;
39 | }
40 |
41 | .grid-loaded {
42 | .component {
43 | opacity: 1;
44 | }
45 | }
46 |
47 | .wrapper {
48 | &::after {
49 | content: '';
50 | position: fixed;
51 | z-index: 1000;
52 | top: 50%;
53 | left: 50%;
54 | width: 70px;
55 | height: 70px;
56 | margin: -35px 0 0 -35px;
57 | pointer-events: none;
58 | border: 3px solid #fff;
59 | border-right-color: #323f5c;
60 | border-radius: 50%;
61 | -webkit-transition: opacity 0.3s;
62 | transition: opacity 0.3s;
63 | -webkit-animation: rotateCircle 0.7s linear infinite forwards;
64 | animation: rotateCircle 0.7s linear infinite forwards;
65 | }
66 | &.grid-loaded::after {
67 | opacity: 0;
68 | }
69 | }
70 |
71 | @-webkit-keyframes rotateCircle {
72 | to {
73 | -webkit-transform: rotate(360deg);
74 | transform: rotate(360deg);
75 | }
76 | }
77 |
78 | @keyframes rotateCircle {
79 | to {
80 | -webkit-transform: rotate(360deg);
81 | transform: rotate(360deg);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/utils/animation-frame.js:
--------------------------------------------------------------------------------
1 | /** ******************************************* */
2 | /** https://gist.github.com/desandro/1866474 * */
3 | /** ******************************************* */
4 | const prefixes = 'webkit moz ms o'.split(' ');
5 | let lastTime = 0;
6 |
7 | export function getRequestAnimationFrame() {
8 | let { requestAnimationFrame } = window;
9 | let prefix;
10 | for (let i = 0; i < prefixes.length; i += 1) {
11 | if (requestAnimationFrame) {
12 | break;
13 | }
14 | prefix = prefixes[i];
15 | requestAnimationFrame =
16 | requestAnimationFrame || window[`${prefix}RequestAnimationFrame`];
17 | }
18 | // fallback to setTimeout and clearTimeout if either request/cancel is not supported
19 | if (!requestAnimationFrame) {
20 | requestAnimationFrame = () => callback => {
21 | const currTime = new Date().getTime();
22 | const timeToCall = Math.max(0, 16 - (currTime - lastTime));
23 | const id = window.setTimeout(() => {
24 | callback(currTime + timeToCall);
25 | }, timeToCall);
26 | lastTime = currTime + timeToCall;
27 | return id;
28 | };
29 | }
30 | return requestAnimationFrame;
31 | }
32 |
33 | export function getCancelAnimationFrame() {
34 | let { cancelAnimationFrame } = window;
35 | let prefix;
36 | for (let i = 0; i < prefixes.length; i += 1) {
37 | if (cancelAnimationFrame) {
38 | break;
39 | }
40 | prefix = prefixes[i];
41 | cancelAnimationFrame =
42 | cancelAnimationFrame ||
43 | window[`${prefix}CancelAnimationFrame`] ||
44 | window[`${prefix}CancelRequestAnimationFrame`];
45 | }
46 | // fallback to setTimeout and clearTimeout if either request/cancel is not supported
47 | if (!requestAnimationFrame || !cancelAnimationFrame) {
48 | cancelAnimationFrame = id => {
49 | window.clearTimeout(id);
50 | };
51 | }
52 | return getCancelAnimationFrame;
53 | }
54 |
--------------------------------------------------------------------------------
/examples/storybooks/basic-stack.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import dynamics from 'dynamics.js';
3 | import IsometricGrid, { Cell } from '../../src';
4 |
5 | const img1 = 'https://picsum.photos/100/100/?random';
6 | const img2 = 'https://picsum.photos/200/100/?random';
7 | const img3 = 'https://picsum.photos/300/200/?random';
8 | const img4 = 'https://picsum.photos/400/400/?random';
9 | const img5 = 'https://picsum.photos/500/300/?random';
10 | const img6 = 'https://picsum.photos/700/500/?random';
11 | const img7 = 'https://picsum.photos/800/400/?random';
12 |
13 | class App extends Component {
14 | render() {
15 | function getRandomInt(min, max) {
16 | return Math.floor(Math.random() * (max - min + 1)) + min;
17 | }
18 |
19 | return (
20 |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
56 |
57 | );
58 | }
59 | }
60 |
61 | export default App;
62 |
--------------------------------------------------------------------------------
/examples/storybooks/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 |
3 | import React from 'react';
4 | import { storiesOf } from '@storybook/react';
5 | // import { action } from '@storybook/addon-actions';
6 | import { withNotes } from '@storybook/addon-notes';
7 | import styles from './generic.scss';
8 | import BasicStack from './basic-stack';
9 | import BasicStackTitle from './basic-stack-with-title';
10 | import Spread from './spread';
11 | import SideStack from './side-stack';
12 | import LoadedCallback from './loaded-callback';
13 | import { handleClick, SANDBOX_URL } from './sandbox-utils';
14 |
15 | const wrapWithSource = (node, src) => (
16 |
35 | );
36 |
37 | storiesOf('Basics', module)
38 | .add(
39 | 'Basic Stack',
40 | withNotes('A basic 3d vertical stack animation')(() =>
41 | wrapWithSource(, 'basic-stack.js')
42 | )
43 | )
44 | .add(
45 | 'Basic Stack with Title',
46 | withNotes('A basic 3d vertical stack animation and title prop')(() =>
47 | wrapWithSource(, 'basic-stack-with-title.js')
48 | )
49 | )
50 | .add(
51 | 'Spread',
52 | withNotes(
53 | 'A spread animation to showcase a bunch of photos that are stacked togther. Using layerStyles'
54 | )(() => wrapWithSource(, 'spread.js'))
55 | )
56 | .add(
57 | 'Side Stack',
58 | withNotes(
59 | 'Using the transform property of react-isometric-grid to display a bookshelf like grid that opens sideways. Using transform, perspective and animationOptions'
60 | )(() => wrapWithSource(, 'side-stack.js'))
61 | );
62 |
63 | storiesOf('Advanced', module).add(
64 | 'onGridLoaded Callback',
65 | withNotes(
66 | 'Using the onGridLoaded callback to wait to display the grid until all images are loaded'
67 | )(() => wrapWithSource(, 'loaded-callback.js'))
68 | );
69 |
--------------------------------------------------------------------------------
/examples/storybooks/side-stack.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import dynamics from 'dynamics.js';
3 | import IsometricGrid, { Cell } from '../../src';
4 |
5 | const img1 = 'https://picsum.photos/100/100/?random';
6 | const img2 = 'https://picsum.photos/200/100/?random';
7 | const img3 = 'https://picsum.photos/300/200/?random';
8 | const img4 = 'https://picsum.photos/400/400/?random';
9 | const img5 = 'https://picsum.photos/500/300/?random';
10 | const img6 = 'https://picsum.photos/700/500/?random';
11 | const img7 = 'https://picsum.photos/800/400/?random';
12 |
13 | class App extends Component {
14 | render() {
15 | const layerStyle = {
16 | transformOrigin: '50% 100%',
17 | };
18 |
19 | return (
20 |
40 | |
44 | |
48 | |
52 | |
56 | |
60 | |
64 | |
68 | |
72 | |
76 |
77 | );
78 | }
79 | }
80 |
81 | export default App;
82 |
--------------------------------------------------------------------------------
/src/react-isometric-grid.scss:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | -webkit-box-sizing: border-box;
5 | box-sizing: border-box;
6 | }
7 |
8 | .grid,
9 | .grid__item,
10 | .grid__link {
11 | -webkit-transform-style: preserve-3d;
12 | transform-style: preserve-3d;
13 | }
14 |
15 | .grid {
16 | position: relative;
17 | margin: 0 auto;
18 | padding: 0;
19 | list-style: none;
20 | }
21 |
22 | .grid__item {
23 | -webkit-backface-visibility: hidden;
24 | backface-visibility: hidden;
25 | }
26 |
27 | .grid__link {
28 | position: relative;
29 | z-index: 1;
30 | display: block;
31 | }
32 |
33 | .grid__link.grid__link--onclick:hover {
34 | cursor: pointer;
35 | }
36 |
37 | .grid__img {
38 | display: block;
39 | max-width: 100%;
40 | }
41 |
42 | .grid__title {
43 | font-size: 0.65em;
44 | font-weight: 600;
45 | position: absolute;
46 | z-index: -1;
47 | bottom: 0;
48 | width: 100%;
49 | text-align: center;
50 | letter-spacing: 2px;
51 | text-transform: uppercase;
52 | opacity: 0;
53 | color: #fff;
54 | -webkit-transform: translate3d(0, -20px, 0);
55 | transform: translate3d(0, -20px, 0);
56 | -webkit-transition: -webkit-transform 0.3s, opacity 0.3s;
57 | transition: transform 0.3s, opacity 0.3s;
58 | }
59 |
60 | .grid__item:hover .grid__title {
61 | opacity: 1;
62 | -webkit-transform: translate3d(0, 0, 0);
63 | transform: translate3d(0, 0, 0);
64 | }
65 |
66 | .layer {
67 | position: relative;
68 | display: block;
69 | position: absolute;
70 | top: 0;
71 | left: 0;
72 | }
73 |
74 | /* Shadow effect */
75 |
76 | .isolayer--shadow {
77 | .grid__link::before {
78 | content: '';
79 | position: absolute;
80 | z-index: -1;
81 | top: 5px;
82 | right: 5px;
83 | bottom: 5px;
84 | left: 5px;
85 | opacity: 0.6;
86 | background: rgba(0, 0, 0, 0.8);
87 | box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.8);
88 | -webkit-transform: translateZ(-1px) scale(0.95);
89 | transform: translateZ(-1px) scale(0.95);
90 | -webkit-transition: transform 0.3s, opacity 0.3s, box-shadow 0.3s;
91 | transition: transform 0.3s, opacity 0.3s, box-shadow 0.3s;
92 | -webkit-backface-visibility: hidden;
93 | backface-visibility: hidden;
94 | }
95 | .grid__item:hover .grid__link::before {
96 | opacity: 0.2;
97 | box-shadow: 0 0 20px 10px rgba(0, 0, 0, 0.8);
98 | -webkit-transform: translateZ(-1px) scale(1);
99 | transform: translateZ(-1px) scale(1);
100 | }
101 | }
102 |
103 | .isolayer {
104 | .grid__item {
105 | padding: 15px;
106 | }
107 | .grid__link {
108 | div.layer {
109 | opacity: 0.4;
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/examples/storybooks/sandbox-utils.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import { getParameters } from 'codesandbox/lib/api/define';
3 |
4 | const GIT_URL =
5 | 'https://api.github.com/repos/wuweiweiwu/react-isometric-grid/contents';
6 |
7 | export const SANDBOX_URL = 'https://codesandbox.io/api/v1/sandboxes/define';
8 |
9 | // full url for github api call
10 | const getURL = filename => `${GIT_URL}/examples/storybooks/${filename}`;
11 |
12 | // strip ../../src from the src
13 | const strip = code => code.replace('../../src', 'react-isometric-grid');
14 |
15 | // modify code so we can just have one file in the sandbox. index.js
16 | const modify = code => {
17 | const addToTop = `import { render } from 'react-dom';\n`;
18 | const addToBottom = `\nrender(, document.getElementById('root'));`;
19 | return addToTop + code + addToBottom;
20 | };
21 |
22 | // parse. Possible the atob throws an exception
23 | const parse = base64 => {
24 | let parsed;
25 | try {
26 | parsed = atob(base64);
27 | } catch (error) {
28 | console.error('Failed to parse base64 from GitHub', error); // eslint-disable-line no-console
29 | }
30 | return parsed;
31 | };
32 |
33 | const html = ``;
34 |
35 | // using codesandbox util
36 | // returns the payload to send to the define endpoint
37 | const getPayload = code =>
38 | getParameters({
39 | files: {
40 | 'package.json': {
41 | content: {
42 | dependencies: {
43 | react: 'latest',
44 | 'react-dom': 'latest',
45 | 'prop-types': 'latest',
46 | 'react-isometric-grid': 'latest',
47 | 'dynamics.js': 'latest',
48 | },
49 | },
50 | },
51 | 'index.js': {
52 | content: code,
53 | },
54 | 'index.html': {
55 | content: html,
56 | },
57 | },
58 | });
59 |
60 | // set the form values and submit the form
61 | const sendSandboxRequest = parameters => {
62 | document.getElementById('codesandbox-parameters').value = parameters;
63 | document.getElementById('codesandbox-form').submit();
64 | };
65 |
66 | // what is called when the view sandbox element is clicked
67 | // get blob from github and the process it and send the POST request
68 | export const handleClick = file => event => {
69 | event.preventDefault();
70 | const url = getURL(file);
71 | fetch(url)
72 | .then(response => response.json())
73 | .catch(error => console.error('Error getting blob from GitHub:', error)) // eslint-disable-line no-console
74 | .then(response => {
75 | const parsed = parse(response.content);
76 | if (!parsed) {
77 | return;
78 | }
79 | const payload = getPayload(modify(strip(parsed)));
80 | sendSandboxRequest(payload);
81 | });
82 | };
83 |
--------------------------------------------------------------------------------
/src/cell.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import stylePropType from 'react-style-proptype';
4 | import classNames from 'classnames';
5 | import uniqid from 'uniqid';
6 |
7 | import styles from './react-isometric-grid.scss';
8 | import { isValidColor } from './utils/misc';
9 |
10 | const DEFAULT_STYLE = {
11 | transformStyle: 'preserve-3d',
12 | width: '200px',
13 | height: '200px',
14 | };
15 |
16 | const DEFAULT_LAYER_STYLE = {
17 | width: '200px',
18 | height: '200px',
19 | };
20 |
21 | class Cell extends Component {
22 | render() {
23 | const { layers, href, title, style, layerStyle, onClick } = this.props;
24 |
25 | const layerList = layers.map(layer => {
26 | if (!layer) {
27 | return null;
28 | }
29 | if (isValidColor(layer)) {
30 | return (
31 |
40 | );
41 | }
42 | return (
43 |
50 | );
51 | });
52 |
53 | return (
54 |
55 |
64 | {layerList.reverse()}
65 | {!!title && {title}}
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | Cell.propTypes = {
73 | // arry of images to be in the stack, or hex string for layer colors
74 | layers: PropTypes.arrayOf(PropTypes.string).isRequired,
75 |
76 | // onclick navigation link
77 | href: PropTypes.string,
78 |
79 | // onClick function
80 | onClick: PropTypes.func,
81 |
82 | // optional tital for the stack
83 | title: PropTypes.string,
84 |
85 | // styling for the Cell element
86 | style: stylePropType,
87 |
88 | // styling for the individual layers
89 | layerStyle: stylePropType,
90 | };
91 |
92 | Cell.defaultProps = {
93 | href: null,
94 | onClick: null,
95 | title: null,
96 | style: DEFAULT_STYLE,
97 | layerStyle: DEFAULT_LAYER_STYLE,
98 | };
99 |
100 | export default Cell;
101 |
--------------------------------------------------------------------------------
/examples/storybooks/basic-stack-with-title.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import dynamics from 'dynamics.js';
3 | import IsometricGrid, { Cell } from '../../src';
4 |
5 | const img1 = 'https://picsum.photos/100/100/?random';
6 | const img2 = 'https://picsum.photos/200/100/?random';
7 | const img3 = 'https://picsum.photos/300/200/?random';
8 | const img4 = 'https://picsum.photos/400/400/?random';
9 | const img5 = 'https://picsum.photos/500/300/?random';
10 | const img6 = 'https://picsum.photos/700/500/?random';
11 | const img7 = 'https://picsum.photos/800/400/?random';
12 |
13 | class App extends Component {
14 | render() {
15 | function getRandomInt(min, max) {
16 | return Math.floor(Math.random() * (max - min + 1)) + min;
17 | }
18 |
19 | return (
20 |
47 | |
51 | |
55 | |
59 | |
63 | |
67 | |
71 | |
75 | |
79 | |
83 |
84 | );
85 | }
86 | }
87 |
88 | export default App;
89 |
--------------------------------------------------------------------------------
/examples/storybooks/loaded-callback.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 | import React, { Component } from 'react';
3 | import dynamics from 'dynamics.js';
4 | import bonzo from 'bonzo';
5 | import IsometricGrid, { Cell } from '../../src';
6 |
7 | import styles from './generic.scss';
8 |
9 | const img1 = 'https://picsum.photos/100/100/?random';
10 | const img2 = 'https://picsum.photos/200/100/?random';
11 | const img3 = 'https://picsum.photos/300/200/?random';
12 | const img4 = 'https://picsum.photos/400/400/?random';
13 | const img5 = 'https://picsum.photos/500/300/?random';
14 | const img6 = 'https://picsum.photos/700/500/?random';
15 | const img7 = 'https://picsum.photos/800/400/?random';
16 |
17 | class App extends Component {
18 | render() {
19 | function getRandomInt(min, max) {
20 | return Math.floor(Math.random() * (max - min + 1)) + min;
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |
31 | bonzo(
32 | document.getElementsByClassName(styles.wrapper)[0]
33 | ).addClass(styles['grid-loaded'])
34 | }
35 | stackItemsAnimation={{
36 | properties(pos) {
37 | return {
38 | translateZ: (pos + 1) * 30,
39 | rotateZ: getRandomInt(-4, 4),
40 | };
41 | },
42 | options(pos, itemstotal) {
43 | return {
44 | type: dynamics.bezier,
45 | duration: 500,
46 | points: [
47 | { x: 0, y: 0, cp: [{ x: 0.2, y: 1 }] },
48 | { x: 1, y: 1, cp: [{ x: 0.3, y: 1 }] },
49 | ],
50 | delay: (itemstotal - pos - 1) * 40,
51 | };
52 | },
53 | }}
54 | style={{
55 | height: '500px',
56 | width: '800px',
57 | }}
58 | >
59 | |
60 | |
61 | |
62 | |
63 | |
64 | |
65 | |
66 | |
67 | |
68 |
69 |
70 |
71 | );
72 | }
73 | }
74 |
75 | export default App;
76 |
--------------------------------------------------------------------------------
/src/react-isometric-grid.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import stylePropType from 'react-style-proptype';
4 | import dynamics from 'dynamics.js';
5 | import classNames from 'classnames';
6 |
7 | import styles from './react-isometric-grid.scss';
8 | import IsometricGrid from './isometric-grid';
9 |
10 | class ReactIsometricGrid extends Component {
11 | componentDidMount() {
12 | const {
13 | onGridLoaded,
14 | perspective,
15 | transform,
16 | stackItemsAnimation,
17 | } = this.props;
18 |
19 | this.isometricGrid = new IsometricGrid(
20 | document.querySelector(`.${styles.isolayer}`),
21 | {
22 | perspective,
23 | transform,
24 | stackItemsAnimation,
25 | onGridLoaded,
26 | }
27 | );
28 | }
29 |
30 | render() {
31 | const { style, shadow, children } = this.props;
32 |
33 | return (
34 |
43 | );
44 | }
45 | }
46 |
47 | ReactIsometricGrid.propTypes = {
48 | // have a shadow under the cells
49 | shadow: PropTypes.bool,
50 |
51 | // ongridloaded callback
52 | onGridLoaded: PropTypes.func,
53 |
54 | // style
55 | style: stylePropType,
56 |
57 | // children (Cell elements)
58 | children: PropTypes.arrayOf(PropTypes.element).isRequired,
59 |
60 | // perspective value, # of px distance from z origin
61 | perspective: PropTypes.number,
62 |
63 | // transform of the isometric grid in 3d space
64 | // https://www.w3schools.com/cssref/css3_pr_transform.asp
65 | transform: PropTypes.string,
66 |
67 | // animation values for each cell dynamicjs
68 | stackItemsAnimation: PropTypes.shape({
69 | // object of the properties/values you want to animate
70 | // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function
71 | properties: PropTypes.func,
72 |
73 | // object representing the animation like duration and easing
74 | // https://github.com/michaelvillar/dynamics.js#dynamicsanimateel-properties-options
75 | options: PropTypes.func,
76 | }),
77 | };
78 |
79 | ReactIsometricGrid.defaultProps = {
80 | shadow: false,
81 | onGridLoaded: () => {},
82 | style: {
83 | height: '600px',
84 | width: '600px',
85 | position: 'absolute',
86 | left: 0,
87 | top: 0,
88 | },
89 | perspective: 3000,
90 | transform: 'scale3d(0.8,0.8,1) rotateY(45deg) rotateZ(-10deg)',
91 | stackItemsAnimation: {
92 | properties(pos) {
93 | return {
94 | rotateX: (pos + 1) * -15,
95 | };
96 | },
97 | options(pos, totalItems) {
98 | return {
99 | type: dynamics.spring,
100 | delay: (totalItems - pos - 1) * 30,
101 | };
102 | },
103 | },
104 | };
105 |
106 | export default ReactIsometricGrid;
107 |
--------------------------------------------------------------------------------
/examples/storybooks/spread.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import dynamics from 'dynamics.js';
3 | import IsometricGrid, { Cell } from '../../src';
4 |
5 | const img1 = 'https://picsum.photos/100/100/?random';
6 | const img2 = 'https://picsum.photos/200/100/?random';
7 | const img3 = 'https://picsum.photos/300/200/?random';
8 | const img4 = 'https://picsum.photos/400/400/?random';
9 | const img5 = 'https://picsum.photos/500/300/?random';
10 | const img6 = 'https://picsum.photos/700/500/?random';
11 | const img7 = 'https://picsum.photos/800/400/?random';
12 |
13 | class App extends Component {
14 | render() {
15 | function getRandomInt(min, max) {
16 | return Math.floor(Math.random() * (max - min + 1)) + min;
17 | }
18 |
19 | const cellStyle = {
20 | border: '20px solid #fff',
21 | borderWidth: '40px 20px',
22 | boxShadow: '-1px 1px 5px rgba(0, 0, 0, 0.1)',
23 | };
24 |
25 | const style = {
26 | transformStyle: 'flat',
27 | padding: '20px',
28 | };
29 |
30 | return (
31 |
58 | |
63 | |
68 | |
73 | |
78 | |
83 | |
88 | |
93 | |
98 |
99 | );
100 | }
101 | }
102 |
103 | export default App;
104 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@weiweiwu.me. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/examples/basic-example/stylesheets/vendor/github-light.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 GitHub Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | .pl-c /* comment */ {
19 | color: #969896;
20 | }
21 |
22 | .pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,
23 | .pl-s .pl-v /* string variable */ {
24 | color: #0086b3;
25 | }
26 |
27 | .pl-e /* entity */,
28 | .pl-en /* entity.name */ {
29 | color: #795da3;
30 | }
31 |
32 | .pl-s .pl-s1 /* string source */,
33 | .pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {
34 | color: #333;
35 | }
36 |
37 | .pl-ent /* entity.name.tag */ {
38 | color: #63a35c;
39 | }
40 |
41 | .pl-k /* keyword, storage, storage.type */ {
42 | color: #a71d5d;
43 | }
44 |
45 | .pl-pds /* punctuation.definition.string, string.regexp.character-class */,
46 | .pl-s /* string */,
47 | .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,
48 | .pl-sr /* string.regexp */,
49 | .pl-sr .pl-cce /* string.regexp constant.character.escape */,
50 | .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */,
51 | .pl-sr .pl-sre /* string.regexp source.ruby.embedded */ {
52 | color: #183691;
53 | }
54 |
55 | .pl-v /* variable */ {
56 | color: #ed6a43;
57 | }
58 |
59 | .pl-id /* invalid.deprecated */ {
60 | color: #b52a1d;
61 | }
62 |
63 | .pl-ii /* invalid.illegal */ {
64 | background-color: #b52a1d;
65 | color: #f8f8f8;
66 | }
67 |
68 | .pl-sr .pl-cce /* string.regexp constant.character.escape */ {
69 | color: #63a35c;
70 | font-weight: bold;
71 | }
72 |
73 | .pl-ml /* markup.list */ {
74 | color: #693a17;
75 | }
76 |
77 | .pl-mh /* markup.heading */,
78 | .pl-mh .pl-en /* markup.heading entity.name */,
79 | .pl-ms /* meta.separator */ {
80 | color: #1d3e81;
81 | font-weight: bold;
82 | }
83 |
84 | .pl-mq /* markup.quote */ {
85 | color: #008080;
86 | }
87 |
88 | .pl-mi /* markup.italic */ {
89 | color: #333;
90 | font-style: italic;
91 | }
92 |
93 | .pl-mb /* markup.bold */ {
94 | color: #333;
95 | font-weight: bold;
96 | }
97 |
98 | .pl-md /* markup.deleted, meta.diff.header.from-file */ {
99 | background-color: #ffecec;
100 | color: #bd2c00;
101 | }
102 |
103 | .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {
104 | background-color: #eaffea;
105 | color: #55a532;
106 | }
107 |
108 | .pl-mdr /* meta.diff.range */ {
109 | color: #795da3;
110 | font-weight: bold;
111 | }
112 |
113 | .pl-mo /* meta.output */ {
114 | color: #1d3e81;
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/examples/basic-example/stylesheets/vendor/stylesheet.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | }
5 |
6 | .btn {
7 | display: inline-block;
8 | margin-bottom: 1rem;
9 | color: rgba(255, 255, 255, 0.7);
10 | background-color: rgba(255, 255, 255, 0.08);
11 | border-color: rgba(255, 255, 255, 0.2);
12 | border-style: solid;
13 | border-width: 1px;
14 | border-radius: 0.3rem;
15 | transition: color 0.2s, background-color 0.2s, border-color 0.2s;
16 | }
17 | .btn + .btn {
18 | margin-left: 1rem;
19 | }
20 |
21 | .btn:hover {
22 | color: rgba(255, 255, 255, 0.8);
23 | text-decoration: none;
24 | background-color: rgba(255, 255, 255, 0.2);
25 | border-color: rgba(255, 255, 255, 0.3);
26 | }
27 |
28 | @media screen and (min-width: 64em) {
29 | .btn {
30 | padding: 0.75rem 1rem;
31 | }
32 | }
33 |
34 | @media screen and (min-width: 42em) and (max-width: 64em) {
35 | .btn {
36 | padding: 0.6rem 0.9rem;
37 | font-size: 0.9rem;
38 | }
39 | }
40 |
41 | @media screen and (max-width: 42em) {
42 | .btn {
43 | display: block;
44 | width: 100%;
45 | padding: 0.75rem;
46 | font-size: 0.9rem;
47 | }
48 | .btn + .btn {
49 | margin-top: 1rem;
50 | margin-left: 0;
51 | }
52 | }
53 |
54 | .page-header {
55 | color: #fff;
56 | text-align: center;
57 | background-color: #159957;
58 | background-image: linear-gradient(120deg, #155799, #159957);
59 | }
60 |
61 | @media screen and (min-width: 64em) {
62 | .page-header {
63 | padding: 5rem 6rem;
64 | }
65 | }
66 |
67 | @media screen and (min-width: 42em) and (max-width: 64em) {
68 | .page-header {
69 | padding: 3rem 4rem;
70 | }
71 | }
72 |
73 | @media screen and (max-width: 42em) {
74 | .page-header {
75 | padding: 2rem 1rem;
76 | }
77 | }
78 |
79 | .project-name {
80 | margin-top: 0;
81 | margin-bottom: 0.1rem;
82 | }
83 |
84 | @media screen and (min-width: 64em) {
85 | .project-name {
86 | font-size: 3.25rem;
87 | }
88 | }
89 |
90 | @media screen and (min-width: 42em) and (max-width: 64em) {
91 | .project-name {
92 | font-size: 2.25rem;
93 | }
94 | }
95 |
96 | @media screen and (max-width: 42em) {
97 | .project-name {
98 | font-size: 1.75rem;
99 | }
100 | }
101 |
102 | .project-tagline {
103 | margin-bottom: 2rem;
104 | font-weight: normal;
105 | opacity: 0.7;
106 | }
107 |
108 | @media screen and (min-width: 64em) {
109 | .project-tagline {
110 | font-size: 1.25rem;
111 | }
112 | }
113 |
114 | @media screen and (min-width: 42em) and (max-width: 64em) {
115 | .project-tagline {
116 | font-size: 1.15rem;
117 | }
118 | }
119 |
120 | @media screen and (max-width: 42em) {
121 | .project-tagline {
122 | font-size: 1rem;
123 | }
124 | }
125 |
126 | @media screen and (min-width: 64em) {
127 | .main-content {
128 | /* max-width: 64rem; */
129 | padding: 2rem 6rem;
130 | /* margin: 0 auto; */
131 | font-size: 1.1rem;
132 | }
133 | }
134 |
135 | @media screen and (min-width: 42em) and (max-width: 64em) {
136 | .main-content {
137 | padding: 2rem 4rem;
138 | font-size: 1.1rem;
139 | }
140 | }
141 |
142 | @media screen and (max-width: 42em) {
143 | .main-content {
144 | padding: 2rem 1rem;
145 | font-size: 1rem;
146 | }
147 | }
148 |
149 | .site-footer {
150 | padding-top: 2rem;
151 | margin-top: 2rem;
152 | border-top: solid 1px #7f7f80;
153 | }
154 |
155 | .site-footer-owner {
156 | display: block;
157 | font-weight: bold;
158 | }
159 |
160 | .site-footer-credits {
161 | color: #819198;
162 | }
163 |
164 | @media screen and (min-width: 64em) {
165 | .site-footer {
166 | font-size: 1rem;
167 | }
168 | }
169 |
170 | @media screen and (min-width: 42em) and (max-width: 64em) {
171 | .site-footer {
172 | font-size: 1rem;
173 | }
174 | }
175 |
176 | @media screen and (max-width: 42em) {
177 | .site-footer {
178 | font-size: 0.9rem;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const autoprefixer = require('autoprefixer');
4 | const nodeExternals = require('webpack-node-externals');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 |
7 | const target = process.env.TARGET || 'umd';
8 |
9 | const styleLoader = {
10 | loader: 'style-loader',
11 | options: { insertAt: 'top' },
12 | };
13 |
14 | const fileLoader = {
15 | loader: 'file-loader',
16 | options: { name: 'static/[name].[ext]' },
17 | };
18 |
19 | const postcssLoader = {
20 | loader: 'postcss-loader',
21 | options: {
22 | plugins: () => [
23 | autoprefixer({ browsers: ['IE >= 9', 'last 2 versions', '> 1%'] }),
24 | ],
25 | },
26 | };
27 |
28 | const cssLoader = isLocal => ({
29 | loader: 'css-loader',
30 | options: {
31 | modules: true,
32 | '-autoprefixer': true,
33 | importLoaders: true,
34 | localIdentName: isLocal ? 'rst__[local]' : null,
35 | },
36 | });
37 |
38 | const config = {
39 | entry: './src/index',
40 | output: {
41 | path: path.join(__dirname, 'dist'),
42 | filename: '[name].js',
43 | libraryTarget: 'umd',
44 | library: 'ReactSortableTree',
45 | },
46 | devtool: 'source-map',
47 | plugins: [
48 | new webpack.EnvironmentPlugin({ NODE_ENV: 'production' }),
49 | new webpack.optimize.OccurrenceOrderPlugin(),
50 | // Use uglify for dead code removal
51 | new webpack.optimize.UglifyJsPlugin({
52 | compress: {
53 | warnings: false,
54 | },
55 | mangle: false,
56 | beautify: true,
57 | comments: true,
58 | }),
59 | ],
60 | module: {
61 | rules: [
62 | {
63 | test: /\.jsx?$/,
64 | use: ['babel-loader'],
65 | exclude: path.join(__dirname, 'node_modules'),
66 | },
67 | {
68 | test: /\.scss$/,
69 | use: [styleLoader, cssLoader(true), postcssLoader, 'sass-loader'],
70 | exclude: path.join(__dirname, 'node_modules'),
71 | },
72 | {
73 | // Used for importing css from external modules (react-virtualized, etc.)
74 | test: /\.css$/,
75 | use: [styleLoader, cssLoader(false), postcssLoader],
76 | // use: [styleLoader, cssLoader(false)],
77 | },
78 | ],
79 | },
80 | };
81 |
82 | switch (target) {
83 | case 'umd':
84 | // Exclude library dependencies from the bundle
85 | config.externals = [
86 | nodeExternals({
87 | // load non-javascript files with extensions, presumably via loaders
88 | whitelist: [/\.(?!(?:jsx?|json)$).{1,5}$/i],
89 | }),
90 | ];
91 | break;
92 | case 'development':
93 | config.devtool = 'eval-source-map';
94 | config.module.rules.push({
95 | test: /\.(jpe?g|png|gif|ico|svg)$/,
96 | use: [fileLoader],
97 | exclude: path.join(__dirname, 'node_modules'),
98 | });
99 | config.entry = ['react-hot-loader/patch', './examples/basic-example/index'];
100 | config.output = {
101 | path: path.join(__dirname, 'build'),
102 | filename: 'static/[name].js',
103 | };
104 | config.plugins = [
105 | new HtmlWebpackPlugin({
106 | inject: true,
107 | template: './examples/basic-example/index.html',
108 | }),
109 | new webpack.EnvironmentPlugin({ NODE_ENV: 'development' }),
110 | new webpack.NoEmitOnErrorsPlugin(),
111 | ];
112 | config.devServer = {
113 | contentBase: path.join(__dirname, 'build'),
114 | port: process.env.PORT || 3001,
115 | stats: 'minimal',
116 | };
117 |
118 | break;
119 | case 'demo':
120 | config.module.rules.push({
121 | test: /\.(jpe?g|png|gif|ico|svg)$/,
122 | use: [fileLoader],
123 | exclude: path.join(__dirname, 'node_modules'),
124 | });
125 | config.entry = './examples/basic-example/index';
126 | config.output = {
127 | path: path.join(__dirname, 'build'),
128 | filename: 'static/[name].js',
129 | };
130 | config.plugins = [
131 | new HtmlWebpackPlugin({
132 | inject: true,
133 | template: './examples/basic-example/index.html',
134 | }),
135 | new webpack.EnvironmentPlugin({ NODE_ENV: 'production' }),
136 | new webpack.optimize.UglifyJsPlugin({
137 | compress: {
138 | warnings: false,
139 | },
140 | }),
141 | ];
142 |
143 | break;
144 | default:
145 | }
146 |
147 | module.exports = config;
148 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-isometric-grid",
3 | "version": "0.0.3",
4 | "description": "Isometric grid component for React",
5 | "scripts": {
6 | "build": "npm run clean && cross-env NODE_ENV=production TARGET=umd webpack --bail",
7 | "build:demo": "npm run clean:demo && cross-env NODE_ENV=production TARGET=demo webpack --bail && npm run build-storybook",
8 | "clean": "rimraf dist",
9 | "clean:demo": "rimraf build",
10 | "start": "cross-env NODE_ENV=development TARGET=development webpack-dev-server --inline --hot",
11 | "lint": "eslint src examples",
12 | "prettier": "prettier --single-quote --trailing-comma es5 --write \"{src,examples}/**/*.{js,scss}\"",
13 | "prepublishOnly": "npm run lint && npm run test && npm run build",
14 | "test": "jest",
15 | "test:watch": "jest --watchAll",
16 | "test:coverage": "jest --coverage && ./cc-test-reporter after-build --id=\"078550efafc3dbb8faf56159f96a1f6aed1bf6633c08cea85d7ac08a45c781b9\"",
17 | "deploy": "npm run build:demo && gh-pages -d build",
18 | "storybook": "cross-env TARGET=development start-storybook -p ${PORT:-3001} -h 0.0.0.0",
19 | "build-storybook": "cross-env NODE_ENV=production TARGET=demo build-storybook -o build/storybook"
20 | },
21 | "main": "dist/main.js",
22 | "files": [
23 | "dist"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/wuweiweiwu/react-isometric-grid"
28 | },
29 | "homepage": "https://github.com/wuweiweiwu/react-isometric-grid",
30 | "bugs": "https://github.com/wuweiweiwu/react-isometric-grid/issues",
31 | "authors": [
32 | "Wei-Wei Wu"
33 | ],
34 | "license": "MIT",
35 | "jest": {
36 | "setupTestFrameworkScriptFile": "./node_modules/jest-enzyme/lib/index.js",
37 | "setupFiles": [
38 | "./test-config/shim.js",
39 | "./test-config/test-setup.js"
40 | ],
41 | "moduleFileExtensions": [
42 | "js",
43 | "jsx",
44 | "json"
45 | ],
46 | "moduleDirectories": [
47 | "node_modules"
48 | ],
49 | "moduleNameMapper": {
50 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js",
51 | "\\.(css|scss|less)$": "identity-obj-proxy"
52 | }
53 | },
54 | "dependencies": {
55 | "bonzo": "^2.0.0",
56 | "classnames": "^2.2.5",
57 | "dynamics.js": "^1.1.5",
58 | "imagesloaded": "^4.1.4",
59 | "masonry-layout": "^4.2.1",
60 | "normalize.css": "^7.0.0",
61 | "prop-types": "^15.6.0",
62 | "react-style-proptype": "^3.1.0",
63 | "uniqid": "^4.1.1"
64 | },
65 | "peerDependencies": {
66 | "react": "^15.3.0 || ^16.0.0",
67 | "react-dom": "^15.3.0 || ^16.0.0"
68 | },
69 | "devDependencies": {
70 | "@storybook/addon-actions": "^3.3.9",
71 | "@storybook/addon-notes": "^3.3.11",
72 | "@storybook/addon-options": "^3.3.9",
73 | "@storybook/addon-storyshots": "^3.3.9",
74 | "@storybook/react": "^3.3.9",
75 | "autoprefixer": "^7.2.5",
76 | "babel-cli": "^6.26.0",
77 | "babel-core": "^6.26.0",
78 | "babel-eslint": "^8.2.1",
79 | "babel-jest": "^22.0.6",
80 | "babel-loader": "^7.1.2",
81 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
82 | "babel-polyfill": "^6.26.0",
83 | "babel-preset-env": "^1.6.1",
84 | "babel-preset-react": "^6.11.1",
85 | "codesandbox": "^1.1.14",
86 | "cross-env": "^5.1.3",
87 | "css-loader": "^0.28.8",
88 | "enzyme": "^3.3.0",
89 | "enzyme-adapter-react-16": "^1.1.1",
90 | "eslint": "^4.15.0",
91 | "eslint-config-airbnb": "^16.1.0",
92 | "eslint-config-prettier": "^2.9.0",
93 | "eslint-loader": "^1.9.0",
94 | "eslint-plugin-import": "^2.8.0",
95 | "eslint-plugin-jsx-a11y": "^6.0.3",
96 | "eslint-plugin-react": "^7.5.1",
97 | "file-loader": "^1.1.6",
98 | "gh-pages": "^1.1.0",
99 | "html-webpack-plugin": "^2.30.1",
100 | "identity-obj-proxy": "^3.0.0",
101 | "jest": "^22.0.6",
102 | "jest-enzyme": "^4.0.2",
103 | "json-loader": "^0.5.4",
104 | "node-sass": "^4.7.2",
105 | "postcss-loader": "^2.0.10",
106 | "prettier": "^1.10.2",
107 | "react": "^16.2.0",
108 | "react-addons-shallow-compare": "^15.6.2",
109 | "react-dnd-test-backend": "^2.5.4",
110 | "react-dnd-touch-backend": "^0.3.20",
111 | "react-dom": "^16.2.0",
112 | "react-hot-loader": "^3.1.3",
113 | "react-sortable-tree-theme-file-explorer": "^1.1.2",
114 | "react-test-renderer": "^16.2.0",
115 | "rimraf": "^2.6.2",
116 | "sass-loader": "^6.0.6",
117 | "style-loader": "^0.19.1",
118 | "webpack": "^3.10.0",
119 | "webpack-dev-server": "^3.1.11",
120 | "webpack-node-externals": "^1.6.0"
121 | },
122 | "keywords": [
123 | "react",
124 | "react-component"
125 | ]
126 | }
127 |
--------------------------------------------------------------------------------
/src/isometric-grid.js:
--------------------------------------------------------------------------------
1 | /*
2 | Credit to codrops for most of this code. I rewrote it as a class and
3 | used npm packages instead of .js files but most of the code is from codrops.com.
4 | */
5 |
6 | import dynamics from 'dynamics.js';
7 | import imagesLoaded from 'imagesloaded';
8 | import Masonry from 'masonry-layout';
9 |
10 | import { extend } from './utils/misc';
11 | import {
12 | getRequestAnimationFrame,
13 | getCancelAnimationFrame,
14 | } from './utils/animation-frame';
15 | import styles from './react-isometric-grid.scss';
16 |
17 | const defaultOptions = {
18 | // grid perspective value
19 | perspective: 0,
20 | // grid transform
21 | transform: '',
22 | // each grid item animation (for the subitems)
23 | stackItemsAnimation: {
24 | // this follows the dynamics.js (https://github.com/michaelvillar/dynamics.js) animate fn syntax
25 | // properties (pos is the current subitem position)
26 | properties(pos) {
27 | return {
28 | translateZ: (pos + 1) * 50,
29 | };
30 | },
31 | // animation options (pos is the current subitem position); itemstotal is the total number of subitems
32 | options() {
33 | return {
34 | type: dynamics.bezier,
35 | duration: 500,
36 | points: [
37 | { x: 0, y: 0, cp: [{ x: 0.2, y: 1 }] },
38 | { x: 1, y: 1, cp: [{ x: 0.3, y: 1 }] },
39 | ],
40 | };
41 | },
42 | },
43 | // callback for loaded grid
44 | onGridLoaded() {
45 | return false;
46 | },
47 | };
48 |
49 | // iso grid class
50 | class IsometricGrid {
51 | constructor(el, options) {
52 | this.isolayerEl = el;
53 |
54 | this.options = extend({}, defaultOptions);
55 | extend(this.options, options);
56 |
57 | if (!this.isolayerEl) {
58 | return;
59 | }
60 |
61 | this.gridEl = this.isolayerEl.querySelector(`.${styles.grid}`);
62 |
63 | // grid items
64 | this.gridItems = [].slice.call(
65 | this.gridEl.querySelectorAll(`.${styles.grid__item}`)
66 | );
67 | this.gridItemsTotal = this.gridItems.length;
68 |
69 | this.didscroll = false;
70 |
71 | this.init();
72 |
73 | // animation frame functions
74 | this.requestAnimationFrame = getRequestAnimationFrame();
75 | this.cancelAnimationFrame = getCancelAnimationFrame();
76 | }
77 |
78 | init() {
79 | const self = this;
80 |
81 | imagesLoaded(this.gridEl, () => {
82 | // initialize masonry
83 | self.msnry = new Masonry(self.gridEl, {
84 | itemSelector: `.${styles.grid__item}`,
85 | isFitWidth: true,
86 | horizontalOrder: true,
87 | });
88 |
89 | self.isolayerEl.style.WebkitTransformStyle = 'preserve-3d';
90 | self.isolayerEl.style.transformStyle = 'preserve-3d';
91 |
92 | const transformValue =
93 | self.options.perspective !== 0
94 | ? `perspective(${self.options.perspective}px) ${
95 | self.options.transform
96 | }`
97 | : self.options.transform;
98 | self.isolayerEl.style.WebkitTransform = transformValue;
99 | self.isolayerEl.style.transform = transformValue;
100 |
101 | // init/bind events
102 | self.initEvents();
103 |
104 | // grid is "loaded" (all images are loaded)
105 | self.options.onGridLoaded();
106 | });
107 | }
108 |
109 | /**
110 | * Initialize/Bind events fn.
111 | */
112 | initEvents() {
113 | const self = this;
114 |
115 | this.gridItems.forEach(item => {
116 | item.addEventListener('mouseenter', e => self.expandSubItems(e.target));
117 | item.addEventListener('mouseleave', e => self.collapseSubItems(e.target));
118 | });
119 | }
120 |
121 | expandSubItems(item) {
122 | const self = this;
123 | const itemLink = item.querySelector('a');
124 | const subItems = [].slice.call(
125 | itemLink.querySelectorAll(`.${styles.layer}`)
126 | );
127 | const subItemsTotal = subItems.length;
128 |
129 | itemLink.style.zIndex = this.gridItemsTotal;
130 | item.style.zIndex = this.gridItemsTotal; // eslint-disable-line no-param-reassign
131 |
132 | subItems.forEach((subitem, pos) => {
133 | dynamics.stop(subitem);
134 | dynamics.animate(
135 | subitem,
136 | self.options.stackItemsAnimation.properties(pos),
137 | self.options.stackItemsAnimation.options(pos, subItemsTotal)
138 | );
139 | });
140 | }
141 |
142 | // eslint-disable-next-line class-methods-use-this
143 | collapseSubItems(item) {
144 | const itemLink = item.querySelector('a');
145 | [].slice
146 | .call(itemLink.querySelectorAll(`.${styles.layer}`))
147 | .forEach(subitem => {
148 | dynamics.stop(subitem);
149 | dynamics.animate(
150 | subitem,
151 | {
152 | // enough to reset any transform value previously set
153 | translateZ: 0,
154 | },
155 | {
156 | duration: 100,
157 | complete() {
158 | itemLink.style.zIndex = 1;
159 | item.style.zIndex = 1; // eslint-disable-line no-param-reassign
160 | },
161 | }
162 | );
163 | });
164 | }
165 | }
166 |
167 | export default IsometricGrid;
168 |
--------------------------------------------------------------------------------
/examples/basic-example/app.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prefer-stateless-function */
2 | import React, { Component } from 'react';
3 | import dynamics from 'dynamics.js';
4 | import bonzo from 'bonzo';
5 |
6 | import IsometricGrid, { Cell } from '../../src';
7 |
8 | import img1 from '../shared/img/1.jpg';
9 | import img2 from '../shared/img/2.jpg';
10 | import img3 from '../shared/img/3.jpg';
11 | import img4 from '../shared/img/4.jpg';
12 | import img5 from '../shared/img/5.jpg';
13 | import img6 from '../shared/img/6.jpg';
14 | import img7 from '../shared/img/7.jpg';
15 |
16 | import styles from './stylesheets/app.scss';
17 | import '../shared/favicon/favicon.ico';
18 |
19 | class App extends Component {
20 | render() {
21 | const projectName = 'React Isometric Grid';
22 | const authorName = 'Wei-Wei Wu';
23 | const authorUrl = 'https://github.com/wuweiweiwu';
24 | const githubUrl = 'https://github.com/wuweiweiwu/react-isometric-grid';
25 |
26 | function getRandomInt(min, max) {
27 | return Math.floor(Math.random() * (max - min + 1)) + min;
28 | }
29 |
30 | return (
31 |
32 |
33 | {projectName}
34 |
35 |
36 | Isometric grid layout and animation for presenting photos and
37 | projects
38 |
39 |
40 |
41 | Demo
42 |
43 |
44 |
48 | bonzo(
49 | document.getElementsByClassName(styles.wrapper)[0]
50 | ).addClass(styles['grid-loaded'])
51 | }
52 | stackItemsAnimation={{
53 | properties(pos) {
54 | return {
55 | translateZ: (pos + 1) * 30,
56 | rotateZ: getRandomInt(-4, 4),
57 | };
58 | },
59 | options(pos, itemstotal) {
60 | return {
61 | type: dynamics.bezier,
62 | duration: 500,
63 | points: [
64 | { x: 0, y: 0, cp: [{ x: 0.2, y: 1 }] },
65 | { x: 1, y: 1, cp: [{ x: 0.3, y: 1 }] },
66 | ],
67 | delay: (itemstotal - pos - 1) * 40,
68 | };
69 | },
70 | }}
71 | style={{
72 | height: '500px',
73 | width: '800px',
74 | position: 'absolute',
75 | left: '25%',
76 | }}
77 | >
78 | |
83 | |
88 | |
93 | |
98 | |
103 | |
108 | |
113 | |
118 | |
123 |
124 |
125 |
126 | Documentation on Github
127 |
128 | More examples on Storybook
129 |
144 |
145 |
146 |
147 |
153 |
154 |
155 | );
156 | }
157 | }
158 |
159 | export default App;
160 |
--------------------------------------------------------------------------------
/examples/shared/img/PageCloud_logo.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-isometric-grids [](https://travis-ci.org/wuweiweiwu/react-isometric-grid) [](https://badge.fury.io/js/react-isometric-grid)
2 |
3 | React Isometric Grids :stuck_out_tongue: Inspired by https://github.com/codrops/IsometricGrids
4 |
5 | Featured in Codrop [Collective 386](https://tympanus.net/codrops/collective/collective-386/)!
6 |
7 | [](https://nodei.co/npm/react-isometric-grid/)
8 |
9 | ## [Demo](http://weiweiwu.me/react-isometric-grid)
10 |
11 | 
12 |
13 | ## Notes
14 |
15 | use with `normalize.css` for best results across browsers.
16 |
17 | ```bash
18 | npm i -S normalize.css
19 |
20 | # and in index.js add
21 |
22 | import 'normalize.css';
23 | ```
24 |
25 | ## Usage
26 |
27 | ```javascript
28 | import React, { Component } from 'react';
29 | import IsometricGrid, { Cell } from 'react-isometric-grid';
30 | import dynamics from 'dynamics.js';
31 |
32 | class App extends Component {
33 | render() {
34 | function getRandomInt(min, max) {
35 | return Math.floor(Math.random() * (max - min + 1)) + min;
36 | }
37 |
38 | return (
39 |
63 | |
71 | |
79 | |
87 | |
95 |
96 | );
97 | }
98 | }
99 |
100 | export default App;
101 | ```
102 |
103 | ## Options
104 |
105 | ### Isometric Grid
106 |
107 | | Prop | Type | Description | Default |
108 | | --------------------------------------------------------------------------- | -------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
109 | | children _(required)_ | React elements | Cells inside the grid | |
110 | | [perspective](https://developer.mozilla.org/en-US/docs/Web/CSS/perspective) | number | px from the z axis | `3000` |
111 | | [transform](https://www.w3schools.com/cssref/css3_pr_transform.asp) | string | css transform applied to the whole grid | `"scale3d(0.8,0.8,1) rotateY(45deg) rotateZ(-10deg)"` |
112 | | stackItemsAnimation | object | animation properties for each cell using dynamic.js | below |
113 | | shadow | boolean | Display a shadow under the cells | false |
114 | | onGridLoaded | function | Callback when the grid is loaded | `()=>{}` |
115 | | style | object | inline css styling for the inner div | `{ height: '600px', width: '600px', position: 'absolute', left: 0, top: 0 }` |
116 |
117 | #### stackItemsAnimation prop example
118 |
119 | dynamic.js animations parameters
120 |
121 | ```javascript
122 | {
123 | // object of the properties/values you want to animate
124 | // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function
125 | properties(pos) {
126 | return {
127 | rotateX: (pos + 1) * -15,
128 | };
129 | },
130 | // object representing the animation like duration and easing
131 | // https://github.com/michaelvillar/dynamics.js#dynamicsanimateel-properties-options
132 | options(pos, totalItems) {
133 | return {
134 | type: dynamics.spring,
135 | delay: (totalItems - pos - 1) * 30,
136 | };
137 | }
138 | ```
139 |
140 | ### Cell
141 |
142 | | Prop | Type | Description | Default |
143 | | ------------------- | --------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
144 | | layers _(required)_ | array of string | what each layer is in the cell.
Can be image urls or valid css colors | |
145 | | href | string | url that the image will link to when clicked | `null` |
146 | | onClick | function | what is executed when the image is clicked. If using with `href`, be sure to `preventDefault()` | `null` |
147 | | title | string | title that is under the layers. Shown on mouse over | `null` |
148 | | style | object | inline styling for the Cell component | `{ width: '200px', height: '200px', transformStyle: 'preserve-3d' }` |
149 | | layerStyle | object | inline styling for each inner layer | `{ width: '200px', height: '200px' }` |
150 | ## Compatibility
151 |
152 | It is compatible in browsers where `transform-style: 3d` is supported.
153 |
154 | 
155 |
156 | ## Troubleshooting
157 |
158 | **z-animations aren't working** Make sure you dont have `overflow` css property set. That messes up z-axis animations. https://stackoverflow.com/questions/21248111/overflow-behavior-after-using-css3-transform
159 |
160 | **2d animations are acting weird** in the `style` prop of cell. set `transformStyle: flat` SEE [#9](https://github.com/wuweiweiwu/react-isometric-grid/issues/9) https://www.w3schools.com/cssref/css3_pr_transform-style.asp
161 |
162 | **The axis of rotation is weird or not what you want** set the `transformOrigin` property of `layerStyle` prop of Cell. https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin
163 |
164 | ## Contributing
165 |
166 | After cloning the repository and running `npm install` or `yarn install` inside, you can use the following commands to develop and build the project.
167 |
168 | ```sh
169 | # Starts a webpack dev server that hosts a demo page with the component.
170 | # It uses react-hot-loader so changes are reflected on save.
171 | npm start
172 | # or
173 | yarn start
174 |
175 | # Start the storybook, which has several different examples to play with.
176 | # Also hot-reloaded.
177 | npm run storybook
178 | # or
179 | yarn storybook
180 |
181 | # Runs the library tests
182 | npm test
183 | # or
184 | yarn test
185 |
186 | # Lints the code with eslint
187 | npm run lint
188 | # or
189 | yarn lint
190 |
191 | # Lints and builds the code, placing the result in the dist directory.
192 | # This build is necessary to reflect changes if youre
193 | # `npm link`-ed to this repository from another local project.
194 | npm run build
195 | # or
196 | yarn build
197 | ```
198 |
199 | Pull requests are welcome!
200 |
201 | ## License
202 |
203 | MIT
204 |
205 | ## Credits
206 |
207 | * [Codrops](http://www.codrops.com)
208 | * [Masonry](http://masonry.desandro.com/) by David DeSandro.
209 | * [Dynamics.js](http://dynamicsjs.com/) by Michael Villar.
210 | * [Unsplash](http://unsplash.com)
211 | * Dribbble artists: [Mike](https://dribbble.com/creativemints), [Forefathers](https://dribbble.com/forefathers), [Julian Lavallee](https://dribbble.com/JulienLavallee), [Cosmin Capitanu](https://dribbble.com/Radium)
212 |
--------------------------------------------------------------------------------
/examples/storybooks/__snapshots__/storyshots.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Storyshots Advanced onGridLoaded Callback 1`] = `
4 |
5 |
8 |
13 |
553 |
554 |
555 |
566 |
572 |
578 | VIEW SOURCE →
579 |
580 |
581 | `;
582 |
583 | exports[`Storyshots Basics Basic Stack 1`] = `
584 |
1148 | `;
1149 |
1150 | exports[`Storyshots Basics Basic Stack with Title 1`] = `
1151 |
1760 | `;
1761 |
1762 | exports[`Storyshots Basics Side Stack 1`] = `
1763 |
2363 | `;
2364 |
2365 | exports[`Storyshots Basics Spread 1`] = `
2366 |
3000 | `;
3001 |
--------------------------------------------------------------------------------