-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[UXIT-1539] Events Page · Schedule (#704)
* Include schedule config to Events * Style day events cards * Style desktop Schedule with TabGroup * Show Schedule * Test data * Add SpeakersSection component for displaying speakers in the event page * CR * Refactor ScheduleTabs component to filter out days without events * CR * CR * CR * Revert data
- Loading branch information
1 parent
2bd3853
commit 0d4c834
Showing
11 changed files
with
253 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { z } from 'zod' | ||
|
||
const EventSchema = z | ||
.object({ | ||
title: z.string(), | ||
description: z.string(), | ||
start: z.coerce.date(), | ||
end: z.coerce.date().optional(), | ||
location: z.string(), | ||
url: z.string().url().optional(), | ||
}) | ||
.strict() | ||
|
||
const EventDaySchema = z | ||
.object({ | ||
date: z.coerce.date(), | ||
events: z.array(EventSchema), | ||
}) | ||
.strict() | ||
|
||
export const ScheduleSchema = z | ||
.object({ | ||
title: z.string().optional(), | ||
days: z.array(EventDaySchema), | ||
}) | ||
.strict() | ||
|
||
export type Schedule = z.infer<typeof ScheduleSchema> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { Event } from '@/types/eventType' | ||
|
||
import { PageSection } from '@/components/PageSection' | ||
|
||
import { ScheduleTabs } from './ScheduleTabs' | ||
|
||
type ScheduleSectionProps = { | ||
schedule: NonNullable<Event['schedule']> | ||
} | ||
|
||
export function ScheduleSection({ schedule }: ScheduleSectionProps) { | ||
return ( | ||
<PageSection kicker="Join Us" title={schedule.title || 'Schedule'}> | ||
<ScheduleTabs schedule={schedule} /> | ||
</PageSection> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
'use client' | ||
|
||
import { useRef } from 'react' | ||
|
||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react' | ||
import theme from 'tailwindcss/defaultTheme' | ||
import { useIsMounted, useMediaQuery } from 'usehooks-ts' | ||
|
||
import type { Event } from '@/types/eventType' | ||
|
||
import { BasicCard } from '@/components/BasicCard' | ||
import { Heading } from '@/components/Heading' | ||
import { TextLink } from '@/components/TextLink' | ||
|
||
import { formatDate, formatTime } from '../utils/dateUtils' | ||
|
||
type ScheduleTabsProps = { | ||
schedule: NonNullable<Event['schedule']> | ||
} | ||
|
||
const { screens } = theme | ||
|
||
export function ScheduleTabs({ schedule }: ScheduleTabsProps) { | ||
const tabGroupRef = useRef<HTMLDivElement>(null) | ||
const isMounted = useIsMounted() | ||
const isScreenBelowLg = useMediaQuery( | ||
`(max-width: ${parseInt(screens.md, 10) - 1}px)`, | ||
) | ||
|
||
const validDays = schedule.days | ||
.filter((day) => day.events.length > 0) | ||
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) | ||
|
||
function scrollToTabGroup() { | ||
if (isMounted() && isScreenBelowLg && tabGroupRef.current) { | ||
tabGroupRef.current.scrollIntoView({ | ||
behavior: 'smooth', | ||
block: 'start', | ||
}) | ||
} | ||
} | ||
|
||
return ( | ||
<TabGroup | ||
ref={tabGroupRef} | ||
className="relative grid gap-6" | ||
onChange={scrollToTabGroup} | ||
> | ||
<TabList className="sticky top-0 -m-2 flex gap-4 overflow-auto bg-brand-800 p-2 lg:static"> | ||
{validDays.map((day) => ( | ||
<Tab | ||
key={formatDate(day.date)} | ||
className="whitespace-nowrap rounded-lg p-3 font-bold text-brand-300 focus:brand-outline data-[hover]:bg-brand-700 data-[selected]:bg-brand-700 data-[selected]:text-brand-400" | ||
> | ||
{formatDate(day.date)} | ||
</Tab> | ||
))} | ||
</TabList> | ||
<TabPanels> | ||
{validDays.map((day) => ( | ||
<TabPanel | ||
key={formatDate(day.date)} | ||
className="rounded-lg focus:brand-outline" | ||
> | ||
<div className="grid gap-4"> | ||
{day.events.map((event) => ( | ||
<BasicCard key={event.title}> | ||
<div className="grid gap-6 lg:grid-cols-3"> | ||
<div className="flex gap-6 text-brand-300 lg:flex-col lg:gap-1"> | ||
<div className="text-sm font-bold"> | ||
<span>{formatTime(event.start)}</span> | ||
{event.end && <span> – {formatTime(event.end)}</span>} | ||
</div> | ||
<span className="text-sm">{event.location}</span> | ||
</div> | ||
<div className="lg:col-span-2"> | ||
<Heading tag="h3" variant="lg"> | ||
{event.title} | ||
</Heading> | ||
<p className="mb-4 mt-2 max-w-readable"> | ||
{event.description} | ||
</p> | ||
{event.url && ( | ||
<TextLink href={event.url}>View Details</TextLink> | ||
)} | ||
</div> | ||
</div> | ||
</BasicCard> | ||
))} | ||
</div> | ||
</TabPanel> | ||
))} | ||
</TabPanels> | ||
</TabGroup> | ||
) | ||
} | ||
|
||
function isScreenBelowLg() { | ||
return window.matchMedia(`(max-width: ${parseInt(screens.md, 10) - 1}px)`) | ||
.matches | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { Event } from '@/types/eventType' | ||
|
||
import { CardGrid } from '@/components/CardGrid' | ||
import { KeyMemberCard } from '@/components/KeyMemberCard' | ||
import { PageSection } from '@/components/PageSection' | ||
|
||
type SpeakersSectionProps = { | ||
speakers: NonNullable<Event['speakers']> | ||
} | ||
|
||
export function SpeakersSection({ speakers }: SpeakersSectionProps) { | ||
return ( | ||
<PageSection kicker="speakers" title="Speakers"> | ||
<CardGrid cols="mdTwo"> | ||
{speakers.map((speaker) => ( | ||
<KeyMemberCard | ||
key={speaker.name} | ||
{...speaker} | ||
image={{ | ||
...speaker.image, | ||
alt: `Photo of ${speaker.name}`, | ||
}} | ||
/> | ||
))} | ||
</CardGrid> | ||
</PageSection> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.