├── src
├── index.css
├── App.test.js
├── index.js
├── components
│ ├── SortableList.css
│ ├── WindowHeight.jsx
│ ├── Block.jsx
│ ├── BlockGenerator.jsx
│ ├── BlockGenerator.css
│ └── SortableList.jsx
├── App.css
├── App.jsx
├── logo.svg
└── registerServiceWorker.js
├── public
├── favicon.ico
├── manifest.json
└── index.html
├── .gitignore
├── config
├── jest
│ ├── fileTransform.js
│ └── cssTransform.js
├── polyfills.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── README.md
├── scripts
├── test.js
├── start.js
└── build.js
├── LICENSE-MIT
└── package.json
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmakGH/shopify-draggable-react-example/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/SortableList.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Hide dragging source element by default.
3 | */
4 |
5 | .hideDragSource {
6 | opacity: 0;
7 | }
8 |
9 | /**
10 | * 1. Grabbing cursor.
11 | * 2. Reset transition duration to ignore any existing on the source element.
12 | * 3. Increase z-index to make sure dragged item sits on top of other elements.
13 | */
14 |
15 | .defaultMirror {
16 | cursor: grabbing;
17 | transition-duration: 0s !important;
18 | z-index: 10;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/WindowHeight.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Component } from 'react';
3 |
4 | export default class WindowHeight extends Component {
5 | static propTypes = {
6 | children: PropTypes.func,
7 | }
8 |
9 | componentDidMount() {
10 | window.addEventListener('resize', this.rerender);
11 | }
12 |
13 | componentWillUnmount() {
14 | window.removeEventListener('resize', this.rerender);
15 | }
16 |
17 | rerender = () => {
18 | this.forceUpdate();
19 | }
20 |
21 | render() {
22 | return this.props.children({ height: window.innerHeight });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
2 |
3 | This is a simple demo to showcase how to integrate Shopify's Draggable library
4 | with React. Currently the only portion for display is a Sortable example. More
5 | will continually be added to cover the other parts of the Draggable library.
6 |
7 | Read more about [Draggable](https://github.com/Shopify/draggable).
8 |
9 | ## Demo
10 |
11 | Checkout the [demo](https://jmakgh.github.io/shopify-draggable-react-example/).
12 |
13 | ## Getting Started
14 |
15 | After cloning the repo, all you have to do is run `npm start` and visit `localhost:3000`.
16 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === 'test') {
21 | require('raf').polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Block.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { PureComponent } from 'react';
3 |
4 | const random255 = () => Math.floor(Math.random() * 256);
5 | const randomColor = () => `rgb(${random255()}, ${random255()}, ${random255()})`;
6 |
7 | export default class Block extends PureComponent {
8 | static propTypes = {
9 | className: PropTypes.string,
10 | label: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
11 | }
12 |
13 | static defaultProps = {
14 | className: '',
15 | label: '#',
16 | }
17 |
18 | // We'll only generate the background style once when a Block is created.
19 | constructor(props) {
20 | super(props);
21 |
22 | this.style = { backgroundColor: randomColor() };
23 | }
24 |
25 | render() {
26 | const { className, label, ...props } = this.props;
27 |
28 | return (
29 |
34 | {label}
35 |
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright 2018 Justin Mak
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | align-items: center;
3 | display: flex;
4 | flex-direction: column;
5 | text-align: center;
6 | }
7 |
8 | .App-body {
9 | margin: 0 auto;
10 | max-width: 780px;
11 | padding-bottom: 40px;
12 | }
13 |
14 | .App-body-count {
15 | display: flex;
16 | flex-wrap: nowrap;
17 | margin-bottom: 80px;
18 | margin-top: 40px;
19 | }
20 |
21 | .App-body-count-text {
22 | color: #E0E0E0;
23 | font-size: 60px;
24 | letter-spacing: -1px;
25 | margin: 0;
26 | text-align: left;
27 | text-transform: uppercase;
28 | }
29 |
30 | .App-body-count-input {
31 | border: none;
32 | color: #E0E0E0;
33 | font-size: 180px;
34 | height: 140px;
35 | letter-spacing: -8px;
36 | margin-left: 20px;
37 | outline-color: #212121;
38 | outline-width: 1px;
39 | width: 300px;
40 | }
41 |
42 | /* Hide HTML5 Up and Down arrows. */
43 | input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button {
44 | -webkit-appearance: none;
45 | margin: 0;
46 | }
47 |
48 | input[type="number"] {
49 | -moz-appearance: textfield;
50 | }
51 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | Shopify Draggable Examples
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import BlockGenerator from 'components/BlockGenerator';
4 | import WindowHeight from 'components/WindowHeight';
5 | import './App.css';
6 |
7 | class App extends Component {
8 | state = {
9 | blockCount: 14, // Default
10 | }
11 |
12 | onChangeCount = ({ target }) => {
13 | // Prevent NaN values.
14 | const value = target.value || 0;
15 |
16 | // Clamp value. Setting a max of 100 blocks for now.
17 | const blockCount = Math.min(Math.max(parseInt(value, 10), 0), 100);
18 |
19 | this.setState({ blockCount });
20 | }
21 |
22 | render() {
23 | const { blockCount } = this.state;
24 |
25 | // Generate blocks.
26 | let blocks = [];
27 |
28 | for (let i = 0; i < blockCount; i++) {
29 | blocks.push(i + 1);
30 | }
31 |
32 | // Use a blank value for 0 in input to prevent leading zeroes such as '01'.
33 | const inputValue = blockCount > 0 ? blockCount : '';
34 |
35 | return (
36 |
37 | {({ height }) => (
38 |
39 |
40 |
41 |
42 | Block count:
43 |
44 |
50 |
51 |
52 |
53 |
54 | )}
55 |
56 | );
57 | }
58 | }
59 |
60 | export default App;
61 |
--------------------------------------------------------------------------------
/src/components/BlockGenerator.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 |
4 | import Block from './Block';
5 | import SortableList from './SortableList';
6 | import './BlockGenerator.css';
7 |
8 | /**
9 | * Returns a copy of the array with items swapped at the specified indices. This
10 | * is non-mutative.
11 | * @param {Array} arr array
12 | * @param {Number} fromIndex
13 | * @param {Number} toIndex
14 | */
15 | const swapAtIndex = (arr, fromIndex, toIndex) => {
16 | const newArr = arr.slice();
17 | const val = newArr[fromIndex];
18 |
19 | newArr.splice(fromIndex, 1);
20 | newArr.splice(toIndex, 0, val);
21 |
22 | return newArr;
23 | };
24 |
25 | /**
26 | * A React component that generates a list of sortable blocks.
27 | */
28 | export default class BlockGenerator extends Component {
29 | static propTypes = {
30 | blocks: PropTypes.array,
31 | }
32 |
33 | static defaultProps = {
34 | blocks: [],
35 | }
36 |
37 | constructor(props) {
38 | super(props);
39 |
40 | this.state = {
41 | blocks: props.blocks,
42 | };
43 | }
44 |
45 | componentWillReceiveProps({ blocks }) {
46 | if (blocks !== this.props.blocks) {
47 | this.setState({ blocks });
48 | }
49 | }
50 |
51 | onSwapBlocks = (oldIndex, newIndex) => {
52 | this.setState({
53 | blocks: swapAtIndex(this.state.blocks, oldIndex, newIndex),
54 | });
55 | }
56 |
57 | render() {
58 | return (
59 |
65 | {({ initSortable }) => (
66 | initSortable(el)}>
67 | {this.state.blocks.map((block, i) => (
68 |
74 | ))}
75 |
76 | )}
77 |
78 | );
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right