@hunterchen/canvas 0.8.0 → 0.9.0
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/components/canvas/backgrounds.js +67 -38
- package/dist/components/canvas/backgrounds.js.map +1 -1
- package/dist/components/canvas/canvas.js +451 -387
- package/dist/components/canvas/canvas.js.map +1 -1
- package/dist/components/canvas/component.js +108 -174
- package/dist/components/canvas/component.js.map +1 -1
- package/dist/components/canvas/draggable.js +168 -151
- package/dist/components/canvas/draggable.js.map +1 -1
- package/dist/components/canvas/navbar/index.js +164 -142
- package/dist/components/canvas/navbar/index.js.map +1 -1
- package/dist/components/canvas/navbar/single-button.js +176 -149
- package/dist/components/canvas/navbar/single-button.js.map +1 -1
- package/dist/components/canvas/toolbar.js +121 -82
- package/dist/components/canvas/toolbar.js.map +1 -1
- package/dist/components/canvas/wrapper.js +127 -99
- package/dist/components/canvas/wrapper.js.map +1 -1
- package/dist/contexts/CanvasContext.js +25 -17
- package/dist/contexts/CanvasContext.js.map +1 -1
- package/dist/contexts/PerformanceContext.js +51 -50
- package/dist/contexts/PerformanceContext.js.map +1 -1
- package/dist/hooks/usePerformanceMode.js +36 -37
- package/dist/hooks/usePerformanceMode.js.map +1 -1
- package/dist/hooks/useWindowDimensions.js +22 -18
- package/dist/hooks/useWindowDimensions.js.map +1 -1
- package/dist/index.js +17 -21
- package/dist/lib/canvas.js +65 -72
- package/dist/lib/canvas.js.map +1 -1
- package/dist/lib/constants.js +78 -92
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/utils.js +10 -5
- package/dist/lib/utils.js.map +1 -1
- package/dist/utils/performance.js +18 -23
- package/dist/utils/performance.js.map +1 -1
- package/package.json +7 -21
- package/dist/components/canvas/offest.js +0 -12
- package/dist/components/canvas/offest.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types/index.js +0 -6
- package/dist/types/index.js.map +0 -1
|
@@ -1,152 +1,179 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useState, useEffect } from "react";
|
|
3
|
-
import * as LucideIcons from "lucide-react";
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
4
2
|
import { AnimatePresence, motion } from "framer-motion";
|
|
5
|
-
import {
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import * as LucideIcons from "lucide-react";
|
|
6
|
+
|
|
7
|
+
//#region src/components/canvas/navbar/single-button.tsx
|
|
8
|
+
function SingleButton({ label, icon, onClick, isPushed, link, onDebouncedClick, displayMode = "icons", buttonConfig = {}, tooltipConfig = {}, isVertical = false }) {
|
|
9
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
10
|
+
const [showTag, setShowTag] = useState(false);
|
|
11
|
+
const [copiedEmail, setCopiedEmail] = useState(false);
|
|
12
|
+
const IconComponent = typeof icon === "string" ? LucideIcons[icon] : icon;
|
|
13
|
+
const { className: buttonClassName, style: buttonStyle, activeClassName, activeStyle, hoverClassName, hoverStyle, iconClassName, iconSize = 20, labelClassName, labelStyle } = buttonConfig;
|
|
14
|
+
const { disabled: tooltipDisabled = false, className: tooltipClassName, style: tooltipStyle, delay: tooltipDelay = 100 } = tooltipConfig;
|
|
15
|
+
const showIcon = displayMode !== "labels";
|
|
16
|
+
const allowExpand = displayMode === "icons";
|
|
17
|
+
const showTooltip = (displayMode === "icons" || displayMode === "compact") && !tooltipDisabled;
|
|
18
|
+
if (showIcon && !IconComponent) throw new Error("A valid 'icon' prop is required (Lucide icon name or custom icon component).");
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let timeoutId;
|
|
21
|
+
if (isHovered && showTooltip) timeoutId = setTimeout(() => {
|
|
22
|
+
setShowTag(true);
|
|
23
|
+
}, tooltipDelay);
|
|
24
|
+
else setShowTag(false);
|
|
25
|
+
return () => {
|
|
26
|
+
clearTimeout(timeoutId);
|
|
27
|
+
};
|
|
28
|
+
}, [
|
|
29
|
+
isHovered,
|
|
30
|
+
showTooltip,
|
|
31
|
+
tooltipDelay
|
|
32
|
+
]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setShowTag(false);
|
|
35
|
+
setIsHovered(false);
|
|
36
|
+
}, [isPushed]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (copiedEmail) {
|
|
39
|
+
const timeoutId = setTimeout(() => {
|
|
40
|
+
setCopiedEmail(false);
|
|
41
|
+
}, 2e3);
|
|
42
|
+
return () => clearTimeout(timeoutId);
|
|
43
|
+
}
|
|
44
|
+
}, [copiedEmail]);
|
|
45
|
+
const performClick = () => {
|
|
46
|
+
if (link) {
|
|
47
|
+
window.open(link, "_blank", "noopener,noreferrer");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
onClick?.();
|
|
51
|
+
};
|
|
52
|
+
const handleClick = () => {
|
|
53
|
+
if (onDebouncedClick) onDebouncedClick(performClick);
|
|
54
|
+
else performClick();
|
|
55
|
+
};
|
|
56
|
+
const displayLabel = copiedEmail ? "Email copied!" : label;
|
|
57
|
+
const baseButtonClass = "relative flex items-center rounded-md p-2 text-neutral-500 transition-colors duration-200 focus:outline-none";
|
|
58
|
+
const stateClass = isPushed ? activeClassName || !activeStyle && "bg-neutral-200" : isHovered ? hoverClassName || !hoverStyle && "bg-neutral-100" : "";
|
|
59
|
+
const computedButtonStyle = {
|
|
60
|
+
...buttonStyle,
|
|
61
|
+
...isPushed && activeStyle,
|
|
62
|
+
...isHovered && !isPushed && hoverStyle
|
|
63
|
+
};
|
|
64
|
+
const iconSizeStyle = {
|
|
65
|
+
width: iconSize,
|
|
66
|
+
height: iconSize
|
|
67
|
+
};
|
|
68
|
+
const baseIconClass = "flex-shrink-0";
|
|
69
|
+
const iconColorClass = buttonStyle?.color ? "" : isPushed ? "text-neutral-700" : "text-neutral-500";
|
|
70
|
+
const baseLabelClass = "whitespace-nowrap font-canvas-figtree text-sm font-medium text-neutral-700";
|
|
71
|
+
const tooltipPositionClass = isVertical ? "left-full top-1/2 -translate-y-1/2 ml-2" : "-top-10 left-1/2";
|
|
72
|
+
const tooltipTransform = isVertical ? {
|
|
73
|
+
x: 0,
|
|
74
|
+
y: "-50%"
|
|
75
|
+
} : { x: "-50%" };
|
|
76
|
+
const renderIcon = () => {
|
|
77
|
+
if (!showIcon || !IconComponent) return null;
|
|
78
|
+
return /* @__PURE__ */ jsx(IconComponent, {
|
|
79
|
+
className: cn(baseIconClass, iconColorClass, iconClassName),
|
|
80
|
+
style: iconSizeStyle
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
const renderLabel = (animated = false) => {
|
|
84
|
+
if (animated) return /* @__PURE__ */ jsx(motion.span, {
|
|
85
|
+
initial: {
|
|
86
|
+
opacity: 0,
|
|
87
|
+
width: 0
|
|
88
|
+
},
|
|
89
|
+
animate: {
|
|
90
|
+
opacity: 1,
|
|
91
|
+
width: "auto"
|
|
92
|
+
},
|
|
93
|
+
exit: {
|
|
94
|
+
opacity: 0,
|
|
95
|
+
width: 0
|
|
96
|
+
},
|
|
97
|
+
transition: {
|
|
98
|
+
duration: .1,
|
|
99
|
+
ease: "easeInOut"
|
|
100
|
+
},
|
|
101
|
+
className: cn("overflow-hidden", baseLabelClass, labelClassName),
|
|
102
|
+
style: labelStyle,
|
|
103
|
+
children: displayLabel
|
|
104
|
+
});
|
|
105
|
+
return /* @__PURE__ */ jsx("span", {
|
|
106
|
+
className: cn(baseLabelClass, labelClassName),
|
|
107
|
+
style: labelStyle,
|
|
108
|
+
children: displayLabel
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const renderTooltip = () => {
|
|
112
|
+
if (!showTooltip || !showTag || isPushed) return null;
|
|
113
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsx(motion.div, {
|
|
114
|
+
initial: {
|
|
115
|
+
opacity: 0,
|
|
116
|
+
y: isVertical ? 0 : 5,
|
|
117
|
+
scale: .9,
|
|
118
|
+
...tooltipTransform
|
|
119
|
+
},
|
|
120
|
+
animate: {
|
|
121
|
+
opacity: 1,
|
|
122
|
+
y: 0,
|
|
123
|
+
scale: 1,
|
|
124
|
+
...tooltipTransform
|
|
125
|
+
},
|
|
126
|
+
exit: {
|
|
127
|
+
opacity: 0,
|
|
128
|
+
y: isVertical ? 0 : 5,
|
|
129
|
+
scale: .9,
|
|
130
|
+
...tooltipTransform
|
|
131
|
+
},
|
|
132
|
+
transition: {
|
|
133
|
+
duration: .05,
|
|
134
|
+
ease: "easeOut"
|
|
135
|
+
},
|
|
136
|
+
className: cn("pointer-events-none absolute z-50", tooltipPositionClass),
|
|
137
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
138
|
+
className: "rounded-sm bg-gradient-to-t from-black/10 to-transparent px-[1px] pb-[2.5px] pt-[1px]",
|
|
139
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
140
|
+
className: cn("whitespace-nowrap rounded-sm px-2 py-1 font-canvas-figtree text-sm", !tooltipStyle?.backgroundColor && "bg-neutral-50", !tooltipStyle?.color && "text-neutral-600", tooltipClassName),
|
|
141
|
+
style: tooltipStyle,
|
|
142
|
+
children: displayLabel
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
}) });
|
|
146
|
+
};
|
|
147
|
+
const renderContent = () => {
|
|
148
|
+
if (displayMode === "labels") return renderLabel();
|
|
149
|
+
if (displayMode === "icons-labels") return /* @__PURE__ */ jsxs("div", {
|
|
150
|
+
className: "flex items-center gap-2",
|
|
151
|
+
children: [renderIcon(), renderLabel()]
|
|
152
|
+
});
|
|
153
|
+
if (displayMode === "compact") return /* @__PURE__ */ jsxs(Fragment, { children: [renderIcon(), renderTooltip()] });
|
|
154
|
+
if (isPushed && allowExpand) return /* @__PURE__ */ jsxs("div", {
|
|
155
|
+
className: "flex items-center gap-2",
|
|
156
|
+
children: [/* @__PURE__ */ jsx("div", { children: renderIcon() }), renderLabel(true)]
|
|
157
|
+
});
|
|
158
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [renderIcon(), renderTooltip()] });
|
|
159
|
+
};
|
|
160
|
+
return /* @__PURE__ */ jsx(motion.button, {
|
|
161
|
+
"aria-label": label,
|
|
162
|
+
className: cn(baseButtonClass, stateClass, buttonClassName),
|
|
163
|
+
style: computedButtonStyle,
|
|
164
|
+
onClick: handleClick,
|
|
165
|
+
onMouseEnter: () => setIsHovered(true),
|
|
166
|
+
onMouseLeave: () => setIsHovered(false),
|
|
167
|
+
whileTap: { scale: .95 },
|
|
168
|
+
transition: {
|
|
169
|
+
type: "spring",
|
|
170
|
+
stiffness: 400,
|
|
171
|
+
damping: 25
|
|
172
|
+
},
|
|
173
|
+
children: renderContent()
|
|
174
|
+
});
|
|
151
175
|
}
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
export { SingleButton as default };
|
|
152
179
|
//# sourceMappingURL=single-button.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"single-button.js","sourceRoot":"","sources":["../../../../src/components/canvas/navbar/single-button.tsx"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"single-button.js","names":[],"sources":["../../../../src/components/canvas/navbar/single-button.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport * as LucideIcons from \"lucide-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport type {\n NavbarDisplayMode,\n NavbarButtonConfig,\n NavbarTooltipConfig,\n} from \"../../../types\";\nimport { cn } from \"../../../lib/utils\";\n\ninterface SingleButtonProps {\n label: string;\n /** Lucide icon name or a custom icon component */\n icon: string | React.ComponentType<{ className?: string }>;\n onClick?: () => void;\n isPushed: boolean;\n link?: string;\n onDebouncedClick?: (callback: () => void) => void;\n /** Display mode for the button */\n displayMode?: NavbarDisplayMode;\n /** Button styling configuration */\n buttonConfig?: NavbarButtonConfig;\n /** Tooltip configuration */\n tooltipConfig?: NavbarTooltipConfig;\n /** Whether the navbar is in vertical layout */\n isVertical?: boolean;\n}\n\nexport default function SingleButton({\n label,\n icon,\n onClick,\n isPushed,\n link,\n onDebouncedClick,\n displayMode = \"icons\",\n buttonConfig = {},\n tooltipConfig = {},\n isVertical = false,\n}: SingleButtonProps) {\n const [isHovered, setIsHovered] = useState(false);\n const [showTag, setShowTag] = useState(false);\n const [copiedEmail, setCopiedEmail] = useState(false);\n\n const isLucideIconName = typeof icon === \"string\";\n const IconComponent = isLucideIconName\n ? (LucideIcons[icon as keyof typeof LucideIcons] as LucideIcons.LucideIcon | undefined)\n : icon;\n\n // Extract config values\n const {\n className: buttonClassName,\n style: buttonStyle,\n activeClassName,\n activeStyle,\n hoverClassName,\n hoverStyle,\n iconClassName,\n iconSize = 20,\n labelClassName,\n labelStyle,\n } = buttonConfig;\n\n const {\n disabled: tooltipDisabled = false,\n className: tooltipClassName,\n style: tooltipStyle,\n delay: tooltipDelay = 100,\n } = tooltipConfig;\n\n // Determine what to show based on display mode\n const showIcon = displayMode !== \"labels\";\n const allowExpand = displayMode === \"icons\"; // Only expand on active in icons mode\n const showTooltip = (displayMode === \"icons\" || displayMode === \"compact\") && !tooltipDisabled;\n\n // Validate icon component for modes that need it\n if (showIcon && !IconComponent) {\n throw new Error(\n \"A valid 'icon' prop is required (Lucide icon name or custom icon component).\",\n );\n }\n\n useEffect(() => {\n let timeoutId: NodeJS.Timeout;\n\n if (isHovered && showTooltip) {\n timeoutId = setTimeout(() => {\n setShowTag(true);\n }, tooltipDelay);\n } else {\n setShowTag(false);\n }\n\n return () => {\n clearTimeout(timeoutId);\n };\n }, [isHovered, showTooltip, tooltipDelay]);\n\n useEffect(() => {\n setShowTag(false);\n setIsHovered(false);\n }, [isPushed]);\n\n // Reset copied email state after 2 seconds\n useEffect(() => {\n if (copiedEmail) {\n const timeoutId = setTimeout(() => {\n setCopiedEmail(false);\n }, 2000);\n return () => clearTimeout(timeoutId);\n }\n }, [copiedEmail]);\n\n const performClick = () => {\n if (link) {\n window.open(link, \"_blank\", \"noopener,noreferrer\");\n return;\n }\n\n onClick?.();\n };\n\n const handleClick = () => {\n if (onDebouncedClick) {\n onDebouncedClick(performClick);\n } else {\n performClick();\n }\n };\n\n const displayLabel = copiedEmail ? \"Email copied!\" : label;\n\n // Compute button classes\n const baseButtonClass = \"relative flex items-center rounded-md p-2 text-neutral-500 transition-colors duration-200 focus:outline-none\";\n // Only apply default classes if no custom style is provided\n const stateClass = isPushed\n ? (activeClassName || (!activeStyle && \"bg-neutral-200\"))\n : isHovered\n ? (hoverClassName || (!hoverStyle && \"bg-neutral-100\"))\n : \"\";\n\n // Compute button styles\n const computedButtonStyle: React.CSSProperties = {\n ...buttonStyle,\n ...(isPushed && activeStyle),\n ...(isHovered && !isPushed && hoverStyle),\n };\n\n // Compute icon classes and styles\n const iconSizeStyle = { width: iconSize, height: iconSize };\n const baseIconClass = \"flex-shrink-0\";\n // Only apply default icon colors if no custom button color style is provided\n const hasCustomColor = buttonStyle?.color;\n const iconColorClass = hasCustomColor\n ? \"\"\n : isPushed\n ? \"text-neutral-700\"\n : \"text-neutral-500\";\n\n // Compute label classes\n const baseLabelClass = \"whitespace-nowrap font-canvas-figtree text-sm font-medium text-neutral-700\";\n\n // Tooltip position based on vertical layout\n const tooltipPositionClass = isVertical\n ? \"left-full top-1/2 -translate-y-1/2 ml-2\"\n : \"-top-10 left-1/2\";\n const tooltipTransform = isVertical\n ? { x: 0, y: \"-50%\" }\n : { x: \"-50%\" };\n\n // Render icon element\n const renderIcon = () => {\n if (!showIcon || !IconComponent) return null;\n return (\n <IconComponent\n className={cn(baseIconClass, iconColorClass, iconClassName)}\n style={iconSizeStyle}\n />\n );\n };\n\n // Render label element\n const renderLabel = (animated = false) => {\n if (animated) {\n return (\n <motion.span\n initial={{ opacity: 0, width: 0 }}\n animate={{ opacity: 1, width: \"auto\" }}\n exit={{ opacity: 0, width: 0 }}\n transition={{\n duration: 0.1,\n ease: \"easeInOut\",\n }}\n className={cn(\"overflow-hidden\", baseLabelClass, labelClassName)}\n style={labelStyle}\n >\n {displayLabel}\n </motion.span>\n );\n }\n return (\n <span\n className={cn(baseLabelClass, labelClassName)}\n style={labelStyle}\n >\n {displayLabel}\n </span>\n );\n };\n\n // Render tooltip\n const renderTooltip = () => {\n if (!showTooltip || !showTag || isPushed) return null;\n\n return (\n <AnimatePresence>\n <motion.div\n initial={{ opacity: 0, y: isVertical ? 0 : 5, scale: 0.9, ...tooltipTransform }}\n animate={{ opacity: 1, y: 0, scale: 1, ...tooltipTransform }}\n exit={{ opacity: 0, y: isVertical ? 0 : 5, scale: 0.9, ...tooltipTransform }}\n transition={{\n duration: 0.05,\n ease: \"easeOut\",\n }}\n className={cn(\"pointer-events-none absolute z-50\", tooltipPositionClass)}\n >\n <div className=\"rounded-sm bg-gradient-to-t from-black/10 to-transparent px-[1px] pb-[2.5px] pt-[1px]\">\n <div\n className={cn(\n \"whitespace-nowrap rounded-sm px-2 py-1 font-canvas-figtree text-sm\",\n !tooltipStyle?.backgroundColor && \"bg-neutral-50\",\n !tooltipStyle?.color && \"text-neutral-600\",\n tooltipClassName,\n )}\n style={tooltipStyle}\n >\n {displayLabel}\n </div>\n </div>\n </motion.div>\n </AnimatePresence>\n );\n };\n\n // Render based on display mode\n const renderContent = () => {\n // Labels only mode\n if (displayMode === \"labels\") {\n return renderLabel();\n }\n\n // Icons + labels always mode\n if (displayMode === \"icons-labels\") {\n return (\n <div className=\"flex items-center gap-2\">\n {renderIcon()}\n {renderLabel()}\n </div>\n );\n }\n\n // Compact mode - icons only, no expansion\n if (displayMode === \"compact\") {\n return (\n <>\n {renderIcon()}\n {renderTooltip()}\n </>\n );\n }\n\n // Icons mode (default) - expands on active\n if (isPushed && allowExpand) {\n return (\n <div className=\"flex items-center gap-2\">\n <div>{renderIcon()}</div>\n {renderLabel(true)}\n </div>\n );\n }\n\n return (\n <>\n {renderIcon()}\n {renderTooltip()}\n </>\n );\n };\n\n return (\n <motion.button\n aria-label={label}\n className={cn(baseButtonClass, stateClass, buttonClassName)}\n style={computedButtonStyle}\n onClick={handleClick}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n whileTap={{ scale: 0.95 }}\n transition={{\n type: \"spring\",\n stiffness: 400,\n damping: 25,\n }}\n >\n {renderContent()}\n </motion.button>\n );\n}\n"],"mappings":";;;;;;;AA4BA,SAAwB,aAAa,EACnC,OACA,MACA,SACA,UACA,MACA,kBACA,cAAc,SACd,eAAe,EAAE,EACjB,gBAAgB,EAAE,EAClB,aAAa,SACO;CACpB,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,aAAa,kBAAkB,SAAS,MAAM;CAGrD,MAAM,gBADmB,OAAO,SAAS,WAEpC,YAAY,QACb;CAGJ,MAAM,EACJ,WAAW,iBACX,OAAO,aACP,iBACA,aACA,gBACA,YACA,eACA,WAAW,IACX,gBACA,eACE;CAEJ,MAAM,EACJ,UAAU,kBAAkB,OAC5B,WAAW,kBACX,OAAO,cACP,OAAO,eAAe,QACpB;CAGJ,MAAM,WAAW,gBAAgB;CACjC,MAAM,cAAc,gBAAgB;CACpC,MAAM,eAAe,gBAAgB,WAAW,gBAAgB,cAAc,CAAC;AAG/E,KAAI,YAAY,CAAC,cACf,OAAM,IAAI,MACR,+EACD;AAGH,iBAAgB;EACd,IAAI;AAEJ,MAAI,aAAa,YACf,aAAY,iBAAiB;AAC3B,cAAW,KAAK;KACf,aAAa;MAEhB,YAAW,MAAM;AAGnB,eAAa;AACX,gBAAa,UAAU;;IAExB;EAAC;EAAW;EAAa;EAAa,CAAC;AAE1C,iBAAgB;AACd,aAAW,MAAM;AACjB,eAAa,MAAM;IAClB,CAAC,SAAS,CAAC;AAGd,iBAAgB;AACd,MAAI,aAAa;GACf,MAAM,YAAY,iBAAiB;AACjC,mBAAe,MAAM;MACpB,IAAK;AACR,gBAAa,aAAa,UAAU;;IAErC,CAAC,YAAY,CAAC;CAEjB,MAAM,qBAAqB;AACzB,MAAI,MAAM;AACR,UAAO,KAAK,MAAM,UAAU,sBAAsB;AAClD;;AAGF,aAAW;;CAGb,MAAM,oBAAoB;AACxB,MAAI,iBACF,kBAAiB,aAAa;MAE9B,eAAc;;CAIlB,MAAM,eAAe,cAAc,kBAAkB;CAGrD,MAAM,kBAAkB;CAExB,MAAM,aAAa,WACd,mBAAoB,CAAC,eAAe,mBACrC,YACG,kBAAmB,CAAC,cAAc,mBACnC;CAGN,MAAM,sBAA2C;EAC/C,GAAG;EACH,GAAI,YAAY;EAChB,GAAI,aAAa,CAAC,YAAY;EAC/B;CAGD,MAAM,gBAAgB;EAAE,OAAO;EAAU,QAAQ;EAAU;CAC3D,MAAM,gBAAgB;CAGtB,MAAM,iBADiB,aAAa,QAEhC,KACA,WACE,qBACA;CAGN,MAAM,iBAAiB;CAGvB,MAAM,uBAAuB,aACzB,4CACA;CACJ,MAAM,mBAAmB,aACrB;EAAE,GAAG;EAAG,GAAG;EAAQ,GACnB,EAAE,GAAG,QAAQ;CAGjB,MAAM,mBAAmB;AACvB,MAAI,CAAC,YAAY,CAAC,cAAe,QAAO;AACxC,SACE,oBAAC;GACC,WAAW,GAAG,eAAe,gBAAgB,cAAc;GAC3D,OAAO;IACP;;CAKN,MAAM,eAAe,WAAW,UAAU;AACxC,MAAI,SACF,QACE,oBAAC,OAAO;GACN,SAAS;IAAE,SAAS;IAAG,OAAO;IAAG;GACjC,SAAS;IAAE,SAAS;IAAG,OAAO;IAAQ;GACtC,MAAM;IAAE,SAAS;IAAG,OAAO;IAAG;GAC9B,YAAY;IACV,UAAU;IACV,MAAM;IACP;GACD,WAAW,GAAG,mBAAmB,gBAAgB,eAAe;GAChE,OAAO;aAEN;IACW;AAGlB,SACE,oBAAC;GACC,WAAW,GAAG,gBAAgB,eAAe;GAC7C,OAAO;aAEN;IACI;;CAKX,MAAM,sBAAsB;AAC1B,MAAI,CAAC,eAAe,CAAC,WAAW,SAAU,QAAO;AAEjD,SACE,oBAAC,6BACC,oBAAC,OAAO;GACN,SAAS;IAAE,SAAS;IAAG,GAAG,aAAa,IAAI;IAAG,OAAO;IAAK,GAAG;IAAkB;GAC/E,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG,OAAO;IAAG,GAAG;IAAkB;GAC5D,MAAM;IAAE,SAAS;IAAG,GAAG,aAAa,IAAI;IAAG,OAAO;IAAK,GAAG;IAAkB;GAC5E,YAAY;IACV,UAAU;IACV,MAAM;IACP;GACD,WAAW,GAAG,qCAAqC,qBAAqB;aAExE,oBAAC;IAAI,WAAU;cACb,oBAAC;KACC,WAAW,GACT,sEACA,CAAC,cAAc,mBAAmB,iBAClC,CAAC,cAAc,SAAS,oBACxB,iBACD;KACD,OAAO;eAEN;MACG;KACF;IACK,GACG;;CAKtB,MAAM,sBAAsB;AAE1B,MAAI,gBAAgB,SAClB,QAAO,aAAa;AAItB,MAAI,gBAAgB,eAClB,QACE,qBAAC;GAAI,WAAU;cACZ,YAAY,EACZ,aAAa;IACV;AAKV,MAAI,gBAAgB,UAClB,QACE,4CACG,YAAY,EACZ,eAAe,IACf;AAKP,MAAI,YAAY,YACd,QACE,qBAAC;GAAI,WAAU;cACb,oBAAC,mBAAK,YAAY,GAAO,EACxB,YAAY,KAAK;IACd;AAIV,SACE,4CACG,YAAY,EACZ,eAAe,IACf;;AAIP,QACE,oBAAC,OAAO;EACN,cAAY;EACZ,WAAW,GAAG,iBAAiB,YAAY,gBAAgB;EAC3D,OAAO;EACP,SAAS;EACT,oBAAoB,aAAa,KAAK;EACtC,oBAAoB,aAAa,MAAM;EACvC,UAAU,EAAE,OAAO,KAAM;EACzB,YAAY;GACV,MAAM;GACN,WAAW;GACX,SAAS;GACV;YAEA,eAAe;GACF"}
|
|
@@ -1,86 +1,125 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { useCanvasContext } from "../../contexts/CanvasContext.js";
|
|
2
|
+
import { TOOLBAR_OPACITY_POS_EPS, TOOLBAR_OPACITY_SCALE_EPS } from "../../lib/constants.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { motion, useTransform } from "framer-motion";
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/components/canvas/toolbar.tsx
|
|
7
9
|
const positionStyles = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
"top-left": "left-4 top-6 sm:top-4",
|
|
11
|
+
"top-right": "right-4 top-6 sm:top-4",
|
|
12
|
+
"bottom-left": "left-4 bottom-6 sm:bottom-4",
|
|
13
|
+
"bottom-right": "right-4 bottom-6 sm:bottom-4"
|
|
12
14
|
};
|
|
13
|
-
const Toolbar = ({ homeCoordinates = {
|
|
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
|
-
|
|
15
|
+
const Toolbar = ({ homeCoordinates = {
|
|
16
|
+
x: 0,
|
|
17
|
+
y: 0
|
|
18
|
+
}, config = {} }) => {
|
|
19
|
+
const { x, y, scale } = useCanvasContext();
|
|
20
|
+
const [hasMounted, setHasMounted] = useState(false);
|
|
21
|
+
const { display = "both", position = "top-left", disableAutoHide = false, className, coordinatesClassName, scaleClassName, separatorClassName, style, coordinatesStyle, scaleStyle, separator = "|", separatorGap, coordinatesFormat, scaleFormat } = config;
|
|
22
|
+
const separatorStyle = separatorGap ? { marginInline: typeof separatorGap === "number" ? `${separatorGap}px` : separatorGap } : void 0;
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setHasMounted(true);
|
|
25
|
+
}, []);
|
|
26
|
+
const rawDx = useTransform([x, scale], ([lx, ls]) => -(lx / ls) + homeCoordinates.x);
|
|
27
|
+
const rawDy = useTransform([y, scale], ([ly, ls]) => -(ly / ls) + homeCoordinates.y);
|
|
28
|
+
const displayX = useTransform(rawDx, (v) => Math.round(v).toString());
|
|
29
|
+
const displayY = useTransform(rawDy, (v) => Math.round(v).toString());
|
|
30
|
+
const displayScale = useTransform(scale, (v) => v.toFixed(2));
|
|
31
|
+
const [currentX, setCurrentX] = useState(0);
|
|
32
|
+
const [currentY, setCurrentY] = useState(0);
|
|
33
|
+
const [currentScale, setCurrentScale] = useState(1);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const unsubX = rawDx.on("change", (v) => setCurrentX(Math.round(v)));
|
|
36
|
+
const unsubY = rawDy.on("change", (v) => setCurrentY(Math.round(v)));
|
|
37
|
+
const unsubScale = scale.on("change", (v) => setCurrentScale(v));
|
|
38
|
+
return () => {
|
|
39
|
+
unsubX();
|
|
40
|
+
unsubY();
|
|
41
|
+
unsubScale();
|
|
42
|
+
};
|
|
43
|
+
}, [
|
|
44
|
+
rawDx,
|
|
45
|
+
rawDy,
|
|
46
|
+
scale
|
|
47
|
+
]);
|
|
48
|
+
const opacity = useTransform([
|
|
49
|
+
rawDx,
|
|
50
|
+
rawDy,
|
|
51
|
+
scale
|
|
52
|
+
], ([dx, dy, ls]) => {
|
|
53
|
+
if (disableAutoHide) return 1;
|
|
54
|
+
return Math.abs(dx) < TOOLBAR_OPACITY_POS_EPS && Math.abs(dy) < TOOLBAR_OPACITY_POS_EPS && Math.abs(ls - 1) < TOOLBAR_OPACITY_SCALE_EPS ? 0 : 1;
|
|
55
|
+
});
|
|
56
|
+
const handlePointerDown = (e) => e.stopPropagation();
|
|
57
|
+
const showCoordinates = display === "coordinates" || display === "both";
|
|
58
|
+
const showScale = display === "scale" || display === "both";
|
|
59
|
+
const showSeparator = display === "both";
|
|
60
|
+
const formattedCoordinates = useMemo(() => {
|
|
61
|
+
if (coordinatesFormat) return coordinatesFormat(currentX, currentY);
|
|
62
|
+
return null;
|
|
63
|
+
}, [
|
|
64
|
+
coordinatesFormat,
|
|
65
|
+
currentX,
|
|
66
|
+
currentY
|
|
67
|
+
]);
|
|
68
|
+
const formattedScale = useMemo(() => {
|
|
69
|
+
if (scaleFormat) return scaleFormat(currentScale);
|
|
70
|
+
return null;
|
|
71
|
+
}, [scaleFormat, currentScale]);
|
|
72
|
+
const placeholderContent = useMemo(() => {
|
|
73
|
+
const parts = [];
|
|
74
|
+
if (showCoordinates) parts.push("(0, 0)");
|
|
75
|
+
if (showSeparator) parts.push(separator);
|
|
76
|
+
if (showScale) parts.push("1.00x");
|
|
77
|
+
return parts.join("");
|
|
78
|
+
}, [
|
|
79
|
+
showCoordinates,
|
|
80
|
+
showScale,
|
|
81
|
+
showSeparator,
|
|
82
|
+
separator
|
|
83
|
+
]);
|
|
84
|
+
return /* @__PURE__ */ jsx(motion.div, {
|
|
85
|
+
className: cn("absolute z-[1000] cursor-default select-none rounded-[10px] border border-border bg-canvas-offwhite p-2 font-mono text-xs text-canvas-heavy shadow-[0_6px_12px_rgba(0,0,0,0.10)] md:text-sm", positionStyles[position], className),
|
|
86
|
+
onPointerDown: handlePointerDown,
|
|
87
|
+
"data-toolbar-button": true,
|
|
88
|
+
style: {
|
|
89
|
+
opacity,
|
|
90
|
+
...style
|
|
91
|
+
},
|
|
92
|
+
children: hasMounted ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
93
|
+
showCoordinates && /* @__PURE__ */ jsx("span", {
|
|
94
|
+
className: coordinatesClassName,
|
|
95
|
+
style: coordinatesStyle,
|
|
96
|
+
children: formattedCoordinates !== null ? formattedCoordinates : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
97
|
+
"(",
|
|
98
|
+
/* @__PURE__ */ jsx(motion.span, { children: displayX }),
|
|
99
|
+
",",
|
|
100
|
+
" ",
|
|
101
|
+
/* @__PURE__ */ jsx(motion.span, { children: displayY }),
|
|
102
|
+
")"
|
|
103
|
+
] })
|
|
104
|
+
}),
|
|
105
|
+
showSeparator && /* @__PURE__ */ jsx("span", {
|
|
106
|
+
className: cn("text-canvas-light", separatorClassName),
|
|
107
|
+
style: separatorStyle,
|
|
108
|
+
children: separator
|
|
109
|
+
}),
|
|
110
|
+
showScale && /* @__PURE__ */ jsx("span", {
|
|
111
|
+
className: scaleClassName,
|
|
112
|
+
style: scaleStyle,
|
|
113
|
+
children: formattedScale !== null ? formattedScale : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(motion.span, { children: displayScale }), "x"] })
|
|
114
|
+
})
|
|
115
|
+
] }) : /* @__PURE__ */ jsx("span", {
|
|
116
|
+
style: { opacity: 0 },
|
|
117
|
+
children: placeholderContent
|
|
118
|
+
})
|
|
119
|
+
});
|
|
84
120
|
};
|
|
85
|
-
|
|
121
|
+
var toolbar_default = Toolbar;
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
export { toolbar_default as default };
|
|
86
125
|
//# sourceMappingURL=toolbar.js.map
|