Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/avatar-group-compact-limit.md
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.
5 changes: 5 additions & 0 deletions .changeset/breadcrumbs-mobile-length.md
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.
5 changes: 5 additions & 0 deletions .changeset/language-dropdown-links.md
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.
5 changes: 5 additions & 0 deletions .changeset/navbar-stateless.md
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.
5 changes: 5 additions & 0 deletions .changeset/sidebar-mobile-disclosure.md
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.
27 changes: 16 additions & 11 deletions apps/site/app/[locale]/error.tsx

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hoiw is this related?

Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
'use client';

import GlowingBackdrop from '@node-core/ui-components/Common/GlowingBackdrop';
import { useTranslations } from 'next-intl';

import Button from '#site/components/Common/Button';
import GlowingBackdropLayout from '#site/layouts/GlowingBackdrop';
import styles from '#site/layouts/layouts.module.css';

import type { FC } from 'react';

const ErrorPage: FC<{ error: Error }> = () => {
const t = useTranslations();

return (
<GlowingBackdropLayout kind="default">
<span>500</span>
<div className={styles.centeredLayout}>
<GlowingBackdrop />

<h1 className="special -mt-4 text-center">
{t('layouts.error.internalServerError.title')}
</h1>
<main id="main" tabIndex={-1}>
<span>500</span>

<p className="-mt-4 max-w-sm text-center text-lg">
{t('layouts.error.internalServerError.description')}
</p>
<h1 className="special -mt-4 text-center">
{t('layouts.error.internalServerError.title')}
</h1>

<Button href="/">{t('layouts.error.backToHome')}</Button>
</GlowingBackdropLayout>
<p className="-mt-4 max-w-sm text-center text-lg">
{t('layouts.error.internalServerError.description')}
</p>

<Button href="/">{t('layouts.error.backToHome')}</Button>
</main>
</div>
);
};

Expand Down
34 changes: 0 additions & 34 deletions apps/site/components/AvatarGroup.tsx

This file was deleted.

15 changes: 15 additions & 0 deletions apps/site/components/AvatarGroup/Avatars.tsx
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;
21 changes: 21 additions & 0 deletions apps/site/components/AvatarGroup/index.tsx
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;
100 changes: 48 additions & 52 deletions apps/site/components/Breadcrumbs.tsx
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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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')}
/>
Expand Down
11 changes: 8 additions & 3 deletions apps/site/components/DownloadSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getLocale } from 'next-intl/server';
import { getClientContext } from '#site/client-context';
import WithNodeRelease from '#site/components/withNodeRelease';
import provideDownloadSnippets from '#site/next-data/providers/downloadSnippets';
import { DetectedOSProvider } from '#site/providers/detectedOSProvider';
import {
ReleaseProvider,
ReleasesProvider,
Expand Down Expand Up @@ -48,9 +49,13 @@ const DownloadSection: FC<DownloadSectionProps> = async ({
return (
<WithNodeRelease status={initialRelease}>
{({ release }) => (
<ReleasesProvider releases={releases} snippets={memoizedSnippets}>
<ReleaseProvider initialRelease={release}>{children}</ReleaseProvider>
</ReleasesProvider>
<DetectedOSProvider>
<ReleasesProvider releases={releases} snippets={memoizedSnippets}>
<ReleaseProvider initialRelease={release}>
{children}
</ReleaseProvider>
</ReleasesProvider>
</DetectedOSProvider>
)}
</WithNodeRelease>
);
Expand Down
13 changes: 7 additions & 6 deletions apps/site/components/Downloads/DownloadButton/index.tsx
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<

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 (
Expand Down
14 changes: 7 additions & 7 deletions apps/site/components/Downloads/DownloadLink.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { getClientContext } from '#site/client-context';
import LinkWithArrow from '#site/components/Common/LinkWithArrow';
import { getNodeDownloadUrl } from '#site/util/url';
import { getUserPlatform } from '#site/util/userAgent';

import type { DownloadKind, NodeRelease } from '#site/types';
import type { DownloadOptions } from '#site/util/url';
import type { FC, PropsWithChildren } from 'react';

type DownloadLinkProps = { release: NodeRelease; kind?: DownloadKind };
type DownloadLinkProps = { release: NodeRelease; kind?: DownloadKind } & Pick<
DownloadOptions,
'os' | 'platform'
>;

const DownloadLink: FC<PropsWithChildren<DownloadLinkProps>> = ({
release: { versionWithPrefix },
kind = 'installer',
os,
platform,
children,
}) => {
const { os, bitness, architecture } = getClientContext();

const platform = getUserPlatform(architecture, bitness);

const downloadLink = getNodeDownloadUrl({
versionWithPrefix,
os,
Expand Down
9 changes: 7 additions & 2 deletions apps/site/components/Downloads/Release/DownloadLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ const DownloadLink: FC<PropsWithChildren<DownloadLinkProps>> = ({
kind = 'installer',
children,
}) => {
const { release } = use(ReleaseContext);
const { release, os, platform } = use(ReleaseContext);

return (
<DownloadLinkBase release={release} kind={kind}>
<DownloadLinkBase
release={release}
kind={kind}
os={os}
platform={platform || undefined}
>
{children}
</DownloadLinkBase>
);
Expand Down
Loading
Loading