UNPKG

9.01 kBJavaScriptView Raw
1/**
2 * @name Table
3 */
4
5import React, {PureComponent} from 'react';
6import PropTypes from 'prop-types';
7import classNames from 'classnames';
8import {arrayMove, sortableContainer} from 'react-sortable-hoc';
9
10import focusSensorHOC from '../global/focus-sensor-hoc';
11import getUID from '../global/get-uid';
12import Shortcuts from '../shortcuts/shortcuts';
13import Loader from '../loader/loader';
14
15import Selection from './selection';
16import Header from './header';
17import style from './table.css';
18import DraggableRow from './draggable-row';
19import selectionShortcutsHOC from './selection-shortcuts-hoc';
20import disableHoverHOC from './disable-hover-hoc';
21
22const alwaysFalse = () => false;
23
24const 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// eslint-disable-next-line react/no-deprecated
63class 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 // focusSensorHOC
91 focused: PropTypes.bool,
92 onFocusRestore: PropTypes.func,
93
94 // selectionShortcutsHOC
95 selection: PropTypes.instanceOf(Selection).isRequired,
96 selectable: PropTypes.bool,
97 onSelect: PropTypes.func,
98 shortcutsMap: PropTypes.object,
99
100 // disableHoverHOC
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 // NOTE: Do not construct new object per render because it causes all rows rerendering
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 /* Sortable props */
261 useDragHandle
262 disabled={!draggable}
263 helperClass={style.draggingRow}
264 onSortEnd={this.onSortEnd}
265 getItemKey={getItemKey}
266 shouldCancelStart={alwaysFalse}
267
268 /* Row props */
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
299export default disableHoverHOC(selectionShortcutsHOC(focusSensorHOC(Table)));