Skip to content

Commit 42a1c58

Browse files
committed
refactor: Replace spinner with board loading indicator for infinite scroll
Use the animated board loading component instead of Ant Design's Spin component when loading more climbs via infinite scroll, providing a more cohesive visual experience that matches the app's theming.
1 parent d880637 commit 42a1c58

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

packages/web/app/components/board-page/climbs-list.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22
import React, { useEffect, useRef, useCallback } from 'react';
3-
import { Row, Col, Skeleton, Spin } from 'antd';
3+
import { Row, Col } from 'antd';
4+
import InlineBoardLoading from '../loading/inline-board-loading';
45
import { track } from '@vercel/analytics';
56
import { Climb, ParsedBoardRouteParameters, BoardDetails } from '@/app/lib/types';
67
import { useQueueContext } from '../graphql-queue';
@@ -158,7 +159,7 @@ const ClimbsList = ({ boardDetails, initialClimbs }: ClimbsListProps) => {
158159
<div ref={loadMoreRef} style={{ height: '20px', marginTop: '16px' }}>
159160
{isFetchingClimbs && climbs.length > 0 && (
160161
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
161-
<Spin />
162+
<InlineBoardLoading boardDetails={boardDetails} />
162163
</div>
163164
)}
164165
{!hasMoreResults && climbs.length > 0 && (
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use client';
2+
3+
import React, { useState, useEffect, useMemo } from 'react';
4+
import BoardRenderer from '../board-renderer/board-renderer';
5+
import { BoardDetails } from '@/app/lib/types';
6+
import { LitUpHoldsMap } from '../board-renderer/types';
7+
import { themeTokens } from '@/app/theme/theme-config';
8+
9+
interface InlineBoardLoadingProps {
10+
boardDetails: BoardDetails;
11+
size?: number;
12+
}
13+
14+
const InlineBoardLoading: React.FC<InlineBoardLoadingProps> = ({ boardDetails, size = 80 }) => {
15+
const [animationFrame, setAnimationFrame] = useState(0);
16+
17+
// Generate animated holds map with radial sweep animation (like clock hands)
18+
const animatedHoldsMap = useMemo<LitUpHoldsMap>(() => {
19+
if (!boardDetails?.holdsData) return {};
20+
21+
// Calculate center of board
22+
const centerX = (boardDetails.edge_left + boardDetails.edge_right) / 2;
23+
const centerY = (boardDetails.edge_top + boardDetails.edge_bottom) / 2;
24+
25+
// Current sweep angle (0-360), advances each frame
26+
const sweepAngle = (animationFrame * 7.2) % 360; // Full rotation over 50 frames
27+
const sweepWidth = 60; // 60 degree sweep arc
28+
29+
const holdsMap: LitUpHoldsMap = {};
30+
// Use theme colors for the animation - primary, secondary, and success for variety
31+
const colors = [themeTokens.colors.primary, themeTokens.colors.secondary, themeTokens.colors.success];
32+
33+
for (const hold of boardDetails.holdsData) {
34+
// Calculate angle from center (in degrees, 0-360)
35+
let angle = Math.atan2(hold.cy - centerY, hold.cx - centerX) * (180 / Math.PI);
36+
angle = (angle + 360) % 360; // Normalize to 0-360
37+
38+
// Check if hold is within the sweep arc
39+
const diff = Math.abs(angle - sweepAngle);
40+
const withinSweep = diff < sweepWidth / 2 || diff > 360 - sweepWidth / 2;
41+
42+
if (withinSweep) {
43+
// Color based on distance from sweep center for gradient effect
44+
const normalizedDiff = Math.min(diff, 360 - diff) / (sweepWidth / 2);
45+
const colorIndex = Math.floor(normalizedDiff * 3);
46+
47+
holdsMap[hold.id] = {
48+
state: 'HAND',
49+
color: colors[colorIndex] || colors[0],
50+
displayColor: colors[colorIndex] || colors[0],
51+
};
52+
}
53+
}
54+
55+
return holdsMap;
56+
}, [boardDetails, animationFrame]);
57+
58+
// Animation frame update for hold movement
59+
useEffect(() => {
60+
const animationInterval = setInterval(() => {
61+
setAnimationFrame((prev) => (prev + 1) % 100);
62+
}, 80); // Update every 80ms for faster rotation
63+
64+
return () => clearInterval(animationInterval);
65+
}, []);
66+
67+
return (
68+
<div
69+
style={{
70+
width: size,
71+
height: size,
72+
display: 'flex',
73+
alignItems: 'center',
74+
justifyContent: 'center',
75+
}}
76+
>
77+
<BoardRenderer litUpHoldsMap={animatedHoldsMap} mirrored={false} boardDetails={boardDetails} thumbnail={false} />
78+
</div>
79+
);
80+
};
81+
82+
export default InlineBoardLoading;

0 commit comments

Comments
 (0)