@hunterchen/canvas 0.8.0 → 0.10.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.
Files changed (48) hide show
  1. package/dist/components/canvas/backgrounds.js +67 -38
  2. package/dist/components/canvas/backgrounds.js.map +1 -1
  3. package/dist/components/canvas/canvas.d.ts +2 -0
  4. package/dist/components/canvas/canvas.d.ts.map +1 -1
  5. package/dist/components/canvas/canvas.js +459 -387
  6. package/dist/components/canvas/canvas.js.map +1 -1
  7. package/dist/components/canvas/component.js +108 -174
  8. package/dist/components/canvas/component.js.map +1 -1
  9. package/dist/components/canvas/draggable.js +168 -151
  10. package/dist/components/canvas/draggable.js.map +1 -1
  11. package/dist/components/canvas/navbar/index.js +164 -142
  12. package/dist/components/canvas/navbar/index.js.map +1 -1
  13. package/dist/components/canvas/navbar/single-button.js +176 -149
  14. package/dist/components/canvas/navbar/single-button.js.map +1 -1
  15. package/dist/components/canvas/toolbar.js +121 -82
  16. package/dist/components/canvas/toolbar.js.map +1 -1
  17. package/dist/components/canvas/wrapper.js +127 -99
  18. package/dist/components/canvas/wrapper.js.map +1 -1
  19. package/dist/contexts/CanvasContext.js +25 -17
  20. package/dist/contexts/CanvasContext.js.map +1 -1
  21. package/dist/contexts/PerformanceContext.js +51 -50
  22. package/dist/contexts/PerformanceContext.js.map +1 -1
  23. package/dist/hooks/usePerformanceMode.js +36 -37
  24. package/dist/hooks/usePerformanceMode.js.map +1 -1
  25. package/dist/hooks/useWindowDimensions.js +22 -18
  26. package/dist/hooks/useWindowDimensions.js.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +17 -21
  30. package/dist/lib/canvas.js +65 -72
  31. package/dist/lib/canvas.js.map +1 -1
  32. package/dist/lib/constants.js +78 -92
  33. package/dist/lib/constants.js.map +1 -1
  34. package/dist/lib/utils.js +10 -5
  35. package/dist/lib/utils.js.map +1 -1
  36. package/dist/types/index.d.ts +22 -0
  37. package/dist/types/index.d.ts.map +1 -1
  38. package/dist/utils/performance.js +18 -23
  39. package/dist/utils/performance.js.map +1 -1
  40. package/package.json +7 -21
  41. package/src/components/canvas/canvas.tsx +16 -4
  42. package/src/index.ts +1 -0
  43. package/src/types/index.ts +24 -0
  44. package/dist/components/canvas/offest.js +0 -12
  45. package/dist/components/canvas/offest.js.map +0 -1
  46. package/dist/index.js.map +0 -1
  47. package/dist/types/index.js +0 -6
  48. package/dist/types/index.js.map +0 -1
