1 |
|
2 |
|
3 |
|
4 |
|
5 | import React, {PureComponent} from 'react';
|
6 | import PropTypes from 'prop-types';
|
7 | import classNames from 'classnames';
|
8 | import {arrayMove, sortableContainer} from 'react-sortable-hoc';
|
9 |
|
10 | import focusSensorHOC from '../global/focus-sensor-hoc';
|
11 | import getUID from '../global/get-uid';
|
12 | import Shortcuts from '../shortcuts/shortcuts';
|
13 | import Loader from '../loader/loader';
|
14 |
|
15 | import Selection from './selection';
|
16 | import Header from './header';
|
17 | import style from './table.css';
|
18 | import DraggableRow from './draggable-row';
|
19 | import selectionShortcutsHOC from './selection-shortcuts-hoc';
|
20 | import disableHoverHOC from './disable-hover-hoc';
|
21 |
|
22 | const alwaysFalse = () => false;
|
23 |
|
24 | const DraggableRows = sortableContainer(props => {
|
25 | const {
|
26 | data, getItemKey, selection, selectable,
|
27 | isItemSelectable, onRowFocus, onRowSelect,
|
28 | getItemLevel, isItemCollapsible, isParentCollapsible,
|
29 | isItemCollapsed, onItemCollapse, onItemExpand,
|
30 | isDisabledSelectionVisible, getCheckboxTooltip,
|
31 | ...restProps
|
32 | } = props;
|
33 |
|
34 | return (
|
35 | <tbody data-test="ring-table-body">
|
36 | {data.map((item, index) => (
|
37 | <DraggableRow
|
38 | key={getItemKey(item)}
|
39 | level={getItemLevel(item)}
|
40 | index={index}
|
41 | item={item}
|
42 | showFocus={selection.isFocused(item)}
|
43 | focused={selection.isFocused(item)}
|
44 | selectable={selectable && isItemSelectable(item)}
|
45 | selected={selectable && selection.isSelected(item)}
|
46 | onFocus={onRowFocus}
|
47 | onSelect={onRowSelect}
|
48 | collapsible={isItemCollapsible(item)}
|
49 | parentCollapsible={isParentCollapsible(item)}
|
50 | collapsed={isItemCollapsed(item)}
|
51 | onCollapse={onItemCollapse}
|
52 | onExpand={onItemExpand}
|
53 | showDisabledSelection={isDisabledSelectionVisible(item)}
|
54 | checkboxTooltip={getCheckboxTooltip(item)}
|
55 | {...restProps}
|
56 | />
|
57 | ))}
|
58 | </tbody>
|
59 | );
|
60 | });
|
61 |
|
62 |
|
63 | class Table extends PureComponent {
|
64 | static propTypes = {
|
65 | className: PropTypes.string,
|
66 | loaderClassName: PropTypes.string,
|
67 | data: PropTypes.array.isRequired,
|
68 | columns: PropTypes.array.isRequired,
|
69 | caption: PropTypes.string,
|
70 | isItemSelectable: PropTypes.func,
|
71 | stickyHeader: PropTypes.bool,
|
72 | stickyHeaderOffset: PropTypes.string,
|
73 | loading: PropTypes.bool,
|
74 | getItemKey: PropTypes.func,
|
75 | onSort: PropTypes.func,
|
76 | onReorder: PropTypes.func,
|
77 | sortKey: PropTypes.string,
|
78 | sortOrder: PropTypes.bool,
|
79 | draggable: PropTypes.bool,
|
80 | alwaysShowDragHandle: PropTypes.bool,
|
81 | getItemLevel: PropTypes.func,
|
82 | isItemCollapsible: PropTypes.func,
|
83 | isParentCollapsible: PropTypes.func,
|
84 | isItemCollapsed: PropTypes.func,
|
85 | onItemCollapse: PropTypes.func,
|
86 | onItemExpand: PropTypes.func,
|
87 | isDisabledSelectionVisible: PropTypes.func,
|
88 | getCheckboxTooltip: PropTypes.func,
|
89 |
|
90 |
|
91 | focused: PropTypes.bool,
|
92 | onFocusRestore: PropTypes.func,
|
93 |
|
94 |
|
95 | selection: PropTypes.instanceOf(Selection).isRequired,
|
96 | selectable: PropTypes.bool,
|
97 | onSelect: PropTypes.func,
|
98 | shortcutsMap: PropTypes.object,
|
99 |
|
100 |
|
101 | disabledHover: PropTypes.bool,
|
102 |
|
103 | remoteSelection: PropTypes.bool
|
104 | };
|
105 |
|
106 | static defaultProps = {
|
107 | isItemSelectable: () => true,
|
108 | loading: false,
|
109 | onSort: () => {},
|
110 | onReorder: () => {},
|
111 | getItemKey: item => item.id,
|
112 | sortKey: 'id',
|
113 | sortOrder: true,
|
114 | draggable: false,
|
115 | alwaysShowDragHandle: false,
|
116 | stickyHeader: true,
|
117 | getItemLevel: () => 0,
|
118 | isItemCollapsible: () => false,
|
119 | isParentCollapsible: () => false,
|
120 | isItemCollapsed: () => false,
|
121 | onItemCollapse: () => {},
|
122 | onItemExpand: () => {},
|
123 | remoteSelection: false,
|
124 | isDisabledSelectionVisible: () => {},
|
125 | getCheckboxTooltip: () => {}
|
126 | };
|
127 |
|
128 | state = {
|
129 | shortcutsEnabled: this.props.selectable && this.props.focused,
|
130 | shortcutsScope: getUID('ring-table-'),
|
131 | userSelectNone: false
|
132 | };
|
133 |
|
134 | componentDidMount() {
|
135 | document.addEventListener('mouseup', this.onMouseUp);
|
136 | }
|
137 |
|
138 | componentWillReceiveProps(nextProps) {
|
139 | const {data, selection, onSelect, selectable} = this.props;
|
140 |
|
141 | if (data !== nextProps.data && !this.props.remoteSelection) {
|
142 | onSelect(selection.cloneWith({data: nextProps.data}));
|
143 | }
|
144 |
|
145 | if (!nextProps.selectable && nextProps.selectable !== selectable) {
|
146 | onSelect(selection.resetSelection());
|
147 | }
|
148 |
|
149 | const shortcutsEnabled = nextProps.focused;
|
150 | if (shortcutsEnabled !== this.state.shortcutsEnabled) {
|
151 | this.setState({shortcutsEnabled});
|
152 | }
|
153 | }
|
154 |
|
155 | componentWillUnmount() {
|
156 | document.removeEventListener('mouseup', this.onMouseUp);
|
157 | }
|
158 |
|
159 | onMouseDown = e => {
|
160 | if (e.shiftKey) {
|
161 | this.setState({userSelectNone: true});
|
162 | }
|
163 | };
|
164 |
|
165 | onMouseUp = () => {
|
166 | if (this.state.userSelectNone) {
|
167 | this.setState({userSelectNone: false});
|
168 | }
|
169 | };
|
170 |
|
171 | onRowFocus = row => {
|
172 | const {selection, onSelect} = this.props;
|
173 | onSelect(selection.focus(row));
|
174 | };
|
175 |
|
176 | onRowSelect = (row, selected) => {
|
177 | const {selection, onSelect} = this.props;
|
178 | if (selected) {
|
179 | onSelect(selection.select(row));
|
180 | } else {
|
181 | onSelect(selection.deselect(row));
|
182 | }
|
183 | };
|
184 |
|
185 | onSortEnd = ({oldIndex, newIndex}) => {
|
186 | const data = arrayMove(this.props.data, oldIndex, newIndex);
|
187 | this.props.onReorder({data, oldIndex, newIndex});
|
188 | };
|
189 |
|
190 | onCheckboxChange = e => {
|
191 | const {checked} = e.target;
|
192 | const {selection, onSelect} = this.props;
|
193 |
|
194 | if (checked) {
|
195 | onSelect(selection.selectAll());
|
196 | } else {
|
197 | onSelect(selection.reset());
|
198 | }
|
199 |
|
200 | this.restoreFocusWithoutScroll();
|
201 | };
|
202 |
|
203 | restoreFocusWithoutScroll = () => {
|
204 | const {scrollX, scrollY} = window;
|
205 | this.props.onFocusRestore();
|
206 | window.scrollTo(scrollX, scrollY);
|
207 | };
|
208 |
|
209 | render() {
|
210 | const {
|
211 | data, selection, columns, caption, getItemKey, selectable,
|
212 | isItemSelectable, getItemLevel, draggable, alwaysShowDragHandle,
|
213 | loading, onSort, sortKey, sortOrder, loaderClassName, stickyHeader,
|
214 | stickyHeaderOffset, isItemCollapsible, isParentCollapsible, isItemCollapsed,
|
215 | onItemCollapse, onItemExpand, isDisabledSelectionVisible, getCheckboxTooltip
|
216 | } = this.props;
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | const headerProps = {
|
222 | caption, selectable, draggable,
|
223 | columns, onSort, sortKey, sortOrder,
|
224 | sticky: stickyHeader,
|
225 | topStickOffset: stickyHeaderOffset
|
226 | };
|
227 |
|
228 | const selectedSize = selection.getSelected().size;
|
229 | const allSelectedSize = selection.selectAll().getSelected().size;
|
230 | headerProps.checked = selectedSize > 0 && selectedSize === allSelectedSize;
|
231 | headerProps.onCheckboxChange = this.onCheckboxChange;
|
232 | headerProps.checkboxDisabled = this.props.data.length === 0;
|
233 |
|
234 | const wrapperClasses = classNames({
|
235 | [style.tableWrapper]: true,
|
236 | [style.loading]: loading
|
237 | });
|
238 |
|
239 | const classes = classNames(this.props.className, {
|
240 | [style.table]: true,
|
241 | [style.multiSelection]: selection.getSelected().size > 0,
|
242 | [style.userSelectNone]: this.state.userSelectNone,
|
243 | [style.disabledHover]: this.props.disabledHover
|
244 | });
|
245 |
|
246 | return (
|
247 | <div className={wrapperClasses} data-test="ring-table-wrapper">
|
248 | {this.state.shortcutsEnabled &&
|
249 | (
|
250 | <Shortcuts
|
251 | map={this.props.shortcutsMap}
|
252 | scope={this.state.shortcutsScope}
|
253 | />
|
254 | )
|
255 | }
|
256 |
|
257 | <table className={classes} onMouseDown={this.onMouseDown} data-test="ring-table">
|
258 | <Header {...headerProps}/>
|
259 | <DraggableRows
|
260 |
|
261 | useDragHandle
|
262 | disabled={!draggable}
|
263 | helperClass={style.draggingRow}
|
264 | onSortEnd={this.onSortEnd}
|
265 | getItemKey={getItemKey}
|
266 | shouldCancelStart={alwaysFalse}
|
267 |
|
268 |
|
269 | draggable={draggable}
|
270 | alwaysShowDragHandle={alwaysShowDragHandle}
|
271 | data={data}
|
272 | columns={columns}
|
273 | selectable={selectable}
|
274 | isItemSelectable={isItemSelectable}
|
275 | selection={selection}
|
276 | onRowFocus={this.onRowFocus}
|
277 | onRowSelect={this.onRowSelect}
|
278 | getItemLevel={getItemLevel}
|
279 | isItemCollapsible={isItemCollapsible}
|
280 | isParentCollapsible={isParentCollapsible}
|
281 | isItemCollapsed={isItemCollapsed}
|
282 | onItemCollapse={onItemCollapse}
|
283 | onItemExpand={onItemExpand}
|
284 | isDisabledSelectionVisible={isDisabledSelectionVisible}
|
285 | getCheckboxTooltip={getCheckboxTooltip}
|
286 | />
|
287 | </table>
|
288 |
|
289 | {loading && (
|
290 | <div className={style.loadingOverlay}>
|
291 | <Loader className={loaderClassName}/>
|
292 | </div>
|
293 | )}
|
294 | </div>
|
295 | );
|
296 | }
|
297 | }
|
298 |
|
299 | export default disableHoverHOC(selectionShortcutsHOC(focusSensorHOC(Table)));
|