From b778c28ed70846bf564898c8f10fa7ddbd00fd07 Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Wed, 20 Nov 2024 18:58:45 +0000 Subject: [PATCH] add initial styles, story, react component --- src/core/Button.tsx | 91 ++++++++++++ src/core/styles.components.css | 1 + src/core/styles/Button.stories.tsx | 183 +++++++++++++++++++------ src/core/styles/Forms.stories.tsx | 2 +- src/core/styles/Typography.stories.tsx | 16 +-- src/core/styles/buttons.css | 109 +++++++++++++++ src/core/styles/gui.css | 33 +++++ src/core/styles/properties.css | 1 + src/core/styles/text.css | 16 --- tailwind.config.js | 1 + 10 files changed, 385 insertions(+), 68 deletions(-) create mode 100644 src/core/Button.tsx create mode 100644 src/core/styles/gui.css diff --git a/src/core/Button.tsx b/src/core/Button.tsx new file mode 100644 index 000000000..3f0237f41 --- /dev/null +++ b/src/core/Button.tsx @@ -0,0 +1,91 @@ +import React, { PropsWithChildren } from "react"; +import { IconName } from "./Icon/types"; +import Icon from "./Icon"; +import clsx from "clsx"; + +export type ButtonType = "priority" | "primary" | "secondary"; + +type ButtonSize = "lg" | "md" | "sm" | "xs"; + +type ButtonProps = { + /** + * The type of button: priority, primary, or secondary. + */ + variant?: ButtonType; + /** + * The button size: lg, sm, or xs. Leave empty for md. + */ + size?: ButtonSize; + /** + * An icon to render on the left side of the button label. + */ + leftIcon?: IconName; + /** + * An icon to render on the right side of the button label. + */ + rightIcon?: IconName; + /** + * Optional classes to add to the button element. + */ + className?: string; +} & React.ButtonHTMLAttributes; + +// got to go the long way round because of ol' mate Taily Waily +const buttonClasses: Record> = { + priority: { + lg: "ui-button-priority-lg", + md: "ui-button-priority", + sm: "ui-button-priority-sm", + xs: "ui-button-priority-xs", + }, + primary: { + lg: "ui-button-primary-lg", + md: "ui-button-primary", + sm: "ui-button-primary-sm", + xs: "ui-button-primary-xs", + }, + secondary: { + lg: "ui-button-secondary-lg", + md: "ui-button-secondary", + sm: "ui-button-secondary-sm", + xs: "ui-button-secondary-xs", + }, +}; + +export const iconModifierClasses: Record< + ButtonSize, + { left: string; right: string } +> = { + lg: { left: "ui-button-lg-left-icon", right: "ui-button-lg-right-icon" }, + md: { left: "ui-button-left-icon", right: "ui-button-right-icon" }, + sm: { left: "ui-button-sm-left-icon", right: "ui-button-sm-right-icon" }, + xs: { left: "", right: "" }, +}; + +const Button: React.FC> = ({ + variant = "primary", + size, + leftIcon, + rightIcon, + children, + className, + ...rest +}) => { + return ( + + ); +}; + +export default Button; diff --git a/src/core/styles.components.css b/src/core/styles.components.css index cb9c83809..d9a444373 100644 --- a/src/core/styles.components.css +++ b/src/core/styles.components.css @@ -5,6 +5,7 @@ @import "./styles/layout.css"; @import "./styles/shadows.css"; @import "./styles/text.css"; +@import "./styles/gui.css"; @layer components { /* Basis for icon component */ diff --git a/src/core/styles/Button.stories.tsx b/src/core/styles/Button.stories.tsx index e9ff82dfa..240b9830d 100644 --- a/src/core/styles/Button.stories.tsx +++ b/src/core/styles/Button.stories.tsx @@ -1,72 +1,169 @@ import React from "react"; +import clsx from "clsx"; +import Button, { ButtonType, iconModifierClasses } from "../Button"; +import Tooltip from "../Tooltip"; export default { - title: "CSS/Button", -}; - -const buttons = { - priority: { - default: "ui-button-priority", - iconLeft: "", - }, + title: "Components/Button", + component: Button, + tags: ["autodocs"], }; const sizes = [ - { label: "Large", symbol: "lg" }, - { label: "Regular", symbol: "" }, - { label: "Small", symbol: "sm" }, - { label: "Extra small", symbol: "xs" }, + { + label: "Large", + key: "lg", + className: { + priority: "ui-button-priority-lg", + primary: "ui-button-primary-lg", + secondary: "ui-button-secondary-lg", + }, + }, + { + label: "Regular", + className: { + priority: "ui-button-priority", + primary: "ui-button-primary", + secondary: "ui-button-secondary", + }, + }, + { + label: "Small", + key: "sm", + className: { + priority: "ui-button-priority-sm", + primary: "ui-button-primary-sm", + secondary: "ui-button-secondary-sm", + }, + }, + { + label: "Extra small", + key: "xs", + className: { + priority: "ui-button-priority-xs", + primary: "ui-button-primary-xs", + secondary: "ui-button-secondary-xs", + }, + }, ]; -type ButtonSetType = "priority" | "primary" | "secondary"; - -const buttonSet = (type: ButtonSetType) => ( -
- {Array(3) +const buttonSet = (type: ButtonType) => ( +
+ {Array(4) .fill(0) .map((_, index) => ( -
+
{sizes.map((size, sizeIndex) => ( - + {index === 1 ? ( + + + + ) : null} + {size.label} + {index === 2 ? ( + + + + ) : null} + + } + > + {clsx( + size.className[type], + { + [iconModifierClasses[size.key ?? "md"].left]: index === 1, + }, + { + [iconModifierClasses[size.key ?? "md"].right]: index === 2, + }, + )} + ))}
))}
); -export const HighPriority = { +export const ReactComponent = { + render: () => ( +
+
+ +
+
+ +
+
+ +
+
+ ), + parameters: { + docs: { + description: { + story: + "Buttons are available as a React component. The `variant` prop can be set to `priority`, `primary`, or `secondary`. The `size` prop can be set to `lg`, `sm`, or `xs`, or left undefined.\n\nIcons can be added to the left or right of the button text by setting the `leftIcon` or `rightIcon` prop to a valid icon name.", + }, + }, + }, +}; + +export const Priority = { render: () => buttonSet("priority"), + parameters: { + docs: { + description: { + story: + "Priority buttons are available as separate CSS classes for use cases outside of React. Classes: `ui-button-priority`, `ui-button-priority-lg`, `ui-button-priority-sm`, `ui-button-priority-xs`. Hover over the icons to see the active classes.\n\nIcons can be set by placing an SVG element inside the button (view the HTML tab in the individual story view for more comprehensive code examples). Adding either `ui-button-{SIZE}-left-icon` or `ui-button-{SIZE}-right-icon` to the button element will adjust the padding to accommodate the icon.", + }, + }, + }, }; export const Primary = { render: () => buttonSet("primary"), + parameters: { + docs: { + description: { + story: + "Primary buttons are available as separate CSS classes for use cases outside of React. Classes: `ui-button-primary`, `ui-button-primary-lg`, `ui-button-primary-sm`, `ui-button-primary-xs`. Hover over the icons to see the active classes.\n\nIcons can be set by placing an SVG element inside the button (view the HTML tab in the individual story view for more comprehensive code examples). Adding either `ui-button-{SIZE}-left-icon` or `ui-button-{SIZE}-right-icon` to the button element will adjust the padding to accommodate the icon.", + }, + }, + }, }; export const Secondary = { render: () => buttonSet("secondary"), + parameters: { + docs: { + description: { + story: + "Secondary buttons are available as separate CSS classes for use cases outside of React. Classes: `ui-button-secondary`, `ui-button-secondary-lg`, `ui-button-secondary-sm`, `ui-button-secondary-xs`. Hover over the icons to see the active classes.\n\nIcons can be set by placing an SVG element inside the button (view the HTML tab in the individual story view for more comprehensive code examples). Adding either `ui-button-{SIZE}-left-icon` or `ui-button-{SIZE}-right-icon` to the button element will adjust the padding to accommodate the icon.", + }, + }, + }, }; diff --git a/src/core/styles/Forms.stories.tsx b/src/core/styles/Forms.stories.tsx index 2e3301754..7c9628e39 100644 --- a/src/core/styles/Forms.stories.tsx +++ b/src/core/styles/Forms.stories.tsx @@ -223,7 +223,7 @@ export const Toggles = {

Pre-checked

diff --git a/src/core/styles/Typography.stories.tsx b/src/core/styles/Typography.stories.tsx index cd1fd09ef..d850610dd 100644 --- a/src/core/styles/Typography.stories.tsx +++ b/src/core/styles/Typography.stories.tsx @@ -34,14 +34,14 @@ const styles = { { label: "Overline 2", className: "ui-text-overline2" }, ], gui: [ - { label: "Button label 1", className: "ui-text-button1" }, - { label: "Button label 2", className: "ui-text-button2" }, - { label: "Button label 3", className: "ui-text-button3" }, - { label: "Button label 4", className: "ui-text-button4" }, - { label: "Menu label 1", className: "ui-text-menu1" }, - { label: "Menu label 2", className: "ui-text-menu2" }, - { label: "Menu label 3", className: "ui-text-menu3" }, - { label: "Menu label 4", className: "ui-text-menu4" }, + { label: "Button label 1", className: "ui-gui-button1" }, + { label: "Button label 2", className: "ui-gui-button2" }, + { label: "Button label 3", className: "ui-gui-button3" }, + { label: "Button label 4", className: "ui-gui-button4" }, + { label: "Menu label 1", className: "ui-gui-menu1" }, + { label: "Menu label 2", className: "ui-gui-menu2" }, + { label: "Menu label 3", className: "ui-gui-menu3" }, + { label: "Menu label 4", className: "ui-gui-menu4" }, { label: "Link", className: "ui-text" }, ], code: [ diff --git a/src/core/styles/buttons.css b/src/core/styles/buttons.css index e69de29bb..49208e557 100644 --- a/src/core/styles/buttons.css +++ b/src/core/styles/buttons.css @@ -0,0 +1,109 @@ +@layer components { + .ui-button-base { + @apply inline-flex rounded justify-center items-center gap-12 text-neutral-000 transition-colors focus:outline-4 focus:outline-gui-blue-focus disabled:cursor-not-allowed; + } + + .ui-button-lg-base { + @apply ui-button-base ui-gui-button1 h-[56px] px-[28px] py-[17px] gap-12 [&>svg]:!w-24 [&>svg]:!h-24; + } + + .ui-button-md-base { + @apply ui-button-base ui-gui-button2 h-[48px] px-24 py-[13.5px] gap-[10px] [&>svg]:!w-24 [&>svg]:!h-24; + } + + .ui-button-sm-base { + @apply ui-button-base ui-gui-button3 h-[40px] px-20 py-[10px] gap-8 [&>svg]:!w-20 [&>svg]:!h-20; + } + + .ui-button-xs-base { + @apply ui-button-base ui-gui-button4 h-[36px] px-12 py-[9px] gap-6 [&>svg]:!w-16 [&>svg]:!h-16; + } + + .ui-button-disabled-base { + @apply disabled:bg-gui-unavailable dark:disabled:bg-gui-unavailable-dark disabled:text-gui-unavailable-dark disabled:dark:text-gui-unavailable disabled:hover:bg-gui-unavailable dark:disabled:hover:bg-gui-unavailable-dark; + } + + .ui-button-priority-base { + @apply bg-gradient-active-orange hover:bg-gradient-to-r hover:from-orange-800 hover:to-orange-800 active:bg-gradient-to-r active:from-orange-800 active:to-orange-800 disabled:bg-none ui-button-disabled-base; + } + + .ui-button-primary-base { + @apply bg-neutral-1300 dark:bg-neutral-000 text-neutral-000 dark:text-neutral-1300 hover:bg-neutral-1200 dark:hover:bg-neutral-100 active:bg-neutral-1100 dark:active:bg-neutral-200 ui-button-disabled-base; + } + + .ui-button-secondary-base { + @apply text-neutral-1300 dark:text-neutral-000 border border-neutral-600 dark:border-neutral-700 hover:border-neutral-700 dark:hover:border-neutral-600 active:border-neutral-400 dark:active:border-neutral-800 active:text-neutral-1000 dark:active:text-neutral-300 disabled:border-gui-unavailable dark:disabled:border-gui-unavailable-dark disabled:text-gui-unavailable dark:disabled:text-gui-unavailable-dark; + } + + .ui-button-priority-lg { + @apply ui-button-lg-base ui-button-priority-base; + } + + .ui-button-priority { + @apply ui-button-md-base ui-button-priority-base; + } + + .ui-button-priority-sm { + @apply ui-button-sm-base ui-button-priority-base; + } + + .ui-button-priority-xs { + @apply ui-button-xs-base ui-button-priority-base; + } + + .ui-button-primary-lg { + @apply ui-button-lg-base ui-button-primary-base; + } + + .ui-button-primary { + @apply ui-button-md-base ui-button-primary-base; + } + + .ui-button-primary-sm { + @apply ui-button-sm-base ui-button-primary-base; + } + + .ui-button-primary-xs { + @apply ui-button-xs-base ui-button-primary-base; + } + + .ui-button-secondary-lg { + @apply ui-button-lg-base ui-button-secondary-base; + } + + .ui-button-secondary { + @apply ui-button-md-base ui-button-secondary-base; + } + + .ui-button-secondary-sm { + @apply ui-button-sm-base ui-button-secondary-base; + } + + .ui-button-secondary-xs { + @apply ui-button-xs-base ui-button-secondary-base; + } + + .ui-button-lg-left-icon { + @apply pl-24; + } + + .ui-button-left-icon { + @apply pl-20; + } + + .ui-button-sm-left-icon { + @apply pl-[18px]; + } + + .ui-button-lg-right-icon { + @apply pr-24; + } + + .ui-button-right-icon { + @apply pr-20; + } + + .ui-button-sm-right-icon { + @apply pr-[18px]; + } +} diff --git a/src/core/styles/gui.css b/src/core/styles/gui.css new file mode 100644 index 000000000..8014d8ebd --- /dev/null +++ b/src/core/styles/gui.css @@ -0,0 +1,33 @@ +@layer components { + .ui-gui-menu1 { + @apply font-sans text-neutral-1000 text-p1 font-medium leading-snug; + } + + .ui-gui-menu2 { + @apply font-sans text-neutral-1000 text-p2 font-medium leading-snug; + } + + .ui-gui-menu3 { + @apply font-sans text-neutral-1000 text-p3 font-medium leading-snug; + } + + .ui-gui-menu4 { + @apply font-sans text-neutral-1000 text-p4 font-medium leading-snug; + } + + .ui-gui-button1 { + @apply font-sans text-p1 font-bold leading-snug; + } + + .ui-gui-button2 { + @apply font-sans text-p2 font-bold leading-snug; + } + + .ui-gui-button3 { + @apply font-sans text-p3 font-bold leading-snug; + } + + .ui-gui-button4 { + @apply font-sans text-p4 font-bold leading-snug; + } +} diff --git a/src/core/styles/properties.css b/src/core/styles/properties.css index c1fa135a6..062788d2b 100644 --- a/src/core/styles/properties.css +++ b/src/core/styles/properties.css @@ -86,6 +86,7 @@ --color-gui-blue-active-dark: #0080ff; --color-gui-blue-focus: #80b9f2; --color-gui-unavailable: #a8a8a8; + --color-gui-unavailable-dark: #575757; --color-gui-success-green: #11cb24; --color-gui-error-red: #fb0c0c; --color-gui-focus: #80b9f2; diff --git a/src/core/styles/text.css b/src/core/styles/text.css index e88204ee9..a0819c6bd 100644 --- a/src/core/styles/text.css +++ b/src/core/styles/text.css @@ -165,20 +165,4 @@ .ui-figcaption { @apply font-mono text-p3 text-neutral-800; } - - .ui-menu-label-1 { - @apply font-sans text-neutral-1000 text-p1 font-medium leading-snug; - } - - .ui-menu-label-2 { - @apply font-sans text-neutral-1000 text-p2 font-medium leading-snug; - } - - .ui-menu-label-3 { - @apply font-sans text-neutral-1000 text-p3 font-medium leading-snug; - } - - .ui-menu-label-4 { - @apply font-sans text-neutral-1000 text-p4 font-medium leading-snug; - } } diff --git a/tailwind.config.js b/tailwind.config.js index bf970b0a4..634b3e91f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -128,6 +128,7 @@ module.exports = { "gui-blue-focus": "var(--color-gui-blue-focus)", "gui-blue-visited": "var(--color-gui-blue-focus)", "gui-unavailable": "var(--color-gui-unavailable)", + "gui-unavailable-dark": "var(--color-gui-unavailable-dark)", "gui-success-green": "var(--color-gui-success-green)", "gui-error-red": "var(--color-gui-error-red)", "gui-focus": "var(--color-gui-focus)",