diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/components/ColorPicker/Picker.tsx | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/excalidraw/components/ColorPicker/Picker.tsx')
| -rw-r--r-- | packages/excalidraw/components/ColorPicker/Picker.tsx | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/packages/excalidraw/components/ColorPicker/Picker.tsx b/packages/excalidraw/components/ColorPicker/Picker.tsx new file mode 100644 index 0000000..88d6876 --- /dev/null +++ b/packages/excalidraw/components/ColorPicker/Picker.tsx @@ -0,0 +1,178 @@ +import React, { useEffect, useState } from "react"; +import { t } from "../../i18n"; + +import type { ExcalidrawElement } from "../../element/types"; +import { ShadeList } from "./ShadeList"; + +import PickerColorList from "./PickerColorList"; +import { useAtom } from "../../editor-jotai"; +import { CustomColorList } from "./CustomColorList"; +import { colorPickerKeyNavHandler } from "./keyboardNavHandlers"; +import PickerHeading from "./PickerHeading"; +import type { ColorPickerType } from "./colorPickerUtils"; +import { + activeColorPickerSectionAtom, + getColorNameAndShadeFromColor, + getMostUsedCustomColors, + isCustomColor, +} from "./colorPickerUtils"; +import type { ColorPaletteCustom } from "../../colors"; +import { + DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX, + DEFAULT_ELEMENT_STROKE_COLOR_INDEX, +} from "../../colors"; +import { KEYS } from "../../keys"; +import { EVENT } from "../../constants"; + +interface PickerProps { + color: string; + onChange: (color: string) => void; + label: string; + type: ColorPickerType; + elements: readonly ExcalidrawElement[]; + palette: ColorPaletteCustom; + updateData: (formData?: any) => void; + children?: React.ReactNode; + onEyeDropperToggle: (force?: boolean) => void; + onEscape: (event: React.KeyboardEvent | KeyboardEvent) => void; +} + +export const Picker = ({ + color, + onChange, + label, + type, + elements, + palette, + updateData, + children, + onEyeDropperToggle, + onEscape, +}: PickerProps) => { + const [customColors] = React.useState(() => { + if (type === "canvasBackground") { + return []; + } + return getMostUsedCustomColors(elements, type, palette); + }); + + const [activeColorPickerSection, setActiveColorPickerSection] = useAtom( + activeColorPickerSectionAtom, + ); + + const colorObj = getColorNameAndShadeFromColor({ + color, + palette, + }); + + useEffect(() => { + if (!activeColorPickerSection) { + const isCustom = isCustomColor({ color, palette }); + const isCustomButNotInList = isCustom && !customColors.includes(color); + + setActiveColorPickerSection( + isCustomButNotInList + ? "hex" + : isCustom + ? "custom" + : colorObj?.shade != null + ? "shades" + : "baseColors", + ); + } + }, [ + activeColorPickerSection, + color, + palette, + setActiveColorPickerSection, + colorObj, + customColors, + ]); + + const [activeShade, setActiveShade] = useState( + colorObj?.shade ?? + (type === "elementBackground" + ? DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX + : DEFAULT_ELEMENT_STROKE_COLOR_INDEX), + ); + + useEffect(() => { + if (colorObj?.shade != null) { + setActiveShade(colorObj.shade); + } + + const keyup = (event: KeyboardEvent) => { + if (event.key === KEYS.ALT) { + onEyeDropperToggle(false); + } + }; + document.addEventListener(EVENT.KEYUP, keyup, { capture: true }); + return () => { + document.removeEventListener(EVENT.KEYUP, keyup, { capture: true }); + }; + }, [colorObj, onEyeDropperToggle]); + + const pickerRef = React.useRef<HTMLDivElement>(null); + + return ( + <div role="dialog" aria-modal="true" aria-label={t("labels.colorPicker")}> + <div + ref={pickerRef} + onKeyDown={(event) => { + const handled = colorPickerKeyNavHandler({ + event, + activeColorPickerSection, + palette, + color, + onChange, + onEyeDropperToggle, + customColors, + setActiveColorPickerSection, + updateData, + activeShade, + onEscape, + }); + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }} + className="color-picker-content properties-content" + // to allow focusing by clicking but not by tabbing + tabIndex={-1} + > + {!!customColors.length && ( + <div> + <PickerHeading> + {t("colorPicker.mostUsedCustomColors")} + </PickerHeading> + <CustomColorList + colors={customColors} + color={color} + label={t("colorPicker.mostUsedCustomColors")} + onChange={onChange} + /> + </div> + )} + + <div> + <PickerHeading>{t("colorPicker.colors")}</PickerHeading> + <PickerColorList + color={color} + label={label} + palette={palette} + onChange={onChange} + activeShade={activeShade} + /> + </div> + + <div> + <PickerHeading>{t("colorPicker.shades")}</PickerHeading> + <ShadeList hex={color} onChange={onChange} palette={palette} /> + </div> + {children} + </div> + </div> + ); +}; |
