Compare commits

...

196 Commits

Author SHA1 Message Date
DhruvArora-03
514fbc6081 fix: comment/fix build errors 2024-02-24 21:05:22 -06:00
DhruvArora-03
0dcf1f2c04 Merge branch 'dhruv/fix-popup-main-icons' into dhruv/change-schedule-calendar-page 2024-02-24 20:59:27 -06:00
DhruvArora-03
dee20c6523 feat: fix icons on PopupMain 2024-02-24 20:52:28 -06:00
doprz
e4a368fbb6 fix: vitest path alias bug 2024-02-22 23:25:08 -06:00
doprz
29247d5dfa chore: lint-format-docs-tests-bugfixes (#105)
* docs: add jsdoc

* feat: change enums to as const objects

* chore(test): add themeColors.test.ts

* fix: fix tests and bugs with strings.ts util

* fix: path alias imports and tsconfig file bug

* fix: remove --max-warnings 0
2024-02-22 22:42:58 -06:00
Samuel Gunter
bb2efc0b46 feat: updated divider component (#99)
* feat: updated divider component

* refactor: inlined Divider's classes, simplified stories

* fix: style to unocss in story

* style: renamed variant to orientation for buttons

* docs: updated comments in Divider after prop name change
2024-02-22 21:24:41 -06:00
doprz
d5a04c745f feat: Best Practices (#102)
* feat: best practices

* feat: add tests workflow

* feat: add best-practices workflow

* fix: wrong indentation in workflow
2024-02-21 15:54:21 -06:00
doprz
f01cb070b3 feat: Conventional Commits (#103)
* feat: add commitlint and husky hook

* chore: fix indentation
2024-02-21 13:36:25 -06:00
b2eac59ae7 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-20 22:06:52 -06:00
e99664fdae Old icon removed in .tsx 2024-02-20 22:06:48 -06:00
49c0d63f0b Icon added successfully 2024-02-20 22:06:26 -06:00
knownotunknown
8c82282467 Update CalendarGrid.tsx 2024-02-20 21:51:42 -06:00
knownotunknown
0a3c31ec09 Fixed some bugs 2024-02-20 21:39:11 -06:00
knownotunknown
ce7917b474 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-20 21:20:40 -06:00
knownotunknown
a5fe6ec06b Can open tabs, updated injected popup heading. basically done 2024-02-20 21:20:37 -06:00
Samuel Gunter
5a2ee0d19a fix: change Chromatic action to build current branch, not base branch (#100) 2024-02-20 17:20:28 -06:00
knownotunknown
9ec0d106f5 Chrome extension works 2024-02-19 23:03:53 -06:00
knownotunknown
70a3f14e0a Squashed commit of the following:
commit c46e4a51c9
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 21:37:46 2024 -0600

    change from reducer pattern to state variables, remove chartData from state

commit 36bcdd2522
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 21:15:41 2024 -0600

    change grade distribution colors to match updated figma

commit 11a50df88d
Merge: c16b301 b4c96a9
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 17:57:13 2024 -0600

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

commit c16b301ff0
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 17:47:21 2024 -0600

    Kinda complete the handlers

commit 1ac1d9095a
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 17:36:59 2024 -0600

    Bunch of renaming

commit 925829ad41
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 17:24:53 2024 -0600

    Fix syllabi url

    Remove unused variable and unnecessary args to url

commit f2e5d51eb3
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 17:24:22 2024 -0600

    Add TODO

    replace current grade colors with a tailwind palette

commit 747ee44440
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 01:26:51 2024 -0600

    Minor tweaks

    change style in header

commit ddfe952a32
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 01:26:38 2024 -0600

    Add Grade Distribution Stuff

commit c27bf3c390
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 01:26:13 2024 -0600

    Modify story to use proper course info

commit 7afdbac1b8
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 16:37:01 2024 -0600

    description stuff done

commit 1a89432276
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 15:26:32 2024 -0600

    Rename CoursePopup

    Old one to "Old", remove "2" from new one

commit 4c2b31e61a
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 15:23:01 2024 -0600

    add todo for calendar button

commit 11b7a51ded
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 15:22:18 2024 -0600

    add course button onclick handlers

commit f2dfcec838
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 14:52:38 2024 -0600

    some unocss updates

commit f9f375514b
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 13:00:46 2024 -0600

    Add rmp callback

commit 122fc6dbdd
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 13:00:16 2024 -0600

    Change test course to 314

commit 19b124b3bd
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 12:19:21 2024 -0600

    complete CourseHeaderAndActions Component

    added course buttons, using proper subcomponents now.

commit 2eea01fc74
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 11:22:12 2024 -0600

    use chip component in header

commit 9cb13c8fd1
Merge: a62b718 9392085
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 11:21:12 2024 -0600

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

commit a62b718c43
Merge: 43d2675 7b7b858
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 10:57:24 2024 -0600

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

commit 43d2675be5
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 10:54:49 2024 -0600

    some work on course popup

    update the stories and create the header component

commit 31bcef3099
Merge: 874f8d5 fa1d737
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Wed Feb 14 14:33:16 2024 -0600

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

    pulling from main

commit 874f8d56cb
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Wed Feb 14 14:30:24 2024 -0600

    some work
2024-02-19 22:39:26 -06:00
knownotunknown
d69707b8e8 Squashed commit of the following:
commit f6896e37e2
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Mon Feb 19 20:46:57 2024 -0600

    Calendar Page mostly styled

commit a28422e6b0
Merge: 297601e 41e6d77
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Mon Feb 19 18:46:59 2024 -0600

    Merge branch 'hackathon' into Som

commit 297601e715
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sun Feb 18 16:28:29 2024 -0600

    Grid works cleanly with up to two course conflicts. Prob needs refactoring

commit 313a9648c9
Merge: b0a95a6 0acd0b7
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sun Feb 18 15:45:57 2024 -0600

    Merge branch 'hackathon' into Som

commit b0a95a6153
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sun Feb 18 14:10:13 2024 -0600

    Made CourseCells and CalendarGridCells more responsive. CourseCells now rendering onto grid. Still, need to make course cells more responsive, and add edge cases

commit a1a0f00514
Merge: 7479004 ac71b83
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sat Feb 17 17:07:06 2024 -0600

    Merge branch 'hackathon' into Som

commit 7479004a65
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sat Feb 17 16:59:34 2024 -0600

    Need to add sorting
2024-02-19 20:48:00 -06:00
knownotunknown
41e6d77d02 Fixed import error 2024-02-19 18:34:42 -06:00
knownotunknown
11fece0595 Merged in finished CalendarGrid 2024-02-19 18:27:06 -06:00
knownotunknown
c676be4765 Fixed build errors and restructured Calendar page 2024-02-19 18:26:09 -06:00
knownotunknown
b4c96a9a10 Fixed build errors and merging in Casey's branch (driodiwb) 2024-02-19 12:03:23 -06:00
knownotunknown
adba5e1bbc Merge remote-tracking branch 'origin/PopupMain' into hackathon 2024-02-19 11:52:48 -06:00
DhruvArora-03
58dc706ece Merge branch 'feat-conflict-row' into hackathon 2024-02-19 11:29:56 -06:00
DhruvArora-03
70c75ff481 add button to the rows, use new ConflictsWithWarning component 2024-02-19 11:29:24 -06:00
DhruvArora-03
7f76af7ab3 fix ConflictsWithWarning 2024-02-19 11:28:33 -06:00
DhruvArora-03
a538f20aad Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-19 11:11:37 -06:00
Lukas Zenick
0acd0b722c html2canvas -> htmlToImage
also fixed derick's bugs
2024-02-18 12:46:35 -06:00
ae32d0b645 feat: Derek/export png (#95)
* Attempting to use more lightweight version

* Did not work.

* This is not what I wanted

* The image saves correctly. Needs padding

* Padding !!

* Removed downloadjs

* Padding more
2024-02-17 19:09:41 -06:00
DhruvArora-03
f214ed7c01 Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-17 18:21:39 -06:00
Lukas Zenick
e44b0c0e45 FIX README 2024-02-17 17:24:27 -06:00
Lukas Zenick
206c97c5b5 fixed README 2024-02-17 17:23:04 -06:00
ac71b838db feat: Derek vinson/calendar header (#94)
* CalendarHeader alignment progress

* Boom

* css

* Between

* Lol

* Gap fix

* whitespace-nowrap

* Gaps

* Finished alignment of CourseStatus and Buttons

* Colors

* ESLint auto format

* Color UT Registration Plus text

* Reverting vscode

---------

Co-authored-by: Vinson Zheng <vinsonzheng499@gmail.com>
2024-02-17 17:02:36 -06:00
Abhinav Chadaga
4f5753917b sort the flattened course schedule (#93)
first by dayIndex, then startIndex, then endIndex.
2024-02-17 16:57:51 -06:00
DhruvArora-03
478ab31706 Merge branch 'testing-useFlattenedCourseSchedule' into hackathon 2024-02-17 16:37:58 -06:00
DhruvArora-03
79c5c97d98 fix extra space 2024-02-17 16:37:35 -06:00
Samuel Gunter
42420f5502 feat: bottom bar for the calendar page (#91) 2024-02-17 16:25:50 -06:00
DhruvArora-03
0ced198f01 remove empty file 2024-02-17 16:23:17 -06:00
vivek12311
ff4ee494b6 Using React-icons. Probably need to take out later 2024-02-17 16:03:28 -06:00
Lukas Zenick
a03bcf17b8 broken file 2024-02-17 15:49:52 -06:00
DhruvArora-03
314ea09b41 nit: reorder classnames 2024-02-17 15:45:56 -06:00
DhruvArora-03
1891bd941e Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:45:34 -06:00
Lukas Zenick
af991e6609 broken close bracket 2024-02-17 15:42:30 -06:00
Lukas Zenick
fe3521dec7 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:40:47 -06:00
Lukas Zenick
a363efb2d2 Save as PNG functionality 2024-02-17 15:40:42 -06:00
knownotunknown
e7ba9c54a6 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:33:29 -06:00
knownotunknown
d69e3435cf WIP 2024-02-17 15:32:42 -06:00
vivek12311
b554b4344d Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:31:05 -06:00
DhruvArora-03
14f241d603 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:30:16 -06:00
DhruvArora-03
419e3066f1 cleanup useFlattenedCourseSchedule 2024-02-17 15:30:14 -06:00
vivek12311
ed3ff846d8 Merge branch 'PopupMain' into hackathon 2024-02-17 15:26:10 -06:00
Lukas Zenick
7132bcf572 adjust location of svg files 2024-02-17 15:26:06 -06:00
vivek12311
0bc30d3d1e test 2024-02-17 15:25:58 -06:00
Lukas Zenick
3878c7104e cal save buttons (no functionality) 2024-02-17 15:15:24 -06:00
knownotunknown
851947db0b Update 2024-02-17 15:03:18 -06:00
Casey Charleston
724e1a1d19 Calendar schedules component (clicking on storybook not working) 2024-02-17 14:40:20 -06:00
Abhinav Chadaga
66fea21abd Merge branch 'hackathon' of https://github.com/ut-developers/UT-Registration-Plus into hackathon 2024-02-17 14:37:59 -06:00
Abhinav Chadaga
7550b55b9b Update CalendarCourseCell.tsx 2024-02-17 14:37:48 -06:00
Abhinav Chadaga
65f0cb27af Merge branch 'hackathon' of https://github.com/ut-developers/UT-Registration-Plus into hackathon 2024-02-17 14:33:51 -06:00
DhruvArora-03
9ce69c2f2e Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 14:32:34 -06:00
DhruvArora-03
a46526fa40 working on course popup - stopping because abhinav already doing 2024-02-17 14:32:28 -06:00
Abhinav Chadaga
c5968f3f11 Update CalendarCourseCell.tsx 2024-02-17 14:31:34 -06:00
Abhinav Chadaga
bba067f591 implement flatten course schedule helper function
takes a course schedule and returns an array of objects that can be used to render all the individual course
2024-02-17 14:30:59 -06:00
knownotunknown
71d8ac7486 Update CalendarGrid.stories.tsx 2024-02-17 14:30:15 -06:00
knownotunknown
2997cb87d4 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 14:29:06 -06:00
knownotunknown
8b2d07033c update 2024-02-17 14:27:32 -06:00
Vinson Zheng
b8fe5109a9 Added CalendarHeader and its Storybook, need to resize 2024-02-17 14:22:24 -06:00
Razboy20
2cffb794db update course block names 2024-02-17 13:32:06 -06:00
Razboy20
73fe14e17a fix calendar course cell spacing 2024-02-17 13:27:07 -06:00
knownotunknown
33ca937d12 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 13:08:43 -06:00
knownotunknown
c0968ef7a0 Revert "Revert "update""
This reverts commit 3c7b35d5f3.
2024-02-17 13:07:43 -06:00
knownotunknown
3c7b35d5f3 Revert "update"
This reverts commit 8edd062588.
2024-02-17 13:06:46 -06:00
knownotunknown
8edd062588 update 2024-02-17 13:02:24 -06:00
DhruvArora-03
42d24f6367 create empty settings component - waiting on design 2024-02-17 13:02:06 -06:00
Razboy20
2f19c57553 fix margins on list component 2024-02-17 12:59:43 -06:00
Razboy20
e3b8e4c270 fix calendar course cell colors 2024-02-17 12:54:10 -06:00
DhruvArora-03
5aef43496f create important links component 2024-02-17 12:44:05 -06:00
Razboy20
e1224e11af Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:24:49 -06:00
Razboy20
7f826c2a78 eslint on save 2024-02-17 12:24:46 -06:00
knownotunknown
89a8e42059 Made draggable only by handle 2024-02-17 12:23:52 -06:00
knownotunknown
ed8915bcd1 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:09:10 -06:00
knownotunknown
1168584b8a Update 2024-02-17 12:09:01 -06:00
Razboy20
a0496fea18 Merge branch 'hackathon' of github.com:UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:07:56 -06:00
Lukas Zenick
fd747e9e8e Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:04:42 -06:00
Lukas Zenick
27fdd9c559 fix undefined color case 2024-02-17 12:04:41 -06:00
Vinson Zheng
ba22ba427c Reverted CalendarGrid and CalendarGridCell back to SCSS from Tailwind 2024-02-17 11:54:50 -06:00
Razboy20
3e4cec43d1 update button stylings 2024-02-17 11:41:48 -06:00
Razboy20
633c9ba593 fix unocss theme color namings 2024-02-17 11:41:36 -06:00
knownotunknown
0f14b8ce1b Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 11:26:51 -06:00
knownotunknown
bee506b79b Merge branch 'pr/85' into hackathon 2024-02-17 11:25:33 -06:00
DhruvArora-03
d8a4c91a80 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 11:24:41 -06:00
DhruvArora-03
14ab537db1 add JSdoc to calendar page 2024-02-17 11:24:40 -06:00
knownotunknown
939208532b Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 11:07:35 -06:00
knownotunknown
8e3502593e Converted all LabelsAndDetails Components to Tailwind 2024-02-17 11:06:36 -06:00
DhruvArora-03
7b7b858cf5 undo dummy commit 2024-02-17 10:55:44 -06:00
DhruvArora-03
82b467a223 dummy commit 2024-02-17 10:53:44 -06:00
knownotunknown
f049a25aac Merge branch 'pr/68' into hackathon 2024-02-17 10:52:02 -06:00
knownotunknown
39b4396f88 Merge branch 'NonVirtualList' into hackathon 2024-02-17 10:49:39 -06:00
knownotunknown
854d24bf0d Changed Chip to tailwind css. Fixed eslint for ConflictsWithWarning 2024-02-17 10:48:25 -06:00
knownotunknown
c47320e9a3 Merge branch 'LabelsAndDetails' into hackathon 2024-02-17 10:40:35 -06:00
knownotunknown
982b7de50e Merge branch 'ScheduleTotalHoursAndCourses' into hackathon 2024-02-17 10:40:16 -06:00
knownotunknown
17efb94e68 Merge branch 'InfoCard' into hackathon 2024-02-17 10:40:01 -06:00
knownotunknown
40631e2421 Merge branch 'ConflictsWithWarningComponent' into hackathon 2024-02-17 10:39:51 -06:00
knownotunknown
81e02c8187 Build without errors 2024-02-17 10:36:28 -06:00
vivek12311
d907a43552 Merge branch 'pr/90' into PopupMain 2024-02-16 17:16:49 -06:00
Vinson Zheng
3e1a20a9f2 Deleted scss files 2024-02-16 16:34:56 -06:00
Vinson Zheng
d21843468b Tailwind conversion halfway done 2024-02-16 16:34:06 -06:00
vinsonzheng499
6b7e45b5f4 Merge branch 'UT-Developers:main' into master 2024-02-16 16:05:29 -06:00
Razboy20
36b9189a6c fix non-virtual dnd 2024-02-14 19:25:04 -06:00
knownotunknown
80148ba0ff Early iteration of non-virtual list 2024-02-14 17:55:19 -06:00
knownotunknown
776cab9297 Merge branch 'main' into ListComponentContinuation 2024-02-14 14:23:45 -06:00
knownotunknown
f95736339a Update CalendarCourse.stories.tsx 2024-02-14 13:24:55 -06:00
knownotunknown
3c83a3c064 Eslint, removed React-beautiful-dnd 2024-02-14 13:23:22 -06:00
Samuel Gunter
0928e8d033 chore: merge branch 'main' into sgunter/button-component 2024-02-13 16:54:36 -06:00
vivek12311
3b9ea8ebd5 Popup 2024-02-13 12:53:51 -06:00
Samuel Gunter
b2e7af64b3 fix: only show button hover effects when not disabled 2024-02-12 01:51:46 -06:00
Samuel Gunter
11ce6be578 chore: suppress useless TS warnings 2024-02-11 23:38:24 -06:00
Samuel Gunter
524e3b46d3 chore: removed unused Button.module.scss 2024-02-11 23:29:42 -06:00
Samuel Gunter
000682b4db feat: buttons with icons in tailwind
i am not proud of some of this code
2024-02-11 23:24:24 -06:00
147d38d5c5 Lined em up 2024-02-10 18:21:18 -06:00
knownotunknown
14a90f3dc0 Made List more extensible 2024-02-10 15:09:49 -06:00
dcc7c731c6 Merge branch 'sghsri:main' into master 2024-02-09 18:40:39 -06:00
ad77d73363 Warning 2024-02-09 17:57:48 -06:00
80ee5bfdda Wat 2024-02-09 17:56:29 -06:00
e93c5d3167 Brought back coursemeeting.ts 2024-02-09 17:54:25 -06:00
5ec0f19c53 Merge branch 'master' of https://github.com/DereC4/UT-Registration-Plus 2024-02-09 17:47:24 -06:00
24da9baffe Y 2024-02-09 17:47:12 -06:00
de16ece2ea Merge branch 'sghsri:main' into master 2024-02-09 17:40:52 -06:00
d42ba72170 Correcting text 2024-02-09 17:35:52 -06:00
64563b2f40 Merge branch 'sghsri:main' into master 2024-02-09 17:27:14 -06:00
18dcf8a139 Cool looking tickmarks 2024-02-09 17:26:26 -06:00
72ecb314e8 Final polishing up 2024-02-09 17:20:46 -06:00
0ce6de8b13 ...now WE GOT IT 2024-02-09 17:13:02 -06:00
fa30c526b9 WE GOT IT 2024-02-09 17:12:39 -06:00
c44fd014e9 ARRAY.FROM PAIN 2024-02-09 17:07:21 -06:00
d44f5216f8 Fixing alignment 2024-02-09 16:55:18 -06:00
Vinson Zheng
f1e8485eb5 Small style adjustments 2024-02-09 16:00:38 -06:00
knownotunknown
b57415c846 Updated packages installed 2024-02-09 15:59:31 -06:00
knownotunknown
ff06d05857 Merge branch 'main' into ListComponentContinuation 2024-02-09 15:54:44 -06:00
knownotunknown
1a4d22ccf0 Updated alignment 2024-02-09 14:33:11 -06:00
knownotunknown
279ac076b0 Updated with Tailwind 2024-02-09 12:05:02 -06:00
knownotunknown
e3301cc200 Done, but might want to update font size 2024-02-09 11:57:22 -06:00
knownotunknown
ccf2c68340 Update ConflictsWithWarning.tsx 2024-02-09 11:01:42 -06:00
knownotunknown
dc19d3975a Implemented ConflictsWithWarning 2024-02-09 10:53:41 -06:00
Samuel Gunter
2eaf1b3c36 chore: merge branch 'main' into sgunter/button-component 2024-02-08 14:08:44 -06:00
Samuel Gunter
16cc2f071e chore: merge branch 'main' into sgunter/button-component
for clsx, and other features
2024-02-07 00:27:55 -06:00
Samuel Gunter
ae08ee02f4 feat: tailwind version of Button component
still WIP, need to figure out icon spacing exactly
2024-02-06 23:56:35 -06:00
4f3ccd9c90 stories for grid cell 2024-02-05 23:22:56 -06:00
aaccd9b562 Combine Days with Hours 2024-02-05 23:12:51 -06:00
349e1e5332 Merge branch 'master' of https://github.com/DereC4/UT-Registration-Plus 2024-02-05 23:12:12 -06:00
08e66c95e3 Now the Day labels are aligned and part of the grid 2024-02-05 23:05:15 -06:00
873be55b8b Filter Function replacing double pop(); 2024-02-05 23:00:42 -06:00
c20fb0827f Space bar 2024-02-05 22:54:23 -06:00
1c0ad51914 Reverted settings.json 2024-02-05 22:54:04 -06:00
d81ec5c2fc Exited Suez Canal 2024-02-05 22:45:04 -06:00
4bc65ec171 Got Delayed in the Suez Canal 2024-02-05 22:24:20 -06:00
e630bc82ee Time Column Being Transported via Crane 2024-02-05 22:23:03 -06:00
36314e0e3b Preparing to re-add hours to the left side 2024-02-05 22:11:58 -06:00
cdc1fc9224 Merge branch 'master' of https://github.com/DereC4/UT-Registration-Plus 2024-02-05 22:11:40 -06:00
be76f4bcc7 Merge branch 'sghsri:main' into master 2024-02-05 22:10:53 -06:00
a71bc0f9c3 THEY'RE EVOLVING CUBES 2024-02-05 22:05:20 -06:00
3d95d03813 THE CUBES HAVE LISTENED TO ME 2024-02-05 22:02:29 -06:00
d1720f3356 Retrying 2024-02-05 21:58:40 -06:00
8a317c590b Tactically Retreating 2024-02-05 21:47:10 -06:00
9ab86cfab1 Off center now 2024-02-05 21:38:34 -06:00
01a3918502 Reverts in Stylesheets 2024-02-05 21:33:59 -06:00
7a014a7aab Screams in Stylesheets 2024-02-05 21:33:40 -06:00
618089b17e Format 2024-02-05 21:15:38 -06:00
96dbe40637 Merge branch 'sghsri:main' into master 2024-02-05 21:03:37 -06:00
Samuel Gunter
13bc06cc6d chore: merge branch 'main' into sgunter/button-component 2024-02-03 15:22:27 -06:00
knownotunknown
429b49111a InfoCard implemented 2024-02-03 15:14:07 -06:00
knownotunknown
6e595d21aa Update Chip.stories.tsx 2024-02-03 14:29:07 -06:00
knownotunknown
80043dc652 Deleted unnecessary imports 2024-02-03 14:20:48 -06:00
knownotunknown
b470bf6996 Chip Implemented
Styled inline since I'll need to rewrite in TailwindCSS anyways
2024-02-03 14:15:18 -06:00
Samuel Gunter
e0bf48805a chore: merge branch 'main' into sgunter/button-component 2024-02-03 13:29:43 -06:00
bb2caa2dda Path alias 2024-02-03 00:47:07 -06:00
knownotunknown
dad74ddf4e Drag only on vertical axis
Made this change a while ago, but didn't push since it looks like we might switch to Elie's DnD. Pushing since it looks like there might still be reviewers.
2024-02-03 00:11:46 -06:00
cbf31aecd5 Storybook Story 2024-02-02 22:14:35 -06:00
6aab174618 Imported DAY_MAP 2024-02-02 21:52:46 -06:00
8f2b61d28a Ok 2024-02-02 21:32:38 -06:00
fb6781e17f Colors modules switch 2024-02-02 20:44:52 -06:00
1683d8c48b Docs 2024-02-02 20:42:11 -06:00
e9f95ad3d8 Docs and needed changes 2024-02-02 20:41:14 -06:00
745b83e945 CalendarGridCell 2024-02-02 20:38:28 -06:00
b0dec80364 Camelcase conversion for css modules 2024-02-02 20:27:18 -06:00
Samuel Gunter
03e4ab9d60 chore: merge branch 'main' into sgunter/button-component 2024-02-02 20:26:02 -06:00
9854c9aafa Merge branch 'sghsri:main' into master 2024-02-02 20:25:58 -06:00
b161e319bd Merge branch 'sghsri:main' into master 2024-02-02 20:09:22 -06:00
knownotunknown
ae0d1a3c67 Revert "rename to course block and fix line height for styling"
This reverts commit 77a1d67af3.
2024-02-02 19:59:52 -06:00
knownotunknown
3da4a395dd Implemented List component
Used CalendarCourseMeeting component in story for demonstration purposes
2024-02-02 13:08:55 -06:00
abhinavchadaga
77a1d67af3 rename to course block and fix line height for styling 2024-02-01 20:55:13 -06:00
abhinavchadaga
856d6cda24 change the default course 2024-01-31 13:10:29 -06:00
abhinavchadaga
cbbea7f810 basic component laid out - missing Text and Right Icon, can add more stories 2024-01-31 03:27:37 -06:00
Samuel Gunter
cedaa27a2c feat: updated Button to v2 design
wip commit
2024-01-31 00:11:01 -06:00
72b7a9d7b1 Calendar Components 3rd Attempt at Merging 2024-01-30 21:17:19 -06:00
173 changed files with 10407 additions and 3900 deletions

108
.eslintrc
View File

@@ -4,12 +4,9 @@
"browser": true, "browser": true,
"es6": true, "es6": true,
"node": true, "node": true,
"webextensions": true "webextensions": true,
}, },
"ignorePatterns": [ "ignorePatterns": ["*.html", "tsconfig.json"],
"*.html",
"tsconfig.json"
],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:react/recommended", "plugin:react/recommended",
@@ -21,18 +18,14 @@
"@unocss", "@unocss",
"prettier", "prettier",
], ],
"plugins": [ "plugins": ["import", "jsdoc", "react-prefer-function-component", "@typescript-eslint", "simple-import-sort"],
"import",
"jsdoc",
"react-prefer-function-component"
],
"globals": { "globals": {
"Atomics": "readonly", "Atomics": "readonly",
"SharedArrayBuffer": "readonly", "SharedArrayBuffer": "readonly",
"debugger": true, "debugger": true,
"browser": true, "browser": true,
"context": true, "context": true,
"JSX": true "JSX": true,
}, },
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
@@ -42,37 +35,35 @@
"ecmaFeatures": { "ecmaFeatures": {
"jsx": true, "jsx": true,
"modules": true, "modules": true,
"experimentalObjectRestSpread": true "experimentalObjectRestSpread": true,
} },
}, },
"settings": { "settings": {
"react": { "react": {
"version": "detect" "version": "detect",
}, },
"jsdoc": { "jsdoc": {
"mode": "typescript" "mode": "typescript",
}, },
"import/parsers": { "import/parsers": {
"@typescript-eslint/parser": [ "@typescript-eslint/parser": [".ts", ".tsx"],
".ts",
".tsx"
]
}, },
"import/resolver": { "import/resolver": {
"typescript": { "typescript": {
"alwaysTryTypes": true, "alwaysTryTypes": true,
"project": "./tsconfig.json" "project": "./tsconfig.json",
} },
} },
}, },
"rules": { "rules": {
"prefer-const": [ "prefer-const": [
"off", "off",
{ {
"destructuring": "any", "destructuring": "any",
"ignoreReadBeforeAssign": false "ignoreReadBeforeAssign": false,
} },
], ],
"no-plusplus": "off",
"no-inner-declarations": "off", "no-inner-declarations": "off",
"sort-imports": "off", "sort-imports": "off",
"no-case-declarations": "off", "no-case-declarations": "off",
@@ -82,20 +73,16 @@
"no-undef": "off", "no-undef": "off",
"no-return-await": "off", "no-return-await": "off",
"@typescript-eslint/return-await": "off", "@typescript-eslint/return-await": "off",
"@typescript-eslint/no-shadow": [ "@typescript-eslint/no-shadow": ["off"],
"off" "@typescript-eslint/no-use-before-define": ["off"],
],
"@typescript-eslint/no-use-before-define": [
"off"
],
"class-methods-use-this": "off", "class-methods-use-this": "off",
"react-hooks/exhaustive-deps": "warn", "react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/lines-between-class-members": "off", "@typescript-eslint/lines-between-class-members": "off",
"no-param-reassign": [ "no-param-reassign": [
"error", "error",
{ {
"props": false "props": false,
} },
], ],
"no-console": "off", "no-console": "off",
"consistent-return": "off", "consistent-return": "off",
@@ -109,8 +96,8 @@
"error", "error",
{ {
"before": true, "before": true,
"after": true "after": true,
} },
], ],
"no-continue": "off", "no-continue": "off",
"space-before-blocks": [ "space-before-blocks": [
@@ -118,24 +105,22 @@
{ {
"functions": "always", "functions": "always",
"keywords": "always", "keywords": "always",
"classes": "always" "classes": "always",
} },
], ],
"react/jsx-filename-extension": [ "react/jsx-filename-extension": [
1, 1,
{ {
"extensions": [ "extensions": [".tsx"],
".tsx" },
]
}
], ],
"react/no-deprecated": "warn", "react/no-deprecated": "warn",
"react/prop-types": "off", "react/prop-types": "off",
"react-prefer-function-component/react-prefer-function-component": [ "react-prefer-function-component/react-prefer-function-component": [
"warn", "warn",
{ {
"allowComponentDidCatch": false "allowComponentDidCatch": false,
} },
], ],
"react/function-component-definition": "off", "react/function-component-definition": "off",
"react/button-has-type": "off", "react/button-has-type": "off",
@@ -153,7 +138,7 @@
"ArrowFunctionExpression": true, "ArrowFunctionExpression": true,
"ClassDeclaration": true, "ClassDeclaration": true,
"ClassExpression": true, "ClassExpression": true,
"FunctionExpression": true "FunctionExpression": true,
}, },
"contexts": [ "contexts": [
"MethodDefinition:not([key.name=\"componentDidMount\"]):not([key.name=\"render\"])", "MethodDefinition:not([key.name=\"componentDidMount\"]):not([key.name=\"render\"])",
@@ -168,9 +153,9 @@
"TSInterfaceDeclaration", "TSInterfaceDeclaration",
"TSMethodSignature", "TSMethodSignature",
"TSModuleDeclaration", "TSModuleDeclaration",
"TSTypeAliasDeclaration" "TSTypeAliasDeclaration",
] ],
} },
], ],
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-unused-vars": "warn",
@@ -185,31 +170,36 @@
{ {
"target": "./src/background", "target": "./src/background",
"from": "./src/views", "from": "./src/views",
"message": "You cannot import into the `background` directory from the `views` directory (i.e. content script files) because it will break the build!" "message": "You cannot import into the `background` directory from the `views` directory (i.e. content script files) because it will break the build!",
}, },
{ {
"target": "./src/views", "target": "./src/views",
"from": "./src/background", "from": "./src/background",
"message": "You cannot import into the `views` directory from the `background` directory (i.e. background script files) because it will break the build!" "message": "You cannot import into the `views` directory from the `background` directory (i.e. background script files) because it will break the build!",
}, },
{ {
"target": "./src/shared", "target": "./src/shared",
"from": "./", "from": "./",
"except": [ "except": ["./src/shared", "./node_modules"],
"./src/shared", "message": "You cannot import into `shared` from an external directory.",
"./node_modules" },
], ],
"message": "You cannot import into `shared` from an external directory." },
}
]
}
], ],
"import/extensions": "off", "import/extensions": "off",
"no-restricted-syntax": [ "no-restricted-syntax": [
"error", "error",
"ForInStatement", "ForInStatement",
"LabeledStatement", "LabeledStatement",
"WithStatement" "WithStatement",
] {
} "selector": "TSEnumDeclaration",
"message": "Don't declare enums",
},
],
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/consistent-type-imports": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
},
} }

