feat: match calendar designs & add functionality (#176)
* feat: match calendar designs * refactor: update breakpoints
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
"@commitlint/types": "^19.0.3",
|
||||
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
||||
"@iconify-json/material-symbols": "^1.1.73",
|
||||
"@iconify-json/ri": "^1.1.20",
|
||||
"@storybook/addon-designs": "^7.0.9",
|
||||
"@storybook/addon-essentials": "^7.6.17",
|
||||
"@storybook/addon-links": "^7.6.17",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -81,6 +81,9 @@ devDependencies:
|
||||
'@iconify-json/material-symbols':
|
||||
specifier: ^1.1.73
|
||||
version: 1.1.73
|
||||
'@iconify-json/ri':
|
||||
specifier: ^1.1.20
|
||||
version: 1.1.20
|
||||
'@storybook/addon-designs':
|
||||
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)
|
||||
@@ -2341,6 +2344,12 @@ packages:
|
||||
'@iconify/types': 2.0.0
|
||||
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:
|
||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
dev: true
|
||||
|
||||
@@ -113,7 +113,7 @@ export default function PopupMain(): JSX.Element {
|
||||
</div>
|
||||
<div className='inline-flex items-center self-center gap-1'>
|
||||
<Text variant='mini' className='text-ut-gray'>
|
||||
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||
DATA LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||
</Text>
|
||||
<button
|
||||
className='h-4 w-4 bg-transparent p-0 btn'
|
||||
|
||||
@@ -11,6 +11,9 @@ import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSched
|
||||
import { MessageListener } from 'chrome-extension-toolkit';
|
||||
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.
|
||||
* @returns
|
||||
@@ -32,6 +35,7 @@ export default function Calendar(): JSX.Element {
|
||||
});
|
||||
|
||||
const [showPopup, setShowPopup] = useState<boolean>(course !== null);
|
||||
const [showSidebar, setShowSidebar] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = new MessageListener<CalendarTabMessages>({
|
||||
@@ -55,15 +59,26 @@ export default function Calendar(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className='h-full w-full flex flex-col'>
|
||||
<CalendarHeader />
|
||||
<div className='h-full flex flex-grow overflow-hidden pl-7.5'>
|
||||
<div className='overflow-auto py-5'>
|
||||
<CalendarHeader
|
||||
onSidebarToggle={() => {
|
||||
setShowSidebar(!showSidebar);
|
||||
}}
|
||||
/>
|
||||
<div className='h-full flex overflow-auto pl-3'>
|
||||
{showSidebar && (
|
||||
<div className='h-full flex flex-none flex-col justify-between pb-5 pl-4.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>
|
||||
<div className='flex flex-grow flex-col' ref={calendarRef}>
|
||||
<div className='flex-grow overflow-auto px-4 pt-6.5'>
|
||||
<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} />
|
||||
</div>
|
||||
<CalendarBottomBar calendarRef={calendarRef} />
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function CalendarBottomBar({ courses, calendarRef }: CalendarBott
|
||||
>
|
||||
{displayCourses && (
|
||||
<>
|
||||
<Text variant='h4'>Other Classes:</Text>
|
||||
<Text variant='h4'>Async/Other:</Text>
|
||||
<div className='inline-flex gap-2.5'>
|
||||
{courses.map(({ courseDeptAndInstr, status, colors, className }) => (
|
||||
<CalendarCourseBlock
|
||||
|
||||
33
src/views/components/calendar/CalendarFooter.tsx
Normal file
33
src/views/components/calendar/CalendarFooter.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export default function CalendarGrid({
|
||||
<div className='w-4 border-b border-r border-gray-300' />
|
||||
{daysOfWeek.map(day => (
|
||||
<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}
|
||||
</Text>
|
||||
</div>
|
||||
@@ -80,6 +80,7 @@ interface AccountForCourseConflictsProps {
|
||||
}
|
||||
|
||||
// TODO: Possibly refactor to be more concise
|
||||
// TODO: Deal with react strict mode (wacky movements)
|
||||
function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseConflictsProps): JSX.Element[] {
|
||||
// Groups by dayIndex to identify overlaps
|
||||
const days = courseCells.reduce((acc, cell: CalendarGridCourse) => {
|
||||
|
||||
@@ -22,19 +22,23 @@ const handleOpenOptions = async (): Promise<void> => {
|
||||
await openTabFromContentScript(url);
|
||||
};
|
||||
|
||||
interface CalendarHeaderProps {
|
||||
onSidebarToggle?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the header component for the calendar.
|
||||
* @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();
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-5 border-b px-7 py-4'>
|
||||
<Button variant='single' icon={MenuIcon} color='ut-gray' />
|
||||
<div className='flex items-center gap-5 border-b border-ut-offwhite px-7 py-4'>
|
||||
<Button variant='single' icon={MenuIcon} color='ut-gray' onClick={onSidebarToggle} />
|
||||
<LargeLogo />
|
||||
<Divider className='mx-4 self-center' size='2.5rem' orientation='vertical' />
|
||||
<div className='flex-grow'>
|
||||
<Divider className='mx-2 self-center md:mx-4' size='2.5rem' orientation='vertical' />
|
||||
<div className='flex-1'>
|
||||
<ScheduleTotalHoursAndCourses
|
||||
scheduleName={activeSchedule.name}
|
||||
totalHours={activeSchedule.hours}
|
||||
@@ -42,14 +46,14 @@ export default function CalendarHeader(): JSX.Element {
|
||||
/>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Text variant='mini' className='text-ut-gray'>
|
||||
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||
DATA LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||
</Text>
|
||||
<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' />
|
||||
</button>
|
||||
</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.CLOSED} />
|
||||
<CourseStatus size='small' status={Status.CANCELLED} />
|
||||
|
||||
@@ -21,7 +21,9 @@ export function CalendarSchedules() {
|
||||
return (
|
||||
<div className='min-w-full w-0 items-center'>
|
||||
<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
|
||||
variant='single'
|
||||
color='theme-black'
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ImportantLinks({ className }: Props): JSX.Element {
|
||||
return (
|
||||
<article className={clsx(className, 'flex flex-col gap-2')}>
|
||||
<Text variant='h3'>Useful Links</Text>
|
||||
{links.map((link, index) => (
|
||||
{links.map(link => (
|
||||
<a
|
||||
key={link.text}
|
||||
href={link.url}
|
||||
|
||||
@@ -8,6 +8,26 @@ type Props = {
|
||||
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
|
||||
* @returns
|
||||
@@ -15,36 +35,19 @@ type Props = {
|
||||
export default function TeamLinks({ className }: Props): JSX.Element {
|
||||
return (
|
||||
<article className={clsx(className, 'flex flex-col gap-2')}>
|
||||
<Text variant='h3'>From The Team</Text>
|
||||
<Text variant='h3'>From the Team</Text>
|
||||
{links.map(link => (
|
||||
<a
|
||||
href='options.html'
|
||||
className='flex items-center gap-0.5 text-ut-burntorange'
|
||||
key={link.text}
|
||||
href={link.url}
|
||||
className='flex items-center gap-0.5 text-ut-burntorange underline-offset-2 hover:underline'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
<Text variant='p'>Credits – Meet the team!</Text>
|
||||
<OutwardArrowIcon className='h-3 w-3' />
|
||||
</a>
|
||||
{/* TODO: ADD THE LINK HERE */}
|
||||
<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>
|
||||
<Text variant='p'>{link.text}</Text>
|
||||
<OutwardArrowIcon className='h-3 w-3' />
|
||||
</a>
|
||||
))}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import type { SVGProps } 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 (
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className={clsx('flex items-center gap-2', className)}>
|
||||
<LogoIcon />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LargeLogo(): JSX.Element {
|
||||
export function LargeLogo({ className }: { className?: string }): JSX.Element {
|
||||
return (
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className={clsx('flex items-center gap-2', className)}>
|
||||
<LogoIcon className='h-12 w-12' />
|
||||
<div className='flex flex-col text-[1.35rem] font-medium leading-[1em]'>
|
||||
<p className='text-ut-burntorange'>UT Registration</p>
|
||||
<div className='hidden flex-col text-[1.35rem] font-medium leading-[1em] md:flex'>
|
||||
<p className='text-nowrap text-ut-burntorange'>UT Registration</p>
|
||||
<p className='text-ut-orange'>Plus</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,13 +21,13 @@ export default function ScheduleTotalHoursAndCourses({
|
||||
totalCourses,
|
||||
}: ScheduleTotalHoursAndCoursesProps): JSX.Element {
|
||||
return (
|
||||
<div className='min-w-64 flex items-center gap-2.5 whitespace-nowrap'>
|
||||
<Text className='text-ut-burntorange uppercase' variant='h1' as='span'>
|
||||
<div className='min-w-full w-0 flex items-center gap-2.5 whitespace-nowrap'>
|
||||
<Text className='truncate text-ut-burntorange uppercase' variant='h1' as='span'>
|
||||
{`${scheduleName}: `}
|
||||
</Text>
|
||||
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
|
||||
{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'}
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
@@ -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',
|
||||
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',
|
||||
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: {
|
||||
easing: {
|
||||
|
||||
Reference in New Issue
Block a user