diff --git a/src/stories/components/CalendarCourseCell.stories.tsx b/src/stories/components/CalendarCourseCell.stories.tsx
new file mode 100644
index 000000000..647a766dd
--- /dev/null
+++ b/src/stories/components/CalendarCourseCell.stories.tsx
@@ -0,0 +1,67 @@
+import { Meta, StoryObj } from '@storybook/react';
+import React from 'react';
+import { Course, Status } from 'src/shared/types/Course';
+import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting';
+import { CourseSchedule } from 'src/shared/types/CourseSchedule';
+import Instructor from 'src/shared/types/Instructor';
+import CalendarCourseCell from 'src/views/components/common/CalendarCourseCell/CalendarCourseCell';
+
+const meta = {
+ title: 'Components/Common/CalendarCourseCell',
+ component: CalendarCourseCell,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ course: { control: 'object' },
+ meetingIdx: { control: 'number' },
+ color: { control: 'color' },
+ },
+ render: (args: any) => (
+
+
+
+ ),
+} satisfies Meta;
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ course: new Course({
+ uniqueId: 123,
+ number: '311C',
+ fullName: "311C - Bevo's Default Course",
+ courseName: "Bevo's Default Course",
+ department: 'BVO',
+ creditHours: 3,
+ status: Status.WAITLISTED,
+ instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
+ isReserved: false,
+ url: '',
+ flags: [],
+ schedule: new CourseSchedule({
+ meetings: [
+ new CourseMeeting({
+ days: [DAY_MAP.M, DAY_MAP.W, DAY_MAP.F],
+ startTime: 480,
+ endTime: 570,
+ location: {
+ building: 'UTC',
+ room: '123',
+ },
+ }),
+ ],
+ }),
+ instructionMode: 'In Person',
+ semester: {
+ year: 2024,
+ season: 'Spring',
+ },
+ }),
+ meetingIdx: 0,
+ color: 'red',
+ },
+};
diff --git a/src/views/components/common/CalendarCourseCell/CalendarCourseCell.tsx b/src/views/components/common/CalendarCourseCell/CalendarCourseCell.tsx
new file mode 100644
index 000000000..27b2519ee
--- /dev/null
+++ b/src/views/components/common/CalendarCourseCell/CalendarCourseCell.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Course, Status } from 'src/shared/types/Course';
+import { CourseMeeting } from 'src/shared/types/CourseMeeting';
+import ClosedIcon from '~icons/material-symbols/lock';
+import WaitlistIcon from '~icons/material-symbols/timelapse';
+import CancelledIcon from '~icons/material-symbols/warning';
+import Text from '../Text/Text';
+
+export interface CalendarCourseBlockProps {
+ /** The Course that the meeting is for. */
+ course: Course;
+ /* index into course meeting array to display */
+ meetingIdx?: number;
+ /** The background color for the course. */
+ color: string;
+}
+
+const CalendarCourseBlock: React.FC = ({ course, meetingIdx }: CalendarCourseBlockProps) => {
+ let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;
+ let rightIcon: React.ReactNode | null = null;
+ if (course.status === Status.WAITLISTED) {
+ rightIcon = ;
+ } else if (course.status === Status.CLOSED) {
+ rightIcon = ;
+ } else if (course.status === Status.CANCELLED) {
+ rightIcon = ;
+ }
+
+ return (
+
+
+
+ {course.department} {course.number} - {course.instructors[0].lastName}
+
+
+ {`${meeting.getTimeString({ separator: '–', capitalize: true })}${
+ meeting.location ? ` – ${meeting.location.building}` : ''
+ }`}
+
+
+ {rightIcon && (
+
+ {rightIcon}
+
+ )}
+
+ );
+};
+
+export default CalendarCourseBlock;