feat(ui): calendar sidebar redesign (#464)
* feat: update calendar sidebar, footer, and header with Figma design * chore: run lint * feat: update header with Figma design * chore: run lint * chore: remove unused vars * chore: fix types * fix: adjust sidebar minimum width * fix: update LogoIcon layout to ensure text is always displayed * feat: add spacing constants * fix: add sidebar styling with spacing system and sticky header * fix: update spacing constants to use rem units * refactor: replace padding with spacing system and colors with UTRP theme * refactor: rename ImportantLinks to ResourceLinks * refactor: simplify CalendarHeader button component by using icon prop * feat: add sidebar open and close transition * refactor: rename unused var * fix: update social icon color * feat: improve layout and spacing in calendar components * refactor: remove unused GearSix icon and options handler * feat: update calendar components with new icons and improved spacing * fix: correct class name * refactor: organize social links into array and update link styling * refactor: remove unused import * fix: adjust gap spacing in radio button * fix: update divider component to use theme offwhite1 * fix: increase size of outward arrow icon * feat: add getSpacingInPx function to convert rem to pixels * fix: update gap spacing in CalendarSchedules component to use spacing system * fix: rollback footer social icons to original icons * fix: update Calendar styles to use theme offwhite1 and adjust padding to account for scrollbar * fix: update LargeLogo component to use gap-spacing-3 * fix: update button variants to 'minimal' and adjust styles for consistency * fix: adjust padding in Calendar component for better layout consistency * fix: increase size of arrow icon * fix: add shrink-0 to radio buttons
This commit is contained in:
@@ -12,3 +12,15 @@ export const spacing = {
|
|||||||
'spacing-7': '1.5rem',
|
'spacing-7': '1.5rem',
|
||||||
'spacing-8': '2rem',
|
'spacing-8': '2rem',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
type SpacingKey = keyof typeof spacing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a spacing value from rem to pixels
|
||||||
|
* @param key - The spacing key to convert
|
||||||
|
* @returns The spacing value in pixels
|
||||||
|
*/
|
||||||
|
export function getSpacingInPx(key: SpacingKey): number {
|
||||||
|
const remValue = parseFloat(spacing[key]);
|
||||||
|
return remValue * 16; // 1rem = 16px
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import ImportantLinks from '@views/components/calendar/ImportantLinks';
|
import ResourceLinks from '@views/components/calendar/ResourceLinks';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Common/ImportantLinks',
|
title: 'Components/Common/ResourceLinks',
|
||||||
component: ImportantLinks,
|
component: ResourceLinks,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
} satisfies Meta<typeof ImportantLinks>;
|
} satisfies Meta<typeof ResourceLinks>;
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
|
import { Sidebar } from '@phosphor-icons/react';
|
||||||
import type { CalendarTabMessages } from '@shared/messages/CalendarMessages';
|
import type { CalendarTabMessages } from '@shared/messages/CalendarMessages';
|
||||||
import type { Course } from '@shared/types/Course';
|
import type { Course } from '@shared/types/Course';
|
||||||
|
import { CRX_PAGES } from '@shared/types/CRXPages';
|
||||||
|
import { openReportWindow } from '@shared/util/openReportWindow';
|
||||||
import CalendarBottomBar from '@views/components/calendar/CalendarBottomBar';
|
import CalendarBottomBar from '@views/components/calendar/CalendarBottomBar';
|
||||||
import CalendarGrid from '@views/components/calendar/CalendarGrid';
|
import CalendarGrid from '@views/components/calendar/CalendarGrid';
|
||||||
import CalendarHeader from '@views/components/calendar/CalendarHeader';
|
import CalendarHeader from '@views/components/calendar/CalendarHeader';
|
||||||
import { CalendarSchedules } from '@views/components/calendar/CalendarSchedules';
|
import { CalendarSchedules } from '@views/components/calendar/CalendarSchedules';
|
||||||
import ImportantLinks from '@views/components/calendar/ImportantLinks';
|
import ResourceLinks from '@views/components/calendar/ResourceLinks';
|
||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
||||||
import { CalendarContext } from '@views/contexts/CalendarContext';
|
import { CalendarContext } from '@views/contexts/CalendarContext';
|
||||||
import useCourseFromUrl from '@views/hooks/useCourseFromUrl';
|
import useCourseFromUrl from '@views/hooks/useCourseFromUrl';
|
||||||
import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule';
|
import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule';
|
||||||
import { MessageListener } from 'chrome-extension-toolkit';
|
import { MessageListener } from 'chrome-extension-toolkit';
|
||||||
|
import clsx from 'clsx';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import CalendarFooter from './CalendarFooter';
|
import OutwardArrowIcon from '~icons/material-symbols/arrow-outward';
|
||||||
import TeamLinks from './TeamLinks';
|
|
||||||
|
|
||||||
|
import { Button } from '../common/Button';
|
||||||
|
import { LargeLogo } from '../common/LogoIcon';
|
||||||
|
import Text from '../common/Text/Text';
|
||||||
|
import CalendarFooter from './CalendarFooter';
|
||||||
/**
|
/**
|
||||||
* Calendar page component
|
* Calendar page component
|
||||||
*/
|
*/
|
||||||
@@ -54,25 +61,68 @@ export default function Calendar(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<CalendarContext.Provider value>
|
<CalendarContext.Provider value>
|
||||||
<div className='h-full w-full flex flex-col'>
|
<div className='h-full w-full flex flex-col'>
|
||||||
<CalendarHeader
|
<div className='h-screen flex overflow-auto'>
|
||||||
onSidebarToggle={() => {
|
<div
|
||||||
setShowSidebar(!showSidebar);
|
className={clsx(
|
||||||
}}
|
'py-spacing-6 relative h-full min-h-screen w-full flex flex-none flex-col justify-between overflow-clip whitespace-nowrap border-r border-theme-offwhite1 shadow-[2px_0_10px,rgba(214_210_196_/_.1)] duration-300 ease-out-expo transition-property-max-width screenshot:hidden',
|
||||||
/>
|
{
|
||||||
<div className='h-full flex overflow-auto pl-3'>
|
'max-w-[20.3125rem] ': showSidebar,
|
||||||
{showSidebar && (
|
'max-w-0 pointer-events-none': !showSidebar,
|
||||||
<div className='h-full flex flex-none flex-col justify-between pb-5 screenshot:hidden'>
|
}
|
||||||
<div className='mb-3 h-full w-fit flex flex-col overflow-auto pb-2 pl-4.5 pr-4 pt-5'>
|
)}
|
||||||
<CalendarSchedules />
|
tabIndex={showSidebar ? 0 : -1}
|
||||||
<Divider orientation='horizontal' size='100%' className='my-5' />
|
aria-hidden={!showSidebar}
|
||||||
<ImportantLinks />
|
{...{ inert: !showSidebar ? '' : undefined }}
|
||||||
<Divider orientation='horizontal' size='100%' className='my-5' />
|
>
|
||||||
<TeamLinks />
|
<div className='sticky top-0 z-50 w-full flex items-center justify-between gap-x-3xl bg-white px-spacing-8 pb-spacing-6'>
|
||||||
</div>
|
<LargeLogo />
|
||||||
<CalendarFooter />
|
<Button
|
||||||
|
variant='minimal'
|
||||||
|
color='theme-black'
|
||||||
|
onClick={() => {
|
||||||
|
setShowSidebar(!showSidebar);
|
||||||
|
}}
|
||||||
|
className='h-fit screenshot:hidden !p-0'
|
||||||
|
icon={Sidebar}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
scrollbarGutter: 'stable',
|
||||||
|
}}
|
||||||
|
className='relative h-full w-full flex grow flex-col gap-y-spacing-6 overflow-x-clip overflow-y-auto pb-spacing-6 pl-spacing-8 pr-4.5'
|
||||||
|
>
|
||||||
|
<CalendarSchedules />
|
||||||
|
<Divider orientation='horizontal' size='100%' />
|
||||||
|
<ResourceLinks />
|
||||||
|
<Divider orientation='horizontal' size='100%' />
|
||||||
|
{/* <TeamLinks /> */}
|
||||||
|
<a
|
||||||
|
href={CRX_PAGES.REPORT}
|
||||||
|
className='flex items-center gap-spacing-2 text-ut-burntorange underline-offset-2 hover:underline'
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
onClick={event => {
|
||||||
|
event.preventDefault();
|
||||||
|
openReportWindow();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text variant='p'>Send us Feedback!</Text>
|
||||||
|
<OutwardArrowIcon className='h-4 w-4' />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CalendarFooter />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='h-full min-w-5xl flex flex-grow flex-col overflow-y-auto'>
|
<div className='h-full min-w-5xl flex flex-grow flex-col overflow-y-auto'>
|
||||||
|
<CalendarHeader
|
||||||
|
sidebarOpen={showSidebar}
|
||||||
|
onSidebarToggle={() => {
|
||||||
|
setShowSidebar(!showSidebar);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div className='min-h-2xl flex-grow overflow-auto pl-2 pr-4 pt-6 screenshot:min-h-xl'>
|
<div className='min-h-2xl flex-grow overflow-auto pl-2 pr-4 pt-6 screenshot:min-h-xl'>
|
||||||
<CalendarGrid courseCells={courseCells} setCourse={setCourse} />
|
<CalendarGrid courseCells={courseCells} setCourse={setCourse} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { GearSix } from '@phosphor-icons/react';
|
||||||
|
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import DiscordIcon from '~icons/bi/discord';
|
import DiscordIcon from '~icons/bi/discord';
|
||||||
@@ -5,36 +7,65 @@ import GithubIcon from '~icons/ri/github-fill';
|
|||||||
import InstagramIcon from '~icons/ri/instagram-line';
|
import InstagramIcon from '~icons/ri/instagram-line';
|
||||||
import LinkedinIcon from '~icons/ri/linkedin-box-fill';
|
import LinkedinIcon from '~icons/ri/linkedin-box-fill';
|
||||||
|
|
||||||
|
import { Button } from '../common/Button';
|
||||||
import Link from '../common/Link';
|
import Link from '../common/Link';
|
||||||
|
|
||||||
|
interface SocialLink {
|
||||||
|
icon: React.FC<React.SVGProps<SVGSVGElement>>;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socialLinks: SocialLink[] = [
|
||||||
|
{
|
||||||
|
icon: InstagramIcon,
|
||||||
|
url: 'https://www.instagram.com/longhorndevelopers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: DiscordIcon,
|
||||||
|
url: 'https://discord.gg/7pQDBGdmb7',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: GithubIcon,
|
||||||
|
url: 'https://github.com/Longhorn-Developers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: LinkedinIcon,
|
||||||
|
url: 'https://www.linkedin.com/company/longhorn-developers/posts/?feedView=all',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the options page in a new tab.
|
||||||
|
* @returns A promise that resolves when the options page is opened.
|
||||||
|
*/
|
||||||
|
const handleOpenOptions = async (): Promise<void> => {
|
||||||
|
const url = chrome.runtime.getURL('/options.html');
|
||||||
|
await openTabFromContentScript(url);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The footer section of the calendar's sidebar
|
* The footer section of the calendar's sidebar
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function CalendarFooter(): JSX.Element {
|
export default function CalendarFooter(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<footer className='min-w-full w-0 pl-4.5 space-y-2'>
|
<footer className='min-w-full w-0 flex items-center justify-between bg-white px-spacing-8 pt-spacing-4'>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-spacing-5'>
|
||||||
<Link className='linkanimate' href='https://www.instagram.com/longhorndevelopers'>
|
{socialLinks.map(({ icon: Icon, url }) => (
|
||||||
<InstagramIcon className='h-6 w-6' />
|
<Link className='linkanimate' href={url}>
|
||||||
</Link>
|
<Icon className='h-6 w-6' />
|
||||||
<Link className='linkanimate' href='https://discord.gg/7pQDBGdmb7'>
|
</Link>
|
||||||
<DiscordIcon className='h-6 w-6' />
|
))}
|
||||||
</Link>
|
</div>
|
||||||
<Link className='linkanimate' href='https://github.com/Longhorn-Developers'>
|
<div>
|
||||||
<GithubIcon className='h-6 w-6' />
|
<Button
|
||||||
</Link>
|
className='h-fit w-fit !p-0'
|
||||||
<Link
|
variant='minimal'
|
||||||
className='linkanimate'
|
icon={GearSix}
|
||||||
href='https://www.linkedin.com/company/longhorn-developers/posts/?feedView=all'
|
color='ut-black'
|
||||||
>
|
onClick={handleOpenOptions}
|
||||||
<LinkedinIcon className='h-6 w-6 -mx-0.75' />
|
/>
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<p className='text-2.5 text-ut-gray 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>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
import { GearSix, Sidebar } from '@phosphor-icons/react';
|
import { Sidebar } from '@phosphor-icons/react';
|
||||||
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
||||||
import { Button } from '@views/components/common/Button';
|
import { Button } from '@views/components/common/Button';
|
||||||
import CourseStatus from '@views/components/common/CourseStatus';
|
import CourseStatus from '@views/components/common/CourseStatus';
|
||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import { LargeLogo } from '@views/components/common/LogoIcon';
|
|
||||||
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses';
|
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses';
|
||||||
import useSchedules from '@views/hooks/useSchedules';
|
import useSchedules from '@views/hooks/useSchedules';
|
||||||
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the options page in a new tab.
|
|
||||||
* @returns A promise that resolves when the options page is opened.
|
|
||||||
*/
|
|
||||||
const handleOpenOptions = async (): Promise<void> => {
|
|
||||||
const url = chrome.runtime.getURL('/options.html');
|
|
||||||
await openTabFromContentScript(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface CalendarHeaderProps {
|
interface CalendarHeaderProps {
|
||||||
|
sidebarOpen?: boolean;
|
||||||
onSidebarToggle?: () => void;
|
onSidebarToggle?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +16,7 @@ interface CalendarHeaderProps {
|
|||||||
* 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({ onSidebarToggle }: CalendarHeaderProps): JSX.Element {
|
export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: CalendarHeaderProps): JSX.Element {
|
||||||
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||||
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -55,23 +45,27 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center gap-5 overflow-x-auto overflow-y-hidden border-b border-ut-offwhite px-7 py-4 md:overflow-x-hidden'>
|
<div className='min-h-[91px] flex items-center gap-5 overflow-x-auto overflow-y-hidden px-7 py-4 md:overflow-x-hidden'>
|
||||||
<Button
|
{!sidebarOpen && (
|
||||||
variant='minimal'
|
<Button
|
||||||
icon={Sidebar}
|
variant='minimal'
|
||||||
color='ut-gray'
|
color='theme-black'
|
||||||
onClick={onSidebarToggle}
|
onClick={onSidebarToggle}
|
||||||
className='screenshot:hidden'
|
className='h-fit w-fit screenshot:hidden !p-0'
|
||||||
/>
|
icon={Sidebar}
|
||||||
<LargeLogo />
|
/>
|
||||||
<Divider className='mx-2 self-center md:mx-4' size='2.5rem' orientation='vertical' />
|
)}
|
||||||
<div className='flex-1 screenshot:transform-origin-left screenshot:scale-120'>
|
|
||||||
|
<div className='screenshot:transform-origin-left screenshot:scale-120'>
|
||||||
<ScheduleTotalHoursAndCourses
|
<ScheduleTotalHoursAndCourses
|
||||||
scheduleName={activeSchedule.name}
|
scheduleName={activeSchedule.name}
|
||||||
totalHours={activeSchedule.hours}
|
totalHours={activeSchedule.hours}
|
||||||
totalCourses={activeSchedule.courses.length}
|
totalCourses={activeSchedule.courses.length}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Divider className='mx-2 self-center md:mx-4 screenshot:hidden' size='2.5rem' orientation='vertical' />
|
||||||
|
|
||||||
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'>
|
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'>
|
||||||
{enableCourseStatusChips && (
|
{enableCourseStatusChips && (
|
||||||
<>
|
<>
|
||||||
@@ -83,7 +77,6 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
|
|||||||
|
|
||||||
{/* <Button variant='single' icon={UndoIcon} color='ut-black' />
|
{/* <Button variant='single' icon={UndoIcon} color='ut-black' />
|
||||||
<Button variant='single' icon={RedoIcon} color='ut-black' /> */}
|
<Button variant='single' icon={RedoIcon} color='ut-black' /> */}
|
||||||
<Button variant='minimal' icon={GearSix} color='theme-black' onClick={handleOpenOptions} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import createSchedule from '@pages/background/lib/createSchedule';
|
import createSchedule from '@pages/background/lib/createSchedule';
|
||||||
import { Plus } from '@phosphor-icons/react';
|
import { Plus } from '@phosphor-icons/react';
|
||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
|
import { getSpacingInPx } from '@shared/types/Spacing';
|
||||||
import { Button } from '@views/components/common/Button';
|
import { Button } from '@views/components/common/Button';
|
||||||
import List from '@views/components/common/List';
|
import List from '@views/components/common/List';
|
||||||
import ScheduleListItem from '@views/components/common/ScheduleListItem';
|
import ScheduleListItem from '@views/components/common/ScheduleListItem';
|
||||||
@@ -26,16 +27,22 @@ export function CalendarSchedules() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='min-w-full w-0 items-center'>
|
<div className='min-w-full w-0 flex flex-col items-center gap-y-spacing-3'>
|
||||||
<div className='m0 m-b-2 w-full flex justify-between'>
|
<div className='m0 w-full flex justify-between'>
|
||||||
<Text variant='h3' className='text-nowrap'>
|
<Text variant='h3' className='text-nowrap text-theme-black'>
|
||||||
MY SCHEDULES
|
MY SCHEDULES
|
||||||
</Text>
|
</Text>
|
||||||
<Button size='mini' variant='minimal' color='theme-black' onClick={handleAddSchedule} icon={Plus} />
|
<Button
|
||||||
|
variant='minimal'
|
||||||
|
color='theme-black'
|
||||||
|
className='h-fit w-fit !p-0 btn'
|
||||||
|
onClick={handleAddSchedule}
|
||||||
|
icon={Plus}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col space-y-2.5'>
|
<div className='w-full flex flex-col'>
|
||||||
<List
|
<List
|
||||||
gap={10}
|
gap={getSpacingInPx('spacing-3')}
|
||||||
draggables={schedules}
|
draggables={schedules}
|
||||||
itemKey={s => s.id}
|
itemKey={s => s.id}
|
||||||
onReordered={reordered => {
|
onReordered={reordered => {
|
||||||
|
|||||||
73
src/views/components/calendar/ResourceLinks.tsx
Normal file
73
src/views/components/calendar/ResourceLinks.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import OutwardArrowIcon from '~icons/material-symbols/arrow-outward';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface LinkItem {
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const links: LinkItem[] = [
|
||||||
|
{
|
||||||
|
text: "Spring '25 Course Schedule",
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Course Schedule Archives',
|
||||||
|
url: 'https://registrar.utexas.edu/schedules/archive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'My Degree Audit (IDA)',
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/degree/audits/',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// text: "Summer '24 Course Schedule",
|
||||||
|
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20246/',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// text: "'24-'25 Academic Calendar",
|
||||||
|
// url: 'https://registrar.utexas.edu/calendars/24-25',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
text: 'Registration Info Sheet (RIS)',
|
||||||
|
url: 'https://utdirect.utexas.edu/registrar/ris.WBX',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Register for Courses',
|
||||||
|
url: 'https://utdirect.utexas.edu/registration/chooseSemester.WBX',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "Resources" section of the calendar website
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ResourceLinks({ className }: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<article className={clsx(className, 'flex flex-col gap-spacing-3')}>
|
||||||
|
<Text className='text-theme-black uppercase' variant='h3'>
|
||||||
|
RESOURCES
|
||||||
|
</Text>
|
||||||
|
<div className='flex flex-col gap-spacing-3'>
|
||||||
|
{links.map(link => (
|
||||||
|
<a
|
||||||
|
key={link.text}
|
||||||
|
href={link.url}
|
||||||
|
className='flex items-center gap-spacing-2 text-ut-burntorange underline-offset-2 hover:underline'
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
<Text variant='p'>{link.text}</Text>
|
||||||
|
<OutwardArrowIcon className='h-4 w-4' />
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ export default function Divider({ className, testId, size, orientation }: Divide
|
|||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
className={clsx('border-solid border-ut-offwhite w-0 h-0', className)}
|
className={clsx('border-solid border-theme-offwhite1 w-0 h-0', className)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ export function SmallLogo({ className }: LogoIconProps): JSX.Element {
|
|||||||
*/
|
*/
|
||||||
export function LargeLogo({ className }: LogoIconProps): JSX.Element {
|
export function LargeLogo({ className }: LogoIconProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('flex items-center gap-2', className)}>
|
<div className={clsx('flex items-center gap-spacing-3', className)}>
|
||||||
<LogoIcon className='h-12 w-12' />
|
<LogoIcon className='h-12 w-12' />
|
||||||
<div className='mt-1 hidden flex-col text-[1.35rem] font-medium leading-[1em] md:flex screenshot:flex'>
|
<div className='mt-1 flex flex-col text-[1.35rem] font-medium leading-[1em] screenshot:flex'>
|
||||||
<p className='text-nowrap text-ut-burntorange'>UT Registration</p>
|
<p className='text-nowrap text-ut-burntorange'>UT Registration</p>
|
||||||
<p className='text-ut-burntorange'>
|
<p className='text-ut-burntorange'>
|
||||||
Plus{' '}
|
Plus{' '}
|
||||||
|
|||||||
@@ -92,23 +92,27 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='rounded bg-white'>
|
<div className='h-7.5 rounded bg-white'>
|
||||||
<li className='w-full flex cursor-pointer items-center text-ut-burntorange'>
|
<li className='h-full w-full flex cursor-pointer items-center gap-[1px] text-ut-burntorange'>
|
||||||
<div className='h-full cursor-move focusable' {...dragHandleProps}>
|
<div className='flex cursor-move items-center justify-center focusable' {...dragHandleProps}>
|
||||||
<DotsSixVertical
|
<DotsSixVertical
|
||||||
weight='bold'
|
weight='bold'
|
||||||
className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400'
|
className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='group relative flex flex-1 items-center overflow-x-hidden'>
|
<div className='group relative flex flex-1 items-center overflow-x-hidden'>
|
||||||
<div
|
<div
|
||||||
className='group/circle flex flex-grow items-center gap-1.5 overflow-x-hidden'
|
className='group/circle flex flex-grow items-center gap-spacing-3 overflow-x-hidden'
|
||||||
onClick={(...e) => !isEditing && onClick?.(...e)}
|
onClick={(...e) => !isEditing && onClick?.(...e)}
|
||||||
>
|
>
|
||||||
{isActive ? (
|
{isActive ? (
|
||||||
<RadioButton className='h-7.5 w-7.5 btn-transition active:scale-95' weight='fill' />
|
<RadioButton
|
||||||
|
className='h-7.5 w-7.5 shrink-0 btn-transition active:scale-95'
|
||||||
|
weight='fill'
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Circle className='h-7.5 w-7.5 btn-transition active:scale-95' />
|
<Circle className='h-7.5 w-7.5 shrink-0 btn-transition active:scale-95' />
|
||||||
)}
|
)}
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -24,36 +24,19 @@ export default function ScheduleTotalHoursAndCourses({
|
|||||||
totalCourses,
|
totalCourses,
|
||||||
}: ScheduleTotalHoursAndCoursesProps): JSX.Element {
|
}: ScheduleTotalHoursAndCoursesProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className='min-w-full w-0 items-center whitespace-nowrap'>
|
<div className='min-w-full flex flex-col items-start whitespace-nowrap'>
|
||||||
<Text className='truncate text-ut-burntorange normal-case!' variant='h1' as='span'>
|
<Text className='truncate text-ut-burntorange' variant='h1' as='span'>
|
||||||
{scheduleName}
|
{`${scheduleName} `}
|
||||||
|
</Text>
|
||||||
|
<Text variant='h3' as='div' className='flex flex-row items-center gap-2.5 text-theme-black'>
|
||||||
|
<Text variant='h4' as='span' className='hidden text-ut-black uppercase screenshot:inline sm:inline'>
|
||||||
|
{totalHours} {totalHours === 1 ? 'Hour' : 'Hours'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text variant='h4' as='span' className='hidden text-ut-black uppercase screenshot:inline sm:inline'>
|
||||||
|
{totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'}
|
||||||
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<div className='flex flex-row items-center gap-2.5 text-theme-black'>
|
|
||||||
<div className='flex flex-row items-center gap-1.25 text-theme-black'>
|
|
||||||
<Text variant='h3' as='span' className='capitalize screenshot:inline sm:inline'>
|
|
||||||
{totalHours}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
variant='h3'
|
|
||||||
as='span'
|
|
||||||
className='capitalize screenshot:inline sm:inline font-all-small-caps!'
|
|
||||||
>
|
|
||||||
{totalHours === 1 ? 'HOUR' : 'HOURS'}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-row items-center gap-1.25 text-theme-black'>
|
|
||||||
<Text variant='h3' as='span' className='capitalize screenshot:inline sm:inline'>
|
|
||||||
{totalCourses}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
variant='h3'
|
|
||||||
as='span'
|
|
||||||
className='capitalize screenshot:inline sm:inline font-all-small-caps!'
|
|
||||||
>
|
|
||||||
{totalCourses === 1 ? 'COURSE' : 'COURSES'}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user