diff --git a/package-lock.json b/package-lock.json index e1eaa6c1..49cd5385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,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", @@ -8350,6 +8351,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.400.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.400.0.tgz", + "integrity": "sha512-rpp7pFHh3Xd93KHixNgB0SqThMHpYNzsGUu69UaQbSZ75Q/J3m5t6EhKyMT3m4w2WOxmJ2mY0tD3vebnXqQryQ==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", diff --git a/package.json b/package.json index 69e2a064..6d53c36e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/__test__/dashBoard/dashBoardSideBar.test.tsx b/src/__test__/dashBoard/dashBoardSideBar.test.tsx new file mode 100644 index 00000000..59b053cd --- /dev/null +++ b/src/__test__/dashBoard/dashBoardSideBar.test.tsx @@ -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(); + 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(); + 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(); + const ordersItem = getByText('Orders'); + fireEvent.click(ordersItem); + expect(ordersItem).toHaveClass('text-black'); + }); + + it('renders the subitems correctly', () => { + const { getByText } = render(); + 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(); + 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(); + 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(); + const toggleButton = getByLabelText('Toggle Menu'); + fireEvent.click(toggleButton); + expect(getByLabelText('Close Menu')).toBeInTheDocument(); + + const closeButton = getByLabelText('Close Menu'); + fireEvent.click(closeButton); + expect(toggleButton).toBeVisible(); + }); +}); diff --git a/src/components/dashBoard/DashboardSideNav.tsx b/src/components/dashBoard/DashboardSideNav.tsx new file mode 100644 index 00000000..baba810f --- /dev/null +++ b/src/components/dashBoard/DashboardSideNav.tsx @@ -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: , + }, + { + path: '/orders', + name: 'Orders', + icon: , + }, + { + path: '/customers', + name: 'Customers', + icon: , + }, + { + name: 'Products', + 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>; +} + +function SideBarItem({ item, activeItem, setActiveItem }: SideBarItemProps) { + const [expanded, setExpanded] = useState(false); + + const handleExpand = () => { + setExpanded(!expanded); + }; + + return ( +
  • +
    { + 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); + } + } + }} + > +
    + {item.icon} + {item.name} +
    + {item.subItems && + (expanded ? ( + + ) : ( + + ))} +
    + {expanded && item.subItems && ( + + )} +
  • + ); +} + +function DashboardSideNav() { + const [isVisible, setIsVisible] = useState(false); + const [activeItem, setActiveItem] = useState('Dashboard'); + + const toggleSidebar = () => { + setIsVisible(!isVisible); + }; + + return ( + <> + + + + ); +} + +export default DashboardSideNav; diff --git a/src/layout/DashbordLayout.tsx b/src/layout/DashbordLayout.tsx new file mode 100644 index 00000000..db88d255 --- /dev/null +++ b/src/layout/DashbordLayout.tsx @@ -0,0 +1,21 @@ +import { Outlet } from 'react-router-dom'; +import DashboardSideNav from '@/components/dashBoard/DashboardSideNav'; +import Navbar from '@/components/Navbar'; + +function DashboardLayout() { + return ( +
    +
    + +
    +
    + +
    +
    + +
    +
    + ); +} + +export default DashboardLayout; diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index 34f73bad..6584d44f 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -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 ( @@ -15,6 +16,7 @@ function AppRoutes() { } /> } /> } /> + } /> } /> );