refactor: ccpopup (#172)
* refactor: dialog animation improvements * refactor: update ccpopup to match designs
This commit is contained in:
@@ -27,7 +27,7 @@ export function Chip({ label }: React.PropsWithChildren<Props>): JSX.Element {
|
|||||||
<Text
|
<Text
|
||||||
as='div'
|
as='div'
|
||||||
variant='h4'
|
variant='h4'
|
||||||
className='min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1 py-0.5'
|
className='min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1.5 py-0.5'
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: '#FFD600',
|
backgroundColor: '#FFD600',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -25,28 +25,28 @@ export default function Dialog(props: PropsWithChildren<DialogProps>): JSX.Eleme
|
|||||||
<ExtensionRoot>
|
<ExtensionRoot>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter='transition duration-300 ease-out'
|
enter='transition duration-300 motion-reduce:duration-150 ease-out'
|
||||||
enterFrom='opacity-0'
|
enterFrom='opacity-0'
|
||||||
enterTo='opacity-100'
|
enterTo='opacity-100'
|
||||||
leave='transition duration-150 ease-in delay-25'
|
leave='transition duration-150 ease-in delay-25'
|
||||||
leaveFrom='opacity-100'
|
leaveFrom='opacity-100'
|
||||||
leaveTo='opacity-0'
|
leaveTo='opacity-0'
|
||||||
>
|
>
|
||||||
<div className={clsx('fixed inset-0 z-50 bg-neutral-500/25')} />
|
<div className={clsx('fixed inset-0 z-50 bg-slate-700/35')} />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter='transition duration-400 ease-[cubic-bezier(0.15,0.3,0.2,1)]'
|
enter='transition duration-375 motion-reduce:duration-0 ease-[cubic-bezier(0.05,0.4,0.2,1)]'
|
||||||
enterFrom='transform scale-95 opacity-0'
|
enterFrom='transform-gpu scale-95 opacity-0'
|
||||||
enterTo='transform scale-100 opacity-100'
|
enterTo='transform-gpu scale-100 opacity-100'
|
||||||
leave='transition duration-250 ease-[cubic-bezier(0.23,0.01,0.92,0.72)]'
|
leave='transition duration-250 motion-reduce:duration-0 ease-[cubic-bezier(0.23,0.01,0.92,0.72)]'
|
||||||
leaveFrom='transform scale-100 opacity-100'
|
leaveFrom='transform-gpu scale-100 opacity-100'
|
||||||
leaveTo='transform scale-95 opacity-0'
|
leaveTo='transform-gpu scale-95 opacity-0'
|
||||||
>
|
>
|
||||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||||
<HDialog.Panel
|
<HDialog.Panel
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'z-99 max-h-[80vh] flex flex-col overflow-y-auto rounded bg-white shadow-xl',
|
'z-99 max-h-[90vh] flex flex-col overflow-y-auto border border-ut-offwhite rounded-lg bg-white shadow-xl ml-[calc(100vw-100%)] mt-[calc(100vw-100%)]',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
.p {
|
.p {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: 0.025rem;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h4 {
|
.h4 {
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
.h2-course {
|
.h2-course {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 0.03125rem;
|
letter-spacing: 0.03125em;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ function CourseCatalogInjectedPopup({ course, ...rest }: CourseCatalogInjectedPo
|
|||||||
const [activeSchedule] = useSchedules();
|
const [activeSchedule] = useSchedules();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog className='max-w-[780px] px-6' {...rest} initialFocus={emptyRef}>
|
<Dialog className='max-w-[780px] overflow-y-hidden px-4' {...rest} initialFocus={emptyRef}>
|
||||||
<div className='hidden' ref={emptyRef} />
|
<div className='hidden' ref={emptyRef} />
|
||||||
<HeadingAndActions course={course} onClose={rest.onClose as () => void} activeSchedule={activeSchedule} />
|
<HeadingAndActions course={course} onClose={rest.onClose as () => void} activeSchedule={activeSchedule} />
|
||||||
|
<div className='overflow-y-auto px-2'>
|
||||||
<Description course={course} />
|
<Description course={course} />
|
||||||
<GradeDistribution course={course} />
|
<GradeDistribution course={course} />
|
||||||
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,26 +56,24 @@ export default function Description({ course }: DescriptionProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{status === LoadStatus.ERROR && (
|
{status === LoadStatus.ERROR && (
|
||||||
<Text color='theme-red'>Please refresh the page and log back in using your UT EID and password</Text>
|
<Text color='theme-red'>Please refresh the page and log back in using your UT EID and password.</Text>
|
||||||
)}
|
)}
|
||||||
{/* TODO (achadaga): would be nice to have a new spinner here */}
|
{/* TODO (achadaga): would be nice to have a new spinner here */}
|
||||||
{status === LoadStatus.LOADING && <Spinner />}
|
{status === LoadStatus.LOADING && <Spinner />}
|
||||||
{status === LoadStatus.DONE && (
|
{status === LoadStatus.DONE && (
|
||||||
<ul className='my-[5px] space-y-1.5 children:marker:text-ut-burntorange'>
|
<ul className='ml-6 mt-1.5 list-disc list-outside space-y-1.5'>
|
||||||
{description.map(line => {
|
{description.map(line => {
|
||||||
const isKeywordPresent = keywords.some(keyword => line.toLowerCase().includes(keyword));
|
const isKeywordPresent = keywords.some(keyword => line.toLowerCase().includes(keyword));
|
||||||
return (
|
return (
|
||||||
<div key={line} className='flex gap-2'>
|
<li
|
||||||
<span className='text-ut-burntorange'>•</span>
|
key={line}
|
||||||
<li key={line}>
|
className={clsx({
|
||||||
<Text
|
'children:font-bold! text-ut-burntorange marker:text-ut-burntorange':
|
||||||
variant='p'
|
isKeywordPresent,
|
||||||
className={clsx({ 'font-bold! text-ut-burntorange': isKeywordPresent })}
|
})}
|
||||||
>
|
>
|
||||||
{line}
|
<Text variant='p'>{line}</Text>
|
||||||
</Text>
|
|
||||||
</li>
|
</li>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -97,13 +97,13 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
|
|||||||
subtitle: { text: undefined },
|
subtitle: { text: undefined },
|
||||||
legend: { enabled: false },
|
legend: { enabled: false },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
title: { text: 'Grade' },
|
title: { text: 'Grades' },
|
||||||
categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F'],
|
categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F'],
|
||||||
crosshair: true,
|
crosshair: true,
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
min: 0,
|
min: 0,
|
||||||
title: { text: 'Number of Students' },
|
title: { text: 'Students' },
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
style: { fontFamily: 'Roboto Flex, Roboto Flex Local', fontWeight: '600' },
|
style: { fontFamily: 'Roboto Flex, Roboto Flex Local', fontWeight: '600' },
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { background } from '@shared/messages';
|
import { background } from '@shared/messages';
|
||||||
import type { Course } from '@shared/types/Course';
|
import type { Course } from '@shared/types/Course';
|
||||||
import { Status } from '@shared/types/Course';
|
|
||||||
import type Instructor from '@shared/types/Instructor';
|
import type Instructor from '@shared/types/Instructor';
|
||||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import { Button } from '@views/components/common/Button/Button';
|
import { Button } from '@views/components/common/Button/Button';
|
||||||
import { Chip, flagMap } from '@views/components/common/Chip/Chip';
|
import { Chip, flagMap } from '@views/components/common/Chip/Chip';
|
||||||
import Divider from '@views/components/common/Divider/Divider';
|
import Divider from '@views/components/common/Divider/Divider';
|
||||||
|
import Link from '@views/components/common/Link/Link';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -66,7 +66,8 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
return `${capitalizeString(firstName)} ${capitalizeString(lastName)}`;
|
return `${capitalizeString(firstName)} ${capitalizeString(lastName)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const instructorString = instructors.map(getInstructorFullName).join(', ');
|
const getBuildingUrl = (building: string) =>
|
||||||
|
`https://utdirect.utexas.edu/apps/campus/buildings/nlogon/maps/UTM/${building}`;
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
navigator.clipboard.writeText(formattedUniqueId);
|
navigator.clipboard.writeText(formattedUniqueId);
|
||||||
@@ -108,50 +109,71 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full pb-3 pt-6'>
|
<div className='w-full px-2 pb-3 pt-6 text-ut-black'>
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
<Text variant='h1' className='truncate'>
|
<Text variant='h1' as='h1' className='truncate text-theme-black'>
|
||||||
{courseName}
|
{courseName}
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant='h1' className='flex-1 whitespace-nowrap'>
|
<Text variant='h1' as='h2' className='flex-1 whitespace-nowrap'>
|
||||||
{' '}
|
|
||||||
({department} {courseNumber})
|
({department} {courseNumber})
|
||||||
</Text>
|
</Text>
|
||||||
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}>
|
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}>
|
||||||
{formattedUniqueId}
|
{formattedUniqueId}
|
||||||
</Button>
|
</Button>
|
||||||
<button className='bg-transparent p-0 btn' onClick={onClose}>
|
<button className='bg-transparent p-0 text-theme-black btn' onClick={onClose}>
|
||||||
<CloseIcon className='h-7 w-7' />
|
<CloseIcon className='h-7 w-7' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex gap-2 flex-content-center'>
|
<div className='flex items-center gap-2'>
|
||||||
{instructorString.length > 0 && (
|
{instructors.length > 0 && (
|
||||||
<Text variant='h4' className='inline-flex items-center justify-center'>
|
<Text variant='h4' as='p' className='items-center justify-center'>
|
||||||
with {instructorString}
|
with{' '}
|
||||||
|
{instructors
|
||||||
|
.map(instructor => (
|
||||||
|
<Link
|
||||||
|
key={instructor.fullName}
|
||||||
|
variant='h4'
|
||||||
|
href={instructor.getDirectoryUrl()}
|
||||||
|
className='link'
|
||||||
|
>
|
||||||
|
{getInstructorFullName(instructor)}
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
.flatMap((el, i) => (i === 0 ? [el] : [', ', el]))}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<div className='flex-content-centr flex gap-1'>
|
<div className='flex gap-1'>
|
||||||
{flags.map(flag => (
|
{flags.map(flag => (
|
||||||
<Chip key={flagMap[flag]} label={flagMap[flag]} />
|
<Chip key={flagMap[flag]} label={flagMap[flag]} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col'>
|
<div className='mt-1 flex flex-col'>
|
||||||
{schedule.meetings.map(meeting => {
|
{schedule.meetings.map(meeting => {
|
||||||
const daysString = meeting.getDaysString({ format: 'long', separator: 'long' });
|
const daysString = meeting.getDaysString({ format: 'long', separator: 'long' });
|
||||||
const timeString = meeting.getTimeString({ separator: ' to ', capitalize: false });
|
const timeString = meeting.getTimeString({ separator: ' to ', capitalize: false });
|
||||||
const locationString = meeting.location ? ` in ${meeting.location.building}` : '';
|
|
||||||
return (
|
return (
|
||||||
<Text key={daysString + timeString + locationString} variant='h4'>
|
<Text key={daysString + timeString + meeting.location.building} variant='h4' as='p'>
|
||||||
{daysString} {timeString}
|
{daysString} {timeString}
|
||||||
{locationString}
|
{meeting.location && (
|
||||||
|
<>
|
||||||
|
{' in '}
|
||||||
|
<Link
|
||||||
|
href={getBuildingUrl(meeting.location.building)}
|
||||||
|
className='link'
|
||||||
|
variant='h4'
|
||||||
|
>
|
||||||
|
{meeting.location.building}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='my-3 flex flex-wrap items-center gap-[15px]'>
|
<div className='my-3 flex flex-wrap items-center gap-x-3.75 gap-y-2.5'>
|
||||||
<Button variant='filled' color='ut-burntorange' icon={CalendarMonth} onClick={handleOpenCalendar} />
|
<Button variant='filled' color='ut-burntorange' icon={CalendarMonth} onClick={handleOpenCalendar} />
|
||||||
<Divider size='1.75rem' orientation='vertical' />
|
<Divider size='1.75rem' orientation='vertical' />
|
||||||
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
||||||
@@ -165,8 +187,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant='filled'
|
variant='filled'
|
||||||
disabled={course.status !== Status.OPEN}
|
color={!courseAdded ? 'ut-green' : 'theme-red'}
|
||||||
color={!courseAdded ? 'ut-green' : 'ut-red'}
|
|
||||||
icon={!courseAdded ? Add : Remove}
|
icon={!courseAdded ? Add : Remove}
|
||||||
onClick={handleAddOrRemoveCourse}
|
onClick={handleAddOrRemoveCourse}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user