From f3b59409032832852c34de75a10d8c7cac352529 Mon Sep 17 00:00:00 2001 From: barbara-chaves Date: Tue, 18 Jun 2024 09:53:42 +0200 Subject: [PATCH] Add language selector and update getBySlug hook --- client/package.json | 2 + client/src/app/[locale]/globals.css | 2 + client/src/app/[locale]/layout.tsx | 6 +- client/src/app/[locale]/map/page.tsx | 2 +- client/src/components/map/index.tsx | 2 +- client/src/components/ui/dropdown-menu.tsx | 187 ++++++++++++++++++ client/src/components/ui/navigation-menu.tsx | 120 +++++++++++ client/src/components/ui/open-close-arrow.tsx | 7 + client/src/containers/header/index.tsx | 13 ++ .../containers/header/language-selector.tsx | 57 ++++++ client/src/containers/header/navigation.tsx | 59 ++++++ client/src/containers/navigation/index.tsx | 2 +- client/src/containers/sidebar/index.tsx | 16 +- client/src/lib/localized-query.ts | 39 +++- client/src/svgs/rangeland-logo.svg | 3 + client/yarn.lock | 98 +++++++++ 16 files changed, 600 insertions(+), 15 deletions(-) create mode 100644 client/src/components/ui/dropdown-menu.tsx create mode 100644 client/src/components/ui/navigation-menu.tsx create mode 100644 client/src/components/ui/open-close-arrow.tsx create mode 100644 client/src/containers/header/index.tsx create mode 100644 client/src/containers/header/language-selector.tsx create mode 100644 client/src/containers/header/navigation.tsx create mode 100644 client/src/svgs/rangeland-logo.svg diff --git a/client/package.json b/client/package.json index 03a1c1b..59397e0 100644 --- a/client/package.json +++ b/client/package.json @@ -20,6 +20,8 @@ "@loaders.gl/csv": "^4.2.2", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-scroll-area": "^1.0.5", diff --git a/client/src/app/[locale]/globals.css b/client/src/app/[locale]/globals.css index c664680..cd5119b 100644 --- a/client/src/app/[locale]/globals.css +++ b/client/src/app/[locale]/globals.css @@ -6,6 +6,8 @@ --foreground-rgb: 33 31 77; --background-rgb: 250 255 246; --global-rgb: 235 135 49; + --header-height: 78px; + --content-height: calc(100vh - var(--header-height)); } body { diff --git a/client/src/app/[locale]/layout.tsx b/client/src/app/[locale]/layout.tsx index aa37234..fe866ce 100644 --- a/client/src/app/[locale]/layout.tsx +++ b/client/src/app/[locale]/layout.tsx @@ -4,6 +4,7 @@ import { getTranslations } from "@/i18n"; import { getMessages } from "next-intl/server"; import LayoutProviders from "./layout-providers"; import NextIntlProvider from "@/components/next-intl-provider"; +import Header from "@/containers/header"; export async function generateMetadata({ params: { locale } }: { params: { locale: string } }) { const t = await getTranslations({ locale }); @@ -29,7 +30,10 @@ export default async function LocaleLayout({ - {children} + +
+
{children}
+ diff --git a/client/src/app/[locale]/map/page.tsx b/client/src/app/[locale]/map/page.tsx index 1f6be13..09e9f37 100644 --- a/client/src/app/[locale]/map/page.tsx +++ b/client/src/app/[locale]/map/page.tsx @@ -5,7 +5,7 @@ import Sidebar from "@/containers/sidebar"; export default async function Home() { return ( // The map is a client component, so it needs to be wrapped in the NextIntlClientProvider to provide the translations -
+
diff --git a/client/src/components/map/index.tsx b/client/src/components/map/index.tsx index e92707d..4334575 100644 --- a/client/src/components/map/index.tsx +++ b/client/src/components/map/index.tsx @@ -132,7 +132,7 @@ export const Map: FC = ({ }, [bounds, isFlying]); return ( -
+
, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/client/src/components/ui/navigation-menu.tsx b/client/src/components/ui/navigation-menu.tsx new file mode 100644 index 0000000..6268493 --- /dev/null +++ b/client/src/components/ui/navigation-menu.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; +import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"; +import { cva } from "class-variance-authority"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const NavigationMenu = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + {/* */} + +)); +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; + +const NavigationMenuList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; + +const NavigationMenuItem = NavigationMenuPrimitive.Item; + +const navigationMenuTriggerStyle = cva( + "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50", +); + +const NavigationMenuTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children}{" "} + +)); +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName; + +const NavigationMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName; + +const NavigationMenuLink = NavigationMenuPrimitive.Link; + +const NavigationMenuViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ +
+)); +NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName; + +const NavigationMenuIndicator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
+ +)); +NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName; + +export { + navigationMenuTriggerStyle, + NavigationMenu, + NavigationMenuList, + NavigationMenuItem, + NavigationMenuContent, + NavigationMenuTrigger, + NavigationMenuLink, + NavigationMenuIndicator, + NavigationMenuViewport, +}; diff --git a/client/src/components/ui/open-close-arrow.tsx b/client/src/components/ui/open-close-arrow.tsx new file mode 100644 index 0000000..4490adb --- /dev/null +++ b/client/src/components/ui/open-close-arrow.tsx @@ -0,0 +1,7 @@ +import { ChevronDown } from "lucide-react"; + +const OpenCloseArrow = () => ( + +); + +export default OpenCloseArrow; diff --git a/client/src/containers/header/index.tsx b/client/src/containers/header/index.tsx new file mode 100644 index 0000000..ea9b7bb --- /dev/null +++ b/client/src/containers/header/index.tsx @@ -0,0 +1,13 @@ +import LanguageSelector from "./language-selector"; +import HeaderNavigation from "./navigation"; + +const Header = () => { + return ( +
+ + +
+ ); +}; + +export default Header; diff --git a/client/src/containers/header/language-selector.tsx b/client/src/containers/header/language-selector.tsx new file mode 100644 index 0000000..9ee4937 --- /dev/null +++ b/client/src/containers/header/language-selector.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, +} from "@/components/ui/dropdown-menu"; +import OpenCloseArrow from "@/components/ui/open-close-arrow"; +import { useTranslations } from "@/i18n"; +import { usePathname, useRouter, locales } from "@/navigation"; +import { LanguagesIcon } from "lucide-react"; +import { useLocale } from "next-intl"; +import { useSearchParams } from "next/navigation"; + +const LanguageSelector = () => { + const locale = useLocale(); + + const t = useTranslations(); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams()?.toString(); + + const onSelectLocale = (nextLocale: string) => { + const path = `${pathname}${searchParams ? `?${searchParams}` : ""}`; + router.push(path, { locale: nextLocale }); + }; + + const localeLabels: Record<(typeof locales)[number], string> = { + en: t("English"), + es: t("Spanish"), + fr: t("French"), + }; + + return ( + + + + {t("Select Language")}: + {localeLabels[locale]} + + + + + {locales.map((l) => ( + + {localeLabels[l]} + + ))} + + + + ); +}; + +export default LanguageSelector; diff --git a/client/src/containers/header/navigation.tsx b/client/src/containers/header/navigation.tsx new file mode 100644 index 0000000..ef576dc --- /dev/null +++ b/client/src/containers/header/navigation.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { Link, usePathname } from "@/navigation"; +import RangelandLogoIcon from "@/svgs/rangeland-logo.svg"; +import { cn } from "@/lib/utils"; +import { + NavigationMenu, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, +} from "@radix-ui/react-navigation-menu"; +import { useTranslations } from "@/i18n"; + +const HeaderNavigation = () => { + const pathname = usePathname(); + const t = useTranslations(); + + const NAVIGATION_ITEMS = [ + { + title: t("Explore Map"), + href: "/map", + }, + ]; + + return ( +
+ + + + + + +

{t("Rangelands Data Platform")}

+ +
+
+ {NAVIGATION_ITEMS.map((item) => { + const isActive = item.href === pathname; + return ( + + + {item.title} + + + ); + })} +
+
+
+ ); +}; + +export default HeaderNavigation; diff --git a/client/src/containers/navigation/index.tsx b/client/src/containers/navigation/index.tsx index bce3779..1004ec8 100644 --- a/client/src/containers/navigation/index.tsx +++ b/client/src/containers/navigation/index.tsx @@ -2,7 +2,7 @@ import { PropsWithChildren } from "react"; const Navigation = ({ children }: PropsWithChildren) => { return ( -
+
{children}
diff --git a/client/src/containers/sidebar/index.tsx b/client/src/containers/sidebar/index.tsx index 09c4401..649e64e 100644 --- a/client/src/containers/sidebar/index.tsx +++ b/client/src/containers/sidebar/index.tsx @@ -14,15 +14,15 @@ const Sidebar = ({ children }: PropsWithChildren): JSX.Element => { return (