TailAdmin / free-nextjs-admin-dashboard

TailAdmin is a Next.js and Tailwind CSS free, open-source admin dashboard template. Provides developers with the necessary tools, components, pages to build a full-featured back-end, dashboard, or admin panel for any web project.
https://nextjs-demo.tailadmin.com
MIT License
653 stars 238 forks source link

Refactor Sidebar Components to Adhere to DRY Principle #14

Closed ouhammmourachid closed 1 month ago

ouhammmourachid commented 5 months ago

The current implementation of Sidebar components in the repository appears to contain duplicated code segments, violating the DRY (Don't Repeat Yourself) principle. This redundancy can lead to maintenance issues, increased development time, and potential bugs.

To improve the codebase's maintainability and readability, it's crucial to refactor the Sidebar components to eliminate redundancy and adhere to the DRY principle. This involves identifying duplicated code segments and abstracting them into reusable functions or components.

here is a suggestion, but I am not sure about the name of class SidebarGroup.tsx

"use client";
import SidebarLinkGroup from "./SidebarLinkGroup";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState,useEffect } from "react";
import React from "react";
import Image from "next/image";

const SidebarGroup = ({...props}) => {
    const { name,iconPath,elements,path } = props;
    const pathname = usePathname();
    let extractedPath = path.split("/")[1];
    if (extractedPath === "") {
      extractedPath = "dashboard";
    }
    let storedSidebarExpanded = "true";
    const [sidebarExpanded, setSidebarExpanded] = useState(
        storedSidebarExpanded === null ? false : storedSidebarExpanded === "true",
      );

    useEffect(() => {
        localStorage.setItem("sidebar-expanded", sidebarExpanded.toString());
        if (sidebarExpanded) {
          document.querySelector("body")?.classList.add("sidebar-expanded");
        } else {
          document.querySelector("body")?.classList.remove("sidebar-expanded");
        }
      }, [sidebarExpanded]);

    return (
      <>
      {elements ?
      <SidebarLinkGroup
      activeCondition={
        pathname === path || pathname.includes(extractedPath)
      }
    >
      {(handleClick, open) => {
        return (
          <React.Fragment>
            <Link
              href="#"
              className={`group relative flex items-center gap-2.5 rounded-sm px-4 py-2 font-medium text-bodydark1 duration-300 ease-in-out hover:bg-graydark dark:hover:bg-meta-4 ${
                (pathname === path ||
                  pathname.includes(extractedPath)) &&
                "bg-graydark dark:bg-meta-4"
              }`}
              onClick={(e) => {
                e.preventDefault();
                sidebarExpanded
                  ? handleClick()
                  : setSidebarExpanded(true);
              }}
            >
              <Image
                  className="fill-current"
                  width={18}
                  height={18}
                  src={iconPath}
                  alt="Logo"
                  priority
              />
              {name}
              <Image
                className={`absolute right-4 top-1/2 -translate-y-1/2 fill-current ${
                  open && "rotate-180"
                }`}
                width={20}
                height={20}
                src="/images/icon/icon-arrow-up.svg"
                alt="Arrow"

              />
            </Link>
            {/* <!-- Dropdown Menu Start --> */}
            <div
              className={`translate transform overflow-hidden ${
                !open && "hidden"
              }`}
            >
              <ul className="mb-5.5 mt-4 flex flex-col gap-2.5 pl-6">
                {elements && elements.map((element: { href: string, name: string }, index: number) => {
                  return (
                    <li key={index}>
                      <Link
                        href={element.href}
                        className={`group relative flex items-center gap-2.5 rounded-md px-4 font-medium text-bodydark2 duration-300 ease-in-out hover:text-white ${
                          pathname === element.href && "text-white"
                        }`}
                      >
                        {element.name}
                      </Link>
                    </li>
                  );
                })}
              </ul>
            </div>
            {/* <!-- Dropdown Menu End --> */}
          </React.Fragment>
        );
      }}
    </SidebarLinkGroup>
      :
      <li>
                <Link
                  href={path}
                  className={`group relative flex items-center gap-2.5 rounded-sm px-4 py-2 font-medium text-bodydark1 duration-300 ease-in-out hover:bg-graydark dark:hover:bg-meta-4 ${
                    pathname.includes("chart") && "bg-graydark dark:bg-meta-4"
                  }`}
                >
                  <Image
                    className="fill-current"
                    width={18}
                    height={18}
                    src={iconPath}
                    alt="Logo"
                    priority
                  />
                  {name}
                </Link>
      </li>
      }
      </>
    )   
};

export default SidebarGroup;

