Repository navigation
chore: more ssr! #9260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
chore: more ssr! #9260
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@node-core/ui-components': minor | ||
| --- | ||
|
|
||
| `AvatarGroup` takes a `compactLimit`, applied below the `xl` breakpoint, and the package now exports a `useMediaQuery` hook. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@node-core/ui-components': minor | ||
| --- | ||
|
|
||
| `Breadcrumbs` takes a `mobileMaxLength`, truncating further below the `sm` breakpoint through CSS. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@node-core/ui-components': minor | ||
| --- | ||
|
|
||
| `LanguageDropDown` renders a link per language, through `href` and an `as` component that receives the `locale`, instead of calling `onChange`, which is removed. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@node-core/ui-components': patch | ||
| --- | ||
|
|
||
| `NavBar` keeps no state, so it can be rendered from a React Server Component. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@node-core/ui-components': minor | ||
| --- | ||
|
|
||
| `Sidebar` collapses its navigation behind a native disclosure on mobile instead of duplicating every page into a `Select`. The `onSelect` prop is removed and `ref` is optional. |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| 'use client'; | ||
|
|
||
| import AvatarGroupContainer from '@node-core/ui-components/Common/AvatarGroup'; | ||
|
|
||
| import Link from '#site/components/Link'; | ||
|
|
||
| import type { ComponentProps, FC } from 'react'; | ||
|
|
||
| type AvatarsProps = Omit<ComponentProps<typeof AvatarGroupContainer>, 'as'>; | ||
|
|
||
| const Avatars: FC<AvatarsProps> = props => ( | ||
| <AvatarGroupContainer as={Link} {...props} /> | ||
| ); | ||
|
|
||
| export default Avatars; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { getAuthors } from '#site/util/author'; | ||
|
|
||
| import type { AuthorProps } from '#site/types'; | ||
| import type { ComponentProps, FC } from 'react'; | ||
|
|
||
| import Avatars from './Avatars'; | ||
|
|
||
| type AvatarGroupProps = Omit<ComponentProps<typeof Avatars>, 'avatars'> & | ||
| AuthorProps; | ||
|
|
||
| // Resolves the authors on the server, so `authors.json` stays there | ||
| const AvatarGroup: FC<AvatarGroupProps> = ({ | ||
| usernames, | ||
| names, | ||
| clickable = true, | ||
| ...props | ||
| }) => ( | ||
| <Avatars avatars={getAuthors({ usernames, names, clickable })} {...props} /> | ||
| ); | ||
|
|
||
| export default AvatarGroup; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,72 +1,68 @@ | ||
| 'use client'; | ||
|
|
||
| import BreadcrumbsContainer from '@node-core/ui-components/Common/Breadcrumbs'; | ||
| import { useTranslations } from 'next-intl'; | ||
| import { getTranslations } from 'next-intl/server'; | ||
|
|
||
| import { getClientContext } from '#site/client-context'; | ||
| import Link from '#site/components/Link'; | ||
| import useClientContext from '#site/hooks/useClientContext'; | ||
| import useMediaQuery from '#site/hooks/useMediaQuery'; | ||
| import useSiteNavigation from '#site/hooks/useSiteNavigation'; | ||
| import { getSideNavigation } from '#site/util/navigation'; | ||
| import { dashToCamelCase } from '#site/util/string'; | ||
|
|
||
| import type { NavigationKeys } from '#site/types'; | ||
| import type { NavigationItem } from '#site/util/navigation'; | ||
| import type { BreadcrumbLink } from '@node-core/ui-components/Common/Breadcrumbs'; | ||
| import type { FC } from 'react'; | ||
|
|
||
| type BreadcrumbsProps = { | ||
| navKeys?: Array<NavigationKeys>; | ||
| }; | ||
|
|
||
| const Breadcrumbs: FC<BreadcrumbsProps> = ({ navKeys = [] }) => { | ||
| const { getSideNavigation } = useSiteNavigation(); | ||
| const t = useTranslations(); | ||
| const { pathname } = useClientContext(); | ||
| const isMobileScreen = useMediaQuery('(max-width: 639px)'); | ||
|
|
||
| const maxLength = isMobileScreen ? 2 : 4; | ||
|
|
||
| const getBreadcrumbs = () => { | ||
| const navigationTree = getSideNavigation(navKeys); | ||
|
|
||
| const pathList = pathname | ||
| .split('/') | ||
| .filter(item => item !== '') | ||
| .map(dashToCamelCase); | ||
|
|
||
| let currentNode = navigationTree; | ||
|
|
||
| // Reduce the pathList to a breadcrumbs array by finding each path in the current navigation layer, | ||
| // updating the currentNode to the found node's items(next layer) for the next iteration. | ||
| return pathList.reduce((breadcrumbs, path, index) => { | ||
| const nodeWithCurrentPath = currentNode.find( | ||
| ([nodePath, entry]) => | ||
| // Checking link in cases where nodePath cannot = path. Like 'discoverJavaScriptTimers' | ||
| (nodePath === path || entry.link === pathname) && | ||
| // Skip checking child path if it is the last path since there is no more child item inside | ||
| (index === pathList.length - 1 || | ||
| entry.items.some( | ||
| ([childPath, entry]) => | ||
| childPath === pathList[index + 1] || entry.link === pathname | ||
| )) | ||
| ); | ||
|
|
||
| if (nodeWithCurrentPath) { | ||
| const [, { label, link = '', items = [] }] = nodeWithCurrentPath; | ||
|
|
||
| // Goes deeper on the tree of items if there are any. | ||
| currentNode = items; | ||
|
|
||
| return label ? [...breadcrumbs, { label, href: link }] : breadcrumbs; | ||
| } | ||
| // Reduce the path to a breadcrumbs array by finding each segment in the current navigation layer, | ||
| // descending into the found node's items (next layer) for the next segment. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: move this fn to an utility file, keeps the component just with the component. Allows you to unit test getBreadcumbs |
||
| const getBreadcrumbs = ( | ||
| navigationTree: Array<NavigationItem>, | ||
| pathname: string | ||
| ) => { | ||
| const pathList = pathname | ||
| .split('/') | ||
| .filter(item => item !== '') | ||
| .map(dashToCamelCase); | ||
|
|
||
| let currentNode = navigationTree; | ||
|
|
||
| return pathList.reduce((breadcrumbs, path, index) => { | ||
| const nodeWithCurrentPath = currentNode.find( | ||
| entry => | ||
| // Checking link in cases where nodePath cannot = path. Like 'discoverJavaScriptTimers' | ||
| (entry.key === path || entry.link === pathname) && | ||
| // Skip checking child path if it is the last path since there is no more child item inside | ||
| (index === pathList.length - 1 || | ||
| entry.items.some( | ||
| child => | ||
| child.key === pathList[index + 1] || child.link === pathname | ||
| )) | ||
| ); | ||
|
|
||
| if (nodeWithCurrentPath) { | ||
| const { label, link, items } = nodeWithCurrentPath; | ||
|
|
||
| // Goes deeper on the tree of items if there are any. | ||
| currentNode = items; | ||
|
|
||
| return label ? [...breadcrumbs, { label, href: link }] : breadcrumbs; | ||
| } | ||
|
|
||
| return breadcrumbs; | ||
| }, [] as Array<BreadcrumbLink>); | ||
| }; | ||
|
|
||
| return breadcrumbs; | ||
| }, [] as Array<BreadcrumbLink>); | ||
| }; | ||
| const Breadcrumbs: FC<BreadcrumbsProps> = async ({ navKeys = [] }) => { | ||
| const t = await getTranslations(); | ||
| const { pathname } = getClientContext(); | ||
|
|
||
| return ( | ||
| <BreadcrumbsContainer | ||
| links={getBreadcrumbs()} | ||
| maxLength={maxLength} | ||
| links={getBreadcrumbs(getSideNavigation(t, navKeys), pathname)} | ||
| maxLength={4} | ||
| mobileMaxLength={2} | ||
| as={Link} | ||
| homeLinkAriaLabel={t('components.common.breadcrumbs.navigateToHome')} | ||
| /> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,26 @@ | ||
| import { CloudArrowDownIcon } from '@heroicons/react/24/outline'; | ||
| import classNames from 'classnames'; | ||
|
|
||
| import { getClientContext } from '#site/client-context'; | ||
| import Button from '#site/components/Common/Button'; | ||
| import { getNodeDownloadUrl } from '#site/util/url'; | ||
| import { getUserPlatform } from '#site/util/userAgent'; | ||
|
|
||
| import type { NodeRelease } from '#site/types'; | ||
| import type { DownloadOptions } from '#site/util/url'; | ||
| import type { FC, PropsWithChildren } from 'react'; | ||
|
|
||
| import styles from './index.module.css'; | ||
|
|
||
| type DownloadButtonProps = { release: NodeRelease }; | ||
| type DownloadButtonProps = { release: NodeRelease } & Pick< | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this better? Those functions are already SSR. |
||
| DownloadOptions, | ||
| 'os' | 'platform' | ||
| >; | ||
|
|
||
| const DownloadButton: FC<PropsWithChildren<DownloadButtonProps>> = ({ | ||
| release: { versionWithPrefix }, | ||
| os, | ||
| platform, | ||
| children, | ||
| }) => { | ||
| const { os, bitness, architecture } = getClientContext(); | ||
|
|
||
| const platform = getUserPlatform(architecture, bitness); | ||
| const downloadLink = getNodeDownloadUrl({ versionWithPrefix, os, platform }); | ||
|
|
||
| return ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hoiw is this related?