优化导航栏性能,完善导航栏弹出菜单的移动端适配;调整导航栏与 store 公共组件的所在目录结构
This commit is contained in:
@@ -1,39 +1,36 @@
|
||||
'use client'
|
||||
import UserCenter from '@/components/composites/user-center'
|
||||
import {useClientStore, useProfileStore} from '@/app/stores'
|
||||
import {buttonVariants} from '@/components/ui/button'
|
||||
import {
|
||||
Navigation,
|
||||
NavigationIndicator,
|
||||
NavigationLink,
|
||||
NavigationLinkItem,
|
||||
NavigationGroup,
|
||||
NavigationTriggerItem,
|
||||
NavigationMenuViewport,
|
||||
} from '@/components/ui/navigation-menu'
|
||||
import Wrap from '@/components/wrap'
|
||||
import {merge} from '@/lib/utils'
|
||||
import {useState, useCallback, useEffect, useContext} from 'react'
|
||||
import {useCallback, useEffect, useMemo, useState, PointerEvent, ComponentProps} from 'react'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import SolutionMenu from './menu-solution'
|
||||
import ProductMenu from './menu-product'
|
||||
import HelpMenu from './menu-help'
|
||||
import MobileMenu from './menu-mobile'
|
||||
import Wrap from '@/components/wrap'
|
||||
import logo from '@/assets/logo.webp'
|
||||
import ProductMenu, {Domestic} from '@/app/(home)/@header/_client/product'
|
||||
import SolutionMenu from '@/app/(home)/@header/_client/solution'
|
||||
import HelpMenu from '@/app/(home)/@header/_client/help'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {useProfileStore} from '@/components/stores-provider'
|
||||
import UserCenter from '@/components/composites/user-center'
|
||||
import {MenuIcon} from 'lucide-react'
|
||||
import down from '@/assets/header/down.svg'
|
||||
import {merge} from '@/lib/utils'
|
||||
import {HeaderContext} from './common'
|
||||
|
||||
export type HeaderProps = {}
|
||||
export type ProviderProps = {}
|
||||
|
||||
export default function Header(props: HeaderProps) {
|
||||
export default function Page(props: ProviderProps) {
|
||||
// ======================
|
||||
// 背景显示状态
|
||||
// 滚动条状态
|
||||
// ======================
|
||||
|
||||
const [expand, setExpand] = useState(false)
|
||||
const [scroll, setScroll] = useState(false)
|
||||
const [scroll, setScroll] = useState(false) // Changed to false for client-side rendering
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
setScroll(window.scrollY > 48)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize scroll state on client
|
||||
setScroll(window.scrollY > 48)
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
@@ -42,10 +39,57 @@ export default function Header(props: HeaderProps) {
|
||||
}, [handleScroll])
|
||||
|
||||
// ======================
|
||||
// 移动端
|
||||
// 菜单状态
|
||||
// ======================
|
||||
|
||||
const lg = useClientStore(state => state.breakpoint.lg)
|
||||
const [menu, setMenu] = useState(false)
|
||||
const [page, setPage] = useState(0)
|
||||
const pages = useMemo(() => [
|
||||
<ProductMenu key="product"/>,
|
||||
<SolutionMenu key="solution"/>,
|
||||
<HelpMenu key="help"/>,
|
||||
<MobileMenu key="mobile"/>,
|
||||
], [])
|
||||
|
||||
const enterMenu = (i: number) => {
|
||||
return (e: PointerEvent) => {
|
||||
setPage(i)
|
||||
if (e.pointerType === 'mouse' || page !== i) {
|
||||
setMenu(true)
|
||||
}
|
||||
else {
|
||||
setMenu(!menu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const exitMenu = (e: PointerEvent) => {
|
||||
if (e.pointerType === 'mouse') {
|
||||
setMenu(false)
|
||||
}
|
||||
}
|
||||
|
||||
const enterMenuContent = (e: PointerEvent) => {
|
||||
setMenu(true)
|
||||
}
|
||||
|
||||
const exitMenuContent = (e: PointerEvent) => {
|
||||
if (e.pointerType === 'mouse') {
|
||||
setMenu(false)
|
||||
}
|
||||
}
|
||||
|
||||
const enterMenuMask = (e: PointerEvent) => {
|
||||
if (e.pointerType !== 'mouse') {
|
||||
setMenu(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 用户信息
|
||||
// ======================
|
||||
|
||||
const profile = useProfileStore(store => store.profile)
|
||||
|
||||
// ======================
|
||||
// render
|
||||
@@ -53,102 +97,188 @@ export default function Header(props: HeaderProps) {
|
||||
|
||||
return (
|
||||
<header
|
||||
data-expand={expand}
|
||||
data-scroll={scroll}
|
||||
data-effect={expand || scroll}
|
||||
className="group/header w-full fixed left-0 top-0 z-20"
|
||||
>
|
||||
<Navigation
|
||||
onValueChange={(value) => {
|
||||
setExpand(!!value)
|
||||
}}
|
||||
>
|
||||
className={merge(
|
||||
'fixed top-0 left-0 w-screen z-10 flex flex-col',
|
||||
menu && 'h-screen',
|
||||
)}>
|
||||
<HeaderContext.Provider value={{setMenu}}>
|
||||
|
||||
{/* 菜单栏 */}
|
||||
<div className={merge(
|
||||
`transition-[background-color,backdrop-filter,box-shadow] duration-200 ease-in-out`,
|
||||
`bg-transparent backdrop-blur-none shadow-none`,
|
||||
`group-data-[effect=true]/header:bg-card/90`,
|
||||
`group-data-[effect=true]/header:backdrop-blur-sm`,
|
||||
`group-data-[scroll=true]/header:shadow-lg`,
|
||||
`flex-none`,
|
||||
`transition-[background,shadow] duration-200 ease-in-out`,
|
||||
menu
|
||||
? `bg-[#fffe] backdrop-blur-sm`
|
||||
: scroll
|
||||
? `bg-[#fffe] backdrop-blur-sm shadow-lg`
|
||||
: `bg-transparent shadow-none`,
|
||||
)}>
|
||||
<Wrap className={merge('h-14 md:h-16 flex justify-between items-stretch')}>
|
||||
<div className="flex items-stretch lg:flex-row-reverse">
|
||||
{lg ? (
|
||||
<NavigationGroup className="gap-0 flex">
|
||||
<NavigationLinkItem href="/" text="首页"/>
|
||||
<Wrap className="h-20 max-md:h-16 flex justify-between">
|
||||
<nav className="flex items-center justify-between lg:flex-row-reverse gap-4 lg:gap-8">
|
||||
|
||||
<NavigationTriggerItem text="产品订购" className="h-80">
|
||||
<ProductMenu/>
|
||||
</NavigationTriggerItem>
|
||||
{/* 桌面端菜单 */}
|
||||
<ul className="h-full items-stretch hidden lg:flex">
|
||||
<LinkItem text="首页" href="/"/>
|
||||
<MenuItem
|
||||
text="产品订购"
|
||||
active={menu && page === 0}
|
||||
onPointerEnter={enterMenu(0)}
|
||||
onPointerLeave={exitMenu}
|
||||
/>
|
||||
<MenuItem
|
||||
text="业务场景"
|
||||
active={menu && page === 1}
|
||||
onPointerEnter={enterMenu(1)}
|
||||
onPointerLeave={exitMenu}
|
||||
/>
|
||||
<MenuItem
|
||||
text="帮助中心"
|
||||
active={menu && page === 2}
|
||||
onPointerEnter={enterMenu(2)}
|
||||
onPointerLeave={exitMenu}
|
||||
/>
|
||||
<LinkItem
|
||||
text="企业服务"
|
||||
href="#"/>
|
||||
<LinkItem
|
||||
text="推广返利"
|
||||
href="#"/>
|
||||
</ul>
|
||||
|
||||
<NavigationTriggerItem text="业务场景" className="h-80">
|
||||
<SolutionMenu/>
|
||||
</NavigationTriggerItem>
|
||||
{/* 移动端菜单 */}
|
||||
<Button
|
||||
theme="ghost"
|
||||
className="lg:hidden size-10"
|
||||
onPointerEnter={enterMenu(3)}
|
||||
onPointerLeave={exitMenu}
|
||||
>
|
||||
<MenuIcon/>
|
||||
</Button>
|
||||
|
||||
<NavigationTriggerItem text="帮助中心" className="h-80">
|
||||
<HelpMenu/>
|
||||
</NavigationTriggerItem>
|
||||
{/* logo */}
|
||||
<Link href="/" className="flex items-center">
|
||||
<Image src={logo} alt="logo" height={40} className="translate-y-0.5"/>
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<NavigationLinkItem href="/" text="企业服务"/>
|
||||
<NavigationLinkItem href="/" text="推广返利"/>
|
||||
|
||||
<NavigationIndicator/>
|
||||
</NavigationGroup>
|
||||
) : (
|
||||
<NavigationGroup className="flex">
|
||||
<NavigationTriggerItem
|
||||
suffix={false}
|
||||
text={<MenuIcon/>}
|
||||
className={merge(
|
||||
`flex flex-col items-start gap-6`,
|
||||
)}
|
||||
>
|
||||
<ProductMenu/>
|
||||
|
||||
<SolutionMenu/>
|
||||
|
||||
<HelpMenu/>
|
||||
|
||||
<NavigationLink href="/" text="企业服务"/>
|
||||
<NavigationLink href="/" text="推广返利"/>
|
||||
</NavigationTriggerItem>
|
||||
</NavigationGroup>
|
||||
)}
|
||||
|
||||
<NavigationLink href="/" text={<Image src={logo} alt="logo" height={36}/>}/>
|
||||
{/* 登录 */}
|
||||
<div className="flex items-center">
|
||||
{profile == undefined
|
||||
? (
|
||||
<>
|
||||
<Link
|
||||
href="/login"
|
||||
className="w-24 h-12 flex items-center justify-center lg:text-lg"
|
||||
>
|
||||
<span>登录</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
className={[
|
||||
`w-20 lg:w-24 h-10 lg:h-12 bg-gradient-to-r rounded-sm flex items-center justify-center lg:text-lg text-white`,
|
||||
`transition-colors duration-200 ease-in-out`,
|
||||
`from-blue-500 to-cyan-400 hover:from-blue-500 hover:to-cyan-300`,
|
||||
].join(' ')}
|
||||
>
|
||||
<span>注册</span>
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<UserCenter profile={profile}/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<AccountRegion/>
|
||||
</Wrap>
|
||||
</div>
|
||||
|
||||
<NavigationMenuViewport className={merge(
|
||||
`bg-card/90 backdrop-blur-sm shadow-lg`,
|
||||
`transition-[padding,opacity,max-height]`,
|
||||
`group-data-[expand=false]/header:delay-[0s,0s,0.2s] group-data-[expand=true]/header:delay-0`,
|
||||
`p-0 group-data-[expand=true]/header:py-4`,
|
||||
`opacity-0 group-data-[effect=true]/header:opacity-100`,
|
||||
`max-h-0 group-data-[effect=true]/header:max-h-[calc(100vh-56px)] overflow-auto`,
|
||||
)}/>
|
||||
</Navigation>
|
||||
{/* 下拉菜单 */}
|
||||
<div
|
||||
className={merge(
|
||||
`flex-auto overflow-auto lg:flex-none lg:basis-72 shadow-lg box-content`,
|
||||
`bg-[#fffe] backdrop-blur-sm`,
|
||||
`transition-[opacity,padding] transition-discrete duration-200 ease-in-out`,
|
||||
menu
|
||||
? `delay-[0s,0s] opacity-100 py-4`
|
||||
: `delay-[0s,0s] opacity-0 py-0 pointer-events-none`,
|
||||
)}
|
||||
onPointerEnter={enterMenuContent}
|
||||
onPointerLeave={exitMenuContent}
|
||||
>
|
||||
{pages[page]}
|
||||
</div>
|
||||
|
||||
{/* 遮罩层 */}
|
||||
<div className="flex-auto" onPointerEnter={enterMenuMask}/>
|
||||
|
||||
</HeaderContext.Provider>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
function AccountRegion() {
|
||||
const profile = useProfileStore(state => state.profile)
|
||||
|
||||
function LinkItem(props: {
|
||||
text: string
|
||||
href: string
|
||||
}) {
|
||||
return (
|
||||
<div className="self-center">
|
||||
{profile ? (
|
||||
<UserCenter profile={profile}/>
|
||||
) : (
|
||||
<NavigationLink
|
||||
href="/login"
|
||||
text="登录 / 注册"
|
||||
classNameOverride={buttonVariants({
|
||||
theme: 'gradient',
|
||||
className: 'h-10 lg:h-12',
|
||||
})}/>
|
||||
)}
|
||||
</div>
|
||||
<li className="group relative">
|
||||
<Link
|
||||
href={props.href}
|
||||
className={[
|
||||
`h-full px-4 flex items-center text-lg`,
|
||||
`transition-colors duration-200 ease-in-out`,
|
||||
`hover:text-blue-500`,
|
||||
].join(' ')}
|
||||
>
|
||||
{props.text}
|
||||
</Link>
|
||||
<div className={[
|
||||
`absolute bottom-0 w-full h-0.5 bg-transparent `,
|
||||
`transition-colors duration-200`,
|
||||
`group-hover:bg-blue-500`,
|
||||
].join(' ')}>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function MenuItem(props: {
|
||||
text: string
|
||||
active: boolean
|
||||
} & ComponentProps<'button'>) {
|
||||
return (
|
||||
<li className="group relative">
|
||||
<button
|
||||
onPointerEnter={props.onPointerEnter}
|
||||
onPointerLeave={props.onPointerLeave}
|
||||
className={[
|
||||
`h-full px-4 flex gap-3 items-center cursor-pointer text-lg`,
|
||||
`transition-colors duration-200 ease-in-out`,
|
||||
props.active
|
||||
? `text-blue-500`
|
||||
: ``,
|
||||
].join(' ')}
|
||||
>
|
||||
<span>{props.text}</span>
|
||||
<Image
|
||||
src={down}
|
||||
alt="drop_menu"
|
||||
className={[
|
||||
`transition-transform duration-200 ease-in-out`,
|
||||
props.active
|
||||
? `rotate-180`
|
||||
: ``,
|
||||
].join(' ')}
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
className={[
|
||||
`absolute bottom-0 w-full h-0.5 pointer-events-none`,
|
||||
`transition-colors duration-200`,
|
||||
props.active
|
||||
? `bg-blue-500`
|
||||
: 'bg-transparent',
|
||||
].join(' ')}/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user