Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: open course dropdown on hover #686

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/planner/CreditsWarnHoverCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const CreditsWarnHoverCard: FC<CreditsWarnHoverCardProps> = ({
side = 'top',
children,
}) => (
<HoverCard.Root open={open} onOpenChange={onOpenChange} openDelay={0}>
<HoverCard.Root open={open} onOpenChange={onOpenChange}>
<HoverCard.Trigger asChild>{children}</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content
Expand Down
29 changes: 11 additions & 18 deletions src/components/planner/Tiles/SemesterCourseItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { UniqueIdentifier, useDraggable } from '@dnd-kit/core';
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
import React, { ComponentPropsWithoutRef, FC, forwardRef, useState, useRef } from 'react';
import React, { ComponentPropsWithoutRef, FC, forwardRef, useState, useRef, memo } from 'react';
import Skeleton from 'react-loading-skeleton';

import Checkbox from '@/components/Checkbox';
import DotsHorizontalIcon from '@/icons/DotsHorizontalIcon';
import FilledWarningIcon from '@/icons/FilledWarningIcon';
import LockIcon from '@/icons/LockIcon';
import { trpc } from '@/utils/trpc';

import SemesterCourseItemDropdown from './SemesterCourseItemDropdown';
import CourseInfoHoverCard from '../CourseInfoHoverCard';
import PrereqWarnHoverCard from '../PrereqWarnHoverCard';
Expand All @@ -11,12 +17,6 @@ import { DragDataFromSemesterTile, DraggableCourse, Semester } from '../types';
import useGetCourseInfo from '../useGetCourseInfo';
import { tagColors } from '../utils';

import Checkbox from '@/components/Checkbox';
import DotsHorizontalIcon from '@/icons/DotsHorizontalIcon';
import FilledWarningIcon from '@/icons/FilledWarningIcon';
import LockIcon from '@/icons/LockIcon';
import { trpc } from '@/utils/trpc';

import 'react-loading-skeleton/dist/skeleton.css';

export interface SemesterCourseItemProps extends ComponentPropsWithoutRef<'div'> {
Expand All @@ -36,7 +36,7 @@ export interface SemesterCourseItemProps extends ComponentPropsWithoutRef<'div'>

/** UI implementation of a semester course */
/* eslint-disable react/prop-types */
export const MemoizedSemesterCourseItem = React.memo(
export const MemoizedSemesterCourseItem = memo(
forwardRef<HTMLDivElement, SemesterCourseItemProps>(function SemesterCourseItem(
{
course,
Expand Down Expand Up @@ -86,7 +86,7 @@ export const MemoizedSemesterCourseItem = React.memo(
}}
onMouseEnter={() => {
if (!dropdownOpen && !course.locked && !semesterLocked)
hoverTimer.current = setTimeout(() => setHoverOpen(true), 500);
hoverTimer.current = setTimeout(() => setHoverOpen(true), 700);
setHoverEllipse(true);
}}
onMouseLeave={() => {
Expand Down Expand Up @@ -157,13 +157,7 @@ export const MemoizedSemesterCourseItem = React.memo(
{!semesterLocked && (
<SemesterCourseItemDropdown
open={dropdownOpen}
onOpenChange={(open) => {
if (hoverOpen) {
setHoverOpen(false);
}
if (!open) setHoverEllipse(false);
setDropdownOpen(open);
}}
onOpenChange={setDropdownOpen}
locked={course.locked}
onPrereqOverrideChange={() =>
onPrereqOverrideChange && onPrereqOverrideChange(!course.prereqOveridden)
Expand All @@ -179,7 +173,6 @@ export const MemoizedSemesterCourseItem = React.memo(
className={`mr-2 rounded-md px-2 py-3 hover:cursor-default ${
course.locked ? 'hover:bg-gray-300' : 'hover:bg-gray-200/[.5]'
}`}
onClick={() => setDropdownOpen(true)}
>
{!semesterLocked && (
<DotsHorizontalIcon
Expand Down Expand Up @@ -285,4 +278,4 @@ const DraggableSemesterCourseItem: FC<DraggableSemesterCourseItemProps> = ({
);
};

export default React.memo(DraggableSemesterCourseItem);
export default memo(DraggableSemesterCourseItem);
25 changes: 20 additions & 5 deletions src/components/planner/Tiles/SemesterCourseItemDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { FC } from 'react';
import { FC, useRef } from 'react';
import { v4 as uuidv4 } from 'uuid';

import ChevronIcon from '@/icons/ChevronIcon';
Expand All @@ -17,6 +17,8 @@ const contentClasses = 'w-64 rounded-md border border-neutral-300 bg-generic-whi

const disabledClasses = 'text-black/25 cursor-default';

const DROPDOWN_OPEN_DELAY_MS = 600;

export interface SemesterTileDropdownProps {
deleteCourse: () => void;
changeColor: (color: keyof typeof tagColors) => void;
Expand All @@ -43,18 +45,31 @@ const SemesterCourseItemDropdown: FC<SemesterTileDropdownProps> = ({
prereqOverriden,
onPrereqOverrideChange,
}) => {
const id = uuidv4();
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

return (
<DropdownMenu.Root open={open} onOpenChange={onOpenChange}>
<DropdownMenu.Trigger data-no-dnd="true" asChild>
<DropdownMenu.Trigger
onPointerOver={() => {
hoverTimer.current = setTimeout(() => {
onOpenChange(true);
hoverTimer.current = null;
}, DROPDOWN_OPEN_DELAY_MS);
}}
onPointerLeave={() => {
if (hoverTimer.current) clearTimeout(hoverTimer.current);
}}
data-no-dnd="true"
asChild
>
{children}
</DropdownMenu.Trigger>

<DropdownMenu.Portal>
<DropdownMenu.Content
data-no-dnd="true"
className={contentClasses + ' animate-[slideUpAndFade_0.3s]'}
sideOffset={10}
sideOffset={5}
align="start"
>
<DropdownMenu.Item
Expand Down Expand Up @@ -107,7 +122,7 @@ const SemesterCourseItemDropdown: FC<SemesterTileDropdownProps> = ({
{Object.entries(tagColors).map(([color, classes]) => (
<DropdownMenu.Item
className={itemClasses}
key={`${id}-tag-${color}`}
key={`${uuidv4()}-tag-${color}`}
onClick={() => changeColor(color as keyof typeof tagColors)}
>
<div className={`h-5 w-5 rounded-sm border ${classes}`}></div>
Expand Down
Loading