Skip to content

Commit 80b7eb7

Browse files
committed
mobile nav: add drawer
1 parent 1ce4450 commit 80b7eb7

File tree

3 files changed

+219
-60
lines changed

3 files changed

+219
-60
lines changed

src/frontend/src/assets/css/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ html,
1111
body {
1212
font-family: 'Barlow Condensed', sans-serif;
1313
box-sizing: border-box;
14+
--hot-font-sans: 'Barlow Condensed', sans-serif;
1415
}
1516

1617
.landing-page p {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { ReactNode, useEffect, useCallback } from 'react';
2+
3+
interface IDrawerProps {
4+
open: boolean;
5+
onClose: () => void;
6+
children?: ReactNode;
7+
className?: string;
8+
overlayClassName?: string;
9+
contentClassName?: string;
10+
}
11+
12+
export default function Drawer({
13+
open,
14+
onClose,
15+
children,
16+
className = '',
17+
overlayClassName = '',
18+
contentClassName = '',
19+
}: IDrawerProps) {
20+
const handleEscape = useCallback(
21+
(e: KeyboardEvent) => {
22+
if (e.key === 'Escape') onClose();
23+
},
24+
[onClose],
25+
);
26+
27+
useEffect(() => {
28+
if (open) {
29+
document.addEventListener('keydown', handleEscape);
30+
}
31+
return () => document.removeEventListener('keydown', handleEscape);
32+
}, [open, handleEscape]);
33+
34+
return (
35+
<div
36+
className={`naxatw-fixed naxatw-inset-0 naxatw-z-[999] naxatw-transition-all naxatw-duration-200 ${
37+
open
38+
? 'naxatw-visible naxatw-opacity-100'
39+
: 'naxatw-invisible naxatw-opacity-0'
40+
} ${className}`.trim()}
41+
>
42+
{/* Overlay */}
43+
<div
44+
className={`naxatw-absolute naxatw-inset-0 naxatw-bg-black/40 ${overlayClassName}`.trim()}
45+
onClick={onClose}
46+
role="presentation"
47+
/>
48+
49+
{/* Drawer panel - slides from top */}
50+
<div
51+
className={`naxatw-absolute naxatw-left-0 naxatw-right-0 naxatw-top-0 naxatw-bg-white naxatw-shadow-lg naxatw-transition-transform naxatw-duration-200 naxatw-ease-out ${
52+
open ? 'naxatw-translate-y-0' : '-naxatw-translate-y-full'
53+
} ${contentClassName}`.trim()}
54+
role="dialog"
55+
aria-modal="true"
56+
>
57+
{children}
58+
</div>
59+
</div>
60+
);
61+
}
Lines changed: 157 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { useState } from 'react';
12
import Image from '@Components/RadixComponents/Image';
23
import { NavLink, useLocation } from 'react-router-dom';
34
import dtmLogo from '@Assets/images/DTM-logo-black.svg';
45
import UserProfile from '../UserProfile';
56
import { FlexRow } from '../Layouts';
7+
import Icon from '../Icon';
8+
import Drawer from '../Drawer';
69
import '@hotosm/tool-menu';
7-
// import Icon from '../Icon';
810

911
// Import Hanko web component when using SSO
1012
const AUTH_PROVIDER = (import.meta as any).env.VITE_AUTH_PROVIDER || 'legacy';
@@ -19,6 +21,7 @@ if (AUTH_PROVIDER === 'hanko') {
1921

2022
export default function Navbar() {
2123
const { pathname } = useLocation();
24+
const [drawerOpen, setDrawerOpen] = useState(false);
2225
const pathnameOnArray = pathname?.split('/');
2326
const isApprovalPage =
2427
pathnameOnArray?.includes('projects') &&
@@ -29,68 +32,162 @@ export default function Navbar() {
2932
// Build return URL for Hanko SSO that goes through /hanko-auth callback
3033
const hankoReturnUrl = `${FRONTEND_URL}/hanko-auth?role=${signedInAs}`;
3134

35+
const navLinkClass = ({
36+
isActive,
37+
forceActive,
38+
}: {
39+
isActive: boolean;
40+
forceActive?: boolean;
41+
}) =>
42+
`${
43+
isActive || forceActive
44+
? 'naxatw-border-b-2 naxatw-border-red'
45+
: 'hover:naxatw-border-b-2 hover:naxatw-border-grey-900'
46+
} -naxatw-mb-[1.4rem] naxatw-px-3 naxatw-pb-2 naxatw-text-body-btn`;
47+
48+
const desktopAuth =
49+
AUTH_PROVIDER === 'hanko' ? (
50+
<hotosm-auth
51+
hanko-url={HANKO_URL}
52+
base-path={HANKO_URL}
53+
redirect-after-login={hankoReturnUrl}
54+
redirect-after-logout={FRONTEND_URL}
55+
button-variant="filled"
56+
button-color="danger"
57+
/>
58+
) : (
59+
<UserProfile />
60+
);
61+
62+
const mobileAuth =
63+
AUTH_PROVIDER === 'hanko' ? (
64+
<hotosm-auth
65+
hanko-url={HANKO_URL}
66+
base-path={HANKO_URL}
67+
redirect-after-login={hankoReturnUrl}
68+
redirect-after-logout={FRONTEND_URL}
69+
button-variant="filled"
70+
button-color="danger"
71+
display="bar"
72+
/>
73+
) : (
74+
<UserProfile />
75+
);
76+
3277
return (
33-
<nav className="naxatw-min-h-[3.3rem] naxatw-border-b naxatw-border-grey-300 naxatw-pb-0 naxatw-pt-2">
34-
<FlexRow className="naxatw-items-center naxatw-justify-between naxatw-px-16">
35-
<a
36-
className="naxatw-cursor-pointer"
37-
role="presentation"
38-
aria-label="Navigate to home page"
39-
href="/"
40-
>
41-
<Image
42-
src={dtmLogo}
43-
alt="Drone Tasking Manager Logo"
44-
className="naxatw-h-8 naxatw-w-40"
45-
/>
46-
</a>
47-
{!isApprovalPage && (
48-
<>
49-
<FlexRow className="naxatw-gap-4">
50-
<NavLink
51-
to="/projects"
52-
className={({ isActive }) =>
53-
`${
54-
isActive || pathname.includes('project')
55-
? 'naxatw-border-b-2 naxatw-border-red'
56-
: 'hover:naxatw-border-b-2 hover:naxatw-border-grey-900'
57-
} -naxatw-mb-[1.4rem] naxatw-px-3 naxatw-pb-2 naxatw-text-body-btn`
58-
}
78+
<>
79+
<nav className="naxatw-min-h-[3.5rem] naxatw-border-b naxatw-border-grey-300 naxatw-pb-0 naxatw-pt-2">
80+
<FlexRow className="naxatw-items-center naxatw-justify-between naxatw-px-4 md:naxatw-px-16">
81+
<a
82+
className="naxatw-cursor-pointer"
83+
role="presentation"
84+
aria-label="Navigate to home page"
85+
href="/"
86+
>
87+
<Image
88+
src={dtmLogo}
89+
alt="Drone Tasking Manager Logo"
90+
className="naxatw-h-8 naxatw-w-40"
91+
/>
92+
</a>
93+
{!isApprovalPage && (
94+
<>
95+
{/* Desktop nav */}
96+
<FlexRow className="naxatw-hidden naxatw-gap-4 md:naxatw-flex">
97+
<NavLink
98+
to="/projects"
99+
className={({ isActive }) =>
100+
navLinkClass({
101+
isActive,
102+
forceActive: pathname.includes('project'),
103+
})
104+
}
105+
>
106+
Projects
107+
</NavLink>
108+
<NavLink
109+
to="/dashboard"
110+
className={({ isActive }) => navLinkClass({ isActive })}
111+
>
112+
Dashboard
113+
</NavLink>
114+
</FlexRow>
115+
<FlexRow
116+
className="naxatw-hidden naxatw-items-center md:naxatw-flex"
117+
gap={2}
59118
>
60-
Projects
61-
</NavLink>
62-
<NavLink
63-
to="/dashboard"
64-
className={({ isActive }) =>
65-
`${
66-
isActive
67-
? 'naxatw-border-b-2 naxatw-border-red'
68-
: 'hover:naxatw-border-b-2 hover:naxatw-border-grey-900'
69-
} -naxatw-mb-[1.4rem] naxatw-px-3 naxatw-pb-2 naxatw-text-body-btn`
70-
}
119+
{desktopAuth}
120+
<hotosm-tool-menu></hotosm-tool-menu>
121+
</FlexRow>
122+
123+
{/* Mobile hamburger */}
124+
<button
125+
type="button"
126+
className="naxatw-flex naxatw-align-middle md:naxatw-hidden"
127+
onClick={() => setDrawerOpen(true)}
128+
aria-label="Open menu"
71129
>
72-
Dashboard
73-
</NavLink>
74-
</FlexRow>
75-
<FlexRow className="naxatw-items-center" gap={2}>
76-
{/* <Icon name="notifications" /> */}
77-
{AUTH_PROVIDER === 'hanko' ? (
78-
<hotosm-auth
79-
hanko-url={HANKO_URL}
80-
base-path={HANKO_URL}
81-
redirect-after-login={hankoReturnUrl}
82-
redirect-after-logout={FRONTEND_URL}
83-
button-variant="filled"
84-
button-color="danger"
85-
/>
86-
) : (
87-
<UserProfile />
88-
)}
89-
<hotosm-tool-menu></hotosm-tool-menu>
130+
<Icon name="menu" />
131+
</button>
132+
</>
133+
)}
134+
</FlexRow>
135+
</nav>
136+
137+
{/* Mobile drawer */}
138+
<Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)}>
139+
<div className="naxatw-flex naxatw-flex-col naxatw-gap-4 naxatw-p-4">
140+
<div className="naxatw-flex naxatw-items-center naxatw-justify-between">
141+
<a href="/" aria-label="Navigate to home page">
142+
<Image
143+
src={dtmLogo}
144+
alt="Drone Tasking Manager Logo"
145+
className="naxatw-h-8 naxatw-w-40"
146+
/>
147+
</a>
148+
<button
149+
type="button"
150+
onClick={() => setDrawerOpen(false)}
151+
aria-label="Close menu"
152+
>
153+
<Icon name="close" />
154+
</button>
155+
</div>
156+
<div className="naxatw-flex naxatw-flex-col naxatw-gap-2">
157+
<NavLink
158+
to="/projects"
159+
onClick={() => setDrawerOpen(false)}
160+
className={({ isActive }) =>
161+
`naxatw-rounded naxatw-px-3 naxatw-py-2 naxatw-text-body-btn ${
162+
isActive || pathname.includes('project')
163+
? 'naxatw-bg-red/10 naxatw-text-red'
164+
: 'hover:naxatw-bg-grey-100'
165+
}`
166+
}
167+
>
168+
Projects
169+
</NavLink>
170+
<NavLink
171+
to="/dashboard"
172+
onClick={() => setDrawerOpen(false)}
173+
className={({ isActive }) =>
174+
`naxatw-rounded naxatw-px-3 naxatw-py-2 naxatw-text-body-btn ${
175+
isActive
176+
? 'naxatw-bg-red/10 naxatw-text-red'
177+
: 'hover:naxatw-bg-grey-100'
178+
}`
179+
}
180+
>
181+
Dashboard
182+
</NavLink>
183+
</div>
184+
<div className="naxatw-border-t naxatw-border-grey-300 naxatw-pt-4">
185+
<FlexRow className="naxatw-items-center naxatw-justify-between">
186+
{mobileAuth}
90187
</FlexRow>
91-
</>
92-
)}
93-
</FlexRow>
94-
</nav>
188+
</div>
189+
</div>
190+
</Drawer>
191+
</>
95192
);
96193
}

0 commit comments

Comments
 (0)