UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

700 lines (585 loc) 25.4 kB
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } import _reduce from "lodash/reduce"; import _set from "lodash/set"; import _has from "lodash/has"; import _isNull from "lodash/isNull"; import _forEach from "lodash/forEach"; import _isUndefined from "lodash/isUndefined"; import _first from "lodash/first"; import _findIndex from "lodash/findIndex"; import _isNumber from "lodash/isNumber"; import _isEmpty from "lodash/isEmpty"; import _map from "lodash/map"; import _property from "lodash/property"; import _assign from "lodash/assign"; import _isString from "lodash/isString"; import _endsWith from "lodash/endsWith"; import _get from "lodash/get"; import _isNil from "lodash/isNil"; import _keys from "lodash/keys"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 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 _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 _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; } import React from 'react'; import PropTypes from 'react-peek/prop-types'; import { lucidClassNames } from '../../util/style-helpers'; import { filterTypes, omitProps } from '../../util/component-types'; import ArrowIcon from '../Icon/ArrowIcon/ArrowIcon'; import DragCaptureZone from '../DragCaptureZone/DragCaptureZone'; var cx = lucidClassNames.bind('&-Table'); var any = PropTypes.any, bool = PropTypes.bool, func = PropTypes.func, node = PropTypes.node, number = PropTypes.number, object = PropTypes.object, string = PropTypes.string, oneOf = PropTypes.oneOf, oneOfType = PropTypes.oneOfType; /** Thead <Thead>: The Table Head component */ var Thead = function Thead(props) { var children = props.children, className = props.className, passThroughs = _objectWithoutProperties(props, ["children", "className"]); return /*#__PURE__*/React.createElement("thead", _extends({}, omitProps(passThroughs, undefined, _keys(Thead.propTypes)), { className: cx('&-Thead', className) }), renderRowsWithIdentifiedEdges(filterTypes(children, Tr), Th)); }; Thead.displayName = 'Table.Thead'; Thead.peek = { description: "\n\t\t`Thead` renders <thead>.\n\t" }; Thead.propTypes = { className: any, children: node }; /** Tbody <Tbody>: The Table Body component */ var Tbody = function Tbody(props) { var children = props.children, className = props.className, passThroughs = _objectWithoutProperties(props, ["children", "className"]); return /*#__PURE__*/React.createElement("tbody", _extends({}, omitProps(passThroughs, undefined, _keys(Tbody.propTypes)), { className: cx('&-Tbody', className) }), renderRowsWithIdentifiedEdges(filterTypes(children, Tr), Td)); }; Tbody.displayName = 'Table.Tbody'; Tbody.peek = { description: "\n\t\t`Tbody` renders <tbody>.\n\t" }; Tbody.propTypes = { className: any, children: node }; /** Tr <Tr>: The Table Row component */ var Tr = function Tr(props) { var className = props.className, children = props.children, isDisabled = props.isDisabled, isSelected = props.isSelected, isActive = props.isActive, passThroughs = _objectWithoutProperties(props, ["className", "children", "isDisabled", "isSelected", "isActive"]); return /*#__PURE__*/React.createElement("tr", _extends({}, omitProps(passThroughs, undefined, _keys(Tr.propTypes).concat(['isActionable'])), { className: cx('&-Tr', { '&-is-disabled': isDisabled, '&-is-selected': isSelected, '&-is-active': isActive }, className) }), children); }; Tr.defaultProps = { isDisabled: false, isSelected: false, isActive: false }; Tr.displayName = 'Table.Tr'; Tr.peek = { description: "\n\t\t`Tr` renders <tr>.\n\t" }; Tr.propTypes = { children: node, className: any, isDisabled: bool, isSelected: bool, isActive: bool }; /** Th <Th>: The Table Header Cell components */ export var Th = /*#__PURE__*/function (_React$Component) { _inherits(Th, _React$Component); var _super = _createSuper(Th); function Th() { var _this; _classCallCheck(this, Th); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _super.call.apply(_super, [this].concat(args)); _defineProperty(_assertThisInitialized(_this), "rootRef", /*#__PURE__*/React.createRef()); _defineProperty(_assertThisInitialized(_this), "state", { // Represents the actively changing width as the cell is resized. activeWidth: _this.props.width || null, // Indicates if a `width` prop was explicitly provided. hasSetWidth: !!_this.props.width, // Indicates whether the cell is currently being resized. isResizing: false, // Indicates a mouse drag is in progress isDragging: false, // Represents the width when the cell is not actively being resized. passiveWidth: _this.props.width || null }); _defineProperty(_assertThisInitialized(_this), "getWidth", function () { var styleWidth = _get(_this.rootRef, 'style.width'); if (_endsWith(styleWidth, 'px')) { return parseInt(styleWidth); } if (_this.rootRef.current) { return _this.rootRef.current.getBoundingClientRect().width; } return null; }); _defineProperty(_assertThisInitialized(_this), "handleClickCapture", function (event) { if (_this.state.isDragging) { event.stopPropagation(); _this.setState({ isDragging: false }); } }); _defineProperty(_assertThisInitialized(_this), "handleMouseEnter", function () { _this.setState({ isDragging: _this.state.isResizing }); }); _defineProperty(_assertThisInitialized(_this), "handleMouseUp", function () { _this.setState({ isDragging: _this.state.isResizing }); }); _defineProperty(_assertThisInitialized(_this), "handleDragEnded", function (coordinates, _ref) { var event = _ref.event; _this.setState({ isResizing: false, passiveWidth: _this.state.activeWidth }); window.document.body.style.cursor = ''; if (_this.props.onResize) { _this.props.onResize(_this.state.activeWidth, { event: event, props: _this.props }); } }); _defineProperty(_assertThisInitialized(_this), "handleDragged", function (coordinates, _ref2) { var event = _ref2.event, props = _ref2.props; var passiveWidth = _this.state.passiveWidth; var minWidth = _this.props.minWidth !== null && _isString(_this.props.minWidth) ? parseInt(_this.props.minWidth) : _this.props.minWidth; if (passiveWidth === null) { return; } else if (_isString(passiveWidth)) { passiveWidth = parseInt(passiveWidth); } var activeWidth = minWidth && passiveWidth + coordinates.dX > minWidth || !minWidth ? passiveWidth + coordinates.dX : minWidth; _this.setState({ activeWidth: activeWidth }); if (_this.props.onResize) { _this.props.onResize(activeWidth, { event: event, props: _this.props }); } }); _defineProperty(_assertThisInitialized(_this), "handleDragStarted", function (coordinates, _ref3) { var event = _ref3.event, props = _ref3.props; var startingWidth = _this.getWidth(); _this.setState({ activeWidth: startingWidth, hasSetWidth: true, isResizing: true, isDragging: true, passiveWidth: startingWidth }); window.document.body.style.cursor = 'ew-resize'; if (_this.props.onResize) { _this.props.onResize(startingWidth, { event: event, props: _this.props }); } }); return _this; } _createClass(Th, [{ key: "UNSAFE_componentWillReceiveProps", value: function UNSAFE_componentWillReceiveProps(_ref4) { var width = _ref4.width; if (!_isNil(width) && width !== this.props.width) { this.setState({ hasSetWidth: true, passiveWidth: width }); } } }, { key: "render", value: function render() { var _this$props = this.props, children = _this$props.children, className = _this$props.className, hasBorderRight = _this$props.hasBorderRight, hasBorderLeft = _this$props.hasBorderLeft, isFirstRow = _this$props.isFirstRow, isLastRow = _this$props.isLastRow, isFirstCol = _this$props.isFirstCol, isFirstSingle = _this$props.isFirstSingle, isLastCol = _this$props.isLastCol, align = _this$props.align, isResizable = _this$props.isResizable, isSortable = _this$props.isSortable, isSorted = _this$props.isSorted, sortDirection = _this$props.sortDirection, style = _this$props.style, passThroughs = _objectWithoutProperties(_this$props, ["children", "className", "hasBorderRight", "hasBorderLeft", "isFirstRow", "isLastRow", "isFirstCol", "isFirstSingle", "isLastCol", "align", "isResizable", "isSortable", "isSorted", "sortDirection", "style"]); var _this$state = this.state, activeWidth = _this$state.activeWidth, hasSetWidth = _this$state.hasSetWidth, isResizing = _this$state.isResizing, passiveWidth = _this$state.passiveWidth; return /*#__PURE__*/React.createElement("th", _extends({}, omitProps(passThroughs, undefined, _keys(Th.propTypes)), { className: cx('&-Th', { '&-is-first-row': isFirstRow, '&-is-last-row': isLastRow, '&-is-first-col': isFirstCol, '&-is-first-single': isFirstSingle, '&-is-last-col': isLastCol, '&-align-left': align === 'left', '&-align-center': align === 'center', '&-align-right': align === 'right', '&-is-resizable': isResizable, '&-is-resizing': isResizing, '&-is-sortable': isSortable === false ? isSortable : isSorted || isSortable, '&-is-sorted': isSorted, '&-has-border-right': hasBorderRight, '&-has-border-left': hasBorderLeft }, className), ref: this.rootRef, onClickCapture: this.handleClickCapture, onMouseEnter: this.handleMouseEnter, onMouseUp: this.handleMouseUp, style: hasSetWidth ? _assign({}, style, { width: isResizing ? activeWidth : passiveWidth }) : style }), /*#__PURE__*/React.createElement("div", { className: cx('&-Th-inner') }, /*#__PURE__*/React.createElement("div", { className: cx('&-Th-inner-content') }, children), isSorted || isSortable ? /*#__PURE__*/React.createElement("div", { className: cx('&-Th-inner-caret') }, /*#__PURE__*/React.createElement(ArrowIcon, { className: cx('&-sort-icon'), direction: sortDirection, size: 10 })) : null, isResizable ? /*#__PURE__*/React.createElement(DragCaptureZone, { className: cx('&-Th-inner-resize'), onDrag: this.handleDragged, onDragEnd: this.handleDragEnded, onDragStart: this.handleDragStarted }) : null)); } }]); return Th; }(React.Component); /** Td <td>: The Table Data Cell element */ _defineProperty(Th, "displayName", 'Table.Th'); _defineProperty(Th, "defaultProps", { align: 'left', isResizable: false, isSorted: false, sortDirection: 'up', rowSpan: 1 }); _defineProperty(Th, "peek", { description: "\n\t\t\t`Th` renders <th>.\n\t\t" }); _defineProperty(Th, "propTypes", { align: string, children: node, className: any, hasBorderRight: bool, hasBorderLeft: bool, isResizable: bool, isSortable: bool, isSorted: bool, onResize: func, sortDirection: oneOf(['left', 'up', 'right', 'down', undefined]), style: object, width: oneOfType([number, string]), minWidth: oneOfType([number, string]), isFirstRow: bool, isLastRow: bool, isFirstCol: bool, isLastCol: bool, isFirstSingle: bool, field: string }); var Td = function Td(props) { var className = props.className, isFirstRow = props.isFirstRow, isLastRow = props.isLastRow, isFirstCol = props.isFirstCol, isLastCol = props.isLastCol, isFirstSingle = props.isFirstSingle, align = props.align, hasBorderRight = props.hasBorderRight, hasBorderLeft = props.hasBorderLeft, truncateContent = props.truncateContent, passThroughs = _objectWithoutProperties(props, ["className", "isFirstRow", "isLastRow", "isFirstCol", "isLastCol", "isFirstSingle", "align", "hasBorderRight", "hasBorderLeft", "truncateContent"]); return /*#__PURE__*/React.createElement("td", _extends({}, omitProps(passThroughs, undefined, _keys(Td.propTypes).concat(['sortDirection'])), { className: cx('&-Td', { '&-is-first-row': isFirstRow, '&-is-last-row': isLastRow, '&-is-first-col': isFirstCol, '&-is-last-col': isLastCol, '&-is-first-single': isFirstSingle, '&-align-left': align === 'left', '&-align-center': align === 'center', '&-align-right': align === 'right', '&-has-border-right': hasBorderRight, '&-has-border-left': hasBorderLeft, '&-truncate-content': truncateContent }, className) })); }; Td.displayName = 'Table.Td'; Td.defaultProps = { align: 'left', hasBorderRight: false, hasBorderLeft: false, rowSpan: 1 }; Td.peek = { description: "\n\t\t`Td` renders <td>.\n\t", categories: [], madeFrom: [] }; Td.propTypes = { align: oneOf(['left', 'center', 'right']), className: any, hasBorderRight: bool, hasBorderLeft: bool, isFirstRow: bool, isLastRow: bool, isFirstCol: bool, isLastCol: bool, isFirstSingle: bool, isEmpty: bool, truncateContent: bool }; /** Table <Table> The Table Component */ var Table = function Table(props) { var className = props.className, hasBorder = props.hasBorder, density = props.density, hasWordWrap = props.hasWordWrap, hasLightHeader = props.hasLightHeader, hasHover = props.hasHover, style = props.style, passThroughs = _objectWithoutProperties(props, ["className", "hasBorder", "density", "hasWordWrap", "hasLightHeader", "hasHover", "style"]); return /*#__PURE__*/React.createElement("table", _extends({}, omitProps(passThroughs, undefined, _keys(Table.propTypes)), { style: style, className: cx('&', { '&-density-extended': density === 'extended', '&-density-compressed': density === 'compressed', '&-has-border': hasBorder, '&-has-word-wrap': hasWordWrap, '&-has-light-header': hasLightHeader, '&-no-hover': !hasHover }, className) })); }; Table.displayName = 'Table'; Table.defaultProps = { density: 'extended', hasBorder: false, hasWordWrap: true, hasLightHeader: true, hasHover: true }; Table.peek = { description: "\n\t\t`Table` provides the most basic components to create a lucid table.\n\t\tIt is recommended to create a wrapper around this component rather than\n\t\tusing it directly in an app.\n\t", categories: ['table'], madeFrom: ['ArrowIcon', 'DragCaptureZone'] }; Table.propTypes = { style: object, className: string, density: oneOf(['compressed', 'extended']), hasLightHeader: bool, hasBorder: bool, hasWordWrap: bool, hasHover: bool }; /** ChildComponents */ Table.Thead = Thead; Table.Th = Th; Table.Tbody = Tbody; Table.Tr = Tr; Table.Td = Td; /** * mapToGrid * * Returns a 2 dimensional array of cell elements of the given component type. The map function can modify value of a cell. */ function mapToGrid(trList) { var cellType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Td; var mapFn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _property('element'); var cellRowList = _map(trList, function (trElement) { return _map(filterTypes(trElement.props.children, cellType)); }); var grid = []; if (_isEmpty(cellRowList)) { return []; } // iterate over each row for (var rowIndex = 0; rowIndex < cellRowList.length; rowIndex++) { var cellRow = cellRowList[rowIndex]; if (_isNil(grid[rowIndex])) { grid[rowIndex] = []; } var canonicalRow = rowIndex; // build out each horizonal duplicates of each cell for (var cellElementIndex = 0; cellElementIndex < cellRow.length; cellElementIndex++) { var cellElement = cellRow[cellElementIndex]; var colSpan = 1; var isCellIncluded = false; if (_isNumber(cellElement.props.colSpan)) { colSpan = cellElement.props.colSpan; } var nilCellIndex = _findIndex(grid[canonicalRow], _isNil); var originCol = nilCellIndex !== -1 ? nilCellIndex : grid[canonicalRow].length; for (var currentColSpan = 0; currentColSpan < colSpan; currentColSpan++) { grid[canonicalRow][originCol + currentColSpan] = { element: cellElement, canonicalPosition: { row: canonicalRow, col: originCol }, isOriginal: !isCellIncluded }; isCellIncluded = true; } } // build out each vertial duplicates of each cell using the new row in the full grid for (var colIndex = 0; colIndex < grid[canonicalRow].length; colIndex++) { var gridCell = grid[canonicalRow][colIndex]; if (gridCell.isOriginal) { var _cellElement = _get(gridCell, 'element'); var rowSpan = 1; if (_isNumber(_get(_cellElement, 'props.rowSpan'))) { rowSpan = _get(_cellElement, 'props.rowSpan'); } for (var currentRowSpan = 1; currentRowSpan < rowSpan; currentRowSpan++) { if (_isNil(grid[canonicalRow + currentRowSpan])) { grid[canonicalRow + currentRowSpan] = []; } grid[canonicalRow + currentRowSpan][colIndex] = _assign({}, grid[canonicalRow + currentRowSpan - 1][colIndex], { isOriginal: false }); } } } } // map new values to each cell in the final grid var finalGrid = []; for (var _rowIndex = 0; _rowIndex < grid.length; _rowIndex++) { finalGrid[_rowIndex] = []; for (var _colIndex = 0; _colIndex < grid[_rowIndex].length; _colIndex++) { finalGrid[_rowIndex][_colIndex] = mapFn(grid[_rowIndex][_colIndex], { row: _rowIndex, col: _colIndex }, finalGrid); } } return finalGrid; } /** * renderRowsWithIdentifiedEdges * * Returns an equivalent list of Tr's where each cell on the perimeter has props set for: `isFirstRow`, `isLastRow`, `isFirstCol`, `isLastCol`, and `isFirstSingle` */ function renderRowsWithIdentifiedEdges(trList) { var cellType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Td; var duplicateReferences = []; var fullCellGrid = mapToGrid(trList, cellType, function (_ref5, currentPos, grid) { var props = _ref5.element.props, isOriginal = _ref5.isOriginal, canonicalPosition = _ref5.canonicalPosition; if (!isOriginal) { // if cell spans multiple positions // store current position and return original cell props reference duplicateReferences.push(currentPos); return grid[canonicalPosition.row][canonicalPosition.col]; } return _assign({}, props); // return a new props object based on old cell }); if (_isEmpty(fullCellGrid)) { return []; } var firstRow = _first(fullCellGrid); if (_isUndefined(firstRow)) { return []; } var firstRowIndex = 0; var lastRowIndex = fullCellGrid.length - 1; var firstColIndex = 0; var lastColIndex = firstRow.length - 1; var firstSingleLookup = {}; // decorate the props of each cell with props that indicate its role in the table _forEach(fullCellGrid, function (cellList, rowIndex) { return _forEach(cellList, function (cellProps, colIndex) { if (!_isNull(cellProps)) { if (rowIndex === firstRowIndex) { cellProps.isFirstRow = true; } if (rowIndex === lastRowIndex) { cellProps.isLastRow = true; } if (colIndex === firstColIndex) { cellProps.isFirstCol = true; } if (colIndex === lastColIndex) { cellProps.isLastCol = true; } if (!_has(firstSingleLookup, rowIndex)) { _set(firstSingleLookup, rowIndex, false); } if (!_get(firstSingleLookup, rowIndex) && _get(cellProps, 'rowSpan', 1) === 1) { _set(firstSingleLookup, rowIndex, true); cellProps.isFirstSingle = true; } } }); }); _forEach(duplicateReferences, function (_ref6) { var row = _ref6.row, col = _ref6.col; fullCellGrid[row][col] = null; // remove duplicate references from grid }); // render the grid back to elements using the updated cell props return _map(trList, function (trElement, rowIndex) { return /*#__PURE__*/React.createElement(Tr, _extends({}, trElement.props, { key: rowIndex }), _reduce(fullCellGrid[rowIndex], function (rowChildren, cellProps, colIndex) { return rowChildren.concat(!_isNull(cellProps) ? [/*#__PURE__*/React.createElement(cellType, _assign({}, cellProps, { key: colIndex }))] : []); }, [])); }); } export default Table;