Skip to content

feat: redesign the previous releases page #7630

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

Merged
merged 36 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7e18186
feat(releaseData): add minorVersions prop
araujogui Apr 8, 2025
7690b45
feat: create draft minor releases table modal
araujogui Apr 15, 2025
5d74cf7
refactor: initials improvements
araujogui Apr 15, 2025
7a0bb61
refactor: mv releasemodal component to correct dir
araujogui Apr 15, 2025
3a884c9
fix: release modal translation keys
araujogui Apr 15, 2025
eaad760
feat: align button group
araujogui Apr 15, 2025
e48060b
chore: rename badge component to badgegroup
araujogui Apr 16, 2025
26179cd
refactor(BadgeGroup): use badge component
araujogui Apr 16, 2025
e57f065
feat(DownloadReleasesTable): add status badge
araujogui Apr 17, 2025
5fd0565
feat(Modal): make subheader optional
araujogui Apr 17, 2025
0617dff
refactor: create kind types
araujogui Apr 17, 2025
15772af
feat(ui-components): create separator component
araujogui Apr 17, 2025
5822520
feat(site): some improvements
araujogui Apr 17, 2025
c801322
refactor: nit
araujogui Apr 17, 2025
81b57b4
feat(site): add eol alerts
araujogui Apr 17, 2025
7f6fe44
fix: remove incorrect anchor href
araujogui Apr 17, 2025
704dba8
feat(ui-components): add smaller badge size
araujogui Apr 17, 2025
741536f
feat(site): add maintenance release status
araujogui Apr 17, 2025
50881df
feat(site): make codename prominent
araujogui Apr 17, 2025
e6955a1
chore: fix vertical separator story
araujogui Apr 17, 2025
0851074
refactor: some minor changes
araujogui Apr 17, 2025
14edfae
fix: badgegroup references
araujogui Apr 17, 2025
dd3f334
refactor: small nit
araujogui Apr 17, 2025
fc55af1
feat: add minor versions release date
araujogui Apr 24, 2025
88dd748
feat: smaller minor releases table
araujogui Apr 24, 2025
fe6c2e7
fix: oops wrong req
araujogui Apr 24, 2025
04ae3ea
feat: create release overview component
araujogui Apr 24, 2025
761ddd6
fix: minor release links
araujogui Apr 24, 2025
76665a5
feat(ui-components): add badge border on dark mode
araujogui Apr 24, 2025
c71369e
chore: fix stylelint
araujogui Apr 24, 2025
ed0092f
chore: fix dep range
araujogui Apr 24, 2025
e8c2872
feat: format datetimes
araujogui Apr 24, 2025
4927366
refactor: small nits
araujogui Apr 25, 2025
f550d22
fix: release announce links
araujogui Apr 25, 2025
737bfa8
refactor: remove comment
araujogui Apr 25, 2025
9a17e53
feat: use npm icon
araujogui Apr 28, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client';

import { useTranslations } from 'next-intl';
import type { FC } from 'react';
import { use } from 'react';

import LinkWithArrow from '@/components/LinkWithArrow';
import { ReleaseModalContext } from '@/providers/releaseModalProvider';
import type { NodeRelease } from '@/types';

type DetailsButtonProps = {
versionData: NodeRelease;
};

const DetailsButton: FC<DetailsButtonProps> = ({ versionData }) => {
const t = useTranslations('components.downloadReleasesTable');

const { openModal } = use(ReleaseModalContext);

return (
<LinkWithArrow
className="cursor-pointer"
onClick={() => openModal(versionData)}
>
{t('details')}
</LinkWithArrow>
);
};

export default DetailsButton;
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import Badge from '@node-core/ui-components/Common/Badge';
import { getTranslations } from 'next-intl/server';
import type { FC } from 'react';

import LinkWithArrow from '@/components/LinkWithArrow';
import DetailsButton from '@/components/Downloads/DownloadReleasesTable/DetailsButton';
import getReleaseData from '@/next-data/releaseData';
import { BASE_CHANGELOG_URL } from '@/next.constants.mjs';
import { getNodeApiLink } from '@/util/getNodeApiLink';