@@ -1,152 +1,179 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
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 { cn } from "../../../lib/utils";
6
- export default function SingleButton({ label, icon, onClick, isPushed, link, onDebouncedClick, displayMode = "icons", buttonConfig = {}, tooltipConfig = {}, isVertical = false, }) {
7
- const [isHovered, setIsHovered] = useState(false);
8
- const [showTag, setShowTag] = useState(false);
9
- const [copiedEmail, setCopiedEmail] = useState(false);
10
- const isLucideIconName = typeof icon === "string";
11
- const IconComponent = isLucideIconName
12
- ? LucideIcons[icon]
13
- : icon;
14
- // Extract config values
15
- const { className: buttonClassName, style: buttonStyle, activeClassName, activeStyle, hoverClassName, hoverStyle, iconClassName, iconSize = 20, labelClassName, labelStyle, } = buttonConfig;
16
- const { disabled: tooltipDisabled = false, className: tooltipClassName, style: tooltipStyle, delay: tooltipDelay = 100, } = tooltipConfig;
17
- // Determine what to show based on display mode
18
- const showIcon = displayMode !== "labels";
19
- const allowExpand = displayMode === "icons"; // Only expand on active in icons mode
20
- const showTooltip = (displayMode === "icons" || displayMode === "compact") && !tooltipDisabled;
21
- // Validate icon component for modes that need it
22
- if (showIcon && !IconComponent) {
23
- throw new Error("A valid 'icon' prop is required (Lucide icon name or custom icon component).");
24
- }
25
- useEffect(() => {
26
- let timeoutId;
27
- if (isHovered && showTooltip) {
28
- timeoutId = setTimeout(() => {
29
- setShowTag(true);
30
- }, tooltipDelay);
31
- }
32
- else {
33
- setShowTag(false);
34
- }
35
- return () => {
36
- clearTimeout(timeoutId);
37
- };
38
- }, [isHovered, showTooltip, tooltipDelay]);
39
- useEffect(() => {
40
- setShowTag(false);
41
- setIsHovered(false);
42
- }, [isPushed]);
43
- // Reset copied email state after 2 seconds
44
- useEffect(() => {
45
- if (copiedEmail) {
46
- const timeoutId = setTimeout(() => {
47
- setCopiedEmail(false);
48
- }, 2000);
49
- return () => clearTimeout(timeoutId);
50
- }
51
- }, [copiedEmail]);
52
- const performClick = () => {
53
- if (link) {
54
- window.open(link, "_blank", "noopener,noreferrer");
55
- return;
56
- }
57
- onClick?.();
58
- };
59
- const handleClick = () => {
60
- if (onDebouncedClick) {
61
- onDebouncedClick(performClick);
62
- }
63
- else {
64
- performClick();
65
- }
66
- };
67
- const displayLabel = copiedEmail ? "Email copied!" : label;
68
- // Compute button classes
69
- const baseButtonClass = "relative flex items-center rounded-md p-2 text-neutral-500 transition-colors duration-200 focus:outline-none";
70
- // Only apply default classes if no custom style is provided
71
- const stateClass = isPushed
72
- ? (activeClassName || (!activeStyle && "bg-neutral-200"))
73
- : isHovered
74
- ? (hoverClassName || (!hoverStyle && "bg-neutral-100"))
75
- : "";
76
- // Compute button styles
77
- const computedButtonStyle = {
78
- ...buttonStyle,
79
- ...(isPushed && activeStyle),
80
- ...(isHovered && !isPushed && hoverStyle),
81
- };
82
- // Compute icon classes and styles
83
- const iconSizeStyle = { width: iconSize, height: iconSize };
84
- const baseIconClass = "flex-shrink-0";
85
- // Only apply default icon colors if no custom button color style is provided
86
- const hasCustomColor = buttonStyle?.color;
87
- const iconColorClass = hasCustomColor
88
- ? ""
89
- : isPushed
90
- ? "text-neutral-700"
91
- : "text-neutral-500";
92
- // Compute label classes
93
- const baseLabelClass = "whitespace-nowrap font-canvas-figtree text-sm font-medium text-neutral-700";
94
- // Tooltip position based on vertical layout
95
- const tooltipPositionClass = isVertical
96
- ? "left-full top-1/2 -translate-y-1/2 ml-2"
97
- : "-top-10 left-1/2";
98
- const tooltipTransform = isVertical
99
- ? { x: 0, y: "-50%" }
100
- : { x: "-50%" };
101
- // Render icon element
102
- const renderIcon = () => {
103
- if (!showIcon || !IconComponent)
104
- return null;
105
- return (_jsx(IconComponent, { className: cn(baseIconClass, iconColorClass, iconClassName), style: iconSizeStyle }));
106
- };
107
- // Render label element
108
- const renderLabel = (animated = false) => {
109
- if (animated) {
110
- return (_jsx(motion.span, { initial: { opacity: 0, width: 0 }, animate: { opacity: 1, width: "auto" }, exit: { opacity: 0, width: 0 }, transition: {
111
- duration: 0.1,
112
- ease: "easeInOut",
113
- }, className: cn("overflow-hidden", baseLabelClass, labelClassName), style: labelStyle, children: displayLabel }));
114
- }
115
- return (_jsx("span", { className: cn(baseLabelClass, labelClassName), style: labelStyle, children: displayLabel }));
116
- };
117
- // Render tooltip
118
- const renderTooltip = () => {
119
- if (!showTooltip || !showTag || isPushed)
120
- return null;
121
- return (_jsx(AnimatePresence, { children: _jsx(motion.div, { initial: { opacity: 0, y: isVertical ? 0 : 5, scale: 0.9, ...tooltipTransform }, animate: { opacity: 1, y: 0, scale: 1, ...tooltipTransform }, exit: { opacity: 0, y: isVertical ? 0 : 5, scale: 0.9, ...tooltipTransform }, transition: {
122
- duration: 0.05,
123
- ease: "easeOut",
124
- }, className: cn("pointer-events-none absolute z-50", tooltipPositionClass), children: _jsx("div", { className: "rounded-sm bg-gradient-to-t from-black/10 to-transparent px-[1px] pb-[2.5px] pt-[1px]", children: _jsx("div", { 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), style: tooltipStyle, children: displayLabel }) }) }) }));
125
- };
126
- // Render based on display mode
127
- const renderContent = () => {
128
- // Labels only mode
129
- if (displayMode === "labels") {
130
- return renderLabel();
131
- }
132
- // Icons + labels always mode
133
- if (displayMode === "icons-labels") {
134
- return (_jsxs("div", { className: "flex items-center gap-2", children: [renderIcon(), renderLabel()] }));
135
- }
136
- // Compact mode - icons only, no expansion
137
- if (displayMode === "compact") {
138
- return (_jsxs(_Fragment, { children: [renderIcon(), renderTooltip()] }));
139
- }
140
- // Icons mode (default) - expands on active
141
- if (isPushed && allowExpand) {
142
- return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { children: renderIcon() }), renderLabel(true)] }));
143
- }
144
- return (_jsxs(_Fragment, { children: [renderIcon(), renderTooltip()] }));
145
- };
146
- return (_jsx(motion.button, { "aria-label": label, className: cn(baseButtonClass, stateClass, buttonClassName), style: computedButtonStyle, onClick: handleClick, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), whileTap: { scale: 0.95 }, transition: {
147
- type: "spring",
148
- stiffness: 400,
149
- damping: 25,
150
- }, children: renderContent() }));
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":"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"}
@@ -1,86 +1,125 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useTransform, motion } from "framer-motion";
3
- import { useEffect, useState, useMemo } from "react";
4
- import { useCanvasContext } from "../../contexts/CanvasContext";
5
- import { TOOLBAR_OPACITY_POS_EPS, TOOLBAR_OPACITY_SCALE_EPS, } from "../../lib/constants";
6
- import { cn } from "../../lib/utils";
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
- "top-left": "left-4 top-6 sm:top-4",
9
- "top-right": "right-4 top-6 sm:top-4",
10
- "bottom-left": "left-4 bottom-6 sm:bottom-4",
11
- "bottom-right": "right-4 bottom-6 sm:bottom-4",
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 = { x: 0, y: 0 }, config = {}, }) => {
14
- const { x, y, scale } = useCanvasContext();
15
- const [hasMounted, setHasMounted] = useState(false);
16
- const { display = "both", position = "top-left", disableAutoHide = false, className, coordinatesClassName, scaleClassName, separatorClassName, style, coordinatesStyle, scaleStyle, separator = "|", separatorGap, coordinatesFormat, scaleFormat, } = config;
17
- const separatorStyle = separatorGap
18
- ? {
19
- marginInline: typeof separatorGap === "number" ? `${separatorGap}px` : separatorGap,
20
- }
21
- : undefined;
22
- useEffect(() => {
23
- setHasMounted(true);
24
- }, []);
25
- // numeric MotionValues
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
- // formatted MotionValues for default display
29
- const displayX = useTransform(rawDx, (v) => Math.round(v).toString());
30
- const displayY = useTransform(rawDy, (v) => Math.round(v).toString());
31
- const displayScale = useTransform(scale, (v) => v.toFixed(2));
32
- // For custom formatters, we need to use state to track values
33
- const [currentX, setCurrentX] = useState(0);
34
- const [currentY, setCurrentY] = useState(0);
35
- const [currentScale, setCurrentScale] = useState(1);
36
- useEffect(() => {
37
- const unsubX = rawDx.on("change", (v) => setCurrentX(Math.round(v)));
38
- const unsubY = rawDy.on("change", (v) => setCurrentY(Math.round(v)));
39
- const unsubScale = scale.on("change", (v) => setCurrentScale(v));
40
- return () => {
41
- unsubX();
42
- unsubY();
43
- unsubScale();
44
- };
45
- }, [rawDx, rawDy, scale]);
46
- const opacity = useTransform([rawDx, rawDy, scale], ([dx, dy, ls]) => {
47
- if (disableAutoHide)
48
- return 1;
49
- return Math.abs(dx) < TOOLBAR_OPACITY_POS_EPS &&
50
- Math.abs(dy) < TOOLBAR_OPACITY_POS_EPS &&
51
- Math.abs(ls - 1) < TOOLBAR_OPACITY_SCALE_EPS
52
- ? 0
53
- : 1;
54
- });
55
- const handlePointerDown = (e) => e.stopPropagation();
56
- const showCoordinates = display === "coordinates" || display === "both";
57
- const showScale = display === "scale" || display === "both";
58
- const showSeparator = display === "both";
59
- // Compute formatted values
60
- const formattedCoordinates = useMemo(() => {
61
- if (coordinatesFormat) {
62
- return coordinatesFormat(currentX, currentY);
63
- }
64
- return null; // Will use motion spans for default
65
- }, [coordinatesFormat, currentX, currentY]);
66
- const formattedScale = useMemo(() => {
67
- if (scaleFormat) {
68
- return scaleFormat(currentScale);
69
- }
70
- return null; // Will use motion span for default
71
- }, [scaleFormat, currentScale]);
72
- // Placeholder content for SSR/initial render
73
- const placeholderContent = useMemo(() => {
74
- const parts = [];
75
- if (showCoordinates)
76
- parts.push("(0, 0)");
77
- if (showSeparator)
78
- parts.push(separator);
79
- if (showScale)
80
- parts.push("1.00x");
81
- return parts.join("");
82
- }, [showCoordinates, showScale, showSeparator, separator]);
83
- return (_jsx(motion.div, { 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), onPointerDown: handlePointerDown, "data-toolbar-button": true, style: { opacity, ...style }, children: hasMounted ? (_jsxs(_Fragment, { children: [showCoordinates && (_jsx("span", { className: coordinatesClassName, style: coordinatesStyle, children: formattedCoordinates !== null ? (formattedCoordinates) : (_jsxs(_Fragment, { children: ["(", _jsx(motion.span, { children: displayX }), ",", " ", _jsx(motion.span, { children: displayY }), ")"] })) })), showSeparator && (_jsx("span", { className: cn("text-canvas-light", separatorClassName), style: separatorStyle, children: separator })), showScale && (_jsx("span", { className: scaleClassName, style: scaleStyle, children: formattedScale !== null ? (formattedScale) : (_jsxs(_Fragment, { children: [_jsx(motion.span, { children: displayScale }), "x"] })) }))] })) : (_jsx("span", { style: { opacity: 0 }, children: placeholderContent })) }));
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
- export default Toolbar;
121
+ var toolbar_default = Toolbar;
122
+
123
+ //#endregion
124
+ export { toolbar_default as default };
86
125
  //# sourceMappingURL=toolbar.js.map