|
1 | 1 | 'use client'
|
2 | 2 |
|
3 |
| -import React, { memo, useMemo } from 'react' |
| 3 | +import React, { memo, useId, useMemo, useState } from 'react' |
| 4 | +import { m } from 'framer-motion' |
4 | 5 | import Link from 'next/link'
|
5 | 6 | import type { IHeaderMenu } from '../config'
|
6 | 7 |
|
7 | 8 | import { FloatPopover } from '~/components/ui/float-popover'
|
| 9 | +import { microdampingPreset } from '~/constants/spring' |
| 10 | +import { clsxm } from '~/lib/helper' |
8 | 11 |
|
9 | 12 | export const MenuPopover: Component<{
|
10 | 13 | subMenu: IHeaderMenu['subMenu']
|
11 | 14 | }> = memo(({ children, subMenu }) => {
|
| 15 | + const currentId = useId() |
12 | 16 | const TriggerComponent = useMemo(() => () => children, [children])
|
13 | 17 | if (!subMenu) return children
|
| 18 | + |
14 | 19 | return (
|
15 | 20 | <FloatPopover
|
16 | 21 | strategy="fixed"
|
17 | 22 | placement="bottom"
|
18 | 23 | offset={10}
|
| 24 | + headless |
19 | 25 | popoverWrapperClassNames="z-[19] relative"
|
20 |
| - popoverClassNames="rounded-xl !p-0" |
| 26 | + popoverClassNames="rounded-xl !p-0 focus-visible:!shadow-none focus-visible:!ring-0" |
21 | 27 | TriggerComponent={TriggerComponent}
|
22 | 28 | >
|
23 | 29 | {!!subMenu.length && (
|
24 |
| - <div className="relative flex w-[130px] flex-col px-4"> |
| 30 | + <div |
| 31 | + className={clsxm( |
| 32 | + 'select-none rounded-xl bg-white/60 outline-none dark:bg-neutral-900/60', |
| 33 | + 'shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur-md', |
| 34 | + 'dark:from-zinc-900/70 dark:to-zinc-800/90 dark:ring-zinc-100/10', |
| 35 | + 'relative flex w-[130px] flex-col py-1', |
| 36 | + )} |
| 37 | + > |
25 | 38 | {subMenu.map((m) => {
|
26 |
| - return ( |
27 |
| - <Link |
28 |
| - key={m.title} |
29 |
| - href={`${m.path}`} |
30 |
| - className="flex w-full items-center justify-around space-x-2 py-3 duration-200 hover:text-accent" |
31 |
| - role="button" |
32 |
| - > |
33 |
| - {!!m.icon && <span>{m.icon}</span>} |
34 |
| - <span>{m.title}</span> |
35 |
| - </Link> |
36 |
| - ) |
| 39 | + return <Item key={m.title} currentId={currentId} {...m} /> |
37 | 40 | })}
|
38 | 41 | </div>
|
39 | 42 | )}
|
40 | 43 | </FloatPopover>
|
41 | 44 | )
|
42 | 45 | })
|
43 | 46 | MenuPopover.displayName = 'MenuPopover'
|
| 47 | + |
| 48 | +const Item = memo(function Item( |
| 49 | + props: IHeaderMenu & { |
| 50 | + currentId: string |
| 51 | + }, |
| 52 | +) { |
| 53 | + const { title, path, icon, currentId } = props |
| 54 | + |
| 55 | + const [isEnter, setIsEnter] = useState(false) |
| 56 | + return ( |
| 57 | + <Link |
| 58 | + key={title} |
| 59 | + href={`${path}`} |
| 60 | + className="relative flex w-full items-center justify-around space-x-2 px-4 py-3 duration-200 hover:text-accent" |
| 61 | + role="button" |
| 62 | + onMouseEnter={() => { |
| 63 | + setIsEnter(true) |
| 64 | + }} |
| 65 | + onMouseLeave={() => { |
| 66 | + setIsEnter(false) |
| 67 | + }} |
| 68 | + > |
| 69 | + {!!icon && <span>{icon}</span>} |
| 70 | + <span>{title}</span> |
| 71 | + |
| 72 | + {isEnter && ( |
| 73 | + <m.span |
| 74 | + layoutId={currentId} |
| 75 | + transition={microdampingPreset} |
| 76 | + className={clsxm( |
| 77 | + 'absolute bottom-0 left-0 right-2 top-0 z-[-1] rounded-md', |
| 78 | + 'bg-zinc-50 dark:bg-neutral-900', |
| 79 | + 'border border-zinc-200 dark:border-zinc-800', |
| 80 | + )} |
| 81 | + /> |
| 82 | + )} |
| 83 | + </Link> |
| 84 | + ) |
| 85 | +}) |
0 commit comments