Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f7fff39
disabled sheet overlay for performance improvements
pettinarip Aug 19, 2025
baa3078
calc filtered locales outside of the component
pettinarip Aug 19, 2025
bfc7d85
disable dialog overlay for performance improvements
pettinarip Aug 20, 2025
b965e5d
reorder nav to enable server rendering
pettinarip Aug 20, 2025
9f13310
refactor mobile menu to implement rsc as much as possible
pettinarip Aug 20, 2025
9a753de
refactor MenuFooter to be server rendered
pettinarip Aug 20, 2025
38c1c6d
refactor the LanguagePicker to precompute the languages list on the s…
pettinarip Aug 21, 2025
a0151bf
add navigation links lib
pettinarip Aug 22, 2025
aecae90
moblie menu
pettinarip Aug 22, 2025
74e00e1
remove back button
pettinarip Aug 22, 2025
7c0d78e
cleanup
pettinarip Aug 22, 2025
24db038
refactor mobile nav
pettinarip Aug 25, 2025
5bb9623
compute progress on the server
pettinarip Aug 25, 2025
65ae384
use collapsible instead of accordion
pettinarip Aug 28, 2025
5aaaa6d
active link styles
pettinarip Aug 28, 2025
849c001
track matomo events
pettinarip Aug 28, 2025
56e96bd
nav: lazy render & loading skeletons
pettinarip Aug 28, 2025
2049df8
hide sheet overlay only for the mobile menu
pettinarip Aug 28, 2025
6b75608
implement SheetDismiss to close menu when a link is clicked
pettinarip Aug 28, 2025
00db348
Merge branch 'staging' into optimize-nav
pettinarip Aug 28, 2025
19f73bd
cleanup duplicated code
pettinarip Aug 28, 2025
f0638e5
create a new sheet component to close on navigation
pettinarip Aug 28, 2025
fa9099e
refactor lang picker to share code between desktop and mobile versions
pettinarip Aug 29, 2025
4fd1b18
remove code duplication for the nav links
pettinarip Aug 29, 2025
2699afd
use intl navigation hooks
pettinarip Aug 29, 2025
59b9d46
fix intl in sc
pettinarip Aug 29, 2025
d007c11
remove unnecessary p tags
pettinarip Aug 29, 2025
0b94574
fix rtl support in mobile menu tab content
pettinarip Aug 29, 2025
ba34098
fix hydration issue with media queries
pettinarip Aug 30, 2025
75d3c7e
update openLanguagePickerMobile function with correct test id
pettinarip Sep 1, 2025
afec995
move localeToDisplayInfo function to lib
pettinarip Sep 1, 2025
7fd2a85
cleanup redundant code component
pettinarip Sep 1, 2025
f8f3f5e
reorg menu footer buttons and replace the serach with menu button
pettinarip Sep 1, 2025
92ffc7f
highlight selected footer button in menu
pettinarip Sep 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default async function LocaleLayout({
// Enable static rendering
setRequestLocale(locale)

const allMessages = await getMessages({ locale })
const allMessages = await getMessages()
const messages = pick(allMessages, "common")

const lastDeployDate = getLastDeployDate()
Expand Down
1 change: 1 addition & 0 deletions app/[locale]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
if (!LOCALES_CODES.includes(locale)) return notFound()

setRequestLocale(locale)

const t = await getTranslations({ locale, namespace: "page-index" })
const tCommon = await getTranslations({ locale, namespace: "common" })
const { direction: dir, isRtl } = getDirection(locale)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-compose-refs": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
Expand Down
60 changes: 60 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions src/components/ClientOnly.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client"

import { type ReactNode } from "react"

import { useIsClient } from "@/hooks/useIsClient"

type ClientOnlyProps = {
children: ReactNode
fallback?: ReactNode
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't block, but perhaps we rename this to loading for consistency with other loading components?

Copy link
Copy Markdown
Member Author

@pettinarip pettinarip Sep 3, 2025

Choose a reason for hiding this comment

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

Hmm, but the intention of fallback is that it can be anything (including null). On the other hand, loading narrows the scope, leading developers to think they should only use loading UIs, which isn’t correct.

}

const ClientOnly = ({ children, fallback = null }: ClientOnlyProps) => {
const isClient = useIsClient()

if (!isClient) return <>{fallback}</>
return <>{children}</>
}

export default ClientOnly
59 changes: 59 additions & 0 deletions src/components/LanguagePicker/Desktop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client"

import type { LocaleDisplayInfo } from "@/lib/types"

import { cn } from "@/lib/utils/cn"

import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"

import LanguagePicker from "."

import { useDisclosure } from "@/hooks/useDisclosure"
import { useEventListener } from "@/hooks/useEventListener"

type DesktopLanguagePickerProps = {
children: React.ReactNode
languages: LocaleDisplayInfo[]
className?: string
}

const DesktopLanguagePicker = ({
children,
languages,
className,
}: DesktopLanguagePickerProps) => {
const { isOpen, setValue, onClose, onOpen } = useDisclosure()

/**
* Adds a keydown event listener to focus filter input (\).
* @param {string} event - The keydown event.
*/
useEventListener("keydown", (e) => {
if (e.key !== "\\" || e.metaKey || e.ctrlKey) return
e.preventDefault()
onOpen()
})

return (
<Popover open={isOpen} onOpenChange={setValue}>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent
align="end"
className={cn(
"flex w-[320px] flex-col bg-background-highlight p-0",
className
)}
>
<LanguagePicker
className="max-h-[calc(100vh-12rem)]"
languages={languages}
onSelect={onClose}
onNoResultsClose={onClose}
onTranslationProgramClick={onClose}
/>
</PopoverContent>
</Popover>
)
}

export default DesktopLanguagePicker
55 changes: 55 additions & 0 deletions src/components/LanguagePicker/LanguagePickerFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useLocale } from "next-intl"

import type { LocaleDisplayInfo } from "@/lib/types"

import { ButtonLink } from "@/components/ui/buttons/Button"

import { DEFAULT_LOCALE } from "@/lib/constants"

import { useTranslation } from "@/hooks/useTranslation"

type LanguagePickerFooterProps = {
intlLanguagePreference?: LocaleDisplayInfo
onTranslationProgramClick: () => void
}

const LanguagePickerFooter = ({
intlLanguagePreference,
onTranslationProgramClick,
}: LanguagePickerFooterProps) => {
const { t } = useTranslation("common")
const locale = useLocale()
return (
<div className="sticky bottom-0 flex border-t-2 border-primary bg-primary-low-contrast p-0 pb-1 pt-1">
<div className="flex w-full items-center justify-between px-4">
<div className="flex min-w-0 flex-col items-start">
{locale === DEFAULT_LOCALE ? (
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-bold text-body">
{intlLanguagePreference
? `${t("page-languages-translate-cta-title")} ${t(`language-${intlLanguagePreference.localeOption}`)}`
: "Translate ethereum.org"}
</p>
) : (
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-bold text-body">
{t("page-languages-translate-cta-title")}{" "}
{t(`language-${locale}`)}
</p>
)}
<p className="text-xs text-body">
{t("page-languages-recruit-community")}
</p>
</div>
<ButtonLink
className="text-nowrap"
href="/contributing/translation-program/"
size="sm"
onClick={onTranslationProgramClick}
>
{t("get-involved")}
</ButtonLink>
</div>
</div>
)
}

export default LanguagePickerFooter
85 changes: 85 additions & 0 deletions src/components/LanguagePicker/LanguagePickerMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { LocaleDisplayInfo } from "@/lib/types"

import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandList,
} from "../ui/command"

