Skip to content

Commit

Permalink
[Closes #166] Update sidebar navigation design (#167)
Browse files Browse the repository at this point in the history
* Add story for Sidebar component, add storybook control for testing differen user states

* Tweak sidebar footer

* Simplify Sidebar implementation

* Hide items/sections not implemented

* Move print QR code link

* Fix initial global user state

* Switch icons package and clean

* Fix typo
  • Loading branch information
francisli authored Dec 13, 2024
1 parent f4eb21c commit 76f3de2
Show file tree
Hide file tree
Showing 21 changed files with 281 additions and 353 deletions.
52 changes: 51 additions & 1 deletion client/.storybook/preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,48 @@ const ColorSchemeWrapper = ({ children }) => {

const queryClient = new QueryClient({});

const USERS = {
NONE: null,
ADMIN: {
id: 'b3dedbd4-5092-4303-a487-98d3d010b4eb',
role: 'ADMIN',
firstName: 'Admin',
lastName: 'User',
email: '[email protected]',
},
STAFF: {
id: 'b3dedbd4-5092-4303-a487-98d3d010b4eb',
role: 'STAFF',
firstName: 'Staff',
lastName: 'User',
email: '[email protected]',
},
VOLUNTEER: {
id: 'b3dedbd4-5092-4303-a487-98d3d010b4eb',
role: 'VOLUNTEER',
firstName: 'Volunteer',
lastName: 'User',
email: '[email protected]',
},
FIRST_RESPONDER: {
id: 'b3dedbd4-5092-4303-a487-98d3d010b4eb',
role: 'FIRST_RESPONDER',
firstName: 'First',
lastName: 'Responder',
email: '[email protected]',
},
};

export const decorators = [
(renderStory) => <ColorSchemeWrapper>{renderStory()}</ColorSchemeWrapper>,
(renderStory) => (
<MantineProvider theme={theme}>{renderStory()}</MantineProvider>
),
(renderStory) => <ContextProvider>{renderStory()}</ContextProvider>,
(renderStory, context) => (
<ContextProvider initialUser={USERS[context.globals.user]}>
{renderStory()}
</ContextProvider>
),
(renderStory) => (
<MemoryRouter initialEntries={['/']}>{renderStory()}</MemoryRouter>
),
Expand All @@ -41,6 +77,20 @@ export const decorators = [
];

const preview = {
globalTypes: {
user: {
description: 'Global user type',
toolbar: {
title: 'User',
icon: 'user',
items: ['NONE', 'ADMIN', 'STAFF', 'VOLUNTEER', 'FIRST_RESPONDER'],
dynamicTitle: true,
},
},
},
initialGlobals: {
user: 'NONE',
},
parameters: {
controls: {
matchers: {
Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
"@mantine/form": "^7.14.3",
"@mantine/hooks": "^7.14.3",
"@mantine/notifications": "^7.14.3",
"@tabler/icons-react": "^3.24.0",
"@tanstack/react-query": "^5.62.2",
"dayjs": "^1.11.13",
"http-status-codes": "^2.3.0",
"inflection": "^3.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.4.0",
"react-imask": "^7.6.1",
"react-qrcode-logo": "^3.0.0",
"react-router": "^7.0.2"
Expand Down
2 changes: 1 addition & 1 deletion client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Layout } from './stories/Layout/Layout';
import Home from './pages/home';
import Login from './pages/auth/login/login';
import Register from './pages/auth/register/register';
import Dashboard from './pages/dashboard/dashboard';
import Dashboard from './pages/dashboard/Dashboard';
import AdminPatientsGenerate from './pages/admin/patients/AdminPatientsGenerate';
import NotFound from './pages/notFound/NotFound';
import { AdminUsers } from './pages/admin/users/AdminUsers';
Expand Down
5 changes: 3 additions & 2 deletions client/src/Context.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ const contextProviderProps = {
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
initialUser: PropTypes.object,
};

/**
*
* @param {PropTypes.InferProps<typeof contextProviderProps>} props
*/
function ContextProvider({ children }) {
const [user, setUser] = useState(null);
function ContextProvider({ children, initialUser }) {
const [user, setUser] = useState(initialUser);

const contextValue = {
user,
Expand Down
200 changes: 100 additions & 100 deletions client/src/components/Sidebar/Sidebar.jsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,70 @@
import React from 'react';
import { ActionIcon, Container, Group, ScrollArea } from '@mantine/core';
import { NavLink as RouterNavLink } from 'react-router';
import { Box, Group, NavLink, Stack, Text, Title } from '@mantine/core';
import {
IconDashboard,
IconEmergencyBed,
IconUsersGroup,
IconZoomCheck,
IconQrcode,
IconUserCircle,
IconNotification,
IconSpeakerphone,
IconSquareArrowRight,
} from '@tabler/icons-react';
// TbHealthRecognition,
TbHeartHandshake,
// TbMessageReport,
// TbNotebook,
// TbSettings,
} from 'react-icons/tb';
import { FiLogOut, FiUsers } from 'react-icons/fi';
import { LuLayoutDashboard } from 'react-icons/lu';
import PropTypes from 'prop-types';

import { SidebarNavSection, SidebarLink } from './SidebarNavSection';

import classes from './sidebar.module.css';
import classes from './Sidebar.module.css';
import { useAuthorization } from '../../hooks/useAuthorization';

const sections = [
{ label: 'Dashboard', icon: <IconDashboard stroke={2} />, href: '/' },
{
label: 'Management',
label: 'Admin panel',
icon: null,
links: [
{
label: 'Patients',
href: '/patients',
icon: <IconEmergencyBed stroke={2} />,
},
{
label: 'Team Member',
href: '/admin/users',
icon: <IconUsersGroup stroke={2} />,
},
{ label: 'Verification', href: '/', icon: <IconZoomCheck stroke={2} /> },
{
label: 'QR Code',
href: '/admin/patients/generate',
target: '_blank',
icon: <IconQrcode stroke={2} />,
label: 'Dashboard',
icon: <LuLayoutDashboard className={classes.navbar__icon} />,
href: '/',
},
],
},
{
label: 'Settings',
label: 'Management',
icon: null,
links: [
{ label: 'Account', href: '/', icon: <IconUserCircle stroke={2} /> },
// {
// label: 'Events',
// href: '/',
// icon: <TbHealthRecognition className={classes.navbar__icon} />,
// },
{
label: 'Notification',
href: '/',
icon: <IconNotification stroke={2} />,
label: 'Members',
href: '/admin/users',
icon: <FiUsers className={classes.navbar__icon} />,
},
{
label: 'Report Issue',
href: '/',
icon: <IconSpeakerphone stroke={2} />,
label: 'Patients',
href: '/patients',
icon: <TbHeartHandshake className={classes.navbar__icon} />,
},
],
},
// {
// label: 'Settings',
// icon: null,
// links: [
// { label: 'Resources', href: '/', icon: <TbNotebook className={classes.navbar__icon} /> },
// {
// label: 'Settings',
// href: '/',
// icon: <TbSettings className={classes.navbar__icon} />,
// },
// {
// label: 'Report Issue',
// href: '/',
// icon: <TbMessageReport className={classes.navbar__icon} />,
// },
// ],
// },
];

const SidebarProps = {
Expand All @@ -71,48 +76,6 @@ const SidebarProps = {
* @param {PropTypes.InferProps<typeof SidebarProps>} props
*/
export function Sidebar({ toggleSidebar }) {
return (
<nav className={classes.navbar}>
<Group align="center" className={classes.title}>
<img
src="/logo.svg"
alt="SF Lifeline Logo"
width={'30rem'}
height={'30rem'}
/>
<p>SF Life Line</p>
</Group>
<ScrollArea className={classes.links}>
<div className={classes.linksInner}>
{sections.map((item) => {
return item.links?.length > 0 ? (
<SidebarNavSection
{...item}
key={`section_${item.label}`}
initiallyOpened
toggleSidebar={toggleSidebar}
/>
) : (
<SidebarLink
toggleSidebar={toggleSidebar}
{...item}
key={`header_${item.label}`}
/>
);
})}
</div>
</ScrollArea>
<AccountFooter />
</nav>
);
}

Sidebar.propTypes = SidebarProps;

/**
*
*/
export function AccountFooter() {
const { user, handleLogout } = useAuthorization();

/**
Expand All @@ -124,24 +87,61 @@ export function AccountFooter() {
}

return (
<footer className={classes.footer}>
<Group justify="space-between" py="md" px="md">
<Container px="0">
<div>
<strong>{`${user?.firstName} ${user?.lastName}`}</strong>
</div>
<div>{user?.email}</div>
</Container>
<ActionIcon
component="a"
href="/logout"
onClick={onLogout}
variant="default"
aria-label="account"
>
<IconSquareArrowRight />
</ActionIcon>
</Group>
</footer>
<>
<Stack justify="space-between" px="md" py="xl" w="100%" h="100%">
<Box>
<Group align="center" gap="sm" mb="lg">
<img
src="/logo.svg"
alt="SF Lifeline Logo"
width={'32rem'}
height={'32rem'}
/>
<Title order={4}>SF Life Line</Title>
</Group>
{sections.map((section) => (
<Box key={section.label} mb="lg">
<Title fw="normal" pl="sm" order={6}>
{section.label}
</Title>
{section.links?.map((link) => (
<NavLink
key={link.label}
component={RouterNavLink}
to={link.href}
label={
<Text fz="md" fw="600">
{link.label}
</Text>
}
leftSection={link.icon}
target={link.target}
onClick={toggleSidebar}
/>
))}
</Box>
))}
</Box>
<Group className={classes.footer} justify="space-between" align="top">
<Box fz="sm">
{user && (
<>
<Box fw="600">{`${user?.firstName} ${user?.lastName}`}</Box>
<Box c="gray.7">{user?.email}</Box>
</>
)}
</Box>
<a
className={classes.footer__logout}
href="/logout"
onClick={onLogout}
>
<FiLogOut />
</a>
</Group>
</Stack>
</>
);
}

Sidebar.propTypes = SidebarProps;
25 changes: 25 additions & 0 deletions client/src/components/Sidebar/Sidebar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.navbar {
border-right: 1px solid var(--mantine-color-gray-3);
}

.navbar__icon {
width: 1.5rem;
height: 1.5rem;
}

.footer {
border-top: 1px solid var(--mantine-color-gray-3);
padding: 1.25rem 0 0;
}

.footer__logout {
width: 2.25rem;
height: 2.25rem;
padding: 0.5rem;
color: var(--mantine-color-text);
}

.footer__logout svg {
width: 1.25rem;
height: 1.25rem;
}
14 changes: 14 additions & 0 deletions client/src/components/Sidebar/Sidebar.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Sidebar } from './Sidebar';

export default {
title: 'Sidebar',
component: Sidebar,
tags: ['autodocs'],
parameters: {
layout: 'fullscreen',
},
};

export const Default = {
args: {},
};
Loading

0 comments on commit 76f3de2

Please sign in to comment.