feat: course color generation (#179)

* feat: course color generation

* feat: add proper TS for hex colors

* refactor: fix oklab and improve contrast ratios

* fix: update HexColor type

* refactor: update color switch point

* refactor: color-related functions and types

* fix: imports and TS issues

* fix: imports and TS issues

* chore: add no-restricted-syntax ForInStatement

* chore(docs): add jsdoc

---------

Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
Razboy20
2024-03-19 18:54:11 -05:00
committed by GitHub
parent c5fc6219e1
commit 5ed81e4be9
30 changed files with 424 additions and 422 deletions

View File

@@ -76,8 +76,8 @@ export default function Calendar(): JSX.Element {
<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'>
<div className='h-full min-w-4xl flex flex-grow flex-col overflow-y-auto' ref={calendarRef}>
<div className='min-h-2xl flex-grow overflow-auto pl-2 pr-4 pt-6'>
<CalendarGrid courseCells={courseCells} setCourse={setCourse} />
</div>
<CalendarBottomBar calendarRef={calendarRef} />

View File

@@ -1,6 +1,6 @@
import type { StatusType } from '@shared/types/Course';
import { Status } from '@shared/types/Course';
import type { CourseColors } from '@shared/util/colors';
import type { CourseColors } from '@shared/types/ThemeColors';
import { pickFontColor } from '@shared/util/colors';
import Text from '@views/components/common/Text/Text';
import clsx from 'clsx';
@@ -51,7 +51,7 @@ export default function CalendarCourseCell({
rightIcon = <CancelledIcon className='h-5 w-5' />;
}
// whiteText based on secondaryColor
// text-white or text-black based on secondaryColor
const fontColor = pickFontColor(colors.primaryColor);
return (
@@ -73,14 +73,19 @@ export default function CalendarCourseCell({
>
<Text
variant='h1-course'
as='p'
className={clsx('leading-tight! truncate', {
'-my-0.8': timeAndLocation,
'-mt-0.8 -mb-0.2': timeAndLocation,
'text-wrap': !timeAndLocation,
})}
>
{courseDeptAndInstr}
</Text>
{timeAndLocation && <Text variant='h3-course'>{timeAndLocation}</Text>}
{timeAndLocation && (
<Text variant='h3-course' as='p'>
{timeAndLocation}
</Text>
)}
</div>
{rightIcon && (
<div

View File

@@ -75,7 +75,7 @@ export const saveAsCal = async () => {
console.log(icsDays);
// Assuming course has date started and ended, adapt as necessary
const year = new Date().getFullYear(); // Example year, adapt accordingly
// const year = new Date().getFullYear(); // Example year, adapt accordingly
// Example event date, adapt startDate according to your needs
const startDate = `20240101T${formattedStartTime}`;
const endDate = `20240101T${formattedEndTime}`;

View File

@@ -1,4 +1,4 @@
import type { ThemeColor } from '@shared/util/themeColors';
import type { ThemeColor } from '@shared/types/ThemeColors';
import { getThemeColorHexByName, getThemeColorRgbByName } from '@shared/util/themeColors';
import Text from '@views/components/common/Text/Text';
import clsx from 'clsx';

View File

@@ -6,12 +6,15 @@ import React, { Fragment } from 'react';
import ExtensionRoot from '../ExtensionRoot/ExtensionRoot';
export interface _DialogProps {
interface _DialogProps {
className?: string;
title?: JSX.Element;
description?: JSX.Element;
}
/**
* Props for the Dialog component.
*/
export type DialogProps = _DialogProps & Omit<TransitionRootProps<typeof HDialog>, 'children'>;
/**

View File

@@ -69,21 +69,21 @@ function Item<T>(props: {
* @example
* <List draggableElements={elements} />
*/
function List<T>(props: ListProps<T>): JSX.Element {
const [items, setItems] = useState(wrap(props.draggables, props.itemKey));
function List<T>({ draggables, itemKey, children, onReordered, gap }: ListProps<T>): JSX.Element {
const [items, setItems] = useState(wrap(draggables, itemKey));
const transformFunction = props.children;
const transformFunction = children;
useEffect(() => {
// check if the draggables content has *actually* changed
if (
props.draggables.length === items.length &&
props.draggables.every((element, index) => props.itemKey(element) === items[index].id)
draggables.length === items.length &&
draggables.every((element, index) => itemKey(element) === items[index].id)
) {
return;
}
setItems(wrap(props.draggables, props.itemKey));
}, [props.draggables]);
setItems(wrap(draggables, itemKey));
}, [draggables, itemKey, items]);
const onDragEnd: OnDragEndResponder = useCallback(
result => {
@@ -94,9 +94,9 @@ function List<T>(props: ListProps<T>): JSX.Element {
const reordered = reorder(items, result.source.index, result.destination.index);
setItems(reordered);
props.onReordered(reordered.map(item => item.content));
onReordered(reordered.map(item => item.content));
},
[items]
[items, onReordered]
);
return (
@@ -131,12 +131,8 @@ function List<T>(props: ListProps<T>): JSX.Element {
);
}}
>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{ marginBottom: `-${props.gap}px` }}
>
{provided => (
<div {...provided.droppableProps} ref={provided.innerRef} style={{ marginBottom: `-${gap}px` }}>
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id.toString()} index={index}>
{draggableProvided => (
@@ -146,7 +142,7 @@ function List<T>(props: ListProps<T>): JSX.Element {
style={{
...draggableProvided.draggableProps.style,
// if last item, don't add margin
marginBottom: `${props.gap}px`,
marginBottom: `${gap}px`,
}}
>
{transformFunction(item.content, draggableProvided.dragHandleProps)}

View File

@@ -2,6 +2,11 @@ import clsx from 'clsx';
import type { SVGProps } from 'react';
import React from 'react';
/**
* Renders the logo icon.
* @param {SVGProps<SVGSVGElement>} props - The SVG props.
* @returns {JSX.Element} The rendered logo icon.
*/
export function LogoIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
return (
<svg width='40' height='40' viewBox='0 0 40 40' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
@@ -13,6 +18,12 @@ export function LogoIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
);
}
/**
* Renders the small logo.
* @param {Object} props - The component props.
* @param {string} props.className - The class name for the logo container.
* @returns {JSX.Element} The rendered small logo.
*/
export function SmallLogo({ className }: { className?: string }): JSX.Element {
return (
<div className={clsx('flex items-center gap-2', className)}>
@@ -25,6 +36,12 @@ export function SmallLogo({ className }: { className?: string }): JSX.Element {
);
}
/**
* Renders the large logo.
* @param {Object} props - The component props.
* @param {string} props.className - The class name for the logo container.
* @returns {JSX.Element} The rendered large logo.
*/
export function LargeLogo({ className }: { className?: string }): JSX.Element {
return (
<div className={clsx('flex items-center gap-2', className)}>

View File

@@ -1,7 +1,7 @@
import { background } from '@shared/messages';
import type { Course } from '@shared/types/Course';
import { Status } from '@shared/types/Course';
import type { CourseColors } from '@shared/util/colors';
import type { CourseColors } from '@shared/types/ThemeColors';
import { pickFontColor } from '@shared/util/colors';
import { StatusIcon } from '@shared/util/icons';
import Text from '@views/components/common/Text/Text';
@@ -31,7 +31,7 @@ export default function PopupCourseBlock({
colors,
dragHandleProps,
}: PopupCourseBlockProps): JSX.Element {
// whiteText based on secondaryColor
// text-white or text-black based on secondaryColor
const fontColor = pickFontColor(colors.primaryColor);
const formattedUniqueId = course.uniqueId.toString().padStart(5, '0');

View File

@@ -28,15 +28,16 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
const [editorValue, setEditorValue] = useState(schedule.name);
const editorRef = React.useRef<HTMLInputElement>(null);
const { current: editor } = editorRef;
useEffect(() => {
const editor = editorRef.current;
setEditorValue(schedule.name);
if (isEditing && editor) {
editor.focus();
editor.setSelectionRange(0, editor.value.length);
}
}, [isEditing, schedule.name, editor]);
}, [isEditing, schedule.name, editorRef]);
const isActive = useMemo(() => activeSchedule.id === schedule.id, [activeSchedule, schedule]);

View File

@@ -8,6 +8,9 @@ import Description from './Description';
import GradeDistribution from './GradeDistribution';
import HeadingAndActions from './HeadingAndActions';
/**
* Props for the CourseCatalogInjectedPopup component.
*/
export type CourseCatalogInjectedPopupProps = DialogProps & {
course: Course;
};

View File

@@ -1,6 +1,6 @@
import type { Course } from '@shared/types/Course';
import type { Distribution, LetterGrade } from '@shared/types/Distribution';
import { extendedColors } from '@shared/util/themeColors';
import { extendedColors } from '@shared/types/ThemeColors';
import Spinner from '@views/components/common/Spinner/Spinner';
import Text from '@views/components/common/Text/Text';
import {