From abe2da4961d5cf3154c9af3248a79e0d7f92eade Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Wed, 25 Dec 2024 02:28:18 +0000 Subject: [PATCH 1/5] feat: introduced new downloads page chore: removed all translated (old) downloads page (forcing re-translation) fix: fix build and extension extraction chore: just update todo text Apply suggestions from code review Co-authored-by: Geoffrey Booth Signed-off-by: Claudio W fix: fixed unit tests chore: unnecessary memoization of reqs chore: some minor code review changes feat: code review and iteration on logic chore: improvements for docker chore: more improvements for texts and sizes chore: minor design updates chore: numerous improvements and fixes chore: some typo fixes feat: bug fixes, improved types, handle unknown scenarios and fallback chore: always use corepack to enable pnpm chore: util will always return string, but `parseNumericBitness` ensures number is set chore: improved types chore: random changed file chore: some code reviews and bug fixes chore: not an external link --- .../blog-data/[category]/[page]/route.ts | 7 +- .../Common/AlertBox/index.module.css | 5 +- .../components/Common/BlogPostCard/index.tsx | 3 +- .../components/Common/Button/index.module.css | 1 + .../components/Common/Select/index.module.css | 10 +- .../Common/Select/index.stories.tsx | 13 +- apps/site/components/Common/Select/index.tsx | 52 ++- .../site/components/Common/Skeleton/index.tsx | 11 +- .../components/Containers/MetaBar/index.tsx | 4 +- .../Downloads/Release/BitnessDropdown.tsx | 116 ++--- .../Downloads/Release/DownloadButton.tsx | 60 ++- .../components/Downloads/Release/NpmLink.tsx | 21 - .../Release/OperatingSystemDropdown.tsx | 73 ++-- .../Release/PackageManagerDropdown.tsx | 53 +++ .../Downloads/Release/PlatformDropdown.tsx | 120 +++--- .../Downloads/Release/ReleaseCodeBox.tsx | 114 +++-- .../Downloads/Release/ReleaseStatus.tsx | 16 - .../Downloads/Release/ReleaseVersion.tsx | 16 - .../Downloads/Release/SourceButton.tsx | 30 -- .../Release/VerifyingBinariesLink.tsx | 11 - .../Downloads/Release/VersionDropdown.tsx | 7 +- .../{Platform => OperatingSystem}/AIX.tsx | 0 .../{Platform => OperatingSystem}/Apple.tsx | 0 .../{Platform => OperatingSystem}/Linux.tsx | 0 .../Microsoft.tsx | 0 .../components/Icons/OperatingSystem/index.ts | 6 + .../components/Icons/PackageManager/index.ts | 5 + .../components/Icons/Platform/Generic.tsx | 22 - apps/site/components/Icons/Platform/index.ts | 7 + apps/site/components/JSX/CodeBox/index.tsx | 3 +- .../__design__/package-manager.stories.tsx | 10 +- .../__design__/platform-logos.stories.tsx | 28 +- apps/site/components/withBlogCrossLinks.tsx | 3 +- .../components/withDownloadCategories.tsx | 79 ---- apps/site/components/withDownloadSection.tsx | 41 ++ apps/site/global.d.ts | 29 ++ .../hooks/react-generic/useSiteNavigation.ts | 2 +- apps/site/layouts/Blog.tsx | 8 +- apps/site/layouts/Download.tsx | 15 +- apps/site/next-data/blogData.ts | 7 +- apps/site/next-data/providers/blogData.ts | 42 +- apps/site/next-env.d.ts | 2 +- apps/site/next.constants.mjs | 10 - apps/site/next.jsx.compiler.mjs | 2 +- apps/site/next.mdx.use.client.mjs | 40 ++ apps/site/next.mdx.use.mjs | 49 --- apps/site/package.json | 8 +- apps/site/pages/en/download/current.mdx | 36 ++ apps/site/pages/en/download/index.mdx | 36 ++ .../en/download/package-manager/current.mdx | 24 -- .../en/download/package-manager/index.mdx | 24 -- .../en/download/prebuilt-binaries/current.mdx | 24 -- .../en/download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../en/download/prebuilt-installer/index.mdx | 26 -- .../pages/en/download/source-code/current.mdx | 24 -- .../pages/en/download/source-code/index.mdx | 24 -- .../pages/es/download/package-manager/all.md | 393 ----------------- .../es/download/package-manager/current.mdx | 24 -- .../es/download/package-manager/index.mdx | 24 -- .../es/download/prebuilt-binaries/current.mdx | 24 -- .../es/download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../es/download/prebuilt-installer/index.mdx | 26 -- .../pages/es/download/source-code/current.mdx | 24 -- .../pages/es/download/source-code/index.mdx | 24 -- .../pages/fa/download/package-manager/all.md | 392 ----------------- .../fa/download/package-manager/current.mdx | 24 -- .../fa/download/package-manager/index.mdx | 24 -- .../fa/download/prebuilt-binaries/current.mdx | 23 - .../fa/download/prebuilt-binaries/index.mdx | 23 - .../download/prebuilt-installer/current.mdx | 26 -- .../fa/download/prebuilt-installer/index.mdx | 26 -- .../pages/fa/download/source-code/current.mdx | 24 -- .../pages/fa/download/source-code/index.mdx | 24 -- .../pages/fr/download/package-manager/all.md | 400 ------------------ .../fr/download/package-manager/current.mdx | 24 -- .../fr/download/package-manager/index.mdx | 24 -- .../fr/download/prebuilt-binaries/current.mdx | 24 -- .../fr/download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../fr/download/prebuilt-installer/index.mdx | 26 -- .../pages/fr/download/source-code/current.mdx | 24 -- .../pages/fr/download/source-code/index.mdx | 24 -- .../pages/id/download/package-manager/all.md | 395 ----------------- .../id/download/package-manager/current.mdx | 24 -- .../id/download/package-manager/index.mdx | 24 -- .../id/download/prebuilt-binaries/current.mdx | 24 -- .../id/download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../id/download/prebuilt-installer/index.mdx | 26 -- .../pages/id/download/source-code/current.mdx | 24 -- .../pages/id/download/source-code/index.mdx | 24 -- .../ja/download/package-manager/current.mdx | 29 -- .../ja/download/package-manager/index.mdx | 29 -- .../ja/download/prebuilt-binaries/current.mdx | 28 -- .../ja/download/prebuilt-binaries/index.mdx | 23 - .../download/prebuilt-installer/current.mdx | 30 -- .../ja/download/prebuilt-installer/index.mdx | 30 -- .../pages/ja/download/source-code/current.mdx | 29 -- .../pages/ja/download/source-code/index.mdx | 29 -- .../pages/ko/download/package-manager/all.md | 390 ----------------- .../ko/download/package-manager/current.mdx | 25 -- .../ko/download/package-manager/index.mdx | 25 -- .../ko/download/prebuilt-binaries/current.mdx | 24 -- .../ko/download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../ko/download/prebuilt-installer/index.mdx | 26 -- .../pages/ko/download/source-code/current.mdx | 25 -- .../pages/ko/download/source-code/index.mdx | 25 -- .../pages/pt/download/package-manager/all.md | 387 ----------------- .../pt/download/package-manager/current.mdx | 24 -- .../pt/download/package-manager/index.mdx | 24 -- .../pt/download/prebuilt-binaries/current.mdx | 24 -- .../pt/download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../pt/download/prebuilt-installer/index.mdx | 26 -- .../pages/pt/download/source-code/current.mdx | 24 -- .../pages/pt/download/source-code/index.mdx | 24 -- .../pages/tr/download/package-manager/all.md | 398 ----------------- .../tr/download/package-manager/current.mdx | 24 -- .../tr/download/package-manager/index.mdx | 25 -- .../tr/download/prebuilt-binaries/current.mdx | 24 -- .../tr/download/prebuilt-binaries/index.mdx | 26 -- .../download/prebuilt-installer/current.mdx | 26 -- .../tr/download/prebuilt-installer/index.mdx | 26 -- .../pages/tr/download/source-code/current.mdx | 24 -- .../pages/tr/download/source-code/index.mdx | 24 -- .../pages/uk/download/package-manager/all.md | 392 ----------------- .../uk/download/package-manager/current.mdx | 24 -- .../uk/download/package-manager/index.mdx | 24 -- .../uk/download/prebuilt-binaries/current.mdx | 24 -- .../uk/download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../uk/download/prebuilt-installer/index.mdx | 26 -- .../pages/uk/download/source-code/current.mdx | 24 -- .../pages/uk/download/source-code/index.mdx | 24 -- .../zh-cn/download/package-manager/all.md | 389 ----------------- .../download/package-manager/current.mdx | 24 -- .../zh-cn/download/package-manager/index.mdx | 24 -- .../download/prebuilt-binaries/current.mdx | 24 -- .../download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../download/prebuilt-installer/index.mdx | 26 -- .../zh-cn/download/source-code/current.mdx | 24 -- .../zh-cn/download/source-code/index.mdx | 24 -- .../zh-tw/download/package-manager/all.md | 391 ----------------- .../download/package-manager/current.mdx | 24 -- .../zh-tw/download/package-manager/index.mdx | 24 -- .../download/prebuilt-binaries/current.mdx | 24 -- .../download/prebuilt-binaries/index.mdx | 24 -- .../download/prebuilt-installer/current.mdx | 26 -- .../download/prebuilt-installer/index.mdx | 26 -- .../zh-tw/download/source-code/current.mdx | 24 -- .../zh-tw/download/source-code/index.mdx | 24 -- apps/site/providers/releaseProvider.tsx | 122 +++--- apps/site/redirects.json | 38 +- apps/site/reducers/releaseReducer.ts | 45 ++ apps/site/snippets/en/download/brew.bash | 16 +- apps/site/snippets/en/download/choco.bash | 16 +- apps/site/snippets/en/download/docker.bash | 17 +- apps/site/snippets/en/download/fnm.bash | 21 +- apps/site/snippets/en/download/npm.bash | 2 + apps/site/snippets/en/download/nvm.bash | 11 +- apps/site/snippets/en/download/pnpm.bash | 5 + apps/site/snippets/en/download/yarn.bash | 5 + apps/site/tsconfig.json | 1 + apps/site/types/blog.ts | 5 +- apps/site/types/navigation.ts | 4 +- apps/site/types/release.ts | 30 +- .../util/__tests__/downloadUtils.test.mjs | 159 ------- .../getUserBitnessByArchitecture.test.mjs | 2 +- apps/site/util/downloadUtils.ts | 173 -------- apps/site/util/downloadUtils.tsx | 295 +++++++++++++ .../site/util/getUserBitnessByArchitecture.ts | 4 +- package-lock.json | 337 +++++++-------- packages/i18n/locales/en.json | 32 +- 177 files changed, 1327 insertions(+), 7561 deletions(-) delete mode 100644 apps/site/components/Downloads/Release/NpmLink.tsx create mode 100644 apps/site/components/Downloads/Release/PackageManagerDropdown.tsx delete mode 100644 apps/site/components/Downloads/Release/ReleaseStatus.tsx delete mode 100644 apps/site/components/Downloads/Release/ReleaseVersion.tsx delete mode 100644 apps/site/components/Downloads/Release/SourceButton.tsx delete mode 100644 apps/site/components/Downloads/Release/VerifyingBinariesLink.tsx rename apps/site/components/Icons/{Platform => OperatingSystem}/AIX.tsx (100%) rename apps/site/components/Icons/{Platform => OperatingSystem}/Apple.tsx (100%) rename apps/site/components/Icons/{Platform => OperatingSystem}/Linux.tsx (100%) rename apps/site/components/Icons/{Platform => OperatingSystem}/Microsoft.tsx (100%) create mode 100644 apps/site/components/Icons/OperatingSystem/index.ts create mode 100644 apps/site/components/Icons/PackageManager/index.ts delete mode 100644 apps/site/components/Icons/Platform/Generic.tsx create mode 100644 apps/site/components/Icons/Platform/index.ts delete mode 100644 apps/site/components/withDownloadCategories.tsx create mode 100644 apps/site/components/withDownloadSection.tsx create mode 100644 apps/site/global.d.ts create mode 100644 apps/site/pages/en/download/current.mdx create mode 100644 apps/site/pages/en/download/index.mdx delete mode 100644 apps/site/pages/en/download/package-manager/current.mdx delete mode 100644 apps/site/pages/en/download/package-manager/index.mdx delete mode 100644 apps/site/pages/en/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/en/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/en/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/en/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/en/download/source-code/current.mdx delete mode 100644 apps/site/pages/en/download/source-code/index.mdx delete mode 100644 apps/site/pages/es/download/package-manager/all.md delete mode 100644 apps/site/pages/es/download/package-manager/current.mdx delete mode 100644 apps/site/pages/es/download/package-manager/index.mdx delete mode 100644 apps/site/pages/es/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/es/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/es/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/es/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/es/download/source-code/current.mdx delete mode 100644 apps/site/pages/es/download/source-code/index.mdx delete mode 100644 apps/site/pages/fa/download/package-manager/all.md delete mode 100644 apps/site/pages/fa/download/package-manager/current.mdx delete mode 100644 apps/site/pages/fa/download/package-manager/index.mdx delete mode 100644 apps/site/pages/fa/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/fa/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/fa/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/fa/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/fa/download/source-code/current.mdx delete mode 100644 apps/site/pages/fa/download/source-code/index.mdx delete mode 100644 apps/site/pages/fr/download/package-manager/all.md delete mode 100644 apps/site/pages/fr/download/package-manager/current.mdx delete mode 100644 apps/site/pages/fr/download/package-manager/index.mdx delete mode 100644 apps/site/pages/fr/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/fr/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/fr/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/fr/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/fr/download/source-code/current.mdx delete mode 100644 apps/site/pages/fr/download/source-code/index.mdx delete mode 100644 apps/site/pages/id/download/package-manager/all.md delete mode 100644 apps/site/pages/id/download/package-manager/current.mdx delete mode 100644 apps/site/pages/id/download/package-manager/index.mdx delete mode 100644 apps/site/pages/id/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/id/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/id/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/id/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/id/download/source-code/current.mdx delete mode 100644 apps/site/pages/id/download/source-code/index.mdx delete mode 100644 apps/site/pages/ja/download/package-manager/current.mdx delete mode 100644 apps/site/pages/ja/download/package-manager/index.mdx delete mode 100644 apps/site/pages/ja/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/ja/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/ja/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/ja/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/ja/download/source-code/current.mdx delete mode 100644 apps/site/pages/ja/download/source-code/index.mdx delete mode 100644 apps/site/pages/ko/download/package-manager/all.md delete mode 100644 apps/site/pages/ko/download/package-manager/current.mdx delete mode 100644 apps/site/pages/ko/download/package-manager/index.mdx delete mode 100644 apps/site/pages/ko/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/ko/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/ko/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/ko/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/ko/download/source-code/current.mdx delete mode 100644 apps/site/pages/ko/download/source-code/index.mdx delete mode 100644 apps/site/pages/pt/download/package-manager/all.md delete mode 100644 apps/site/pages/pt/download/package-manager/current.mdx delete mode 100644 apps/site/pages/pt/download/package-manager/index.mdx delete mode 100644 apps/site/pages/pt/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/pt/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/pt/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/pt/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/pt/download/source-code/current.mdx delete mode 100644 apps/site/pages/pt/download/source-code/index.mdx delete mode 100644 apps/site/pages/tr/download/package-manager/all.md delete mode 100644 apps/site/pages/tr/download/package-manager/current.mdx delete mode 100644 apps/site/pages/tr/download/package-manager/index.mdx delete mode 100644 apps/site/pages/tr/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/tr/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/tr/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/tr/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/tr/download/source-code/current.mdx delete mode 100644 apps/site/pages/tr/download/source-code/index.mdx delete mode 100644 apps/site/pages/uk/download/package-manager/all.md delete mode 100644 apps/site/pages/uk/download/package-manager/current.mdx delete mode 100644 apps/site/pages/uk/download/package-manager/index.mdx delete mode 100644 apps/site/pages/uk/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/uk/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/uk/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/uk/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/uk/download/source-code/current.mdx delete mode 100644 apps/site/pages/uk/download/source-code/index.mdx delete mode 100644 apps/site/pages/zh-cn/download/package-manager/all.md delete mode 100644 apps/site/pages/zh-cn/download/package-manager/current.mdx delete mode 100644 apps/site/pages/zh-cn/download/package-manager/index.mdx delete mode 100644 apps/site/pages/zh-cn/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/zh-cn/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/zh-cn/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/zh-cn/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/zh-cn/download/source-code/current.mdx delete mode 100644 apps/site/pages/zh-cn/download/source-code/index.mdx delete mode 100644 apps/site/pages/zh-tw/download/package-manager/all.md delete mode 100644 apps/site/pages/zh-tw/download/package-manager/current.mdx delete mode 100644 apps/site/pages/zh-tw/download/package-manager/index.mdx delete mode 100644 apps/site/pages/zh-tw/download/prebuilt-binaries/current.mdx delete mode 100644 apps/site/pages/zh-tw/download/prebuilt-binaries/index.mdx delete mode 100644 apps/site/pages/zh-tw/download/prebuilt-installer/current.mdx delete mode 100644 apps/site/pages/zh-tw/download/prebuilt-installer/index.mdx delete mode 100644 apps/site/pages/zh-tw/download/source-code/current.mdx delete mode 100644 apps/site/pages/zh-tw/download/source-code/index.mdx create mode 100644 apps/site/reducers/releaseReducer.ts create mode 100644 apps/site/snippets/en/download/npm.bash create mode 100644 apps/site/snippets/en/download/pnpm.bash create mode 100644 apps/site/snippets/en/download/yarn.bash delete mode 100644 apps/site/util/__tests__/downloadUtils.test.mjs delete mode 100644 apps/site/util/downloadUtils.ts create mode 100644 apps/site/util/downloadUtils.tsx diff --git a/apps/site/app/[locale]/next-data/blog-data/[category]/[page]/route.ts b/apps/site/app/[locale]/next-data/blog-data/[category]/[page]/route.ts index c0061c6a366ad..ed7977b3d6252 100644 --- a/apps/site/app/[locale]/next-data/blog-data/[category]/[page]/route.ts +++ b/apps/site/app/[locale]/next-data/blog-data/[category]/[page]/route.ts @@ -3,8 +3,13 @@ import { providePaginatedBlogPosts, } from '@/next-data/providers/blogData'; import { defaultLocale } from '@/next.locales.mjs'; +import type { BlogCategory } from '@/types'; -type DynamicStaticPaths = { locale: string; category: string; page: string }; +type DynamicStaticPaths = { + locale: string; + category: BlogCategory; + page: string; +}; type StaticParams = { params: Promise }; // This is the Route Handler for the `GET` method which handles the request diff --git a/apps/site/components/Common/AlertBox/index.module.css b/apps/site/components/Common/AlertBox/index.module.css index 3714979473e5d..c3e31ae888704 100644 --- a/apps/site/components/Common/AlertBox/index.module.css +++ b/apps/site/components/Common/AlertBox/index.module.css @@ -10,9 +10,10 @@ text-white; a { - @apply font-ibm-plex-mono + @apply font-bold text-white - underline; + underline + hover:text-white; &:hover { @apply no-underline; diff --git a/apps/site/components/Common/BlogPostCard/index.tsx b/apps/site/components/Common/BlogPostCard/index.tsx index 4f8696dd20565..30815ad9ed62c 100644 --- a/apps/site/components/Common/BlogPostCard/index.tsx +++ b/apps/site/components/Common/BlogPostCard/index.tsx @@ -5,13 +5,14 @@ import FormattedTime from '@/components/Common/FormattedTime'; import Preview from '@/components/Common/Preview'; import Link from '@/components/Link'; import WithAvatarGroup from '@/components/withAvatarGroup'; +import type { BlogCategory } from '@/types'; import { mapBlogCategoryToPreviewType } from '@/util/blogUtils'; import styles from './index.module.css'; type BlogPostCardProps = { title: string; - category: string; + category: BlogCategory; description?: string; authors?: Array; date?: Date; diff --git a/apps/site/components/Common/Button/index.module.css b/apps/site/components/Common/Button/index.module.css index 4d8786bc63b14..6f2d69b8e9bef 100644 --- a/apps/site/components/Common/Button/index.module.css +++ b/apps/site/components/Common/Button/index.module.css @@ -3,6 +3,7 @@ relative inline-flex items-center + justify-center gap-2 py-2.5 text-center diff --git a/apps/site/components/Common/Select/index.module.css b/apps/site/components/Common/Select/index.module.css index 346ee8ee1ef1d..1e13248039a83 100644 --- a/apps/site/components/Common/Select/index.module.css +++ b/apps/site/components/Common/Select/index.module.css @@ -108,8 +108,14 @@ } } -.dropdown:has(.label) .text > span > span { - @apply pl-3; +.dropdown:has(.label) .text > span { + &:has(svg) > svg { + @apply ml-3; + } + + &:not(&:has(svg)) > span { + @apply ml-3; + } } .inline { diff --git a/apps/site/components/Common/Select/index.stories.tsx b/apps/site/components/Common/Select/index.stories.tsx index 59b5da23b7f48..4c24dd474aca0 100644 --- a/apps/site/components/Common/Select/index.stories.tsx +++ b/apps/site/components/Common/Select/index.stories.tsx @@ -1,10 +1,7 @@ import type { Meta as MetaObj, StoryObj } from '@storybook/react'; import Select from '@/components/Common/Select'; -import AIX from '@/components/Icons/Platform/AIX'; -import Apple from '@/components/Icons/Platform/Apple'; -import Linux from '@/components/Icons/Platform/Linux'; -import Microsoft from '@/components/Icons/Platform/Microsoft'; +import OSIcons from '@/components/Icons/OperatingSystem'; type Story = StoryObj; type Meta = MetaObj; @@ -79,22 +76,22 @@ export const InlineSelect: Story = { { value: 'linux', label: 'Linux', - iconImage: , + iconImage: , }, { value: 'macos', label: 'macOS', - iconImage: , + iconImage: , }, { value: 'windows', label: 'Windows', - iconImage: , + iconImage: , }, { value: 'aix', label: 'AIX', - iconImage: , + iconImage: , }, ], }, diff --git a/apps/site/components/Common/Select/index.tsx b/apps/site/components/Common/Select/index.tsx index 66dfed76ff0ab..1428b39a0f487 100644 --- a/apps/site/components/Common/Select/index.tsx +++ b/apps/site/components/Common/Select/index.tsx @@ -5,44 +5,47 @@ import * as ScrollPrimitive from '@radix-ui/react-scroll-area'; import * as SelectPrimitive from '@radix-ui/react-select'; import classNames from 'classnames'; import { useEffect, useId, useMemo, useState } from 'react'; -import type { FC } from 'react'; +import type { ReactElement, ReactNode } from 'react'; import Skeleton from '@/components/Common/Skeleton'; import type { FormattedMessage } from '@/types'; import styles from './index.module.css'; -type SelectValue = { - label: FormattedMessage; - value: string; - iconImage?: React.ReactNode; +export type SelectValue = { + label: FormattedMessage | string; + value: T; + iconImage?: ReactElement; disabled?: boolean; }; -type SelectGroup = { - label?: FormattedMessage; - items: Array; +export type SelectGroup = { + label?: FormattedMessage | string; + items: Array>; }; const isStringArray = (values: Array): values is Array => Boolean(values[0] && typeof values[0] === 'string'); -const isValuesArray = (values: Array): values is Array => +const isValuesArray = ( + values: Array +): values is Array> => Boolean(values[0] && typeof values[0] === 'object' && 'value' in values[0]); -type SelectProps = { - values: Array; - defaultValue?: string; +type SelectProps = { + values: Array> | Array | Array>; + defaultValue?: T; placeholder?: string; label?: string; inline?: boolean; - onChange?: (value: string) => void; + onChange?: (value: T) => void; className?: string; ariaLabel?: string; loading?: boolean; + disabled?: boolean; }; -const Select: FC = ({ +const Select = ({ values = [], defaultValue, placeholder, @@ -52,7 +55,8 @@ const Select: FC = ({ className, ariaLabel, loading = false, -}) => { + disabled = false, +}: SelectProps): ReactNode => { const id = useId(); const [value, setValue] = useState(defaultValue); @@ -69,7 +73,7 @@ const Select: FC = ({ return [{ items: mappedValues }]; } - return mappedValues as Array; + return mappedValues as Array>; }, [values]); // We render the actual item slotted to fix/prevent the issue @@ -83,7 +87,7 @@ const Select: FC = ({ ); // Both change the internal state and emit the change event - const handleChange = (value: string) => { + const handleChange = (value: T) => { setValue(value); if (typeof onChange === 'function') { @@ -106,15 +110,23 @@ const Select: FC = ({ )} - + - {currentItem?.iconImage} - {currentItem?.label} + {currentItem !== undefined && ( + <> + {currentItem.iconImage} + {currentItem.label} + + )} diff --git a/apps/site/components/Common/Skeleton/index.tsx b/apps/site/components/Common/Skeleton/index.tsx index cb5439345c09b..640c1f2e3852d 100644 --- a/apps/site/components/Common/Skeleton/index.tsx +++ b/apps/site/components/Common/Skeleton/index.tsx @@ -3,12 +3,21 @@ import { isValidElement } from 'react'; import styles from './index.module.css'; -type SkeletonProps = { loading?: boolean }; +type SkeletonProps = { hide?: boolean; loading?: boolean }; const Skeleton: FC> = ({ children, + hide = false, loading = true, }) => { + // This can be used to completely hide the children after the Skeleton has loaded + // If certain criterias do not match. This is useful for conditional rendering without + // changing the actual tree that the Skeleton is wrapping + if (!loading && hide) { + return null; + } + + // If we finished loading, we can hide the Skeleton and render the children tree if (!loading) { return children; } diff --git a/apps/site/components/Containers/MetaBar/index.tsx b/apps/site/components/Containers/MetaBar/index.tsx index b4bc0fccf70f8..febcc88bf3e60 100644 --- a/apps/site/components/Containers/MetaBar/index.tsx +++ b/apps/site/components/Containers/MetaBar/index.tsx @@ -8,7 +8,7 @@ import Link from '@/components/Link'; import styles from './index.module.css'; type MetaBarProps = { - items: Record; + items: Partial>; headings?: { items: Array; minDepth?: number; @@ -33,7 +33,7 @@ const MetaBar: FC = ({ items, headings }) => { .filter(([, value]) => !!value) .map(([key, value]) => ( -
{t(key)}
+
{t(key as IntlMessageKeys)}
{value}
))} diff --git a/apps/site/components/Downloads/Release/BitnessDropdown.tsx b/apps/site/components/Downloads/Release/BitnessDropdown.tsx index e48b44cba48b0..b4b7b2ad83db6 100644 --- a/apps/site/components/Downloads/Release/BitnessDropdown.tsx +++ b/apps/site/components/Downloads/Release/BitnessDropdown.tsx @@ -3,107 +3,63 @@ import { useTranslations } from 'next-intl'; import type { FC } from 'react'; import { useEffect, useContext, useMemo } from 'react'; -import semVer from 'semver'; import Select from '@/components/Common/Select'; import { useClientContext } from '@/hooks'; import { ReleaseContext } from '@/providers/releaseProvider'; -import { bitnessItems, formatDropdownItems } from '@/util/downloadUtils'; +import { ARCHITECTURES, nextItem, parseCompat } from '@/util/downloadUtils'; import { getUserBitnessByArchitecture } from '@/util/getUserBitnessByArchitecture'; const parseNumericBitness = (bitness: string) => /^\d+$/.test(bitness) ? Number(bitness) : bitness; const BitnessDropdown: FC = () => { - const { bitness: userBitness, architecture: userArchitecture } = - useClientContext(); - const { bitness, os, release, setBitness } = useContext(ReleaseContext); - const t = useTranslations(); - - useEffect(() => { - setBitness(getUserBitnessByArchitecture(userArchitecture, userBitness)); - // we shouldn't update the effect on setter state change - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [userArchitecture, userBitness]); - - // @TODO: We should have a proper utility that gives - // disabled OSs, Platforms, based on specific criteria - // this can be an optimisation for the future - // to remove this logic from this component - const disabledItems = useMemo(() => { - const disabledItems = []; - - if (os === 'WIN' && semVer.satisfies(release.version, '< 19.9.0')) { - disabledItems.push('arm64'); - } - - if (os === 'WIN' && semVer.satisfies(release.version, '>= 23.0.0')) { - disabledItems.push('86'); - } - - if (os === 'LINUX' && semVer.satisfies(release.version, '< 4.0.0')) { - disabledItems.push('arm64', 'armv7l'); - } - - if (os === 'LINUX' && semVer.satisfies(release.version, '< 4.4.0')) { - disabledItems.push('ppc64le'); - } + const { architecture, bitness } = useClientContext(); - if (os === 'LINUX' && semVer.satisfies(release.version, '< 6.6.0')) { - disabledItems.push('s390x'); - } + const release = useContext(ReleaseContext); + const t = useTranslations(); - if (os === 'AIX' && semVer.satisfies(release.version, '< 6.7.0')) { - disabledItems.push('ppc64'); + // Prevents the Bitness from being set during OS loading state + // and always correctly parses the Bitness to a number when needed + const setBitness = (bitness: string) => { + if (release.os !== 'LOADING') { + release.setBitness(parseNumericBitness(bitness)); } + }; - return disabledItems; - }, [os, release.version]); - - // @TODO: We should have a proper utility that gives - // disabled OSs, Platforms, based on specific criteria - // this can be an optimisation for the future - // to remove this logic from this component - useEffect(() => { - const mappedBitnessValues = bitnessItems[os].map(({ value }) => value); - - const currentBitnessExcluded = - // Different OSs support different Bitnessess, hence we should also check - // if besides the current bitness not being supported for a given release version - // we also should check if it is not supported by the OS - disabledItems.includes(String(bitness)) || - !mappedBitnessValues.includes(String(bitness)); - - const nonExcludedBitness = mappedBitnessValues.find( - bitness => !disabledItems.includes(bitness) - ); + useEffect( + () => setBitness(getUserBitnessByArchitecture(architecture, bitness)), + // Only react on the change of the Client Context Architecture and Bitness + // eslint-disable-next-line react-hooks/exhaustive-deps + [architecture, bitness] + ); - if (currentBitnessExcluded && nonExcludedBitness) { - // We set it as a Number for cases where it is 64 or 86 otherwise we are - // setting it as a string (ARMv7, ARMv6, etc.) - const numericBitness = Number(nonExcludedBitness); + // We parse the compatibility of the dropdown items + const parsedArchitectures = useMemo( + () => parseCompat(ARCHITECTURES[release.os], release), + // We only want to react on the change of the OS, Bitness, and Version + // eslint-disable-next-line react-hooks/exhaustive-deps + [release.os, release.bitness, release.version] + ); - setBitness( - numericBitness.toString() === nonExcludedBitness - ? numericBitness - : nonExcludedBitness - ); - } - // we shouldn't react when "actions" change + // We set the Bitness to the next available Architecture when the current + // one is not valid anymore due to OS or Version changes + useEffect( + () => setBitness(nextItem(String(release.bitness), parsedArchitectures)), + // We only want to react on the change of the OS and Version // eslint-disable-next-line react-hooks/exhaustive-deps - }, [os, disabledItems]); + [release.os, release.version, release.bitness] + ); return ( , - MAC: , - LINUX: , - AIX: , - }, - })} - defaultValue={os} - loading={os === 'LOADING'} + + values={parsedOperatingSystems} + defaultValue={release.os} + loading={release.os === 'LOADING'} + placeholder={t('layouts.download.dropdown.unknown')} ariaLabel={t('layouts.download.dropdown.os')} - onChange={value => setOS(value as UserOS)} + onChange={value => setOS(value)} className="min-w-[8.5rem]" inline={true} /> diff --git a/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx b/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx new file mode 100644 index 0000000000000..14d21ad2b7a5b --- /dev/null +++ b/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useContext, useEffect, useMemo } from 'react'; +import type { FC } from 'react'; + +import Select from '@/components/Common/Select'; +import { ReleaseContext } from '@/providers/releaseProvider'; +import type { PackageManager } from '@/types/release'; +import { nextItem, PACKAGE_MANAGERS, parseCompat } from '@/util/downloadUtils'; + +const PackageManagerDropdown: FC = () => { + const release = useContext(ReleaseContext); + const t = useTranslations(); + + // Prevents the Package Manager from being set during OS loading state + const setManager = (manager: PackageManager | '') => { + if (release.os !== 'LOADING') { + release.setPackageManager(manager); + } + }; + + // We parse the compatibility of the dropdown items + const parsedPackageManagers = useMemo( + () => parseCompat(PACKAGE_MANAGERS, release), + // We only want to react on the change of the Version + // eslint-disable-next-line react-hooks/exhaustive-deps + [release.version] + ); + + // We set the Package Manager to the next available Package Manager when the current + // one is not valid anymore due to Version changes + useEffect( + () => setManager(nextItem(release.packageManager, parsedPackageManagers)), + // We only want to react on the change of the Version + // eslint-disable-next-line react-hooks/exhaustive-deps + [release.version] + ); + + return ( + + values={parsedPackageManagers} + defaultValue={release.packageManager} + loading={release.os === 'LOADING' || release.platform === ''} + ariaLabel={t('layouts.download.dropdown.packageManager')} + onChange={manager => manager && setManager(manager)} + className="min-w-28" + inline={true} + /> + ); +}; + +export default PackageManagerDropdown; diff --git a/apps/site/components/Downloads/Release/PlatformDropdown.tsx b/apps/site/components/Downloads/Release/PlatformDropdown.tsx index 2839fab44b357..3b31bd60b7a82 100644 --- a/apps/site/components/Downloads/Release/PlatformDropdown.tsx +++ b/apps/site/components/Downloads/Release/PlatformDropdown.tsx @@ -5,82 +5,80 @@ import { useContext, useEffect, useMemo } from 'react'; import type { FC } from 'react'; import Select from '@/components/Common/Select'; -import Choco from '@/components/Icons/Platform/Choco'; -import Docker from '@/components/Icons/Platform/Docker'; -import FNM from '@/components/Icons/Platform/FNM'; -import Homebrew from '@/components/Icons/Platform/Homebrew'; -import NVM from '@/components/Icons/Platform/NVM'; import { ReleaseContext } from '@/providers/releaseProvider'; -import type { PackageManager } from '@/types/release'; -import { formatDropdownItems, platformItems } from '@/util/downloadUtils'; - -const supportedHomebrewVersions = ['LTS', 'Current']; +import type { InstallationMethod } from '@/types/release'; +import { nextItem, INSTALL_METHODS, parseCompat } from '@/util/downloadUtils'; const PlatformDropdown: FC = () => { - const { release, os, platform, setPlatform } = useContext(ReleaseContext); + const release = useContext(ReleaseContext); const t = useTranslations(); - // @TODO: We should have a proper utility that gives - // disabled OSs, Platforms, based on specific criteria - // this can be an optimisation for the future - // to remove this logic from this component - const disabledItems = useMemo(() => { - const disabledItems = []; - - if (os === 'WIN') { - disabledItems.push('BREW', 'NVM'); - } - - if (os === 'LINUX' || os === 'MAC') { - disabledItems.push('CHOCO'); + // Prevents the Platform from being set during OS loading state + // This also prevents the Platform from being set (by Dropdwon or Automatic methods) + // when we haven't yet loaded the OS and defined the initial Platform + const setPlaform = (platform: InstallationMethod | '') => { + if (release.os !== 'LOADING' && release.platform !== '') { + release.setPlatform(platform); } + }; - const releaseSupportsHomebrew = supportedHomebrewVersions.includes( - release.status - ); - - if (!releaseSupportsHomebrew) { - disabledItems.push('BREW'); - } + // We parse the compatibility of the dropdown items + const parsedPlatforms = useMemo( + () => parseCompat(INSTALL_METHODS, release), + // We only want to react on the change of the OS and Version + // eslint-disable-next-line react-hooks/exhaustive-deps + [release.os, release.version] + ); - return disabledItems; - }, [os, release.status]); + // We group Platforms on the Platform Dropdown to provide the User + // understanding of what is recommended/official and what is not. + const grouppedPlatforms = useMemo( + () => [ + { + label: t('layouts.download.dropdown.platformGroups.official'), + items: parsedPlatforms.filter(({ recommended }) => recommended), + }, + { + label: t('layouts.download.dropdown.platformGroups.unofficial'), + items: parsedPlatforms.filter(({ recommended }) => !recommended), + }, + ], + // We only want to react on the change of the parsedPlatforms + // eslint-disable-next-line react-hooks/exhaustive-deps + [parsedPlatforms] + ); - // @TODO: We should have a proper utility that gives - // disabled OSs, Platforms, based on specific criteria - // this can be an optimisation for the future - // to remove this logic from this component useEffect(() => { - const currentPlatformExcluded = disabledItems.includes(platform); - - const nonExcludedPlatform = platformItems - .map(({ value }) => value) - .find(platform => !disabledItems.includes(platform)); - - if (currentPlatformExcluded && nonExcludedPlatform) { - setPlatform(nonExcludedPlatform); + // We should only define the initial Platform if the current platform is empty + // (aka has not yet been set) and the OS has finished loading (in the sense that) + // `detectOS` has finished running and decided what platform we are running on. + if (release.os !== 'LOADING' && release.platform === '') { + release.setPlatform( + // Sets either the utmost recommended platform or the first non-disabled one + // Note that the first item of groupped platforms is always the recommended one + nextItem('', grouppedPlatforms[0].items) || + nextItem('', parsedPlatforms) + ); } - // we shouldn't react when "actions" change // eslint-disable-next-line react-hooks/exhaustive-deps - }, [release.status, disabledItems, platform]); + }, [parsedPlatforms, release.platform, release.os]); + + // We set the Platform to the next available platform when the current + // one is not valid anymore due to OS or Version changes + useEffect( + () => setPlaform(nextItem(release.platform, parsedPlatforms)), + // We only want to react on the change of the OS and Version + // eslint-disable-next-line react-hooks/exhaustive-deps + [release.os, release.version] + ); return ( - setBitness(bitness)} - className="min-w-20" - inline={true} - /> - ); -}; - -export default BitnessDropdown; diff --git a/apps/site/components/Downloads/Release/InstallationMethodDropdown.tsx b/apps/site/components/Downloads/Release/InstallationMethodDropdown.tsx new file mode 100644 index 0000000000000..a5d4a9307db52 --- /dev/null +++ b/apps/site/components/Downloads/Release/InstallationMethodDropdown.tsx @@ -0,0 +1,88 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useContext, useEffect, useMemo } from 'react'; +import type { FC } from 'react'; + +import Select from '@/components/Common/Select'; +import { ReleaseContext } from '@/providers/releaseProvider'; +import type { InstallationMethod } from '@/types/release'; +import { nextItem, INSTALL_METHODS, parseCompat } from '@/util/downloadUtils'; + +const InstallationMethodDropdown: FC = () => { + const release = useContext(ReleaseContext); + const t = useTranslations(); + + // We parse the compatibility of the dropdown items + const parsedInstallMethods = useMemo( + () => parseCompat(INSTALL_METHODS, release), + // We only want to react on the change of the OS and Version + // eslint-disable-next-line react-hooks/exhaustive-deps + [release.os, release.version] + ); + + // We group Platforms on the Platform Dropdown to provide the User + // understanding of what is recommended/official and what is not. + const grouppedMethods = useMemo( + () => [ + { + label: t('layouts.download.dropdown.platformGroups.official'), + items: parsedInstallMethods.filter(({ recommended }) => recommended), + }, + { + label: t('layouts.download.dropdown.platformGroups.unofficial'), + items: parsedInstallMethods.filter(({ recommended }) => !recommended), + }, + ], + // We only want to react on the change of the parsedPlatforms + // eslint-disable-next-line react-hooks/exhaustive-deps + [parsedInstallMethods] + ); + + useEffect(() => { + // We should only define the initial Platform if the current platform is empty + // (aka has not yet been set) and the OS has finished loading (in the sense that) + // `detectOS` has finished running and decided what platform we are running on. + if (release.os !== 'LOADING' && release.installMethod === '') { + const installationMethod = + // Sets either the utmost recommended platform or the first non-disabled one + // Note that the first item of groupped platforms is always the recommended one + nextItem('', grouppedMethods[0].items) || + nextItem('', parsedInstallMethods); + + // This will never return an empty string as there should always be an item + // when the OS has finished loading for a given installation method + release.setInstallMethod(installationMethod as InstallationMethod); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [parsedInstallMethods, release.installMethod, release.os]); + + // We set the Platform to the next available platform when the current + // one is not valid anymore due to OS or Version changes + useEffect( + () => { + if (release.os !== 'LOADING' && release.installMethod !== '') { + release.setInstallMethod( + nextItem(release.installMethod, parsedInstallMethods) + ); + } + }, + // We only want to react on the change of the OS and Version + // eslint-disable-next-line react-hooks/exhaustive-deps + [release.os, release.version] + ); + + return ( + + values={grouppedMethods} + defaultValue={release.installMethod} + loading={release.os === 'LOADING' || release.installMethod === ''} + ariaLabel={t('layouts.download.dropdown.platform')} + onChange={platform => platform && release.setInstallMethod(platform)} + className="min-w-28" + inline={true} + /> + ); +}; + +export default InstallationMethodDropdown; diff --git a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx index 0a3d30811bbc8..385717528faf9 100644 --- a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx +++ b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx @@ -17,35 +17,32 @@ const OperatingSystemDropdown: FC = () => { const release = useContext(ReleaseContext); const t = useTranslations(); - // Prevents the OS from being set during OS loading state - const setOS = (newOS: UserOS) => { - if (release.os !== 'LOADING') { - release.setOS(newOS); + useEffect(() => { + if (os !== 'LOADING') { + release.setOS(os); } - }; - - // Reacts on Client Context change of OS - // Only this Hook is allowed to bypass the `setOS` from above - // As this Hook is what defined the initial OS state - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => release.setOS(os), [os]); + // Reacts on Client Context change of OS + // Only this Hook is allowed to bypass the `setOS` from above + // As this Hook is what defined the initial OS state + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [os]); // We parse the compatibility of the dropdown items const parsedOperatingSystems = useMemo( () => parseCompat(OPERATING_SYSTEMS, release), // We only want to react on the change of the OS, Bitness, and Version // eslint-disable-next-line react-hooks/exhaustive-deps - [release.os, release.bitness, release.version] + [release.os, release.platform, release.version] ); return ( values={parsedOperatingSystems} - defaultValue={release.os} + defaultValue={release.os !== 'LOADING' ? release.os : undefined} loading={release.os === 'LOADING'} placeholder={t('layouts.download.dropdown.unknown')} ariaLabel={t('layouts.download.dropdown.os')} - onChange={value => setOS(value)} + onChange={value => release.setOS(value)} className="min-w-[8.5rem]" inline={true} /> diff --git a/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx b/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx index 14d21ad2b7a5b..0a0fdafe5173c 100644 --- a/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx +++ b/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx @@ -13,13 +13,6 @@ const PackageManagerDropdown: FC = () => { const release = useContext(ReleaseContext); const t = useTranslations(); - // Prevents the Package Manager from being set during OS loading state - const setManager = (manager: PackageManager | '') => { - if (release.os !== 'LOADING') { - release.setPackageManager(manager); - } - }; - // We parse the compatibility of the dropdown items const parsedPackageManagers = useMemo( () => parseCompat(PACKAGE_MANAGERS, release), @@ -31,19 +24,22 @@ const PackageManagerDropdown: FC = () => { // We set the Package Manager to the next available Package Manager when the current // one is not valid anymore due to Version changes useEffect( - () => setManager(nextItem(release.packageManager, parsedPackageManagers)), + () => + release.setPackageManager( + nextItem(release.packageManager, parsedPackageManagers) + ), // We only want to react on the change of the Version // eslint-disable-next-line react-hooks/exhaustive-deps [release.version] ); return ( - + values={parsedPackageManagers} defaultValue={release.packageManager} - loading={release.os === 'LOADING' || release.platform === ''} + loading={release.os === 'LOADING' || release.installMethod === ''} ariaLabel={t('layouts.download.dropdown.packageManager')} - onChange={manager => manager && setManager(manager)} + onChange={manager => release.setPackageManager(manager)} className="min-w-28" inline={true} /> diff --git a/apps/site/components/Downloads/Release/PlatformDropdown.tsx b/apps/site/components/Downloads/Release/PlatformDropdown.tsx index 3b31bd60b7a82..419cb5f6b9a28 100644 --- a/apps/site/components/Downloads/Release/PlatformDropdown.tsx +++ b/apps/site/components/Downloads/Release/PlatformDropdown.tsx @@ -1,85 +1,70 @@ 'use client'; import { useTranslations } from 'next-intl'; -import { useContext, useEffect, useMemo } from 'react'; import type { FC } from 'react'; +import { useEffect, useContext, useMemo } from 'react'; import Select from '@/components/Common/Select'; +import { useClientContext } from '@/hooks'; import { ReleaseContext } from '@/providers/releaseProvider'; -import type { InstallationMethod } from '@/types/release'; -import { nextItem, INSTALL_METHODS, parseCompat } from '@/util/downloadUtils'; +import type { UserPlatform } from '@/types/userOS'; +import { PLATFORMS, nextItem, parseCompat } from '@/util/downloadUtils'; +import { getUserPlatform } from '@/util/getUserPlatform'; const PlatformDropdown: FC = () => { + const { architecture, bitness } = useClientContext(); + const release = useContext(ReleaseContext); const t = useTranslations(); - // Prevents the Platform from being set during OS loading state - // This also prevents the Platform from being set (by Dropdwon or Automatic methods) - // when we haven't yet loaded the OS and defined the initial Platform - const setPlaform = (platform: InstallationMethod | '') => { - if (release.os !== 'LOADING' && release.platform !== '') { - release.setPlatform(platform); - } - }; + useEffect( + () => { + if (architecture && bitness) { + const autoDetectedPlatform = getUserPlatform(architecture, bitness); - // We parse the compatibility of the dropdown items - const parsedPlatforms = useMemo( - () => parseCompat(INSTALL_METHODS, release), - // We only want to react on the change of the OS and Version + release.setPlatform(autoDetectedPlatform); + } + }, + // Only react on the change of the Client Context Architecture and Bitness // eslint-disable-next-line react-hooks/exhaustive-deps - [release.os, release.version] + [architecture, bitness] ); - // We group Platforms on the Platform Dropdown to provide the User - // understanding of what is recommended/official and what is not. - const grouppedPlatforms = useMemo( - () => [ - { - label: t('layouts.download.dropdown.platformGroups.official'), - items: parsedPlatforms.filter(({ recommended }) => recommended), - }, - { - label: t('layouts.download.dropdown.platformGroups.unofficial'), - items: parsedPlatforms.filter(({ recommended }) => !recommended), - }, - ], - // We only want to react on the change of the parsedPlatforms + // We parse the compatibility of the dropdown items + const parsedPlatforms = useMemo( + () => + // We only want to parse the compatibility when the OS has finished loading + // Otherwise, we would be parsing the compatibility of an empty array + release.os !== 'LOADING' + ? parseCompat(PLATFORMS[release.os], release) + : [], + // We only want to react on the change of the OS, Platform, and Version // eslint-disable-next-line react-hooks/exhaustive-deps - [parsedPlatforms] + [release.os, release.platform, release.version] ); - useEffect(() => { - // We should only define the initial Platform if the current platform is empty - // (aka has not yet been set) and the OS has finished loading (in the sense that) - // `detectOS` has finished running and decided what platform we are running on. - if (release.os !== 'LOADING' && release.platform === '') { - release.setPlatform( - // Sets either the utmost recommended platform or the first non-disabled one - // Note that the first item of groupped platforms is always the recommended one - nextItem('', grouppedPlatforms[0].items) || - nextItem('', parsedPlatforms) - ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [parsedPlatforms, release.platform, release.os]); - - // We set the Platform to the next available platform when the current + // We set the Platform to the next available Architecture when the current // one is not valid anymore due to OS or Version changes useEffect( - () => setPlaform(nextItem(release.platform, parsedPlatforms)), + () => { + if (release.os !== 'LOADING' && release.platform !== '') { + release.setPlatform(nextItem(release.platform, parsedPlatforms)); + } + }, // We only want to react on the change of the OS and Version // eslint-disable-next-line react-hooks/exhaustive-deps - [release.os, release.version] + [release.os, release.version, release.platform] ); return ( - - values={grouppedPlatforms} + + values={parsedPlatforms} + loading={release.os === 'LOADING'} + placeholder={t('layouts.download.dropdown.unknown')} + ariaLabel={t('layouts.download.dropdown.installMethod')} defaultValue={release.platform} - loading={release.os === 'LOADING' || release.platform === ''} - ariaLabel={t('layouts.download.dropdown.platform')} - onChange={platform => platform && setPlaform(platform)} - className="min-w-28" + onChange={platform => platform && release.setPlatform(platform)} + className="min-w-20" inline={true} /> ); diff --git a/apps/site/components/Downloads/Release/DownloadButton.tsx b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx similarity index 58% rename from apps/site/components/Downloads/Release/DownloadButton.tsx rename to apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx index 893571251210a..8aee25d695491 100644 --- a/apps/site/components/Downloads/Release/DownloadButton.tsx +++ b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx @@ -17,15 +17,24 @@ import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; // Retrieves the pure extension piece from the input string const getExtension = (input: string) => String(input.split('.').slice(-1)); -const DownloadButton: FC = () => { +const PrebuiltDownloadButtons: FC = () => { const t = useTranslations(); - const { release, os, bitness } = useContext(ReleaseContext); - - const version = release.versionWithPrefix; + const { release, os, platform } = useContext(ReleaseContext); const { installerUrl, installerExt, binaryUrl, binaryExt } = useMemo(() => { - const installerUrl = getNodeDownloadUrl(version, os, bitness, 'installer'); - const binaryUrl = getNodeDownloadUrl(version, os, bitness, 'binary'); + const installerUrl = getNodeDownloadUrl( + release.versionWithPrefix, + os, + platform || undefined, + 'installer' + ); + + const binaryUrl = getNodeDownloadUrl( + release.versionWithPrefix, + os, + platform || undefined, + 'binary' + ); return { installerUrl, @@ -33,37 +42,30 @@ const DownloadButton: FC = () => { installerExt: getExtension(installerUrl), binaryExt: getExtension(binaryUrl), }; - }, [version, os, bitness]); + }, [os, platform, release.versionWithPrefix]); return (
- {OS_NOT_SUPPORTING_INSTALLERS.includes(os) || ( - - - - )} - + + + + @@ -71,4 +73,4 @@ const DownloadButton: FC = () => { ); }; -export default DownloadButton; +export default PrebuiltDownloadButtons; diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 98d583a1cf9d5..105d91a53c4e5 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -35,7 +35,12 @@ const parseSnippet = (s: string, releaseContext: ReleaseContextType) => { const ReleaseCodeBox: FC = () => { const { snippets } = useContext(ReleasesContext); - const { platform, os, packageManager, release } = useContext(ReleaseContext); + const { + installMethod: platform, + os, + packageManager, + release, + } = useContext(ReleaseContext); const t = useTranslations(); @@ -61,7 +66,7 @@ const ReleaseCodeBox: FC = () => { // Bundles the Platform and Package Manager snippets `${platformSnippet?.content ?? ''}\n${packageManagerSnippet?.content ?? ''}`, // Passes a partial state of only the things we need to the parser - { release, platform, os } as ReleaseContextType + { release, installMethod: platform, os } as ReleaseContextType ); }, [snippets, release, platform, os, packageManager]); diff --git a/apps/site/hooks/react-client/useDetectOS.ts b/apps/site/hooks/react-client/useDetectOS.ts index df5a8d7061643..01d5e50f862ef 100644 --- a/apps/site/hooks/react-client/useDetectOS.ts +++ b/apps/site/hooks/react-client/useDetectOS.ts @@ -2,21 +2,20 @@ import { useEffect, useState } from 'react'; -import type { UserOS } from '@/types/userOS'; +import type { UserArchitecture, UserBitness, UserOS } from '@/types/userOS'; import { detectOS } from '@/util/detectOS'; -import { getArchitecture } from '@/util/getArchitecture'; -import { getBitness } from '@/util/getBitness'; +import { getHighEntropyValues } from '@/util/getHighEntropyValues'; type UserOSState = { - os: UserOS; - bitness: number; - architecture: string; + os: UserOS | 'LOADING'; + bitness: UserBitness | ''; + architecture: UserArchitecture | ''; }; const useDetectOS = () => { const [userOSState, setUserOSState] = useState({ os: 'LOADING', - bitness: 64, + bitness: '', architecture: '', }); @@ -26,22 +25,23 @@ const useDetectOS = () => { navigator.userAgent ); - // We immediately set the OS to LOADING, and then we update it with the detected OS. - // This is due to that initial render set within the state will indicate a mismatch from - // the server-side rendering versus what the initial state is from the client-side - setUserOSState(current => ({ ...current, os: detectOS() })); - - // We then update the bitness based on the detected OS and the user agent - getBitness().then((bitness = '64') => - setUserOSState(current => ({ - ...current, - bitness: bitness === '64' || uaIndicates64 ? 64 : 86, - })) - ); - - // We then update the architecture based on the detected OS - getArchitecture().then((architecture = '') => - setUserOSState(current => ({ ...current, architecture })) + // We attempt to get the high entropy values from the Browser and set the User OS State + // based from the values we get from the Browser, if it fails we fallback to the User Agent + // to determine the bitness and architecture of the User OS. + getHighEntropyValues(['bitness', 'architecture']).then( + ({ + // If there is no getHighEntropyValues API on the Browser or it failed to resolve + // we attempt to fallback to what the User Agent indicates + bitness = uaIndicates64 ? '64' : '32', + architecture = 'x86', + }) => { + setUserOSState(current => ({ + ...current, + os: detectOS(), + bitness: bitness as UserBitness, + architecture: architecture as UserArchitecture, + })); + } ); }, []); diff --git a/apps/site/next.mdx.use.client.mjs b/apps/site/next.mdx.use.client.mjs index 36394d4451a69..40b5959d455ca 100644 --- a/apps/site/next.mdx.use.client.mjs +++ b/apps/site/next.mdx.use.client.mjs @@ -4,14 +4,14 @@ import Blockquote from './components/Common/Blockquote'; import Button from './components/Common/Button'; import DownloadButton from './components/Downloads/DownloadButton'; import DownloadLink from './components/Downloads/DownloadLink'; -import BitnessDropdown from './components/Downloads/Release/BitnessDropdown'; import BlogPostLink from './components/Downloads/Release/BlogPostLink'; import ChangelogLink from './components/Downloads/Release/ChangelogLink'; -import ReleaseDownloadButton from './components/Downloads/Release/DownloadButton'; +import InstallationMethodDropdown from './components/Downloads/Release/InstallationMethodDropdown'; import LinkWithArrow from './components/Downloads/Release/LinkWithArrow'; import OperatingSystemDropdown from './components/Downloads/Release/OperatingSystemDropdown'; import PackageManagerDropdown from './components/Downloads/Release/PackageManagerDropdown'; import PlatformDropdown from './components/Downloads/Release/PlatformDropdown'; +import PrebuiltDownloadButtons from './components/Downloads/Release/PrebuiltDownloadButtons'; import ReleaseCodeBox from './components/Downloads/Release/ReleaseCodeBox'; import VersionDropdown from './components/Downloads/Release/VersionDropdown'; import Link from './components/Link'; @@ -46,17 +46,17 @@ export const clientMdxComponents = { // Renders a drop-down menu to select a version VersionDropdown: VersionDropdown, // Renders a drop-down menu to select a platform - PlatformDropdown: PlatformDropdown, + InstallationMethodDropdown: InstallationMethodDropdown, // Renders a drop-down menu to select a package manager PackageManagerDropdown: PackageManagerDropdown, // Renders a drop-down menu to select a bitness - BitnessDropdown: BitnessDropdown, + PlatformDropdown: PlatformDropdown, // Renders a drop-down menu to select an operating system OperatingSystemDropdown: OperatingSystemDropdown, // Renders a Blog Post Link for the selected release BlogPostLink: BlogPostLink, // Renders a Download Button for the selected release - DownloadButton: ReleaseDownloadButton, + PrebuiltDownloadButtons: PrebuiltDownloadButtons, // Renders a Release CodeBox ReleaseCodeBox: ReleaseCodeBox, // Renders a Changelog Modal Link Button diff --git a/apps/site/pages/en/download/current.mdx b/apps/site/pages/en/download/current.mdx index 8ecea635592a7..ee6cab110ddc2 100644 --- a/apps/site/pages/en/download/current.mdx +++ b/apps/site/pages/en/download/current.mdx @@ -5,15 +5,15 @@ title: Download Node.js®
-Get Node.js® for using with +Get Node.js® for using with -Or get a prebuilt Node.js® for running a architecture. +Or get a prebuilt Node.js® for running a architecture. - + diff --git a/apps/site/pages/en/download/index.mdx b/apps/site/pages/en/download/index.mdx index 8ecea635592a7..ee6cab110ddc2 100644 --- a/apps/site/pages/en/download/index.mdx +++ b/apps/site/pages/en/download/index.mdx @@ -5,15 +5,15 @@ title: Download Node.js®
-Get Node.js® for using with +Get Node.js® for using with -Or get a prebuilt Node.js® for running a architecture. +Or get a prebuilt Node.js® for running a architecture. - + diff --git a/apps/site/reducers/releaseReducer.ts b/apps/site/reducers/releaseReducer.ts index 6a0610889e963..dc24e4db7ea2e 100644 --- a/apps/site/reducers/releaseReducer.ts +++ b/apps/site/reducers/releaseReducer.ts @@ -3,16 +3,19 @@ import type { Dispatch } from 'react'; import type * as Types from '@/types/release'; export const releaseState: Types.ReleaseState = { + // The selected Node.js version to be downloaded + version: '', // The initial LOADING state is used to render a skeleton loader // Until the User's OS is detected (or failed to be detected) os: 'LOADING', - bitness: '', + // The detected User Platform from a combination of Bitness and Architecture platform: '', + // The selected installation method when not choosing an installer or prebuilt binary + installMethod: '', // The package manager field is always set by default to `NPM` // as that is the default package manager for the Node.js ecosystem // and the one mainly recommended by the project. packageManager: 'NPM', - version: '', }; export const getActions = ( @@ -20,21 +23,25 @@ export const getActions = ( ): Types.ReleaseDispatchActions => ({ setVersion: payload => dispatch({ type: 'SET_VERSION', payload }), setOS: payload => dispatch({ type: 'SET_OS', payload }), - setBitness: payload => dispatch({ type: 'SET_BITNESS', payload }), setPlatform: payload => dispatch({ type: 'SET_PLATFORM', payload }), + setInstallMethod: payload => + dispatch({ type: 'SET_INSTALL_METHOD', payload }), setPackageManager: payload => dispatch({ type: 'SET_MANAGER', payload }), }); -const reducer = (state: Types.ReleaseState, action: Types.ReleaseAction) => { +const reducer = ( + state: Types.ReleaseState, + action: Types.ReleaseAction +): Types.ReleaseState => { switch (action.type) { case 'SET_VERSION': return { ...state, version: action.payload }; case 'SET_OS': return { ...state, os: action.payload }; - case 'SET_BITNESS': - return { ...state, bitness: action.payload }; case 'SET_PLATFORM': return { ...state, platform: action.payload }; + case 'SET_INSTALL_METHOD': + return { ...state, installMethod: action.payload }; case 'SET_MANAGER': return { ...state, packageManager: action.payload }; default: diff --git a/apps/site/types/release.ts b/apps/site/types/release.ts index 226146d5951e9..3ac64a633d340 100644 --- a/apps/site/types/release.ts +++ b/apps/site/types/release.ts @@ -1,31 +1,33 @@ import type { DownloadSnippet } from '@/types/downloads'; import type { NodeRelease } from '@/types/releases'; -import type { UserOS } from '@/types/userOS'; +import type { UserOS, UserPlatform } from '@/types/userOS'; export type InstallationMethod = 'NVM' | 'FNM' | 'BREW' | 'DOCKER' | 'CHOCO'; export type PackageManager = 'NPM' | 'YARN' | 'PNPM'; +// Items with a pipe/default value mean that they are auto inferred +// during runtime and do not have necessarily a consistent initial value export interface ReleaseState { - os: UserOS; version: string; - bitness: string | number; - platform: InstallationMethod | ''; - packageManager: PackageManager | ''; + os: UserOS | 'LOADING'; + platform: UserPlatform | ''; + installMethod: InstallationMethod | ''; + packageManager: PackageManager; } export type ReleaseAction = - | { type: 'SET_OS'; payload: UserOS } | { type: 'SET_VERSION'; payload: string } - | { type: 'SET_BITNESS'; payload: string | number } - | { type: 'SET_PLATFORM'; payload: InstallationMethod | '' } - | { type: 'SET_MANAGER'; payload: PackageManager | '' }; + | { type: 'SET_OS'; payload: UserOS } + | { type: 'SET_PLATFORM'; payload: UserPlatform } + | { type: 'SET_INSTALL_METHOD'; payload: InstallationMethod } + | { type: 'SET_MANAGER'; payload: PackageManager }; export interface ReleaseDispatchActions { setVersion: (version: string) => void; setOS: (os: UserOS) => void; - setBitness: (bitness: string | number) => void; - setPlatform: (platform: InstallationMethod | '') => void; - setPackageManager: (packageManager: PackageManager | '') => void; + setPlatform: (bitness: UserPlatform) => void; + setInstallMethod: (installMethod: InstallationMethod) => void; + setPackageManager: (packageManager: PackageManager) => void; } export interface ReleasesContextType { diff --git a/apps/site/types/userOS.ts b/apps/site/types/userOS.ts index 3c3ea522e213d..39a3911e55b0e 100644 --- a/apps/site/types/userOS.ts +++ b/apps/site/types/userOS.ts @@ -1 +1,15 @@ -export type UserOS = 'MAC' | 'WIN' | 'LINUX' | 'AIX' | 'OTHER' | 'LOADING'; +export type UserOS = 'MAC' | 'WIN' | 'LINUX' | 'AIX' | 'OTHER'; + +export type UserBitness = '64' | '32'; + +export type UserArchitecture = 'arm' | 'x86'; + +export type UserPlatform = + | 'arm64' + | 'armv7l' + | 'ppc64le' + | 'ppc64' + | 's390x' + | 'ppc64' + | 'x64' + | 'x86'; diff --git a/apps/site/util/__tests__/getBitness.test.mjs b/apps/site/util/__tests__/getBitness.test.mjs deleted file mode 100644 index 9fe4feeba15f5..0000000000000 --- a/apps/site/util/__tests__/getBitness.test.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import { getBitness } from '@/util/getBitness'; - -describe('getBitness', () => { - it('returns the bitness value when available', async () => { - const mockGetHighEntropyValues = jest - .fn() - .mockResolvedValue({ bitness: 64 }); - - const mockNavigator = { - userAgentData: { getHighEntropyValues: mockGetHighEntropyValues }, - }; - - jest.spyOn(global, 'navigator', 'get').mockReturnValue(mockNavigator); - - const result = await getBitness(); - - expect(result).toBe(64); - expect(mockGetHighEntropyValues).toHaveBeenCalledWith(['bitness']); - - jest.restoreAllMocks(); - }); - - it('returns undefined when navigator.userAgentData or getHighEntropyValues is not available', async () => { - const mockNavigator = {}; - - jest.spyOn(global, 'navigator', 'get').mockReturnValue(mockNavigator); - - const result = await getBitness(); - - expect(result).toBeUndefined(); - - jest.restoreAllMocks(); - }); - - it('returns undefined when getHighEntropyValues fails', async () => { - const mockGetHighEntropyValues = jest - .fn() - .mockRejectedValue(new Error('Some error')); - - const mockNavigator = { - userAgentData: { getHighEntropyValues: mockGetHighEntropyValues }, - }; - - jest.spyOn(global, 'navigator', 'get').mockReturnValue(mockNavigator); - - const result = await getBitness(); - - expect(result).toBeUndefined(); - expect(mockGetHighEntropyValues).toHaveBeenCalledWith(['bitness']); - - jest.restoreAllMocks(); - }); -}); diff --git a/apps/site/util/__tests__/getUserBitnessByArchitecture.test.mjs b/apps/site/util/__tests__/getUserBitnessByArchitecture.test.mjs deleted file mode 100644 index 73af5a12e6b60..0000000000000 --- a/apps/site/util/__tests__/getUserBitnessByArchitecture.test.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { getUserBitnessByArchitecture } from '@/util/getUserBitnessByArchitecture'; - -describe('getUserBitnessByArchitecture', () => { - it('should return "arm64" for arm architecture and 64-bit', () => { - const result = getUserBitnessByArchitecture('arm', 64); - expect(result).toBe('arm64'); - }); - - it('should return the user bitness for other architectures', () => { - const result = getUserBitnessByArchitecture('x86', 32); - expect(result).toBe('32'); - }); -}); diff --git a/apps/site/util/downloadUtils.tsx b/apps/site/util/downloadUtils.tsx index 9db3209c1c940..61ff7ccba9937 100644 --- a/apps/site/util/downloadUtils.tsx +++ b/apps/site/util/downloadUtils.tsx @@ -6,15 +6,16 @@ import PackageManagerIcons from '@/components/Icons/PackageManager'; import PlatformIcons from '@/components/Icons/Platform'; import type { NodeReleaseStatus } from '@/types'; import type * as Types from '@/types/release'; -import type { UserOS } from '@/types/userOS'; +import type { UserOS, UserPlatform } from '@/types/userOS'; // This is a manual list of OS's that do not support/have a way of being installed // with an executable installer. This is used to disable the installer button. // Note: Windows has one tiny exception for x64 on Node.js versions < 4.0.0 -export const OS_NOT_SUPPORTING_INSTALLERS: Array = [ +export const OS_NOT_SUPPORTING_INSTALLERS: Array = [ 'LINUX', 'AIX', 'OTHER', + 'LOADING', ]; export enum OperatingSystemLabel { @@ -42,8 +43,8 @@ export enum PackageManagerLabel { type DownloadCompatibility = { os: Array; - platform: Array; - bitness: Array; + installMethod: Array; + platform: Array; semver: Array; releases: Array; }; @@ -92,32 +93,33 @@ export const parseCompat = < T extends DownloadDropdownItem, >( items: Array, - { os, platform, bitness, version, release }: Types.ReleaseContextType + { os, installMethod, platform, version, release }: Types.ReleaseContextType ): Array => { const satisfiesSemver = (semver: string) => satisfies(version, semver); - const supportsOS = (item: T) => item.compatibility.os?.includes(os) ?? true; + const supportsOS = (i: T['compatibility']) => + os === 'LOADING' || (i.os?.includes(os) ?? true); - const supportsPlatform = (item: T) => - item.compatibility.platform?.includes(platform) ?? true; + const supportsInstallMethod = (i: T['compatibility']) => + (installMethod === '' || i.installMethod?.includes(installMethod)) ?? true; - const supportsBitness = (item: T) => - item.compatibility.bitness?.includes(String(bitness)) ?? true; + const supportsPlatform = (i: T['compatibility']) => + platform === '' || (i.platform?.includes(platform) ?? true); - const supportsVersion = (item: T) => - item.compatibility.semver?.some(satisfiesSemver) ?? true; + const supportsVersion = (i: T['compatibility']) => + i.semver?.some(satisfiesSemver) ?? true; - const supportsRelease = (item: T) => - item.compatibility.releases?.includes(release.status) ?? true; + const supportsRelease = (i: T['compatibility']) => + i.releases?.includes(release.status) ?? true; return items.map(item => ({ ...item, disabled: - !supportsOS(item) || - !supportsPlatform(item) || - !supportsBitness(item) || - !supportsVersion(item) || - !supportsRelease(item), + !supportsOS(item.compatibility) || + !supportsInstallMethod(item.compatibility) || + !supportsPlatform(item.compatibility) || + !supportsVersion(item.compatibility) || + !supportsRelease(item.compatibility), })); }; @@ -143,7 +145,7 @@ export const OPERATING_SYSTEMS: Array> = [ { label: OperatingSystemLabel.AIX, value: 'AIX', - compatibility: { platform: [''] }, + compatibility: { installMethod: [] }, iconImage: , }, ]; @@ -223,19 +225,19 @@ export const PACKAGE_MANAGERS: Array< }, ]; -export const ARCHITECTURES: Record< +export const PLATFORMS: Record< UserOS, - Array> + Array> > = { WIN: [ { label: 'x64', - value: '64', + value: 'x64', compatibility: {}, }, { label: 'x86', - value: '86', + value: 'x86', compatibility: { semver: ['< 23.0.0'] }, }, { @@ -247,7 +249,7 @@ export const ARCHITECTURES: Record< MAC: [ { label: 'x64', - value: '64', + value: 'x64', compatibility: {}, }, { @@ -259,7 +261,7 @@ export const ARCHITECTURES: Record< LINUX: [ { label: 'x64', - value: '64', + value: 'x64', compatibility: {}, }, { @@ -291,5 +293,4 @@ export const ARCHITECTURES: Record< }, ], OTHER: [], - LOADING: [], }; diff --git a/apps/site/util/getArchitecture.ts b/apps/site/util/getArchitecture.ts deleted file mode 100644 index 1601e3cb828df..0000000000000 --- a/apps/site/util/getArchitecture.ts +++ /dev/null @@ -1,19 +0,0 @@ -/// - -export const getArchitecture = async () => { - // This is necessary to detect Windows 11 on Edge. - // [MDN](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues) - // [MSFT](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11) - if (typeof navigator?.userAgentData?.getHighEntropyValues === 'function') { - const entropyValues = navigator.userAgentData.getHighEntropyValues([ - 'architecture', - ]); - - // Apparently in some cases this is not a Promise, we can Promisify it. - return Promise.resolve(entropyValues) - .then(({ architecture }) => architecture) - .catch(() => undefined); - } - - return undefined; -}; diff --git a/apps/site/util/getBitness.ts b/apps/site/util/getBitness.ts deleted file mode 100644 index 55f9509e86635..0000000000000 --- a/apps/site/util/getBitness.ts +++ /dev/null @@ -1,19 +0,0 @@ -/// - -export const getBitness = async () => { - // This is necessary to detect Windows 11 on Edge. - // [MDN](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues) - // [MSFT](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11) - if (typeof navigator?.userAgentData?.getHighEntropyValues === 'function') { - const entropyValues = navigator.userAgentData.getHighEntropyValues([ - 'bitness', - ]); - - // Apparently in some cases this is not a Promise, we can Promisify it. - return Promise.resolve(entropyValues) - .then(({ bitness }) => bitness) - .catch(() => undefined); - } - - return undefined; -}; diff --git a/apps/site/util/getHighEntropyValues.ts b/apps/site/util/getHighEntropyValues.ts new file mode 100644 index 0000000000000..8a066490bb666 --- /dev/null +++ b/apps/site/util/getHighEntropyValues.ts @@ -0,0 +1,33 @@ +/// + +// This method is used to retrieve a User's platform based on their architecture and bitness. +// @see https://wicg.github.io/ua-client-hints/#http-ua-hints +export const getHighEntropyValues = async >( + hints: T +) => { + let result: UADataValues = {}; + + // This is necessary to detect Windows 11 on Edge. + // [MDN](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues) + // [MSFT](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11) + if (typeof navigator?.userAgentData?.getHighEntropyValues === 'function') { + const entropyValues = navigator.userAgentData.getHighEntropyValues(hints); + + // Apparently in some cases this is not a Promise, we can Promisify it. + result = await Promise.resolve(entropyValues).catch( + // Fallback to an empty object if the Promise fails. + () => ({}) as UADataValues + ); + } + + const mappedResult = hints.map(hint => [ + hint, + // Since the values could come as empty string in some situations + // we should check if the hint is present in the result and if it's not an empty string. + hint in result && result[hint] ? result[hint] : undefined, + ]); + + return Object.fromEntries(mappedResult) as { + [K in T[number]]: UADataValues[K]; + }; +}; diff --git a/apps/site/util/getNodeDownloadUrl.ts b/apps/site/util/getNodeDownloadUrl.ts index 12ad77e8e02c9..9775c4c675188 100644 --- a/apps/site/util/getNodeDownloadUrl.ts +++ b/apps/site/util/getNodeDownloadUrl.ts @@ -1,12 +1,14 @@ import { DIST_URL } from '@/next.constants.mjs'; -import type { UserOS } from '@/types/userOS'; +import type { UserOS, UserPlatform } from '@/types/userOS'; + +type DownloadKind = 'installer' | 'binary' | 'source'; export const getNodeDownloadUrl = ( versionWithPrefix: string, - os: UserOS, - bitness: string | number, - kind: 'installer' | 'binary' | 'source' = 'installer' -): string => { + os: UserOS | 'LOADING', + platform: UserPlatform = 'x64', + kind: DownloadKind = 'installer' +) => { const baseURL = `${DIST_URL}${versionWithPrefix}`; if (kind === 'source') { @@ -21,8 +23,8 @@ export const getNodeDownloadUrl = ( } // Prepares a downloadable Node.js link for the ARM64 platform - if (typeof bitness === 'string') { - return `${baseURL}/node-${versionWithPrefix}-darwin-${bitness}.tar.gz`; + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-darwin-${platform}.tar.gz`; } // Prepares a downloadable Node.js link for the x64 platform. @@ -32,27 +34,27 @@ export const getNodeDownloadUrl = ( case 'WIN': { if (kind === 'installer') { // Prepares a downloadable Node.js installer link for the ARM platforms - if (typeof bitness === 'string') { - return `${baseURL}/node-${versionWithPrefix}-${bitness}.msi`; + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-${platform}.msi`; } // Prepares a downloadable Node.js installer link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-x${bitness}.msi`; + return `${baseURL}/node-${versionWithPrefix}-x${platform}.msi`; } // Prepares a downloadable Node.js link for the ARM64 platform - if (typeof bitness === 'string') { - return `${baseURL}/node-${versionWithPrefix}-win-${bitness}.zip`; + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-win-${platform}.zip`; } // Prepares a downloadable Node.js link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-win-x${bitness}.zip`; + return `${baseURL}/node-${versionWithPrefix}-win-x${platform}.zip`; } case 'LINUX': // Prepares a downloadable Node.js link for the ARM platforms such as // ARMv7 and ARMv8 - if (typeof bitness === 'string') { - return `${baseURL}/node-${versionWithPrefix}-linux-${bitness}.tar.xz`; + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-linux-${platform}.tar.xz`; } // Prepares a downloadable Node.js link for the x64 platform. @@ -61,8 +63,8 @@ export const getNodeDownloadUrl = ( return `${baseURL}/node-${versionWithPrefix}-linux-x64.tar.xz`; case 'AIX': // Prepares a downloadable Node.js link for AIX - if (typeof bitness === 'string') { - return `${baseURL}/node-${versionWithPrefix}-aix-${bitness}.tar.gz`; + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-aix-${platform}.tar.gz`; } return `${baseURL}/node-${versionWithPrefix}-aix-ppc64.tar.gz`; diff --git a/apps/site/util/getUserBitnessByArchitecture.ts b/apps/site/util/getUserBitnessByArchitecture.ts deleted file mode 100644 index 4e5edfa2cf2c3..0000000000000 --- a/apps/site/util/getUserBitnessByArchitecture.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const getUserBitnessByArchitecture = ( - userArchitecture: string, - userBitness: number | string -) => { - if (userArchitecture === 'arm' && userBitness === 64) { - return 'arm64'; - } - - return String(userBitness); -}; diff --git a/apps/site/util/getUserPlatform.ts b/apps/site/util/getUserPlatform.ts new file mode 100644 index 0000000000000..3341cccca5286 --- /dev/null +++ b/apps/site/util/getUserPlatform.ts @@ -0,0 +1,15 @@ +import type * as Types from '@/types/userOS'; + +// This method is used to retrieve a User's platform based on their architecture and bitness. +// Note: This is only used for automatic Platform detection for supported platforms by using `useDetectOS` +// @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues +export const getUserPlatform = ( + userArchitecture: Types.UserArchitecture | '', + userBitness: Types.UserBitness | '' +): Types.UserPlatform => { + if (userArchitecture === 'arm' && userBitness === '64') { + return 'arm64'; + } + + return userBitness === '64' ? 'x64' : 'x86'; +}; diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index 776807a0f9b6f..37af22658066d 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -284,10 +284,10 @@ "binary": "Standalone Binary (.{extension})" }, "dropdown": { - "bitness": "Bitness", + "platform": "Platform", "os": "Operating System", "version": "Version", - "platform": "Platform", + "installMethod": "Install Method", "packageManager": "Package Manager", "unknown": "Unknown", "platformGroups": { From 3acca715aad6ecacd384a7025fd6e65becccee5d Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Sat, 28 Dec 2024 02:10:05 +0000 Subject: [PATCH 4/5] chore: final improvements before PR merge --- .../Downloads/Release/PlatformDropdown.tsx | 6 +-- .../Release/PrebuiltDownloadButtons.tsx | 49 +++++++------------ .../Downloads/Release/ReleaseCodeBox.tsx | 2 +- .../__tests__/useDetectOS.test.mjs | 22 ++++----- apps/site/hooks/react-client/useDetectOS.ts | 6 ++- .../__tests__/matterProvider.test.mjs | 9 ++-- apps/site/reducers/releaseReducer.ts | 3 +- 7 files changed, 40 insertions(+), 57 deletions(-) diff --git a/apps/site/components/Downloads/Release/PlatformDropdown.tsx b/apps/site/components/Downloads/Release/PlatformDropdown.tsx index 419cb5f6b9a28..6acb57beb0872 100644 --- a/apps/site/components/Downloads/Release/PlatformDropdown.tsx +++ b/apps/site/components/Downloads/Release/PlatformDropdown.tsx @@ -57,12 +57,12 @@ const PlatformDropdown: FC = () => { ); return ( - + values={parsedPlatforms} - loading={release.os === 'LOADING'} + defaultValue={release.platform !== '' ? release.platform : undefined} + loading={release.os === 'LOADING' || release.platform === ''} placeholder={t('layouts.download.dropdown.unknown')} ariaLabel={t('layouts.download.dropdown.installMethod')} - defaultValue={release.platform} onChange={platform => platform && release.setPlatform(platform)} className="min-w-20" inline={true} diff --git a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx index 8aee25d695491..8be309c5f46da 100644 --- a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx +++ b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx @@ -2,7 +2,7 @@ import { CloudArrowDownIcon } from '@heroicons/react/24/outline'; import { useTranslations } from 'next-intl'; -import { useContext, useMemo } from 'react'; +import { useContext } from 'react'; import type { FC } from 'react'; import Button from '@/components/Common/Button'; @@ -21,52 +21,37 @@ const PrebuiltDownloadButtons: FC = () => { const t = useTranslations(); const { release, os, platform } = useContext(ReleaseContext); - const { installerUrl, installerExt, binaryUrl, binaryExt } = useMemo(() => { - const installerUrl = getNodeDownloadUrl( - release.versionWithPrefix, - os, - platform || undefined, - 'installer' - ); + const installerUrl = platform + ? getNodeDownloadUrl(release.versionWithPrefix, os, platform, 'installer') + : ''; - const binaryUrl = getNodeDownloadUrl( - release.versionWithPrefix, - os, - platform || undefined, - 'binary' - ); - - return { - installerUrl, - binaryUrl, - installerExt: getExtension(installerUrl), - binaryExt: getExtension(binaryUrl), - }; - }, [os, platform, release.versionWithPrefix]); + const binaryUrl = platform + ? getNodeDownloadUrl(release.versionWithPrefix, os, platform, 'binary') + : ''; return (
- - - +
diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 105d91a53c4e5..8174d334c12bf 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -100,7 +100,7 @@ const ReleaseCodeBox: FC = () => { )} - + {parsedSnippets} diff --git a/apps/site/hooks/react-client/__tests__/useDetectOS.test.mjs b/apps/site/hooks/react-client/__tests__/useDetectOS.test.mjs index 214ab70a7c88a..41e683a44647e 100644 --- a/apps/site/hooks/react-client/__tests__/useDetectOS.test.mjs +++ b/apps/site/hooks/react-client/__tests__/useDetectOS.test.mjs @@ -23,7 +23,7 @@ describe('useDetectOS', () => { value: { userAgent: windowsUserAgent, userAgentData: { - getHighEntropyValues: jest.fn().mockResolvedValue({ bitness: 64 }), + getHighEntropyValues: jest.fn().mockResolvedValue({ bitness: '64' }), }, }, writable: true, @@ -34,17 +34,15 @@ describe('useDetectOS', () => { await waitFor(() => { expect(result.current).toStrictEqual({ os: 'WIN', - bitness: 64, - architecture: '', + bitness: '64', + architecture: 'x86', }); }); }); it('should detect WIN OS and 64 bitness from user agent', async () => { Object.defineProperty(global, 'navigator', { - value: { - userAgent: windowsUserAgent, - }, + value: { userAgent: windowsUserAgent }, writable: true, }); @@ -53,17 +51,15 @@ describe('useDetectOS', () => { await waitFor(() => { expect(result.current).toStrictEqual({ os: 'WIN', - bitness: 64, - architecture: '', + bitness: '64', + architecture: 'x86', }); }); }); it('should detect MAC OS and default bitness', async () => { Object.defineProperty(global, 'navigator', { - value: { - userAgent: macUserAgent, - }, + value: { userAgent: macUserAgent }, writable: true, }); @@ -72,8 +68,8 @@ describe('useDetectOS', () => { await waitFor(() => { expect(result.current).toStrictEqual({ os: 'MAC', - bitness: 64, - architecture: '', + bitness: '32', + architecture: 'x86', }); }); }); diff --git a/apps/site/hooks/react-client/useDetectOS.ts b/apps/site/hooks/react-client/useDetectOS.ts index 01d5e50f862ef..68854dee2abf5 100644 --- a/apps/site/hooks/react-client/useDetectOS.ts +++ b/apps/site/hooks/react-client/useDetectOS.ts @@ -25,6 +25,11 @@ const useDetectOS = () => { navigator.userAgent ); + // We immediately set the OS to LOADING, and then we update it with the detected OS. + // This is due to that initial render set within the state will indicate a mismatch from + // the server-side rendering versus what the initial state is from the client-side + setUserOSState(current => ({ ...current, os: detectOS() })); + // We attempt to get the high entropy values from the Browser and set the User OS State // based from the values we get from the Browser, if it fails we fallback to the User Agent // to determine the bitness and architecture of the User OS. @@ -37,7 +42,6 @@ const useDetectOS = () => { }) => { setUserOSState(current => ({ ...current, - os: detectOS(), bitness: bitness as UserBitness, architecture: architecture as UserArchitecture, })); diff --git a/apps/site/providers/__tests__/matterProvider.test.mjs b/apps/site/providers/__tests__/matterProvider.test.mjs index 4114da6537eeb..bfc0aba9faa74 100644 --- a/apps/site/providers/__tests__/matterProvider.test.mjs +++ b/apps/site/providers/__tests__/matterProvider.test.mjs @@ -8,18 +8,15 @@ const mockContext = { headings: [], readingTime: { text: '', minutes: 0, time: 0, words: 0 }, filename: '', - // @TODO: For some reason the initial value of the provider is flipping between - // LOADING and OTHER, although the initial state is LOADING, render() might be doing more - // than just initial rendering; This requires more investigation. - os: expect.any(String), + os: 'LOADING', architecture: '', - bitness: 64, + bitness: '', }; describe('MatterProvider', () => { it('renders the provider with the provided context value', () => { render( - + {value => { expect(value).toEqual(mockContext); diff --git a/apps/site/reducers/releaseReducer.ts b/apps/site/reducers/releaseReducer.ts index dc24e4db7ea2e..6b475f170e92c 100644 --- a/apps/site/reducers/releaseReducer.ts +++ b/apps/site/reducers/releaseReducer.ts @@ -9,7 +9,8 @@ export const releaseState: Types.ReleaseState = { // Until the User's OS is detected (or failed to be detected) os: 'LOADING', // The detected User Platform from a combination of Bitness and Architecture - platform: '', + // We set the default value to `x64` as it is the most common platform for modern systems + platform: 'x64', // The selected installation method when not choosing an installer or prebuilt binary installMethod: '', // The package manager field is always set by default to `NPM` From 7dd604c4a74143bbe5c1564b7b552836f9df3646 Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Sat, 28 Dec 2024 03:02:01 +0000 Subject: [PATCH 5/5] chore: react optimizations and profiling optimizations --- apps/site/components/Common/Select/index.tsx | 57 ++++++++++-------- .../Downloads/Release/ReleaseCodeBox.tsx | 59 +++++++++++-------- apps/site/components/JSX/CodeBox/index.tsx | 38 ------------ apps/site/components/withLayout.tsx | 2 +- apps/site/layouts/GlowingBackdrop.tsx | 2 +- 5 files changed, 69 insertions(+), 89 deletions(-) delete mode 100644 apps/site/components/JSX/CodeBox/index.tsx diff --git a/apps/site/components/Common/Select/index.tsx b/apps/site/components/Common/Select/index.tsx index 1428b39a0f487..b2abbbd1b848e 100644 --- a/apps/site/components/Common/Select/index.tsx +++ b/apps/site/components/Common/Select/index.tsx @@ -86,6 +86,37 @@ const Select = ({ [mappedValues, value] ); + const memoizedMappedValues = useMemo(() => { + return mappedValues.map(({ label, items }, key) => ( + + {label && ( + + {label} + + )} + + {items.map(({ value, label, iconImage, disabled }) => ( + + + {iconImage} + {label} + + + ))} + + )); + // We explicitly want to recalculate these values only when the values themselves changed + // This is to prevent re-rendering and re-calcukating the values on every render + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(values)]); + // Both change the internal state and emit the change event const handleChange = (value: T) => { setValue(value); @@ -141,31 +172,7 @@ const Select = ({ - {mappedValues.map(({ label, items }, key) => ( - - {label && ( - - {label} - - )} - - {items.map(({ value, label, iconImage, disabled }) => ( - - - {iconImage} - {label} - - - ))} - - ))} + {memoizedMappedValues} diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 8174d334c12bf..46701c5b17fa6 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -5,13 +5,14 @@ import type { FC } from 'react'; import { useContext, useMemo } from 'react'; import AlertBox from '@/components/Common/AlertBox'; +import CodeBox from '@/components/Common/CodeBox'; import Skeleton from '@/components/Common/Skeleton'; -import JSXCodeBox from '@/components/JSX/CodeBox'; import Link from '@/components/Link'; import { createSval } from '@/next.jsx.compiler.mjs'; import { ReleaseContext, ReleasesContext } from '@/providers/releaseProvider'; import type { ReleaseContextType } from '@/types/release'; import { INSTALL_METHODS } from '@/util/downloadUtils'; +import { highlightToHtml } from '@/util/getHighlighter'; import LinkWithArrow from './LinkWithArrow'; @@ -35,46 +36,56 @@ const parseSnippet = (s: string, releaseContext: ReleaseContextType) => { const ReleaseCodeBox: FC = () => { const { snippets } = useContext(ReleasesContext); - const { - installMethod: platform, - os, - packageManager, - release, - } = useContext(ReleaseContext); + const { installMethod, os, packageManager, release } = + useContext(ReleaseContext); const t = useTranslations(); // Retrieves the current platform (Dropdown Item) based on the selected platform value const currentPlatform = useMemo( - () => INSTALL_METHODS.find(({ value }) => value === platform), - [platform] + () => INSTALL_METHODS.find(({ value }) => value === installMethod), + [installMethod] ); // Parses the snippets based on the selected platform, package manager, and release context const parsedSnippets = useMemo(() => { // Retrieves a snippet for the given Installation Method (aka Platform) - const platformSnippet = snippets.find( - s => s.name === platform.toLowerCase() + const installMethodSnippet = snippets.find( + ({ name }) => name === installMethod.toLowerCase() ); // Retrieves a snippet for the given Package Manager to be bundled with the Platform snippet const packageManagerSnippet = snippets.find( - s => s.name === packageManager.toLowerCase() + ({ name }) => name === packageManager.toLowerCase() ); - return parseSnippet( - // Bundles the Platform and Package Manager snippets - `${platformSnippet?.content ?? ''}\n${packageManagerSnippet?.content ?? ''}`, - // Passes a partial state of only the things we need to the parser - { release, installMethod: platform, os } as ReleaseContextType - ); - }, [snippets, release, platform, os, packageManager]); + // Prevents numerous recalculations of `sval` and `Shiki` when not necessary + // As we only want to parse the snippets when both the Platform and Package Manager snippets are available + if (installMethodSnippet && packageManagerSnippet) { + const content = parseSnippet( + // Bundles the Platform and Package Manager snippets + `${installMethodSnippet.content}\n${packageManagerSnippet.content}`, + // Passes a partial state of only the things we need to the parser + { release, os } as ReleaseContextType + ); + + // We use Shikis's `hast-util-to-html` to convert the highlighted code into plain HTML (Pretty much using Rehype) + // This is actually faster than using `hast-util-to-jsx-runtime` and then rendering the JSX + // As it requires React's runtime to interpolate and build these components dynamically + // Which also leads to a lot o GC being emitted. (Tested via Profiling) + return highlightToHtml(content, os === 'WIN' ? 'ps1' : 'bash'); + } + + return ''; + // Only change to these specific properties which are relevant for the re-rendering of the CodeBox + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [release.versionWithPrefix, installMethod, os, packageManager]); // Determines the code language based on the OS - const codeLanguage = os === 'WIN' ? 'ps1' : 'bash'; + const displayName = os === 'WIN' ? 'PowerShell' : 'Bash'; // Determines if the code box should render the skeleton loader - const renderSkeleton = os === 'LOADING' || platform === ''; + const renderSkeleton = os === 'LOADING' || installMethod === ''; // Defines fallbacks for the currentPlatform object const { @@ -100,9 +111,9 @@ const ReleaseCodeBox: FC = () => { )} - - {parsedSnippets} - + + + diff --git a/apps/site/components/JSX/CodeBox/index.tsx b/apps/site/components/JSX/CodeBox/index.tsx deleted file mode 100644 index ddc2c8643e1e9..0000000000000 --- a/apps/site/components/JSX/CodeBox/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import classNames from 'classnames'; -import { toJsxRuntime } from 'hast-util-to-jsx-runtime'; -import type { FC, PropsWithChildren } from 'react'; - -import MDXCodeBox from '@/components/MDX/CodeBox'; -import { reactRuntime } from '@/next.mdx.compiler.mjs'; -import { highlightToHast } from '@/util/getHighlighter'; - -type CodeBoxProps = { - language: string; - showCopyButton?: boolean; - className?: string; -}; - -const CodeBox: FC> = ({ - children, - language, - showCopyButton, - className, -}) => { - const highlighted = highlightToHast(String(children), language); - - return toJsxRuntime(highlighted, { - ...reactRuntime, - components: { - pre: ({ children }) => ( - - {children} - - ), - }, - }); -}; - -export default CodeBox; diff --git a/apps/site/components/withLayout.tsx b/apps/site/components/withLayout.tsx index 23eab80e4efd6..0c4a1638ee5b3 100644 --- a/apps/site/components/withLayout.tsx +++ b/apps/site/components/withLayout.tsx @@ -12,7 +12,7 @@ import type { Layouts } from '@/types'; const layouts = { about: AboutLayout, - home: props => , + home: GlowingBackdropLayout, learn: LearnLayout, page: DefaultLayout, 'blog-post': PostLayout, diff --git a/apps/site/layouts/GlowingBackdrop.tsx b/apps/site/layouts/GlowingBackdrop.tsx index 2457baf8e0877..377eac2e18903 100644 --- a/apps/site/layouts/GlowingBackdrop.tsx +++ b/apps/site/layouts/GlowingBackdrop.tsx @@ -12,7 +12,7 @@ type GlowingBackdropLayoutProps = PropsWithChildren<{ }>; const GlowingBackdropLayout: FC = ({ - kind, + kind = 'home', children, }) => ( <>