wix-style-react
Version:
wix-style-react
296 lines • 17.3 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import defaultTo from 'lodash/defaultTo';
import classNames from 'classnames';
import { ScrollSync } from 'react-scroll-sync';
import { classes } from './Table.st.css';
import DataTable from './DataTable';
import Checkbox from '../Checkbox';
import { TableContext } from './TableContext';
import { BulkSelection } from './BulkSelection';
import { TooltipCommonProps } from '../common/PropTypes/TooltipCommon';
import * as dataTableRowVirtualStyle from './DataTable/components/DataTableRowVirtual.st.css';
import { TableBulkSelectionCheckbox, TableContent, TableEmptyState, TableSubToolbar, TableTitleBar, TableToolbarContainer, } from './components';
const hasUnselectablesSymbol = Symbol('hasUnselectables');
export function createColumns({ tableProps, bulkSelectionContext }) {
const { dragAndDrop, isDragAndDropDisabled } = tableProps;
const includeDragHandleColumn = dragAndDrop != null &&
dragAndDrop.createDragHandleColumn != null &&
!isDragAndDropDisabled;
const createCheckboxColumn = ({ toggleSelectionById, isSelected, selectionDisabled, }) => {
const isRowSelectionDisabled = rowData => selectionDisabled === true ||
(typeof selectionDisabled === 'function' && selectionDisabled(rowData));
return {
key: 'bulk-selection-cell',
title: tableProps.hideBulkSelectionCheckbox ? ('') : (React.createElement(TableBulkSelectionCheckbox, { dataHook: "table-select" })),
dataHook: 'bulk-selection-cell',
onCellClick: (column, row, rowNum, event) => {
if (row.unselectable) {
return;
}
event.stopPropagation();
if (isRowSelectionDisabled(row)) {
return;
}
const id = defaultTo(row.id, rowNum);
toggleSelectionById(id, 'Checkbox');
},
render: (row, rowNum) => {
const id = defaultTo(row.id, rowNum);
const tooltipContent = row.checkboxTooltipContent;
return row.unselectable ? null : (React.createElement(Checkbox, { disabled: isRowSelectionDisabled(row), dataHook: "row-select", checked: isSelected(id), tooltipProps: { disabled: !tooltipContent }, tooltipContent: tooltipContent }));
},
width: '12px',
style: (_, row) => (row.unselectable ? undefined : { cursor: 'pointer' }),
};
};
const checkboxColumn = tableProps.showSelection
? createCheckboxColumn(bulkSelectionContext)
: null;
const columns = [
...(includeDragHandleColumn
? [dragAndDrop.createDragHandleColumn(tableProps)]
: []),
...(checkboxColumn ? [checkboxColumn] : []),
...tableProps.columns,
];
// Apply different column width when checkbox column is the last sticky column
// (looks weird otherwise when stickied with default 12px width).
if (checkboxColumn &&
columns.indexOf(checkboxColumn) + 1 === tableProps.stickyColumns) {
checkboxColumn.width = '34px';
}
return columns;
}
export function getDataTableProps(tableProps) {
/* eslint-disable no-unused-vars */
const { showSelection, onSelectionChanged, onSelectionStarted, dataHook, ...props } = tableProps;
return {
...props,
rowClass: classNames(tableProps.rowClass, classes.tableRow),
};
}
/**
* Table is a composite component that allows adding SelectionColumn, Toolbar (on top of the TitleBar).
* It is a context provider, and thus the Table.Consumer, Table.TitleBar and Table.Content can be rendered separately.
*/
export class Table extends React.Component {
constructor() {
super(...arguments);
this.state = {
leftShadowVisible: false,
rightShadowVisible: false,
};
this._handleUpdateScrollShadows = (leftShadowVisible, rightShadowVisible) => {
if (leftShadowVisible !== this.state.leftShadowVisible) {
this.setState({ leftShadowVisible });
}
if (rightShadowVisible !== this.state.rightShadowVisible) {
this.setState({ rightShadowVisible });
}
};
}
shouldComponentUpdate() {
// Table is not really a PureComponent
return true;
}
setSelectedIds(selectedIds) {
this.bulkSelection.setSelectedIds(selectedIds);
}
renderChildren() {
const { children, withWrapper, dataHook } = this.props;
return withWrapper ? React.createElement("div", { "data-hook": dataHook }, children) : children;
}
render() {
const { data, selectedIds, showSelection, deselectRowsByDefault, infiniteScroll, totalSelectableCount, onSelectionChanged, onSelectionStarted, hasMore, horizontalScroll, selectionDisabled, dragAndDrop, onDragEnd, } = this.props;
let hasUnselectables = null;
let allIds = data.map((rowData, rowIndex) => rowData.unselectable ||
(typeof selectionDisabled === 'function' && selectionDisabled(rowData))
? (hasUnselectables = hasUnselectablesSymbol)
: defaultTo(rowData.id, rowIndex));
if (hasUnselectables === hasUnselectablesSymbol) {
allIds = allIds.filter(rowId => rowId !== hasUnselectablesSymbol);
}
const { leftShadowVisible, rightShadowVisible } = this.state;
const contextValue = {
...this.props,
leftShadowVisible,
rightShadowVisible,
onUpdateScrollShadows: this._handleUpdateScrollShadows,
};
let table = (React.createElement(TableContext.Provider, { value: contextValue }, showSelection ? (React.createElement(BulkSelection, { ref: _ref => (this.bulkSelection = _ref), selectedIds: selectedIds, deselectRowsByDefault: deselectRowsByDefault, selectionDisabled: selectionDisabled, hasMoreInBulkSelection: infiniteScroll && Boolean(totalSelectableCount) && hasMore, totalCount: totalSelectableCount, allIds: allIds, onSelectionChanged: onSelectionChanged, onSelectionStarted: onSelectionStarted }, this.renderChildren())) : (this.renderChildren())));
table = horizontalScroll ? (React.createElement(ScrollSync, { proportional: false, horizontal: true, vertical: false }, table)) : (table);
return table;
}
}
Table.ToolbarContainer = TableToolbarContainer;
Table.Titlebar = TableTitleBar;
Table.Content = TableContent;
Table.SubToolbar = TableSubToolbar;
Table.EmptyState = TableEmptyState;
Table.BulkSelectionCheckbox = TableBulkSelectionCheckbox;
Table.displayName = 'Table';
Table.dataTableRowVirtualStyle = dataTableRowVirtualStyle;
Table.defaultProps = {
...DataTable.defaultProps,
showSelection: false,
hideBulkSelectionCheckbox: false,
children: [React.createElement(Table.Content, { key: "content" })],
withWrapper: true,
showLastRowDivider: false,
horizontalScroll: false,
stickyColumns: 0,
isRowDisabled: () => false,
deselectRowsByDefault: false,
};
Table.propTypes = {
/** Accept any wrapper component as a child element. It must eventually include <Table.Content/>. */
children: PropTypes.any,
/** Applies a data-hook HTML attribute to be used in the tests */
dataHook: PropTypes.string,
/** Defines a callback function which is called when selection happens. <br />
* There are 3 types of selections:
* * `ALL` - bulk selection checkbox has been triggered to select all items
* * `SINGLE_TOGGLE` - a checkbox to select one item from the list has been triggered. The change object will also include an `id` prop with the unique item identifier and a `value` prop with the new boolean selection state of the item.
* * `NONE` - bulk selection has been triggered to unselect all items.
* */
onSelectionChanged: PropTypes.func,
/** Called when item selection triggered but not changed yet. */
onSelectionStarted: PropTypes.func,
/** Displays selection checkbox as a first column in each row. To hide the selection checkbox from a specific row, set its `row.unselectable` value to true in the data array. */
showSelection: PropTypes.bool,
/** Hides bulk selection checkbox in the table titlebar */
hideBulkSelectionCheckbox: PropTypes.bool,
/** Defines an array of selected rows */
selectedIds: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.arrayOf(PropTypes.number),
]),
/** Specifies whether table row selection is restricted at a given moment. Can be defined as:
* * `bool` disables selection for all table rows
* * `function` that will be called for each row to check whether to disable selection or not depending on specified condition */
selectionDisabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
/** Controls bulk selection checkbox behaviour in indeterminate state. By default, first click on indeterminate state selects all items in the table. If set to `true`, first click will deselect all items instead. */
deselectRowsByDefault: PropTypes.bool,
/**
* When false then Table would not create a `<div/>` wrapper around it's children.
* Useful when using `<Table/>` to wrap a `<Page/>` component, in that case we use the `<Table/>` only as a context provider and it does not render anything to the DOM by itself.*/
withWrapper: PropTypes.bool,
/**
* Defines a callback function which is called on column title click
*/
onSortClick: PropTypes.func,
// The following props are derived directly from <DataTable/> component
/** Allows to expand multiple row details at once */
allowMultiDetailsExpansion: PropTypes.bool,
/** Defines any amount of data to be displayed in the table. Properties for each entry:<br>
* * `id`: should be provided and will be used as a React key internally
* * `unselectable`: specifies whether a row can be selected by a user when `showSelection` is enabled for the whole table
* * `checkboxTooltipContent`: defines tooltip content to show when user hovers selection checkbox
* * any number of records with unique identifiers for column rendering
* */
data: PropTypes.array, // Not performing any shape validation to not hurt performance.
/** Configures table columns. Required properties for each column:<br />
* * `title`: specifies a text string or an element to display in the table titlebar
* * `render`: defines a function which will be called to display this row value for this column<br>
*
* Optional properties for each column:
* * `onCellClick`: defines a callback function which is called each time a cell in this column is clicked. Signature: `onCellClick(column, rowData, rowNum, event)`
* * `sortable`: specifies whether field is sortable. If true clicking the header will call `onSortClick`.
* * `sortDescending`: specifies what sort icon to display in the column header. `true` shows an up arrow, `false` shows a down arrow, `undefined' shows no icon.
* * `infoTooltipProps`: controls info [tooltip](/?path=/story/components-overlays--tooltip) appearance in titlebar. Note: `dataHook`, `moveBy` and `children` will not be passed to tooltip.
* * `style`: applies a custom CSS class to the component’s root element
* * `align`: controls the alignment of the column content
* * `width`: sets the width of a column. No value means column will try to contain its children, if possible.
* * `important`: increases text size of this column data across all rows, sets font color to D10
* * `stickyActionCell`: specifies whether `<TableActionCell/>` controls are fixed to the right side of the table
* */
columns: PropTypes.arrayOf(PropTypes.shape({
title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]).isRequired,
render: PropTypes.func.isRequired,
onCellClick: PropTypes.func,
sortable: PropTypes.bool,
sortDescending: PropTypes.bool,
infoTooltipProps: PropTypes.shape(TooltipCommonProps),
style: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
align: PropTypes.oneOf(['start', 'center', 'end']),
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
important: PropTypes.bool,
stickyActionCell: PropTypes.bool,
})).isRequired,
/** Defines a function that gets row data and returns a class(es) to apply to that specific row */
dynamicRowClass: PropTypes.func,
/** Defines a function that gets row data and returns boolean value specifying whether row is highlighted or not */
isRowHighlight: PropTypes.func,
/** Indicates whether there is additional data to load from external sources though `loadMore` function. When set to true `loadMore` property will be called until false value is received. */
hasMore: PropTypes.bool,
/** Invokes `loadMore` on the initial table render */
initialLoad: PropTypes.bool,
/** Assigns an unique identifier for the table */
id: PropTypes.string,
/** Renders data gradually as user scrolls down the list */
infiniteScroll: PropTypes.bool,
/** Specifies the total number of selectable items in the table (including items not loaded yet).
* When ‘Select all’ is triggered in infinite scroll tables, newly loaded items will be selected automatically. In this case, `SelectionContext` holds not-selected items rather than the selected ones.
* SelectionContext.infiniteBulkSelected is true and SelectionContext.selectedCount is the value of totalSelectableCount minus the count of unselected items. */
totalSelectableCount: PropTypes.number,
/** Determines the number of rows to rendered on each load of an infinite scroll table */
itemsPerPage: PropTypes.number,
/** Pass a loader to show when loading more items in infinite scroll table. Most commonly pass <Loader/> or <Text/> components. */
loader: PropTypes.node,
/** Defines a callback function which is called each time user request to load more items */
loadMore: PropTypes.func,
/** Defines a callback function which is called each time user clicks on the row. This prop is required to enable row hover effect. */
onRowClick: PropTypes.func,
/** Defines a callback function which is called each time mouse enters the row */
onMouseEnterRow: PropTypes.func,
/** Defines a callback function which is called each time mouse leaves the row */
onMouseLeaveRow: PropTypes.func,
/** Add scroll listeners to the window, or else, the component's parentNode. */
useWindow: PropTypes.bool,
/** Adds scroll listeners to specified DOM object */
scrollElement: PropTypes.object,
/** Controls the amount of white space above and below row content:
* - `large`: 24px
* - `medium`: 18px
* - `small`: with the feature toggle: 15px, without the feature toggle: 12px
* - `tiny`: 12px
* */
rowVerticalPadding: PropTypes.oneOf(['tiny', 'small', 'medium', 'large']),
/** Defines a function that returns a component to render in the expanded row content area */
rowDetails: PropTypes.func,
/** Removes paddings from an expanded row content area */
removeRowDetailsPadding: PropTypes.bool,
/** Applies a data-hook HTML attribute or a function that calculates the data-hook for each row on the table */
rowDataHook: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/** A class to apply to all table body rows */
rowClass: PropTypes.string,
/** Specifies whether to show table title bar when there’s no data in the table */
showHeaderWhenEmpty: PropTypes.bool,
/** Specifies whether last row in the table should have a bottom divider */
showLastRowDivider: PropTypes.bool,
/** A flag specifying weather to apply column width style to table cells */
isApplyColumnWidthStyle: PropTypes.bool,
/** ++EXPERIMENTAL++ Virtualize the table scrolling for long list items */
virtualized: PropTypes.bool,
/** ++EXPERIMENTAL++ Controls virtualized table height */
virtualizedTableHeight: PropTypes.number,
/** ++EXPERIMENTAL++ Set virtualized table row height */
virtualizedLineHeight: PropTypes.number,
/** ++EXPERIMENTAL++ Set ref on virtualized list containing table rows */
virtualizedListRef: PropTypes.any,
/** Defines the width of the table in percentages or pixels. */
width: PropTypes.string,
/** Controls the appearance of a table title bar */
skin: PropTypes.oneOf(['standard', 'neutral']),
/** Specifies whether table content can exceed the width of the table. If enabled, table can be scrolled horizontally. */
horizontalScroll: PropTypes.bool,
/** Specifies the number of columns to stick to the left side in tables that support horizontal scroll */
stickyColumns: PropTypes.number,
/** Defines a function to call for each row to check whether row should appear as disabled or not */
isRowDisabled: PropTypes.func,
dragAndDrop: PropTypes.object,
onDragEnd: PropTypes.func,
};
// export default Table;
//# sourceMappingURL=Table.js.map