Improved color picker (#174)

* Add react-color

* Prettier

* Better styles

* Use enum for color pickers instead of strings

* Run prettier on .scss file
This commit is contained in:
Jared Palmer 2020-01-05 13:05:55 -08:00 committed by Christopher Chedeau
parent e7e676e1eb
commit b5c67260d7
4 changed files with 207 additions and 22 deletions

45
package-lock.json generated
View File

@ -1034,6 +1034,11 @@
"@hapi/hoek": "^8.3.0" "@hapi/hoek": "^8.3.0"
} }
}, },
"@icons/material": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
},
"@jest/console": { "@jest/console": {
"version": "24.9.0", "version": "24.9.0",
"resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
@ -1511,6 +1516,15 @@
"csstype": "^2.2.0" "csstype": "^2.2.0"
} }
}, },
"@types/react-color": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.1.tgz",
"integrity": "sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-dom": { "@types/react-dom": {
"version": "16.9.4", "version": "16.9.4",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz",
@ -9369,6 +9383,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -12094,6 +12113,19 @@
"whatwg-fetch": "^3.0.0" "whatwg-fetch": "^3.0.0"
} }
}, },
"react-color": {
"version": "2.17.3",
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz",
"integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==",
"requires": {
"@icons/material": "^0.2.4",
"lodash": "^4.17.11",
"material-colors": "^1.2.1",
"prop-types": "^15.5.10",
"reactcss": "^1.2.0",
"tinycolor2": "^1.4.1"
}
},
"react-dev-utils": { "react-dev-utils": {
"version": "10.0.0", "version": "10.0.0",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.0.0.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.0.0.tgz",
@ -12315,6 +12347,14 @@
"workbox-webpack-plugin": "4.3.1" "workbox-webpack-plugin": "4.3.1"
} }
}, },
"reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
"integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
"requires": {
"lodash": "^4.0.1"
}
},
"read-pkg": { "read-pkg": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@ -14397,6 +14437,11 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
}, },
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
},
"tmp": { "tmp": {
"version": "0.0.33", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

View File

@ -7,12 +7,14 @@
"main": "src/index.js", "main": "src/index.js",
"dependencies": { "dependencies": {
"react": "16.12.0", "react": "16.12.0",
"react-color": "^2.17.3",
"react-dom": "16.12.0", "react-dom": "16.12.0",
"react-scripts": "3.3.0", "react-scripts": "3.3.0",
"roughjs": "3.1.0" "roughjs": "3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "16.9.17", "@types/react": "16.9.17",
"@types/react-color": "^3.0.1",
"@types/react-dom": "16.9.4", "@types/react-dom": "16.9.4",
"husky": "3.1.0", "husky": "3.1.0",
"lint-staged": "9.5.0", "lint-staged": "9.5.0",

View File

@ -2,6 +2,7 @@ import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import rough from "roughjs/bin/wrappers/rough"; import rough from "roughjs/bin/wrappers/rough";
import { RoughCanvas } from "roughjs/bin/canvas"; import { RoughCanvas } from "roughjs/bin/canvas";
import { SketchPicker } from "react-color";
import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex"; import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
@ -816,9 +817,16 @@ function restore(
} }
} }
enum ColorPicker {
CANVAS_BACKGROUND,
SHAPE_STROKE,
SHAPE_BACKGROUND
}
type AppState = { type AppState = {
draggingElement: ExcalidrawElement | null; draggingElement: ExcalidrawElement | null;
resizingElement: ExcalidrawElement | null; resizingElement: ExcalidrawElement | null;
currentColorPicker: ColorPicker | null;
elementType: string; elementType: string;
exportBackground: boolean; exportBackground: boolean;
currentItemStrokeColor: string; currentItemStrokeColor: string;
@ -889,7 +897,6 @@ const SHAPES = [
const shapesShortcutKeys = SHAPES.map(shape => shape.value[0]); const shapesShortcutKeys = SHAPES.map(shape => shape.value[0]);
function capitalize(str: string) { function capitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1); return str.charAt(0).toUpperCase() + str.slice(1);
} }
@ -953,6 +960,7 @@ class App extends React.Component<{}, AppState> {
draggingElement: null, draggingElement: null,
resizingElement: null, resizingElement: null,
elementType: "selection", elementType: "selection",
currentColorPicker: null,
exportBackground: true, exportBackground: true,
currentItemStrokeColor: "#000000", currentItemStrokeColor: "#000000",
currentItemBackgroundColor: "#ffffff", currentItemBackgroundColor: "#ffffff",
@ -1134,7 +1142,11 @@ class App extends React.Component<{}, AppState> {
<h4>Shapes</h4> <h4>Shapes</h4>
<div className="panelTools"> <div className="panelTools">
{SHAPES.map(({ value, icon }) => ( {SHAPES.map(({ value, icon }) => (
<label key={value} className="tool" title={`${capitalize(value)} - ${capitalize(value)[0]}`}> <label
key={value}
className="tool"
title={`${capitalize(value)} - ${capitalize(value)[0]}`}
>
<input <input
type="radio" type="radio"
checked={this.state.elementType === value} checked={this.state.elementType === value}
@ -1152,36 +1164,123 @@ class App extends React.Component<{}, AppState> {
</div> </div>
<h4>Colors</h4> <h4>Colors</h4>
<div className="panelColumn"> <div className="panelColumn">
<label> <h5>Canvas Background</h5>
<input <div>
type="color" <button
value={this.state.viewBackgroundColor} className="swatch"
onChange={e => { style={{
this.setState({ viewBackgroundColor: e.target.value }); backgroundColor: this.state.viewBackgroundColor
}} }}
/> onClick={() =>
Background this.setState(s => ({
</label> currentColorPicker:
<label> s.currentColorPicker === ColorPicker.CANVAS_BACKGROUND
? null
: ColorPicker.CANVAS_BACKGROUND
}))
}
></button>
{this.state.currentColorPicker === ColorPicker.CANVAS_BACKGROUND ? (
<div className="popover">
<div
className="cover"
onClick={() => this.setState({ currentColorPicker: null })}
></div>
<SketchPicker
color={this.state.viewBackgroundColor}
onChange={color => {
this.setState({ viewBackgroundColor: color.hex });
}}
/>
</div>
) : null}
<input <input
type="color" type="text"
className="swatch-input"
value={this.state.viewBackgroundColor}
onChange={e =>
this.setState({ viewBackgroundColor: e.target.value })
}
/>
</div>
<h5>Shape Stroke</h5>
<div>
<button
className="swatch"
style={{
backgroundColor: this.state.currentItemStrokeColor
}}
onClick={() =>
this.setState(s => ({
currentColorPicker:
s.currentColorPicker === ColorPicker.SHAPE_STROKE
? null
: ColorPicker.SHAPE_STROKE
}))
}
></button>
{this.state.currentColorPicker === ColorPicker.SHAPE_STROKE ? (
<div className="popover">
<div
className="cover"
onClick={() => this.setState({ currentColorPicker: null })}
></div>
<SketchPicker
color={this.state.currentItemStrokeColor}
onChange={color => {
this.setState({ currentItemStrokeColor: color.hex });
}}
/>
</div>
) : null}
<input
type="text"
className="swatch-input"
value={this.state.currentItemStrokeColor} value={this.state.currentItemStrokeColor}
onChange={e => { onChange={e => {
this.setState({ currentItemStrokeColor: e.target.value }); this.setState({ currentItemStrokeColor: e.target.value });
}} }}
/> />
Shape Stroke </div>
</label> <h5>Shape Background</h5>
<label> <div>
<button
className="swatch"
style={{
backgroundColor: this.state.currentItemBackgroundColor
}}
onClick={() =>
this.setState(s => ({
currentColorPicker:
s.currentColorPicker === ColorPicker.SHAPE_BACKGROUND
? null
: ColorPicker.SHAPE_BACKGROUND
}))
}
></button>
{this.state.currentColorPicker === ColorPicker.SHAPE_BACKGROUND ? (
<div className="popover">
<div
className="cover"
onClick={() => this.setState({ currentColorPicker: null })}
></div>
<SketchPicker
color={this.state.currentItemBackgroundColor}
onChange={color => {
this.setState({ currentItemBackgroundColor: color.hex });
}}
/>
</div>
) : null}
<input <input
type="color" type="text"
value={this.state.currentItemBackgroundColor} className="swatch-input"
value={this.state.currentItemStrokeColor}
onChange={e => { onChange={e => {
this.setState({ currentItemBackgroundColor: e.target.value }); this.setState({ currentItemStrokeColor: e.target.value });
}} }}
/> />
Shape Background </div>
</label>
</div> </div>
<h4>Canvas</h4> <h4>Canvas</h4>
<div className="panelColumn"> <div className="panelColumn">

View File

@ -22,7 +22,6 @@ body {
.sidePanel { .sidePanel {
width: 230px; width: 230px;
background-color: #eee; background-color: #eee;
padding: 10px; padding: 10px;
overflow-y: auto; overflow-y: auto;
@ -42,6 +41,17 @@ body {
.panelColumn { .panelColumn {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
h5 {
margin-top: 4px;
margin-bottom: 4px;
font-size: 12px;
color: #333;
}
h5:first-of-type {
margin-top: 0;
}
} }
} }
@ -134,3 +144,32 @@ button {
cursor: not-allowed; cursor: not-allowed;
} }
} }
.popover {
position: absolute;
z-index: 2;
.cover {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
.swatch {
height: 24px;
width: 24px;
display: inline;
margin-right: 4px;
}
.swatch-input {
font-size: 16px;
display: inline;
width: 100px;
border-radius: 2px;
padding: 2px 4px;
border: 1px solid #ddd;
}