Skip to content

Commit 3b54c92

Browse files
feat(frontend): add animations and update block state tracker UI
1 parent c9e1154 commit 3b54c92

File tree

12 files changed

+469
-152
lines changed

12 files changed

+469
-152
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ yarn-error.log*
6969
# typescript
7070
*.tsbuildinfo
7171
next-env.d.ts
72+
.pnpm-store/
7273

7374
# Sentry Config File
7475
.env.sentry-build-plugin

frontend/app/globals.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,3 +616,13 @@
616616
border: 2px solid transparent;
617617
background-clip: padding-box;
618618
}
619+
620+
/* Hide scrollbar utility */
621+
.scrollbar-none {
622+
-ms-overflow-style: none;
623+
scrollbar-width: none;
624+
}
625+
626+
.scrollbar-none::-webkit-scrollbar {
627+
display: none;
628+
}

frontend/app/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import BlockSpawner from '../components/block-spawner'
1+
import BlockStateTracker from '@/components/block-state-tracker'
22

33
export default function Home() {
44
return (
5-
<div className="h-screen overflow-hidden text-white font-sans sm:min-h-screen sm:h-auto sm:overflow-visible">
6-
<main className="h-full flex flex-col py-4 px-4 max-w-6xl mx-auto sm:block sm:h-auto sm:py-8 sm:px-6 md:py-12">
7-
<h1 className="text-2xl font-bold mb-4 text-center shrink-0 md:mb-8">
5+
<div className="min-h-screen text-white font-sans">
6+
<main className="py-6 px-4 max-w-5xl mx-auto sm:py-8 sm:px-6 md:py-12">
7+
<h1 className="text-2xl sm:text-3xl font-bold mb-6 text-center md:mb-8 bg-linear-to-r from-indigo-400 via-violet-400 to-purple-400 bg-clip-text text-transparent">
88
Execution Events SDK Showcase
99
</h1>
10-
<BlockSpawner />
10+
<BlockStateTracker />
1111
</main>
1212
</div>
1313
)

frontend/components/block-spawner.tsx

Lines changed: 0 additions & 147 deletions
This file was deleted.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use client'
2+
3+
import { AnimatePresence, motion } from 'framer-motion'
4+
import { BLOCK_STATE_CONFIG } from '@/constants/block-state'
5+
import { cn } from '@/lib/utils'
6+
import type { Block } from '@/types/block'
7+
8+
interface BlockCardProps {
9+
block: Block
10+
showLabel?: boolean
11+
compact?: boolean
12+
}
13+
14+
/**
15+
* Individual block card with animated state transitions.
16+
*
17+
* Uses framer-motion's layoutId for smooth position animations between containers.
18+
* Color transitions are handled via CSS transitions for smoother performance.
19+
* Label appearance and text changes are animated for visual smoothness.
20+
*/
21+
export function BlockCard({
22+
block,
23+
showLabel = false,
24+
compact = false,
25+
}: BlockCardProps) {
26+
const config = BLOCK_STATE_CONFIG[block.state]
27+
28+
return (
29+
<motion.div
30+
layoutId={`block-${block.id}`}
31+
initial={false}
32+
transition={{
33+
layout: { type: 'spring', stiffness: 300, damping: 30 },
34+
}}
35+
style={{
36+
background: config.gradient,
37+
boxShadow: config.shadow,
38+
}}
39+
className={cn(
40+
'flex flex-col items-center justify-center rounded-lg font-semibold',
41+
'select-none cursor-default text-white',
42+
'transition-[background,box-shadow] duration-400 ease-in-out',
43+
compact
44+
? 'w-14 h-14 text-xs sm:w-16 sm:h-16 sm:text-sm'
45+
: 'w-16 h-16 text-sm sm:w-20 sm:h-20 sm:text-base',
46+
)}
47+
>
48+
<span className="font-bold drop-shadow-[0_1px_1px_rgba(0,0,0,0.3)]">
49+
#{block.id}
50+
</span>
51+
<AnimatePresence mode="wait">
52+
{showLabel && (
53+
<motion.span
54+
key={config.label}
55+
initial={{ opacity: 0, y: 4 }}
56+
animate={{ opacity: 0.9, y: 0 }}
57+
exit={{ opacity: 0, y: -4 }}
58+
transition={{ duration: 0.2, ease: 'easeOut' }}
59+
className="text-[10px] mt-0.5 sm:text-xs drop-shadow-[0_1px_1px_rgba(0,0,0,0.2)]"
60+
>
61+
{config.label}
62+
</motion.span>
63+
)}
64+
</AnimatePresence>
65+
</motion.div>
66+
)
67+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
'use client'
2+
3+
import { AnimatePresence, motion } from 'framer-motion'
4+
import { useLayoutEffect, useRef } from 'react'
5+
import { BLOCK_STATE_CONFIG } from '@/constants/block-state'
6+
import type { Block } from '@/types/block'
7+
import { BlockCard } from './block-card'
8+
9+
interface BlockchainProps {
10+
finalizedBlocks: Block[]
11+
verifiedBlocks: Block[]
12+
}
13+
14+
/**
15+
* Horizontal blockchain visualization showing finalized and verified blocks.
16+
* Auto-scrolls to show the newest blocks on the right.
17+
*/
18+
export function Blockchain({
19+
finalizedBlocks,
20+
verifiedBlocks,
21+
}: BlockchainProps) {
22+
const scrollContainerRef = useRef<HTMLDivElement>(null)
23+
const prevBlockCountRef = useRef(0)
24+
25+
// Combine and sort blocks: lower IDs on left, higher IDs on right
26+
const chainBlocks = [...verifiedBlocks, ...finalizedBlocks].sort(
27+
(a, b) => a.id - b.id,
28+
)
29+
30+
// Scroll BEFORE paint so layout animation target is visible
31+
useLayoutEffect(() => {
32+
const container = scrollContainerRef.current
33+
if (!container) return
34+
35+
const currentCount = chainBlocks.length
36+
const prevCount = prevBlockCountRef.current
37+
38+
if (currentCount > prevCount && currentCount > 0) {
39+
container.scrollLeft = container.scrollWidth
40+
}
41+
42+
prevBlockCountRef.current = currentCount
43+
}, [chainBlocks.length])
44+
45+
return (
46+
<div className="flex flex-col bg-[#16162a]/80 rounded-xl border border-[#2a2a4a]/50">
47+
<div className="px-4 py-3 border-b border-[#2a2a4a]/50 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
48+
<h3 className="text-sm font-semibold uppercase tracking-wider text-[#8888a0]">
49+
Blockchain
50+
</h3>
51+
{/* Legend */}
52+
<div className="flex items-center gap-4 text-xs text-[#6a6a7a]">
53+
<div className="flex items-center gap-1.5">
54+
<div
55+
className="w-3 h-3 rounded"
56+
style={{ background: BLOCK_STATE_CONFIG.finalized.gradient }}
57+
/>
58+
<span>Finalized</span>
59+
</div>
60+
<div className="flex items-center gap-1.5">
61+
<div
62+
className="w-3 h-3 rounded"
63+
style={{ background: BLOCK_STATE_CONFIG.verified.gradient }}
64+
/>
65+
<span>Verified</span>
66+
</div>
67+
</div>
68+
</div>
69+
<div
70+
ref={scrollContainerRef}
71+
className="flex-1 p-4 overflow-x-auto overflow-y-hidden scrollbar-none"
72+
>
73+
<div className="flex items-center min-h-[100px] sm:min-h-[120px] w-fit">
74+
<AnimatePresence mode="popLayout">
75+
{chainBlocks.map((block, index) => (
76+
<motion.div
77+
key={block.id}
78+
className="flex items-center shrink-0"
79+
layout
80+
transition={{
81+
layout: { type: 'spring', stiffness: 300, damping: 30 },
82+
}}
83+
>
84+
{/* Chain connector */}
85+
{index > 0 && (
86+
<div className="w-4 h-0.5 bg-[#3a3a5a] sm:w-6 shrink-0" />
87+
)}
88+
<BlockCard block={block} showLabel compact />
89+
</motion.div>
90+
))}
91+
</AnimatePresence>
92+
</div>
93+
</div>
94+
</div>
95+
)
96+
}

0 commit comments

Comments
 (0)