aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/FilledButton.tsx
blob: 1360908484c6ebc8fb17de7ba98a5a4689679463 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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>
    );
  },
);