The Node.js® Website

refactor: remove unnecessary hook (#6120)

* refactor: remove unnecessary hook

* fix: promisify instead of ignoring

* Update util/getBitness.ts

Co-authored-by: canerakdas <canerakdas@gmail.com>
Signed-off-by: Claudio W <cwunder@gnome.org>

---------

Signed-off-by: Claudio W <cwunder@gnome.org>
Co-authored-by: canerakdas <canerakdas@gmail.com>

authored by Claudio W canerakdas and committed by GitHub 33f18242 ea0e0a04

Changed files
+80 -135
components
Common
ActiveLink
ActiveLocalizedLink
__tests__
Containers
NavItem
hooks
layouts
styles
old
layout
page-modules
util
+41
components/Common/ActiveLink/__tests__/index.test.mjs
··· 1 + import { render, screen } from '@testing-library/react'; 2 + 3 + import ActiveLink from '..'; 4 + 5 + describe('ActiveLink', () => { 6 + it('renders as localized link', () => { 7 + render( 8 + <ActiveLink className="link" activeClassName="active" href="/link"> 9 + Link 10 + </ActiveLink> 11 + ); 12 + 13 + expect(screen.findByText('Link')).resolves.toHaveAttribute( 14 + 'href', 15 + '/en/link' 16 + ); 17 + }); 18 + 19 + it('ignores active class when href not matches location.href', () => { 20 + render( 21 + <ActiveLink className="link" activeClassName="active" href="/not-link"> 22 + Link 23 + </ActiveLink> 24 + ); 25 + 26 + expect(screen.findByText('Link')).resolves.toHaveAttribute('class', 'link'); 27 + }); 28 + 29 + it('sets active class when href matches location.href', () => { 30 + render( 31 + <ActiveLink className="link" activeClassName="active" href="/link"> 32 + Link 33 + </ActiveLink> 34 + ); 35 + 36 + expect(screen.findByText('Link')).resolves.toHaveAttribute( 37 + 'class', 38 + 'link active' 39 + ); 40 + }); 41 + });
-53
components/Common/ActiveLocalizedLink/__tests__/index.test.mjs
··· 1 - import { render, screen } from '@testing-library/react'; 2 - 3 - import ActiveLocalizedLink from '..'; 4 - 5 - describe('ActiveLocalizedLink', () => { 6 - it('renders as localized link', () => { 7 - render( 8 - <ActiveLocalizedLink 9 - className="link" 10 - activeClassName="active" 11 - href="/link" 12 - > 13 - Link 14 - </ActiveLocalizedLink> 15 - ); 16 - 17 - expect(screen.findByText('Link')).resolves.toHaveAttribute( 18 - 'href', 19 - '/en/link' 20 - ); 21 - }); 22 - 23 - it('ignores active class when href not matches location.href', () => { 24 - render( 25 - <ActiveLocalizedLink 26 - className="link" 27 - activeClassName="active" 28 - href="/not-link" 29 - > 30 - Link 31 - </ActiveLocalizedLink> 32 - ); 33 - 34 - expect(screen.findByText('Link')).resolves.toHaveAttribute('class', 'link'); 35 - }); 36 - 37 - it('sets active class when href matches location.href', () => { 38 - render( 39 - <ActiveLocalizedLink 40 - className="link" 41 - activeClassName="active" 42 - href="/link" 43 - > 44 - Link 45 - </ActiveLocalizedLink> 46 - ); 47 - 48 - expect(screen.findByText('Link')).resolves.toHaveAttribute( 49 - 'class', 50 - 'link active' 51 - ); 52 - }); 53 - });
+9 -7
components/Common/ActiveLocalizedLink/index.tsx components/Common/ActiveLink/index.tsx
··· 7 7 import { usePathname } from '@/navigation.mjs'; 8 8 9 9 type ActiveLocalizedLinkProps = ComponentProps<typeof Link> & { 10 - activeClassName: string; 10 + activeClassName?: string; 11 + allowSubPath?: boolean; 11 12 }; 12 13 13 - const ActiveLocalizedLink: FC<ActiveLocalizedLinkProps> = ({ 14 + const ActiveLink: FC<ActiveLocalizedLinkProps> = ({ 14 15 children, 15 - activeClassName, 16 + activeClassName = 'active', 17 + allowSubPath = false, 16 18 className, 17 19 href = '', 18 20 ...props 19 21 }) => { 20 22 const pathname = usePathname(); 21 23 22 - const linkURL = new URL(href.toString(), location.href); 23 - 24 24 const finalClassName = classNames(className, { 25 - [activeClassName]: linkURL.pathname === pathname, 25 + [activeClassName]: allowSubPath 26 + ? pathname.startsWith(href.toString()) 27 + : href.toString() === pathname, 26 28 }); 27 29 28 30 return ( ··· 32 34 ); 33 35 }; 34 36 35 - export default ActiveLocalizedLink; 37 + export default ActiveLink;
+4 -4
components/Containers/NavItem/index.tsx
··· 3 3 import type { FC, PropsWithChildren } from 'react'; 4 4 import { useMemo } from 'react'; 5 5 6 - import ActiveLocalizedLink from '@/components/Common/ActiveLocalizedLink'; 6 + import ActiveLink from '@/components/Common/ActiveLink'; 7 7 8 8 import styles from './index.module.css'; 9 9 ··· 22 22 className, 23 23 }) => { 24 24 const showIcon = useMemo( 25 - () => type === 'nav' && !href.toString().startsWith('/'), 25 + () => type === 'nav' && href.toString().startsWith('http'), 26 26 [href, type] 27 27 ); 28 28 29 29 return ( 30 - <ActiveLocalizedLink 30 + <ActiveLink 31 31 href={href} 32 32 className={classNames(styles.navItem, styles[type], className)} 33 33 activeClassName={styles.active} 34 34 > 35 35 <span className={styles.label}>{children}</span> 36 36 {showIcon && <ArrowUpRightIcon className={styles.icon} />} 37 - </ActiveLocalizedLink> 37 + </ActiveLink> 38 38 ); 39 39 }; 40 40
+6 -8
components/Header.tsx
··· 1 1 'use client'; 2 2 3 - import classNames from 'classnames'; 4 3 import Image from 'next/image'; 5 4 import { useTranslations } from 'next-intl'; 6 5 import { useTheme } from 'next-themes'; 7 6 import { useState } from 'react'; 8 7 8 + import ActiveLink from '@/components/Common/ActiveLink'; 9 9 import Link from '@/components/Link'; 10 - import { useIsCurrentPathname, useSiteNavigation } from '@/hooks'; 10 + import { useSiteNavigation } from '@/hooks'; 11 11 import { usePathname } from '@/navigation.mjs'; 12 12 import { BASE_PATH } from '@/next.constants.mjs'; 13 13 import { availableLocales } from '@/next.locales.mjs'; 14 14 15 15 const Header = () => { 16 16 const { navigationItems } = useSiteNavigation(); 17 - const { isCurrentLocaleRoute } = useIsCurrentPathname(); 18 17 const [showLangPicker, setShowLangPicker] = useState(false); 19 18 const { theme, setTheme } = useTheme(); 20 19 21 20 const pathname = usePathname(); 22 21 const t = useTranslations(); 23 22 24 - const getLinkClassName = (href: string) => 25 - classNames({ active: isCurrentLocaleRoute(href, href !== '/') }); 26 - 27 23 const toggleLanguage = t('components.header.buttons.toggleLanguage'); 28 24 const toggleDarkMode = t('components.header.buttons.toggleDarkMode'); 29 25 ··· 43 39 <nav aria-label="primary"> 44 40 <ul className="list-divider-pipe"> 45 41 {navigationItems.map((item, key) => ( 46 - <li key={key} className={getLinkClassName(item.link)}> 47 - <Link href={item.link}>{item.text}</Link> 42 + <li key={key}> 43 + <ActiveLink href={item.link} allowSubPath> 44 + {item.text} 45 + </ActiveLink> 48 46 </li> 49 47 ))} 50 48 </ul>
+7 -14
components/SideNavigation.tsx
··· 1 - 'use client'; 2 - 3 - import classNames from 'classnames'; 4 1 import type { RichTranslationValues } from 'next-intl'; 5 2 import type { FC } from 'react'; 6 3 7 - import Link from '@/components/Link'; 8 - import { useIsCurrentPathname, useSiteNavigation } from '@/hooks'; 4 + import ActiveLink from '@/components/Common/ActiveLink'; 5 + import { useSiteNavigation } from '@/hooks/server'; 9 6 import type { NavigationKeys } from '@/types'; 10 7 11 8 type SideNavigationProps = { ··· 18 15 context, 19 16 }) => { 20 17 const { getSideNavigation } = useSiteNavigation(); 21 - const { isCurrentLocaleRoute } = useIsCurrentPathname(); 22 18 23 19 const sideNavigationItems = getSideNavigation(navigationKey, context); 24 - 25 - const getLinkClasses = (href: string, level: number) => 26 - classNames({ active: isCurrentLocaleRoute(href), level }); 27 20 28 21 return ( 29 22 <nav aria-label="secondary"> 30 23 <ul> 31 24 {sideNavigationItems.map(item => ( 32 - <li key={item.key} className={getLinkClasses(item.link, item.level)}> 33 - <Link href={item.link}>{item.text}</Link> 25 + <li key={item.key}> 26 + <ActiveLink href={item.link}>{item.text}</ActiveLink> 34 27 35 28 {item.items.length > 0 && ( 36 29 <ul> 37 - {item.items.map(({ link, level, text, key }) => ( 38 - <li key={key} className={getLinkClasses(link, level)}> 39 - <Link href={link}>{text}</Link> 30 + {item.items.map(({ link, text, key }) => ( 31 + <li key={key}> 32 + <ActiveLink href={link}>{text}</ActiveLink> 40 33 </li> 41 34 ))} 42 35 </ul>
-1
hooks/react-client/index.ts
··· 1 1 export { default as useCopyToClipboard } from './useCopyToClipboard'; 2 2 export { default as useDetectOS } from './useDetectOS'; 3 - export { default as useIsCurrentPathname } from './useIsCurrentPathname'; 4 3 export { default as useMediaQuery } from './useMediaQuery'; 5 4 export { default as useNotification } from './useNotification'; 6 5 export { default as useClientContext } from './useClientContext';
-14
hooks/react-client/useIsCurrentPathname.ts
··· 1 - 'use client'; 2 - 3 - import useClientContext from '@/hooks/react-client/useClientContext'; 4 - 5 - const useIsCurrentPathname = () => { 6 - const { pathname } = useClientContext(); 7 - 8 - return { 9 - isCurrentLocaleRoute: (route: string, allowSubPath?: boolean) => 10 - allowSubPath ? pathname.startsWith(route) : route === pathname, 11 - }; 12 - }; 13 - 14 - export default useIsCurrentPathname;
-1
hooks/react-server/index.ts
··· 1 1 export { default as useCopyToClipboard } from './useCopyToClipboard'; 2 2 export { default as useDetectOS } from './useDetectOS'; 3 - export { default as useIsCurrentPathname } from './useIsCurrentPathname'; 4 3 export { default as useMediaQuery } from './useMediaQuery'; 5 4 export { default as useNotification } from './useNotification'; 6 5 export { default as useClientContext } from './useClientContext';
-17
hooks/react-server/useIsCurrentPathname.ts
··· 1 - import useClientContext from '@/hooks/react-server/useClientContext'; 2 - 3 - const useIsCurrentPathname = () => { 4 - const { pathname } = useClientContext(); 5 - 6 - return { 7 - isCurrentLocaleRoute: (route: string, allowSubPath?: boolean) => { 8 - const asPathJustPath = pathname.replace(/[#|?].*$/, ''); 9 - 10 - return allowSubPath 11 - ? asPathJustPath.startsWith(route) 12 - : route === asPathJustPath; 13 - }, 14 - }; 15 - }; 16 - 17 - export default useIsCurrentPathname;
-2
layouts/DocsLayout.tsx
··· 1 - 'use client'; 2 - 3 1 import type { RichTranslationValues } from 'next-intl'; 4 2 import type { FC, PropsWithChildren } from 'react'; 5 3
+2 -4
styles/old/index.css
··· 66 66 margin-left: -10px; 67 67 margin-right: -10px; 68 68 padding: 5px 10px; 69 - } 70 69 71 - .active { 72 - > a, 73 - > a:hover { 70 + &.active, 71 + &.active:hover { 74 72 background-color: $active-green; 75 73 color: $white; 76 74 }
+5 -4
styles/old/layout/dark-theme.css
··· 30 30 &:hover { 31 31 color: $white; 32 32 } 33 - } 34 33 35 - .active > a { 36 - background-color: $active-green; 37 - color: $white; 34 + &.active, 35 + &.active:hover { 36 + background-color: $active-green; 37 + color: $white; 38 + } 38 39 } 39 40 } 40 41
+2 -2
styles/old/page-modules/header.css
··· 142 142 143 143 $border-width: 14px; 144 144 145 - &.active::after { 145 + &:has(a.active)::after { 146 146 border: solid transparent; 147 147 border-top-color: $node-gray; 148 148 border-width: $border-width; ··· 156 156 width: 0; 157 157 } 158 158 159 - &.active:first-child::after { 159 + &:has(a.active):first-child::after { 160 160 margin-left: -$border-width; 161 161 } 162 162 }
+4 -4
util/getBitness.ts
··· 9 9 'bitness', 10 10 ]); 11 11 12 - // Apparently in some cases this is not a Promise, then we should ignore 13 - if (entropyValues instanceof Promise) { 14 - return entropyValues.then(ua => ua.bitness); 15 - } 12 + // Apparently in some cases this is not a Promise, we can Promisify it. 13 + return Promise.resolve(entropyValues) 14 + .then(({ bitness }) => bitness) 15 + .catch(() => undefined); 16 16 } 17 17 18 18 return undefined;