Skip to content

Commit 782e9f6

Browse files
feat: smoother animation for icons
1 parent 8c0a2e0 commit 782e9f6

File tree

1 file changed

+68
-10
lines changed
  • apps/sim/app/(landing)/components/hero

1 file changed

+68
-10
lines changed

apps/sim/app/(landing)/components/hero/hero.tsx

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import React from 'react'
4+
import { motion } from 'framer-motion'
45
import { ArrowUp, CodeIcon } from 'lucide-react'
56
import { useRouter } from 'next/navigation'
67
import { type Edge, type Node, Position } from 'reactflow'
@@ -152,6 +153,16 @@ export default function Hero() {
152153
const [lastHoveredIndex, setLastHoveredIndex] = React.useState<number | null>(null)
153154
const intervalRef = React.useRef<NodeJS.Timeout | null>(null)
154155

156+
const iconRowRef = React.useRef<HTMLDivElement | null>(null)
157+
const buttonRefs = React.useRef<(HTMLDivElement | null)[]>([])
158+
const [pillLayout, setPillLayout] = React.useState<{
159+
left: number
160+
top: number
161+
width: number
162+
height: number
163+
} | null>(null)
164+
const [layoutVersion, setLayoutVersion] = React.useState(0)
165+
155166
/**
156167
* Handle service icon click to populate textarea with template
157168
*/
@@ -246,6 +257,29 @@ export default function Hero() {
246257
}
247258
}
248259

260+
const activeIconIndex =
261+
isUserHovering && lastHoveredIndex !== null ? lastHoveredIndex : autoHoverIndex
262+
263+
React.useLayoutEffect(() => {
264+
const container = iconRowRef.current
265+
const target = buttonRefs.current[activeIconIndex]
266+
if (!container || !target) return
267+
const cr = container.getBoundingClientRect()
268+
const tr = target.getBoundingClientRect()
269+
setPillLayout({
270+
left: tr.left - cr.left,
271+
top: tr.top - cr.top,
272+
width: tr.width,
273+
height: tr.height,
274+
})
275+
}, [activeIconIndex, layoutVersion, visibleIconCount])
276+
277+
React.useEffect(() => {
278+
const onResize = () => setLayoutVersion((v) => v + 1)
279+
window.addEventListener('resize', onResize)
280+
return () => window.removeEventListener('resize', onResize)
281+
}, [])
282+
249283
/**
250284
* Handle form submission
251285
*/
@@ -377,24 +411,48 @@ export default function Hero() {
377411
Build and deploy AI agent workflows
378412
</p>
379413
<div
380-
className='flex items-center justify-center gap-[2px] pt-[18px] sm:pt-[32px]'
414+
ref={iconRowRef}
415+
className='relative flex items-center justify-center gap-[2px] pt-[18px] sm:pt-[32px]'
381416
onMouseEnter={handleIconContainerMouseEnter}
382417
onMouseLeave={handleIconContainerMouseLeave}
383418
>
384-
{/* Service integration buttons */}
419+
{pillLayout !== null && (
420+
<motion.div
421+
className='pointer-events-none absolute rounded-xl border border-[#E5E5E5] shadow-[0_2px_4px_0_rgba(0,0,0,0.08)]'
422+
initial={false}
423+
animate={{
424+
left: pillLayout.left,
425+
top: pillLayout.top,
426+
width: pillLayout.width,
427+
height: pillLayout.height,
428+
}}
429+
transition={{
430+
type: 'spring',
431+
stiffness: 325,
432+
damping: 33,
433+
mass: 1.12,
434+
}}
435+
/>
436+
)}
385437
{serviceIcons.slice(0, visibleIconCount).map((service, index) => {
386438
const Icon = service.icon
387439
return (
388-
<IconButton
440+
<div
389441
key={service.key}
390-
aria-label={service.label}
391-
onClick={() => handleServiceClick(service.key as keyof typeof SERVICE_TEMPLATES)}
392-
onMouseEnter={() => setLastHoveredIndex(index)}
393-
style={service.style}
394-
isAutoHovered={!isUserHovering && index === autoHoverIndex}
442+
ref={(el) => {
443+
buttonRefs.current[index] = el
444+
}}
395445
>
396-
<Icon className='h-5 w-5 sm:h-6 sm:w-6' />
397-
</IconButton>
446+
<IconButton
447+
aria-label={service.label}
448+
onClick={() => handleServiceClick(service.key as keyof typeof SERVICE_TEMPLATES)}
449+
onMouseEnter={() => setLastHoveredIndex(index)}
450+
style={service.style}
451+
isAutoHovered={!isUserHovering && index === autoHoverIndex}
452+
>
453+
<Icon className='h-5 w-5 sm:h-6 sm:w-6' />
454+
</IconButton>
455+
</div>
398456
)
399457
})}
400458
</div>

0 commit comments

Comments
 (0)