-
Notifications
You must be signed in to change notification settings - Fork 3.4k
fix(landing): replace per-icon hover with smooth sliding pill highlight #3480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -152,6 +152,18 @@ export default function Hero() { | |||||
| const [lastHoveredIndex, setLastHoveredIndex] = React.useState<number | null>(null) | ||||||
| const intervalRef = React.useRef<NodeJS.Timeout | null>(null) | ||||||
|
|
||||||
| /** | ||||||
| * Refs for smooth sliding pill highlight | ||||||
| */ | ||||||
| const iconContainerRef = React.useRef<HTMLDivElement>(null) | ||||||
| const iconRefs = React.useRef<(HTMLButtonElement | null)[]>([]) | ||||||
| const [pillStyle, setPillStyle] = React.useState<React.CSSProperties>({ | ||||||
| opacity: 0, | ||||||
| width: 0, | ||||||
| height: 0, | ||||||
| transform: 'translateX(0px)', | ||||||
| }) | ||||||
|
|
||||||
| /** | ||||||
| * Handle service icon click to populate textarea with template | ||||||
| */ | ||||||
|
|
@@ -225,6 +237,40 @@ export default function Hero() { | |||||
| } | ||||||
| }, [isUserHovering, visibleIconCount]) | ||||||
|
|
||||||
| /** | ||||||
| * Compute the active icon index and update the pill position | ||||||
| */ | ||||||
| const activeIndex = isUserHovering ? lastHoveredIndex : autoHoverIndex | ||||||
|
|
||||||
| const updatePillPosition = React.useCallback(() => { | ||||||
| if (activeIndex == null) { | ||||||
| setPillStyle((prev) => ({ ...prev, opacity: 0 })) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pill disappears on first mouse entry into containerMedium Severity On the very first user hover interaction, Additional Locations (1) |
||||||
| return | ||||||
| } | ||||||
|
|
||||||
| const button = iconRefs.current[activeIndex] | ||||||
| const container = iconContainerRef.current | ||||||
| if (!button || !container) return | ||||||
|
|
||||||
| const containerRect = container.getBoundingClientRect() | ||||||
| const buttonRect = button.getBoundingClientRect() | ||||||
| const offsetX = buttonRect.left - containerRect.left | ||||||
|
|
||||||
| setPillStyle({ | ||||||
| width: buttonRect.width, | ||||||
| height: buttonRect.height, | ||||||
| transform: `translateX(${offsetX}px)`, | ||||||
| opacity: 1, | ||||||
| }) | ||||||
| }, [activeIndex]) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pill stays visible at stale position after icon count shrinksMedium Severity When Additional Locations (1) |
||||||
|
|
||||||
| React.useEffect(() => { | ||||||
| updatePillPosition() | ||||||
|
|
||||||
| window.addEventListener('resize', updatePillPosition) | ||||||
| return () => window.removeEventListener('resize', updatePillPosition) | ||||||
| }, [updatePillPosition]) | ||||||
|
|
||||||
| /** | ||||||
| * Handle mouse enter on icon container | ||||||
| */ | ||||||
|
|
@@ -377,21 +423,30 @@ export default function Hero() { | |||||
| Build and deploy AI agent workflows | ||||||
| </p> | ||||||
| <div | ||||||
| className='flex items-center justify-center gap-[2px] pt-[18px] sm:pt-[32px]' | ||||||
| ref={iconContainerRef} | ||||||
| className='relative flex items-center justify-center gap-[2px] pt-[18px] sm:pt-[32px]' | ||||||
| onMouseEnter={handleIconContainerMouseEnter} | ||||||
| onMouseLeave={handleIconContainerMouseLeave} | ||||||
| > | ||||||
| {/* Sliding highlight pill */} | ||||||
| <div | ||||||
| className='pointer-events-none absolute top-[18px] left-0 rounded-xl border border-[#E5E5E5] shadow-[0_2px_4px_0_rgba(0,0,0,0.08)] transition-all duration-300 ease-in-out sm:top-[32px]' | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The pill initialises with
Suggested change
If you also want the opacity fade on first appearance,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded The pill's |
||||||
| style={pillStyle} | ||||||
| aria-hidden='true' | ||||||
| /> | ||||||
| {/* Service integration buttons */} | ||||||
| {serviceIcons.slice(0, visibleIconCount).map((service, index) => { | ||||||
| const Icon = service.icon | ||||||
| return ( | ||||||
| <IconButton | ||||||
| key={service.key} | ||||||
| ref={(el) => { | ||||||
| iconRefs.current[index] = el | ||||||
| }} | ||||||
| aria-label={service.label} | ||||||
| onClick={() => handleServiceClick(service.key as keyof typeof SERVICE_TEMPLATES)} | ||||||
| onMouseEnter={() => setLastHoveredIndex(index)} | ||||||
| style={service.style} | ||||||
| isAutoHovered={!isUserHovering && index === autoHoverIndex} | ||||||
| > | ||||||
| <Icon className='h-5 w-5 sm:h-6 sm:w-6' /> | ||||||
| </IconButton> | ||||||
|
|
||||||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pill disappears on first container entry
When a user moves the cursor into the container padding area (the
pt-[18px] / pt-[32px]zone above the icons) before hovering any individual icon,isUserHoveringbecomestruewhilelastHoveredIndexis stillnull. This makesactiveIndex === null, which causesupdatePillPositionto fade the pill toopacity: 0— creating a visible flash where the pill vanishes before reappearing on the first icon hover.The fix is to fall back to
autoHoverIndexwhen the user hasn't yet hovered over a specific icon: