feat: add tooltip for Other in grade distribution (#709)

* feat: add tooltip

* refactor: lint happy

* chore: lint

---------

Co-authored-by: Derek Chen <derex1987@gmail.com>
This commit is contained in:
Albert Jiang
2026-01-30 17:33:40 -06:00
committed by GitHub
parent ea54d926ab
commit 7b3fbafa50
3 changed files with 72 additions and 5 deletions

View File

@@ -0,0 +1,44 @@
import clsx from 'clsx';
import type { PropsWithChildren, ReactNode } from 'react';
import React from 'react';
interface TooltipProps {
className?: string;
contentClassName?: string;
content: ReactNode;
offsetX: number;
offsetY: number;
maxWidth?: number;
}
/**
* Tooltip that displays content on hover
*/
export default function Tooltip({
className,
contentClassName,
content,
offsetX,
offsetY,
maxWidth,
children,
}: PropsWithChildren<TooltipProps>): JSX.Element {
return (
<span className={clsx('relative inline-flex group', className)}>
{children}
<span
className={clsx(
'pointer-events-none absolute rounded-md bg-white px-3 py-2 text-xs invisible opacity-0 transition-opacity group-hover:visible group-hover:opacity-100 whitespace-normal break-words',
contentClassName
)}
style={{
marginTop: offsetY,
marginLeft: offsetX,
maxWidth,
}}
>
{content}
</span>
</span>
);
}

View File

@@ -3,6 +3,7 @@ import type { Distribution, LetterGrade } from '@shared/types/Distribution';
import { extendedColors } from '@shared/types/ThemeColors'; import { extendedColors } from '@shared/types/ThemeColors';
import Link from '@views/components/common/Link'; import Link from '@views/components/common/Link';
import Text from '@views/components/common/Text/Text'; import Text from '@views/components/common/Text/Text';
import Tooltip from '@views/components/common/Tooltip';
import { import {
NoDataError, NoDataError,
queryAggregateDistribution, queryAggregateDistribution,
@@ -12,10 +13,12 @@ import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official'; import HighchartsReact from 'highcharts-react-official';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton';
const UT_GRADE_DISTRIBUTION_URL = 'https://reports.utexas.edu/spotlight-data/ut-course-grade-distributions'; const UT_GRADE_DISTRIBUTION_URL = 'https://reports.utexas.edu/spotlight-data/ut-course-grade-distributions';
const TOOLTIP_CONTENT =
"The 'Other' grade category includes all non-standard letter grades, including: In Progress, Incomplete, Permanent Incomplete, Oblit, Q-Drop, Withdrawn, Credit, No Credit, Satisfactory, Unsatisfactory, and Registered on CR/F or CR/NC basis.";
interface GradeDistributionProps { interface GradeDistributionProps {
course: Course; course: Course;
} }
@@ -126,6 +129,25 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
lineHeight: 'normal', lineHeight: 'normal',
fontStyle: 'normal', fontStyle: 'normal',
}, },
useHTML: true,
formatter() {
// eslint-disable-next-line react/no-this-in-sfc
const val = `${this.value}`;
return val === 'Other'
? renderToStaticMarkup(
<Tooltip
content={TOOLTIP_CONTENT}
className='underline'
offsetX={-425}
offsetY={-175}
maxWidth={500}
>
Other
</Tooltip>
)
: val;
},
}, },
title: { title: {
text: 'Grades', text: 'Grades',
@@ -135,6 +157,7 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
fontWeight: '400', fontWeight: '400',
}, },
}, },
categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F', 'Other'], categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F', 'Other'],
tickInterval: 1, tickInterval: 1,
tickWidth: 1, tickWidth: 1,

View File

@@ -4,10 +4,10 @@ import React from 'react';
* Lightweight skeleton placeholder for contributor cards while data loads * Lightweight skeleton placeholder for contributor cards while data loads
*/ */
export const ContributorCardSkeleton: React.FC = () => ( export const ContributorCardSkeleton: React.FC = () => (
<div className='border border-gray-300 rounded bg-ut-gray/10 p-4 animate-pulse'> <div className='animate-pulse border border-gray-300 rounded bg-ut-gray/10 p-4'>
<div className='h-4 w-3/4 bg-gray-300 rounded mb-2' /> <div className='mb-2 h-4 w-3/4 rounded bg-gray-300' />
<div className='h-3 w-1/2 bg-gray-300 rounded mb-1' /> <div className='mb-1 h-3 w-1/2 rounded bg-gray-300' />
<div className='h-3 w-1/4 bg-gray-300 rounded' /> <div className='h-3 w-1/4 rounded bg-gray-300' />
</div> </div>
); );