@devrongx/games 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/matches/MatchCalendar.tsx +185 -165
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @devrongx/games — matches/MatchCalendar.tsx
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
-
import { useRef, useMemo } from "react";
|
|
4
|
+
import { useRef, useMemo, useEffect } from "react";
|
|
5
5
|
import { motion } from "framer-motion";
|
|
6
|
-
import { Calendar, MapPin, Loader2,
|
|
6
|
+
import { Calendar, MapPin, Loader2, Trophy, Zap } from "lucide-react";
|
|
7
7
|
import { useTDMatches } from "./useTDMatches";
|
|
8
8
|
import type { ITDMatch } from "./types";
|
|
9
9
|
|
|
@@ -14,6 +14,8 @@ const MATCH_STATUS = {
|
|
|
14
14
|
COMPLETED: 5,
|
|
15
15
|
} as const;
|
|
16
16
|
|
|
17
|
+
const OUTFIT = { fontFamily: "Outfit, sans-serif" };
|
|
18
|
+
|
|
17
19
|
/* ── Date helpers ── */
|
|
18
20
|
|
|
19
21
|
function toLocalDate(iso: string): Date {
|
|
@@ -57,13 +59,19 @@ function getTeamShortName(match: ITDMatch, team: "a" | "b"): string {
|
|
|
57
59
|
return t.short_name || t.name.slice(0, 3).toUpperCase();
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/** Map popularity 0-10 to border shine opacity (0 = no shine, 10 = full intensity) */
|
|
63
|
+
function getShineOpacity(popularity: number): number {
|
|
64
|
+
if (popularity <= 0) return 0;
|
|
65
|
+
return Math.min(popularity / 10, 1);
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
/* ── Grouping: by date ── */
|
|
61
69
|
|
|
62
70
|
interface IMatchDay {
|
|
63
|
-
dateKey: string;
|
|
71
|
+
dateKey: string;
|
|
64
72
|
date: Date;
|
|
65
|
-
label: string;
|
|
66
|
-
dayMonth: string;
|
|
73
|
+
label: string;
|
|
74
|
+
dayMonth: string;
|
|
67
75
|
matches: ITDMatch[];
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -93,91 +101,124 @@ function groupByDate(matches: ITDMatch[]): IMatchDay[] {
|
|
|
93
101
|
}));
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
/* ──
|
|
104
|
+
/* ── Match card with border shine animation ── */
|
|
97
105
|
|
|
98
|
-
function
|
|
106
|
+
function MatchCard({ match }: { match: ITDMatch }) {
|
|
99
107
|
const isLive = match.status === MATCH_STATUS.LIVE;
|
|
100
108
|
const isCompleted = match.status === MATCH_STATUS.COMPLETED;
|
|
101
109
|
const time = match.scheduled_start_at ? formatTime(toLocalDate(match.scheduled_start_at)) : "";
|
|
102
|
-
const venue = match.venue?.city || match.venue?.name;
|
|
110
|
+
const venue = match.venue?.city?.trim() || match.venue?.name;
|
|
111
|
+
const shineOpacity = getShineOpacity(match.rating_popularity);
|
|
112
|
+
const showShine = shineOpacity > 0 && !isCompleted;
|
|
103
113
|
|
|
104
114
|
return (
|
|
105
|
-
<div
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
: isCompleted
|
|
111
|
-
? "rgba(255,255,255,0.02)"
|
|
112
|
-
: "rgba(255,255,255,0.03)",
|
|
113
|
-
border: isLive
|
|
114
|
-
? "1px solid rgba(248,60,197,0.2)"
|
|
115
|
-
: "1px solid rgba(255,255,255,0.04)",
|
|
116
|
-
}}
|
|
117
|
-
>
|
|
118
|
-
{/* Time + status row */}
|
|
119
|
-
<div className="flex items-center justify-between">
|
|
120
|
-
<span
|
|
121
|
-
className="text-[10px]"
|
|
115
|
+
<div className="relative w-full rounded-xl p-[1px] overflow-hidden">
|
|
116
|
+
{/* Spinning border shine — intensity based on popularity */}
|
|
117
|
+
{showShine && (
|
|
118
|
+
<div
|
|
119
|
+
className="absolute inset-[-50%] pointer-events-none"
|
|
122
120
|
style={{
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
background: isLive
|
|
122
|
+
? `conic-gradient(from 0deg, transparent 0%, transparent 65%, rgba(248,60,197,${shineOpacity * 0.6}) 75%, rgba(255,255,255,${shineOpacity * 0.7}) 80%, rgba(248,60,197,${shineOpacity * 0.6}) 85%, transparent 95%, transparent 100%)`
|
|
123
|
+
: `conic-gradient(from 0deg, transparent 0%, transparent 70%, rgba(34,227,232,${shineOpacity * 0.5}) 78%, rgba(255,255,255,${shineOpacity * 0.6}) 80%, rgba(34,227,232,${shineOpacity * 0.5}) 82%, transparent 90%, transparent 100%)`,
|
|
124
|
+
animation: `borderShine ${6 - shineOpacity * 2}s linear infinite`,
|
|
125
125
|
}}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
126
|
+
/>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{/* Static border for no-shine / completed */}
|
|
130
|
+
{!showShine && (
|
|
131
|
+
<div
|
|
132
|
+
className="absolute inset-0 rounded-xl"
|
|
133
|
+
style={{
|
|
134
|
+
border: isCompleted
|
|
135
|
+
? "1px solid rgba(255,255,255,0.06)"
|
|
136
|
+
: "1px solid rgba(255,255,255,0.08)",
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
<div
|
|
142
|
+
className="relative flex flex-col gap-1.5 rounded-xl px-3 py-2.5"
|
|
143
|
+
style={{
|
|
144
|
+
background: isLive ? "rgba(10,10,18,0.95)" : "rgba(10,10,18,0.98)",
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{/* Time + live indicator */}
|
|
148
|
+
<div className="flex items-center justify-between">
|
|
141
149
|
<span
|
|
142
|
-
className="text-[
|
|
143
|
-
style={{
|
|
150
|
+
className="text-[10px]"
|
|
151
|
+
style={{
|
|
152
|
+
...OUTFIT,
|
|
153
|
+
color: isLive ? "#f83cc5" : isCompleted ? "rgba(255,255,255,0.35)" : "#fff",
|
|
154
|
+
}}
|
|
144
155
|
>
|
|
145
|
-
{
|
|
156
|
+
{time}
|
|
146
157
|
</span>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
>
|
|
165
|
-
{getTeamShortName(match, "b")}
|
|
166
|
-
</span>
|
|
167
|
-
</div>
|
|
158
|
+
{isLive && (
|
|
159
|
+
<div className="flex items-center gap-1">
|
|
160
|
+
<span className="relative flex h-1.5 w-1.5">
|
|
161
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#f83cc5] opacity-75" />
|
|
162
|
+
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-[#f83cc5]" />
|
|
163
|
+
</span>
|
|
164
|
+
<span className="text-[8px] barlowcondensedBold tracking-widest" style={{ color: "#f83cc5" }}>
|
|
165
|
+
LIVE
|
|
166
|
+
</span>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
{isCompleted && match.winner && (
|
|
170
|
+
<span className="text-[8px]" style={{ ...OUTFIT, color: "rgba(255,255,255,0.35)" }}>
|
|
171
|
+
{match.winner.short_name || match.winner.name} won
|
|
172
|
+
</span>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
168
175
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
{/* Teams */}
|
|
177
|
+
<div className="flex items-center gap-1.5 justify-center">
|
|
178
|
+
<span
|
|
179
|
+
className="barlowcondensedBold text-[20px] leading-none"
|
|
180
|
+
style={{ color: isCompleted ? "rgba(255,255,255,0.35)" : "#fff" }}
|
|
181
|
+
>
|
|
182
|
+
{getTeamShortName(match, "a")}
|
|
183
|
+
</span>
|
|
184
|
+
<span className="text-[9px]" style={{ color: "rgba(255,255,255,0.3)" }}>
|
|
185
|
+
v
|
|
186
|
+
</span>
|
|
173
187
|
<span
|
|
174
|
-
className="text-[
|
|
175
|
-
style={{
|
|
188
|
+
className="barlowcondensedBold text-[20px] leading-none"
|
|
189
|
+
style={{ color: isCompleted ? "rgba(255,255,255,0.35)" : "#fff" }}
|
|
176
190
|
>
|
|
177
|
-
{
|
|
191
|
+
{getTeamShortName(match, "b")}
|
|
178
192
|
</span>
|
|
179
193
|
</div>
|
|
180
|
-
|
|
194
|
+
|
|
195
|
+
{/* Venue */}
|
|
196
|
+
{venue && (
|
|
197
|
+
<div className="flex items-center gap-1 justify-center">
|
|
198
|
+
<MapPin size={8} style={{ color: "rgba(255,255,255,0.3)" }} />
|
|
199
|
+
<span
|
|
200
|
+
className="text-[8px] truncate max-w-[100px]"
|
|
201
|
+
style={{ ...OUTFIT, color: "rgba(255,255,255,0.3)" }}
|
|
202
|
+
>
|
|
203
|
+
{venue}
|
|
204
|
+
</span>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{/* Popularity indicator */}
|
|
209
|
+
{match.rating_popularity > 0 && !isCompleted && (
|
|
210
|
+
<div className="flex items-center gap-0.5 justify-center mt-0.5">
|
|
211
|
+
{Array.from({ length: Math.min(Math.ceil(match.rating_popularity / 2), 5) }).map((_, i) => (
|
|
212
|
+
<Zap
|
|
213
|
+
key={i}
|
|
214
|
+
size={7}
|
|
215
|
+
fill={isLive ? "#f83cc5" : "#22E3E8"}
|
|
216
|
+
style={{ color: isLive ? "#f83cc5" : "#22E3E8", opacity: 0.4 + i * 0.15 }}
|
|
217
|
+
/>
|
|
218
|
+
))}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
181
222
|
</div>
|
|
182
223
|
);
|
|
183
224
|
}
|
|
@@ -195,55 +236,38 @@ function DayColumn({ day, index }: { day: IMatchDay; index: number }) {
|
|
|
195
236
|
animate={{ opacity: 1, y: 0 }}
|
|
196
237
|
transition={{ duration: 0.3, delay: index * 0.03 }}
|
|
197
238
|
className="shrink-0 flex flex-col gap-1.5"
|
|
198
|
-
style={{ scrollSnapAlign: "start", width: "
|
|
239
|
+
style={{ scrollSnapAlign: "start", width: "140px" }}
|
|
199
240
|
>
|
|
200
241
|
{/* Day header */}
|
|
201
242
|
<div className="flex flex-col items-center gap-0.5 pb-1">
|
|
202
243
|
<span
|
|
203
244
|
className="text-[10px] barlowcondensedBold tracking-widest"
|
|
204
245
|
style={{
|
|
205
|
-
color: hasLive
|
|
206
|
-
? "#f83cc5"
|
|
207
|
-
: hasToday
|
|
208
|
-
? "#22E3E8"
|
|
209
|
-
: allCompleted
|
|
210
|
-
? "rgba(255,255,255,0.15)"
|
|
211
|
-
: "rgba(255,255,255,0.4)",
|
|
246
|
+
color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.25)" : "#fff",
|
|
212
247
|
}}
|
|
213
248
|
>
|
|
214
249
|
{day.label}
|
|
215
250
|
</span>
|
|
216
251
|
<span
|
|
217
|
-
className="text-[
|
|
252
|
+
className="text-[12px] font-semibold"
|
|
218
253
|
style={{
|
|
219
|
-
|
|
220
|
-
color: hasLive
|
|
221
|
-
? "rgba(248,60,197,0.8)"
|
|
222
|
-
: hasToday
|
|
223
|
-
? "rgba(34,227,232,0.7)"
|
|
224
|
-
: allCompleted
|
|
225
|
-
? "rgba(255,255,255,0.15)"
|
|
226
|
-
: "rgba(255,255,255,0.5)",
|
|
254
|
+
...OUTFIT,
|
|
255
|
+
color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.25)" : "#fff",
|
|
227
256
|
}}
|
|
228
257
|
>
|
|
229
258
|
{day.dayMonth}
|
|
230
259
|
</span>
|
|
231
|
-
{/* Indicator dot */}
|
|
232
260
|
{(hasToday || hasLive) && (
|
|
233
261
|
<div
|
|
234
262
|
className="w-5 h-[2px] rounded-full mt-0.5"
|
|
235
|
-
style={{
|
|
236
|
-
background: hasLive
|
|
237
|
-
? "#f83cc5"
|
|
238
|
-
: "#22E3E8",
|
|
239
|
-
}}
|
|
263
|
+
style={{ background: hasLive ? "#f83cc5" : "#22E3E8" }}
|
|
240
264
|
/>
|
|
241
265
|
)}
|
|
242
266
|
</div>
|
|
243
267
|
|
|
244
268
|
{/* Match cards for the day */}
|
|
245
269
|
{day.matches.map((match) => (
|
|
246
|
-
<
|
|
270
|
+
<MatchCard key={match.id} match={match} />
|
|
247
271
|
))}
|
|
248
272
|
</motion.div>
|
|
249
273
|
);
|
|
@@ -264,48 +288,43 @@ export function MatchCalendar({ tournamentId }: MatchCalendarProps) {
|
|
|
264
288
|
|
|
265
289
|
const days = useMemo(() => groupByDate(matches), [matches]);
|
|
266
290
|
|
|
267
|
-
//
|
|
268
|
-
const
|
|
269
|
-
const now = new Date();
|
|
270
|
-
// Find today
|
|
291
|
+
// Auto-scroll to today or first upcoming day
|
|
292
|
+
const scrollTargetIndex = useMemo(() => {
|
|
271
293
|
const tIdx = days.findIndex((d) => isToday(d.date));
|
|
272
294
|
if (tIdx >= 0) return tIdx;
|
|
273
|
-
|
|
295
|
+
const now = new Date();
|
|
274
296
|
const uIdx = days.findIndex((d) => d.date >= now);
|
|
275
|
-
if (uIdx >= 0) return Math.max(0, uIdx
|
|
297
|
+
if (uIdx >= 0) return Math.max(0, uIdx);
|
|
276
298
|
return 0;
|
|
277
299
|
}, [days]);
|
|
278
300
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!stages.has(m.stage.id)) {
|
|
286
|
-
stages.set(m.stage.id, { name: m.stage.name, orderNo: m.stage.order_no, days: [] });
|
|
287
|
-
}
|
|
288
|
-
}
|
|
301
|
+
useEffect(() => {
|
|
302
|
+
if (scrollRef.current && scrollTargetIndex > 0) {
|
|
303
|
+
const container = scrollRef.current;
|
|
304
|
+
const target = container.children[scrollTargetIndex] as HTMLElement;
|
|
305
|
+
if (target) {
|
|
306
|
+
container.scrollTo({ left: target.offsetLeft - 20, behavior: "smooth" });
|
|
289
307
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
308
|
+
}
|
|
309
|
+
}, [scrollTargetIndex, days]);
|
|
310
|
+
|
|
311
|
+
// Stage groups
|
|
312
|
+
const stageGroups = useMemo(() => {
|
|
313
|
+
const stages = new Map<number, { name: string; orderNo: number }>();
|
|
314
|
+
for (const m of matches) {
|
|
315
|
+
if (m.stage && !stages.has(m.stage.id)) {
|
|
316
|
+
stages.set(m.stage.id, { name: m.stage.name, orderNo: m.stage.order_no });
|
|
297
317
|
}
|
|
298
318
|
}
|
|
299
319
|
return Array.from(stages.values()).sort((a, b) => a.orderNo - b.orderNo);
|
|
300
|
-
}, [
|
|
320
|
+
}, [matches]);
|
|
301
321
|
|
|
302
|
-
// Tournament date range from first match
|
|
303
322
|
const tournament = matches[0]?.tournament;
|
|
304
323
|
|
|
305
324
|
if (isLoading) {
|
|
306
325
|
return (
|
|
307
|
-
<div className="flex items-center justify-center py-
|
|
308
|
-
<Loader2 size={16} className="animate-spin" style={{ color: "rgba(255,255,255,0.
|
|
326
|
+
<div className="flex items-center justify-center py-6">
|
|
327
|
+
<Loader2 size={16} className="animate-spin" style={{ color: "rgba(255,255,255,0.3)" }} />
|
|
309
328
|
</div>
|
|
310
329
|
);
|
|
311
330
|
}
|
|
@@ -313,54 +332,55 @@ export function MatchCalendar({ tournamentId }: MatchCalendarProps) {
|
|
|
313
332
|
if (error || matches.length === 0) return null;
|
|
314
333
|
|
|
315
334
|
return (
|
|
316
|
-
<div className="relative z-30 w-full
|
|
335
|
+
<div className="relative z-30 w-full mt-6 mb-4">
|
|
336
|
+
{/* Tournament info header */}
|
|
337
|
+
{tournament && (
|
|
338
|
+
<div className="px-5 mb-3">
|
|
339
|
+
<div className="flex items-center gap-2 mb-1">
|
|
340
|
+
<Trophy size={14} style={{ color: "#22E3E8" }} />
|
|
341
|
+
<span className="barlowcondensedBold text-[16px] tracking-wide text-white">
|
|
342
|
+
{tournament.name}
|
|
343
|
+
</span>
|
|
344
|
+
</div>
|
|
345
|
+
<div className="flex items-center gap-3">
|
|
346
|
+
{tournament.start_date && tournament.end_date && (
|
|
347
|
+
<span className="text-[11px] text-white" style={OUTFIT}>
|
|
348
|
+
{formatDayMonth(toLocalDate(tournament.start_date))} – {formatDayMonth(toLocalDate(tournament.end_date))}
|
|
349
|
+
</span>
|
|
350
|
+
)}
|
|
351
|
+
<span className="text-[11px] text-white" style={OUTFIT}>
|
|
352
|
+
{matches.length} matches
|
|
353
|
+
</span>
|
|
354
|
+
{stageGroups.length > 0 && (
|
|
355
|
+
<span
|
|
356
|
+
className="text-[9px] barlowcondensedBold tracking-widest px-2 py-0.5 rounded-full"
|
|
357
|
+
style={{
|
|
358
|
+
color: "#22E3E8",
|
|
359
|
+
background: "rgba(34,227,232,0.08)",
|
|
360
|
+
border: "1px solid rgba(34,227,232,0.15)",
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
{stageGroups[0].name.toUpperCase()}
|
|
364
|
+
</span>
|
|
365
|
+
)}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
)}
|
|
369
|
+
|
|
317
370
|
{/* Section header */}
|
|
318
|
-
<div className="flex items-center gap-2 px-5 mb-
|
|
319
|
-
<Calendar size={12} style={{ color: "
|
|
320
|
-
<span className="barlowCondensedSemiBold text-[13px] tracking-wider
|
|
371
|
+
<div className="flex items-center gap-2 px-5 mb-2.5">
|
|
372
|
+
<Calendar size={12} style={{ color: "#22E3E8" }} />
|
|
373
|
+
<span className="barlowCondensedSemiBold text-[13px] tracking-wider text-white">
|
|
321
374
|
SCHEDULE
|
|
322
375
|
</span>
|
|
323
|
-
{
|
|
324
|
-
{tournament?.start_date && tournament?.end_date && (
|
|
325
|
-
<>
|
|
326
|
-
<ChevronRight size={10} style={{ color: "rgba(255,255,255,0.15)" }} />
|
|
327
|
-
<span className="text-[10px]" style={{ fontFamily: "Outfit, sans-serif", color: "rgba(255,255,255,0.2)" }}>
|
|
328
|
-
{formatDayMonth(toLocalDate(tournament.start_date))} – {formatDayMonth(toLocalDate(tournament.end_date))}
|
|
329
|
-
</span>
|
|
330
|
-
</>
|
|
331
|
-
)}
|
|
332
|
-
<div className="flex-1" />
|
|
333
|
-
<span className="text-[10px]" style={{ fontFamily: "Outfit, sans-serif", color: "rgba(255,255,255,0.2)" }}>
|
|
334
|
-
{matches.length} matches
|
|
335
|
-
</span>
|
|
376
|
+
<div className="flex-1 h-px" style={{ background: "rgba(255,255,255,0.06)" }} />
|
|
336
377
|
</div>
|
|
337
378
|
|
|
338
|
-
{/* Stage label if exists */}
|
|
339
|
-
{stageGroups.length > 0 && stageGroups[0].name && (
|
|
340
|
-
<div className="px-5 mb-2">
|
|
341
|
-
<span
|
|
342
|
-
className="text-[9px] barlowcondensedBold tracking-widest px-2 py-0.5 rounded-full"
|
|
343
|
-
style={{
|
|
344
|
-
color: "rgba(34,227,232,0.5)",
|
|
345
|
-
background: "rgba(34,227,232,0.06)",
|
|
346
|
-
border: "1px solid rgba(34,227,232,0.1)",
|
|
347
|
-
}}
|
|
348
|
-
>
|
|
349
|
-
{stageGroups[0].name.toUpperCase()}
|
|
350
|
-
</span>
|
|
351
|
-
</div>
|
|
352
|
-
)}
|
|
353
|
-
|
|
354
379
|
{/* Scrollable day columns */}
|
|
355
380
|
<div
|
|
356
381
|
ref={scrollRef}
|
|
357
|
-
className="flex gap-2 overflow-x-auto px-5 pb-
|
|
358
|
-
style={{
|
|
359
|
-
scrollSnapType: "x mandatory",
|
|
360
|
-
WebkitOverflowScrolling: "touch",
|
|
361
|
-
// Auto-scroll to today/upcoming
|
|
362
|
-
...(todayIndex > 0 ? {} : {}),
|
|
363
|
-
}}
|
|
382
|
+
className="flex gap-2.5 overflow-x-auto px-5 pb-3 scrollbar-hide"
|
|
383
|
+
style={{ scrollSnapType: "x mandatory", WebkitOverflowScrolling: "touch" }}
|
|
364
384
|
>
|
|
365
385
|
{days.map((day, i) => (
|
|
366
386
|
<DayColumn key={day.dateKey} day={day} index={i} />
|