Skip to content

Commit 1dfa97e

Browse files
hemanandrclaude
andcommitted
feat: add smooth sliding animation for new status data in TrendBlocks
Implement timestamp-based detection for new data arrival with coordinated sliding animations: - Detect new data using timestamp comparison (ts field) - Slide existing blocks left when new data arrives - New block grows from scale(0.1) to scale(1) while sliding in - Coordinated 500ms animation with opacity fade-in - Pause heartbeat during transitions, resume on completion Creates engaging visual feedback when status updates arrive, making it clear when new monitoring data is received. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 6df6423 commit 1dfa97e

File tree

1 file changed

+50
-2
lines changed

1 file changed

+50
-2
lines changed

thingconnect.pulse.client/src/components/status/TrendBlocks.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { HStack, Box } from '@chakra-ui/react';
22
import type { SparklinePoint } from '@/api/types';
3+
import { useEffect, useState, useRef } from 'react';
34

45
const TrendBlocks = ({ data }: { data: SparklinePoint[] }) => {
56
const maxBlocks = 20;
7+
const [isSliding, setIsSliding] = useState(false);
8+
const [showNewData, setShowNewData] = useState(false);
9+
const lastTimestampRef = useRef<string | null>(null);
10+
611
// Take the last 20 points, but maintain order (oldest to newest)
712
const recentData = data.slice(-maxBlocks);
813

@@ -12,6 +17,25 @@ const TrendBlocks = ({ data }: { data: SparklinePoint[] }) => {
1217
return dataIdx >= 0 ? recentData[dataIdx] : null;
1318
});
1419

20+
// Detect new data by checking if the latest timestamp changed
21+
useEffect(() => {
22+
const latestTimestamp = recentData.length > 0 ? recentData[recentData.length - 1]?.ts : null;
23+
24+
if (latestTimestamp && lastTimestampRef.current && latestTimestamp !== lastTimestampRef.current) {
25+
// New data detected - trigger slide animation
26+
setIsSliding(true);
27+
setShowNewData(false);
28+
29+
// After slide completes, show the new data
30+
setTimeout(() => {
31+
setShowNewData(true);
32+
setIsSliding(false);
33+
}, 500);
34+
}
35+
36+
lastTimestampRef.current = latestTimestamp;
37+
}, [recentData]);
38+
1539
return (
1640
<>
1741
<style>
@@ -21,19 +45,35 @@ const TrendBlocks = ({ data }: { data: SparklinePoint[] }) => {
2145
50% { transform: scale(1.10); }
2246
100% { transform: scale(0.9); }
2347
}
48+
@keyframes slideLeftGroup {
49+
0% { transform: translateX(0); }
50+
100% { transform: translateX(-16px); }
51+
}
52+
@keyframes slideInFromRight {
53+
0% { transform: translateX(16px) scale(0.1); opacity: 0; }
54+
50% { transform: translateX(8px) scale(0.5); opacity: 0.5; }
55+
100% { transform: translateX(0) scale(1); opacity: 1; }
56+
}
2457
.heartbeat-animation {
2558
animation: heartbeat 1.5s ease-in-out infinite;
2659
}
60+
.slide-left-animation {
61+
animation: slideLeftGroup 500ms ease-out;
62+
}
63+
.slide-in-animation {
64+
animation: slideInFromRight 500ms ease-out;
65+
}
2766
`}
2867
</style>
2968
<HStack gap={1} alignItems="center" overflow="hidden" py={"2px"} pr={"2px"}>
3069
{displayBlocks.map((point, idx) => {
3170
const isLastElement = idx === displayBlocks.length - 1 && point !== null;
3271
const isEmpty = point === null;
72+
const isNewElement = isLastElement && showNewData;
3373

3474
return (
3575
<Box
36-
key={idx}
76+
key={point?.ts || `empty-${idx}`}
3777
w='3'
3878
h='5'
3979
borderRadius='sm'
@@ -46,7 +86,15 @@ const TrendBlocks = ({ data }: { data: SparklinePoint[] }) => {
4686
? 'gray.700'
4787
: point.s === 'd' ? 'red.600' : 'green.600'
4888
}}
49-
className={isLastElement ? 'heartbeat-animation' : undefined}
89+
className={
90+
isNewElement
91+
? 'slide-in-animation'
92+
: isSliding && !isEmpty
93+
? 'slide-left-animation'
94+
: isLastElement && !isSliding && !showNewData
95+
? 'heartbeat-animation'
96+
: undefined
97+
}
5098
transformOrigin="center"
5199
position="relative"
52100
zIndex={isLastElement ? 2 : 1}

0 commit comments

Comments
 (0)