import MenuItem from "./MenuItem"
import NoResultsCallout from "./NoResultsCallout"

import { useTranslation } from "@/hooks/useTranslation"

type LanguagePickerMenuProps = {
className?: string
languages: LocaleDisplayInfo[]
onClose: () => void
onSelect: (value: string) => void
}

const LanguagePickerMenu = ({
className,
languages,
onClose,
onSelect,
}: LanguagePickerMenuProps) => {
const { t } = useTranslation("common")

return (
<Command
className={className}
filter={(value: string, search: string) => {
const item = languages.find((name) => name.localeOption === value)

if (!item) return 0

const { localeOption, sourceName, targetName, englishName } = item

if (
(localeOption + sourceName + targetName + englishName)
.toLowerCase()
.includes(search.toLowerCase())
) {
return 1
}

return 0
}}
>
<div className="text-xs text-body-medium">
{t("page-languages-filter-label")}{" "}
<span className="lowercase">
({languages.length} {t("common:languages")})
</span>
</div>

<CommandInput
placeholder={t("page-languages-filter-placeholder")}
className="h-9"
kbdShortcut="\"
data-testid="language-filter-input"
/>

<CommandList className="max-h-full">
<CommandEmpty className="py-0 text-left text-base">
<NoResultsCallout onClose={onClose} />
</CommandEmpty>
<CommandGroup className="p-0">
{languages.map((displayInfo) => (
<MenuItem
key={"item-" + displayInfo.localeOption}
displayInfo={displayInfo}
onSelect={onSelect}
data-testid={`language-option-${displayInfo.localeOption}`}
/>
))}
</CommandGroup>
</CommandList>
</Command>
)
}

export default LanguagePickerMenu
15 changes: 2 additions & 13 deletions src/components/LanguagePicker/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,13 @@ const MenuItem = ({ displayInfo, ...props }: ItemProps) => {
sourceName,
targetName,
approvalProgress,
wordsApproved,
progress,
words,
} = displayInfo
const { t } = useTranslation("common")
const locale = useLocale()
const isCurrent = localeOption === locale

const getProgressInfo = (approvalProgress: number, wordsApproved: number) => {
const percentage = new Intl.NumberFormat(locale!, {
style: "percent",
}).format(approvalProgress / 100)
const progress =
approvalProgress === 0 ? "<" + percentage.replace("0", "1") : percentage
const words = new Intl.NumberFormat(locale!).format(wordsApproved)
return { progress, words }
}

const { progress, words } = getProgressInfo(approvalProgress, wordsApproved)

return (
<CommandItem
value={localeOption}
Expand Down
Loading
Loading