@netless/fastboard-react 0.1.1 → 0.2.3

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.
package/package.json CHANGED
@@ -1,11 +1,8 @@
1
1
  {
2
2
  "name": "@netless/fastboard-react",
3
- "version": "0.1.1",
3
+ "version": "0.2.3",
4
4
  "description": "A UI kit built on top of @netless/fastboard.",
5
5
  "main": "dist/index.js",
6
- "module": "dist/index.mjs",
7
- "types": "src/index.ts",
8
- "sideEffects": false,
9
6
  "files": [
10
7
  "src",
11
8
  "dist"
@@ -13,30 +10,37 @@
13
10
  "dependencies": {
14
11
  "@tippyjs/react": "^4.2.6",
15
12
  "clsx": "^1.1.1",
16
- "framer-motion": "^5.6.0",
17
- "i18next": "^21.6.6",
13
+ "framer-motion": "^6.2.3",
14
+ "i18next": "^21.6.9",
18
15
  "rc-slider": "^9.7.5"
19
16
  },
20
17
  "peerDependencies": {
21
- "@netless/fastboard": "*",
18
+ "@netless/fastboard-core": "*",
22
19
  "@netless/window-manager": ">=0.4.0",
23
20
  "react": "*",
24
21
  "react-dom": "*",
25
22
  "white-web-sdk": ">=2.16.0"
26
23
  },
27
24
  "devDependencies": {
28
- "@netless/app-slide": "^0.0.55",
29
- "@netless/fastboard": "0.1.1",
30
- "@netless/window-manager": "^0.4.0-canary.17",
25
+ "@netless/fastboard-core": "0.2.3",
26
+ "@netless/window-manager": "^0.4.0-canary.31",
31
27
  "@types/react": "^17.0.38",
32
28
  "@types/react-dom": "^17.0.11",
33
29
  "sass": "^1.49.0",
34
30
  "tippy.js": "^6.3.7",
35
31
  "tsup": "^5.11.11",
36
- "white-web-sdk": "^2.16.0"
32
+ "white-web-sdk": "^2.16.5"
33
+ },
34
+ "publishConfig": {
35
+ "main": "dist/index.js",
36
+ "module": "dist/index.mjs",
37
+ "types": "src/index.ts"
37
38
  },
38
39
  "scripts": {
39
40
  "build": "tsup",
40
- "cleanup": "rimraf dist"
41
- }
41
+ "cleanup": "rimraf dist",
42
+ "check": "tsc --noEmit"
43
+ },
44
+ "module": "dist/index.mjs",
45
+ "types": "src/index.ts"
42
46
  }
@@ -1,4 +1,4 @@
1
- import type { FastboardApp } from "@netless/fastboard";
1
+ import type { FastboardApp } from "@netless/fastboard-core";
2
2
  import type { ForwardedRef } from "react";
3
3
  import type { Language, Theme } from "../typings";
4
4
 
