an app to share curated trails
sidetrail.app
1import type { TrailCardData } from "../data/queries";
2import { TrailCard } from "./TrailCard";
3import { TrailCardWalkers } from "./TrailCardWalkers";
4import { HomeEmptyState } from "./HomeEmptyState";
5import "./HomeTrailsList.css";
6
7type Props = {
8 trails: TrailCardData[];
9};
10
11// Reorder items for CSS columns masonry: [1,2,3,4,5,6] -> [1,3,5,2,4,6]
12// CSS columns fills top-to-bottom, so left column gets first half of DOM.
13// We put odd-ranked items first (left col), then even-ranked (right col).
14// Visual order reads left-to-right: 1,2 / 3,4 / 5,6
15// Tab order follows DOM: 1 -> 3 -> 5 -> 2 -> 4 -> 6 (column-wise)
16// On mobile, CSS order property restores original ranking.
17function interleaveForMasonry<T>(items: T[]): { item: T; originalIndex: number }[] {
18 const result: { item: T; originalIndex: number }[] = [];
19 for (let i = 0; i < items.length; i += 2) {
20 result.push({ item: items[i], originalIndex: i });
21 }
22 for (let i = 1; i < items.length; i += 2) {
23 result.push({ item: items[i], originalIndex: i });
24 }
25 return result;
26}
27
28export function HomeTrailsList({ trails }: Props) {
29 if (trails.length === 0) {
30 return (
31 <HomeEmptyState message="no trails are trending right now" buttonText="start a new trail" />
32 );
33 }
34
35 const reordered = interleaveForMasonry(trails);
36
37 return (
38 <div className="HomeTrailsList-grid">
39 {reordered.map(({ item: trail, originalIndex }) => (
40 <div key={trail.uri} style={{ "--original-index": originalIndex } as React.CSSProperties}>
41 <TrailCard
42 rkey={trail.rkey}
43 creatorHandle={trail.creatorHandle}
44 title={trail.title}
45 description={trail.description}
46 accentColor={trail.accentColor}
47 backgroundColor={trail.backgroundColor}
48 creator={trail.creator}
49 stopsCount={trail.stopsCount}
50 walkersSlot={<TrailCardWalkers trailUri={trail.uri} />}
51 />
52 </div>
53 ))}
54 </div>
55 );
56}