43
.github/workflows/best-practices.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Best Practices
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install dependencies
run: pnpm install
- name: Run ESLint
run: pnpm run lint
format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install dependencies
run: pnpm install
- name: Run Prettier
run: pnpm run prettier

View File

@@ -1,26 +1,26 @@
name: "Chromatic" name: 'Chromatic'
on: [push, pull_request_target] on: [push, pull_request]
jobs: jobs:
chromatic: chromatic:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v3 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install
- name: Publish to Chromatic - name: Publish to Chromatic
uses: chromaui/action@latest uses: chromaui/action@latest
with: with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitZeroOnChanges: true exitZeroOnChanges: true
autoAcceptChanges: "main" autoAcceptChanges: 'main'

24
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm test

1
.husky/commit-msg Normal file
View File

@@ -0,0 +1 @@
npx --no -- commitlint --edit $1

View File

@@ -34,4 +34,4 @@
} }
] ]
] ]
} }

View File

@@ -0,0 +1,30 @@
// .storybook/vite-storybook.config.ts
import react from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/@vitejs+plugin-react-swc@3.6.0_vite@5.1.2/node_modules/@vitejs/plugin-react-swc/index.mjs";
import { resolve } from "path";
import UnoCSS from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/unocss@0.58.5_postcss@8.4.35_vite@5.1.2/node_modules/unocss/dist/vite.mjs";
import Icons from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/unplugin-icons@0.18.5_@svgr+core@8.1.0/node_modules/unplugin-icons/dist/vite.js";
import { defineConfig } from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/vite@5.1.2_@types+node@20.11.17_sass@1.70.0/node_modules/vite/dist/node/index.js";
var __vite_injected_original_dirname = "C:\\Users\\somgu\\OneDrive\\Desktop\\UT-Registration-Plus\\.storybook";
var root = resolve(__vite_injected_original_dirname, "../src");
var pagesDir = resolve(root, "pages");
var assetsDir = resolve(root, "assets");
var publicDir = resolve(__vite_injected_original_dirname, "../public");
console.log(root);
var vite_storybook_config_default = defineConfig({
plugins: [react(), UnoCSS(), Icons({ compiler: "jsx", jsx: "react" })],
resolve: {
alias: {
src: root,
"@assets": assetsDir,
"@pages": pagesDir,
"@public": publicDir,
"@shared": resolve(root, "shared"),
"@background": resolve(pagesDir, "background"),
"@views": resolve(root, "views")
}
}
});
export {
vite_storybook_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLnN0b3J5Ym9vay92aXRlLXN0b3J5Ym9vay5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxzb21ndVxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXFVULVJlZ2lzdHJhdGlvbi1QbHVzXFxcXC5zdG9yeWJvb2tcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFVzZXJzXFxcXHNvbWd1XFxcXE9uZURyaXZlXFxcXERlc2t0b3BcXFxcVVQtUmVnaXN0cmF0aW9uLVBsdXNcXFxcLnN0b3J5Ym9va1xcXFx2aXRlLXN0b3J5Ym9vay5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0M6L1VzZXJzL3NvbWd1L09uZURyaXZlL0Rlc2t0b3AvVVQtUmVnaXN0cmF0aW9uLVBsdXMvLnN0b3J5Ym9vay92aXRlLXN0b3J5Ym9vay5jb25maWcudHNcIjtpbXBvcnQgcmVhY3QgZnJvbSAnQHZpdGVqcy9wbHVnaW4tcmVhY3Qtc3djJztcbmltcG9ydCB7IHJlc29sdmUgfSBmcm9tICdwYXRoJztcbmltcG9ydCBVbm9DU1MgZnJvbSAndW5vY3NzL3ZpdGUnO1xuaW1wb3J0IEljb25zIGZyb20gJ3VucGx1Z2luLWljb25zL3ZpdGUnO1xuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XG5cbmNvbnN0IHJvb3QgPSByZXNvbHZlKF9fZGlybmFtZSwgJy4uL3NyYycpO1xuY29uc3QgcGFnZXNEaXIgPSByZXNvbHZlKHJvb3QsICdwYWdlcycpO1xuY29uc3QgYXNzZXRzRGlyID0gcmVzb2x2ZShyb290LCAnYXNzZXRzJyk7XG5jb25zdCBwdWJsaWNEaXIgPSByZXNvbHZlKF9fZGlybmFtZSwgJy4uL3B1YmxpYycpO1xuXG5jb25zb2xlLmxvZyhyb290KTtcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gICAgcGx1Z2luczogW3JlYWN0KCksIFVub0NTUygpLCBJY29ucyh7IGNvbXBpbGVyOiAnanN4JywganN4OiAncmVhY3QnIH0pXSxcbiAgICByZXNvbHZlOiB7XG4gICAgICAgIGFsaWFzOiB7XG4gICAgICAgICAgICBzcmM6IHJvb3QsXG4gICAgICAgICAgICAnQGFzc2V0cyc6IGFzc2V0c0RpcixcbiAgICAgICAgICAgICdAcGFnZXMnOiBwYWdlc0RpcixcbiAgICAgICAgICAgICdAcHVibGljJzogcHVibGljRGlyLFxuICAgICAgICAgICAgJ0BzaGFyZWQnOiByZXNvbHZlKHJvb3QsICdzaGFyZWQnKSxcbiAgICAgICAgICAgICdAYmFja2dyb3VuZCc6IHJlc29sdmUocGFnZXNEaXIsICdiYWNrZ3JvdW5kJyksXG4gICAgICAgICAgICAnQHZpZXdzJzogcmVzb2x2ZShyb290LCAndmlld3MnKSxcbiAgICAgICAgfSxcbiAgICB9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWlaLE9BQU8sV0FBVztBQUNuYSxTQUFTLGVBQWU7QUFDeEIsT0FBTyxZQUFZO0FBQ25CLE9BQU8sV0FBVztBQUNsQixTQUFTLG9CQUFvQjtBQUo3QixJQUFNLG1DQUFtQztBQU16QyxJQUFNLE9BQU8sUUFBUSxrQ0FBVyxRQUFRO0FBQ3hDLElBQU0sV0FBVyxRQUFRLE1BQU0sT0FBTztBQUN0QyxJQUFNLFlBQVksUUFBUSxNQUFNLFFBQVE7QUFDeEMsSUFBTSxZQUFZLFFBQVEsa0NBQVcsV0FBVztBQUVoRCxRQUFRLElBQUksSUFBSTtBQUdoQixJQUFPLGdDQUFRLGFBQWE7QUFBQSxFQUN4QixTQUFTLENBQUMsTUFBTSxHQUFHLE9BQU8sR0FBRyxNQUFNLEVBQUUsVUFBVSxPQUFPLEtBQUssUUFBUSxDQUFDLENBQUM7QUFBQSxFQUNyRSxTQUFTO0FBQUEsSUFDTCxPQUFPO0FBQUEsTUFDSCxLQUFLO0FBQUEsTUFDTCxXQUFXO0FBQUEsTUFDWCxVQUFVO0FBQUEsTUFDVixXQUFXO0FBQUEsTUFDWCxXQUFXLFFBQVEsTUFBTSxRQUFRO0FBQUEsTUFDakMsZUFBZSxRQUFRLFVBQVUsWUFBWTtBQUFBLE1BQzdDLFVBQVUsUUFBUSxNQUFNLE9BQU87QUFBQSxJQUNuQztBQUFBLEVBQ0o7QUFDSixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=

View File

@@ -1,5 +1,8 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": { "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
@@ -34,4 +37,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
} "tailwindCSS.includeLanguages": {
"plaintext": "javascript"
}
}

