@netless/fastboard-react 0.2.0 → 0.2.4
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/dist/index.js +316 -357
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +256 -297
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -7
- package/src/components/Toolbar/Content.tsx +19 -4
- package/src/components/Toolbar/Toolbar.scss +26 -3
- package/src/components/Toolbar/Toolbar.tsx +15 -47
- package/src/components/hooks.ts +8 -1
- package/src/components/Toolbar/components/Mask.tsx +0 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/fastboard-react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "A UI kit built on top of @netless/fastboard.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@tippyjs/react": "^4.2.6",
|
|
12
12
|
"clsx": "^1.1.1",
|
|
13
|
-
"framer-motion": "^6.2.
|
|
14
|
-
"i18next": "^21.6.
|
|
13
|
+
"framer-motion": "^6.2.3",
|
|
14
|
+
"i18next": "^21.6.9",
|
|
15
15
|
"rc-slider": "^9.7.5"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
@@ -22,15 +22,14 @@
|
|
|
22
22
|
"white-web-sdk": ">=2.16.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@netless/
|
|
26
|
-
"@netless/
|
|
27
|
-
"@netless/window-manager": "^0.4.0-canary.24",
|
|
25
|
+
"@netless/fastboard-core": "0.2.4",
|
|
26
|
+
"@netless/window-manager": "^0.4.0-canary.31",
|
|
28
27
|
"@types/react": "^17.0.38",
|
|
29
28
|
"@types/react-dom": "^17.0.11",
|
|
30
29
|
"sass": "^1.49.0",
|
|
31
30
|
"tippy.js": "^6.3.7",
|
|
32
31
|
"tsup": "^5.11.11",
|
|
33
|
-
"white-web-sdk": "^2.16.
|
|
32
|
+
"white-web-sdk": "^2.16.5"
|
|
34
33
|
},
|
|
35
34
|
"publishConfig": {
|
|
36
35
|
"main": "dist/index.js",
|
|
@@ -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
|
-
|
|
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:
|
|
102
|
-
height:
|
|
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:
|
|
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,
|
|
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
|
|
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 [
|
|
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
|
-
|
|
78
|
-
|
|
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={() =>
|
|
83
|
-
onAnimationComplete={() =>
|
|
84
|
-
style={{ pointerEvents
|
|
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={
|
|
71
|
+
onClick={() => setExpanded(true)}
|
|
100
72
|
initial={{ x: -100 }}
|
|
101
73
|
animate={{ x: 0, transition: { duration: 0.5 } }}
|
|
102
74
|
>
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
}
|
package/src/components/hooks.ts
CHANGED
|
@@ -43,12 +43,19 @@ 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() {
|
|
49
|
+
const writable = useWritable();
|
|
47
50
|
const maximized = useMaximized();
|
|
48
51
|
const focusedApp = useFocusedApp();
|
|
49
52
|
|
|
53
|
+
if (!writable) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
if (maximized) {
|
|
51
|
-
if (
|
|
58
|
+
if (AppsShouldShowToolbar.some(kind => focusedApp?.includes(kind))) {
|
|
52
59
|
return "toolbar-only";
|
|
53
60
|
} else {
|
|
54
61
|
return true;
|
|
@@ -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
|
-
});
|