feat: improve overlapping events display in weekly calendar view (#24880)
* feat: improve overlapping events display in weekly calendar view - Add cascading layout algorithm for overlapping events - First event at 80% width, subsequent events offset by 8% - Implement hover behavior to bring events to front (z-index 100) - Extract overlap logic into reusable utility functions - Add comprehensive unit tests for overlap detection and layout calculation - Sort events by start time, then by duration for consistent rendering Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * feat: add weekly calendar playground page for testing overlapping events - Create comprehensive playground page with 8 test scenarios - Include two overlapping, three cascading, non-overlapping, same start time, chain overlaps, dense day, touching events, and mixed statuses - Add focused view with scenario selector and grid view for side-by-side comparison - Update playground index to include weekly calendar link - Each scenario includes description, expected behavior, and collapsible event data Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * update base props * remove unneccessary things * fix: isolate Calendar store instances to prevent shared state - Refactor useCalendarStore to use Zustand's createStore with React Context - Each Calendar component now creates its own isolated store instance - Maintains backward compatibility with global store fallback - Fixes issue where multiple Calendar instances on same page shared state This allows the playground page to render multiple Calendar instances without state conflicts between them. Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: prevent event overflow in dense overlap scenarios - Dynamically compress offset step based on overlap group size - Ensure leftOffset + width never exceeds 100% to prevent bleeding into next day - Add width clamping as safety guard against rounding errors - Add 3 new tests for overflow prevention with dense event scenarios Fixes issue where 10+ overlapping events would cascade beyond day boundary and bleed into the next day's column. The algorithm now calculates the maximum safe step size per overlap group while maintaining visual cascade. Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * remove global store * fix: comprehensive overflow prevention with safety margin and CSS fix - Add safetyMarginPercent (0.5%) to prevent rounding and CSS box model overflow - Implement floor3 rounding for width to guarantee left + width <= 100 - safetyMargin - Calculate width from rounded left offset to avoid rounding mismatch - Remove inset-x-1 class conflict that was setting both left and right positioning - Change from marginLeft to left positioning for proper control - Add test for 20+ overlapping events to verify no overflow - Update existing tests to verify safety margin is respected This fixes the slight overflow issue reported by the user where events were still bleeding into adjacent day columns despite the initial dynamic step compression fix. The root cause was a combination of: 1. CSS conflict: inset-x-1 class setting both left and right positioning 2. Rounding mismatch: width calculated from unrounded left offset 3. No safety margin for CSS box model effects (borders, padding) All 21 tests now pass including new safety margin verification. Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * feat: update Dense Day scenario to 20+ events - Increase from 11 to 21 overlapping events in Dense Day scenario - Update title from '10+ Events' to '20+ Events' - Add 10 more diverse events with staggered start times - Better stress test for overflow prevention with safety margin This provides a more comprehensive test case for the overflow prevention fix and matches the user's request for 20+ events instead of 10+. Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * WIP * feat: enhance event tooltip to show full event details - Replace simple title-only tooltip with rich content tooltip - Display event title, time range, description, and status - Add color indicator matching the event's visual style - Set min-width (200px) and max-width (300px) for better readability - Use inverted theme colors for better contrast in tooltip Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * faster animation * feat: position tooltip based on day of week - Show tooltip on right side for Monday-Thursday (days 1-4) - Show tooltip on left side for Friday-Sunday (days 5-0) - Calculate day of week using dayjs from event start date Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * add margin for tooltip * feat: add event duration layout test scenario - Add new scenario with events of varying durations (3, 7, 15, 20, 30, 53 minutes) - Test layout logic where eventDuration > 30 changes flex direction - Events ≤30min show horizontal layout (title and time inline) - Events >30min show vertical layout (title and time stacked) Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * update styles * feat: add minimum height of 15px for events - Ensure very short events (e.g., 3 minutes) are still visible - Use CSS max() to apply minimum height while respecting duration-based height - Prevents events from becoming too small to interact with Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * update playground * do not mutate * pre-compute some values --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Sean Brydon <sean@cal.com>
This commit is contained in:
co-authored by
eunjae@cal.com <hey@eunjae.dev>
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sean Brydon
parent
c48f2043f2
commit
b01cb27c56
@@ -9,6 +9,10 @@ const LINKS = [
|
||||
title: "Bookings by Hour",
|
||||
href: "/settings/admin/playground/bookings-by-hour",
|
||||
},
|
||||
{
|
||||
title: "Weekly Calendar",
|
||||
href: "/settings/admin/playground/weekly-calendar",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
|
||||
+521
@@ -0,0 +1,521 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { Calendar } from "@calcom/features/calendars/weeklyview";
|
||||
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
|
||||
import type { CalendarComponentProps } from "@calcom/features/calendars/weeklyview/types/state";
|
||||
|
||||
const makeDate = (dayOffset: number, hour: number, minute: number = 0) => {
|
||||
return dayjs("2025-01-06").add(dayOffset, "day").hour(hour).minute(minute).second(0).toDate();
|
||||
};
|
||||
|
||||
const getBaseProps = (events: CalendarEvent[]): CalendarComponentProps => ({
|
||||
startDate: dayjs("2025-01-06").toDate(), // Monday
|
||||
endDate: dayjs("2025-01-12").toDate(), // Sunday
|
||||
events,
|
||||
startHour: 6,
|
||||
endHour: 18,
|
||||
gridCellsPerHour: 4,
|
||||
timezone: "UTC",
|
||||
showBackgroundPattern: false,
|
||||
showBorder: false,
|
||||
hideHeader: true,
|
||||
borderColor: "subtle",
|
||||
});
|
||||
|
||||
type Scenario = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
expected: string;
|
||||
events: CalendarEvent[];
|
||||
};
|
||||
|
||||
const scenarios: Scenario[] = [
|
||||
{
|
||||
id: "two-overlapping",
|
||||
title: "Two Overlapping Events",
|
||||
description: "Two events with overlapping time ranges on the same day",
|
||||
expected:
|
||||
"Second event should be offset 8% to the right, both 80% width. Hover should bring event to front.",
|
||||
events: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Meeting A",
|
||||
start: makeDate(0, 10, 0),
|
||||
end: makeDate(0, 11, 0),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Meeting B",
|
||||
start: makeDate(0, 10, 30),
|
||||
end: makeDate(0, 11, 30),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "three-cascading",
|
||||
title: "Three Overlapping Events (Cascading)",
|
||||
description: "Three events that overlap, creating a cascading effect",
|
||||
expected:
|
||||
"Events should cascade with offsets 0%, 8%, 16%. Z-index should increment. Hover brings any to top.",
|
||||
events: [
|
||||
{
|
||||
id: 3,
|
||||
title: "Long Meeting",
|
||||
start: makeDate(1, 10, 0),
|
||||
end: makeDate(1, 12, 0),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Mid Meeting",
|
||||
start: makeDate(1, 10, 30),
|
||||
end: makeDate(1, 11, 30),
|
||||
options: { status: "PENDING", color: "#f59e0b" },
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Late Meeting",
|
||||
start: makeDate(1, 11, 0),
|
||||
end: makeDate(1, 12, 30),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "non-overlapping",
|
||||
title: "Non-Overlapping Events",
|
||||
description: "Events that don't overlap should not cascade",
|
||||
expected: "Both events at 0% offset (separate groups), no cascade. Both should be 80% width.",
|
||||
events: [
|
||||
{
|
||||
id: 6,
|
||||
title: "Morning Meeting",
|
||||
start: makeDate(2, 10, 0),
|
||||
end: makeDate(2, 11, 0),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: "Afternoon Meeting",
|
||||
start: makeDate(2, 12, 0),
|
||||
end: makeDate(2, 13, 0),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "same-start-time",
|
||||
title: "Same Start Time, Different Durations",
|
||||
description: "Multiple events starting at the same time with varying lengths",
|
||||
expected: "Longest event first (base of cascade), shorter ones offset 8%, 16%. All start at 10:00.",
|
||||
events: [
|
||||
{
|
||||
id: 8,
|
||||
title: "2-Hour Meeting",
|
||||
start: makeDate(3, 10, 0),
|
||||
end: makeDate(3, 12, 0),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: "1.5-Hour Meeting",
|
||||
start: makeDate(3, 10, 0),
|
||||
end: makeDate(3, 11, 30),
|
||||
options: { status: "ACCEPTED", color: "#f59e0b" },
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: "30-Min Meeting",
|
||||
start: makeDate(3, 10, 0),
|
||||
end: makeDate(3, 10, 30),
|
||||
options: { status: "PENDING", color: "#10b981" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "chain-overlaps",
|
||||
title: "Chain Overlaps (A→B→C)",
|
||||
description: "Events where A overlaps B, and B overlaps C",
|
||||
expected: "Single overlap group with cascading offsets 0%, 8%, 16%.",
|
||||
events: [
|
||||
{
|
||||
id: 11,
|
||||
title: "Event A",
|
||||
start: makeDate(4, 10, 0),
|
||||
end: makeDate(4, 11, 0),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: "Event B",
|
||||
start: makeDate(4, 10, 30),
|
||||
end: makeDate(4, 11, 30),
|
||||
options: { status: "ACCEPTED", color: "#f59e0b" },
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
title: "Event C",
|
||||
start: makeDate(4, 11, 0),
|
||||
end: makeDate(4, 12, 0),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "dense-day",
|
||||
title: "Dense Day (20+ Events)",
|
||||
description: "A very busy day with many overlapping events",
|
||||
expected:
|
||||
"Visually tight stack with multiple cascading levels. Right edge should not overflow. Hover should still work.",
|
||||
events: [
|
||||
{
|
||||
id: 14,
|
||||
title: "Team Standup",
|
||||
start: makeDate(5, 9, 0),
|
||||
end: makeDate(5, 9, 30),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
title: "Client Call",
|
||||
start: makeDate(5, 9, 15),
|
||||
end: makeDate(5, 10, 0),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
title: "Design Review",
|
||||
start: makeDate(5, 9, 45),
|
||||
end: makeDate(5, 11, 0),
|
||||
options: { status: "PENDING", color: "#f59e0b" },
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
title: "1-on-1",
|
||||
start: makeDate(5, 10, 0),
|
||||
end: makeDate(5, 10, 30),
|
||||
options: { status: "ACCEPTED", color: "#8b5cf6" },
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
title: "Planning",
|
||||
start: makeDate(5, 10, 15),
|
||||
end: makeDate(5, 11, 30),
|
||||
options: { status: "ACCEPTED", color: "#ec4899" },
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
title: "Code Review",
|
||||
start: makeDate(5, 11, 0),
|
||||
end: makeDate(5, 12, 0),
|
||||
options: { status: "ACCEPTED", color: "#14b8a6" },
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
title: "Lunch Meeting",
|
||||
start: makeDate(5, 11, 30),
|
||||
end: makeDate(5, 12, 30),
|
||||
options: { status: "ACCEPTED", color: "#f97316" },
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
title: "Workshop",
|
||||
start: makeDate(5, 12, 0),
|
||||
end: makeDate(5, 13, 30),
|
||||
options: { status: "ACCEPTED", color: "#06b6d4" },
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
title: "Interview",
|
||||
start: makeDate(5, 12, 30),
|
||||
end: makeDate(5, 13, 0),
|
||||
options: { status: "PENDING", color: "#84cc16" },
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
title: "Retrospective",
|
||||
start: makeDate(5, 13, 0),
|
||||
end: makeDate(5, 14, 0),
|
||||
options: { status: "ACCEPTED", color: "#a855f7" },
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
title: "Demo",
|
||||
start: makeDate(5, 13, 15),
|
||||
end: makeDate(5, 14, 0),
|
||||
options: { status: "CANCELLED", color: "#ef4444" },
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
title: "Quick Sync",
|
||||
start: makeDate(5, 9, 10),
|
||||
end: makeDate(5, 9, 40),
|
||||
options: { status: "ACCEPTED", color: "#6366f1" },
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
title: "Architecture Discussion",
|
||||
start: makeDate(5, 9, 30),
|
||||
end: makeDate(5, 10, 30),
|
||||
options: { status: "ACCEPTED", color: "#ec4899" },
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
title: "Sprint Planning",
|
||||
start: makeDate(5, 10, 10),
|
||||
end: makeDate(5, 11, 0),
|
||||
options: { status: "ACCEPTED", color: "#f59e0b" },
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
title: "Product Demo",
|
||||
start: makeDate(5, 10, 45),
|
||||
end: makeDate(5, 11, 45),
|
||||
options: { status: "PENDING", color: "#14b8a6" },
|
||||
},
|
||||
{
|
||||
id: 34,
|
||||
title: "Bug Triage",
|
||||
start: makeDate(5, 11, 15),
|
||||
end: makeDate(5, 12, 15),
|
||||
options: { status: "ACCEPTED", color: "#8b5cf6" },
|
||||
},
|
||||
{
|
||||
id: 35,
|
||||
title: "Customer Feedback",
|
||||
start: makeDate(5, 11, 45),
|
||||
end: makeDate(5, 12, 45),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
{
|
||||
id: 36,
|
||||
title: "Team Sync",
|
||||
start: makeDate(5, 12, 15),
|
||||
end: makeDate(5, 13, 15),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
title: "Tech Talk",
|
||||
start: makeDate(5, 12, 45),
|
||||
end: makeDate(5, 13, 45),
|
||||
options: { status: "ACCEPTED", color: "#f97316" },
|
||||
},
|
||||
{
|
||||
id: 38,
|
||||
title: "Standup Follow-up",
|
||||
start: makeDate(5, 13, 30),
|
||||
end: makeDate(5, 14, 0),
|
||||
options: { status: "ACCEPTED", color: "#06b6d4" },
|
||||
},
|
||||
{
|
||||
id: 39,
|
||||
title: "Office Hours",
|
||||
start: makeDate(5, 13, 45),
|
||||
end: makeDate(5, 14, 30),
|
||||
options: { status: "ACCEPTED", color: "#a855f7" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "touching-events",
|
||||
title: "Touching Events (Edge Case)",
|
||||
description: "Events that touch exactly at boundaries",
|
||||
expected: "Separate groups; no cascade; both at 0% offset. Events touching at 11:00 should not overlap.",
|
||||
events: [
|
||||
{
|
||||
id: 25,
|
||||
title: "Morning Block",
|
||||
start: makeDate(6, 10, 0),
|
||||
end: makeDate(6, 11, 0),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 26,
|
||||
title: "Afternoon Block",
|
||||
start: makeDate(6, 11, 0),
|
||||
end: makeDate(6, 12, 0),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "mixed-statuses",
|
||||
title: "Mixed Event Statuses",
|
||||
description: "Events with different booking statuses",
|
||||
expected:
|
||||
"Visual styling should differ by status (ACCEPTED, PENDING, CANCELLED). Cascade should still work.",
|
||||
events: [
|
||||
{
|
||||
id: 27,
|
||||
title: "Confirmed Meeting",
|
||||
start: makeDate(0, 14, 0),
|
||||
end: makeDate(0, 15, 0),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 28,
|
||||
title: "Pending Approval",
|
||||
start: makeDate(0, 14, 30),
|
||||
end: makeDate(0, 15, 30),
|
||||
options: { status: "PENDING", color: "#f59e0b" },
|
||||
},
|
||||
{
|
||||
id: 29,
|
||||
title: "Cancelled Meeting",
|
||||
start: makeDate(0, 15, 0),
|
||||
end: makeDate(0, 16, 0),
|
||||
options: { status: "CANCELLED", color: "#ef4444" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "event-durations",
|
||||
title: "Event Duration Layout Tests",
|
||||
description: "Events with different durations to test layout logic (eventDuration > 30 changes flex-col)",
|
||||
expected:
|
||||
"Events ≤30min show horizontal layout (title and time inline). Events >30min show vertical layout (title and time stacked).",
|
||||
events: [
|
||||
{
|
||||
id: 40,
|
||||
title: "3min Quick Chat",
|
||||
start: makeDate(0, 9, 0),
|
||||
end: makeDate(0, 9, 3),
|
||||
options: { status: "ACCEPTED", color: "#3b82f6" },
|
||||
},
|
||||
{
|
||||
id: 41,
|
||||
title: "7min Standup",
|
||||
start: makeDate(0, 9, 15),
|
||||
end: makeDate(0, 9, 22),
|
||||
options: { status: "ACCEPTED", color: "#10b981" },
|
||||
},
|
||||
{
|
||||
id: 42,
|
||||
title: "15min Check-in",
|
||||
start: makeDate(0, 9, 30),
|
||||
end: makeDate(0, 9, 45),
|
||||
options: { status: "PENDING", color: "#f59e0b" },
|
||||
},
|
||||
{
|
||||
id: 43,
|
||||
title: "20min Review",
|
||||
start: makeDate(0, 10, 0),
|
||||
end: makeDate(0, 10, 20),
|
||||
options: { status: "ACCEPTED", color: "#8b5cf6" },
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
title: "30min Discussion",
|
||||
start: makeDate(0, 10, 30),
|
||||
end: makeDate(0, 11, 0),
|
||||
options: { status: "ACCEPTED", color: "#ec4899" },
|
||||
},
|
||||
{
|
||||
id: 45,
|
||||
title: "32min Discussion",
|
||||
start: makeDate(0, 11, 10),
|
||||
end: makeDate(0, 11, 42),
|
||||
options: { status: "ACCEPTED", color: "#ec4899" },
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
title: "40min Discussion",
|
||||
start: makeDate(0, 12, 0),
|
||||
end: makeDate(0, 12, 40),
|
||||
options: { status: "ACCEPTED", color: "#ec4899" },
|
||||
},
|
||||
{
|
||||
id: 47,
|
||||
title: "53min Workshop",
|
||||
description: "Learn about the latest trends in web development",
|
||||
start: makeDate(0, 13, 0),
|
||||
end: makeDate(0, 13, 53),
|
||||
options: { status: "ACCEPTED", color: "#14b8a6" },
|
||||
},
|
||||
{
|
||||
id: 48,
|
||||
title: "90min Workshop",
|
||||
description: "Learn about the latest trends in web development",
|
||||
start: makeDate(0, 14, 0),
|
||||
end: makeDate(0, 15, 30),
|
||||
options: { status: "ACCEPTED", color: "#14b8a6" },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function ScenarioCard({ scenario }: { scenario: Scenario }) {
|
||||
const [showData, setShowData] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="border-subtle rounded-lg border p-4">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold">{scenario.title}</h3>
|
||||
<p className="text-subtle mt-1 text-sm">{scenario.description}</p>
|
||||
<div className="bg-subtle mt-2 rounded p-2 text-xs">
|
||||
<strong>Expected:</strong> {scenario.expected}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[600px] overflow-hidden rounded border">
|
||||
<Calendar {...getBaseProps(scenario.events)} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setShowData(!showData)}
|
||||
className="text-emphasis mt-2 text-sm underline hover:no-underline">
|
||||
{showData ? "Hide" : "Show"} Event Data
|
||||
</button>
|
||||
|
||||
{showData && (
|
||||
<pre className="bg-subtle mt-2 overflow-auto rounded p-2 text-xs">
|
||||
{JSON.stringify(scenario.events, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WeeklyCalendarPlayground() {
|
||||
return (
|
||||
<div className="space-y-8 p-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Weekly Calendar Playground</h1>
|
||||
<p className="text-subtle mt-2">
|
||||
Test the overlapping events cascading layout with various scenarios. Events should cascade with 80%
|
||||
width and 8% left offset per overlap level. Hovering an event should bring it to the front.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Grid View of All Scenarios */}
|
||||
<div className="space-y-8">
|
||||
<h2 className="mb-4 text-2xl font-bold">All Scenarios (Side-by-Side)</h2>
|
||||
{scenarios.map((scenario, index) => (
|
||||
<ScenarioCard key={index} scenario={scenario} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Testing Checklist */}
|
||||
<div className="border-subtle rounded-lg border p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Testing Checklist</h2>
|
||||
<ul className="text-subtle space-y-2 text-sm">
|
||||
<li>✓ Visual appearance matches expectations (80% width with 8% cascading offsets)</li>
|
||||
<li>✓ Hover behavior works smoothly - hovered event appears topmost</li>
|
||||
<li>✓ No visual glitches with 3+ overlapping events</li>
|
||||
<li>✓ Performance is acceptable with dense event days (10+ events)</li>
|
||||
<li>✓ Edge case: Events with identical start times render correctly</li>
|
||||
<li>✓ Edge case: Events that touch exactly (end = next start) don't incorrectly overlap</li>
|
||||
<li>✓ Different booking statuses (ACCEPTED, PENDING, CANCELLED) display correctly</li>
|
||||
<li>✓ Color bars on the left side of events display correctly</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user