aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/FilledButton.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/excalidraw/components/FilledButton.tsx')
-rw-r--r--packages/excalidraw/components/FilledButton.tsx114
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>
+ );
+ },
+);