feat: match calendar designs & add functionality (#176)

* feat: match calendar designs

* refactor: update breakpoints
This commit is contained in:
Razboy20
2024-03-18 10:06:23 -05:00
committed by GitHub
parent dc77cc27da
commit 8027c3d1bf
14 changed files with 132 additions and 61 deletions

View File

@@ -48,6 +48,7 @@
"@commitlint/types": "^19.0.3", "@commitlint/types": "^19.0.3",
"@crxjs/vite-plugin": "2.0.0-beta.21", "@crxjs/vite-plugin": "2.0.0-beta.21",
"@iconify-json/material-symbols": "^1.1.73", "@iconify-json/material-symbols": "^1.1.73",
"@iconify-json/ri": "^1.1.20",
"@storybook/addon-designs": "^7.0.9", "@storybook/addon-designs": "^7.0.9",
"@storybook/addon-essentials": "^7.6.17", "@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17", "@storybook/addon-links": "^7.6.17",

9
pnpm-lock.yaml generated
View File

@@ -81,6 +81,9 @@ devDependencies:
'@iconify-json/material-symbols': '@iconify-json/material-symbols':
specifier: ^1.1.73 specifier: ^1.1.73
version: 1.1.73 version: 1.1.73
'@iconify-json/ri':
specifier: ^1.1.20
version: 1.1.20
'@storybook/addon-designs': '@storybook/addon-designs':
specifier: ^7.0.9 specifier: ^7.0.9
version: 7.0.9(@storybook/addon-docs@7.6.17)(@storybook/addons@7.6.17)(@storybook/components@7.6.17)(@storybook/manager-api@7.6.17)(@storybook/preview-api@7.6.17)(@storybook/theming@7.6.17)(react-dom@18.2.0)(react@18.2.0) version: 7.0.9(@storybook/addon-docs@7.6.17)(@storybook/addons@7.6.17)(@storybook/components@7.6.17)(@storybook/manager-api@7.6.17)(@storybook/preview-api@7.6.17)(@storybook/theming@7.6.17)(react-dom@18.2.0)(react@18.2.0)
@@ -2341,6 +2344,12 @@ packages:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/ri@1.1.20:
resolution: {integrity: sha512-yScIGjLFBCJKWKskQTWRjNI2Awoq+VRDkRxEsCQvSfdz41n+xkRtFG2K6J1OVI90ClRHfjFC8VJ2+WzxxyFjTQ==}
dependencies:
'@iconify/types': 2.0.0
dev: true
/@iconify/types@2.0.0: /@iconify/types@2.0.0:
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
dev: true dev: true

View File

@@ -113,7 +113,7 @@ export default function PopupMain(): JSX.Element {
</div> </div>
<div className='inline-flex items-center self-center gap-1'> <div className='inline-flex items-center self-center gap-1'>
<Text variant='mini' className='text-ut-gray'> <Text variant='mini' className='text-ut-gray'>
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)} DATA LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
</Text> </Text>
<button <button
className='h-4 w-4 bg-transparent p-0 btn' className='h-4 w-4 bg-transparent p-0 btn'

View File

