From 77536181729c53087ce118d945304d2877892e3c Mon Sep 17 00:00:00 2001
From: Caroline Horn <549577+cchaos@users.noreply.github.com>
Date: Wed, 1 May 2019 12:49:32 -0400
Subject: [PATCH] Selectable EuiCards (#1895)
---
CHANGELOG.md | 1 +
src-docs/src/views/card/card_example.js | 56 ++++++-
src-docs/src/views/card/card_selectable.js | 84 ++++++++++
.../button/button_empty/_button_empty.scss | 13 +-
.../card/__snapshots__/card.test.js.snap | 149 +++++++++++++++++-
.../__snapshots__/card_select.test.js.snap | 103 ++++++++++++
src/components/card/_card.scss | 31 ++--
src/components/card/_card_graphic.scss | 3 +
src/components/card/_card_select.scss | 21 +++
src/components/card/_index.scss | 4 +
src/components/card/_mixins.scss | 11 ++
src/components/card/_variables.scss | 21 +++
src/components/card/card.js | 31 +++-
src/components/card/card.test.js | 140 +++++++++++-----
src/components/card/card_select.js | 105 ++++++++++++
src/components/card/card_select.test.js | 70 ++++++++
16 files changed, 768 insertions(+), 75 deletions(-)
create mode 100644 src-docs/src/views/card/card_selectable.js
create mode 100644 src/components/card/__snapshots__/card_select.test.js.snap
create mode 100644 src/components/card/_card_graphic.scss
create mode 100644 src/components/card/_card_select.scss
create mode 100644 src/components/card/_mixins.scss
create mode 100644 src/components/card/_variables.scss
create mode 100644 src/components/card/card_select.js
create mode 100644 src/components/card/card_select.test.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85c8c02fc2c..c1ab7ba4621 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)
+- Added `selectable` prop to `EuiCard` ([#1895](https://github.com/elastic/eui/pull/1895))
- Converted `EuiValidatableControl` to TS ([#1879](https://github.com/elastic/eui/pull/1879))
**Bug fixes**
diff --git a/src-docs/src/views/card/card_example.js b/src-docs/src/views/card/card_example.js
index 4a22edc672c..f658f1d6807 100644
--- a/src-docs/src/views/card/card_example.js
+++ b/src-docs/src/views/card/card_example.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import { renderToHtml } from '../../services';
@@ -12,6 +12,10 @@ import {
EuiCallOut,
} from '../../../../src/components';
+import {
+ EuiCardSelect
+} from '../../../../src/components/card/card_select';
+
import Card from './card';
const cardSource = require('!!raw-loader!./card');
const cardHtml = renderToHtml(Card);
@@ -32,6 +36,10 @@ import CardLayout from './card_layout';
const cardLayoutSource = require('!!raw-loader!./card_layout');
const cardLayoutHtml = renderToHtml(CardLayout);
+import CardSelectable from './card_selectable';
+const cardSelectableSource = require('!!raw-loader!./card_selectable');
+const cardSelectableHtml = renderToHtml(CardSelectable);
+
export const CardExample = {
title: 'Card',
sections: [{
@@ -90,7 +98,7 @@ export const CardExample = {
/>
),
- components: { EuiCard },
+ props: { EuiCard },
demo:
+ When you have a list of cards that can be selected but do not navigate anywhere, you
+ can add the
+ The select button is essentially an EuiButtonEmpty and so the
+ Card description +
++ Card description +
+Card description @@ -23,7 +87,7 @@ exports[`EuiCard horizontal 1`] = `
Card description @@ -73,22 +139,93 @@ exports[`EuiCard icon 1`] = `
+ Card description +
++ Card description +
+
Card description
diff --git a/src/components/card/__snapshots__/card_select.test.js.snap b/src/components/card/__snapshots__/card_select.test.js.snap
new file mode 100644
index 00000000000..56d3a14c309
--- /dev/null
+++ b/src/components/card/__snapshots__/card_select.test.js.snap
@@ -0,0 +1,103 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiCardSelect is rendered 1`] = `
+
+`;
+
+exports[`EuiCardSelect props can override color 1`] = `
+
+`;
+
+exports[`EuiCardSelect props can override text 1`] = `
+
+`;
+
+exports[`EuiCardSelect props isDisabled 1`] = `
+
+`;
+
+exports[`EuiCardSelect props isSelected 1`] = `
+
+`;
diff --git a/src/components/card/_card.scss b/src/components/card/_card.scss
index c5f6a5b2048..83772b5e230 100644
--- a/src/components/card/_card.scss
+++ b/src/components/card/_card.scss
@@ -1,11 +1,6 @@
-@import '../panel/variables';
@import '../panel/mixins';
@import '../badge/beta_badge/mixins';
-$euiCardSpacing: map-get($euiPanelPaddingModifiers, 'paddingMedium');
-$euiCardTitleSize: 18px; // Hardcoded pixel value for theme parity.
-$euiCardGraphicHeight: 40px; // Actual height of the svg used in EuiCardGraphic
-
// Start with a base of EuiPanel styles
@include euiPanel($selector: 'euiCard');
@@ -62,10 +57,22 @@ $euiCardGraphicHeight: 40px; // Actual height of the svg used in EuiCardGraphic
}
}
- &.euiCard--hasBottomGraphic {
+ &.euiCard--hasBottomGraphic,
+ &.euiCard--isSelectable {
position: relative;
padding-bottom: $euiCardSpacing + $euiCardGraphicHeight;
}
+
+ &.euiCard-isSelected {
+ @include euiBottomShadowMedium;
+ transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance, border-color $euiAnimSpeedFast $euiAnimSlightResistance;
+ }
+}
+
+@each $name, $color in $euiCardSelectButtonBorders {
+ .euiCard--isSelectable--#{$name}.euiCard-isSelected {
+ border-color: $color;
+ }
}
.euiCard__top {
@@ -151,14 +158,4 @@ $euiCardGraphicHeight: 40px; // Actual height of the svg used in EuiCardGraphic
}
}
-// Optional decorative graphics
-.euiCard__graphic {
- position: absolute;
- bottom: 0;
- left: 0;
- height: $euiCardGraphicHeight;
- width: 100%;
- overflow: hidden;
- border-bottom-left-radius: $euiBorderRadius;
- border-bottom-right-radius: $euiBorderRadius;
-}
+
diff --git a/src/components/card/_card_graphic.scss b/src/components/card/_card_graphic.scss
new file mode 100644
index 00000000000..9052151998e
--- /dev/null
+++ b/src/components/card/_card_graphic.scss
@@ -0,0 +1,3 @@
+.euiCard__graphic {
+ @include euiCardBottomNodePosition;
+}
diff --git a/src/components/card/_card_select.scss b/src/components/card/_card_select.scss
new file mode 100644
index 00000000000..6a63a9e141f
--- /dev/null
+++ b/src/components/card/_card_select.scss
@@ -0,0 +1,21 @@
+.euiCardSelect {
+ // Option select button that expands to sides and bottom
+ @include euiCardBottomNodePosition;
+ font-weight: $euiFontWeightBold;
+
+ // Create button modifiers based upon the map.
+ @each $name, $color in $euiCardSelectButtonBackgrounds {
+ &--#{$name}:enabled {
+ background-color: $color;
+
+ // Custom success text color since it doesn't exist on EuiButtonEmpty
+ @if ($name == 'success') {
+ color: makeHighContrastColor($euiColorSuccess, $color);
+ }
+ }
+ }
+
+ &:disabled {
+ background-color: $euiPageBackgroundColor;
+ }
+}
diff --git a/src/components/card/_index.scss b/src/components/card/_index.scss
index c68691f95f2..316afc276c2 100644
--- a/src/components/card/_index.scss
+++ b/src/components/card/_index.scss
@@ -1 +1,5 @@
+@import 'variables';
+@import 'mixins';
@import 'card';
+@import 'card_graphic';
+@import 'card_select';
diff --git a/src/components/card/_mixins.scss b/src/components/card/_mixins.scss
new file mode 100644
index 00000000000..af0aa29439a
--- /dev/null
+++ b/src/components/card/_mixins.scss
@@ -0,0 +1,11 @@
+@mixin euiCardBottomNodePosition {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ height: $euiCardGraphicHeight !important; // sass-lint:disable-line no-important -- To override .euiButtonEmpty--xSmall
+ width: 100%;
+ overflow: hidden;
+ // Subtract 1px from border radius since it sits inside a border-radius
+ border-bottom-left-radius: $euiBorderRadius - 1px;
+ border-bottom-right-radius: $euiBorderRadius - 1px;
+}
diff --git a/src/components/card/_variables.scss b/src/components/card/_variables.scss
new file mode 100644
index 00000000000..cc4fb8d85fe
--- /dev/null
+++ b/src/components/card/_variables.scss
@@ -0,0 +1,21 @@
+@import '../panel/variables';
+
+$euiCardSpacing: map-get($euiPanelPaddingModifiers, 'paddingMedium');
+$euiCardTitleSize: 18px; // Hardcoded pixel value for theme parity.
+$euiCardGraphicHeight: 40px; // Actual height of the svg used in EuiCardGraphic
+
+$euiCardSelectButtonBorders: (
+ text: $euiColorSuccess, // Default for selected
+ primary: $euiColorPrimary,
+ success: $euiColorSuccess,
+ danger: $euiColorDanger,
+ ghost: $euiColorDarkShade,
+);
+
+$euiCardSelectButtonBackgrounds: (
+ text: $euiColorLightestShade, // Default for unselected
+ primary: tintOrShade($euiColorPrimary, 90%, 70%),
+ success: tintOrShade($euiColorSuccess, 90%, 70%),
+ danger: tintOrShade($euiColorDanger, 90%, 70%),
+ ghost: $euiColorDarkShade,
+);
diff --git a/src/components/card/card.js b/src/components/card/card.js
index d8b6d1d5a89..a3de82e19c7 100644
--- a/src/components/card/card.js
+++ b/src/components/card/card.js
@@ -6,6 +6,8 @@ import { getSecureRelForTarget } from '../../services';
import { EuiText } from '../text';
import { EuiTitle } from '../title';
import { EuiBetaBadge } from '../badge/beta_badge';
+import { EuiCardSelect, EuiCardSelectProps, euiCardSelectableColor } from './card_select';
+import makeId from '../form/form_row/make_id';
const textAlignToClassNameMap = {
left: 'euiCard--leftAligned',
@@ -55,8 +57,12 @@ export const EuiCard = ({
betaBadgeTitle,
layout,
bottomGraphic,
+ selectable,
...rest,
}) => {
+ const selectableColorClass = selectable ?
+ `euiCard--isSelectable--${euiCardSelectableColor(selectable.color, selectable.isSelected)}` : undefined;
+
const classes = classNames(
'euiCard',
textAlignToClassNameMap[textAlign],
@@ -66,10 +72,15 @@ export const EuiCard = ({
'euiCard--hasBetaBadge': betaBadgeLabel,
'euiCard--hasIcon': icon,
'euiCard--hasBottomGraphic': bottomGraphic,
+ 'euiCard--isSelectable': selectable,
+ 'euiCard-isSelected': selectable && selectable.isSelected,
},
+ selectableColorClass,
className,
);
+ const ariaId = makeId();
+
let secureRel;
if (href) {
secureRel = getSecureRelForTarget({ href, target, rel });
@@ -135,6 +146,15 @@ export const EuiCard = ({
);
}
+ let optionalSelectButton;
+ if (selectable) {
+ if (bottomGraphic) {
+ console.warn('EuiCard cannot support both `bottomGraphic` and `selectable`. It will ignore the bottomGraphic.');
+ }
+
+ optionalSelectButton = {description}