Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial sidebar implementation #11

Merged
merged 7 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@
"prepare": "husky"
},
"dependencies": {
"@nextui-org/button": "2.0.38",
"@nextui-org/button": "^2.0.38",
"@nextui-org/card": "^2.0.34",
"@nextui-org/chip": "^2.0.33",
"@nextui-org/code": "2.0.33",
"@nextui-org/dropdown": "^2.1.31",
"@nextui-org/image": "^2.0.32",
"@nextui-org/input": "2.2.5",
"@nextui-org/dropdown": "^2.1.31",
"@nextui-org/kbd": "2.0.34",
"@nextui-org/link": "2.0.35",
"@nextui-org/listbox": "2.1.27",
"@nextui-org/listbox": "^2.1.27",
"@nextui-org/navbar": "2.0.37",
"@nextui-org/popover": "^2.1.29",
"@nextui-org/react": "^2.4.8",
"@nextui-org/skeleton": "^2.0.32",
"@nextui-org/snippet": "2.0.43",
"@nextui-org/switch": "2.0.34",
"@nextui-org/switch": "^2.0.34",
"@nextui-org/system": "2.2.6",
"@nextui-org/theme": "2.2.11",
"@nextui-org/tooltip": "^2.0.41",
Expand All @@ -36,6 +39,7 @@
"framer-motion": "~11.1.1",
"highcharts": "^11.4.8",
"highcharts-react-official": "^3.2.1",
"iconsax-react": "^0.0.8",
"intl-messageformat": "^10.5.0",
"leaflet": "^1.9.4",
"leaflet-defaulticon-compatibility": "^0.1.2",
Expand Down Expand Up @@ -90,4 +94,4 @@
]
}
}
}
}
Binary file added public/menu_conflicts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/menu_fcs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/menu_hazards.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/menu_ipc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/menu_ndvi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/menu_nutri.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/menu_rainfall.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/wfp_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import '@/styles/globals.css';
import clsx from 'clsx';
import { Metadata, Viewport } from 'next';

import { AlertsMenuWrapper } from '@/components/AlertsMenu/AlertsMenuWrapper';
import { Sidebar } from '@/components/Sidebar/Sidebar';
import { fontSans } from '@/config/fonts';
import { siteConfig } from '@/config/site';

Expand Down Expand Up @@ -33,6 +35,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<body className={clsx('min-h-screen bg-background font-sans antialiased', fontSans.variable)}>
<Providers themeProps={{ attribute: 'class', defaultTheme: 'dark' }}>
<div className="relative h-screen">
<Sidebar />
<AlertsMenuWrapper />
<main className="h-full w-full">{children}</main>
</div>
</Providers>
Expand Down
6 changes: 5 additions & 1 deletion src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes';
import type { ThemeProviderProps } from 'next-themes/dist/types.d.ts';
import * as React from 'react';

import { SidebarProvider } from '@/domain/contexts/SidebarContext';

export interface ProvidersProps {
children: React.ReactNode;
themeProps?: ThemeProviderProps;
Expand All @@ -16,7 +18,9 @@ export function Providers({ children, themeProps }: ProvidersProps) {

return (
<NextUIProvider navigate={router.push}>
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
<NextThemesProvider defaultTheme="system" {...themeProps}>
<SidebarProvider>{children} </SidebarProvider>
</NextThemesProvider>
</NextUIProvider>
);
}
25 changes: 25 additions & 0 deletions src/components/AlertsMenu/AlertButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Button } from '@nextui-org/button';
import { Image } from '@nextui-org/image';
import clsx from 'clsx';
import { forwardRef } from 'react';

import { AlertButtonProps } from '@/domain/props/AlertButtonProps';

export const AlertButton = forwardRef<HTMLButtonElement, AlertButtonProps>(
({ icon, label, isSelected, onClick, className, ...props }, ref) => {
return (
<Button
isIconOnly
radius="full"
className={clsx(isSelected ? 'bg-primary' : 'bg-content2', className)}
onClick={onClick}
ref={ref}
{...props}
>
<div className="w-6 h-6 flex items-center justify-center">
<Image src={icon} alt={label} className="object-contain w-auto h-auto max-w-full max-h-full" />
</div>
</Button>
);
}
);
57 changes: 57 additions & 0 deletions src/components/AlertsMenu/AlertsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client';

import { Popover, PopoverContent, PopoverTrigger } from '@nextui-org/popover';
import { useMemo } from 'react';

