feat(ui): update popup and course blocks (#506)

* feat(ui): add time and location to popup

* feat(ui): memoize meeting times

* feat(ui): remove resizing

* feat(ui): add no select to copy course id button

* feat(ui): complete update to popup and course blocks

* chore: update settings page

* chore: fix types

* fix(ui): update spacing, padding, and remove last updated section

* chore: fix type issues

* fix(ui): update borders to offwhite/50

* fix(ui): apply proper offwhite styling

* fix(ui): add unique key to async courses in bottom bar
This commit is contained in:
Preston Cook
2025-02-13 18:07:05 -06:00
committed by GitHub
parent b171f01d01
commit ee4c6ce699
14 changed files with 163 additions and 123 deletions

View File

@@ -9,7 +9,7 @@
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 400px; width: 430px;
height: 540px; height: 540px;
} }
</style> </style>

View File

@@ -7,9 +7,6 @@ export interface IOptionsStore {
/** whether we should enable course status chips (indicator for waitlisted, cancelled, and closed courses) */ /** whether we should enable course status chips (indicator for waitlisted, cancelled, and closed courses) */
enableCourseStatusChips: boolean; enableCourseStatusChips: boolean;
/** whether we should enable course's time and location in the extension's popup */
enableTimeAndLocationInPopup: boolean;
/** whether we should automatically highlight conflicts on the course schedule page (adds a red strikethrough to courses that have conflicting times) */ /** whether we should automatically highlight conflicts on the course schedule page (adds a red strikethrough to courses that have conflicting times) */
enableHighlightConflicts: boolean; enableHighlightConflicts: boolean;
@@ -25,10 +22,9 @@ export interface IOptionsStore {
export const OptionsStore = createSyncStore<IOptionsStore>({ export const OptionsStore = createSyncStore<IOptionsStore>({
enableCourseStatusChips: false, enableCourseStatusChips: false,
enableTimeAndLocationInPopup: false,
enableHighlightConflicts: true, enableHighlightConflicts: true,
enableScrollToLoad: true, enableScrollToLoad: true,
enableDataRefreshing: true, enableDataRefreshing: false,
alwaysOpenCalendarInNewTab: false, alwaysOpenCalendarInNewTab: false,
}); });
@@ -40,7 +36,6 @@ export const OptionsStore = createSyncStore<IOptionsStore>({
export const initSettings = async () => export const initSettings = async () =>
({ ({
enableCourseStatusChips: await OptionsStore.get('enableCourseStatusChips'), enableCourseStatusChips: await OptionsStore.get('enableCourseStatusChips'),
enableTimeAndLocationInPopup: await OptionsStore.get('enableTimeAndLocationInPopup'),
enableHighlightConflicts: await OptionsStore.get('enableHighlightConflicts'), enableHighlightConflicts: await OptionsStore.get('enableHighlightConflicts'),
enableScrollToLoad: await OptionsStore.get('enableScrollToLoad'), enableScrollToLoad: await OptionsStore.get('enableScrollToLoad'),
enableDataRefreshing: await OptionsStore.get('enableDataRefreshing'), enableDataRefreshing: await OptionsStore.get('enableDataRefreshing'),

View File

@@ -53,9 +53,8 @@ export class CourseMeeting {
* @param meeting - The meeting to check for conflicts with * @param meeting - The meeting to check for conflicts with
* @returns True if the given meeting conflicts with this meeting, false otherwise * @returns True if the given meeting conflicts with this meeting, false otherwise
*/ */
isConflicting(meeting: CourseMeeting): boolean { isConflicting({ days: otherDays, startTime: otherStartTime, endTime: otherEndTime }: CourseMeeting): boolean {
const { days, startTime, endTime } = this; const { days, startTime, endTime } = this;
const { days: otherDays, startTime: otherStartTime, endTime: otherEndTime } = meeting;
const hasDayConflict = days.some(day => otherDays.includes(day)); const hasDayConflict = days.some(day => otherDays.includes(day));
const hasTimeConflict = startTime < otherEndTime && endTime > otherStartTime; const hasTimeConflict = startTime < otherEndTime && endTime > otherStartTime;
@@ -69,14 +68,13 @@ export class CourseMeeting {
* @param options - Options for the string representation * @param options - Options for the string representation
* @returns String representation of the days of the week that this meeting is taught * @returns String representation of the days of the week that this meeting is taught
*/ */
getDaysString(options: DaysStringOptions): string { getDaysString({ format, separator }: DaysStringOptions): string {
let { format, separator } = options;
let { days } = this; let { days } = this;
if (format === 'short') { if (format === 'short') {
days = Object.keys(DAY_MAP).filter(day => days.includes(DAY_MAP[day as keyof typeof DAY_MAP])) as Day[]; days = Object.keys(DAY_MAP).filter(day => days.includes(DAY_MAP[day as keyof typeof DAY_MAP])) as Day[];
} }
if (separator === 'none') { if (!separator) {
return days.join(''); return days.join('');
} }
const listFormat = new Intl.ListFormat('en-US', { const listFormat = new Intl.ListFormat('en-US', {
@@ -92,7 +90,7 @@ export class CourseMeeting {
* @param options - Options for the string representation * @param options - Options for the string representation
* @returns String representation of the time range for the course * @returns String representation of the time range for the course
*/ */
getTimeString(options: TimeStringOptions): string { getTimeString({ separator = '-' }: TimeStringOptions): string {
const { startTime, endTime } = this; const { startTime, endTime } = this;
const startHour = Math.floor(startTime / 60); const startHour = Math.floor(startTime / 60);
const startMinute = startTime % 60; const startMinute = startTime % 60;
@@ -124,7 +122,7 @@ export class CourseMeeting {
endTimeString += endMinute === 0 ? ':00' : `:${endMinute}`; endTimeString += endMinute === 0 ? ':00' : `:${endMinute}`;
endTimeString += endHour >= 12 ? 'pm' : 'am'; endTimeString += endHour >= 12 ? 'pm' : 'am';
return `${startTimeString} ${options.separator} ${endTimeString}`; return `${startTimeString} ${separator} ${endTimeString}`;
} }
} }
@@ -153,5 +151,5 @@ type DaysStringOptions = {
* *
* 'narrow' = `Monday Wednesday Friday` * 'narrow' = `Monday Wednesday Friday`
*/ */
separator: Intl.ListFormatStyle | 'none'; separator?: Intl.ListFormatStyle;
}; };

View File

@@ -76,7 +76,7 @@ const exampleCourses = generateCourses(numberOfCourses);
type CourseWithId = Course & BaseItem; type CourseWithId = Course & BaseItem;
const meta = { const meta = {
title: 'Components/Common/List', title: 'Components/Common/SortableList',
component: SortableList, component: SortableList,
parameters: { parameters: {
layout: 'centered', layout: 'centered',

View File

@@ -1,15 +1,13 @@
import splashText from '@assets/insideJokes'; import splashText from '@assets/insideJokes';
import createSchedule from '@pages/background/lib/createSchedule'; import createSchedule from '@pages/background/lib/createSchedule';
import { CalendarDots, Flag, GearSix, Plus } from '@phosphor-icons/react'; import { CalendarDots, GearSix, Plus } from '@phosphor-icons/react';
import { background } from '@shared/messages'; import { background } from '@shared/messages';
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore'; import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { openReportWindow } from '@shared/util/openReportWindow';
import Divider from '@views/components/common/Divider'; import Divider from '@views/components/common/Divider';
import Text from '@views/components/common/Text/Text'; import Text from '@views/components/common/Text/Text';
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit'; import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules'; import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
import useKC_DABR_WASM from 'kc-dabr-wasm'; import useKC_DABR_WASM from 'kc-dabr-wasm';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -27,14 +25,14 @@ import { SortableList } from './common/SortableList';
*/ */
export default function PopupMain(): JSX.Element { export default function PopupMain(): 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);
useKC_DABR_WASM(); useKC_DABR_WASM();
useEffect(() => { useEffect(() => {
const initAllSettings = async () => { const initAllSettings = async () => {
const { enableCourseStatusChips, enableDataRefreshing } = await initSettings(); const { enableCourseStatusChips } = await initSettings();
setEnableCourseStatusChips(enableCourseStatusChips); setEnableCourseStatusChips(enableCourseStatusChips);
setEnableDataRefreshing(enableDataRefreshing); // setEnableDataRefreshing(enableDataRefreshing);
}; };
initAllSettings(); initAllSettings();
@@ -44,14 +42,14 @@ export default function PopupMain(): JSX.Element {
// console.log('enableCourseStatusChips', newValue); // console.log('enableCourseStatusChips', newValue);
}); });
const l2 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => { // const l2 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => {
setEnableDataRefreshing(newValue); // setEnableDataRefreshing(newValue);
// console.log('enableDataRefreshing', newValue); // // console.log('enableDataRefreshing', newValue);
}); // });
return () => { return () => {
OptionsStore.removeListener(l1); OptionsStore.removeListener(l1);
OptionsStore.removeListener(l2); // OptionsStore.removeListener(l2);
}; };
}, []); }, []);
@@ -86,7 +84,7 @@ export default function PopupMain(): JSX.Element {
return ( return (
<div className='h-screen max-h-full flex flex-col bg-white'> <div className='h-screen max-h-full flex flex-col bg-white'>
<div className='p-5 py-3.5'> <div className='px-spacing-6 py-spacing-5'>
<div className='flex items-center justify-between bg-white'> <div className='flex items-center justify-between bg-white'>
<SmallLogo /> <SmallLogo />
<div className='flex items-center gap-2.5'> <div className='flex items-center gap-2.5'>
@@ -95,13 +93,15 @@ export default function PopupMain(): JSX.Element {
color='ut-burntorange' color='ut-burntorange'
onClick={handleCalendarOpenOnClick} onClick={handleCalendarOpenOnClick}
icon={CalendarDots} icon={CalendarDots}
/> iconProps={{ weight: 'fill' }}
>
Calendar
</Button>
<Button variant='minimal' color='ut-black' onClick={handleOpenOptions} icon={GearSix} /> <Button variant='minimal' color='ut-black' onClick={handleOpenOptions} icon={GearSix} />
<Button variant='minimal' color='ut-black' onClick={openReportWindow} icon={Flag} />
</div> </div>
</div> </div>
</div> </div>
<Divider orientation='horizontal' size='100%' /> <Divider className='bg-ut-offwhite/50' orientation='horizontal' size='100%' />
<div className='px-5 pb-2.5 pt-3.75'> <div className='px-5 pb-2.5 pt-3.75'>
<ScheduleDropdown> <ScheduleDropdown>
<SortableList <SortableList
@@ -139,7 +139,10 @@ export default function PopupMain(): JSX.Element {
</Text> </Text>
</div> </div>
)} )}
<div className='flex-1 self-stretch overflow-y-auto px-5'> <div
style={{ scrollbarGutter: 'stable' }}
className='flex-1 self-stretch overflow-y-scroll pl-spacing-6 pr-[6px]'
>
{activeSchedule?.courses?.length > 0 && ( {activeSchedule?.courses?.length > 0 && (
<SortableList <SortableList
draggables={activeSchedule.courses.map(course => ({ draggables={activeSchedule.courses.map(course => ({
@@ -157,21 +160,21 @@ export default function PopupMain(): JSX.Element {
/> />
)} )}
</div> </div>
<div className='w-full flex flex-col items-center gap-1.25 p-5 pt-3.75'> <div className='w-full flex flex-col items-center gap-1.25 px-spacing-6 py-spacing-5'>
<div className='flex gap-2.5'> <div className='flex gap-spacing-6'>
{enableCourseStatusChips && ( {enableCourseStatusChips && (
<> <>
<CourseStatus status='WAITLISTED' size='mini' /> <CourseStatus status='WAITLISTED' size='small' />
<CourseStatus status='CLOSED' size='mini' /> <CourseStatus status='CLOSED' size='small' />
<CourseStatus status='CANCELLED' size='mini' /> <CourseStatus status='CANCELLED' size='small' />
</> </>
)} )}
</div> </div>
{enableDataRefreshing && ( {/* {enableDataRefreshing && (
<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 !font-normal'> {/* <Text variant='mini' className='text-ut-gray !font-normal'>
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)} 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'
onClick={() => { onClick={() => {
@@ -184,8 +187,8 @@ export default function PopupMain(): JSX.Element {
})} })}
/> />
</button> */} </button> */}
</div> {/* </div>
)} )} */}
</div> </div>
</div> </div>
); );

View File

@@ -65,7 +65,7 @@ export default function Calendar(): JSX.Element {
<div className='h-screen flex overflow-auto'> <div className='h-screen flex overflow-auto'>
<div <div
className={clsx( 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-offwhite/50 shadow-[2px_0_10px,rgba(214_210_196_/_.1)] motion-safe:duration-300 motion-safe:ease-out-expo motion-safe:transition-[max-width] screenshot:hidden', '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-ut-offwhite/50 shadow-[2px_0_10px,rgba(214_210_196_/_.1)] motion-safe:duration-300 motion-safe:ease-out-expo motion-safe:transition-[max-width] screenshot:hidden',
{ {
'max-w-[20.3125rem] ': showSidebar, 'max-w-[20.3125rem] ': showSidebar,
'max-w-0 pointer-events-none': !showSidebar, 'max-w-0 pointer-events-none': !showSidebar,

View File

@@ -43,9 +43,9 @@ export default function CalendarBottomBar({ courseCells, setCourse }: CalendarBo
const { courseDeptAndInstr, status, className } = block.componentProps; const { courseDeptAndInstr, status, className } = block.componentProps;
return ( return (
<CalendarCourseBlock <CalendarCourseBlock
key={block.course.uniqueId}
courseDeptAndInstr={courseDeptAndInstr} courseDeptAndInstr={courseDeptAndInstr}
status={status} status={status}
key={courseDeptAndInstr}
className={clsx(className, 'w-35! h-12.5! items-center')} className={clsx(className, 'w-35! h-12.5! items-center')}
onClick={() => setCourse(block.course)} onClick={() => setCourse(block.course)}
blockData={block} blockData={block}

View File

@@ -105,7 +105,7 @@ export default function CourseCellColorPicker({ defaultColor }: CourseCellColorP
}; };
return ( return (
<div className='inline-flex flex-col border border-1 border-ut-offwhite rounded-1 bg-white p-1.25'> <div className='inline-flex flex-col border border-ut-offwhite rounded-1 bg-white p-1.25'>
<div className='grid grid-cols-6 gap-1'> <div className='grid grid-cols-6 gap-1'>
{Array.from(colorPatchColors.keys()).map(baseColor => ( {Array.from(colorPatchColors.keys()).map(baseColor => (
<ColorPatch <ColorPatch

View File

@@ -62,7 +62,7 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
className={clsx([ className={clsx([
styleResetClass, styleResetClass,
'mt-spacing-3', 'mt-spacing-3',
'min-w-max cursor-pointer origin-top-right rounded bg-white p-1 text-black shadow-lg transition border border-theme-offwhite1 focus:outline-none', 'min-w-max cursor-pointer origin-top-right rounded bg-white p-1 text-black shadow-lg transition border border-ut-offwhite/50 focus:outline-none',
'data-[closed]:(opacity-0 scale-95)', 'data-[closed]:(opacity-0 scale-95)',
'data-[enter]:(ease-out-expo duration-150)', 'data-[enter]:(ease-out-expo duration-150)',
'data-[leave]:(ease-out duration-50)', 'data-[leave]:(ease-out duration-50)',

View File

@@ -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-theme-offwhite/50 w-0 h-0', className)} className={clsx('border-solid border-ut-offwhite/50 w-0 h-0', className)}
/> />
); );
} }

View File

@@ -8,7 +8,7 @@ import { pickFontColor } from '@shared/util/colors';
import { StatusIcon } from '@shared/util/icons'; import { StatusIcon } from '@shared/util/icons';
import Text from '@views/components/common/Text/Text'; import Text from '@views/components/common/Text/Text';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react'; import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
import { Button } from './Button'; import { Button } from './Button';
import { SortableListDragHandle } from './SortableListDragHandle'; import { SortableListDragHandle } from './SortableListDragHandle';
@@ -24,6 +24,19 @@ export interface PopupCourseBlockProps {
const IS_STORYBOOK = import.meta.env.STORYBOOK; const IS_STORYBOOK = import.meta.env.STORYBOOK;
const CourseMeeting = memo(
({ meeting, fontColor }: { meeting: Course['schedule']['meetings'][0]; fontColor: string }) => {
const dateString = meeting.getDaysString({ format: 'short' });
return (
<Text key={dateString} className={clsx('flex-1 truncate select-none', fontColor)} variant='h3-course'>
{`${dateString} ${meeting.getTimeString({ separator: '-' })}${
meeting.location ? `, ${meeting.location.building} ${meeting.location.room}` : ''
}`}
</Text>
);
}
);
/** /**
* The "course block" to be used in the extension popup. * The "course block" to be used in the extension popup.
* *
@@ -35,12 +48,18 @@ const IS_STORYBOOK = import.meta.env.STORYBOOK;
*/ */
export default function PopupCourseBlock({ className, course, colors }: PopupCourseBlockProps): JSX.Element { export default function PopupCourseBlock({ className, course, colors }: PopupCourseBlockProps): JSX.Element {
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false); const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
const [isCopied, setIsCopied] = useState<boolean>(false); const [isCopied, setIsCopied] = useState<boolean>(false);
const lastCopyTime = useRef<number>(0); const lastCopyTime = useRef<number>(0);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
initSettings().then(({ enableCourseStatusChips }) => setEnableCourseStatusChips(enableCourseStatusChips)); const initAllSettings = async () => {
const { enableCourseStatusChips } = await initSettings();
setEnableCourseStatusChips(enableCourseStatusChips);
};
initAllSettings();
const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => { const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => {
setEnableCourseStatusChips(newValue); setEnableCourseStatusChips(newValue);
@@ -84,39 +103,64 @@ export default function PopupCourseBlock({ className, course, colors }: PopupCou
setTimeout(() => setIsCopied(false), 500); setTimeout(() => setIsCopied(false), 500);
}; };
const meetings = useMemo(
() =>
course.schedule.meetings.map(meeting => (
<CourseMeeting
key={meeting.getDaysString({ format: 'short' })}
meeting={meeting}
fontColor={fontColor}
/>
)),
[course.schedule.meetings, fontColor]
);
return ( return (
<div <div
style={{ style={{
backgroundColor: colors.primaryColor, backgroundColor: colors.primaryColor,
}} }}
className={clsx( className={clsx(
'h-full w-full inline-flex items-center justify-center gap-1 rounded pr-2 focusable cursor-pointer text-left hover:shadow-md ease-out group-[.is-dragging]:shadow-md', 'w-full inline-flex items-center justify-center gap-1 rounded focusable cursor-pointer text-left hover:shadow-md ease-out group-[.is-dragging]:shadow-md min-h-[55px]',
className className
)} )}
onClick={handleClick} onClick={handleClick}
ref={ref} ref={ref}
> >
{IS_STORYBOOK ? ( {IS_STORYBOOK ? (
<div
style={{
backgroundColor: colors.secondaryColor,
}}
className='flex cursor-move items-center self-stretch rounded rounded-r-0 px-spacing-2'
>
<DotsSixVertical weight='bold' className='h-6 w-6 cursor-move text-white' /> <DotsSixVertical weight='bold' className='h-6 w-6 cursor-move text-white' />
</div>
) : ( ) : (
<SortableListDragHandle <SortableListDragHandle
style={{ style={{
backgroundColor: colors.secondaryColor, backgroundColor: colors.secondaryColor,
}} }}
className='flex cursor-move items-center self-stretch rounded rounded-r-0' className='flex cursor-move items-center self-stretch rounded rounded-r-0 px-spacing-2'
> >
<DotsSixVertical weight='bold' className='h-6 w-6 cursor-move text-white' /> <DotsSixVertical weight='bold' className='h-6 w-6 cursor-move text-white' />
</SortableListDragHandle> </SortableListDragHandle>
)} )}
<div className='h-full flex flex-1 justify-center gap-spacing-3 p-spacing-3'>
<div className='flex flex-1 flex-col justify-center gap-spacing-1'>
<Text <Text
className={clsx('flex-1 py-spacing-5 truncate ml-spacing-3 select-none', fontColor)} className={clsx(
`truncate select-none justify-center ${meetings.length > 0 ? 'mb-auto' : 'my-auto'}`,
fontColor
)}
variant='h1-course' variant='h1-course'
> >
{course.department} {course.number} {course.department} {course.number}
{course.instructors.length > 0 ? <> &ndash; </> : ''} {course.instructors.length > 0 ? <> &ndash; </> : ''}
{course.instructors.map(v => v.toString({ format: 'last' })).join('; ')} {course.instructors.map(v => v.toString({ format: 'last' })).join('; ')}
</Text> </Text>
<div className='flex flex-col'>{meetings}</div>
</div>
{enableCourseStatusChips && course.status !== Status.OPEN && ( {enableCourseStatusChips && course.status !== Status.OPEN && (
<div <div
style={{ style={{
@@ -127,16 +171,16 @@ export default function PopupCourseBlock({ className, course, colors }: PopupCou
<StatusIcon status={course.status} className='h-6 w-6' /> <StatusIcon status={course.status} className='h-6 w-6' />
</div> </div>
)} )}
<div className='flex flex-col justify-center'>
<Button <Button
color='ut-gray' color='ut-gray'
onClick={handleCopy} onClick={handleCopy}
className='h-full max-h-[30px] w-fit gap-spacing-2 rounded py-spacing-2 text-white px-spacing-3!' className='h-full max-h-[30px] max-w-fit gap-spacing-2 rounded text-white px-spacing-3! py-spacing-2!'
style={{ style={{
backgroundColor: colors.secondaryColor, backgroundColor: colors.secondaryColor,
}} }}
> >
<div className='relative h-5.5 w-5.5'> <div className='relative h-[21px] w-[21px]'>
<Check <Check
className={clsx( className={clsx(
'absolute size-full inset-0 text-white transition-all duration-250 ease-in-out', 'absolute size-full inset-0 text-white transition-all duration-250 ease-in-out',
@@ -146,15 +190,17 @@ export default function PopupCourseBlock({ className, course, colors }: PopupCou
<Copy <Copy
weight='fill' weight='fill'
className={clsx( className={clsx(
'absolute size-full inset-0 text-white transition-all duration-250 ease-in-out', 'absolute size-full inset-0 text-white transition-all duration-250 ease-in-out select-none',
isCopied ? 'opacity-0 scale-75' : 'opacity-100 scale-100' isCopied ? 'opacity-0 scale-75' : 'opacity-100 scale-100'
)} )}
/> />
</div> </div>
<Text variant='h2' className='no-select text-base!'> <Text variant='h2' className='select-none text-base!'>
{formattedUniqueId} {formattedUniqueId}
</Text> </Text>
</Button> </Button>
</div> </div>
</div>
</div>
); );
} }

View File

@@ -19,7 +19,7 @@ export default function ScheduleDropdown({ defaultOpen, children }: ScheduleDrop
const [activeSchedule] = useSchedules(); const [activeSchedule] = useSchedules();
return ( return (
<div className='border border-ut-offwhite rounded border-solid bg-white'> <div className='border border-ut-offwhite/50 rounded bg-white'>
<Disclosure defaultOpen={defaultOpen}> <Disclosure defaultOpen={defaultOpen}>
{({ open }) => ( {({ open }) => (
<> <>

View File

@@ -166,7 +166,7 @@ export default function ScheduleListItem({ schedule, onClick }: ScheduleListItem
as={ExtensionRootWrapper} as={ExtensionRootWrapper}
className={clsx([ className={clsx([
styleResetClass, styleResetClass,
'w-fit cursor-pointer origin-top-right rounded bg-white p-1 text-black shadow-lg transition border border-theme-offwhite/50 focus:outline-none', 'w-fit cursor-pointer origin-top-right rounded bg-white p-1 text-black shadow-lg transition border border-ut-offwhite/50 focus:outline-none',
'data-[closed]:(opacity-0 scale-95)', 'data-[closed]:(opacity-0 scale-95)',
'data-[enter]:(ease-out-expo duration-150)', 'data-[enter]:(ease-out-expo duration-150)',
'data-[leave]:(ease-out duration-50)', 'data-[leave]:(ease-out duration-50)',

View File

@@ -86,7 +86,7 @@ const useDevMode = (targetCount: number): [boolean, () => void] => {
*/ */
export default function Settings(): JSX.Element { export default function Settings(): JSX.Element {
const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false); const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
const [_showTimeLocation, setShowTimeLocation] = useState<boolean>(false); // const [_showTimeLocation, setShowTimeLocation] = useState<boolean>(false);
const [highlightConflicts, setHighlightConflicts] = useState<boolean>(false); const [highlightConflicts, setHighlightConflicts] = useState<boolean>(false);
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false); const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false); const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
@@ -119,14 +119,13 @@ export default function Settings(): JSX.Element {
const initAndSetSettings = async () => { const initAndSetSettings = async () => {
const { const {
enableCourseStatusChips, enableCourseStatusChips,
enableTimeAndLocationInPopup,
enableHighlightConflicts, enableHighlightConflicts,
enableScrollToLoad, enableScrollToLoad,
enableDataRefreshing, enableDataRefreshing,
alwaysOpenCalendarInNewTab, alwaysOpenCalendarInNewTab,
} = await initSettings(); } = await initSettings();
setEnableCourseStatusChips(enableCourseStatusChips); setEnableCourseStatusChips(enableCourseStatusChips);
setShowTimeLocation(enableTimeAndLocationInPopup); // setShowTimeLocation(enableTimeAndLocationInPopup);
setHighlightConflicts(enableHighlightConflicts); setHighlightConflicts(enableHighlightConflicts);
setLoadAllCourses(enableScrollToLoad); setLoadAllCourses(enableScrollToLoad);
setEnableDataRefreshing(enableDataRefreshing); setEnableDataRefreshing(enableDataRefreshing);
@@ -150,27 +149,27 @@ export default function Settings(): JSX.Element {
// console.log('enableCourseStatusChips', newValue); // console.log('enableCourseStatusChips', newValue);
}); });
const l2 = OptionsStore.listen('enableTimeAndLocationInPopup', async ({ newValue }) => { // const l2 = OptionsStore.listen('enableTimeAndLocationInPopup', async ({ newValue }) => {
setShowTimeLocation(newValue); // setShowTimeLocation(newValue);
// console.log('enableTimeAndLocationInPopup', newValue); // // console.log('enableTimeAndLocationInPopup', newValue);
}); // });
const l3 = OptionsStore.listen('enableHighlightConflicts', async ({ newValue }) => { const l2 = OptionsStore.listen('enableHighlightConflicts', async ({ newValue }) => {
setHighlightConflicts(newValue); setHighlightConflicts(newValue);
// console.log('enableHighlightConflicts', newValue); // console.log('enableHighlightConflicts', newValue);
}); });
const l4 = OptionsStore.listen('enableScrollToLoad', async ({ newValue }) => { const l3 = OptionsStore.listen('enableScrollToLoad', async ({ newValue }) => {
setLoadAllCourses(newValue); setLoadAllCourses(newValue);
// console.log('enableScrollToLoad', newValue); // console.log('enableScrollToLoad', newValue);
}); });
const l5 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => { const l4 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => {
setEnableDataRefreshing(newValue); setEnableDataRefreshing(newValue);
// console.log('enableDataRefreshing', newValue); // console.log('enableDataRefreshing', newValue);
}); });
const l6 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => { const l5 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => {
setCalendarNewTab(newValue); setCalendarNewTab(newValue);
// console.log('alwaysOpenCalendarInNewTab', newValue); // console.log('alwaysOpenCalendarInNewTab', newValue);
}); });
@@ -182,7 +181,6 @@ export default function Settings(): JSX.Element {
OptionsStore.removeListener(l3); OptionsStore.removeListener(l3);
OptionsStore.removeListener(l4); OptionsStore.removeListener(l4);
OptionsStore.removeListener(l5); OptionsStore.removeListener(l5);
OptionsStore.removeListener(l6);
window.removeEventListener('keydown', handleKeyPress); window.removeEventListener('keydown', handleKeyPress);
}; };