diff --git a/src/views/components/common/Button.tsx b/src/views/components/common/Button.tsx index 68752cdc..722dbb49 100644 --- a/src/views/components/common/Button.tsx +++ b/src/views/components/common/Button.tsx @@ -10,7 +10,7 @@ interface Props { style?: React.CSSProperties; variant?: 'filled' | 'outline' | 'minimal'; size?: 'regular' | 'small' | 'mini'; - onClick?: () => void; + onClick?: (event: React.MouseEvent) => void; icon?: Icon; iconProps?: IconProps; disabled?: boolean; diff --git a/src/views/components/common/PopupCourseBlock.tsx b/src/views/components/common/PopupCourseBlock.tsx index d902cba8..80760a47 100644 --- a/src/views/components/common/PopupCourseBlock.tsx +++ b/src/views/components/common/PopupCourseBlock.tsx @@ -1,5 +1,5 @@ import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; -import { DotsSixVertical } from '@phosphor-icons/react'; +import { Check, Copy, DotsSixVertical } from '@phosphor-icons/react'; import { background } from '@shared/messages'; import { initSettings, OptionsStore } from '@shared/storage/OptionsStore'; import type { Course } from '@shared/types/Course'; @@ -11,6 +11,8 @@ import Text from '@views/components/common/Text/Text'; import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; +import { Button } from './Button'; + /** * Props for PopupCourseBlock */ @@ -37,6 +39,8 @@ export default function PopupCourseBlock({ dragHandleProps, }: PopupCourseBlockProps): JSX.Element { const [enableCourseStatusChips, setEnableCourseStatusChips] = useState(false); + const [isCopied, setIsCopied] = useState(false); + const lastCopyTime = useRef(0); const ref = useRef(null); useEffect(() => { @@ -72,13 +76,26 @@ export default function PopupCourseBlock({ window.close(); }; + const handleCopy = async (e: React.MouseEvent) => { + e.stopPropagation(); + const now = Date.now(); + if (now - lastCopyTime.current < 500) { + return; + } + + lastCopyTime.current = now; + await navigator.clipboard.writeText(formattedUniqueId); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 500); + }; + return (
- - {formattedUniqueId} {course.department} {course.number} + + {course.department} {course.number} {course.instructors.length > 0 ? <> – : ''} {course.instructors.map(v => v.toString({ format: 'last' })).join('; ')} @@ -103,11 +120,39 @@ export default function PopupCourseBlock({ style={{ backgroundColor: colors.secondaryColor, }} - className='ml-1 flex items-center justify-center justify-self-end rounded p-1px text-white' + className='ml-1 flex items-center justify-center justify-self-end rounded p-[3px] text-white' > - + )} + + ); } diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx index 75e5bf17..9c49e9ff 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx @@ -1,4 +1,15 @@ -import { ArrowUpRight, CalendarDots, ChatText, Copy, FileText, Minus, Plus, Smiley, X } from '@phosphor-icons/react'; +import { + ArrowUpRight, + CalendarDots, + ChatText, + Check, + Copy, + FileText, + Minus, + Plus, + Smiley, + X, +} from '@phosphor-icons/react'; import { background } from '@shared/messages'; import type { Course } from '@shared/types/Course'; import type Instructor from '@shared/types/Instructor'; @@ -9,7 +20,8 @@ import Divider from '@views/components/common/Divider'; import Link from '@views/components/common/Link'; import Text from '@views/components/common/Text/Text'; import { useCalendar } from '@views/contexts/CalendarContext'; -import React from 'react'; +import clsx from 'clsx'; +import React, { useRef, useState } from 'react'; import DisplayMeetingInfo from './DisplayMeetingInfo'; @@ -46,10 +58,22 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H const formattedUniqueId = uniqueId.toString().padStart(5, '0'); const isInCalendar = useCalendar(); + const [isCopied, setIsCopied] = useState(false); + const lastCopyTime = useRef(0); + const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' }); - const handleCopy = () => { - navigator.clipboard.writeText(formattedUniqueId); + const handleCopy = async (e: React.MouseEvent) => { + e.stopPropagation(); + const now = Date.now(); + if (now - lastCopyTime.current < 500) { + return; + } + + lastCopyTime.current = now; + await navigator.clipboard.writeText(formattedUniqueId); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 500); }; const handleOpenRateMyProf = async () => { @@ -107,7 +131,21 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H ({department} {courseNumber}) -