index.tsx

"use client";

import React, { useEffect, useRef, useState } from "react";
import { usePathname } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import SidebarGroup from "./SidebarGroup";

interface SidebarProps {
  sidebarOpen: boolean;
  setSidebarOpen: (arg: boolean) => void;
}

const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
  const pathname = usePathname();

  const trigger = useRef<any>(null);
  const sidebar = useRef<any>(null);

  // dashboard elements
  const dashboardElements = [
    {
      name: "eCommerce",
      href: "/",
    }
  ];

  // ui elements
  const uiElements = [
    {
      name: "Alerts",
      href: "/ui/alerts",
    },
    {
      name: "Buttons",
      href: "/ui/buttons",
    },
  ];

  // authonetication elements
  const authElements = [
    {
      name: "Sign In",
      href: "/auth/signin",
    },
    {
      name: "Sign Up",
      href: "/auth/signup",
    },
  ];

  // close on click outside
  useEffect(() => {
    const clickHandler = ({ target }: MouseEvent) => {
      if (!sidebar.current || !trigger.current) return;
      if (
        !sidebarOpen ||
        sidebar.current.contains(target) ||
        trigger.current.contains(target)
      )
        return;
      setSidebarOpen(false);
    };
    document.addEventListener("click", clickHandler);
    return () => document.removeEventListener("click", clickHandler);
  });

  // close if the esc key is pressed
  useEffect(() => {
    const keyHandler = ({ key }: KeyboardEvent) => {
      if (!sidebarOpen || key !== "Escape") return;
      setSidebarOpen(false);
    };
    document.addEventListener("keydown", keyHandler);
    return () => document.removeEventListener("keydown", keyHandler);
  });

  return (
    <aside
      ref={sidebar}
      className={`absolute left-0 top-0 z-9999 flex h-screen w-72.5 flex-col overflow-y-hidden bg-black duration-300 ease-linear dark:bg-boxdark lg:static lg:translate-x-0 ${
        sidebarOpen ? "translate-x-0" : "-translate-x-full"
      }`}
    >
      {/* <!-- SIDEBAR HEADER --> */}
      <div className="flex items-center justify-between gap-2 px-6 py-5.5 lg:py-6.5">
        <Link href="/">
          <Image
            width={180}
            height={32}
            src={"/images/logo/logo.svg"}
            alt="Logo"
            priority
          />
        </Link>

        <button
          ref={trigger}
          onClick={() => setSidebarOpen(!sidebarOpen)}
          aria-controls="sidebar"
          aria-expanded={sidebarOpen}
          className="block lg:hidden"
        >
          <Image
            width={20}
            height={18}
            src="/images/icon/icon-close.svg"
            alt="Close"
            priority
          />
        </button>
      </div>
      {/* <!-- SIDEBAR HEADER --> */}

      <div className="no-scrollbar flex flex-col overflow-y-auto duration-300 ease-linear">
        {/* <!-- Sidebar Menu --> */}
        <nav className="mt-5 px-4 py-4 lg:mt-9 lg:px-6">
          {/* <!-- Menu Group --> */}
          <div>
            <h3 className="mb-4 ml-4 text-sm font-semibold text-bodydark2">
              MENU
            </h3>

            <ul className="mb-6 flex flex-col gap-1.5">
              {/* <!-- Element of MENU --> */}

              <SidebarGroup 
                name="Dashboard" 
                iconPath="/images/icon/icon-dashboard.svg"
                elements={dashboardElements}
                path="/"/>

            </ul>
          </div>

          {/* <!-- Others Group --> */}
          <div>
            <h3 className="mb-4 ml-4 text-sm font-semibold text-bodydark2">
              OTHERS
            </h3>

            <ul className="mb-6 flex flex-col gap-1.5">
              {/* <!-- Elements of OTHERS --> */}
              <SidebarGroup
                name="Chart"
                iconPath="/images/icon/icon-chart.svg"
                path="/chart"
                />

              <SidebarGroup
                name="UI Elements"
                iconPath="/images/icon/icon-ui.svg"
                elements={uiElements}
                path="/ui"
                />

              <SidebarGroup
                name="Authentication"
                iconPath="/images/icon/icon-auth.svg"
                elements={authElements}
                path="/auth"
                />
              {/* <!-- Menu Item Auth Pages --> */}
            </ul>
          </div>
        </nav>
        {/* <!-- Sidebar Menu --> */}
      </div>
    </aside>
  );
};

export default Sidebar;
Juhan-Ahamed commented 1 month ago

We have refactored Sidebar Components to Adhere to the DRY Principle