@vuer-ai/vuer-uikit 0.0.96 → 0.0.98
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 +106 -7
- package/cli/dial-cli.js +49 -7
- package/dist/SyncScroll/SyncScroll.cjs +10 -10
- package/dist/SyncScroll/SyncScroll.mjs +3 -3
- package/dist/SyncScroll/index.cjs +10 -10
- package/dist/SyncScroll/index.mjs +3 -3
- package/dist/chunk-3HEZVWRW.mjs +62 -0
- package/dist/chunk-4KWGGESI.cjs +494 -0
- package/dist/{chunk-CCMKL2OA.cjs → chunk-7GWDO25E.cjs} +2 -2
- package/dist/chunk-A5LCX2UQ.cjs +208 -0
- package/dist/chunk-BEJIZ56L.mjs +300 -0
- package/dist/chunk-C7VGRU3O.mjs +283 -0
- package/dist/{chunk-LONOMMFA.cjs → chunk-LJMNHTTG.cjs} +21 -21
- package/dist/chunk-O66RESRR.cjs +285 -0
- package/dist/{chunk-RINTUFYQ.cjs → chunk-RMK6W774.cjs} +24 -19
- package/dist/{chunk-BFQ2WL5U.mjs → chunk-TTYSYGVE.mjs} +2 -2
- package/dist/chunk-VA3PEYFM.mjs +489 -0
- package/dist/chunk-VBBJSIY7.cjs +308 -0
- package/dist/{chunk-AIINOWEH.mjs → chunk-W4JCKCW7.mjs} +5 -5
- package/dist/chunk-WWGF6TBZ.mjs +206 -0
- package/dist/chunk-ZGN4UEJR.cjs +679 -0
- package/dist/chunk-ZQLRMOUW.mjs +661 -0
- package/dist/dial/DialPanel.cjs +24 -24
- package/dist/dial/DialPanel.mjs +23 -23
- package/dist/dial/DialProvider.cjs +3 -3
- package/dist/dial/DialProvider.d.cts +1 -0
- package/dist/dial/DialProvider.d.ts +1 -0
- package/dist/dial/DialProvider.example.cjs +72 -0
- package/dist/dial/DialProvider.example.d.cts +7 -0
- package/dist/dial/DialProvider.example.d.ts +7 -0
- package/dist/dial/DialProvider.example.mjs +68 -0
- package/dist/dial/DialProvider.mjs +1 -1
- package/dist/dial/index.cjs +42 -42
- package/dist/dial/index.mjs +23 -23
- package/dist/dial/wrapped-inputs/ControlledInputs.cjs +27 -27
- package/dist/dial/wrapped-inputs/ControlledInputs.mjs +23 -23
- package/dist/dial/wrapped-inputs/DialInputs.cjs +34 -34
- package/dist/dial/wrapped-inputs/DialInputs.mjs +23 -23
- package/dist/dial/wrapped-inputs/DialVectorInput.cjs +24 -24
- package/dist/dial/wrapped-inputs/DialVectorInput.mjs +23 -23
- package/dist/dial/wrapped-inputs/index.cjs +39 -39
- package/dist/dial/wrapped-inputs/index.mjs +23 -23
- package/dist/highlight-cursor/cursor-provider.cjs +3 -3
- package/dist/highlight-cursor/cursor-provider.mjs +2 -2
- package/dist/highlight-cursor/enhanced-components.cjs +10 -10
- package/dist/highlight-cursor/enhanced-components.mjs +5 -5
- package/dist/highlight-cursor/index.cjs +16 -16
- package/dist/highlight-cursor/index.mjs +6 -6
- package/dist/hooks/index.cjs +5 -5
- package/dist/hooks/index.mjs +1 -1
- package/dist/index.cjs +190 -190
- package/dist/index.mjs +23 -23
- package/dist/ui/UIKitBadge.cjs +6 -6
- package/dist/ui/UIKitBadge.mjs +2 -2
- package/dist/ui/avatar.cjs +1 -1
- package/dist/ui/avatar.mjs +1 -1
- package/dist/ui/badge.cjs +1 -1
- package/dist/ui/badge.d.cts +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/badge.mjs +1 -1
- package/dist/ui/button.cjs +1 -1
- package/dist/ui/button.mjs +1 -1
- package/dist/ui/card.cjs +1 -1
- package/dist/ui/card.mjs +1 -1
- package/dist/ui/checkbox.cjs +1 -1
- package/dist/ui/checkbox.mjs +1 -1
- package/dist/ui/collapsible.cjs +1 -1
- package/dist/ui/collapsible.mjs +1 -1
- package/dist/ui/drawer.cjs +1 -1
- package/dist/ui/drawer.mjs +1 -1
- package/dist/ui/dropdown.cjs +1 -1
- package/dist/ui/dropdown.mjs +1 -1
- package/dist/ui/index.cjs +107 -107
- package/dist/ui/index.mjs +17 -17
- package/dist/ui/inputs/color-input.cjs +1 -1
- package/dist/ui/inputs/color-input.mjs +1 -1
- package/dist/ui/inputs/index.cjs +11 -11
- package/dist/ui/inputs/index.mjs +3 -3
- package/dist/ui/inputs/input-numbers.cjs +1 -1
- package/dist/ui/inputs/input-numbers.mjs +1 -1
- package/dist/ui/inputs/input.cjs +1 -1
- package/dist/ui/inputs/input.d.cts +1 -1
- package/dist/ui/inputs/input.d.ts +1 -1
- package/dist/ui/inputs/input.mjs +1 -1
- package/dist/ui/inputs/number-inputs/CmInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/CmInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/DegInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/DegInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/EulerDegInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/EulerDegInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/EulerInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/EulerInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/EulerRadInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/EulerRadInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/InchInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/InchInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/IntInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/IntInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/KVectorInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/KVectorInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/QuaternionInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/QuaternionInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/RadInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/RadInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/TimeInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/TimeInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/Vec3Input.cjs +1 -1
- package/dist/ui/inputs/number-inputs/Vec3Input.mjs +1 -1
- package/dist/ui/inputs/number-inputs/VectorInput.cjs +1 -1
- package/dist/ui/inputs/number-inputs/VectorInput.mjs +1 -1
- package/dist/ui/inputs/number-inputs/index.cjs +11 -11
- package/dist/ui/inputs/number-inputs/index.mjs +3 -3
- package/dist/ui/inputs/presets-input.cjs +1 -1
- package/dist/ui/inputs/presets-input.mjs +1 -1
- package/dist/ui/label.cjs +1 -1
- package/dist/ui/label.mjs +1 -1
- package/dist/ui/layout.cjs +1 -1
- package/dist/ui/layout.mjs +1 -1
- package/dist/ui/layouts/dock-layout/DockLayoutView.cjs +1 -1
- package/dist/ui/layouts/dock-layout/DockLayoutView.mjs +1 -1
- package/dist/ui/layouts/dock-layout/LayoutSlots.cjs +1 -1
- package/dist/ui/layouts/dock-layout/LayoutSlots.mjs +1 -1
- package/dist/ui/layouts/dock-layout/index.cjs +1 -1
- package/dist/ui/layouts/dock-layout/index.mjs +1 -1
- package/dist/ui/layouts/index.cjs +2 -2
- package/dist/ui/layouts/index.mjs +2 -2
- package/dist/ui/layouts/liquid-layout/LayoutSlots.cjs +1 -1
- package/dist/ui/layouts/liquid-layout/LayoutSlots.mjs +1 -1
- package/dist/ui/layouts/liquid-layout/LiquidLayoutView.cjs +1 -1
- package/dist/ui/layouts/liquid-layout/LiquidLayoutView.mjs +1 -1
- package/dist/ui/layouts/liquid-layout/index.cjs +1 -1
- package/dist/ui/layouts/liquid-layout/index.mjs +1 -1
- package/dist/ui/modal.cjs +1 -1
- package/dist/ui/modal.mjs +1 -1
- package/dist/ui/navigation.cjs +1 -1
- package/dist/ui/navigation.mjs +1 -1
- package/dist/ui/pagination.cjs +1 -1
- package/dist/ui/pagination.mjs +1 -1
- package/dist/ui/panel.cjs +1 -1
- package/dist/ui/panel.mjs +1 -1
- package/dist/ui/popover.cjs +1 -1
- package/dist/ui/popover.mjs +1 -1
- package/dist/ui/radio-group.cjs +1 -1
- package/dist/ui/radio-group.mjs +1 -1
- package/dist/ui/resizable.cjs +1 -1
- package/dist/ui/resizable.mjs +1 -1
- package/dist/ui/select.cjs +1 -1
- package/dist/ui/select.d.cts +1 -1
- package/dist/ui/select.d.ts +1 -1
- package/dist/ui/select.mjs +1 -1
- package/dist/ui/separator.cjs +1 -1
- package/dist/ui/separator.mjs +1 -1
- package/dist/ui/sheet.cjs +1 -1
- package/dist/ui/sheet.mjs +1 -1
- package/dist/ui/sidebar.cjs +26 -26
- package/dist/ui/sidebar.mjs +2 -2
- package/dist/ui/simple-tree-view.cjs +1 -1
- package/dist/ui/simple-tree-view.mjs +1 -1
- package/dist/ui/skeleton.cjs +1 -1
- package/dist/ui/skeleton.mjs +1 -1
- package/dist/ui/slider.cjs +1 -1
- package/dist/ui/slider.mjs +1 -1
- package/dist/ui/switch.cjs +1 -1
- package/dist/ui/switch.mjs +1 -1
- package/dist/ui/table.cjs +1 -1
- package/dist/ui/table.mjs +1 -1
- package/dist/ui/tabs.cjs +1 -1
- package/dist/ui/tabs.mjs +1 -1
- package/dist/ui/textarea.cjs +1 -1
- package/dist/ui/textarea.d.cts +1 -1
- package/dist/ui/textarea.d.ts +1 -1
- package/dist/ui/textarea.mjs +1 -1
- package/dist/ui/theme/ThemeToggles.cjs +1 -1
- package/dist/ui/theme/ThemeToggles.mjs +1 -1
- package/dist/ui/theme/index.cjs +1 -1
- package/dist/ui/theme/index.mjs +1 -1
- package/dist/ui/toggle-buttons.cjs +1 -1
- package/dist/ui/toggle-buttons.mjs +1 -1
- package/dist/ui/toggle-group.cjs +1 -1
- package/dist/ui/toggle-group.mjs +1 -1
- package/dist/ui/toggle.cjs +1 -1
- package/dist/ui/toggle.mjs +1 -1
- package/dist/ui/toolbar.cjs +1 -1
- package/dist/ui/toolbar.mjs +1 -1
- package/dist/ui/tooltip.cjs +1 -1
- package/dist/ui/tooltip.mjs +1 -1
- package/dist/ui/tree-view/TreeSearchBar.cjs +1 -1
- package/dist/ui/tree-view/TreeSearchBar.mjs +1 -1
- package/dist/ui/tree-view/TreeView.cjs +1 -1
- package/dist/ui/tree-view/TreeView.mjs +1 -1
- package/dist/ui/tree-view/index.cjs +6 -6
- package/dist/ui/tree-view/index.mjs +2 -2
- package/dist/ui/tree-view-legacy.cjs +9 -9
- package/dist/ui/tree-view-legacy.mjs +5 -5
- package/dist/ui/waterfall/CursorOverlay.cjs +1 -1
- package/dist/ui/waterfall/CursorOverlay.mjs +1 -1
- package/dist/ui/waterfall/TimelineEvent.cjs +1 -1
- package/dist/ui/waterfall/TimelineEvent.mjs +1 -1
- package/dist/ui/waterfall/TimelineProcessBar.cjs +1 -1
- package/dist/ui/waterfall/TimelineProcessBar.mjs +1 -1
- package/dist/ui/waterfall/Wedges.cjs +1 -1
- package/dist/ui/waterfall/Wedges.mjs +1 -1
- package/dist/ui/waterfall/index.cjs +8 -8
- package/dist/ui/waterfall/index.mjs +7 -7
- package/package.json +28 -2
- package/src/SyncScroll/README.md +283 -0
- package/src/SyncScroll/SyncScroll.tsx +361 -0
- package/src/SyncScroll/index.ts +22 -0
- package/src/SyncScroll/useSyncScroll.tsx +226 -0
- package/src/cli/dial-cli.ts +1133 -0
- package/src/dial/DialPanel.tsx +208 -0
- package/src/dial/DialProvider.example.tsx +80 -0
- package/src/dial/DialProvider.tsx +138 -0
- package/src/dial/index.ts +26 -0
- package/src/dial/wrapped-inputs/ControlledInputs.tsx +176 -0
- package/src/dial/wrapped-inputs/DialInputs.tsx +401 -0
- package/src/dial/wrapped-inputs/DialVectorInput.tsx +125 -0
- package/src/dial/wrapped-inputs/index.ts +25 -0
- 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/hooks/clientOnly.tsx +54 -0
- package/src/hooks/cn.ts +33 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useDocument.tsx +18 -0
- package/src/hooks/useDragSelect.ts +116 -0
- package/src/hooks/useIsMobile.ts +35 -0
- package/src/hooks/useLocalStorage.ts +122 -0
- package/src/hooks/useLocation.tsx +95 -0
- package/src/hooks/useQueryParams.tsx +31 -0
- package/src/hooks/useWindow.tsx +18 -0
- package/src/index.css +5 -0
- package/src/index.ts +5 -0
- package/src/styles/.editorconfig +0 -0
- package/src/styles/theme.css +152 -0
- package/src/styles/toast.css +67 -0
- package/src/styles/variables.css +229 -0
- package/src/ui/UIKitBadge.tsx +171 -0
- package/src/ui/avatar.tsx +221 -0
- package/src/ui/badge.tsx +74 -0
- package/src/ui/button.tsx +143 -0
- package/src/ui/card.tsx +146 -0
- package/src/ui/checkbox.tsx +78 -0
- package/src/ui/collapsible.tsx +43 -0
- package/src/ui/drag-selectable/DragSelectProvider.tsx +178 -0
- package/src/ui/drag-selectable/createSelectable.tsx +43 -0
- package/src/ui/drag-selectable/index.ts +2 -0
- package/src/ui/drawer.tsx +137 -0
- package/src/ui/dropdown.tsx +707 -0
- package/src/ui/icons/MouseCursorIcons.tsx +39 -0
- package/src/ui/icons/cursor.tsx +38 -0
- package/src/ui/icons/index.ts +2 -0
- package/src/ui/index.ts +45 -0
- package/src/ui/inputs/color-input.tsx +61 -0
- package/src/ui/inputs/index.tsx +5 -0
- package/src/ui/inputs/input-numbers.tsx +394 -0
- package/src/ui/inputs/input.tsx +155 -0
- package/src/ui/inputs/number-inputs/CmInput.tsx +26 -0
- package/src/ui/inputs/number-inputs/DegInput.tsx +26 -0
- package/src/ui/inputs/number-inputs/EulerDegInput.tsx +14 -0
- package/src/ui/inputs/number-inputs/EulerInput.tsx +30 -0
- package/src/ui/inputs/number-inputs/EulerRadInput.tsx +14 -0
- package/src/ui/inputs/number-inputs/InchInput.tsx +26 -0
- package/src/ui/inputs/number-inputs/IntInput.tsx +46 -0
- package/src/ui/inputs/number-inputs/KVectorInput.tsx +20 -0
- package/src/ui/inputs/number-inputs/QuaternionInput.tsx +27 -0
- package/src/ui/inputs/number-inputs/RadInput.tsx +26 -0
- package/src/ui/inputs/number-inputs/TimeInput.tsx +26 -0
- package/src/ui/inputs/number-inputs/Vec3Input.tsx +26 -0
- package/src/ui/inputs/number-inputs/VectorInput.tsx +38 -0
- package/src/ui/inputs/number-inputs/index.ts +38 -0
- package/src/ui/inputs/presets-input.tsx +59 -0
- package/src/ui/label.tsx +35 -0
- package/src/ui/layout.tsx +43 -0
- package/src/ui/layouts/dock-layout/DockLayoutView.tsx +128 -0
- package/src/ui/layouts/dock-layout/LayoutSlots.tsx +85 -0
- package/src/ui/layouts/dock-layout/index.tsx +2 -0
- package/src/ui/layouts/index.ts +2 -0
- package/src/ui/layouts/liquid-layout/LayoutSlots.tsx +82 -0
- package/src/ui/layouts/liquid-layout/LiquidLayoutView.tsx +76 -0
- package/src/ui/layouts/liquid-layout/index.ts +1 -0
- package/src/ui/modal.tsx +211 -0
- package/src/ui/navigation.tsx +86 -0
- package/src/ui/pagination.tsx +129 -0
- package/src/ui/panel.tsx +146 -0
- package/src/ui/popover.tsx +112 -0
- package/src/ui/radio-group.tsx +63 -0
- package/src/ui/resizable.tsx +52 -0
- package/src/ui/select.tsx +365 -0
- package/src/ui/separator.tsx +26 -0
- package/src/ui/sheet.tsx +257 -0
- package/src/ui/sidebar.tsx +695 -0
- package/src/ui/simple-tree-view.tsx +604 -0
- package/src/ui/skeleton.tsx +15 -0
- package/src/ui/slider.tsx +204 -0
- package/src/ui/switch.tsx +45 -0
- package/src/ui/table.tsx +99 -0
- package/src/ui/tabs.tsx +192 -0
- package/src/ui/textarea.tsx +55 -0
- package/src/ui/theme/ThemeProvider.tsx +319 -0
- package/src/ui/theme/ThemeToggles.tsx +84 -0
- package/src/ui/theme/index.ts +21 -0
- package/src/ui/theme/themeScript.tsx +179 -0
- package/src/ui/theme/types.ts +61 -0
- package/src/ui/toast.tsx +30 -0
- package/src/ui/toggle-buttons.tsx +290 -0
- package/src/ui/toggle-group.tsx +236 -0
- package/src/ui/toggle.tsx +94 -0
- package/src/ui/toolbar.tsx +54 -0
- package/src/ui/tooltip.tsx +171 -0
- package/src/ui/tree-view/TreeSearchBar.tsx +88 -0
- package/src/ui/tree-view/TreeView.tsx +232 -0
- package/src/ui/tree-view/hooks.tsx +289 -0
- package/src/ui/tree-view/index.ts +4 -0
- package/src/ui/tree-view/types.ts +23 -0
- package/src/ui/tree-view-legacy.tsx +644 -0
- package/src/ui/version-badge.tsx +0 -0
- package/src/ui/waterfall/CursorOverlay.tsx +96 -0
- package/src/ui/waterfall/NavigationControls.tsx +42 -0
- package/src/ui/waterfall/Tick.tsx +51 -0
- package/src/ui/waterfall/TimeRuleEventDot.tsx +19 -0
- package/src/ui/waterfall/TimelineEvent.tsx +60 -0
- package/src/ui/waterfall/TimelineProcessBar.tsx +207 -0
- package/src/ui/waterfall/Wedges.tsx +67 -0
- package/src/ui/waterfall/WheelZoomContext.tsx +128 -0
- package/src/ui/waterfall/hooks/useTimelineState.tsx +134 -0
- package/src/ui/waterfall/hooks/useViewport.tsx +293 -0
- package/src/ui/waterfall/index.tsx +326 -0
- package/src/ui/waterfall/types.ts +20 -0
- package/src/ui/waterfall/utils.ts +91 -0
- package/dist/chunk-W2YAQV5N.mjs +0 -57
- package/dist/{chunk-QLCEHV4Q.mjs → chunk-2OZK5DY5.mjs} +2 -2
- package/dist/{chunk-Z35DNFRZ.cjs → chunk-7IS37C3P.cjs} +2 -2
- package/dist/{chunk-4AOAH77D.mjs → chunk-A2PCEL5S.mjs} +2 -2
- package/dist/{chunk-K4VD5PPY.mjs → chunk-BIUDC66P.mjs} +1 -1
- package/dist/{chunk-RKJR6RZU.cjs → chunk-OYNLQTHW.cjs} +1 -1
- package/dist/{chunk-VE3XLQLO.cjs → chunk-QUFZWF4E.cjs} +2 -2
|
@@ -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 "../hooks/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-uk-md transition-[width,height,left,top] duration-${transitionDuration} ease-out`;
|
|
222
|
+
} else {
|
|
223
|
+
styleClass = `opacity-90 rounded-full 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/inputs/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
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executes a function only on the client side (when window is defined)
|
|
5
|
+
* @param fn - Function to execute on client side
|
|
6
|
+
* @returns Result of the function or undefined if on server side
|
|
7
|
+
*/
|
|
8
|
+
export function clientOnly<T>(fn: () => T): T | undefined {
|
|
9
|
+
if (typeof window !== "undefined") {
|
|
10
|
+
return fn();
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a React component that only renders on the client side after hydration
|
|
17
|
+
* @param Component - The component to render client-side
|
|
18
|
+
* @param fallback - Optional fallback to render server-side
|
|
19
|
+
* @returns A component that avoids hydration mismatches
|
|
20
|
+
*/
|
|
21
|
+
export function createClientOnlyComponent<P extends object = Record<string, unknown>>(
|
|
22
|
+
Component: React.ComponentType<P>,
|
|
23
|
+
fallback?: React.ComponentType<P>,
|
|
24
|
+
): React.ComponentType<P> {
|
|
25
|
+
return function ClientOnlyWrapper(props: P) {
|
|
26
|
+
const [isClient, setIsClient] = React.useState(false);
|
|
27
|
+
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
setIsClient(true);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
if (!isClient) {
|
|
33
|
+
return fallback ? React.createElement(fallback, props) : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return React.createElement(Component, props);
|
|
37
|
+
} as React.ComponentType<P>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Checks if code is running on the client side
|
|
42
|
+
* @returns true if running on client, false if on server
|
|
43
|
+
*/
|
|
44
|
+
export function isClient(): boolean {
|
|
45
|
+
return typeof window !== "undefined";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks if code is running on the server side
|
|
50
|
+
* @returns true if running on server, false if on client
|
|
51
|
+
*/
|
|
52
|
+
export function isServer(): boolean {
|
|
53
|
+
return typeof window === "undefined";
|
|
54
|
+
}
|
package/src/hooks/cn.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from "clsx";
|
|
2
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
const twMerge = extendTailwindMerge({
|
|
5
|
+
extend: {
|
|
6
|
+
classGroups: {
|
|
7
|
+
rounded: [
|
|
8
|
+
"rounded-uk-xs",
|
|
9
|
+
"rounded-uk-sm",
|
|
10
|
+
"rounded-uk-md",
|
|
11
|
+
"rounded-uk-lg",
|
|
12
|
+
"rounded-uk-xl",
|
|
13
|
+
],
|
|
14
|
+
p: ["p-xxs", "p-xs", "p-sm", "p-md", "p-lg", "p-xl"],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
override: {
|
|
18
|
+
classGroups: {
|
|
19
|
+
"font-size": [
|
|
20
|
+
"text-uk-xs",
|
|
21
|
+
"text-uk-sm",
|
|
22
|
+
"text-uk-md",
|
|
23
|
+
"text-uk-lg",
|
|
24
|
+
"text-uk-xl",
|
|
25
|
+
"text-uk-xxl",
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export function cn(...inputs: ClassValue[]) {
|
|
32
|
+
return twMerge(clsx(inputs));
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./cn";
|
|
2
|
+
export * from "./useLocalStorage";
|
|
3
|
+
export * from "./clientOnly";
|
|
4
|
+
export * from "./useIsMobile";
|
|
5
|
+
export * from "./useWindow";
|
|
6
|
+
export * from "./useDocument";
|
|
7
|
+
export * from "./useLocation";
|
|
8
|
+
export * from "./useQueryParams";
|
|
9
|
+
export * from "./useDragSelect";
|