Skip to content

Commit

Permalink
Merge pull request #10 from Vizzuality/SS-49-logo-collapsable-sidebar
Browse files Browse the repository at this point in the history
Display the navigation
  • Loading branch information
clementprdhomme authored Oct 22, 2024
2 parents 688ecd4 + 605d245 commit a5db74e
Show file tree
Hide file tree
Showing 29 changed files with 1,238 additions and 17 deletions.
6 changes: 5 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.0",
"@artsy/fresnel": "7.1.4",
"@radix-ui/react-dialog": "1.1.2",
"@radix-ui/react-separator": "1.1.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-tooltip": "1.1.3",
"@t3-oss/env-nextjs": "0.11.1",
"@types/mapbox-gl": "3.4.0",
"class-variance-authority": "0.7.0",
Expand Down
15 changes: 14 additions & 1 deletion client/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@
body {
@apply text-rhino-blue-950;
}

.mapboxgl-ctrl-bottom-right {
@apply !pb-1 !pr-5 sm:!p-0 flex flex-row items-end gap-3 [&_div]:shrink-0 [&_div]:!m-0 [&_div:nth-child(1)]:pb-1;
}

@layer base {
:root {
--radius: 0.5rem
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%
}
}
3 changes: 3 additions & 0 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Jost, DM_Serif_Text } from "next/font/google";

import Head from "@/components/head";

import type { Metadata } from "next";

import "./globals.css";
Expand Down Expand Up @@ -32,6 +34,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className={`${jost.variable} ${dmSerifText.variable}`}>
<Head />
<body>{children}</body>
</html>
);
Expand Down
8 changes: 7 additions & 1 deletion client/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Metadata } from "next";
import dynamic from "next/dynamic";

import Navigation from "@/components/navigation";
import { SidebarProvider } from "@/components/ui/sidebar";