View File

@@ -2,17 +2,29 @@
## Built Using ## Built Using
- React 18 - React 18
- TypeScript - TypeScript
- Vite 5 - Vite 5
- ESLint - ESLint
- Prettier - Prettier
- Semantic-Release - Semantic-Release
- Custom Messaging & Storage Wrappers - Custom Messaging & Storage Wrappers
## Getting Started ## Getting Started
1. Clone this repo 1. Clone this repo
2. Run `pnpm install` to install and patch all the required dependencies 2. Run `pnpm install` to install and patch all the required dependencies
3. Run `pnpm run dev` to start the development server
4. Run `pnpm build` to build the extension for production - If you want to run the development build:
- Run `pnpm run dev`
- If you want to build the extension for production:
- Run `pnpm build`
You may have to rename the `__uno.css.js` to `uno.css.js` in dist
Go to chrome://extensions, ensure you have "Developer Mode" enabled, and click 'Load unpacked'
Navigate to the 'dist' folder and click 'select' to import the extension

123
commitlint.config.ts Normal file
View File

@@ -0,0 +1,123 @@
import { RuleConfigCondition, RuleConfigSeverity, TargetCaseType } from '@commitlint/types';
export default {
parserPreset: 'conventional-changelog-conventionalcommits',
rules: {
'body-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
'body-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
'footer-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
'footer-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
'header-max-length': [RuleConfigSeverity.Error, 'always', 100] as const,
'header-trim': [RuleConfigSeverity.Error, 'always'] as const,
'subject-case': [
RuleConfigSeverity.Error,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
] as [RuleConfigSeverity, RuleConfigCondition, TargetCaseType[]],
'subject-empty': [RuleConfigSeverity.Error, 'never'] as const,
'subject-full-stop': [RuleConfigSeverity.Error, 'never', '.'] as const,
'type-case': [RuleConfigSeverity.Error, 'always', 'lower-case'] as const,
'type-empty': [RuleConfigSeverity.Error, 'never'] as const,
'type-enum': [
RuleConfigSeverity.Error,
'always',
['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'],
] as [RuleConfigSeverity, RuleConfigCondition, string[]],
},
prompt: {
questions: {
type: {
description: "Select the type of change that you're committing",
enum: {
feat: {
description: 'A new feature',
title: 'Features',
emoji: '✨',
},
fix: {
description: 'A bug fix',
title: 'Bug Fixes',
emoji: '🐛',
},
docs: {
description: 'Documentation only changes',
title: 'Documentation',
emoji: '📚',
},
style: {
description:
'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
title: 'Styles',
emoji: '💎',
},
refactor: {
description: 'A code change that neither fixes a bug nor adds a feature',
title: 'Code Refactoring',
emoji: '📦',
},
perf: {
description: 'A code change that improves performance',
title: 'Performance Improvements',
emoji: '🚀',
},
test: {
description: 'Adding missing tests or correcting existing tests',
title: 'Tests',
emoji: '🚨',
},
build: {
description:
'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
title: 'Builds',
emoji: '🛠',
},
ci: {
description:
'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
title: 'Continuous Integrations',
emoji: '⚙️',
},
chore: {
description: "Other changes that don't modify src or test files",
title: 'Chores',
emoji: '♻️',
},
revert: {
description: 'Reverts a previous commit',
title: 'Reverts',
emoji: '🗑',
},
},
},
scope: {
description: 'What is the scope of this change (e.g. component or file name)',
},
subject: {
description: 'Write a short, imperative tense description of the change',
},
body: {
description: 'Provide a longer description of the change',
},
isBreaking: {
description: 'Are there any breaking changes?',
},
breakingBody: {
description:
'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself',
},
breaking: {
description: 'Describe the breaking changes',
},
isIssueAffected: {
description: 'Does this change affect any open issues?',
},
issuesBody: {
description:
'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself',
},
issues: {
description: 'Add issue references (e.g. "fix #123", "re #123".)',
},
},
},
};

