From 000682b4db82159f632bea9158abd66d37ad7b26 Mon Sep 17 00:00:00 2001 From: Samuel Gunter Date: Sun, 11 Feb 2024 23:24:24 -0600 Subject: [PATCH] feat: buttons with icons in tailwind i am not proud of some of this code --- src/shared/util/themeColors.ts | 60 +++++++++ src/stories/components/Button.stories.tsx | 127 ++++++++++++------ src/views/components/common/Button/Button.tsx | 59 ++++---- .../ExtensionRoot/ExtensionRoot.module.scss | 2 +- .../common/ExtensionRoot/ExtensionRoot.tsx | 1 - unocss.config.ts | 34 +++-- 6 files changed, 195 insertions(+), 88 deletions(-) create mode 100644 src/shared/util/themeColors.ts 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 b095168e..89c6ac94 100644 --- a/src/stories/components/Button.stories.tsx +++ b/src/stories/components/Button.stories.tsx @@ -1,7 +1,14 @@ 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 = { @@ -16,6 +23,7 @@ const meta = { // More on argTypes: https://storybook.js.org/docs/api/argtypes args: { children: 'Button', + icon: ImagePlaceholderIcon, }, argTypes: { children: { control: 'text' }, @@ -27,71 +35,79 @@ 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: { + variant: 'filled', + color: 'ut-black', disabled: true, }, }; export const Grid: Story = { render: props => ( -
-
-
-
-

-
-
-
-
), }; -export const Icons: Story = { - render: props => ( -
-
- ), -}; - -// TODO: Actually show the buttons as they appear in the design -export const CourseButtons: Story = { +export const PrettyColors: Story = { args: { - children: 'Add Course', + children: '', }, + render: props => { + const colorsNames = Object.keys(colorsFlattened) as (keyof typeof colorsFlattened)[]; + + return ( +
+ {colorsNames.map(color => ( +
+ + + +
+ ))} +
+ ); + }, +}; + +export const CourseButtons: Story = { render: props => ( -
-
-
-
-
+
+ +
), parameters: { @@ -101,3 +117,26 @@ export const CourseButtons: Story = { }, }, }; + +export const CourseCatalogActionButtons: Story = { + args: { + children: '', + }, + render: props => ( +
+ + + + +
+ ), +}; diff --git a/src/views/components/common/Button/Button.tsx b/src/views/components/common/Button/Button.tsx index f52119b8..498fef16 100644 --- a/src/views/components/common/Button/Button.tsx +++ b/src/views/components/common/Button/Button.tsx @@ -1,22 +1,20 @@ 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'; + variant: 'filled' | 'outline' | 'single'; onClick?: () => void; - icon?: React.ReactNode; + icon?: typeof IconComponent; disabled?: boolean; title?: string; - testId?: string; - useScss?: boolean; + color: ThemeColor; } -const BUTTON_BASE_CLASS = - 'm-2.5 h-10 w-auto flex cursor-pointer content-center items-center gap-2 rounded-1 px-4 py-0 text-4.5 font-500 leading-normal font-sans btn-transition'; - /** * A reusable button component that follows the design system of the extension. * @returns @@ -29,34 +27,49 @@ export function Button({ icon, disabled, title, - testId, + color, children, - useScss = false, }: 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/common/ExtensionRoot/ExtensionRoot.tsx b/src/views/components/common/ExtensionRoot/ExtensionRoot.tsx index d2a4c533..bb2fd081 100644 --- a/src/views/components/common/ExtensionRoot/ExtensionRoot.tsx +++ b/src/views/components/common/ExtensionRoot/ExtensionRoot.tsx @@ -3,7 +3,6 @@ import styles from './ExtensionRoot.module.scss'; import '@unocss/reset/tailwind-compat.css'; import 'uno.css'; -import '@unocss/reset/tailwind-compat.css'; interface Props { testId?: string; 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: [