UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

720 lines (707 loc) 26 kB
/** * MSKCC 2021, 2024 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var PropTypes = require('prop-types'); var React = require('react'); var isEqual = require('lodash.isequal'); var getDerivedStateFromProps = require('./state/getDerivedStateFromProps.js'); var sorting = require('./state/sorting.js'); var cells = require('./tools/cells.js'); var denormalize = require('./tools/denormalize.js'); var events = require('../../tools/events.js'); var filter = require('./tools/filter.js'); var sorting$1 = require('./tools/sorting.js'); var instanceId = require('./tools/instanceId.js'); var Table = require('./Table.js'); var TableActionList = require('./TableActionList.js'); var TableBatchAction = require('./TableBatchAction.js'); var TableBatchActions = require('./TableBatchActions.js'); var TableBody = require('./TableBody.js'); var TableCell = require('./TableCell.js'); var TableContainer = require('./TableContainer.js'); var TableExpandHeader = require('./TableExpandHeader.js'); var TableExpandRow = require('./TableExpandRow.js'); var TableExpandedRow = require('./TableExpandedRow.js'); var TableHead = require('./TableHead.js'); var TableHeader = require('./TableHeader.js'); var TableRow = require('./TableRow.js'); var TableSelectAll = require('./TableSelectAll.js'); var TableSelectRow = require('./TableSelectRow.js'); var TableToolbar = require('./TableToolbar.js'); var TableToolbarAction = require('./TableToolbarAction.js'); var TableToolbarContent = require('./TableToolbarContent.js'); var TableToolbarSearch = require('./TableToolbarSearch.js'); var TableToolbarMenu = require('./TableToolbarMenu.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var isEqual__default = /*#__PURE__*/_interopDefaultLegacy(isEqual); const getInstanceId = instanceId["default"](); const translationKeys = { expandRow: 'carbon.table.row.expand', collapseRow: 'carbon.table.row.collapse', expandAll: 'carbon.table.all.expand', collapseAll: 'carbon.table.all.collapse', selectAll: 'carbon.table.all.select', unselectAll: 'carbon.table.all.unselect', selectRow: 'carbon.table.row.select', unselectRow: 'carbon.table.row.unselect' }; const defaultTranslations = { [translationKeys.expandAll]: 'Expand all rows', [translationKeys.collapseAll]: 'Collapse all rows', [translationKeys.expandRow]: 'Expand current row', [translationKeys.collapseRow]: 'Collapse current row', [translationKeys.selectAll]: 'Select all rows', [translationKeys.unselectAll]: 'Unselect all rows', [translationKeys.selectRow]: 'Select row', [translationKeys.unselectRow]: 'Unselect row' }; const translateWithId = id => defaultTranslations[id]; /** * Data Tables are used to represent a collection of resources, displaying a * subset of their fields in columns, or headers. We prioritize direct updates * to the state of what we're rendering, so internally we end up normalizing the * given data and then denormalizing it when rendering. * * As a result, each part of the DataTable is accessible through look-up by id, * and updating the state of the single entity will cascade updates to the * consumer. */ class DataTable extends React__default["default"].Component { constructor(_props) { var _this; super(_props); _this = this; /** * Get the props associated with the given header. Mostly used for adding in * sorting behavior. * * @param {object} config * @param {string} config.header the header we want the props for * @param {Function} config.onClick a custom click handler for the header * @param {boolean} config.isSortable * @returns {object} */ _rollupPluginBabelHelpers.defineProperty(this, "getHeaderProps", _ref => { let { header, onClick, isSortable = this.props.isSortable, ...rest } = _ref; const { sortDirection, sortHeaderKey } = this.state; return { ...rest, key: header.key, sortDirection, isSortable, isSortHeader: sortHeaderKey === header.key, onClick: event => { const nextSortState = sorting.getNextSortState(this.props, this.state, { key: header.key }); this.setState(nextSortState, () => { onClick && this.handleOnHeaderClick(onClick, { sortHeaderKey: header.key, sortDirection: nextSortState.sortDirection })(event); }); } }; }); /** * Get the props associated with the given expand header. * * @param {object} config * @param {Function} config.onClick a custom click handler for the expand header * @param {Function} config.onExpand a custom click handler called when header is expanded * @returns {object} */ _rollupPluginBabelHelpers.defineProperty(this, "getExpandHeaderProps", function () { let { onClick, onExpand, ...rest } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const { translateWithId: t } = _this.props; const { isExpandedAll, rowIds, rowsById } = _this.state; const isExpanded = isExpandedAll || rowIds.every(id => rowsById[id].isExpanded); const translationKey = isExpanded ? translationKeys.collapseAll : translationKeys.expandAll; return { ...rest, ariaLabel: t(translationKey), isExpanded, // Compose the event handlers so we don't overwrite a consumer's `onClick` // handler onExpand: events.composeEventHandlers([_this.handleOnExpandAll, onExpand, onClick ? _this.handleOnExpandHeaderClick(onClick, { isExpanded }) : null]) }; }); /** * Decorate consumer's `onClick` event handler with sort parameters * * @param {Function} onClick * @param {object} sortParams * @returns {Function} */ _rollupPluginBabelHelpers.defineProperty(this, "handleOnHeaderClick", (onClick, sortParams) => { return e => onClick(e, sortParams); }); /** * Decorate consumer's `onClick` event handler with sort parameters * * @param {Function} onClick * @param {object} expandParams * @returns {Function} */ _rollupPluginBabelHelpers.defineProperty(this, "handleOnExpandHeaderClick", (onClick, expandParams) => { return e => onClick(e, expandParams); }); /** * Get the props associated with the given row. Mostly used for expansion. * * @param {object} config * @param {object} config.row the row we want the props for * @param {Function} config.onClick a custom click handler for the header * @returns {object} */ _rollupPluginBabelHelpers.defineProperty(this, "getRowProps", _ref2 => { let { row, onClick, ...rest } = _ref2; const { translateWithId: t } = this.props; const translationKey = row.isExpanded ? translationKeys.collapseRow : translationKeys.expandRow; return { ...rest, key: row.id, // Compose the event handlers so we don't overwrite a consumer's `onClick` // handler onExpand: events.composeEventHandlers([this.handleOnExpandRow(row.id), onClick]), isExpanded: row.isExpanded, ariaLabel: t(translationKey), isSelected: row.isSelected, disabled: row.disabled }; }); /** * Gets the props associated with selection for a header or a row, where * applicable. Most often used to indicate selection status of the table or * for a specific row. * * @param {object} [row] an optional row that we want to access the props for * @param {Function} row.onClick * @param {object} row.row * @returns {object} */ _rollupPluginBabelHelpers.defineProperty(this, "getSelectionProps", function () { let { onClick, row, ...rest } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const { translateWithId: t } = _this.props; // If we're given a row, return the selection state values for that row if (row) { const translationKey = row.isSelected ? translationKeys.unselectRow : translationKeys.selectRow; return { ...rest, checked: row.isSelected, onSelect: events.composeEventHandlers([_this.handleOnSelectRow(row.id), onClick]), id: `${_this.getTablePrefix()}__select-row-${row.id}`, name: `select-row-${row.id}`, ariaLabel: t(translationKey), disabled: row.disabled, radio: _this.props.radio || null }; } // Otherwise, we're working on `TableSelectAll` which handles toggling the // selection state of all rows. const rowCount = _this.state.rowIds.length; const selectedRowCount = _this.getSelectedRows().length; const checked = rowCount > 0 && selectedRowCount === rowCount; const indeterminate = rowCount > 0 && selectedRowCount > 0 && selectedRowCount !== rowCount; const translationKey = checked || indeterminate ? translationKeys.unselectAll : translationKeys.selectAll; return { ...rest, ariaLabel: t(translationKey), checked, id: `${_this.getTablePrefix()}__select-all`, indeterminate, name: 'select-all', onSelect: events.composeEventHandlers([_this.handleSelectAll, onClick]) }; }); _rollupPluginBabelHelpers.defineProperty(this, "getToolbarProps", function () { let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const { size } = _this.props; let isSmall = size === 'xs' || size === 'sm'; return { ...props, size: isSmall ? 'sm' : undefined }; }); _rollupPluginBabelHelpers.defineProperty(this, "getBatchActionProps", function () { let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const { shouldShowBatchActions } = _this.state; const totalSelected = _this.getSelectedRows().length; return { ...props, shouldShowBatchActions: shouldShowBatchActions && totalSelected > 0, totalSelected, onCancel: _this.handleOnCancel }; }); /** * Helper utility to get the Table Props. */ _rollupPluginBabelHelpers.defineProperty(this, "getTableProps", () => { const { useZebraStyles, size, isSortable, useStaticWidth, stickyHeader, overflowMenuOnHover, experimentalAutoAlign } = this.props; return { useZebraStyles, size, isSortable, useStaticWidth, stickyHeader, overflowMenuOnHover, experimentalAutoAlign }; }); /** * Helper utility to get the TableContainer Props. */ _rollupPluginBabelHelpers.defineProperty(this, "getTableContainerProps", () => { const { stickyHeader, useStaticWidth } = this.props; return { stickyHeader, useStaticWidth }; }); /** * Helper utility to get all the currently selected rows * @returns {Array<string>} the array of rowIds that are currently selected */ _rollupPluginBabelHelpers.defineProperty(this, "getSelectedRows", () => this.state.rowIds.filter(id => { const row = this.state.rowsById[id]; return row.isSelected && !row.disabled; })); /** * Helper utility to get all of the available rows after applying the filter * @returns {Array<string>} the array of rowIds that are currently included through the filter * */ _rollupPluginBabelHelpers.defineProperty(this, "getFilteredRowIds", () => { const filteredRowIds = typeof this.state.filterInputValue === 'string' ? this.props.filterRows({ rowIds: this.state.rowIds, headers: this.props.headers, cellsById: this.state.cellsById, inputValue: this.state.filterInputValue, getCellId: cells.getCellId }) : this.state.rowIds; if (filteredRowIds.length == 0) { return []; } return filteredRowIds; }); /** * Helper for getting the table prefix for elements that require an * `id` attribute that is unique. * * @returns {string} */ _rollupPluginBabelHelpers.defineProperty(this, "getTablePrefix", () => `data-table-${this.instanceId}`); /** * Helper for toggling all selected items in a state. Does not call * setState, so use it when setting state. * @param {object} initialState * @returns {object} object to put into this.setState (use spread operator) */ _rollupPluginBabelHelpers.defineProperty(this, "setAllSelectedState", (initialState, isSelected, filteredRowIds) => { const { rowIds } = initialState; const isFiltered = rowIds.length != filteredRowIds.length; return { rowsById: rowIds.reduce((acc, id) => { const row = { ...initialState.rowsById[id] }; if (!row.disabled && (!isFiltered || filteredRowIds.includes(id))) { row.isSelected = isSelected; } acc[id] = row; // Local mutation for performance with large tables return acc; }, {}) }; }); /** * Handler for the `onCancel` event to hide the batch action bar and * deselect all selected rows */ _rollupPluginBabelHelpers.defineProperty(this, "handleOnCancel", () => { this.setState(state => { return { shouldShowBatchActions: false, ...this.setAllSelectedState(state, false, this.getFilteredRowIds()) }; }); }); /** * Handler for toggling the selection state of all rows in the database */ _rollupPluginBabelHelpers.defineProperty(this, "handleSelectAll", () => { this.setState(state => { const filteredRowIds = this.getFilteredRowIds(); const { rowsById } = state; const isSelected = !(Object.values(rowsById).filter(row => row.isSelected && !row.disabled).length > 0); return { shouldShowBatchActions: isSelected, ...this.setAllSelectedState(state, isSelected, filteredRowIds) }; }); }); /** * Handler for toggling the selection state of a given row. * * @param {string} rowId * @returns {Function} */ _rollupPluginBabelHelpers.defineProperty(this, "handleOnSelectRow", rowId => () => { this.setState(state => { const row = state.rowsById[rowId]; if (this.props.radio) { // deselect all radio buttons const rowsById = Object.entries(state.rowsById).reduce((p, c) => { const [key, val] = c; val.isSelected = false; p[key] = val; return p; }, {}); return { shouldShowBatchActions: false, rowsById: { ...rowsById, [rowId]: { ...row, isSelected: !row.isSelected } } }; } const selectedRows = state.rowIds.filter(id => state.rowsById[id].isSelected).length; // Predict the length of the selected rows after this change occurs const selectedRowsCount = !row.isSelected ? selectedRows + 1 : selectedRows - 1; return { // Basic assumption here is that we want to show the batch action bar if // the row is being selected. If it's being unselected, then see if we // have a non-zero number of selected rows that batch actions could // still apply to shouldShowBatchActions: !row.isSelected || selectedRowsCount > 0, rowsById: { ...state.rowsById, [rowId]: { ...row, isSelected: !row.isSelected } } }; }); }); /** * Handler for toggling the expansion state of a given row. * * @param {string} rowId * @returns {Function} */ _rollupPluginBabelHelpers.defineProperty(this, "handleOnExpandRow", rowId => () => { this.setState(state => { const row = state.rowsById[rowId]; const { isExpandedAll } = state; return { isExpandedAll: row.isExpanded ? false : isExpandedAll, rowsById: { ...state.rowsById, [rowId]: { ...row, isExpanded: !row.isExpanded } } }; }); }); /** * Handler for changing the expansion state of all rows. */ _rollupPluginBabelHelpers.defineProperty(this, "handleOnExpandAll", () => { this.setState(state => { const { rowIds, isExpandedAll } = state; return { isExpandedAll: !isExpandedAll, rowsById: rowIds.reduce((acc, id) => ({ ...acc, [id]: { ...state.rowsById[id], isExpanded: !isExpandedAll } }), {}) }; }); }); /** * Handler for transitioning to the next sort state of the table * * @param {string} headerKey the field for the header that we are sorting by * @returns {Function} */ _rollupPluginBabelHelpers.defineProperty(this, "handleSortBy", headerKey => () => { this.setState(state => sorting.getNextSortState(this.props, state, { key: headerKey })); }); /** * Event handler for transitioning input value state changes for the table * filter component. * * @param {Event} event */ _rollupPluginBabelHelpers.defineProperty(this, "handleOnInputValueChange", (event, defaultValue) => { if (event.target) { this.setState({ filterInputValue: event.target.value }); } if (defaultValue) { this.setState({ filterInputValue: defaultValue }); } }); this.state = { ...getDerivedStateFromProps["default"](_props, {}), isExpandedAll: false // Start with collapsed state, treat `undefined` as neutral state }; this.instanceId = getInstanceId(); } // if state needs to be updated then wait for only update after state is finished shouldComponentUpdate(nextProps) { if (this.props !== nextProps) { const nextRowIds = nextProps.rows.map(row => row.id); const rowIds = this.props.rows.map(row => row.id); if (!isEqual__default["default"](nextRowIds, rowIds)) { this.setState(state => getDerivedStateFromProps["default"](this.props, state)); return false; } const nextHeaders = nextProps.headers.map(header => header.key); const headers = this.props.headers.map(header => header.key); if (!isEqual__default["default"](nextHeaders, headers)) { this.setState(state => getDerivedStateFromProps["default"](this.props, state)); return false; } if (!isEqual__default["default"](nextProps.rows, this.props.rows)) { this.setState(state => getDerivedStateFromProps["default"](this.props, state)); return false; } } return true; } render() { // eslint-disable-next-line react/prop-types const { children, filterRows, headers, render } = this.props; const { filterInputValue, rowIds, rowsById, cellsById } = this.state; const filteredRowIds = typeof filterInputValue === 'string' ? filterRows({ rowIds, headers, cellsById, inputValue: filterInputValue, getCellId: cells.getCellId }) : rowIds; const renderProps = { // Data derived from state rows: denormalize["default"](filteredRowIds, rowsById, cellsById), headers: this.props.headers, selectedRows: denormalize["default"](this.getSelectedRows(), rowsById, cellsById), // Prop accessors/getters getHeaderProps: this.getHeaderProps, getExpandHeaderProps: this.getExpandHeaderProps, getRowProps: this.getRowProps, getSelectionProps: this.getSelectionProps, getToolbarProps: this.getToolbarProps, getBatchActionProps: this.getBatchActionProps, getTableProps: this.getTableProps, getTableContainerProps: this.getTableContainerProps, // Custom event handlers onInputChange: this.handleOnInputValueChange, // Expose internal state change actions sortBy: headerKey => this.handleSortBy(headerKey)(), selectAll: this.handleSelectAll, selectRow: rowId => this.handleOnSelectRow(rowId)(), expandRow: rowId => this.handleOnExpandRow(rowId)(), expandAll: this.handleOnExpandAll, radio: this.props.radio }; if (render !== undefined) { return render(renderProps); } if (children !== undefined) { return children(renderProps); } return null; } } _rollupPluginBabelHelpers.defineProperty(DataTable, "propTypes", { /** * Experimental property. Allows table to align cell contents to the top if there is text wrapping in the content. Might have performance issues, intended for smaller tables */ experimentalAutoAlign: PropTypes__default["default"].bool, /** * Optional hook to manually control filtering of the rows from the * TableToolbarSearch component */ filterRows: PropTypes__default["default"].func, /** * The `headers` prop represents the order in which the headers should * appear in the table. We expect an array of objects to be passed in, where * `key` is the name of the key in a row object, and `header` is the name of * the header. */ headers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].shape({ key: PropTypes__default["default"].string.isRequired, header: PropTypes__default["default"].node.isRequired })).isRequired, /** * Specify whether the table should be able to be sorted by its headers */ isSortable: PropTypes__default["default"].bool, /** * Provide a string for the current locale */ locale: PropTypes__default["default"].string, /** * Specify whether the overflow menu (if it exists) should be shown always, or only on hover */ overflowMenuOnHover: PropTypes__default["default"].bool, /** * Specify whether the control should be a radio button or inline checkbox */ radio: PropTypes__default["default"].bool, /** * The `rows` prop is where you provide us with a list of all the rows that * you want to render in the table. The only hard requirement is that this * is an array of objects, and that each object has a unique `id` field * available on it. */ rows: PropTypes__default["default"].arrayOf(PropTypes__default["default"].shape({ id: PropTypes__default["default"].string.isRequired, disabled: PropTypes__default["default"].bool, isSelected: PropTypes__default["default"].bool, isExpanded: PropTypes__default["default"].bool })).isRequired, /** * Change the row height of table. Currently supports `xs`, `sm`, `md`, `lg`, and `xl`. */ size: PropTypes__default["default"].oneOf(['xs', 'sm', 'md', 'lg', 'xl']), /** * Optional hook to manually control sorting of the rows. */ sortRow: PropTypes__default["default"].func, /** * Specify whether the header should be sticky. * Still experimental: may not work with every combination of table props */ stickyHeader: PropTypes__default["default"].bool, /** * Optional method that takes in a message id and returns an * internationalized string. See `DataTable.translationKeys` for all * available message ids. */ translateWithId: PropTypes__default["default"].func, /** * `false` If true, will use a width of 'auto' instead of 100% */ useStaticWidth: PropTypes__default["default"].bool, /** * `true` to add useZebraStyles striping. */ useZebraStyles: PropTypes__default["default"].bool }); _rollupPluginBabelHelpers.defineProperty(DataTable, "defaultProps", { sortRow: sorting$1.defaultSortRow, filterRows: filter.defaultFilterRows, locale: 'en', size: 'lg', overflowMenuOnHover: true, translateWithId }); _rollupPluginBabelHelpers.defineProperty(DataTable, "translationKeys", Object.values(translationKeys)); DataTable.Table = Table.Table; DataTable.TableActionList = TableActionList["default"]; DataTable.TableBatchAction = TableBatchAction["default"]; DataTable.TableBatchActions = TableBatchActions["default"]; DataTable.TableBody = TableBody["default"]; DataTable.TableCell = TableCell["default"]; DataTable.TableContainer = TableContainer["default"]; DataTable.TableExpandHeader = TableExpandHeader["default"]; DataTable.TableExpandRow = TableExpandRow["default"]; DataTable.TableExpandedRow = TableExpandedRow["default"]; DataTable.TableHead = TableHead["default"]; DataTable.TableHeader = TableHeader["default"]; DataTable.TableRow = TableRow["default"]; DataTable.TableSelectAll = TableSelectAll["default"]; DataTable.TableSelectRow = TableSelectRow["default"]; DataTable.TableToolbar = TableToolbar["default"]; DataTable.TableToolbarAction = TableToolbarAction["default"]; DataTable.TableToolbarContent = TableToolbarContent["default"]; DataTable.TableToolbarSearch = TableToolbarSearch["default"]; DataTable.TableToolbarMenu = TableToolbarMenu["default"]; var DataTable$1 = DataTable; exports["default"] = DataTable$1;