-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathTopNav.tsx
451 lines (429 loc) · 21.9 KB
/
TopNav.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import {
useState,
useRef,
useCallback,
useEffect,
startTransition,
Suspense,
} from 'react';
import Image from 'next/image';
import * as React from 'react';
import cn from 'classnames';
import NextLink from 'next/link';
import {useRouter} from 'next/router';
import {disableBodyScroll, enableBodyScroll} from 'body-scroll-lock';
import {IconClose} from 'components/Icon/IconClose';
import {IconHamburger} from 'components/Icon/IconHamburger';
import {IconSearch} from 'components/Icon/IconSearch';
import {Search} from 'components/Search';
import {Logo} from '../../Logo';
import {Feedback} from '../Feedback';
import {SidebarRouteTree} from '../Sidebar';
import type {RouteItem} from '../getRouteMeta';
import {siteConfig} from 'siteConfig';
import BrandMenu from './BrandMenu';
declare global {
interface Window {
__theme: string;
__setPreferredTheme: (theme: string) => void;
}
}
const darkIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
viewBox="0 0 32 32">
<g fill="none" fillRule="evenodd" transform="translate(-440 -200)">
<path
fill="currentColor"
fillRule="nonzero"
stroke="currentColor"
strokeWidth={0.5}
d="M102,21 C102,18.1017141 103.307179,15.4198295 105.51735,13.6246624 C106.001939,13.2310647 105.821611,12.4522936 105.21334,12.3117518 C104.322006,12.1058078 103.414758,12 102.5,12 C95.8722864,12 90.5,17.3722864 90.5,24 C90.5,30.6277136 95.8722864,36 102.5,36 C106.090868,36 109.423902,34.4109093 111.690274,31.7128995 C112.091837,31.2348572 111.767653,30.5041211 111.143759,30.4810139 C106.047479,30.2922628 102,26.1097349 102,21 Z M102.5,34.5 C96.7007136,34.5 92,29.7992864 92,24 C92,18.2007136 96.7007136,13.5 102.5,13.5 C102.807386,13.5 103.113925,13.5136793 103.419249,13.5407785 C101.566047,15.5446378 100.5,18.185162 100.5,21 C100.5,26.3198526 104.287549,30.7714322 109.339814,31.7756638 L109.516565,31.8092927 C107.615276,33.5209452 105.138081,34.5 102.5,34.5 Z"
transform="translate(354.5 192)"
/>
<polygon points="444 228 468 228 468 204 444 204" />
</g>
</svg>
);
const lightIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 32 32">
<g fill="none" fillRule="evenodd" transform="translate(-442 -200)">
<g fill="currentColor" transform="translate(356 144)">
<path
fillRule="nonzero"
d="M108.5 24C108.5 27.5902136 105.590214 30.5 102 30.5 98.4097864 30.5 95.5 27.5902136 95.5 24 95.5 20.4097864 98.4097864 17.5 102 17.5 105.590214 17.5 108.5 20.4097864 108.5 24zM107 24C107 21.2382136 104.761786 19 102 19 99.2382136 19 97 21.2382136 97 24 97 26.7617864 99.2382136 29 102 29 104.761786 29 107 26.7617864 107 24zM101 12.75L101 14.75C101 15.1642136 101.335786 15.5 101.75 15.5 102.164214 15.5 102.5 15.1642136 102.5 14.75L102.5 12.75C102.5 12.3357864 102.164214 12 101.75 12 101.335786 12 101 12.3357864 101 12.75zM95.7255165 14.6323616L96.7485165 16.4038616C96.9556573 16.7625614 97.4143618 16.8854243 97.7730616 16.6782835 98.1317614 16.4711427 98.2546243 16.0124382 98.0474835 15.6537384L97.0244835 13.8822384C96.8173427 13.5235386 96.3586382 13.4006757 95.9999384 13.6078165 95.6412386 13.8149573 95.5183757 14.2736618 95.7255165 14.6323616zM91.8822384 19.0244835L93.6537384 20.0474835C94.0124382 20.2546243 94.4711427 20.1317614 94.6782835 19.7730616 94.8854243 19.4143618 94.7625614 18.9556573 94.4038616 18.7485165L92.6323616 17.7255165C92.2736618 17.5183757 91.8149573 17.6412386 91.6078165 17.9999384 91.4006757 18.3586382 91.5235386 18.8173427 91.8822384 19.0244835zM90.75 25L92.75 25C93.1642136 25 93.5 24.6642136 93.5 24.25 93.5 23.8357864 93.1642136 23.5 92.75 23.5L90.75 23.5C90.3357864 23.5 90 23.8357864 90 24.25 90 24.6642136 90.3357864 25 90.75 25zM92.6323616 30.2744835L94.4038616 29.2514835C94.7625614 29.0443427 94.8854243 28.5856382 94.6782835 28.2269384 94.4711427 27.8682386 94.0124382 27.7453757 93.6537384 27.9525165L91.8822384 28.9755165C91.5235386 29.1826573 91.4006757 29.6413618 91.6078165 30.0000616 91.8149573 30.3587614 92.2736618 30.4816243 92.6323616 30.2744835zM97.0244835 34.1177616L98.0474835 32.3462616C98.2546243 31.9875618 98.1317614 31.5288573 97.7730616 31.3217165 97.4143618 31.1145757 96.9556573 31.2374386 96.7485165 31.5961384L95.7255165 33.3676384C95.5183757 33.7263382 95.6412386 34.1850427 95.9999384 34.3921835 96.3586382 34.5993243 96.8173427 34.4764614 97.0244835 34.1177616zM103 35.25L103 33.25C103 32.8357864 102.664214 32.5 102.25 32.5 101.835786 32.5 101.5 32.8357864 101.5 33.25L101.5 35.25C101.5 35.6642136 101.835786 36 102.25 36 102.664214 36 103 35.6642136 103 35.25zM108.274483 33.3676384L107.251483 31.5961384C107.044343 31.2374386 106.585638 31.1145757 106.226938 31.3217165 105.868239 31.5288573 105.745376 31.9875618 105.952517 32.3462616L106.975517 34.1177616C107.182657 34.4764614 107.641362 34.5993243 108.000062 34.3921835 108.358761 34.1850427 108.481624 33.7263382 108.274483 33.3676384zM112.117762 28.9755165L110.346262 27.9525165C109.987562 27.7453757 109.528857 27.8682386 109.321717 28.2269384 109.114576 28.5856382 109.237439 29.0443427 109.596138 29.2514835L111.367638 30.2744835C111.726338 30.4816243 112.185043 30.3587614 112.392183 30.0000616 112.599324 29.6413618 112.476461 29.1826573 112.117762 28.9755165zM113.25 23L111.25 23C110.835786 23 110.5 23.3357864 110.5 23.75 110.5 24.1642136 110.835786 24.5 111.25 24.5L113.25 24.5C113.664214 24.5 114 24.1642136 114 23.75 114 23.3357864 113.664214 23 113.25 23zM111.367638 17.7255165L109.596138 18.7485165C109.237439 18.9556573 109.114576 19.4143618 109.321717 19.7730616 109.528857 20.1317614 109.987562 20.2546243 110.346262 20.0474835L112.117762 19.0244835C112.476461 18.8173427 112.599324 18.3586382 112.392183 17.9999384 112.185043 17.6412386 111.726338 17.5183757 111.367638 17.7255165zM106.975517 13.8822384L105.952517 15.6537384C105.745376 16.0124382 105.868239 16.4711427 106.226938 16.6782835 106.585638 16.8854243 107.044343 16.7625614 107.251483 16.4038616L108.274483 14.6323616C108.481624 14.2736618 108.358761 13.8149573 108.000062 13.6078165 107.641362 13.4006757 107.182657 13.5235386 106.975517 13.8822384z"
transform="translate(0 48)"
stroke="currentColor"
strokeWidth={0.25}
/>
<path
d="M98.6123,60.1372 C98.6123,59.3552 98.8753,58.6427 99.3368,58.0942 C99.5293,57.8657 99.3933,57.5092 99.0943,57.5017 C99.0793,57.5012 99.0633,57.5007 99.0483,57.5007 C97.1578,57.4747 95.5418,59.0312 95.5008,60.9217 C95.4578,62.8907 97.0408,64.5002 98.9998,64.5002 C99.7793,64.5002 100.4983,64.2452 101.0798,63.8142 C101.3183,63.6372 101.2358,63.2627 100.9478,63.1897 C99.5923,62.8457 98.6123,61.6072 98.6123,60.1372"
transform="translate(3 11)"
/>
</g>
<polygon points="444 228 468 228 468 204 444 204" />
</g>
</svg>
);
const languageIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24">
<path
fill="currentColor"
d=" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z "
/>
</svg>
);
const githubIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</g>
</svg>
);
function Link({
href,
children,
...props
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
return (
<NextLink
href={`${href}`}
className="inline leading-normal transition duration-100 ease-in border-b border-opacity-0 text-primary dark:text-primary-dark hover:text-link hover:dark:text-link-dark border-link hover:border-opacity-100"
{...props}>
{children}
</NextLink>
);
}
function NavItem({url, isActive, children}: any) {
return (
<div className="flex flex-auto sm:flex-1">
<Link
href={url}
className={cn(
'active:scale-95 transition-transform w-full text-center outline-link py-1.5 px-1.5 xs:px-3 sm:px-4 rounded-full capitalize whitespace-nowrap',
!isActive && 'hover:bg-primary/5 hover:dark:bg-primary-dark/5',
isActive &&
'bg-highlight dark:bg-highlight-dark text-link dark:text-link-dark'
)}>
{children}
</Link>
</div>
);
}
function Kbd(props: {children?: React.ReactNode; wide?: boolean}) {
const {wide, ...rest} = props;
const width = wide ? 'w-10' : 'w-5';
return (
<kbd
className={`${width} h-5 border border-transparent me-1 bg-wash dark:bg-wash-dark text-gray-30 align-middle p-0 inline-flex justify-center items-center text-xs text-center rounded-md`}
{...rest}
/>
);
}
export default function TopNav({
routeTree,
breadcrumbs,
section,
}: {
routeTree: RouteItem;
breadcrumbs: RouteItem[];
section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown';
}) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [showSearch, setShowSearch] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const scrollParentRef = useRef<HTMLDivElement>(null);
const {asPath} = useRouter();
// HACK. Fix up the data structures instead.
if ((routeTree as any).routes.length === 1) {
routeTree = (routeTree as any).routes[0];
}
// While the overlay is open, disable body scroll.
useEffect(() => {
if (isMenuOpen) {
const preferredScrollParent = scrollParentRef.current!;
disableBodyScroll(preferredScrollParent);
return () => enableBodyScroll(preferredScrollParent);
} else {
return undefined;
}
}, [isMenuOpen]);
// Close the overlay on any navigation.
useEffect(() => {
setIsMenuOpen(false);
}, [asPath]);
// Also close the overlay if the window gets resized past mobile layout.
// (This is also important because we don't want to keep the body locked!)
useEffect(() => {
const media = window.matchMedia(`(max-width: 1023px)`);
function closeIfNeeded() {
if (!media.matches) {
setIsMenuOpen(false);
}
}
closeIfNeeded();
media.addEventListener('change', closeIfNeeded);
return () => {
media.removeEventListener('change', closeIfNeeded);
};
}, []);
const scrollDetectorRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
setIsScrolled(!entry.isIntersecting);
});
},
{
root: null,
rootMargin: `0px 0px`,
threshold: 0,
}
);
observer.observe(scrollDetectorRef.current!);
return () => observer.disconnect();
}, []);
const onOpenSearch = useCallback(() => {
startTransition(() => {
setShowSearch(true);
});
}, []);
const onCloseSearch = useCallback(() => {
setShowSearch(false);
}, []);
return (
<>
<Search
isOpen={showSearch}
onOpen={onOpenSearch}
onClose={onCloseSearch}
/>
<div ref={scrollDetectorRef} />
<div
className={cn(
isMenuOpen
? 'h-screen sticky top-0 lg:bottom-0 lg:h-screen flex flex-col shadow-nav dark:shadow-nav-dark z-20'
: 'z-40 sticky top-0'
)}>
<nav
className={cn(
'duration-300 backdrop-filter backdrop-blur-lg backdrop-saturate-200 transition-shadow bg-opacity-90 items-center w-full flex justify-between bg-wash dark:bg-wash-dark dark:bg-opacity-95 px-1.5 lg:pe-5 lg:ps-4 z-40',
{'dark:shadow-nav-dark shadow-nav': isScrolled || isMenuOpen}
)}>
<div className="flex items-center justify-between w-full h-16 gap-0 sm:gap-3">
<div className="flex flex-row 3xl:flex-1 items-centers">
<button
type="button"
aria-label="Menu"
onClick={() => setIsMenuOpen(!isMenuOpen)}
className={cn(
'active:scale-95 transition-transform flex lg:hidden w-12 h-12 rounded-full items-center justify-center hover:bg-primary/5 hover:dark:bg-primary-dark/5 outline-link',
{
'text-link dark:text-link-dark': isMenuOpen,
}
)}>
{isMenuOpen ? <IconClose /> : <IconHamburger />}
</button>
<BrandMenu>
<div className="flex items-center">
<div className="uwu-visible flex items-center justify-center h-full">
<NextLink href="/">
<Image
alt="logo by @sawaratsuki1004"
title="logo by @sawaratsuki1004"
className="h-8"
priority
width={63}
height={32}
src="/images/uwu.png"
/>
</NextLink>
</div>
<div className="uwu-hidden">
<NextLink
href="/"
className={`active:scale-95 overflow-hidden transition-transform relative items-center text-primary dark:text-primary-dark p-1 whitespace-nowrap outline-link rounded-full 3xl:rounded-xl inline-flex text-lg font-normal gap-2`}>
<Logo
className={cn(
'text-sm me-0 w-10 h-10 text-brand dark:text-brand-dark flex origin-center transition-all ease-in-out'
)}
/>
<span className="sr-only 3xl:not-sr-only">React</span>
</NextLink>
</div>
</div>
</BrandMenu>
<div className="flex flex-column justify-center items-center">
<NextLink
href="/versions"
className=" flex py-2 flex-column justify-center items-center text-gray-50 dark:text-gray-30 hover:text-link hover:dark:text-link-dark hover:underline text-sm ms-1 cursor-pointer">
v{siteConfig.version}
</NextLink>
</div>
</div>
<div className="items-center justify-center flex-1 hidden w-full md:flex 3xl:w-auto 3xl:shrink-0 3xl:justify-center">
<button
type="button"
className={cn(
'flex 3xl:w-[56rem] 3xl:mx-0 relative ps-4 pe-1 py-1 h-10 bg-gray-30/20 dark:bg-gray-40/20 outline-none focus:outline-link betterhover:hover:bg-opacity-80 pointer items-center text-start w-full text-gray-30 rounded-full align-middle text-base'
)}
onClick={onOpenSearch}>
<IconSearch className="align-middle me-3 text-gray-30 shrink-0 group-betterhover:hover:text-gray-70" />
Search
<span className="hidden ms-auto sm:flex item-center me-1">
<Kbd data-platform="mac">⌘</Kbd>
<Kbd data-platform="win" wide>
Ctrl
</Kbd>
<Kbd>K</Kbd>
</span>
</button>
</div>
<div className="text-base justify-center items-center gap-1.5 flex 3xl:flex-1 flex-row 3xl:justify-end">
<div className="mx-2.5 gap-1.5 hidden lg:flex">
<NavItem isActive={section === 'learn'} url="/learn">
Learn
</NavItem>
<NavItem
isActive={section === 'reference'}
url="/reference/react">
Reference
</NavItem>
<NavItem isActive={section === 'community'} url="/community">
Community
</NavItem>
<NavItem isActive={section === 'blog'} url="/blog">
Blog
</NavItem>
</div>
<div className="flex w-full md:hidden"></div>
<div className="flex items-center -space-x-2.5 xs:space-x-0 ">
<div className="flex md:hidden">
<button
aria-label="Search"
type="button"
className="flex items-center justify-center w-12 h-12 transition-transform rounded-full active:scale-95 md:hidden hover:bg-secondary-button hover:dark:bg-secondary-button-dark outline-link"
onClick={onOpenSearch}>
<IconSearch className="w-5 h-5 align-middle" />
</button>
</div>
<div className="flex dark:hidden">
<button
type="button"
aria-label="Use Dark Mode"
onClick={() => {
window.__setPreferredTheme('dark');
}}
className="flex items-center justify-center w-12 h-12 transition-transform rounded-full active:scale-95 hover:bg-primary/5 hover:dark:bg-primary-dark/5 outline-link">
{darkIcon}
</button>
</div>
<div className="hidden dark:flex">
<button
type="button"
aria-label="Use Light Mode"
onClick={() => {
window.__setPreferredTheme('light');
}}
className="flex items-center justify-center w-12 h-12 transition-transform rounded-full active:scale-95 hover:bg-primary/5 hover:dark:bg-primary-dark/5 outline-link">
{lightIcon}
</button>
</div>
<div className="flex">
<Link
href="/community/translations"
aria-label="Translations"
className="active:scale-95 transition-transform flex w-12 h-12 rounded-full items-center justify-center hover:bg-primary/5 hover:dark:bg-primary-dark/5 outline-link">
{languageIcon}
</Link>
</div>
<div className="flex">
<Link
href="https://github.com/facebook/react/releases"
target="_blank"
rel="noreferrer noopener"
aria-label="Open on GitHub"
className="flex items-center justify-center w-12 h-12 transition-transform rounded-full active:scale-95 hover:bg-primary/5 hover:dark:bg-primary-dark/5 outline-link">
{githubIcon}
</Link>
</div>
</div>
</div>
</div>
</nav>
{isMenuOpen && (
<div
ref={scrollParentRef}
className="overflow-y-scroll isolate no-bg-scrollbar lg:w-[342px] grow bg-wash dark:bg-wash-dark">
<aside
className={cn(
`lg:grow lg:flex flex-col w-full pb-8 lg:pb-0 lg:max-w-custom-xs z-40`,
isMenuOpen ? 'block z-30' : 'hidden lg:block'
)}>
<nav
role="navigation"
style={{'--bg-opacity': '.2'} as React.CSSProperties} // Need to cast here because CSS vars aren't considered valid in TS types (cuz they could be anything)
className="w-full pt-4 scrolling-touch lg:h-auto grow pe-0 lg:pe-5 lg:py-6 md:pt-4 lg:pt-4 scrolling-gpu">
{/* No fallback UI so need to be careful not to suspend directly inside. */}
<Suspense fallback={null}>
<div className="ps-3 xs:ps-5 xs:gap-0.5 xs:text-base overflow-x-auto flex flex-row lg:hidden text-base font-bold text-secondary dark:text-secondary-dark">
<NavItem isActive={section === 'learn'} url="/learn">
Learn
</NavItem>
<NavItem
isActive={section === 'reference'}
url="/reference/react">
Reference
</NavItem>
<NavItem
isActive={section === 'community'}
url="/community">
Community
</NavItem>
<NavItem isActive={section === 'blog'} url="/blog">
Blog
</NavItem>
</div>
<div
role="separator"
className="mt-4 mb-2 border-b ms-5 border-border dark:border-border-dark"
/>
<SidebarRouteTree
// Don't share state between the desktop and mobile versions.
// This avoids unnecessary animations and visual flicker.
key={isMenuOpen ? 'mobile-overlay' : 'desktop-or-hidden'}
routeTree={routeTree}
breadcrumbs={breadcrumbs}
isForceExpanded={isMenuOpen}
/>
</Suspense>
<div className="h-16" />
</nav>
<div className="fixed bottom-0 hidden lg:block">
<Feedback />
</div>
</aside>
</div>
)}
</div>
</>
);
}