forked from Longhorn-Developers/UT-Registration-Plus
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: PopupCourseBlock Component (Longhorn-Developers#79)
Co-authored-by: Razboy20 <[email protected]> Co-authored-by: Razboy20 <[email protected]>
- Loading branch information
1 parent
dd2f696
commit f045b40
Showing
7 changed files
with
291 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { theme } from 'unocss/preset-mini'; | ||
|
||
export interface CourseColors { | ||
primaryColor: string; | ||
secondaryColor: string; | ||
} | ||
|
||
// calculates luminance of a hex string | ||
function getLuminance(hex: string): number { | ||
let r = parseInt(hex.substring(1, 3), 16); | ||
let g = parseInt(hex.substring(3, 5), 16); | ||
let b = parseInt(hex.substring(5, 7), 16); | ||
|
||
[r, g, b] = [r, g, b].map(color => { | ||
let c = color / 255; | ||
|
||
c = c > 0.03928 ? ((c + 0.055) / 1.055) ** 2.4 : (c /= 12.92); | ||
|
||
return c; | ||
}); | ||
|
||
return 0.2126 * r + 0.7152 * g + 0.0722 * b; | ||
} | ||
|
||
// calculates contrast ratio between two hex strings | ||
function contrastRatioPair(hex1: string, hex2: string) { | ||
const lum1 = getLuminance(hex1); | ||
const lum2 = getLuminance(hex2); | ||
|
||
return (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05); | ||
} | ||
|
||
/** | ||
* Generate a tailwind classname for the font color based on the background color | ||
* @param bgColor the tailwind classname for background ex. "bg-emerald-500" | ||
*/ | ||
export function pickFontColor(bgColor: string): 'text-white' | 'text-black' { | ||
return contrastRatioPair(bgColor, '#606060') > contrastRatioPair(bgColor, '#ffffff') ? 'text-black' : 'text-white'; | ||
} | ||
|
||
/** | ||
* Get primary and secondary colors from a tailwind colorway | ||
* @param colorway the tailwind colorway ex. "emerald" | ||
*/ | ||
export function getCourseColors(colorway: keyof typeof theme.colors): CourseColors { | ||
return { | ||
primaryColor: theme.colors[colorway][600] as string, | ||
secondaryColor: theme.colors[colorway][800] as string, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import React from 'react'; | ||
import { Course, Status } from 'src/shared/types/Course'; | ||
import { CourseMeeting } from 'src/shared/types/CourseMeeting'; | ||
import Instructor from 'src/shared/types/Instructor'; | ||
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock'; | ||
import { getCourseColors } from 'src/shared/util/colors'; | ||
import { theme } from 'unocss/preset-mini'; | ||
|
||
const exampleCourse: Course = new Course({ | ||
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', | ||
creditHours: 3, | ||
department: 'C S', | ||
description: [ | ||
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.', | ||
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.', | ||
'May be counted toward the Quantitative Reasoning flag requirement.', | ||
'Designed to accommodate 100 or more students.', | ||
'Taught as a Web-based course.', | ||
], | ||
flags: ['Quantitative Reasoning'], | ||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB', | ||
instructionMode: 'Online', | ||
instructors: [ | ||
new Instructor({ | ||
firstName: 'Bevo', | ||
lastName: 'Bevo', | ||
fullName: 'Bevo Bevo', | ||
}), | ||
], | ||
isReserved: false, | ||
number: '303E', | ||
schedule: { | ||
meetings: [ | ||
new CourseMeeting({ | ||
days: ['Tuesday', 'Thursday'], | ||
endTime: 660, | ||
startTime: 570, | ||
}), | ||
], | ||
}, | ||
semester: { | ||
code: '12345', | ||
season: 'Spring', | ||
year: 2024, | ||
}, | ||
status: Status.WAITLISTED, | ||
uniqueId: 12345, | ||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', | ||
}); | ||
|
||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export | ||
const meta = { | ||
title: 'Components/Common/PopupCourseBlock', | ||
component: PopupCourseBlock, | ||
parameters: { | ||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout | ||
layout: 'centered', | ||
}, | ||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs | ||
tags: ['autodocs'], | ||
// More on argTypes: https://storybook.js.org/docs/api/argtypes | ||
args: { | ||
colors: getCourseColors('emerald'), | ||
course: exampleCourse, | ||
}, | ||
argTypes: { | ||
colors: { | ||
description: 'the colors to use for the course block', | ||
control: 'object', | ||
}, | ||
course: { | ||
description: 'the course to show data for', | ||
control: 'object', | ||
}, | ||
}, | ||
} satisfies Meta<typeof PopupCourseBlock>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args | ||
export const Default: Story = { | ||
args: {}, | ||
}; | ||
|
||
export const Variants: Story = { | ||
render: props => ( | ||
<div className='grid grid-cols-2 max-w-2xl w-90vw gap-x-4 gap-y-2'> | ||
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.OPEN })} /> | ||
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CLOSED })} /> | ||
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.WAITLISTED })} /> | ||
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CANCELLED })} /> | ||
</div> | ||
), | ||
}; | ||
|
||
const colors = Object.keys(theme.colors) | ||
// check that the color is a colorway (is an object) | ||
.filter(color => typeof theme.colors[color] === 'object') | ||
.slice(0, 17) | ||
.map(color => getCourseColors(color as keyof typeof theme.colors)); | ||
|
||
export const AllColors: Story = { | ||
render: props => ( | ||
<div className='grid grid-rows-9 grid-cols-2 grid-flow-col max-w-2xl w-90vw gap-x-4 gap-y-2'> | ||
{colors.map((color, i) => ( | ||
<PopupCourseBlock key={color.primaryColor} course={exampleCourse} colors={color} /> | ||
))} | ||
</div> | ||
), | ||
parameters: { | ||
design: { | ||
type: 'figma', | ||
url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=1046-6714&mode=design&t=5Bjr7qGHNXmjfMTc-0', | ||
}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
src/views/components/common/PopupCourseBlock/PopupCourseBlock.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import clsx from 'clsx'; | ||
import React, { useState } from 'react'; | ||
import { Course, Status } from '@shared/types/Course'; | ||
import { StatusIcon } from '@shared/util/icons'; | ||
import { CourseColors, getCourseColors, pickFontColor } from '@shared/util/colors'; | ||
import DragIndicatorIcon from '~icons/material-symbols/drag-indicator'; | ||
import Text from '../Text/Text'; | ||
|
||
/** | ||
* Props for PopupCourseBlock | ||
*/ | ||
export interface PopupCourseBlockProps { | ||
className?: string; | ||
course: Course; | ||
colors: CourseColors; | ||
} | ||
|
||
/** | ||
* The "course block" to be used in the extension popup. | ||
* | ||
* @param props PopupCourseBlockProps | ||
*/ | ||
export default function PopupCourseBlock({ className, course, colors }: PopupCourseBlockProps): JSX.Element { | ||
// whiteText based on secondaryColor | ||
const fontColor = pickFontColor(colors.primaryColor); | ||
|
||
return ( | ||
<div | ||
style={{ | ||
backgroundColor: colors.primaryColor, | ||
}} | ||
className={clsx('h-full w-full inline-flex items-center justify-center gap-1 rounded pr-3', className)} | ||
> | ||
<div | ||
style={{ | ||
backgroundColor: colors.secondaryColor, | ||
}} | ||
className='flex cursor-move items-center self-stretch rounded rounded-r-0' | ||
> | ||
<DragIndicatorIcon className='h-6 w-6 text-white' /> | ||
</div> | ||
<Text | ||
className={clsx('flex-1 py-3.5 text-ellipsis whitespace-nowrap overflow-hidden', fontColor)} | ||
variant='h1-course' | ||
> | ||
<span className='px-0.5 font-450'>{course.uniqueId}</span> {course.department} {course.number} –{' '} | ||
{course.instructors.length === 0 ? 'Unknown' : course.instructors.map(v => v.lastName)} | ||
</Text> | ||
{course.status !== Status.OPEN && ( | ||
<div | ||
style={{ | ||
backgroundColor: colors.secondaryColor, | ||
}} | ||
className='ml-1 flex items-center justify-center justify-self-end rounded p-1px text-white' | ||
> | ||
<StatusIcon status={course.status} className='h-5 w-5' /> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,67 @@ | ||
@use 'src/views/styles/colors.module.scss'; | ||
@use 'src/views/styles/fonts.module.scss'; | ||
|
||
.text { | ||
font-family: 'Roboto Flex', sans-serif; | ||
line-height: normal; | ||
font-style: normal; | ||
} | ||
@layer theme { | ||
.text { | ||
font-family: 'Roboto Flex', sans-serif; | ||
line-height: normal; | ||
font-style: normal; | ||
} | ||
|
||
.mini { | ||
font-size: 0.79rem; | ||
font-weight: 500; | ||
} | ||
.mini { | ||
font-size: 0.79rem; | ||
font-weight: 500; | ||
} | ||
|
||
.small { | ||
font-size: 0.88875rem; | ||
font-weight: 500; | ||
} | ||
.small { | ||
font-size: 0.88875rem; | ||
font-weight: 500; | ||
} | ||
|
||
.p { | ||
font-size: 1rem; | ||
font-weight: 400; | ||
letter-spacing: 0.025rem; | ||
} | ||
.p { | ||
font-size: 1rem; | ||
font-weight: 400; | ||
letter-spacing: 0.025rem; | ||
} | ||
|
||
.h4 { | ||
font-size: 1.125rem; | ||
font-weight: 500; | ||
} | ||
.h4 { | ||
font-size: 1.125rem; | ||
font-weight: 500; | ||
} | ||
|
||
.h3-course { | ||
font-size: 0.6875rem; | ||
font-weight: 400; | ||
line-height: 100%; /* 0.6875rem */ | ||
} | ||
.h3-course { | ||
font-size: 0.6875rem; | ||
font-weight: 400; | ||
line-height: 100%; /* 0.6875rem */ | ||
} | ||
|
||
.h3 { | ||
font-size: 1.26563rem; | ||
font-weight: 600; | ||
text-transform: uppercase; | ||
} | ||
.h3 { | ||
font-size: 1.26563rem; | ||
font-weight: 600; | ||
text-transform: uppercase; | ||
} | ||
|
||
.h2-course { | ||
font-size: 1rem; | ||
font-weight: 500; | ||
letter-spacing: 0.03125rem; | ||
text-transform: capitalize; | ||
} | ||
.h2-course { | ||
font-size: 1rem; | ||
font-weight: 500; | ||
letter-spacing: 0.03125rem; | ||
text-transform: capitalize; | ||
} | ||
|
||
.h2 { | ||
font-size: 1.42375rem; | ||
font-weight: 500; | ||
} | ||
.h2 { | ||
font-size: 1.42375rem; | ||
font-weight: 500; | ||
} | ||
|
||
.h1-course { | ||
font-size: 1rem; | ||
font-weight: 600; | ||
text-transform: capitalize; | ||
} | ||
.h1-course { | ||
font-size: 1rem; | ||
font-weight: 600; | ||
text-transform: capitalize; | ||
} | ||
|
||
.h1 { | ||
font-size: 1.60188rem; | ||
font-weight: 700; | ||
text-transform: uppercase; | ||
.h1 { | ||
font-size: 1.60188rem; | ||
font-weight: 700; | ||
text-transform: uppercase; | ||
} | ||
} |