@netless/fastboard-react 0.2.7 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/index.d.ts +146 -0
  2. package/dist/index.js +3207 -310
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +3158 -261
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +7 -7
  7. package/src/components/Fastboard.scss +6 -1
  8. package/src/components/Fastboard.tsx +3 -10
  9. package/src/components/PageControl/PageControl.scss +15 -11
  10. package/src/components/PageControl/PageControl.tsx +15 -17
  11. package/src/components/PlayerControl/PlayerControl.scss +13 -12
  12. package/src/components/PlayerControl/PlayerControl.tsx +3 -3
  13. package/src/components/PlayerControl/components/Button.tsx +1 -2
  14. package/src/components/RedoUndo/RedoUndo.scss +10 -10
  15. package/src/components/ReplayFastboard.tsx +36 -0
  16. package/src/components/Toolbar/Toolbar.scss +46 -17
  17. package/src/components/Toolbar/components/AppsButton.tsx +20 -11
  18. package/src/components/Toolbar/components/Button.tsx +1 -2
  19. package/src/components/Toolbar/icons/Loading.tsx +13 -0
  20. package/src/components/ZoomControl/ZoomControl.scss +15 -11
  21. package/src/components/ZoomControl/ZoomControl.tsx +4 -2
  22. package/src/components/tippy-util.ts +18 -0
  23. package/src/icons/Left.tsx +15 -0
  24. package/src/icons/Minus.tsx +2 -2
  25. package/src/icons/Plus.tsx +2 -2
  26. package/src/icons/Redo.tsx +6 -5
  27. package/src/icons/Reset.tsx +4 -6
  28. package/src/icons/Right.tsx +15 -0
  29. package/src/icons/Undo.tsx +6 -5
  30. package/src/icons/WhiteboardAdd.tsx +26 -0
  31. package/src/index.ts +1 -0
  32. package/src/icons/ChevronLeft.tsx +0 -21
  33. package/src/icons/ChevronRight.tsx +0 -21
  34. package/src/icons/FilePlus.tsx +0 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/fastboard-react",
3
- "version": "0.2.7",
3
+ "version": "0.2.10",
4
4
  "description": "A UI kit built on top of @netless/fastboard.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -11,7 +11,7 @@
11
11
  "@tippyjs/react": "^4.2.6",
12
12
  "clsx": "^1.1.1",
13
13
  "framer-motion": "^6.2.8",
14
- "i18next": "^21.6.12",
14
+ "i18next": "^21.6.13",
15
15
  "rc-slider": "^9.7.5"
16
16
  },
17
17
  "peerDependencies": {
@@ -22,11 +22,11 @@
22
22
  "white-web-sdk": ">=2.16.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@netless/fastboard-core": "0.2.7",
26
- "@netless/window-manager": "^0.4.7",
25
+ "@netless/fastboard-core": "0.2.10",
26
+ "@netless/window-manager": "^0.4.10",
27
27
  "@types/react": "^17.0.39",
28
- "@types/react-dom": "^17.0.11",
29
- "sass": "^1.49.8",
28
+ "@types/react-dom": "^17.0.13",
29
+ "sass": "^1.49.9",
30
30
  "tippy.js": "^6.3.7",
31
31
  "tsup": "^5.11.13",
32
32
  "white-web-sdk": "^2.16.10"
@@ -37,5 +37,5 @@
37
37
  "check": "tsc --noEmit"
38
38
  },
39
39
  "module": "dist/index.mjs",
40
- "types": "src/index.ts"
40
+ "types": "dist/index.d.ts"
41
41
  }
@@ -36,6 +36,11 @@
36
36
 
37
37
  .fastboard-bottom-right {
38
38
  @extend .fastboard-bottom-left;
39
- left: initial;
39
+ left: auto;
40
+ right: 8px;
41
+ }
42
+
43
+ .fastboard-bottom {
44
+ @extend .fastboard-bottom-left;
40
45
  right: 8px;
41
46
  }
@@ -10,6 +10,7 @@ import { PageControl } from "./PageControl";
10
10
  import { RedoUndo } from "./RedoUndo";
11
11
  import { Toolbar } from "./Toolbar";
12
12
  import { ZoomControl } from "./ZoomControl";
13
+ import { hideAll } from "./tippy-util";
13
14
 
