import React, { useEffect } from "react"; import * as Popover from "@radix-ui/react-popover"; import { isArrowKey, KEYS } from "../keys"; import { getLanguage, t } from "../i18n"; import clsx from "clsx"; import Collapsible from "./Stats/Collapsible"; import { atom, useAtom } from "../editor-jotai"; import { useDevice } from "./App"; import "./IconPicker.scss"; const moreOptionsAtom = atom(false); type Option = { value: T; text: string; icon: JSX.Element; keyBinding: string | null; }; function Picker({ options, value, label, onChange, onClose, numberOfOptionsToAlwaysShow = options.length, }: { label: string; value: T; options: readonly Option[]; onChange: (value: T) => void; onClose: () => void; numberOfOptionsToAlwaysShow?: number; }) { const device = useDevice(); const handleKeyDown = (event: React.KeyboardEvent) => { const pressedOption = options.find( (option) => option.keyBinding === event.key.toLowerCase(), )!; if (!(event.metaKey || event.altKey || event.ctrlKey) && pressedOption) { // Keybinding navigation onChange(pressedOption.value); event.preventDefault(); } else if (event.key === KEYS.TAB) { const index = options.findIndex((option) => option.value === value); const nextIndex = event.shiftKey ? (options.length + index - 1) % options.length : (index + 1) % options.length; onChange(options[nextIndex].value); } else if (isArrowKey(event.key)) { // Arrow navigation const isRTL = getLanguage().rtl; const index = options.findIndex((option) => option.value === value); if (index !== -1) { const length = options.length; let nextIndex = index; switch (event.key) { // Select the next option case isRTL ? KEYS.ARROW_LEFT : KEYS.ARROW_RIGHT: nextIndex = (index + 1) % length; break; // Select the previous option case isRTL ? KEYS.ARROW_RIGHT : KEYS.ARROW_LEFT: nextIndex = (length + index - 1) % length; break; // Go the next row case KEYS.ARROW_DOWN: { nextIndex = (index + (numberOfOptionsToAlwaysShow ?? 1)) % length; break; } // Go the previous row case KEYS.ARROW_UP: { nextIndex = (length + index - (numberOfOptionsToAlwaysShow ?? 1)) % length; break; } } onChange(options[nextIndex].value); } event.preventDefault(); } else if (event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) { // Close on escape or enter event.preventDefault(); onClose(); } event.nativeEvent.stopImmediatePropagation(); event.stopPropagation(); }; const [showMoreOptions, setShowMoreOptions] = useAtom(moreOptionsAtom); const alwaysVisibleOptions = React.useMemo( () => options.slice(0, numberOfOptionsToAlwaysShow), [options, numberOfOptionsToAlwaysShow], ); const moreOptions = React.useMemo( () => options.slice(numberOfOptionsToAlwaysShow), [options, numberOfOptionsToAlwaysShow], ); useEffect(() => { if (!alwaysVisibleOptions.some((option) => option.value === value)) { setShowMoreOptions(true); } }, [value, alwaysVisibleOptions, setShowMoreOptions]); const renderOptions = (options: Option[]) => { return (
{options.map((option, i) => ( ))}
); }; return (
{renderOptions(alwaysVisibleOptions)} {moreOptions.length > 0 && ( { setShowMoreOptions((value) => !value); }} className="picker-collapsible" > {renderOptions(moreOptions)} )}
); } export function IconPicker({ value, label, options, onChange, group = "", numberOfOptionsToAlwaysShow, }: { label: string; value: T; options: readonly { value: T; text: string; icon: JSX.Element; keyBinding: string | null; }[]; onChange: (value: T) => void; numberOfOptionsToAlwaysShow?: number; group?: string; }) { const [isActive, setActive] = React.useState(false); const rPickerButton = React.useRef(null); return (
setActive(open)}> setActive(!isActive)} ref={rPickerButton} className={isActive ? "active" : ""} > {options.find((option) => option.value === value)?.icon} {isActive && ( { setActive(false); }} numberOfOptionsToAlwaysShow={numberOfOptionsToAlwaysShow} /> )}
); }