revamp template
This commit is contained in:
75
src/layouts/auth-branded/AuthBrandedLayout.tsx
Normal file
75
src/layouts/auth-branded/AuthBrandedLayout.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { Link, Outlet } from 'react-router-dom';
|
||||
import { Fragment, useState, useEffect } from 'react';
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
import useBodyClasses from '@/hooks/useBodyClasses';
|
||||
import { AuthBrandedLayoutProvider } from './AuthBrandedLayoutProvider';
|
||||
|
||||
const Layout = () => {
|
||||
const backgroundImages = [
|
||||
'/media/login-image/img_01.jpg',
|
||||
'/media/login-image/img_02.jpg',
|
||||
'/media/login-image/img_03.jpg'
|
||||
];
|
||||
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||
const [nextImageIndex, setNextImageIndex] = useState(1);
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
useBodyClasses('dark:bg-coal-500');
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const nextIndex = (currentImageIndex + 1) % backgroundImages.length;
|
||||
setNextImageIndex(nextIndex);
|
||||
setIsTransitioning(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setCurrentImageIndex(nextIndex);
|
||||
setIsTransitioning(false);
|
||||
}, 3000);
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [currentImageIndex, backgroundImages.length]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="flex h-full w-full">
|
||||
<div
|
||||
className="order-1 lg:order-1 hidden md:block xl:bg-cover lg:w-8/12 bg-no-repeat branded-bg"
|
||||
style={{ borderRadius: 0 }}
|
||||
>
|
||||
<div className="relative h-screen w-full overflow-hidden">
|
||||
<img
|
||||
src={`${toAbsoluteUrl(backgroundImages[currentImageIndex])}`}
|
||||
className={`absolute top-0 left-0 h-full w-full transition-opacity duration-3000 ${
|
||||
isTransitioning ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
style={{ objectFit: 'cover' }}
|
||||
alt={`Background ${currentImageIndex}`}
|
||||
/>
|
||||
|
||||
{/* Next Image */}
|
||||
<img
|
||||
src={`${toAbsoluteUrl(backgroundImages[nextImageIndex])}`}
|
||||
className={`absolute top-0 left-0 h-full w-full transition-opacity duration-3000 ${
|
||||
isTransitioning ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
style={{ objectFit: 'cover' }}
|
||||
alt={`Background ${nextImageIndex}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center items-center lg:w-4/12 w-full order-2 lg:order-2 bg-white">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const AuthBrandedLayout = () => (
|
||||
<AuthBrandedLayoutProvider>
|
||||
<Layout />
|
||||
</AuthBrandedLayoutProvider>
|
||||
);
|
||||
|
||||
export { AuthBrandedLayout };
|
||||
12
src/layouts/auth-branded/AuthBrandedLayoutConfig.ts
Normal file
12
src/layouts/auth-branded/AuthBrandedLayoutConfig.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { type ILayoutConfig } from '@/providers/LayoutProvider';
|
||||
|
||||
// Defining the configuration for the branded authentication layout
|
||||
const authLayoutBrandedConfig: ILayoutConfig = {
|
||||
// Setting the layout name to 'auth-branded'
|
||||
name: 'auth-branded',
|
||||
|
||||
// Currently no additional options defined, but this object can be extended in the future
|
||||
options: {}
|
||||
};
|
||||
|
||||
export { authLayoutBrandedConfig };
|
||||
52
src/layouts/auth-branded/AuthBrandedLayoutProvider.tsx
Normal file
52
src/layouts/auth-branded/AuthBrandedLayoutProvider.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { createContext, type PropsWithChildren, useContext, useEffect, useState } from 'react';
|
||||
import { deepMerge } from '@/utils';
|
||||
import { ILayoutConfig, useLayout } from '@/providers';
|
||||
import { authLayoutBrandedConfig } from './AuthBrandedLayoutConfig';
|
||||
|
||||
// Defining the interface for AuthLayoutProvider's props, which includes a layout of type ILayoutConfig
|
||||
interface AuthLayoutProviderProps {
|
||||
layout: ILayoutConfig;
|
||||
}
|
||||
|
||||
// Initial layout properties for the AuthBrandedLayoutProvider, using authLayoutBrandedConfig as the default layout
|
||||
const initalLayoutProps: AuthLayoutProviderProps = {
|
||||
layout: authLayoutBrandedConfig
|
||||
};
|
||||
|
||||
// Creating a context for the AuthBrandedLayout with the initial layout properties
|
||||
const LayoutContext = createContext<AuthLayoutProviderProps>(initalLayoutProps);
|
||||
|
||||
// Custom hook to access the AuthBrandedLayout context, allowing other components to use the layout data
|
||||
const useAuthBrandedLayout = () => useContext(LayoutContext);
|
||||
|
||||
// AuthBrandedLayoutProvider component that wraps its children with the layout context
|
||||
const AuthBrandedLayoutProvider = ({ children }: PropsWithChildren) => {
|
||||
const { getLayout, setCurrentLayout } = useLayout(); // Access layout-related functions
|
||||
|
||||
// Function to merge the current layout with the branded auth layout configuration
|
||||
const getLayoutConfig = () => {
|
||||
return deepMerge(authLayoutBrandedConfig, getLayout(authLayoutBrandedConfig.name));
|
||||
};
|
||||
|
||||
// Setting the layout state with the merged layout configuration
|
||||
const [layout] = useState(getLayoutConfig);
|
||||
|
||||
// Effect hook to set the current layout whenever the layout state changes
|
||||
useEffect(() => {
|
||||
setCurrentLayout(layout);
|
||||
}, []);
|
||||
|
||||
// Providing the layout context to all child components wrapped by AuthBrandedLayoutProvider
|
||||
return (
|
||||
<LayoutContext.Provider
|
||||
value={{
|
||||
layout
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LayoutContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { AuthBrandedLayoutProvider, useAuthBrandedLayout };
|
||||
1
src/layouts/auth-branded/index.ts
Normal file
1
src/layouts/auth-branded/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './AuthBrandedLayout';
|
||||
24
src/layouts/demo2/Demo2Layout.tsx
Normal file
24
src/layouts/demo2/Demo2Layout.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import useBodyClasses from '@/hooks/useBodyClasses';
|
||||
import { Demo2LayoutProvider, Main } from './';
|
||||
|
||||
const Demo2Layout = () => {
|
||||
// Using the custom hook to set multiple CSS variables and class properties
|
||||
useBodyClasses(`
|
||||
[--tw-page-bg:var(--tw-light)]
|
||||
[--tw-page-bg-dark:var(--tw-coal-500)]
|
||||
[--tw-header-height-default:100px]
|
||||
[[data-sticky-header=on]&]:[--tw-header-height:60px]
|
||||
[--tw-header-height:--tw-header-height-default]
|
||||
bg-[--tw-page-bg]
|
||||
dark:bg-[--tw-page-bg-dark]
|
||||
`);
|
||||
|
||||
return (
|
||||
// Providing layout context and rendering the main content
|
||||
<Demo2LayoutProvider>
|
||||
<Main />
|
||||
</Demo2LayoutProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export { Demo2Layout };
|
||||
13
src/layouts/demo2/Demo2LayoutConfig.ts
Normal file
13
src/layouts/demo2/Demo2LayoutConfig.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { type ILayoutConfig } from '@/providers';
|
||||
|
||||
// Defining the layout configuration specific to Demo2 layout
|
||||
const Demo2LayoutConfig: ILayoutConfig = {
|
||||
name: 'demo2-layout', // Unique name identifier for this layout
|
||||
options: {
|
||||
header: {
|
||||
stickyOffset: 200 // Offset value (in pixels) that determines when the header becomes sticky on scroll
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { Demo2LayoutConfig };
|
||||
75
src/layouts/demo2/Demo2LayoutProvider.tsx
Normal file
75
src/layouts/demo2/Demo2LayoutProvider.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { createContext, type PropsWithChildren, useContext, useEffect, useState } from 'react';
|
||||
import { MENU_SIDEBAR } from '@/config';
|
||||
import { useScrollPosition } from '@/hooks/useScrollPosition';
|
||||
import { useMenus } from '@/providers';
|
||||
import { ILayoutConfig, useLayout } from '@/providers';
|
||||
import { deepMerge } from '@/utils';
|
||||
import { Demo2LayoutConfig } from './';
|
||||
|
||||
// Interface defining the properties of the layout provider context
|
||||
export interface IDemo2LayoutProviderProps {
|
||||
layout: ILayoutConfig; // The layout configuration object
|
||||
headerSticky: boolean; // Whether the header should stick to the top on scroll
|
||||
mobileSidebarOpen: boolean; // Whether the mobile sidebar is open
|
||||
setMobileSidebarOpen: (open: boolean) => void; // Function to toggle the mobile sidebar
|
||||
}
|
||||
|
||||
// Initial layout provider properties, using Demo2 layout configuration as the default
|
||||
const initalLayoutProps: IDemo2LayoutProviderProps = {
|
||||
layout: Demo2LayoutConfig, // Default layout configuration
|
||||
headerSticky: false, // Header is not sticky by default
|
||||
mobileSidebarOpen: false, // Mobile sidebar is closed by default
|
||||
setMobileSidebarOpen: (open: boolean) => {
|
||||
console.log(`${open}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Create a context to manage the layout-related state and logic for Demo2 layout
|
||||
const Demo2LayoutContext = createContext<IDemo2LayoutProviderProps>(initalLayoutProps);
|
||||
|
||||
// Custom hook to access the layout context in other components
|
||||
const useDemo2Layout = () => useContext(Demo2LayoutContext);
|
||||
|
||||
// Provider component that sets up the layout state and context for Demo2 layout
|
||||
const Demo2LayoutProvider = ({ children }: PropsWithChildren) => {
|
||||
const { setMenuConfig } = useMenus(); // Hook to manage menu configurations
|
||||
const { getLayout, setCurrentLayout } = useLayout(); // Hook to get and set layout configuration
|
||||
|
||||
// Merge the Demo2 layout configuration with the current layout configuration fetched via getLayout
|
||||
const layoutConfig = deepMerge(Demo2LayoutConfig, getLayout(Demo2LayoutConfig.name));
|
||||
|
||||
// Set the initial state for layout and mobile sidebar
|
||||
const [layout] = useState(layoutConfig); // Layout configuration is stored in state
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); // Manage state for mobile sidebar
|
||||
|
||||
// Get the current scroll position using a custom hook
|
||||
const scrollPosition = useScrollPosition();
|
||||
|
||||
// Calculate whether the header should be sticky based on the scroll position and the layout's sticky offset
|
||||
const headerSticky: boolean = scrollPosition > layout.options.header.stickyOffset;
|
||||
|
||||
// Set the menu configuration for the primary menu using the provided MENU_SIDEBAR configuration
|
||||
setMenuConfig('primary', MENU_SIDEBAR);
|
||||
|
||||
// When the layout state changes, set the current layout configuration in the layout provider
|
||||
useEffect(() => {
|
||||
setCurrentLayout(layout); // Update the current layout in the global layout state
|
||||
}, [layout, setCurrentLayout]); // Re-run this effect if layout or setCurrentLayout changes
|
||||
|
||||
// Provide the layout state, sticky header state, and sidebar state to children components via context
|
||||
return (
|
||||
<Demo2LayoutContext.Provider
|
||||
value={{
|
||||
layout, // The current layout configuration
|
||||
headerSticky, // Whether the header should be sticky based on the scroll position
|
||||
mobileSidebarOpen, // Whether the mobile sidebar is currently open
|
||||
setMobileSidebarOpen // Function to toggle the mobile sidebar state
|
||||
}}
|
||||
>
|
||||
{children} {/* Render child components that consume this context */}
|
||||
</Demo2LayoutContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { Demo2LayoutProvider, useDemo2Layout };
|
||||
22
src/layouts/demo2/footer/Footer.tsx
Normal file
22
src/layouts/demo2/footer/Footer.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { Container } from '@/components/container';
|
||||
|
||||
const Footer = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="footer">
|
||||
<Container>
|
||||
<div className="flex flex-col md:flex-row justify-center md:justify-between items-center gap-3 py-5">
|
||||
<div className="flex order-2 md:order-1 gap-2 font-normal text-2sm">
|
||||
<span className="text-gray-500">{currentYear}©</span>
|
||||
<a href="https://" target="_blank" className="text-gray-600 hover:text-primary">
|
||||
Brillian Dev.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export { Footer };
|
||||
1
src/layouts/demo2/footer/index.ts
Normal file
1
src/layouts/demo2/footer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Footer';
|
||||
38
src/layouts/demo2/header/Header.tsx
Normal file
38
src/layouts/demo2/header/Header.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import clsx from 'clsx';
|
||||
import { Container } from '@/components/container';
|
||||
import { HeaderLogo, HeaderTopbar } from '.';
|
||||
import { useDemo2Layout } from '../';
|
||||
import { useEffect } from 'react';
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
|
||||
const Header = () => {
|
||||
const { headerSticky } = useDemo2Layout();
|
||||
|
||||
useEffect(() => {
|
||||
if (headerSticky) {
|
||||
document.body.setAttribute('data-sticky-header', 'on');
|
||||
} else {
|
||||
document.body.removeAttribute('data-sticky-header');
|
||||
}
|
||||
}, [headerSticky]);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={clsx(
|
||||
'flex items-center transition-[height] shrink-0 h-[--tw-header-height] bg-[length:600px] bg-no-repeat',
|
||||
headerSticky &&
|
||||
'transition-[height] fixed z-10 top-0 left-0 right-0 shadow-sm backdrop-blur-md bg-white/70 dark:bg-coal-500/70 dark:border-b dark:border-b-coal-100'
|
||||
)}
|
||||
style={{
|
||||
backgroundImage: `url('${toAbsoluteUrl('/media/images/2600x1200/bg-14.png')}')`
|
||||
}}
|
||||
>
|
||||
<Container className="flex justify-between items-center lg:gap-4">
|
||||
<HeaderLogo />
|
||||
<HeaderTopbar />
|
||||
</Container>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export { Header };
|
||||
104
src/layouts/demo2/header/HeaderLogo.tsx
Normal file
104
src/layouts/demo2/header/HeaderLogo.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { KeenIcon } from '@/components/keenicons';
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
import {
|
||||
Menu,
|
||||
MenuArrow,
|
||||
MenuIcon,
|
||||
MenuItem,
|
||||
MenuLink,
|
||||
MenuSub,
|
||||
MenuTitle,
|
||||
MenuToggle
|
||||
} from '@/components/menu';
|
||||
import { MENU_ROOT } from '@/config';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLanguage } from '@/i18n';
|
||||
|
||||
const HeaderLogo = () => {
|
||||
const { pathname } = useLocation();
|
||||
const { isRTL } = useLanguage();
|
||||
const [selectedMenuItem, setSelectedMenuItem] = useState(MENU_ROOT[0]);
|
||||
|
||||
useEffect(() => {
|
||||
MENU_ROOT.forEach((item) => {
|
||||
if (item.rootPath && pathname.includes(item.rootPath)) {
|
||||
setSelectedMenuItem(item);
|
||||
}
|
||||
});
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
// <div className="flex items-center gap-2 lg:gap-5 2xl:-ml-[60px]">
|
||||
<div className="flex items-center gap-2 lg:gap-5">
|
||||
<Link to="/" className="shrink-0">
|
||||
{/* <img
|
||||
src={toAbsoluteUrl('/media/app/mini-logo-circle.svg')}
|
||||
className="dark:hidden min-h-[42px]"
|
||||
alt="logo"
|
||||
/>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/app/mini-logo-circle-dark.svg')}
|
||||
className="hidden dark:inline-block min-h-[42px]"
|
||||
alt="logo"
|
||||
/> */}
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/app/bri_tl_logo.png')}
|
||||
className="dark:hidden h-10"
|
||||
alt="logo"
|
||||
/>
|
||||
<img
|
||||
src={toAbsoluteUrl('/media/app/bri_tl_logo.png')}
|
||||
className="hidden dark:inline-block min-h-[42px]"
|
||||
alt="logo"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center">
|
||||
<h3 className="text-gray-700 text-base hidden md:block">Brillian Apps</h3>
|
||||
<span className="text-sm text-gray-400 font-medium px-2.5 hidden md:inline">/</span>
|
||||
|
||||
<Menu className="menu-default">
|
||||
<MenuItem
|
||||
toggle="dropdown"
|
||||
trigger="hover"
|
||||
dropdownProps={{
|
||||
placement: isRTL() ? 'bottom-end' : 'bottom-start',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 10] // [skid, distance]
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
>
|
||||
<MenuToggle className="text-gray-900 font-medium">
|
||||
{selectedMenuItem.title}
|
||||
<MenuArrow>
|
||||
<KeenIcon icon="down" />
|
||||
</MenuArrow>
|
||||
</MenuToggle>
|
||||
<MenuSub className="menu-default w-48">
|
||||
{MENU_ROOT.map((item, index) => (
|
||||
<MenuItem key={index} className={item === selectedMenuItem ? 'active' : ''}>
|
||||
<MenuLink path={item.path}>
|
||||
{item.icon && (
|
||||
<MenuIcon>
|
||||
<KeenIcon icon={item.icon} />
|
||||
</MenuIcon>
|
||||
)}
|
||||
<MenuTitle>{item.title}</MenuTitle>
|
||||
</MenuLink>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuSub>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { HeaderLogo };
|
||||
51
src/layouts/demo2/header/HeaderTopbar.tsx
Normal file
51
src/layouts/demo2/header/HeaderTopbar.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { useRef } from 'react';
|
||||
import { KeenIcon } from '@/components/keenicons';
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
import { Menu, MenuItem, MenuToggle } from '@/components';
|
||||
import { DropdownUser } from '@/partials/dropdowns/user';
|
||||
import { useLanguage } from '@/i18n';
|
||||
|
||||
const HeaderTopbar = () => {
|
||||
const itemChatRef = useRef<any>(null);
|
||||
const itemUserRef = useRef<any>(null);
|
||||
const itemNotificationsRef = useRef<any>(null);
|
||||
const { isRTL } = useLanguage();
|
||||
|
||||
const handleDropdownChatShow = () => {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3.5">
|
||||
<Menu>
|
||||
<MenuItem
|
||||
ref={itemUserRef}
|
||||
toggle="dropdown"
|
||||
trigger="click"
|
||||
dropdownProps={{
|
||||
placement: isRTL() ? 'bottom-start' : 'bottom-end',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [20, 10] // [skid, distance]
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
>
|
||||
<MenuToggle className="btn btn-icon rounded-full">
|
||||
<img
|
||||
className="size-9 rounded-full justify-center border border-gray-500 shrink-0"
|
||||
src={toAbsoluteUrl('/media/avatars/blank.png')}
|
||||
alt=""
|
||||
/>
|
||||
</MenuToggle>
|
||||
{DropdownUser({ menuItemRef: itemUserRef })}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { HeaderTopbar };
|
||||
3
src/layouts/demo2/header/index.ts
Normal file
3
src/layouts/demo2/header/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './Header';
|
||||
export * from './HeaderLogo';
|
||||
export * from './HeaderTopbar';
|
||||
8
src/layouts/demo2/index.ts
Normal file
8
src/layouts/demo2/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './Demo2Layout';
|
||||
export * from './Demo2LayoutConfig';
|
||||
export * from './Demo2LayoutProvider';
|
||||
export * from './main';
|
||||
export * from './header';
|
||||
export * from './navbar';
|
||||
export * from './toolbar';
|
||||
export * from './footer';
|
||||
38
src/layouts/demo2/main/Main.tsx
Normal file
38
src/layouts/demo2/main/Main.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { useMenuCurrentItem } from '@/components/menu';
|
||||
import { useMenus } from '@/providers';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { Outlet, useLocation } from 'react-router';
|
||||
import { Footer, Header, Navbar } from '../';
|
||||
import { Toolbar, ToolbarHeading } from '../toolbar';
|
||||
|
||||
const Main = () => {
|
||||
const { pathname } = useLocation();
|
||||
const { getMenuConfig } = useMenus();
|
||||
const menuConfig = getMenuConfig('primary');
|
||||
const menuItem = useMenuCurrentItem(pathname, menuConfig);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>{menuItem?.title}</title>
|
||||
</Helmet>
|
||||
<div className="flex grow flex-col [[data-sticky-header=on]_&]:pt-[--tw-header-height-default]">
|
||||
<Header />
|
||||
|
||||
<Navbar />
|
||||
|
||||
<main className="grow" role="content">
|
||||
<Toolbar>
|
||||
<ToolbarHeading />
|
||||
</Toolbar>
|
||||
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export { Main };
|
||||
1
src/layouts/demo2/main/index.ts
Normal file
1
src/layouts/demo2/main/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Main';
|
||||
14
src/layouts/demo2/navbar/Navbar.tsx
Normal file
14
src/layouts/demo2/navbar/Navbar.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Container } from '@/components/container';
|
||||
import { NavbarMenu } from '../';
|
||||
|
||||
const Navbar = () => {
|
||||
return (
|
||||
<div className="border-b border-gray-200 pb-5 lg:pb-0 mb-5 lg:mb-5">
|
||||
<Container className="flex flex-wrap justify-between items-center gap-2">
|
||||
<NavbarMenu />
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Navbar };
|
||||
159
src/layouts/demo2/navbar/NavbarMenu.tsx
Normal file
159
src/layouts/demo2/navbar/NavbarMenu.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import { KeenIcon } from '@/components/keenicons';
|
||||
import {
|
||||
Menu,
|
||||
MenuArrow,
|
||||
TMenuConfig,
|
||||
MenuItem,
|
||||
MenuLink,
|
||||
MenuSub,
|
||||
MenuTitle
|
||||
} from '@/components/menu';
|
||||
import { useMenus } from '@/providers';
|
||||
// import { useLocation } from 'react-router';
|
||||
import { useLanguage } from '@/i18n';
|
||||
import { doGetNavbarMenu } from '@/actions/NavbarMenuActions';
|
||||
import { RoleList, MappedMenu } from '@/types/GlobalTypes';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const NavbarMenu = () => {
|
||||
// const { pathname } = useLocation();
|
||||
// const { getMenuConfig } = useMenus();
|
||||
// const primaryMenu = getMenuConfig('primary');
|
||||
const { isRTL } = useLanguage();
|
||||
const [menuData, setMenuData] = useState<RoleList[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
let navbarMenu;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchNavbarMenu = async () => {
|
||||
try {
|
||||
const result = await doGetNavbarMenu();
|
||||
if (result.status && result.data) {
|
||||
setMenuData(result.data.roles_list);
|
||||
} else {
|
||||
setError(result.message);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to fetch navbar menu');
|
||||
}
|
||||
};
|
||||
|
||||
fetchNavbarMenu();
|
||||
}, []);
|
||||
|
||||
const mapMenuData = (data: RoleList[]): MappedMenu[] => {
|
||||
return data.map((item) => {
|
||||
const mappedItem: MappedMenu = {
|
||||
title: item.name,
|
||||
path: item.link !== '-' ? item.link : '/'
|
||||
};
|
||||
|
||||
if (item.children && item.children.length > 0) {
|
||||
mappedItem.children = mapMenuData(item.children);
|
||||
}
|
||||
|
||||
return mappedItem;
|
||||
});
|
||||
};
|
||||
|
||||
navbarMenu = mapMenuData(menuData);
|
||||
// navbarMenu = primaryMenu?.[0].children;
|
||||
|
||||
const buildMenu = (items: TMenuConfig) => {
|
||||
return items.map((item, index) => {
|
||||
if (item.children) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
className="border-b-2 border-b-transparent menu-item-active:border-b-gray-900 menu-item-here:border-b-gray-900"
|
||||
trigger="hover"
|
||||
toggle="dropdown"
|
||||
dropdownProps={{
|
||||
placement: isRTL() ? 'bottom-end' : 'bottom-start'
|
||||
}}
|
||||
>
|
||||
<MenuLink className="gap-1.5 pb-2 lg:pb-4">
|
||||
<MenuTitle className="text-nowrap text-sm text-gray-800 menu-item-active:text-gray-900 menu-item-active:font-medium menu-item-here:text-gray-900 menu-item-here:font-medium menu-item-show:text-gray-900 menu-link-hover:text-gray-900">
|
||||
{item.title}
|
||||
</MenuTitle>
|
||||
<MenuArrow>
|
||||
<KeenIcon icon="down" className="text-2xs text-gray-500" />
|
||||
</MenuArrow>
|
||||
</MenuLink>
|
||||
<MenuSub className="menu-default py-2" rootClassName="min-w-[200px]">
|
||||
{buildMenuChildren(item.children)}
|
||||
</MenuSub>
|
||||
</MenuItem>
|
||||
);
|
||||
} else if (!item.disabled) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
className="border-b-2 border-b-transparent menu-item-active:border-b-gray-900 menu-item-here:border-b-gray-900"
|
||||
>
|
||||
<MenuLink path={item.path} className="gap-2.5 pb-2 lg:pb-4">
|
||||
<MenuTitle className="text-nowrap text-sm text-gray-800 menu-item-active:text-gray-900 menu-item-active:font-medium menu-item-here:text-gray-900 menu-item-here:font-medium menu-item-show:text-gray-900 menu-link-hover:text-gray-900">
|
||||
{item.title}
|
||||
</MenuTitle>
|
||||
</MenuLink>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const buildMenuChildren = (items: TMenuConfig) => {
|
||||
return items.map((item, index) => {
|
||||
if (item.children) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
trigger="hover"
|
||||
toggle="dropdown"
|
||||
dropdownProps={{
|
||||
placement: isRTL() ? 'left-start' : 'right-start',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [-10, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
>
|
||||
<MenuLink>
|
||||
<MenuTitle>{item.title}</MenuTitle>
|
||||
<MenuArrow>
|
||||
<KeenIcon icon="down" className="text-2xs [.menu-dropdown_&]:-rotate-90" />
|
||||
</MenuArrow>
|
||||
</MenuLink>
|
||||
<MenuSub className="menu-default" rootClassName="min-w-[200px]">
|
||||
{buildMenuChildren(item.children)}
|
||||
</MenuSub>
|
||||
</MenuItem>
|
||||
);
|
||||
} else if (!item.disabled) {
|
||||
return (
|
||||
<MenuItem key={index}>
|
||||
<MenuLink path={item.path}>
|
||||
<MenuTitle>{item.title}</MenuTitle>
|
||||
</MenuLink>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid">
|
||||
<div className="scrollable-x-auto">
|
||||
<Menu highlight={true} className="gap-5 lg:gap-7.5">
|
||||
{navbarMenu && navbarMenu && buildMenu(navbarMenu)}
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { NavbarMenu };
|
||||
2
src/layouts/demo2/navbar/index.ts
Normal file
2
src/layouts/demo2/navbar/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './Navbar';
|
||||
export * from './NavbarMenu';
|
||||
18
src/layouts/demo2/toolbar/Toolbar.tsx
Normal file
18
src/layouts/demo2/toolbar/Toolbar.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Container } from '@/components';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface IToolbarProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const Toolbar = ({ children }: IToolbarProps) => {
|
||||
return (
|
||||
<div className="mb-5 lg:mb-5">
|
||||
<Container className="flex items-center justify-between flex-wrap gap-5">
|
||||
{children}
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Toolbar };
|
||||
11
src/layouts/demo2/toolbar/ToolbarActions.tsx
Normal file
11
src/layouts/demo2/toolbar/ToolbarActions.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface IToolbarActionsProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const ToolbarActions = ({ children }: IToolbarActionsProps) => {
|
||||
return <div className="flex items-center gap-1">{children}</div>;
|
||||
};
|
||||
|
||||
export { ToolbarActions };
|
||||
32
src/layouts/demo2/toolbar/ToolbarBreadcrumbs.tsx
Normal file
32
src/layouts/demo2/toolbar/ToolbarBreadcrumbs.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Fragment } from 'react';
|
||||
import { useMenuBreadcrumbs } from '@/components';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useMenus } from '@/providers';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
const ToolbarBreadcrumbs = () => {
|
||||
const { getMenuConfig } = useMenus();
|
||||
const { pathname } = useLocation();
|
||||
const items = useMenuBreadcrumbs(pathname, getMenuConfig('primary'));
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-sm font-normal">
|
||||
{items.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
{item.path ? (
|
||||
<Link to={item.path} className="text-gray-700 hover:text-primary">
|
||||
{item.title}
|
||||
</Link>
|
||||
) : (
|
||||
<span className={index === items.length - 1 ? 'text-gray-900' : 'text-gray-700'}>
|
||||
{item.title}
|
||||
</span>
|
||||
)}
|
||||
{index !== items.length - 1 && <span className="text-gray-400 text-sm">/</span>}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ToolbarBreadcrumbs };
|
||||
24
src/layouts/demo2/toolbar/ToolbarHeading.tsx
Normal file
24
src/layouts/demo2/toolbar/ToolbarHeading.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { useMenus } from '@/providers';
|
||||
import { useMenuCurrentItem } from '@/components';
|
||||
import { useLocation } from 'react-router';
|
||||
import { ToolbarBreadcrumbs } from './ToolbarBreadcrumbs';
|
||||
|
||||
export interface IToolbarHeadingProps {
|
||||
title?: string | ReactNode;
|
||||
}
|
||||
|
||||
const ToolbarHeading = ({ title = '' }: IToolbarHeadingProps) => {
|
||||
const { getMenuConfig } = useMenus();
|
||||
const { pathname } = useLocation();
|
||||
const currentMenuItem = useMenuCurrentItem(pathname, getMenuConfig('primary'));
|
||||
|
||||
return (
|
||||
<div className="flex items-center flex-wrap gap-1 lg:gap-5">
|
||||
<h1 className="font-medium text-lg text-gray-900">{title || currentMenuItem?.title}</h1>
|
||||
<ToolbarBreadcrumbs />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ToolbarHeading };
|
||||
4
src/layouts/demo2/toolbar/index.ts
Normal file
4
src/layouts/demo2/toolbar/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './Toolbar';
|
||||
export * from './ToolbarBreadcrumbs';
|
||||
export * from './ToolbarHeading';
|
||||
export * from './ToolbarActions';
|
||||
18
src/layouts/errors/ErrorsLayout.tsx
Normal file
18
src/layouts/errors/ErrorsLayout.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { ErrorsLayoutProvider } from './ErrorsLayoutProvider';
|
||||
|
||||
const Layout = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center grow h-[95%]">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorsLayout = () => (
|
||||
<ErrorsLayoutProvider>
|
||||
<Layout />
|
||||
</ErrorsLayoutProvider>
|
||||
);
|
||||
|
||||
export { ErrorsLayout };
|
||||
9
src/layouts/errors/ErrorsLayoutConfig.ts
Normal file
9
src/layouts/errors/ErrorsLayoutConfig.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { type ILayoutConfig } from '@/providers';
|
||||
|
||||
// Defining the layout configuration for the errors layout
|
||||
const errorsLayoutConfig: ILayoutConfig = {
|
||||
name: 'errors-layout', // Unique identifier for the layout
|
||||
options: {} // Placeholder for layout options, can be customized later
|
||||
};
|
||||
|
||||
export { errorsLayoutConfig };
|
||||
51
src/layouts/errors/ErrorsLayoutProvider.tsx
Normal file
51
src/layouts/errors/ErrorsLayoutProvider.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { createContext, type PropsWithChildren, useContext, useEffect, useState } from 'react';
|
||||
import { deepMerge } from '@/utils';
|
||||
import { ILayoutConfig, useLayout } from '@/providers';
|
||||
import { errorsLayoutConfig } from './ErrorsLayoutConfig';
|
||||
|
||||
// Interface defining the properties for the AuthLayoutProvider
|
||||
interface AuthLayoutProviderProps {
|
||||
layout: ILayoutConfig; // The layout configuration object
|
||||
}
|
||||
|
||||
// Initial layout properties using the errors layout configuration
|
||||
const initalLayoutProps: AuthLayoutProviderProps = {
|
||||
layout: errorsLayoutConfig // Initial layout is set to errorsLayoutConfig
|
||||
};
|
||||
|
||||
// Creating a context for managing layout-related state and logic
|
||||
const LayoutContext = createContext<AuthLayoutProviderProps>(initalLayoutProps);
|
||||
|
||||
// Custom hook to access the layout context, simplifying its use in other components
|
||||
const useErrorsLayout = () => useContext(LayoutContext);
|
||||
|
||||
// Provider component that manages the state and context for the Errors layout
|
||||
const ErrorsLayoutProvider = ({ children }: PropsWithChildren) => {
|
||||
const { getLayout, setCurrentLayout } = useLayout(); // Hook to get and set the layout configuration
|
||||
|
||||
// Function to get and merge the layout configuration
|
||||
const getLayoutConfig = () => {
|
||||
return deepMerge(errorsLayoutConfig, getLayout(errorsLayoutConfig.name)); // Merge errors layout config with any layout changes from getLayout
|
||||
};
|
||||
|
||||
// State that holds the current layout configuration
|
||||
const [layout] = useState(getLayoutConfig); // Initializing layout state with merged configuration
|
||||
|
||||
// Effect to set the current layout whenever the layout state changes
|
||||
useEffect(() => {
|
||||
setCurrentLayout(layout); // Sets the layout context to the current layout configuration
|
||||
});
|
||||
|
||||
return (
|
||||
<LayoutContext.Provider
|
||||
value={{
|
||||
layout // Providing the layout object to child components
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LayoutContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { ErrorsLayoutProvider, useErrorsLayout };
|
||||
2
src/layouts/errors/index.ts
Normal file
2
src/layouts/errors/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './ErrorsLayout';
|
||||
export * from './ErrorsLayoutConfig';
|
||||
Reference in New Issue
Block a user