├── .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 | ![Demo](/demo.gif) 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 | --------------------------------------------------------------------------------