From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001 From: kj_sh604 Date: Sun, 15 Mar 2026 16:19:35 -0400 Subject: refactor: packages/ --- packages/excalidraw/components/ToolButton.tsx | 206 ++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 packages/excalidraw/components/ToolButton.tsx (limited to 'packages/excalidraw/components/ToolButton.tsx') diff --git a/packages/excalidraw/components/ToolButton.tsx b/packages/excalidraw/components/ToolButton.tsx new file mode 100644 index 0000000..09b0b6c --- /dev/null +++ b/packages/excalidraw/components/ToolButton.tsx @@ -0,0 +1,206 @@ +import "./ToolIcon.scss"; + +import type { CSSProperties } from "react"; +import React, { useEffect, useRef, useState } from "react"; +import clsx from "clsx"; +import { useExcalidrawContainer } from "./App"; +import { AbortError } from "../errors"; +import Spinner from "./Spinner"; +import type { PointerType } from "../element/types"; +import { isPromiseLike } from "../utils"; + +export type ToolButtonSize = "small" | "medium"; + +type ToolButtonBaseProps = { + icon?: React.ReactNode; + "aria-label": string; + "aria-keyshortcuts"?: string; + "data-testid"?: string; + label?: string; + title?: string; + name?: string; + id?: string; + size?: ToolButtonSize; + keyBindingLabel?: string | null; + showAriaLabel?: boolean; + hidden?: boolean; + visible?: boolean; + selected?: boolean; + disabled?: boolean; + className?: string; + style?: CSSProperties; + isLoading?: boolean; +}; + +type ToolButtonProps = + | (ToolButtonBaseProps & { + type: "button"; + children?: React.ReactNode; + onClick?(event: React.MouseEvent): void; + }) + | (ToolButtonBaseProps & { + type: "submit"; + children?: React.ReactNode; + onClick?(event: React.MouseEvent): void; + }) + | (ToolButtonBaseProps & { + type: "icon"; + children?: React.ReactNode; + onClick?(): void; + }) + | (ToolButtonBaseProps & { + type: "radio"; + checked: boolean; + onChange?(data: { pointerType: PointerType | null }): void; + onPointerDown?(data: { pointerType: PointerType }): void; + }); + +export const ToolButton = React.forwardRef( + ( + { + size = "medium", + visible = true, + className = "", + ...props + }: ToolButtonProps, + ref, + ) => { + const { id: excalId } = useExcalidrawContainer(); + const innerRef = React.useRef(null); + React.useImperativeHandle(ref, () => innerRef.current); + const sizeCn = `ToolIcon_size_${size}`; + + const [isLoading, setIsLoading] = useState(false); + + const isMountedRef = useRef(true); + + const onClick = async (event: React.MouseEvent) => { + const ret = "onClick" in props && props.onClick?.(event); + + if (isPromiseLike(ret)) { + try { + setIsLoading(true); + await ret; + } catch (error: any) { + if (!(error instanceof AbortError)) { + throw error; + } else { + console.warn(error); + } + } finally { + if (isMountedRef.current) { + setIsLoading(false); + } + } + } + }; + + useEffect(() => { + isMountedRef.current = true; + return () => { + isMountedRef.current = false; + }; + }, []); + + const lastPointerTypeRef = useRef(null); + + if ( + props.type === "button" || + props.type === "icon" || + props.type === "submit" + ) { + const type = (props.type === "icon" ? "button" : props.type) as + | "button" + | "submit"; + return ( + + ); + } + + return ( + + ); + }, +); + +ToolButton.displayName = "ToolButton"; -- cgit v1.2.3