From 08f2d453b51e6eb7bf29c53c1d28623ed2837711 Mon Sep 17 00:00:00 2001
From: Caroline Horn <549577+cchaos@users.noreply.github.com>
Date: Wed, 12 Sep 2018 14:09:26 -0400
Subject: [PATCH] Table actions visibility (#1103)
---
CHANGELOG.md | 8 ++-
src-docs/src/views/tables/actions/actions.js | 55 ++++++++++------
.../views/tables/actions/actions_section.js | 8 ++-
src-docs/src/views/tool_tip/tool_tip.js | 10 +++
.../__snapshots__/basic_table.test.js.snap | 63 +++++++++++++++++--
.../collapsed_item_actions.test.js.snap | 28 +++++----
.../default_item_action.test.js.snap | 52 ++++++++-------
.../expanded_item_actions.test.js.snap | 2 +
src/components/basic_table/basic_table.js | 24 ++++---
.../basic_table/collapsed_item_actions.js | 13 +++-
.../basic_table/default_item_action.js | 41 +++++++-----
.../basic_table/expanded_item_actions.js | 23 ++++++-
src/components/table/_responsive.scss | 18 +++---
src/components/table/_table.scss | 32 ++++++++--
src/components/tool_tip/_tool_tip.scss | 5 ++
src/components/tool_tip/tool_tip.js | 15 +++++
16 files changed, 294 insertions(+), 103 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 190aa3bf874..3c27c1a6c6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
## [`master`](https://github.com/elastic/eui/tree/master)
-No public interface changes since `3.11.0`.
+- Added `delay` prop to `EuiToolTip` ([#1103](https://github.com/elastic/eui/pull/1103))
+
+**Breaking changes**
+
+- `EuiBasicTable` now shows up to 2 actions before condensing to all popover, but still displaying the top/primary 2 actions as well ([#1103](https://github.com/elastic/eui/pull/1103))
+- `EuiBasicTable` will automatically add `hasActions` and `isSelectable` to allow proper responsive style handling, but are still overridable ([#1103](https://github.com/elastic/eui/pull/1103))
+
## [`3.11.0`](https://github.com/elastic/eui/tree/v3.11.0)
diff --git a/src-docs/src/views/tables/actions/actions.js b/src-docs/src/views/tables/actions/actions.js
index f4d868f2126..cc5f5e909af 100644
--- a/src-docs/src/views/tables/actions/actions.js
+++ b/src-docs/src/views/tables/actions/actions.js
@@ -11,7 +11,6 @@ import {
EuiFlexItem,
EuiSwitch,
EuiSpacer,
- EuiText,
} from '../../../../../src/components';
/*
@@ -93,13 +92,15 @@ export class Table extends Component {
}
return (
-
- Delete {selectedItems.length} Users
-
+
+
+ Delete {selectedItems.length} Users
+
+
);
}
@@ -145,31 +146,47 @@ export class Table extends Component {
? [{
render: (item) => {
return (
- this.cloneUser(item)}>
+ this.cloneUser(item)}>
Clone
-
+
);
}
}, {
render: (item) => {
return (
- this.deleteUser(item)}>
+ this.deleteUser(item)}>
Delete
-
+
);
}
}]
: [{
name: 'Clone',
- description: 'Clone this person',
+ description: 'Clone this user',
icon: 'copy',
onClick: this.cloneUser
}, {
name: 'Delete',
- description: 'Delete this person',
+ description: 'Delete this user',
icon: 'trash',
color: 'danger',
- onClick: this.deleteUser
+ type: 'icon',
+ onClick: this.deleteUser,
+ isPrimary: true,
+ }, {
+ name: 'Edit',
+ isPrimary: true,
+ description: 'Edit this user',
+ icon: 'pencil',
+ type: 'icon',
+ onClick: () => {},
+ }, {
+ name: 'Share',
+ isPrimary: true,
+ description: 'Share this user',
+ icon: 'share',
+ type: 'icon',
+ onClick: () => {},
}];
} else {
actions = customAction
@@ -187,7 +204,7 @@ export class Table extends Component {
}]
: [{
name: 'Delete',
- description: 'Delete this person',
+ description: 'Delete this user',
icon: 'trash',
color: 'danger',
type: 'icon',
@@ -270,7 +287,6 @@ export class Table extends Component {
return (
- {deleteButton}
+
+ {deleteButton}
@@ -296,8 +314,7 @@ export class Table extends Component {
pagination={pagination}
sorting={sorting}
selection={selection}
- isSelectable={true}
- hasActions={true}
+ hasActions={customAction ? false : true}
onChange={this.onTableChange}
/>
diff --git a/src-docs/src/views/tables/actions/actions_section.js b/src-docs/src/views/tables/actions/actions_section.js
index 3609c93edc0..640c54ffb45 100644
--- a/src-docs/src/views/tables/actions/actions_section.js
+++ b/src-docs/src/views/tables/actions/actions_section.js
@@ -32,11 +32,13 @@ export const section = {
-
- There can only be a single action tool visible per row. When more than one action is defined,
- they will collapse under a single popover represented by the gear icon.
+ There can only be up to 2 actions visible per row. When more than two actions are defined,
+ the first 2
isPrimary
actions will stay visible, an ellipses icon button will hold all actions
+ in a single popover.
-
- Actions are only visible when the user hovers over the row with the mouse.
+ Actions are change opacity when user hovers over the row with the mouse. When more than 2 actions are supplied,
+ only the ellipses icon button stays visible at all times.
diff --git a/src-docs/src/views/tool_tip/tool_tip.js b/src-docs/src/views/tool_tip/tool_tip.js
index 58f4fbff504..2e8d25501cf 100644
--- a/src-docs/src/views/tool_tip/tool_tip.js
+++ b/src-docs/src/views/tool_tip/tool_tip.js
@@ -45,6 +45,16 @@ export default () => (
+
+ This tooltip has a long delay because it might be in a repeatable component{' '}
+
+ wink
+
+
+
This tooltip appears on the bottom of this icon:{' '}
+
+
+
}
closePopover={[Function]}
hasArrow={true}
diff --git a/src/components/basic_table/__snapshots__/default_item_action.test.js.snap b/src/components/basic_table/__snapshots__/default_item_action.test.js.snap
index dee76650464..e07983baee7 100644
--- a/src/components/basic_table/__snapshots__/default_item_action.test.js.snap
+++ b/src/components/basic_table/__snapshots__/default_item_action.test.js.snap
@@ -1,29 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DefaultItemAction render - button 1`] = `
-
- action1
-
+
+ action1
+
+
`;
exports[`DefaultItemAction render - icon 1`] = `
-
+
+
+
`;
diff --git a/src/components/basic_table/__snapshots__/expanded_item_actions.test.js.snap b/src/components/basic_table/__snapshots__/expanded_item_actions.test.js.snap
index cad74905029..811e7ae0ccf 100644
--- a/src/components/basic_table/__snapshots__/expanded_item_actions.test.js.snap
+++ b/src/components/basic_table/__snapshots__/expanded_item_actions.test.js.snap
@@ -10,6 +10,7 @@ Array [
"onClick": [Function],
}
}
+ className=""
enabled={true}
index={0}
item={
@@ -28,6 +29,7 @@ Array [
"render": [Function],
}
}
+ className=""
enabled={true}
index={1}
item={
diff --git a/src/components/basic_table/basic_table.js b/src/components/basic_table/basic_table.js
index 26db8be6c34..546ad341a56 100644
--- a/src/components/basic_table/basic_table.js
+++ b/src/components/basic_table/basic_table.js
@@ -4,6 +4,7 @@ import React, {
} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
+import { dropWhile, slice } from 'lodash';
import {
formatAuto, formatBoolean, formatDate, formatNumber, formatText, LEFT_ALIGNMENT, PropertySortType,
RIGHT_ALIGNMENT, SortDirection
@@ -63,6 +64,7 @@ const DefaultItemActionType = PropTypes.shape({
onClick: PropTypes.func.isRequired, // (item) => void,
available: PropTypes.func, // (item) => boolean;
enabled: PropTypes.func, // (item) => boolean;
+ isPrimary: PropTypes.bool,
icon: PropTypes.oneOfType([ // required when type is 'icon'
PropTypes.oneOf(ICON_TYPES),
PropTypes.func // (item) => oneOf(ICON_TYPES)
@@ -76,7 +78,8 @@ const DefaultItemActionType = PropTypes.shape({
const CustomItemActionType = PropTypes.shape({
render: PropTypes.func.isRequired, // (item, enabled) => PropTypes.node;
available: PropTypes.func, // (item) => boolean;
- enabled: PropTypes.func // (item) => boolean;
+ enabled: PropTypes.func, // (item) => boolean;
+ isPrimary: PropTypes.bool,
});
const SupportedItemActionType = PropTypes.oneOfType([
@@ -566,13 +569,17 @@ export class EuiBasicTable extends Component {
getItemId(selectedItem, itemIdCallback) === itemId
));
+ let calculatedHasSelection;
if (selection) {
cells.push(this.renderItemSelectionCell(itemId, item, selected));
+ calculatedHasSelection = true;
}
+ let calculatedHasActions;
columns.forEach((column, columnIndex) => {
if (column.actions) {
cells.push(this.renderItemActionsCell(itemId, item, column, columnIndex, rowIndex));
+ calculatedHasActions = true;
} else if (column.field) {
cells.push(this.renderItemFieldDataCell(itemId, item, column, columnIndex));
} else {
@@ -607,9 +614,9 @@ export class EuiBasicTable extends Component {
@@ -660,7 +667,10 @@ export class EuiBasicTable extends Component {
this.state.selection.length === 0 && (!action.enabled || action.enabled(item));
let actualActions = column.actions;
- if (column.actions.length > 1) {
+ if (column.actions.length > 2) {
+
+ // if any of the actions `isPrimary`, add them inline as well, but only the first 2
+ actualActions = slice(dropWhile(column.actions, function (o) { return !o.isPrimary; }), 0, 2);
// if we have more than 1 action, we don't show them all in the cell, instead we
// put them all in a popover tool. This effectively means we can only have a maximum
@@ -668,9 +678,9 @@ export class EuiBasicTable extends Component {
//
// here we create a single custom action that triggers the popover with all the configured actions
- actualActions = [
+ actualActions.push(
{
- name: 'Actions',
+ name: 'All actions',
render: (item) => {
return (
);
+ const withTooltip = !allDisabled && (
+
+ {popoverButton}
+
+ );
+
return (
action.onClick(item);
const color = this.resolveActionColor();
const icon = this.resolveActionIcon();
+
+ let button;
if (action.type === 'icon') {
if (!icon) {
throw new Error(`Cannot render item action [${action.name}]. It is configured to render as an icon but no
icon is provided. Make sure to set the 'icon' property of the action`);
}
- return (
+ button = (
);
+ } else {
+ button = (
+
+ {action.name}
+
+ );
}
- return (
-
- {action.name}
-
- );
+ return (enabled && action.description) ? (
+
+ {button}
+
+ ) : button;
}
resolveActionIcon() {
diff --git a/src/components/basic_table/expanded_item_actions.js b/src/components/basic_table/expanded_item_actions.js
index d9bb928b97e..fa40eadb403 100644
--- a/src/components/basic_table/expanded_item_actions.js
+++ b/src/components/basic_table/expanded_item_actions.js
@@ -1,22 +1,39 @@
import React from 'react';
+import classNames from 'classnames';
import { DefaultItemAction } from './default_item_action';
import { CustomItemAction } from './custom_item_action';
-export const ExpandedItemActions = ({ actions, itemId, item, actionEnabled, className }) => {
+export const ExpandedItemActions = ({
+ actions,
+ itemId,
+ item,
+ actionEnabled,
+ className,
+}) => {
+
+ const moreThanThree = actions.length > 2;
return actions.reduce((tools, action, index) => {
+
const available = action.available ? action.available(item) : true;
if (!available) {
return tools;
}
+
const enabled = actionEnabled(action);
+
const key = `item_action_${itemId}_${index}`;
+
+ const classes = classNames(className, {
+ 'expandedItemActions__completelyHide': moreThanThree && index < 2,
+ });
+
if (action.render) {
// custom action has a render function
tools.push(
*:not(:first-child) {
+ margin-left: $euiSizeS;
+ }
+}
+
+.euiTableRow-hasActions {
+ .euiTableCellContent--showOnHover {
+ .euiTableCellContent__hoverItem {
+ flex-shrink: 0;
+ opacity: 0.7;
+ filter: grayscale(100%);
+ transition: all $euiAnimSpeedNormal $euiAnimSlightResistance;
+ }
- .euiTableRow:hover &,
- &:hover, &:focus {
+ .expandedItemActions__completelyHide,
+ .expandedItemActions__completelyHide:disabled,
+ .expandedItemActions__completelyHide:disabled:hover,
+ .expandedItemActions__completelyHide:disabled:focus,
+ .euiTableRow:hover & .expandedItemActions__completelyHide:disabled {
+ filter: grayscale(0%);
+ opacity: 0;
+ }
+ }
+
+ &:hover .euiTableCellContent--showOnHover .euiTableCellContent__hoverItem:not(:disabled) {
+ &,
+ &:hover,
+ &:focus {
opacity: 1;
+ filter: grayscale(0%);
}
}
}
diff --git a/src/components/tool_tip/_tool_tip.scss b/src/components/tool_tip/_tool_tip.scss
index 100e7818c5e..3d882c51fc4 100644
--- a/src/components/tool_tip/_tool_tip.scss
+++ b/src/components/tool_tip/_tool_tip.scss
@@ -16,6 +16,11 @@
animation: euiToolTipTop $euiAnimSpeedSlow ease-out $euiAnimSpeedNormal forwards;
z-index: $euiZLevel9;
+ // Animation delays
+ &.euiToolTip--delayLong {
+ animation-delay: $euiAnimSpeedNormal * 5;
+ }
+
// Custom sizing
$arrowSize: $euiSizeM;
$arrowPlusSize: (($arrowSize/2) + 1px) * -1; /* 1 */
diff --git a/src/components/tool_tip/tool_tip.js b/src/components/tool_tip/tool_tip.js
index ed9ae1e20d8..78183f23d22 100644
--- a/src/components/tool_tip/tool_tip.js
+++ b/src/components/tool_tip/tool_tip.js
@@ -22,6 +22,13 @@ const positionsToClassNameMap = {
export const POSITIONS = Object.keys(positionsToClassNameMap);
+const delayToClassNameMap = {
+ regular: null,
+ long: 'euiToolTip--delayLong',
+};
+
+export const DELAY = Object.keys(delayToClassNameMap);
+
const DEFAULT_TOOLTIP_STYLES = {
// position the tooltip content near the top-left
// corner of the window so it can't create scrollbars
@@ -161,6 +168,7 @@ export class EuiToolTip extends Component {
anchorClassName,
content,
title,
+ delay,
...rest
} = this.props;
@@ -169,6 +177,7 @@ export class EuiToolTip extends Component {
const classes = classNames(
'euiToolTip',
positionsToClassNameMap[this.state.calculatedPosition],
+ delayToClassNameMap[delay],
className
);
@@ -254,6 +263,11 @@ EuiToolTip.propTypes = {
*/
position: PropTypes.oneOf(POSITIONS),
+ /**
+ * Delay before showing tooltip. Good for repeatable items.
+ */
+ delay: PropTypes.oneOf(DELAY),
+
/**
* Passes onto the tooltip itself, not the trigger.
*/
@@ -267,4 +281,5 @@ EuiToolTip.propTypes = {
EuiToolTip.defaultProps = {
position: 'top',
+ delay: 'regular',
};