chore: merge branch 'main' into sgunter/button-component
This commit is contained in:
@@ -18,7 +18,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"@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",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
@@ -56,6 +55,7 @@
|
|||||||
"@unocss/postcss": "^0.58.4",
|
"@unocss/postcss": "^0.58.4",
|
||||||
"@unocss/preset-uno": "^0.58.4",
|
"@unocss/preset-uno": "^0.58.4",
|
||||||
"@unocss/preset-web-fonts": "^0.58.4",
|
"@unocss/preset-web-fonts": "^0.58.4",
|
||||||
|
"@unocss/reset": "^0.58.5",
|
||||||
"@unocss/transformer-directives": "^0.58.4",
|
"@unocss/transformer-directives": "^0.58.4",
|
||||||
"@unocss/transformer-variant-group": "^0.58.4",
|
"@unocss/transformer-variant-group": "^0.58.4",
|
||||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -16,9 +16,6 @@ dependencies:
|
|||||||
'@types/sql.js':
|
'@types/sql.js':
|
||||||
specifier: ^1.4.9
|
specifier: ^1.4.9
|
||||||
version: 1.4.9
|
version: 1.4.9
|
||||||
'@unocss/reset':
|
|
||||||
specifier: ^0.58.5
|
|
||||||
version: 0.58.5
|
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1(vite@5.0.12)
|
version: 4.2.1(vite@5.0.12)
|
||||||
@@ -126,6 +123,9 @@ devDependencies:
|
|||||||
'@unocss/preset-web-fonts':
|
'@unocss/preset-web-fonts':
|
||||||
specifier: ^0.58.4
|
specifier: ^0.58.4
|
||||||
version: 0.58.4
|
version: 0.58.4
|
||||||
|
'@unocss/reset':
|
||||||
|
specifier: ^0.58.5
|
||||||
|
version: 0.58.5
|
||||||
'@unocss/transformer-directives':
|
'@unocss/transformer-directives':
|
||||||
specifier: ^0.58.4
|
specifier: ^0.58.4
|
||||||
version: 0.58.4
|
version: 0.58.4
|
||||||
@@ -5051,7 +5051,7 @@ packages:
|
|||||||
|
|
||||||
/@unocss/reset@0.58.5:
|
/@unocss/reset@0.58.5:
|
||||||
resolution: {integrity: sha512-2wMrkCj3SSb5hrx9TKs5jZa34QIRkHv9FotbJutAPo7o8hx+XXn56ogzdoUrcFPJZJUx2R2nyOVbSlGMIjtFtw==}
|
resolution: {integrity: sha512-2wMrkCj3SSb5hrx9TKs5jZa34QIRkHv9FotbJutAPo7o8hx+XXn56ogzdoUrcFPJZJUx2R2nyOVbSlGMIjtFtw==}
|
||||||
dev: false
|
dev: true
|
||||||
|
|
||||||
/@unocss/rule-utils@0.58.4:
|
/@unocss/rule-utils@0.58.4:
|
||||||
resolution: {integrity: sha512-52Jp4I+joGTaDm7ehB/7uZ2kJL+9BZcYRDUVk4IDacDH5W9yxf1F75LzYT8jJVWXD/HIhiS0r9V6qhcBq2OWZw==}
|
resolution: {integrity: sha512-52Jp4I+joGTaDm7ehB/7uZ2kJL+9BZcYRDUVk4IDacDH5W9yxf1F75LzYT8jJVWXD/HIhiS0r9V6qhcBq2OWZw==}
|
||||||
|
|||||||
50
src/shared/util/colors.ts
Normal file
50
src/shared/util/colors.ts
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
118
src/stories/components/PopupCourseBlock.stories.tsx
Normal file
118
src/stories/components/PopupCourseBlock.stories.tsx
Normal file
@@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styles from './ExtensionRoot.module.scss';
|
import styles from './ExtensionRoot.module.scss';
|
||||||
|
|
||||||
|
import '@unocss/reset/tailwind-compat.css';
|
||||||
import 'uno.css';
|
import 'uno.css';
|
||||||
import '@unocss/reset/tailwind-compat.css';
|
import '@unocss/reset/tailwind-compat.css';
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,65 +1,67 @@
|
|||||||
@use 'src/views/styles/colors.module.scss';
|
@use 'src/views/styles/colors.module.scss';
|
||||||
@use 'src/views/styles/fonts.module.scss';
|
@use 'src/views/styles/fonts.module.scss';
|
||||||
|
|
||||||
.text {
|
@layer theme {
|
||||||
|
.text {
|
||||||
font-family: 'Roboto Flex', sans-serif;
|
font-family: 'Roboto Flex', sans-serif;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini {
|
.mini {
|
||||||
font-size: 0.79rem;
|
font-size: 0.79rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
font-size: 0.88875rem;
|
font-size: 0.88875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p {
|
.p {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: 0.025rem;
|
letter-spacing: 0.025rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h4 {
|
.h4 {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h3-course {
|
.h3-course {
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 100%; /* 0.6875rem */
|
line-height: 100%; /* 0.6875rem */
|
||||||
}
|
}
|
||||||
|
|
||||||
.h3 {
|
.h3 {
|
||||||
font-size: 1.26563rem;
|
font-size: 1.26563rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h2-course {
|
.h2-course {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 0.03125rem;
|
letter-spacing: 0.03125rem;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h2 {
|
.h2 {
|
||||||
font-size: 1.42375rem;
|
font-size: 1.42375rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h1-course {
|
.h1-course {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h1 {
|
.h1 {
|
||||||
font-size: 1.60188rem;
|
font-size: 1.60188rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user