// By forcing the map to load in the client, we can perform some media queries immediately. Without
// this, the map would still be loaded in the client only anyway.
const Map = dynamic(() => import("@/components/map"), { ssr: false });
Expand All @@ -15,7 +18,10 @@ export const metadata: Metadata = {
export default function Home() {
return (
<main className="h-svh w-svw">
<Map />
<SidebarProvider className="flex h-full w-full flex-col-reverse xl:block">
<Navigation />
<Map />
</SidebarProvider>
</main>
);
}
16 changes: 16 additions & 0 deletions client/src/components/head/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import React from "react";

import { mediaStyles } from "@/media";

function RootHead() {
return (
// eslint-disable-next-line @next/next/no-head-element
<head>
<style key="fresnel-css" dangerouslySetInnerHTML={{ __html: mediaStyles }} type="text/css" />
</head>
);
}

export default RootHead;
21 changes: 21 additions & 0 deletions client/src/components/intro/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface IntroProps {
showDescription?: boolean;
}

const Intro = ({ showDescription = true }: IntroProps) => {
return (
<div className="text-center xl:text-left">
<h1 className="font-serif text-4xl text-supernova-yellow-400 xl:text-[46px] xl:leading-[63px]">
South Sudan
</h1>
{showDescription && (
<p className="mx-auto mt-0.5 max-w-[280px] text-xs xl:mt-4 xl:max-w-none xl:text-sm">
Introduction lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor
sit amet, consectetu elit.
</p>
)}
</div>
);
};

export default Intro;
40 changes: 33 additions & 7 deletions client/src/components/map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"use client";

import { useCallback, useRef } from "react";
import { useCallback, useEffect, useMemo, useRef } from "react";
import ReactMapGL from "react-map-gl";

import { SIDEBAR_WIDTH } from "@/components/ui/sidebar";
import { env } from "@/env";
import useBreakpoint from "@/hooks/use-breakpoint";
import useIsSidebarExpanded from "@/hooks/use-is-sidebar-expanded";
import useMapBounds from "@/hooks/use-map-bounds";
import usePrevious from "@/hooks/use-previous";

import { DESKTOP_MAX_BOUNDS, MOBILE_MAX_BOUNDS } from "./constants";
import Controls from "./controls";
Expand All @@ -18,28 +21,51 @@ const Map = () => {
const isDesktop = useBreakpoint("xl", false, true);
const mapRef = useRef<MapRef>(null);

const isSidebarExpanded = useIsSidebarExpanded();
const previousIsSidebarExpanded = usePrevious(isSidebarExpanded);

const [bounds, setBounds] = useMapBounds();

const initialViewState = useMemo(() => {
const padding = isDesktop ? 100 : 20;
const sidebarPadding = isSidebarExpanded ? Number.parseInt(SIDEBAR_WIDTH.replace("px", "")) : 0;

return {
bounds: bounds,
fitBoundsOptions: {
padding: {
top: padding,
right: padding,
bottom: padding,
left: padding + sidebarPadding,
},
},
};
}, [bounds, isDesktop, isSidebarExpanded]);

const onMove = useCallback(() => {
if (mapRef.current) {
setBounds(mapRef.current.getBounds()?.toArray() as [LngLatLike, LngLatLike]);
}
}, [mapRef, setBounds]);

// Update the position of the map based on the sidebar's state
useEffect(() => {
if (isSidebarExpanded !== previousIsSidebarExpanded) {
mapRef.current?.fitBounds(bounds, initialViewState.fitBoundsOptions);
}
}, [isSidebarExpanded, previousIsSidebarExpanded, initialViewState, bounds]);

return (
<ReactMapGL
ref={mapRef}
mapboxAccessToken={env.NEXT_PUBLIC_MAPBOX_TOKEN}
initialViewState={{
bounds: bounds,
fitBoundsOptions: {
padding: isDesktop ? 100 : 20,
},
}}
initialViewState={initialViewState}
maxBounds={isDesktop ? DESKTOP_MAX_BOUNDS : MOBILE_MAX_BOUNDS}
style={{ width: "100%", height: "100%" }}
mapStyle={env.NEXT_PUBLIC_MAPBOX_STYLE}
onMove={onMove}
logoPosition="bottom-right"
>
<Controls />
</ReactMapGL>
Expand Down
21 changes: 21 additions & 0 deletions client/src/components/navigation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import { Media, MediaContextProvider } from "@/media";

import NavigationDesktop from "./navigation-desktop";
import NavigationMobile from "./navigation-mobile";

const Navigation = () => {
return (
<MediaContextProvider>
<Media lessThan="xl">
<NavigationMobile />
</Media>
<Media greaterThanOrEqual="xl" className="absolute">
<NavigationDesktop />
</Media>
</MediaContextProvider>
);
};

export default Navigation;
21 changes: 21 additions & 0 deletions client/src/components/navigation/logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { cn } from "@/lib/utils";

import { useSidebar } from "../ui/sidebar";

const Logo = () => {
const { state } = useSidebar();

return (
<div
className={cn({
"absolute left-10 top-6 z-20 w-[200px] text-[15px] font-semibold leading-[18px] text-white transition-all duration-500 ease-out":
true,
"translate-x-6 text-rhino-blue-950": state === "collapsed",
})}
>
Hydrological Information Management Systems
</div>
);
};

export default Logo;
28 changes: 28 additions & 0 deletions client/src/components/navigation/navigation-desktop/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import Intro from "@/components/intro";
import MainPanel from "@/components/panels/main";
import { Sidebar, SidebarContent, SidebarHeader, SidebarTrigger } from "@/components/ui/sidebar";

import Logo from "../logo";

const NavigationDesktop = () => {
return (
<>
<Logo />
<Sidebar>
<SidebarHeader className="h-[88px] bg-rhino-blue-900 px-10 py-6 text-white">
<SidebarTrigger className="absolute right-0 top-6 z-10 translate-x-1/2 transition-transform group-data-[state=collapsed]:translate-x-full [&_svg]:rotate-90 group-data-[state=collapsed]:[&_svg]:-rotate-90" />
</SidebarHeader>
<SidebarContent className="overflow-auto">
<div className="bg-rhino-blue-900 px-10 pb-6 text-white">
<Intro />
</div>
<MainPanel />
</SidebarContent>
</Sidebar>
</>
);
};

export default NavigationDesktop;
23 changes: 23 additions & 0 deletions client/src/components/navigation/navigation-mobile/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ChartBarIcon from "@/svgs/chart-bar.svg";
import MapPinIcon from "@/svgs/map-pin.svg";
import MapIcon from "@/svgs/map.svg";

import { Tab } from "./types";

export const TABS: Record<
Tab,
{ name: string; icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>> }
> = {
location: {
name: "Location",
icon: MapPinIcon,
},
main: {
name: "Analysis",
icon: ChartBarIcon,
},
map: {
name: "Map",
icon: MapIcon,
},
};
56 changes: 56 additions & 0 deletions client/src/components/navigation/navigation-mobile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import { useState } from "react";

import Intro from "@/components/intro";
import LocationPanel from "@/components/panels/location";
import MainPanel from "@/components/panels/main";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetHeader, SheetOverlay, SheetTitle } from "@/components/ui/sheet";

import { TABS } from "./constants";
import { Tab } from "./types";

const NavigationMobile = () => {
const [tab, setTab] = useState<Tab>(Tab.Main);

return (
<>
<Sheet modal={false} open={tab === Tab.Main || tab === Tab.Location}>
<SheetOverlay className="!bottom-[68px]" />
<SheetContent side="bottom" className="bottom-[68px] h-[calc(100%_-_68px)]">
<SheetHeader className="text-center">
<SheetTitle className="sr-only">{TABS[tab].name}</SheetTitle>
<Intro showDescription={tab === Tab.Main} />
</SheetHeader>
<div className="mt-6">
{tab === Tab.Main && <MainPanel />}
{tab === Tab.Location && <LocationPanel />}
</div>
</SheetContent>
</Sheet>
<div
role="menubar"
className="relative z-20 flex items-center justify-center bg-rhino-blue-900 px-5 py-2.5 text-white"
>
{Object.entries(TABS).map(([key, { name, icon: Icon }]) => (
<Button
key={key}
variant="ghost"
size="auto"
type="button"
role="menuitemradio"
aria-checked={key === tab}
className="w-16 flex-col items-center aria-checked:text-supernova-yellow-400"
onClick={() => setTab(key as Tab)}
>
<Icon className="shrink-0" aria-hidden />
<span className="text-xs">{name}</span>
</Button>
))}
</div>
</>
);
};

export default NavigationMobile;
5 changes: 5 additions & 0 deletions client/src/components/navigation/navigation-mobile/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum Tab {
Main = "main",
Location = "location",
Map = "map",
}
5 changes: 5 additions & 0 deletions client/src/components/panels/location/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const LocationPanel = () => {
return <div className="py-11 text-center font-semibold">Coming soon!</div>;
};

export default LocationPanel;
5 changes: 5 additions & 0 deletions client/src/components/panels/main/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const MainPanel = () => {
return <div className="py-11 text-center font-semibold">Coming soon!</div>;
};

export default MainPanel;
3 changes: 3 additions & 0 deletions client/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ const buttonVariants = cva(
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950",
yellow:
"bg-supernova-yellow-400 hover:bg-supernova-yellow-300 text-casper-blue-950 focus-visible:ring-casper-blue-400",
ghost:
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950",
},
size: {
default: "h-10 px-4 py-2",
icon: "h-10 w-10",
auto: "",
},
},
defaultVariants: {
Expand Down
Loading

0 comments on commit a5db74e

Please sign in to comment.