3267
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,60 +9,76 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "prettier": "prettier src --check",
"prettier:fix": "prettier src --write",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
"lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
"test": "vitest",
"test:ui": "vitest --ui",
"coverage": "vitest run --coverage",
"preview": "vite preview", "preview": "vite preview",
"devtools": "react-devtools", "devtools": "react-devtools",
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"build-storybook": "storybook build" "build-storybook": "storybook build",
"prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.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",
"chrome-extension-toolkit": "^0.0.51", "chrome-extension-toolkit": "^0.0.51",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"highcharts": "^11.3.0", "highcharts": "^11.3.0",
"highcharts-react-official": "^3.2.1", "highcharts-react-official": "^3.2.1",
"html-to-image": "^1.11.11",
"react": "^18.2.0", "react": "^18.2.0",
"react-devtools-core": "^5.0.0", "react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"sass": "^1.70.0", "sass": "^1.71.1",
"sql.js": "1.10.2", "sql.js": "1.10.2",
"styled-components": "^6.1.8",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@crxjs/vite-plugin": "2.0.0-beta.21", "@crxjs/vite-plugin": "2.0.0-beta.21",
"@iconify-json/material-symbols": "^1.1.72", "@iconify-json/material-symbols": "^1.1.73",
"@storybook/addon-designs": "^7.0.9", "@storybook/addon-designs": "^7.0.9",
"@storybook/addon-essentials": "^7.6.12", "@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.12", "@storybook/addon-links": "^7.6.17",
"@storybook/blocks": "^7.6.12", "@storybook/blocks": "^7.6.17",
"@storybook/react": "^7.6.12", "@storybook/react": "^7.6.17",
"@storybook/react-vite": "^7.6.12", "@storybook/react-vite": "^7.6.17",
"@storybook/test": "^7.6.12", "@storybook/test": "^7.6.17",
"@svgr/core": "^8.1.0", "@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0",
"@types/chrome": "^0.0.260", "@types/chrome": "^0.0.260",
"@types/node": "^20.11.16", "@types/node": "^20.11.19",
"@types/prompts": "^2.4.9", "@types/prompts": "^2.4.9",
"@types/react": "^18.2.51", "@types/react": "^18.2.57",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.19",
"@types/semver": "^7.5.6", "@types/semver": "^7.5.7",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.20.0", "@typescript-eslint/parser": "^6.21.0",
"@unocss/eslint-config": "^0.58.4", "@unocss/eslint-config": "^0.58.5",
"@unocss/postcss": "^0.58.4", "@unocss/postcss": "^0.58.5",
"@unocss/preset-uno": "^0.58.4", "@unocss/preset-uno": "^0.58.5",
"@unocss/preset-web-fonts": "^0.58.4", "@unocss/preset-web-fonts": "^0.58.5",
"@unocss/reset": "^0.58.5", "@unocss/reset": "^0.58.5",
"@unocss/transformer-directives": "^0.58.4", "@unocss/transformer-directives": "^0.58.5",
"@unocss/transformer-variant-group": "^0.58.4", "@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-react-swc": "^3.6.0", "@vitejs/plugin-react-swc": "^3.6.0",
"chromatic": "^10.9.1", "@vitest/coverage-v8": "^1.3.1",
"@vitest/ui": "^1.3.1",
"chromatic": "^10.9.6",
"cssnano": "^6.0.3", "cssnano": "^6.0.3",
"cssnano-preset-advanced": "^6.0.3", "cssnano-preset-advanced": "^6.0.3",
"dotenv": "^16.4.1", "dotenv": "^16.4.5",
"es-module-lexer": "^1.4.1", "es-module-lexer": "^1.4.1",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
@@ -71,24 +87,27 @@
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsdoc": "^48.0.4", "eslint-plugin-jsdoc": "^48.1.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-prefer-function-component": "^3.3.0", "eslint-plugin-react-prefer-function-component": "^3.3.0",
"eslint-plugin-react-refresh": "^0.4.5", "eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-storybook": "^0.6.15", "eslint-plugin-storybook": "^0.6.15",
"husky": "^9.0.11",
"path": "^0.12.7", "path": "^0.12.7",
"postcss": "^8.4.33", "postcss": "^8.4.35",
"prettier": "^3.2.4", "prettier": "^3.2.5",
"react-dev-utils": "^12.0.1", "react-dev-utils": "^12.0.1",
"react-devtools": "^5.0.0", "react-devtools": "^5.0.0",
"storybook": "^7.6.12", "storybook": "^7.6.17",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"unocss": "^0.58.4", "unocss": "^0.58.5",
"unplugin-icons": "^0.18.3", "unplugin-icons": "^0.18.5",
"vite": "^5.0.12", "vite": "^5.1.4",
"vite-plugin-inspect": "^0.8.3" "vite-plugin-inspect": "^0.8.3",
"vitest": "^1.3.1"
}, },
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {

4996
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

8
src/assets/icons/cal.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_3175_7842" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="25">
<rect y="0.5" width="24" height="24" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_3175_7842)">
<path d="M12 14.5C11.7167 14.5 11.4792 14.4042 11.2875 14.2125C11.0958 14.0208 11 13.7833 11 13.5C11 13.2167 11.0958 12.9792 11.2875 12.7875C11.4792 12.5958 11.7167 12.5 12 12.5C12.2833 12.5 12.5208 12.5958 12.7125 12.7875C12.9042 12.9792 13 13.2167 13 13.5C13 13.7833 12.9042 14.0208 12.7125 14.2125C12.5208 14.4042 12.2833 14.5 12 14.5ZM8 14.5C7.71667 14.5 7.47917 14.4042 7.2875 14.2125C7.09583 14.0208 7 13.7833 7 13.5C7 13.2167 7.09583 12.9792 7.2875 12.7875C7.47917 12.5958 7.71667 12.5 8 12.5C8.28333 12.5 8.52083 12.5958 8.7125 12.7875C8.90417 12.9792 9 13.2167 9 13.5C9 13.7833 8.90417 14.0208 8.7125 14.2125C8.52083 14.4042 8.28333 14.5 8 14.5ZM16 14.5C15.7167 14.5 15.4792 14.4042 15.2875 14.2125C15.0958 14.0208 15 13.7833 15 13.5C15 13.2167 15.0958 12.9792 15.2875 12.7875C15.4792 12.5958 15.7167 12.5 16 12.5C16.2833 12.5 16.5208 12.5958 16.7125 12.7875C16.9042 12.9792 17 13.2167 17 13.5C17 13.7833 16.9042 14.0208 16.7125 14.2125C16.5208 14.4042 16.2833 14.5 16 14.5ZM12 18.5C11.7167 18.5 11.4792 18.4042 11.2875 18.2125C11.0958 18.0208 11 17.7833 11 17.5C11 17.2167 11.0958 16.9792 11.2875 16.7875C11.4792 16.5958 11.7167 16.5 12 16.5C12.2833 16.5 12.5208 16.5958 12.7125 16.7875C12.9042 16.9792 13 17.2167 13 17.5C13 17.7833 12.9042 18.0208 12.7125 18.2125C12.5208 18.4042 12.2833 18.5 12 18.5ZM8 18.5C7.71667 18.5 7.47917 18.4042 7.2875 18.2125C7.09583 18.0208 7 17.7833 7 17.5C7 17.2167 7.09583 16.9792 7.2875 16.7875C7.47917 16.5958 7.71667 16.5 8 16.5C8.28333 16.5 8.52083 16.5958 8.7125 16.7875C8.90417 16.9792 9 17.2167 9 17.5C9 17.7833 8.90417 18.0208 8.7125 18.2125C8.52083 18.4042 8.28333 18.5 8 18.5ZM16 18.5C15.7167 18.5 15.4792 18.4042 15.2875 18.2125C15.0958 18.0208 15 17.7833 15 17.5C15 17.2167 15.0958 16.9792 15.2875 16.7875C15.4792 16.5958 15.7167 16.5 16 16.5C16.2833 16.5 16.5208 16.5958 16.7125 16.7875C16.9042 16.9792 17 17.2167 17 17.5C17 17.7833 16.9042 18.0208 16.7125 18.2125C16.5208 18.4042 16.2833 18.5 16 18.5ZM5 22.5C4.45 22.5 3.97917 22.3042 3.5875 21.9125C3.19583 21.5208 3 21.05 3 20.5V6.5C3 5.95 3.19583 5.47917 3.5875 5.0875C3.97917 4.69583 4.45 4.5 5 4.5H6V2.5H8V4.5H16V2.5H18V4.5H19C19.55 4.5 20.0208 4.69583 20.4125 5.0875C20.8042 5.47917 21 5.95 21 6.5V20.5C21 21.05 20.8042 21.5208 20.4125 21.9125C20.0208 22.3042 19.55 22.5 19 22.5H5ZM5 20.5H19V10.5H5V20.5Z" fill="#333F48"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

8
src/assets/icons/png.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_3211_5369" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="25">
<rect y="0.5" width="24" height="24" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_3211_5369)">
<path d="M5 21.5C4.45 21.5 3.97917 21.3042 3.5875 20.9125C3.19583 20.5208 3 20.05 3 19.5V5.5C3 4.95 3.19583 4.47917 3.5875 4.0875C3.97917 3.69583 4.45 3.5 5 3.5H19C19.55 3.5 20.0208 3.69583 20.4125 4.0875C20.8042 4.47917 21 4.95 21 5.5V19.5C21 20.05 20.8042 20.5208 20.4125 20.9125C20.0208 21.3042 19.55 21.5 19 21.5H5ZM6 17.5H18L14.25 12.5L11.25 16.5L9 13.5L6 17.5Z" fill="#333F48"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 702 B

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,4 +1,5 @@
import { defineManifest } from '@crxjs/vite-plugin'; import { defineManifest } from '@crxjs/vite-plugin';
import packageJson from '../package.json'; import packageJson from '../package.json';
// Convert from Semver (example: 0.1.0-beta6) // Convert from Semver (example: 0.1.0-beta6)

View File

@@ -1,5 +1,6 @@
import { BACKGROUND_MESSAGES } from '@shared/messages'; import type { BACKGROUND_MESSAGES } from '@shared/messages';
import { MessageListener } from 'chrome-extension-toolkit'; import { MessageListener } from 'chrome-extension-toolkit';
import onInstall from './events/onInstall'; import onInstall from './events/onInstall';
import onServiceWorkerAlive from './events/onServiceWorkerAlive'; import onServiceWorkerAlive from './events/onServiceWorkerAlive';
import onUpdate from './events/onUpdate'; import onUpdate from './events/onUpdate';

View File

