├── .babelrc
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── demo.gif
├── dist
├── Grid.js
├── Grid.module.css
└── index.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── index.js
└── lib
│ ├── Grid.js
│ ├── Grid.module.css
│ └── index.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-app"],
3 | "plugins": ["babel-plugin-transform-flow-comments"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | { "extends": "react-app" }
2 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 | [options]
10 |
11 | [strict]
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "flow.useNPMPackagedFlow": true,
3 | "javascript.validate.enable": false,
4 | "files.associations": {
5 | "*.js": "javascriptreact"
6 | }
7 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-sketch-grid
2 |
3 | A React port of Sketch's grid overlay.
4 |
5 | 
6 |
7 | ## Live Demo
8 |
9 | Go to [alonso.io](http://alonso.io), and press _ctrl+g_ to toggle the grid on and off.
10 |
11 | ## Features
12 |
13 | 🔥 **Hotkeys** - toggle on/off with ctrl+G, just like in Sketch
14 |
15 | 💾 **Persistent** - remembers state across page reloads
16 |
17 | 👩🎨 **Customizable** - control grid size, color, and more
18 |
19 | 🧘♀️ **Flexible** - can be used for whole-page grids, or inside single components
20 |
21 | ## Goal
22 |
23 | I design in Sketch, and I use grids pretty heavily. When I start implementing the layout in code, I want to be looking at exactly the same grid I used in the design phase.
24 |
25 | ## Installation
26 |
27 | `yarn add react-sketch-grid`
28 |
29 | ## Usage
30 |
31 | 1. Add the `` component.
32 | 2. Add `position: relative` to any parent element you want the grid to “fill”
33 |
34 | Here are the available props:
35 |
36 | ```js
37 | type Props = {
38 | // Width, in pixels, of each small grid line
39 | blockSize: number,
40 |
41 | // Show thikk lines every N thin lines
42 | thickLinesEvery: number,
43 |
44 | // Color of the thin lines
45 | lightColor: string,
46 |
47 | // Color of the thick lines
48 | darkColor: string
49 | };
50 | ```
51 |
52 | ## Example
53 |
54 | ```jsx
55 | import Grid from 'react-sketch-grid';
56 |
57 |
58 |
59 |
Control + G to toggle grid
60 |
The grid will fill the whole div
61 |
;
62 | ```
63 |
64 | ## TODO list / help wanted
65 |
66 | - react-native compatibility (`` —> ``)
67 | - remove need for `position: relative` on parent element?
68 | - counting blocks is no fun - find a better way?
69 | - make hotkey customizable
70 | - add prop for line thickness?
71 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holmesal/react-sketch-grid/ac2e3e2160fa598259ed58122f46a81c2609c235/demo.gif
--------------------------------------------------------------------------------
/dist/Grid.js:
--------------------------------------------------------------------------------
1 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
6 |
7 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
8 |
9 | import React, { Component } from 'react';
10 | import './Grid.module.css';
11 | import range from 'lodash.range';
12 | import Measure from 'react-measure';
13 |
14 | /*:: type Props = {
15 | // Width, in pixels, of each small grid line
16 | blockSize: number,
17 |
18 | // Show thikk lines every N thin lines
19 | thickLinesEvery: number,
20 |
21 | // Color of the thin lines
22 | lightColor?: string,
23 |
24 | // Color of the thick lines
25 | darkColor?: string
26 | };*/
27 | /*:: type State = {
28 | dimensions: {
29 | width: number,
30 | height: number
31 | },
32 | visible: boolean
33 | };*/
34 | /*:: type direction = 'vertical' | 'horizontal';*/
35 |
36 |
37 | var LOCAL_STORAGE_KEY = 'Grid.Visible';
38 |
39 | var Grid = function (_Component) {
40 | _inherits(Grid, _Component);
41 |
42 | function Grid() {
43 | var _ref;
44 |
45 | var _temp, _this, _ret;
46 |
47 | _classCallCheck(this, Grid);
48 |
49 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
50 | args[_key] = arguments[_key];
51 | }
52 |
53 | return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Grid.__proto__ || Object.getPrototypeOf(Grid)).call.apply(_ref, [this].concat(args))), _this), _this.state = {
54 | visible: false,
55 | dimensions: {
56 | width: 0,
57 | height: 0
58 | }
59 | }, _this.onKeyDown = function (_ref2) {
60 | var key = _ref2.key,
61 | ctrlKey = _ref2.ctrlKey;
62 |
63 | if (key === 'g' && ctrlKey) _this.toggleVisible();
64 | }, _this.toggleVisible = function () {
65 | var visible = !_this.state.visible;
66 | _this.setState({ visible: visible });
67 | localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(visible));
68 | }, _this.renderLine = function (direction /*: direction*/, idx /*: number*/) {
69 | var _this$props = _this.props,
70 | blockSize = _this$props.blockSize,
71 | thickLinesEvery = _this$props.thickLinesEvery,
72 | lightColor = _this$props.lightColor,
73 | darkColor = _this$props.darkColor;
74 |
75 | var key = direction + idx;
76 | var style = Object.assign({}, {
77 | backgroundColor: idx % thickLinesEvery === 0 ? darkColor : lightColor
78 | }, direction === 'horizontal' ? {
79 | left: 0,
80 | right: 0,
81 | height: 1,
82 | top: idx * blockSize
83 | } : {
84 | left: idx * blockSize,
85 | top: 0,
86 | bottom: 0,
87 | width: 1
88 | });
89 | return React.createElement('div', { key: key, style: style, className: 'GridLine' });
90 | }, _temp), _possibleConstructorReturn(_this, _ret);
91 | }
92 |
93 | _createClass(Grid, [{
94 | key: 'componentDidMount',
95 | value: function componentDidMount() {
96 | document.addEventListener('keydown', this.onKeyDown);
97 |
98 | var raw = localStorage.getItem(LOCAL_STORAGE_KEY);
99 |
100 | if (raw) {
101 | var _visible = !!JSON.parse(raw);
102 | this.setState({ visible: _visible });
103 | }
104 | }
105 | }, {
106 | key: 'componentWillUnmount',
107 | value: function componentWillUnmount() {
108 | document.removeEventListener('keydown', this.onKeyDown);
109 | }
110 | }, {
111 | key: 'render',
112 | value: function render() {
113 | var _this2 = this;
114 |
115 | var _state = this.state,
116 | visible = _state.visible,
117 | dimensions = _state.dimensions;
118 | var blockSize = this.props.blockSize;
119 |
120 |
121 | if (!visible) return React.createElement('div', null);
122 | var numHorizontal = Math.floor(dimensions.height / blockSize);
123 | var numVertical = Math.floor(dimensions.width / blockSize);
124 | return React.createElement(
125 | Measure,
126 | {
127 | bounds: true,
128 | onResize: function onResize(contentRect) {
129 | _this2.setState({ dimensions: contentRect.bounds });
130 | }
131 | },
132 | function (_ref3) {
133 | var measureRef = _ref3.measureRef;
134 | return React.createElement(
135 | 'div',
136 | { ref: measureRef, className: 'Grid' },
137 | range(1, numHorizontal + 1).map(function (idx) {
138 | return _this2.renderLine('horizontal', idx);
139 | }),
140 | range(0, numVertical + 1).map(function (idx) {
141 | return _this2.renderLine('vertical', idx);
142 | })
143 | );
144 | }
145 | );
146 | }
147 | }]);
148 |
149 | return Grid;
150 | }(Component);
151 |
152 | Grid.defaultProps = {
153 | blockSize: 12,
154 | thickLinesEvery: 6,
155 | lightColor: 'rgba(255, 0, 0, 0.2)',
156 | darkColor: 'rgba(0, 0, 255, 0.2)'
157 | };
158 |
159 |
160 | export default Grid;
--------------------------------------------------------------------------------
/dist/Grid.module.css:
--------------------------------------------------------------------------------
1 | .Grid {
2 | display: flex;
3 | flex: 1;
4 | align-self: stretch;
5 | pointer-events: none;
6 | position: absolute;
7 | top: 0;
8 | left: 0;
9 | right: 0;
10 | bottom: 0;
11 | }
12 |
13 | .GridLine {
14 | position: absolute;
15 | }
16 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | import Grid from './Grid';
2 | export default Grid;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-sketch-grid",
3 | "description": "A React port of Sketch's grid overlay",
4 | "version": "0.1.5",
5 | "main": "dist/index.js",
6 | "module": "dist/index.js",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/holmesal/react-sketch-grid"
10 | },
11 | "files": [
12 | "dist"
13 | ],
14 | "keywords": [
15 | "react-sketch-grid",
16 | "react sketch grid",
17 | "sketch grid",
18 | "sketch",
19 | "grid",
20 | "react sketch"
21 | ],
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build-examples": "react-scripts build",
25 | "test": "react-scripts test --env=jsdom",
26 | "eject": "react-scripts eject",
27 | "build": "rimraf dist && NODE_ENV=production babel src/lib --out-dir dist --copy-files --ignore __tests__,spec.js,test.js,__snapshots__"
28 | },
29 | "dependencies": {
30 | "lodash.range": "^3.2.0",
31 | "react-measure": "^2.1.2"
32 | },
33 | "peerDependencies": {
34 | "react": "^16.5.1",
35 | "react-dom": "^16.5.1"
36 | },
37 | "devDependencies": {
38 | "babel-cli": "^6.26.0",
39 | "babel-plugin-transform-flow-comments": "^6.22.0",
40 | "flow-bin": "^0.81.0",
41 | "react": "^16.5.1",
42 | "react-dom": "^16.5.1",
43 | "react-scripts": "^1.1.5",
44 | "rimraf": "^2.6.2"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holmesal/react-sketch-grid/ac2e3e2160fa598259ed58122f46a81c2609c235/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import Grid from './lib';
4 |
5 | const App = () => (
6 |
7 |
8 |
Control + G to toggle grid
9 |
10 | );
11 |
12 | render(, document.getElementById('root'));
13 |
--------------------------------------------------------------------------------
/src/lib/Grid.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import './Grid.module.css';
4 | import range from 'lodash.range';
5 | import Measure from 'react-measure';
6 |
7 | type Props = {
8 | // Width, in pixels, of each small grid line
9 | blockSize: number,
10 |
11 | // Show thikk lines every N thin lines
12 | thickLinesEvery: number,
13 |
14 | // Color of the thin lines
15 | lightColor?: string,
16 |
17 | // Color of the thick lines
18 | darkColor?: string
19 | };
20 |
21 | type State = {
22 | dimensions: {
23 | width: number,
24 | height: number
25 | },
26 | visible: boolean
27 | };
28 |
29 | type direction = 'vertical' | 'horizontal';
30 |
31 | const LOCAL_STORAGE_KEY = 'Grid.Visible';
32 |
33 | class Grid extends Component {
34 | static defaultProps = {
35 | blockSize: 12,
36 | thickLinesEvery: 6,
37 | lightColor: 'rgba(255, 0, 0, 0.2)',
38 | darkColor: 'rgba(0, 0, 255, 0.2)'
39 | };
40 |
41 | state = {
42 | visible: false,
43 | dimensions: {
44 | width: 0,
45 | height: 0
46 | }
47 | };
48 |
49 | componentDidMount() {
50 | document.addEventListener('keydown', this.onKeyDown);
51 |
52 | const raw = localStorage.getItem(LOCAL_STORAGE_KEY);
53 |
54 | if (raw) {
55 | const visible = !!JSON.parse(raw);
56 | this.setState({ visible });
57 | }
58 | }
59 |
60 | componentWillUnmount() {
61 | document.removeEventListener('keydown', this.onKeyDown);
62 | }
63 |
64 | onKeyDown = ({ key, ctrlKey }: KeyboardEvent) => {
65 | if (key === 'g' && ctrlKey) this.toggleVisible();
66 | };
67 |
68 | toggleVisible = () => {
69 | const visible = !this.state.visible;
70 | this.setState({ visible });
71 | localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(visible));
72 | };
73 |
74 | renderLine = (direction: direction, idx: number) => {
75 | const { blockSize, thickLinesEvery, lightColor, darkColor } = this.props;
76 | const key = direction + idx;
77 | const style = Object.assign(
78 | {},
79 | {
80 | backgroundColor: idx % thickLinesEvery === 0 ? darkColor : lightColor
81 | },
82 | direction === 'horizontal'
83 | ? {
84 | left: 0,
85 | right: 0,
86 | height: 1,
87 | top: idx * blockSize
88 | }
89 | : {
90 | left: idx * blockSize,
91 | top: 0,
92 | bottom: 0,
93 | width: 1
94 | }
95 | );
96 | return ;
97 | };
98 |
99 | render() {
100 | const { visible, dimensions } = this.state;
101 | const { blockSize } = this.props;
102 |
103 | if (!visible) return ;
104 | const numHorizontal = Math.floor(dimensions.height / blockSize);
105 | const numVertical = Math.floor(dimensions.width / blockSize);
106 | return (
107 | {
110 | this.setState({ dimensions: contentRect.bounds });
111 | }}
112 | >
113 | {({ measureRef }) => (
114 |
115 | {range(1, numHorizontal + 1).map(idx =>
116 | this.renderLine('horizontal', idx)
117 | )}
118 | {range(0, numVertical + 1).map(idx =>
119 | this.renderLine('vertical', idx)
120 | )}
121 |
122 | )}
123 |
124 | );
125 | }
126 | }
127 |
128 | export default Grid;
129 |
--------------------------------------------------------------------------------
/src/lib/Grid.module.css:
--------------------------------------------------------------------------------
1 | .Grid {
2 | display: flex;
3 | flex: 1;
4 | align-self: stretch;
5 | pointer-events: none;
6 | position: absolute;
7 | top: 0;
8 | left: 0;
9 | right: 0;
10 | bottom: 0;
11 | }
12 |
13 | .GridLine {
14 | position: absolute;
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/index.js:
--------------------------------------------------------------------------------
1 | import Grid from './Grid';
2 | export default Grid;
3 |
--------------------------------------------------------------------------------