14
15
  export interface FastboardProps {
15
16
  app?: FastboardApp | null;
@@ -58,7 +59,7 @@ function FastboardInternal({
58
59
 
59
60
  const useWhiteboard = useCallback(
60
61
  (container: HTMLDivElement | null) => {
61
- if (container && app) app.manager.bindContainer(container);
62
+ if (container && app) app.bindContainer(container);
62
63
  },
63
64
  [app]
64
65
  );
@@ -77,11 +78,7 @@ function FastboardInternal({
77
78
  <ThemeContext.Provider value={theme}>
78
79
  <I18nContext.Provider value={i18n}>
79
80
  <div {...restProps} className="fastboard-root" ref={forwardedRef}>
80
- <div
81
- className="fastboard-view"
82
- ref={useWhiteboard}
83
- onPointerDownCapture={focusThisElementImmediate}
84
- />
81
+ <div className="fastboard-view" ref={useWhiteboard} onTouchStartCapture={hideAll} />
85
82
  {children ? (
86
83
  children
87
84
  ) : (
@@ -109,7 +106,3 @@ function FastboardInternal({
109
106
  </ThemeContext.Provider>
110
107
  );
111
108
  }
112
-
113
- function focusThisElementImmediate(ev: React.PointerEvent<HTMLDivElement>) {
114
- ev.currentTarget.focus();
115
- }
@@ -6,19 +6,19 @@ $name: "fastboard-page-control";
6
6
  gap: 4px;
7
7
  padding: 4px;
8
8
  border-radius: 4px;
9
- backdrop-filter: blur(2px);
10
- -webkit-backdrop-filter: blur(2px);
9
+ backdrop-filter: blur(5px);
10
+ -webkit-backdrop-filter: blur(5px);
11
11
 
12
12
  &.light {
13
13
  color: #333;
14
- background-color: rgba($color: #fff, $alpha: 0.85);
15
- border: 1px solid rgba(0, 0, 0, 0.15);
14
+ background-color: rgba(255, 255, 255, 0.9);
15
+ border: 1px solid #e5e8f0;
16
16
  }
17
17
 
18
18
  &.dark {
19
19
  color: #ddd;
20
- background-color: rgba($color: #333, $alpha: 0.85);
21
- border: 1px solid rgba(0, 0, 0, 0.45);
20
+ background-color: #14181e;
21
+ border: 1px solid #383b42;
22
22
  }
23
23
  }
24
24
 
@@ -37,8 +37,8 @@ $name: "fastboard-page-control";
37
37
 
38
38
  svg,
39
39
  img {
40
- width: 1em;
41
- height: 1em;
40
+ width: 100%;
41
+ height: 100%;
42
42
  }
43
43
 
44
44
  &:disabled {
@@ -47,11 +47,11 @@ $name: "fastboard-page-control";
47
47
  }
48
48
 
49
49
  &.light:not(:disabled):hover {
50
- background-color: rgba(51, 129, 255, 0.1);
50
+ background-color: #ebf2ff;
51
51
  }
52
52
 
53
53
  &.dark:not(:disabled):hover {
54
- background-color: rgba(51, 129, 255, 0.25);
54
+ background-color: #383b42;
55
55
  }
56
56
  }
57
57
 
@@ -72,9 +72,13 @@ $name: "fastboard-page-control";
72
72
  opacity: 0.6;
73
73
  }
74
74
 
75
+ .#{$name}-text {
76
+ line-height: 24px;
77
+ }
78
+
75
79
  .#{$name}-page,
76
80
  .#{$name}-slash,
77
81
  .#{$name}-page-count {
78
- font-size: 12px;
82
+ font-size: 14px;
79
83
  font-variant-numeric: tabular-nums;
80
84
  }
@@ -6,9 +6,9 @@ import React from "react";
6
6
 
7
7
  import { useTranslation } from "../../i18n";
8
8
  import { Icon } from "../../icons";
9
- import { ChevronLeft } from "../../icons/ChevronLeft";
10
- import { ChevronRight } from "../../icons/ChevronRight";
11
- import { FilePlus } from "../../icons/FilePlus";
9
+ import { Left } from "../../icons/Left";
10
+ import { Right } from "../../icons/Right";
11
+ import { WhiteboardAdd } from "../../icons/WhiteboardAdd";
12
12
  import { TopOffset } from "../../theme";
13
13
  import { useTheme, useWritable } from "../hooks";
14
14
  import { usePageControl } from "./hooks";
@@ -52,16 +52,14 @@ export function PageControl({
52
52
  disabled={disabled || pageIndex === 0}
53
53
  onClick={actions.prevPage}
54
54
  >
55
- <Icon
56
- fallback={<ChevronLeft theme={theme} />}
57
- src={disabled ? prevIconDisable : prevIcon}
58
- alt="[prev]"
59
- />
55
+ <Icon fallback={<Left theme={theme} />} src={disabled ? prevIconDisable : prevIcon} alt="[prev]" />
60
56
  </button>
61
57
  </Tippy>
62
- <span className={clsx(`${name}-page`, theme)}>{pageCount === 0 ? "\u2026" : pageIndex + 1}</span>
63
- <span className={clsx(`${name}-slash`, theme)}>/</span>
64
- <span className={clsx(`${name}-page-count`, theme)}>{pageCount}</span>
58
+ <span className={clsx(`${name}-text`, theme)}>
59
+ <span className={clsx(`${name}-page`, theme)}>{pageCount === 0 ? "\u2026" : pageIndex + 1}</span>
60
+ <span className={clsx(`${name}-slash`, theme)}>/</span>
61
+ <span className={clsx(`${name}-page-count`, theme)}>{pageCount}</span>
62
+ </span>
65
63
  <Tippy
66
64
  className="fastboard-tip"
67
65
  content={t("nextPage")}
@@ -77,11 +75,7 @@ export function PageControl({
77
75
  disabled={disabled || pageIndex === pageCount - 1}
78
76
  onClick={actions.nextPage}
79
77
  >
80
- <Icon
81
- fallback={<ChevronRight theme={theme} />}
82
- src={disabled ? nextIconDisable : nextIcon}
83
- alt="[next]"
84
- />
78
+ <Icon fallback={<Right theme={theme} />} src={disabled ? nextIconDisable : nextIcon} alt="[next]" />
85
79
  </button>
86
80
  </Tippy>
87
81
  <Tippy
@@ -95,7 +89,11 @@ export function PageControl({
95
89
  offset={TopOffset}
96
90
  >
97
91
  <button className={clsx(`${name}-btn`, "add", theme)} disabled={disabled} onClick={actions.addPage}>
98
- <Icon fallback={<FilePlus theme={theme} />} src={disabled ? addIconDisable : addIcon} alt="[add]" />
92
+ <Icon
93
+ fallback={<WhiteboardAdd theme={theme} />}
94
+ src={disabled ? addIconDisable : addIcon}
95
+ alt="[add]"
96
+ />
99
97
  </button>
100
98
  </Tippy>
101
99
  </div>
@@ -7,8 +7,9 @@ $name: "fastboard-player-control";
7
7
  gap: 4px;
8
8
  padding: 4px;
9
9
  border-radius: 4px;
10
- backdrop-filter: blur(2px);
11
- -webkit-backdrop-filter: blur(2px);
10
+ backdrop-filter: blur(5px);
11
+ -webkit-backdrop-filter: blur(5px);
12
+ z-index: 100;
12
13
 
13
14
  &.auto-hide {
14
15
  opacity: 0;
@@ -45,14 +46,14 @@ $name: "fastboard-player-control";
45
46
 
46
47
  &.light {
47
48
  color: #333;
48
- background-color: rgba($color: #fff, $alpha: 0.85);
49
- border: 1px solid rgba(0, 0, 0, 0.15);
49
+ background-color: rgba(255, 255, 255, 0.9);
50
+ border: 1px solid #e5e8f0;
50
51
  }
51
52
 
52
53
  &.dark {
53
54
  color: #ddd;
54
- background-color: rgba($color: #333, $alpha: 0.85);
55
- border: 1px solid rgba(0, 0, 0, 0.45);
55
+ background-color: #14181e;
56
+ border: 1px solid #383b42;
56
57
  }
57
58
  }
58
59
 
@@ -74,8 +75,8 @@ $name: "fastboard-player-control";
74
75
 
75
76
  svg,
76
77
  img {
77
- width: 1em;
78
- height: 1em;
78
+ width: 100%;
79
+ height: 100%;
79
80
  }
80
81
 
81
82
  &:disabled {
@@ -84,11 +85,11 @@ $name: "fastboard-player-control";
84
85
  }
85
86
 
86
87
  &.light:not(:disabled):hover {
87
- background-color: rgba(51, 129, 255, 0.1);
88
+ background-color: #ebf2ff;
88
89
  }
89
90
 
90
91
  &.dark:not(:disabled):hover {
91
- background-color: rgba(51, 129, 255, 0.25);
92
+ background-color: #383b42;
92
93
  }
93
94
 
94
95
  &.loading {
@@ -113,7 +114,7 @@ $name: "fastboard-player-control";
113
114
  width: initial;
114
115
  height: initial;
115
116
  user-select: none;
116
- font-size: 12px;
117
+ font-size: 14px;
117
118
  padding: 4px;
118
119
  justify-content: flex-end;
119
120
 
@@ -140,6 +141,6 @@ $name: "fastboard-player-control";
140
141
  .#{$name}-slash,
141
142
  .#{$name}-total,
142
143
  .#{$name}-speed-text {
143
- font-size: 12px;
144
+ font-size: 14px;
144
145
  font-variant-numeric: tabular-nums;
145
146
  }
@@ -1,4 +1,4 @@
1
- import type { Player } from "white-web-sdk";
1
+ import type { FastboardPlayer } from "@netless/fastboard-core";
2
2
  import type { CommonProps, GenericIcon } from "../../typings";
3
3
 
4
4
  import Tippy from "@tippyjs/react";
@@ -17,7 +17,7 @@ import { Icons } from "./icons";
17
17
 
18
18
  export type PlayerControlProps = {
19
19
  autoHide?: boolean;
20
- player?: Player;
20
+ player?: FastboardPlayer;
21
21
  } & Omit<CommonProps, "room"> &
22
22
  GenericIcon<"play" | "pause" | "loading">;
23
23
 
@@ -28,7 +28,7 @@ export function PlayerControl({ theme, autoHide = false, player: player_, ...ico
28
28
  const { t } = useTranslation();
29
29
 
30
30
  const [currentTime, setCurrentTime] = useState(0);
31
- const player = usePlayerControl(player_);
31
+ const player = usePlayerControl(player_?.player);
32
32
 
33
33
  useEffect(() => {
34
34
  setCurrentTime(player.currentTime);
@@ -1,4 +1,3 @@
1
- import type { Placement } from "tippy.js";
2
1
  import type { Theme } from "../../../typings";
3
2
 
4
3
  import clsx from "clsx";
@@ -14,7 +13,7 @@ type ButtonProps = PropsWithChildren<{
14
13
  active?: boolean;
15
14
  onClick?: () => void;
16
15
  interactive?: boolean;
17
- placement?: Placement;
16
+ placement?: "top" | "right"; // not using tippy.js's placement to satisfy dts
18
17
  }>;
19
18
 
20
19
  export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
@@ -6,19 +6,19 @@ $name: "fastboard-redo-undo";
6
6
  gap: 4px;
7
7
  padding: 4px;
8
8
  border-radius: 4px;
9
- backdrop-filter: blur(2px);
10
- -webkit-backdrop-filter: blur(2px);
9
+ backdrop-filter: blur(5px);
10
+ -webkit-backdrop-filter: blur(5px);
11
11
 
12
12
  &.light {
13
13
  color: #333;
14
- background-color: rgba($color: #fff, $alpha: 0.85);
15
- border: 1px solid rgba(0, 0, 0, 0.15);
14
+ background-color: rgba(255, 255, 255, 0.9);
15
+ border: 1px solid #e5e8f0;
16
16
  }
17
17
 
18
18
  &.dark {
19
19
  color: #ddd;
20
- background-color: rgba($color: #333, $alpha: 0.85);
21
- border: 1px solid rgba(0, 0, 0, 0.45);
20
+ background-color: #14181e;
21
+ border: 1px solid #383b42;
22
22
  }
23
23
  }
24
24
 
@@ -37,8 +37,8 @@ $name: "fastboard-redo-undo";
37
37
 
38
38
  svg,
39
39
  img {
40
- width: 1em;
41
- height: 1em;
40
+ width: 100%;
41
+ height: 100%;
42
42
  }
43
43
 
44
44
  &:disabled {
@@ -47,10 +47,10 @@ $name: "fastboard-redo-undo";
47
47
  }
48
48
 
49
49
  &.light:not(:disabled):hover {
50
- background-color: rgba(51, 129, 255, 0.1);
50
+ background-color: #ebf2ff;
51
51
  }
52
52
 
53
53
  &.dark:not(:disabled):hover {
54
- background-color: rgba(51, 129, 255, 0.25);
54
+ background-color: #383b42;
55
55
  }
56
56
  }
@@ -0,0 +1,36 @@
1
+ import type { FastboardPlayer } from "@netless/fastboard-core";
2
+ import type { DivProps } from "./Fastboard";
3
+ import type { Theme } from "../typings";
4
+
5
+ import React, { forwardRef, useCallback } from "react";
6
+ import { PlayerControl } from "./PlayerControl";
7
+
8
+ export interface ReplayFastboardProps {
9
+ player?: FastboardPlayer | null;
10
+ theme?: Theme;
11
+ autoHideControl?: boolean;
12
+ }
13
+
14
+ export const ReplayFastboard = /* @__PURE__ */ forwardRef<HTMLDivElement, ReplayFastboardProps & DivProps>(
15
+ function ReplayFastboard({ player, theme = "light", autoHideControl = false, ...restProps }, ref) {
16
+ const useWhiteboard = useCallback(
17
+ (container: HTMLDivElement | null) => {
18
+ if (container && player) player.bindContainer(container);
19
+ },
20
+ [player]
21
+ );
22
+
23
+ if (!player) {
24
+ return <div className="fastboard-root" ref={ref} {...restProps} />;
25
+ }
26
+
27
+ return (
28
+ <div className="fastboard-root" ref={ref} {...restProps}>
29
+ <div className="fastboard-view" ref={useWhiteboard} />
30
+ <div className="fastboard-bottom">
31
+ <PlayerControl player={player} theme={theme} autoHide={autoHideControl} />
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+ );
@@ -9,8 +9,8 @@ $name: "fastboard-toolbar";
9
9
  gap: 4px;
10
10
  position: absolute;
11
11
  z-index: 100;
12
- backdrop-filter: blur(2px);
13
- -webkit-backdrop-filter: blur(2px);
12
+ backdrop-filter: blur(5px);
13
+ -webkit-backdrop-filter: blur(5px);
14
14
 
15
15
  .rc-slider {
16
16
  padding: 6px 0;
@@ -36,17 +36,18 @@ $name: "fastboard-toolbar";
36
36
 
37
37
  &.light {
38
38
  color: #333;
39
- background-color: rgba($color: #fff, $alpha: 0.85);
40
- border: 1px solid rgba(0, 0, 0, 0.15);
41
- }
42
-
43
- &.expanded {
44
- border: 1px solid rgba(0, 0, 0, 0.15);
39
+ background-color: rgba(255, 255, 255, 0.9);
40
+ border: 1px solid #e5e8f0;
45
41
  }
46
42
 
47
43
  &.dark {
48
44
  color: #ddd;
49
- background-color: rgba($color: #333, $alpha: 0.85);
45
+ background-color: #14181e;
46
+ border: 1px solid #383b42;
47
+ }
48
+
49
+ &.expanded {
50
+ border: 1px solid rgba(0, 0, 0, 0.15);
50
51
  }
51
52
 
52
53
  &.expanded:hover {
@@ -108,11 +109,11 @@ $name: "fastboard-toolbar";
108
109
  }
109
110
 
110
111
  &.light:not(:disabled):hover {
111
- background-color: rgba(51, 129, 255, 0.1);
112
+ background-color: #ebf2ff;
112
113
  }
113
114
 
114
115
  &.dark:not(:disabled):hover {
115
- background-color: rgba(51, 129, 255, 0.25);
116
+ background-color: #383b42;
116
117
  }
117
118
  }
118
119
 
@@ -152,12 +153,12 @@ $name: "fastboard-toolbar";
152
153
  }
153
154
  }
154
155
 
155
- &-section + &-mask {
156
+ &-section ~ &-mask {
156
157
  opacity: 0;
157
158
  transition: 0.5s opacity 0.4s;
158
159
  }
159
160
 
160
- &-section:hover + &-mask,
161
+ &-section:hover ~ &-mask,
161
162
  &-mask:hover {
162
163
  opacity: 1;
163
164
  transition: 0.2s opacity;
@@ -172,7 +173,7 @@ $name: "fastboard-toolbar";
172
173
  gap: 8px;
173
174
 
174
175
  &.apps {
175
- width: 240px - 8px * 2;
176
+ width: 240px + 8px * 2;
176
177
  }
177
178
  }
178
179
 
@@ -206,14 +207,33 @@ $name: "fastboard-toolbar";
206
207
  }
207
208
  }
208
209
 
210
+ &-app-icon-wrapper {
211
+ position: relative;
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ }
216
+
217
+ &-app-icon-mask {
218
+ position: absolute;
219
+ width: 36px;
220
+ height: 36px;
221
+ animation: fastboard-app-loading-rotate 0.5s linear infinite;
222
+ transform-origin: center;
223
+ }
224
+
209
225
  &-app-is-loading {
210
- opacity: 0.8;
211
226
  cursor: wait;
227
+ button:disabled {
228
+ cursor: wait;
229
+ }
212
230
  }
213
231
 
214
232
  &-app-is-failed {
215
- opacity: 0.5;
216
233
  cursor: not-allowed;
234
+ button:disabled {
235
+ cursor: not-allowed;
236
+ }
217
237
  }
218
238
 
219
239
  &-app-icon {
@@ -228,7 +248,7 @@ $name: "fastboard-toolbar";
228
248
  }
229
249
 
230
250
  &-text {
231
- font-size: 12px;
251
+ font-size: 14px;
232
252
  color: #5d5d5d;
233
253
  overflow: hidden;
234
254
  white-space: nowrap;
@@ -312,3 +332,12 @@ $name: "fastboard-toolbar";
312
332
  left: 0;
313
333
  }
314
334
  }
335
+
336
+ @keyframes fastboard-app-loading-rotate {
337
+ from {
338
+ transform: rotate(0deg);
339
+ }
340
+ to {
341
+ transform: rotate(360deg);
342
+ }
343
+ }
@@ -12,6 +12,7 @@ import { Icons } from "../icons";
12
12
  import { ToolbarContext } from "../Toolbar";
13
13
  import { Button } from "./Button";
14
14
  import { useFastboardApp } from "../../hooks";
15
+ import { Loading } from "../icons/Loading";
15
16
 
16
17
  export interface AppsButtonProps {
17
18
  content?: React.ReactNode;
@@ -104,22 +105,30 @@ interface AppIconProps {
104
105
  }
105
106
 
106
107
  function AppIcon({ title, src, alt, appStatus, onClick }: AppIconProps) {
108
+ const { theme } = useContext(ToolbarContext);
107
109
  const { status = "idle", reason } = appStatus || {};
108
110
  const loading = status === "loading";
109
111
  const failed = status === "failed";
110
112
  const unifiedTitle = loading ? "loading" : failed ? reason : title;
111
113
 
112
114
  return (
113
- <span
114
- className={clsx("fastboard-toolbar-app-icon", {
115
- "fastboard-toolbar-app-is-loading": loading,
116
- "fastboard-toolbar-app-is-failed": failed,
117
- })}
118
- >
119
- <Button disabled={failed} placement="top" content={unifiedTitle} onClick={onClick}>
120
- <img src={src} alt={alt} title={unifiedTitle} />
121
- </Button>
122
- <span className="fastboard-toolbar-app-icon-text">{title}</span>
123
- </span>
115
+ <div className="fastboard-toolbar-app-icon-wrapper">
116
+ <span
117
+ className={clsx("fastboard-toolbar-app-icon", {
118
+ "fastboard-toolbar-app-is-loading": loading,
119
+ "fastboard-toolbar-app-is-failed": failed,
120
+ })}
121
+ >
122
+ <Button disabled={failed || loading} placement="top" content={unifiedTitle} onClick={onClick}>
123
+ <img src={src} alt={alt} title={unifiedTitle} />
124
+ </Button>
125
+ <span className="fastboard-toolbar-app-icon-text">{title}</span>
126
+ </span>
127
+ {loading && (
128
+ <span className="fastboard-toolbar-app-icon-mask">
129
+ <Loading theme={theme} />
130
+ </span>
131
+ )}
132
+ </div>
124
133
  );
125
134
  }
@@ -1,4 +1,3 @@
1
- import type { Placement } from "tippy.js";
2
1
  import type { PropsWithChildren } from "react";
3
2
 
4
3
  import clsx from "clsx";
@@ -14,7 +13,7 @@ type ButtonProps = PropsWithChildren<{
14
13
  active?: boolean;
15
14
  onClick?: () => void;
16
15
  interactive?: boolean;
17
- placement?: Placement;
16
+ placement?: "top" | "right"; // not using tippy.js's placement to satisfy dts
18
17
  }>;
19
18
 
20
19
  export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
@@ -0,0 +1,13 @@
1
+ import type { IconProps } from "../../../typings";
2
+
3
+ import React from "react";
4
+ import { getStroke } from "../../../theme";
5
+
6
+ export const Loading = (props: IconProps) => {
7
+ const stroke = getStroke(props);
8
+ return (
9
+ <svg viewBox="0 0 24 24">
10
+ <path fill={stroke} d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8Z"></path>
11
+ </svg>
12
+ );
13
+ };