Skip to content

Commit

Permalink
side bar implementation (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dawaic6 authored Jul 9, 2024
1 parent e63c50e commit 4b8c675
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 0 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"hero-slider": "^3.2.1",
"history": "^5.3.0",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.400.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
Expand Down
69 changes: 69 additions & 0 deletions src/__test__/dashBoard/dashBoardSideBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { render, fireEvent } from '@testing-library/react';
import DashboardSideNav from '@/components/dashBoard/DashboardSideNav';

describe('DashboardSideNav', () => {
it('renders the sidebar items', () => {
const { getByText } = render(<DashboardSideNav />);
expect(getByText('Dashboard')).toBeInTheDocument();
expect(getByText('Orders')).toBeInTheDocument();
expect(getByText('Customers')).toBeInTheDocument();
expect(getByText('Products')).toBeInTheDocument();
});

it('expands and collapses the subitems', () => {
const { getByText, queryByText } = render(<DashboardSideNav />);
const productsItem = getByText('Products');
fireEvent.click(productsItem);
expect(getByText(/all products/i)).toBeVisible();
fireEvent.click(productsItem);
expect(queryByText(/all products/i)).not.toBeInTheDocument();
});

it('updates the active item', () => {
const { getByText } = render(<DashboardSideNav />);
const ordersItem = getByText('Orders');
fireEvent.click(ordersItem);
expect(ordersItem).toHaveClass('text-black');
});

it('renders the subitems correctly', () => {
const { getByText } = render(<DashboardSideNav />);
const productsItem = getByText('Products');
fireEvent.click(productsItem);
expect(getByText('All Products')).toBeInTheDocument();
expect(getByText('Add New')).toBeInTheDocument();
expect(getByText('Categories')).toBeInTheDocument();
expect(getByText('Tags')).toBeInTheDocument();
});

it('updates the active subitem', () => {
const { getByText } = render(<DashboardSideNav />);
const productsItem = getByText('Products');
fireEvent.click(productsItem);
const allProductsItem = getByText('All Products');
fireEvent.click(allProductsItem);
expect(allProductsItem).toHaveClass('text-black');
});

it('handles keydown events for subitems', () => {
const { getByText, getAllByText } = render(<DashboardSideNav />);
const productsItem = getByText('Products');
fireEvent.keyDown(productsItem, { key: 'Enter' });
const allProductsElements = getAllByText(/all products/i);
fireEvent.keyDown(productsItem, { key: ' ' });
expect(allProductsElements.some((element) => element.offsetWidth > 0)).toBe(
false
);
});

it('toggles sidebar visibility', () => {
const { getByLabelText } = render(<DashboardSideNav />);
const toggleButton = getByLabelText('Toggle Menu');
fireEvent.click(toggleButton);
expect(getByLabelText('Close Menu')).toBeInTheDocument();

const closeButton = getByLabelText('Close Menu');
fireEvent.click(closeButton);
expect(toggleButton).toBeVisible();
});
});
177 changes: 177 additions & 0 deletions src/components/dashBoard/DashboardSideNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import React, { useState } from 'react';
import { AiOutlineClose, AiOutlineMenu } from 'react-icons/ai';
import { MdDashboard } from 'react-icons/md';
import {
ShoppingCart,
Users,
Box,
ChevronDown,
ChevronRight,
} from 'lucide-react';

const sideBarItems = [
{
path: '/dashboard',
name: 'Dashboard',
icon: <MdDashboard className="icon" />,
},
{
path: '/orders',
name: 'Orders',
icon: <ShoppingCart className="icon" />,
},
{
path: '/customers',
name: 'Customers',
icon: <Users className="icon" />,
},
{
name: 'Products',
icon: <Box className="icon" />,
subItems: [
{
path: '/products/all',
name: 'All Products',
},
{
path: '/products/add',
name: 'Add New',
},
{
path: '/products/categories',
name: 'Categories',
},
{
path: '/products/tags',
name: 'Tags',
},
],
},
];

interface SideBarItemProps {
item: {
path?: string;
name: string;
icon: React.ReactNode;
subItems?: { path: string; name: string }[];
};
activeItem: string;
setActiveItem: React.Dispatch<React.SetStateAction<string>>;
}

function SideBarItem({ item, activeItem, setActiveItem }: SideBarItemProps) {
const [expanded, setExpanded] = useState(false);

const handleExpand = () => {
setExpanded(!expanded);
};

return (
<li
className={`p-3 ${activeItem === item.name ? 'bg-primary-lightblue' : ''}`}
>
<div
className="flex items-center justify-between cursor-pointer hover:bg-[#6C32E4] hover:text-white p-2 rounded-md"
onClick={() => {
if (item.subItems) {
handleExpand();
} else {
setActiveItem(item.name);
}
}}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
if (item.subItems) {
handleExpand();
} else {
setActiveItem(item.name);
}
}
}}
>
<div className="flex items-center gap-3 text-lg ">
{item.icon}
<span className="text-black hover:text-white">{item.name}</span>
</div>
{item.subItems &&
(expanded ? (
<ChevronDown className="ml-6" />
) : (
<ChevronRight className="ml-6" />
))}
</div>
{expanded && item.subItems && (
<ul className="pl-6 p-2 bg-slate-300 rounded-md">
{item.subItems.map((subItem) => (
<li
key={subItem.name}
className="py-1 px-1 hover:bg-[#6C32E4] w-[80%] "
>
<a
href={subItem.path}
className="flex items-center gap-3 text-lg "
onClick={() => setActiveItem(subItem.name)}
>
<span className="text-black hover:text-white">
{subItem.name}
</span>
</a>
</li>
))}
</ul>
)}
</li>
);
}