@@ -1,5 +1,5 @@
import BrowserActionMessages from '@shared/messages/BrowserActionMessages'; import type BrowserActionMessages from '@shared/messages/BrowserActionMessages';
import { MessageHandler } from 'chrome-extension-toolkit'; import type { MessageHandler } from 'chrome-extension-toolkit';
const browserActionHandler: MessageHandler<BrowserActionMessages> = { const browserActionHandler: MessageHandler<BrowserActionMessages> = {
disableBrowserAction({ sender, sendResponse }) { disableBrowserAction({ sender, sendResponse }) {

View File

@@ -1,6 +1,6 @@
import HotReloadingMessages from '@shared/messages/HotReloadingMessages'; import type HotReloadingMessages from '@shared/messages/HotReloadingMessages';
import { DevStore } from '@shared/storage/DevStore'; import { DevStore } from '@shared/storage/DevStore';
import { MessageHandler } from 'chrome-extension-toolkit'; import type { MessageHandler } from 'chrome-extension-toolkit';
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = { const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
async reloadExtension({ sendResponse }) { async reloadExtension({ sendResponse }) {

View File

@@ -1,5 +1,6 @@
import TabManagementMessages from '@shared/messages/TabManagementMessages'; import type TabManagementMessages from '@shared/messages/TabManagementMessages';
import { MessageHandler } from 'chrome-extension-toolkit'; import type { MessageHandler } from 'chrome-extension-toolkit';
import openNewTab from '../util/openNewTab'; import openNewTab from '../util/openNewTab';
const tabManagementHandler: MessageHandler<TabManagementMessages> = { const tabManagementHandler: MessageHandler<TabManagementMessages> = {

View File

@@ -1,6 +1,7 @@
import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages'; import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import { Course } from '@shared/types/Course'; import { Course } from '@shared/types/Course';
import { MessageHandler } from 'chrome-extension-toolkit'; import type { MessageHandler } from 'chrome-extension-toolkit';
import addCourse from '../lib/addCourse'; import addCourse from '../lib/addCourse';
import clearCourses from '../lib/clearCourses'; import clearCourses from '../lib/clearCourses';
import createSchedule from '../lib/createSchedule'; import createSchedule from '../lib/createSchedule';

View File

@@ -1,5 +1,5 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course'; import type { Course } from '@shared/types/Course';
/** /**
* *

View File

@@ -1,5 +1,10 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Clears the courses for a given schedule.
* @param scheduleName - The name of the schedule.
* @throws Error if the schedule does not exist.
*/
export default async function clearCourses(scheduleName: string): Promise<void> { export default async function clearCourses(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules'); const schedules = await UserScheduleStore.get('schedules');
const schedule = schedules.find(schedule => schedule.name === scheduleName); const schedule = schedules.find(schedule => schedule.name === scheduleName);

View File

@@ -14,6 +14,7 @@ export default async function createSchedule(scheduleName: string): Promise<stri
schedules.push({ schedules.push({
name: scheduleName, name: scheduleName,
courses: [], courses: [],
hours: 0,
}); });
await UserScheduleStore.set('schedules', schedules); await UserScheduleStore.set('schedules', schedules);

View File

@@ -1,5 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Deletes a schedule with the specified name.
*
* @param scheduleName - The name of the schedule to delete.
* @returns A promise that resolves to a string if there is an error, or undefined if the schedule is deleted successfully.
*/
export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> { export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> {
const [schedules, activeIndex] = await Promise.all([ const [schedules, activeIndex] = await Promise.all([
UserScheduleStore.get('schedules'), UserScheduleStore.get('schedules'),

View File

@@ -1,5 +1,5 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course'; import type { Course } from '@shared/types/Course';
/** /**
* *

View File

@@ -1,5 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Renames a schedule with the specified name to a new name.
* @param scheduleName - The name of the schedule to be renamed.
* @param newName - The new name for the schedule.
* @returns A promise that resolves to a string if there is an error, or undefined if the schedule is renamed successfully.
*/
export default async function renameSchedule(scheduleName: string, newName: string): Promise<string | undefined> { export default async function renameSchedule(scheduleName: string, newName: string): Promise<string | undefined> {
const schedules = await UserScheduleStore.get('schedules'); const schedules = await UserScheduleStore.get('schedules');
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName); const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);

View File

@@ -1,5 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Switches the active schedule to the specified schedule name.
* Throws an error if the schedule does not exist.
* @param scheduleName - The name of the schedule to switch to.
* @returns A Promise that resolves when the active schedule is successfully switched.
*/
export default async function switchSchedule(scheduleName: string): Promise<void> { export default async function switchSchedule(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules'); const schedules = await UserScheduleStore.get('schedules');

View File

@@ -1,10 +1,15 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import React from 'react';
import { Calendar } from 'src/views/components/calendar/Calendar/Calendar';
/**
* Calendar page
* @returns entire page
*/
export default function CalendarMain() { export default function CalendarMain() {
return ( return (
<ExtensionRoot> <ExtensionRoot>
<div>Calendar Placeholder</div> <Calendar />
</ExtensionRoot> </ExtensionRoot>
); );
} }

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Calendar</title>
</head>
<head> <body>
<meta charset="utf-8" /> <noscript>You need to enable JavaScript to run this app.</noscript>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <div id="root"></div>
<meta name="theme-color" content="#000000" />
<title>Calendar</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<script src="./index.tsx" type="module"></script>
</body>
</html> </html>

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import CalendarMain from './CalendarMain'; import CalendarMain from './CalendarMain';
createRoot(document.getElementById('root')).render(<CalendarMain />); createRoot(document.getElementById('root')).render(<CalendarMain />);

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import CourseCatalogMain from '@views/components/CourseCatalogMain'; import CourseCatalogMain from '@views/components/CourseCatalogMain';
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport'; import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
import React from 'react';
import { createRoot } from 'react-dom/client';
const support = getSiteSupport(window.location.href); const support = getSiteSupport(window.location.href);

View File

@@ -1,5 +1,5 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import React from 'react';
/** /**
* *

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Debug</title>
</head>
<head> <body>
<meta charset="utf-8" /> <noscript>You need to enable JavaScript to run this app.</noscript>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <div id="root"></div>
<meta name="theme-color" content="#000000" />
<title>Debug</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<script src="./index.tsx" type="module"></script>
</body>
</html> </html>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import React from 'react';
/** /**
* *

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<head> <body>
<meta charset="utf-8" /> <noscript>You need to enable JavaScript to run this app.</noscript>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <div id="root"></div>
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<script src="./index.tsx" type="module"></script>
</body>
</html> </html>

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import App from './App'; import App from './App';
createRoot(document.getElementById('root')).render(<App />); createRoot(document.getElementById('root')).render(<App />);

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<head> <body>
<meta charset="utf-8" /> <noscript>You need to enable JavaScript to run this app.</noscript>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <div id="root"></div>
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<script src="./index.tsx" type="module"></script>
</body>
</html> </html>

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import PopupMain from '../../views/components/PopupMain'; import PopupMain from '../../views/components/PopupMain';
createRoot(document.getElementById('root')).render(<PopupMain />); createRoot(document.getElementById('root')).render(<PopupMain />);

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
export default interface BrowserActionMessages { export default interface BrowserActionMessages {
/** make it so that clicking the browser action will open the popup.html */ /** make it so that clicking the browser action will open the popup.html */
enableBrowserAction: () => void; enableBrowserAction: () => void;

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
export default interface HotReloadingMessages { export default interface HotReloadingMessages {
reloadExtension: () => void; reloadExtension: () => void;
} }

View File

@@ -1,5 +1,8 @@
import { Course } from '../types/Course'; import type { Course } from '@shared/types/Course';
/**
* Represents a collection of user schedule messages.
*/
export interface UserScheduleMessages { export interface UserScheduleMessages {
/** /**
* Add a course to a schedule * Add a course to a schedule

View File

@@ -1,8 +1,9 @@
import { createMessenger } from 'chrome-extension-toolkit'; import { createMessenger } from 'chrome-extension-toolkit';
import BrowserActionMessages from './BrowserActionMessages';
import TabManagementMessages from './TabManagementMessages'; import type BrowserActionMessages from './BrowserActionMessages';
import TAB_MESSAGES from './TabMessages'; import type TabManagementMessages from './TabManagementMessages';
import { UserScheduleMessages } from './UserScheduleMessages'; import type TAB_MESSAGES from './TabMessages';
import type { UserScheduleMessages } from './UserScheduleMessages';
/** /**
* This is a type with all the message definitions that can be sent TO the background script * This is a type with all the message definitions that can be sent TO the background script

View File

@@ -15,6 +15,4 @@ export const ExtensionStore = createLocalStore<IExtensionStore>({
lastUpdate: Date.now(), lastUpdate: Date.now(),
}); });
debugStore({ ExtensionStore }); debugStore({ ExtensionStore });

View File

@@ -14,6 +14,7 @@ export const UserScheduleStore = createLocalStore<IUserScheduleStore>({
new UserSchedule({ new UserSchedule({
courses: [], courses: [],
name: 'Schedule 1', name: 'Schedule 1',
hours: 0,
}), }),
], ],
activeIndex: 0, activeIndex: 0,

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-classes-per-file */ import type { Serialized } from 'chrome-extension-toolkit';
import { Serialized } from 'chrome-extension-toolkit';
import { CourseMeeting } from './CourseMeeting'; import type { CourseMeeting } from './CourseMeeting';
import { CourseSchedule } from './CourseSchedule'; import { CourseSchedule } from './CourseSchedule';
import Instructor from './Instructor'; import Instructor from './Instructor';
@@ -12,12 +12,17 @@ export type InstructionMode = 'Online' | 'In Person' | 'Hybrid';
/** /**
* The status of a course (e.g. open, closed, waitlisted, cancelled) * The status of a course (e.g. open, closed, waitlisted, cancelled)
*/ */
export enum Status { export const Status = {
OPEN = 'OPEN', OPEN: 'OPEN',
CLOSED = 'CLOSED', CLOSED: 'CLOSED',
WAITLISTED = 'WAITLISTED', WAITLISTED: 'WAITLISTED',
CANCELLED = 'CANCELLED', CANCELLED: 'CANCELLED',
} } as const;
/**
* Represents the type of status for a course.
*/
export type StatusType = (typeof Status)[keyof typeof Status];
/** /**
* Represents a semester, with the year and the season for when a course is offered * Represents a semester, with the year and the season for when a course is offered
@@ -49,7 +54,7 @@ export class Course {
/** The number of credits that a course is worth */ /** The number of credits that a course is worth */
creditHours: number; creditHours: number;
/** Is the course open, closed, waitlisted, or cancelled? */ /** Is the course open, closed, waitlisted, or cancelled? */
status: Status; status: StatusType;
/** all the people that are teaching this course, and some metadata about their names */ /** all the people that are teaching this course, and some metadata about their names */
instructors: Instructor[]; instructors: Instructor[];
/** Some courses at UT are reserved for certain groups of people or people within a certain major, which makes it difficult for people outside of that group to register for the course. */ /** Some courses at UT are reserved for certain groups of people or people within a certain major, which makes it difficult for people outside of that group to register for the course. */

View File

@@ -1,7 +1,8 @@
import { Serialized } from 'chrome-extension-toolkit'; import type { Serialized } from 'chrome-extension-toolkit';
/** /**
* a map of the days of the week that a class is taught, and the corresponding abbreviation * a map of the days of the week that a class is taught, and the corresponding abbreviation
* Don't modify the keys
*/ */
export const DAY_MAP = { export const DAY_MAP = {
M: 'Monday', M: 'Monday',
@@ -14,7 +15,7 @@ export const DAY_MAP = {
} as const; } as const;
/** A day of the week that a class is taught */ /** A day of the week that a class is taught */
export type Day = typeof DAY_MAP[keyof typeof DAY_MAP]; export type Day = (typeof DAY_MAP)[keyof typeof DAY_MAP];
/** A physical room that a class is taught in */ /** A physical room that a class is taught in */
export type Location = { export type Location = {

View File

@@ -1,5 +1,7 @@
import { Serialized } from 'chrome-extension-toolkit'; import type { Serialized } from 'chrome-extension-toolkit';
import { CourseMeeting, Day, DAY_MAP } from './CourseMeeting';
import type { Day } from './CourseMeeting';
import { CourseMeeting, DAY_MAP } from './CourseMeeting';
/** /**
* This represents the schedule for a course, which includes all the meeting times for the course, as well as helper functions for parsing, serializing, and deserializing the schedule * This represents the schedule for a course, which includes all the meeting times for the course, as well as helper functions for parsing, serializing, and deserializing the schedule

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-classes-per-file */
import { PointOptionsObject } from 'highcharts'; import { PointOptionsObject } from 'highcharts';
import { Semester } from './Course'; import { Semester } from './Course';
/** /**
* Each of the possible letter grades that can be given in a course * Each of the possible letter grades that can be given in a course

View File

@@ -1,4 +1,5 @@
import { Serialized } from 'chrome-extension-toolkit'; import type { Serialized } from 'chrome-extension-toolkit';
import { capitalize } from '../util/string'; import { capitalize } from '../util/string';
/** /**

View File

@@ -1,4 +1,5 @@
import { Serialized } from 'chrome-extension-toolkit'; import type { Serialized } from 'chrome-extension-toolkit';
import { Course } from './Course'; import { Course } from './Course';
/** /**
@@ -7,10 +8,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 {

View File

@@ -1,12 +1,20 @@
import { theme } from 'unocss/preset-mini'; import { theme } from 'unocss/preset-mini';
/**
* Represents the colors for a course.
*/
export interface CourseColors { export interface CourseColors {
primaryColor: string; primaryColor: string;
secondaryColor: string; secondaryColor: string;
} }
// calculates luminance of a hex string /**
function getLuminance(hex: string): number { * Calculates the luminance of a given hexadecimal color.
*
* @param hex - The hexadecimal color value.
* @returns The luminance value between 0 and 1.
*/
export function getLuminance(hex: string): number {
let r = parseInt(hex.substring(1, 3), 16); let r = parseInt(hex.substring(1, 3), 16);
let g = parseInt(hex.substring(3, 5), 16); let g = parseInt(hex.substring(3, 5), 16);
let b = parseInt(hex.substring(5, 7), 16); let b = parseInt(hex.substring(5, 7), 16);
@@ -42,9 +50,9 @@ export function pickFontColor(bgColor: string): 'text-white' | 'text-black' {
* Get primary and secondary colors from a tailwind colorway * Get primary and secondary colors from a tailwind colorway
* @param colorway the tailwind colorway ex. "emerald" * @param colorway the tailwind colorway ex. "emerald"
*/ */
export function getCourseColors(colorway: keyof typeof theme.colors): CourseColors { export function getCourseColors(colorway: keyof typeof theme.colors, index = 600, offset = 200): CourseColors {
return { return {
primaryColor: theme.colors[colorway][600] as string, primaryColor: theme.colors[colorway][index] as string,
secondaryColor: theme.colors[colorway][800] as string, secondaryColor: theme.colors[colorway][index + offset] as string,
}; };
} }

View File

@@ -1,15 +1,18 @@
import React, { SVGProps } from 'react'; import type { StatusType } from '@shared/types/Course';
import { Status } from '@shared/types/Course';
import type { SVGProps } from 'react';
import React from 'react';
import ClosedIcon from '~icons/material-symbols/lock'; import ClosedIcon from '~icons/material-symbols/lock';
import WaitlistIcon from '~icons/material-symbols/timelapse'; import WaitlistIcon from '~icons/material-symbols/timelapse';
import CancelledIcon from '~icons/material-symbols/warning'; import CancelledIcon from '~icons/material-symbols/warning';
import { Status } from '../types/Course';
/** /**
* Get Icon component based on status * Get Icon component based on status
* @param props.status status * @param props.status status
* @returns React.ReactElement - the icon component * @returns React.ReactElement - the icon component
*/ */
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: Status }): React.ReactElement { export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): React.ReactElement {
const { status, ...rest } = props; const { status, ...rest } = props;
switch (props.status) { switch (props.status) {

View File

@@ -18,6 +18,8 @@ export function capitalize(input: string): string {
} }
capitalized += ' '; capitalized += ' ';
} }
capitalized = capitalized.trim(); // Remove extra space
return capitalized; return capitalized;
} }
@@ -31,7 +33,7 @@ export function capitalizeFirstLetter(input: string): string {
} }
/** /**
* Cuts the * Cuts the input string to the specified length and adds an ellipsis if the string is longer than the specified length.
* @param input The string to ellipsify. * @param input The string to ellipsify.
* @param length The length of the string to return. * @param length The length of the string to return.
* @returns The ellipsified string. * @returns The ellipsified string.

View File

@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest';
import { getLuminance } from '../colors';
describe('getLuminance', () => {
it('should return the correct luminance value for a given hex color', () => {
// Test case 1: Hex color #FFFFFF (white)
expect(getLuminance('#FFFFFF')).toBeCloseTo(1);
// Test case 2: Hex color #000000 (black)
expect(getLuminance('#000000')).toBeCloseTo(0);
// Test case 3: Hex color #FF0000 (red)
expect(getLuminance('#FF0000')).toBeCloseTo(0.2126);
// Test case 4: Hex color #00FF00 (green)
expect(getLuminance('#00FF00')).toBeCloseTo(0.7152);
// Test case 5: Hex color #0000FF (blue)
expect(getLuminance('#0000FF')).toBeCloseTo(0.0722);
});
});

View File

@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import { generateRandomId } from '../random';
describe('generateRandomId', () => {
it('should generate a random ID with the specified length', () => {
// Test case 1: Length 5
expect(generateRandomId(5)).toHaveLength(5);
// Test case 2: Length 10
expect(generateRandomId(10)).toHaveLength(10);
// Test case 3: Length 15
expect(generateRandomId(15)).toHaveLength(15);
});
it('should generate a unique ID each time', () => {
// Generate 100 IDs and check if they are all unique
const ids = new Set<string>();
for (let i = 0; i < 100; i += 1) {
const id = generateRandomId();
expect(ids.has(id)).toBe(false);
ids.add(id);
}
});
});

View File

@@ -0,0 +1,64 @@
import { describe, expect, it } from 'vitest';
import { capitalize, capitalizeFirstLetter, ellipsify } from '../string';
// TODO: Fix `string.ts` and `string.test.ts` to make the tests pass
// `capitalize` is adding an extra space at the end of the word.
describe('capitalize', () => {
it('should capitalize the first letter of each word', () => {
// Debug
// const word = 'hello world';
// const capitalized = capitalize(word);
// console.log(capitalize(word));
// console.log(capitalized.length);
// console.log(capitalized.split(''));
// Test case 1: Single word
expect(capitalize('hello')).toBe('Hello');
// Test case 2: Multiple words
expect(capitalize('hello world')).toBe('Hello World');
// Test case 3: Words with hyphens
expect(capitalize('hello-world')).toBe('Hello-World');
// Test case 4: Words with hyphens and spaces
expect(capitalize('hello-world test')).toBe('Hello-World Test');
});
});
describe('capitalizeFirstLetter', () => {
it('should return a string with the first letter capitalized', () => {
// Test case 1: Single word
expect(capitalizeFirstLetter('hello')).toBe('Hello');
// Test case 2: Word with all lowercase letters
expect(capitalizeFirstLetter('world')).toBe('World');
// Test case 3: Word with all uppercase letters
expect(capitalizeFirstLetter('EXAMPLE')).toBe('Example');
// Test case 4: Word with mixed case letters
expect(capitalizeFirstLetter('tEsT')).toBe('Test');
});
it('should handle empty string input', () => {
expect(capitalizeFirstLetter('')).toBe('');
});
});
describe('ellipsify', () => {
it('should add ellipsis if the input string exceeds the specified character limit', () => {
// Test case 1: Input string is shorter than the character limit
expect(ellipsify('Hello', 10)).toBe('Hello');
// Test case 2: Input string is equal to the character limit
expect(ellipsify('Hello World', 11)).toBe('Hello World');
// Test case 3: Input string is longer than the character limit
expect(ellipsify('Hello World', 5)).toBe('Hello...');
// Test case 4: Input string is empty
expect(ellipsify('', 5)).toBe('');
});
});

View File

@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest';
import { getThemeColorHexByName, getThemeColorRgbByName, hexToRgb } from '../themeColors';
describe('hexToRgb', () => {
it('should convert hex color to RGB', () => {
expect(hexToRgb('#BF5700')).toEqual([191, 87, 0]);
expect(hexToRgb('#333F48')).toEqual([51, 63, 72]);
expect(hexToRgb('#f8971f')).toEqual([248, 151, 31]);
expect(hexToRgb('#ffd600')).toEqual([255, 214, 0]);
expect(hexToRgb('#a6cd57')).toEqual([166, 205, 87]);
expect(hexToRgb('#579d42')).toEqual([87, 157, 66]);
expect(hexToRgb('#00a9b7')).toEqual([0, 169, 183]);
expect(hexToRgb('#005f86')).toEqual([0, 95, 134]);
expect(hexToRgb('#9cadb7')).toEqual([156, 173, 183]);
expect(hexToRgb('#d6d2c4')).toEqual([214, 210, 196]);
expect(hexToRgb('#95a5a6')).toEqual([149, 165, 166]);
expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]);
expect(hexToRgb('#af2e2d')).toEqual([175, 46, 45]);
expect(hexToRgb('#1a2024')).toEqual([26, 32, 36]);
expect(hexToRgb('#22c55e')).toEqual([34, 197, 94]);
expect(hexToRgb('#a3e635')).toEqual([163, 230, 53]);
expect(hexToRgb('#84CC16')).toEqual([132, 204, 22]);
expect(hexToRgb('#FDE047')).toEqual([253, 224, 71]);
expect(hexToRgb('#FACC15')).toEqual([250, 204, 21]);
expect(hexToRgb('#F59E0B')).toEqual([245, 158, 11]);
expect(hexToRgb('#FB923C')).toEqual([251, 146, 60]);
expect(hexToRgb('#F97316')).toEqual([249, 115, 22]);
expect(hexToRgb('#EA580C')).toEqual([234, 88, 12]);
expect(hexToRgb('#DC2626')).toEqual([220, 38, 38]);
expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]);
});
});
describe('getThemeColorHexByName', () => {
it('should return the hex color value by name', () => {
expect(getThemeColorHexByName('ut-burntorange')).toEqual('#BF5700');
expect(getThemeColorHexByName('ut-offwhite')).toEqual('#D6D2C4');
expect(getThemeColorHexByName('ut-black')).toEqual('#333F48');
// Add more test cases for other theme color names
});
});
describe('getThemeColorRgbByName', () => {
it('should return the RGB color value by name', () => {
expect(getThemeColorRgbByName('ut-burntorange')).toEqual([191, 87, 0]);
expect(getThemeColorRgbByName('ut-offwhite')).toEqual([214, 210, 196]);
expect(getThemeColorRgbByName('ut-black')).toEqual([51, 63, 72]);
// Add more test cases for other theme color names
});
});

View File

@@ -0,0 +1,14 @@
import { describe, expect, it } from 'vitest';
import { sleep } from '../time';
describe('sleep', () => {
it('should resolve after the specified number of milliseconds', async () => {
const start = Date.now();
const milliseconds = 1000;
await sleep(milliseconds);
const end = Date.now();
const elapsed = end - start;
expect(elapsed).toBeGreaterThanOrEqual(milliseconds);
});
});

View File

@@ -0,0 +1,88 @@
export const colors = {
ut: {
burntorange: '#BF5700',
black: '#333F48',
orange: '#F8971F',
yellow: '#FFD600',
lightgreen: '#A6CD57',
green: '#579D42',
teal: '#00A9B7',
blue: '#005F86',
gray: '#9CADB7',
offwhite: '#D6D2C4',
concrete: '#95A5A6',
red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green
},
theme: {
red: '#AF2E2D',
black: '#1A2024',
},
gradeDistribution: {
a: '#22C55E',
aminus: '#A3E635',
bplus: '#84CC16',
b: '#FDE047',
bminus: '#FACC15',
cplus: '#F59E0B',
c: '#FB923C',
cminus: '#F97316',
dplus: '#EA580C', // TODO (achadaga): copilot generated, get actual color from Isaiah
d: '#DC2626',
dminus: '#B91C1C',
f: '#B91C1C',
},
} as const satisfies Record<string, Record<string, string>>;
type NestedKeys<T> = {
[K in keyof T]: T[K] extends Record<string, any> ? `${string & K}-${string & keyof T[K]}` : never;
}[keyof T];
/**
* A union of all colors in the theme
*/
export type ThemeColor = NestedKeys<typeof colors>;
/**
* Flattened colors object.
* @type {Record<ThemeColor, string>}
*/
export const colorsFlattened = Object.entries(colors).reduce(
(acc, [prefix, group]) => {
for (const [name, hex] of Object.entries(group)) {
acc[`${prefix}-${name}`] = hex;
}
return acc;
},
{} as Record<ThemeColor, string>
);
/**
* Converts a hexadecimal color code to an RGB color array.
* @param hex The hexadecimal color code to convert.
* @returns An array representing the RGB color values.
*/
export const hexToRgb = (hex: string) =>
hex.match(/[0-9a-f]{2}/gi).map(partialHex => parseInt(partialHex, 16)) as [number, number, number];
/**
* Represents the flattened RGB values of the colors.
* @type {Record<ThemeColor, ReturnType<typeof hexToRgb>>}
*/
const colorsFlattenedRgb = Object.fromEntries(
Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRgb(hex)])
) as Record<ThemeColor, ReturnType<typeof hexToRgb>>;
/**
* Retrieves the hexadecimal color value by name from the theme.
*
* @param name - The name of the theme color.
* @returns The hexadecimal color value.
*/
export const getThemeColorHexByName = (name: ThemeColor): string => colorsFlattened[name];
/**
*
* @param name - The name of the theme color.
* @returns An array of the red, green, and blue values, respectively
*/
export const getThemeColorRgbByName = (name: ThemeColor) => colorsFlattenedRgb[name];

View File

@@ -5,7 +5,9 @@ export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR; export const DAY = 24 * HOUR;
/** /**
* * Pauses the execution for the specified number of milliseconds.
* @param milliseconds - The number of milliseconds to sleep.
* @returns A promise that resolves after the specified number of milliseconds.
*/ */
export const sleep = (milliseconds: number): Promise<void> => new Promise(resolve => setTimeout(resolve, milliseconds)); export const sleep = (milliseconds: number): Promise<void> => new Promise(resolve => setTimeout(resolve, milliseconds));

View File

@@ -1,24 +1,34 @@
import { Button } from 'src/views/components/common/Button/Button'; import { colorsFlattened } from '@shared/util/themeColors';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@views/components/common/Button/Button';
import React from 'react'; import React from 'react';
import AddIcon from '~icons/material-symbols/add';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
import DescriptionIcon from '~icons/material-symbols/description';
import ImagePlaceholderIcon from '~icons/material-symbols/image';
import HappyFaceIcon from '~icons/material-symbols/mood';
import RemoveIcon from '~icons/material-symbols/remove';
import ReviewsIcon from '~icons/material-symbols/reviews';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = { const meta = {
title: 'Components/Common/Button', title: 'Components/Common/Button',
component: Button, component: Button,
parameters: { parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered', layout: 'centered',
}, },
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'], tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes // More on argTypes: https://storybook.js.org/docs/api/argtypes
args: { args: {
children: 'Button', children: 'Button',
}, icon: ImagePlaceholderIcon,
argTypes: { },
children: { control: 'text' }, argTypes: {
}, children: { control: 'text' },
},
} satisfies Meta<typeof Button>; } satisfies Meta<typeof Button>;
export default meta; export default meta;
@@ -26,72 +36,112 @@ type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Default: Story = { export const Default: Story = {
args: {}, args: {
variant: 'filled',
color: 'ut-black',
},
}; };
export const Disabled: Story = { export const Disabled: Story = {
args: { args: {
disabled: true, variant: 'filled',
}, color: 'ut-black',
}; disabled: true,
export const Grid: Story = {
render: props => (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex' }}>
<Button {...props} type='primary' />
<Button {...props} type='secondary' />
<Button {...props} type='tertiary' />
<Button {...props} type='danger' />
<Button {...props} type='warning' />
<Button {...props} type='success' />
<Button {...props} type='info' />
</div>
<div style={{ display: 'flex' }}>
<Button {...props} type='primary' disabled />
<Button {...props} type='secondary' disabled />
<Button {...props} type='tertiary' disabled />
<Button {...props} type='danger' disabled />
<Button {...props} type='warning' disabled />
<Button {...props} type='success' disabled />
<Button {...props} type='info' disabled />
</div>
</div>
),
};
// TODO: Actually show the buttons as they appear in the design
export const CourseButtons: Story = {
args: {
children: 'Add Course',
},
render: props => (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex' }}>
<Button {...props} type='primary' />
<Button {...props} type='secondary' />
<Button {...props} type='tertiary' />
<Button {...props} type='danger' />
<Button {...props} type='warning' />
<Button {...props} type='success' />
<Button {...props} type='info' />
</div>
<div style={{ display: 'flex' }}>
<Button {...props} type='primary' disabled />
<Button {...props} type='secondary' disabled />
<Button {...props} type='tertiary' disabled />
<Button {...props} type='danger' disabled />
<Button {...props} type='warning' disabled />
<Button {...props} type='success' disabled />
<Button {...props} type='info' disabled />
</div>
</div>
),
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=324-389&mode=design&t=BoS5xBrpSsjgQXqv-4',
}, },
}, };
// @ts-ignore
export const Grid: Story = {
render: props => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' />
<Button {...props} variant='outline' color='ut-black' />
<Button {...props} variant='single' color='ut-black' />
</div>
<hr />
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-black' disabled />
<Button {...props} variant='outline' color='ut-black' disabled />
<Button {...props} variant='single' color='ut-black' disabled />
</div>
</div>
),
};
export const PrettyColors: Story = {
// @ts-ignore
args: {
children: '',
},
render: props => {
const colorsNames = Object.keys(colorsFlattened) as (keyof typeof colorsFlattened)[];
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
{colorsNames.map(color => (
<div style={{ display: 'flex', gap: '15px' }} key={color}>
<Button {...props} variant='filled' color={color}>
Button
</Button>
<Button {...props} variant='outline' color={color}>
Button
</Button>
<Button {...props} variant='single' color={color}>
Button
</Button>
<Button {...props} variant='filled' color={color} />
<Button {...props} variant='outline' color={color} />
<Button {...props} variant='single' color={color} />
</div>
))}
</div>
);
},
};
// @ts-ignore
export const CourseButtons: Story = {
render: props => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px', alignItems: 'center' }}>
<Button {...props} variant='filled' color='ut-green' icon={AddIcon}>
Add Course
</Button>
<Button {...props} variant='filled' color='theme-red' icon={RemoveIcon}>
Remove Course
</Button>
</div>
),
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=324-389&mode=design&t=BoS5xBrpSsjgQXqv-4',
},
},
};
export const CourseCatalogActionButtons: Story = {
// @ts-ignore
args: {
children: '',
},
render: props => (
<div style={{ display: 'flex', gap: '15px' }}>
<Button {...props} variant='filled' color='ut-burntorange' icon={CalendarMonthIcon} />
<Button {...props} variant='outline' color='ut-blue' icon={ReviewsIcon}>
RateMyProf
</Button>
<Button {...props} variant='outline' color='ut-teal' icon={HappyFaceIcon}>
CES
</Button>
<Button {...props} variant='outline' color='ut-orange' icon={DescriptionIcon}>
Past Syllabi
</Button>
<Button {...props} variant='filled' color='ut-green' icon={AddIcon}>
Add Course
</Button>
</div>
),
}; };

View File

@@ -1,19 +0,0 @@
// Calendar.stories.tsx
import React from 'react';
import Calendar from '@views/components/common/CalendarGrid/CalendarGrid';
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
title: 'Components/Common/Calendar',
component: Calendar,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
tags: ['autodocs'],
}
} satisfies Meta<typeof Calendar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -1,20 +1,20 @@
import Card from 'src/views/components/common/Card/Card';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import Card from '@views/components/common/Card/Card';
import React from 'react'; import React from 'react';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = { const meta = {
title: 'Components/Common/Card', title: 'Components/Common/Card',
component: Card, component: Card,
parameters: { parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered', layout: 'centered',
}, },
args: { args: {
children: <div>Hello</div>, children: <div>Hello</div>,
}, },
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'], tags: ['autodocs'],
} satisfies Meta<typeof Card>; } satisfies Meta<typeof Card>;
export default meta; export default meta;

View File

@@ -0,0 +1,23 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Chip } from '@views/components/common/Chip/Chip';
const meta = {
title: 'Components/Common/Chip',
component: Chip,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
label: { control: 'text' },
},
} satisfies Meta<typeof Chip>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
label: 'QR',
},
};

View File

@@ -0,0 +1,117 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import type { Meta, StoryObj } from '@storybook/react';
import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning';
export const ExampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3,
department: 'C S',
description: [
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
'May be counted toward the Quantitative Reasoning flag requirement.',
'Designed to accommodate 100 or more students.',
'Taught as a Web-based course.',
],
flags: ['Quantitative Reasoning'],
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
instructionMode: 'Online',
instructors: [
new Instructor({
firstName: 'Bevo',
lastName: 'Bevo',
fullName: 'Bevo Bevo',
}),
],
isReserved: false,
number: '303E',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
endTime: 660,
startTime: 570,
}),
],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.WAITLISTED,
uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
export const ExampleCourse2: Course = new Course({
courseName: 'PRINCIPLES OF COMPUTER SYSTEMS',
creditHours: 3,
department: 'C S',
description: [
'Restricted to computer science majors.',
'An introduction to computer systems software abstractions with an emphasis on the connection of these abstractions to underlying computer hardware. Key abstractions include threads, virtual memory, protection, and I/O. Requires writing of synchronized multithreaded programs and pieces of an operating system.',
'Computer Science 439 and 439H may not both be counted.',
'Prerequisite: Computer Science 429, or 429H with a grade of at least C-.',
'May be counted toward the Independent Inquiry flag requirement.',
],
flags: ['Independent Inquiry'],
fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
instructionMode: 'In Person',
instructors: [
new Instructor({
firstName: 'Allison',
lastName: 'Norman',
fullName: 'Allison Norman',
}),
],
isReserved: false,
number: '439',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
startTime: 930,
endTime: 1050,
}),
new CourseMeeting({
days: ['Friday'],
startTime: 600,
endTime: 720,
}),
],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.WAITLISTED,
uniqueId: 67890,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
const meta = {
title: 'Components/Common/ConflictsWithWarning',
component: ConflictsWithWarning,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
conflicts: { control: 'object' },
},
args: {
conflicts: [ExampleCourse, ExampleCourse2],
},
} satisfies Meta<typeof ConflictsWithWarning>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
conflicts: [ExampleCourse, ExampleCourse2],
},
};

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { Status } from '@shared/types/Course'; import { Status } from '@shared/types/Course';
import { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import CourseStatus from '@views/components/common/CourseStatus/CourseStatus'; import CourseStatus from '@views/components/common/CourseStatus/CourseStatus';
import React from 'react';
const meta = { const meta = {
title: 'Components/Common/CourseStatus', title: 'Components/Common/CourseStatus',
@@ -40,7 +39,7 @@ export const Default: Story = {};
export const Variants: Story = { export const Variants: Story = {
render: args => ( render: args => (
<div className='flex flex-col gap-4 items-center'> <div className='flex flex-col items-center gap-4'>
<CourseStatus {...args} size='small' /> <CourseStatus {...args} size='small' />
<CourseStatus {...args} size='mini' /> <CourseStatus {...args} size='mini' />
</div> </div>

View File

@@ -1,18 +0,0 @@
import Divider from 'src/views/components/common/Divider/Divider';
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
title: 'Components/Common/Divider',
component: Divider,
tags: ['autodocs'],
argTypes: {
color: {
control: 'color',
},
},
} satisfies Meta<typeof Divider>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -0,0 +1,78 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@views/components/common/Button/Button';
import Divider from '@views/components/common/Divider/Divider';
import React from 'react';
import AddIcon from '~icons/material-symbols/add';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
import DescriptionIcon from '~icons/material-symbols/description';
import HappyFaceIcon from '~icons/material-symbols/mood';
import ReviewsIcon from '~icons/material-symbols/reviews';
const meta = {
title: 'Components/Common/Divider',
component: Divider,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Divider>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Vertical: Story = {
args: {
size: '2.5rem',
orientation: 'vertical',
},
render: props => <Divider {...props} />,
};
export const Horizontal: Story = {
args: {
size: '2.5rem',
orientation: 'horizontal',
},
render: props => <Divider {...props} />,
};
export const IGotHorizontalIGotVerticalWhatYouWant: Story = {
args: {
size: '2.5rem',
orientation: 'vertical',
},
render: props => (
<div className='grid grid-cols-7 grid-rows-3 items-center justify-items-center gap-3.75'>
{Array.from({ length: 21 }).map((_, i) => (
<Divider {...props} orientation={i % 2 === 0 ? 'horizontal' : 'vertical'} />
))}
</div>
),
};
export const CourseCatalogActionButtons: Story = {
args: {
size: '1.75rem',
orientation: 'vertical',
},
render: props => (
<div className='flex items-center gap-3.75'>
<Button variant='filled' color='ut-burntorange' icon={CalendarMonthIcon} />
<Divider {...props} />
<Button variant='outline' color='ut-blue' icon={ReviewsIcon}>
RateMyProf
</Button>
<Button variant='outline' color='ut-teal' icon={HappyFaceIcon}>
CES
</Button>
<Button variant='outline' color='ut-orange' icon={DescriptionIcon}>
Past Syllabi
</Button>
<Button variant='filled' color='ut-green' icon={AddIcon}>
Add Course
</Button>
</div>
),
};

View File

@@ -0,0 +1,157 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import { UserSchedule } from '@shared/types/UserSchedule';
import type { Meta, StoryObj } from '@storybook/react';
import Dropdown from '@views/components/common/Dropdown/Dropdown';
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
import type { Serialized } from 'chrome-extension-toolkit';
import React from 'react';
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) => (
<ScheduleListItem active={index === 0} name={schedule.name} />
)),
},
};

View File

@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';
import ImportantLinks from '@views/components/calendar/ImportantLinks';
const meta = {
title: 'Components/Common/ImportantLinks',
component: ImportantLinks,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof ImportantLinks>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};

View File

@@ -0,0 +1,25 @@
import type { Meta, StoryObj } from '@storybook/react';
import { InfoCard } from '@views/components/common/InfoCard/InfoCard';
const meta = {
title: 'Components/Common/InfoCard',
component: InfoCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
titleText: { control: 'text' },
bodyText: { control: 'text' },
},
} satisfies Meta<typeof InfoCard>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
titleText: 'WAITLIST SIZE',
bodyText: '14 Students',
},
};