@@ -11,6 +11,9 @@ import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSched
import { MessageListener } from 'chrome-extension-toolkit'; import { MessageListener } from 'chrome-extension-toolkit';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import CalendarFooter from '../CalendarFooter';
import TeamLinks from '../TeamLinks';
/** /**
* A reusable chip component that follows the design system of the extension. * A reusable chip component that follows the design system of the extension.
* @returns * @returns
@@ -32,6 +35,7 @@ export default function Calendar(): JSX.Element {
}); });
const [showPopup, setShowPopup] = useState<boolean>(course !== null); const [showPopup, setShowPopup] = useState<boolean>(course !== null);
const [showSidebar, setShowSidebar] = useState<boolean>(true);
useEffect(() => { useEffect(() => {
const listener = new MessageListener<CalendarTabMessages>({ const listener = new MessageListener<CalendarTabMessages>({
@@ -55,15 +59,26 @@ export default function Calendar(): JSX.Element {
return ( return (
<div className='h-full w-full flex flex-col'> <div className='h-full w-full flex flex-col'>
<CalendarHeader /> <CalendarHeader
<div className='h-full flex flex-grow overflow-hidden pl-7.5'> onSidebarToggle={() => {
<div className='overflow-auto py-5'> setShowSidebar(!showSidebar);
<CalendarSchedules /> }}
<Divider orientation='horizontal' size='100%' className='my-5' /> />
<ImportantLinks /> <div className='h-full flex overflow-auto pl-3'>
</div> {showSidebar && (
<div className='flex flex-grow flex-col' ref={calendarRef}> <div className='h-full flex flex-none flex-col justify-between pb-5 pl-4.5'>
<div className='flex-grow overflow-auto px-4 pt-6.5'> <div className='mb-3 h-full w-fit flex flex-col overflow-auto pb-2 pr-4 pt-5'>
<CalendarSchedules />
<Divider orientation='horizontal' size='100%' className='my-5' />
<ImportantLinks />
<Divider orientation='horizontal' size='100%' className='my-5' />
<TeamLinks />
</div>
<CalendarFooter />
</div>
)}
<div className='h-full min-w-3xl flex flex-grow flex-col overflow-y-auto' ref={calendarRef}>
<div className='min-h-2xl flex-grow overflow-auto pl-2 pr-4 pt-2xl'>
<CalendarGrid courseCells={courseCells} setCourse={setCourse} /> <CalendarGrid courseCells={courseCells} setCourse={setCourse} />
</div> </div>
<CalendarBottomBar calendarRef={calendarRef} /> <CalendarBottomBar calendarRef={calendarRef} />

View File

@@ -35,7 +35,7 @@ export default function CalendarBottomBar({ courses, calendarRef }: CalendarBott
> >
{displayCourses && ( {displayCourses && (
<> <>
<Text variant='h4'>Other Classes:</Text> <Text variant='h4'>Async/Other:</Text>
<div className='inline-flex gap-2.5'> <div className='inline-flex gap-2.5'>
{courses.map(({ courseDeptAndInstr, status, colors, className }) => ( {courses.map(({ courseDeptAndInstr, status, colors, className }) => (
<CalendarCourseBlock <CalendarCourseBlock

View File

@@ -0,0 +1,33 @@
import React from 'react';
import DiscordIcon from '~icons/ri/discord-line';
import GithubIcon from '~icons/ri/github-fill';
import InstagramIcon from '~icons/ri/instagram-line';
import Link from '../common/Link/Link';
/**
* The footer section of the calendar's sidebar
* @returns
*/
export default function CalendarFooter(): JSX.Element {
return (
<footer className='min-w-full w-0 space-y-2'>
<div className='flex gap-2'>
<Link className='linkanimate' href='#'>
<InstagramIcon className='h-6 w-6' />
</Link>
<Link className='linkanimate' href='#'>
<DiscordIcon className='h-6 w-6' />
</Link>
<Link className='linkanimate' href='#'>
<GithubIcon className='h-6 w-6' />
</Link>
</div>
<p className='text-2.5 text-ut-concrete font-light tracking-wide'>
UT Registration Plus is a project under Longhorn Developers, a student-led organization aimed at
addressing issues at UT Austin.
</p>
</footer>
);
}

View File

