Bläddra i källkod

Improved color picker (#174)

* Add react-color

* Prettier

* Better styles

* Use enum for color pickers instead of strings

* Run prettier on .scss file
Jared Palmer 5 år sedan
förälder
incheckning
b5c67260d7
4 ändrade filer med 205 tillägg och 20 borttagningar
  1. 45 0
      package-lock.json
  2. 2 0
      package.json
  3. 118 19
      src/index.tsx
  4. 40 1
      src/styles.scss

+ 45 - 0
package-lock.json

@@ -1034,6 +1034,11 @@
         "@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": {
       "version": "24.9.0",
       "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
@@ -1511,6 +1516,15 @@
         "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": {
       "version": "16.9.4",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz",
@@ -9369,6 +9383,11 @@
         "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": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -12094,6 +12113,19 @@
         "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": {
       "version": "10.0.0",
       "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"
       }
     },
+    "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": {
       "version": "3.0.0",
       "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",
       "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": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

+ 2 - 0
package.json

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

+ 118 - 19
src/index.tsx

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

+ 40 - 1
src/styles.scss

@@ -22,7 +22,6 @@ body {
 .sidePanel {
   width: 230px;
   background-color: #eee;
-
   padding: 10px;
   overflow-y: auto;
 
@@ -42,6 +41,17 @@ body {
   .panelColumn {
     display: flex;
     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;
   }
 }
+
+.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;
+}