Просмотр исходного кода

Improved color picker (#174)

* Add react-color

* Prettier

* Better styles

* Use enum for color pickers instead of strings

* Run prettier on .scss file
vanilla_orig
Jared Palmer 5 лет назад
Родитель
Сommit
b5c67260d7
4 измененных файлов: 205 добавлений и 20 удалений
  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
         "@hapi/hoek": "^8.3.0"
1034
         "@hapi/hoek": "^8.3.0"
1035
       }
1035
       }
1036
     },
1036
     },
1037
+    "@icons/material": {
1038
+      "version": "0.2.4",
1039
+      "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
1040
+      "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
1041
+    },
1037
     "@jest/console": {
1042
     "@jest/console": {
1038
       "version": "24.9.0",
1043
       "version": "24.9.0",
1039
       "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
1044
       "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
1511
         "csstype": "^2.2.0"
1516
         "csstype": "^2.2.0"
1512
       }
1517
       }
1513
     },
1518
     },
1519
+    "@types/react-color": {
1520
+      "version": "3.0.1",
1521
+      "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.1.tgz",
1522
+      "integrity": "sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==",
1523
+      "dev": true,
1524
+      "requires": {
1525
+        "@types/react": "*"
1526
+      }
1527
+    },
1514
     "@types/react-dom": {
1528
     "@types/react-dom": {
1515
       "version": "16.9.4",
1529
       "version": "16.9.4",
1516
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz",
1530
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz",
9369
         "object-visit": "^1.0.0"
9383
         "object-visit": "^1.0.0"
9370
       }
9384
       }
9371
     },
9385
     },
9386
+    "material-colors": {
9387
+      "version": "1.2.6",
9388
+      "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
9389
+      "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
9390
+    },
9372
     "md5.js": {
9391
     "md5.js": {
9373
       "version": "1.3.5",
9392
       "version": "1.3.5",
9374
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
9393
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
12094
         "whatwg-fetch": "^3.0.0"
12113
         "whatwg-fetch": "^3.0.0"
12095
       }
12114
       }
12096
     },
12115
     },
12116
+    "react-color": {
12117
+      "version": "2.17.3",
12118
+      "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz",
12119
+      "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==",
12120
+      "requires": {
12121
+        "@icons/material": "^0.2.4",
12122
+        "lodash": "^4.17.11",
12123
+        "material-colors": "^1.2.1",
12124
+        "prop-types": "^15.5.10",
12125
+        "reactcss": "^1.2.0",
12126
+        "tinycolor2": "^1.4.1"
12127
+      }
12128
+    },
12097
     "react-dev-utils": {
12129
     "react-dev-utils": {
12098
       "version": "10.0.0",
12130
       "version": "10.0.0",
12099
       "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.0.0.tgz",
12131
       "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.0.0.tgz",
12315
         "workbox-webpack-plugin": "4.3.1"
12347
         "workbox-webpack-plugin": "4.3.1"
12316
       }
12348
       }
12317
     },
12349
     },
12350
+    "reactcss": {
12351
+      "version": "1.2.3",
12352
+      "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
12353
+      "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
12354
+      "requires": {
12355
+        "lodash": "^4.0.1"
12356
+      }
12357
+    },
12318
     "read-pkg": {
12358
     "read-pkg": {
12319
       "version": "3.0.0",
12359
       "version": "3.0.0",
12320
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
12360
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
14397
       "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
14437
       "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
14398
       "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
14438
       "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
14399
     },
14439
     },
14440
+    "tinycolor2": {
14441
+      "version": "1.4.1",
14442
+      "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
14443
+      "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
14444
+    },
14400
     "tmp": {
14445
     "tmp": {
14401
       "version": "0.0.33",
14446
       "version": "0.0.33",
14402
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
14447
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

+ 2
- 0
package.json Просмотреть файл

7
   "main": "src/index.js",
7
   "main": "src/index.js",
8
   "dependencies": {
8
   "dependencies": {
9
     "react": "16.12.0",
9
     "react": "16.12.0",
10
+    "react-color": "^2.17.3",
10
     "react-dom": "16.12.0",
11
     "react-dom": "16.12.0",
11
     "react-scripts": "3.3.0",
12
     "react-scripts": "3.3.0",
12
     "roughjs": "3.1.0"
13
     "roughjs": "3.1.0"
13
   },
14
   },
14
   "devDependencies": {
15
   "devDependencies": {
15
     "@types/react": "16.9.17",
16
     "@types/react": "16.9.17",
17
+    "@types/react-color": "^3.0.1",
16
     "@types/react-dom": "16.9.4",
18
     "@types/react-dom": "16.9.4",
17
     "husky": "3.1.0",
19
     "husky": "3.1.0",
18
     "lint-staged": "9.5.0",
20
     "lint-staged": "9.5.0",

+ 118
- 19
src/index.tsx Просмотреть файл

2
 import ReactDOM from "react-dom";
2
 import ReactDOM from "react-dom";
3
 import rough from "roughjs/bin/wrappers/rough";
3
 import rough from "roughjs/bin/wrappers/rough";
4
 import { RoughCanvas } from "roughjs/bin/canvas";
4
 import { RoughCanvas } from "roughjs/bin/canvas";
5
+import { SketchPicker } from "react-color";
5
 
6
 
6
 import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
7
 import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
7
 
8
 
816
   }
817
   }
817
 }
818
 }
