diff options
Diffstat (limited to 'packages/excalidraw/components/FilledButton.tsx')
| -rw-r--r-- | packages/excalidraw/components/FilledButton.tsx | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/excalidraw/components/FilledButton.tsx b/packages/excalidraw/components/FilledButton.tsx new file mode 100644 index 0000000..1360908 --- /dev/null +++ b/packages/excalidraw/components/FilledButton.tsx @@ -0,0 +1,114 @@ +import React, { forwardRef, useState } from "react"; +import clsx from "clsx"; + +import "./FilledButton.scss"; +import { AbortError } from "../errors"; +import Spinner from "./Spinner"; +import { isPromiseLike } from "../utils"; +import { tablerCheckIcon } from "./icons"; + +export type ButtonVariant = "filled" | "outlined" | "icon"; +export type ButtonColor = + | "primary" + | "danger" + | "warning" + | "muted" + | "success"; +export type ButtonSize = "medium" | "large"; + +export type FilledButtonProps = { + label: string; + + children?: React.ReactNode; + onClick?: (event: React.MouseEvent) => void; + status?: null | "loading" | "success"; + + variant?: ButtonVariant; + color?: ButtonColor; + size?: ButtonSize; + className?: string; + fullWidth?: boolean; + + icon?: React.ReactNode; +}; + +export const FilledButton = forwardRef<HTMLButtonElement, FilledButtonProps>( + ( + { + children, + icon, + onClick, + label, + variant = "filled", + color = "primary", + size = "medium", + fullWidth, + className, + status, + }, + ref, + ) => { + const [isLoading, setIsLoading] = useState(false); + + const _onClick = async (event: React.MouseEvent) => { + const ret = onClick?.(event); + + if (isPromiseLike(ret)) { + // delay loading state to prevent flicker in case of quick response + const timer = window.setTimeout(() => { + setIsLoading(true); + }, 50); + try { + await ret; + } catch (error: any) { + if (!(error instanceof AbortError)) { + throw error; + } else { + console.warn(error); + } + } finally { + clearTimeout(timer); + setIsLoading(false); + } + } + }; + + const _status = isLoading ? "loading" : status; + color = _status === "success" ? "success" : color; + + return ( + <button + className={clsx( + "ExcButton", + `ExcButton--color-${color}`, + `ExcButton--variant-${variant}`, + `ExcButton--size-${size}`, + `ExcButton--status-${_status}`, + { "ExcButton--fullWidth": fullWidth }, + className, + )} + onClick={_onClick} + type="button" + aria-label={label} + ref={ref} + disabled={_status === "loading" || _status === "success"} + > + <div className="ExcButton__contents"> + {_status === "loading" ? ( + <Spinner className="ExcButton__statusIcon" /> + ) : ( + _status === "success" && ( + <div className="ExcButton__statusIcon">{tablerCheckIcon}</div> + ) + )} + {icon && ( + <div className="ExcButton__icon" aria-hidden> + {icon} + </div> + )} + {variant !== "icon" && (children ?? label)} + </div> + </button> + ); + }, +); |