View File

@@ -1,21 +1,21 @@
import Link from 'src/views/components/common/Link/Link';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import Link from '@views/components/common/Link/Link';
const meta = { const meta = {
title: 'Components/Common/Link', title: 'Components/Common/Link',
component: Link, component: Link,
parameters: { parameters: {
layout: 'centered', layout: 'centered',
}, },
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
color: { color: {
control: 'color', control: 'color',
},
},
args: {
children: 'Link',
}, },
},
args: {
children: 'Link',
},
} satisfies Meta<typeof Link>; } satisfies Meta<typeof Link>;
export default meta; export default meta;

View File

@@ -0,0 +1,103 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import type { Meta, StoryObj } from '@storybook/react';
import List from '@views/components/common/List/List';
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import React from 'react';
import { TestColors } from './PopupCourseBlock.stories';
const numberOfCourses = 5;
/**
* Generates an array of courses.
*
* @param count - The number of courses to generate.
* @returns An array of generated courses.
*/
export const GenerateCourses = count => {
const courses = [];
for (let i = 0; i < count; i++) {
const course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3,
department: 'C S',
description: [
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
'May be counted toward the Quantitative Reasoning flag requirement.',
'Designed to accommodate 100 or more students.',
'Taught as a Web-based course.',
],
flags: ['Quantitative Reasoning'],
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
instructionMode: 'Online',
instructors: [
new Instructor({
firstName: 'Bevo',
lastName: 'Bevo',
fullName: 'Bevo Bevo',
}),
],
isReserved: false,
number: '303E',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
endTime: 660,
startTime: 570,
}),
],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.WAITLISTED,
uniqueId: 12345 + i, // Make uniqueId different for each course
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
courses.push(course);
}
return courses;
};
const exampleCourses = GenerateCourses(numberOfCourses);
const generateCourseBlocks = (exampleCourses, colors) =>
exampleCourses.map((course, i) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors[i]} />);
export const ExampleCourseBlocks = generateCourseBlocks(exampleCourses, TestColors);
const meta = {
title: 'Components/Common/List',
component: List,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
draggableElements: { control: 'object' },
itemHeight: { control: 'number' },
listHeight: { control: 'number' },
listWidth: { control: 'number' },
gap: { control: 'number' },
},
} satisfies Meta<typeof List>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
draggableElements: ExampleCourseBlocks,
itemHeight: 55,
listHeight: 300,
listWidth: 300,
gap: 12,
},
};

