Link component, Card component, Course Popup component styling, and wrangling with the serialization type"
This commit is contained in:
@@ -6,7 +6,7 @@ import { CourseSchedule } from './CourseSchedule';
|
||||
* Also includes a link to their RateMyProfessor page
|
||||
*/
|
||||
export type Instructor = {
|
||||
name: string;
|
||||
fullName: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
middleInitial?: string;
|
||||
@@ -75,6 +75,32 @@ export class Course {
|
||||
constructor(course: Course | Serialized<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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
8
src/views/components/common/Card/Card.module.scss
Normal file
8
src/views/components/common/Card/Card.module.scss
Normal 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;
|
||||
}
|
||||
27
src/views/components/common/Card/Card.tsx
Normal file
27
src/views/components/common/Card/Card.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
5
src/views/components/common/Link/Link.module.scss
Normal file
5
src/views/components/common/Link/Link.module.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
.link {
|
||||
font-family: 'Inter', sans-serif;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
25
src/views/components/common/Link/Link.tsx
Normal file
25
src/views/components/common/Link/Link.tsx
Normal 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)} />;
|
||||
}
|
||||
@@ -17,16 +17,12 @@ export default function Popup(props: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div
|
||||
style={props.style}
|
||||
className={classNames(
|
||||
styles.container,
|
||||
{
|
||||
[styles.overlay]: props.overlay,
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
className={classNames(styles.container, {
|
||||
[styles.overlay]: props.overlay,
|
||||
})}
|
||||
data-testid={props.testId}
|
||||
>
|
||||
<div className={styles.body}>{props.children}</div>
|
||||
<div className={classNames(styles.body, props.className)}>{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 styles from './Text.module.scss';
|
||||
|
||||
type Props = {
|
||||
export type TextProps = {
|
||||
color?: keyof ISassColors;
|
||||
weight: Weight;
|
||||
weight?: Weight;
|
||||
size: Size;
|
||||
span?: boolean;
|
||||
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
|
||||
*/
|
||||
export default function Text(props: PropsWithChildren<Props>) {
|
||||
export default function Text(props: PropsWithChildren<TextProps>) {
|
||||
const style = props.style || {};
|
||||
|
||||
style.textAlign ??= props.align;
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
.popupBody {
|
||||
height: auto;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.popup {
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
|
||||
.coursePopupBase {
|
||||
position: fixed;
|
||||
transform: translateY(-50%);
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 2147483647;
|
||||
}
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.coursePopupHeader {
|
||||
display: flex;
|
||||
height: 50;
|
||||
background-color: #29465b;
|
||||
width: 100%;
|
||||
|
||||
.closePopupButton {
|
||||
display: flex;
|
||||
.body {
|
||||
height: auto;
|
||||
color: white;
|
||||
padding: 12px;
|
||||
margin: 40px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.uniqueId {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Course } from 'src/shared/types/Course';
|
||||
import Card from '../../common/Card/Card';
|
||||
import Icon from '../../common/Icon/Icon';
|
||||
import Link from '../../common/Link/Link';
|
||||
import Popup from '../../common/Popup/Popup';
|
||||
import Text from '../../common/Text/Text';
|
||||
import styles from './CoursePopup.module.scss';
|
||||
|
||||
interface Props {
|
||||
@@ -14,11 +17,24 @@ interface Props {
|
||||
*/
|
||||
export default function CoursePopup({ course, onClose }: Props) {
|
||||
return (
|
||||
<Popup overlay>
|
||||
<div className={styles.popupBody}>
|
||||
<div className={styles.courseTitle}>{course.courseName}</div>
|
||||
<div className={styles.courseDescription}>{course.uniqueId}</div>
|
||||
</div>
|
||||
<Popup className={styles.popup} overlay>
|
||||
<Icon className={styles.close} size='large' name='close' onClick={onClose} />
|
||||
<Card className={styles.body}>
|
||||
<Text className={styles.title} size='large' weight='bold' color='black'>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,12 +159,12 @@ export class CourseCatalogScraper {
|
||||
.map(name => name.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return names.map(name => {
|
||||
const [lastName, rest] = name.split(',').map(s => s.trim());
|
||||
return names.map(fullName => {
|
||||
const [lastName, rest] = fullName.split(',').map(s => s.trim());
|
||||
const [firstName, middleInitial] = rest.split(' ');
|
||||
|
||||
return {
|
||||
name,
|
||||
fullName,
|
||||
firstName,
|
||||
lastName,
|
||||
middleInitial,
|
||||
|
||||
Reference in New Issue
Block a user