diff --git a/package.json b/package.json
index cd1f1a4..4ce557e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-mouse-follower",
- "version": "1.1.7",
+ "version": "2.0.1",
"description": "React mouse follower is a package based on react and framer motion. It provides components to add and customise cool mouse follower to your cursor",
"repository": {
"type": "git",
@@ -25,7 +25,8 @@
"chromatic": "chromatic --exit-zero-on-changes"
},
"dependencies": {
- "framer-motion": "^10.12.18"
+ "framer-motion": "^10.12.18",
+ "zustand": "^4.4.1"
},
"peerDependencies": {
"react": "^18.2.0",
diff --git a/src/component/follower.tsx b/src/component/follower.tsx
new file mode 100644
index 0000000..976018c
--- /dev/null
+++ b/src/component/follower.tsx
@@ -0,0 +1,5 @@
+import { FollowerInitialiserComponent } from './follower_init.js';
+
+export function Follower() {
+ return ;
+}
diff --git a/src/component/follower_div.tsx b/src/component/follower_div.tsx
index a050fe7..a8e85ba 100644
--- a/src/component/follower_div.tsx
+++ b/src/component/follower_div.tsx
@@ -1,14 +1,15 @@
import { MousePosition, MouseSettings } from '../types/index.js';
-import { motion } from 'framer-motion';
+import { AnimatePresence, motion } from 'framer-motion';
export function FollowerDiv({ pos, options }: { pos: MousePosition; options: MouseSettings }) {
const calculatePosition = (): MousePosition => {
- if (options.customLocation != undefined) {
+ if (options.customLocation != null) {
return { x: options.customLocation.x, y: options.customLocation.y };
- } else if (options.customPosition != undefined) {
+ } else if (options.customPosition != null) {
const rect = options.customPosition.current.getBoundingClientRect();
- const x = rect.left + rect.width / 2 - options.radius;
- const y = rect.top + rect.height / 2 - options.radius;
+ const radius = options.radius ? options.radius : 12 / 2;
+ const x = rect.left + rect.width / 2 - radius;
+ const y = rect.top + rect.height / 2 - radius;
return { x, y };
} else {
return { x: pos.x, y: pos.y };
@@ -20,22 +21,20 @@ export function FollowerDiv({ pos, options }: { pos: MousePosition; options: Mou
x: pos.x,
y: pos.y,
scale: 0,
+ backgroundColor: options.backgroundColor || 'black',
+ zIndex: options.zIndex || -5,
+ mixBlendMode: options.mixBlendMode || 'initial',
}}
animate={{
x: calculatePosition().x,
y: calculatePosition().y,
- scale: options.scale || 1,
+ scale: options.scale != null ? options.scale : 1,
rotate: options.rotate || 0,
- }}
- exit={{
- x: pos.x,
- y: pos.y,
- scale: 0,
- }}
- style={{
backgroundColor: options.backgroundColor || 'black',
- mixBlendMode: options.mixBlendMode || 'initial',
zIndex: options.zIndex || -5,
+ mixBlendMode: options.mixBlendMode || 'initial',
+ }}
+ style={{
position: 'fixed',
inset: 0,
pointerEvents: 'none',
@@ -72,21 +71,36 @@ export function FollowerDiv({ pos, options }: { pos: MousePosition; options: Mou
}}
>
{options.text && !options.backgroundElement ? (
-
- {options.text}
-
+
+
+ {options.text}
+
+
) : null}
- {options.backgroundElement ? options.backgroundElement : null}
+
+ {options.backgroundElement ? (
+
+ {options.backgroundElement}
+
+ ) : null}
+
diff --git a/src/component/follower_init.tsx b/src/component/follower_init.tsx
index c5e259c..2194940 100644
--- a/src/component/follower_init.tsx
+++ b/src/component/follower_init.tsx
@@ -1,10 +1,14 @@
import { useEffect, useState } from 'react';
-import { AnimatePresence } from 'framer-motion';
-import type { MousePosition, MouseSettings } from '../types/index.js';
+import { MouseSettings, type MousePosition } from '../types/index.js';
import { FollowerDiv } from './follower_div.js';
+import useMouseStore from '../store/index.js';
+import { AnimatePresence } from 'framer-motion';
+
+const defaultRadius = 12 / 2;
-export function FollowerInitialiserComponent({ options }: { options: MouseSettings }) {
+export function FollowerInitialiserComponent() {
const [isHovering, setIsHovering] = useState(false);
+ const options = useMouseStore((store) => store.curSettings);
useEffect(() => {
const handleMouseLeave = () => {
@@ -25,20 +29,31 @@ export function FollowerInitialiserComponent({ options }: { options: MouseSettin
};
}, []);
- return ;
+ return (
+
+ );
}
-function PositionHandler({ options, show }: { options: MouseSettings; show: boolean }) {
+function ManagePosition({ options }: { options: MouseSettings }) {
+
const [pos, setPos] = useState({
x: 0,
y: 0,
});
+
useEffect(() => {
const mouseMove = (event: any) => {
- setPos({
- x: event.clientX - options.radius,
- y: event.clientY - options.radius,
- });
+ if (options.radius != null) {
+ setPos({
+ x: event.clientX - options.radius,
+ y: event.clientY - options.radius,
+ });
+ } else {
+ setPos({
+ x: event.clientX - defaultRadius,
+ y: event.clientY - defaultRadius,
+ });
+ }
};
window.addEventListener('mousemove', mouseMove);
return () => {
@@ -46,5 +61,9 @@ function PositionHandler({ options, show }: { options: MouseSettings; show: bool
};
}, [options?.radius]);
- return {show ? : null};
+ return (
+
+ {options.visible !== false ? : null}
+
+ );
}
diff --git a/src/component/index.ts b/src/component/index.ts
index 98816b1..fbbdb95 100644
--- a/src/component/index.ts
+++ b/src/component/index.ts
@@ -1 +1,2 @@
export * from './update_follower.js';
+export * from './follower.js';
diff --git a/src/component/update_follower.tsx b/src/component/update_follower.tsx
index 328f1e8..7ea87ae 100644
--- a/src/component/update_follower.tsx
+++ b/src/component/update_follower.tsx
@@ -1,6 +1,6 @@
import { CSSProperties, ReactNode, useContext } from 'react';
-import { MousePropertiesContext } from '../context/mouse.context.js';
import { MouseSettings } from '../types/index.js';
+import useMouseStore from '../store/index.js';
export function UpdateFollower({
mouseOptions,
@@ -19,7 +19,7 @@ export function UpdateFollower({
onClick?: () => void;
children?: ReactNode;
}) {
- const { addLayer, removeLayer } = useContext(MousePropertiesContext);
+ const { addLayer, removeLayer } = useMouseStore((state) => ({ addLayer: state.pushLayer, removeLayer: state.popLayer }));
function handleMouseEnter() {
addLayer(mouseOptions);
if (onMouseEnter) {
diff --git a/src/context/index.ts b/src/context/index.ts
deleted file mode 100644
index 7bdb6e0..0000000
--- a/src/context/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { FollowerProvider } from './mouse.context.js';
-
-export { FollowerProvider };
diff --git a/src/context/mouse.context.tsx b/src/context/mouse.context.tsx
deleted file mode 100644
index f645fc6..0000000
--- a/src/context/mouse.context.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { ReactNode, createContext, useRef } from 'react';
-import { FollowerInitialiserComponent } from '../component/follower_init.js';
-import { useStack } from '../hook/stack.hook.js';
-
-import type { MouseSettings } from '../types/index.js';
-
-interface ContextInterface {
- addLayer: (options: MouseSettings) => void;
- removeLayer: () => MouseSettings | undefined;
- peekStack: () => MouseSettings | undefined;
- clearStack: () => void;
- logStack: () => void;
-}
-
-export const MousePropertiesContext = createContext(null);
-
-export const FollowerProvider = ({ visible, children }: { visible?: boolean; children?: ReactNode }) => {
- const layerStack = useStack();
- const addLayer = (layerOptions: MouseSettings) => {
- layerStack.push(layerOptions);
- };
-
- const removeLayer = () => {
- return layerStack.pop();
- };
-
- const value: ContextInterface = {
- addLayer,
- removeLayer,
- clearStack: layerStack.clear,
- logStack: layerStack.logStack,
- peekStack: layerStack.peek,
- };
-
- return (
-
- {visible !== false ? : null}
- {children}
-
- );
-};
diff --git a/src/hook/control_options.hook.tsx b/src/hook/control_options.hook.tsx
index c5d16a2..a2a5879 100644
--- a/src/hook/control_options.hook.tsx
+++ b/src/hook/control_options.hook.tsx
@@ -1,13 +1,14 @@
-import { useContext } from 'react';
-import { MousePropertiesContext } from '../context/mouse.context.js';
+import useMouseStore from '../store/index.js';
export function useControlOptions() {
- const { addLayer, removeLayer, clearStack, logStack, peekStack } = useContext(MousePropertiesContext);
+ const store = useMouseStore((state) => ({
+ addOptionLayer: state.pushLayer,
+ removePreviousLayer: state.popLayer,
+ clearLayers: state.clearLayers,
+ }));
+
return {
- addOptionLayer: addLayer,
- removePreviousLayer: removeLayer,
- clearLayers: clearStack,
- logLayers: logStack,
- topLayer: peekStack,
+ // logLayers: logStack,
+ ...store,
};
}
diff --git a/src/hook/stack.hook.tsx b/src/hook/stack.hook.tsx
deleted file mode 100644
index 399d765..0000000
--- a/src/hook/stack.hook.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { useEffect, useState } from 'react';
-import type { MouseSettings } from '../types/index.js';
-
-const defaultMouseProperties: MouseSettings = {
- radius: 12 / 2,
-};
-
-export const useStack = (): {
- stack: MouseSettings[];
- push: (options: MouseSettings) => void;
- pop: () => MouseSettings | undefined;
- peek: () => MouseSettings | undefined;
- isEmpty: () => boolean;
- clear: () => void;
- size: () => number;
- logStack: () => void;
-} => {
- const [stack, setStack] = useState([defaultMouseProperties]);
-
- const push = (options: MouseSettings): void => {
- setStack((prevStack) => {
- const item: MouseSettings = {
- ...defaultMouseProperties,
- ...prevStack[prevStack.length - 1],
- ...options,
- };
- return [...prevStack, item];
- });
- };
-
- const pop = (): MouseSettings | undefined => {
- let item = {};
- setStack((prevStack) => {
- item = prevStack.pop();
- return [...prevStack];
- });
- return item;
- };
-
- const peek = (): MouseSettings | undefined => {
- if (stack.length > 0) {
- return stack[stack.length - 1];
- }
- return defaultMouseProperties;
- };
-
- const isEmpty = (): boolean => {
- return stack.length === 0;
- };
-
- const clear = (): void => {
- setStack([]);
- };
-
- const size = (): number => {
- return stack.length;
- };
-
- const logStack = (): void => {
- console.log('logging all layers');
- stack.forEach((item, i) => {
- console.log(i, item);
- });
- };
-
- // useEffect(() => {
- // logStack();
- // }, [stack]);
-
- return {
- stack,
- push,
- pop,
- peek,
- isEmpty,
- clear,
- size,
- logStack,
- };
-};
diff --git a/src/index.ts b/src/index.ts
index a46d21e..4d1cba8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,2 @@
export * from './component/index.js';
-export * from './context/index.js';
export * from './hook/index.js';
diff --git a/src/store/index.ts b/src/store/index.ts
new file mode 100644
index 0000000..bdf64b8
--- /dev/null
+++ b/src/store/index.ts
@@ -0,0 +1,54 @@
+// import { create } from 'zustand';
+import { create, StateCreator, StoreApi, SetState, GetState } from 'zustand';
+import { MouseSettings } from '../types/index.js';
+
+interface useMouseStoreInterface {
+ curSettings: MouseSettings;
+ layers: MouseSettings[];
+
+ pushLayer: (newLayer: MouseSettings) => void;
+ popLayer: () => void;
+ clearLayers: () => void;
+}
+
+const log =
+ (config: StateCreator) =>
+ (set: SetState, get: GetState, api: StoreApi) =>
+ config(
+ (args) => {
+ console.log(' applying', args);
+ set(args);
+ console.log(' new state', get());
+ },
+ get,
+ api,
+ );
+
+const useMouseStore = create(
+ log((set) => ({
+ curSettings: {},
+ layers: [],
+
+ pushLayer: (newLayer: MouseSettings) =>
+ set((state) => {
+ const newCur = { ...state.curSettings, ...newLayer };
+ state.layers.push(newCur);
+ return { layers: state.layers, curSettings: newCur };
+ }),
+ popLayer: () =>
+ set((state) => {
+ if (state.layers.length > 1) {
+ state.layers.pop();
+ return { layers: state.layers, curSettings: state.layers.at(state.layers.length - 1) };
+ } else {
+ return { layers: [], curSettings: {} };
+ }
+ }),
+ clearLayers: () =>
+ set((state) => {
+ return { layers: [], curSettings: {} };
+ }),
+ })),
+);
+
+export default useMouseStore;
diff --git a/src/stories/FollowerBasic.stories.tsx b/src/stories/FollowerBasic.stories.tsx
index 5e50c71..954b18c 100644
--- a/src/stories/FollowerBasic.stories.tsx
+++ b/src/stories/FollowerBasic.stories.tsx
@@ -1,17 +1,18 @@
import React from 'react';
import type { Meta } from '@storybook/react';
-import { FollowerProvider, UpdateFollower } from '../index';
+import { Follower, UpdateFollower } from '../index';
import * as DivStories from './FollowerContainer.stories';
const meta: Meta = {
title: 'Context/FollowerProvider',
- component: FollowerProvider,
+ component: Follower,
decorators: [
(Story) => (
-
+ <>
+
-
+ >
),
],
argTypes: {
diff --git a/src/stories/UpdateFollower.stories.tsx b/src/stories/UpdateFollower.stories.tsx
index 1e309e4..06e97e9 100644
--- a/src/stories/UpdateFollower.stories.tsx
+++ b/src/stories/UpdateFollower.stories.tsx
@@ -1,7 +1,7 @@
import React, { useRef, useState } from 'react';
import type { Meta } from '@storybook/react';
-import { FollowerProvider, UpdateFollower } from '../index';
+import { Follower, UpdateFollower } from '../index';
import * as DivStories from './FollowerContainer.stories';
import './css/update_follower.css';
@@ -11,6 +11,14 @@ const meta: Meta = {
parameters: {
layout: 'fullscreen',
},
+ decorators: [
+ (Story) => (
+ <>
+
+
+ >
+ ),
+ ],
};
export default meta;
@@ -18,41 +26,39 @@ export default meta;
export const NestedUpdateCalls: Meta = {
decorators: [
() => (
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
-
+
),
],
};
@@ -63,32 +69,30 @@ export const CustomPosition: Meta = {
const containerRef = useRef(null);
const [isHovering, setIsHovering] = useState(false);
return (
-
-
-
{
- setIsHovering(true);
- }}
- onMouseLeave={() => {
- setIsHovering(false);
- }}
+
+
{
+ setIsHovering(true);
+ }}
+ onMouseLeave={() => {
+ setIsHovering(false);
+ }}
+ >
+
-
+
+
+
+
+
+
);
},
],
@@ -98,22 +102,49 @@ export const Rotate: Meta = {
decorators: [
() => {
return (
-
+
+ );
+ },
+ ],
+};
+
+export const FollowSpeed: Meta = {
+ decorators: [
+ () => {
+ return (
+
@@ -121,38 +152,7 @@ export const Rotate: Meta = {
-
- );
- },
- ],
-};
-
-export const FollowSpeed: Meta = {
- decorators: [
- () => {
- return (
-
-
-
-
-
+
);
},
],
diff --git a/tsconfig.json b/tsconfig.json
index b6ad655..e247d53 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,7 +3,7 @@
"target": "es6",
"useDefineForClassFields": true,
"lib": ["es5", "es2015", "es2016", "dom", "esnext", "es2020"],
- "module": "ESNext",
+ "module": "NodeNext",
"moduleResolution": "nodenext",
"noImplicitAny": true,
"skipLibCheck": true,
diff --git a/yarn.lock b/yarn.lock
index e58ed11..ff5e0f4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7056,6 +7056,11 @@ use-resize-observer@^9.1.0:
dependencies:
"@juggle/resize-observer" "^3.3.1"
+use-sync-external-store@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
+ integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -7349,3 +7354,10 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+zustand@^4.4.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.1.tgz#0cd3a3e4756f21811bd956418fdc686877e8b3b0"
+ integrity sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==
+ dependencies:
+ use-sync-external-store "1.2.0"