Skip to content

Commit

Permalink
desktop timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
lassejaco committed Nov 8, 2024
1 parent 1459149 commit ad1a17f
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ export const SessionCard = ({

<p
className={cn(
'text-xs font-medium text-gray-800 line-clamp-2 group-hover:line-clamp-none sticky left-[108px] lg:left-[8px] leading-[12px]',
'text-xs font-medium text-gray-800 line-clamp-2 group-hover:line-clamp-none sticky left-[8px] lg:left-[8px] leading-[12px]',
getTrackColor(track)
)}
>
Expand Down
240 changes: 126 additions & 114 deletions devcon-app/src/components/domain/app/dc7/sessions/timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { useMemo, useRef } from 'react'
import { Session as SessionType } from 'types/Session'
import { Event } from 'types/Event'
import moment from 'moment'
Expand Down Expand Up @@ -48,26 +48,36 @@ const DayGrid = ({
timeSlots: any[]
day: string
}) => {
const scrollSyncRef = useRef<HTMLDivElement>(null)
return (
<div className="flex flex-nowrap shrink-0 relative left-[100px] lg:left-0">
<div
<div className="flex shrink-0 w-full relative left-[100px] lg:left-0">
{/* <div
data-type="day"
className="absolute left-0 top-0 w-full h-[40px] z-[10] flex items-center translate-x-[-100px]"
>
<div className="sticky left-0 !bg-[#F5F7FA] h-full inline-flex items-center text-sm font-semibold w-[100px] justify-center">
{day}
</div>
</div>
<div className="flex flex-col relative">
</div> */}
<div className="flex flex-col">
<div
className="grid shrink-0 sticky top-[100px] lg:top-[106px] z-[2] glass"
style={{ gridTemplateColumns: `repeat(${timeSlots.length}, minmax(80px, 1fr))` }}
className="grid shrink-0 sticky top-[100px] lg:top-[106px] z-[6] glass !border-none"
style={{ gridTemplateColumns: `repeat(${timeSlots.length}, minmax(100px, 1fr))` }}
ref={scrollSyncRef}
>
<div
data-type="day"
className="absolute left-0 top-0 w-[100px] h-[40px] flex items-center translate-x-[-100px] border-bottom border-top z-[1]"
>
<div className="sticky left-0 !bg-[#F5F7FA] h-full inline-flex items-center text-sm font-semibold w-[100px] justify-center">
{day}
</div>
</div>
{timeSlots.map((time, index) => (
<div
key={index}
data-id={time.format('h:mm')}
className="#F5F7FA py-2 text-sm whitespace-nowrap flex items-center h-[40px] border border-gray-200 border-t-solid !bg-[#F5F7FA]"
className="py-2 text-sm whitespace-nowrap flex items-center w-[100px] h-[40px] border-top !bg-[#F5F7FA] border-bottom"
>
<div
style={{ transform: index > 0 ? 'translateX(-50%)' : 'translateX(0)' }}
Expand All @@ -79,71 +89,73 @@ const DayGrid = ({
</div>
))}
</div>
<SwipeToScroll noScrollReset>
<div className='flex'>
<div
className="grid relative shrink-0"
style={{ gridTemplateColumns: `repeat(${timeSlots.length}, minmax(80px, 1fr))` }}
>
{rooms.map((room, roomIndex) => {
const sessions = sessionsByRoom[room]

const sessionByTimeslotStart: Record<
string,
{ session: SessionType; columns: number; columnIndent: number }
> = {}

if (sessions) {
sessions.forEach((session: SessionType) => {
const start = moment.utc(session.slot_start).add(7, 'hours')
const end = moment.utc(session.slot_end).add(7, 'hours')
const durationInMinutes = end.diff(start, 'minutes')
// const columns = Math.ceil(durationInMinutes / 10) // Since timeslots are 10 minutes each
const columns = durationInMinutes / 10

const excessMinutes = start.minute() % 10

const nearestTen = start.clone().subtract(excessMinutes, 'minutes')

const startFormatted = nearestTen.format('h:mm A')

sessionByTimeslotStart[startFormatted] = {
session,
columns,
columnIndent: excessMinutes === 5 ? 0.5 : 0,
<SwipeToScroll noScrollReset syncElement={scrollSyncRef}>
<div className="flex">
<div
className="grid relative shrink-0"
style={{ gridTemplateColumns: `repeat(${timeSlots.length}, minmax(100px, 1fr))` }}
>
{rooms.map((room, roomIndex) => {
const sessions = sessionsByRoom[room]

const sessionByTimeslotStart: Record<
string,
{ session: SessionType; columns: number; columnIndent: number }
> = {}

if (sessions) {
sessions.forEach((session: SessionType) => {
const start = moment.utc(session.slot_start).add(7, 'hours')
const end = moment.utc(session.slot_end).add(7, 'hours')
const durationInMinutes = end.diff(start, 'minutes')
// const columns = Math.ceil(durationInMinutes / 10) // Since timeslots are 10 minutes each
const columns = durationInMinutes / 10

const excessMinutes = start.minute() % 10

const nearestTen = start.clone().subtract(excessMinutes, 'minutes')

const startFormatted = nearestTen.format('h:mm A')

sessionByTimeslotStart[startFormatted] = {
session,
columns,
columnIndent: excessMinutes === 5 ? 0.5 : 0,
}
})
}
})
}

return (
<React.Fragment key={roomIndex}>
{timeSlots.map((timeslot, slotIndex) => {
const match = sessionByTimeslotStart[timeslot.format('h:mm A')]

if (!match)
// || room !== 'Main Stage')
return <div key={slotIndex} className="bg-white border border-gray-100 border-solid h-[40px]"></div>

return (
<div
key={slotIndex}
className={`bg-white border border-gray-100 border-solid h-[40px] relative max-w-[100px]`}
// style={{ gridColumn: `span ${match.columns}` }}
>
<div
className={``}
style={{ width: `${match.columns * 100}px`, marginLeft: `${match.columnIndent * 100}px` }}
>
<SessionCard session={match.session} tiny className="z-[1] hover:z-[2]" />
</div>
</div>
)
})}
</React.Fragment>
)
})}
</div>
</div>

return (
<React.Fragment key={roomIndex}>
{timeSlots.map((timeslot, slotIndex) => {
const match = sessionByTimeslotStart[timeslot.format('h:mm A')]

if (!match)
// || room !== 'Main Stage')
return (
<div key={slotIndex} className="bg-white border border-gray-100 border-solid h-[40px]"></div>
)

return (
<div
key={slotIndex}
className={`bg-white border border-gray-100 border-solid h-[40px] relative max-w-[100px]`}
// style={{ gridColumn: `span ${match.columns}` }}
>
<div
className={``}
style={{ width: `${match.columns * 100}px`, marginLeft: `${match.columnIndent * 100}px` }}
>
<SessionCard session={match.session} tiny className="z-[1] hover:z-[2]" />
</div>
</div>
)
})}
</React.Fragment>
)
})}
</div>
</div>
</SwipeToScroll>
</div>
</div>
Expand Down Expand Up @@ -192,48 +204,48 @@ const Timeline = ({ sessions, event, days }: { sessions: SessionType[]; event: E
}) as string[]

return (
<div className="flex flex-nowrap" style={{ contain: 'paint' }}>
<RoomGrid rooms={rooms} />
{/* <SwipeToScroll noScrollReset> */}
<div className="flex flex-nowrap gap-[120px]">
{days.map(day => {
const sessionsForDay = sessions.filter(session => moment(session.slot_start).format('MMM DD') === day)

if (!sessionsForDay.length) return null

const sessionsByRoom: any = {}

const firstTimeSlot = moment.utc(sessionsForDay[0].slot_start).add(7, 'hours')
const lastTimeSlot = moment.utc(sessionsForDay[sessionsForDay.length - 1].slot_end).add(7, 'hours')

sessionsForDay.forEach((session: any) => {
if (sessionsByRoom[session.slot_room?.name]) {
sessionsByRoom[session.slot_room?.name].push(session)
} else {
sessionsByRoom[session.slot_room?.name] = [session]
}
})

const generateTimeSlots = () => {
const slots = []
const startTime = moment.utc(firstTimeSlot)
startTime.subtract(startTime.minute() % 10, 'minutes')

const endTime = moment.utc(lastTimeSlot).add(10, 'minutes') // Add buffer after last session + 7 for bangkok

while (startTime <= endTime) {
slots.push(startTime.clone())
startTime.add(10, 'minutes')
}
return slots
}

const timeSlots = generateTimeSlots()

return <DayGrid day={day} rooms={rooms} key={day} sessionsByRoom={sessionsByRoom} timeSlots={timeSlots} />
})}
</div>
{/* </SwipeToScroll> */}
<div className="flex flex-col gap-[64px]" style={{ contain: 'paint' }}>
{days.map(day => {
const sessionsForDay = sessions.filter(session => moment(session.slot_start).format('MMM DD') === day)

if (!sessionsForDay.length) return null

const sessionsByRoom: any = {}

const firstTimeSlot = moment.utc(sessionsForDay[0].slot_start).add(7, 'hours')
const lastTimeSlot = moment.utc(sessionsForDay[sessionsForDay.length - 1].slot_end).add(7, 'hours')

sessionsForDay.forEach((session: any) => {
if (sessionsByRoom[session.slot_room?.name]) {
sessionsByRoom[session.slot_room?.name].push(session)
} else {
sessionsByRoom[session.slot_room?.name] = [session]
}
})

const generateTimeSlots = () => {
const slots = []
const startTime = moment.utc(firstTimeSlot)
startTime.subtract(startTime.minute() % 10, 'minutes')

const endTime = moment.utc(lastTimeSlot).add(10, 'minutes') // Add buffer after last session + 7 for bangkok

while (startTime <= endTime) {
slots.push(startTime.clone())
startTime.add(10, 'minutes')
}
return slots
}

const timeSlots = generateTimeSlots()

return (
<div key={day} className="flex relative">
<RoomGrid rooms={rooms} />
<DayGrid day={day} rooms={rooms} sessionsByRoom={sessionsByRoom} timeSlots={timeSlots} />
</div>
)
})}
</div>
)
}
Expand Down
45 changes: 42 additions & 3 deletions lib/components/event-schedule/swipe-to-scroll/SwipeToScroll.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { forwardRef, useImperativeHandle } from "react";
import React, { forwardRef, useEffect, useImperativeHandle } from "react";
import css from "./sts.module.scss";
import { useDrag } from "react-use-gesture";
import useDimensions from "react-cool-dimensions";
Expand All @@ -7,6 +7,7 @@ type SwipeToScrollProps = {
noBounds?: boolean;
noScrollReset?: boolean;
focusRef?: React.RefObject<HTMLElement>;
syncElement?: React.RefObject<HTMLElement>;
children: React.ReactChild | React.ReactChild[];
scrollIndicatorDirections?: {
["left"]?: boolean;
Expand Down Expand Up @@ -87,11 +88,28 @@ const SwipeToScroll = forwardRef((props: SwipeToScrollProps, ref) => {
]
);

// useEffect(() => {
// if (props.syncElement?.current) {
// if (isNativeScroll) {
// props.syncElement.current.style.overflowX = 'auto';
// props.syncElement.current.style.transform = 'translateX(0px) !important';
// } else {
// props.syncElement.current.style.overflowX = '';
// props.syncElement.current.style.transform = '';
// }
// }
// }, [isNativeScroll]);

const reset = React.useCallback(() => {
if (el.current) {
const scrollContainer = el.current;
lastX.current = 0;
scrollContainer.style.transform = `translateX(0px)`;

if (props.syncElement?.current) {
props.syncElement.current.style.transform = `translateX(0px)`;
}

syncScrollIndicators(scrollContainer);
}
}, [syncScrollIndicators]);
Expand Down Expand Up @@ -154,9 +172,20 @@ const SwipeToScroll = forwardRef((props: SwipeToScrollProps, ref) => {
left: clampedScrollLeft, // elementRect.left - 16,
behavior: "smooth",
});

if (props.syncElement?.current) {
props.syncElement.current.scrollTo({
left: clampedScrollLeft,
behavior: "smooth",
});
}
} else {
// Use translateX for devices with a cursor
scrollContainer.style.transform = `translateX(-${clampedScrollLeft}px)`;

if (props.syncElement?.current) {
props.syncElement.current.style.transform = `translateX(-${clampedScrollLeft}px)`;
}
}

lastX.current = clampedScrollLeft;
Expand All @@ -182,6 +211,10 @@ const SwipeToScroll = forwardRef((props: SwipeToScrollProps, ref) => {
lastX.current = Math.min(Math.max(0, lastX.current - delta[0]), maxScroll);
scrollContainer.style.transform = `translateX(-${lastX.current}px)`;

if (props.syncElement?.current) {
props.syncElement.current.style.transform = `translateX(-${lastX.current}px)`;
}

if (down) {
containerEl.current!.style.cursor = "grabbing";
} else {
Expand All @@ -194,9 +227,9 @@ const SwipeToScroll = forwardRef((props: SwipeToScrollProps, ref) => {
if (scrollIndicatorClass) className += ` ${scrollIndicatorClass}`;
if (props.noBounds) className += ` ${css["no-bounds"]}`;

let scrollContainerClass = css["swipe-to-scroll"];
let scrollContainerClass = "h-full select-none";

if (isNativeScroll) scrollContainerClass += ` ${css["is-native-scroll"]}`;
if (isNativeScroll) scrollContainerClass += " overflow-x-auto !translate-x-0";

return (
<div
Expand All @@ -211,6 +244,12 @@ const SwipeToScroll = forwardRef((props: SwipeToScrollProps, ref) => {
observe(element);
}}
className={scrollContainerClass}
onScroll={(e) => {
if (props.syncElement?.current) {
// @ts-ignore
props.syncElement.current.scrollLeft = e.target.scrollLeft;
}
}}
// This prevents selection (text, image) while dragging
onMouseDown={(e) => {
e.preventDefault();
Expand Down

0 comments on commit ad1a17f

Please sign in to comment.