From 442be8cbee06ec403467b100d8d5300dd4a7ea72 Mon Sep 17 00:00:00 2001 From: Razboy20 Date: Wed, 13 Mar 2024 23:09:43 -0500 Subject: [PATCH] feat: update dialog component to headlessui (#159) --- ...=> CourseCatalogInjectedPopup.stories.tsx} | 8 ++ src/views/components/CourseCatalogMain.tsx | 21 +++-- .../components/calendar/Calendar/Calendar.tsx | 25 +++--- .../components/calendar/ImportantLinks.tsx | 87 +++++++++---------- src/views/components/common/Dialog/Dialog.tsx | 58 +++++++++++++ .../components/common/Popup/Popup.module.scss | 24 ----- src/views/components/common/Popup/Popup.tsx | 65 -------------- .../CourseCatalogInjectedPopup.tsx | 31 ++++--- 8 files changed, 149 insertions(+), 170 deletions(-) rename src/stories/injected/{CourseCatalogInjectedPopup.stories.ts => CourseCatalogInjectedPopup.stories.tsx} (83%) create mode 100644 src/views/components/common/Dialog/Dialog.tsx delete mode 100644 src/views/components/common/Popup/Popup.module.scss delete mode 100644 src/views/components/common/Popup/Popup.tsx diff --git a/src/stories/injected/CourseCatalogInjectedPopup.stories.ts b/src/stories/injected/CourseCatalogInjectedPopup.stories.tsx similarity index 83% rename from src/stories/injected/CourseCatalogInjectedPopup.stories.ts rename to src/stories/injected/CourseCatalogInjectedPopup.stories.tsx index 5880530d..73c8f1fa 100644 --- a/src/stories/injected/CourseCatalogInjectedPopup.stories.ts +++ b/src/stories/injected/CourseCatalogInjectedPopup.stories.tsx @@ -3,6 +3,7 @@ import { Status } from '@shared/types/Course'; import { UserSchedule } from '@shared/types/UserSchedule'; import type { Meta, StoryObj } from '@storybook/react'; import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; +import React, { useState } from 'react'; import { bevoCourse, bevoScheule, MikeScottCS314Course, MikeScottCS314Schedule } from './mocked'; @@ -10,6 +11,7 @@ const meta = { title: 'Components/Injected/CourseCatalogInjectedPopup', component: CourseCatalogInjectedPopup, args: { + open: true, onClose: () => {}, }, argTypes: { @@ -29,6 +31,12 @@ const meta = { }, }, }, + render(args) { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [isOpen, setIsOpen] = useState(args.open); + + return setIsOpen(false)} />; + }, } satisfies Meta; export default meta; diff --git a/src/views/components/CourseCatalogMain.tsx b/src/views/components/CourseCatalogMain.tsx index 5e0b12de..ff40c568 100644 --- a/src/views/components/CourseCatalogMain.tsx +++ b/src/views/components/CourseCatalogMain.tsx @@ -24,11 +24,18 @@ interface Props { export default function CourseCatalogMain({ support }: Props): JSX.Element { const [rows, setRows] = React.useState([]); const [selectedCourse, setSelectedCourse] = useState(null); + const [showPopup, setShowPopup] = useState(false); useEffect(() => { populateSearchInputs(); }, []); + useEffect(() => { + if (selectedCourse) { + setShowPopup(true); + } + }, [selectedCourse]); + useEffect(() => { const tableRows = getCourseTableRows(document); const ccs = new CourseCatalogScraper(support); @@ -75,13 +82,13 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element { /> ) )} - {selectedCourse && ( - - )} + setShowPopup(false)} + afterLeave={handleClearSelectedCourse} + /> ); diff --git a/src/views/components/calendar/Calendar/Calendar.tsx b/src/views/components/calendar/Calendar/Calendar.tsx index c4d78dfe..bac0b924 100644 --- a/src/views/components/calendar/Calendar/Calendar.tsx +++ b/src/views/components/calendar/Calendar/Calendar.tsx @@ -4,7 +4,6 @@ import CalendarGrid from '@views/components/calendar/CalendarGrid/CalendarGrid'; import CalendarHeader from '@views/components/calendar/CalendarHeader/CalenderHeader'; import { CalendarSchedules } from '@views/components/calendar/CalendarSchedules/CalendarSchedules'; import ImportantLinks from '@views/components/calendar/ImportantLinks'; -import TeamLinks from '@views/components/calendar/TeamLinks'; import Divider from '@views/components/common/Divider/Divider'; import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule'; @@ -19,6 +18,7 @@ export default function Calendar(): JSX.Element { const calendarRef = useRef(null); const { courseCells, activeSchedule } = useFlattenedCourseSchedule(); const [course, setCourse] = useState(null); + const [showPopup, setShowPopup] = useState(false); const [sidebarWidth, setSidebarWidth] = useState('20%'); const [scale, setScale] = useState(1); @@ -48,6 +48,10 @@ export default function Calendar(): JSX.Element { return () => window.removeEventListener('resize', adjustLayout); }, []); + useEffect(() => { + if (course) setShowPopup(true); + }, [course]); + const calendarContainerStyle = { transform: `scale(${scale})`, transformOrigin: 'top left', @@ -68,10 +72,6 @@ export default function Calendar(): JSX.Element {
- -
- -
@@ -80,13 +80,14 @@ export default function Calendar(): JSX.Element {
- {course ? ( - setCourse(null)} - /> - ) : null} + + setShowPopup(false)} + open={showPopup} + afterLeave={() => setCourse(null)} + /> ); } diff --git a/src/views/components/calendar/ImportantLinks.tsx b/src/views/components/calendar/ImportantLinks.tsx index 26414cfb..6cdce39a 100644 --- a/src/views/components/calendar/ImportantLinks.tsx +++ b/src/views/components/calendar/ImportantLinks.tsx @@ -8,6 +8,34 @@ type Props = { className?: string; }; +interface LinkItem { + text: string; + url: string; +} + +const links: LinkItem[] = [ + { + text: "Summer '24 Course Schedule", + url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/', + }, + { + text: "Fall '24 Course Schedule", + url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20236/', + }, + { + text: 'Registration Info Sheet', + url: 'https://utdirect.utexas.edu/registrar/ris.WBX', + }, + { + text: 'Register For Courses', + url: 'https://utdirect.utexas.edu/registration/chooseSemester.WBX', + }, + { + text: 'Degree Audit', + url: 'https://utdirect.utexas.edu/apps/degree/audits/', + }, +]; + /** * The "Important Links" section of the calendar website * @returns @@ -15,52 +43,19 @@ type Props = { export default function ImportantLinks({ className }: Props): JSX.Element { return ( ); } diff --git a/src/views/components/common/Dialog/Dialog.tsx b/src/views/components/common/Dialog/Dialog.tsx new file mode 100644 index 00000000..a3ed57bc --- /dev/null +++ b/src/views/components/common/Dialog/Dialog.tsx @@ -0,0 +1,58 @@ +import type { TransitionRootProps } from '@headlessui/react'; +import { Dialog as HDialog, Transition } from '@headlessui/react'; +import clsx from 'clsx'; +import type { PropsWithChildren } from 'react'; +import React, { Fragment } from 'react'; + +export interface _DialogProps { + className?: string; + title?: JSX.Element; + description?: JSX.Element; +} + +export type DialogProps = _DialogProps & Omit, 'children'>; + +/** + * A reusable popup component that can be used to display content on the page + */ +export default function Dialog(props: PropsWithChildren): JSX.Element { + const { children, className, open, onTransitionEnd, ...rest } = props; + + return ( + + +
+ + +
+ + {props.title && {props.title}} + {props.description && {props.description}} + {children} + +
+
+ + ); +} diff --git a/src/views/components/common/Popup/Popup.module.scss b/src/views/components/common/Popup/Popup.module.scss deleted file mode 100644 index 0a56e3b1..00000000 --- a/src/views/components/common/Popup/Popup.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -@use 'src/views/styles/colors.module.scss'; - -.container { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - position: fixed; - top: 0; - - &.overlay { - background-color: rgba(0, 0, 0, 0.5); - z-index: 2147483647; - } -} - -.body { - overflow-y: auto; - z-index: 2147483647; - background-color: colors.$white; - box-shadow: 0px 12px 30px 0px #323e5f29; - transition: box-shadow 0.15s; -} diff --git a/src/views/components/common/Popup/Popup.tsx b/src/views/components/common/Popup/Popup.tsx deleted file mode 100644 index 948ecf2b..00000000 --- a/src/views/components/common/Popup/Popup.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import clsx from 'clsx'; -import type { PropsWithChildren } from 'react'; -import React, { useCallback } from 'react'; - -import styles from './Popup.module.scss'; - -interface Props { - testId?: string; - style?: React.CSSProperties; - className?: string; - /** Should it display a subtle dark overlay over the rest of the screen */ - overlay?: boolean; - onClose?: () => void; -} - -/** - * A reusable popup component that can be used to display content on the page - */ -export default function Popup({ - onClose, - children, - className, - style, - testId, - overlay, -}: PropsWithChildren): JSX.Element { - const containerRef = React.useRef(null); - const bodyRef = React.useRef(null); - - const handleClickOutside = useCallback( - (event: MouseEvent) => { - if (!bodyRef.current) return; - if (!bodyRef.current.contains(event.target as Node)) { - onClose?.(); - } - }, - [onClose, bodyRef] - ); - - React.useEffect(() => { - const shadowRoot = document.getElementById('ut-registration-plus-container')?.shadowRoot; - if (!shadowRoot) return; - - shadowRoot.addEventListener('mousedown', handleClickOutside); - - return () => { - shadowRoot.removeEventListener('mousedown', handleClickOutside); - }; - }, [handleClickOutside]); - - return ( -
-
- {children} -
-
- ); -} diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx index b710e0ca..7c606015 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup.tsx @@ -1,17 +1,17 @@ import type { Course } from '@shared/types/Course'; import type { UserSchedule } from '@shared/types/UserSchedule'; -import Popup from '@views/components/common/Popup/Popup'; +import type { DialogProps } from '@views/components/common/Dialog/Dialog'; +import Dialog from '@views/components/common/Dialog/Dialog'; import React from 'react'; import Description from './Description'; import GradeDistribution from './GradeDistribution'; import HeadingAndActions from './HeadingAndActions'; -interface CourseCatalogInjectedPopupProps { +export type CourseCatalogInjectedPopupProps = DialogProps & { course: Course; activeSchedule: UserSchedule; - onClose: () => void; -} +}; /** * CourseCatalogInjectedPopup component displays a popup with course details. @@ -23,18 +23,17 @@ interface CourseCatalogInjectedPopupProps { * @param {Function} props.onClose - The function to close the popup. * @returns {JSX.Element} The CourseCatalogInjectedPopup component. */ -export default function CourseCatalogInjectedPopup({ - course, - activeSchedule, - onClose, -}: CourseCatalogInjectedPopupProps): JSX.Element { +function CourseCatalogInjectedPopup({ course, activeSchedule, ...rest }: CourseCatalogInjectedPopupProps): JSX.Element { + const emptyRef = React.useRef(null); + return ( - -
- - - -
-
+ +
+ void} activeSchedule={activeSchedule} /> + + +
); } + +export default React.memo(CourseCatalogInjectedPopup);