feat: calendar matchings (#173)

* feat: calendar matchings

* fix: build

* refactor: resolve pr comments

* fix: destrucure editorRef

---------

Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
Razboy20
2024-03-17 00:32:50 -05:00
committed by GitHub
parent df1849180d
commit 791a42bcd4
23 changed files with 243 additions and 410 deletions

View File

@@ -46,7 +46,7 @@ export default function Dialog(props: PropsWithChildren<DialogProps>): JSX.Eleme
<div className='fixed inset-0 z-50 flex items-center justify-center'>
<HDialog.Panel
className={clsx(
'z-99 max-h-[90vh] flex flex-col overflow-y-auto border border-ut-offwhite rounded-lg bg-white shadow-xl ml-[calc(100vw-100%)] mt-[calc(100vw-100%)]',
'z-99 max-h-[90vh] flex flex-col overflow-y-auto border border-solid border-ut-offwhite rounded bg-white shadow-xl ml-[calc(100vw-100%)] mt-[calc(100vw-100%)]',
className
)}
>

View File

@@ -3,18 +3,21 @@ import 'uno.css';
import type TabInfoMessages from '@shared/messages/TabInfoMessages';
import { MessageListener } from 'chrome-extension-toolkit';
import clsx from 'clsx';
import React, { useEffect } from 'react';
import styles from './ExtensionRoot.module.scss';
interface Props {
testId?: string;
className?: string;
}
/**
* A wrapper component for the extension elements that adds some basic styling to them
*/
export default function ExtensionRoot(props: React.PropsWithChildren<Props>): JSX.Element {
// TODO: move out of ExtensionRoot
useEffect(() => {
const tabInfoListener = new MessageListener<TabInfoMessages>({
getTabInfo: ({ sendResponse }) => {
@@ -31,7 +34,7 @@ export default function ExtensionRoot(props: React.PropsWithChildren<Props>): JS
}, []);
return (
<div className={styles.extensionRoot} data-testid={props.testId}>
<div className={clsx(styles.extensionRoot, props.className)} data-testid={props.testId}>
{props.children}
</div>
);

View File

@@ -1,9 +1,9 @@
import type { SVGProps } from 'react';
import React from 'react';
export function LogoIcon(props: SVGProps<SVGSVGElement>) {
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'>
<svg width='40' height='40' viewBox='0 0 40 40' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
<circle cx='20' cy='20' r='20' fill='#BF5700' />
<circle cx='20' cy='20' r='15.5' stroke='white' strokeWidth='3' />
<rect x='18' y='10' width='4' height='19.5489' fill='white' />
@@ -11,3 +11,27 @@ export function LogoIcon(props: SVGProps<SVGSVGElement>) {
</svg>
);
}
export function SmallLogo(): JSX.Element {
return (
<div className='flex items-center gap-2'>
<LogoIcon />
<div className='flex flex-col text-lg font-medium leading-[1em]'>
<p className='text-ut-burntorange'>UT Registration</p>
<p className='text-ut-orange'>Plus</p>
</div>
</div>
);
}
export function LargeLogo(): JSX.Element {
return (
<div className='flex items-center gap-2'>
<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>
<p className='text-ut-orange'>Plus</p>
</div>
</div>
);
}

View File

@@ -1,9 +1,12 @@
import deleteSchedule from '@pages/background/lib/deleteSchedule';
import renameSchedule from '@pages/background/lib/renameSchedule';
import type { UserSchedule } from '@shared/types/UserSchedule';
import Text from '@views/components/common/Text/Text';
import useSchedules from '@views/hooks/useSchedules';
import clsx from 'clsx';
import React, { useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import XIcon from '~icons/material-symbols/close';
import DragIndicatorIcon from '~icons/material-symbols/drag-indicator';
/**
@@ -19,31 +22,80 @@ export type Props = {
/**
* This is a reusable dropdown component that can be used to toggle the visiblity of information
*/
export default function ScheduleListItem({ style, schedule, dragHandleProps, onClick }: Props): JSX.Element {
export default function ScheduleListItem({ schedule, dragHandleProps, onClick }: Props): JSX.Element {
const [activeSchedule] = useSchedules();
const [isEditing, setIsEditing] = useState(false);
const [editorValue, setEditorValue] = useState(schedule.name);
const editorRef = React.useRef<HTMLInputElement>(null);
const { current: editor } = editorRef;
useEffect(() => {
setEditorValue(schedule.name);
if (isEditing && editor) {
editor.focus();
editor.setSelectionRange(0, editor.value.length);
}
}, [isEditing, schedule.name, editor]);
const isActive = useMemo(() => activeSchedule.id === schedule.id, [activeSchedule, schedule]);
const handleBlur = () => {
if (editorValue.trim() !== '') {
schedule.name = editorValue.trim();
renameSchedule(schedule.id, schedule.name);
}
setIsEditing(false);
};
return (
<div style={{ ...style }} className='items-center rounded bg-white'>
<li className='w-100% flex cursor-pointer items-center self-stretch justify-left text-ut-burntorange'>
<div className='flex justify-center'>
<div className='rounded bg-white'>
<li className='w-full flex cursor-pointer items-center text-ut-burntorange'>
<div className='h-full cursor-move focusable' {...dragHandleProps}>
<DragIndicatorIcon className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400' />
</div>
<div className='group flex flex-1 items-center overflow-x-hidden'>
<div
className='flex cursor-move items-center self-stretch rounded rounded-r-0'
{...dragHandleProps}
className='flex flex-grow items-center gap-1.5 overflow-x-hidden'
onClick={(...e) => !isEditing && onClick(...e)}
>
<DragIndicatorIcon className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400' />
</div>
<div className='group inline-flex items-center justify-center gap-1.5' onClick={onClick}>
<div
className={clsx(
'h-5.5 w-5.5 relative border-2px border-current rounded-full btn-transition group-active:scale-95 after:(absolute content-empty bg-current h-2.9 w-2.9 rounded-full transition tansform scale-100 ease-out-expo duration-250 -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2)',
'h-5.5 w-5.5 relative border-2px border-current rounded-full btn-transition group-active:scale-95 after:(absolute content-empty bg-current h-2.9 w-2.9 rounded-full transition tansform-gpu scale-100 ease-out-expo duration-250 -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2)',
{
'after:(scale-0! opacity-0 ease-in-out! duration-200!)': !isActive,
}
)}
/>
<Text variant='p'>{schedule.name}</Text>
{isEditing && (
<Text
variant='p'
as='input'
className='mr-1 flex-1 px-0.5 outline-blue-500 -ml-0.5'
value={editorValue}
onChange={e => setEditorValue(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter') handleBlur();
if (e.key === 'Escape') {
setIsEditing(false);
}
}}
onBlur={handleBlur}
ref={editorRef}
/>
)}
{!isEditing && (
<Text variant='p' className='flex-1 truncate' onDoubleClick={() => setIsEditing(true)}>
{schedule.name}
</Text>
)}
</div>
<div>
<XIcon
className='invisible h-5 w-5 text-ut-red group-hover:visible'
onClick={() => deleteSchedule(schedule.id)}
/>
</div>
</div>
</li>

View File

@@ -21,14 +21,14 @@ export default function ScheduleTotalHoursAndCourses({
totalCourses,
}: ScheduleTotalHoursAndCoursesProps): JSX.Element {
return (
<div className='min-w-64 flex content-center items-baseline gap-2 whitespace-nowrap uppercase'>
<Text className='text-ut-burntorange' variant='h1' as='span'>
<div className='min-w-64 flex items-center gap-2.5 whitespace-nowrap'>
<Text className='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'>
{totalCourses} {totalCourses === 1 ? 'COURSE' : 'COURSES'}
{totalHours} {totalHours === 1 ? 'Hour' : 'Hours'}
<Text variant='h4' as='span' className='text-ut-black capitalize'>
{totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'}
</Text>
</Text>
</div>

View File

@@ -9,11 +9,13 @@
.mini {
font-size: 0.79rem;
font-weight: 500;
letter-spacing: 0;
}
.small {
font-size: 0.88875rem;
font-weight: 500;
letter-spacing: 0;
}
.p {
@@ -25,11 +27,13 @@
.h4 {
font-size: 1.125rem;
font-weight: 500;
letter-spacing: 0;
}
.h3-course {
font-size: 0.6875rem;
font-weight: 400;
letter-spacing: 0;
line-height: 100%; /* 0.6875rem */
}
@@ -37,6 +41,7 @@
font-size: 1.26563rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0;
}
.h2-course {
@@ -49,16 +54,19 @@
.h2 {
font-size: 1.42375rem;
font-weight: 500;
letter-spacing: 0;
}
.h1-course {
font-size: 1rem;
font-weight: 600;
text-transform: capitalize;
letter-spacing: 0;
}
.h1 {
font-size: 1.60188rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0;
}

View File

@@ -1,6 +1,6 @@
import type { PropsOf, ReactTag } from '@headlessui/react/dist/types';
import clsx from 'clsx';
import type { ElementType, ReactNode } from 'react';
import type { ElementType, ReactNode, Ref } from 'react';
import React from 'react';
import styles from './Text.module.scss';
@@ -13,6 +13,7 @@ type CleanProps<TTag extends ReactTag, TOmitableProps extends PropertyKey = neve
type OurProps<TTag extends ReactTag> = {
as?: TTag;
children?: ReactNode;
ref?: React.ForwardedRef<React.ElementRef<TTag>>;
};
type AsProps<TTag extends ReactTag, TOverrides = {}> = CleanProps<TTag, keyof TOverrides> & OurProps<TTag> & TOverrides;
@@ -36,14 +37,14 @@ export type TextProps<TTag extends ElementType = 'span'> = PropsOf<TTag>['classN
/**
* A reusable Text component with props that build on top of the design system for the extension
*/
export default function Text<TTag extends ElementType = 'span'>({
as,
className,
variant,
...rest
}: TextProps<TTag>): JSX.Element {
function Text<TTag extends ElementType = 'span'>(
{ as, className, variant, ...rest }: TextProps<TTag>,
ref: Ref<HTMLElement>
): JSX.Element {
const Comp = as || 'span';
const mergedClassName = clsx(styles.text, styles[variant || 'p'], className);
return <Comp className={mergedClassName} {...rest} />;
return <Comp className={mergedClassName} {...rest} ref={ref} />;
}
export default React.forwardRef(Text) as typeof Text;