@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.
Files changed (60) hide show
  1. package/README.md +196 -8
  2. package/dist/components/canvas/backgrounds.d.ts +4 -4
  3. package/dist/components/canvas/backgrounds.d.ts.map +1 -1
  4. package/dist/components/canvas/backgrounds.js +68 -39
  5. package/dist/components/canvas/backgrounds.js.map +1 -1
  6. package/dist/components/canvas/canvas.d.ts +3 -1
  7. package/dist/components/canvas/canvas.d.ts.map +1 -1
  8. package/dist/components/canvas/canvas.js +451 -387
  9. package/dist/components/canvas/canvas.js.map +1 -1
  10. package/dist/components/canvas/component.js +108 -174
  11. package/dist/components/canvas/component.js.map +1 -1
  12. package/dist/components/canvas/draggable.js +168 -151
  13. package/dist/components/canvas/draggable.js.map +1 -1
  14. package/dist/components/canvas/navbar/index.d.ts +4 -2
  15. package/dist/components/canvas/navbar/index.d.ts.map +1 -1
  16. package/dist/components/canvas/navbar/index.js +168 -101
  17. package/dist/components/canvas/navbar/index.js.map +1 -1
  18. package/dist/components/canvas/navbar/single-button.d.ts +10 -1
  19. package/dist/components/canvas/navbar/single-button.d.ts.map +1 -1
  20. package/dist/components/canvas/navbar/single-button.js +176 -69
  21. package/dist/components/canvas/navbar/single-button.js.map +1 -1
  22. package/dist/components/canvas/toolbar.js +121 -82
  23. package/dist/components/canvas/toolbar.js.map +1 -1
  24. package/dist/components/canvas/wrapper.js +127 -99
  25. package/dist/components/canvas/wrapper.js.map +1 -1
  26. package/dist/contexts/CanvasContext.js +25 -17
  27. package/dist/contexts/CanvasContext.js.map +1 -1
  28. package/dist/contexts/PerformanceContext.js +51 -50
  29. package/dist/contexts/PerformanceContext.js.map +1 -1
  30. package/dist/hooks/usePerformanceMode.js +36 -37
  31. package/dist/hooks/usePerformanceMode.js.map +1 -1
  32. package/dist/hooks/useWindowDimensions.js +22 -18
  33. package/dist/hooks/useWindowDimensions.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +17 -21
  37. package/dist/lib/canvas.js +65 -72
  38. package/dist/lib/canvas.js.map +1 -1
  39. package/dist/lib/constants.js +78 -92
  40. package/dist/lib/constants.js.map +1 -1
  41. package/dist/lib/utils.js +10 -5
  42. package/dist/lib/utils.js.map +1 -1
  43. package/dist/styles.css +1 -1
  44. package/dist/types/index.d.ts +69 -0
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/utils/performance.js +18 -23
  47. package/dist/utils/performance.js.map +1 -1
  48. package/package.json +7 -21
  49. package/src/components/canvas/backgrounds.tsx +7 -7
  50. package/src/components/canvas/canvas.tsx +9 -2
  51. package/src/components/canvas/navbar/index.tsx +91 -15
  52. package/src/components/canvas/navbar/single-button.tsx +210 -56
  53. package/src/index.ts +5 -0
  54. package/src/styles.css +15 -13
  55. package/src/types/index.ts +91 -0
  56. package/dist/components/canvas/offest.js +0 -12
  57. package/dist/components/canvas/offest.js.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/dist/types/index.js +0 -6
  60. package/dist/types/index.js.map +0 -1
@@ -1,104 +1,171 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { useCanvasContext } from "../../../contexts/CanvasContext.js";
2
+ import { NAVBAR_DEBOUNCE_MS, RESPONSIVE_ZOOM_MAP } from "../../../lib/constants.js";
3
+ import { getScreenSizeEnum, getSectionPanCoordinates } from "../../../lib/canvas.js";
4
+ import useWindowDimensions_default from "../../../hooks/useWindowDimensions.js";
5
+ import { cn } from "../../../lib/utils.js";
6
+ import SingleButton from "./single-button.js";
7
+ import { usePerformanceMode } from "../../../hooks/usePerformanceMode.js";
2
8
  import { motion, useMotionValueEvent } from "framer-motion";
