@hunterchen/canvas 0.5.0 → 0.7.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 +137 -1
- package/dist/components/canvas/canvas.d.ts +5 -1
- package/dist/components/canvas/canvas.d.ts.map +1 -1
- package/dist/components/canvas/canvas.js +19 -17
- package/dist/components/canvas/canvas.js.map +1 -1
- package/dist/components/canvas/toolbar.d.ts +3 -1
- package/dist/components/canvas/toolbar.d.ts.map +1 -1
- package/dist/components/canvas/toolbar.js +67 -9
- package/dist/components/canvas/toolbar.js.map +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +1 -1
- package/dist/lib/canvas.d.ts.map +1 -1
- package/dist/lib/canvas.js +3 -3
- package/dist/lib/canvas.js.map +1 -1
- package/dist/lib/constants.d.ts +2 -2
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +2 -2
- package/dist/lib/constants.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/index.d.ts +43 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/components/canvas/canvas.tsx +59 -36
- package/src/components/canvas/toolbar.tsx +129 -16
- package/src/index.ts +46 -28
- package/src/lib/canvas.ts +4 -4
- package/src/lib/constants.ts +2 -2
- package/src/types/index.ts +59 -0
|
@@ -1,19 +1,56 @@
|
|
|
1
1
|
import { type Point, useTransform, motion } from "framer-motion";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useEffect, useState, useMemo } from "react";
|
|
3
3
|
import { useCanvasContext } from "../../contexts/CanvasContext";
|
|
4
4
|
import {
|
|
5
5
|
TOOLBAR_OPACITY_POS_EPS,
|
|
6
6
|
TOOLBAR_OPACITY_SCALE_EPS,
|
|
7
7
|
} from "../../lib/constants";
|
|
8
|
+
import { cn } from "../../lib/utils";
|
|
9
|
+
import type { ToolbarConfig, ToolbarPosition } from "../../types";
|
|
8
10
|
|
|
9
11
|
type ToolbarProps = {
|
|
10
12
|
homeCoordinates?: Point;
|
|
13
|
+
config?: ToolbarConfig;
|
|
11
14
|
};
|
|
12
15
|
|
|
13
|
-
const
|
|
16
|
+
const positionStyles: Record<ToolbarPosition, string> = {
|
|
17
|
+
"top-left": "left-4 top-6 sm:top-4",
|
|
18
|
+
"top-right": "right-4 top-6 sm:top-4",
|
|
19
|
+
"bottom-left": "left-4 bottom-6 sm:bottom-4",
|
|
20
|
+
"bottom-right": "right-4 bottom-6 sm:bottom-4",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const Toolbar = ({
|
|
24
|
+
homeCoordinates = { x: 0, y: 0 },
|
|
25
|
+
config = {},
|
|
26
|
+
}: ToolbarProps) => {
|
|
14
27
|
const { x, y, scale } = useCanvasContext();
|
|
15
28
|
const [hasMounted, setHasMounted] = useState(false);
|
|
16
29
|
|
|
30
|
+
const {
|
|
31
|
+
display = "both",
|
|
32
|
+
position = "top-left",
|
|
33
|
+
disableAutoHide = false,
|
|
34
|
+
className,
|
|
35
|
+
coordinatesClassName,
|
|
36
|
+
scaleClassName,
|
|
37
|
+
separatorClassName,
|
|
38
|
+
style,
|
|
39
|
+
coordinatesStyle,
|
|
40
|
+
scaleStyle,
|
|
41
|
+
separator = "|",
|
|
42
|
+
separatorGap,
|
|
43
|
+
coordinatesFormat,
|
|
44
|
+
scaleFormat,
|
|
45
|
+
} = config;
|
|
46
|
+
|
|
47
|
+
const separatorStyle: React.CSSProperties | undefined = separatorGap
|
|
48
|
+
? {
|
|
49
|
+
marginInline:
|
|
50
|
+
typeof separatorGap === "number" ? `${separatorGap}px` : separatorGap,
|
|
51
|
+
}
|
|
52
|
+
: undefined;
|
|
53
|
+
|
|
17
54
|
useEffect(() => {
|
|
18
55
|
setHasMounted(true);
|
|
19
56
|
}, []);
|
|
@@ -21,44 +58,120 @@ const Toolbar = ({ homeCoordinates = { x: 0, y: 0 } }: ToolbarProps) => {
|
|
|
21
58
|
// numeric MotionValues
|
|
22
59
|
const rawDx = useTransform(
|
|
23
60
|
[x, scale],
|
|
24
|
-
([lx, ls]) => -((lx as number) / (ls as number)) + homeCoordinates.x
|
|
61
|
+
([lx, ls]) => -((lx as number) / (ls as number)) + homeCoordinates.x
|
|
25
62
|
);
|
|
26
63
|
const rawDy = useTransform(
|
|
27
64
|
[y, scale],
|
|
28
|
-
([ly, ls]) => -((ly as number) / (ls as number)) + homeCoordinates.y
|
|
65
|
+
([ly, ls]) => -((ly as number) / (ls as number)) + homeCoordinates.y
|
|
29
66
|
);
|
|
30
67
|
|
|
31
|
-
// formatted MotionValues
|
|
68
|
+
// formatted MotionValues for default display
|
|
32
69
|
const displayX = useTransform(rawDx, (v) => Math.round(v).toString());
|
|
33
70
|
const displayY = useTransform(rawDy, (v) => Math.round(v).toString());
|
|
34
71
|
const displayScale = useTransform(scale, (v) => v.toFixed(2));
|
|
35
72
|
|
|
36
|
-
|
|
37
|
-
|
|
73
|
+
// For custom formatters, we need to use state to track values
|
|
74
|
+
const [currentX, setCurrentX] = useState(0);
|
|
75
|
+
const [currentY, setCurrentY] = useState(0);
|
|
76
|
+
const [currentScale, setCurrentScale] = useState(1);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const unsubX = rawDx.on("change", (v) => setCurrentX(Math.round(v)));
|
|
80
|
+
const unsubY = rawDy.on("change", (v) => setCurrentY(Math.round(v)));
|
|
81
|
+
const unsubScale = scale.on("change", (v) => setCurrentScale(v));
|
|
82
|
+
return () => {
|
|
83
|
+
unsubX();
|
|
84
|
+
unsubY();
|
|
85
|
+
unsubScale();
|
|
86
|
+
};
|
|
87
|
+
}, [rawDx, rawDy, scale]);
|
|
88
|
+
|
|
89
|
+
const opacity = useTransform([rawDx, rawDy, scale], ([dx, dy, ls]) => {
|
|
90
|
+
if (disableAutoHide) return 1;
|
|
91
|
+
return Math.abs(dx as number) < TOOLBAR_OPACITY_POS_EPS &&
|
|
38
92
|
Math.abs(dy as number) < TOOLBAR_OPACITY_POS_EPS &&
|
|
39
93
|
Math.abs((ls as number) - 1) < TOOLBAR_OPACITY_SCALE_EPS
|
|
40
94
|
? 0
|
|
41
|
-
: 1
|
|
42
|
-
);
|
|
95
|
+
: 1;
|
|
96
|
+
});
|
|
43
97
|
|
|
44
98
|
const handlePointerDown = (e: React.PointerEvent) => e.stopPropagation();
|
|
45
99
|
|
|
100
|
+
const showCoordinates = display === "coordinates" || display === "both";
|
|
101
|
+
const showScale = display === "scale" || display === "both";
|
|
102
|
+
const showSeparator = display === "both";
|
|
103
|
+
|
|
104
|
+
// Compute formatted values
|
|
105
|
+
const formattedCoordinates = useMemo(() => {
|
|
106
|
+
if (coordinatesFormat) {
|
|
107
|
+
return coordinatesFormat(currentX, currentY);
|
|
108
|
+
}
|
|
109
|
+
return null; // Will use motion spans for default
|
|
110
|
+
}, [coordinatesFormat, currentX, currentY]);
|
|
111
|
+
|
|
112
|
+
const formattedScale = useMemo(() => {
|
|
113
|
+
if (scaleFormat) {
|
|
114
|
+
return scaleFormat(currentScale);
|
|
115
|
+
}
|
|
116
|
+
return null; // Will use motion span for default
|
|
117
|
+
}, [scaleFormat, currentScale]);
|
|
118
|
+
|
|
119
|
+
// Placeholder content for SSR/initial render
|
|
120
|
+
const placeholderContent = useMemo(() => {
|
|
121
|
+
const parts: string[] = [];
|
|
122
|
+
if (showCoordinates) parts.push("(0, 0)");
|
|
123
|
+
if (showSeparator) parts.push(separator);
|
|
124
|
+
if (showScale) parts.push("1.00x");
|
|
125
|
+
return parts.join("");
|
|
126
|
+
}, [showCoordinates, showScale, showSeparator, separator]);
|
|
127
|
+
|
|
46
128
|
return (
|
|
47
129
|
<motion.div
|
|
48
|
-
className=
|
|
130
|
+
className={cn(
|
|
131
|
+
"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",
|
|
132
|
+
positionStyles[position],
|
|
133
|
+
className
|
|
134
|
+
)}
|
|
49
135
|
onPointerDown={handlePointerDown}
|
|
50
136
|
data-toolbar-button
|
|
51
|
-
style={{ opacity }}
|
|
137
|
+
style={{ opacity, ...style }}
|
|
52
138
|
>
|
|
53
139
|
{hasMounted ? (
|
|
54
140
|
<>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
141
|
+
{showCoordinates && (
|
|
142
|
+
<span className={coordinatesClassName} style={coordinatesStyle}>
|
|
143
|
+
{formattedCoordinates !== null ? (
|
|
144
|
+
formattedCoordinates
|
|
145
|
+
) : (
|
|
146
|
+
<>
|
|
147
|
+
(<motion.span>{displayX}</motion.span>,{" "}
|
|
148
|
+
<motion.span>{displayY}</motion.span>)
|
|
149
|
+
</>
|
|
150
|
+
)}
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
{showSeparator && (
|
|
154
|
+
<span
|
|
155
|
+
className={cn("text-canvas-light", separatorClassName)}
|
|
156
|
+
style={separatorStyle}
|
|
157
|
+
>
|
|
158
|
+
{separator}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
{showScale && (
|
|
162
|
+
<span className={scaleClassName} style={scaleStyle}>
|
|
163
|
+
{formattedScale !== null ? (
|
|
164
|
+
formattedScale
|
|
165
|
+
) : (
|
|
166
|
+
<>
|
|
167
|
+
<motion.span>{displayScale}</motion.span>x
|
|
168
|
+
</>
|
|
169
|
+
)}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
59
172
|
</>
|
|
60
173
|
) : (
|
|
61
|
-
<span style={{ opacity: 0 }}>
|
|
174
|
+
<span style={{ opacity: 0 }}>{placeholderContent}</span>
|
|
62
175
|
)}
|
|
63
176
|
</motion.div>
|
|
64
177
|
);
|
package/src/index.ts
CHANGED
|
@@ -1,41 +1,59 @@
|
|
|
1
1
|
// Components
|
|
2
|
-
export { default as Canvas } from
|
|
3
|
-
export { CanvasComponent } from
|
|
4
|
-
export { Draggable, DraggableImage } from
|
|
5
|
-
export { CanvasWrapper, growTransition } from
|
|
6
|
-
export { default as CanvasToolbar } from
|
|
7
|
-
export { default as CanvasNavbar } from
|
|
2
|
+
export { default as Canvas } from "./components/canvas/canvas";
|
|
3
|
+
export { CanvasComponent } from "./components/canvas/component";
|
|
4
|
+
export { Draggable, DraggableImage } from "./components/canvas/draggable";
|
|
5
|
+
export { CanvasWrapper, growTransition } from "./components/canvas/wrapper";
|
|
6
|
+
export { default as CanvasToolbar } from "./components/canvas/toolbar";
|
|
7
|
+
export { default as CanvasNavbar } from "./components/canvas/navbar";
|
|
8
8
|
|
|
9
9
|
// Background Components
|
|
10
10
|
export {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from
|
|
11
|
+
DefaultCanvasBackground,
|
|
12
|
+
DefaultWrapperBackground,
|
|
13
|
+
DefaultIntroContent,
|
|
14
|
+
DEFAULT_CANVAS_GRADIENT,
|
|
15
|
+
DEFAULT_INTRO_GRADIENT,
|
|
16
|
+
DEFAULT_CANVAS_BOX_GRADIENT,
|
|
17
|
+
} from "./components/canvas/backgrounds";
|
|
18
18
|
export type {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from
|
|
19
|
+
DefaultCanvasBackgroundProps,
|
|
20
|
+
DefaultWrapperBackgroundProps,
|
|
21
|
+
DefaultIntroContentProps,
|
|
22
|
+
} from "./components/canvas/backgrounds";
|
|
23
23
|
|
|
24
24
|
// Contexts
|
|
25
|
-
export {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
export {
|
|
26
|
+
CanvasContext,
|
|
27
|
+
CanvasProvider,
|
|
28
|
+
useCanvasContext,
|
|
29
|
+
} from "./contexts/CanvasContext";
|
|
30
|
+
export type { CanvasContextState } from "./contexts/CanvasContext";
|
|
31
|
+
export {
|
|
32
|
+
PerformanceProvider,
|
|
33
|
+
usePerformanceMode,
|
|
34
|
+
usePerformance,
|
|
35
|
+
} from "./contexts/PerformanceContext";
|
|
36
|
+
export type {
|
|
37
|
+
PerformanceMode,
|
|
38
|
+
PerformanceConfig,
|
|
39
|
+
} from "./contexts/PerformanceContext";
|
|
29
40
|
|
|
30
41
|
// Hooks
|
|
31
|
-
export { default as useWindowDimensions } from
|
|
32
|
-
export { usePerformanceMode as usePerformanceModeLegacy } from
|
|
42
|
+
export { default as useWindowDimensions } from "./hooks/useWindowDimensions";
|
|
43
|
+
export { usePerformanceMode as usePerformanceModeLegacy } from "./hooks/usePerformanceMode";
|
|
33
44
|
|
|
34
45
|
// Utilities
|
|
35
|
-
export * from
|
|
36
|
-
export * from
|
|
37
|
-
export * from
|
|
38
|
-
export * from
|
|
46
|
+
export * from "./lib/canvas";
|
|
47
|
+
export * from "./lib/constants";
|
|
48
|
+
export * from "./lib/utils";
|
|
49
|
+
export * from "./utils/performance";
|
|
39
50
|
|
|
40
51
|
// Types
|
|
41
|
-
export type {
|
|
52
|
+
export type {
|
|
53
|
+
SectionCoordinates,
|
|
54
|
+
NavItem,
|
|
55
|
+
CanvasSection,
|
|
56
|
+
ToolbarConfig,
|
|
57
|
+
ToolbarPosition,
|
|
58
|
+
ToolbarDisplayMode,
|
|
59
|
+
} from "./types";
|
package/src/lib/canvas.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { animate, type MotionValue, type Point } from "framer-motion";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
DEFAULT_CANVAS_WIDTH,
|
|
5
|
+
DEFAULT_CANVAS_HEIGHT,
|
|
6
6
|
MAX_DIM_RATIO,
|
|
7
7
|
INTRO_ASPECT_RATIO,
|
|
8
8
|
PAN_SPRING,
|
|
9
9
|
ScreenSizeEnum,
|
|
10
10
|
} from "./constants";
|
|
11
11
|
|
|
12
|
-
export const canvasWidth =
|
|
13
|
-
export const canvasHeight =
|
|
12
|
+
export const canvasWidth = DEFAULT_CANVAS_WIDTH;
|
|
13
|
+
export const canvasHeight = DEFAULT_CANVAS_HEIGHT;
|
|
14
14
|
|
|
15
15
|
// Re-export ScreenSizeEnum for backward compatibility
|
|
16
16
|
export { ScreenSizeEnum } from "./constants";
|
package/src/lib/constants.ts
CHANGED
|
@@ -24,10 +24,10 @@ export enum ScreenSizeEnum {
|
|
|
24
24
|
// ============================================================================
|
|
25
25
|
|
|
26
26
|
/** Default canvas width in pixels */
|
|
27
|
-
export const
|
|
27
|
+
export const DEFAULT_CANVAS_WIDTH = 6000;
|
|
28
28
|
|
|
29
29
|
/** Default canvas height in pixels */
|
|
30
|
-
export const
|
|
30
|
+
export const DEFAULT_CANVAS_HEIGHT = 4000;
|
|
31
31
|
|
|
32
32
|
// ============================================================================
|
|
33
33
|
// INTRO ANIMATION
|
package/src/types/index.ts
CHANGED
|
@@ -38,3 +38,62 @@ export interface NavItem {
|
|
|
38
38
|
/** If true, clicking this section triggers the reset/home behavior */
|
|
39
39
|
isHome?: boolean;
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Preset positions for the toolbar
|
|
44
|
+
*/
|
|
45
|
+
export type ToolbarPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* What to display in the toolbar
|
|
49
|
+
*/
|
|
50
|
+
export type ToolbarDisplayMode = 'coordinates' | 'scale' | 'both';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Configuration options for the canvas toolbar
|
|
54
|
+
*/
|
|
55
|
+
export interface ToolbarConfig {
|
|
56
|
+
// === Visibility ===
|
|
57
|
+
/** Hide the toolbar entirely. Default: false */
|
|
58
|
+
hidden?: boolean;
|
|
59
|
+
|
|
60
|
+
// === Display Mode ===
|
|
61
|
+
/** What to show: 'coordinates', 'scale', or 'both'. Default: 'both' */
|
|
62
|
+
display?: ToolbarDisplayMode;
|
|
63
|
+
|
|
64
|
+
// === Positioning ===
|
|
65
|
+
/** Preset position. Default: 'top-left' */
|
|
66
|
+
position?: ToolbarPosition;
|
|
67
|
+
|
|
68
|
+
// === Auto-hide Behavior ===
|
|
69
|
+
/** Disable auto-hide when at home position. Default: false */
|
|
70
|
+
disableAutoHide?: boolean;
|
|
71
|
+
|
|
72
|
+
// === Styling (Tailwind-friendly) ===
|
|
73
|
+
/** Additional className for the container */
|
|
74
|
+
className?: string;
|
|
75
|
+
/** Additional className for the coordinates text */
|
|
76
|
+
coordinatesClassName?: string;
|
|
77
|
+
/** Additional className for the scale text */
|
|
78
|
+
scaleClassName?: string;
|
|
79
|
+
/** Additional className for the separator */
|
|
80
|
+
separatorClassName?: string;
|
|
81
|
+
|
|
82
|
+
// === Styling (non-Tailwind / inline styles) ===
|
|
83
|
+
/** Inline styles for the container */
|
|
84
|
+
style?: React.CSSProperties;
|
|
85
|
+
/** Inline styles for the coordinates */
|
|
86
|
+
coordinatesStyle?: React.CSSProperties;
|
|
87
|
+
/** Inline styles for the scale */
|
|
88
|
+
scaleStyle?: React.CSSProperties;
|
|
89
|
+
|
|
90
|
+
// === Content Customization ===
|
|
91
|
+
/** Custom separator between coordinates and scale. Default: ' | ' */
|
|
92
|
+
separator?: string;
|
|
93
|
+
/** Gap around the separator in pixels or CSS value. Default: undefined (uses inline spacing) */
|
|
94
|
+
separatorGap?: number | string;
|
|
95
|
+
/** Format for coordinates. Default: '(x, y)' */
|
|
96
|
+
coordinatesFormat?: (x: number, y: number) => string;
|
|
97
|
+
/** Format for scale. Default: '1.00x' */
|
|
98
|
+
scaleFormat?: (scale: number) => string;
|
|
99
|
+
}
|