隨時隨地執行 JavaScript
diff --git a/apps/site/providers/releaseModalProvider.tsx b/apps/site/providers/releaseModalProvider.tsx
new file mode 100644
index 0000000000000..acbcb17cc8e5c
--- /dev/null
+++ b/apps/site/providers/releaseModalProvider.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { createContext, useState } from 'react';
+import type { FC, PropsWithChildren } from 'react';
+
+import ReleaseModal from '@/components/Downloads/ReleaseModal';
+import type { NodeRelease } from '@/types';
+
+type ReleaseModalContextType = {
+ activeRelease: NodeRelease | null;
+ openModal: (activeRelease: NodeRelease) => void;
+ closeModal: () => void;
+};
+
+export const ReleaseModalContext = createContext
({
+ activeRelease: null,
+ openModal: () => {},
+ closeModal: () => {},
+});
+
+export const ReleaseModalProvider: FC = ({ children }) => {
+ const [activeRelease, setValue] = useState(null);
+
+ const openModal = (activeRelease: NodeRelease) => {
+ setValue(activeRelease);
+ };
+
+ const closeModal = () => {
+ setValue(null);
+ };
+
+ return (
+
+ {children}
+
+ {activeRelease && (
+
+ )}
+
+ );
+};
diff --git a/apps/site/types/releases.ts b/apps/site/types/releases.ts
index 2fc4a4f101d54..cf71f7bea406d 100644
--- a/apps/site/types/releases.ts
+++ b/apps/site/types/releases.ts
@@ -1,4 +1,9 @@
-export type NodeReleaseStatus = 'LTS' | 'Current' | 'End-of-life' | 'Pending';
+export type NodeReleaseStatus =
+ | 'LTS'
+ | 'Maintenance'
+ | 'Current'
+ | 'End-of-life'
+ | 'Pending';
export interface NodeReleaseSource {
major: number;
@@ -9,13 +14,20 @@ export interface NodeReleaseSource {
maintenanceStart?: string;
endOfLife: string;
npm?: string;
- v8?: string;
- releaseDate?: string;
+ v8: string;
+ releaseDate: string;
modules?: string;
}
+export interface MinorVersion {
+ version: string;
+ releaseDate: string;
+}
+
export interface NodeRelease extends NodeReleaseSource {
versionWithPrefix: string;
isLts: boolean;
status: NodeReleaseStatus;
+ releaseAnnounceLink?: string;
+ minorVersions: Array;
}
diff --git a/package-lock.json b/package-lock.json
index f91b24ed5ea71..bf274d0bd3a97 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4365,6 +4365,85 @@
}
}
},
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.3.tgz",
+ "integrity": "sha512-2omrWKJvxR0U/tkIXezcc1nFMwtLU0+b/rDK40gnzJqTLWQ/TD/D5IYVefp9sC3QWfeQbpSbEA6op9MQKyaALQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.3.tgz",
+ "integrity": "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
@@ -21241,6 +21320,7 @@
"@radix-ui/react-label": "~2.1.2",
"@radix-ui/react-scroll-area": "~1.2.3",
"@radix-ui/react-select": "~2.1.6",
+ "@radix-ui/react-separator": "~1.1.3",
"@radix-ui/react-tabs": "~1.1.3",
"@radix-ui/react-toast": "~1.2.6",
"@radix-ui/react-tooltip": "~1.1.8",
diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json
index ab38e13762342..7a0f6b1620cc7 100644
--- a/packages/i18n/locales/en.json
+++ b/packages/i18n/locales/en.json
@@ -148,15 +148,38 @@
"downloadReleasesTable": {
"version": "Node.js",
"nApiVersion": "N-API",
- "npmVersion": "npm",
"codename": "Codename",
"releaseDate": "Released at",
+ "firstReleased": "First released",
+ "lastUpdated": "Last updated",
+ "status": "Status",
+ "details": "Details"
+ },
+ "releaseModal": {
+ "title": "Node.js {version} ({codename})",
+ "titleWithoutCodename": "Node.js {version}",
+ "overview": "Overview",
+ "minorVersions": "Minor versions",
+ "releaseAnnouncement": "Release Announcement",
+ "unsupportedVersionWarning": "This version is out of maintenance. Please use a currently supported version."
+ },
+ "minorReleasesTable": {
+ "version": "Version",
+ "links": "Links",
"actions": {
+ "release": "Release",
"changelog": "Changelog",
- "releases": "Releases",
"docs": "Docs"
}
},
+ "releaseOverview": {
+ "firstReleased": "First released",
+ "lastUpdated": "Last updated",
+ "minorVersions": "Minor versions",
+ "nApiVersion": "N-API version",
+ "npmVersion": "npm version",
+ "v8Version": "V8 version"
+ },
"pagination": {
"next": "Next",
"previous": "Previous"
diff --git a/packages/ui-components/Common/Badge/index.module.css b/packages/ui-components/Common/Badge/index.module.css
index 9420740a87cf1..19bdba865743c 100644
--- a/packages/ui-components/Common/Badge/index.module.css
+++ b/packages/ui-components/Common/Badge/index.module.css
@@ -1,101 +1,38 @@
@reference "../../styles/index.css";
-.wrapper {
- @apply flex
- w-fit
- items-center
- rounded-full
+.badge {
+ @apply rounded-full
border
- py-1
- pl-1
- pr-2.5
- text-sm
- font-medium;
+ text-center
+ text-white;
- .icon {
- @apply size-4;
- }
-
- .badge {
- @apply mr-2
- rounded-full
- border
- px-2.5
+ &.small {
+ @apply px-1.5
py-0.5
- text-center
- text-white;
+ text-xs;
}
- .message {
- @apply mr-1;
+ &.medium {
+ @apply px-2.5
+ py-0.5
+ text-base;
}
&.default {
@apply border-green-200
- bg-green-100
- dark:border-green-700
- dark:bg-neutral-900;
-
- .icon {
- @apply text-green-500
- dark:text-green-300;
- }
-
- .badge {
- @apply border-green-200
- bg-green-600
- dark:border-green-600;
- }
-
- .message {
- @apply text-green-700
- dark:text-green-300;
- }
+ bg-green-600
+ dark:border-green-800;
}
&.error {
@apply border-danger-200
- bg-danger-100
- dark:border-danger-700
- dark:bg-neutral-900;
-
- .icon {
- @apply text-danger-500
- dark:text-danger-300;
- }
-
- .badge {
- @apply border-danger-200
- bg-danger-600
- dark:border-danger-600;
- }
-
- .message {
- @apply text-danger-700
- dark:text-danger-300;
- }
+ bg-danger-600
+ dark:border-danger-800;
}
&.warning {
@apply border-warning-200
- bg-warning-100
- dark:border-warning-700
- dark:bg-neutral-900;
-
- .icon {
- @apply text-warning-500
- dark:text-warning-300;
- }
-
- .badge {
- @apply border-warning-200
- bg-warning-600
- dark:border-warning-600;
- }
-
- .message {
- @apply text-warning-700
- dark:text-warning-300;
- }
+ bg-warning-600
+ dark:border-warning-600;
}
}
diff --git a/packages/ui-components/Common/Badge/index.stories.tsx b/packages/ui-components/Common/Badge/index.stories.tsx
index b37dfa5b0a621..874ec97c26fd9 100644
--- a/packages/ui-components/Common/Badge/index.stories.tsx
+++ b/packages/ui-components/Common/Badge/index.stories.tsx
@@ -7,29 +7,32 @@ type Meta = MetaObj;
export const Default: Story = {
args: {
- href: '/',
- children: 'OpenJS Foundation Certification 2023',
kind: 'default',
- badgeText: 'New',
},
};
export const Error: Story = {
args: {
- href: '/',
- children: 'OpenJS Foundation Certification 2023',
kind: 'error',
- badgeText: 'New',
},
};
export const Warning: Story = {
args: {
- href: '/',
- children: 'OpenJS Foundation Certification 2023',
kind: 'warning',
- badgeText: 'New',
},
};
-export default { component: Badge } as Meta;
+export const Small: Story = {
+ args: {
+ size: 'small',
+ },
+};
+
+export const Medium: Story = {
+ args: {
+ size: 'medium',
+ },
+};
+
+export default { component: Badge, args: { children: 'Badge' } } as Meta;
diff --git a/packages/ui-components/Common/Badge/index.tsx b/packages/ui-components/Common/Badge/index.tsx
index 49590ebefe08e..5b9730368e35d 100644
--- a/packages/ui-components/Common/Badge/index.tsx
+++ b/packages/ui-components/Common/Badge/index.tsx
@@ -1,28 +1,35 @@
-import ArrowRightIcon from '@heroicons/react/24/solid/ArrowRightIcon';
-import type { ComponentProps, FC, PropsWithChildren } from 'react';
-
-import type { LinkLike } from '@node-core/ui-components/types';
+import classNames from 'classnames';
+import type { FC, HTMLAttributes, PropsWithChildren } from 'react';
import styles from './index.module.css';
-type BadgeProps = {
- kind?: 'default' | 'warning' | 'error';
- badgeText?: string;
- as: LinkLike;
-} & ComponentProps;
+type BadgeKind = 'default' | 'warning' | 'error';
+
+type BadgeProps = HTMLAttributes & {
+ size?: 'small' | 'medium';
+ kind?: BadgeKind;
+};
const Badge: FC> = ({
kind = 'default',
- badgeText,
+ size = 'medium',
+ className,
children,
- as: Component = 'a',
- ...args
-}) => (
-
- {badgeText && {badgeText}}
- {children}
- {args.href && }
-
-);
+ ...props
+}) => {
+ return (
+
+ {children}
+
+ );
+};
export default Badge;
diff --git a/packages/ui-components/Common/BadgeGroup/index.module.css b/packages/ui-components/Common/BadgeGroup/index.module.css
new file mode 100644
index 0000000000000..754356aa6012c
--- /dev/null
+++ b/packages/ui-components/Common/BadgeGroup/index.module.css
@@ -0,0 +1,77 @@
+@reference "../../styles/index.css";
+
+.wrapper {
+ @apply flex
+ w-fit
+ items-center
+ rounded-full
+ border
+ py-1
+ pl-1
+ pr-2.5
+ text-sm
+ font-medium;
+
+ .icon {
+ @apply size-4;
+ }
+
+ .badge {
+ @apply mr-2;
+ }
+
+ .message {
+ @apply mr-1;
+ }
+
+ &.default {
+ @apply border-green-200
+ bg-green-100
+ dark:border-green-700
+ dark:bg-neutral-900;
+
+ .icon {
+ @apply text-green-500
+ dark:text-green-300;
+ }
+
+ .message {
+ @apply text-green-700
+ dark:text-green-300;
+ }
+ }
+
+ &.error {
+ @apply border-danger-200
+ bg-danger-100
+ dark:border-danger-700
+ dark:bg-neutral-900;
+
+ .icon {
+ @apply text-danger-500
+ dark:text-danger-300;
+ }
+
+ .message {
+ @apply text-danger-700
+ dark:text-danger-300;
+ }
+ }
+
+ &.warning {
+ @apply border-warning-200
+ bg-warning-100
+ dark:border-warning-700
+ dark:bg-neutral-900;
+
+ .icon {
+ @apply text-warning-500
+ dark:text-warning-300;
+ }
+
+ .message {
+ @apply text-warning-700
+ dark:text-warning-300;
+ }
+ }
+}
diff --git a/packages/ui-components/Common/BadgeGroup/index.stories.tsx b/packages/ui-components/Common/BadgeGroup/index.stories.tsx
new file mode 100644
index 0000000000000..ac2fe623dfdb5
--- /dev/null
+++ b/packages/ui-components/Common/BadgeGroup/index.stories.tsx
@@ -0,0 +1,35 @@
+import type { Meta as MetaObj, StoryObj } from '@storybook/react';
+
+import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup';
+
+type Story = StoryObj;
+type Meta = MetaObj;
+
+export const Default: Story = {
+ args: {
+ href: '/',
+ children: 'OpenJS Foundation Certification 2023',
+ kind: 'default',
+ badgeText: 'New',
+ },
+};
+
+export const Error: Story = {
+ args: {
+ href: '/',
+ children: 'OpenJS Foundation Certification 2023',
+ kind: 'error',
+ badgeText: 'New',
+ },
+};
+
+export const Warning: Story = {
+ args: {
+ href: '/',
+ children: 'OpenJS Foundation Certification 2023',
+ kind: 'warning',
+ badgeText: 'New',
+ },
+};
+
+export default { component: BadgeGroup } as Meta;
diff --git a/packages/ui-components/Common/BadgeGroup/index.tsx b/packages/ui-components/Common/BadgeGroup/index.tsx
new file mode 100644
index 0000000000000..761ca9e3aaec6
--- /dev/null
+++ b/packages/ui-components/Common/BadgeGroup/index.tsx
@@ -0,0 +1,35 @@
+import ArrowRightIcon from '@heroicons/react/24/solid/ArrowRightIcon';
+import type { ComponentProps, FC, PropsWithChildren } from 'react';
+
+import Badge from '@node-core/ui-components/Common/Badge';
+import type { LinkLike } from '@node-core/ui-components/types';
+
+import styles from './index.module.css';
+
+type BadgeGroupKind = 'default' | 'warning' | 'error';
+
+type BadgeGroupProps = {
+ kind?: BadgeGroupKind;
+ badgeText?: string;
+ as: LinkLike;
+} & ComponentProps;
+
+const BadgeGroup: FC> = ({
+ kind = 'default',
+ badgeText,
+ children,
+ as: Component = 'a',
+ ...args
+}) => (
+
+ {badgeText && (
+
+ {badgeText}
+
+ )}
+ {children}
+ {args.href && }
+
+);
+
+export default BadgeGroup;
diff --git a/packages/ui-components/Common/Modal/index.module.css b/packages/ui-components/Common/Modal/index.module.css
index 1495d7e71bf46..5b399767fdf7e 100644
--- a/packages/ui-components/Common/Modal/index.module.css
+++ b/packages/ui-components/Common/Modal/index.module.css
@@ -15,6 +15,7 @@
m-4
inline-flex
w-full
+ max-w-3xl
flex-col
overflow-y-auto
rounded
@@ -24,7 +25,6 @@
p-8
focus:outline-none
sm:my-20
- lg:max-w-[900px]
xl:p-12
dark:bg-neutral-950;
}
diff --git a/packages/ui-components/Common/Modal/index.tsx b/packages/ui-components/Common/Modal/index.tsx
index 635ab82a54cfa..787a80e0cb73a 100644
--- a/packages/ui-components/Common/Modal/index.tsx
+++ b/packages/ui-components/Common/Modal/index.tsx
@@ -8,7 +8,7 @@ import styles from './index.module.css';
type ModalProps = PropsWithChildren<{
heading: string;
- subheading: string;
+ subheading?: string;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}>;
@@ -30,9 +30,11 @@ const Modal: FC = ({
{heading}
-
- {subheading}
-
+ {subheading && (
+
+ {subheading}
+
+ )}
{children}
diff --git a/packages/ui-components/Common/Separator/index.module.css b/packages/ui-components/Common/Separator/index.module.css
new file mode 100644
index 0000000000000..61d7dc140faa0
--- /dev/null
+++ b/packages/ui-components/Common/Separator/index.module.css
@@ -0,0 +1,16 @@
+@reference "../../styles/index.css";
+
+.root {
+ @apply shrink-0
+ bg-neutral-800;
+
+ &.horizontal {
+ @apply h-px
+ w-full;
+ }
+
+ &.vertical {
+ @apply h-full
+ w-px;
+ }
+}
diff --git a/packages/ui-components/Common/Separator/index.stories.tsx b/packages/ui-components/Common/Separator/index.stories.tsx
new file mode 100644
index 0000000000000..4fe7337c8b58c
--- /dev/null
+++ b/packages/ui-components/Common/Separator/index.stories.tsx
@@ -0,0 +1,32 @@
+import type { Meta as MetaObj, StoryObj } from '@storybook/react';
+
+import Separator from '@node-core/ui-components/Common/Separator';
+
+type Story = StoryObj;
+type Meta = MetaObj;
+
+export const Horizontal: Story = {
+ args: {
+ orientation: 'horizontal',
+ },
+};
+
+export const Vertical: Story = {
+ args: {
+ orientation: 'vertical',
+ },
+};
+
+export default {
+ component: Separator,
+ parameters: {
+ layout: 'centered',
+ },
+ decorators: [
+ Story => (
+
+
+
+ ),
+ ],
+} as Meta;
diff --git a/packages/ui-components/Common/Separator/index.tsx b/packages/ui-components/Common/Separator/index.tsx
new file mode 100644
index 0000000000000..2a51b8bd41adc
--- /dev/null
+++ b/packages/ui-components/Common/Separator/index.tsx
@@ -0,0 +1,27 @@
+'use client';
+
+import * as SeparatorPrimitive from '@radix-ui/react-separator';
+import classNames from 'classnames';
+import type { FC, ComponentProps } from 'react';
+
+import styles from './index.module.css';
+
+const Separator: FC> = ({
+ className,
+ orientation = 'horizontal',
+ decorative = true,
+ ...props
+}) => (
+
+);
+
+export default Separator;
diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json
index c8631c59943e3..9610a025a7e9a 100644
--- a/packages/ui-components/package.json
+++ b/packages/ui-components/package.json
@@ -23,6 +23,7 @@
"@radix-ui/react-label": "~2.1.2",
"@radix-ui/react-scroll-area": "~1.2.3",
"@radix-ui/react-select": "~2.1.6",
+ "@radix-ui/react-separator": "~1.1.3",
"@radix-ui/react-tabs": "~1.1.3",
"@radix-ui/react-toast": "~1.2.6",
"@radix-ui/react-tooltip": "~1.1.8",