|
| 1 | +"use client" |
| 2 | + |
| 3 | +import { useRef } from "react" |
| 4 | +import dynamic from "next/dynamic" |
| 5 | +import { useLocale } from "next-intl" |
| 6 | +import { BsTranslate } from "react-icons/bs" |
| 7 | + |
| 8 | +import SearchButton from "@/components/Search/SearchButton" |
| 9 | +import SearchInputButton from "@/components/Search/SearchInputButton" |
| 10 | +import { Button } from "@/components/ui/buttons/Button" |
| 11 | +import { Skeleton } from "@/components/ui/skeleton" |
| 12 | + |
| 13 | +import { DESKTOP_LANGUAGE_BUTTON_NAME } from "@/lib/constants" |
| 14 | + |
| 15 | +import { useNavigation } from "../useNavigation" |
| 16 | +import { useThemeToggle } from "../useThemeToggle" |
| 17 | + |
| 18 | +import { useBreakpointValue } from "@/hooks/useBreakpointValue" |
| 19 | +import { useTranslation } from "@/hooks/useTranslation" |
| 20 | + |
| 21 | +const Menu = dynamic(() => import("../Menu"), { |
| 22 | + ssr: false, |
| 23 | + loading: () => ( |
| 24 | + <div className="me-8 flex w-full items-center gap-10 px-6 max-md:hidden"> |
| 25 | + {Array.from({ length: 5 }).map((_, i) => ( |
| 26 | + <Skeleton key={i} className="h-6 w-12 py-2" /> |
| 27 | + ))} |
| 28 | + </div> |
| 29 | + ), |
| 30 | +}) |
| 31 | + |
| 32 | +const MobileMenuLoading = () => ( |
| 33 | + <Skeleton data-label="mobile-menu" className="ms-2 size-6" /> |
| 34 | +) |
| 35 | + |
| 36 | +const MobileNavMenu = dynamic(() => import("../Mobile"), { |
| 37 | + ssr: false, |
| 38 | + loading: MobileMenuLoading, |
| 39 | +}) |
| 40 | + |
| 41 | +const SearchProvider = dynamic(() => import("../../Search"), { |
| 42 | + ssr: false, |
| 43 | + loading: () => ( |
| 44 | + <> |
| 45 | + <div className="flex items-center gap-6 px-2 max-md:hidden xl:px-3"> |
| 46 | + <Skeleton |
| 47 | + data-label="search-xl" |
| 48 | + className="hidden h-6 w-[169px] xl:flex" |
| 49 | + /> |
| 50 | + <Skeleton data-label="search" className="size-6 xl:hidden" /> |
| 51 | + </div> |
| 52 | + <div className="flex items-center md:hidden"> |
| 53 | + <Skeleton data-label="search" className="mx-2 size-6" /> |
| 54 | + <MobileMenuLoading /> |
| 55 | + </div> |
| 56 | + </> |
| 57 | + ), |
| 58 | +}) |
| 59 | + |
| 60 | +const LanguagePicker = dynamic(() => import("../../LanguagePicker"), { |
| 61 | + ssr: false, |
| 62 | + loading: () => ( |
| 63 | + // LG skeleton width approximates English "[icon] Languages EN" text width |
| 64 | + <Skeleton className="h-6 max-md:hidden md:mx-2 md:w-[54px] lg:mx-3 lg:w-[8.875rem]" /> |
| 65 | + ), |
| 66 | +}) |
| 67 | + |
| 68 | +const ClientSideNav = () => { |
| 69 | + const { t } = useTranslation("common") |
| 70 | + const locale = useLocale() |
| 71 | + |
| 72 | + const { linkSections } = useNavigation() |
| 73 | + const { toggleColorMode, ThemeIcon, themeIconAriaLabel } = useThemeToggle() |
| 74 | + |
| 75 | + const languagePickerRef = useRef<HTMLButtonElement>(null) |
| 76 | + |
| 77 | + // avoid rendering/adding desktop Menu version to DOM on mobile |
| 78 | + const desktopScreen = useBreakpointValue({ base: false, md: true }) |
| 79 | + |
| 80 | + return ( |
| 81 | + <> |
| 82 | + {desktopScreen && ( |
| 83 | + <Menu |
| 84 | + className="animate-fade-in max-md:hidden" |
| 85 | + sections={linkSections} |
| 86 | + /> |
| 87 | + )} |
| 88 | + |
| 89 | + <div className="flex items-center"> |
| 90 | + <SearchProvider> |
| 91 | + {({ onOpen }) => { |
| 92 | + return ( |
| 93 | + <> |
| 94 | + <SearchInputButton |
| 95 | + className="animate-fade-in max-xl:hidden" |
| 96 | + onClick={onOpen} |
| 97 | + /> |
| 98 | + <SearchButton |
| 99 | + className="animate-fade-in xl:hidden" |
| 100 | + onClick={onOpen} |
| 101 | + /> |
| 102 | + |
| 103 | + {!desktopScreen && ( |
| 104 | + <MobileNavMenu |
| 105 | + toggleColorMode={toggleColorMode} |
| 106 | + linkSections={linkSections} |
| 107 | + toggleSearch={onOpen} |
| 108 | + className="flex animate-fade-in md:hidden" |
| 109 | + /> |
| 110 | + )} |
| 111 | + </> |
| 112 | + ) |
| 113 | + }} |
| 114 | + </SearchProvider> |
| 115 | + |
| 116 | + {desktopScreen && ( |
| 117 | + <Button |
| 118 | + aria-label={themeIconAriaLabel} |
| 119 | + variant="ghost" |
| 120 | + isSecondary |
| 121 | + className="group animate-fade-in px-2 max-md:hidden xl:px-3 [&>svg]:transition-transform [&>svg]:duration-500 [&>svg]:hover:rotate-12 [&>svg]:hover:text-primary-hover" |
| 122 | + onClick={toggleColorMode} |
| 123 | + > |
| 124 | + <ThemeIcon className="transform-transform duration-500 group-hover:rotate-12 group-hover:transition-transform group-hover:duration-500" /> |
| 125 | + </Button> |
| 126 | + )} |
| 127 | + |
| 128 | + {desktopScreen && ( |
| 129 | + <LanguagePicker className="max-md:hidden"> |
| 130 | + <Button |
| 131 | + name={DESKTOP_LANGUAGE_BUTTON_NAME} |
| 132 | + ref={languagePickerRef} |
| 133 | + variant="ghost" |
| 134 | + className="animate-fade-in gap-0 px-2 text-body transition-transform duration-500 active:bg-primary-low-contrast active:text-primary-hover data-[state='open']:bg-primary-low-contrast data-[state='open']:text-primary-hover max-md:hidden xl:px-3 [&_svg]:transition-transform [&_svg]:duration-500 [&_svg]:hover:rotate-12" |
| 135 | + > |
| 136 | + <BsTranslate className="me-2 align-middle text-2xl" /> |
| 137 | + <span className="max-lg:hidden">{t("languages")} </span> |
| 138 | + {locale!.toUpperCase()} |
| 139 | + </Button> |
| 140 | + </LanguagePicker> |
| 141 | + )} |
| 142 | + </div> |
| 143 | + </> |
| 144 | + ) |
| 145 | +} |
| 146 | + |
| 147 | +ClientSideNav.displayName = "ClientSideNav" |
| 148 | + |
| 149 | +export default ClientSideNav |
0 commit comments