Link component, Card component, Course Popup component styling, and wrangling with the serialization type"

This commit is contained in:
Sriram Hariharan
2023-03-05 22:52:11 -06:00
parent 6d69cd2548
commit ad8a06d831
10 changed files with 147 additions and 49 deletions

View File

@@ -6,7 +6,7 @@ import { CourseSchedule } from './CourseSchedule';
* Also includes a link to their RateMyProfessor page * Also includes a link to their RateMyProfessor page
*/ */
export type Instructor = { export type Instructor = {
name: string; fullName: string;
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
middleInitial?: string; middleInitial?: string;
@@ -75,6 +75,32 @@ export class Course {
constructor(course: Course | Serialized<Course>) { constructor(course: Course | Serialized<Course>) {
Object.assign(this, course); Object.assign(this, course);
} }
getInstructorString(options: {
/** The maximum number of instructors to show */
max?: number;
format: 'abbr' | 'first_last' | 'last' | 'full_name';
}): string {
const { max = 3, format } = options;
if (!this.instructors) {
return 'Undecided';
}
const instructors = this.instructors.slice(0, max);
switch (format) {
case 'abbr':
return instructors.map(instructor => `${instructor.firstName?.[0]}. ${instructor.lastName}`).join(', ');
case 'full_name':
return instructors.map(instructor => instructor.fullName).join(', ');
case 'first_last':
return instructors.map(instructor => `${instructor.firstName} ${instructor.lastName}`).join(', ');
case 'last':
return instructors.map(instructor => instructor.lastName).join(', ');
default:
throw new Error(`Invalid Instructor String format: ${format}`);
}
}
} }
/** /**

View File

@@ -0,0 +1,8 @@
@import 'src/views/styles/base.module.scss';
.card {
background: $white;
border: 1px solid #c3cee0;
box-sizing: border-box;
border-radius: 8px;
}

View File

@@ -0,0 +1,27 @@
import classNames from 'classnames';
import React, { Component } from 'react';
import styles from './Card.module.scss';
export type Props = {
style?: React.CSSProperties;
className?: string;
onClick?: (...args) => void;
children?: React.ReactNode;
testId?: string;
};
/**
* A reusable Card component that can be used to wrap other components
*/
export default function Card(props: Props) {
return (
<div
style={props.style}
className={classNames(styles.card, props.className)}
onClick={props.onClick}
data-testid={props.testId}
>
{props.children}
</div>
);
}

View File

@@ -0,0 +1,5 @@
.link {
font-family: 'Inter', sans-serif;
text-decoration: underline;
cursor: pointer;
}

View File

@@ -0,0 +1,25 @@
import classNames from 'classnames';
import React, { PropsWithChildren } from 'react';
import { bMessenger } from 'src/shared/messages';
import Text, { TextProps } from '../Text/Text';
import styles from './Link.module.scss';
type Props = TextProps & {
url?: string;
};
/**
* A reusable Text component with props that build on top of the design system for the extension
*/
export default function Link(props: PropsWithChildren<Props>) {
let passedProps = {
...props,
};
const { url } = props;
if (url && !props.onClick) {
passedProps.onClick = () => bMessenger.openNewTab({ url });
}
return <Text {...passedProps} className={classNames(styles.link, props.className)} />;
}

View File

@@ -17,16 +17,12 @@ export default function Popup(props: PropsWithChildren<Props>) {
return ( return (
<div <div
style={props.style} style={props.style}
className={classNames( className={classNames(styles.container, {
styles.container,
{
[styles.overlay]: props.overlay, [styles.overlay]: props.overlay,
}, })}
props.className
)}
data-testid={props.testId} data-testid={props.testId}
> >
<div className={styles.body}>{props.children}</div> <div className={classNames(styles.body, props.className)}>{props.children}</div>
</div> </div>
); );
} }

View File

@@ -4,9 +4,9 @@ import colors, { ISassColors } from 'src/views/styles/colors.module.scss';
import fonts, { Size, Weight } from 'src/views/styles/fonts.module.scss'; import fonts, { Size, Weight } from 'src/views/styles/fonts.module.scss';
import styles from './Text.module.scss'; import styles from './Text.module.scss';
type Props = { export type TextProps = {
color?: keyof ISassColors; color?: keyof ISassColors;
weight: Weight; weight?: Weight;
size: Size; size: Size;
span?: boolean; span?: boolean;
className?: string; className?: string;
@@ -18,7 +18,7 @@ type Props = {
/** /**
* 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(props: PropsWithChildren<Props>) { export default function Text(props: PropsWithChildren<TextProps>) {
const style = props.style || {}; const style = props.style || {};
style.textAlign ??= props.align; style.textAlign ??= props.align;

View File

@@ -1,35 +1,30 @@
.popupBody { .popup {
border-radius: 12px;
position: relative;
.close {
position: absolute;
top: 12px;
right: 12px;
cursor: pointer;
}
.body {
height: auto; height: auto;
color: white; color: white;
padding: 10px; padding: 12px;
margin: 40px;
align-items: center;
justify-content: center;
.title {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
}
.coursePopupBase { .uniqueId {
position: fixed; margin-left: 8px;
transform: translateY(-50%); }
top: 15px; }
right: 15px;
z-index: 2147483647;
}
.coursePopupHeader {
display: flex;
height: 50;
background-color: #29465b;
width: 100%;
.closePopupButton {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin: 5px;
margin-left: auto;
cursor: pointer;
color: white;
} }
} }

View File

@@ -1,7 +1,10 @@
import React from 'react'; import React from 'react';
import { Course } from 'src/shared/types/Course'; import { Course } from 'src/shared/types/Course';
import Card from '../../common/Card/Card';
import Icon from '../../common/Icon/Icon'; import Icon from '../../common/Icon/Icon';
import Link from '../../common/Link/Link';
import Popup from '../../common/Popup/Popup'; import Popup from '../../common/Popup/Popup';
import Text from '../../common/Text/Text';
import styles from './CoursePopup.module.scss'; import styles from './CoursePopup.module.scss';
interface Props { interface Props {
@@ -14,11 +17,24 @@ interface Props {
*/ */
export default function CoursePopup({ course, onClose }: Props) { export default function CoursePopup({ course, onClose }: Props) {
return ( return (
<Popup overlay> <Popup className={styles.popup} overlay>
<div className={styles.popupBody}> <Icon className={styles.close} size='large' name='close' onClick={onClose} />
<div className={styles.courseTitle}>{course.courseName}</div> <Card className={styles.body}>
<div className={styles.courseDescription}>{course.uniqueId}</div> <Text className={styles.title} size='large' weight='bold' color='black'>
</div> {course.courseName} ({course.department} {course.number})
<Link
span
url={course.url}
className={styles.uniqueId}
size='medium'
weight='semi_bold'
color='burnt_orange'
>
#{course.uniqueId}
</Link>
</Text>
</Card>
</Popup> </Popup>
); );
} }

View File

@@ -159,12 +159,12 @@ export class CourseCatalogScraper {
.map(name => name.trim()) .map(name => name.trim())
.filter(Boolean); .filter(Boolean);
return names.map(name => { return names.map(fullName => {
const [lastName, rest] = name.split(',').map(s => s.trim()); const [lastName, rest] = fullName.split(',').map(s => s.trim());
const [firstName, middleInitial] = rest.split(' '); const [firstName, middleInitial] = rest.split(' ');
return { return {
name, fullName,
firstName, firstName,
lastName, lastName,
middleInitial, middleInitial,