chore: merge branch 'main' into sgunter/button-component
for clsx, and other features
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
"@unocss/reset": "^0.58.5",
|
"@unocss/reset": "^0.58.5",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"chrome-extension-toolkit": "^0.0.51",
|
"chrome-extension-toolkit": "^0.0.51",
|
||||||
"classnames": "^2.5.1",
|
"clsx": "^2.1.0",
|
||||||
"highcharts": "^11.3.0",
|
"highcharts": "^11.3.0",
|
||||||
"highcharts-react-official": "^3.2.1",
|
"highcharts-react-official": "^3.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -25,9 +25,9 @@ dependencies:
|
|||||||
chrome-extension-toolkit:
|
chrome-extension-toolkit:
|
||||||
specifier: ^0.0.51
|
specifier: ^0.0.51
|
||||||
version: 0.0.51
|
version: 0.0.51
|
||||||
classnames:
|
clsx:
|
||||||
specifier: ^2.5.1
|
specifier: ^2.1.0
|
||||||
version: 2.5.1
|
version: 2.1.0
|
||||||
highcharts:
|
highcharts:
|
||||||
specifier: ^11.3.0
|
specifier: ^11.3.0
|
||||||
version: 11.3.0
|
version: 11.3.0
|
||||||
@@ -6111,10 +6111,6 @@ packages:
|
|||||||
consola: 3.2.3
|
consola: 3.2.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/classnames@2.5.1:
|
|
||||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/clean-stack@2.2.0:
|
/clean-stack@2.2.0:
|
||||||
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -6166,6 +6162,11 @@ packages:
|
|||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/clsx@2.1.0:
|
||||||
|
resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color-convert@1.9.3:
|
/color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
24
src/shared/util/icons.tsx
Normal file
24
src/shared/util/icons.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React, { SVGProps } from 'react';
|
||||||
|
import ClosedIcon from '~icons/material-symbols/lock';
|
||||||
|
import WaitlistIcon from '~icons/material-symbols/timelapse';
|
||||||
|
import CancelledIcon from '~icons/material-symbols/warning';
|
||||||
|
import { Status } from '../types/Course';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Icon component based on status
|
||||||
|
* @param props.status status
|
||||||
|
* @returns React.ReactElement - the icon component
|
||||||
|
*/
|
||||||
|
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: Status }): React.ReactElement {
|
||||||
|
const { status, ...rest } = props;
|
||||||
|
|
||||||
|
switch (props.status) {
|
||||||
|
case Status.WAITLISTED:
|
||||||
|
return <WaitlistIcon {...rest} />;
|
||||||
|
case Status.CLOSED:
|
||||||
|
return <ClosedIcon {...rest} />;
|
||||||
|
case Status.CANCELLED:
|
||||||
|
return <CancelledIcon {...rest} />;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/stories/components/CalendarCourseCell.stories.tsx
Normal file
67
src/stories/components/CalendarCourseCell.stories.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { Course, Status } from 'src/shared/types/Course';
|
||||||
|
import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting';
|
||||||
|
import { CourseSchedule } from 'src/shared/types/CourseSchedule';
|
||||||
|
import Instructor from 'src/shared/types/Instructor';
|
||||||
|
import CalendarCourseCell from 'src/views/components/common/CalendarCourseCell/CalendarCourseCell';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/CalendarCourseCell',
|
||||||
|
component: CalendarCourseCell,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
course: { control: 'object' },
|
||||||
|
meetingIdx: { control: 'number' },
|
||||||
|
color: { control: 'color' },
|
||||||
|
},
|
||||||
|
render: (args: any) => (
|
||||||
|
<div className="w-45">
|
||||||
|
<CalendarCourseCell {...args} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
} satisfies Meta<typeof CalendarCourseCell>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
course: new Course({
|
||||||
|
uniqueId: 123,
|
||||||
|
number: '311C',
|
||||||
|
fullName: "311C - Bevo's Default Course",
|
||||||
|
courseName: "Bevo's Default Course",
|
||||||
|
department: 'BVO',
|
||||||
|
creditHours: 3,
|
||||||
|
status: Status.WAITLISTED,
|
||||||
|
instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
|
||||||
|
isReserved: false,
|
||||||
|
url: '',
|
||||||
|
flags: [],
|
||||||
|
schedule: new CourseSchedule({
|
||||||
|
meetings: [
|
||||||
|
new CourseMeeting({
|
||||||
|
days: [DAY_MAP.M, DAY_MAP.W, DAY_MAP.F],
|
||||||
|
startTime: 480,
|
||||||
|
endTime: 570,
|
||||||
|
location: {
|
||||||
|
building: 'UTC',
|
||||||
|
room: '123',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
instructionMode: 'In Person',
|
||||||
|
semester: {
|
||||||
|
year: 2024,
|
||||||
|
season: 'Spring',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
meetingIdx: 0,
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styles from './Button.module.scss';
|
import styles from './Button.module.scss';
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ export function Button({
|
|||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
className={classNames(useScss ? styles.button : BUTTON_BASE_CLASS, className, {
|
className={clsx(useScss ? styles.button : BUTTON_BASE_CLASS, className, {
|
||||||
[styles[variant]]: useScss,
|
[styles[variant]]: useScss,
|
||||||
[styles.disabled]: disabled && useScss,
|
[styles.disabled]: disabled && useScss,
|
||||||
'disabled:(cursor-not-allowed opacity-50)': disabled && !useScss,
|
'disabled:(cursor-not-allowed opacity-50)': disabled && !useScss,
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Course, Status } from 'src/shared/types/Course';
|
||||||
|
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
|
||||||
|
import ClosedIcon from '~icons/material-symbols/lock';
|
||||||
|
import WaitlistIcon from '~icons/material-symbols/timelapse';
|
||||||
|
import CancelledIcon from '~icons/material-symbols/warning';
|
||||||
|
import Text from '../Text/Text';
|
||||||
|
|
||||||
|
export interface CalendarCourseBlockProps {
|
||||||
|
/** The Course that the meeting is for. */
|
||||||
|
course: Course;
|
||||||
|
/* index into course meeting array to display */
|
||||||
|
meetingIdx?: number;
|
||||||
|
/** The background color for the course. */
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CalendarCourseBlock: React.FC<CalendarCourseBlockProps> = ({ course, meetingIdx }: CalendarCourseBlockProps) => {
|
||||||
|
let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;
|
||||||
|
let rightIcon: React.ReactNode | null = null;
|
||||||
|
if (course.status === Status.WAITLISTED) {
|
||||||
|
rightIcon = <WaitlistIcon className='h-5 w-5' />;
|
||||||
|
} else if (course.status === Status.CLOSED) {
|
||||||
|
rightIcon = <ClosedIcon className='h-5 w-5' />;
|
||||||
|
} else if (course.status === Status.CANCELLED) {
|
||||||
|
rightIcon = <CancelledIcon className='h-5 w-5' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex justify-center rounded bg-slate-300 p-2 text-ut-black'>
|
||||||
|
<div className='flex flex-1 flex-col gap-1'>
|
||||||
|
<Text variant='h1-course' className='leading-[75%]!'>
|
||||||
|
{course.department} {course.number} - {course.instructors[0].lastName}
|
||||||
|
</Text>
|
||||||
|
<Text variant='h3-course' className='leading-[75%]!'>
|
||||||
|
{`${meeting.getTimeString({ separator: '–', capitalize: true })}${
|
||||||
|
meeting.location ? ` – ${meeting.location.building}` : ''
|
||||||
|
}`}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
{rightIcon && (
|
||||||
|
<div className='h-fit flex items-center justify-center justify-self-start rounded bg-slate-700 p-0.5 text-white'>
|
||||||
|
{rightIcon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalendarCourseBlock;
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
.dayLabelContainer {
|
.dayLabelContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
height: 13px;
|
height: 13px;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
min-height: 13px;
|
min-height: 13px;
|
||||||
@@ -13,24 +14,44 @@
|
|||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dayLabel {
|
.calendarGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-template-rows: repeat(13, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendarRow {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
// display: grid;
|
||||||
|
// grid-template-columns: auto repeat(5, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
gap: 5px;
|
||||||
color: colors.$burnt_orange;
|
color: colors.$burnt_orange;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: Roboto;
|
|
||||||
font-size: 14.22px;
|
font-size: 14.22px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar {
|
.timeAndGrid {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: auto repeat(5, 1fr);
|
|
||||||
gap: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.day {
|
.timeColumn {
|
||||||
gap: 5px;
|
display: flex;
|
||||||
|
min-height: 573px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex: 1 0 0;
|
||||||
|
border-radius: var(--border-radius-none, 0px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeBlock {
|
.timeBlock {
|
||||||
@@ -41,18 +62,33 @@
|
|||||||
max-width: 50px;
|
max-width: 50px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.timeLabelContainer {
|
||||||
|
display: flex;
|
||||||
|
max-height: 20px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 17px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
align-self: stretch;
|
||||||
|
border-radius: var(--border-radius-none, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #1A2024;
|
||||||
|
text-align: left;
|
||||||
|
height: 6.6px;
|
||||||
|
align-self: stretch;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
/* Type scale/small */
|
||||||
|
font-family: "Roboto Flex";
|
||||||
|
font-size: 14.22px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeLabelContainer {
|
|
||||||
display: flex;
|
|
||||||
max-height: 20px;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 17px;
|
|
||||||
flex: 1 0 0;
|
|
||||||
align-self: stretch;
|
|
||||||
border-radius: var(--border-radius-none, 0px);
|
|
||||||
}
|
|
||||||
@@ -3,35 +3,50 @@ import styles from './CalendarGrid.module.scss';
|
|||||||
import CalendarCell from '../CalendarGridCell/CalendarGridCell';
|
import CalendarCell from '../CalendarGridCell/CalendarGridCell';
|
||||||
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
|
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
|
||||||
|
|
||||||
const daysOfWeek = Object.values(DAY_MAP);
|
const daysOfWeek = Object.values(DAY_MAP).filter(d => d != "Saturday" && d != "Sunday")
|
||||||
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
|
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
|
||||||
|
const grid = Array.from({ length: 5 }, () =>
|
||||||
|
Array.from({ length: 13 }, (_, columnIndex) => (
|
||||||
|
<CalendarCell key={columnIndex} />
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grid of CalendarGridCell components forming the user's course schedule calendar view
|
* Grid of CalendarGridCell components forming the user's course schedule calendar view
|
||||||
* @param props
|
* @param props
|
||||||
*/
|
*/
|
||||||
const Calendar: React.FC = (props) => {
|
const Calendar: React.FC = (props) => {
|
||||||
|
return (
|
||||||
return (
|
<div className={styles.calendar}>
|
||||||
<div className={styles.calendar}>
|
<div className={styles.dayLabelContainer}>
|
||||||
<div className={styles.dayLabelContainer}></div>
|
{/* Empty cell in the top-left corner */}
|
||||||
{daysOfWeek.map((day, dayIndex) => (
|
<div className={styles.day} />
|
||||||
<div key={dayIndex} className={styles.day}>
|
{/* Displaying day labels */}
|
||||||
<div className={styles.dayLabelContainer}>
|
{daysOfWeek.map(day => (
|
||||||
<div className={styles.dayLabel}>{day}</div>
|
<div key={day} className={styles.day}>
|
||||||
</div>
|
{day}
|
||||||
{hoursOfDay.map((hour) => (
|
|
||||||
<div key={`${day}-${hour}`} className={styles.timeBlock}>
|
|
||||||
<div className={styles.timeLabelContainer}>
|
|
||||||
<span>{hour}:00</span>
|
|
||||||
</div>
|
|
||||||
<CalendarCell />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
{/* Displaying the rest of the calendar */}
|
||||||
};
|
<div className={styles.timeAndGrid}>
|
||||||
|
<div className={styles.timeColumn}>
|
||||||
export default Calendar;
|
{hoursOfDay.map((hour) => (
|
||||||
|
<div key={hour} className={styles.timeBlock}>
|
||||||
|
<div className={styles.timeLabelContainer}>
|
||||||
|
<p>{hour % 12 === 0 ? 12 : hour % 12}:00 {hour < 12 ? 'AM' : 'PM'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className={styles.calendarGrid}>
|
||||||
|
{grid.map((row, rowIndex) => (
|
||||||
|
row
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Calendar;
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
.calendarCell {
|
.calendarCell {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 165px;
|
width: 165px;
|
||||||
height: 52.231px;
|
height: 52.231px;
|
||||||
min-width: 45px;
|
min-width: 45px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
position: relative;
|
border: 1px solid #DADCE0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hourLine {
|
.hourLine {
|
||||||
position: absolute;
|
width: 165px;
|
||||||
left: 0;
|
height: 1px;
|
||||||
right: 0;
|
border-radius: var(--border-radius-none, 0px);
|
||||||
top: 50%;
|
background: rgba(218, 220, 224, 0.25);
|
||||||
border-top: 1px solid black; /* Adjust line styles as needed */
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@@ -8,7 +8,9 @@ import styles from './CalendarGridCell.module.scss';
|
|||||||
const CalendarCell: React.FC = (props) => {
|
const CalendarCell: React.FC = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.calendarCell}>
|
<div className={styles.calendarCell}>
|
||||||
<div className={styles.hourLine}></div>
|
<div className={styles.hourLine}>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import styles from './Card.module.scss';
|
import styles from './Card.module.scss';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
@@ -17,7 +17,7 @@ export default function Card(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={props.style}
|
style={props.style}
|
||||||
className={classNames(styles.card, props.className)}
|
className={clsx(styles.card, props.className)}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
data-testid={props.testId}
|
data-testid={props.testId}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Color } from '@views/styles/colors.module.scss';
|
import { Color } from '@views/styles/colors.module.scss';
|
||||||
import styles from './Divider.module.scss';
|
import styles from './Divider.module.scss';
|
||||||
@@ -21,5 +21,5 @@ export default function Divider(props: Props) {
|
|||||||
borderStyle: props.type,
|
borderStyle: props.type,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <hr data-testid={props.testId} style={style} className={classnames(styles.divider, props.className)} />;
|
return <hr data-testid={props.testId} style={style} className={clsx(styles.divider, props.className)} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import colors, { Color } from '@views/styles/colors.module.scss';
|
import colors, { Color } from '@views/styles/colors.module.scss';
|
||||||
import fonts, { Size } from '@views/styles/fonts.module.scss';
|
import fonts, { Size } from '@views/styles/fonts.module.scss';
|
||||||
@@ -36,7 +36,7 @@ export default function Icon(props: Props) {
|
|||||||
<span
|
<span
|
||||||
data-testid={props.testId}
|
data-testid={props.testId}
|
||||||
style={style}
|
style={style}
|
||||||
className={classNames(styles.icon, props.className)}
|
className={clsx(styles.icon, props.className)}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
{props.name}
|
{props.name}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { background } from '@shared/messages';
|
import { background } from '@shared/messages';
|
||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React, { PropsWithChildren } from 'react';
|
import React, { PropsWithChildren } from 'react';
|
||||||
import Text, { TextProps } from '../Text/Text';
|
import Text, { TextProps } from '../Text/Text';
|
||||||
import styles from './Link.module.scss';
|
import styles from './Link.module.scss';
|
||||||
@@ -29,7 +29,7 @@ export default function Link(props: PropsWithChildren<Props>) {
|
|||||||
color='bluebonnet'
|
color='bluebonnet'
|
||||||
{...passedProps}
|
{...passedProps}
|
||||||
span
|
span
|
||||||
className={classNames(
|
className={clsx(
|
||||||
styles.link,
|
styles.link,
|
||||||
{
|
{
|
||||||
[styles.disabled]: isDisabled,
|
[styles.disabled]: isDisabled,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React, { PropsWithChildren, useCallback } from 'react';
|
import React, { PropsWithChildren, useCallback } from 'react';
|
||||||
import styles from './Popup.module.scss';
|
import styles from './Popup.module.scss';
|
||||||
|
|
||||||
@@ -46,12 +46,12 @@ export default function Popup({ onClose, children, className, style, testId, ove
|
|||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={classNames(styles.container, {
|
className={clsx(styles.container, {
|
||||||
[styles.overlay]: overlay,
|
[styles.overlay]: overlay,
|
||||||
})}
|
})}
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
>
|
>
|
||||||
<div ref={bodyRef} className={classNames(styles.body, className)}>
|
<div ref={bodyRef} className={clsx(styles.body, className)}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styles from './Spinner.module.scss';
|
import styles from './Spinner.module.scss';
|
||||||
|
|
||||||
@@ -12,5 +12,5 @@ type Props = {
|
|||||||
* A simple spinner component that can be used to indicate loading.
|
* A simple spinner component that can be used to indicate loading.
|
||||||
*/
|
*/
|
||||||
export default function Spinner({ className, testId, style }: Props) {
|
export default function Spinner({ className, testId, style }: Props) {
|
||||||
return <div data-testid={testId} style={style} className={classNames(styles.spinner, className)} />;
|
return <div data-testid={testId} style={style} className={clsx(styles.spinner, className)} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React, { PropsWithChildren } from 'react';
|
import React, { PropsWithChildren } from 'react';
|
||||||
import styles from './Text.module.scss';
|
import styles from './Text.module.scss';
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ type Variant = (typeof variants)[number];
|
|||||||
* A reusable Text component with props that build on top of the design system for the extension
|
* A reusable Text component with props that build on top of the design system for the extension
|
||||||
*/
|
*/
|
||||||
export default function Text({ variant, as, className, ...props }: PropsWithChildren<TextProps>) {
|
export default function Text({ variant, as, className, ...props }: PropsWithChildren<TextProps>) {
|
||||||
const mergedClassName = classNames(styles.text, styles[variant], className);
|
const mergedClassName = clsx(styles.text, styles[variant], className);
|
||||||
|
|
||||||
if (as === 'div') return <div className={mergedClassName} {...props} />;
|
if (as === 'div') return <div className={mergedClassName} {...props} />;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Course } from '@shared/types/Course';
|
import { Course } from '@shared/types/Course';
|
||||||
import classNames from 'classnames';
|
import clsx from 'clsx';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Spinner from '@views/components/common/Spinner/Spinner';
|
import Spinner from '@views/components/common/Spinner/Spinner';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
@@ -64,7 +64,7 @@ interface LineProps {
|
|||||||
function DescriptionLine({ line }: LineProps) {
|
function DescriptionLine({ line }: LineProps) {
|
||||||
const lowerCaseLine = line.toLowerCase();
|
const lowerCaseLine = line.toLowerCase();
|
||||||
|
|
||||||
const className = classNames({
|
const className = clsx({
|
||||||
[styles.prerequisite]: lowerCaseLine.includes('prerequisite'),
|
[styles.prerequisite]: lowerCaseLine.includes('prerequisite'),
|
||||||
[styles.onlyOne]:
|
[styles.onlyOne]:
|
||||||
lowerCaseLine.includes('may be') || lowerCaseLine.includes('only one') || lowerCaseLine.includes('may not'),
|
lowerCaseLine.includes('may be') || lowerCaseLine.includes('only one') || lowerCaseLine.includes('may not'),
|
||||||
|
|||||||
Reference in New Issue
Block a user