1+ 'use client' ;
2+
13import Link from 'next/link' ;
24import styles from './Header.module.scss' ;
35import cn from 'classnames' ;
@@ -6,7 +8,7 @@ import { ThemeImage } from '../ThemeImage';
68import { FC , useEffect , useRef , useState } from 'react' ;
79import { usePathname } from 'next/navigation' ;
810import { Button } from '../Button' ;
9- import gsap from 'gsap'
11+ import gsap from 'gsap' ;
1012
1113const menuItems = [
1214 {
@@ -23,20 +25,107 @@ export interface HeaderProps {
2325 setModalOpen : ( x : boolean ) => void ;
2426}
2527
26- export const Header :FC < HeaderProps > = ( { setModalOpen } ) => {
28+ export const Header : FC < HeaderProps > = ( { setModalOpen } ) => {
2729 const { resolvedTheme, setTheme } = useTheme ( ) ;
2830 const pathname = usePathname ( ) ;
2931 const [ isMobileMenu , setMobileMenu ] = useState ( false ) ;
3032 const headerRef = useRef < HTMLDivElement > ( null ) ;
31- const logoRef = useRef < HTMLAnchorElement > ( null ) ;
32- const navLinksRef = useRef < HTMLDivElement > ( null ) ;
33- const themeSwitcherRef = useRef < HTMLDivElement > ( null ) ;
34- const buttonRef = useRef < HTMLDivElement > ( null ) ;
33+ const timelineRef = useRef < gsap . core . Timeline | null > ( null ) ;
34+ const mobileMenuRef = useRef < HTMLDivElement > ( null ) ;
3535
3636 const toggleDarkMode = ( ) => {
3737 setTheme ( resolvedTheme === 'dark' ? 'light' : 'dark' ) ;
3838 } ;
3939
40+ useEffect ( ( ) => {
41+ if ( typeof window === 'undefined' || window . innerWidth < 1024 || ! headerRef . current ) return ;
42+
43+ const logo = headerRef . current . querySelector ( `.${ styles . logoLink } ` ) ;
44+ const navLinks = Array . from ( headerRef . current . querySelectorAll ( `.${ styles . navLinks } a` ) ) ;
45+ const themeSwitcher = headerRef . current . querySelector ( `.${ styles . themeSwitcher } ` ) ;
46+ const button = headerRef . current . querySelector ( `.${ styles . mvmButton } ` ) ;
47+
48+ const animatableElements : gsap . TweenTarget [ ] = [ ] ;
49+
50+ if ( logo ) animatableElements . push ( logo ) ;
51+ animatableElements . push ( ...navLinks ) ;
52+ if ( themeSwitcher ) animatableElements . push ( themeSwitcher ) ;
53+ if ( button ) animatableElements . push ( button ) ;
54+
55+ timelineRef . current = gsap . timeline ( )
56+ . fromTo ( logo ,
57+ { opacity : 0 , x : - 30 } ,
58+ { opacity : 1 , x : 0 , duration : 0.6 , ease : 'power3.out' }
59+ )
60+ . fromTo ( navLinks ,
61+ { opacity : 0 , y : 15 } ,
62+ {
63+ opacity : 1 ,
64+ y : 0 ,
65+ duration : 0.5 ,
66+ stagger : 0.1 ,
67+ ease : 'back.out(1.2)'
68+ } ,
69+ '-=0.4'
70+ ) ;
71+
72+ if ( themeSwitcher && button ) {
73+ timelineRef . current
74+ . fromTo ( [ themeSwitcher , button ] ,
75+ { opacity : 0 , scale : 0.8 } ,
76+ {
77+ opacity : 1 ,
78+ scale : 1 ,
79+ duration : 0.4 ,
80+ stagger : 0.1 ,
81+ ease : 'elastic.out(1, 0.5)'
82+ } ,
83+ '-=0.3'
84+ ) ;
85+ }
86+
87+ return ( ) => {
88+ timelineRef . current ?. kill ( ) ;
89+ } ;
90+ } , [ ] ) ;
91+
92+ useEffect ( ( ) => {
93+ if ( typeof window === 'undefined' || ! mobileMenuRef . current ) return ;
94+
95+ if ( isMobileMenu ) {
96+ gsap . fromTo ( mobileMenuRef . current ,
97+ { opacity : 0 , y : - 20 } ,
98+ {
99+ opacity : 1 ,
100+ y : 0 ,
101+ duration : 0.3 ,
102+ ease : 'power2.out'
103+ }
104+ ) ;
105+
106+ gsap . fromTo ( mobileMenuRef . current . querySelectorAll ( 'a' ) ,
107+ { opacity : 0 , y : 10 } ,
108+ {
109+ opacity : 1 ,
110+ y : 0 ,
111+ duration : 0.4 ,
112+ stagger : 0.08 ,
113+ ease : 'back.out(1.2)' ,
114+ delay : 0.2
115+ }
116+ ) ;
117+ } else {
118+ gsap . to ( mobileMenuRef . current ,
119+ {
120+ opacity : 0 ,
121+ y : - 20 ,
122+ duration : 0.2 ,
123+ ease : 'power2.in'
124+ }
125+ ) ;
126+ }
127+ } , [ isMobileMenu ] ) ;
128+
40129 useEffect ( ( ) => {
41130 const updateDimensions = ( ) => {
42131 if ( typeof window !== 'undefined' && window . innerWidth >= 1025 ) {
@@ -50,36 +139,12 @@ export const Header:FC<HeaderProps>=({ setModalOpen }) =>{
50139 return ( ) => window . removeEventListener ( 'resize' , updateDimensions ) ;
51140 } , [ ] ) ;
52141
53- useEffect ( ( ) => {
54- if ( typeof window === 'undefined' || window . innerWidth < 1024 ) return ;
55-
56- const elements = [
57- logoRef . current ,
58- navLinksRef . current ,
59- themeSwitcherRef . current ,
60- buttonRef . current
61- ] . filter ( Boolean ) as HTMLElement [ ] ;
62-
63- gsap . set ( elements , { opacity : 0 , y : 40 } ) ;
64-
65- gsap . to ( elements , {
66- opacity : 1 ,
67- y : 0 ,
68- duration : 0.8 ,
69- stagger : 0.15 ,
70- ease : 'power2.in' ,
71- delay : 0.3
72- } ) ;
73-
74- } , [ ] ) ;
75-
76142 return (
77143 < div className = { styles . wrapper } ref = { headerRef } >
78144 < Link
79145 prefetch = { false }
80146 className = { cn ( styles . linkHover , styles . logoLink ) }
81147 href = '/'
82- ref = { logoRef }
83148 >
84149 < ThemeImage
85150 width = { 178 }
@@ -90,7 +155,7 @@ export const Header:FC<HeaderProps>=({ setModalOpen }) =>{
90155 />
91156 </ Link >
92157
93- < div className = { styles . navLinks } ref = { navLinksRef } >
158+ < div className = { styles . navLinks } >
94159 { menuItems . map ( ( menuItem , i ) => (
95160 < Link prefetch = { false } key = { i } href = { menuItem . path } >
96161 < div
@@ -104,7 +169,7 @@ export const Header:FC<HeaderProps>=({ setModalOpen }) =>{
104169 ) ) }
105170 </ div >
106171
107- < div className = { styles . themeSwitcher } onClick = { toggleDarkMode } ref = { themeSwitcherRef } >
172+ < div className = { styles . themeSwitcher } onClick = { toggleDarkMode } >
108173 < ThemeImage
109174 width = { 34 }
110175 height = { 34 }
@@ -113,96 +178,91 @@ export const Header:FC<HeaderProps>=({ setModalOpen }) =>{
113178 />
114179 </ div >
115180
116-
117- < Button
118- onClick = { ( ) => setModalOpen ( true ) }
119- classNames = { styles . mvmButton }
120- text = 'MWM'
121- isPrimary
122- ref = { buttonRef }
123- />
124-
181+ < Button
182+ onClick = { ( ) => setModalOpen ( true ) }
183+ classNames = { styles . mvmButton }
184+ text = 'MWM'
185+ isPrimary
186+ />
125187
126188 < div
127189 onClick = { ( ) => setMobileMenu ( ! isMobileMenu ) }
128190 className = { styles . burger }
129191 >
130- < div className = { styles . burgerLines } > </ div >
192+ < div className = { isMobileMenu ? styles . burgerLinesActive : styles . burgerLines } > </ div >
131193 </ div >
132194
133- { isMobileMenu && (
134- < div className = { styles . mobileMenu } >
135- < div className = { styles . mobileMenuContent } >
136- < div className = { styles . mobileHeader } >
137- < Link prefetch = { false } className = { styles . logoLink } href = '/' >
138- < ThemeImage
139- width = { 178 }
140- height = { 50 }
141- alt = 'Dapplets'
142- src = 'icons/header/logo.svg'
143- className = { styles . logoImage }
144- />
145- </ Link >
146- < div className = { styles . themeSwitcher } onClick = { toggleDarkMode } >
147- < ThemeImage
148- width = { 34 }
149- height = { 34 }
150- alt = 'darkMode'
151- src = 'icons/header/theme-switcher.svg'
152- />
153- </ div >
154- < div
155- onClick = { ( ) => setMobileMenu ( ! isMobileMenu ) }
156- className = { styles . burger }
157- >
158- < div className = { styles . burgerLinesActive } > </ div >
159- </ div >
195+ < div className = { styles . mobileMenu } ref = { mobileMenuRef } style = { { display : isMobileMenu ? 'block' : 'none' } } >
196+ < div className = { styles . mobileMenuContent } >
197+ < div className = { styles . mobileHeader } >
198+ < Link prefetch = { false } className = { styles . logoLink } href = '/' >
199+ < ThemeImage
200+ width = { 178 }
201+ height = { 50 }
202+ alt = 'Dapplets'
203+ src = 'icons/header/logo.svg'
204+ className = { styles . logoImage }
205+ />
206+ </ Link >
207+ < div className = { styles . themeSwitcher } onClick = { toggleDarkMode } >
208+ < ThemeImage
209+ width = { 34 }
210+ height = { 34 }
211+ alt = 'darkMode'
212+ src = 'icons/header/theme-switcher.svg'
213+ />
160214 </ div >
215+ < div
216+ onClick = { ( ) => setMobileMenu ( ! isMobileMenu ) }
217+ className = { styles . burger }
218+ >
219+ < div className = { styles . burgerLinesActive } > </ div >
220+ </ div >
221+ </ div >
161222
162- < div className = { styles . mobileNav } >
163- { menuItems . map ( ( menuItem , i ) => (
223+ < div className = { styles . mobileNav } >
224+ { menuItems . map ( ( menuItem , i ) => (
225+ < Link
226+ prefetch = { false }
227+ key = { i }
228+ href = { menuItem . path }
229+ onClick = { ( ) => setMobileMenu ( false ) }
230+ >
231+ < div
232+ className = { cn ( styles . linkHover , {
233+ [ styles . active ] : pathname === menuItem . path ,
234+ } ) }
235+ >
236+ { menuItem . title }
237+ </ div >
238+ </ Link >
239+ ) ) }
240+ </ div >
241+
242+ < div className = { styles . socialLinks } >
243+ { [ 'github' , 'discord' , 'tg' , 'medium' , 'x' , 'email' ] . map (
244+ ( social ) => (
164245 < Link
246+ key = { social }
165247 prefetch = { false }
166- key = { i }
167- href = { menuItem . path }
168- onClick = { ( ) => setMobileMenu ( false ) }
248+ target = '_blank'
249+ href = { `#${ social } ` }
169250 >
170- < div
171- className = { cn ( styles . linkHover , {
172- [ styles . active ] : pathname === menuItem . path ,
173- } ) }
174- >
175- { menuItem . title }
176- </ div >
251+ < ThemeImage
252+ className = { styles . socialIcon }
253+ width = { 36 }
254+ height = { 36 }
255+ alt = { social }
256+ src = { `icons/footer/ ${ social } .svg` }
257+ / >
177258 </ Link >
178- ) ) }
179- </ div >
180-
181- < div className = { styles . socialLinks } >
182- { [ 'github' , 'discord' , 'tg' , 'medium' , 'x' , 'email' ] . map (
183- ( social ) => (
184- < Link
185- key = { social }
186- prefetch = { false }
187- target = '_blank'
188- href = { `#${ social } ` }
189- >
190- < ThemeImage
191- className = { styles . socialIcon }
192- width = { 36 }
193- height = { 36 }
194- alt = { social }
195- src = { `icons/footer/${ social } .svg` }
196- />
197- </ Link >
198- )
199- ) }
200- </ div >
259+ )
260+ ) }
201261 </ div >
202262 </ div >
203- ) }
263+ </ div >
204264 </ div >
205265 ) ;
206- }
266+ } ;
207267
208- export default Header
268+ export default Header ;
0 commit comments