update: layout
All checks were successful
Build and Release / release (push) Successful in 27s

This commit is contained in:
2026-04-29 16:32:46 +07:00
parent 65806d197f
commit 41af501b51
5 changed files with 128 additions and 87 deletions

View File

@@ -8,7 +8,6 @@ import { apiGetCurrentUser } from "@/service/auth";
import { setUserData } from "@/store/features/userSlice"; import { setUserData } from "@/store/features/userSlice";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { usePathname } from "next/navigation";
export default function AdminLayout({ export default function AdminLayout({
children, children,
@@ -17,8 +16,6 @@ export default function AdminLayout({
}) { }) {
const { isExpanded, isHovered, isMobileOpen } = useSidebar(); const { isExpanded, isHovered, isMobileOpen } = useSidebar();
const dispatch = useDispatch() const dispatch = useDispatch()
const pathname = usePathname();
const isHomePage = pathname === "/";
useEffect(() => { useEffect(() => {
const fetchUser = async () => { const fetchUser = async () => {
@@ -34,19 +31,17 @@ export default function AdminLayout({
// Dynamic class for main content margin based on sidebar state // Dynamic class for main content margin based on sidebar state
const mainContentMargin = isHomePage const mainContentMargin = isMobileOpen
? "ml-0"
: isMobileOpen
? "ml-0" ? "ml-0"
: isExpanded || isHovered : isExpanded || isHovered
? "lg:ml-[290px]" ? "lg:ml-[290px]"
: "lg:ml-[90px]"; : "lg:ml-[0px]";
return ( return (
<div className="min-h-screen xl:flex"> <div className="min-h-screen xl:flex">
{/* Sidebar and Backdrop */} {/* Sidebar and Backdrop */}
{!isHomePage && <AppSidebar />} <AppSidebar />
{!isHomePage && <Backdrop />} <Backdrop />
{/* Main Content Area */} {/* Main Content Area */}
<div <div
className={`flex-1 transition-all duration-300 ease-in-out ${mainContentMargin}`} className={`flex-1 transition-all duration-300 ease-in-out ${mainContentMargin}`}

View File

@@ -27,7 +27,7 @@ export const useSidebar = () => {
export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({ export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({
children, children,
}) => { }) => {
const [isExpanded, setIsExpanded] = useState(true); const [isExpanded, setIsExpanded] = useState(false);
const [isMobileOpen, setIsMobileOpen] = useState(false); const [isMobileOpen, setIsMobileOpen] = useState(false);
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);

View File

@@ -5,15 +5,12 @@ import UserDropdown from "@/components/header/UserDropdown";
import { useSidebar } from "@/context/SidebarContext"; import { useSidebar } from "@/context/SidebarContext";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation";
import React, { useState ,useEffect,useRef} from "react"; import React, { useState ,useEffect,useRef} from "react";
const AppHeader: React.FC = () => { const AppHeader: React.FC = () => {
const [isApplicationMenuOpen, setApplicationMenuOpen] = useState(false); const [isApplicationMenuOpen, setApplicationMenuOpen] = useState(false);
const { isMobileOpen, toggleSidebar, toggleMobileSidebar } = useSidebar(); const { isMobileOpen, toggleSidebar, toggleMobileSidebar } = useSidebar();
const pathname = usePathname();
const isHomePage = pathname === "/";
const handleToggle = () => { const handleToggle = () => {
if (window.innerWidth >= 1024) { if (window.innerWidth >= 1024) {
@@ -47,7 +44,6 @@ const AppHeader: React.FC = () => {
<header className="sticky top-0 flex w-full bg-white border-gray-200 z-99 dark:border-gray-800 dark:bg-gray-900 lg:border-b"> <header className="sticky top-0 flex w-full bg-white border-gray-200 z-99 dark:border-gray-800 dark:bg-gray-900 lg:border-b">
<div className="flex flex-col items-center justify-between grow lg:flex-row lg:px-6"> <div className="flex flex-col items-center justify-between grow lg:flex-row lg:px-6">
<div className="flex items-center justify-between w-full gap-2 px-3 py-3 border-b border-gray-200 dark:border-gray-800 sm:gap-4 lg:justify-normal lg:border-b-0 lg:px-0 lg:py-4"> <div className="flex items-center justify-between w-full gap-2 px-3 py-3 border-b border-gray-200 dark:border-gray-800 sm:gap-4 lg:justify-normal lg:border-b-0 lg:px-0 lg:py-4">
{!isHomePage && (
<button <button
className="items-center justify-center w-10 h-10 text-gray-500 border-gray-200 rounded-lg z-99999 dark:border-gray-800 lg:flex dark:text-gray-400 lg:h-11 lg:w-11 lg:border" className="items-center justify-center w-10 h-10 text-gray-500 border-gray-200 rounded-lg z-99999 dark:border-gray-800 lg:flex dark:text-gray-400 lg:h-11 lg:w-11 lg:border"
onClick={handleToggle} onClick={handleToggle}
@@ -86,7 +82,6 @@ const AppHeader: React.FC = () => {
)} )}
{/* Cross Icon */} {/* Cross Icon */}
</button> </button>
)}
<Link href="/" className="lg:hidden"> <Link href="/" className="lg:hidden">
<Image <Image

View File

@@ -90,19 +90,22 @@ const AppSidebar: React.FC = () => {
const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar(); const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar();
const pathname = usePathname(); const pathname = usePathname();
const [openSubmenu, setOpenSubmenu] = useState<{ const [openSubmenu, setOpenSubmenu] = useState<{
type: "main" | "others"; type: "main" | "others";
index: number; index: number;
} | null>(null); } | null>(null);
const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>({}); const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>(
{},
);
const subMenuRefs = useRef<Record<string, HTMLDivElement | null>>({}); const subMenuRefs = useRef<Record<string, HTMLDivElement | null>>({});
const isActive = useCallback((path: string) => path === pathname, [pathname]); const isActive = useCallback((path: string) => path === pathname, [pathname]);
const handleSubmenuToggle = (index: number, menuType: "main" | "others") => { const handleSubmenuToggle = (index: number, menuType: "main" | "others") => {
setOpenSubmenu((prev) => setOpenSubmenu((prev) =>
prev?.type === menuType && prev?.index === index ? null : { type: menuType, index } prev?.type === menuType && prev?.index === index
? null
: { type: menuType, index },
); );
}; };
@@ -151,23 +154,41 @@ const AppSidebar: React.FC = () => {
onClick={() => handleSubmenuToggle(index, menuType)} onClick={() => handleSubmenuToggle(index, menuType)}
className={`menu-item group uppercase ${ className={`menu-item group uppercase ${
openSubmenu?.type === menuType && openSubmenu?.index === index openSubmenu?.type === menuType && openSubmenu?.index === index
? "menu-item-active" : "menu-item-inactive" ? "menu-item-active"
: "menu-item-inactive"
} cursor-pointer ${!isExpanded && !isHovered ? "lg:justify-center" : "lg:justify-start"}`} } cursor-pointer ${!isExpanded && !isHovered ? "lg:justify-center" : "lg:justify-start"}`}
> >
<span className={openSubmenu?.type === menuType && openSubmenu?.index === index ? "menu-item-icon-active" : "menu-item-icon-inactive"}> <span
className={
openSubmenu?.type === menuType && openSubmenu?.index === index
? "menu-item-icon-active"
: "menu-item-icon-inactive"
}
>
{nav.icon} {nav.icon}
</span> </span>
{(isExpanded || isHovered || isMobileOpen) && ( {(isExpanded || isHovered || isMobileOpen) && (
<> <>
<span className={`menu-item-text`}>{nav.name}</span> <span className={`menu-item-text`}>{nav.name}</span>
<ChevronDownIcon className={`ml-auto w-5 h-5 transition-transform duration-200 ${openSubmenu?.type === menuType && openSubmenu?.index === index ? "rotate-180 text-brand-500" : ""}`} /> <ChevronDownIcon
className={`ml-auto w-5 h-5 transition-transform duration-200 ${openSubmenu?.type === menuType && openSubmenu?.index === index ? "rotate-180 text-brand-500" : ""}`}
/>
</> </>
)} )}
</button> </button>
) : ( ) : (
nav.path && ( nav.path && (
<Link href={nav.path} className={`menu-item group ${isActive(nav.path) ? "menu-item-active" : "menu-item-inactive"}`}> <Link
<span className={isActive(nav.path) ? "menu-item-icon-active" : "menu-item-icon-inactive"}> href={nav.path}
className={`menu-item group ${isActive(nav.path) ? "menu-item-active" : "menu-item-inactive"}`}
>
<span
className={
isActive(nav.path)
? "menu-item-icon-active"
: "menu-item-icon-inactive"
}
>
{nav.icon} {nav.icon}
</span> </span>
{(isExpanded || isHovered || isMobileOpen) && ( {(isExpanded || isHovered || isMobileOpen) && (
@@ -178,18 +199,40 @@ const AppSidebar: React.FC = () => {
)} )}
{nav.subItems && (isExpanded || isHovered || isMobileOpen) && ( {nav.subItems && (isExpanded || isHovered || isMobileOpen) && (
<div <div
ref={(el) => { subMenuRefs.current[`${menuType}-${index}`] = el; }} ref={(el) => {
subMenuRefs.current[`${menuType}-${index}`] = el;
}}
className="overflow-hidden transition-all duration-300" className="overflow-hidden transition-all duration-300"
style={{ height: openSubmenu?.type === menuType && openSubmenu?.index === index ? `${subMenuHeight[`${menuType}-${index}`]}px` : "0px" }} style={{
height:
openSubmenu?.type === menuType && openSubmenu?.index === index
? `${subMenuHeight[`${menuType}-${index}`]}px`
: "0px",
}}
> >
<ul className="mt-2 space-y-1 ml-9"> <ul className="mt-2 space-y-1 ml-9">
{nav.subItems.map((subItem) => ( {nav.subItems.map((subItem) => (
<li key={subItem.name}> <li key={subItem.name}>
<Link href={subItem.path} className={`menu-dropdown-item ${isActive(subItem.path) ? "menu-dropdown-item-active" : "menu-dropdown-item-inactive"}`}> <Link
href={subItem.path}
className={`menu-dropdown-item ${isActive(subItem.path) ? "menu-dropdown-item-active" : "menu-dropdown-item-inactive"}`}
>
{subItem.name} {subItem.name}
<span className="flex items-center gap-1 ml-auto"> <span className="flex items-center gap-1 ml-auto">
{subItem.new && <span className={`${isActive(subItem.path) ? "menu-dropdown-badge-active" : "menu-dropdown-badge-inactive"} menu-dropdown-badge`}>new</span>} {subItem.new && (
{subItem.pro && <span className={`${isActive(subItem.path) ? "menu-dropdown-badge-active" : "menu-dropdown-badge-inactive"} menu-dropdown-badge`}>pro</span>} <span
className={`${isActive(subItem.path) ? "menu-dropdown-badge-active" : "menu-dropdown-badge-inactive"} menu-dropdown-badge`}
>
new
</span>
)}
{subItem.pro && (
<span
className={`${isActive(subItem.path) ? "menu-dropdown-badge-active" : "menu-dropdown-badge-inactive"} menu-dropdown-badge`}
>
pro
</span>
)}
</span> </span>
</Link> </Link>
</li> </li>
@@ -204,13 +247,15 @@ const AppSidebar: React.FC = () => {
return ( return (
<aside <aside
className={`fixed mt-16 flex flex-col lg:mt-0 top-0 px-5 left-0 bg-white dark:bg-gray-900 dark:border-gray-800 text-gray-900 h-screen transition-all duration-300 ease-in-out z-50 border-r border-gray-200 className={`fixed mt-16 flex flex-col lg:mt-0 top-0 left-0 bg-white dark:bg-gray-900 dark:border-gray-800 text-gray-900 h-screen transition-all duration-300 ease-in-out z-50 border-r border-gray-200
${isExpanded || isMobileOpen ? "w-[290px]" : isHovered ? "w-[290px]" : "w-[90px]"} ${isExpanded || isMobileOpen || isHovered ? "w-[290px] px-5" : "w-0 px-0 border-none overflow-hidden"}
${isMobileOpen ? "translate-x-0" : "-translate-x-full"} lg:translate-x-0`} ${isMobileOpen ? "translate-x-0" : "-translate-x-full"} lg:translate-x-0`}
onMouseEnter={() => !isExpanded && setIsHovered(true)} onMouseEnter={() => !isExpanded && setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
> >
<div className={`py-8 flex ${!isExpanded && !isHovered ? "lg:justify-center" : "justify-start"}`}> <div
className={`py-8 flex ${!isExpanded && !isHovered ? "lg:justify-center" : "justify-start"}`}
>
<Link href="/"> <Link href="/">
<Image <Image
src="/images/logo/logo.svg" src="/images/logo/logo.svg"
@@ -224,8 +269,14 @@ const AppSidebar: React.FC = () => {
<nav className="mb-6"> <nav className="mb-6">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div> <div>
<h2 className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${!isExpanded && !isHovered ? "lg:justify-center" : "justify-start"}`}> <h2
{isExpanded || isHovered || isMobileOpen ? "Menu" : <HorizontaLDots />} className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${!isExpanded && !isHovered ? "lg:justify-center" : "justify-start"}`}
>
{isExpanded || isHovered || isMobileOpen ? (
"Menu"
) : (
<HorizontaLDots />
)}
</h2> </h2>
{renderMenuItems(ALL_NAV_ITEMS, "main")} {renderMenuItems(ALL_NAV_ITEMS, "main")}
</div> </div>