-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add skeleton code for Button component * feat: add more options to Button * feat: continue building out Button * feat: add lots of Button styles * feat: add additional variants of Button * feat: additional styling fixes and code comments * docs: add docs for Button * chore: add Aria functionality to the Button * test: add tests for Button * chore: add Button to index exports * chore: tweak docs and remove router interface * fix: button prop typo * chore: update Button component to use `--seeds` nomenclature * test: use the right icon class in Button test * chore: update Button with more prefix fixes * chore: fix icon sizing * feat: provide more options for external link icons * chore: refactor Button internals, restyle hovers * chore: tweak Highlight color scheme * fix: use `button-` prefix on component tokens
- Loading branch information
1 parent
6fe423b
commit 2b09432
Showing
9 changed files
with
638 additions
and
0 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
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,185 @@ | ||
.seeds-button { | ||
--button-border-width: var(--seeds-border-2); | ||
--button-font-family: var(--seeds-font-alt-sans); | ||
--button-font-weight: bold; | ||
--button-interior-gap: var(--seeds-s3); | ||
--button-icon-size-percentage: 75%; | ||
--button-icon-side-padding: var(--seeds-s4); | ||
|
||
--button-padding-sm: calc(var(--seeds-s3_5) - var(--button-border-width)) | ||
calc(var(--seeds-s5) - var(--button-border-width)); | ||
--button-font-size-sm: var(--seeds-font-size-sm); | ||
--button-border-radius-sm: var(--seeds-rounded); | ||
--button-padding-md: calc(var(--seeds-s4) - var(--button-border-width)) | ||
calc(var(--seeds-s6) - var(--button-border-width)); | ||
--button-font-size-md: var(--seeds-font-size-base); | ||
--button-border-radius-md: var(--seeds-rounded); | ||
--button-padding-lg: calc(var(--seeds-s5) - var(--button-border-width)) | ||
calc(var(--seeds-s7) - var(--button-border-width)); | ||
--button-font-size-lg: var(--seeds-font-size-lg); | ||
--button-border-radius-lg: var(--seeds-rounded); | ||
|
||
all: initial; | ||
display: inline-flex; | ||
gap: var(--button-interior-gap); | ||
padding: var(--button-padding-md); | ||
border-style: solid; | ||
border-width: var(--button-border-width); | ||
border-radius: var(--button-border-radius-md); | ||
font-family: var(--button-font-family); | ||
font-weight: var(--button-font-weight); | ||
font-size: var(--button-font-size-md); | ||
line-height: var(--seeds-line-height-tight); | ||
cursor: pointer; | ||
|
||
&[data-size="sm"] { | ||
padding: var(--button-padding-sm); | ||
font-size: var(--button-font-size-sm); | ||
border-radius: var(--button-border-radius-sm); | ||
} | ||
|
||
&[data-size="lg"] { | ||
padding: var(--button-padding-lg); | ||
font-size: var(--button-font-size-lg); | ||
border-radius: var(--button-border-radius-lg); | ||
} | ||
|
||
&[data-variant="primary"] { | ||
background-color: var(--seeds-color-primary); | ||
border-color: var(--seeds-color-primary); | ||
color: var(--seeds-color-on-inverse); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-primary-dark); | ||
border-color: var(--seeds-color-primary-dark); | ||
} | ||
} | ||
|
||
&[data-variant="primary-outlined"] { | ||
background-color: var(--seeds-bg-color-surface); | ||
border-color: var(--seeds-color-primary); | ||
color: var(--seeds-color-primary); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-primary-dark); | ||
border-color: var(--seeds-color-primary-dark); | ||
color: var(--seeds-color-on-inverse); | ||
} | ||
} | ||
|
||
&[data-variant="secondary"] { | ||
background-color: var(--seeds-color-secondary); | ||
border-color: var(--seeds-color-secondary); | ||
color: var(--seeds-color-on-inverse); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-secondary-dark); | ||
border-color: var(--seeds-color-secondary-dark); | ||
} | ||
} | ||
|
||
&[data-variant="secondary-outlined"] { | ||
background-color: var(--seeds-bg-color-surface); | ||
border-color: var(--seeds-color-secondary); | ||
color: var(--seeds-color-secondary); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-secondary-dark); | ||
border-color: var(--seeds-color-secondary-dark); | ||
color: var(--seeds-color-on-inverse); | ||
} | ||
} | ||
|
||
&[data-variant="alert"] { | ||
background-color: var(--seeds-color-alert); | ||
border-color: var(--seeds-color-alert); | ||
color: var(--seeds-color-on-inverse); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-alert-dark); | ||
border-color: var(--seeds-color-alert-dark); | ||
} | ||
} | ||
|
||
&[data-variant="alert-outlined"] { | ||
background-color: var(--seeds-bg-color-surface); | ||
border-color: var(--seeds-color-alert); | ||
color: var(--seeds-color-alert); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-alert-dark); | ||
border-color: var(--seeds-color-alert-dark); | ||
color: var(--seeds-color-on-inverse); | ||
} | ||
} | ||
|
||
&[data-variant="highlight"] { | ||
background-color: var(--seeds-color-highlight); | ||
border-color: var(--seeds-color-highlight); | ||
color: var(--seeds-color-secondary-darker); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-highlight-dark); | ||
border-color: var(--seeds-color-highlight-dark); | ||
} | ||
} | ||
|
||
&[data-variant="highlight-outlined"] { | ||
background-color: var(--seeds-bg-color-surface); | ||
border-color: var(--seeds-color-highlight); | ||
color: var(--seeds-color-secondary-darker); | ||
|
||
&:hover { | ||
background-color: var(--seeds-color-highlight-dark); | ||
border-color: var(--seeds-color-highlight-dark); | ||
} | ||
} | ||
|
||
&[data-variant="borderless-text"] { | ||
background-color: transparent; | ||
border-color: transparent; | ||
color: var(--seeds-color-primary); | ||
font-weight: normal; | ||
text-decoration: underline; | ||
gap: var(--seeds-s1); | ||
} | ||
|
||
&:not([data-variant$="-outlined"]) { | ||
&[disabled], | ||
&[disabled]:hover { | ||
background-color: var(--seeds-color-gray-450); | ||
border-color: var(--seeds-color-gray-450); | ||
color: white; | ||
} | ||
} | ||
|
||
&[data-variant$="-outlined"] { | ||
&[disabled], | ||
&[disabled]:hover { | ||
background-color: var(--seeds-color-bg-color-surface); | ||
border-color: var(--seeds-color-gray-450); | ||
color: var(--seeds-color-gray-450); | ||
} | ||
} | ||
|
||
&.has-lead-icon { | ||
padding-inline-start: var(--button-icon-side-padding); | ||
} | ||
|
||
&.has-tail-icon { | ||
padding-inline-end: var(--button-icon-side-padding); | ||
} | ||
|
||
& > * { | ||
align-self: center; | ||
} | ||
|
||
& > .seeds-icon { | ||
font-size: var(--button-icon-size-percentage); | ||
} | ||
|
||
&:focus-visible { | ||
outline: var(--seeds-focus-ring-outline); | ||
box-shadow: var(--seeds-focus-ring-box-shadow); | ||
} | ||
} |
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,128 @@ | ||
import React, { useContext } from "react" | ||
import { | ||
NavigationContext, | ||
isExternalLink, | ||
shouldShowExternalLinkIcon, | ||
} from "../global/NavigationContext" | ||
import Icon from "../icons/Icon" | ||
|
||
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons" | ||
|
||
import "./Button.scss" | ||
|
||
export interface ButtonProps { | ||
/** Button content */ | ||
children: React.ReactNode | ||
/** Appearance of the component */ | ||
variant?: | ||
| "primary" | ||
| "primary-outlined" | ||
| "secondary" | ||
| "secondary-outlined" | ||
| "alert" | ||
| "alert-outlined" | ||
| "highlight" | ||
| "highlight-outlined" | ||
| "borderless-text" | ||
/** Button size */ | ||
size?: "sm" | "md" | "lg" | ||
/** Icon to show before the label text */ | ||
leadIcon?: React.ReactNode | ||
/** Icon to show after the label text */ | ||
tailIcon?: React.ReactNode | ||
/** Set to true if you don't want external links to show a related icon */ | ||
hideExternalLinkIcon?: boolean | ||
/** Event handler for the button click */ | ||
onClick?: (e: React.MouseEvent) => void | ||
/** Use an `<a href>` tag instead of `<button>` for a hyperlink */ | ||
href?: string | ||
/** HTML button type */ | ||
type?: "button" | "submit" | "reset" | ||
/** Set to true to disable the button */ | ||
disabled?: boolean | ||
/** Set to true to hide the button from the accessibility tree */ | ||
ariaHidden?: boolean | ||
/** Accessible label if button doesn't contain text content */ | ||
ariaLabel?: string | ||
/** Element ID */ | ||
id?: string | ||
/** Additional CSS classes */ | ||
className?: string | ||
} | ||
|
||
const setupButtonProps = (props: ButtonProps) => { | ||
const classNames = ["seeds-button"] | ||
|
||
const tailIcon = shouldShowExternalLinkIcon(props) ? ( | ||
<Icon icon={faArrowUpRightFromSquare} /> | ||
) : ( | ||
props.tailIcon | ||
) | ||
|
||
if (props.className) classNames.push(props.className) | ||
if (props.leadIcon) classNames.push("has-lead-icon") | ||
if (tailIcon) classNames.push("has-tail-icon") | ||
|
||
return { | ||
updatedProps: { | ||
"data-variant": props.variant || "primary", | ||
"data-size": props.size, | ||
id: props.id, | ||
className: classNames.join(" "), | ||
"aria-label": props.ariaLabel, | ||
"aria-hidden": props.ariaHidden, | ||
tabIndex: props.ariaHidden ? -1 : null, | ||
}, | ||
inner: { | ||
leadIcon: props.leadIcon, | ||
tailIcon, | ||
children: props.children, | ||
}, | ||
} | ||
} | ||
|
||
const ButtonElement = (props: ButtonProps) => { | ||
return <button {...props} /> | ||
} | ||
|
||
const LinkButton = (props: ButtonProps) => { | ||
if (props.href && isExternalLink(props.href)) { | ||
return <a target="_blank" {...props} /> | ||
} else { | ||
const { LinkComponent } = useContext(NavigationContext) | ||
return <LinkComponent {...props} /> | ||
} | ||
} | ||
|
||
const Button = (props: ButtonProps) => { | ||
const { updatedProps, inner } = setupButtonProps(props) | ||
|
||
const buttonInner = ( | ||
<> | ||
{inner.leadIcon} | ||
{inner.children} | ||
{inner.tailIcon} | ||
</> | ||
) | ||
|
||
if (props.href) { | ||
return ( | ||
<LinkButton href={props.href} {...updatedProps}> | ||
{buttonInner} | ||
</LinkButton> | ||
) | ||
} else { | ||
return ( | ||
<ButtonElement | ||
type={props.type || "button"} | ||
disabled={props.disabled} | ||
onClick={props.onClick} | ||
{...updatedProps} | ||
> | ||
{buttonInner} | ||
</ButtonElement> | ||
) | ||
} | ||
} | ||
|
||
export default Button |
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,28 @@ | ||
import { ArgsTable } from "@storybook/addon-docs" | ||
import Button from "../Button" | ||
|
||
# <Button /> | ||
|
||
## Properties | ||
|
||
<ArgsTable of={Button} /> | ||
|
||
## Theme Variables | ||
|
||
| Name | Description | Default | | ||
| ------------------------------- | ----------------------------------------- | ----------------------- | | ||
| `--button-border-width` | Border width | `--seeds-border-2` | | ||
| `--button-font-family` | Font family | `--seeds-font-alt-sans` | | ||
| `--button-font-weight` | Font weight | `none` | | ||
| `--button-interior-gap` | Space between icons/text | `--seeds-s3` | | ||
| `--button-icon-size-percentage` | Relative size to base font | `75%` | | ||
| `--button-icon-side-padding` | Space between an icon and the button edge | `--seeds-s4` | | ||
| `--button-padding-sm` | Small button padding | | | ||
| `--button-font-size-sm` | Small button font size | `--seeds-font-size-sm` | | ||
| `--button-border-radius-sm` | Small button border radius | `--seeds-rounded` | | ||
| `--button-padding-md` | Medium button padding | | | ||
| `--button-font-size-md` | Medium button font size | `--seeds-font-size-md` | | ||
| `--button-border-radius-md` | Medium button border radius | `--seeds-rounded` | | ||
| `--button-padding-lg` | Large button padding | | | ||
| `--button-font-size-lg` | Large button font size | `--seeds-font-size-lg` | | ||
| `--button-border-radius-lg` | Large button border radius | `--seeds-rounded` | |
Oops, something went wrong.