3
- import { useState, useRef, useEffect, useCallback, useMemo } from "react";
4
- import SingleButton from "./single-button";
5
- import { useCanvasContext } from "../../../contexts/CanvasContext";
6
- import useWindowDimensions from "../../../hooks/useWindowDimensions";
7
- import { usePerformanceMode } from "../../../hooks/usePerformanceMode";
8
- import { getScreenSizeEnum, getSectionPanCoordinates, } from "../../../lib/canvas";
9
- import { RESPONSIVE_ZOOM_MAP, NAVBAR_DEBOUNCE_MS, } from "../../../lib/constants";
10
- export default function Navbar({ panToOffset, onReset, items, }) {
11
- const { x, y, scale, animationStage, setNextTargetSection } = useCanvasContext();
12
- const [expandedButton, setExpandedButton] = useState(null);
13
- const activePans = useRef(0);
14
- const panTimeout = useRef(null);
15
- // Debounce state
16
- const debounceBlocked = useRef(false);
17
- const debounceCooldownTimeout = useRef(null);
18
- const { height, width } = useWindowDimensions();
19
- const { mode } = usePerformanceMode();
20
- const defaultZoom = RESPONSIVE_ZOOM_MAP[getScreenSizeEnum(width)];
21
- // Derive debounce duration from performance mode
22
- const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;
23
- // Find the home section from items
24
- const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);
25
- // Leading-edge debounce handler
26
- const handleDebouncedClick = useCallback((callback) => {
27
- if (debounceMs === 0) {
28
- callback();
29
- return;
30
- }
31
- if (debounceBlocked.current) {
32
- // We're in the cooldown window; ignore this click
33
- return;
34
- }
35
- // Enter cooldown and perform the click immediately
36
- debounceBlocked.current = true;
37
- callback();
38
- if (debounceCooldownTimeout.current) {
39
- clearTimeout(debounceCooldownTimeout.current);
40
- }
41
- debounceCooldownTimeout.current = setTimeout(() => {
42
- debounceBlocked.current = false;
43
- debounceCooldownTimeout.current = null;
44
- }, debounceMs);
45
- }, [debounceMs]);
46
- const updateExpandedButton = () => {
47
- // reset activePans if no movement has occurred recently
48
- if (panTimeout.current)
49
- clearTimeout(panTimeout.current);
50
- panTimeout.current = setTimeout(() => {
51
- activePans.current = 0;
52
- }, 500);
53
- if (activePans.current == 0)
54
- setExpandedButton(null);
55
- };
56
- useMotionValueEvent(x, "change", updateExpandedButton);
57
- useMotionValueEvent(y, "change", updateExpandedButton);
58
- useMotionValueEvent(scale, "change", updateExpandedButton);
59
- const handlePan = useCallback(function handlePan(item) {
60
- setExpandedButton(item.id);
61
- activePans.current++;
62
- // Predictive pre-render hint: mark the target section so its CanvasComponent can
63
- // render even before it comes fully into view.
64
- setNextTargetSection(item.id);
65
- if (item.isHome) {
66
- onReset();
67
- return;
68
- }
69
- const panCoords = getSectionPanCoordinates({
70
- windowDimensions: { width, height },
71
- coords: { x: item.x, y: item.y, width: item.width, height: item.height },
72
- targetZoom: defaultZoom,
73
- negative: true,
74
- });
75
- panToOffset(panCoords, () => {
76
- activePans.current--;
77
- }, defaultZoom);
78
- }, [panToOffset, onReset, width, height, defaultZoom, setNextTargetSection]);
79
- // Clean up timers on unmount and pan to home on animation complete
80
- useEffect(() => {
81
- if (animationStage < 2)
82
- return;
83
- if (homeItem) {
84
- handlePan(homeItem);
85
- }
86
- return () => {
87
- if (panTimeout.current)
88
- clearTimeout(panTimeout.current);
89
- if (debounceCooldownTimeout.current)
90
- clearTimeout(debounceCooldownTimeout.current);
91
- };
92
- }, [handlePan, animationStage, homeItem]);
93
- return (_jsx("div", { className: "bottom-12 md:bottom-4", style: {
94
- position: "fixed",
95
- left: "50%",
96
- transform: "translateX(-50%)",
97
- zIndex: 1000,
98
- pointerEvents: "auto",
99
- display: "flex",
100
- justifyContent: "center",
101
- alignItems: "center",
102
- }, children: _jsx("div", { className: "px-4 md:px-8", children: _jsx(motion.div, { className: "flex select-none items-center justify-center gap-1 rounded-[10px] border-[1px] border-border bg-canvas-offwhite p-1 shadow-[0_6px_12px_rgba(0,0,0,0.10)]", children: _jsx("div", { className: "flex items-center gap-1", children: items.map((item) => (_jsx(SingleButton, { label: item.label, icon: item.icon, onClick: () => handlePan(item), isPushed: expandedButton === item.id, onDebouncedClick: handleDebouncedClick }, item.id))) }) }) }) }));
9
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { jsx } from "react/jsx-runtime";
11
+
12
+ //#region src/components/canvas/navbar/index.tsx
13
+ const positionStyles = {
14
+ top: {
15
+ top: "1rem",
16
+ bottom: "auto",
17
+ left: "50%",
18
+ transform: "translateX(-50%)"
19
+ },
20
+ bottom: {
21
+ bottom: "1rem",
22
+ top: "auto",
23
+ left: "50%",
24
+ transform: "translateX(-50%)"
25
+ },
26
+ left: {
27
+ left: "1rem",
28
+ right: "auto",
29
+ top: "50%",
30
+ transform: "translateY(-50%)"
31
+ },
32
+ right: {
33
+ right: "1rem",
34
+ left: "auto",
35
+ top: "50%",
36
+ transform: "translateY(-50%)"
37
+ }
38
+ };
39
+ const responsivePositionClasses = {
40
+ top: "top-12 md:top-4",
41
+ bottom: "bottom-12 md:bottom-4",
42
+ left: "left-4",
43
+ right: "right-4"
44
+ };
45
+ function Navbar({ panToOffset, onReset, items, config = {} }) {
46
+ const { x, y, scale, animationStage, setNextTargetSection } = useCanvasContext();
47
+ const [expandedButton, setExpandedButton] = useState(null);
48
+ const activePans = useRef(0);
49
+ const panTimeout = useRef(null);
50
+ const debounceBlocked = useRef(false);
51
+ const debounceCooldownTimeout = useRef(null);
52
+ const { height, width } = useWindowDimensions_default();
53
+ const { mode } = usePerformanceMode();
54
+ const defaultZoom = RESPONSIVE_ZOOM_MAP[getScreenSizeEnum(width)];
55
+ const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;
56
+ const { display = "icons", position = "bottom", className, style, buttonConfig, tooltipConfig, gap, padding } = config;
57
+ const isVertical = position === "left" || position === "right";
58
+ const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);
59
+ const handleDebouncedClick = useCallback((callback) => {
60
+ if (debounceMs === 0) {
61
+ callback();
62
+ return;
63
+ }
64
+ if (debounceBlocked.current) return;
65
+ debounceBlocked.current = true;
66
+ callback();
67
+ if (debounceCooldownTimeout.current) clearTimeout(debounceCooldownTimeout.current);
68
+ debounceCooldownTimeout.current = setTimeout(() => {
69
+ debounceBlocked.current = false;
70
+ debounceCooldownTimeout.current = null;
71
+ }, debounceMs);
72
+ }, [debounceMs]);
73
+ const updateExpandedButton = () => {
74
+ if (panTimeout.current) clearTimeout(panTimeout.current);
75
+ panTimeout.current = setTimeout(() => {
76
+ activePans.current = 0;
77
+ }, 500);
78
+ if (activePans.current == 0) setExpandedButton(null);
79
+ };
80
+ useMotionValueEvent(x, "change", updateExpandedButton);
81
+ useMotionValueEvent(y, "change", updateExpandedButton);
82
+ useMotionValueEvent(scale, "change", updateExpandedButton);
83
+ const handlePan = useCallback(function handlePan(item) {
84
+ setExpandedButton(item.id);
85
+ activePans.current++;
86
+ setNextTargetSection(item.id);
87
+ if (item.isHome) {
88
+ onReset();
89
+ return;
90
+ }
91
+ panToOffset(getSectionPanCoordinates({
92
+ windowDimensions: {
93
+ width,
94
+ height
95
+ },
96
+ coords: {
97
+ x: item.x,
98
+ y: item.y,
99
+ width: item.width,
100
+ height: item.height
101
+ },
102
+ targetZoom: defaultZoom,
103
+ negative: true
104
+ }), () => {
105
+ activePans.current--;
106
+ }, defaultZoom);
107
+ }, [
108
+ panToOffset,
109
+ onReset,
110
+ width,
111
+ height,
112
+ defaultZoom,
113
+ setNextTargetSection
114
+ ]);
115
+ useEffect(() => {
116
+ if (animationStage < 2) return;
117
+ if (homeItem) handlePan(homeItem);
118
+ return () => {
119
+ if (panTimeout.current) clearTimeout(panTimeout.current);
120
+ if (debounceCooldownTimeout.current) clearTimeout(debounceCooldownTimeout.current);
121
+ };
122
+ }, [
123
+ handlePan,
124
+ animationStage,
125
+ homeItem
126
+ ]);
127
+ const containerStyle = {
128
+ position: "fixed",
129
+ zIndex: 1e3,
130
+ pointerEvents: "auto",
131
+ display: "flex",
132
+ justifyContent: "center",
133
+ alignItems: "center",
134
+ ...positionStyles[position]
135
+ };
136
+ const innerStyle = {
137
+ ...gap !== void 0 && { gap: `${gap}px` },
138
+ ...padding !== void 0 && { padding: `${padding}px` },
139
+ ...isVertical && { flexDirection: "column" },
140
+ ...style
141
+ };
142
+ return /* @__PURE__ */ jsx("div", {
143
+ className: responsivePositionClasses[position],
144
+ style: containerStyle,
145
+ children: /* @__PURE__ */ jsx("div", {
146
+ className: isVertical ? "py-4 md:py-8" : "px-4 md:px-8",
147
+ children: /* @__PURE__ */ jsx(motion.div, {
148
+ className: cn("flex select-none items-center justify-center gap-1 rounded-[10px] border p-1 shadow-[0_6px_12px_rgba(0,0,0,0.08)]", !style?.backgroundColor && "bg-white", !style?.borderColor && "border-neutral-200", isVertical && "flex-col", className),
149
+ style: innerStyle,
150
+ children: /* @__PURE__ */ jsx("div", {
151
+ className: cn("flex items-center gap-1", isVertical && "flex-col"),
152
+ children: items.map((item) => /* @__PURE__ */ jsx(SingleButton, {
153
+ label: item.label,
154
+ icon: item.icon,
155
+ onClick: () => handlePan(item),
156
+ isPushed: expandedButton === item.id,
157
+ onDebouncedClick: handleDebouncedClick,
158
+ displayMode: display,
159
+ buttonConfig,
160
+ tooltipConfig,
161
+ isVertical
162
+ }, item.id))
163
+ })
164
+ })
165
+ })
166
+ });
103
167
  }