@@ -21,20 +21,19 @@ export interface FastboardProps {
21
21
  export type DivProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
22
22
  export type WithForwardedRef<T = HTMLDivElement> = { forwardedRef: ForwardedRef<T> };
23
23
 
24
- export const Fastboard = forwardRef<HTMLDivElement, FastboardProps & DivProps>(function Fastboard(
25
- { app, theme, layout, language, ...restProps },
26
- ref
27
- ) {
28
- if (!app) {
29
- return <div className="fastboard-root" ref={ref} {...restProps} />;
30
- }
24
+ export const Fastboard = /* @__PURE__ */ forwardRef<HTMLDivElement, FastboardProps & DivProps>(
25
+ function Fastboard({ app, theme, layout, language, ...restProps }, ref) {
26
+ if (!app) {
27
+ return <div className="fastboard-root" ref={ref} {...restProps} />;
28
+ }
31
29
 
32
- return (
33
- <FastboardAppContext.Provider value={app}>
34
- <FastboardInternal forwardedRef={ref} {...{ theme, layout, language }} {...restProps} />
35
- </FastboardAppContext.Provider>
36
- );
37
- });
30
+ return (
31
+ <FastboardAppContext.Provider value={app}>
32
+ <FastboardInternal forwardedRef={ref} {...{ theme, layout, language }} {...restProps} />
33
+ </FastboardAppContext.Provider>
34
+ );
35
+ }
36
+ );
38
37
 
39
38
  function FastboardInternal({
40
39
  forwardedRef,
@@ -1,5 +1,6 @@
1
1
  import clsx from "clsx";
2
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
3
+ import { Icon } from "../../icons";
3
4
 
4
5
  import { clamp } from "../../internal";
5
6
  import { CleanButton, ClickerButton, EraserButton, SelectorButton } from "./components/ApplianceButtons";
@@ -9,12 +10,20 @@ import { ShapesButton } from "./components/ShapesButton";
9
10
  import { TextButton } from "./components/TextButton";
10
11
  import { DownButton, UpButton } from "./components/UpDownButtons";
11
12
  import { ItemHeight, ItemsCount, MaxHeight, MinHeight } from "./const";
12
- import { name } from "./Toolbar";
13
+ import { name, ToolbarContext } from "./Toolbar";
13
14
 
14
- export const Content = React.memo(() => {
15
+ import collapsePNG from "./components/assets/collapsed.png";
16
+
17
+ export interface ContextProps {
18
+ onCollapse: () => void;
19
+ }
20
+
21
+ export function Content({ onCollapse }: ContextProps) {
22
+ const { theme, icons, writable } = useContext(ToolbarContext);
15
23
  const ref = useRef<HTMLDivElement>(null);
16
24
  const [scrollTop, setScrollTop] = useState(0);
17
25
  const [parentHeight, setParentHeight] = useState(0);
26
+ const disabled = !writable;
18
27
 
19
28
  const needScroll = parentHeight < ItemHeight * ItemsCount + 48;
20
29
  const sectionHeight = clamp(parentHeight - 48 * (needScroll ? 3 : 1), MinHeight, MaxHeight);
@@ -69,6 +78,12 @@ export const Content = React.memo(() => {
69
78
  <AppsButton />
70
79
  </div>
71
80
  {needScroll && <DownButton scrollTo={scrollTo} disabled={disableScrollDown} />}
81
+ <div className={clsx("fastboard-toolbar-mask", theme)} onClick={onCollapse}>
82
+ <Icon
83
+ fallback={<img draggable={false} className={clsx(`${name}-mask-btn`, theme)} src={collapsePNG} />}
84
+ src={disabled ? icons?.expandIconDisable : icons?.expandIcon}
85
+ />
86
+ </div>
72
87
  </>
73
88
  );
74
- });
89
+ }
@@ -98,8 +98,8 @@ $name: "fastboard-toolbar";
98
98
 
99
99
  svg,
100
100
  img {
101
- width: 1em;
102
- height: 1em;
101
+ width: 100%;
102
+ height: 100%;
103
103
  }
104
104
 
105
105
  &:disabled {
@@ -152,6 +152,17 @@ $name: "fastboard-toolbar";
152
152
  }
153
153
  }
154
154
 
155
+ &-section + &-mask {
156
+ opacity: 0;
157
+ transition: 0.5s opacity 0.4s;
158
+ }
159
+
160
+ &-section:hover + &-mask,
161
+ &-mask:hover {
162
+ opacity: 1;
163
+ transition: 0.2s opacity;
164
+ }
165
+
155
166
  &-panel {
156
167
  width: 136px - 8px * 2;
157
168
  padding: 0;
@@ -191,7 +202,7 @@ $name: "fastboard-toolbar";
191
202
  .#{$name}-btn {
192
203
  width: 40px;
193
204
  height: 40px;
194
- font-size: 40px;
205
+ font-size: 0;
195
206
  }
196
207
  }
197
208
 
@@ -263,10 +274,22 @@ $name: "fastboard-toolbar";
263
274
  }
264
275
  }
265
276
 