// This is a React Async Server Component
// Note that Hooks cannot be used in a RSC async component
Expand All @@ -19,37 +18,34 @@ const DownloadReleasesTable: FC = async () => {
<thead>
<tr>
<th>{t('components.downloadReleasesTable.version')}</th>
<th>{t('components.downloadReleasesTable.nApiVersion')}</th>
<th>{t('components.downloadReleasesTable.codename')}</th>
<th>{t('components.downloadReleasesTable.releaseDate')}</th>
<th>{t('components.downloadReleasesTable.npmVersion')}</th>
<th>{t('components.downloadReleasesTable.firstReleased')}</th>
<th>{t('components.downloadReleasesTable.lastUpdated')}</th>
<th>{t('components.downloadReleasesTable.status')}</th>
<th></th>
</tr>
</thead>
<tbody>
{releaseData.map(release => (
<tr key={release.major}>
<td data-label="Version">v{release.version}</td>
<td data-label="Modules">v{release.modules}</td>
<td data-label="Version">v{release.major}</td>
<td data-label="LTS">{release.codename || '-'}</td>
<td data-label="Date">
<time>{release.currentStart}</time>
</td>
<td data-label="Date">
<time>{release.releaseDate}</time>
</td>
<td data-label="npm">v{release.npm}</td>
<td className="download-table-last">
<LinkWithArrow
href={`https://nodejs.org/download/release/${release.versionWithPrefix}/`}
<td data-label="Status">
<Badge
kind={release.status === 'End-of-life' ? 'warning' : 'default'}
size="small"
>
{t('components.downloadReleasesTable.actions.releases')}
</LinkWithArrow>

<LinkWithArrow href={`${BASE_CHANGELOG_URL}${release.version}`}>
{t('components.downloadReleasesTable.actions.changelog')}
</LinkWithArrow>

<LinkWithArrow href={getNodeApiLink(release.versionWithPrefix)}>
{t('components.downloadReleasesTable.actions.docs')}
</LinkWithArrow>
{release.status}
</Badge>
</td>
<td className="download-table-last">
<DetailsButton versionData={release} />
</td>
</tr>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@reference "../../../styles/index.css";

.links {
@apply flex
h-4
items-center
gap-2;
}
64 changes: 64 additions & 0 deletions apps/site/components/Downloads/MinorReleasesTable/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import Separator from '@node-core/ui-components/Common/Separator';
import { useTranslations } from 'next-intl';
import type { FC } from 'react';

import Link from '@/components/Link';
import { BASE_CHANGELOG_URL } from '@/next.constants.mjs';
import type { MinorVersion } from '@/types';
import { getNodeApiLink } from '@/util/getNodeApiLink';

import styles from './index.module.css';

type MinorReleasesTableProps = {
releases: Array<MinorVersion>;
};

export const MinorReleasesTable: FC<MinorReleasesTableProps> = ({
releases,
}) => {
const t = useTranslations('components.minorReleasesTable');

return (
<table>
<thead>
<tr>
<th>{t('version')}</th>
<th>{t('links')}</th>
</tr>
</thead>
<tbody>
{releases.map(release => (
<tr key={release.version}>
<td>v{release.version}</td>
<td>
<div className={styles.links}>
<Link
kind="neutral"
href={`https://nodejs.org/download/release/v${release.version}/`}
>
{t('actions.release')}
</Link>
<Separator orientation="vertical" />
<Link
kind="neutral"
href={`${BASE_CHANGELOG_URL}${release.version}`}
>
{t('actions.changelog')}
</Link>
<Separator orientation="vertical" />
<Link
kind="neutral"
href={getNodeApiLink(`v${release.version}`)}
>
{t('actions.docs')}
</Link>
</div>
</td>
</tr>
))}
</tbody>
</table>
);
};
65 changes: 65 additions & 0 deletions apps/site/components/Downloads/ReleaseModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import AlertBox from '@node-core/ui-components/Common/AlertBox';
import Modal from '@node-core/ui-components/Common/Modal';
import { useTranslations } from 'next-intl';
import type { FC } from 'react';

import { MinorReleasesTable } from '@/components/Downloads/MinorReleasesTable';
import { ReleaseOverview } from '@/components/Downloads/ReleaseOverview';
import LinkWithArrow from '@/components/LinkWithArrow';
import type { NodeRelease } from '@/types';
import { getReleaseAnnounceLink } from '@/util/getReleaseAnnounceLink';

type ReleaseModalProps = {
isOpen: boolean;
closeModal: () => void;
release: NodeRelease;
};

const ReleaseModal: FC<ReleaseModalProps> = ({
isOpen,
closeModal,
release,
}) => {
const t = useTranslations();

const modalHeadingKey = release.codename
? 'components.releaseModal.title'
: 'components.releaseModal.titleWithoutCodename';

const modalHeading = t(modalHeadingKey, {
version: release.major,
codename: release.codename ?? '',
});

const releaseAnnounceLink = getReleaseAnnounceLink(release);

return (
<Modal open={isOpen} onOpenChange={closeModal} heading={modalHeading}>
{release.status === 'End-of-life' && (
<AlertBox
title={t('components.common.alertBox.warning')}
level="warning"
size="small"
>
{t('components.releaseModal.unsupportedVersionWarning')}
</AlertBox>
)}

{releaseAnnounceLink && (
<LinkWithArrow href={releaseAnnounceLink}>
{t('components.releaseModal.releaseAnnouncement')}
</LinkWithArrow>
)}

<h5>{t('components.releaseModal.overview')}</h5>

<ReleaseOverview release={release} />

<h5>{t('components.releaseModal.minorVersions')}</h5>

<MinorReleasesTable releases={release.minorVersions} />
</Modal>
);
};

export default ReleaseModal;
39 changes: 39 additions & 0 deletions apps/site/components/Downloads/ReleaseOverview/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@reference "../../../styles/index.css";

.root {
@apply rounded
border
border-neutral-200
p-4
text-neutral-900
dark:border-neutral-800
dark:text-white;

.container {
@apply grid
grid-cols-2
gap-4
lg:grid-cols-3;
}

.item {
@apply flex
items-center
gap-2;

h1 {
@apply text-base
font-semibold;
}

h2 {
@apply text-xs
font-normal;
}

svg {
@apply h-4
w-4;
}
}
}
83 changes: 83 additions & 0 deletions apps/site/components/Downloads/ReleaseOverview/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
CalendarIcon,
ClockIcon,
CodeBracketSquareIcon,
Square3Stack3DIcon,
} from '@heroicons/react/24/outline';
import { useTranslations } from 'next-intl';
import type { FC, ReactNode } from 'react';

import type { NodeRelease } from '@/types';

import styles from './index.module.css';

type ItemProps = {
icon: React.JSX.Element;
title: ReactNode;
subtitle: ReactNode;
};

const Item: FC<ItemProps> = ({ icon, title, subtitle }) => {
return (
<div className={styles.item}>
{icon}
<div>
<h2>{subtitle}</h2>
<h1>{title}</h1>
</div>
</div>
);
};

type ReleaseOverviewProps = {
release: NodeRelease;
};

export const ReleaseOverview: FC<ReleaseOverviewProps> = ({ release }) => {
const t = useTranslations();

return (
<div className={styles.root}>
<div className={styles.container}>
<Item
icon={<CalendarIcon />}
title={release.currentStart}
subtitle={t('components.releaseOverview.firstReleased')}
/>
{release.releaseDate && (
<Item
icon={<ClockIcon />}
title={release.releaseDate}
subtitle={t('components.releaseOverview.lastUpdated')}
/>
)}
<Item
icon={<Square3Stack3DIcon />}
title={release.minorVersions.length}
subtitle={t('components.releaseOverview.minorVersions')}
/>
{release.modules && (
<Item
icon={<CodeBracketSquareIcon />}
title={`v${release.modules}`}
subtitle={t('components.releaseOverview.nApiVersion')}
/>
)}
{release.npm && (
<Item
icon={<CodeBracketSquareIcon />}
title={`v${release.npm}`}
subtitle={t('components.releaseOverview.npmVersion')}
/>
)}
{release.v8 && (
<Item
icon={<CodeBracketSquareIcon />}
title={`v${release.v8}`}
subtitle={t('components.releaseOverview.v8Version')}
/>
)}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import Badge from '@node-core/ui-components/Common/Badge';
import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup';
import type { FC } from 'react';

import Link from '@/components/Link';
import { siteConfig } from '@/next.json.mjs';
import { dateIsBetween } from '@/util/dateUtils';

const WithBadge: FC<{ section: string }> = ({ section }) => {
const WithBadgeGroup: FC<{ section: string }> = ({ section }) => {
const badge = siteConfig.websiteBadges[section];

if (badge && dateIsBetween(badge.startDate, badge.endDate)) {
return (
<Badge
<BadgeGroup
as={Link}
badgeText={badge.title}
kind={badge.kind}
href={badge.link}
>
{badge.text}
</Badge>
</BadgeGroup>
);
}

return null;
};

export default WithBadge;
export default WithBadgeGroup;
Loading
Loading