168
+
169
+ //#endregion
170
+ export { Navbar as default };
104
171
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/canvas/navbar/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["useWindowDimensions"],"sources":["../../../../src/components/canvas/navbar/index.tsx"],"sourcesContent":["import { motion, useMotionValueEvent } from \"framer-motion\";\nimport { useState, useRef, useEffect, useCallback, useMemo } from \"react\";\nimport SingleButton from \"./single-button\";\nimport type { NavItem, NavbarConfig, NavbarPosition } from \"../../../types\";\nimport { useCanvasContext } from \"../../../contexts/CanvasContext\";\nimport useWindowDimensions from \"../../../hooks/useWindowDimensions\";\nimport { usePerformanceMode } from \"../../../hooks/usePerformanceMode\";\nimport {\n getScreenSizeEnum,\n getSectionPanCoordinates,\n} from \"../../../lib/canvas\";\nimport {\n RESPONSIVE_ZOOM_MAP,\n NAVBAR_DEBOUNCE_MS,\n} from \"../../../lib/constants\";\nimport { cn } from \"../../../lib/utils\";\n\ninterface NavbarProps {\n panToOffset: (\n offset: { x: number; y: number },\n onComplete?: () => void,\n zoom?: number,\n ) => void;\n onReset: () => void;\n /** Array of navigation items defining sections, their icons, and coordinates */\n items: NavItem[];\n /** Navbar configuration options */\n config?: NavbarConfig;\n}\n\nconst positionStyles: Record<NavbarPosition, React.CSSProperties> = {\n top: {\n top: \"1rem\",\n bottom: \"auto\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n },\n bottom: {\n bottom: \"1rem\",\n top: \"auto\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n },\n left: {\n left: \"1rem\",\n right: \"auto\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n },\n right: {\n right: \"1rem\",\n left: \"auto\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n },\n};\n\n// Responsive position adjustments (mobile vs desktop)\nconst responsivePositionClasses: Record<NavbarPosition, string> = {\n top: \"top-12 md:top-4\",\n bottom: \"bottom-12 md:bottom-4\",\n left: \"left-4\",\n right: \"right-4\",\n};\n\nexport default function Navbar({\n panToOffset,\n onReset,\n items,\n config = {},\n}: NavbarProps) {\n const { x, y, scale, animationStage, setNextTargetSection } =\n useCanvasContext();\n const [expandedButton, setExpandedButton] = useState<string | null>(null);\n const activePans = useRef(0);\n const panTimeout = useRef<NodeJS.Timeout | null>(null);\n\n // Debounce state\n const debounceBlocked = useRef(false);\n const debounceCooldownTimeout = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n\n const { height, width } = useWindowDimensions();\n const { mode } = usePerformanceMode();\n\n const defaultZoom = RESPONSIVE_ZOOM_MAP[getScreenSizeEnum(width)];\n\n // Derive debounce duration from performance mode\n const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;\n\n // Extract config values\n const {\n display = \"icons\",\n position = \"bottom\",\n className,\n style,\n buttonConfig,\n tooltipConfig,\n gap,\n padding,\n } = config;\n\n const isVertical = position === \"left\" || position === \"right\";\n\n // Find the home section from items\n const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);\n\n // Leading-edge debounce handler\n const handleDebouncedClick = useCallback(\n (callback: () => void) => {\n if (debounceMs === 0) {\n callback();\n return;\n }\n\n if (debounceBlocked.current) {\n // We're in the cooldown window; ignore this click\n return;\n }\n\n // Enter cooldown and perform the click immediately\n debounceBlocked.current = true;\n callback();\n\n if (debounceCooldownTimeout.current) {\n clearTimeout(debounceCooldownTimeout.current);\n }\n\n debounceCooldownTimeout.current = setTimeout(() => {\n debounceBlocked.current = false;\n debounceCooldownTimeout.current = null;\n }, debounceMs);\n },\n [debounceMs],\n );\n\n const updateExpandedButton = () => {\n // reset activePans if no movement has occurred recently\n if (panTimeout.current) clearTimeout(panTimeout.current);\n panTimeout.current = setTimeout(() => {\n activePans.current = 0;\n }, 500);\n\n if (activePans.current == 0) setExpandedButton(null);\n };\n\n useMotionValueEvent(x, \"change\", updateExpandedButton);\n useMotionValueEvent(y, \"change\", updateExpandedButton);\n useMotionValueEvent(scale, \"change\", updateExpandedButton);\n\n const handlePan = useCallback(\n function handlePan(item: NavItem) {\n setExpandedButton(item.id);\n activePans.current++;\n\n // Predictive pre-render hint: mark the target section so its CanvasComponent can\n // render even before it comes fully into view.\n setNextTargetSection(item.id);\n\n if (item.isHome) {\n onReset();\n return;\n }\n\n const panCoords = getSectionPanCoordinates({\n windowDimensions: { width, height },\n coords: { x: item.x, y: item.y, width: item.width, height: item.height },\n targetZoom: defaultZoom,\n negative: true,\n });\n\n panToOffset(\n panCoords,\n () => {\n activePans.current--;\n },\n defaultZoom,\n );\n },\n [panToOffset, onReset, width, height, defaultZoom, setNextTargetSection],\n );\n\n // Clean up timers on unmount and pan to home on animation complete\n useEffect(() => {\n if (animationStage < 2) return;\n if (homeItem) {\n handlePan(homeItem);\n }\n return () => {\n if (panTimeout.current) clearTimeout(panTimeout.current);\n if (debounceCooldownTimeout.current)\n clearTimeout(debounceCooldownTimeout.current);\n };\n }, [handlePan, animationStage, homeItem]);\n\n // Compute container styles (positioning only)\n const containerStyle: React.CSSProperties = {\n position: \"fixed\",\n zIndex: 1000,\n pointerEvents: \"auto\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n ...positionStyles[position],\n };\n\n // Compute inner container styles (visual styling)\n const innerStyle: React.CSSProperties = {\n ...(gap !== undefined && { gap: `${gap}px` }),\n ...(padding !== undefined && { padding: `${padding}px` }),\n ...(isVertical && { flexDirection: \"column\" }),\n ...style,\n };\n\n return (\n <div\n className={responsivePositionClasses[position]}\n style={containerStyle}\n >\n {/* padding to prevent edge bug */}\n <div className={isVertical ? \"py-4 md:py-8\" : \"px-4 md:px-8\"}>\n <motion.div\n className={cn(\n \"flex select-none items-center justify-center gap-1 rounded-[10px] border p-1 shadow-[0_6px_12px_rgba(0,0,0,0.08)]\",\n !style?.backgroundColor && \"bg-white\",\n !style?.borderColor && \"border-neutral-200\",\n isVertical && \"flex-col\",\n className,\n )}\n style={innerStyle}\n >\n <div className={cn(\"flex items-center gap-1\", isVertical && \"flex-col\")}>\n {items.map((item) => (\n <SingleButton\n key={item.id}\n label={item.label}\n icon={item.icon}\n onClick={() => handlePan(item)}\n isPushed={expandedButton === item.id}\n onDebouncedClick={handleDebouncedClick}\n displayMode={display}\n buttonConfig={buttonConfig}\n tooltipConfig={tooltipConfig}\n isVertical={isVertical}\n />\n ))}\n </div>\n </motion.div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { NavbarDisplayMode, NavbarButtonConfig, NavbarTooltipConfig } from "../../../types";
1
2
  interface SingleButtonProps {
2
3
  label: string;
3
4
  /** Lucide icon name or a custom icon component */
@@ -8,7 +9,15 @@ interface SingleButtonProps {
8
9
  isPushed: boolean;
9
10
  link?: string;
10
11
  onDebouncedClick?: (callback: () => void) => void;
12
+ /** Display mode for the button */
13
+ displayMode?: NavbarDisplayMode;
14
+ /** Button styling configuration */
15
+ buttonConfig?: NavbarButtonConfig;
16
+ /** Tooltip configuration */
17
+ tooltipConfig?: NavbarTooltipConfig;
18
+ /** Whether the navbar is in vertical layout */
19
+ isVertical?: boolean;
11
20
  }
12
- export default function SingleButton({ label, icon, onClick, isPushed, link, onDebouncedClick, }: SingleButtonProps): import("react/jsx-runtime").JSX.Element;
21
+ export default function SingleButton({ label, icon, onClick, isPushed, link, onDebouncedClick, displayMode, buttonConfig, tooltipConfig, isVertical, }: SingleButtonProps): import("react/jsx-runtime").JSX.Element;
13
22
  export {};
14
23
  //# sourceMappingURL=single-button.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"single-button.d.ts","sourceRoot":"","sources":["../../../../src/components/canvas/navbar/single-button.tsx"],"names":[],"mappings":"AAIA,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,IAAI,EACJ,gBAAgB,GACjB,EAAE,iBAAiB,2CAoInB"}
1
+ {"version":3,"file":"single-button.d.ts","sourceRoot":"","sources":["../../../../src/components/canvas/navbar/single-button.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAGxB,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAClD,kCAAkC;IAClC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,mCAAmC;IACnC,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,4BAA4B;IAC5B,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,+CAA+C;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,IAAI,EACJ,gBAAgB,EAChB,WAAqB,EACrB,YAAiB,EACjB,aAAkB,EAClB,UAAkB,GACnB,EAAE,iBAAiB,2CA4QnB"}
@@ -1,72 +1,179 @@
1
- import { jsx as _jsx, jsxs as _jsxs } 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
- export default function SingleButton({ label, icon, onClick, isPushed, link, onDebouncedClick, }) {
6
- const [isHovered, setIsHovered] = useState(false);
7
- const [showTag, setShowTag] = useState(false);
8
- const [copiedEmail, setCopiedEmail] = useState(false);
9
- const isLucideIconName = typeof icon === "string";
10
- const IconComponent = isLucideIconName
11
- ? LucideIcons[icon]
12
- : icon;
13
- const TagDelay = 100;
14
- if (!IconComponent) {
15
- throw new Error("A valid 'icon' prop is required (Lucide icon name or custom icon component).");
16
- }
17
- useEffect(() => {
18
- let timeoutId;
19
- if (isHovered) {
20
- timeoutId = setTimeout(() => {
21
- setShowTag(true);
22
- }, TagDelay);
23
- }
24
- else {
25
- setShowTag(false);
26
- }
27
- return () => {
28
- clearTimeout(timeoutId);
29
- };
30
- }, [isHovered]);
31
- useEffect(() => {
32
- setShowTag(false);
33
- setIsHovered(false);
34
- }, [isPushed]);
35
- // Reset copied email state after 2 seconds
36
- useEffect(() => {
37
- if (copiedEmail) {
38
- const timeoutId = setTimeout(() => {
39
- setCopiedEmail(false);
40
- }, 2000);
41
- return () => clearTimeout(timeoutId);
42
- }
43
- }, [copiedEmail]);
44
- const performClick = () => {
45
- if (link) {
46
- window.open(link, "_blank", "noopener,noreferrer");
47
- return;
48
- }
49
- onClick?.();
50
- };
51
- const handleClick = () => {
52
- if (onDebouncedClick) {
53
- onDebouncedClick(performClick);
54
- }
55
- else {
56
- performClick();
57
- }
58
- };
59
- const displayLabel = copiedEmail ? "Email copied!" : label;
60
- return (_jsx(motion.button, { "aria-label": label, className: `relative flex items-center rounded-md p-2 text-canvas-medium transition-colors duration-200 ${isPushed ? "bg-[#EEE2FB]" : isHovered ? "bg-canvas-highlight" : ""}`, onClick: handleClick, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), whileTap: { scale: 0.95 }, transition: {
61
- type: "spring",
62
- stiffness: 400,
63
- damping: 25,
64
- }, children: isPushed ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { children: _jsx(IconComponent, { className: `h-5 w-5 flex-shrink-0 ${isPushed ? (isLucideIconName ? "text-canvas-emphasis" : "text-white") : "text-canvas-medium"}` }) }), _jsx(motion.span, { initial: { opacity: 0, width: 0 }, animate: { opacity: 1, width: "auto" }, exit: { opacity: 0, width: 0 }, transition: {
65
- duration: 0.1,
66
- ease: "easeInOut",
67
- }, className: "overflow-hidden whitespace-nowrap font-canvas-figtree text-sm font-medium text-canvas-emphasis", children: displayLabel })] })) : (_jsxs("div", { children: [_jsx(IconComponent, { className: `h-5 w-5 flex-shrink-0 ${isPushed ? (isLucideIconName ? "text-canvas-emphasis" : "text-white") : "text-canvas-medium"}` }), _jsx(AnimatePresence, { children: showTag && !isPushed && (_jsx(motion.div, { initial: { opacity: 0, y: 5, scale: 0.9, x: "-50%" }, animate: { opacity: 1, y: 0, scale: 1, x: "-50%" }, exit: { opacity: 0, y: 5, scale: 0.9, x: "-50%" }, transition: {
68
- duration: 0.05,
69
- ease: "easeOut",
70
- }, className: "pointer-events-none absolute -top-10 left-1/2 z-50", 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: "whitespace-nowrap rounded-sm bg-canvas-offwhite px-2 py-1 font-canvas-figtree text-sm text-canvas-medium", children: displayLabel }) }) })) })] })) }));
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
+ });
71
175
  }
176
+
177
+ //#endregion
178
+ export { SingleButton as default };
72
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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"}