From 35522701423e8c01504004ae51efbf4a28f6d40d Mon Sep 17 00:00:00 2001 From: Google Date: Fri, 9 May 2025 21:10:12 +0700 Subject: [PATCH 1/2] fix: refactor Topnav --- src/components/HeaderComponent/index.tsx | 35 ++++++ src/components/Layout/TopNav/TopNav.tsx | 117 +++--------------- src/components/NavItem/index.tsx | 20 +++ src/components/PageHeading.tsx | 1 - src/components/index.tsx | 2 + .../learn/passing-data-deeply-with-context.md | 8 ++ src/hooks/index.tsx | 1 + src/hooks/useMenuAndScroll.ts | 87 +++++++++++++ 8 files changed, 169 insertions(+), 102 deletions(-) create mode 100644 src/components/HeaderComponent/index.tsx create mode 100644 src/components/NavItem/index.tsx create mode 100644 src/components/index.tsx create mode 100644 src/hooks/index.tsx create mode 100644 src/hooks/useMenuAndScroll.ts diff --git a/src/components/HeaderComponent/index.tsx b/src/components/HeaderComponent/index.tsx new file mode 100644 index 00000000000..349479bd28a --- /dev/null +++ b/src/components/HeaderComponent/index.tsx @@ -0,0 +1,35 @@ +import {Seo} from 'components/Seo'; +import SocialBanner from '../SocialBanner'; + +interface HeaderProps { + title: string; + titleForTitleTag: string; + section: string; + routeTree: any; + breadcrumbs: any; + isHomePage: boolean; + searchOrder: number | undefined; +} + +const HeaderComponent = ({ + title, + titleForTitleTag, + section, + isHomePage, + searchOrder, +}: HeaderProps) => { + return ( + <> + + + {/* */} + + ); +}; +export default HeaderComponent; diff --git a/src/components/Layout/TopNav/TopNav.tsx b/src/components/Layout/TopNav/TopNav.tsx index cc5c654e3d0..f1deb58f45b 100644 --- a/src/components/Layout/TopNav/TopNav.tsx +++ b/src/components/Layout/TopNav/TopNav.tsx @@ -2,30 +2,23 @@ * 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 Image from 'next/image'; import NextLink from 'next/link'; -import {useRouter} from 'next/router'; -import {disableBodyScroll, enableBodyScroll} from 'body-scroll-lock'; +import * as React from 'react'; +import {Suspense} from 'react'; import {IconClose} from 'components/Icon/IconClose'; import {IconHamburger} from 'components/Icon/IconHamburger'; import {IconSearch} from 'components/Icon/IconSearch'; +import NavItem from 'components/NavItem'; import {Search} from 'components/Search'; +import {useMenuAndScroll} from 'hooks'; +import {siteConfig} from 'siteConfig'; 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 { @@ -120,23 +113,6 @@ function Link({ ); } -function NavItem({url, isActive, children}: any) { - return ( -
- - {children} - -
- ); -} - function Kbd(props: {children?: React.ReactNode; wide?: boolean}) { const {wide, ...rest} = props; const width = wide ? 'w-10' : 'w-5'; @@ -158,77 +134,16 @@ export default function TopNav({ 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(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); - }, []); + const { + isMenuOpen, + setIsMenuOpen, + showSearch, + isScrolled, + scrollParentRef, + scrollDetectorRef, + onOpenSearch, + onCloseSearch, + } = useMenuAndScroll(routeTree); return ( <> diff --git a/src/components/NavItem/index.tsx b/src/components/NavItem/index.tsx new file mode 100644 index 00000000000..b1a6256cf55 --- /dev/null +++ b/src/components/NavItem/index.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link'; +import cn from 'classnames'; + +const NavItem = ({url, isActive, children}: any) => { + return ( +
+ + {children} + +
+ ); +}; +export default NavItem; diff --git a/src/components/PageHeading.tsx b/src/components/PageHeading.tsx index 409bd03b8ed..3f15afe9536 100644 --- a/src/components/PageHeading.tsx +++ b/src/components/PageHeading.tsx @@ -27,7 +27,6 @@ function PageHeading({ tags = [], breadcrumbs, }: PageHeadingProps) { - console.log('version', version); return (
diff --git a/src/components/index.tsx b/src/components/index.tsx new file mode 100644 index 00000000000..4af4df3a5df --- /dev/null +++ b/src/components/index.tsx @@ -0,0 +1,2 @@ +export {default as HeaderComponent} from './HeaderComponent'; +export {default as NavItem} from './NavItem'; diff --git a/src/content/learn/passing-data-deeply-with-context.md b/src/content/learn/passing-data-deeply-with-context.md index e81678c8e2e..f0b477e9fdc 100644 --- a/src/content/learn/passing-data-deeply-with-context.md +++ b/src/content/learn/passing-data-deeply-with-context.md @@ -583,6 +583,14 @@ export default function Page() { ...
... +
+ ... +
+
+ + + ); +} ``` Since context lets you read information from a component above, each `Section` could read the `level` from the `Section` above, and pass `level + 1` down automatically. Here is how you could do it: diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx new file mode 100644 index 00000000000..c8aa82ccfbc --- /dev/null +++ b/src/hooks/index.tsx @@ -0,0 +1 @@ +export {default as useMenuAndScroll} from './useMenuAndScroll'; diff --git a/src/hooks/useMenuAndScroll.ts b/src/hooks/useMenuAndScroll.ts new file mode 100644 index 00000000000..6210af930c1 --- /dev/null +++ b/src/hooks/useMenuAndScroll.ts @@ -0,0 +1,87 @@ +import {useState, useEffect, useRef, useCallback, startTransition} from 'react'; +import {useRouter} from 'next/router'; +import {disableBodyScroll, enableBodyScroll} from 'body-scroll-lock'; +import {RouteItem} from 'components/Layout/getRouteMeta'; + +const useMenuAndScroll = (routeTree: RouteItem) => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [showSearch, setShowSearch] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); + const scrollParentRef = useRef(null); + const {asPath} = useRouter(); + const scrollDetectorRef = useRef(null); + + if ((routeTree as any).routes.length === 1) { + routeTree = (routeTree as any).routes[0]; + } + + useEffect(() => { + if (isMenuOpen) { + const preferredScrollParent = scrollParentRef.current!; + disableBodyScroll(preferredScrollParent); + return () => enableBodyScroll(preferredScrollParent); + } else { + return undefined; + } + }, [isMenuOpen]); + + useEffect(() => { + setIsMenuOpen(false); + }, [asPath]); + + 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); + }; + }, []); + + 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 { + isMenuOpen, + setIsMenuOpen, + showSearch, + setShowSearch, + isScrolled, + scrollParentRef, + scrollDetectorRef, + onOpenSearch, + onCloseSearch, + }; +}; +export default useMenuAndScroll; From 10e53dbe77f332abc859f4468c9304ad912b5d07 Mon Sep 17 00:00:00 2001 From: Google Date: Fri, 9 May 2025 21:50:23 +0700 Subject: [PATCH 2/2] fix: refactor icon Topnav --- src/components/Icon/IconDark.tsx | 28 ++++++++ src/components/Icon/IconLanguage.tsx | 25 ++++++++ src/components/Icon/IconLight.tsx | 33 ++++++++++ src/components/Layout/TopNav/TopNav.tsx | 85 ++++--------------------- 4 files changed, 97 insertions(+), 74 deletions(-) create mode 100644 src/components/Icon/IconDark.tsx create mode 100644 src/components/Icon/IconLanguage.tsx create mode 100644 src/components/Icon/IconLight.tsx diff --git a/src/components/Icon/IconDark.tsx b/src/components/Icon/IconDark.tsx new file mode 100644 index 00000000000..4d388cf7516 --- /dev/null +++ b/src/components/Icon/IconDark.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import type {SVGProps} from 'react'; +import {memo} from 'react'; + +export const IconDark = memo>(function IconDark() { + return ( + + + + + + + ); +}); diff --git a/src/components/Icon/IconLanguage.tsx b/src/components/Icon/IconLanguage.tsx new file mode 100644 index 00000000000..208da8e789b --- /dev/null +++ b/src/components/Icon/IconLanguage.tsx @@ -0,0 +1,25 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import type {SVGProps} from 'react'; + +export const IconLanguage = memo>(function IconLanguage( + props +) { + return ( + + + + ); +}); diff --git a/src/components/Icon/IconLight.tsx b/src/components/Icon/IconLight.tsx new file mode 100644 index 00000000000..f22110c1d90 --- /dev/null +++ b/src/components/Icon/IconLight.tsx @@ -0,0 +1,33 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import type {SVGProps} from 'react'; + +export const IconLight = memo>(function IconLight() { + return ( + + + + + + + + + + ); +}); diff --git a/src/components/Layout/TopNav/TopNav.tsx b/src/components/Layout/TopNav/TopNav.tsx index f1deb58f45b..8333b33d95f 100644 --- a/src/components/Layout/TopNav/TopNav.tsx +++ b/src/components/Layout/TopNav/TopNav.tsx @@ -20,6 +20,10 @@ import {Feedback} from '../Feedback'; import {SidebarRouteTree} from '../Sidebar'; import type {RouteItem} from '../getRouteMeta'; import BrandMenu from './BrandMenu'; +import {IconGitHub} from 'components/Icon/IconGitHub'; +import {IconLanguage} from 'components/Icon/IconLanguage'; +import {IconLight} from 'components/Icon/IconLight'; +import {IconDark} from 'components/Icon/IconDark'; declare global { interface Window { @@ -28,76 +32,6 @@ declare global { } } -const darkIcon = ( - - - - - - -); - -const lightIcon = ( - - - - - - - - - -); - -const languageIcon = ( - - - -); - -const githubIcon = ( - - - - - -); - function Link({ href, children, @@ -269,7 +203,8 @@ export default function TopNav({ 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} + {/* {darkIcon} */} +
@@ -280,7 +215,8 @@ export default function TopNav({ 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} + {/* {lightIcon} */} +
@@ -288,7 +224,7 @@ export default function TopNav({ 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} +
@@ -298,7 +234,8 @@ export default function TopNav({ 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} + {/* {githubIcon} */} +