Skip to content

feat(landing): smooth sliding pill for hero service icons#3469

Open
adithyaakrishna wants to merge 3 commits intosimstudioai:mainfrom
adithyaakrishna:feat/smoother-icons
Open

feat(landing): smooth sliding pill for hero service icons#3469
adithyaakrishna wants to merge 3 commits intosimstudioai:mainfrom
adithyaakrishna:feat/smoother-icons

Conversation

@adithyaakrishna
Copy link

Summary

  • Replaces per icon hover styling on the landing hero service icons with a single sliding pill that animates to the active icon.
  • The pill position is driven by measuring the active icon and animated with a spring so the highlight moves smoothly between icons instead of jumping

Fixes #3468

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

  • Manual: hover across hero service icons and confirm one continuous pill moves between them; confirm auto-hover cycle still advances the pill smoothly.
  • Responsive: verify pill alignment and animation at different viewport widths (6 vs 13 icons).

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

HeroBeforeAfter.mp4

@cursor
Copy link

cursor bot commented Mar 8, 2026

PR Summary

Low Risk
Landing-page UI-only changes that replace per-button hover styling with a measured, animated overlay; main risk is minor visual/layout regressions across breakpoints or resize.

Overview
Updates the landing hero service icon row to use a single sliding “pill” highlight (framer-motion) that animates between icons based on auto-hover, user hover, and click selection.

Refactors IconButton to remove the isAutoHovered styling path and allow hover effects to be disabled (highlightFromParent) so the parent-driven pill provides the visual state, with added resize/breakpoint handling to keep indices and pill measurements aligned.

Written by Cursor Bugbot for commit b4b7a86. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Mar 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 8, 2026 10:53am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 8, 2026

Greptile Summary

This PR replaces per-icon CSS hover styling on the landing hero service icons with a framer-motion spring-animated pill that measures each active icon's position via getBoundingClientRect and smoothly slides between them. The animation setup is clean and the spring parameters produce a nice feel. However, there is a visual conflict: IconButton still applies its own isAutoHovered border/shadow immediately when the auto-hover index changes, which appears alongside the pill during its 300-400ms spring transition, creating a double-highlight effect that contradicts the intended smooth single-highlight behavior.

Confidence Score: 3/5

  • Safe to merge with low visual regression risk, but the double-highlight conflict should be fixed for the animation to work as visually intended.
  • The change is isolated to a single UI component with no data, routing, or API implications. The primary concern is a logic conflict between the new pill animation and the existing isAutoHovered border styling on IconButton, which causes two highlights to appear simultaneously during the spring animation — directly contradicting the feature's stated goal of a smooth sliding pill.
  • apps/sim/app/(landing)/components/hero/hero.tsx — specifically line 451 where isAutoHovered is passed to IconButton. The double-highlight can be resolved by either passing isAutoHovered={false} or by updating IconButton to suppress the border/shadow when the pill is active.

Sequence Diagram

sequenceDiagram
    participant Timer as Auto-Hover Timer
    participant State as React State
    participant LayoutEffect as useLayoutEffect
    participant Pill as motion.div (Pill)
    participant Button as IconButton (isAutoHovered)

    Timer->>State: setAutoHoverIndex(next)
    State->>State: activeIconIndex = next
    State->>LayoutEffect: trigger (activeIconIndex changed)
    LayoutEffect->>LayoutEffect: measure buttonRefs[activeIconIndex].getBoundingClientRect()
    LayoutEffect->>State: setPillLayout({left, top, width, height})
    State->>Pill: animate to new pillLayout (spring: stiffness=325, damping=33)
    Note over Pill: Pill slides smoothly over ~300ms
    State->>Button: isAutoHovered=true → border-[#E5E5E5] applied immediately (CSS transition 300ms)
    Note over Pill,Button: ⚠️ Both pill (mid-slide) AND button border (static at new icon) visible simultaneously
Loading

Last reviewed commit: 782e9f6

Comment on lines +446 to +454
<IconButton
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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IconButton applies border-[#E5E5E5] shadow-[0_2px_4px_0_rgba(0,0,0,0.08)] immediately when isAutoHovered=true (with a 300ms CSS transition). The sliding pill carries the same border/shadow. During the spring animation from icon A to icon B:

  1. Icon B's button immediately renders its own border (because isAutoHovered flipped to true)
  2. The pill is still mid-slide between A and B

This creates two visible bordered highlights simultaneously — one static at B (the button's own border) and one sliding (the pill) — which defeats the purpose of the smooth transition.

Since the pill is now the sole source of the highlight, consider suppressing the isAutoHovered border/shadow on IconButton:

Suggested change
<IconButton
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>
<IconButton
aria-label={service.label}
onClick={() => handleServiceClick(service.key as keyof typeof SERVICE_TEMPLATES)}
onMouseEnter={() => setLastHoveredIndex(index)}
style={service.style}
isAutoHovered={false}
>
<Icon className='h-5 w-5 sm:h-6 sm:w-6' />
</IconButton>

Alternatively, update IconButton to not apply the border/shadow when isAutoHovered=true so the pill is the only visual indicator.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

setSelectedIconIndex((idx) =>
idx !== null ? Math.min(idx, maxIndex) : null
)
}, [visibleIconCount])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clamped selectedIconIndex mismatches textarea template content

Low Severity

When visibleIconCount shrinks (e.g., resizing from desktop to mobile), selectedIconIndex is clamped to maxIndex but textValue is left unchanged. This causes the sliding pill to highlight a different service icon than the one whose template is displayed in the textarea. For instance, clicking icon 10 (Google Sheets) then resizing to mobile clamps selectedIconIndex to 5 (Supabase), so the pill highlights Supabase while the textarea still shows the Google Sheets template.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UX] - Staggered hover feels abrupt on landing page

1 participant