@@ -57,7 +57,7 @@ export default function CalendarGrid({
<div className='w-4 border-b border-r border-gray-300' /> <div className='w-4 border-b border-r border-gray-300' />
{daysOfWeek.map(day => ( {daysOfWeek.map(day => (
<div className='h-4 flex items-end justify-center border-b border-r border-gray-300 pb-1.5'> <div className='h-4 flex items-end justify-center border-b border-r border-gray-300 pb-1.5'>
<Text key={day} variant='small' className='text text-center text-ut-burntorange' as='div'> <Text key={day} variant='small' className='text-center text-ut-burntorange' as='div'>
{day} {day}
</Text> </Text>
</div> </div>
@@ -80,6 +80,7 @@ interface AccountForCourseConflictsProps {
} }
// TODO: Possibly refactor to be more concise // TODO: Possibly refactor to be more concise
// TODO: Deal with react strict mode (wacky movements)
function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseConflictsProps): JSX.Element[] { function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseConflictsProps): JSX.Element[] {
// Groups by dayIndex to identify overlaps // Groups by dayIndex to identify overlaps
const days = courseCells.reduce((acc, cell: CalendarGridCourse) => { const days = courseCells.reduce((acc, cell: CalendarGridCourse) => {

View File

@@ -22,19 +22,23 @@ const handleOpenOptions = async (): Promise<void> => {
await openTabFromContentScript(url); await openTabFromContentScript(url);
}; };
interface CalendarHeaderProps {
onSidebarToggle?: () => void;
}
/** /**
* Renders the header component for the calendar. * Renders the header component for the calendar.
* @returns The JSX element representing the calendar header. * @returns The JSX element representing the calendar header.
*/ */
export default function CalendarHeader(): JSX.Element { export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps): JSX.Element {
const [activeSchedule] = useSchedules(); const [activeSchedule] = useSchedules();
return ( return (
<div className='flex items-center gap-5 border-b px-7 py-4'> <div className='flex items-center gap-5 border-b border-ut-offwhite px-7 py-4'>
<Button variant='single' icon={MenuIcon} color='ut-gray' /> <Button variant='single' icon={MenuIcon} color='ut-gray' onClick={onSidebarToggle} />
<LargeLogo /> <LargeLogo />
<Divider className='mx-4 self-center' size='2.5rem' orientation='vertical' /> <Divider className='mx-2 self-center md:mx-4' size='2.5rem' orientation='vertical' />
<div className='flex-grow'> <div className='flex-1'>
<ScheduleTotalHoursAndCourses <ScheduleTotalHoursAndCourses
scheduleName={activeSchedule.name} scheduleName={activeSchedule.name}
totalHours={activeSchedule.hours} totalHours={activeSchedule.hours}
@@ -42,14 +46,14 @@ export default function CalendarHeader(): JSX.Element {
/> />
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>
<Text variant='mini' className='text-ut-gray'> <Text variant='mini' className='text-ut-gray'>
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)} DATA LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
</Text> </Text>
<button className='inline-block h-4 w-4 bg-transparent p-0 btn'> <button className='inline-block h-4 w-4 bg-transparent p-0 btn'>
<RefreshIcon className='h-4 w-4 animate-duration-800 text-ut-black' /> <RefreshIcon className='h-4 w-4 animate-duration-800 text-ut-black' />
</button> </button>
</div> </div>
</div> </div>
<div className='flex flex-row items-center justify-end gap-6'> <div className='hidden flex-row items-center justify-end gap-6 lg:flex'>
<CourseStatus size='small' status={Status.WAITLISTED} /> <CourseStatus size='small' status={Status.WAITLISTED} />
<CourseStatus size='small' status={Status.CLOSED} /> <CourseStatus size='small' status={Status.CLOSED} />
<CourseStatus size='small' status={Status.CANCELLED} /> <CourseStatus size='small' status={Status.CANCELLED} />

View File

@@ -21,7 +21,9 @@ export function CalendarSchedules() {
return ( return (
<div className='min-w-full w-0 items-center'> <div className='min-w-full w-0 items-center'>
<div className='m0 m-b-2 w-full flex justify-between'> <div className='m0 m-b-2 w-full flex justify-between'>
<Text variant='h3'>MY SCHEDULES</Text> <Text variant='h3' className='text-nowrap'>
MY SCHEDULES
</Text>
<Button <Button
variant='single' variant='single'
color='theme-black' color='theme-black'

View File

@@ -48,7 +48,7 @@ export default function ImportantLinks({ className }: Props): JSX.Element {
return ( return (
<article className={clsx(className, 'flex flex-col gap-2')}> <article className={clsx(className, 'flex flex-col gap-2')}>
<Text variant='h3'>Useful Links</Text> <Text variant='h3'>Useful Links</Text>
{links.map((link, index) => ( {links.map(link => (
<a <a
key={link.text} key={link.text}
href={link.url} href={link.url}

View File

@@ -8,6 +8,26 @@ type Props = {
className?: string; className?: string;
}; };
interface LinkItem {
text: string;
url: string;
}
const links: LinkItem[] = [
{
text: 'Feedback Form',
url: '#',
},
{
text: 'Apply to Longhorn Developers',
url: '#',
},
{
text: 'Become a Beta Tester',
url: '#',
},
];
/** /**
* The "From The Team" section of the calendar website * The "From The Team" section of the calendar website
* @returns * @returns
@@ -15,36 +35,19 @@ type Props = {
export default function TeamLinks({ className }: Props): JSX.Element { export default function TeamLinks({ className }: Props): JSX.Element {
return ( return (
<article className={clsx(className, 'flex flex-col gap-2')}> <article className={clsx(className, 'flex flex-col gap-2')}>
<Text variant='h3'>From The Team</Text> <Text variant='h3'>From the Team</Text>
<a {links.map(link => (
href='options.html' <a
className='flex items-center gap-0.5 text-ut-burntorange' key={link.text}
target='_blank' href={link.url}
rel='noreferrer' className='flex items-center gap-0.5 text-ut-burntorange underline-offset-2 hover:underline'
> target='_blank'
<Text variant='p'>Credits Meet the team!</Text> rel='noreferrer'
<OutwardArrowIcon className='h-3 w-3' /> >
</a> <Text variant='p'>{link.text}</Text>
{/* TODO: ADD THE LINK HERE */} <OutwardArrowIcon className='h-3 w-3' />
<a </a>
href='application-link' ))}
className='flex items-center gap-0.5 text-ut-burntorange'
target='_blank'
rel='noreferrer'
>
<Text variant='p'>Apply to Longhorn Developers</Text>
<OutwardArrowIcon className='h-3 w-3' />
</a>
{/* TODO: ADD THE LINK HERE */}
<a
href='beta_tester-link'
className='flex items-center gap-0.5 text-ut-burntorange'
target='_blank'
rel='noreferrer'
>
<Text variant='p'>Become a Beta Tester</Text>
<OutwardArrowIcon className='h-3 w-3' />
</a>
</article> </article>
); );
} }

View File

@@ -1,3 +1,4 @@
import clsx from 'clsx';
import type { SVGProps } from 'react'; import type { SVGProps } from 'react';
import React from 'react'; import React from 'react';
@@ -12,24 +13,24 @@ export function LogoIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
); );
} }
export function SmallLogo(): JSX.Element { export function SmallLogo({ className }: { className?: string }): JSX.Element {
return ( return (
<div className='flex items-center gap-2'> <div className={clsx('flex items-center gap-2', className)}>
<LogoIcon /> <LogoIcon />
<div className='flex flex-col text-lg font-medium leading-[1em]'> <div className='flex flex-col text-lg font-medium leading-[1em]'>
<p className='text-ut-burntorange'>UT Registration</p> <p className='text-nowrap text-ut-burntorange'>UT Registration</p>
<p className='text-ut-orange'>Plus</p> <p className='text-ut-orange'>Plus</p>
</div> </div>
</div> </div>
); );
} }
export function LargeLogo(): JSX.Element { export function LargeLogo({ className }: { className?: string }): JSX.Element {
return ( return (
<div className='flex items-center gap-2'> <div className={clsx('flex items-center gap-2', className)}>
<LogoIcon className='h-12 w-12' /> <LogoIcon className='h-12 w-12' />
<div className='flex flex-col text-[1.35rem] font-medium leading-[1em]'> <div className='hidden flex-col text-[1.35rem] font-medium leading-[1em] md:flex'>
<p className='text-ut-burntorange'>UT Registration</p> <p className='text-nowrap text-ut-burntorange'>UT Registration</p>
<p className='text-ut-orange'>Plus</p> <p className='text-ut-orange'>Plus</p>
</div> </div>
</div> </div>

View File

@@ -21,13 +21,13 @@ export default function ScheduleTotalHoursAndCourses({
totalCourses, totalCourses,
}: ScheduleTotalHoursAndCoursesProps): JSX.Element { }: ScheduleTotalHoursAndCoursesProps): JSX.Element {
return ( return (
<div className='min-w-64 flex items-center gap-2.5 whitespace-nowrap'> <div className='min-w-full w-0 flex items-center gap-2.5 whitespace-nowrap'>
<Text className='text-ut-burntorange uppercase' variant='h1' as='span'> <Text className='truncate text-ut-burntorange uppercase' variant='h1' as='span'>
{`${scheduleName}: `} {`${scheduleName}: `}
</Text> </Text>
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'> <Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
{totalHours} {totalHours === 1 ? 'Hour' : 'Hours'} {totalHours} {totalHours === 1 ? 'Hour' : 'Hours'}
<Text variant='h4' as='span' className='text-ut-black capitalize'> <Text variant='h4' as='span' className='hidden text-ut-black capitalize sm:inline'>
{totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'} {totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'}
</Text> </Text>
</Text> </Text>

View File

@@ -23,6 +23,8 @@ export default defineConfig({
focusable: 'outline-none ring-blue-500/50 dark:ring-blue-400/60 ring-0 focus-visible:ring-4', focusable: 'outline-none ring-blue-500/50 dark:ring-blue-400/60 ring-0 focus-visible:ring-4',
btn: 'h-10 w-auto flex cursor-pointer justify-center items-center gap-2 rounded-1 px-4 py-0 text-4.5 btn-transition disabled:(cursor-not-allowed opacity-50) active:enabled:scale-96 focusable', btn: 'h-10 w-auto flex cursor-pointer justify-center items-center gap-2 rounded-1 px-4 py-0 text-4.5 btn-transition disabled:(cursor-not-allowed opacity-50) active:enabled:scale-96 focusable',
link: 'text-ut-burntorange underline underline-offset-2 hover:text-ut-orange focus-visible:text-ut-orange focusable btn-transition ease-out-expo', link: 'text-ut-burntorange underline underline-offset-2 hover:text-ut-orange focus-visible:text-ut-orange focusable btn-transition ease-out-expo',
linkanimate:
'relative cursor-pointer transition duration-100 ease-out after:(absolute left-0.4 right-0.4 h-2px scale-x-95 bg-ut-orange opacity-0 transition duration-250 ease-out-expo content-empty -bottom-0.75 -translate-y-0.5) active:scale-95 hover:text-ut-orange focus-visible:text-ut-orange hover:after:(opacity-100) !hover:after:translate-y-0 !hover:after:scale-x-100',
}, },
theme: { theme: {
easing: { easing: {