packages
This commit is contained in:
@@ -17,6 +17,8 @@
|
|||||||
"build-storybook": "storybook build"
|
"build-storybook": "storybook build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@headlessui/react": "^1.7.18",
|
||||||
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@hello-pangea/dnd": "^16.5.0",
|
"@hello-pangea/dnd": "^16.5.0",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
|||||||
576
pnpm-lock.yaml
generated
576
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,15 @@ import { Course } from './Course';
|
|||||||
export class UserSchedule {
|
export class UserSchedule {
|
||||||
courses: Course[];
|
courses: Course[];
|
||||||
name: string;
|
name: string;
|
||||||
|
hours: number;
|
||||||
|
|
||||||
constructor(schedule: Serialized<UserSchedule>) {
|
constructor(schedule: Serialized<UserSchedule>) {
|
||||||
this.courses = schedule.courses.map(c => new Course(c));
|
this.courses = schedule.courses.map(c => new Course(c));
|
||||||
this.name = schedule.name;
|
this.name = schedule.name;
|
||||||
|
this.hours = 0;
|
||||||
|
for (const course of this.courses) {
|
||||||
|
this.hours += course.creditHours;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
containsCourse(course: Course): boolean {
|
containsCourse(course: Course): boolean {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const meta = {
|
|||||||
argTypes: {
|
argTypes: {
|
||||||
dummySchedules: { control: 'object' },
|
dummySchedules: { control: 'object' },
|
||||||
dummyActiveIndex: { control: 'number' },
|
dummyActiveIndex: { control: 'number' },
|
||||||
|
|
||||||
},
|
},
|
||||||
render: (args: any) => (
|
render: (args: any) => (
|
||||||
<div>
|
<div>
|
||||||
@@ -64,6 +65,7 @@ const schedules = [
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
name: 'Main Schedule',
|
name: 'Main Schedule',
|
||||||
|
hours: 0, // Add the missing 'hours' property
|
||||||
}),
|
}),
|
||||||
new UserSchedule({
|
new UserSchedule({
|
||||||
courses: [
|
courses: [
|
||||||
@@ -131,6 +133,7 @@ const schedules = [
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
name: 'Backup #3',
|
name: 'Backup #3',
|
||||||
|
hours: 0, // Add the missing 'hours' property
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -138,5 +141,6 @@ export const Default: Story = {
|
|||||||
args: {
|
args: {
|
||||||
dummySchedules: schedules,
|
dummySchedules: schedules,
|
||||||
dummyActiveIndex: 0,
|
dummyActiveIndex: 0,
|
||||||
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
159
src/stories/components/Dropdown.stories.tsx
Normal file
159
src/stories/components/Dropdown.stories.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import { Course, Status } from '@shared/types/Course';
|
||||||
|
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Serialized } from 'chrome-extension-toolkit';
|
||||||
|
import React from 'react';
|
||||||
|
import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting';
|
||||||
|
import { CourseSchedule } from 'src/shared/types/CourseSchedule';
|
||||||
|
import Instructor from 'src/shared/types/Instructor';
|
||||||
|
import Dropdown from 'src/views/components/common/Dropdown/Dropdown';
|
||||||
|
import ScheduleListItem from 'src/views/components/common/ScheduleListItem/ScheduleListItem';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Dropdown> = {
|
||||||
|
title: 'Components/Common/Dropdown',
|
||||||
|
component: Dropdown,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
dummySchedules: { control: 'object' },
|
||||||
|
dummyActiveIndex: { control: 'number' },
|
||||||
|
scheduleComponents: { control: 'object' },
|
||||||
|
},
|
||||||
|
render: (args: any) => (
|
||||||
|
<div className='w-80'>
|
||||||
|
<Dropdown {...args} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
} satisfies Meta<typeof Dropdown>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const schedules = [
|
||||||
|
new UserSchedule({
|
||||||
|
courses: [
|
||||||
|
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: 'Fall'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
name: 'Main Schedule',
|
||||||
|
hours: 0,
|
||||||
|
} as Serialized<UserSchedule>),
|
||||||
|
new UserSchedule({
|
||||||
|
courses: [
|
||||||
|
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: 'Fall'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
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: 'Fall'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
name: 'Backup #3',
|
||||||
|
hours: 0,
|
||||||
|
} as Serialized<UserSchedule>),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Hidden: Story = {
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=1579-5083&mode=dev',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
args: {
|
||||||
|
dummySchedules: schedules,
|
||||||
|
dummyActiveIndex: 0,
|
||||||
|
scheduleComponents: schedules.map((schedule, index) => (
|
||||||
|
<div onClick={() => {}} className='p-l-3'>
|
||||||
|
<ScheduleListItem active={index === 0} name={schedule.name} />
|
||||||
|
</div>
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UserSchedule } from '@shared/types/UserSchedule';
|
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import AddSchedule from '~icons/material-symbols/add';
|
import AddSchedule from '~icons/material-symbols/add';
|
||||||
import List from '../List/List';
|
import List from '../List/List';
|
||||||
import ScheduleListItem from '../ScheduleListItem/ScheduleListItem';
|
import ScheduleListItem from '../ScheduleListItem/ScheduleListItem';
|
||||||
@@ -15,12 +15,9 @@ export function CalendarSchedules(props: Props) {
|
|||||||
const [activeScheduleIndex, setActiveScheduleIndex] = useState(props.dummyActiveIndex || 0);
|
const [activeScheduleIndex, setActiveScheduleIndex] = useState(props.dummyActiveIndex || 0);
|
||||||
const [schedules, setSchedules] = useState(props.dummySchedules || []);
|
const [schedules, setSchedules] = useState(props.dummySchedules || []);
|
||||||
|
|
||||||
let scheduleComponents = schedules.map((schedule, index) => (
|
const scheduleComponents = schedules.map((schedule, index) => (
|
||||||
<div onClick={() => setActiveScheduleIndex(index)}>
|
<div onClick={() => setActiveScheduleIndex(index)}>
|
||||||
<ScheduleListItem
|
<ScheduleListItem active={index === activeScheduleIndex} name={schedule.name} />
|
||||||
active={index === activeScheduleIndex}
|
|
||||||
name={schedule.name}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -35,7 +32,7 @@ export function CalendarSchedules(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<List gap={10} draggableElements={scheduleComponents} itemHeight={30} listHeight={0} listWidth={240} />
|
<List gap={10} draggableElements={scheduleComponents} itemHeight={30} listHeight={30} listWidth={240} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
110
src/views/components/common/Dropdown/Dropdown.tsx
Normal file
110
src/views/components/common/Dropdown/Dropdown.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user