Skip to content

Commit 509d188

Browse files
committed
feat: Nav menu with Framer Motion animations
1 parent 9363ffb commit 509d188

File tree

3 files changed

+56
-37
lines changed

3 files changed

+56
-37
lines changed

bun.lockb

1.24 KB
Binary file not shown.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@radix-ui/themes": "^3.2.0",
1919
"@tanstack/react-query": "^5.56.2",
2020
"axios": "^1.7.5",
21+
"framer-motion": "^12.4.10",
2122
"jwt-decode": "^3.1.2",
2223
"react": "^19.0.0",
2324
"react-dom": "^19.0.0",

src/components/Nav.tsx

+55-37
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { RoutePath } from '__generated__/routes.types';
22
import styled from '@emotion/styled';
3+
import { motion } from 'framer-motion';
34
import { useBodyScrollLock } from 'hooks/useBodyScrollLock';
45
import useUserStore from 'hooks/useUserStore';
56
import { useState } from 'react';
@@ -38,7 +39,7 @@ const Nav = () => {
3839
<source srcSet="/images/logo.avif" type="image/avif" />
3940
<source srcSet="/images/logo.webp" type="image/webp" />
4041
<source srcSet="/images/logo.png" type="image/png" />
41-
<NavLogo src="/images/logo.png" alt="수위키 로고" width={110} height={30} />
42+
<NavLogo src="/images/logo.png" alt="수위키 로고" height={24} />
4243
</picture>
4344
</LogoWrapper>
4445

@@ -47,7 +48,34 @@ const Nav = () => {
4748
aria-expanded={isOpen}
4849
aria-label={isOpen ? '메뉴 닫기' : '메뉴 열기'}
4950
>
50-
{isOpen ? <CloseIcon /> : <MenuIcon />}
51+
<MenuIconContainer>
52+
<MenuLine
53+
animate={isOpen ? 'open' : 'closed'}
54+
variants={{
55+
closed: { rotate: 0, translateY: 0 },
56+
open: { rotate: 45, translateY: 7 },
57+
}}
58+
transition={{ duration: 0.3 }}
59+
/>
60+
61+
<MenuLine
62+
animate={isOpen ? 'open' : 'closed'}
63+
variants={{
64+
closed: { opacity: 1 },
65+
open: { opacity: 0 },
66+
}}
67+
transition={{ duration: 0.2 }}
68+
/>
69+
70+
<MenuLine
71+
animate={isOpen ? 'open' : 'closed'}
72+
variants={{
73+
closed: { rotate: 0, translateY: 0 },
74+
open: { rotate: -45, translateY: -7 },
75+
}}
76+
transition={{ duration: 0.3 }}
77+
/>
78+
</MenuIconContainer>
5179
</MenuButton>
5280

5381
<NavMenu isOpen={isOpen}>
@@ -120,7 +148,6 @@ const NavMenu = styled.div<NavMenuProps>`
120148
gap: 2rem;
121149
122150
@media screen and (max-width: 768px) {
123-
justify-content: center;
124151
flex-direction: column;
125152
width: 100%;
126153
height: calc(100vh - 60px);
@@ -129,8 +156,9 @@ const NavMenu = styled.div<NavMenuProps>`
129156
left: ${({ isOpen }) => (isOpen ? 0 : '-100%')};
130157
transition: all 0.3s ease;
131158
background: #ffffff;
132-
z-index: 999;
133-
padding: 2rem;
159+
z-index: 10;
160+
padding: 56px 8px;
161+
gap: 16px;
134162
}
135163
`;
136164

@@ -150,39 +178,29 @@ const NavItem = styled.button<{ isHighlight?: boolean }>`
150178
151179
@media screen and (max-width: 768px) {
152180
width: 100%;
153-
text-align: center;
181+
text-align: left;
154182
padding: 1rem;
183+
font-size: 24px;
184+
font-weight: 600;
155185
}
156186
`;
157187

158-
const CloseIcon = () => (
159-
<svg
160-
stroke="currentColor"
161-
fill="currentColor"
162-
strokeWidth="0"
163-
viewBox="0 0 16 16"
164-
height="1em"
165-
width="1em"
166-
xmlns="http://www.w3.org/2000/svg"
167-
>
168-
<path
169-
fillRule="evenodd"
170-
clipRule="evenodd"
171-
d="M7.116 8l-4.558 4.558.884.884L8 8.884l4.558 4.558.884-.884L8.884 8l4.558-4.558-.884-.884L8 7.116 3.442 2.558l-.884.884L7.116 8z"
172-
/>
173-
</svg>
174-
);
175-
176-
const MenuIcon = () => (
177-
<svg
178-
stroke="currentColor"
179-
fill="currentColor"
180-
strokeWidth="0"
181-
viewBox="0 0 512 512"
182-
height="1em"
183-
width="1em"
184-
xmlns="http://www.w3.org/2000/svg"
185-
>
186-
<path d="M32 96v64h448V96H32zm0 128v64h448v-64H32zm0 128v64h448v-64H32z" />
187-
</svg>
188-
);
188+
const MenuIconContainer = styled.div`
189+
width: 24px;
190+
height: 24px;
191+
display: flex;
192+
flex-direction: column;
193+
justify-content: space-between;
194+
align-items: center;
195+
position: relative;
196+
padding: 4px 0;
197+
`;
198+
199+
const MenuLine = styled(motion.div)`
200+
width: 24px;
201+
height: 2px;
202+
background-color: black;
203+
border-radius: 2px;
204+
transform-origin: center;
205+
position: relative;
206+
`;

0 commit comments

Comments
 (0)