818
 
819
 
820
+enum ColorPicker {
821
+  CANVAS_BACKGROUND,
822
+  SHAPE_STROKE,
823
+  SHAPE_BACKGROUND
824
+}
825
+
819
 type AppState = {
826
 type AppState = {
820
   draggingElement: ExcalidrawElement | null;
827
   draggingElement: ExcalidrawElement | null;
821
   resizingElement: ExcalidrawElement | null;
828
   resizingElement: ExcalidrawElement | null;
829
+  currentColorPicker: ColorPicker | null;
822
   elementType: string;
830
   elementType: string;
823
   exportBackground: boolean;
831
   exportBackground: boolean;
824
   currentItemStrokeColor: string;
832
   currentItemStrokeColor: string;
889
 
897
 
890
 const shapesShortcutKeys = SHAPES.map(shape => shape.value[0]);
898
 const shapesShortcutKeys = SHAPES.map(shape => shape.value[0]);
891
 
899
 
892
-
893
 function capitalize(str: string) {
900
 function capitalize(str: string) {
894
   return str.charAt(0).toUpperCase() + str.slice(1);
901
   return str.charAt(0).toUpperCase() + str.slice(1);
895
 }
902
 }
953
     draggingElement: null,
960
     draggingElement: null,
954
     resizingElement: null,
961
     resizingElement: null,
955
     elementType: "selection",
962
     elementType: "selection",
963
+    currentColorPicker: null,
956
     exportBackground: true,
964
     exportBackground: true,
957
     currentItemStrokeColor: "#000000",
965
     currentItemStrokeColor: "#000000",
958
     currentItemBackgroundColor: "#ffffff",
966
     currentItemBackgroundColor: "#ffffff",
1134
           <h4>Shapes</h4>
1142
           <h4>Shapes</h4>
1135
           <div className="panelTools">
1143
           <div className="panelTools">
1136
             {SHAPES.map(({ value, icon }) => (
1144
             {SHAPES.map(({ value, icon }) => (
1137
-              <label key={value} className="tool" title={`${capitalize(value)} - ${capitalize(value)[0]}`}>
1145
+              <label
1146
+                key={value}
1147
+                className="tool"
1148
+                title={`${capitalize(value)} - ${capitalize(value)[0]}`}
1149
+              >
1138
                 <input
1150
                 <input
1139
                   type="radio"
1151
                   type="radio"
1140
                   checked={this.state.elementType === value}
1152
                   checked={this.state.elementType === value}
1152
           </div>
1164
           </div>
1153
           <h4>Colors</h4>
1165
           <h4>Colors</h4>
1154
           <div className="panelColumn">
1166
           <div className="panelColumn">
1155
-            <label>
1167
+            <h5>Canvas Background</h5>
1168
+            <div>
1169
+              <button
1170
+                className="swatch"
1171
+                style={{
1172
+                  backgroundColor: this.state.viewBackgroundColor
1173
+                }}
1174
+                onClick={() =>
1175
+                  this.setState(s => ({
1176
+                    currentColorPicker:
1177
+                      s.currentColorPicker === ColorPicker.CANVAS_BACKGROUND
1178
+                        ? null
1179
+                        : ColorPicker.CANVAS_BACKGROUND
1180
+                  }))
1181
+                }
1182
+              ></button>
1183
+              {this.state.currentColorPicker === ColorPicker.CANVAS_BACKGROUND ? (
1184
+                <div className="popover">
1185
+                  <div
1186
+                    className="cover"
1187
+                    onClick={() => this.setState({ currentColorPicker: null })}
1188
+                  ></div>
1189
+                  <SketchPicker
1190
+                    color={this.state.viewBackgroundColor}
1191
+                    onChange={color => {
1192
+                      this.setState({ viewBackgroundColor: color.hex });
1193
+                    }}
1194
+                  />
1195
+                </div>
1196
+              ) : null}
1156
               <input
1197
               <input
1157
-                type="color"
1198
+                type="text"
1199
+                className="swatch-input"
1158
                 value={this.state.viewBackgroundColor}
1200
                 value={this.state.viewBackgroundColor}
1159
-                onChange={e => {
1160
-                  this.setState({ viewBackgroundColor: e.target.value });
1161
-                }}
1201
+                onChange={e =>
1202
+                  this.setState({ viewBackgroundColor: e.target.value })
1203
+                }
1162
               />
1204
               />
1163
-              Background
1164
-            </label>
1165
-            <label>
1205
+            </div>
1206
+            <h5>Shape Stroke</h5>
1207
+            <div>
1208
+              <button
1209
+                className="swatch"
1210
+                style={{
1211
+                  backgroundColor: this.state.currentItemStrokeColor
1212
+                }}
1213
+                onClick={() =>
1214
+                  this.setState(s => ({
1215
+                    currentColorPicker:
1216
+                      s.currentColorPicker === ColorPicker.SHAPE_STROKE
1217
+                        ? null
1218
+                        : ColorPicker.SHAPE_STROKE
1219
+                  }))
1220
+                }
1221
+              ></button>
1222
+              {this.state.currentColorPicker === ColorPicker.SHAPE_STROKE ? (
1223
+                <div className="popover">
1224
+                  <div
1225
+                    className="cover"
1226
+                    onClick={() => this.setState({ currentColorPicker: null })}
1227
+                  ></div>
1228
+                  <SketchPicker
1229
+                    color={this.state.currentItemStrokeColor}
1230
+                    onChange={color => {
1231
+                      this.setState({ currentItemStrokeColor: color.hex });
1232
+                    }}
1233
+                  />
1234
+                </div>
1235
+              ) : null}
1166
               <input
1236
               <input
1167
-                type="color"
1237
+                type="text"
1238
+                className="swatch-input"
1168
                 value={this.state.currentItemStrokeColor}
1239
                 value={this.state.currentItemStrokeColor}
1169
                 onChange={e => {
1240
                 onChange={e => {
1170
                   this.setState({ currentItemStrokeColor: e.target.value });
1241
                   this.setState({ currentItemStrokeColor: e.target.value });
1171
                 }}
1242
                 }}
1172
               />
1243
               />
1173
-              Shape Stroke
1174
-            </label>
1175
-            <label>
1244
+            </div>
1245
+            <h5>Shape Background</h5>
1246
+            <div>
1247
+              <button
1248
+                className="swatch"
1249
+                style={{
1250
+                  backgroundColor: this.state.currentItemBackgroundColor
1251
+                }}
1252
+                onClick={() =>
1253
+                  this.setState(s => ({
1254
+                    currentColorPicker:
1255
+                      s.currentColorPicker === ColorPicker.SHAPE_BACKGROUND
1256
+                        ? null
1257
+                        : ColorPicker.SHAPE_BACKGROUND
1258
+                  }))
1259
+                }
1260
+              ></button>
1261
+              {this.state.currentColorPicker === ColorPicker.SHAPE_BACKGROUND ? (
1262
+                <div className="popover">
1263
+                  <div
1264
+                    className="cover"
1265
+                    onClick={() => this.setState({ currentColorPicker: null })}
1266
+                  ></div>
1267
+                  <SketchPicker
1268
+                    color={this.state.currentItemBackgroundColor}
1269
+                    onChange={color => {
1270
+                      this.setState({ currentItemBackgroundColor: color.hex });
1271
+                    }}
1272
+                  />
1273
+                </div>
1274
+              ) : null}
1176
               <input
1275
               <input
1177
-                type="color"
1178
-                value={this.state.currentItemBackgroundColor}
1276
+                type="text"
1277
+                className="swatch-input"
1278
+                value={this.state.currentItemStrokeColor}
1179
                 onChange={e => {
1279
                 onChange={e => {
1180
-                  this.setState({ currentItemBackgroundColor: e.target.value });
1280
+                  this.setState({ currentItemStrokeColor: e.target.value });
1181
                 }}
1281
                 }}
1182
               />
1282
               />
1183
-              Shape Background
1184
-            </label>
1283
+            </div>
1185
           </div>
1284
           </div>
1186
           <h4>Canvas</h4>
1285
           <h4>Canvas</h4>
1187
           <div className="panelColumn">
1286
           <div className="panelColumn">

+ 40
- 1
src/styles.scss Просмотреть файл

22
 .sidePanel {
22
 .sidePanel {
23
   width: 230px;
23
   width: 230px;
24
   background-color: #eee;
24
   background-color: #eee;
25
-
26
   padding: 10px;
25
   padding: 10px;
27
   overflow-y: auto;
26
   overflow-y: auto;
28
 
27
 
42
   .panelColumn {
41
   .panelColumn {
43
     display: flex;
42
     display: flex;
44
     flex-direction: column;
43
     flex-direction: column;
44
+
45
+    h5 {
46
+      margin-top: 4px;
47
+      margin-bottom: 4px;
48
+      font-size: 12px;
49
+      color: #333;
50
+    }
51
+
52
+    h5:first-of-type {
53
+      margin-top: 0;
54
+    }
45
   }
55
   }
46
 }
56
 }
47
 
57
 
134
     cursor: not-allowed;
144
     cursor: not-allowed;
135
   }
145
   }
136
 }
146
 }
147
+
148
+.popover {
149
+  position: absolute;
150
+  z-index: 2;
151
+
152
+  .cover {
153
+    position: fixed;
154
+    top: 0;
155
+    left: 0;
156
+    right: 0;
157
+    bottom: 0;
158
+  }
159
+}
160
+
161
+.swatch {
162
+  height: 24px;
163
+  width: 24px;
164
+  display: inline;
165
+  margin-right: 4px;
166
+}
167
+
168
+.swatch-input {
169
+  font-size: 16px;
170
+  display: inline;
171
+  width: 100px;
172
+  border-radius: 2px;
173
+  padding: 2px 4px;
174
+  border: 1px solid #ddd;
175
+}

Загрузка…
Отмена
Сохранить