View File

@@ -1,13 +1,19 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { Course, Status } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import Instructor from 'src/shared/types/Instructor';
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock'; import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import { getCourseColors } from 'src/shared/util/colors'; import React from 'react';
import { theme } from 'unocss/preset-mini'; import { theme } from 'unocss/preset-mini';
const exampleCourse: Course = new Course({ /**
* Represents an example course.
*
* @remarks
* This is a sample course object that provides information about a specific course.
*/
export const ExampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3, creditHours: 3,
department: 'C S', department: 'C S',
@@ -62,7 +68,7 @@ const meta = {
// More on argTypes: https://storybook.js.org/docs/api/argtypes // More on argTypes: https://storybook.js.org/docs/api/argtypes
args: { args: {
colors: getCourseColors('emerald'), colors: getCourseColors('emerald'),
course: exampleCourse, course: ExampleCourse,
}, },
argTypes: { argTypes: {
colors: { colors: {
@@ -87,15 +93,15 @@ export const Default: Story = {
export const Variants: Story = { export const Variants: Story = {
render: props => ( render: props => (
<div className='grid grid-cols-2 max-w-2xl w-90vw gap-x-4 gap-y-2'> <div className='grid grid-cols-2 max-w-2xl w-90vw gap-x-4 gap-y-2'>
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.OPEN })} /> <PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.OPEN })} />
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CLOSED })} /> <PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.CLOSED })} />
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.WAITLISTED })} /> <PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.WAITLISTED })} />
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CANCELLED })} /> <PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.CANCELLED })} />
</div> </div>
), ),
}; };
const colors = Object.keys(theme.colors) export const TestColors = Object.keys(theme.colors)
// check that the color is a colorway (is an object) // check that the color is a colorway (is an object)
.filter(color => typeof theme.colors[color] === 'object') .filter(color => typeof theme.colors[color] === 'object')
.slice(0, 17) .slice(0, 17)
@@ -103,9 +109,9 @@ const colors = Object.keys(theme.colors)
export const AllColors: Story = { export const AllColors: Story = {
render: props => ( render: props => (
<div className='grid grid-rows-9 grid-cols-2 grid-flow-col max-w-2xl w-90vw gap-x-4 gap-y-2'> <div className='grid grid-flow-col grid-cols-2 grid-rows-9 max-w-2xl w-90vw gap-x-4 gap-y-2'>
{colors.map((color, i) => ( {TestColors.map((color, i) => (
<PopupCourseBlock key={color.primaryColor} course={exampleCourse} colors={color} /> <PopupCourseBlock key={color.primaryColor} course={ExampleCourse} colors={color} />
))} ))}
</div> </div>
), ),

View File

@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';
import PopupMain from '@views/components/PopupMain';
const meta = {
title: 'Components/Common/PopupMain',
component: PopupMain,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof PopupMain>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};

View File

@@ -0,0 +1,27 @@
import type { Meta, StoryObj } from '@storybook/react';
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses';
const meta = {
title: 'Components/Common/ScheduleTotalHoursAndCourses',
component: ScheduleTotalHoursAndCourses,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
scheduleName: { control: 'text' },
totalHours: { control: 'number' },
totalCourses: { control: 'number' },
},
} satisfies Meta<typeof ScheduleTotalHoursAndCourses>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
scheduleName: 'SCHEDULE',
totalHours: 22,
totalCourses: 8,
},
};

View File

@@ -0,0 +1,37 @@
/* eslint-disable jsdoc/require-jsdoc */
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
import React from 'react';
export default {
title: 'Components/Common/ScheduleListItem',
component: ScheduleListItem,
parameters: {
layout: 'centered',
tags: ['autodocs'],
},
argTypes: {
active: { control: 'boolean' },
name: { control: 'text' },
},
};
export const Default = args => <ScheduleListItem {...args} />;
Default.args = {
name: 'My Schedule',
active: true,
};
export const Active = args => <ScheduleListItem {...args} />;
Active.args = {
name: 'My Schedule',
active: true,
};
export const Inactive = args => <ScheduleListItem {...args} />;
Inactive.args = {
name: 'My Schedule',
active: false,
};

View File

@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';
import Settings from '@views/components/Settings';
const meta = {
title: 'Components/Common/Settings',
component: Settings,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof Settings>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};

View File

@@ -1,11 +1,11 @@
import Spinner from 'src/views/components/common/Spinner/Spinner';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import Spinner from '@views/components/common/Spinner/Spinner';
const meta = { const meta = {
title: 'Components/Common/Spinner', title: 'Components/Common/Spinner',
component: Spinner, component: Spinner,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: {}, argTypes: {},
} satisfies Meta<typeof Spinner>; } satisfies Meta<typeof Spinner>;
export default meta; export default meta;

View File

@@ -1,7 +1,6 @@
import { Button } from 'src/views/components/common/Button/Button';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import Text from '@views/components/common/Text/Text';
import React from 'react'; import React from 'react';
import Text from '../../views/components/common/Text/Text';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = { const meta = {

View File

@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Calendar } from '@views/components/calendar/Calendar/Calendar';
const meta = {
title: 'Components/Calendar/Calendar',
component: Calendar,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof Calendar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};

View File

@@ -0,0 +1,101 @@
import { Course, Status } from '@shared/types/Course';
import Instructor from '@shared/types/Instructor';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react';
import { CalendarBottomBar } from '@views/components/calendar/CalendarBottomBar/CalendarBottomBar';
import React from 'react';
const exampleGovCourse: Course = new Course({
courseName: 'Nope',
creditHours: 3,
department: 'GOV',
description: ['nah', 'aint typing this', 'corndog'],
flags: ['no flag for you >:)'],
fullName: 'GOV 312L Something something',
instructionMode: 'Online',
instructors: [
new Instructor({
firstName: 'Bevo',
lastName: 'Barrymore',
fullName: 'Bevo Barrymore',
}),
],
isReserved: false,
number: '312L',
schedule: {
meetings: [],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.OPEN,
uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
const examplePsyCourse: Course = new Course({
courseName: 'Nope Again',
creditHours: 3,
department: 'PSY',
description: ['nah', 'aint typing this', 'corndog'],
flags: ['no flag for you >:)'],
fullName: 'PSY 317L Yada yada',
instructionMode: 'Online',
instructors: [
new Instructor({
firstName: 'Bevo',
lastName: 'Etz',
fullName: 'Bevo Etz',
}),
],
isReserved: false,
number: '317L',
schedule: {
meetings: [],
},
semester: {
code: '12346',
season: 'Spring',
year: 2024,
},
status: Status.CLOSED,
uniqueId: 12346,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
const meta = {
title: 'Components/Calendar/CalendarBottomBar',
component: CalendarBottomBar,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof CalendarBottomBar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
courses: [
{
colors: getCourseColors('pink', 200),
courseDeptAndInstr: `${exampleGovCourse.department} ${exampleGovCourse.number} ${exampleGovCourse.instructors[0].lastName}`,
status: exampleGovCourse.status,
},
{
colors: getCourseColors('slate', 500),
courseDeptAndInstr: `${examplePsyCourse.department} ${examplePsyCourse.number} ${examplePsyCourse.instructors[0].lastName}`,
status: examplePsyCourse.status,
},
],
},
render: props => (
<div className='outline-red outline w-292.5!'>
<CalendarBottomBar {...props} />
</div>
),
};

View File

@@ -1,14 +1,13 @@
import { Meta, StoryObj } from '@storybook/react'; import { Course, Status } from '@shared/types/Course';
import React from 'react'; import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { Course, Status } from 'src/shared/types/Course'; import { CourseSchedule } from '@shared/types/CourseSchedule';
import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting'; import Instructor from '@shared/types/Instructor';
import { CourseSchedule } from 'src/shared/types/CourseSchedule'; import type { Meta, StoryObj } from '@storybook/react';
import Instructor from 'src/shared/types/Instructor'; import CalendarCourse from '@views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting';
import CalendarCourseCell from 'src/views/components/common/CalendarCourseCell/CalendarCourseCell';
const meta = { const meta = {
title: 'Components/Common/CalendarCourseCell', title: 'Components/Calendar/CalendarCourseMeeting',
component: CalendarCourseCell, component: CalendarCourse,
parameters: { parameters: {
layout: 'centered', layout: 'centered',
}, },
@@ -17,13 +16,9 @@ const meta = {
course: { control: 'object' }, course: { control: 'object' },
meetingIdx: { control: 'number' }, meetingIdx: { control: 'number' },
color: { control: 'color' }, color: { control: 'color' },
rightIcon: { control: 'object' },
}, },
render: (args: any) => ( } satisfies Meta<typeof CalendarCourse>;
<div className="w-45">
<CalendarCourseCell {...args} />
</div>
),
} satisfies Meta<typeof CalendarCourseCell>;
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
@@ -37,7 +32,7 @@ export const Default: Story = {
courseName: "Bevo's Default Course", courseName: "Bevo's Default Course",
department: 'BVO', department: 'BVO',
creditHours: 3, creditHours: 3,
status: Status.WAITLISTED, status: Status.OPEN,
instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })], instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
isReserved: false, isReserved: false,
url: '', url: '',

View File

@@ -0,0 +1,115 @@
import { Course, Status } from '@shared/types/Course';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react';
import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell/CalendarCourseCell';
import React from 'react';
import { ExampleCourse } from '../PopupCourseBlock.stories';
const meta = {
title: 'Components/Calendar/CalendarCourseCell',
component: CalendarCourseCell,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
courseDeptAndInstr: { control: { type: 'text' } },
className: { control: { type: 'text' } },
status: { control: { type: 'select', options: Object.values(Status) } },
timeAndLocation: { control: { type: 'text' } },
colors: { control: { type: 'object' } },
},
render: (args: any) => (
<div className='w-45'>
<CalendarCourseCell {...args} />
</div>
),
args: {
courseDeptAndInstr: ExampleCourse.department,
className: ExampleCourse.number,
status: ExampleCourse.status,
timeAndLocation: ExampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }),
colors: getCourseColors('emerald', 500),
},
} satisfies Meta<typeof CalendarCourseCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Variants: Story = {
render: props => (
<div className='grid grid-cols-2 h-40 max-w-60 w-90vw gap-x-4 gap-y-2'>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.OPEN })}
// Course = new Course({
// courseName: 'PRINCIPLES OF COMPUTER SYSTEMS',
// creditHours: 3,
// department: 'C S',
// description: [
// 'Restricted to computer science majors.',
// 'An introduction to computer systems software abstractions with an emphasis on the connection of these abstractions to underlying computer hardware. Key abstractions include threads, virtual memory, protection, and I/O. Requires writing of synchronized multithreaded programs and pieces of an operating system.',
// 'Computer Science 439 and 439H may not both be counted.',
// 'Prerequisite: Computer Science 429, or 429H with a grade of at least C-.',
// 'May be counted toward the Independent Inquiry flag requirement.',
// ],
// flags: ['Independent Inquiry'],
// fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
// instructionMode: 'In Person',
// instructors: [
// new Instructor({
// firstName: 'Allison',
// lastName: 'Norman',
// fullName: 'Allison Norman',
// }),
// ],
// isReserved: false,
// number: '439',
// schedule: {
// meetings: [
// new CourseMeeting({
// days: ['Tuesday', 'Thursday'],
// startTime: 930,
// endTime: 1050,
// }),
// new CourseMeeting({
// days: ['Friday'],
// startTime: 600,
// endTime: 720,
// }),
// ],
// },
// semester: {
// code: '12345',
// season: 'Spring',
// year: 2024,
// },
// status: Status.WAITLISTED,
// uniqueId: 67890,
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
// });
colors={getCourseColors('green', 500)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.CLOSED })}
colors={getCourseColors('teal', 400)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.WAITLISTED })}
colors={getCourseColors('indigo', 400)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.CANCELLED })}
colors={getCourseColors('red', 500)}
/>
</div>
),
};

View File

@@ -0,0 +1,121 @@
import { Status } from '@shared/types/Course';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react';
import CalendarGrid from '@views/components/calendar/CalendarGrid/CalendarGrid';
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
const meta = {
title: 'Components/Calendar/CalendarGrid',
component: CalendarGrid,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
saturdayClass: { control: 'boolean' },
},
} satisfies Meta<typeof CalendarGrid>;
export default meta;
const testData: CalendarGridCourse[] = [
{
calendarGridPoint: {
dayIndex: 4,
startIndex: 10,
endIndex: 11,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 2,
startIndex: 5,
endIndex: 6,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 2',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 4,
startIndex: 10,
endIndex: 11,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 2',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 3',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 4',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
];
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
saturdayClass: true,
courseCells: testData,
},
};

View File

@@ -0,0 +1,18 @@
// Calendar.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import CalendarCell from '@views/components/calendar/CalendarGridCell/CalendarGridCell';
const meta = {
title: 'Components/Calendar/CalendarGridCell',
component: CalendarCell,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
tags: ['autodocs'],
},
} satisfies Meta<typeof CalendarCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -0,0 +1,16 @@
import type { Meta, StoryObj } from '@storybook/react';
import CalendarHeader from '@views/components/calendar/CalendarHeader/CalenderHeader';
const meta = {
title: 'Components/Calendar/CalendarHeader',
component: CalendarHeader,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof CalendarHeader>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -0,0 +1,144 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import { UserSchedule } from '@shared/types/UserSchedule';
import type { Meta, StoryObj } from '@storybook/react';
import { CalendarSchedules } from '@views/components/calendar/CalendarSchedules/CalendarSchedules';
import React from 'react';
const meta = {
title: 'Components/Calendar/CalendarSchedules',
component: CalendarSchedules,
parameters: {
layout: 'centered',
tags: ['autodocs'],
},
argTypes: {
dummySchedules: { control: 'object' },
dummyActiveIndex: { control: 'number' },
},
render: (args: any) => (
<div>
<CalendarSchedules {...args} />
</div>
),
} satisfies Meta<typeof CalendarSchedules>;
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, // Add the missing 'hours' property
}),
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: 'Spring',
},
}),
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, // Add the missing 'hours' property
}),
];
export const Default: Story = {
args: {
dummySchedules: schedules,
dummyActiveIndex: 0,
},
};

View File

@@ -0,0 +1,68 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import type { Meta, StoryObj } from '@storybook/react';
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
const exampleCourse: Course = new Course({
uniqueId: 50805,
number: '314',
fullName: 'C S 314 DATA STRUCTURES',
courseName: 'DATA STRUCTURES',
department: 'C S',
creditHours: 3,
status: Status.OPEN,
instructors: [
new Instructor({ fullName: 'SCOTT, MICHAEL', firstName: 'MICHAEL', lastName: 'SCOTT', middleInitial: 'D' }),
],
isReserved: true,
description: [
'Second part of a two-part sequence in programming. Introduction to specifications, simple unit testing, and debugging; building and using canonical data structures; algorithm analysis and reasoning techniques such as assertions and invariants.',
'Computer Science 314 and 314H may not both be counted.',
'BVO 311C and 312H may not both be counted.',
'Prerequisite: Computer Science 312 or 312H with a grade of at least C-.',
'May be counted toward the Quantitative Reasoning flag requirement.',
],
schedule: new CourseSchedule({
meetings: [
new CourseMeeting({
days: [DAY_MAP.T, DAY_MAP.TH],
startTime: 480,
endTime: 570,
location: { building: 'UTC', room: '123' },
}),
new CourseMeeting({
days: [DAY_MAP.TH],
startTime: 570,
endTime: 630,
location: { building: 'JES', room: '123' },
}),
],
}),
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
flags: ['Writing', 'Independent Inquiry'],
instructionMode: 'In Person',
semester: {
code: '12345',
year: 2024,
season: 'Spring',
},
});
const meta: Meta<typeof CourseCatalogInjectedPopup> = {
title: 'Components/Injected/CourseCatalogInjectedPopup',
component: CourseCatalogInjectedPopup,
argTypes: {
onClose: { action: 'onClose' },
},
};
export default meta;
type Story = StoryObj<typeof CourseCatalogInjectedPopup>;
export const Default: Story = {
args: {
course: exampleCourse,
},
};

