Skip to content

Commit 29d0e39

Browse files
sanvishuklahasansyed107
authored andcommitted
feat: add horizontal List view parallel to Grid view (#113)
* feat: add horizontal List view parallel to Grid view * fix: resolve file conflicts and format layout * change UI * Fix pre-commit issues
1 parent 0da78e6 commit 29d0e39

3 files changed

Lines changed: 272 additions & 8 deletions

File tree

src/App.jsx

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default function App() {
2525
const [selectedRegion, setSelectedRegion] = useUrlState("region", "");
2626
const [selectedCategory, setSelectedCategory] = useUrlState("category", "");
2727
const [currentPage, setCurrentPage] = useUrlState("page", "events");
28-
const [viewMode, setViewMode] = useUrlState("view", "list");
28+
const [viewMode, setViewMode] = useUrlState("view", "grid");
2929

3030
const [dateFilterType, setDateFilterType] = useUrlState("dateType", "all");
3131
const [customDate, setCustomDate] = useUrlState("customDate", "");
@@ -178,6 +178,23 @@ export default function App() {
178178
rangeEnd,
179179
]);
180180

181+
// Group events by month for list view
182+
const groupedEvents = useMemo(() => {
183+
if (viewMode !== "list") return null;
184+
const groups = {};
185+
filteredEvents.forEach((event) => {
186+
const date = parseISODate(event.date);
187+
if (!date) return;
188+
const key = date.toLocaleDateString("en-US", {
189+
month: "long",
190+
year: "numeric",
191+
});
192+
if (!groups[key]) groups[key] = [];
193+
groups[key].push(event);
194+
});
195+
return groups;
196+
}, [filteredEvents, viewMode]);
197+
181198
return (
182199
<>
183200
<Header
@@ -235,6 +252,44 @@ export default function App() {
235252
border: "1px solid var(--border-subtle)",
236253
}}
237254
>
255+
<button
256+
onClick={() => setViewMode("grid")}
257+
style={{
258+
padding: "0.5rem 1rem",
259+
borderRadius: "8px",
260+
background:
261+
viewMode === "grid"
262+
? "var(--accent-primary)"
263+
: "transparent",
264+
color: viewMode === "grid" ? "#fff" : "var(--text-muted)",
265+
border: "none",
266+
cursor: "pointer",
267+
fontSize: "13px",
268+
fontWeight: "bold",
269+
transition: "all 0.2s",
270+
display: "flex",
271+
alignItems: "center",
272+
gap: "6px",
273+
}}
274+
>
275+
<svg
276+
xmlns="http://www.w3.org/2000/svg"
277+
width="16"
278+
height="16"
279+
viewBox="0 0 24 24"
280+
fill="none"
281+
stroke="currentColor"
282+
strokeWidth="2"
283+
strokeLinecap="round"
284+
strokeLinejoin="round"
285+
>
286+
<rect x="3" y="3" width="7" height="7"></rect>
287+
<rect x="14" y="3" width="7" height="7"></rect>
288+
<rect x="14" y="14" width="7" height="7"></rect>
289+
<rect x="3" y="14" width="7" height="7"></rect>
290+
</svg>
291+
Grid
292+
</button>
238293
<button
239294
onClick={() => setViewMode("list")}
240295
style={{
@@ -313,11 +368,39 @@ export default function App() {
313368
</div>
314369
</div>
315370

316-
{viewMode === "list" ? (
371+
{viewMode === "grid" ? (
317372
<div className="events-grid" id="events-grid">
318373
{filteredEvents.length > 0 ? (
319374
filteredEvents.map((event) => (
320-
<EventCard key={event.id} event={event} />
375+
<EventCard key={event.id} event={event} viewMode="grid" />
376+
))
377+
) : (
378+
<div className="empty-state" id="empty-state">
379+
<div className="empty-state__icon">🔎</div>
380+
<h2 className="empty-state__title">No events found</h2>
381+
<p className="empty-state__description">
382+
Try adjusting your search terms or filters to find events
383+
near you.
384+
</p>
385+
</div>
386+
)}
387+
</div>
388+
) : viewMode === "list" ? (
389+
<div className="events-list" id="events-list">
390+
{filteredEvents.length > 0 ? (
391+
Object.entries(groupedEvents).map(([month, monthEvents]) => (
392+
<div key={month} className="events-list__month-group">
393+
<h3 className="events-list__month-heading">{month}</h3>
394+
<div className="events-list__month-rows">
395+
{monthEvents.map((event) => (
396+
<EventCard
397+
key={event.id}
398+
event={event}
399+
viewMode="list"
400+
/>
401+
))}
402+
</div>
403+
</div>
321404
))
322405
) : (
323406
<div className="empty-state" id="empty-state">

src/components/EventCard.jsx

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getEventStatus } from "../utils/eventHelpers";
22

3-
export default function EventCard({ event }) {
3+
export default function EventCard({ event, viewMode = "grid" }) {
44
const status = getEventStatus(event.date);
55

66
const formattedDate = event.date
@@ -18,6 +18,44 @@ export default function EventCard({ event }) {
1818
ended: "status-badge--ended",
1919
};
2020

21+
// List view
22+
if (viewMode === "list") {
23+
return (
24+
<article className="event-list-row" id={`event-${event.id}`}>
25+
<div className="event-list-row__title-wrap">
26+
{event.url ? (
27+
<a
28+
href={event.url}
29+
className="event-list-row__title"
30+
target="_blank"
31+
rel="noopener noreferrer"
32+
>
33+
{event.title}
34+
</a>
35+
) : (
36+
<span className="event-list-row__title">{event.title}</span>
37+
)}
38+
</div>
39+
40+
<div className="event-list-row__right">
41+
<span className="event-list-row__category">{event.category}</span>
42+
43+
{status !== "none" && (
44+
<div
45+
className={`status-badge ${statusMap[status]} event-list-row__status`}
46+
>
47+
{status === "live" && <span className="live-dot" />}
48+
{status === "live" ? "Live" : status}
49+
</div>
50+
)}
51+
52+
<span className="event-list-row__date">{formattedDate}</span>
53+
</div>
54+
</article>
55+
);
56+
}
57+
58+
// Grid view
2159
return (
2260
<article className="event-card" id={`event-${event.id}`}>
2361
{/* Header */}
@@ -40,22 +78,30 @@ export default function EventCard({ event }) {
4078

4179
<div className="event-card__meta">
4280
<div className="event-card__meta-item">
43-
<span className="event-card__meta-icon">📅</span>
81+
<span className="event-card__meta-icon" aria-hidden="true">
82+
📅
83+
</span>
4484
<span>{formattedDate}</span>
4585
</div>
4686

4787
<div className="event-card__meta-item">
48-
<span className="event-card__meta-icon">🕐</span>
88+
<span className="event-card__meta-icon" aria-hidden="true">
89+
🕐
90+
</span>
4991
<span>{event.time || "Time TBD"}</span>
5092
</div>
5193

5294
<div className="event-card__meta-item">
53-
<span className="event-card__meta-icon">📍</span>
95+
<span className="event-card__meta-icon" aria-hidden="true">
96+
📍
97+
</span>
5498
<span>{event.location || "Location TBD"}</span>
5599
</div>
56100

57101
<div className="event-card__meta-item">
58-
<span className="event-card__meta-icon">🎟️</span>
102+
<span className="event-card__meta-icon" aria-hidden="true">
103+
🎟️
104+
</span>
59105
<span>
60106
{event.capacity !== undefined
61107
? `${event.capacity} spots`

src/index.css

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@
7777
--accent-secondary: #8b5cf6;
7878

7979
--gradient-hero: linear-gradient(135deg, #e2e8f0 0%, #edf2f7 100%);
80+
--gradient-card: linear-gradient(
81+
135deg,
82+
rgba(99, 102, 241, 0.04) 0%,
83+
rgba(139, 92, 246, 0.02) 100%
84+
);
8085
--border-subtle: rgba(0, 0, 0, 0.1);
8186
--border-accent: rgba(99, 102, 241, 0.4);
8287
}
@@ -527,6 +532,136 @@ body {
527532
gap: 1.5rem;
528533
}
529534

535+
/* ===================================
536+
List View — Grouped by Month
537+
=================================== */
538+
.events-list {
539+
display: flex;
540+
flex-direction: column;
541+
gap: 2rem;
542+
}
543+
544+
.events-list__month-group {
545+
display: flex;
546+
flex-direction: column;
547+
gap: 0.5rem;
548+
}
549+
550+
.events-list__month-heading {
551+
font-size: 1rem;
552+
font-weight: 700;
553+
color: var(--accent-primary);
554+
letter-spacing: -0.01em;
555+
padding-bottom: 0.5rem;
556+
margin-bottom: 0.25rem;
557+
border-bottom: 1px solid var(--border-subtle);
558+
}
559+
560+
.events-list__month-rows {
561+
display: flex;
562+
flex-direction: column;
563+
gap: 0.4rem;
564+
}
565+
566+
/* List Row Item */
567+
.event-list-row {
568+
display: flex;
569+
align-items: center;
570+
justify-content: space-between;
571+
gap: 1rem;
572+
padding: 0.75rem 1.25rem;
573+
background: var(--gradient-card);
574+
border: 1px solid var(--border-subtle);
575+
border-radius: 100px;
576+
transition: all var(--transition-slow);
577+
cursor: default;
578+
overflow: hidden;
579+
min-height: 48px;
580+
}
581+
582+
.event-list-row:hover {
583+
background: var(--bg-card-hover);
584+
border-color: var(--border-accent);
585+
box-shadow: var(--shadow-md), var(--shadow-glow);
586+
transform: translateX(6px);
587+
}
588+
589+
.event-list-row__title-wrap {
590+
flex: 1;
591+
min-width: 0;
592+
}
593+
594+
.event-list-row__title {
595+
font-size: 0.95rem;
596+
font-weight: 600;
597+
color: var(--text-primary);
598+
text-decoration: none;
599+
white-space: nowrap;
600+
overflow: hidden;
601+
text-overflow: ellipsis;
602+
display: block;
603+
transition: color var(--transition-fast);
604+
}
605+
606+
a.event-list-row__title:hover {
607+
color: var(--accent-primary);
608+
text-decoration: underline;
609+
}
610+
611+
span.event-list-row__title {
612+
color: var(--text-primary);
613+
}
614+
615+
.event-list-row__right {
616+
display: flex;
617+
align-items: center;
618+
gap: 0.75rem;
619+
flex-shrink: 0;
620+
}
621+
622+
.event-list-row__category {
623+
font-size: 0.7rem;
624+
font-weight: 600;
625+
text-transform: uppercase;
626+
letter-spacing: 0.05em;
627+
color: var(--accent-secondary);
628+
background: rgba(192, 132, 252, 0.12);
629+
border: 1px solid rgba(192, 132, 252, 0.2);
630+
padding: 0.25rem 0.65rem;
631+
border-radius: var(--radius-sm);
632+
white-space: nowrap;
633+
}
634+
635+
.event-list-row__status {
636+
font-size: 0.65rem !important;
637+
}
638+
639+
.event-list-row__date {
640+
font-size: 0.82rem;
641+
color: var(--text-secondary);
642+
white-space: nowrap;
643+
min-width: 100px;
644+
text-align: right;
645+
}
646+
647+
@media (max-width: 640px) {
648+
.event-list-row {
649+
border-radius: var(--radius-md);
650+
padding: 0.75rem 1rem;
651+
flex-wrap: wrap;
652+
gap: 0.5rem;
653+
}
654+
655+
.event-list-row__right {
656+
width: 100%;
657+
justify-content: flex-end;
658+
}
659+
660+
.event-list-row__date {
661+
min-width: auto;
662+
}
663+
}
664+
530665
/* ===================================
531666
Event Card
532667
=================================== */

0 commit comments

Comments
 (0)