UNPKG

@medalsocial/meda

Version:

Shared Meda UI shell and runtime package.

40 lines (39 loc) 3.8 kB
'use client'; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { useMemo } from 'react'; import { cn } from '../lib/utils.js'; import { formatDayOfWeek, formatHour, getDateKey, getHoursOfDay, getWeekDays, isToday, } from './internal/date.js'; import { TimeSlot } from './internal/time-slot.js'; import { DEFAULT_CALENDAR_LABELS } from './types.js'; export function WeekView({ date, events, onEventClick, onSlotClick, onDateClick, weekStartsOn = 0, locale = 'en-US', labels: labelOverrides, className, renderEvent, }) { const labels = { ...DEFAULT_CALENDAR_LABELS, ...labelOverrides }; const weekDays = useMemo(() => getWeekDays(date, weekStartsOn), [date, weekStartsOn]); const hours = useMemo(() => getHoursOfDay(), []); const eventsByDateHour = useMemo(() => { const map = new Map(); for (const event of events) { if (event.allDay) continue; const dateKey = getDateKey(event.start); const hour = event.start.getHours(); const key = `${dateKey}-${hour}`; const existing = map.get(key) ?? []; existing.push(event); map.set(key, existing); } for (const [key, slot] of map.entries()) { slot.sort((a, b) => a.start.getTime() - b.start.getTime()); map.set(key, slot); } return map; }, [events]); return (_jsxs("section", { "data-slot": "calendar-week-view", "aria-label": labels.weekView, className: cn('@container/calendar overflow-hidden rounded-lg border border-border bg-background', className), children: [_jsxs("div", { className: "sticky top-0 z-10 grid grid-cols-[64px_repeat(7,minmax(0,1fr))] border-border border-b bg-muted/50", children: [_jsx("div", { className: "border-border border-r px-2 py-3" }), weekDays.map((d) => { const isTodayDate = isToday(d); const isInteractive = typeof onDateClick === 'function'; const dayLabel = formatDayOfWeek(d, locale, 'short'); const node = (_jsxs(_Fragment, { children: [_jsx("div", { className: "font-medium text-muted-foreground text-[11px]", children: dayLabel }), _jsx("div", { className: cn('mt-0.5 font-semibold text-base', isTodayDate ? 'text-primary' : 'text-foreground'), children: d.getDate() })] })); return isInteractive ? (_jsx("button", { type: "button", onClick: () => onDateClick?.(d), className: cn('min-h-[44px] border-border border-r px-2 py-3 text-center transition-colors last:border-r-0', 'hover:bg-accent/50', isTodayDate && 'bg-primary/5'), children: node }, `header-${getDateKey(d)}`)) : (_jsx("div", { className: cn('min-h-[44px] border-border border-r px-2 py-3 text-center last:border-r-0', isTodayDate && 'bg-primary/5'), children: node }, `header-${getDateKey(d)}`)); })] }), _jsx("div", { // biome-ignore lint/a11y/noNoninteractiveTabindex: scrollable region needs keyboard focus per WCAG (axe scrollable-region-focusable) tabIndex: 0, className: "max-h-[36rem] overflow-y-auto focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", children: hours.map((hour) => (_jsxs("div", { className: "grid grid-cols-[64px_repeat(7,minmax(0,1fr))]", children: [_jsx("div", { className: "border-border border-r border-b px-2 py-2 text-right text-muted-foreground text-[11px]", children: formatHour(hour, locale) }), weekDays.map((d) => (_jsx(TimeSlot, { date: d, hour: hour, events: eventsByDateHour.get(`${getDateKey(d)}-${hour}`) ?? [], onSlotClick: onSlotClick, onEventClick: onEventClick, renderEvent: renderEvent, variant: "week" }, `slot-${getDateKey(d)}-${hour}`)))] }, `row-${hour}`))) })] })); }