返回 presentation-ai
color-picker.tsx
根目录 / src / components / ui / color-picker.tsx
1 import { Button } from "@/components/ui/button";
2 import {
3 DropdownMenu,
4 DropdownMenuContent,
5 DropdownMenuTrigger,
6 } from "@/components/ui/dropdown-menu";
7 import {
8 Tooltip,
9 TooltipContent,
10 TooltipProvider,
11 TooltipTrigger,
12 } from "@/components/ui/tooltip";
13 import { cn } from "@/lib/utils";
14 import debounce from "lodash.debounce";
15 import { Plus } from "lucide-react";
16 import React from "react";
17 import { HexColorPicker } from "react-colorful";
18 import { Popover, PopoverContent, PopoverTrigger } from "./popover";
19
20 export const DEFAULT_COLORS = [
21 {
22 isBrightColor: false,
23 name: "black",
24 value: "#000000",
25 },
26 {
27 isBrightColor: false,
28 name: "dark grey 4",
29 value: "#434343",
30 },
31 {
32 isBrightColor: false,
33 name: "dark grey 3",
34 value: "#666666",
35 },
36 {
37 isBrightColor: false,
38 name: "dark grey 2",
39 value: "#999999",
40 },
41 {
42 isBrightColor: false,
43 name: "dark grey 1",
44 value: "#B7B7B7",
45 },
46 {
47 isBrightColor: false,
48 name: "grey",
49 value: "#CCCCCC",
50 },
51 {
52 isBrightColor: false,
53 name: "light grey 1",
54 value: "#D9D9D9",
55 },
56 {
57 isBrightColor: true,
58 name: "light grey 2",
59 value: "#EFEFEF",
60 },
61 {
62 isBrightColor: true,
63 name: "light grey 3",
64 value: "#F3F3F3",
65 },
66 {
67 isBrightColor: true,
68 name: "white",
69 value: "#FFFFFF",
70 },
71 {
72 isBrightColor: false,
73 name: "red berry",
74 value: "#980100",
75 },
76 {
77 isBrightColor: false,
78 name: "red",
79 value: "#FE0000",
80 },
81 {
82 isBrightColor: false,
83 name: "orange",
84 value: "#FE9900",
85 },
86 {
87 isBrightColor: true,
88 name: "yellow",
89 value: "#FEFF00",
90 },
91 {
92 isBrightColor: false,
93 name: "green",
94 value: "#00FF00",
95 },
96 {
97 isBrightColor: false,
98 name: "cyan",
99 value: "#00FFFF",
100 },
101 {
102 isBrightColor: false,
103 name: "cornflower blue",
104 value: "#4B85E8",
105 },
106 {
107 isBrightColor: false,
108 name: "blue",
109 value: "#1300FF",
110 },
111 {
112 isBrightColor: false,
113 name: "purple",
114 value: "#9900FF",
115 },
116 {
117 isBrightColor: false,
118 name: "magenta",
119 value: "#FF00FF",
120 },
121
122 {
123 isBrightColor: false,
124 name: "light red berry 3",
125 value: "#E6B8AF",
126 },
127 {
128 isBrightColor: false,
129 name: "light red 3",
130 value: "#F4CCCC",
131 },
132 {
133 isBrightColor: true,
134 name: "light orange 3",
135 value: "#FCE4CD",
136 },
137 {
138 isBrightColor: true,
139 name: "light yellow 3",
140 value: "#FFF2CC",
141 },
142 {
143 isBrightColor: true,
144 name: "light green 3",
145 value: "#D9EAD3",
146 },
147 {
148 isBrightColor: false,
149 name: "light cyan 3",
150 value: "#D0DFE3",
151 },
152 {
153 isBrightColor: false,
154 name: "light cornflower blue 3",
155 value: "#C9DAF8",
156 },
157 {
158 isBrightColor: true,
159 name: "light blue 3",
160 value: "#CFE1F3",
161 },
162 {
163 isBrightColor: true,
164 name: "light purple 3",
165 value: "#D9D2E9",
166 },
167 {
168 isBrightColor: true,
169 name: "light magenta 3",
170 value: "#EAD1DB",
171 },
172
173 {
174 isBrightColor: false,
175 name: "light red berry 2",
176 value: "#DC7E6B",
177 },
178 {
179 isBrightColor: false,
180 name: "light red 2",
181 value: "#EA9999",
182 },
183 {
184 isBrightColor: false,
185 name: "light orange 2",
186 value: "#F9CB9C",
187 },
188 {
189 isBrightColor: true,
190 name: "light yellow 2",
191 value: "#FFE598",
192 },
193 {
194 isBrightColor: false,
195 name: "light green 2",
196 value: "#B7D6A8",
197 },
198 {
199 isBrightColor: false,
200 name: "light cyan 2",
201 value: "#A1C4C9",
202 },
203 {
204 isBrightColor: false,
205 name: "light cornflower blue 2",
206 value: "#A4C2F4",
207 },
208 {
209 isBrightColor: false,
210 name: "light blue 2",
211 value: "#9FC5E8",
212 },
213 {
214 isBrightColor: false,
215 name: "light purple 2",
216 value: "#B5A7D5",
217 },
218 {
219 isBrightColor: false,
220 name: "light magenta 2",
221 value: "#D5A6BD",
222 },
223
224 {
225 isBrightColor: false,
226 name: "light red berry 1",
227 value: "#CC4125",
228 },
229 {
230 isBrightColor: false,
231 name: "light red 1",
232 value: "#E06666",
233 },
234 {
235 isBrightColor: false,
236 name: "light orange 1",
237 value: "#F6B26B",
238 },
239 {
240 isBrightColor: false,
241 name: "light yellow 1",
242 value: "#FFD966",
243 },
244 {
245 isBrightColor: false,
246 name: "light green 1",
247 value: "#93C47D",
248 },
249 {
250 isBrightColor: false,
251 name: "light cyan 1",
252 value: "#76A5AE",
253 },
254 {
255 isBrightColor: false,
256 name: "light cornflower blue 1",
257 value: "#6C9EEB",
258 },
259 {
260 isBrightColor: false,
261 name: "light blue 1",
262 value: "#6FA8DC",
263 },
264 {
265 isBrightColor: false,
266 name: "light purple 1",
267 value: "#8D7CC3",
268 },
269 {
270 isBrightColor: false,
271 name: "light magenta 1",
272 value: "#C27BA0",
273 },
274
275 {
276 isBrightColor: false,
277 name: "dark red berry 1",
278 value: "#A61B00",
279 },
280 {
281 isBrightColor: false,
282 name: "dark red 1",
283 value: "#CC0000",
284 },
285 {
286 isBrightColor: false,
287 name: "dark orange 1",
288 value: "#E59138",
289 },
290 {
291 isBrightColor: false,
292 name: "dark yellow 1",
293 value: "#F1C231",
294 },
295 {
296 isBrightColor: false,
297 name: "dark green 1",
298 value: "#6AA74F",
299 },
300 {
301 isBrightColor: false,
302 name: "dark cyan 1",
303 value: "#45818E",
304 },
305 {
306 isBrightColor: false,
307 name: "dark cornflower blue 1",
308 value: "#3B78D8",
309 },
310 {
311 isBrightColor: false,
312 name: "dark blue 1",
313 value: "#3E84C6",
314 },
315 {
316 isBrightColor: false,
317 name: "dark purple 1",
318 value: "#664EA6",
319 },
320 {
321 isBrightColor: false,
322 name: "dark magenta 1",
323 value: "#A64D78",
324 },
325
326 {
327 isBrightColor: false,
328 name: "dark red berry 2",
329 value: "#84200D",
330 },
331 {
332 isBrightColor: false,
333 name: "dark red 2",
334 value: "#990001",
335 },
336 {
337 isBrightColor: false,
338 name: "dark orange 2",
339 value: "#B45F05",
340 },
341 {
342 isBrightColor: false,
343 name: "dark yellow 2",
344 value: "#BF9002",
345 },
346 {
347 isBrightColor: false,
348 name: "dark green 2",
349 value: "#38761D",
350 },
351 {
352 isBrightColor: false,
353 name: "dark cyan 2",
354 value: "#124F5C",
355 },
356 {
357 isBrightColor: false,
358 name: "dark cornflower blue 2",
359 value: "#1155CB",
360 },
361 {
362 isBrightColor: false,
363 name: "dark blue 2",
364 value: "#0C5394",
365 },
366 {
367 isBrightColor: false,
368 name: "dark purple 2",
369 value: "#351C75",
370 },
371 {
372 isBrightColor: false,
373 name: "dark magenta 2",
374 value: "#741B47",
375 },
376
377 {
378 isBrightColor: false,
379 name: "dark red berry 3",
380 value: "#5B0F00",
381 },
382 {
383 isBrightColor: false,
384 name: "dark red 3",
385 value: "#660000",
386 },
387 {
388 isBrightColor: false,
389 name: "dark orange 3",
390 value: "#783F04",
391 },
392 {
393 isBrightColor: false,
394 name: "dark yellow 3",
395 value: "#7E6000",
396 },
397 {
398 isBrightColor: false,
399 name: "dark green 3",
400 value: "#274E12",
401 },
402 {
403 isBrightColor: false,
404 name: "dark cyan 3",
405 value: "#0D343D",
406 },
407 {
408 isBrightColor: false,
409 name: "dark cornflower blue 3",
410 value: "#1B4487",
411 },
412 {
413 isBrightColor: false,
414 name: "dark blue 3",
415 value: "#083763",
416 },
417 {
418 isBrightColor: false,
419 name: "dark purple 3",
420 value: "#1F124D",
421 },
422 {
423 isBrightColor: false,
424 name: "dark magenta 3",
425 value: "#4C1130",
426 },
427 ];
428
429 export const DEFAULT_CUSTOM_COLORS = [
430 {
431 isBrightColor: false,
432 name: "dark orange 3",
433 value: "#783F04",
434 },
435 {
436 isBrightColor: false,
437 name: "dark grey 3",
438 value: "#666666",
439 },
440 {
441 isBrightColor: false,
442 name: "dark grey 2",
443 value: "#999999",
444 },
445 {
446 isBrightColor: false,
447 name: "light cornflower blue 1",
448 value: "#6C9EEB",
449 },
450 {
451 isBrightColor: false,
452 name: "dark magenta 3",
453 value: "#4C1130",
454 },
455 ];
456
457 function ColorPicker({
458 value,
459 onChange,
460 disabled,
461 children,
462 }: {
463 value: string;
464 onChange: (value: string) => void;
465 disabled?: boolean;
466 children?: React.ReactNode;
467 }) {
468 const [_, setCustomColor] = React.useState(value);
469 const [localColor, setLocalColor] = React.useState(value);
470
471 // Create a debounced version of onChange
472 const debouncedOnChange = React.useMemo(
473 () =>
474 debounce((color: string) => {
475 onChange(color);
476 }, 100), // 100ms delay
477 [onChange],
478 );
479
480 React.useEffect(() => {
481 setCustomColor(value);
482 setLocalColor(value);
483 }, [value]);
484
485 // Cleanup debounce on unmount
486 React.useEffect(() => {
487 return () => {
488 debouncedOnChange.cancel();
489 };
490 }, [debouncedOnChange]);
491
492 const handleColorChange = (color: string) => {
493 setLocalColor(color); // Update local state immediately for UI
494 setCustomColor(color);
495 debouncedOnChange(color); // Debounce the actual onChange call
496 };
497
498 return (
499 <div id="color-picker">
500 <DropdownMenu modal={false}>
501 <DropdownMenuTrigger asChild>
502 {children ? (
503 children
504 ) : (
505 <Button
506 variant="outline"
507 className={cn("flex h-10 w-10 items-center justify-center p-0")}
508 style={{ backgroundColor: localColor }}
509 disabled={disabled}
510 >
511 <span className="sr-only">Pick a color</span>
512 </Button>
513 )}
514 </DropdownMenuTrigger>
515 <DropdownMenuContent
516 align="start"
517 onWheel={(e) => e.stopPropagation()}
518 onPointerDown={(e) => e.stopPropagation()}
519 onMouseDown={(e) => e.stopPropagation()}
520 data-design-floating-toolbar="true"
521 className="ignore-click-outside/toolbar max-h-96 overflow-y-auto p-3"
522 >
523 <div className="grid grid-cols-5 gap-2">
524 <TooltipProvider>
525 {/* Custom color picker - moved to first position */}
526 <Tooltip>
527 <TooltipTrigger asChild>
528 <Popover>
529 <PopoverTrigger asChild>
530 <button
531 type="button"
532 className={cn(
533 "h-8 w-8 rounded-full border-2 border-dashed border-gray-300 bg-white",
534 "flex items-center justify-center transition-transform hover:scale-110",
535 )}
536 onPointerDown={(e) => e.stopPropagation()}
537 onMouseDown={(e) => e.stopPropagation()}
538 >
539 <Plus className="h-4 w-4 text-gray-500" />
540 </button>
541 </PopoverTrigger>
542 <PopoverContent
543 align="start"
544 data-design-floating-toolbar="true"
545 onPointerDown={(e) => e.stopPropagation()}
546 onMouseDown={(e) => e.stopPropagation()}
547 className="w-auto p-4"
548 >
549 <HexColorPicker
550 color={localColor}
551 onChange={handleColorChange}
552 />
553 </PopoverContent>
554 </Popover>
555 </TooltipTrigger>
556 <TooltipContent>
557 <div className="font-medium">Custom color</div>
558 </TooltipContent>
559 </Tooltip>
560
561 {DEFAULT_COLORS.map((color) => (
562 <Tooltip key={color.value}>
563 <TooltipTrigger asChild>
564 <button
565 type="button"
566 className={cn(
567 "h-8 w-8 rounded-full transition-transform hover:scale-110 focus:ring-2 focus:ring-offset-2",
568 localColor === color.value && "ring-2 ring-offset-2",
569 )}
570 style={{ backgroundColor: color.value }}
571 onPointerDown={(e) => e.stopPropagation()}
572 onMouseDown={(e) => e.stopPropagation()}
573 onClick={() => handleColorChange(color.value)}
574 >
575 <span className="sr-only">{color.name}</span>
576 </button>
577 </TooltipTrigger>
578 <TooltipContent>
579 <div className="font-medium">{color.name}</div>
580 <div className="text-muted-foreground">{color.value}</div>
581 </TooltipContent>
582 </Tooltip>
583 ))}
584 </TooltipProvider>
585 </div>
586 </DropdownMenuContent>
587 </DropdownMenu>
588 </div>
589 );
590 }
591
592 export default ColorPicker;
593
593 lines Plain Text