@vuer-ai/vuer-uikit 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-CP7QYV4N.mjs → chunk-44KW6PTD.mjs} +1 -0
- package/dist/{chunk-KQAHAGUG.js → chunk-AOUGWGQR.js} +2 -2
- package/dist/{chunk-J7PTSP4Q.mjs → chunk-EAZQH4IX.mjs} +8 -0
- package/dist/chunk-EOFYA6CS.js +53 -0
- package/dist/{chunk-O4TK3AKI.mjs → chunk-EYIFKSU2.mjs} +1 -1
- package/dist/{chunk-WMXX2UMD.mjs → chunk-FEOONYZ2.mjs} +2 -2
- package/dist/{chunk-66DYOZI3.js → chunk-FORVP7XH.js} +6 -5
- package/dist/{chunk-7X6N54LX.mjs → chunk-G5KMD72J.mjs} +6 -5
- package/dist/{chunk-5MZCQW4U.js → chunk-IXHMIR5P.js} +4 -4
- package/dist/{chunk-CCGUGCCV.js → chunk-JAJNLRY5.js} +9 -9
- package/dist/{chunk-WOZWC6KO.mjs → chunk-JMPVDU2C.mjs} +35 -4
- package/dist/{chunk-E5KWKBH3.mjs → chunk-LEPXSM36.mjs} +10 -24
- package/dist/{chunk-3XVIJY3H.mjs → chunk-MRVMXKZO.mjs} +1 -1
- package/dist/{chunk-UMIQYHO6.mjs → chunk-OOI3SJDU.mjs} +10 -4
- package/dist/{chunk-B5EMQBVL.js → chunk-OTPUWWEB.js} +8 -3
- package/dist/{chunk-FSMKJAZT.js → chunk-PVRQNCSJ.js} +9 -23
- package/dist/chunk-Q5KEB4UK.mjs +51 -0
- package/dist/{chunk-XUQ5A7ZA.js → chunk-QEY7BWIR.js} +10 -4
- package/dist/{chunk-QZASHJNJ.js → chunk-QJR6MCTR.js} +8 -0
- package/dist/{chunk-IOI3YFTY.js → chunk-U2ZLDPOE.js} +8 -8
- package/dist/{chunk-ZJXBC33M.js → chunk-UFHMEEGL.js} +1 -0
- package/dist/{chunk-5P3KAS53.js → chunk-WJ7T7FAH.js} +34 -3
- package/dist/{chunk-6L4KPPOD.mjs → chunk-XMG3IVGZ.mjs} +8 -3
- package/dist/{chunk-XCCC7HVN.mjs → chunk-ZVG2UKKC.mjs} +1 -1
- package/dist/highlight-cursor/cursor-provider.js +2 -2
- package/dist/highlight-cursor/cursor-provider.mjs +1 -1
- package/dist/highlight-cursor/enhanced-components.js +11 -10
- package/dist/highlight-cursor/enhanced-components.mjs +6 -5
- package/dist/highlight-cursor/index.js +15 -14
- package/dist/highlight-cursor/index.mjs +7 -6
- package/dist/highlight-cursor/tabs-cursor-context.js +3 -3
- package/dist/highlight-cursor/tabs-cursor-context.mjs +1 -1
- package/dist/highlight-cursor/with-cursor.js +2 -2
- package/dist/highlight-cursor/with-cursor.mjs +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +64 -59
- package/dist/index.mjs +15 -14
- package/dist/ui/avatar.js +2 -1
- package/dist/ui/avatar.mjs +2 -1
- package/dist/ui/badge.js +2 -1
- package/dist/ui/badge.mjs +2 -1
- package/dist/ui/button.js +2 -1
- package/dist/ui/button.mjs +2 -1
- package/dist/ui/card.js +2 -1
- package/dist/ui/card.mjs +2 -1
- package/dist/ui/checkbox.js +2 -1
- package/dist/ui/checkbox.mjs +2 -1
- package/dist/ui/collapsible.js +2 -1
- package/dist/ui/collapsible.mjs +2 -1
- package/dist/ui/dropdown.js +2 -1
- package/dist/ui/dropdown.mjs +2 -1
- package/dist/ui/index.js +49 -48
- package/dist/ui/index.mjs +12 -11
- package/dist/ui/input-numbers.js +4 -3
- package/dist/ui/input-numbers.mjs +3 -2
- package/dist/ui/input.js +2 -1
- package/dist/ui/input.mjs +2 -1
- package/dist/ui/layout.js +2 -1
- package/dist/ui/layout.mjs +2 -1
- package/dist/ui/modal.js +2 -1
- package/dist/ui/modal.mjs +2 -1
- package/dist/ui/popover.js +2 -1
- package/dist/ui/popover.mjs +2 -1
- package/dist/ui/radio-group.js +2 -1
- package/dist/ui/radio-group.mjs +2 -1
- package/dist/ui/select.js +2 -1
- package/dist/ui/select.mjs +2 -1
- package/dist/ui/slider.js +2 -1
- package/dist/ui/slider.mjs +2 -1
- package/dist/ui/switch.js +2 -1
- package/dist/ui/switch.mjs +2 -1
- package/dist/ui/tabs.js +8 -7
- package/dist/ui/tabs.mjs +4 -3
- package/dist/ui/textarea.js +2 -1
- package/dist/ui/textarea.mjs +2 -1
- package/dist/ui/theme-context.js +6 -3
- package/dist/ui/theme-context.mjs +4 -1
- package/dist/ui/timeline.js +6 -5
- package/dist/ui/timeline.mjs +5 -4
- package/dist/ui/toast.d.mts +1 -1
- package/dist/ui/toast.d.ts +1 -1
- package/dist/ui/toast.js +3 -3
- package/dist/ui/toast.mjs +1 -1
- package/dist/ui/toggle-group.js +2 -1
- package/dist/ui/toggle-group.mjs +2 -1
- package/dist/ui/toggle.js +2 -1
- package/dist/ui/toggle.mjs +2 -1
- package/dist/ui/tooltip.d.mts +3 -3
- package/dist/ui/tooltip.d.ts +3 -3
- package/dist/ui/tooltip.js +7 -6
- package/dist/ui/tooltip.mjs +3 -2
- package/dist/ui/tree-view-v2.js +5 -4
- package/dist/ui/tree-view-v2.mjs +4 -3
- package/dist/ui/version-badge.js +2 -1
- package/dist/ui/version-badge.mjs +2 -1
- package/dist/utils/index.d.mts +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +6 -1
- package/dist/utils/index.mjs +2 -1
- package/dist/utils/use-local-storage.d.mts +3 -0
- package/dist/utils/use-local-storage.d.ts +3 -0
- package/dist/utils/use-local-storage.js +10 -0
- package/dist/utils/use-local-storage.mjs +1 -0
- package/package.json +6 -4
- package/src/highlight-cursor/cursor-context.tsx +23 -0
- package/src/highlight-cursor/cursor-provider.tsx +264 -0
- package/src/highlight-cursor/enhanced-components.tsx +16 -0
- package/src/highlight-cursor/index.ts +21 -0
- package/src/highlight-cursor/tabs-cursor-context.tsx +121 -0
- package/src/highlight-cursor/types.ts +40 -0
- package/src/highlight-cursor/with-cursor.tsx +144 -0
- package/src/index.css +5 -0
- package/src/index.ts +5 -0
- package/src/styles/theme.css +80 -0
- package/src/styles/toast.css +64 -0
- package/src/styles/variables.css +194 -0
- package/src/ui/avatar.tsx +92 -0
- package/src/ui/badge.tsx +68 -0
- package/src/ui/button.tsx +100 -0
- package/src/ui/card.tsx +88 -0
- package/src/ui/checkbox.tsx +76 -0
- package/src/ui/collapsible.tsx +36 -0
- package/src/ui/dropdown.tsx +375 -0
- package/src/ui/index.ts +31 -0
- package/src/ui/input-numbers.tsx +201 -0
- package/src/ui/input.tsx +141 -0
- package/src/ui/layout.tsx +41 -0
- package/src/ui/modal.tsx +198 -0
- package/src/ui/popover.tsx +59 -0
- package/src/ui/radio-group.tsx +56 -0
- package/src/ui/select.tsx +292 -0
- package/src/ui/sheet.tsx +131 -0
- package/src/ui/slider.tsx +189 -0
- package/src/ui/switch.tsx +43 -0
- package/src/ui/tabs.tsx +128 -0
- package/src/ui/textarea.tsx +52 -0
- package/src/ui/theme-context.tsx +80 -0
- package/src/ui/timeline.tsx +717 -0
- package/src/ui/toast.tsx +27 -0
- package/src/ui/toggle-group.tsx +82 -0
- package/src/ui/toggle.tsx +88 -0
- package/src/ui/tooltip.tsx +132 -0
- package/src/ui/tree-view-v2.tsx +300 -0
- package/src/ui/tree-view.tsx +550 -0
- package/src/ui/version-badge.tsx +65 -0
- package/src/utils/cn.ts +21 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/use-local-storage.ts +59 -0
- package/dist/chunk-OBWGRG45.js +0 -21
- package/dist/chunk-Z6XRPTIQ.mjs +0 -15
- /package/dist/{chunk-QS7WS6DD.mjs → chunk-M4IVLZIH.mjs} +0 -0
- /package/dist/{chunk-ST4P6AN6.js → chunk-XWS4SIB5.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vuer-ai/vuer-uikit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "React UI Kit components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -27,19 +27,21 @@
|
|
|
27
27
|
},
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"files": [
|
|
30
|
-
"dist"
|
|
30
|
+
"dist",
|
|
31
|
+
"src"
|
|
31
32
|
],
|
|
32
33
|
"lint-staged": {
|
|
33
34
|
"*.{js,ts,jsx,tsx}": "eslint --fix"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
37
|
+
"lucide-react": ">=0.400.0",
|
|
36
38
|
"react": "^19.0.0",
|
|
37
39
|
"react-dom": "^19.0.0",
|
|
38
40
|
"tailwindcss": "^4.0.0"
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {
|
|
41
43
|
"@radix-ui/react-accordion": "^1.2.11",
|
|
42
|
-
"@radix-ui/react-avatar": "^1.1.
|
|
44
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
43
45
|
"@radix-ui/react-checkbox": "^1.3.2",
|
|
44
46
|
"@radix-ui/react-collapsible": "^1.1.11",
|
|
45
47
|
"@radix-ui/react-compose-refs": "^1.1.2",
|
|
@@ -59,7 +61,6 @@
|
|
|
59
61
|
"class-variance-authority": "^0.7.1",
|
|
60
62
|
"clsx": "^2.1.1",
|
|
61
63
|
"framer-motion": "^11.15.0",
|
|
62
|
-
"lucide-react": "^0.525.0",
|
|
63
64
|
"sonner": "^2.0.6",
|
|
64
65
|
"tailwind-merge": "^3.3.1"
|
|
65
66
|
},
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"globals": "^16.3.0",
|
|
80
81
|
"husky": "^9.1.7",
|
|
81
82
|
"lint-staged": "^16.1.2",
|
|
83
|
+
"lucide-react": "^0.525.0",
|
|
82
84
|
"postcss": "^8.5.6",
|
|
83
85
|
"postcss-cli": "^11.0.1",
|
|
84
86
|
"postcss-import": "^16.1.1",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import type { CursorContextType } from "./types";
|
|
4
|
+
|
|
5
|
+
const defaultDimensions = {
|
|
6
|
+
width: 0,
|
|
7
|
+
height: 0,
|
|
8
|
+
x: 0,
|
|
9
|
+
y: 0,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const defaultContext: CursorContextType = {
|
|
13
|
+
mousePosition: { x: 0, y: 0 },
|
|
14
|
+
hoveredElementId: null,
|
|
15
|
+
elementDimensions: defaultDimensions,
|
|
16
|
+
registerHoveredElement: () => {},
|
|
17
|
+
unregisterHoveredElement: () => {},
|
|
18
|
+
updateElementDimensions: () => {},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const CursorContext = createContext<CursorContextType>(defaultContext);
|
|
22
|
+
|
|
23
|
+
export const useCursor = () => useContext(CursorContext);
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
import { CursorContext } from "./cursor-context";
|
|
5
|
+
import type { CursorProviderProps } from "./types";
|
|
6
|
+
import { cn } from "../utils/cn";
|
|
7
|
+
|
|
8
|
+
export const CursorProvider = ({
|
|
9
|
+
children,
|
|
10
|
+
maxOffsetX = 5,
|
|
11
|
+
maxOffsetY = 20,
|
|
12
|
+
cursorSize = 20,
|
|
13
|
+
transitionDuration = 100,
|
|
14
|
+
cursorClassName,
|
|
15
|
+
className,
|
|
16
|
+
as: Component = "div",
|
|
17
|
+
...props
|
|
18
|
+
}: CursorProviderProps) => {
|
|
19
|
+
// Check if we're in a browser environment
|
|
20
|
+
const [isClient, setIsClient] = useState(false);
|
|
21
|
+
const [isMouseInside, setIsMouseInside] = useState(false);
|
|
22
|
+
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
|
23
|
+
const [hoveredElementId, setHoveredElementId] = useState<string | null>(null);
|
|
24
|
+
const [elementDimensions, setElementDimensions] = useState({
|
|
25
|
+
width: 0,
|
|
26
|
+
height: 0,
|
|
27
|
+
x: 0,
|
|
28
|
+
y: 0,
|
|
29
|
+
right: 0,
|
|
30
|
+
bottom: 0,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const hoveredElementRef = useRef<HTMLElement | null>(null);
|
|
34
|
+
|
|
35
|
+
// Set isClient to true after component mounts (client-side only)
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setIsClient(true);
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!isClient) return;
|
|
42
|
+
|
|
43
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
44
|
+
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
48
|
+
return () => document.removeEventListener("mousemove", handleMouseMove);
|
|
49
|
+
}, [isClient]);
|
|
50
|
+
|
|
51
|
+
const handleMouseEnter = useCallback(() => {
|
|
52
|
+
setIsMouseInside(true);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const handleMouseLeave = useCallback(() => {
|
|
56
|
+
setIsMouseInside(false);
|
|
57
|
+
setHoveredElementId(null);
|
|
58
|
+
hoveredElementRef.current = null;
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const checkMouseInElement = useCallback(() => {
|
|
62
|
+
if (!hoveredElementRef.current || !hoveredElementId) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const rect = hoveredElementRef.current.getBoundingClientRect();
|
|
67
|
+
|
|
68
|
+
setElementDimensions({
|
|
69
|
+
width: rect.width,
|
|
70
|
+
height: rect.height,
|
|
71
|
+
x: rect.left,
|
|
72
|
+
y: rect.top,
|
|
73
|
+
right: rect.right,
|
|
74
|
+
bottom: rect.bottom,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const isInside =
|
|
78
|
+
mousePosition.x >= rect.left &&
|
|
79
|
+
mousePosition.x <= rect.right &&
|
|
80
|
+
mousePosition.y >= rect.top &&
|
|
81
|
+
mousePosition.y <= rect.bottom;
|
|
82
|
+
|
|
83
|
+
if (!isInside) {
|
|
84
|
+
setHoveredElementId(null);
|
|
85
|
+
hoveredElementRef.current = null;
|
|
86
|
+
}
|
|
87
|
+
}, [mousePosition, hoveredElementId]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!isClient) return;
|
|
91
|
+
|
|
92
|
+
const handleScroll = () => {
|
|
93
|
+
checkMouseInElement();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
document.addEventListener("scroll", handleScroll, { passive: true, capture: true });
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
document.removeEventListener("scroll", handleScroll, { capture: true });
|
|
100
|
+
};
|
|
101
|
+
}, [checkMouseInElement, isClient]);
|
|
102
|
+
|
|
103
|
+
const registerHoveredElement = useCallback(
|
|
104
|
+
(id: string, dimensions: DOMRect, element?: HTMLElement) => {
|
|
105
|
+
setHoveredElementId(id);
|
|
106
|
+
setElementDimensions({
|
|
107
|
+
width: dimensions.width,
|
|
108
|
+
height: dimensions.height,
|
|
109
|
+
x: dimensions.left,
|
|
110
|
+
y: dimensions.top,
|
|
111
|
+
right: dimensions.right,
|
|
112
|
+
bottom: dimensions.bottom,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (element) {
|
|
116
|
+
hoveredElementRef.current = element;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
[],
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const unregisterHoveredElement = useCallback(() => {
|
|
123
|
+
setHoveredElementId(null);
|
|
124
|
+
hoveredElementRef.current = null;
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
const updateElementDimensions = useCallback(
|
|
128
|
+
(dimensions: DOMRect) => {
|
|
129
|
+
if (hoveredElementId) {
|
|
130
|
+
setElementDimensions({
|
|
131
|
+
width: dimensions.width,
|
|
132
|
+
height: dimensions.height,
|
|
133
|
+
x: dimensions.left,
|
|
134
|
+
y: dimensions.top,
|
|
135
|
+
right: dimensions.right,
|
|
136
|
+
bottom: dimensions.bottom,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
[hoveredElementId],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const getCursorPosition = () => {
|
|
144
|
+
if (!hoveredElementId) {
|
|
145
|
+
return {
|
|
146
|
+
x: mousePosition.x - cursorSize / 2,
|
|
147
|
+
y: mousePosition.y - cursorSize / 2,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Calculate the center of the element
|
|
152
|
+
const elementCenterX = elementDimensions.x + elementDimensions.width / 2;
|
|
153
|
+
const elementCenterY = elementDimensions.y + elementDimensions.height / 2;
|
|
154
|
+
|
|
155
|
+
// Calculate offset from center (with some dampening for smoothness)
|
|
156
|
+
const offsetX = (mousePosition.x - elementCenterX) * 0.1;
|
|
157
|
+
const offsetY = (mousePosition.y - elementCenterY) * 0.1;
|
|
158
|
+
|
|
159
|
+
// Apply bounds to keep cursor within reasonable limits
|
|
160
|
+
const boundedOffsetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, offsetX));
|
|
161
|
+
const boundedOffsetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, offsetY));
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
x: elementDimensions.x + boundedOffsetX,
|
|
165
|
+
y: elementDimensions.y + boundedOffsetY,
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const contextValue = useMemo(
|
|
170
|
+
() => ({
|
|
171
|
+
// State values that change during component lifecycle
|
|
172
|
+
mousePosition,
|
|
173
|
+
hoveredElementId,
|
|
174
|
+
elementDimensions,
|
|
175
|
+
|
|
176
|
+
// Callbacks that should be stable references
|
|
177
|
+
registerHoveredElement,
|
|
178
|
+
unregisterHoveredElement,
|
|
179
|
+
updateElementDimensions,
|
|
180
|
+
}),
|
|
181
|
+
[
|
|
182
|
+
mousePosition,
|
|
183
|
+
hoveredElementId,
|
|
184
|
+
elementDimensions,
|
|
185
|
+
registerHoveredElement,
|
|
186
|
+
unregisterHoveredElement,
|
|
187
|
+
updateElementDimensions,
|
|
188
|
+
],
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const cursorPosition = getCursorPosition();
|
|
192
|
+
|
|
193
|
+
const cursorStyleObject = useMemo(
|
|
194
|
+
() => ({
|
|
195
|
+
left: cursorPosition.x,
|
|
196
|
+
top: cursorPosition.y,
|
|
197
|
+
width: hoveredElementId
|
|
198
|
+
? elementDimensions.width > 0
|
|
199
|
+
? elementDimensions.width
|
|
200
|
+
: cursorSize
|
|
201
|
+
: cursorSize,
|
|
202
|
+
height: hoveredElementId
|
|
203
|
+
? elementDimensions.height > 0
|
|
204
|
+
? elementDimensions.height
|
|
205
|
+
: cursorSize
|
|
206
|
+
: cursorSize,
|
|
207
|
+
willChange: "transform, width, height",
|
|
208
|
+
}),
|
|
209
|
+
[
|
|
210
|
+
cursorPosition.x,
|
|
211
|
+
cursorPosition.y,
|
|
212
|
+
hoveredElementId,
|
|
213
|
+
elementDimensions.width,
|
|
214
|
+
elementDimensions.height,
|
|
215
|
+
cursorSize,
|
|
216
|
+
],
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
let styleClass;
|
|
220
|
+
if (hoveredElementId) {
|
|
221
|
+
styleClass = `opacity-[0.05] rounded-md transition-[width,height,left,top] duration-${transitionDuration} ease-out`;
|
|
222
|
+
} else {
|
|
223
|
+
styleClass = `opacity-90 rounded-xl z-10 transition-[width,height,left,top] duration-${transitionDuration} ease-out transition-opacity duration-${transitionDuration}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Create cursor element to be portaled
|
|
227
|
+
const cursorElement = isClient && isMouseInside && (
|
|
228
|
+
<div
|
|
229
|
+
className={`pointer-events-none fixed ${styleClass} ${hoveredElementId ? "" : cursorClassName || ""}`}
|
|
230
|
+
style={{
|
|
231
|
+
...cursorStyleObject,
|
|
232
|
+
backgroundColor: "var(--shadow-tertiary)",
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Use React.Fragment at the top level to avoid any DOM structure that might affect layout
|
|
238
|
+
return (
|
|
239
|
+
<>
|
|
240
|
+
{/* Portal the cursor overlay to document.body to completely avoid layout impact */}
|
|
241
|
+
{isClient &&
|
|
242
|
+
typeof document !== "undefined" &&
|
|
243
|
+
document.body &&
|
|
244
|
+
cursorElement &&
|
|
245
|
+
createPortal(cursorElement, document.body)}
|
|
246
|
+
{/* Context provider with no DOM element */}
|
|
247
|
+
<CursorContext.Provider value={contextValue}>
|
|
248
|
+
{/* Use the Component directly with no extra wrappers */}
|
|
249
|
+
<Component
|
|
250
|
+
{...props}
|
|
251
|
+
className={cn(className)}
|
|
252
|
+
style={{
|
|
253
|
+
cursor: isMouseInside && !hoveredElementId ? "none" : "auto",
|
|
254
|
+
...props.style,
|
|
255
|
+
}}
|
|
256
|
+
onMouseEnter={handleMouseEnter}
|
|
257
|
+
onMouseLeave={handleMouseLeave}
|
|
258
|
+
>
|
|
259
|
+
{children}
|
|
260
|
+
</Component>
|
|
261
|
+
</CursorContext.Provider>
|
|
262
|
+
</>
|
|
263
|
+
);
|
|
264
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { withCursor } from "./with-cursor";
|
|
2
|
+
import { Button } from "../ui/button";
|
|
3
|
+
import { InputRoot } from "../ui/input";
|
|
4
|
+
import { SelectTrigger } from "../ui/select";
|
|
5
|
+
import { Tabs } from "../ui/tabs";
|
|
6
|
+
import { Textarea } from "../ui/textarea";
|
|
7
|
+
|
|
8
|
+
export const CursorButton = withCursor(Button);
|
|
9
|
+
|
|
10
|
+
export const CursorTabs = withCursor(Tabs);
|
|
11
|
+
|
|
12
|
+
export const CursorInputRoot = withCursor(InputRoot);
|
|
13
|
+
|
|
14
|
+
export const CursorSelectTrigger = withCursor(SelectTrigger) as typeof SelectTrigger;
|
|
15
|
+
|
|
16
|
+
export const CursorTextarea = withCursor(Textarea) as typeof Textarea;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CursorProvider } from "./cursor-provider";
|
|
2
|
+
import {
|
|
3
|
+
CursorButton,
|
|
4
|
+
CursorTabs,
|
|
5
|
+
CursorInputRoot,
|
|
6
|
+
CursorSelectTrigger,
|
|
7
|
+
CursorTextarea,
|
|
8
|
+
} from "./enhanced-components";
|
|
9
|
+
import { TabsCursorProvider } from "./tabs-cursor-context";
|
|
10
|
+
import { withCursor } from "./with-cursor";
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
CursorProvider,
|
|
14
|
+
TabsCursorProvider,
|
|
15
|
+
withCursor,
|
|
16
|
+
CursorButton,
|
|
17
|
+
CursorTabs,
|
|
18
|
+
CursorInputRoot,
|
|
19
|
+
CursorSelectTrigger,
|
|
20
|
+
CursorTextarea,
|
|
21
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { motion } from "framer-motion";
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useState,
|
|
5
|
+
useEffect,
|
|
6
|
+
useContext,
|
|
7
|
+
useCallback,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
type CSSProperties,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { createPortal } from "react-dom";
|
|
12
|
+
|
|
13
|
+
interface TabsCursorContextType {
|
|
14
|
+
setCursorTarget: (el: HTMLElement | null) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TabsCursorContext = createContext<TabsCursorContextType | null>(null);
|
|
18
|
+
|
|
19
|
+
export const TabsCursorProvider = ({
|
|
20
|
+
children,
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}: {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
style?: CSSProperties;
|
|
27
|
+
}) => {
|
|
28
|
+
const [isClient, setIsClient] = useState(false);
|
|
29
|
+
const [cursorTarget, setCursorTarget] = useState<HTMLElement | null>(null);
|
|
30
|
+
const [targetRect, setTargetRect] = useState<DOMRect | null>(null);
|
|
31
|
+
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
|
32
|
+
const [isMouseInside, setIsMouseInside] = useState(false);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setIsClient(true);
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!isClient) return;
|
|
40
|
+
|
|
41
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
42
|
+
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
46
|
+
return () => document.removeEventListener("mousemove", handleMouseMove);
|
|
47
|
+
}, [isClient]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (cursorTarget) {
|
|
51
|
+
setTargetRect(cursorTarget.getBoundingClientRect());
|
|
52
|
+
} else {
|
|
53
|
+
setTargetRect(null);
|
|
54
|
+
}
|
|
55
|
+
}, [cursorTarget]);
|
|
56
|
+
|
|
57
|
+
const handleMouseEnter = useCallback(() => {
|
|
58
|
+
setIsMouseInside(true);
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const handleMouseLeave = useCallback(() => {
|
|
62
|
+
setIsMouseInside(false);
|
|
63
|
+
setCursorTarget(null);
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
const contextValue = { setCursorTarget };
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
{...props}
|
|
71
|
+
className={className}
|
|
72
|
+
style={{
|
|
73
|
+
cursor: isMouseInside && !cursorTarget ? "none" : "auto",
|
|
74
|
+
...props.style,
|
|
75
|
+
}}
|
|
76
|
+
onMouseEnter={handleMouseEnter}
|
|
77
|
+
onMouseLeave={handleMouseLeave}
|
|
78
|
+
>
|
|
79
|
+
<TabsCursorContext.Provider value={contextValue}>
|
|
80
|
+
{children}
|
|
81
|
+
{isClient &&
|
|
82
|
+
isMouseInside &&
|
|
83
|
+
typeof document !== "undefined" &&
|
|
84
|
+
document.body &&
|
|
85
|
+
createPortal(
|
|
86
|
+
<motion.div
|
|
87
|
+
className="pointer-events-none"
|
|
88
|
+
initial={false}
|
|
89
|
+
animate={{
|
|
90
|
+
x: targetRect ? targetRect.left : mousePosition.x - 8,
|
|
91
|
+
y: targetRect ? targetRect.top : mousePosition.y - 8,
|
|
92
|
+
width: targetRect ? targetRect.width : 16,
|
|
93
|
+
height: targetRect ? targetRect.height : 16,
|
|
94
|
+
borderRadius:
|
|
95
|
+
targetRect && cursorTarget && typeof window !== "undefined"
|
|
96
|
+
? getComputedStyle(cursorTarget).borderRadius
|
|
97
|
+
: "50%",
|
|
98
|
+
}}
|
|
99
|
+
transition={{ type: "spring", stiffness: 1000, damping: 60, mass: 1 }}
|
|
100
|
+
style={{
|
|
101
|
+
position: "fixed",
|
|
102
|
+
top: 0,
|
|
103
|
+
left: 0,
|
|
104
|
+
backgroundColor: "var(--shadow-tertiary)",
|
|
105
|
+
zIndex: 9999,
|
|
106
|
+
}}
|
|
107
|
+
/>,
|
|
108
|
+
document.body,
|
|
109
|
+
)}
|
|
110
|
+
</TabsCursorContext.Provider>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const useTabsCursor = () => {
|
|
116
|
+
const context = useContext(TabsCursorContext);
|
|
117
|
+
if (!context) {
|
|
118
|
+
return { setCursorTarget: () => {} };
|
|
119
|
+
}
|
|
120
|
+
return context;
|
|
121
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode, ComponentType, HTMLAttributes, ElementType, MouseEvent } from "react";
|
|
2
|
+
|
|
3
|
+
export interface CursorContextType {
|
|
4
|
+
mousePosition: { x: number; y: number };
|
|
5
|
+
hoveredElementId: string | null;
|
|
6
|
+
elementDimensions: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
right?: number;
|
|
12
|
+
bottom?: number;
|
|
13
|
+
};
|
|
14
|
+
registerHoveredElement: (id: string, dimensions: DOMRect, element?: HTMLElement) => void;
|
|
15
|
+
unregisterHoveredElement: () => void;
|
|
16
|
+
updateElementDimensions: (dimensions: DOMRect) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CursorProviderProps extends HTMLAttributes<HTMLDivElement> {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
maxOffsetX?: number;
|
|
22
|
+
maxOffsetY?: number;
|
|
23
|
+
cursorSize?: number;
|
|
24
|
+
transitionDuration?: number;
|
|
25
|
+
cursorClassName?: string;
|
|
26
|
+
as?: ElementType;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WithCursorProps {
|
|
30
|
+
id?: string;
|
|
31
|
+
onMouseEnter?: (event: MouseEvent<HTMLElement>) => void;
|
|
32
|
+
onMouseMove?: (event: MouseEvent<HTMLElement>) => void;
|
|
33
|
+
onMouseLeave?: (event: MouseEvent<HTMLElement>) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type EnhancedComponentProps<P> = P & WithCursorProps;
|
|
37
|
+
|
|
38
|
+
export type EnhancedComponent<P = Record<string, unknown>> = ComponentType<
|
|
39
|
+
EnhancedComponentProps<P>
|
|
40
|
+
>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { forwardRef, useRef, useId, type ComponentType } from "react";
|
|
2
|
+
|
|
3
|
+
import { useCursor } from "./cursor-context";
|
|
4
|
+
import type { EnhancedComponentProps } from "./types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helper function to determine the visible portion of an element within scrollable containers
|
|
8
|
+
* @param element The DOM element we want to check visibility for
|
|
9
|
+
* @param elementRect The original bounding client rect of the element
|
|
10
|
+
* @returns A DOMRect representing only the visible portion of the element
|
|
11
|
+
*/
|
|
12
|
+
const getVisibleRect = (element: HTMLElement, elementRect: DOMRect): DOMRect => {
|
|
13
|
+
// Find all scrollable parent containers
|
|
14
|
+
let parent = element.parentElement;
|
|
15
|
+
let visibleRect = {
|
|
16
|
+
...elementRect,
|
|
17
|
+
left: elementRect.left,
|
|
18
|
+
right: elementRect.right,
|
|
19
|
+
top: elementRect.top,
|
|
20
|
+
bottom: elementRect.bottom,
|
|
21
|
+
width: elementRect.width,
|
|
22
|
+
height: elementRect.height,
|
|
23
|
+
x: elementRect.x,
|
|
24
|
+
y: elementRect.y,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Traverse up through parent elements to find scrollable containers
|
|
28
|
+
while (parent) {
|
|
29
|
+
if (typeof window === "undefined") break;
|
|
30
|
+
|
|
31
|
+
const style = window.getComputedStyle(parent);
|
|
32
|
+
const hasOverflow = [style.overflowY, style.overflowX].some((overflow) =>
|
|
33
|
+
["auto", "scroll", "hidden"].includes(overflow),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (hasOverflow) {
|
|
37
|
+
const parentRect = parent.getBoundingClientRect();
|
|
38
|
+
|
|
39
|
+
// Calculate the intersection between the element and its scrollable parent
|
|
40
|
+
const newLeft = Math.max(visibleRect.left, parentRect.left);
|
|
41
|
+
const newTop = Math.max(visibleRect.top, parentRect.top);
|
|
42
|
+
const newRight = Math.min(visibleRect.right, parentRect.right);
|
|
43
|
+
const newBottom = Math.min(visibleRect.bottom, parentRect.bottom);
|
|
44
|
+
|
|
45
|
+
// Update the visible rect dimensions
|
|
46
|
+
visibleRect = {
|
|
47
|
+
...visibleRect,
|
|
48
|
+
left: newLeft,
|
|
49
|
+
top: newTop,
|
|
50
|
+
right: newRight,
|
|
51
|
+
bottom: newBottom,
|
|
52
|
+
width: Math.max(0, newRight - newLeft),
|
|
53
|
+
height: Math.max(0, newBottom - newTop),
|
|
54
|
+
x: newLeft,
|
|
55
|
+
y: newTop,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// If element is completely outside the viewport of the parent, return empty rect
|
|
59
|
+
if (visibleRect.width <= 0 || visibleRect.height <= 0) {
|
|
60
|
+
return new DOMRect(0, 0, 0, 0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
parent = parent.parentElement;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return DOMRect.fromRect(visibleRect);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export function withCursor<P extends object>(Component: ComponentType<P>) {
|
|
71
|
+
// Create a display name for the enhanced component
|
|
72
|
+
const displayName = Component.displayName || Component.name || "Component";
|
|
73
|
+
|
|
74
|
+
const EnhancedComponent = forwardRef<HTMLElement, EnhancedComponentProps<P>>(
|
|
75
|
+
({ id: propId, onMouseEnter, onMouseMove, onMouseLeave, ...props }, ref) => {
|
|
76
|
+
const { registerHoveredElement, unregisterHoveredElement, updateElementDimensions } =
|
|
77
|
+
useCursor();
|
|
78
|
+
|
|
79
|
+
const generatedId = useId();
|
|
80
|
+
const id = propId || `cursor-element-${generatedId}`;
|
|
81
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
82
|
+
|
|
83
|
+
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
|
|
84
|
+
if (elementRef.current) {
|
|
85
|
+
const elementRect = elementRef.current.getBoundingClientRect();
|
|
86
|
+
const visibleRect = getVisibleRect(elementRef.current, elementRect);
|
|
87
|
+
registerHoveredElement(id, visibleRect, elementRef.current);
|
|
88
|
+
}
|
|
89
|
+
onMouseEnter?.(event);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleMouseMove = (event: React.MouseEvent<HTMLElement>) => {
|
|
93
|
+
if (elementRef.current) {
|
|
94
|
+
const elementRect = elementRef.current.getBoundingClientRect();
|
|
95
|
+
const visibleRect = getVisibleRect(elementRef.current, elementRect);
|
|
96
|
+
updateElementDimensions(visibleRect);
|
|
97
|
+
}
|
|
98
|
+
onMouseMove?.(event);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleMouseOut = (event: React.MouseEvent<HTMLElement>) => {
|
|
102
|
+
const rect = elementRef.current?.getBoundingClientRect();
|
|
103
|
+
if (rect) {
|
|
104
|
+
const { clientX, clientY } = event;
|
|
105
|
+
const isOutside =
|
|
106
|
+
clientX < rect.left ||
|
|
107
|
+
clientX > rect.right ||
|
|
108
|
+
clientY < rect.top ||
|
|
109
|
+
clientY > rect.bottom;
|
|
110
|
+
|
|
111
|
+
if (isOutside) {
|
|
112
|
+
unregisterHoveredElement();
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
unregisterHoveredElement();
|
|
116
|
+
}
|
|
117
|
+
onMouseLeave?.(event);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Component
|
|
122
|
+
{...(props as P)}
|
|
123
|
+
id={id}
|
|
124
|
+
ref={(node: HTMLElement) => {
|
|
125
|
+
// Handle both function and object refs
|
|
126
|
+
if (typeof ref === "function") {
|
|
127
|
+
ref(node);
|
|
128
|
+
} else if (ref) {
|
|
129
|
+
ref.current = node;
|
|
130
|
+
}
|
|
131
|
+
elementRef.current = node;
|
|
132
|
+
}}
|
|
133
|
+
onMouseEnter={handleMouseEnter}
|
|
134
|
+
onMouseMove={handleMouseMove}
|
|
135
|
+
onMouseOut={handleMouseOut}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
EnhancedComponent.displayName = `withCursor(${displayName})`;
|
|
142
|
+
|
|
143
|
+
return EnhancedComponent;
|
|
144
|
+
}
|
package/src/index.css
ADDED