diff --git a/apps/sim/app/(landing)/components/hero/components/icon-button.tsx b/apps/sim/app/(landing)/components/hero/components/icon-button.tsx
index c8e523e2f86..722476b9653 100644
--- a/apps/sim/app/(landing)/components/hero/components/icon-button.tsx
+++ b/apps/sim/app/(landing)/components/hero/components/icon-button.tsx
@@ -1,5 +1,6 @@
'use client'
+import { forwardRef } from 'react'
import type React from 'react'
interface IconButtonProps {
@@ -8,31 +9,23 @@ interface IconButtonProps {
onMouseEnter?: () => void
style?: React.CSSProperties
'aria-label': string
- isAutoHovered?: boolean
}
-export function IconButton({
- children,
- onClick,
- onMouseEnter,
- style,
- 'aria-label': ariaLabel,
- isAutoHovered = false,
-}: IconButtonProps) {
+export const IconButton = forwardRef(function IconButton(
+ { children, onClick, onMouseEnter, style, 'aria-label': ariaLabel },
+ ref
+) {
return (
)
-}
+})
diff --git a/apps/sim/app/(landing)/components/hero/hero.tsx b/apps/sim/app/(landing)/components/hero/hero.tsx
index 546dc47627f..e793f8517bc 100644
--- a/apps/sim/app/(landing)/components/hero/hero.tsx
+++ b/apps/sim/app/(landing)/components/hero/hero.tsx
@@ -152,6 +152,18 @@ export default function Hero() {
const [lastHoveredIndex, setLastHoveredIndex] = React.useState(null)
const intervalRef = React.useRef(null)
+ /**
+ * Refs for smooth sliding pill highlight
+ */
+ const iconContainerRef = React.useRef(null)
+ const iconRefs = React.useRef<(HTMLButtonElement | null)[]>([])
+ const [pillStyle, setPillStyle] = React.useState({
+ 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 }))
+ 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])
+
+ 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
+ {/* Sliding highlight pill */}
+
{/* Service integration buttons */}
{serviceIcons.slice(0, visibleIconCount).map((service, index) => {
const Icon = service.icon
return (
{
+ 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}
>