From 4802b6c3091252261394a0fc298150e965285660 Mon Sep 17 00:00:00 2001 From: Yuta Okuma Date: Sun, 14 Apr 2024 21:36:19 +0900 Subject: [PATCH 1/4] :sparkles: feat: implement custom cursor effect --- src/app/layout.tsx | 2 ++ src/components/Cursor/Cursor.tsx | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/components/Cursor/Cursor.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3b9de294..be2a6707 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from 'react'; import { Montserrat } from 'next/font/google'; +import Cursor from '@/components/Cursor/Cursor'; import PageAnimatePresence from '@/components/HOC/PageAnimatePresence/PageAnimatePresence'; import Menu from '@/components/Menu/Menu'; @@ -30,6 +31,7 @@ const RootLayout = ({ {children} + ); diff --git a/src/components/Cursor/Cursor.tsx b/src/components/Cursor/Cursor.tsx new file mode 100644 index 00000000..9e469826 --- /dev/null +++ b/src/components/Cursor/Cursor.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { useEffect, type FC } from 'react'; + +import { useMotionValue, useSpring, motion } from 'framer-motion'; + +const Cursor: FC = () => { + const cursorSize = 15; + + const mouse = { + x: useMotionValue(0), + y: useMotionValue(0), + }; + + const smoothOptions = { damping: 20, stiffness: 300, mass: 0.5 }; + const smoothMouse = { + x: useSpring(mouse.x, smoothOptions), + y: useSpring(mouse.y, smoothOptions), + }; + + const manageMouseMove = (e: MouseEvent) => { + const { clientX, clientY } = e; + mouse.x.set(clientX - cursorSize / 2); + mouse.y.set(clientY - cursorSize / 2); + }; + + useEffect(() => { + window.addEventListener('mousemove', manageMouseMove); + return () => { + window.removeEventListener('mousemove', manageMouseMove); + }; + }, [manageMouseMove]); + + return ( + + ); +}; + +export default Cursor; From 2eb210e91792afff3c0744090bb47e06ebb791de Mon Sep 17 00:00:00 2001 From: Yuta Okuma Date: Sun, 14 Apr 2024 22:13:53 +0900 Subject: [PATCH 2/4] :white_check_mark: test: create the cursor test --- src/components/Cursor/Cursor.test.tsx | 23 +++++++++++++++++++++++ src/components/Cursor/Cursor.tsx | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/components/Cursor/Cursor.test.tsx diff --git a/src/components/Cursor/Cursor.test.tsx b/src/components/Cursor/Cursor.test.tsx new file mode 100644 index 00000000..6d22ad7e --- /dev/null +++ b/src/components/Cursor/Cursor.test.tsx @@ -0,0 +1,23 @@ +import { screen, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import Cursor from './Cursor'; + +import type { RenderResult } from '@testing-library/react'; + +describe('src/components/Cursor/Cursor.test.tsx', () => { + let renderResult: RenderResult; + + beforeEach(() => { + renderResult = render(); + }); + + afterEach(() => { + renderResult.unmount(); + }); + + it('should render the Cursor component without crashing', () => { + const cursorElement = screen.getByTestId('cursor'); + expect(cursorElement).toBeInTheDocument(); + }); +}); diff --git a/src/components/Cursor/Cursor.tsx b/src/components/Cursor/Cursor.tsx index 9e469826..4f96dcfd 100644 --- a/src/components/Cursor/Cursor.tsx +++ b/src/components/Cursor/Cursor.tsx @@ -29,12 +29,13 @@ const Cursor: FC = () => { return () => { window.removeEventListener('mousemove', manageMouseMove); }; - }, [manageMouseMove]); + }, []); return ( ); }; From d105cd0a5d9fb5b94d0ef40ff95c015c1bad26ba Mon Sep 17 00:00:00 2001 From: Yuta Okuma Date: Sun, 14 Apr 2024 22:17:20 +0900 Subject: [PATCH 3/4] :white_check_mark: test: create the cursor storybook --- src/components/Cursor/Cursor.stories.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/components/Cursor/Cursor.stories.tsx diff --git a/src/components/Cursor/Cursor.stories.tsx b/src/components/Cursor/Cursor.stories.tsx new file mode 100644 index 00000000..974d915f --- /dev/null +++ b/src/components/Cursor/Cursor.stories.tsx @@ -0,0 +1,15 @@ +import Cursor from './Cursor'; + +import type { Meta, StoryObj } from '@storybook/react'; + +const meta: Meta = { + component: Cursor, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; From f68a28d171ba43c20b8242ea37e9f2b7859ef56d Mon Sep 17 00:00:00 2001 From: Yuta Okuma Date: Sat, 20 Apr 2024 11:25:52 +0900 Subject: [PATCH 4/4] :white_check_mark: test: add mouse movement test --- src/components/Cursor/Cursor.test.tsx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/Cursor/Cursor.test.tsx b/src/components/Cursor/Cursor.test.tsx index 6d22ad7e..f937dc68 100644 --- a/src/components/Cursor/Cursor.test.tsx +++ b/src/components/Cursor/Cursor.test.tsx @@ -1,4 +1,4 @@ -import { screen, render } from '@testing-library/react'; +import { screen, render, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import Cursor from './Cursor'; @@ -20,4 +20,25 @@ describe('src/components/Cursor/Cursor.test.tsx', () => { const cursorElement = screen.getByTestId('cursor'); expect(cursorElement).toBeInTheDocument(); }); + + it('should render the Cursor component with mouse movement', async () => { + const cursorElement = screen.getByTestId('cursor'); + const initialPosition = { + x: cursorElement.style.left, + y: cursorElement.style.top, + }; + + fireEvent.mouseMove(document, { + clientX: 100, + clinetY: 200, + }); + + await waitFor(() => { + expect(cursorElement.style.left).not.toBe(initialPosition.x); + }); + + await waitFor(() => { + expect(cursorElement.style.top).not.toBe(initialPosition.y); + }); + }); });