Skip to content

Commit 7867e59

Browse files
authored
conf 2025 — add current time marker to schedule (#2125)
* Add current time marker * Add a button to scroll to current time marker
1 parent e21d8cf commit 7867e59

File tree

4 files changed

+106
-13
lines changed

4 files changed

+106
-13
lines changed

src/app/conf/2025/schedule/_components/filters.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,18 @@ export function ResetFiltersButton({
9797
}) {
9898
const hasFilters = Object.values(filters).flat().length > 0
9999

100+
if (!hasFilters) {
101+
return null
102+
}
103+
100104
return (
101105
<Button
102106
variant="tertiary"
103107
title="Reset filters"
104108
onClick={onReset}
105109
disabled={!hasFilters}
106110
className={clsx(
107-
"h-fit items-center gap-x-2 bg-neu-100 !p-2 text-neu-700 transition-opacity hover:bg-neu-200/80 hover:text-neu-900 disabled:opacity-0",
111+
"h-fit items-center gap-x-2 bg-neu-100 !p-2 text-neu-700 transition-opacity hover:bg-neu-200/80 hover:text-neu-900",
108112
className,
109113
)}
110114
>

src/app/conf/2025/schedule/_components/schedule-list.tsx

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
ResetFiltersButton,
1818
} from "./filters"
1919
import { formatBlockTime } from "./format-block-time"
20+
import { useCurrentTimeMarker } from "./use-current-time-marker"
21+
import { Button } from "@/app/conf/_design-system/button"
2022

2123
export interface FiltersConfig
2224
extends Partial<
@@ -138,21 +140,32 @@ export function ScheduleList({
138140
const firstDayIsDayZero = Object.keys(firstDay).length < 3
139141
const startIndex = firstDayIsDayZero ? 0 : 1
140142

143+
const { getTimeMarker } = useCurrentTimeMarker()
144+
141145
return (
142146
<>
143147
<div className="flex justify-between gap-1 max-lg:flex-col">
144148
<BookmarkOnSched year={year} />
145-
<ResetFiltersButton
146-
filters={filtersState}
147-
onReset={() =>
148-
setFiltersState(
149-
FilterStates.initial(
150-
Object.keys(filterFields) as (keyof ScheduleSession)[],
151-
),
152-
)
153-
}
154-
className="max-lg:mb-4 max-lg:w-fit max-lg:self-end"
155-
/>
149+
<div className="flex gap-2">
150+
<Button
151+
href="#current-time-marker"
152+
variant="tertiary"
153+
className="hidden h-fit items-center gap-x-2 bg-neu-100 !p-2 text-neu-700 transition-opacity hover:bg-neu-200/80 hover:text-neu-900 disabled:opacity-0 [body:has(#current-time-marker)_&]:flex"
154+
>
155+
Scroll to current block
156+
</Button>
157+
<ResetFiltersButton
158+
filters={filtersState}
159+
onReset={() =>
160+
setFiltersState(
161+
FilterStates.initial(
162+
Object.keys(filterFields) as (keyof ScheduleSession)[],
163+
),
164+
)
165+
}
166+
className="max-lg:mb-4 max-lg:w-fit max-lg:self-end"
167+
/>
168+
</div>
156169
</div>
157170
{showFilter && (
158171
<Filters
@@ -229,6 +242,16 @@ export function ScheduleList({
229242
blockEnd.getTime(),
230243
)
231244

245+
let timeMarker = getTimeMarker(sessionDate, blockEnd)
246+
// if end times differ and blockEnd is far from start, we treat this as a long event, like "solutions showcase"
247+
if (
248+
endTimesDiffer &&
249+
blockEnd.getTime() - new Date(sessionDate).getTime() >
250+
1000 * 60 * 60 * 2
251+
) {
252+
timeMarker = null
253+
}
254+
232255
return (
233256
<div
234257
key={`concurrent sessions on ${sessionDate}`}
@@ -256,6 +279,18 @@ export function ScheduleList({
256279
))}
257280
</div>
258281
</div>
282+
{timeMarker && (
283+
<div
284+
id="current-time-marker"
285+
className="typography-body-xs pointer-events-none absolute -right-1 z-10 -translate-y-full font-mono tabular-nums text-pri-base before:absolute before:inset-x-0 before:bottom-0 before:border-b before:border-pri-base before:opacity-80 after:absolute after:bottom-0 after:left-[-100vw] after:w-screen after:border-t after:border-pri-base after:opacity-20 dark:text-pri-light dark:before:border-pri-light dark:after:border-pri-light max-xl:bg-neu-0 xl:translate-x-full"
286+
style={{
287+
top: `${timeMarker.positionPercentage}%`,
288+
}}
289+
>
290+
<span className="max-2xl:hidden">now: </span>
291+
{timeMarker.currentTime}
292+
</div>
293+
)}
259294
{hasDashedBorder && (
260295
<svg
261296
className="absolute -bottom-px left-0 h-px w-full text-neu-50"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useEffect, useState } from "react"
2+
import { format } from "date-fns"
3+
4+
const CONFERENCE_START = new Date("2025-09-08T00:00:00")
5+
const CONFERENCE_END = new Date("2025-09-10T23:59:59")
6+
7+
export function useCurrentTimeMarker() {
8+
const [now, setNow] = useState<Date>(new Date())
9+
10+
useEffect(() => {
11+
const id = setInterval(() => setNow(new Date()), 60 * 1000)
12+
return () => clearInterval(id)
13+
}, [])
14+
15+
const showNowMarkers =
16+
now.getTime() >= CONFERENCE_START.getTime() &&
17+
now.getTime() <= CONFERENCE_END.getTime()
18+
19+
const getTimeMarker = (sessionDate: string, blockEnd: Date) => {
20+
const blockStart = new Date(sessionDate)
21+
const isCurrentBlock =
22+
showNowMarkers &&
23+
now.getTime() >= blockStart.getTime() &&
24+
now.getTime() < blockEnd.getTime()
25+
26+
if (!isCurrentBlock) {
27+
return null
28+
}
29+
30+
const blockDuration = blockEnd.getTime() - blockStart.getTime()
31+
const timePassed = now.getTime() - blockStart.getTime()
32+
const positionPercentage = Math.min(
33+
100,
34+
Math.max(0, (timePassed / blockDuration) * 100),
35+
)
36+
const currentTimeFormatted = format(now, "HH:mm")
37+
38+
return {
39+
positionPercentage,
40+
currentTime: currentTimeFormatted,
41+
}
42+
}
43+
44+
return { getTimeMarker }
45+
}

src/app/conf/_design-system/anchor.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@ export const Anchor = forwardRef(function Anchor(
2626
return isInternal(props) ? (
2727
<NextLink {...props} ref={ref} />
2828
) : (
29-
<a ref={ref} rel="noopener noreferrer" target="_blank" {...props} />
29+
<a
30+
ref={ref}
31+
{...(!props.href.startsWith("#")
32+
? {
33+
rel: "noopener noreferrer",
34+
target: "_blank",
35+
}
36+
: {})}
37+
{...props}
38+
/>
3039
)
3140
}) as (props: AnchorProps) => ReactElement
3241

0 commit comments

Comments
 (0)