@hunterchen/canvas 0.7.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/README.md +196 -8
- package/dist/components/canvas/backgrounds.d.ts +4 -4
- package/dist/components/canvas/backgrounds.d.ts.map +1 -1
- package/dist/components/canvas/backgrounds.js +68 -39
- package/dist/components/canvas/backgrounds.js.map +1 -1
- package/dist/components/canvas/canvas.d.ts +3 -1
- package/dist/components/canvas/canvas.d.ts.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.d.ts +4 -2
- package/dist/components/canvas/navbar/index.d.ts.map +1 -1
- package/dist/components/canvas/navbar/index.js +168 -101
- package/dist/components/canvas/navbar/index.js.map +1 -1
- package/dist/components/canvas/navbar/single-button.d.ts +10 -1
- package/dist/components/canvas/navbar/single-button.d.ts.map +1 -1
- package/dist/components/canvas/navbar/single-button.js +176 -69
- 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.d.ts +1 -1
- package/dist/index.d.ts.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/styles.css +1 -1
- package/dist/types/index.d.ts +69 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/performance.js +18 -23
- package/dist/utils/performance.js.map +1 -1
- package/package.json +7 -21
- package/src/components/canvas/backgrounds.tsx +7 -7
- package/src/components/canvas/canvas.tsx +9 -2
- package/src/components/canvas/navbar/index.tsx +91 -15
- package/src/components/canvas/navbar/single-button.tsx +210 -56
- package/src/index.ts +5 -0
- package/src/styles.css +15 -13
- package/src/types/index.ts +91 -0
- 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,7 +1,7 @@
|
|
|
1
1
|
import { motion, useMotionValueEvent } from "framer-motion";
|
|
2
2
|
import { useState, useRef, useEffect, useCallback, useMemo } from "react";
|
|
3
3
|
import SingleButton from "./single-button";
|
|
4
|
-
import type { NavItem } from "../../../types";
|
|
4
|
+
import type { NavItem, NavbarConfig, NavbarPosition } from "../../../types";
|
|
5
5
|
import { useCanvasContext } from "../../../contexts/CanvasContext";
|
|
6
6
|
import useWindowDimensions from "../../../hooks/useWindowDimensions";
|
|
7
7
|
import { usePerformanceMode } from "../../../hooks/usePerformanceMode";
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
RESPONSIVE_ZOOM_MAP,
|
|
14
14
|
NAVBAR_DEBOUNCE_MS,
|
|
15
15
|
} from "../../../lib/constants";
|
|
16
|
+
import { cn } from "../../../lib/utils";
|
|
16
17
|
|
|
17
18
|
interface NavbarProps {
|
|
18
19
|
panToOffset: (
|
|
@@ -23,12 +24,50 @@ interface NavbarProps {
|
|
|
23
24
|
onReset: () => void;
|
|
24
25
|
/** Array of navigation items defining sections, their icons, and coordinates */
|
|
25
26
|
items: NavItem[];
|
|
27
|
+
/** Navbar configuration options */
|
|
28
|
+
config?: NavbarConfig;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
const positionStyles: Record<NavbarPosition, React.CSSProperties> = {
|
|
32
|
+
top: {
|
|
33
|
+
top: "1rem",
|
|
34
|
+
bottom: "auto",
|
|
35
|
+
left: "50%",
|
|
36
|
+
transform: "translateX(-50%)",
|
|
37
|
+
},
|
|
38
|
+
bottom: {
|
|
39
|
+
bottom: "1rem",
|
|
40
|
+
top: "auto",
|
|
41
|
+
left: "50%",
|
|
42
|
+
transform: "translateX(-50%)",
|
|
43
|
+
},
|
|
44
|
+
left: {
|
|
45
|
+
left: "1rem",
|
|
46
|
+
right: "auto",
|
|
47
|
+
top: "50%",
|
|
48
|
+
transform: "translateY(-50%)",
|
|
49
|
+
},
|
|
50
|
+
right: {
|
|
51
|
+
right: "1rem",
|
|
52
|
+
left: "auto",
|
|
53
|
+
top: "50%",
|
|
54
|
+
transform: "translateY(-50%)",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Responsive position adjustments (mobile vs desktop)
|
|
59
|
+
const responsivePositionClasses: Record<NavbarPosition, string> = {
|
|
60
|
+
top: "top-12 md:top-4",
|
|
61
|
+
bottom: "bottom-12 md:bottom-4",
|
|
62
|
+
left: "left-4",
|
|
63
|
+
right: "right-4",
|
|
64
|
+
};
|
|
65
|
+
|
|
28
66
|
export default function Navbar({
|
|
29
67
|
panToOffset,
|
|
30
68
|
onReset,
|
|
31
69
|
items,
|
|
70
|
+
config = {},
|
|
32
71
|
}: NavbarProps) {
|
|
33
72
|
const { x, y, scale, animationStage, setNextTargetSection } =
|
|
34
73
|
useCanvasContext();
|
|
@@ -50,6 +89,20 @@ export default function Navbar({
|
|
|
50
89
|
// Derive debounce duration from performance mode
|
|
51
90
|
const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;
|
|
52
91
|
|
|
92
|
+
// Extract config values
|
|
93
|
+
const {
|
|
94
|
+
display = "icons",
|
|
95
|
+
position = "bottom",
|
|
96
|
+
className,
|
|
97
|
+
style,
|
|
98
|
+
buttonConfig,
|
|
99
|
+
tooltipConfig,
|
|
100
|
+
gap,
|
|
101
|
+
padding,
|
|
102
|
+
} = config;
|
|
103
|
+
|
|
104
|
+
const isVertical = position === "left" || position === "right";
|
|
105
|
+
|
|
53
106
|
// Find the home section from items
|
|
54
107
|
const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);
|
|
55
108
|
|
|
@@ -141,24 +194,43 @@ export default function Navbar({
|
|
|
141
194
|
};
|
|
142
195
|
}, [handlePan, animationStage, homeItem]);
|
|
143
196
|
|
|
197
|
+
// Compute container styles (positioning only)
|
|
198
|
+
const containerStyle: React.CSSProperties = {
|
|
199
|
+
position: "fixed",
|
|
200
|
+
zIndex: 1000,
|
|
201
|
+
pointerEvents: "auto",
|
|
202
|
+
display: "flex",
|
|
203
|
+
justifyContent: "center",
|
|
204
|
+
alignItems: "center",
|
|
205
|
+
...positionStyles[position],
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Compute inner container styles (visual styling)
|
|
209
|
+
const innerStyle: React.CSSProperties = {
|
|
210
|
+
...(gap !== undefined && { gap: `${gap}px` }),
|
|
211
|
+
...(padding !== undefined && { padding: `${padding}px` }),
|
|
212
|
+
...(isVertical && { flexDirection: "column" }),
|
|
213
|
+
...style,
|
|
214
|
+
};
|
|
215
|
+
|
|
144
216
|
return (
|
|
145
217
|
<div
|
|
146
|
-
className=
|
|
147
|
-
style={
|
|
148
|
-
position: "fixed",
|
|
149
|
-
left: "50%",
|
|
150
|
-
transform: "translateX(-50%)",
|
|
151
|
-
zIndex: 1000,
|
|
152
|
-
pointerEvents: "auto",
|
|
153
|
-
display: "flex",
|
|
154
|
-
justifyContent: "center",
|
|
155
|
-
alignItems: "center",
|
|
156
|
-
}}
|
|
218
|
+
className={responsivePositionClasses[position]}
|
|
219
|
+
style={containerStyle}
|
|
157
220
|
>
|
|
158
221
|
{/* padding to prevent edge bug */}
|
|
159
|
-
<div className="px-4 md:px-8">
|
|
160
|
-
<motion.div
|
|
161
|
-
|
|
222
|
+
<div className={isVertical ? "py-4 md:py-8" : "px-4 md:px-8"}>
|
|
223
|
+
<motion.div
|
|
224
|
+
className={cn(
|
|
225
|
+
"flex select-none items-center justify-center gap-1 rounded-[10px] border p-1 shadow-[0_6px_12px_rgba(0,0,0,0.08)]",
|
|
226
|
+
!style?.backgroundColor && "bg-white",
|
|
227
|
+
!style?.borderColor && "border-neutral-200",
|
|
228
|
+
isVertical && "flex-col",
|
|
229
|
+
className,
|
|
230
|
+
)}
|
|
231
|
+
style={innerStyle}
|
|
232
|
+
>
|
|
233
|
+
<div className={cn("flex items-center gap-1", isVertical && "flex-col")}>
|
|
162
234
|
{items.map((item) => (
|
|
163
235
|
<SingleButton
|
|
164
236
|
key={item.id}
|
|
@@ -167,6 +239,10 @@ export default function Navbar({
|
|
|
167
239
|
onClick={() => handlePan(item)}
|
|
168
240
|
isPushed={expandedButton === item.id}
|
|
169
241
|
onDebouncedClick={handleDebouncedClick}
|
|
242
|
+
displayMode={display}
|
|
243
|
+
buttonConfig={buttonConfig}
|
|
244
|
+
tooltipConfig={tooltipConfig}
|
|
245
|
+
isVertical={isVertical}
|
|
170
246
|
/>
|
|
171
247
|
))}
|
|
172
248
|
</div>
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import * as LucideIcons from "lucide-react";
|
|
3
3
|
import { AnimatePresence, motion } from "framer-motion";
|
|
4
|
+
import type {
|
|
5
|
+
NavbarDisplayMode,
|
|
6
|
+
NavbarButtonConfig,
|
|
7
|
+
NavbarTooltipConfig,
|
|
8
|
+
} from "../../../types";
|
|
9
|
+
import { cn } from "../../../lib/utils";
|
|
4
10
|
|
|
5
11
|
interface SingleButtonProps {
|
|
6
12
|
label: string;
|
|
@@ -10,6 +16,14 @@ interface SingleButtonProps {
|
|
|
10
16
|
isPushed: boolean;
|
|
11
17
|
link?: string;
|
|
12
18
|
onDebouncedClick?: (callback: () => void) => void;
|
|
19
|
+
/** Display mode for the button */
|
|
20
|
+
displayMode?: NavbarDisplayMode;
|
|
21
|
+
/** Button styling configuration */
|
|
22
|
+
buttonConfig?: NavbarButtonConfig;
|
|
23
|
+
/** Tooltip configuration */
|
|
24
|
+
tooltipConfig?: NavbarTooltipConfig;
|
|
25
|
+
/** Whether the navbar is in vertical layout */
|
|
26
|
+
isVertical?: boolean;
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
export default function SingleButton({
|
|
@@ -19,17 +33,48 @@ export default function SingleButton({
|
|
|
19
33
|
isPushed,
|
|
20
34
|
link,
|
|
21
35
|
onDebouncedClick,
|
|
36
|
+
displayMode = "icons",
|
|
37
|
+
buttonConfig = {},
|
|
38
|
+
tooltipConfig = {},
|
|
39
|
+
isVertical = false,
|
|
22
40
|
}: SingleButtonProps) {
|
|
23
41
|
const [isHovered, setIsHovered] = useState(false);
|
|
24
42
|
const [showTag, setShowTag] = useState(false);
|
|
25
43
|
const [copiedEmail, setCopiedEmail] = useState(false);
|
|
44
|
+
|
|
26
45
|
const isLucideIconName = typeof icon === "string";
|
|
27
46
|
const IconComponent = isLucideIconName
|
|
28
47
|
? (LucideIcons[icon as keyof typeof LucideIcons] as LucideIcons.LucideIcon | undefined)
|
|
29
48
|
: icon;
|
|
30
|
-
const TagDelay = 100;
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
// Extract config values
|
|
51
|
+
const {
|
|
52
|
+
className: buttonClassName,
|
|
53
|
+
style: buttonStyle,
|
|
54
|
+
activeClassName,
|
|
55
|
+
activeStyle,
|
|
56
|
+
hoverClassName,
|
|
57
|
+
hoverStyle,
|
|
58
|
+
iconClassName,
|
|
59
|
+
iconSize = 20,
|
|
60
|
+
labelClassName,
|
|
61
|
+
labelStyle,
|
|
62
|
+
} = buttonConfig;
|
|
63
|
+
|
|
64
|
+
const {
|
|
65
|
+
disabled: tooltipDisabled = false,
|
|
66
|
+
className: tooltipClassName,
|
|
67
|
+
style: tooltipStyle,
|
|
68
|
+
delay: tooltipDelay = 100,
|
|
69
|
+
} = tooltipConfig;
|
|
70
|
+
|
|
71
|
+
// Determine what to show based on display mode
|
|
72
|
+
const showIcon = displayMode !== "labels";
|
|
73
|
+
const allowExpand = displayMode === "icons"; // Only expand on active in icons mode
|
|
74
|
+
const showTooltip = (displayMode === "icons" || displayMode === "compact") && !tooltipDisabled;
|
|
75
|
+
|
|
76
|
+
// Validate icon component for modes that need it
|
|
77
|
+
if (showIcon && !IconComponent) {
|
|
33
78
|
throw new Error(
|
|
34
79
|
"A valid 'icon' prop is required (Lucide icon name or custom icon component).",
|
|
35
80
|
);
|
|
@@ -38,10 +83,10 @@ export default function SingleButton({
|
|
|
38
83
|
useEffect(() => {
|
|
39
84
|
let timeoutId: NodeJS.Timeout;
|
|
40
85
|
|
|
41
|
-
if (isHovered) {
|
|
86
|
+
if (isHovered && showTooltip) {
|
|
42
87
|
timeoutId = setTimeout(() => {
|
|
43
88
|
setShowTag(true);
|
|
44
|
-
},
|
|
89
|
+
}, tooltipDelay);
|
|
45
90
|
} else {
|
|
46
91
|
setShowTag(false);
|
|
47
92
|
}
|
|
@@ -49,7 +94,7 @@ export default function SingleButton({
|
|
|
49
94
|
return () => {
|
|
50
95
|
clearTimeout(timeoutId);
|
|
51
96
|
};
|
|
52
|
-
}, [isHovered]);
|
|
97
|
+
}, [isHovered, showTooltip, tooltipDelay]);
|
|
53
98
|
|
|
54
99
|
useEffect(() => {
|
|
55
100
|
setShowTag(false);
|
|
@@ -85,11 +130,168 @@ export default function SingleButton({
|
|
|
85
130
|
|
|
86
131
|
const displayLabel = copiedEmail ? "Email copied!" : label;
|
|
87
132
|
|
|
133
|
+
// Compute button classes
|
|
134
|
+
const baseButtonClass = "relative flex items-center rounded-md p-2 text-neutral-500 transition-colors duration-200 focus:outline-none";
|
|
135
|
+
// Only apply default classes if no custom style is provided
|
|
136
|
+
const stateClass = isPushed
|
|
137
|
+
? (activeClassName || (!activeStyle && "bg-neutral-200"))
|
|
138
|
+
: isHovered
|
|
139
|
+
? (hoverClassName || (!hoverStyle && "bg-neutral-100"))
|
|
140
|
+
: "";
|
|
141
|
+
|
|
142
|
+
// Compute button styles
|
|
143
|
+
const computedButtonStyle: React.CSSProperties = {
|
|
144
|
+
...buttonStyle,
|
|
145
|
+
...(isPushed && activeStyle),
|
|
146
|
+
...(isHovered && !isPushed && hoverStyle),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Compute icon classes and styles
|
|
150
|
+
const iconSizeStyle = { width: iconSize, height: iconSize };
|
|
151
|
+
const baseIconClass = "flex-shrink-0";
|
|
152
|
+
// Only apply default icon colors if no custom button color style is provided
|
|
153
|
+
const hasCustomColor = buttonStyle?.color;
|
|
154
|
+
const iconColorClass = hasCustomColor
|
|
155
|
+
? ""
|
|
156
|
+
: isPushed
|
|
157
|
+
? "text-neutral-700"
|
|
158
|
+
: "text-neutral-500";
|
|
159
|
+
|
|
160
|
+
// Compute label classes
|
|
161
|
+
const baseLabelClass = "whitespace-nowrap font-canvas-figtree text-sm font-medium text-neutral-700";
|
|
162
|
+
|
|
163
|
+
// Tooltip position based on vertical layout
|
|
164
|
+
const tooltipPositionClass = isVertical
|
|
165
|
+
? "left-full top-1/2 -translate-y-1/2 ml-2"
|
|
166
|
+
: "-top-10 left-1/2";
|
|
167
|
+
const tooltipTransform = isVertical
|
|
168
|
+
? { x: 0, y: "-50%" }
|
|
169
|
+
: { x: "-50%" };
|
|
170
|
+
|
|
171
|
+
// Render icon element
|
|
172
|
+
const renderIcon = () => {
|
|
173
|
+
if (!showIcon || !IconComponent) return null;
|
|
174
|
+
return (
|
|
175
|
+
<IconComponent
|
|
176
|
+
className={cn(baseIconClass, iconColorClass, iconClassName)}
|
|
177
|
+
style={iconSizeStyle}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Render label element
|
|
183
|
+
const renderLabel = (animated = false) => {
|
|
184
|
+
if (animated) {
|
|
185
|
+
return (
|
|
186
|
+
<motion.span
|
|
187
|
+
initial={{ opacity: 0, width: 0 }}
|
|
188
|
+
animate={{ opacity: 1, width: "auto" }}
|
|
189
|
+
exit={{ opacity: 0, width: 0 }}
|
|
190
|
+
transition={{
|
|
191
|
+
duration: 0.1,
|
|
192
|
+
ease: "easeInOut",
|
|
193
|
+
}}
|
|
194
|
+
className={cn("overflow-hidden", baseLabelClass, labelClassName)}
|
|
195
|
+
style={labelStyle}
|
|
196
|
+
>
|
|
197
|
+
{displayLabel}
|
|
198
|
+
</motion.span>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return (
|
|
202
|
+
<span
|
|
203
|
+
className={cn(baseLabelClass, labelClassName)}
|
|
204
|
+
style={labelStyle}
|
|
205
|
+
>
|
|
206
|
+
{displayLabel}
|
|
207
|
+
</span>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Render tooltip
|
|
212
|
+
const renderTooltip = () => {
|
|
213
|
+
if (!showTooltip || !showTag || isPushed) return null;
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<AnimatePresence>
|
|
217
|
+
<motion.div
|
|
218
|
+
initial={{ opacity: 0, y: isVertical ? 0 : 5, scale: 0.9, ...tooltipTransform }}
|
|
219
|
+
animate={{ opacity: 1, y: 0, scale: 1, ...tooltipTransform }}
|
|
220
|
+
exit={{ opacity: 0, y: isVertical ? 0 : 5, scale: 0.9, ...tooltipTransform }}
|
|
221
|
+
transition={{
|
|
222
|
+
duration: 0.05,
|
|
223
|
+
ease: "easeOut",
|
|
224
|
+
}}
|
|
225
|
+
className={cn("pointer-events-none absolute z-50", tooltipPositionClass)}
|
|
226
|
+
>
|
|
227
|
+
<div className="rounded-sm bg-gradient-to-t from-black/10 to-transparent px-[1px] pb-[2.5px] pt-[1px]">
|
|
228
|
+
<div
|
|
229
|
+
className={cn(
|
|
230
|
+
"whitespace-nowrap rounded-sm px-2 py-1 font-canvas-figtree text-sm",
|
|
231
|
+
!tooltipStyle?.backgroundColor && "bg-neutral-50",
|
|
232
|
+
!tooltipStyle?.color && "text-neutral-600",
|
|
233
|
+
tooltipClassName,
|
|
234
|
+
)}
|
|
235
|
+
style={tooltipStyle}
|
|
236
|
+
>
|
|
237
|
+
{displayLabel}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</motion.div>
|
|
241
|
+
</AnimatePresence>
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Render based on display mode
|
|
246
|
+
const renderContent = () => {
|
|
247
|
+
// Labels only mode
|
|
248
|
+
if (displayMode === "labels") {
|
|
249
|
+
return renderLabel();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Icons + labels always mode
|
|
253
|
+
if (displayMode === "icons-labels") {
|
|
254
|
+
return (
|
|
255
|
+
<div className="flex items-center gap-2">
|
|
256
|
+
{renderIcon()}
|
|
257
|
+
{renderLabel()}
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Compact mode - icons only, no expansion
|
|
263
|
+
if (displayMode === "compact") {
|
|
264
|
+
return (
|
|
265
|
+
<>
|
|
266
|
+
{renderIcon()}
|
|
267
|
+
{renderTooltip()}
|
|
268
|
+
</>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Icons mode (default) - expands on active
|
|
273
|
+
if (isPushed && allowExpand) {
|
|
274
|
+
return (
|
|
275
|
+
<div className="flex items-center gap-2">
|
|
276
|
+
<div>{renderIcon()}</div>
|
|
277
|
+
{renderLabel(true)}
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<>
|
|
284
|
+
{renderIcon()}
|
|
285
|
+
{renderTooltip()}
|
|
286
|
+
</>
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
|
|
88
290
|
return (
|
|
89
291
|
<motion.button
|
|
90
292
|
aria-label={label}
|
|
91
|
-
className={
|
|
92
|
-
|
|
293
|
+
className={cn(baseButtonClass, stateClass, buttonClassName)}
|
|
294
|
+
style={computedButtonStyle}
|
|
93
295
|
onClick={handleClick}
|
|
94
296
|
onMouseEnter={() => setIsHovered(true)}
|
|
95
297
|
onMouseLeave={() => setIsHovered(false)}
|
|
@@ -100,55 +302,7 @@ export default function SingleButton({
|
|
|
100
302
|
damping: 25,
|
|
101
303
|
}}
|
|
102
304
|
>
|
|
103
|
-
{
|
|
104
|
-
<div className="flex items-center gap-2">
|
|
105
|
-
<div>
|
|
106
|
-
<IconComponent
|
|
107
|
-
className={`h-5 w-5 flex-shrink-0 ${isPushed ? (isLucideIconName ? "text-canvas-emphasis" : "text-white") : "text-canvas-medium"
|
|
108
|
-
}`}
|
|
109
|
-
/>
|
|
110
|
-
</div>
|
|
111
|
-
<motion.span
|
|
112
|
-
initial={{ opacity: 0, width: 0 }}
|
|
113
|
-
animate={{ opacity: 1, width: "auto" }}
|
|
114
|
-
exit={{ opacity: 0, width: 0 }}
|
|
115
|
-
transition={{
|
|
116
|
-
duration: 0.1,
|
|
117
|
-
ease: "easeInOut",
|
|
118
|
-
}}
|
|
119
|
-
className="overflow-hidden whitespace-nowrap font-canvas-figtree text-sm font-medium text-canvas-emphasis"
|
|
120
|
-
>
|
|
121
|
-
{displayLabel}
|
|
122
|
-
</motion.span>
|
|
123
|
-
</div>
|
|
124
|
-
) : (
|
|
125
|
-
<div>
|
|
126
|
-
<IconComponent
|
|
127
|
-
className={`h-5 w-5 flex-shrink-0 ${isPushed ? (isLucideIconName ? "text-canvas-emphasis" : "text-white") : "text-canvas-medium"
|
|
128
|
-
}`}
|
|
129
|
-
/>
|
|
130
|
-
<AnimatePresence>
|
|
131
|
-
{showTag && !isPushed && (
|
|
132
|
-
<motion.div
|
|
133
|
-
initial={{ opacity: 0, y: 5, scale: 0.9, x: "-50%" }}
|
|
134
|
-
animate={{ opacity: 1, y: 0, scale: 1, x: "-50%" }}
|
|
135
|
-
exit={{ opacity: 0, y: 5, scale: 0.9, x: "-50%" }}
|
|
136
|
-
transition={{
|
|
137
|
-
duration: 0.05,
|
|
138
|
-
ease: "easeOut",
|
|
139
|
-
}}
|
|
140
|
-
className="pointer-events-none absolute -top-10 left-1/2 z-50"
|
|
141
|
-
>
|
|
142
|
-
<div className="rounded-sm bg-gradient-to-t from-black/10 to-transparent px-[1px] pb-[2.5px] pt-[1px]">
|
|
143
|
-
<div className="whitespace-nowrap rounded-sm bg-canvas-offwhite px-2 py-1 font-canvas-figtree text-sm text-canvas-medium">
|
|
144
|
-
{displayLabel}
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
</motion.div>
|
|
148
|
-
)}
|
|
149
|
-
</AnimatePresence>
|
|
150
|
-
</div>
|
|
151
|
-
)}
|
|
305
|
+
{renderContent()}
|
|
152
306
|
</motion.button>
|
|
153
307
|
);
|
|
154
308
|
}
|
package/src/index.ts
CHANGED
package/src/styles.css
CHANGED
|
@@ -4,19 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
@layer base {
|
|
6
6
|
:root {
|
|
7
|
-
|
|
8
|
-
--canvas-
|
|
9
|
-
--canvas-
|
|
10
|
-
--canvas-
|
|
11
|
-
--canvas-
|
|
12
|
-
--canvas-
|
|
13
|
-
--canvas-
|
|
14
|
-
--canvas-
|
|
15
|
-
--canvas-
|
|
16
|
-
--canvas-
|
|
17
|
-
--canvas-
|
|
18
|
-
--canvas-
|
|
19
|
-
--canvas-
|
|
7
|
+
/* Neutral gray palette (default) */
|
|
8
|
+
--canvas-beige: #f5f5f5;
|
|
9
|
+
--canvas-coral: #d4d4d4;
|
|
10
|
+
--canvas-lilac: #e5e5e5;
|
|
11
|
+
--canvas-salmon: #a3a3a3;
|
|
12
|
+
--canvas-heavy: #171717;
|
|
13
|
+
--canvas-emphasis: #262626;
|
|
14
|
+
--canvas-active: #525252;
|
|
15
|
+
--canvas-tinted: #a3a3a3;
|
|
16
|
+
--canvas-medium: #737373;
|
|
17
|
+
--canvas-light: #a3a3a3;
|
|
18
|
+
--canvas-faint-lilac: #fafafa;
|
|
19
|
+
--canvas-offwhite: #ffffff;
|
|
20
|
+
--canvas-highlight: #f5f5f5;
|
|
21
|
+
--canvas-pushed: #e5e5e5;
|
|
20
22
|
--canvas-border-light: 0 0% 89%;
|
|
21
23
|
}
|
|
22
24
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -97,3 +97,94 @@ export interface ToolbarConfig {
|
|
|
97
97
|
/** Format for scale. Default: '1.00x' */
|
|
98
98
|
scaleFormat?: (scale: number) => string;
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Preset positions for the navbar
|
|
103
|
+
*/
|
|
104
|
+
export type NavbarPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Display modes for navbar items
|
|
108
|
+
*/
|
|
109
|
+
export type NavbarDisplayMode =
|
|
110
|
+
| 'icons' // Icons only, label shows on expand (default)
|
|
111
|
+
| 'labels' // Labels only, no icons
|
|
112
|
+
| 'icons-labels' // Always show both icon and label
|
|
113
|
+
| 'compact'; // Icons only, no expansion - just highlight
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Tooltip configuration for navbar buttons
|
|
117
|
+
*/
|
|
118
|
+
export interface NavbarTooltipConfig {
|
|
119
|
+
/** Disable tooltips entirely. Default: false */
|
|
120
|
+
disabled?: boolean;
|
|
121
|
+
/** Additional className for tooltip */
|
|
122
|
+
className?: string;
|
|
123
|
+
/** Inline styles for tooltip */
|
|
124
|
+
style?: React.CSSProperties;
|
|
125
|
+
/** Delay before showing tooltip in ms. Default: 100 */
|
|
126
|
+
delay?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Button styling configuration for navbar
|
|
131
|
+
*/
|
|
132
|
+
export interface NavbarButtonConfig {
|
|
133
|
+
/** Additional className for all buttons */
|
|
134
|
+
className?: string;
|
|
135
|
+
/** Inline styles for all buttons */
|
|
136
|
+
style?: React.CSSProperties;
|
|
137
|
+
/** Active/pushed state className */
|
|
138
|
+
activeClassName?: string;
|
|
139
|
+
/** Active state inline styles */
|
|
140
|
+
activeStyle?: React.CSSProperties;
|
|
141
|
+
/** Hover state className */
|
|
142
|
+
hoverClassName?: string;
|
|
143
|
+
/** Hover state inline styles */
|
|
144
|
+
hoverStyle?: React.CSSProperties;
|
|
145
|
+
/** Icon className */
|
|
146
|
+
iconClassName?: string;
|
|
147
|
+
/** Icon size in pixels. Default: 20 */
|
|
148
|
+
iconSize?: number;
|
|
149
|
+
/** Label className */
|
|
150
|
+
labelClassName?: string;
|
|
151
|
+
/** Label inline styles */
|
|
152
|
+
labelStyle?: React.CSSProperties;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Configuration options for the canvas navbar
|
|
157
|
+
*/
|
|
158
|
+
export interface NavbarConfig {
|
|
159
|
+
// === Visibility ===
|
|
160
|
+
/** Hide the navbar entirely. Default: false */
|
|
161
|
+
hidden?: boolean;
|
|
162
|
+
|
|
163
|
+
// === Display Mode ===
|
|
164
|
+
/** How to display items. Default: 'icons' */
|
|
165
|
+
display?: NavbarDisplayMode;
|
|
166
|
+
|
|
167
|
+
// === Positioning ===
|
|
168
|
+
/** Preset position. Default: 'bottom' */
|
|
169
|
+
position?: NavbarPosition;
|
|
170
|
+
|
|
171
|
+
// === Container Styling ===
|
|
172
|
+
/** Additional className for the navbar container */
|
|
173
|
+
className?: string;
|
|
174
|
+
/** Inline styles for the navbar container */
|
|
175
|
+
style?: React.CSSProperties;
|
|
176
|
+
|
|
177
|
+
// === Button Configuration ===
|
|
178
|
+
/** Button styling options */
|
|
179
|
+
buttonConfig?: NavbarButtonConfig;
|
|
180
|
+
|
|
181
|
+
// === Tooltip Configuration ===
|
|
182
|
+
/** Tooltip options */
|
|
183
|
+
tooltipConfig?: NavbarTooltipConfig;
|
|
184
|
+
|
|
185
|
+
// === Spacing ===
|
|
186
|
+
/** Gap between buttons in pixels. Default: 4 */
|
|
187
|
+
gap?: number;
|
|
188
|
+
/** Padding inside the navbar in pixels. Default: 4 */
|
|
189
|
+
padding?: number;
|
|
190
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { motion } from "framer-motion";
|
|
3
|
-
export const OffsetComponent = ({ offset, children, }) => {
|
|
4
|
-
return (_jsx(motion.div, { style: {
|
|
5
|
-
position: "absolute",
|
|
6
|
-
top: offset.y,
|
|
7
|
-
left: offset.x,
|
|
8
|
-
width: "100%",
|
|
9
|
-
height: "100%",
|
|
10
|
-
}, children: children }));
|
|
11
|
-
};
|
|
12
|
-
//# sourceMappingURL=offest.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"offest.js","sourceRoot":"","sources":["../../../src/components/canvas/offest.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAc,MAAM,eAAe,CAAC;AAEnD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAC9B,MAAM,EACN,QAAQ,GAIT,EAAE,EAAE;IACH,OAAO,CACL,KAAC,MAAM,CAAC,GAAG,IACT,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,GAAG,EAAE,MAAM,CAAC,CAAC;YACb,IAAI,EAAE,MAAM,CAAC,CAAC;YACd,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,MAAM;SACf,YAEA,QAAQ,GACE,CACd,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAErE,wBAAwB;AACxB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,iCAAiC,CAAC;AAOzC,WAAW;AACX,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,+BAA+B,CAAC;AAMvC,QAAQ;AACR,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE5F,YAAY;AACZ,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC"}
|
package/dist/types/index.js
DELETED
package/dist/types/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|