refactor: split calendar page components
This commit is contained in:
666
webui/src/features/calendar/CalendarViews.tsx
Normal file
666
webui/src/features/calendar/CalendarViews.tsx
Normal file
@@ -0,0 +1,666 @@
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
type DragEvent as ReactDragEvent,
|
||||
} from "react";
|
||||
|
||||
import { i18nMessage } from "@govoplan/core-webui";
|
||||
|
||||
import type { CalendarEvent } from "../../api/calendar";
|
||||
import {
|
||||
HOUR_ROW_HEIGHT,
|
||||
INITIAL_SCROLL_HOUR,
|
||||
calendarEventColorStyle,
|
||||
chunk,
|
||||
continuousVirtualWindow,
|
||||
dayKey,
|
||||
dayMonthLabel,
|
||||
dropSlotMinuteOfDay,
|
||||
eventTimeLabel,
|
||||
isWeekend,
|
||||
layoutTimedEventsForDay,
|
||||
minuteOfDayLabel,
|
||||
monthYearLabel,
|
||||
sameDay,
|
||||
sameMonth,
|
||||
timedEventStyle,
|
||||
timedOverflowStyle,
|
||||
timeGridStyle,
|
||||
weekdayLong,
|
||||
type CalendarDropTarget,
|
||||
type CalendarMode,
|
||||
type CalendarResizeEdge,
|
||||
type CalendarTimeDropTarget,
|
||||
type CalendarViewPreferences,
|
||||
type ContinuousViewport,
|
||||
} from "./calendarViewModel";
|
||||
|
||||
type CalendarViewCallbacks = {
|
||||
onEventSelect: (event: CalendarEvent) => void;
|
||||
onEventHover: (eventId: string) => void;
|
||||
onEventDragStart: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
event: CalendarEvent,
|
||||
) => void;
|
||||
onEventDragEnd: () => void;
|
||||
onEventDragOverDay: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
day: Date,
|
||||
) => void;
|
||||
onDropTargetLeave: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
) => void;
|
||||
onEventDropOnDay: (
|
||||
dropEvent: ReactDragEvent<HTMLElement>,
|
||||
day: Date,
|
||||
) => void;
|
||||
};
|
||||
|
||||
type CalendarWeekRowsProps = CalendarViewCallbacks & {
|
||||
days: Date[];
|
||||
eventsByDay: Map<string, CalendarEvent[]>;
|
||||
calendarColorById: Map<string, string>;
|
||||
focusDate: Date;
|
||||
variant: "month" | "continuous";
|
||||
preferences: CalendarViewPreferences;
|
||||
canWrite: boolean;
|
||||
draggingEventId: string;
|
||||
hoveredEventId: string;
|
||||
dropTarget: CalendarDropTarget;
|
||||
viewport?: ContinuousViewport;
|
||||
};
|
||||
|
||||
export function CalendarWeekRows({
|
||||
days,
|
||||
eventsByDay,
|
||||
calendarColorById,
|
||||
focusDate,
|
||||
variant,
|
||||
preferences,
|
||||
canWrite,
|
||||
draggingEventId,
|
||||
hoveredEventId,
|
||||
dropTarget,
|
||||
viewport,
|
||||
onEventSelect,
|
||||
onEventHover,
|
||||
onEventDragStart,
|
||||
onEventDragEnd,
|
||||
onEventDragOverDay,
|
||||
onDropTargetLeave,
|
||||
onEventDropOnDay,
|
||||
}: CalendarWeekRowsProps) {
|
||||
const weeks = chunk(days, 7);
|
||||
const virtualWindow =
|
||||
variant === "continuous" && preferences.continuousVirtualization
|
||||
? continuousVirtualWindow(
|
||||
weeks.length,
|
||||
viewport ?? { scrollTop: 0, height: 0 },
|
||||
preferences.continuousOverscanWeeks,
|
||||
)
|
||||
: {
|
||||
start: 0,
|
||||
end: weeks.length,
|
||||
topSpacerHeight: 0,
|
||||
bottomSpacerHeight: 0,
|
||||
};
|
||||
const visibleWeeks = weeks.slice(
|
||||
virtualWindow.start,
|
||||
virtualWindow.end,
|
||||
);
|
||||
const eventLimit = variant === "month" ? 5 : 3;
|
||||
|
||||
return (
|
||||
<div className={`calendar-week-rows is-${variant}`}>
|
||||
<div className="calendar-weekday-header">
|
||||
{[
|
||||
"i18n:govoplan-calendar.mon.24b2a099",
|
||||
"i18n:govoplan-calendar.tue.529541bb",
|
||||
"i18n:govoplan-calendar.wed.23408b19",
|
||||
"i18n:govoplan-calendar.thu.3593ccd9",
|
||||
"i18n:govoplan-calendar.fri.bbd6e32e",
|
||||
"i18n:govoplan-calendar.sat.6b782d41",
|
||||
"i18n:govoplan-calendar.sun.48c98cab",
|
||||
].map((label) => (
|
||||
<span key={label}>{label}</span>
|
||||
))}
|
||||
</div>
|
||||
{virtualWindow.topSpacerHeight > 0 && (
|
||||
<div
|
||||
className="calendar-week-spacer"
|
||||
style={{ height: virtualWindow.topSpacerHeight }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{visibleWeeks.map((week) => (
|
||||
<div className="calendar-week-row" key={dayKey(week[0])}>
|
||||
{week.map((day) => {
|
||||
const key = dayKey(day);
|
||||
const dayEvents = eventsByDay.get(key) ?? [];
|
||||
const outsideFocusMonth =
|
||||
variant === "month" && !sameMonth(day, focusDate);
|
||||
return (
|
||||
<section
|
||||
key={key}
|
||||
className={[
|
||||
"calendar-day-cell",
|
||||
outsideFocusMonth ? "is-muted" : "",
|
||||
sameDay(day, new Date()) ? "is-today" : "",
|
||||
dropTarget?.kind === "day" &&
|
||||
dropTarget.key === key
|
||||
? "is-drop-target"
|
||||
: "",
|
||||
variant === "continuous" &&
|
||||
preferences.alternateContinuousMonths
|
||||
? day.getMonth() % 2 === 0
|
||||
? "is-even-month"
|
||||
: "is-odd-month"
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
onDragOver={
|
||||
canWrite
|
||||
? (event) => onEventDragOverDay(event, day)
|
||||
: undefined
|
||||
}
|
||||
onDragLeave={
|
||||
canWrite ? onDropTargetLeave : undefined
|
||||
}
|
||||
onDrop={
|
||||
canWrite
|
||||
? (event) => onEventDropOnDay(event, day)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<header>
|
||||
<span className="calendar-day-number">
|
||||
{day.getDate()}
|
||||
</span>
|
||||
{day.getDate() === 1 && (
|
||||
<small>{monthYearLabel(day)}</small>
|
||||
)}
|
||||
</header>
|
||||
<div className="calendar-event-stack">
|
||||
{dayEvents.slice(0, eventLimit).map((event) => (
|
||||
<CalendarEventChip
|
||||
key={event.id}
|
||||
event={event}
|
||||
color={calendarColorById.get(event.calendar_id)}
|
||||
canDrag={canWrite}
|
||||
dragging={draggingEventId === event.id}
|
||||
linkedHover={hoveredEventId === event.id}
|
||||
onSelect={onEventSelect}
|
||||
onHover={onEventHover}
|
||||
onDragStart={onEventDragStart}
|
||||
onDragEnd={onEventDragEnd}
|
||||
/>
|
||||
))}
|
||||
{dayEvents.length > eventLimit && (
|
||||
<span className="calendar-more-events">
|
||||
+{dayEvents.length - eventLimit}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
{virtualWindow.bottomSpacerHeight > 0 && (
|
||||
<div
|
||||
className="calendar-week-spacer"
|
||||
style={{ height: virtualWindow.bottomSpacerHeight }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type CalendarTimeGridProps = CalendarViewCallbacks & {
|
||||
days: Date[];
|
||||
eventsByDay: Map<string, CalendarEvent[]>;
|
||||
calendarColorById: Map<string, string>;
|
||||
mode: CalendarMode;
|
||||
preferences: CalendarViewPreferences;
|
||||
canWrite: boolean;
|
||||
draggingEventId: string;
|
||||
hoveredEventId: string;
|
||||
dropTarget: CalendarDropTarget;
|
||||
onEventResizeDragStart: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
event: CalendarEvent,
|
||||
edge: CalendarResizeEdge,
|
||||
) => void;
|
||||
onEventDragOverTime: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
day: Date,
|
||||
) => void;
|
||||
onEventDropOnTime: (
|
||||
dropEvent: ReactDragEvent<HTMLElement>,
|
||||
day: Date,
|
||||
minuteOfDay: number,
|
||||
) => void;
|
||||
};
|
||||
|
||||
export function CalendarTimeGrid({
|
||||
days,
|
||||
eventsByDay,
|
||||
calendarColorById,
|
||||
mode,
|
||||
preferences,
|
||||
canWrite,
|
||||
draggingEventId,
|
||||
hoveredEventId,
|
||||
dropTarget,
|
||||
onEventSelect,
|
||||
onEventHover,
|
||||
onEventDragStart,
|
||||
onEventResizeDragStart,
|
||||
onEventDragEnd,
|
||||
onEventDragOverDay,
|
||||
onEventDragOverTime,
|
||||
onDropTargetLeave,
|
||||
onEventDropOnDay,
|
||||
onEventDropOnTime,
|
||||
}: CalendarTimeGridProps) {
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const hours = Array.from({ length: 24 }, (_item, index) => index);
|
||||
const columns = `72px repeat(${days.length}, minmax(132px, 1fr))`;
|
||||
const dimWeekends = preferences.dimWeekends && mode === "week";
|
||||
const gridStyle = timeGridStyle(columns, preferences);
|
||||
const allDayEventsByDay = days.map((day) => ({
|
||||
key: dayKey(day),
|
||||
date: day,
|
||||
events: (eventsByDay.get(dayKey(day)) ?? []).filter(
|
||||
(event) => event.all_day,
|
||||
),
|
||||
}));
|
||||
const hasAllDayEvents = allDayEventsByDay.some(
|
||||
(day) => day.events.length > 0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop =
|
||||
INITIAL_SCROLL_HOUR * HOUR_ROW_HEIGHT;
|
||||
}
|
||||
}, [days.length, days[0]?.getTime()]);
|
||||
|
||||
return (
|
||||
<div className="calendar-time-view">
|
||||
<div
|
||||
className="calendar-time-header"
|
||||
style={{ gridTemplateColumns: columns }}
|
||||
>
|
||||
<div className="calendar-time-corner" />
|
||||
{days.map((day) => (
|
||||
<header
|
||||
key={dayKey(day)}
|
||||
className={[
|
||||
sameDay(day, new Date()) ? "is-today" : "",
|
||||
dimWeekends && isWeekend(day) ? "is-weekend" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
>
|
||||
<strong>{weekdayLong(day)}</strong>
|
||||
<span>{dayMonthLabel(day)}</span>
|
||||
</header>
|
||||
))}
|
||||
</div>
|
||||
{hasAllDayEvents && (
|
||||
<div
|
||||
className="calendar-all-day-strip"
|
||||
style={{ gridTemplateColumns: columns }}
|
||||
>
|
||||
<div className="calendar-all-day-label">
|
||||
i18n:govoplan-calendar.all_day.11457433
|
||||
</div>
|
||||
{allDayEventsByDay.map((day) => (
|
||||
<div
|
||||
key={day.key}
|
||||
className={[
|
||||
"calendar-all-day-cell",
|
||||
dimWeekends && isWeekend(day.date)
|
||||
? "is-weekend"
|
||||
: "",
|
||||
dropTarget?.kind === "day" &&
|
||||
dropTarget.key === day.key
|
||||
? "is-drop-target"
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
onDragOver={
|
||||
canWrite
|
||||
? (event) => onEventDragOverDay(event, day.date)
|
||||
: undefined
|
||||
}
|
||||
onDragLeave={
|
||||
canWrite ? onDropTargetLeave : undefined
|
||||
}
|
||||
onDrop={
|
||||
canWrite
|
||||
? (event) => onEventDropOnDay(event, day.date)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{day.events.map((event) => (
|
||||
<CalendarEventChip
|
||||
key={event.id}
|
||||
event={event}
|
||||
color={calendarColorById.get(event.calendar_id)}
|
||||
compact
|
||||
canDrag={canWrite}
|
||||
dragging={draggingEventId === event.id}
|
||||
linkedHover={hoveredEventId === event.id}
|
||||
onSelect={onEventSelect}
|
||||
onHover={onEventHover}
|
||||
onDragStart={onEventDragStart}
|
||||
onDragEnd={onEventDragEnd}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="calendar-time-scroll" ref={scrollRef}>
|
||||
<div className="calendar-time-grid" style={gridStyle}>
|
||||
<div className="calendar-hour-label-column">
|
||||
{hours.map((hour) => (
|
||||
<div className="calendar-hour-label" key={hour}>
|
||||
{String(hour).padStart(2, "0")}:00
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{days.map((day) => (
|
||||
<TimedDayColumn
|
||||
key={dayKey(day)}
|
||||
day={day}
|
||||
events={eventsByDay.get(dayKey(day)) ?? []}
|
||||
calendarColorById={calendarColorById}
|
||||
dimWeekend={dimWeekends && isWeekend(day)}
|
||||
canWrite={canWrite}
|
||||
draggingEventId={draggingEventId}
|
||||
hoveredEventId={hoveredEventId}
|
||||
dropTarget={
|
||||
dropTarget?.kind === "time" &&
|
||||
dropTarget.key === dayKey(day)
|
||||
? dropTarget
|
||||
: null
|
||||
}
|
||||
onEventSelect={onEventSelect}
|
||||
onEventHover={onEventHover}
|
||||
onEventDragStart={onEventDragStart}
|
||||
onEventResizeDragStart={onEventResizeDragStart}
|
||||
onEventDragEnd={onEventDragEnd}
|
||||
onEventDragOverTime={onEventDragOverTime}
|
||||
onDropTargetLeave={onDropTargetLeave}
|
||||
onEventDropOnTime={onEventDropOnTime}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type TimedDayColumnProps = {
|
||||
day: Date;
|
||||
events: CalendarEvent[];
|
||||
calendarColorById: Map<string, string>;
|
||||
dimWeekend: boolean;
|
||||
canWrite: boolean;
|
||||
draggingEventId: string;
|
||||
hoveredEventId: string;
|
||||
dropTarget: CalendarTimeDropTarget | null;
|
||||
onEventSelect: (event: CalendarEvent) => void;
|
||||
onEventHover: (eventId: string) => void;
|
||||
onEventDragStart: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
event: CalendarEvent,
|
||||
) => void;
|
||||
onEventResizeDragStart: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
event: CalendarEvent,
|
||||
edge: CalendarResizeEdge,
|
||||
) => void;
|
||||
onEventDragEnd: () => void;
|
||||
onEventDragOverTime: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
day: Date,
|
||||
) => void;
|
||||
onDropTargetLeave: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
) => void;
|
||||
onEventDropOnTime: (
|
||||
dropEvent: ReactDragEvent<HTMLElement>,
|
||||
day: Date,
|
||||
minuteOfDay: number,
|
||||
) => void;
|
||||
};
|
||||
|
||||
function TimedDayColumn({
|
||||
day,
|
||||
events,
|
||||
calendarColorById,
|
||||
dimWeekend,
|
||||
canWrite,
|
||||
draggingEventId,
|
||||
hoveredEventId,
|
||||
dropTarget,
|
||||
onEventSelect,
|
||||
onEventHover,
|
||||
onEventDragStart,
|
||||
onEventResizeDragStart,
|
||||
onEventDragEnd,
|
||||
onEventDragOverTime,
|
||||
onDropTargetLeave,
|
||||
onEventDropOnTime,
|
||||
}: TimedDayColumnProps) {
|
||||
const layout = useMemo(
|
||||
() => layoutTimedEventsForDay(events, day),
|
||||
[day, events],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"calendar-day-time-column",
|
||||
dimWeekend ? "is-weekend" : "",
|
||||
dropTarget ? "is-drop-target" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
onDragOver={
|
||||
canWrite
|
||||
? (event) => onEventDragOverTime(event, day)
|
||||
: undefined
|
||||
}
|
||||
onDragLeave={canWrite ? onDropTargetLeave : undefined}
|
||||
onDrop={
|
||||
canWrite
|
||||
? (event) =>
|
||||
onEventDropOnTime(event, day, dropSlotMinuteOfDay(event))
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{dropTarget && (
|
||||
<div
|
||||
className="calendar-time-drop-marker"
|
||||
style={{
|
||||
top: `${
|
||||
(dropTarget.minuteOfDay / 60) * HOUR_ROW_HEIGHT
|
||||
}px`,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span>{minuteOfDayLabel(dropTarget.minuteOfDay)}</span>
|
||||
</div>
|
||||
)}
|
||||
{layout.items.map((item) => (
|
||||
<div
|
||||
key={item.event.id}
|
||||
className={[
|
||||
"calendar-timed-event",
|
||||
draggingEventId === item.event.id ? "is-dragging" : "",
|
||||
hoveredEventId === item.event.id
|
||||
? "is-linked-hover"
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
style={timedEventStyle(
|
||||
item,
|
||||
calendarColorById.get(item.event.calendar_id),
|
||||
)}
|
||||
draggable={canWrite}
|
||||
onMouseEnter={() => onEventHover(item.event.id)}
|
||||
onMouseLeave={() => onEventHover("")}
|
||||
onDragStart={
|
||||
canWrite
|
||||
? (event) => onEventDragStart(event, item.event)
|
||||
: undefined
|
||||
}
|
||||
onDragEnd={canWrite ? onEventDragEnd : undefined}
|
||||
title={i18nMessage(
|
||||
"i18n:govoplan-calendar.edit_value.fad75899",
|
||||
{ value0: item.event.summary },
|
||||
)}
|
||||
>
|
||||
{canWrite && (
|
||||
<span
|
||||
className="calendar-event-resize-handle is-start"
|
||||
draggable
|
||||
title="i18n:govoplan-calendar.change_start_time.06eea3d3"
|
||||
onDragStart={(event) =>
|
||||
onEventResizeDragStart(event, item.event, "start")
|
||||
}
|
||||
onDragEnd={onEventDragEnd}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="calendar-timed-event-content"
|
||||
onClick={() => onEventSelect(item.event)}
|
||||
onFocus={() => onEventHover(item.event.id)}
|
||||
onBlur={() => onEventHover("")}
|
||||
title={i18nMessage(
|
||||
"i18n:govoplan-calendar.edit_value.fad75899",
|
||||
{ value0: item.event.summary },
|
||||
)}
|
||||
>
|
||||
<EventInlineLabel event={item.event} />
|
||||
</button>
|
||||
{canWrite && (
|
||||
<span
|
||||
className="calendar-event-resize-handle is-end"
|
||||
draggable
|
||||
title="i18n:govoplan-calendar.change_end_time.db66ef4b"
|
||||
onDragStart={(event) =>
|
||||
onEventResizeDragStart(event, item.event, "end")
|
||||
}
|
||||
onDragEnd={onEventDragEnd}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{layout.overflows.map((overflow) => (
|
||||
<button
|
||||
key={overflow.key}
|
||||
type="button"
|
||||
className="calendar-timed-overflow"
|
||||
style={timedOverflowStyle(overflow)}
|
||||
title={overflow.events
|
||||
.map((event) => event.summary)
|
||||
.join(", ")}
|
||||
onClick={() => onEventSelect(overflow.events[0])}
|
||||
>
|
||||
+{overflow.events.length}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type CalendarEventChipProps = {
|
||||
event: CalendarEvent;
|
||||
color?: string | null;
|
||||
compact?: boolean;
|
||||
canDrag?: boolean;
|
||||
dragging?: boolean;
|
||||
linkedHover?: boolean;
|
||||
onSelect: (event: CalendarEvent) => void;
|
||||
onHover?: (eventId: string) => void;
|
||||
onDragStart?: (
|
||||
dragEvent: ReactDragEvent<HTMLElement>,
|
||||
event: CalendarEvent,
|
||||
) => void;
|
||||
onDragEnd?: () => void;
|
||||
};
|
||||
|
||||
function CalendarEventChip({
|
||||
event,
|
||||
color,
|
||||
compact = false,
|
||||
canDrag = false,
|
||||
dragging = false,
|
||||
linkedHover = false,
|
||||
onSelect,
|
||||
onHover,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
}: CalendarEventChipProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
compact
|
||||
? "calendar-event-chip is-compact"
|
||||
: "calendar-event-chip",
|
||||
dragging ? "is-dragging" : "",
|
||||
linkedHover ? "is-linked-hover" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
style={calendarEventColorStyle(color)}
|
||||
draggable={canDrag}
|
||||
onClick={() => onSelect(event)}
|
||||
onMouseEnter={onHover ? () => onHover(event.id) : undefined}
|
||||
onMouseLeave={onHover ? () => onHover("") : undefined}
|
||||
onFocus={onHover ? () => onHover(event.id) : undefined}
|
||||
onBlur={onHover ? () => onHover("") : undefined}
|
||||
onDragStart={
|
||||
canDrag && onDragStart
|
||||
? (dragEvent) => onDragStart(dragEvent, event)
|
||||
: undefined
|
||||
}
|
||||
onDragEnd={canDrag ? onDragEnd : undefined}
|
||||
title={i18nMessage(
|
||||
"i18n:govoplan-calendar.edit_value.fad75899",
|
||||
{ value0: event.summary },
|
||||
)}
|
||||
>
|
||||
<EventInlineLabel event={event} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function EventInlineLabel({
|
||||
event,
|
||||
}: {
|
||||
event: CalendarEvent;
|
||||
}) {
|
||||
return (
|
||||
<span className="calendar-event-line">
|
||||
<span className="calendar-event-time">
|
||||
{eventTimeLabel(event)}
|
||||
</span>
|
||||
<span className="calendar-event-separator">-</span>
|
||||
<strong>{event.summary}</strong>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user