|
| 1 | +'use client' |
| 2 | + |
| 3 | +import { useEffect, useRef } from 'react' |
1 | 4 | import { useCalendarContext } from '../../calendar-context' |
2 | | -import { startOfWeek, addDays } from 'date-fns' |
| 5 | +import { startOfWeek, addDays, isSameDay } from 'date-fns' |
3 | 6 | import CalendarBodyMarginDayMargin from '../day/calendar-body-margin-day-margin' |
4 | 7 | import CalendarBodyDayContent from '../day/calendar-body-day-content' |
| 8 | +import CalendarBodyHeader from '../calendar-body-header' |
| 9 | +import { |
| 10 | + CurrentTimeLine, |
| 11 | + getCurrentTimeOffsetTop, |
| 12 | +} from '../current-time-line' |
| 13 | +import { |
| 14 | + GRID_HEADER_HEIGHT, |
| 15 | + PIXELS_PER_HOUR, |
| 16 | +} from '../day/calendar-body-margin-day-margin' |
| 17 | + |
| 18 | +const TOTAL_GRID_HEIGHT = 24 * PIXELS_PER_HOUR // Header is now outside the scrollable area |
| 19 | + |
5 | 20 | export default function CalendarBodyWeek() { |
6 | 21 | const { date } = useCalendarContext() |
| 22 | + const scrollRef = useRef<HTMLDivElement>(null) |
7 | 23 |
|
8 | 24 | const weekStart = startOfWeek(date, { weekStartsOn: 1 }) |
9 | 25 | const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i)) |
| 26 | + const today = new Date() |
| 27 | + const showTimeline = weekDays.some((day) => isSameDay(day, today)) |
| 28 | + |
| 29 | + useEffect(() => { |
| 30 | + if (!scrollRef.current) return |
| 31 | + const el = scrollRef.current |
| 32 | + const scrollToCenter = () => { |
| 33 | + if (!el.clientHeight || !el.scrollHeight) return |
| 34 | + const top = getCurrentTimeOffsetTop() |
| 35 | + const center = el.clientHeight / 2 |
| 36 | + const scrollTop = Math.max( |
| 37 | + 0, |
| 38 | + Math.min(top - center, el.scrollHeight - el.clientHeight) |
| 39 | + ) |
| 40 | + el.scrollTop = scrollTop |
| 41 | + } |
| 42 | + |
| 43 | + // Try multiple times to ensure DOM is ready |
| 44 | + const timeouts: NodeJS.Timeout[] = [] |
| 45 | + timeouts.push(setTimeout(scrollToCenter, 0)) |
| 46 | + timeouts.push(setTimeout(scrollToCenter, 50)) |
| 47 | + timeouts.push(setTimeout(scrollToCenter, 150)) |
| 48 | + |
| 49 | + return () => timeouts.forEach(clearTimeout) |
| 50 | + }, [showTimeline, date]) |
10 | 51 |
|
11 | 52 | return ( |
12 | | - <div className="flex divide-x flex-grow overflow-hidden"> |
13 | | - <div className="flex flex-col flex-grow divide-y overflow-hidden"> |
14 | | - <div className="flex flex-col flex-1 overflow-y-auto"> |
15 | | - <div className="relative flex flex-1 divide-x flex-col md:flex-row"> |
16 | | - <CalendarBodyMarginDayMargin className="hidden md:block" /> |
17 | | - {weekDays.map((day) => ( |
18 | | - <div |
19 | | - key={day.toISOString()} |
20 | | - className="flex flex-1 divide-x md:divide-x-0" |
21 | | - > |
22 | | - <CalendarBodyMarginDayMargin className="block md:hidden" /> |
23 | | - <CalendarBodyDayContent date={day} /> |
24 | | - </div> |
25 | | - ))} |
| 53 | + <div className="flex flex-col flex-1 min-h-0 divide-x overflow-hidden"> |
| 54 | + {/* Sticky: day headers row */} |
| 55 | + <div className="flex shrink-0 border-b bg-background sticky top-0 z-30"> |
| 56 | + <div className="w-12 shrink-0 hidden md:block bg-background" aria-hidden /> |
| 57 | + {weekDays.map((day) => ( |
| 58 | + <div key={day.toISOString()} className="flex-1 min-w-0"> |
| 59 | + <CalendarBodyHeader date={day} /> |
26 | 60 | </div> |
| 61 | + ))} |
| 62 | + </div> |
| 63 | + {/* Scrollable: time grid only */} |
| 64 | + <div |
| 65 | + ref={scrollRef} |
| 66 | + className="flex-1 min-h-0 overflow-y-auto" |
| 67 | + > |
| 68 | + <div |
| 69 | + className="relative flex divide-x flex-col md:flex-row" |
| 70 | + style={{ minHeight: TOTAL_GRID_HEIGHT }} |
| 71 | + > |
| 72 | + <CalendarBodyMarginDayMargin className="hidden md:block" /> |
| 73 | + {weekDays.map((day) => ( |
| 74 | + <div |
| 75 | + key={day.toISOString()} |
| 76 | + className="flex flex-1 divide-x md:divide-x-0" |
| 77 | + > |
| 78 | + <CalendarBodyMarginDayMargin className="block md:hidden" /> |
| 79 | + <CalendarBodyDayContent date={day} hideHeader /> |
| 80 | + </div> |
| 81 | + ))} |
| 82 | + {showTimeline && <CurrentTimeLine />} |
27 | 83 | </div> |
28 | 84 | </div> |
29 | 85 | </div> |
|
0 commit comments