add: side menu transition
This commit is contained in:
+7
-13
@@ -1,6 +1,6 @@
|
||||
import BottomNavBar from "@/components/navigation/BottomNavBar";
|
||||
import { ReactQueryProvider } from "@/components/providers/ReactQueryProvider";
|
||||
import { Menu } from '@/components/menu/Menu';
|
||||
import ClientSideWrapper from "@/components/wrapper/ClientSideWrapper";
|
||||
import ClientMenuRouteWrapper from "@/components/wrapper/ClientMenuRouteWrapper.tsx";
|
||||
|
||||
export const metadata = {
|
||||
title: 'Menu',
|
||||
@@ -14,17 +14,11 @@ export default function MenuLayout({
|
||||
|
||||
return (
|
||||
<ReactQueryProvider>
|
||||
<main className="h-full pb-0">
|
||||
{children}
|
||||
</main>
|
||||
<aside>
|
||||
|
||||
<Menu />
|
||||
</aside>
|
||||
<footer>
|
||||
<BottomNavBar />
|
||||
|
||||
</footer>
|
||||
<ClientSideWrapper>
|
||||
<ClientMenuRouteWrapper>
|
||||
{children}
|
||||
</ClientMenuRouteWrapper>
|
||||
</ClientSideWrapper>
|
||||
</ReactQueryProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
|
||||
} & React.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
export default function NightModeSwitch({ checked, onClick }: Props) {
|
||||
return (
|
||||
<input
|
||||
onClick={onClick}
|
||||
className={`visual-switch relative inline-flex justify-between px-1.5 h-6 w-11 items-center rounded-full transition-colors duration-300 ${checked ? "bg-green-500" : "bg-gray-300"
|
||||
}`}
|
||||
>
|
||||
A
|
||||
<span
|
||||
className={`absolute inline-block h-5 w-5 transform rounded-full bg-white transition-transform duration-300 ${checked ? "translate-x-1" : "-translate-x-3.5"
|
||||
}`}
|
||||
/>
|
||||
B
|
||||
</input>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react'
|
||||
import { PiSun, PiMoon } from 'react-icons/pi'
|
||||
|
||||
export default function NightModeSwitch({ state, toggle }) {
|
||||
return (
|
||||
<button
|
||||
onClick={toggle}
|
||||
className={`visual-switch relative inline-flex justify-between px-1.5 h-6 w-11 items-center rounded-full transition-colors duration-300 ${state ? "bg-green-500" : "bg-gray-300"
|
||||
}`}
|
||||
>
|
||||
<PiMoon color={state ? "#FFA800" : "#292D32"} className='z-10' size={12} />
|
||||
<span
|
||||
className={`absolute inline-block h-5 w-5 transform rounded-full bg-white transition-transform duration-300 ${state ? "translate-x-1" : "-translate-x-3.5"
|
||||
}`}
|
||||
/>
|
||||
<PiSun color={!state ? "#FFA800" : "#292D32"} className='z-10' size={12} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = React.SVGProps<SVGSVGElement>;
|
||||
|
||||
const BurgerMenuIcon = (props: Props) => (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3 7H21"
|
||||
stroke="#292D32"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 12H21"
|
||||
stroke="#292D32"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 17H21"
|
||||
stroke="#292D32"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default BurgerMenuIcon;
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import MenuItemType from './MenuItem'
|
||||
import MenuItem from './MenuItem';
|
||||
import NotificationBellIcon from '../icons/NotificationBellIcon';
|
||||
@@ -63,12 +63,21 @@ const menuItems: Array<Array<MenuItemType>> = [
|
||||
]
|
||||
]
|
||||
|
||||
export function Menu() {
|
||||
const [menuState, setMenuState] = useState(true);
|
||||
type Props = {
|
||||
menuState: boolean,
|
||||
toggleMenuState: React.MouseEventHandler<HTMLDivElement> | undefined
|
||||
}
|
||||
|
||||
export function Menu({ menuState, toggleMenuState }: Props) {
|
||||
const menuStateMemo = useMemo(() => menuState, [menuState]);
|
||||
|
||||
return (
|
||||
<BlurredOverlayContainer bgOpacity={40} onClick={() => setMenuState((state) => !state)} visible={menuState}>
|
||||
<div data-visible={menuState} onClick={() => setMenuState((state) => !state)} className='absolute top-0 -right-full data-[visible]:right-0 transition-all duration-200 ease-in-out not-xl:!rounded-s-none overflow-clip bg-white w-[200px] h-full flex flex-col z-40'>
|
||||
<BlurredOverlayContainer bgOpacity='40' visible={menuStateMemo} changeDelay='300'>
|
||||
<div className='w-full h-full' onClick={toggleMenuState}></div>
|
||||
<div
|
||||
data-isvisible={menuStateMemo}
|
||||
onClick={(e) => { e.stopPropagation(); }}
|
||||
className={`absolute top-0 -right-full data-[isvisible=true]:right-0 transition-all duration-300 ease-in-out not-xl:!rounded-s-none overflow-clip bg-white w-[200px] h-full flex flex-col z-40`}>
|
||||
<ul className="overflow-y-auto hide-scrollbar pt-20 pb-10 flex flex-col justify-between h-full ">
|
||||
<li>
|
||||
<div className='flex flex-col gap-[60px]'>
|
||||
|
||||
@@ -1,37 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import PingAnimation from './animations/PingAnimation';
|
||||
|
||||
type Props = {
|
||||
visible?: boolean;
|
||||
bgOpacity?: number;
|
||||
delay?: number;
|
||||
} & React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
|
||||
visible?: boolean
|
||||
bgOpacity?: string
|
||||
changeDelay?: string
|
||||
effectdelay?: string
|
||||
} & React.DetailedHTMLProps<React.HTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
|
||||
|
||||
const BlurredOverlayContainer = ({ visible = true, bgOpacity = 30, delay = 0, children = <PingAnimation /> }: Props) => {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const BlurredOverlayContainer = ({
|
||||
visible = true,
|
||||
bgOpacity = '30',
|
||||
changeDelay = '0',
|
||||
effectdelay = '0',
|
||||
children,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [loaded, setLoaded] = useState(visible)
|
||||
|
||||
useEffect(() => {
|
||||
// Trigger fade-in after mount
|
||||
const timeout = setTimeout(() => setLoaded(visible), 300);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [visible]);
|
||||
if (visible) {
|
||||
// Show immediately or after delay
|
||||
const timeout = setTimeout(() => setLoaded(true), +changeDelay)
|
||||
return () => clearTimeout(timeout)
|
||||
} else {
|
||||
// Hide with delay (matches opacity transition duration)
|
||||
const timeout = setTimeout(() => setLoaded(false), +changeDelay) // 300ms matches Tailwind transition
|
||||
return () => clearTimeout(timeout)
|
||||
}
|
||||
}, [visible, changeDelay])
|
||||
|
||||
const zClass =
|
||||
visible || loaded ? 'z-50' : !visible && !loaded ? '-z-50' : 'z-0'
|
||||
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
data-visible={visible}
|
||||
data-loaded={loaded}
|
||||
className={`absolute inset-0 flex items-center justify-center
|
||||
backdrop-blur-sm bg-black/${bgOpacity}
|
||||
opacity-0 transition-opacity duration-300 ease-in-out delay-${delay}
|
||||
data-[loaded=false]:-z-50
|
||||
data-[visible=true]:z-50
|
||||
data-[visible=true]:opacity-100`}
|
||||
backdrop-blur-sm bg-black/${bgOpacity}
|
||||
opacity-0 transition-opacity duration-300 ease-in-out delay-${effectdelay}
|
||||
${zClass}
|
||||
${visible ? 'opacity-100' : 'opacity-0'}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlurredOverlayContainer
|
||||
export default BlurredOverlayContainer
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import NightModeSwitch from '../button/NightModeSwitch'
|
||||
import BurgerMenuIcon from '../icons/BurgerMenuIcon'
|
||||
|
||||
type Props = {
|
||||
profileDropState: boolean,
|
||||
toggleProfileDropState: React.MouseEventHandler<HTMLButtonElement> | undefined
|
||||
menuState: boolean,
|
||||
toggleMenuState: React.MouseEventHandler<HTMLButtonElement> | undefined
|
||||
searchModalState: boolean,
|
||||
toggleSearchModalState: React.MouseEventHandler<HTMLDivElement> | undefined
|
||||
nightModeState: boolean,
|
||||
toggleNightModeState: React.MouseEventHandler<HTMLDivElement> | undefined
|
||||
}
|
||||
|
||||
function TopBar({ profileDropState, toggleProfileDropState, menuState, toggleMenuState, searchModalState, toggleSearchModalState, nightModeState, toggleNightModeState }: Props) {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className='bg-white rounded-[15px] w-full h-12 lg:h-24 px-4 sm:p-6 xl:gap-8 items-center flex justify-between overflow-auto'>
|
||||
<div className='inline-flex justify-between items-center'>
|
||||
<button className='inline-block xl:hidden' onClick={toggleMenuState}>
|
||||
<BurgerMenuIcon />
|
||||
</button>
|
||||
<div onClick={toggleSearchModalState} className='xl:bg-[#EEF0F7] xl:hidden inline-flex rounded-xl px-4 h-12 xl:w-[370px] items-center content-center' >
|
||||
S
|
||||
</div>
|
||||
<div
|
||||
className={`z-50 top-0 left-0 w-full h-full px-5 py-15 md:px-15 xl:p-0 fixed xl:relative bg-[#00000044] xl:bg-transparent backdrop-blur-sm xl:backdrop-blur-none justify-center xl:block ${searchModalState ? 'flex' : 'hidden'}`}>
|
||||
<div
|
||||
className={`w-full h-full absolute`}
|
||||
onClick={toggleSearchModalState}>
|
||||
</div>
|
||||
<div className='z-55 bg-white w-full md:w-min xl:bg-transparent p-5 xl:p-0 items-center flex xl:block rounded-lg h-max'>
|
||||
X
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center justify-between gap-6'>
|
||||
{/* <NightModeSwitch checked={nightMode} onClick={toggleNightMode} /> */}
|
||||
{/* <img className='hidden sm:block' src={rectangles} /> */}
|
||||
<div className='bg-[#EEF0F7] items-center inline-flex min-w-fit p-0.5 rounded-xl'>
|
||||
<div className='text-[12px] px-2.5 text-nowrap hidden xl:block'>
|
||||
۲,۰۰۰,۰۰۰ تومان
|
||||
</div>
|
||||
{/* <img className='bg-white rounded-[11px]' src={wallet} /> */}
|
||||
</div>
|
||||
{/* <img src={bell} /> */}
|
||||
<button className='flex items-center justify-between' onClick={toggleProfileDropState}>
|
||||
{/* <img className='rounded-full min-w-8 w-8' src={avatar} /> */}
|
||||
<div className='pe-6 hidden xl:inline-flex' >
|
||||
<div className='ps-2 pe-1 text-xs text-nowrap'>مهرداد مظفری</div>
|
||||
{/* <img src={arrow} /> */}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/* <Dropdown state={profileDrop} toggle={setProfileDrop} /> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TopBar;
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import TopBar from '../topbar/TopBar'
|
||||
import { Menu } from '../menu/Menu'
|
||||
import BottomNavBar from '../navigation/BottomNavBar'
|
||||
|
||||
type Props = {} & React.AllHTMLAttributes<HTMLBodyElementEventMap>
|
||||
|
||||
function ClientMenuRouteWrapper({ children }: Props) {
|
||||
const [profileDrop, setProfileDrop] = useState(false);
|
||||
const [nightMode, setNightMode] = useState(false);
|
||||
const [menuState, setMenuState] = useState(false);
|
||||
const [searchModal, setSearchModal] = useState(false);
|
||||
|
||||
const toggleProfileDrop = () => {
|
||||
setProfileDrop((state) => !state);
|
||||
}
|
||||
|
||||
const toggleNightMode = () => {
|
||||
setNightMode((state) => !state);
|
||||
}
|
||||
|
||||
const toggleMenuState = () => {
|
||||
setMenuState((state) => !state);
|
||||
}
|
||||
|
||||
const toggleSearchModal = () => {
|
||||
setSearchModal((state) => !state);
|
||||
}
|
||||
|
||||
// const location = useRouter();
|
||||
|
||||
// useEffect(() => {
|
||||
// setMenuState(false)
|
||||
// }, [location])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header>
|
||||
<TopBar
|
||||
profileDropState={profileDrop} toggleProfileDropState={toggleProfileDrop}
|
||||
menuState={menuState} toggleMenuState={toggleMenuState}
|
||||
searchModalState={searchModal} toggleSearchModalState={toggleSearchModal}
|
||||
nightModeState={nightMode} toggleNightModeState={toggleNightMode} />
|
||||
</header>
|
||||
<main className="h-full pb-0">
|
||||
{children}
|
||||
</main>
|
||||
<aside>
|
||||
|
||||
<Menu menuState={menuState} toggleMenuState={toggleMenuState} />
|
||||
</aside>
|
||||
<footer>
|
||||
<BottomNavBar />
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClientMenuRouteWrapper
|
||||
@@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
type Props = { } & React.AllHTMLAttributes<HTMLBodyElementEventMap>
|
||||
|
||||
function ClientSideWrapper({ children }: Props) {
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClientSideWrapper
|
||||
Reference in New Issue
Block a user