function DashboardSideNav() {
const [isVisible, setIsVisible] = useState(false);
const [activeItem, setActiveItem] = useState<string>('Dashboard');

const toggleSidebar = () => {
setIsVisible(!isVisible);
};

return (
<>
<button
className="md:hidden fixed top-4 left-4 z-50 p-2 bg-white rounded-md shadow-md"
onClick={toggleSidebar}
type="button"
aria-label="Toggle Menu"
>
<AiOutlineMenu className="text-2xl" />
</button>
<aside
className={`h-screen bg-white fixed left-0 z-40 ${isVisible ? 'block' : 'hidden'} md:block`}
>
<nav className="h-full flex flex-col justify-between shadow-sm">
<ul className="flex-1 mt-6">
<li className="md:hidden flex justify-end p-3">
<button
onClick={toggleSidebar}
type="button"
aria-label="Close Menu"
>
<AiOutlineClose className="text-2xl cursor-pointer" />
</button>
</li>
{sideBarItems.map((item) => (
<SideBarItem
key={item.name}
item={item}
activeItem={activeItem}
setActiveItem={setActiveItem}
/>
))}
</ul>
</nav>
</aside>
</>
);
}

export default DashboardSideNav;
21 changes: 21 additions & 0 deletions src/layout/DashbordLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Outlet } from 'react-router-dom';
import DashboardSideNav from '@/components/dashBoard/DashboardSideNav';
import Navbar from '@/components/Navbar';

function DashboardLayout() {
return (
<div className="bg-[#F5F6F6] flex-1 flex flex-col w-full min-h-screen">
<div className="bg-white">
<Navbar />
</div>
<div className="flex flex-1">
<DashboardSideNav />
</div>
<div className="flex-1 p-5">
<Outlet />
</div>
</div>
);
}

export default DashboardLayout;
2 changes: 2 additions & 0 deletions src/routes/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ErrorPage from '@/pages/ErrorPage';
import SignUp from '@/pages/SignUp';
import SignIn from '@/pages/SignIn';
import TwoFactorAuthForm from '@/pages/TwoFactorAuthForm';
import DashboardLayout from '@/layout/DashbordLayout';

function AppRoutes() {
return (
Expand All @@ -15,6 +16,7 @@ function AppRoutes() {
<Route path="/signup" element={<SignUp />} />
<Route path="/signIn" element={<SignIn />} />
<Route path="/verify-2fa/:id/:email" element={<TwoFactorAuthForm />} />
<Route path="/adminDashboard" element={<DashboardLayout />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
);
Expand Down

0 comments on commit 4b8c675

Please sign in to comment.