11"use client" ;
22
3- import { navigationMenuTriggerStyle } from "@cap/ui" ;
4- import { classNames } from "@cap/utils" ;
3+ import { navigationMenuTriggerStyle } from "@cap/ui/navigation-menu " ;
4+ import { classNames } from "@cap/utils/helpers " ;
55import { ChevronDown , Clapperboard , Zap } from "lucide-react" ;
66import Link from "next/link" ;
77import { usePathname } from "next/navigation" ;
@@ -126,10 +126,46 @@ const dropdownStyle = (width: number | undefined): CSSProperties => ({
126126 maxWidth : "calc(100vw - 2rem)" ,
127127} ) ;
128128
129+ const BUBBLE_CSS = `
130+ .ht-nav-bubble {
131+ transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
132+ width 300ms cubic-bezier(0.22, 1, 0.36, 1),
133+ height 300ms cubic-bezier(0.22, 1, 0.36, 1),
134+ opacity 180ms ease;
135+ animation: ht-nav-bubble-in 260ms cubic-bezier(0.22, 1, 0.36, 1);
136+ }
137+ @keyframes ht-nav-bubble-in {
138+ from { opacity: 0; scale: 0.86; }
139+ to { opacity: 1; scale: 1; }
140+ }
141+ ` ;
142+
129143export function DesktopNavLinks ( ) {
130144 const pathname = usePathname ( ) ;
131145 const previousPathname = useRef ( pathname ) ;
132146 const [ openDropdown , setOpenDropdown ] = useState < string | null > ( null ) ;
147+ const listRef = useRef < HTMLUListElement | null > ( null ) ;
148+ const [ bubble , setBubble ] = useState < {
149+ x : number ;
150+ y : number ;
151+ w : number ;
152+ h : number ;
153+ } | null > ( null ) ;
154+ const [ bubbleOn , setBubbleOn ] = useState ( false ) ;
155+
156+ const showBubble = ( item : HTMLElement ) => {
157+ const list = listRef . current ;
158+ if ( ! list ) return ;
159+ const rect = item . getBoundingClientRect ( ) ;
160+ const base = list . getBoundingClientRect ( ) ;
161+ setBubble ( {
162+ x : rect . left - base . left ,
163+ y : rect . top - base . top ,
164+ w : rect . width ,
165+ h : rect . height ,
166+ } ) ;
167+ setBubbleOn ( true ) ;
168+ } ;
133169
134170 useEffect ( ( ) => {
135171 if ( previousPathname . current === pathname ) {
@@ -160,15 +196,34 @@ export function DesktopNavLinks() {
160196
161197 return (
162198 < nav aria-label = "Main" >
163- < ul className = "flex items-center px-0 space-x-0 list-none" >
199+ < ul
200+ ref = { listRef }
201+ className = "relative flex items-center gap-0.5 px-0 list-none"
202+ onMouseLeave = { ( ) => setBubbleOn ( false ) }
203+ >
204+ { bubble ? (
205+ < span
206+ aria-hidden = "true"
207+ className = "ht-nav-bubble pointer-events-none absolute left-0 top-0 z-0 rounded-[8px] bg-gray-3"
208+ style = { {
209+ transform : `translate(${ bubble . x } px, ${ bubble . y } px)` ,
210+ width : bubble . w ,
211+ height : bubble . h ,
212+ opacity : bubbleOn ? 1 : 0 ,
213+ } }
214+ />
215+ ) : null }
164216 { Links . map ( ( link ) => {
165217 const isOpen = openDropdown === link . label ;
166218
167219 return (
168220 < li
169221 key = { link . label }
170- className = "relative"
171- onMouseEnter = { ( ) => setOpenDropdown ( link . label ) }
222+ className = "relative z-10"
223+ onMouseEnter = { ( event ) => {
224+ showBubble ( event . currentTarget ) ;
225+ setOpenDropdown ( link . label ) ;
226+ } }
172227 onMouseLeave = { ( ) =>
173228 setOpenDropdown ( ( current ) =>
174229 current === link . label ? null : current ,
@@ -186,34 +241,31 @@ export function DesktopNavLinks() {
186241 onClick = { ( ) => setOpenDropdown ( link . label ) }
187242 className = { classNames (
188243 navigationMenuTriggerStyle ( ) ,
189- "flex gap-1 items-center px-2 py-0 text-sm font-medium text-gray-10 transition-colors hover:text-blue-9 focus:text-blue-9" ,
190- isOpen && "text-blue-9" ,
244+ "bg-transparent hover:bg-transparent focus:bg-transparent data-[state=open]:bg-transparent" ,
245+ "flex gap-1.5 items-center px-2.5 py-2 text-[14.5px] font-medium text-[rgba(17,17,17,0.85)] transition-colors hover:text-[#111111] focus:text-[#111111] xl:px-3 xl:text-[15.5px]" ,
246+ isOpen && "text-[#111111]" ,
191247 ) }
192248 >
193249 { link . label }
194250 < ChevronDown
195251 className = { classNames (
196- "size-3.5 transition-transform duration-200 ease-out" ,
252+ "size-[15px] transition-transform duration-200 ease-out" ,
197253 isOpen && "rotate-180" ,
198254 ) }
199- strokeWidth = { 2.25 }
255+ strokeWidth = { 2 }
200256 aria-hidden = "true"
201257 />
202258 </ button >
203259 < div
204260 className = { classNames (
205- "absolute top-full left-1/2 z-50 -translate-x-1/2 pt-3 transition duration-150" ,
261+ "absolute top-full left-0 z-50 pt-3 transition duration-150" ,
206262 isOpen
207263 ? "visible block opacity-100"
208264 : "invisible hidden opacity-0" ,
209265 ) }
210266 >
211267 < div className = "relative" style = { dropdownStyle ( link . width ) } >
212- < span
213- className = "absolute -top-[7px] left-1/2 z-10 size-3.5 -translate-x-1/2 rotate-45 rounded-tl-[4px] border-t border-l border-zinc-200/70 bg-white"
214- aria-hidden = "true"
215- />
216- < div className = "overflow-hidden relative bg-white rounded-2xl border shadow-xl border-zinc-200/70" >
268+ < div className = "overflow-hidden relative bg-white rounded-2xl border border-zinc-200/70" >
217269 < ul className = "grid grid-cols-2 gap-1.5 p-3 list-none" >
218270 { link . dropdown . map ( ( sublink ) => (
219271 < li key = { sublink . href } >
@@ -243,7 +295,8 @@ export function DesktopNavLinks() {
243295 onClick = { closeDropdown }
244296 className = { classNames (
245297 navigationMenuTriggerStyle ( ) ,
246- "px-2 py-0 text-sm font-medium text-gray-10 hover:text-blue-9 focus:text-blue-9" ,
298+ "bg-transparent hover:bg-transparent focus:bg-transparent data-[state=open]:bg-transparent" ,
299+ "px-2.5 py-2 text-[14.5px] font-medium text-[rgba(17,17,17,0.85)] hover:text-[#111111] focus:text-[#111111] xl:px-3 xl:text-[15.5px]" ,
247300 ) }
248301 >
249302 { link . label }
@@ -253,6 +306,7 @@ export function DesktopNavLinks() {
253306 ) ;
254307 } ) }
255308 </ ul >
309+ < style > { BUBBLE_CSS } </ style >
256310 </ nav >
257311 ) ;
258312}
0 commit comments