From 8546cc3e9866e040708628a10e62243cd037bd5d Mon Sep 17 00:00:00 2001
From: Natalie Pina <34781875+nataliepina@users.noreply.github.com>
Date: Fri, 20 Oct 2023 14:16:03 -0500
Subject: [PATCH] feat: new outline button components (#1068)
Introduces a new OutlineButton component,
along with an OutlineDropdownButton.
Closes D2IQ-99363
---
packages/button/README.md | 1 +
packages/button/components/ButtonBase.tsx | 3 +-
packages/button/components/OutlineButton.tsx | 16 ++
.../components/OutlineDropdownButton.tsx | 14 +
packages/button/index.ts | 2 +
.../button/stories/DefaultButton.stories.tsx | 8 +-
.../button/stories/DropdownButton.stories.tsx | 9 +-
packages/button/style.ts | 72 ++++-
.../__snapshots__/ButtonBase.test.tsx.snap | 246 ++++++++++++++++++
packages/index.ts | 2 +
10 files changed, 369 insertions(+), 4 deletions(-)
create mode 100644 packages/button/components/OutlineButton.tsx
create mode 100644 packages/button/components/OutlineDropdownButton.tsx
diff --git a/packages/button/README.md b/packages/button/README.md
index 936b443fa..db7391c0b 100644
--- a/packages/button/README.md
+++ b/packages/button/README.md
@@ -10,6 +10,7 @@ There are several types of buttons to choose from based on the intended action:
| --------------- | ------------------------------------------------------------------------------- |
| Primary | Used for the most important call-to-action on a page, such as Save or Deploy. |
| Secondary | Used primarily for a call-to-action that is a link. |
+| Outline | Used as an alternative secondary button with an outline/ghost style. |
| Warning | Used to advise caution. |
| Danger | Used to highlight something dangerous that can't be undone, such as "Delete". |
| Icon only | Use when an icon is enough to represent the action, such as "Create a Service". |
diff --git a/packages/button/components/ButtonBase.tsx b/packages/button/components/ButtonBase.tsx
index 8e341acea..3e024329a 100644
--- a/packages/button/components/ButtonBase.tsx
+++ b/packages/button/components/ButtonBase.tsx
@@ -30,7 +30,8 @@ export enum ButtonAppearances {
Standard = "standard",
Danger = "danger",
Success = "success",
- Warning = "warning"
+ Warning = "warning",
+ Outline = "outline"
}
export interface ButtonProps extends LinkProps {
diff --git a/packages/button/components/OutlineButton.tsx b/packages/button/components/OutlineButton.tsx
new file mode 100644
index 000000000..3b49b1df9
--- /dev/null
+++ b/packages/button/components/OutlineButton.tsx
@@ -0,0 +1,16 @@
+import * as React from "react";
+import {
+ default as ButtonBase,
+ ButtonProps,
+ ButtonAppearances
+} from "./ButtonBase";
+
+const OutlineButton = (props: ButtonProps) => (
+
+);
+
+export default OutlineButton;
diff --git a/packages/button/components/OutlineDropdownButton.tsx b/packages/button/components/OutlineDropdownButton.tsx
new file mode 100644
index 000000000..0a1274e2c
--- /dev/null
+++ b/packages/button/components/OutlineDropdownButton.tsx
@@ -0,0 +1,14 @@
+import * as React from "react";
+import OutlineButton from "./OutlineButton";
+import { ButtonProps } from "./ButtonBase";
+import { SystemIcons } from "../../icons/dist/system-icons-enum";
+
+const OutlineDropdownButton = (props: ButtonProps) => (
+
+);
+
+export default OutlineDropdownButton;
diff --git a/packages/button/index.ts b/packages/button/index.ts
index 0b7062318..864c7d141 100644
--- a/packages/button/index.ts
+++ b/packages/button/index.ts
@@ -4,6 +4,7 @@ export { default as StandardButton } from "./components/StandardButton";
export { default as SuccessButton } from "./components/SuccessButton";
export { default as DangerButton } from "./components/DangerButton";
export { default as WarningButton } from "./components/WarningButton";
+export { default as OutlineButton } from "./components/OutlineButton";
export { default as PrimaryDropdownButton } from "./components/PrimaryDropdownButton";
export { default as SecondaryDropdownButton } from "./components/SecondaryDropdownButton";
@@ -11,5 +12,6 @@ export { default as StandardDropdownButton } from "./components/StandardDropdown
export { default as SuccessDropdownButton } from "./components/SuccessDropdownButton";
export { default as WarningDropdownButton } from "./components/WarningDropdownButton";
export { default as DangerDropdownButton } from "./components/DangerDropdownButton";
+export { default as OutlineDropdownButton } from "./components/OutlineDropdownButton";
export { default as ResetButton } from "./components/ResetButton";
diff --git a/packages/button/stories/DefaultButton.stories.tsx b/packages/button/stories/DefaultButton.stories.tsx
index 752f48d8d..25c40aa7f 100644
--- a/packages/button/stories/DefaultButton.stories.tsx
+++ b/packages/button/stories/DefaultButton.stories.tsx
@@ -6,7 +6,8 @@ import {
StandardButton,
SuccessButton,
DangerButton,
- WarningButton
+ WarningButton,
+ OutlineButton
} from "../..";
import { action } from "@storybook/addon-actions";
import { SystemIcons } from "../../icons/dist/system-icons-enum";
@@ -17,6 +18,7 @@ export default {
component: StandardButton,
subComponents: {
PrimaryButton,
+ OutlineButton,
SecondaryButton,
SuccessButton,
DangerButton,
@@ -59,6 +61,10 @@ export const _SecondaryButton = {
render: args => {args.children}
};
+export const _OutlineButton = {
+ render: args => {args.children}
+};
+
export const _SuccessButton = {
render: args => {args.children}
};
diff --git a/packages/button/stories/DropdownButton.stories.tsx b/packages/button/stories/DropdownButton.stories.tsx
index 198e1f21a..b9fe25475 100644
--- a/packages/button/stories/DropdownButton.stories.tsx
+++ b/packages/button/stories/DropdownButton.stories.tsx
@@ -6,7 +6,8 @@ import {
StandardDropdownButton,
SuccessDropdownButton,
WarningDropdownButton,
- DangerDropdownButton
+ DangerDropdownButton,
+ OutlineDropdownButton
} from "../../index";
import { SystemIcons } from "../../icons/dist/system-icons-enum";
@@ -49,6 +50,12 @@ export const _SecondaryDropdownButton = {
)
};
+export const _OutlineDropdownButton = {
+ render: args => (
+ {args.children}
+ )
+};
+
export const _SuccessDropdownButton = {
render: args => (
{args.children}
diff --git a/packages/button/style.ts b/packages/button/style.ts
index dc9ca2b9e..fba3ff3e3 100644
--- a/packages/button/style.ts
+++ b/packages/button/style.ts
@@ -18,7 +18,10 @@ import {
themeErrorInverted,
themeTextColorDisabledInverted,
themeWarningInverted,
- themeWarning
+ themeWarning,
+ purpleLighten5,
+ purpleLighten4,
+ themeBgPrimary
} from "../design-tokens/build/js/designTokens";
import { darken, getTextColor } from "../shared/styles/color";
import { ButtonAppearances } from "./components/ButtonBase";
@@ -70,6 +73,7 @@ export const filledButton = (
const mutedButton = css`
${tintContent(themeTextColorDisabled)};
+ border-color: ${themeBgDisabled};
cursor: default;
pointer-events: none;
@@ -196,6 +200,26 @@ export const focusStyleByAppearance = (appearance, isInverse) => {
return isInverse
? focusStyles(getHoverColor(getCSSVarValue(themeWarningInverted)))
: focusStyles(getHoverColor(getCSSVarValue(themeWarning)));
+ case "outline":
+ return css`
+ ${(isInverse
+ ? focusStyles(
+ getCSSVarValue(themeBgPrimary),
+ getHoverColor(getCSSVarValue(themeTextColorInteractiveInverted))
+ )
+ : getCSSVarValue(themeBgPrimary),
+ focusStyles(getHoverColor(getCSSVarValue(themeTextColorInteractive))))};
+ &:focus {
+ background-color: ${purpleLighten5};
+
+ &:after {
+ bottom: -4px;
+ left: -4px;
+ right: -4px;
+ top: -4px;
+ }
+ }
+ `;
default:
return "";
}
@@ -251,6 +275,32 @@ export const button = appearance => {
getHoverColor(getCSSVarValue(themeWarning)),
getActiveColor(getCSSVarValue(themeWarning))
);
+ case "outline":
+ return css`
+ border: 1px solid ${getCSSVarValue(themeTextColorInteractive)};
+ border-radius: ${borderRadiusDefault};
+ padding: ${buttonPadding.vert} ${buttonPadding.horiz};
+
+ ${tintContent(themeTextColorInteractive)};
+
+ &:hover {
+ background-color: ${purpleLighten5};
+ ${tintContent(
+ getHoverColor(getCSSVarValue(themeTextColorInteractive))
+ )};
+ }
+ &:active {
+ background-color: ${purpleLighten4};
+ ${tintContent(
+ getActiveColor(getCSSVarValue(themeTextColorInteractive))
+ )};
+ }
+ &[href],
+ &[href]:visited {
+ background-color: ${purpleLighten4};
+ ${tintContent(themeTextColorInteractive)};
+ }
+ `;
default:
return "";
}
@@ -306,6 +356,26 @@ export const buttonInverse = appearance => {
getHoverColor(getCSSVarValue(themeWarningInverted)),
getActiveColor(getCSSVarValue(themeWarningInverted))
);
+ case "outline":
+ return css`
+ border: 1px solid ${getCSSVarValue(themeTextColorInteractiveInverted)};
+ ${tintContent(themeTextColorInteractiveInverted)};
+
+ &:hover {
+ ${tintContent(
+ getHoverColor(getCSSVarValue(themeTextColorInteractiveInverted))
+ )};
+ }
+ &:active {
+ ${tintContent(
+ getActiveColor(getCSSVarValue(themeTextColorInteractiveInverted))
+ )};
+ }
+ &[href],
+ &[href]:visited {
+ ${tintContent(themeTextColorInteractiveInverted)};
+ }
+ `;
default:
return "";
}
diff --git a/packages/button/tests/__snapshots__/ButtonBase.test.tsx.snap b/packages/button/tests/__snapshots__/ButtonBase.test.tsx.snap
index 824681fa8..9a22f357e 100644
--- a/packages/button/tests/__snapshots__/ButtonBase.test.tsx.snap
+++ b/packages/button/tests/__snapshots__/ButtonBase.test.tsx.snap
@@ -46,6 +46,7 @@ exports[`ButtonBase renders all appearances with props 1`] = `
width: 100%;
color: var(--themeTextColorDisabled, #AEB0B4);
fill: var(--themeTextColorDisabled, #AEB0B4);
+ border-color: var(--themeBgDisabled, #E8EAED);
cursor: default;
pointer-events: none;
background-color: var(--themeBgDisabled, #E8EAED);
@@ -273,6 +274,7 @@ exports[`ButtonBase renders all appearances with props 2`] = `
width: 100%;
color: var(--themeTextColorDisabled, #AEB0B4);
fill: var(--themeTextColorDisabled, #AEB0B4);
+ border-color: var(--themeBgDisabled, #E8EAED);
cursor: default;
pointer-events: none;
}
@@ -491,6 +493,7 @@ exports[`ButtonBase renders all appearances with props 3`] = `
width: 100%;
color: var(--themeTextColorDisabled, #AEB0B4);
fill: var(--themeTextColorDisabled, #AEB0B4);
+ border-color: var(--themeBgDisabled, #E8EAED);
cursor: default;
pointer-events: none;
background-color: var(--themeBgDisabled, #E8EAED);
@@ -714,6 +717,7 @@ exports[`ButtonBase renders all appearances with props 4`] = `
width: 100%;
color: var(--themeTextColorDisabled, #AEB0B4);
fill: var(--themeTextColorDisabled, #AEB0B4);
+ border-color: var(--themeBgDisabled, #E8EAED);
cursor: default;
pointer-events: none;
background-color: var(--themeBgDisabled, #E8EAED);
@@ -937,6 +941,7 @@ exports[`ButtonBase renders all appearances with props 5`] = `
width: 100%;
color: var(--themeTextColorDisabled, #AEB0B4);
fill: var(--themeTextColorDisabled, #AEB0B4);
+ border-color: var(--themeBgDisabled, #E8EAED);
cursor: default;
pointer-events: none;
background-color: var(--themeBgDisabled, #E8EAED);
@@ -1160,6 +1165,7 @@ exports[`ButtonBase renders all appearances with props 6`] = `
width: 100%;
color: var(--themeTextColorDisabled, #AEB0B4);
fill: var(--themeTextColorDisabled, #AEB0B4);
+ border-color: var(--themeBgDisabled, #E8EAED);
cursor: default;
pointer-events: none;
background-color: var(--themeBgDisabled, #E8EAED);
@@ -1336,3 +1342,243 @@ exports[`ButtonBase renders all appearances with props 6`] = `
`;
+
+exports[`ButtonBase renders all appearances with props 7`] = `
+
+ .emotion-0 {
+ outline: none;
+ position: relative;
+}
+
+.emotion-0:focus {
+ background-color: #704fe5;
+}
+
+.emotion-0:focus:after {
+ border: 2px solid #704fe5;
+ border-radius: 4px;
+ bottom: -3px;
+ content: "";
+ left: -3px;
+ position: absolute;
+ right: -3px;
+ top: -3px;
+}
+
+.emotion-0:focus {
+ background-color: #F8F6FF;
+}
+
+.emotion-0:focus:after {
+ bottom: -4px;
+ left: -4px;
+ right: -4px;
+ top: -4px;
+}
+
+.emotion-1 {
+ background: none;
+ border: 0;
+ color: inherit;
+ font: inherit;
+ line-height: normal;
+ overflow: visible;
+ padding: 0;
+ text-align: inherit;
+ border: 1px solid #7D58FF;
+ border-radius: 4px;
+ padding: 10px 18px;
+ color: var(--themeTextColorInteractive, #7D58FF);
+ fill: var(--themeTextColorInteractive, #7D58FF);
+ cursor: pointer;
+ display: inline-block;
+ outline: none;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ font-weight: 500;
+ text-align: center;
+ width: 100%;
+ color: var(--themeTextColorDisabled, #AEB0B4);
+ fill: var(--themeTextColorDisabled, #AEB0B4);
+ border-color: var(--themeBgDisabled, #E8EAED);
+ cursor: default;
+ pointer-events: none;
+ background-color: var(--themeBgDisabled, #E8EAED);
+}
+
+.emotion-1::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+.emotion-1:hover {
+ background-color: #F8F6FF;
+ color: #704fe5;
+ fill: #704fe5;
+}
+
+.emotion-1:active {
+ background-color: #E5DDFF;
+ color: #6446cc;
+ fill: #6446cc;
+}
+
+.emotion-1[href],
+.emotion-1[href]:visited {
+ background-color: #E5DDFF;
+ color: var(--themeTextColorInteractive, #7D58FF);
+ fill: var(--themeTextColorInteractive, #7D58FF);
+}
+
+.emotion-1:hover,
+.emotion-1:focus,
+.emotion-1:active {
+ color: var(--themeTextColorDisabled, #AEB0B4);
+ fill: var(--themeTextColorDisabled, #AEB0B4);
+}
+
+.emotion-1:hover,
+.emotion-1:focus,
+.emotion-1:active {
+ background-color: var(--themeBgDisabled, #E8EAED);
+}
+
+.emotion-2 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ height: auto;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+ box-sizing: border-box;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ min-height: 0;
+}
+
+.emotion-2>div {
+ width: auto;
+}
+
+.emotion-3 {
+ box-sizing: border-box;
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto;
+ -webkit-box-flex: 0;
+ -webkit-flex-grow: 0;
+ -ms-flex-positive: 0;
+ flex-grow: 0;
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+ width: initial;
+ display: inherit;
+}
+
+.emotion-4 {
+ vertical-align: middle;
+ fill: inherit;
+}
+
+.emotion-4 use {
+ pointer-events: none;
+}
+
+.emotion-5 {
+ box-sizing: border-box;
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto;
+ -webkit-box-flex: 0;
+ -webkit-flex-grow: 0;
+ -ms-flex-positive: 0;
+ flex-grow: 0;
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+ width: initial;
+ padding-left: 8px;
+}
+
+.emotion-5:after {
+ content: "...";
+}
+
+.emotion-6 {
+ box-sizing: border-box;
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto;
+ -webkit-box-flex: 0;
+ -webkit-flex-grow: 0;
+ -ms-flex-positive: 0;
+ flex-grow: 0;
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+ width: initial;
+ display: inherit;
+ padding-left: 4px;
+}
+
+
+
+`;
diff --git a/packages/index.ts b/packages/index.ts
index 912cea1cb..a51dc5c92 100644
--- a/packages/index.ts
+++ b/packages/index.ts
@@ -30,12 +30,14 @@ export {
SuccessButton,
DangerButton,
WarningButton,
+ OutlineButton,
PrimaryDropdownButton,
SecondaryDropdownButton,
StandardDropdownButton,
SuccessDropdownButton,
WarningDropdownButton,
DangerDropdownButton,
+ OutlineDropdownButton,
ResetButton
} from "./button";
export { ButtonCard, Card, LinkCard } from "./card";