diff --git a/src/shared/util/themeColors.ts b/src/shared/util/themeColors.ts new file mode 100644 index 00000000..3e939c57 --- /dev/null +++ b/src/shared/util/themeColors.ts @@ -0,0 +1,60 @@ +export const colors = { + ut: { + 'burnt-orange': '#BF5700', + black: '#333F48', + orange: '#f8971f', + yellow: '#ffd600', + 'light-green': '#a6cd57', + green: '#579d42', + teal: '#00a9b7', + blue: '#005f86', + gray: '#9cadb7', + 'off-white': '#d6d2c4', + concrete: '#95a5a6', + }, + theme: { + red: '#af2e2d', + black: '#1a2024', + }, +} as const; + +type NestedKeys = { + [K in keyof T]: T[K] extends Record ? `${string & K}-${string & keyof T[K]}` : never; +}[keyof T]; + +/** + * A union of all colors in the theme + */ +export type ThemeColor = NestedKeys; + +export const colorsFlattened = Object.entries(colors).reduce( + (acc, [prefix, group]) => { + for (const [name, hex] of Object.entries(group)) { + acc[`${prefix}-${name}`] = hex; + } + return acc; + }, + {} as Record +); + +const hexToRgb = (hex: string) => + hex.match(/[0-9a-f]{2}/gi).map(partialHex => parseInt(partialHex, 16)) as [number, number, number]; + +const colorsFlattenedRgb = Object.fromEntries( + Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRgb(hex)]) +) as Record>; + +/** + * Retrieves the hexadecimal color value by name from the theme. + * + * @param name - The name of the theme color. + * @returns The hexadecimal color value. + */ +export const getThemeColorHexByName = (name: ThemeColor): string => colorsFlattened[name]; + +/** + * + * @param name - The name of the theme color. + * @returns An array of the red, green, and blue values, respectively + */ +export const getThemeColorRgbByName = (name: ThemeColor) => colorsFlattenedRgb[name]; diff --git a/src/stories/components/Button.stories.tsx b/src/stories/components/Button.stories.tsx index d49daa38..f6de21a2 100644 --- a/src/stories/components/Button.stories.tsx +++ b/src/stories/components/Button.stories.tsx @@ -1,24 +1,33 @@ import { Button } from 'src/views/components/common/Button/Button'; import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; +import { colorsFlattened } from 'src/shared/util/themeColors'; +import ImagePlaceholderIcon from '~icons/material-symbols/image'; +import AddIcon from '~icons/material-symbols/add'; +import RemoveIcon from '~icons/material-symbols/remove'; +import CalendarMonthIcon from '~icons/material-symbols/calendar-month'; +import ReviewsIcon from '~icons/material-symbols/reviews'; +import HappyFaceIcon from '~icons/material-symbols/mood'; +import DescriptionIcon from '~icons/material-symbols/description'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export const meta = { - title: 'Components/Common/Button', - component: Button, - 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: { - children: 'Button', - }, - argTypes: { - children: { control: 'text' }, - }, + title: 'Components/Common/Button', + component: Button, + 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: { + children: 'Button', + icon: ImagePlaceholderIcon, + }, + argTypes: { + children: { control: 'text' }, + }, } satisfies Meta; export default meta; @@ -26,72 +35,112 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args export const Default: Story = { - args: {}, + args: { + variant: 'filled', + color: 'ut-black', + }, }; export const Disabled: Story = { - args: { - disabled: true, - }, -}; - -export const Grid: Story = { - render: props => ( -
-
-
-
-
-
- ), -}; - - -// TODO: Actually show the buttons as they appear in the design -export const CourseButtons: Story = { - args: { - children: 'Add Course', - }, - render: props => ( -
-
-
-
-
-
- ), - parameters: { - design: { - type: 'figma', - url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=324-389&mode=design&t=BoS5xBrpSsjgQXqv-4', + args: { + variant: 'filled', + color: 'ut-black', + disabled: true, }, - }, +}; + +// @ts-ignore +export const Grid: Story = { + render: props => ( +
+
+
+ +
+ +
+
+
+ ), +}; + +export const PrettyColors: Story = { + // @ts-ignore + args: { + children: '', + }, + render: props => { + const colorsNames = Object.keys(colorsFlattened) as (keyof typeof colorsFlattened)[]; + + return ( +
+ {colorsNames.map(color => ( +
+ + + +
+ ))} +
+ ); + }, +}; + +// @ts-ignore +export const CourseButtons: Story = { + render: props => ( +
+ + +
+ ), + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=324-389&mode=design&t=BoS5xBrpSsjgQXqv-4', + }, + }, +}; + +export const CourseCatalogActionButtons: Story = { + // @ts-ignore + args: { + children: '', + }, + render: props => ( +
+ + + + +
+ ), }; diff --git a/src/views/components/common/Button/Button.module.scss b/src/views/components/common/Button/Button.module.scss deleted file mode 100644 index 84b39c9a..00000000 --- a/src/views/components/common/Button/Button.module.scss +++ /dev/null @@ -1,67 +0,0 @@ -@use 'sass:color'; -@use 'src/views/styles/colors.module.scss'; - -.button { - background-color: #000; - color: #fff; - padding: 10px; - margin: 10px; - border-radius: 8px; - border: none; - cursor: pointer; - transition: all 0.1s ease-in-out; - font-family: 'Inter'; - - display: flex; - align-items: center; - justify-content: center; - - &:hover { - background-color: #fff; - color: #000; - } - - &:active { - transform: scale(0.96); - } - - &.disabled { - cursor: not-allowed !important; - opacity: 0.5 !important; - - &:active { - transform: unset; - } - } - - @each $color, - $value - in ( - primary: colors.$burnt_orange, - secondary: colors.$charcoal, - tertiary: colors.$bluebonnet, - danger: colors.$speedway_brick, - warning: colors.$tangerine, - success: colors.$turtle_pond, - info: colors.$turquoise - ) - { - &.#{$color} { - background-color: $value; - color: #fff; - - &:hover { - background-color: color.adjust($value, $lightness: 10%); - } - - &:focus-visible, - &:active { - background-color: color.adjust($value, $lightness: -10%); - } - - &.disabled { - background-color: $value !important; - } - } - } -} diff --git a/src/views/components/common/Button/Button.tsx b/src/views/components/common/Button/Button.tsx index 98d1e4ef..d2be0bd3 100644 --- a/src/views/components/common/Button/Button.tsx +++ b/src/views/components/common/Button/Button.tsx @@ -1,15 +1,18 @@ import clsx from 'clsx'; import React from 'react'; -import styles from './Button.module.scss'; +import { ThemeColor, getThemeColorHexByName, getThemeColorRgbByName } from '../../../../shared/util/themeColors'; +import type IconComponent from '~icons/material-symbols'; +import Text from '../Text/Text'; interface Props { className?: string; style?: React.CSSProperties; + variant: 'filled' | 'outline' | 'single'; onClick?: () => void; - type?: 'primary' | 'secondary' | 'tertiary' | 'danger' | 'warning' | 'success' | 'info'; + icon?: typeof IconComponent; disabled?: boolean; title?: string; - testId?: string; + color: ThemeColor; } /** @@ -17,26 +20,57 @@ interface Props { * @returns */ export function Button({ - style, className, - type, - testId, - children, + style, + variant, + onClick, + icon, disabled, title, - onClick, + color, + children, }: React.PropsWithChildren): JSX.Element { + const Icon = icon; + const isIconOnly = !children && !!icon; + const colorHex = getThemeColorHexByName(color); + const colorRgb = getThemeColorRgbByName(color).join(' '); + return ( ); } diff --git a/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss b/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss index 78e9eef2..bc3136b0 100644 --- a/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss +++ b/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss @@ -1,7 +1,7 @@ @import 'src/views/styles/base.module.scss'; .extensionRoot { - font-family: 'Inter' !important; + @apply font-sans; color: #303030; -webkit-box-sizing: border-box; box-sizing: border-box; diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx index 5f83b836..87a8fc6a 100644 --- a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx +++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx @@ -83,7 +83,7 @@ export default function CourseButtons({ course, activeSchedule }: Props) { {conflicts.length > 0 && ( diff --git a/unocss.config.ts b/unocss.config.ts index a5cadfc9..605cad07 100644 --- a/unocss.config.ts +++ b/unocss.config.ts @@ -3,10 +3,23 @@ import presetWebFonts from '@unocss/preset-web-fonts'; import transformerDirectives from '@unocss/transformer-directives'; import transformerVariantGroup from '@unocss/transformer-variant-group'; import { defineConfig } from 'unocss'; +import { colors } from './src/shared/util/themeColors'; export default defineConfig({ rules: [ ['btn-transition', { transition: 'color 180ms, border-color 150ms, background-color 150ms, transform 50ms' }], + [ + 'btn-shadow', + { + 'box-shadow': '0px 1px 3px 1px var(--shadow-color-15), 0px 1px 2px 0px var(--shadow-color-30);', + }, + ], + [ + 'btn-shade', + { + 'background-color': 'var(--bg-color-8)', + }, + ], [ 'ring-offset-0', { @@ -16,31 +29,14 @@ export default defineConfig({ ], shortcuts: { focusable: 'outline-none ring-blue-500/50 dark:ring-blue-400/60 ring-0 focus-visible:ring-4', + btn: 'h-10 w-auto flex cursor-pointer justify-center items-center gap-2 rounded-1 px-4 py-0 text-4.5 btn-transition', }, theme: { easing: { 'in-out-expo': 'cubic-bezier(.46, 0, .21, 1)', 'out-expo': 'cubic-bezier(0.19, 1, 0.22, 1)', }, - colors: { - ut: { - 'burnt-orange': '#BF5700', - black: '#333F48', - orange: '#f8971f', - yellow: '#ffd600', - 'light-green': '#a6cd57', - green: '#579d42', - teal: '#00a9b7', - blue: '#005f86', - gray: '#9cadb7', - 'off-white': '#d6d2c4', - concrete: '#95a5a6', - }, - theme: { - red: '#af2e2d', - black: '#1a2024', - }, - }, + colors, }, presets: [