Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/app/conf/2025/schedule/_components/filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,18 @@ export function ResetFiltersButton({
}) {
const hasFilters = Object.values(filters).flat().length > 0

if (!hasFilters) {
return null
}

return (
<Button
variant="tertiary"
title="Reset filters"
onClick={onReset}
disabled={!hasFilters}
className={clsx(
"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",
"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",
className,
)}
>
Expand Down
57 changes: 46 additions & 11 deletions src/app/conf/2025/schedule/_components/schedule-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
ResetFiltersButton,
} from "./filters"
import { formatBlockTime } from "./format-block-time"
import { useCurrentTimeMarker } from "./use-current-time-marker"
import { Button } from "@/app/conf/_design-system/button"

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

const { getTimeMarker } = useCurrentTimeMarker()

return (
<>
<div className="flex justify-between gap-1 max-lg:flex-col">
<BookmarkOnSched year={year} />
<ResetFiltersButton
filters={filtersState}
onReset={() =>
setFiltersState(
FilterStates.initial(
Object.keys(filterFields) as (keyof ScheduleSession)[],
),
)
}
className="max-lg:mb-4 max-lg:w-fit max-lg:self-end"
/>
<div className="flex gap-2">
<Button
href="#current-time-marker"
variant="tertiary"
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"
>
Scroll to current block
</Button>
<ResetFiltersButton
filters={filtersState}
onReset={() =>
setFiltersState(
FilterStates.initial(
Object.keys(filterFields) as (keyof ScheduleSession)[],
),
)
}
className="max-lg:mb-4 max-lg:w-fit max-lg:self-end"
/>
</div>
</div>
{showFilter && (
<Filters
Expand Down Expand Up @@ -229,6 +242,16 @@ export function ScheduleList({
blockEnd.getTime(),
)

let timeMarker = getTimeMarker(sessionDate, blockEnd)
// if end times differ and blockEnd is far from start, we treat this as a long event, like "solutions showcase"
if (
endTimesDiffer &&
blockEnd.getTime() - new Date(sessionDate).getTime() >
1000 * 60 * 60 * 2
) {
timeMarker = null
}

return (
<div
key={`concurrent sessions on ${sessionDate}`}
Expand Down Expand Up @@ -256,6 +279,18 @@ export function ScheduleList({
))}
</div>
</div>
{timeMarker && (
<div
id="current-time-marker"
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"
style={{
top: `${timeMarker.positionPercentage}%`,
}}
>
<span className="max-2xl:hidden">now: </span>
{timeMarker.currentTime}
</div>
)}
{hasDashedBorder && (
<svg
className="absolute -bottom-px left-0 h-px w-full text-neu-50"
Expand Down
45 changes: 45 additions & 0 deletions src/app/conf/2025/schedule/_components/use-current-time-marker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useEffect, useState } from "react"
import { format } from "date-fns"

const CONFERENCE_START = new Date("2025-09-08T00:00:00")
const CONFERENCE_END = new Date("2025-09-10T23:59:59")

export function useCurrentTimeMarker() {
const [now, setNow] = useState<Date>(new Date())

useEffect(() => {
const id = setInterval(() => setNow(new Date()), 60 * 1000)
return () => clearInterval(id)
}, [])

const showNowMarkers =
now.getTime() >= CONFERENCE_START.getTime() &&
now.getTime() <= CONFERENCE_END.getTime()

const getTimeMarker = (sessionDate: string, blockEnd: Date) => {
const blockStart = new Date(sessionDate)
const isCurrentBlock =
showNowMarkers &&
now.getTime() >= blockStart.getTime() &&
now.getTime() < blockEnd.getTime()

if (!isCurrentBlock) {
return null
}

const blockDuration = blockEnd.getTime() - blockStart.getTime()
const timePassed = now.getTime() - blockStart.getTime()
const positionPercentage = Math.min(
100,
Math.max(0, (timePassed / blockDuration) * 100),
)
const currentTimeFormatted = format(now, "HH:mm")

return {
positionPercentage,
currentTime: currentTimeFormatted,
}
}

return { getTimeMarker }
}
11 changes: 10 additions & 1 deletion src/app/conf/_design-system/anchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ export const Anchor = forwardRef(function Anchor(
return isInternal(props) ? (
<NextLink {...props} ref={ref} />
) : (
<a ref={ref} rel="noopener noreferrer" target="_blank" {...props} />
<a
ref={ref}
{...(!props.href.startsWith("#")
? {
rel: "noopener noreferrer",
target: "_blank",
}
: {})}
{...props}
/>
)
}) as (props: AnchorProps) => ReactElement

Expand Down