View File

@@ -1,99 +1,115 @@
import { Course, Status } from 'src/shared/types/Course'; import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting'; import { CourseMeeting } from '@shared/types/CourseMeeting';
import { UserSchedule } from 'src/shared/types/UserSchedule'; import Instructor from '@shared/types/Instructor';
import CoursePopup from 'src/views/components/injected/CoursePopup/CoursePopup'; import { UserSchedule } from '@shared/types/UserSchedule';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import CoursePopup from '@views/components/injected/CoursePopupOld/CoursePopup';
const exampleCourse: Course = new Course({ const exampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3, creditHours: 3,
department: 'C S', department: 'C S',
description: [ description: [
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.', 'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.', 'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
'May be counted toward the Quantitative Reasoning flag requirement.', 'May be counted toward the Quantitative Reasoning flag requirement.',
'Designed to accommodate 100 or more students.', 'Designed to accommodate 100 or more students.',
'Taught as a Web-based course.', 'Taught as a Web-based course.',
],
flags: ['Quantitative Reasoning'],
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
instructionMode: 'Online',
instructors: [],
isReserved: false,
number: '303E',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
endTime: 660,
startTime: 570,
}),
], ],
}, flags: ['Quantitative Reasoning'],
semester: { fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
code: '12345', instructionMode: 'Online',
season: 'Spring', instructors: [
year: 2024, new Instructor({
}, firstName: 'William',
status: Status.CANCELLED, lastName: 'Young',
uniqueId: 12345, middleInitial: 'D',
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', fullName: 'William D Young',
}),
new Instructor({
firstName: 'William',
lastName: 'Young',
middleInitial: 'D',
fullName: 'William D Young',
}),
],
isReserved: false,
number: '303E',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
endTime: 660,
startTime: 570,
}),
],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.CANCELLED,
uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
}); });
const exampleSchedule: UserSchedule = new UserSchedule({ const exampleSchedule: UserSchedule = new UserSchedule({
courses: [exampleCourse], courses: [exampleCourse],
name: 'Example Schedule', name: 'Example Schedule',
hours: 0,
}); });
const meta = { const meta = {
title: 'Components/Injected/CoursePopup', title: 'Components/Injected/CoursePopup',
component: CoursePopup, component: CoursePopup,
// tags: ['autodocs'], // tags: ['autodocs'],
args: { args: {
course: exampleCourse, course: exampleCourse,
activeSchedule: exampleSchedule, activeSchedule: exampleSchedule,
},
argTypes: {
course: {
control: {
type: 'other',
},
}, },
activeSchedule: { argTypes: {
control: { course: {
type: 'other', control: {
}, type: 'other',
},
},
activeSchedule: {
control: {
type: 'other',
},
},
}, },
}, parameters: {
parameters: { design: {
design: { type: 'figma',
type: 'figma', url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=602-1879&mode=design&t=BoS5xBrpSsjgQXqv-11',
url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=602-1879&mode=design&t=BoS5xBrpSsjgQXqv-11', },
}, },
},
} satisfies Meta<typeof CoursePopup>; } satisfies Meta<typeof CoursePopup>;
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
export const Open: Story = { export const Open: Story = {
args: { args: {
course: new Course({ ...exampleCourse, status: Status.OPEN }), course: new Course({ ...exampleCourse, status: Status.OPEN }),
activeSchedule: new UserSchedule({ activeSchedule: new UserSchedule({
courses: [], courses: [],
name: 'Example Schedule', name: 'Example Schedule',
}), hours: 0,
}, }),
},
}; };
export const Closed: Story = { export const Closed: Story = {
args: { args: {
course: new Course({ ...exampleCourse, status: Status.CLOSED }), course: new Course({ ...exampleCourse, status: Status.CLOSED }),
}, },
}; };
export const Cancelled: Story = { export const Cancelled: Story = {
args: { args: {
course: new Course({ ...exampleCourse, status: Status.CANCELLED }), course: new Course({ ...exampleCourse, status: Status.CANCELLED }),
}, },
}; };

View File

@@ -2,13 +2,7 @@
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"lib": [ "lib": ["DOM", "es2021"],
"DOM", "types": ["chrome", "node"]
"es2021" }
],
"types": [
"chrome",
"node",
],
},
} }

View File

@@ -1,21 +1,23 @@
import { Course, ScrapedRow } from '@shared/types/Course'; import type { Course, ScrapedRow } from '@shared/types/Course';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useKeyPress } from '../hooks/useKeyPress'; import { useKeyPress } from '../hooks/useKeyPress';
import useSchedules from '../hooks/useSchedules'; import useSchedules from '../hooks/useSchedules';
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper'; import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
import getCourseTableRows from '../lib/getCourseTableRows'; import getCourseTableRows from '../lib/getCourseTableRows';
import { SiteSupport } from '../lib/getSiteSupport'; import type { SiteSupport } from '../lib/getSiteSupport';
import { populateSearchInputs } from '../lib/populateSearchInputs'; import { populateSearchInputs } from '../lib/populateSearchInputs';
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot'; import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
import AutoLoad from './injected/AutoLoad/AutoLoad'; import AutoLoad from './injected/AutoLoad/AutoLoad';
import CoursePopup from './injected/CoursePopup/CoursePopup'; import CourseCatalogInjectedPopup from './injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
import CoursePopup from './injected/CoursePopupOld/CoursePopup';
import RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner'; import RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner';
import TableHead from './injected/TableHead'; import TableHead from './injected/TableHead';
import TableRow from './injected/TableRow/TableRow'; import TableRow from './injected/TableRow/TableRow';
import TableSubheading from './injected/TableSubheading/TableSubheading'; import TableSubheading from './injected/TableSubheading/TableSubheading';
interface Props { interface Props {
support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST; support: any; // SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST;
} }
/** /**
@@ -79,7 +81,7 @@ export default function CourseCatalogMain({ support }: Props) {
); );
})} })}
{selectedCourse && ( {selectedCourse && (
<CoursePopup <CourseCatalogInjectedPopup
course={selectedCourse} course={selectedCourse}
activeSchedule={activeSchedule} activeSchedule={activeSchedule}
onClose={handleClearSelectedCourse} onClose={handleClearSelectedCourse}

View File

@@ -1,24 +1,164 @@
import { background } from '@shared/messages'; import logoImage from '@assets/logo.png'; // Adjust the path as necessary
import { Status } from '@shared/types/Course';
import { StatusIcon } from '@shared/util/icons';
import Divider from '@views/components/common/Divider/Divider';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import List from '@views/components/common/List/List'; // Ensure this path is correctly pointing to your List component
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import Text from '@views/components/common/Text/Text';
import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions';
import useSchedules from '@views/hooks/useSchedules';
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
import React from 'react'; import React from 'react';
import useSchedules from '../hooks/useSchedules'; import { TestColors } from 'src/stories/components/PopupCourseBlock.stories';
import { Button } from './common/Button/Button';
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
import CalendarIcon from '~icons/material-symbols/calendar-month';
import RefreshIcon from '~icons/material-symbols/refresh';
import SettingsIcon from '~icons/material-symbols/settings';
/**
* Renders the main popup component.
* This component displays the main schedule, courses, and options buttons.
*/
export default function PopupMain() { export default function PopupMain() {
const [activeSchedule, schedules] = useSchedules(); const [activeSchedule] = useSchedules();
// TODO: Add a button to to switch the active schedule const draggableElements = activeSchedule?.courses.map((course, i) => (
<PopupCourseBlock key={course.uniqueId} course={course} colors={TestColors[i]} />
));
const handleOpenOptions = async () => {
// Not sure if it's bad practice to export this
const url = chrome.runtime.getURL('/src/pages/options/index.html');
await openTabFromContentScript(url);
};
return ( return (
<ExtensionRoot> <ExtensionRoot>
<Button <div className='mx-auto max-w-sm rounded-lg bg-white p-4 shadow-md'>
onClick={() => { <div className='mb-2 flex items-center justify-between bg-white'>
if (!activeSchedule) return; <div className='flex items-center'>
background.clearCourses({ scheduleName: activeSchedule?.name }); <img src={logoImage} alt='Logo' style={{ width: '40px', height: '40px', marginRight: '8px' }} />
}} <div>
> <Text as='div' variant='h1-course' style={{ color: '#bf5700', fontSize: '1.3rem' }}>
Clear Courses UT Registration
</Button> </Text>
<Text as='div' variant='h1-course' style={{ color: '#f8971f', fontSize: '1.3rem' }}>
Plus
</Text>
</div>
</div>
<div className='flex items-center'>
<button
style={{ backgroundColor: '#bf5700', borderRadius: '8px', padding: '8px' }}
onClick={handleOpenCalendar}
>
<CalendarIcon color='white' />
</button>
<button
style={{
backgroundColor: 'white',
marginLeft: '10px',
borderRadius: '8px',
padding: '8px',
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)',
}}
onClick={handleOpenOptions}
>
<SettingsIcon color='#C05621' />
</button>
</div>
</div>
{/* <Divider color='#E2E8F0' type='solid' style={{ margin: '1rem 0' }} /> */}
<div
className='mb-4 rounded-lg bg-white p-2 text-left shadow-inner'
style={{ backgroundColor: 'white', border: '1px solid #FBD38D', borderRadius: '0.5rem' }}
>
<Text as='div' variant='h2-course' style={{ color: '#DD6B20', fontSize: '1.2rem' }}>
MAIN SCHEDULE:
</Text>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'start', color: '#333f48' }}>
<Text
as='div'
variant='h1'
style={{ fontSize: '1.2rem', fontWeight: 'bold', marginRight: '0.5rem' }}
>
22 HOURS
</Text>
<Text as='div' variant='h2-course' style={{ fontSize: '1.2rem' }}>
8 Courses
</Text>
</div>
</div>
{/* Integrate the List component here */}
{activeSchedule ? (
<List
draggableElements={draggableElements}
itemHeight={100} // Adjust based on your content size
listHeight={500} // Adjust based on total height you want for the list
listWidth={350} // Adjust based on your layout/design
gap={12} // Spacing between items
/>
) : null}
<div className='mt-4 flex justify-between border-t border-gray-200 p-4 text-xs'>
<div className='flex items-center'>
<div
style={{
backgroundColor: '#6B7280',
padding: '1px',
borderRadius: '4px',
marginRight: '3px',
marginLeft: '8px',
}}
>
<StatusIcon status={Status.WAITLISTED} className='h-5 w-5 text-white' />
</div>
<Text as='span' variant='mini'>
WAITLISTED
</Text>
</div>
<div className='flex items-center'>
<div
style={{
backgroundColor: '#6B7280',
padding: '1px',
borderRadius: '4px',
marginRight: '3px',
marginLeft: '8px',
}}
>
<StatusIcon status={Status.CLOSED} className='h-5 w-5 text-white' />
</div>
<Text as='span' variant='mini'>
CLOSED
</Text>
</div>
<div className='flex items-center'>
<div
style={{
backgroundColor: '#6B7280',
padding: '1px',
borderRadius: '4px',
marginRight: '3px',
marginLeft: '8px',
}}
>
<StatusIcon status={Status.CANCELLED} className='h-5 w-5 text-white' />
</div>
<Text as='span' variant='mini'>
CANCELLED
</Text>
</div>
</div>
<div className='mt-2 text-center text-xs'>
<div className='inline-flex items-center justify-center'>
<Text as='div' variant='mini'>
DATA UPDATED ON: 12:00 AM 02/01/2024
</Text>
<RefreshIcon className='ml-2 h-4 w-4 text-gray-600' />
</div>
</div>
</div>
</ExtensionRoot> </ExtensionRoot>
); );
} }

View File

@@ -0,0 +1,14 @@
import React from 'react';
type Props = {
className?: string;
};
/**
* Component to hold everything for the settings page
* @param props className
* @returns The content for the settings page
*/
export default function Settings({ className }: Props) {
return <div className={className}>this will be finished laterrrrrrr</div>;
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import CalendarHeader from 'src/views/components/calendar/CalendarHeader/CalenderHeader';
import { CalendarBottomBar } from '../CalendarBottomBar/CalendarBottomBar';
import CalendarGrid from '../CalendarGrid/CalendarGrid';
import { CalendarSchedules } from '../CalendarSchedules/CalendarSchedules';
import ImportantLinks from '../ImportantLinks';
export const flags = ['WR', 'QR', 'GC', 'CD', 'E', 'II'];
interface Props {
label: string;
}
/**
* A reusable chip component that follows the design system of the extension.
* @returns
*/
export function Calendar(): JSX.Element {
return (
<>
<CalendarHeader />
<div className='h-screen w-full flex flex-col md:flex-row'>
<div className='min-h-[30%] flex flex-col items-start gap-2.5 p-5 pl-7'>
<div className='min-h-[30%]'>
<CalendarSchedules />
</div>
<ImportantLinks />
</div>
<div className='flex flex-grow flex-col gap-4 overflow-hidden'>
<div className='flex-grow overflow-auto'>
<CalendarGrid />
</div>
<div>
<CalendarBottomBar />
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,47 @@
import clsx from 'clsx';
import React from 'react';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
import ImageIcon from '~icons/material-symbols/image';
import { Button } from '../../common/Button/Button';
import Text from '../../common/Text/Text';
import type { CalendarCourseCellProps } from '../CalendarCourseCell/CalendarCourseCell';
import CalendarCourseBlock from '../CalendarCourseCell/CalendarCourseCell';
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

@@ -0,0 +1,34 @@
.component {
display: flex;
padding: 7px 7px 9px 7px;
flex-direction: column;
align-items: flex-start;
gap: 5px;
flex: 1 0 0;
align-self: stretch;
border-radius: 4px;
background: #cbd5e1;
.content {
display: flex;
gap: 7px;
.course-detail {
display: flex;
flex-direction: column;
gap: 5px;
width: 154px;
.course {
font-size: 16px;
line-height: 16px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.time-and-location {
font-size: 11px;
line-height: 11px;
font-weight: 400;
}
}
}
}

View File

@@ -0,0 +1,52 @@
import React from 'react';
import type { Course } from 'src/shared/types/Course';
import type { CourseMeeting } from 'src/shared/types/CourseMeeting';
import styles from './CalendarCourseMeeting.module.scss';
/**
* Props for the CalendarCourseMeeting component.
*/
export interface CalendarCourseMeetingProps {
/** 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;
/** The icon to display on the right side of the course. This is optional. */
rightIcon?: React.ReactNode;
}
/**
* `CalendarCourseMeeting` is a functional component that displays a course meeting.
*
* @example
* <CalendarCourseMeeting course={course} meeting={meeting} color="red" rightIcon={<Icon />} />
*/
const CalendarCourseMeeting: React.FC<CalendarCourseMeetingProps> = ({
course,
meetingIdx,
color,
rightIcon,
}: CalendarCourseMeetingProps) => {
let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;
return (
<div className={styles.component}>
<div className={styles.content}>
<div className={styles['course-detail']}>
<div className={styles.course}>
{course.department} {course.number} - {course.instructors[0].lastName}
</div>
<div className={styles['time-and-location']}>
{`${meeting.getTimeString({ separator: '-', capitalize: true })}${
meeting.location ? ` - ${meeting.location.building}` : ''
}`}
</div>
</div>
</div>
</div>
);
};
export default CalendarCourseMeeting;

Some files were not shown because too many files have changed in this diff Show More