From 153732e951b6cf44c1c6ecaab2de7afbb3a97180 Mon Sep 17 00:00:00 2001 From: Andrew Holloway Date: Wed, 22 May 2024 15:54:32 -0500 Subject: [PATCH] fix(Card): address QA design and eng feedback - add isInteractive implementation and stories - allow .Header body to avoid flex issues - update brand tokens to have lowEmphasis values - update stories to avoid mixing eyebrow with icon - documentation on individual stories --- .storybook/data/tokens.json | 6 + src/components/Card/Card-v2.module.css | 31 +++-- src/components/Card/Card-v2.stories.tsx | 74 ++++++++-- src/components/Card/Card-v2.tsx | 10 +- .../Card/__snapshots__/Card-v2.test.ts.snap | 130 +++++++++++++++--- src/design-tokens/themes.json | 58 ++++++-- src/tokens-dist/css/variables.css | 6 + src/tokens-dist/json/variables-nested.json | 28 +++- src/tokens-dist/ts/colors.ts | 6 + 9 files changed, 293 insertions(+), 56 deletions(-) diff --git a/.storybook/data/tokens.json b/.storybook/data/tokens.json index 7544e85e7..bed76184b 100644 --- a/.storybook/data/tokens.json +++ b/.storybook/data/tokens.json @@ -166,13 +166,19 @@ "eds-theme-color-background-brand-primary-strong": "#6B65E2", "eds-theme-color-background-brand-primary-strong-hover": "#3E42B1", "eds-theme-color-background-brand-red": "#4D0118", + "eds-theme-color-background-brand-red-low-emphasis": "#FDF3F4", "eds-theme-color-background-brand-orange": "#FFA070", + "eds-theme-color-background-brand-orange-low-emphasis": "#FFEEE5", "eds-theme-color-background-brand-yellow": "#F9DA78", + "eds-theme-color-background-brand-yellow-low-emphasis": "#FDF3D3", "eds-theme-color-background-brand-green": "#57B083", + "eds-theme-color-background-brand-green-low-emphasis": "#E6F5ED", "eds-theme-color-background-brand-blue-1": "#99CCFF", "eds-theme-color-background-brand-blue-2": "#3165D2", "eds-theme-color-background-brand-blue-3": "#0F2163", + "eds-theme-color-background-brand-blue-low-emphasis": "#EAF4FF", "eds-theme-color-background-brand-purple": "#C580E7", + "eds-theme-color-background-brand-purple-low-emphasis": "#FBF5FD", "eds-theme-color-background-brand-pink": "#DB458D", "eds-theme-color-background-utility-base-0": "rgb(var(--eds-color-white) / 1)", "eds-theme-color-background-utility-base-1": "#FDF9F8", diff --git a/src/components/Card/Card-v2.module.css b/src/components/Card/Card-v2.module.css index 6700af3e0..4b4dde4c3 100755 --- a/src/components/Card/Card-v2.module.css +++ b/src/components/Card/Card-v2.module.css @@ -22,6 +22,10 @@ outline-offset: 1px; outline: 0.125rem solid var(--eds-theme-color-border-utility-focus); } + + &.card--is-interactive { + cursor: pointer; + } } :where(.child-card) { @@ -47,12 +51,14 @@ .card--container-color-default { background-color: var(--eds-theme-color-background-utility-container); - &:hover { - background-color: var(--eds-theme-color-background-utility-container-hover); - } + &.card--is-interactive { + &:hover { + background-color: var(--eds-theme-color-background-utility-container-hover); + } - &:active { - background-color: var(--eds-theme-color-background-utility-container-active); + &:active { + background-color: var(--eds-theme-color-background-utility-container-active); + } } } @@ -60,12 +66,14 @@ background-color: var(--eds-theme-color-background-utility-information-low-emphasis); border-color: var(--eds-theme-color-border-utility-informational); - &:hover { - background-color: var(--eds-theme-color-background-utility-information-low-emphasis-hover); - } + &.card--is-interactive { + &:hover { + background-color: var(--eds-theme-color-background-utility-information-low-emphasis-hover); + } - &:active { - background-color: var(--eds-theme-color-background-utility-information-low-emphasis-active); + &:active { + background-color: var(--eds-theme-color-background-utility-information-low-emphasis-active); + } } } @@ -110,7 +118,8 @@ flex-grow: 0; } -.header__text { +.header__text, +.header__custom { flex-grow: 2; } diff --git a/src/components/Card/Card-v2.stories.tsx b/src/components/Card/Card-v2.stories.tsx index a64b16dfa..013b1b9df 100644 --- a/src/components/Card/Card-v2.stories.tsx +++ b/src/components/Card/Card-v2.stories.tsx @@ -40,16 +40,13 @@ export default { }, topStripeColor: { options: [ - '', - 'bg-brand-blue-1', - 'bg-brand-blue-2', - 'bg-brand-blue-3', - 'bg-brand-green', - 'bg-brand-orange', - 'bg-brand-pink', - 'bg-brand-purple', - 'bg-brand-red', - 'bg-brand-yellow', + 'bg-brand-blue-lowEmphasis', + 'bg-brand-green-lowEmphasis', + 'bg-brand-orange-lowEmphasis', + 'bg-brand-pink-lowEmphasis', + 'bg-brand-purple-lowEmphasis', + 'bg-brand-red-lowEmphasis', + 'bg-brand-yellow-lowEmphasis', ], control: { type: 'select', @@ -65,6 +62,19 @@ type Args = React.ComponentProps; */ export const Default: StoryObj = {}; +/** + * Cards can be made interactive by using `isInteractive`. With this, it will require being linked to `Link` or some control that performs an action or is being dragged. + */ +export const IsInteractive: StoryObj = { + args: { + ...Default.args, + isInteractive: true, + }, +}; + +/** + * This demonstrates all that's possible with the header component + */ export const WithFullHeader: StoryObj = { args: { children: ( @@ -85,12 +95,14 @@ export const WithFullHeader: StoryObj = { }, }; +/** + * When using an icon in the header, we should not use `eyebrow` as it causes a noisy appearance. This is, however, technically possible. + */ export const WithFullHeaderAndIcon: StoryObj = { args: { children: ( <> Get to know your colleagues} title="Question of the day" @@ -106,6 +118,10 @@ export const WithFullHeaderAndIcon: StoryObj = { }, }; +/** + * Example implementation of a menu with a clickable icon button, to demonstrate adding a menu to a card + * @returns ReactNode + */ function CardMenu() { return ( @@ -141,13 +157,15 @@ function CardMenu() { ); } +/** + * You can combine a small header with a menu icon in the top-right. + */ export const WithSmallFullHeaderAndIcon: StoryObj = { args: { children: ( <> } - eyebrow="Recommended for you" icon="person-encircled" size="sm" subTitle="Get to know your colleagues" @@ -164,6 +182,27 @@ export const WithSmallFullHeaderAndIcon: StoryObj = { }, }; +/** + * It's possible to customize the card header with formatted text and content, which replaces the pre-defined slots + */ +export const WithCustomizedHeader: StoryObj = { + args: { + children: ( + <> + + Displaying some text with mixed formatting. + + +
Card Body
+
+ +
Card Footer
+
+ + ), + }, +}; + export const WithHorizontalPrimaryButton: StoryObj = { args: { children: ( @@ -198,6 +237,9 @@ export const CustomBrandCard: StoryObj = { }, }; +/** + * You can add a stripe along the top of a card to enhance and emphasis its appearance + */ export const TopStripe: StoryObj = { args: { topStripe: 'medium', @@ -226,10 +268,13 @@ export const TopStripe: StoryObj = { }, }; +/** + * Cards also allow for using custom top stripe colors. Use one of the low-emphasis brand colors (see the control above to test) + */ export const CustomTopStripe: StoryObj = { args: { topStripe: 'high', - topStripeColor: 'bg-brand-purple', + topStripeColor: 'bg-brand-purple-low-emphasis', children: ( <> = { }, }; +/** + * Another visual change to cards allow for adjusting the background color, to change how the card looks, using `containerColor` + */ export const BackgroundCallout: StoryObj = { args: { containerColor: 'call-out', diff --git a/src/components/Card/Card-v2.tsx b/src/components/Card/Card-v2.tsx index 8a2befeac..7beee1656 100644 --- a/src/components/Card/Card-v2.tsx +++ b/src/components/Card/Card-v2.tsx @@ -38,6 +38,12 @@ export interface CardProps extends HTMLAttributes { * or used programmatically with drag and drop libraries */ isDragging?: boolean; + /** + * Whether `Card` itself is directly interactive (clicking will perform some navigation or action) + * + * **Default is `false`**. + */ + isInteractive?: boolean; /** * Decorative top bar used to cause a highlight on a given card. When present, this * corresponds to a specified emphasis level. @@ -111,6 +117,7 @@ export const Card = ({ children, containerStyle = 'low', isDragging, + isInteractive = false, topStripe = 'none', topStripeColor = '', ...other @@ -121,6 +128,7 @@ export const Card = ({ styles[`card--container-color-${containerColor}`], typeof isDragging !== 'undefined' && styles[`card--is-dragging-${isDragging}`], + isInteractive && styles['card--is-interactive'], className, ); return ( @@ -201,7 +209,7 @@ const CardHeader = ({ return children ? (
- {children} +
{children}
) : (
diff --git a/src/components/Card/__snapshots__/Card-v2.test.ts.snap b/src/components/Card/__snapshots__/Card-v2.test.ts.snap index 8b2b1cb51..6e5386d12 100644 --- a/src/components/Card/__snapshots__/Card-v2.test.ts.snap +++ b/src/components/Card/__snapshots__/Card-v2.test.ts.snap @@ -266,9 +266,13 @@ exports[` (v2) CustomBrandCard story renders snapshot 1`] = ` class="card__header header--size-md" >
- Card Header +
+ Card Header +
(v2) CustomTopStripe story renders snapshot 1`] = `
@@ -406,9 +410,58 @@ exports[` (v2) Default story renders snapshot 1`] = ` class="card__header header--size-md" >
+
+ Card Header +
+
+ +
+
+ Card Body +
+
+
+
+ Card Footer +
+
+
+ +
+`; + +exports[` (v2) IsInteractive story renders snapshot 1`] = ` +
+
+
+
- Card Header +
+ Card Header +
(v2) WhileDragging story renders snapshot 1`] = ` class="card__header header--size-md" >
- Card Header +
+ Card Header +
+
+ +
+
+ Card Body +
+
+
+
+ Card Footer +
+
+
+
+
+`; + +exports[` (v2) WithCustomizedHeader story renders snapshot 1`] = ` +
+
+
+
+ Displaying + + some text + + with + + mixed formatting + + .
(v2) WithFullHeaderAndIcon story renders snapshot 1`] = `
-
- Recommended for you -
@@ -836,11 +937,6 @@ exports[` (v2) WithSmallFullHeaderAndIcon story renders snapshot 1`] = `
-
- Recommended for you -
diff --git a/src/design-tokens/themes.json b/src/design-tokens/themes.json index d5b1e0789..99b2a7e33 100644 --- a/src/design-tokens/themes.json +++ b/src/design-tokens/themes.json @@ -146,30 +146,72 @@ } }, "red": { - "value": "{eds.color.red.850}" + "@": { + "value": "{eds.color.red.850}", + "group": "color" + }, + "lowEmphasis": { + "value": "{eds.color.red.050}", + "group": "color" + } }, "orange": { - "value": "{eds.color.orange.250}" + "@": { + "value": "{eds.color.orange.250}", + "group": "color" + }, + "lowEmphasis": { + "value": "{eds.color.orange.050}", + "group": "color" + } }, "yellow": { - "value": "{eds.color.yellow.150}" + "@": { + "value": "{eds.color.yellow.150}", + "group": "color" + }, + "lowEmphasis": { + "value": "{eds.color.yellow.050}", + "group": "color" + } }, "green": { - "value": "{eds.color.green.350}" + "@": { + "value": "{eds.color.green.350}", + "group": "color" + }, + "lowEmphasis": { + "value": "{eds.color.green.050}", + "group": "color" + } }, "blue": { "1": { - "value": "{eds.color.blue.200}" + "value": "{eds.color.blue.200}", + "group": "color" }, "2": { - "value": "{eds.color.blue.550}" + "value": "{eds.color.blue.550}", + "group": "color" }, "3": { - "value": "{eds.color.blue.850}" + "value": "{eds.color.blue.850}", + "group": "color" + }, + "lowEmphasis": { + "value": "{eds.color.blue.050}", + "group": "color" } }, "purple": { - "value": "{eds.color.purple.350}" + "@": { + "value": "{eds.color.purple.350}", + "group": "color" + }, + "lowEmphasis": { + "value": "{eds.color.purple.050}", + "group": "color" + } }, "pink": { "value": "{eds.color.pink.450}" diff --git a/src/tokens-dist/css/variables.css b/src/tokens-dist/css/variables.css index 9265f6af9..1533c1c63 100644 --- a/src/tokens-dist/css/variables.css +++ b/src/tokens-dist/css/variables.css @@ -735,13 +735,19 @@ --eds-theme-color-background-utility-container-hover: var(--eds-color-neutral-050); --eds-theme-color-background-utility-base-1: var(--eds-color-neutral-025); --eds-theme-color-background-brand-pink: var(--eds-color-pink-450); + --eds-theme-color-background-brand-purple-low-emphasis: var(--eds-color-purple-050); --eds-theme-color-background-brand-purple: var(--eds-color-purple-350); + --eds-theme-color-background-brand-blue-low-emphasis: var(--eds-color-blue-050); --eds-theme-color-background-brand-blue-3: var(--eds-color-blue-850); --eds-theme-color-background-brand-blue-2: var(--eds-color-blue-550); --eds-theme-color-background-brand-blue-1: var(--eds-color-blue-200); + --eds-theme-color-background-brand-green-low-emphasis: var(--eds-color-green-050); --eds-theme-color-background-brand-green: var(--eds-color-green-350); + --eds-theme-color-background-brand-yellow-low-emphasis: var(--eds-color-yellow-050); --eds-theme-color-background-brand-yellow: var(--eds-color-yellow-150); + --eds-theme-color-background-brand-orange-low-emphasis: var(--eds-color-orange-050); --eds-theme-color-background-brand-orange: var(--eds-color-orange-250); + --eds-theme-color-background-brand-red-low-emphasis: var(--eds-color-red-050); --eds-theme-color-background-brand-red: var(--eds-color-red-850); --eds-theme-color-background-brand-primary-strong-hover: var(--eds-color-brand-grape-800); --eds-theme-color-background-brand-primary-strong: var(--eds-color-brand-grape-600); diff --git a/src/tokens-dist/json/variables-nested.json b/src/tokens-dist/json/variables-nested.json index 6448e8649..f6b5e17e8 100644 --- a/src/tokens-dist/json/variables-nested.json +++ b/src/tokens-dist/json/variables-nested.json @@ -350,16 +350,32 @@ "hover": "#3E42B1" } }, - "red": "#4D0118", - "orange": "#FFA070", - "yellow": "#F9DA78", - "green": "#57B083", + "red": { + "@": "#4D0118", + "lowEmphasis": "#FDF3F4" + }, + "orange": { + "@": "#FFA070", + "lowEmphasis": "#FFEEE5" + }, + "yellow": { + "@": "#F9DA78", + "lowEmphasis": "#FDF3D3" + }, + "green": { + "@": "#57B083", + "lowEmphasis": "#E6F5ED" + }, "blue": { "1": "#99CCFF", "2": "#3165D2", - "3": "#0F2163" + "3": "#0F2163", + "lowEmphasis": "#EAF4FF" + }, + "purple": { + "@": "#C580E7", + "lowEmphasis": "#FBF5FD" }, - "purple": "#C580E7", "pink": "#DB458D" }, "utility": { diff --git a/src/tokens-dist/ts/colors.ts b/src/tokens-dist/ts/colors.ts index 4e924e411..fc465bb2a 100644 --- a/src/tokens-dist/ts/colors.ts +++ b/src/tokens-dist/ts/colors.ts @@ -12,13 +12,19 @@ export const EdsThemeColorBackgroundBrandPrimarySubtle = '#F0F0FC'; export const EdsThemeColorBackgroundBrandPrimaryStrong = '#6B65E2'; export const EdsThemeColorBackgroundBrandPrimaryStrongHover = '#3E42B1'; export const EdsThemeColorBackgroundBrandRed = '#4D0118'; +export const EdsThemeColorBackgroundBrandRedLowEmphasis = '#FDF3F4'; export const EdsThemeColorBackgroundBrandOrange = '#FFA070'; +export const EdsThemeColorBackgroundBrandOrangeLowEmphasis = '#FFEEE5'; export const EdsThemeColorBackgroundBrandYellow = '#F9DA78'; +export const EdsThemeColorBackgroundBrandYellowLowEmphasis = '#FDF3D3'; export const EdsThemeColorBackgroundBrandGreen = '#57B083'; +export const EdsThemeColorBackgroundBrandGreenLowEmphasis = '#E6F5ED'; export const EdsThemeColorBackgroundBrandBlue1 = '#99CCFF'; export const EdsThemeColorBackgroundBrandBlue2 = '#3165D2'; export const EdsThemeColorBackgroundBrandBlue3 = '#0F2163'; +export const EdsThemeColorBackgroundBrandBlueLowEmphasis = '#EAF4FF'; export const EdsThemeColorBackgroundBrandPurple = '#C580E7'; +export const EdsThemeColorBackgroundBrandPurpleLowEmphasis = '#FBF5FD'; export const EdsThemeColorBackgroundBrandPink = '#DB458D'; export const EdsThemeColorBackgroundUtilityBase0 = 'rgb(var(--eds-color-white) / 1)';