lucid-ui
Version:
A UI component library from AppNexus.
445 lines (396 loc) • 21.6 kB
JavaScript
import _times from "lodash/times";
import _isNil from "lodash/isNil";
import _toString from "lodash/toString";
import _isEmpty from "lodash/isEmpty";
import _assign from "lodash/assign";
import _pick from "lodash/pick";
import _clamp from "lodash/clamp";
import _keys from "lodash/keys";
import _isNull from "lodash/isNull";
import _reduce from "lodash/reduce";
import _size from "lodash/size";
import _get from "lodash/get";
import _partial from "lodash/partial";
import _omit from "lodash/omit";
import _map from "lodash/map";
import _every from "lodash/every";
import _slice from "lodash/slice";
import _compact from "lodash/compact";
import _flow from "lodash/flow";
import _some from "lodash/some";
import _noop from "lodash/noop";
var _templateObject, _templateObject2;
function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import React, { useState } from 'react';
import PropTypes from 'react-peek/prop-types';
import { lucidClassNames } from '../../util/style-helpers';
import { findTypes, filterTypes, getFirst, omitProps } from '../../util/component-types';
import Checkbox from '../Checkbox/Checkbox';
import EmptyStateWrapper from '../EmptyStateWrapper/EmptyStateWrapper';
import ScrollTable from '../ScrollTable/ScrollTable';
import Table from '../Table/Table';
var Thead = ScrollTable.Thead,
Tbody = ScrollTable.Tbody,
Tr = ScrollTable.Tr,
Th = ScrollTable.Th,
Td = ScrollTable.Td;
var cx = lucidClassNames.bind('&-DataTable');
var cxe = lucidClassNames.bind('&-DataTable-EmptyStateWrapper');
var SELECTOR_COLUMN_WIDTH = 41;
var any = PropTypes.any,
func = PropTypes.func,
number = PropTypes.number,
object = PropTypes.object,
stringProps = PropTypes.string,
bool = PropTypes.bool,
arrayOf = PropTypes.arrayOf;
var defaultProps = {
emptyCellText: '--',
isActionable: false,
isSelectable: false,
onRowClick: _noop,
onSelect: _noop,
onSelectAll: _noop,
onSort: _noop,
minRows: 10,
hasFixedHeader: false,
fixedColumnCount: 0
};
var defaultState = {
// Represents the actively changing width as the cell is resized.
activeWidth: {}
};
export var DataTable = function DataTable(props) {
var _useState = useState(defaultState),
_useState2 = _slicedToArray(_useState, 2),
state = _useState2[0],
setState = _useState2[1];
var fixedHeaderUnfixedColumnsRef;
var fixedBodyFixedColumnsRef;
var handleSelect = function handleSelect(rowIndex, _ref) {
var event = _ref.event;
var data = props.data,
onSelect = props.onSelect;
onSelect(data[rowIndex], rowIndex, {
props: props,
event: event
});
};
var handleSelectAll = function handleSelectAll(_ref2) {
var event = _ref2.event;
var onSelectAll = props.onSelectAll;
onSelectAll({
props: props,
event: event
});
};
var handleRowClick = function handleRowClick(rowIndex, event) {
var data = props.data,
onRowClick = props.onRowClick;
var targetTagName = event.target.tagName.toLowerCase();
if (targetTagName === 'td' || targetTagName === 'tr') {
onRowClick(data[rowIndex], rowIndex, {
props: props,
event: event
});
}
};
var handleSort = function handleSort(field, event) {
var onSort = props.onSort;
event.stopPropagation();
event.preventDefault();
event.bubbles = false;
onSort(field, {
props: props,
event: event
});
};
var handleFixedBodyUnfixedColumnsScroll = function handleFixedBodyUnfixedColumnsScroll(event) {
fixedHeaderUnfixedColumnsRef && (fixedHeaderUnfixedColumnsRef.scrollLeft = event.target.scrollLeft);
fixedBodyFixedColumnsRef && (fixedBodyFixedColumnsRef.scrollTop = event.target.scrollTop);
};
var handleResize = function handleResize(columnWidth, _ref3) {
var field = _ref3.props.field;
// setting latest column width to Tbody
setState(function (state) {
return {
activeWidth: _objectSpread(_objectSpread({}, state.activeWidth), {}, _defineProperty({}, field, columnWidth))
};
});
};
var renderHeader = function renderHeader(startColumn, endColumn, childComponentElements, flattenedColumns) {
var isSelectable = props.isSelectable,
data = props.data;
var hasGroupedColumns = _some(childComponentElements, function (childComponentElement) {
return childComponentElement.type === DataTable.ColumnGroup;
});
var columnSlicer = _flow(_compact, function (columns) {
return _slice(columns, startColumn, endColumn);
});
var allSelected = _every(data, 'isSelected');
return /*#__PURE__*/React.createElement(Thead, null, /*#__PURE__*/React.createElement(Tr, null, columnSlicer([isSelectable ? /*#__PURE__*/React.createElement(Th, {
key: cx('&-row-selector'),
rowSpan: hasGroupedColumns ? 2 : null,
width: SELECTOR_COLUMN_WIDTH
}, /*#__PURE__*/React.createElement(Checkbox, {
isDisabled: !data || !data.length,
isSelected: !!data && data.length > 0 && allSelected,
isIndeterminate: !allSelected && !!data.find(function (d) {
return d.isSelected;
}),
onSelect: handleSelectAll
})) : null].concat(_map(childComponentElements, function (_ref4, index) {
var props = _ref4.props,
type = _ref4.type;
return type === DataTable.Column ? /*#__PURE__*/React.createElement(Th, _extends({
onResize: props.isResizable ? handleResize : null
}, _omit(props, ['children', 'title']), {
onClick: DataTable.shouldColumnHandleSort(props) ? _partial(handleSort, props.field) : null,
rowSpan: hasGroupedColumns ? 2 : null,
field: props.field || index,
key: _get(props, 'field', index)
}), props.title || props.children) : /*#__PURE__*/React.createElement(Th, _extends({
colSpan: _size(filterTypes(props.children, DataTable.Column))
}, _omit(props, ['field', 'children', 'width', 'title']), {
key: _get(props, 'field', index)
}), props.title || props.children);
})))), hasGroupedColumns ? /*#__PURE__*/React.createElement(Tr, null, _reduce(flattenedColumns, function (acc, _ref5, index) {
var columnProps = _ref5.props,
columnGroupProps = _ref5.columnGroupProps;
return acc.concat(_isNull(columnGroupProps) ? [] : [/*#__PURE__*/React.createElement(Th, _extends({}, omitProps(columnProps, undefined, _keys(DataTable.Column.propTypes), false), {
onClick: DataTable.shouldColumnHandleSort(columnProps) ? _partial(handleSort, columnProps.field) : null,
style: {
width: columnProps.width
},
key: _get(columnProps, 'field', index)
}), columnProps.title || columnProps.children)]);
}, [])) : null);
};
var renderBody = function renderBody(startColumn, endColumn, flattenedColumns) {
var data = props.data,
isSelectable = props.isSelectable,
isActionable = props.isActionable,
minRows = props.minRows,
emptyCellText = props.emptyCellText,
fixedRowHeight = props.fixedRowHeight,
truncateContent = props.truncateContent;
var fillerRowCount = _clamp(minRows - _size(data), 0, Infinity);
var isFixedColumn = endColumn < Infinity;
var columnSlicer = _flow(_compact, function (columns) {
return _slice(columns, startColumn, endColumn);
});
return /*#__PURE__*/React.createElement(Tbody, null, _map(data, function (row, index) {
return /*#__PURE__*/React.createElement(Tr, _extends({}, _pick(row, ['isDisabled', 'isActive', 'isSelected']), {
isActionable: isActionable,
onClick: _partial(handleRowClick, index),
key: 'row' + index,
style: _assign(row.style, fixedRowHeight ? {
height: fixedRowHeight
} : {})
}), columnSlicer([isSelectable ? /*#__PURE__*/React.createElement(Td, {
key: cx('&-row-selector'),
width: SELECTOR_COLUMN_WIDTH,
hasBorderRight: isFixedColumn && flattenedColumns.length === 0
}, /*#__PURE__*/React.createElement(Checkbox, {
isSelected: row.isSelected,
onSelect: _partial(handleSelect, index)
})) : null].concat(_map(flattenedColumns, function (_ref6, columnIndex) {
var columnProps = _ref6.props;
var cellValue = _get(row, columnProps.field);
var isEmpty = _isEmpty(_toString(cellValue));
return /*#__PURE__*/React.createElement(Td, _extends({}, _omit(columnProps, ['field', 'children', 'width', 'title', 'isSortable', 'isSorted', 'isResizable']), {
hasBorderRight: !_isNil(columnProps.hasBorderRight) ? columnProps.hasBorderRight : isFixedColumn && columnIndex + 1 + (isSelectable ? 1 : 0) === endColumn,
style: {
width: // @ts-ignore
state.activeWidth[columnProps.field || columnIndex] || columnProps.width
},
key: 'row' + index + _get(columnProps, 'field', columnIndex),
truncateContent: truncateContent
}), isEmpty ? emptyCellText : cellValue);
}))));
}), _times(fillerRowCount, function (index) {
return /*#__PURE__*/React.createElement(Tr, {
isDisabled: true,
key: 'row' + index,
style: {
height: fixedRowHeight || '32px'
}
}, columnSlicer([isSelectable ? /*#__PURE__*/React.createElement(Td, {
key: cx('&-row-selector')
}) : null].concat(_map(flattenedColumns, function (_ref7, columnIndex) {
var columnProps = _ref7.props;
return /*#__PURE__*/React.createElement(Td, _extends({}, _omit(columnProps, ['field', 'children', 'width', 'title', 'isSortable', 'isSorted', 'isResizable']), {
style: {
width: columnProps.width
},
key: 'row' + index + _get(columnProps, 'field', columnIndex)
}));
}))));
}));
};
var className = props.className,
data = props.data,
isFullWidth = props.isFullWidth,
isLoading = props.isLoading,
style = props.style,
hasFixedHeader = props.hasFixedHeader,
fixedColumnCount = props.fixedColumnCount,
anchorMessage = props.anchorMessage,
passThroughs = _objectWithoutProperties(props, ["className", "data", "isFullWidth", "isLoading", "style", "hasFixedHeader", "fixedColumnCount", "anchorMessage"]);
var childComponentElements = findTypes(props, [DataTable.Column, DataTable.ColumnGroup]);
var flattenedColumns = _reduce(childComponentElements, function (acc, childComponentElement) {
if (childComponentElement.type === DataTable.Column) {
return acc.concat([{
props: childComponentElement.props,
columnGroupProps: null
}]);
}
if (childComponentElement.type === DataTable.ColumnGroup) {
return acc.concat(_map(findTypes(childComponentElement.props, DataTable.Column), function (columnChildComponent) {
return {
props: columnChildComponent.props,
columnGroupProps: childComponentElement.props
};
}));
}
}, []);
var emptyStateWrapper = getFirst(props, DataTable.EmptyStateWrapper) || /*#__PURE__*/React.createElement(DataTable.EmptyStateWrapper, {
Title: "No items found.",
Body: "Try creating a new object or removing a filter."
});
var emptyStateWrapperClassName = cxe({
'&-has-fixed-header': hasFixedHeader
}, emptyStateWrapper.props.className);
return /*#__PURE__*/React.createElement(EmptyStateWrapper, _extends({}, emptyStateWrapper.props, {
isEmpty: _isEmpty(data),
isLoading: isLoading,
className: emptyStateWrapperClassName,
anchorMessage: anchorMessage
}), emptyStateWrapper.props.children, hasFixedHeader ? /*#__PURE__*/React.createElement("div", {
className: cx('&-fixed')
}, /*#__PURE__*/React.createElement("div", {
className: cx('&-fixed-header')
}, /*#__PURE__*/React.createElement("div", {
className: cx('&-fixed-header-fixed-columns')
}, fixedColumnCount > 0 ? /*#__PURE__*/React.createElement(Table, _extends({}, omitProps(passThroughs, undefined, _keys(DataTable.propTypes), false), {
style: style,
className: cx('&-fixed-header-fixed-columns-Table')
}), renderHeader(0, fixedColumnCount, childComponentElements, flattenedColumns)) : null), /*#__PURE__*/React.createElement("div", {
className: cx('&-fixed-header-unfixed-columns'),
ref: function ref(_ref8) {
return fixedHeaderUnfixedColumnsRef = _ref8;
}
}, /*#__PURE__*/React.createElement(Table, _extends({}, omitProps(passThroughs, undefined, _keys(DataTable.propTypes), false), {
style: style,
className: cx('&-fixed-header-unfixed-columns-Table')
}), renderHeader(fixedColumnCount, Infinity, childComponentElements, flattenedColumns)))), /*#__PURE__*/React.createElement("div", {
className: cx('&-fixed-body')
}, /*#__PURE__*/React.createElement("div", {
className: cx('&-fixed-body-fixed-columns'),
ref: function ref(_ref9) {
return fixedBodyFixedColumnsRef = _ref9;
}
}, fixedColumnCount > 0 ? /*#__PURE__*/React.createElement(Table, _extends({}, omitProps(passThroughs, undefined, _keys(DataTable.propTypes), false), {
style: style,
className: cx('&-fixed-body-fixed-columns-Table'),
hasWordWrap: false
/* try to protect against vertical overflow */
}), renderBody(0, fixedColumnCount, flattenedColumns)) : null), /*#__PURE__*/React.createElement("div", {
onScroll: handleFixedBodyUnfixedColumnsScroll,
className: cx('&-fixed-body-unfixed-columns')
}, /*#__PURE__*/React.createElement("span", {
className: cx('&-fixed-body-unfixed-columns-shadow')
}), /*#__PURE__*/React.createElement(Table, _extends({}, omitProps(passThroughs, undefined, _keys(DataTable.propTypes), false), {
style: style,
className: cx('&-fixed-body-unfixed-columns-Table'),
hasWordWrap: false
/* try to protect against vertical overflow */
}), renderBody(fixedColumnCount, Infinity, flattenedColumns))))) : /*#__PURE__*/React.createElement(ScrollTable, _extends({
style: style,
tableWidth: isFullWidth ? '100%' : undefined
}, omitProps(passThroughs, undefined, _keys(DataTable.propTypes), false), {
className: cx('&', {
'&-full-width': isFullWidth
}, className)
}), renderHeader(0, Infinity, childComponentElements, flattenedColumns), renderBody(0, Infinity, flattenedColumns)));
};
DataTable.displayName = 'DataTable';
DataTable.propTypes = {
className: stringProps(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n\t\tClass names that are appended to the defaults.\n\t"]))),
data: arrayOf(object),
emptyCellText: stringProps(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n\t\tThe text to display in cells which have no data.\n\t"]))),
isActionable: bool,
isFullWidth: bool,
isLoading: bool,
isSelectable: bool,
anchorMessage: bool,
style: object,
minRows: number,
onRowClick: func,
onSelect: func,
onSelectAll: func,
onSort: func,
hasFixedHeader: bool,
fixedColumnCount: number,
fixedRowHeight: number,
truncateContent: bool,
Column: any,
ColumnGroup: any
};
DataTable.defaultProps = defaultProps;
DataTable.peek = {
description: "\n\t\t`DataTable` provides a simple abstraction over the `Table`\n\t\tcomponent to make it easier to define data-driven tables and render an\n\t\tarray of objects.\n\t",
notes: {
overview: "\n\t\t\t`DataTable` provides a simple abstraction over the `Table`\n\t\t\tcomponent to make it easier to define data-driven tables and render an\n\t\t\tarray of objects.\n\t\t",
intendedUse: "\n\t\t\t`DataTable` is optimized for our two main uses, full page and in-line tables.\n\n\t\t\t**Full page table**\n\t\t\t\n\t\t\tTables that cover the entire page, or are the main focus on the page. Generally used for managing and monitoring objects.\n\t\t\t\n\t\t\t**In-line tables**\n\t\t\t\n\t\t\tTables insides containers such as `Dialog` or `Panel`. Generally used for details panels and actions dialogs.\n\t\t\t\t\t\t\t\t\n\t\t\t**Styling notes**\n\t\t\t\n\t\t\t- Preferred column alignment shown in `basic` example, column header alignment should match column content\n\t\t\t\t- strings left-aligned\n\t\t\t\t- currency right-aligned\n\t\t\t\t- icons/buttons centered\n\t\t\t- Use grey footer for full page tables, `hasLightFooter={false}`\n\t\t\t- Use white footer for in-line tables, `hasLightFooter={true}`\n\t\t",
technicalRecommendations: "\n\t\t\t- There is a pre-styled state for tables with no data, see the `empty` example\n\t\t\t- There should be no row hover state if the rows are not clickable, see example (?)\n\t\t"
},
categories: ['table'],
madeFrom: ['Checkbox', 'EmptyStateWrapper', 'ScrollTable']
};
DataTable.EmptyStateWrapper = EmptyStateWrapper;
// type IColumnProps = Overwrite<typeof Th, IColumnPropsRaw>;
var Column = function Column(props) {
return null;
};
Column.displayName = 'DataTable.Column';
Column.peek = {
description: "\n\t\tRenders a `Th` for the table. It accepts all the props of `Table.Th`\n\t"
};
Column.propTypes = {
field: stringProps.isRequired,
title: stringProps,
isResizable: bool
};
DataTable.Column = Column;
var ColumnGroup = function ColumnGroup(_ref10) {
var children = _ref10.children;
return children;
};
ColumnGroup.displayName = 'DataTable.ColumnGroup';
ColumnGroup.peek = {
description: "\n\t\tRenders a group of `Th`s. It accepts all the props of Table.Th\n\t"
};
ColumnGroup.propTypes = {
title: stringProps
};
ColumnGroup.defaultProps = {
align: 'center'
};
DataTable.ColumnGroup = ColumnGroup;
DataTable.shouldColumnHandleSort = function (column) {
return _isNil(column.isSortable) ? column.isSorted : column.isSortable;
};
export default DataTable;