Skip to content

Commit 49fa4e5

Browse files
committed
Add more options to [Add to calendar]
1 parent 61f4d2e commit 49fa4e5

File tree

3 files changed

+141
-44
lines changed

3 files changed

+141
-44
lines changed

src/app/conf/2025/schedule/_components/schedule-session-card.tsx

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import React from "react"
2-
import { ics } from "calendar-link"
1+
import React, { Fragment } from "react"
2+
import { ics, google, outlook, CalendarEvent } from "calendar-link"
33
import { clsx } from "clsx"
4+
import {
5+
Menu,
6+
MenuButton,
7+
MenuItem,
8+
MenuItems,
9+
Transition,
10+
} from "@headlessui/react"
411

512
import { SchedSpeaker, ScheduleSession } from "@/app/conf/_api/sched-types"
613
import { Anchor } from "@/app/conf/_design-system/anchor"
@@ -85,7 +92,9 @@ export function ScheduleSessionCard({
8592
id={`session-${session.id}`}
8693
href={`/conf/${year}/schedule/${session.id}?name=${session.name}`}
8794
className="absolute inset-0 z-[1] ring-inset ring-neu-400 hover:ring-1 dark:ring-neu-100"
88-
aria-label={`Read more about "${eventTitle}" by ${speakers.map(s => s.name).join(", ")}`}
95+
aria-label={`Read more about "${eventTitle}" by ${speakers
96+
.map(s => s.name)
97+
.join(", ")}`}
8998
/>
9099
<span className="flex h-full flex-col justify-start">
91100
{eventType && (
@@ -159,27 +168,70 @@ function AddToCalendarLink({
159168
speakers: SchedSpeaker[]
160169
className?: string
161170
}) {
171+
const calendarEvent: CalendarEvent = {
172+
title: eventTitle,
173+
start: session.event_start,
174+
end: session.event_end,
175+
description: session.description,
176+
location: session.venue,
177+
organizer: {
178+
name: `GraphQLConf ${new Date().getFullYear()}`,
179+
180+
},
181+
guests: speakers.map(s => s.name),
182+
}
183+
184+
const calendars = {
185+
ICS: ics,
186+
Google: google,
187+
Outlook: outlook,
188+
}
189+
162190
return (
163-
<a
164-
className={clsx(
165-
"relative z-[2] -m-1 flex gap-0.5 p-1 ring-neu-400 hover:bg-neu-50/50 hover:ring-1 dark:ring-neu-100",
166-
className,
167-
)}
168-
href={ics({
169-
title: eventTitle,
170-
start: session.event_start,
171-
end: session.event_end,
172-
description: session.description,
173-
location: session.venue,
174-
organizer: {
175-
name: `GraphQLConf ${new Date().getFullYear()}`,
176-
177-
},
178-
guests: speakers.map(s => s.name),
179-
})}
191+
<Menu
192+
as="div"
193+
className={clsx("relative z-[2] inline-block text-left", className)}
180194
>
181-
<CalendarIcon className="size-4 shrink-0 text-pri-base" />
182-
<span className="typography-body-xs">Add to calendar</span>
183-
</a>
195+
<div>
196+
<MenuButton
197+
className={clsx(
198+
"inline-flex w-full items-center justify-center gap-0.5 p-1",
199+
"ring-neu-400 hover:bg-neu-50/50 hover:ring-1 focus:outline-none focus:ring-1 dark:ring-neu-100 [&[aria-expanded=true]]:ring-2",
200+
)}
201+
>
202+
<CalendarIcon className="size-4 shrink-0 text-pri-base" />
203+
<span className="typography-body-xs">Add to calendar</span>
204+
</MenuButton>
205+
</div>
206+
<Transition
207+
as={Fragment}
208+
enter="transition ease-out duration-100"
209+
enterFrom="transform opacity-0 scale-95"
210+
enterTo="transform opacity-100 scale-100"
211+
leave="transition ease-in duration-75"
212+
leaveFrom="transform opacity-100 scale-100"
213+
leaveTo="transform opacity-0 scale-95"
214+
>
215+
<MenuItems
216+
anchor="bottom end"
217+
className="mt-2 w-40 origin-top-right border border-neu-400 bg-neu-0 focus:outline-none dark:bg-neu-900"
218+
>
219+
<div className="p-1">
220+
{Object.entries(calendars).map(([name, calendar]) => (
221+
<MenuItem key={name}>
222+
<a
223+
href={calendar(calendarEvent)}
224+
target="_blank"
225+
rel="noopener noreferrer"
226+
className="group typography-body-xs flex w-full items-center px-2 py-1 text-neu-800 [&[data-active]]:bg-neu-100 [&[data-active]]:text-neu-900"
227+
>
228+
{name}
229+
</a>
230+
</MenuItem>
231+
))}
232+
</div>
233+
</MenuItems>
234+
</Transition>
235+
</Menu>
184236
)
185237
}

src/app/conf/2025/speakers/[id]/long-session-card.tsx

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { clsx } from "clsx"
2-
import { ics } from "calendar-link"
2+
import { CalendarEvent, google, ics, outlook } from "calendar-link"
33

44
import { SchedSpeaker, ScheduleSession } from "@/app/conf/2023/types"
55
import { Button } from "@/app/conf/_design-system/button"
@@ -11,8 +11,10 @@ import PlusIcon from "@/app/conf/_design-system/pixelarticons/plus.svg?svgr"
1111
import PlayIcon from "@/app/conf/_design-system/pixelarticons/play.svg?svgr"
1212
import { findVideo } from "../../schedule/[id]/session-video"
1313
import { getEventTitle } from "../../utils"
14-
import React from "react"
14+
import React, { Fragment } from "react"
1515
import { SessionTags } from "../../components/session-tags"
16+
import { Menu, MenuItem, MenuItems, Transition } from "@headlessui/react"
17+
import { MenuButton } from "@headlessui/react"
1618

1719
export interface LongSessionCardProps
1820
extends React.HTMLAttributes<HTMLDivElement> {
@@ -159,29 +161,71 @@ function AddToCalendarLink({
159161
eventTitle,
160162
session,
161163
speakers,
164+
className,
162165
}: {
163166
eventTitle: string
164167
session: ScheduleSession
165168
speakers: SchedSpeaker[]
169+
className?: string
166170
}) {
171+
const calendarEvent: CalendarEvent = {
172+
title: eventTitle,
173+
start: session.event_start,
174+
end: session.event_end,
175+
description: session.description,
176+
location: session.venue,
177+
organizer: {
178+
name: `GraphQLConf ${new Date().getFullYear()}`,
179+
180+
},
181+
guests: speakers.map(s => s.name),
182+
}
183+
184+
const calendars = {
185+
ICS: ics,
186+
Google: google,
187+
Outlook: outlook,
188+
}
189+
167190
return (
168-
<a
169-
className="relative z-[2] flex h-full flex-row items-center justify-center gap-0.5 p-4 text-neu-800 ring-inset ring-neu-400 hover:bg-sec-base/[.035] hover:ring-1 dark:ring-neu-100 lg:px-6"
170-
href={ics({
171-
title: eventTitle,
172-
start: session.event_start,
173-
end: session.event_end,
174-
description: session.description,
175-
location: session.venue,
176-
organizer: {
177-
name: `GraphQLConf ${new Date().getFullYear()}`,
178-
179-
},
180-
guests: speakers.map(s => s.name),
181-
})}
182-
>
183-
<PlusIcon className="size-4 shrink-0 text-sec-dark" />
184-
<span className="typography-body-xs">Add to calendar</span>
185-
</a>
191+
<Menu as="div" className={clsx("relative z-[2] flex h-full", className)}>
192+
<MenuButton
193+
className={clsx(
194+
"relative z-[2] flex size-full h-full flex-row items-center justify-center gap-0.5 p-4 text-neu-800 ring-inset ring-neu-400 hover:bg-sec-base/[.035] hover:ring-1 focus:outline-none focus:ring-1 dark:ring-neu-100 lg:px-6 [&[aria-expanded=true]]:ring-1",
195+
)}
196+
>
197+
<PlusIcon className="size-4 shrink-0 text-sec-dark" />
198+
<span className="typography-body-xs">Add to calendar</span>
199+
</MenuButton>
200+
<Transition
201+
as={Fragment}
202+
enter="transition ease-out duration-100"
203+
enterFrom="transform opacity-0 scale-95"
204+
enterTo="transform opacity-100 scale-100"
205+
leave="transition ease-in duration-75"
206+
leaveFrom="transform opacity-100 scale-100"
207+
leaveTo="transform opacity-0 scale-95"
208+
>
209+
<MenuItems
210+
anchor="bottom end"
211+
className="mt-2 w-40 origin-top-right border border-neu-400 bg-neu-0 focus:outline-none dark:bg-neu-900"
212+
>
213+
<div className="p-1">
214+
{Object.entries(calendars).map(([name, calendar]) => (
215+
<MenuItem key={name}>
216+
<a
217+
href={calendar(calendarEvent)}
218+
target="_blank"
219+
rel="noopener noreferrer"
220+
className="group typography-body-sm flex w-full items-center p-3 text-neu-800 [&[data-active]]:bg-neu-100 [&[data-active]]:text-neu-900"
221+
>
222+
{name}
223+
</a>
224+
</MenuItem>
225+
))}
226+
</div>
227+
</MenuItems>
228+
</Transition>
229+
</Menu>
186230
)
187231
}

src/globals.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ div[id^="headlessui-menu-items"] {
533533
}
534534

535535
/* without this Headless UI breaks sticky navbar */
536-
html:has([role="listbox"][data-open]) {
536+
html:has([role="listbox"][data-open]),
537+
html:has([role="menu"][data-open]) {
537538
overflow: visible !important;
538539
}

0 commit comments

Comments
 (0)