Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup

This commit is contained in:
Abhinav Chadaga
2024-02-19 17:57:13 -06:00
54 changed files with 5103 additions and 1101 deletions

View File

@@ -1,7 +1,7 @@
import clsx from 'clsx';
import React from 'react';
import { ThemeColor, getThemeColorHexByName, getThemeColorRgbByName } from '../../../../shared/util/themeColors';
import type IconComponent from '~icons/material-symbols';
import { ThemeColor, getThemeColorHexByName, getThemeColorRgbByName } from '../../../../shared/util/themeColors';
import Text from '../Text/Text';
interface Props {
@@ -33,28 +33,24 @@ export function Button({
const Icon = icon;
const isIconOnly = !children && !!icon;
const colorHex = getThemeColorHexByName(color);
const colorRgb = getThemeColorRgbByName(color).join(' ');
const colorRgb = getThemeColorRgbByName(color)?.join(' ');
return (
<button
style={
{
...style,
'--color': colorHex,
'--bg-color-8': `rgba(${colorRgb} / 0.08)`,
'--shadow-color-15': `rgba(${colorRgb} / 0.15)`,
'--shadow-color-30': `rgba(${colorRgb} / 0.3)`,
color: colorHex,
backgroundColor: `rgb(${colorRgb} / var(--un-bg-opacity)`,
} as React.CSSProperties
}
className={clsx(
'btn',
{
'disabled:(cursor-not-allowed opacity-50)': disabled,
'color-white bg-[var(--color)] border-[var(--color)] hover:enabled:btn-shadow':
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
variant === 'filled',
'color-[var(--color)] bg-white border-current hover:enabled:btn-shade border border-solid':
variant === 'outline',
'color-[var(--color)] bg-white border-white hover:enabled:btn-shade': variant === 'single', // settings is the only "single"
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border': variant === 'outline',
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'single', // settings is the only "single"
'px-2 py-1.25': isIconOnly && variant !== 'outline',
'px-1.75 py-1.25': isIconOnly && variant === 'outline',
'px-3.75': variant === 'outline' && !isIconOnly,
@@ -65,7 +61,7 @@ export function Button({
disabled={disabled}
onClick={disabled ? undefined : onClick}
>
{icon && <Icon className='size-6' />}
{icon && <Icon className='h-6 w-6' />}
{!isIconOnly && (
<Text variant='h4' className='translate-y-0.08'>
{children}

View File

@@ -0,0 +1,44 @@
import React from 'react';
import clsx from 'clsx';
import Text from '../Text/Text';
import CalendarCourseBlock, { CalendarCourseCellProps } from '../CalendarCourseCell/CalendarCourseCell';
import { Button } from '../Button/Button';
import ImageIcon from '~icons/material-symbols/image';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
type CalendarBottomBarProps = {
courses: CalendarCourseCellProps[];
};
/**
*
*/
export const CalendarBottomBar = ({ courses }: CalendarBottomBarProps): JSX.Element => {
if (courses.length === -1) console.log('foo'); // dumb line to make eslint happy
return (
<div className='w-full flex py-1.25'>
<div className='flex flex-grow items-center gap-3.75 pl-7.5 pr-2.5'>
<Text variant='h4'>Async. and Other:</Text>
<div className='h-14 inline-flex gap-2.5'>
{courses.map(course => (
<CalendarCourseBlock
courseDeptAndInstr={course.courseDeptAndInstr}
status={course.status}
colors={course.colors}
key={course.courseDeptAndInstr}
className={clsx(course.className, 'w-35!')}
/>
))}
</div>
</div>
<div className='flex items-center pl-2.5 pr-7.5'>
<Button variant='single' color='ut-black' icon={CalendarMonthIcon}>
Save as .CAL
</Button>
<Button variant='single' color='ut-black' icon={ImageIcon}>
Save as .PNG
</Button>
</div>
</div>
);
};

View File

@@ -1,45 +1,68 @@
import { Status } from '@shared/types/Course';
import clsx from 'clsx';
import React from 'react';
import { Course, Status } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import { CourseColors, pickFontColor } from 'src/shared/util/colors';
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;
export interface CalendarCourseCellProps {
courseDeptAndInstr: string;
timeAndLocation?: string;
status: Status;
colors: CourseColors;
className?: string;
}
const CalendarCourseBlock: React.FC<CalendarCourseBlockProps> = ({ course, meetingIdx }: CalendarCourseBlockProps) => {
let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;
const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
courseDeptAndInstr,
timeAndLocation,
status,
colors,
className,
}: CalendarCourseCellProps) => {
let rightIcon: React.ReactNode | null = null;
if (course.status === Status.WAITLISTED) {
if (status === Status.WAITLISTED) {
rightIcon = <WaitlistIcon className='h-5 w-5' />;
} else if (course.status === Status.CLOSED) {
} else if (status === Status.CLOSED) {
rightIcon = <ClosedIcon className='h-5 w-5' />;
} else if (course.status === Status.CANCELLED) {
} else if (status === Status.CANCELLED) {
rightIcon = <CancelledIcon className='h-5 w-5' />;
}
// whiteText based on secondaryColor
const fontColor = pickFontColor(colors.primaryColor);
return (
<div className='w-full flex justify-center rounded bg-slate-300 p-2 text-ut-black'>
<div className='flex flex-1 flex-col gap-1'>
<Text variant='h1-course' className='leading-[75%]!'>
{course.department} {course.number} - {course.instructors[0].lastName}
</Text>
<Text variant='h3-course' className='leading-[75%]!'>
{`${meeting.getTimeString({ separator: '', capitalize: true })}${
meeting.location ? ` ${meeting.location.building}` : ''
}`}
<div
className={clsx('w-full flex justify-center rounded p-2', fontColor, className)}
style={{
backgroundColor: colors.primaryColor,
}}
>
<div className='flex flex-1 flex-col gap-1 overflow-x-hidden'>
<Text
variant='h1-course'
className={clsx('-my-0.8 leading-tight', {
truncate: timeAndLocation,
})}
>
{courseDeptAndInstr}
</Text>
{timeAndLocation && (
<Text variant='h3-course' className='-mb-0.5'>
{timeAndLocation}
</Text>
)}
</div>
{rightIcon && (
<div className='h-fit flex items-center justify-center justify-self-start rounded bg-slate-700 p-0.5 text-white'>
<div
className='h-fit flex items-center justify-center justify-self-start rounded p-0.5 text-white'
style={{
backgroundColor: colors.secondaryColor,
}}
>
{rightIcon}
</div>
)}
@@ -47,4 +70,4 @@ const CalendarCourseBlock: React.FC<CalendarCourseBlockProps> = ({ course, meeti
);
};
export default CalendarCourseBlock;
export default CalendarCourseCell;

View File

@@ -1,6 +1,3 @@
@use 'sass:color';
@use 'src/views/styles/colors.module.scss';
.dayLabelContainer {
display: flex;
flex-direction: row;
@@ -16,7 +13,7 @@
.calendarGrid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(13, 1fr);
}
@@ -25,19 +22,24 @@
}
.calendar {
// display: grid;
// grid-template-columns: auto repeat(5, 1fr);
display: flex;
flex-direction: column;
gap: 10px;
position: relative; // Ensuring that child elements can be positioned in relation to this.
}
.day {
gap: 5px;
color: colors.$burnt_orange;
color: #bf5700;
text-align: center;
font-size: 14.22px;
font-style: normal;
font-weight: 500;
line-height: normal;
margin-top: 20px;
border-right: 1px solid #dadce0;
border-bottom: 1px solid #dadce0;
border-left: 1px solid #dadce0;
}
.timeAndGrid {
@@ -56,14 +58,11 @@
.timeBlock {
display: flex;
width: 50px;
height: 40.279px;
min-width: 50px;
max-width: 50px;
min-height: 40px;
width: auto;
height: auto;
flex-direction: column;
align-items: flex-end;
.timeLabelContainer {
display: flex;
max-height: 20px;
@@ -71,24 +70,58 @@
align-items: flex-end;
gap: 17px;
flex: 1 0 0;
align-self: stretch;
border-radius: var(--border-radius-none, 0px);
}
p {
color: #1A2024;
color: #1a2024;
text-align: left;
height: 6.6px;
align-self: stretch;
margin-top: 0;
margin-top: -10px;
margin-right: 10px;
margin-bottom: 0;
/* Type scale/small */
font-family: "Roboto Flex";
font-family: 'Roboto Flex';
font-size: 14.22px;
font-style: normal;
font-weight: 500;
line-height: normal;
}
}
}
.buttonContainer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
padding: 10px;
margin-top: auto;
}
.calendarButton {
display: flex;
align-items: center;
gap: 5px;
padding: 6px 6px;
border: none;
background-color: transparent;
color: #333;
cursor: pointer;
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
}
.buttonIcon {
height: 24px;
fill: currentColor;
}
.divider {
height: 30px;
width: 1px;
background-color: grey;
}

View File

@@ -1,52 +1,132 @@
import React from 'react';
import styles from './CalendarGrid.module.scss';
import CalendarCell from '../CalendarGridCell/CalendarGridCell';
import React, { useRef } from 'react';
import * as htmlToImage from 'html-to-image';
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import calIcon from 'src/assets/icons/cal.svg';
import pngIcon from 'src/assets/icons/png.svg';
import CalendarCell from '../CalendarGridCell/CalendarGridCell';
import CalendarCourseCell from '../CalendarCourseCell/CalendarCourseCell';
import styles from './CalendarGrid.module.scss';
const daysOfWeek = Object.values(DAY_MAP).filter(d => d != "Saturday" && d != "Sunday")
const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key));
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
const grid = Array.from({ length: 5 }, () =>
Array.from({ length: 13 }, (_, columnIndex) => (
<CalendarCell key={columnIndex} />
))
);
const grid = [];
for (let i = 0; i < 13; i++) {
const row = [];
let hour = hoursOfDay[i];
row.push(
<div key={hour} className={styles.timeBlock}>
<div className={styles.timeLabelContainer}>
<p>{(hour % 12 === 0 ? 12 : hour % 12) + (hour < 12 ? ' AM' : ' PM')}</p>
</div>
</div>
);
row.push(Array.from({ length: 5 }, (_, j) => <CalendarCell key={j} />));
grid.push(row);
}
interface Props {
courseCells: CalendarGridCourse[];
saturdayClass: boolean;
}
/**
* Grid of CalendarGridCell components forming the user's course schedule calendar view
* @param props
* @param props
*/
const Calendar: React.FC = (props) => {
return (
<div className={styles.calendar}>
<div className={styles.dayLabelContainer}>
{/* Empty cell in the top-left corner */}
<div className={styles.day} />
{/* Displaying day labels */}
{daysOfWeek.map(day => (
<div key={day} className={styles.day}>
{day}
</div>
))}
</div>
{/* Displaying the rest of the calendar */}
<div className={styles.timeAndGrid}>
<div className={styles.timeColumn}>
{hoursOfDay.map((hour) => (
<div key={hour} className={styles.timeBlock}>
<div className={styles.timeLabelContainer}>
<p>{hour % 12 === 0 ? 12 : hour % 12}:00 {hour < 12 ? 'AM' : 'PM'}</p>
</div>
</div>
))}
</div>
<div className={styles.calendarGrid}>
{grid.map((row, rowIndex) => (
row
))}
</div>
</div>
</div>
)
};
function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Props>): JSX.Element {
const calendarRef = useRef(null); // Create a ref for the calendar grid
export default Calendar;
const saveAsPNG = () => {
htmlToImage
.toPng(calendarRef.current, {
backgroundColor: 'white',
style: {
background: 'white',
marginTop: '20px',
marginBottom: '20px',
marginRight: '20px',
marginLeft: '20px',
},
})
.then(dataUrl => {
let img = new Image();
img.src = dataUrl;
fetch(dataUrl)
.then(response => response.blob())
.then(blob => {
const href = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.download = 'my-schedule.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(error => console.error('Error downloading file:', error));
})
.catch(error => {
console.error('oops, something went wrong!', error);
});
};
return (
<div className={styles.calendar}>
<div className={styles.dayLabelContainer} />
{/* Displaying the rest of the calendar */}
<div ref={calendarRef} className={styles.timeAndGrid}>
{/* <div className={styles.timeColumn}>
<div className={styles.timeBlock}></div>
{hoursOfDay.map((hour) => (
<div key={hour} className={styles.timeBlock}>
<div className={styles.timeLabelContainer}>
<p>{hour % 12 === 0 ? 12 : hour % 12} {hour < 12 ? 'AM' : 'PM'}</p>
</div>
</div>
))}
</div> */}
<div className={styles.calendarGrid}>
{/* Displaying day labels */}
<div className={styles.timeBlock} />
{daysOfWeek.map(day => (
<div key={day} className={styles.day}>
{day}
</div>
))}
{grid.map((row, index) => (
<React.Fragment key={index}>{row}</React.Fragment>
))}
</div>
</div>
{courseCells.map((block: CalendarGridCourse) => (
<div
key={`${block}`}
style={{
gridColumn: `${block.calendarGridPoint.dayIndex}`,
gridRow: `${block.calendarGridPoint.startIndex} / ${block.calendarGridPoint.endIndex}`,
}}
>
<CalendarCourseCell
courseDeptAndInstr={block.componentProps.courseDeptAndInstr}
status={block.componentProps.status}
colors={block.componentProps.colors}
/>
</div>
))}
<div className={styles.buttonContainer}>
<div className={styles.divider} /> {/* First divider */}
<button className={styles.calendarButton}>
<img src={calIcon} className={styles.buttonIcon} alt='CAL' />
Save as .CAL
</button>
<div className={styles.divider} /> {/* Second divider */}
<button onClick={saveAsPNG} className={styles.calendarButton}>
<img src={pngIcon} className={styles.buttonIcon} alt='PNG' />
Save as .PNG
</button>
</div>
</div>
);
}
export default CalendarGrid;

View File

@@ -1,18 +1,18 @@
.calendarCell {
display: flex;
width: 165px;
height: 52.231px;
min-width: 45px;
min-height: 40px;
flex-direction: column;
justify-content: center;
align-items: flex-start;
border: 1px solid #DADCE0;
display: flex;
width: 213.8px;
height: 44.769px;
min-width: 45px;
min-height: 40px;
flex-direction: column;
justify-content: center;
align-items: flex-start;
border: 1px solid #dadce0;
}
.hourLine {
width: 165px;
height: 1px;
border-radius: var(--border-radius-none, 0px);
background: rgba(218, 220, 224, 0.25);
}
width: 213.8px;
height: 1px;
border-radius: var(--border-radius-none, 0px);
background: rgba(218, 220, 224, 0.25);
}

View File

@@ -3,16 +3,12 @@ import styles from './CalendarGridCell.module.scss';
/**
* Component representing each 1 hour time block of a calendar
* @param props
* @param props
*/
const CalendarCell: React.FC = (props) => {
return (
const CalendarCell: React.FC = props => (
<div className={styles.calendarCell}>
<div className={styles.hourLine}>
</div>
<div className={styles.hourLine} />
</div>
);
};
);
export default CalendarCell;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import { Status } from '@shared/types/Course';
import Divider from '../Divider/Divider';
import { Button } from '../Button/Button';
import Text from '../Text/Text';
import MenuIcon from '~icons/material-symbols/menu';
import LogoIcon from '~icons/material-symbols/add-circle-outline';
import UndoIcon from '~icons/material-symbols/undo';
import RedoIcon from '~icons/material-symbols/redo';
import SettingsIcon from '~icons/material-symbols/settings';
import ScheduleTotalHoursAndCourses from '../ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses';
import CourseStatus from '../CourseStatus/CourseStatus';
const CalendarHeader = () => (
<div className='min-h-79px min-w-672px flex px-0 py-15'>
<div className='flex flex-row gap-20'>
<div className='flex gap-10'>
<div className='flex gap-1'>
<Button variant='single' icon={MenuIcon} color='ut-gray' />
<div className='flex items-center'>
<LogoIcon style={{ marginRight: '5px' }} />
<div className='flex flex-col gap-1 whitespace-nowrap'>
<Text className='leading-trim text-cap font-roboto text-base text-ut-burntorange font-medium'>
UT Registration
</Text>
<Text className='leading-trim text-cap font-roboto text-base text-ut-orange font-medium'>
Plus
</Text>
</div>
</div>
</div>
<div className='flex flex-col'>
<ScheduleTotalHoursAndCourses scheduleName='SCHEDULE' totalHours={22} totalCourses={8} />
DATA UPDATED ON: 12:00 AM 02/01/2024
</div>
</div>
<div className='flex flex-row items-center space-x-8'>
<div className='flex flex-row space-x-4'>
<CourseStatus size='small' status={Status.WAITLISTED} />
<CourseStatus size='small' status={Status.CLOSED} />
<CourseStatus size='small' status={Status.CANCELLED} />
</div>
<div className='flex flex-row'>
<Button variant='single' icon={UndoIcon} color='ut-black' />
<Button variant='single' icon={RedoIcon} color='ut-black' />
<Button variant='single' icon={SettingsIcon} color='ut-black' />
</div>
</div>
</div>
<Divider type='solid' />
</div>
);
export default CalendarHeader;

View File

@@ -0,0 +1,36 @@
import { UserSchedule } from '@shared/types/UserSchedule';
import React, { useState } from 'react';
import AddSchedule from '~icons/material-symbols/add';
import List from '../List/List';
import ScheduleListItem from '../ScheduleListItem/ScheduleListItem';
import Text from '../Text/Text';
export type Props = {
style?: React.CSSProperties;
dummySchedules?: UserSchedule[];
dummyActiveIndex?: number;
};
export function CalendarSchedules(props: Props) {
const [activeScheduleIndex, setActiveScheduleIndex] = useState(props.dummyActiveIndex || 0);
const [schedules, setSchedules] = useState(props.dummySchedules || []);
const scheduleComponents = schedules.map((schedule, index) => (
<ScheduleListItem active={index === activeScheduleIndex} name={schedule.name} />
));
return (
<div style={{ ...props.style }} className='items-center'>
<div className='m0 m-b-2 w-full flex justify-between'>
<Text variant='h3'>MY SCHEDULES</Text>
<div className='cursor-pointer items-center justify-center btn-transition -ml-1.5 hover:text-zinc-400'>
<Text variant='h3'>
<AddSchedule />
</Text>
</div>
</div>
<List gap={10} draggableElements={scheduleComponents} itemHeight={30} listHeight={30} listWidth={240} />
</div>
);
}

View File

@@ -1,43 +1,37 @@
import React from 'react';
import { Course } from 'src/shared/types/Course';
import clsx from 'clsx';
import Text from '../Text/Text';
/**
* Props for ConflictWithWarningProps
*/
export interface ConflictsWithWarningProps {
ConflictingCourse: string;
SectionNumber: string;
className?: string;
conflicts: Course[];
}
/**
* The ConflictsWithWarning component is used to display a warning message when a course conflicts
* The ConflictsWithWarning component is used to display a warning message when a course conflicts
* with another course as part of the labels and details section
*
* @param props ConflictsWithWarningProps
*/
export default function ConflictsWithWarning( { ConflictingCourse, SectionNumber }: ConflictsWithWarningProps): JSX.Element {
const UniqueCourseConflictText = `${ConflictingCourse} (${SectionNumber})`;
return (
<div className="min-w-21 w-21 flex flex-col items-start gap-2.5 rounded bg-[#AF2E2D] p-2.5">
<ConflictsWithoutWarningText>
Conflicts With:
</ConflictsWithoutWarningText>
<ConflictsWithoutWarningText>
{UniqueCourseConflictText}
</ConflictsWithoutWarningText>
</div>
);
}
function ConflictsWithoutWarningText( {children}: {children: string} ) {
export default function ConflictsWithWarning({ className, conflicts }: ConflictsWithWarningProps): JSX.Element {
return (
<Text
variant='mini'
as='span'
className='text-white'
className={clsx(
className,
'min-w-21 w-21 flex flex-col items-start gap-2.5 rounded bg-[#AF2E2D] p-2.5 text-white'
)}
>
{children}
<div>Conflicts With:</div>
{conflicts.map(course => (
<div>
{`${course.department} ${course.number} (${course.uniqueId})`}
</div>
))}
</Text>
);
}

View File

@@ -0,0 +1,110 @@
import { Disclosure, Transition } from '@headlessui/react';
import { UserSchedule } from '@shared/types/UserSchedule';
import React from 'react';
import userScheduleHandler from 'src/pages/background/handler/userScheduleHandler';
import DropdownArrowDown from '~icons/material-symbols/arrow-drop-down';
import DropdownArrowUp from '~icons/material-symbols/arrow-drop-up';
import List from '../List/List';
import Text from '../Text/Text';
export type Props = {
style?: React.CSSProperties;
// Dummy value solely for storybook
dummySchedules?: UserSchedule[];
dummyActiveIndex?: number;
dummyActiveSchedule?: UserSchedule;
scheduleComponents?: any[];
};
/**
* This is a reusable dropdown component that can be used to toggle the visiblity of information
*/
export default function Dropdown(props: Props) {
// Expand/Hide state for dropdown
let [expanded, toggle] = React.useState(false);
let [activeScheduleIndex, select] = React.useState(props.dummyActiveIndex);
let [activeSchedule, selectFrom] = React.useState(props.dummyActiveSchedule);
let [scheduleComponents, setScheduleComponents] = React.useState(props.scheduleComponents);
const schedules = props.dummySchedules;
if (schedules == null) {
// if no dummy values passed in
// useSchedules hook here
}
const toggleSwitch = () => {
toggle(!expanded);
};
// WIP function to swap schedules. Prefer to use the hook when in production
const switchSchedule = (index: number) => {
const scheduleToSwitchTo = schedules[index];
select(index);
selectFrom(scheduleToSwitchTo);
if (scheduleToSwitchTo != null && scheduleToSwitchTo.name != null) {
userScheduleHandler.switchSchedule({
data: {
scheduleName: scheduleToSwitchTo.name,
},
sender: null,
sendResponse: null,
});
}
};
return (
<div
style={{ ...props.style, height: expanded && schedules ? `${40 * schedules.length + 54}px` : '72px' }}
className='items-left absolute w-72 flex flex-col border'
>
<Disclosure>
<Disclosure.Button>
<div className='flex items-center border-none bg-white p-3 text-left'>
<div className='flex-1'>
<Text as='div' variant='h4' className='text-ut-burntorange mb-1 w-100%'>
MAIN SCHEDULE:
</Text>
<div>
<Text variant='h3' className='text-theme-black leading-[75%]!'>
{activeSchedule ? activeSchedule.hours : 0} HOURS
</Text>
<Text variant='h4' className='ml-2.5 text-ut-black leading-[75%]!'>
{activeSchedule ? activeSchedule.courses.length : 0} Courses
</Text>
</div>
</div>
<Text className='text-ut-burntorange text-2xl font-normal'>
{expanded ? <DropdownArrowDown /> : <DropdownArrowUp />}
</Text>
</div>
</Disclosure.Button>
<Transition
enter='transition duration-100 ease-out'
enterFrom='transform scale-95 opacity-0'
enterTo='transform scale-100 opacity-100'
leave='transition duration-75 ease-out'
leaveFrom='transform scale-100 opacity-100'
leaveTo='transform scale-95 opacity-0'
beforeEnter={() => {
toggleSwitch();
}}
afterLeave={() => {
toggleSwitch();
}}
>
<Disclosure.Panel>
<List
draggableElements={scheduleComponents}
itemHeight={30}
listHeight={30}
listWidth={240}
gap={10}
/>
</Disclosure.Panel>
</Transition>
</Disclosure>
</div>
);
}

View File

@@ -135,8 +135,7 @@ const List: React.FC<ListProps> = ({ draggableElements, itemHeight, listHeight,
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{ width: `${listWidth}px` }}
className=''
style={{ width: `${listWidth}px`, marginBottom: `-${gap}px` }}
>
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
@@ -144,14 +143,15 @@ const List: React.FC<ListProps> = ({ draggableElements, itemHeight, listHeight,
<div
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
style={{
...draggableProvided.draggableProps.style,
// if last item, don't add margin
marginBottom: index === items.length - 1 ? '0px' : `${gap}px`,
marginBottom: `${gap}px`,
}}
>
{item.content}
{React.cloneElement(item.content, {
dragHandleProps: draggableProvided.dragHandleProps,
})}
</div>
)}
</Draggable>

View File

@@ -1,8 +1,8 @@
import clsx from 'clsx';
import React, { useState } from 'react';
import { Course, Status } from '@shared/types/Course';
import { CourseColors, pickFontColor } from '@shared/util/colors';
import { StatusIcon } from '@shared/util/icons';
import { CourseColors, getCourseColors, pickFontColor } from '@shared/util/colors';
import clsx from 'clsx';
import React from 'react';
import DragIndicatorIcon from '~icons/material-symbols/drag-indicator';
import Text from '../Text/Text';
@@ -21,7 +21,12 @@ export interface PopupCourseBlockProps {
*
* @param props PopupCourseBlockProps
*/
export default function PopupCourseBlock({ className, course, colors, dragHandleProps }: PopupCourseBlockProps): JSX.Element {
export default function PopupCourseBlock({
className,
course,
colors,
dragHandleProps,
}: PopupCourseBlockProps): JSX.Element {
// whiteText based on secondaryColor
const fontColor = pickFontColor(colors.primaryColor);
@@ -41,10 +46,7 @@ export default function PopupCourseBlock({ className, course, colors, dragHandle
>
<DragIndicatorIcon className='h-6 w-6 text-white' />
</div>
<Text
className={clsx('flex-1 py-3.5 text-ellipsis whitespace-nowrap overflow-hidden', fontColor)}
variant='h1-course'
>
<Text className={clsx('flex-1 py-3.5 truncate', fontColor)} variant='h1-course'>
<span className='px-0.5 font-450'>{course.uniqueId}</span> {course.department} {course.number} &ndash;{' '}
{course.instructors.length === 0 ? 'Unknown' : course.instructors.map(v => v.lastName)}
</Text>

View File

@@ -0,0 +1,46 @@
import clsx from 'clsx';
import React from 'react';
import DragIndicatorIcon from '~icons/material-symbols/drag-indicator';
import Text from '../Text/Text';
export type Props = {
style?: React.CSSProperties;
active?: boolean;
name: string;
dragHandleProps?: any;
};
/**
* This is a reusable dropdown component that can be used to toggle the visiblity of information
*/
export default function ScheduleListItem(props: Props) {
const { dragHandleProps } = props;
console.log(props);
return (
<div style={{ ...props.style }} className='items-center'>
<li className='text-ut-burntorange w-100% flex cursor-pointer items-center self-stretch justify-left'>
<div className='group flex justify-center'>
<div
className='flex cursor-move items-center self-stretch rounded rounded-r-0'
{...dragHandleProps}
>
<DragIndicatorIcon className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400' />
</div>
<div className='inline-flex items-center justify-center gap-1.5'>
<div className='h-5.5 w-5.5 flex items-center justify-center border-2px border-current rounded-full btn-transition group-active:scale-95'>
<div
className={clsx(
'bg-current h-3 w-3 rounded-full transition tansform scale-100 ease-out-expo duration-250',
{
'scale-0! opacity-0 ease-in-out! duration-200!': !props.active,
}
)}
/>
</div>
<Text variant='p'>{props.name}</Text>
</div>
</div>
</li>
</div>
);
}

View File

@@ -15,27 +15,19 @@ export interface ScheduleTotalHoursAndCoursesProps {
*
* @param props ScheduleTotalHoursAndCoursesProps
*/
export default function ScheduleTotalHoursAndCoursess({ scheduleName, totalHours, totalCourses }: ScheduleTotalHoursAndCoursesProps): JSX.Element {
export default function ScheduleTotalHoursAndCourses({
scheduleName,
totalHours,
totalCourses,
}: ScheduleTotalHoursAndCoursesProps): JSX.Element {
return (
<div className="min-w-64 flex flex-wrap content-center items-baseline gap-2 uppercase">
<Text
className="text-[#BF5700]"
variant='h1'
as='span'
>
<div className='min-w-64 flex whitespace-nowrap content-center items-baseline gap-2 uppercase'>
<Text className='text-[#BF5700]' variant='h1' as='span'>
{`${scheduleName}: `}
</Text>
<Text
variant='h3'
as='div'
className="flex flex-row items-center gap-2 text-[#1A2024]"
>
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-[#1A2024]'>
{`${totalHours} HOURS`}
<Text
variant='h4'
as='span'
className="text-[#333F48]"
>
<Text variant='h4' as='span' className='text-[#333F48]'>
{`${totalCourses} courses`}
</Text>
</Text>