277
+ &-mask {
278
+ position: absolute;
279
+ left: calc(100% + 1px);
280
+ top: 50%;
281
+ transform: translateY(-50%);
282
+ opacity: 0.85;
283
+ &.dark {
284
+ left: calc(100%);
285
+ }
286
+ }
287
+
266
288
  &-mask-btn {
267
289
  width: 17px;
268
290
  height: 62px;
269
291
  cursor: pointer;
292
+ opacity: 0.85;
270
293
  &.dark {
271
294
  filter: invert(0.8);
272
295
  }
@@ -3,14 +3,12 @@ import type { ToolbarHook } from "./hooks";
3
3
 
4
4
  import clsx from "clsx";
5
5
  import { AnimatePresence, motion } from "framer-motion";
6
- import React, { createContext, useCallback, useEffect, useState } from "react";
6
+ import React, { createContext, useState } from "react";
7
7
 
8
- import collapsePNG from "./components/assets/collapsed.png";
9
8
  import expandPNG from "./components/assets/expanded.png";
10
9
 
11
10
  import { Icon } from "../../icons";
12
11
  import { useTheme } from "../hooks";
13
- import { Mask } from "./components/Mask";
14
12
  import { Content } from "./Content";
15
13
  import { EmptyToolbarHook, useToolbar } from "./hooks";
16
14
 
@@ -42,75 +40,45 @@ export const ToolbarContext = createContext<ToolbarContextType>({
42
40
 
43
41
  export const name = "fastboard-toolbar";
44
42
 
45
- export const Toolbar = ({ theme, icons }: ToolbarProps) => {
43
+ export function Toolbar({ theme, icons }: ToolbarProps) {
46
44
  theme = useTheme(theme);
47
45
 
48
46
  const hook = useToolbar();
49
47
  const [expanded, setExpanded] = useState(true);
50
- const [toolbar, toolbarRef] = useState<HTMLDivElement | null>(null);
51
- const [onHover, setOnHover] = useState(false);
52
- const [delayedOnHover, setDelayedOnHover] = useState(false);
53
- const [pointEvents, setPointEvents] = useState(true);
48
+ const [pointerEvents, setPointerEvents] = useState<"auto" | "none">("auto");
54
49
  const disabled = !hook.writable;
55
50
 
56
- const toggle = useCallback(() => {
57
- setExpanded(e => !e);
58
- }, []);
59
-
60
- useEffect(() => {
61
- const timer = setTimeout(() => {
62
- setDelayedOnHover(onHover);
63
- }, 400);
64
- return () => clearTimeout(timer);
65
- }, [onHover]);
66
-
67
51
  return (
68
52
  <ToolbarContext.Provider value={{ theme, icons, ...hook }}>
69
53
  <AnimatePresence>
70
54
  {expanded ? (
71
55
  <motion.div
72
- initial={{ x: -100 }}
73
- animate={{ x: 0, transition: { duration: 0.5 } }}
74
56
  key="toolbar"
75
- ref={toolbarRef}
76
57
  className={clsx(name, theme)}
77
- onPointerEnter={() => {
78
- expanded && setOnHover(true);
79
- }}
80
- onMouseLeave={() => setOnHover(false)}
58
+ initial={{ x: -100 }}
59
+ animate={{ x: 0, transition: { duration: 0.5 } }}
81
60
  exit={{ x: -100, transition: { duration: 0.5 } }}
82
- onAnimationStart={() => setPointEvents(false)}
83
- onAnimationComplete={() => setPointEvents(true)}
84
- style={{ pointerEvents: pointEvents ? "auto" : "none" }}
61
+ onAnimationStart={() => setPointerEvents("none")}
62
+ onAnimationComplete={() => setPointerEvents("auto")}
63
+ style={{ pointerEvents }}
85
64
  >
86
- <Content />
87
- {expanded && (onHover || delayedOnHover) && (
88
- <Mask toolbar={toolbar}>
89
- <div onClick={toggle}>
90
- <img draggable={false} className={clsx(`${name}-mask-btn`, theme)} src={collapsePNG} />
91
- </div>
92
- </Mask>
93
- )}
65
+ <Content onCollapse={() => setExpanded(false)} />
94
66
  </motion.div>
95
67
  ) : (
96
68
  <motion.div
97
69
  className={clsx(`${name}-expand-btn`, theme)}
98
70
  key="expand"
99
- onClick={toggle}
71
+ onClick={() => setExpanded(true)}
100
72
  initial={{ x: -100 }}
101
73
  animate={{ x: 0, transition: { duration: 0.5 } }}
102
74
  >
103
- {!expanded && (
104
- <Icon
105
- fallback={
106
- <img draggable={false} src={expandPNG} className={clsx(`${name}-mask-btn`, theme)} />
107
- }
108
- src={disabled ? icons?.expandIconDisable : icons?.expandIcon}
109
- />
110
- )}
75
+ <Icon
76
+ fallback={<img draggable={false} src={expandPNG} className={clsx(`${name}-mask-btn`, theme)} />}
77
+ src={disabled ? icons?.expandIconDisable : icons?.expandIcon}
78
+ />
111
79
  </motion.div>
112
80
  )}
113
81
  </AnimatePresence>
114
82
  </ToolbarContext.Provider>
115
83
  );
116
- };
84
+ }
@@ -1,4 +1,4 @@
1
- import type { FastboardApp, FastboardReadable } from "@netless/fastboard";
1
+ import type { FastboardApp, FastboardReadable } from "@netless/fastboard-core";
2
2
  import type { Theme } from "../typings";
3
3
 
4
4
  import { BuiltinApps } from "@netless/window-manager";
@@ -43,12 +43,14 @@ export function useMaximized() {
43
43
  return useBoxState() === "maximized";
44
44
  }
45
45
 
46
+ const AppsShouldShowToolbar = /* @__PURE__ */ (() => [BuiltinApps.DocsViewer, "Slide"])();
47
+
46
48
  export function useHideControls() {
47
49
  const maximized = useMaximized();
48
50
  const focusedApp = useFocusedApp();
49
51
 
50
52
  if (maximized) {
51
- if (Object.values(BuiltinApps).some(kind => focusedApp?.includes(kind))) {
53
+ if (AppsShouldShowToolbar.some(kind => focusedApp?.includes(kind))) {
52
54
  return "toolbar-only";
53
55
  } else {
54
56
  return true;
package/src/index.ts CHANGED
@@ -8,3 +8,4 @@ export * from "./components/PageControl";
8
8
  export * from "./components/Toolbar";
9
9
  export * from "./components/PlayerControl";
10
10
  export * from "./components/Fastboard";
11
+ export * from "./vanilla";
@@ -0,0 +1,18 @@
1
+ import type { FastboardApp } from "@netless/fastboard-core";
2
+ import type { DivProps, FastboardProps } from "../components/Fastboard";
3
+
4
+ import React from "react";
5
+ import ReactDOM from "react-dom";
6
+ import { Fastboard } from "../components/Fastboard";
7
+
8
+ /**
9
+ * Mount fastboard app to some dom, returns the disposer, which will unmount the app.
10
+ * @example
11
+ * let app = await createFastboard({ ...config })
12
+ * let disposer = mount(app, document.getElementById("whiteboard"))
13
+ * disposer()
14
+ */
15
+ export function mount(app: FastboardApp, dom: HTMLElement, props: Omit<FastboardProps & DivProps, "ref">) {
16
+ ReactDOM.render(<Fastboard app={app} {...props} />, dom);
17
+ return () => ReactDOM.unmountComponentAtNode(dom);
18
+ }
@@ -1,44 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import ReactDOM from "react-dom";
3
-
4
- interface MaskProps {
5
- toolbar: HTMLDivElement | null;
6
- children: React.ReactNode;
7
- }
8
-
9
- export const Mask = React.memo(({ toolbar, children }: MaskProps) => {
10
- const [rootElement] = useState<HTMLDivElement | null>(() => {
11
- const element = document.createElement("div");
12
- element.style.position = "absolute";
13
- return element;
14
- });
15
-
16
- useEffect(() => {
17
- if (toolbar && rootElement) {
18
- toolbar.appendChild(rootElement);
19
- }
20
- }, [rootElement, toolbar]);
21
-
22
- useEffect(() => {
23
- if (rootElement && toolbar) {
24
- toolbar.appendChild(rootElement);
25
-
26
- const toolbarRect = toolbar.getBoundingClientRect();
27
- const halfHeight = toolbarRect.height / 2 - 31;
28
- rootElement.style.top = halfHeight + "px";
29
- rootElement.style.left = "41px";
30
- rootElement.style.width = "17px";
31
- rootElement.style.height = "62px";
32
-
33
- return () => {
34
- toolbar.removeChild(rootElement);
35
- };
36
- }
37
- }, [rootElement, toolbar]);
38
-
39
- if (rootElement) {
40
- return ReactDOM.createPortal(children, rootElement);
41
- } else {
42
- return null;
43
- }
44
- });