import { AlertButton } from '@/components/AlertsMenu/AlertButton';
import { useSidebar } from '@/domain/contexts/SidebarContext';
import { AlertType } from '@/domain/enums/AlertType';
import { AlertsMenuProps } from '@/domain/props/AlertsMenuProps';
import { SidebarOperations } from '@/operations/charts/SidebarOperations';

export function AlertsMenu({ variant }: AlertsMenuProps) {
const { isAlertSelected, toggleAlert } = useSidebar();

const isSubAlertClicked = useMemo(
() => (mainAlert: AlertType) => {
const alert = SidebarOperations.getSidebarAlertTypeByKey(mainAlert);
return alert?.subalerts?.some((subalert) => isAlertSelected(subalert.key)) ?? false;
},
[isAlertSelected]
);

return (
<div className="flex gap-1">
{SidebarOperations.getSidebarAlertTypes().map((item) =>
SidebarOperations.hasSubalerts(item) ? (
<Popover placement={variant === 'inside' ? 'bottom' : 'top'} key={item.key}>
<PopoverTrigger>
<AlertButton icon={item.icon} label={item.label} isSelected={isSubAlertClicked(item.key)} />
</PopoverTrigger>
<PopoverContent>
<div className="gap-1 flex">
{item.subalerts.map((subalert) => (
<AlertButton
key={subalert.key}
icon={subalert.icon}
label={subalert.label}
isSelected={isAlertSelected(subalert.key)}
onClick={() => toggleAlert(subalert.key)}
/>
))}
</div>
</PopoverContent>
</Popover>
) : (
<AlertButton
key={item.key}
icon={item.icon}
label={item.label}
isSelected={isAlertSelected(item.key)}
onClick={() => toggleAlert(item.key)}
/>
)
)}
</div>
);
}
18 changes: 18 additions & 0 deletions src/components/AlertsMenu/AlertsMenuWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';
Copy link
Collaborator

Choose a reason for hiding this comment

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

this doesn't use any client side APIs so could be a server component

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I get an error when I make server component, because it uses useSidebar from the context


import { AlertsMenu } from '@/components/AlertsMenu/AlertsMenu';
import { useSidebar } from '@/domain/contexts/SidebarContext';
import { AlertsMenuVariant } from '@/domain/enums/AlertsMenuVariant';

export function AlertsMenuWrapper() {
const { isSidebarOpen } = useSidebar();

if (isSidebarOpen) {
return null;
}
return (
<div className="absolute bottom-0 left-0 z-50 p-4">
<AlertsMenu variant={AlertsMenuVariant.Outside} />
</div>
);
}
10 changes: 8 additions & 2 deletions src/components/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

import 'leaflet/dist/leaflet.css';

import { MapContainer, TileLayer } from 'react-leaflet';
import { MapContainer, TileLayer, ZoomControl } from 'react-leaflet';

import { MapProps } from '@/domain/props/MapProps';

import { CountryPolygon } from './CountryPolygon';

export default function Map({ countries }: MapProps) {
return (
<MapContainer center={[21.505, -0.09]} zoom={4} style={{ height: '100%', width: '100%' }}>
<MapContainer
center={[21.505, -0.09]}
zoom={4}
style={{ height: '100%', width: '100%', zIndex: 40 }}
zoomControl={false}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
Expand All @@ -21,6 +26,7 @@ export default function Map({ countries }: MapProps) {
// TODO fix the layout, this is just an example
<CountryPolygon country={c} key={c.properties.adm0_id} />
))}
<ZoomControl position="topright" />
</MapContainer>
);
}
45 changes: 45 additions & 0 deletions src/components/Sidebar/CollapsedSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Button } from '@nextui-org/button';
import { Card, CardBody, CardHeader } from '@nextui-org/card';
import { Image } from '@nextui-org/image';
import { Listbox, ListboxItem } from '@nextui-org/listbox';
import { SidebarRight } from 'iconsax-react';

import { CollapsedSidebarProps } from '@/domain/props/CollapsedSidebarProps';
import { SidebarOperations } from '@/operations/charts/SidebarOperations';

