UNPKG

grommet

Version:

focus on the essential experience

423 lines (414 loc) 18.8 kB
"use strict"; exports.__esModule = true; exports.Header = void 0; var _react = _interopRequireWildcard(require("react")); var _styledComponents = _interopRequireWildcard(require("styled-components")); var _DataContext = require("../../contexts/DataContext"); var _MessageContext = require("../../contexts/MessageContext"); var _Box = require("../Box"); var _Button = require("../Button"); var _CheckBox = require("../CheckBox"); var _TableCell = require("../TableCell/TableCell"); var _Text = require("../Text"); var _Resizer = require("./Resizer"); var _Searcher = require("./Searcher"); var _ExpanderCell = require("./ExpanderCell"); var _StyledDataTable = require("./StyledDataTable"); var _buildState = require("./buildState"); var _styles = require("../../utils/styles"); var _colors = require("../../utils/colors"); var _useThemeValue2 = require("../../utils/useThemeValue"); var _excluded = ["background", "border", "color", "font", "gap", "pad", "units"], _excluded2 = ["allowSelectAll", "cellProps", "columns", "data", "disabled", "fill", "filtering", "filters", "groupBy", "groups", "groupState", "messages", "onFilter", "onFiltering", "onResize", "onSelect", "onSort", "onToggle", "onWidths", "pin", "pinnedOffset", "primaryProperty", "selected", "rowDetails", "sort", "widths", "verticalAlign"]; /* eslint-disable no-underscore-dangle */ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } // delay before triggering width update. This allows most/all header resizes // to be batched together causing fewer render passes var WIDTH_UPDATE_DELAY = 100; // separate theme values into groupings depending on what // part of header cell they should style var separateThemeProps = function separateThemeProps(theme) { var _theme$dataTable$head = theme.dataTable.header, background = _theme$dataTable$head.background, border = _theme$dataTable$head.border, color = _theme$dataTable$head.color, font = _theme$dataTable$head.font, gap = _theme$dataTable$head.gap, pad = _theme$dataTable$head.pad, units = _theme$dataTable$head.units, rest = _objectWithoutPropertiesLoose(_theme$dataTable$head, _excluded); var textProps = _extends({ color: color }, font); var iconProps = { color: color }; var layoutProps = _extends({}, rest); return [layoutProps, textProps, iconProps]; }; // build up CSS from basic to specific based on the supplied sub-object paths. // adapted from StyledButtonKind to only include parts relevant for DataTable var buttonStyle = function buttonStyle(_ref) { var pad = _ref.pad, theme = _ref.theme, verticalAlign = _ref.verticalAlign; var styles = []; var _separateThemeProps = separateThemeProps(theme), layoutProps = _separateThemeProps[0], iconProps = _separateThemeProps[2]; // if cell is sortable, we want pad to be applied // to the button instead of the cell if (pad) { styles.push((0, _styles.kindPartStyles)({ pad: pad }, theme)); } if (layoutProps) { styles.push((0, _styles.kindPartStyles)(layoutProps, theme)); } if (layoutProps.hover) { // CSS for this sub-object in the theme var partStyles = (0, _styles.kindPartStyles)(layoutProps.hover, theme); if (partStyles.length > 0) styles.push((0, _styledComponents.css)(["&:hover{", "}"], partStyles)); } if (iconProps.color) { styles.push((0, _styledComponents.css)(["svg{stroke:", ";fill:", ";}"], (0, _colors.normalizeColor)(iconProps.color, theme), (0, _colors.normalizeColor)(iconProps.color, theme))); } var align = 'center'; if (verticalAlign === 'bottom') align = 'end'; if (verticalAlign === 'top') align = 'start'; if (verticalAlign) { styles.push((0, _styledComponents.css)(["display:inline-flex;align-items:", ";"], align)); } return styles; }; var StyledHeaderCellButton = (0, _styledComponents["default"])(_Button.Button).withConfig({ displayName: "Header__StyledHeaderCellButton", componentId: "sc-1baku5q-0" })(["", ""], function (props) { return buttonStyle(props); }); // allow extend to spread onto Box that surrounds column label var StyledContentBox = (0, _styledComponents["default"])(_Box.Box).withConfig({ displayName: "Header__StyledContentBox", componentId: "sc-1baku5q-1" })(["", ""], function (props) { return props.extend; }); var Header = exports.Header = /*#__PURE__*/(0, _react.forwardRef)(function (_ref2, ref) { var _theme$dataTable$expa; var allowSelectAll = _ref2.allowSelectAll, cellProps = _ref2.cellProps, columns = _ref2.columns, data = _ref2.data, disabled = _ref2.disabled, fill = _ref2.fill, filtering = _ref2.filtering, filters = _ref2.filters, groupBy = _ref2.groupBy, groups = _ref2.groups, groupState = _ref2.groupState, messages = _ref2.messages, onFilter = _ref2.onFilter, onFiltering = _ref2.onFiltering, _onResize = _ref2.onResize, onSelect = _ref2.onSelect, onSort = _ref2.onSort, onToggle = _ref2.onToggle, onWidths = _ref2.onWidths, pinProp = _ref2.pin, pinnedOffset = _ref2.pinnedOffset, primaryProperty = _ref2.primaryProperty, selected = _ref2.selected, rowDetails = _ref2.rowDetails, sort = _ref2.sort, widths = _ref2.widths, verticalAlign = _ref2.verticalAlign, rest = _objectWithoutPropertiesLoose(_ref2, _excluded2); var _useThemeValue = (0, _useThemeValue2.useThemeValue)(), theme = _useThemeValue.theme, passThemeFlag = _useThemeValue.passThemeFlag; var _separateThemeProps2 = separateThemeProps(theme), layoutProps = _separateThemeProps2[0], textProps = _separateThemeProps2[1]; var _useContext = (0, _react.useContext)(_DataContext.DataContext), contextTotal = _useContext.total; var _useContext2 = (0, _react.useContext)(_MessageContext.MessageContext), format = _useContext2.format; var cellWidthsRef = (0, _react.useRef)({}); var timerRef = (0, _react.useRef)(); var handleWidths = (0, _react.useCallback)(function () { var cellWidths = cellWidthsRef.current; if (onWidths && cellWidths) { var internalColumnWidths = selected || onSelect ? [cellWidths._grommetDataTableSelect] : []; onWidths([].concat(internalColumnWidths, columns.map(function (_ref3) { var property = _ref3.property; return cellWidths[property]; }))); } }, [columns, onSelect, onWidths, selected]); var updateWidths = (0, _react.useCallback)(function (property, width) { if (typeof width !== 'number') return; // Only update if width actually changed if ((cellWidthsRef == null ? void 0 : cellWidthsRef.current[property]) !== width) { cellWidthsRef.current[property] = width; if (timerRef.current) clearTimeout(timerRef.current); timerRef.current = setTimeout(handleWidths, WIDTH_UPDATE_DELAY); } }, [handleWidths]); var pin = pinProp ? ['top'] : []; var selectPin = pinnedOffset != null && pinnedOffset._grommetDataTableSelect ? [].concat(pin, ['left']) : pin; var totalSelectedGroups = groupBy != null && groupBy.select ? Object.keys(groupBy.select).reduce(function (total, cur) { return cur && groupBy.select[cur] === 'all' ? total + 1 : total; }, 0) : 0; var totalSelected = ((selected == null ? void 0 : selected.length) || 0) + totalSelectedGroups; var onChangeSelection = (0, _react.useCallback)(function () { var nextSelected = [].concat(selected); var nextGroupSelected = {}; // get primary values for current data view var primaryValues = data.map(function (datum) { return (0, _buildState.datumValue)(datum, primaryProperty); }) || []; // enabled includes what can be changed var enabled = disabled && primaryValues.filter(function (v) { return !disabled.includes(v); }) || primaryValues; // enabledSelected includes what can be changed and is currently selected var enabledSelected = selected && enabled.filter(function (v) { return selected.includes(v); }) || primaryValues; var allSelected = groupBy != null && groupBy.select ? groupBy.select[''] === 'all' : enabledSelected.length === enabled.length; // if all enabled are already selected, remove them from selected, // otherwise add them. if (allSelected) { enabledSelected.forEach(function (p) { var index = nextSelected.indexOf(p); if (index >= 0) { nextSelected.splice(index, 1); } }); nextGroupSelected[''] = 'none'; } else { var _groupBy$expandable; enabled.forEach(function (p) { if (!nextSelected.includes(p)) { nextSelected.push(p); } }); nextGroupSelected[''] = 'all'; groupBy == null || (_groupBy$expandable = groupBy.expandable) == null || _groupBy$expandable.forEach(function (key) { nextGroupSelected[key] = 'all'; }); } if (groupBy != null && groupBy.onSelect) { groupBy.onSelect(nextSelected, undefined, nextGroupSelected); } else onSelect(nextSelected); }, [data, disabled, groupBy, onSelect, primaryProperty, selected]); return /*#__PURE__*/_react["default"].createElement(_StyledDataTable.StyledDataTableHeader, _extends({ ref: ref, fillProp: fill }, rest), /*#__PURE__*/_react["default"].createElement(_StyledDataTable.StyledDataTableRowHeader, null, groups && /*#__PURE__*/_react["default"].createElement(_ExpanderCell.ExpanderCell, { background: cellProps.background, border: cellProps.border, context: "header", expanded: Object.keys(groupState).filter(function (k) { return !groupState[k].expanded; }).length === 0, onToggle: onToggle, pad: cellProps.pad }), (selected || onSelect) && /*#__PURE__*/_react["default"].createElement(_StyledDataTable.StyledDataTableCell, _extends({ background: cellProps.background, onWidth: function onWidth(width) { return updateWidths('_grommetDataTableSelect', width); }, plain: "noPad", size: "auto", context: "header", scope: "col", pin: selectPin, pinnedOffset: pinnedOffset == null ? void 0 : pinnedOffset._grommetDataTableSelect, verticalAlign: verticalAlign }, passThemeFlag), onSelect && allowSelectAll && /*#__PURE__*/_react["default"].createElement(_CheckBox.CheckBox, { a11yTitle: totalSelected === data.length ? 'unselect all' : 'select all', checked: groupBy != null && groupBy.select ? groupBy.select[''] === 'all' : totalSelected > 0 && data.length > 0 && totalSelected === (contextTotal || data.length), indeterminate: groupBy != null && groupBy.select ? groupBy.select[''] === 'some' : totalSelected > 0 && totalSelected < (contextTotal || data.length), onChange: onChangeSelection, pad: cellProps.pad })), rowDetails && /*#__PURE__*/_react["default"].createElement(_TableCell.TableCell, { size: (_theme$dataTable$expa = theme.dataTable.expand) == null ? void 0 : _theme$dataTable$expa.size, plain: true, pad: "none" }), columns.map(function (_ref4) { var property = _ref4.property, header = _ref4.header, align = _ref4.align, columnPin = _ref4.pin, search = _ref4.search, sortable = _ref4.sortable, columnVerticalAlign = _ref4.verticalAlign, size = _ref4.size, units = _ref4.units; var content; var unitsContent = units ? /*#__PURE__*/_react["default"].createElement(_Text.Text, _extends({}, textProps, theme.dataTable.header.units), units) : undefined; if (typeof header === 'string') { content = /*#__PURE__*/_react["default"].createElement(_Text.Text, textProps, header); if (Object.keys(layoutProps).length && (sortable === false || !onSort)) { // apply rest of layout styling if cell is not sortable, // otherwise this styling will be applied by // StyledHeaderCellButton content = /*#__PURE__*/_react["default"].createElement(StyledContentBox, layoutProps, content); } } else content = header; if (unitsContent) { content = /*#__PURE__*/_react["default"].createElement(_Box.Box, { justify: align, direction: "row" }, content, unitsContent); } if (verticalAlign || columnVerticalAlign) { var vertical = verticalAlign || columnVerticalAlign; content = /*#__PURE__*/_react["default"].createElement(_Box.Box, { height: "100%", justify: _TableCell.verticalAlignToJustify[vertical] }, content); } var ariaSort; if (onSort && sortable !== false) { var _theme$dataTable$sort; var Icon; var iconAriaLabel; if (onSort && sortable !== false) { if (sort && sort.property === property) { Icon = theme.dataTable.icons[sort.direction !== 'asc' ? 'ascending' : 'descending']; if (sort.direction === 'asc') { ariaSort = 'ascending'; iconAriaLabel = format({ id: 'dataTable.ascending', messages: messages }); } else if (sort.direction === 'desc') { ariaSort = 'descending'; iconAriaLabel = format({ id: 'dataTable.descending', messages: messages }); } } else if (theme.dataTable.icons.sortable) { Icon = theme.dataTable.icons.sortable; } } content = /*#__PURE__*/_react["default"].createElement(StyledHeaderCellButton, _extends({ plain: true, column: property, fill: "vertical", focusIndicator: size ? 'inset' : undefined, onClick: onSort(property), sort: sort, pad: cellProps.pad, sortable: true, verticalAlign: verticalAlign || columnVerticalAlign }, passThemeFlag), /*#__PURE__*/_react["default"].createElement(_Box.Box, { direction: "row", align: "center", gap: (_theme$dataTable$sort = theme.dataTable.sort) == null ? void 0 : _theme$dataTable$sort.gap, justify: align }, content, Icon && /*#__PURE__*/_react["default"].createElement(Icon, { "aria-label": iconAriaLabel }))); } // content should fill any available space in cell // If `onResize` or `search` is true we need to explicitly set // fill because later if either of these props is true content // will be wrapped with an additional Box, preventing this Box // from automatically filling the vertical space. content = /*#__PURE__*/_react["default"].createElement(_Box.Box, { flex: _onResize || search ? { grow: 1, shrink: 1 } : 'grow', fill: _onResize || search ? 'vertical' : false, justify: !align && 'center' || align }, content); if (search) { var searcher = search && filters ? /*#__PURE__*/_react["default"].createElement(_Searcher.Searcher, { filtering: filtering, filters: filters, focusIndicator: size ? 'inset' : undefined, messages: messages, property: property, onFilter: onFilter, onFiltering: onFiltering }) : null; content = /*#__PURE__*/_react["default"].createElement(_Box.Box, { direction: "row", align: "center", justify: !align || align === 'start' ? 'between' : align, gap: theme.dataTable.header.gap, fill: "vertical", style: _onResize ? { position: 'relative' } : undefined }, content, searcher && _onResize ? /*#__PURE__*/_react["default"].createElement(_Box.Box, { flex: { shrink: filtering === property ? 1 : 0 }, direction: filtering === property ? 'column' : 'row' // margin right set to half (12px) of resizer width // (24px) to prevent overlap with resizer control. // this also creates enough space when search input // is open. so, padding right is not needed for // the search input box any longer. // see Searcher.js , margin: { right: '12px' } }, searcher) : searcher); } var cellPin = [].concat(pin); if (columnPin) cellPin.push('left'); var headerId = "grommet-data-table-header-" + property; return /*#__PURE__*/_react["default"].createElement(_StyledDataTable.StyledDataTableCell, _extends({ "aria-sort": ariaSort, key: property, align: align, context: "header", verticalAlign: verticalAlign || columnVerticalAlign, background: cellProps.background, border: cellProps.border, id: headerId, onWidth: function onWidth(width) { return updateWidths(property, width); } // if sortable, pad will be included in the button styling , pad: sortable === false || !onSort ? cellProps.pad : 'none', pin: cellPin, plain: true, pinnedOffset: pinnedOffset && pinnedOffset[property], scope: "col", size: widths && widths[property] ? undefined : size, style: { width: widths != null && widths[property] ? widths[property] + "px" : undefined, boxSizing: _onResize ? 'border-box' : undefined, // Don't override positioning when DataTable has pin prop position: _onResize && !columnPin && !pinProp ? 'relative' : undefined, overflow: _onResize ? 'visible' : undefined }, onResize: _onResize, property: property }, passThemeFlag), content, _onResize && /*#__PURE__*/_react["default"].createElement(_Resizer.Resizer, { property: property, onResize: function onResize(prop, width) { _onResize(prop, width); updateWidths(prop, width); }, headerText: typeof header === 'string' ? header : property, messages: messages, headerId: headerId })); }))); }); Header.displayName = 'Header';