import React from "react"; import { Popover } from "./Popover"; import "./ColorPicker.scss"; import { isArrowKey, KEYS } from "../keys"; import { t, getLanguage } from "../i18n"; import { isWritableElement } from "../utils"; import colors from "../colors"; const isValidColor = (color: string) => { const style = new Option().style; style.color = color; return !!style.color; }; const getColor = (color: string): string | null => { if (color === "transparent") { return color; } return isValidColor(color) ? color : isValidColor(`#${color}`) ? `#${color}` : null; }; // This is a narrow reimplementation of the awesome react-color Twitter component // https://github.com/casesandberg/react-color/blob/master/src/components/twitter/Twitter.js // Unfortunately, we can't detect keyboard layout in the browser. So this will // only work well for QWERTY but not AZERTY or others... const keyBindings = [ ["1", "2", "3", "4", "5"], ["q", "w", "e", "r", "t"], ["a", "s", "d", "f", "g"], ].flat(); const Picker = ({ colors, color, onChange, onClose, label, showInput = true, type, }: { colors: string[]; color: string | null; onChange: (color: string) => void; onClose: () => void; label: string; showInput: boolean; type: "canvasBackground" | "elementBackground" | "elementStroke"; }) => { const firstItem = React.useRef(); const activeItem = React.useRef(); const gallery = React.useRef(); const colorInput = React.useRef(); React.useEffect(() => { // After the component is first mounted focus on first input if (activeItem.current) { activeItem.current.focus(); } else if (colorInput.current) { colorInput.current.focus(); } else if (gallery.current) { gallery.current.focus(); } }, []); const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === KEYS.TAB) { const { activeElement } = document; if (event.shiftKey) { if (activeElement === firstItem.current) { colorInput.current?.focus(); event.preventDefault(); } } else if (activeElement === colorInput.current) { firstItem.current?.focus(); event.preventDefault(); } } else if (isArrowKey(event.key)) { const { activeElement } = document; const isRTL = getLanguage().rtl; const index = Array.prototype.indexOf.call( gallery!.current!.children, activeElement, ); if (index !== -1) { const length = gallery!.current!.children.length - (showInput ? 1 : 0); const nextIndex = event.key === (isRTL ? KEYS.ARROW_LEFT : KEYS.ARROW_RIGHT) ? (index + 1) % length : event.key === (isRTL ? KEYS.ARROW_RIGHT : KEYS.ARROW_LEFT) ? (length + index - 1) % length : event.key === KEYS.ARROW_DOWN ? (index + 5) % length : event.key === KEYS.ARROW_UP ? (length + index - 5) % length : index; (gallery!.current!.children![nextIndex] as any).focus(); } event.preventDefault(); } else if ( keyBindings.includes(event.key.toLowerCase()) && !isWritableElement(event.target) ) { const index = keyBindings.indexOf(event.key.toLowerCase()); (gallery!.current!.children![index] as any).focus(); event.preventDefault(); } else if (event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) { event.preventDefault(); onClose(); } event.nativeEvent.stopImmediatePropagation(); }; return (
{ if (el) { gallery.current = el; } }} tabIndex={0} > {colors.map((_color, i) => ( ))} {showInput && ( { onChange(color); }} ref={colorInput} /> )}
); }; const ColorInput = React.forwardRef( ( { color, onChange, label, }: { color: string | null; onChange: (color: string) => void; label: string; }, ref, ) => { const [innerValue, setInnerValue] = React.useState(color); const inputRef = React.useRef(null); React.useEffect(() => { setInnerValue(color); }, [color]); React.useImperativeHandle(ref, () => inputRef.current); const changeColor = React.useCallback( (inputValue: string) => { const value = inputValue.toLowerCase(); const color = getColor(value); if (color) { onChange(color); } setInnerValue(value); }, [onChange], ); return ( ); }, ); export const ColorPicker = ({ type, color, onChange, label, }: { type: "canvasBackground" | "elementBackground" | "elementStroke"; color: string | null; onChange: (color: string) => void; label: string; }) => { const [isActive, setActive] = React.useState(false); const pickerButton = React.useRef(null); return (
{isActive ? ( event.target !== pickerButton.current && setActive(false) } > { onChange(changedColor); }} onClose={() => { setActive(false); pickerButton.current?.focus(); }} label={label} showInput={false} type={type} /> ) : null}
); };