export function CollapsedSidebar({ selectedMapType, handleSelectionChange, toggleSidebar }: CollapsedSidebarProps) {
return (
<div className="absolute top-0 left-0 z-50 p-4">
<Card className="h-full">
<CardHeader className="flex justify-center items-center">
<Button isIconOnly variant="light" onClick={toggleSidebar} aria-label="Close sidebar">
<SidebarRight size={24} />
</Button>
</CardHeader>
<CardBody>
<Listbox
variant="flat"
aria-label="Listbox menu with sections"
selectionMode="single"
selectedKeys={new Set(selectedMapType)}
onSelectionChange={handleSelectionChange}
disallowEmptySelection
hideSelectedIcon
>
{SidebarOperations.getSidebarMapTypes().map((item) => (
<ListboxItem key={item.key} textValue={item.label}>
<div className="w-6 h-6 flex items-center justify-center">
<Image
src={item.icon}
alt={item.label}
className="object-contain w-auto h-auto max-w-full max-h-full"
/>
</div>
</ListboxItem>
))}
</Listbox>
</CardBody>
</Card>
</div>
);
}
101 changes: 101 additions & 0 deletions src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
'use client';

import { Button } from '@nextui-org/button';
import { Card, CardBody, CardFooter, CardHeader } from '@nextui-org/card';
import { Input } from '@nextui-org/input';
import { Listbox, ListboxItem, ListboxSection } from '@nextui-org/listbox';
import { SidebarLeft } from 'iconsax-react';
import NextImage from 'next/image';

import { AlertsMenu } from '@/components/AlertsMenu/AlertsMenu';
import { CollapsedSidebar } from '@/components/Sidebar/CollapsedSidebar';
import { ThemeSwitch } from '@/components/Sidebar/ThemeSwitch';
import { useSidebar } from '@/domain/contexts/SidebarContext';
import { AlertsMenuVariant } from '@/domain/enums/AlertsMenuVariant';
import { GlobalInsight } from '@/domain/enums/GlobalInsight';
import { SidebarOperations } from '@/operations/charts/SidebarOperations';

export function Sidebar() {
const { isSidebarOpen, toggleSidebar, selectedMapType, setSelectedMapType } = useSidebar();

const handleSelectionChange = (key: 'all' | Set<string | number>) => {
return key instanceof Set
? setSelectedMapType(Array.from(key)[0] as GlobalInsight)
: setSelectedMapType(key as GlobalInsight);
};

if (!isSidebarOpen) {
return (
<CollapsedSidebar
selectedMapType={selectedMapType}
handleSelectionChange={handleSelectionChange}
toggleSidebar={toggleSidebar}
/>
);
}

return (
<div className="absolute top-0 left-0 z-50 h-screen p-4">
<Card
classNames={{
base: 'h-full',
header: 'flex flex-col gap-4 w-full items-start',
footer: 'flex flex-col items-start',
}}
>
<CardHeader>
<div className="flex items-center w-full gap-4">
<NextImage src="/wfp_logo.svg" alt="HungerMap" width={45} height={45} />
<p className="text-lg font-medium flex-1">HungerMap LIVE</p>
<Button isIconOnly variant="light" onClick={toggleSidebar} aria-label="Close sidebar">
<SidebarLeft size={24} />
</Button>
</div>

<ThemeSwitch />
<Input className="w-full" color="primary" placeholder="Search a country" variant="faded" />
</CardHeader>
<CardBody>
<Listbox
variant="flat"
aria-label="Listbox menu with sections"
selectionMode="single"
selectedKeys={new Set(selectedMapType)}
onSelectionChange={handleSelectionChange}
disallowEmptySelection
hideSelectedIcon
>
<ListboxSection title="Global Insights">
{SidebarOperations.getSidebarMapTypes().map((item) => (
<ListboxItem
key={item.key}
startContent={item.icon && <NextImage src={item.icon} alt={item.label} width={24} height={24} />}
>
{item.label}
</ListboxItem>
))}
</ListboxSection>
</Listbox>
<div className="p-1 w-full">
<span className="text-xs text-foreground-500 pl-1">Alerts</span>
<div className="pt-1">
<AlertsMenu variant={AlertsMenuVariant.Inside} />
</div>
</div>
</CardBody>
<CardFooter>
<Button radius="full" onClick={() => alert('Subscribe!')} size="sm">

Check warning on line 87 in src/components/Sidebar/Sidebar.tsx

View workflow job for this annotation

GitHub Actions / lint-and-format

Unexpected alert
SUBSCRIBE
</Button>
<Listbox variant="flat" aria-label="Listbox menu with sections">
<ListboxItem key="home">Home</ListboxItem>
<ListboxItem key="about">About</ListboxItem>
<ListboxItem key="glossary">Glossary</ListboxItem>
<ListboxItem key="methodology">Methodology</ListboxItem>
<ListboxItem key="disclaimer">Disclaimer</ListboxItem>
</Listbox>
</CardFooter>
</Card>
</div>
);
}
Loading