/node_modules/"
102 | ]
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/screenshots/advance-table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Autodesk/react-base-table/ef4f9e34488e5a149aa750d429a8adaf071275ae/screenshots/advance-table.png
--------------------------------------------------------------------------------
/src/AutoResizer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import AutoSizer from 'react-virtualized-auto-sizer';
4 |
5 | /**
6 | * Decorator component that automatically adjusts the width and height of a single child
7 | */
8 | const AutoResizer = ({ className, width, height, children, onResize }) => {
9 | const disableWidth = typeof width === 'number';
10 | const disableHeight = typeof height === 'number';
11 |
12 | if (disableWidth && disableHeight) {
13 | return (
14 |
15 | {children({ width, height })}
16 |
17 | );
18 | }
19 |
20 | return (
21 |
22 | {size =>
23 | children({
24 | width: disableWidth ? width : size.width,
25 | height: disableHeight ? height : size.height,
26 | })
27 | }
28 |
29 | );
30 | };
31 |
32 | AutoResizer.propTypes = {
33 | /**
34 | * Class name for the component
35 | */
36 | className: PropTypes.string,
37 | /**
38 | * the width of the component, will be the container's width if not set
39 | */
40 | width: PropTypes.number,
41 | /**
42 | * the height of the component, will be the container's width if not set
43 | */
44 | height: PropTypes.number,
45 | /**
46 | * A callback function to render the children component
47 | * The handler is of the shape of `({ width, height }) => node`
48 | */
49 | children: PropTypes.func.isRequired,
50 | /**
51 | * A callback function when the size of the table container changed if the width and height are not set
52 | * The handler is of the shape of `({ width, height }) => *`
53 | */
54 | onResize: PropTypes.func,
55 | };
56 |
57 | export default AutoResizer;
58 |
--------------------------------------------------------------------------------
/src/BaseTable.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 |
4 | import BaseTable from './BaseTable';
5 |
6 | const RENDERER = () => null;
7 |
8 | const columns = [
9 | {
10 | key: 'code',
11 | title: 'code',
12 | dataKey: 'code',
13 | width: 50,
14 | },
15 | {
16 | key: 'name',
17 | title: 'name',
18 | dataKey: 'name',
19 | width: 50,
20 | },
21 | ];
22 |
23 | const data = [
24 | {
25 | id: '1',
26 | code: '1',
27 | name: '1',
28 | },
29 | {
30 | id: '2',
31 | code: '2',
32 | name: '2',
33 | },
34 | ];
35 |
36 | const Table = props => ;
37 |
38 | describe('Table', function() {
39 | test('renders correctly', () => {
40 | const tree = renderer.create().toJSON();
41 | expect(tree).toMatchSnapshot();
42 | });
43 |
44 | test('table can receive className', () => {
45 | const tree = renderer.create().toJSON();
46 | expect(tree).toMatchSnapshot();
47 | });
48 |
49 | test('table can receive style', () => {
50 | const tree = renderer.create().toJSON();
51 | expect(tree).toMatchSnapshot();
52 | });
53 |
54 | test('table can receive children', () => {
55 | const tree = renderer
56 | .create(
57 |
61 | )
62 | .toJSON();
63 | expect(tree).toMatchSnapshot();
64 | });
65 |
66 | test('table can receive empty data', () => {
67 | const tree = renderer.create().toJSON();
68 | expect(tree).toMatchSnapshot();
69 | });
70 |
71 | test('table can specific a different rowKey', () => {
72 | const tree = renderer.create().toJSON();
73 | expect(tree).toMatchSnapshot();
74 | });
75 |
76 | test('table can receive width', () => {
77 | const tree = renderer.create().toJSON();
78 | expect(tree).toMatchSnapshot();
79 | });
80 |
81 | test('table can receive height', () => {
82 | const tree = renderer.create().toJSON();
83 | expect(tree).toMatchSnapshot();
84 | });
85 |
86 | test('table can receive rowHeight', () => {
87 | const tree = renderer.create().toJSON();
88 | expect(tree).toMatchSnapshot();
89 | });
90 |
91 | test('table can receive headerHeight', () => {
92 | const tree = renderer.create().toJSON();
93 | expect(tree).toMatchSnapshot();
94 | });
95 |
96 | test('table can be set to fixed', () => {
97 | const tree = renderer.create().toJSON();
98 | expect(tree).toMatchSnapshot();
99 | });
100 |
101 | test('table can be set to disabled', () => {
102 | const tree = renderer.create().toJSON();
103 | expect(tree).toMatchSnapshot();
104 | });
105 |
106 | test('table can hide the header', () => {
107 | const tree = renderer.create().toJSON();
108 | expect(tree).toMatchSnapshot();
109 | });
110 |
111 | test('table can freeze rows', () => {
112 | const tree = renderer.create().toJSON();
113 | expect(tree).toMatchSnapshot();
114 | });
115 |
116 | test('table can receive an emptyRenderer callback', () => {
117 | const tree = renderer.create().toJSON();
118 | expect(tree).toMatchSnapshot();
119 | });
120 |
121 | test('table can receive an headerRenderer callback', () => {
122 | const tree = renderer.create().toJSON();
123 | expect(tree).toMatchSnapshot();
124 | });
125 |
126 | test('table can receive an rowRenderer callback', () => {
127 | const tree = renderer.create().toJSON();
128 | expect(tree).toMatchSnapshot();
129 | });
130 |
131 | test('table can receive headerClassName', () => {
132 | const tree = renderer.create().toJSON();
133 | expect(tree).toMatchSnapshot();
134 | });
135 |
136 | test('table can receive rowClassName', () => {
137 | const tree = renderer.create().toJSON();
138 | expect(tree).toMatchSnapshot();
139 | });
140 |
141 | test('table can receive expandColumnKey', () => {
142 | const tree = renderer.create().toJSON();
143 | expect(tree).toMatchSnapshot();
144 | });
145 |
146 | test('table can receive defaultExpandedRowKeys', () => {
147 | const tree = renderer.create().toJSON();
148 | expect(tree).toMatchSnapshot();
149 | });
150 |
151 | test('table can receive expandedRowKeys', () => {
152 | const tree = renderer.create().toJSON();
153 | expect(tree).toMatchSnapshot();
154 | });
155 | });
156 |
--------------------------------------------------------------------------------
/src/Column.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const Alignment = {
5 | LEFT: 'left',
6 | CENTER: 'center',
7 | RIGHT: 'right',
8 | };
9 |
10 | export const FrozenDirection = {
11 | LEFT: 'left',
12 | RIGHT: 'right',
13 | DEFAULT: true,
14 | NONE: false,
15 | };
16 |
17 | /**
18 | * Column for BaseTable
19 | */
20 | class Column extends React.Component {}
21 |
22 | Column.propTypes = {
23 | /**
24 | * Class name for the column cell, could be a callback to return the class name
25 | * The callback is of the shape of `({ cellData, columns, column, columnIndex, rowData, rowIndex }) => string`
26 | */
27 | className: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
28 | /**
29 | * Class name for the column header, could be a callback to return the class name
30 | * The callback is of the shape of `({ columns, column, columnIndex, headerIndex }) => string`
31 | */
32 | headerClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
33 | /**
34 | * Custom style for the column cell, including the header cells
35 | */
36 | style: PropTypes.object,
37 | /**
38 | * Title for the column header
39 | */
40 | title: PropTypes.node,
41 | /**
42 | * Data key for the column cell, could be "a.b.c"
43 | */
44 | dataKey: PropTypes.string,
45 | /**
46 | * Custom cell data getter
47 | * The handler is of the shape of `({ columns, column, columnIndex, rowData, rowIndex }) => node`
48 | */
49 | dataGetter: PropTypes.func,
50 | /**
51 | * Alignment of the column cell
52 | */
53 | align: PropTypes.oneOf(['left', 'center', 'right']),
54 | /**
55 | * Flex grow style, defaults to 0
56 | */
57 | flexGrow: PropTypes.number,
58 | /**
59 | * Flex shrink style, defaults to 1 for flexible table and 0 for fixed table
60 | */
61 | flexShrink: PropTypes.number,
62 | /**
63 | * The width of the column, gutter width is not included
64 | */
65 | width: PropTypes.number.isRequired,
66 | /**
67 | * Maximum width of the column, used if the column is resizable
68 | */
69 | maxWidth: PropTypes.number,
70 | /**
71 | * Minimum width of the column, used if the column is resizable
72 | */
73 | minWidth: PropTypes.number,
74 | /**
75 | * Whether the column is frozen and what's the frozen side
76 | */
77 | frozen: PropTypes.oneOf(['left', 'right', true, false]),
78 | /**
79 | * Whether the column is hidden
80 | */
81 | hidden: PropTypes.bool,
82 | /**
83 | * Whether the column is resizable, defaults to false
84 | */
85 | resizable: PropTypes.bool,
86 | /**
87 | * Whether the column is sortable, defaults to false
88 | */
89 | sortable: PropTypes.bool,
90 | /**
91 | * Custom column cell renderer
92 | * The renderer receives props `{ cellData, columns, column, columnIndex, rowData, rowIndex, container, isScrolling }`
93 | */
94 | cellRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
95 | /**
96 | * Custom column header renderer
97 | * The renderer receives props `{ columns, column, columnIndex, headerIndex, container }`
98 | */
99 | headerRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
100 | };
101 |
102 | Column.Alignment = Alignment;
103 | Column.FrozenDirection = FrozenDirection;
104 |
105 | export default Column;
106 |
--------------------------------------------------------------------------------
/src/ColumnManager.js:
--------------------------------------------------------------------------------
1 | import { FrozenDirection } from './Column';
2 |
3 | export default class ColumnManager {
4 | constructor(columns, fixed) {
5 | this._origColumns = [];
6 | this.reset(columns, fixed);
7 | }
8 |
9 | _cache(key, fn) {
10 | if (key in this._cached) return this._cached[key];
11 | this._cached[key] = fn();
12 | return this._cached[key];
13 | }
14 |
15 | reset(columns, fixed) {
16 | this._columns = columns.map(column => {
17 | let width = column.width;
18 | if (column.resizable) {
19 | // don't reset column's `width` if `width` prop doesn't change
20 | const idx = this._origColumns.findIndex(x => x.key === column.key);
21 | if (idx >= 0 && this._origColumns[idx].width === column.width) {
22 | width = this._columns[idx].width;
23 | }
24 | }
25 | return { ...column, width };
26 | });
27 | this._origColumns = columns;
28 | this._fixed = fixed;
29 | this._cached = {};
30 | this._columnStyles = this.recomputeColumnStyles();
31 | }
32 |
33 | resetCache() {
34 | this._cached = {};
35 | }
36 |
37 | getOriginalColumns() {
38 | return this._origColumns;
39 | }
40 |
41 | getColumns() {
42 | return this._columns;
43 | }
44 |
45 | getVisibleColumns() {
46 | return this._cache('visibleColumns', () => {
47 | return this._columns.filter(column => !column.hidden);
48 | });
49 | }
50 |
51 | hasFrozenColumns() {
52 | return this._cache('hasFrozenColumns', () => {
53 | return this._fixed && this.getVisibleColumns().some(column => !!column.frozen);
54 | });
55 | }
56 |
57 | hasLeftFrozenColumns() {
58 | return this._cache('hasLeftFrozenColumns', () => {
59 | return (
60 | this._fixed &&
61 | this.getVisibleColumns().some(column => column.frozen === FrozenDirection.LEFT || column.frozen === true)
62 | );
63 | });
64 | }
65 |
66 | hasRightFrozenColumns() {
67 | return this._cache('hasRightFrozenColumns', () => {
68 | return this._fixed && this.getVisibleColumns().some(column => column.frozen === FrozenDirection.RIGHT);
69 | });
70 | }
71 |
72 | getMainColumns() {
73 | return this._cache('mainColumns', () => {
74 | const columns = this.getVisibleColumns();
75 | if (!this.hasFrozenColumns()) return columns;
76 |
77 | const mainColumns = [];
78 | this.getLeftFrozenColumns().forEach(column => {
79 | //columns placeholder for the fixed table above them
80 | mainColumns.push({ ...column, [ColumnManager.PlaceholderKey]: true });
81 | });
82 | this.getVisibleColumns().forEach(column => {
83 | if (!column.frozen) mainColumns.push(column);
84 | });
85 | this.getRightFrozenColumns().forEach(column => {
86 | mainColumns.push({ ...column, [ColumnManager.PlaceholderKey]: true });
87 | });
88 |
89 | return mainColumns;
90 | });
91 | }
92 |
93 | getLeftFrozenColumns() {
94 | return this._cache('leftFrozenColumns', () => {
95 | if (!this._fixed) return [];
96 | return this.getVisibleColumns().filter(
97 | column => column.frozen === FrozenDirection.LEFT || column.frozen === true
98 | );
99 | });
100 | }
101 |
102 | getRightFrozenColumns() {
103 | return this._cache('rightFrozenColumns', () => {
104 | if (!this._fixed) return [];
105 | return this.getVisibleColumns().filter(column => column.frozen === FrozenDirection.RIGHT);
106 | });
107 | }
108 |
109 | getColumn(key) {
110 | const idx = this._columns.findIndex(column => column.key === key);
111 | return this._columns[idx];
112 | }
113 |
114 | getColumnsWidth() {
115 | return this._cache('columnsWidth', () => {
116 | return this.recomputeColumnsWidth(this.getVisibleColumns());
117 | });
118 | }
119 |
120 | getLeftFrozenColumnsWidth() {
121 | return this._cache('leftFrozenColumnsWidth', () => {
122 | return this.recomputeColumnsWidth(this.getLeftFrozenColumns());
123 | });
124 | }
125 |
126 | getRightFrozenColumnsWidth() {
127 | return this._cache('rightFrozenColumnsWidth', () => {
128 | return this.recomputeColumnsWidth(this.getRightFrozenColumns());
129 | });
130 | }
131 |
132 | recomputeColumnsWidth(columns) {
133 | return columns.reduce((width, column) => width + column.width, 0);
134 | }
135 |
136 | setColumnWidth(key, width) {
137 | const column = this.getColumn(key);
138 | column.width = width;
139 | this._cached = {};
140 | this._columnStyles[column.key] = this.recomputeColumnStyle(column);
141 | }
142 |
143 | getColumnStyle(key) {
144 | return this._columnStyles[key];
145 | }
146 |
147 | getColumnStyles() {
148 | return this._columnStyles;
149 | }
150 |
151 | recomputeColumnStyle(column) {
152 | let flexGrow = 0;
153 | let flexShrink = 0;
154 | if (!this._fixed) {
155 | flexGrow = typeof column.flexGrow === 'number' ? column.flexGrow : 0;
156 | flexShrink = typeof column.flexShrink === 'number' ? column.flexShrink : 1;
157 | }
158 | // workaround for Flex bug on IE: https://github.com/philipwalton/flexbugs#flexbug-7
159 | const flexValue = `${flexGrow} ${flexShrink} auto`;
160 |
161 | const style = {
162 | ...column.style,
163 | flex: flexValue,
164 | msFlex: flexValue,
165 | WebkitFlex: flexValue,
166 | width: column.width,
167 | overflow: 'hidden',
168 | };
169 |
170 | if (!this._fixed && column.maxWidth) {
171 | style.maxWidth = column.maxWidth;
172 | }
173 | if (!this._fixed && column.minWidth) {
174 | style.minWidth = column.minWidth;
175 | }
176 |
177 | return style;
178 | }
179 |
180 | recomputeColumnStyles() {
181 | return this._columns.reduce((styles, column) => {
182 | styles[column.key] = this.recomputeColumnStyle(column);
183 | return styles;
184 | }, {});
185 | }
186 | }
187 |
188 | ColumnManager.PlaceholderKey = '__placeholder__';
189 |
--------------------------------------------------------------------------------
/src/ColumnResizer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { noop, addClassName, removeClassName } from './utils';
5 |
6 | const INVALID_VALUE = null;
7 |
8 | // copied from https://github.com/mzabriskie/react-draggable/blob/master/lib/utils/domFns.js
9 | export function addUserSelectStyles(doc) {
10 | if (!doc) return;
11 | let styleEl = doc.getElementById('react-draggable-style-el');
12 | if (!styleEl) {
13 | styleEl = doc.createElement('style');
14 | styleEl.type = 'text/css';
15 | styleEl.id = 'react-draggable-style-el';
16 | styleEl.innerHTML = '.react-draggable-transparent-selection *::-moz-selection {all: inherit;}\n';
17 | styleEl.innerHTML += '.react-draggable-transparent-selection *::selection {all: inherit;}\n';
18 | doc.getElementsByTagName('head')[0].appendChild(styleEl);
19 | }
20 | if (doc.body) addClassName(doc.body, 'react-draggable-transparent-selection');
21 | }
22 |
23 | export function removeUserSelectStyles(doc) {
24 | if (!doc) return;
25 | try {
26 | if (doc.body) removeClassName(doc.body, 'react-draggable-transparent-selection');
27 | if (doc.selection) {
28 | doc.selection.empty();
29 | } else {
30 | // Remove selection caused by scroll, unless it's a focused input
31 | // (we use doc.defaultView in case we're in an iframe)
32 | const selection = (doc.defaultView || window).getSelection();
33 | if (selection && selection.type !== 'Caret') {
34 | selection.removeAllRanges();
35 | }
36 | }
37 | } catch (e) {
38 | // probably IE
39 | }
40 | }
41 |
42 | const eventsFor = {
43 | touch: {
44 | start: 'touchstart',
45 | move: 'touchmove',
46 | stop: 'touchend',
47 | },
48 | mouse: {
49 | start: 'mousedown',
50 | move: 'mousemove',
51 | stop: 'mouseup',
52 | },
53 | };
54 |
55 | let dragEventFor = eventsFor.mouse;
56 |
57 | /**
58 | * ColumnResizer for BaseTable
59 | */
60 | class ColumnResizer extends React.PureComponent {
61 | constructor(props) {
62 | super(props);
63 |
64 | this.isDragging = false;
65 | this.lastX = INVALID_VALUE;
66 | this.width = 0;
67 |
68 | this._setHandleRef = this._setHandleRef.bind(this);
69 | this._handleClick = this._handleClick.bind(this);
70 | this._handleMouseDown = this._handleMouseDown.bind(this);
71 | this._handleMouseUp = this._handleMouseUp.bind(this);
72 | this._handleTouchStart = this._handleTouchStart.bind(this);
73 | this._handleTouchEnd = this._handleTouchEnd.bind(this);
74 | this._handleDragStart = this._handleDragStart.bind(this);
75 | this._handleDragStop = this._handleDragStop.bind(this);
76 | this._handleDrag = this._handleDrag.bind(this);
77 | }
78 |
79 | componentWillUnmount() {
80 | if (this.handleRef) {
81 | const { ownerDocument } = this.handleRef;
82 | ownerDocument.removeEventListener(eventsFor.mouse.move, this._handleDrag);
83 | ownerDocument.removeEventListener(eventsFor.mouse.stop, this._handleDragStop);
84 | ownerDocument.removeEventListener(eventsFor.touch.move, this._handleDrag);
85 | ownerDocument.removeEventListener(eventsFor.touch.stop, this._handleDragStop);
86 | removeUserSelectStyles(ownerDocument);
87 | }
88 | }
89 |
90 | render() {
91 | const { style, column, onResizeStart, onResize, onResizeStop, minWidth, ...rest } = this.props;
92 |
93 | return (
94 |
113 | );
114 | }
115 |
116 | _setHandleRef(ref) {
117 | this.handleRef = ref;
118 | }
119 |
120 | _handleClick(e) {
121 | e.stopPropagation();
122 | }
123 |
124 | _handleMouseDown(e) {
125 | dragEventFor = eventsFor.mouse;
126 | this._handleDragStart(e);
127 | }
128 |
129 | _handleMouseUp(e) {
130 | dragEventFor = eventsFor.mouse;
131 | this._handleDragStop(e);
132 | }
133 |
134 | _handleTouchStart(e) {
135 | dragEventFor = eventsFor.touch;
136 | this._handleDragStart(e);
137 | }
138 |
139 | _handleTouchEnd(e) {
140 | dragEventFor = eventsFor.touch;
141 | this._handleDragStop(e);
142 | }
143 |
144 | _handleDragStart(e) {
145 | if (typeof e.button === 'number' && e.button !== 0) return;
146 |
147 | this.isDragging = true;
148 | this.lastX = INVALID_VALUE;
149 | this.width = this.props.column.width;
150 | this.props.onResizeStart(this.props.column);
151 |
152 | const { ownerDocument } = this.handleRef;
153 | addUserSelectStyles(ownerDocument);
154 | ownerDocument.addEventListener(dragEventFor.move, this._handleDrag);
155 | ownerDocument.addEventListener(dragEventFor.stop, this._handleDragStop);
156 | }
157 |
158 | _handleDragStop(e) {
159 | if (!this.isDragging) return;
160 | this.isDragging = false;
161 |
162 | this.props.onResizeStop(this.props.column);
163 |
164 | const { ownerDocument } = this.handleRef;
165 | removeUserSelectStyles(ownerDocument);
166 | ownerDocument.removeEventListener(dragEventFor.move, this._handleDrag);
167 | ownerDocument.removeEventListener(dragEventFor.stop, this._handleDragStop);
168 | }
169 |
170 | _handleDrag(e) {
171 | let clientX = e.clientX;
172 | if (e.type === eventsFor.touch.move) {
173 | e.preventDefault();
174 | if (e.targetTouches && e.targetTouches[0]) clientX = e.targetTouches[0].clientX;
175 | }
176 |
177 | const { offsetParent } = this.handleRef;
178 | const offsetParentRect = offsetParent.getBoundingClientRect();
179 | const x = clientX + offsetParent.scrollLeft - offsetParentRect.left;
180 |
181 | if (this.lastX === INVALID_VALUE) {
182 | this.lastX = x;
183 | return;
184 | }
185 |
186 | const { column, minWidth: MIN_WIDTH } = this.props;
187 | const { width, maxWidth, minWidth = MIN_WIDTH } = column;
188 | const movedX = x - this.lastX;
189 | if (!movedX) return;
190 |
191 | this.width = this.width + movedX;
192 | this.lastX = x;
193 |
194 | let newWidth = this.width;
195 | if (maxWidth && newWidth > maxWidth) {
196 | newWidth = maxWidth;
197 | } else if (newWidth < minWidth) {
198 | newWidth = minWidth;
199 | }
200 |
201 | if (newWidth === width) return;
202 | this.props.onResize(column, newWidth);
203 | }
204 | }
205 |
206 | ColumnResizer.defaultProps = {
207 | onResizeStart: noop,
208 | onResize: noop,
209 | onResizeStop: noop,
210 | minWidth: 30,
211 | };
212 |
213 | ColumnResizer.propTypes = {
214 | /**
215 | * Custom style for the drag handler
216 | */
217 | style: PropTypes.object,
218 | /**
219 | * The column object to be dragged
220 | */
221 | column: PropTypes.object,
222 | /**
223 | * A callback function when resizing started
224 | * The callback is of the shape of `(column) => *`
225 | */
226 | onResizeStart: PropTypes.func,
227 | /**
228 | * A callback function when resizing the column
229 | * The callback is of the shape of `(column, width) => *`
230 | */
231 | onResize: PropTypes.func,
232 | /**
233 | * A callback function when resizing stopped
234 | * The callback is of the shape of `(column) => *`
235 | */
236 | onResizeStop: PropTypes.func,
237 | /**
238 | * Minimum width of the column could be resized to if the column's `minWidth` is not set
239 | */
240 | minWidth: PropTypes.number,
241 | };
242 |
243 | export default ColumnResizer;
244 |
--------------------------------------------------------------------------------
/src/ExpandIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import cn from 'classnames';
4 |
5 | /**
6 | * default ExpandIcon for BaseTable
7 | */
8 | class ExpandIcon extends React.PureComponent {
9 | constructor(props) {
10 | super(props);
11 |
12 | this._handleClick = this._handleClick.bind(this);
13 | }
14 |
15 | render() {
16 | const { expandable, expanded, indentSize, depth, onExpand, ...rest } = this.props;
17 | if (!expandable && indentSize === 0) return null;
18 |
19 | const cls = cn('BaseTable__expand-icon', {
20 | 'BaseTable__expand-icon--expanded': expanded,
21 | });
22 | return (
23 |
42 | {expandable && '\u25B8'}
43 |
44 | );
45 | }
46 |
47 | _handleClick(e) {
48 | e.stopPropagation();
49 | e.preventDefault();
50 | const { onExpand, expanded } = this.props;
51 | onExpand(!expanded);
52 | }
53 | }
54 |
55 | ExpandIcon.defaultProps = {
56 | depth: 0,
57 | indentSize: 16,
58 | };
59 |
60 | ExpandIcon.propTypes = {
61 | expandable: PropTypes.bool,
62 | expanded: PropTypes.bool,
63 | indentSize: PropTypes.number,
64 | depth: PropTypes.number,
65 | onExpand: PropTypes.func,
66 | };
67 |
68 | export default ExpandIcon;
69 |
--------------------------------------------------------------------------------
/src/SortIndicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import cn from 'classnames';
4 |
5 | import SortOrder from './SortOrder';
6 |
7 | /**
8 | * default SortIndicator for BaseTable
9 | */
10 | const SortIndicator = ({ sortOrder, className, style }) => {
11 | const cls = cn('BaseTable__sort-indicator', className, {
12 | 'BaseTable__sort-indicator--descending': sortOrder === SortOrder.DESC,
13 | });
14 | return (
15 |
26 | {sortOrder === SortOrder.DESC ? '\u2193' : '\u2191'}
27 |
28 | );
29 | };
30 |
31 | SortIndicator.propTypes = {
32 | sortOrder: PropTypes.oneOf([SortOrder.ASC, SortOrder.DESC]),
33 | className: PropTypes.string,
34 | style: PropTypes.object,
35 | };
36 |
37 | export default SortIndicator;
38 |
--------------------------------------------------------------------------------
/src/SortOrder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Sort order for BaseTable
3 | */
4 | const SortOrder = {
5 | /**
6 | * Sort data in ascending order
7 | */
8 | ASC: 'asc',
9 | /**
10 | * Sort data in descending order
11 | */
12 | DESC: 'desc',
13 | };
14 |
15 | export default SortOrder;
16 |
--------------------------------------------------------------------------------
/src/TableCell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { toString } from './utils';
4 |
5 | /**
6 | * Cell component for BaseTable
7 | */
8 | const TableCell = ({ className, cellData, column, columnIndex, rowData, rowIndex }) => (
9 | {React.isValidElement(cellData) ? cellData : toString(cellData)}
10 | );
11 |
12 | TableCell.propTypes = {
13 | className: PropTypes.string,
14 | cellData: PropTypes.any,
15 | column: PropTypes.object,
16 | columnIndex: PropTypes.number,
17 | rowData: PropTypes.object,
18 | rowIndex: PropTypes.number,
19 | };
20 |
21 | export default TableCell;
22 |
--------------------------------------------------------------------------------
/src/TableHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class TableHeader extends React.PureComponent {
5 | constructor(props) {
6 | super(props);
7 |
8 | this.renderHeaderRow = this.renderHeaderRow.bind(this);
9 | this.renderFrozenRow = this.renderFrozenRow.bind(this);
10 | this._setRef = this._setRef.bind(this);
11 | }
12 |
13 | scrollTo(offset) {
14 | requestAnimationFrame(() => {
15 | if (this.headerRef) this.headerRef.scrollLeft = offset;
16 | });
17 | }
18 |
19 | renderHeaderRow(height, index) {
20 | const { columns, headerRenderer } = this.props;
21 | if (height <= 0) return null;
22 |
23 | const style = { width: '100%', height };
24 | return headerRenderer({ style, columns, headerIndex: index });
25 | }
26 |
27 | renderFrozenRow(rowData, index) {
28 | const { columns, rowHeight, rowRenderer } = this.props;
29 | const style = { width: '100%', height: rowHeight };
30 | // for frozen row the `rowIndex` is negative
31 | const rowIndex = -index - 1;
32 | return rowRenderer({ style, columns, rowData, rowIndex });
33 | }
34 |
35 | render() {
36 | const { className, width, height, rowWidth, headerHeight, frozenData } = this.props;
37 | if (height <= 0) return null;
38 |
39 | const style = {
40 | width,
41 | height: height,
42 | position: 'relative',
43 | overflow: 'hidden',
44 | };
45 |
46 | const innerStyle = {
47 | width: rowWidth,
48 | height,
49 | };
50 |
51 | const rowHeights = Array.isArray(headerHeight) ? headerHeight : [headerHeight];
52 | return (
53 |
54 |
55 | {rowHeights.map(this.renderHeaderRow)}
56 | {frozenData.map(this.renderFrozenRow)}
57 |
58 |
59 | );
60 | }
61 |
62 | _setRef(ref) {
63 | this.headerRef = ref;
64 | }
65 | }
66 |
67 | TableHeader.propTypes = {
68 | className: PropTypes.string,
69 | width: PropTypes.number.isRequired,
70 | height: PropTypes.number.isRequired,
71 | headerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
72 | rowWidth: PropTypes.number.isRequired,
73 | rowHeight: PropTypes.number.isRequired,
74 | columns: PropTypes.arrayOf(PropTypes.object).isRequired,
75 | data: PropTypes.array.isRequired,
76 | frozenData: PropTypes.array,
77 | headerRenderer: PropTypes.func.isRequired,
78 | rowRenderer: PropTypes.func.isRequired,
79 | };
80 |
81 | export default TableHeader;
82 |
--------------------------------------------------------------------------------
/src/TableHeaderCell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /**
5 | * HeaderCell component for BaseTable
6 | */
7 | const TableHeaderCell = ({ className, column, columnIndex }) => {column.title}
;
8 |
9 | TableHeaderCell.propTypes = {
10 | className: PropTypes.string,
11 | column: PropTypes.object,
12 | columnIndex: PropTypes.number,
13 | };
14 |
15 | export default TableHeaderCell;
16 |
--------------------------------------------------------------------------------
/src/TableHeaderRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { renderElement } from './utils';
5 |
6 | /**
7 | * HeaderRow component for BaseTable
8 | */
9 | const TableHeaderRow = ({
10 | className,
11 | style,
12 | columns,
13 | headerIndex,
14 | cellRenderer,
15 | headerRenderer,
16 | expandColumnKey,
17 | expandIcon: ExpandIcon,
18 | tagName: Tag,
19 | ...rest
20 | }) => {
21 | let cells = columns.map((column, columnIndex) =>
22 | cellRenderer({
23 | columns,
24 | column,
25 | columnIndex,
26 | headerIndex,
27 | expandIcon: column.key === expandColumnKey && ,
28 | })
29 | );
30 |
31 | if (headerRenderer) {
32 | cells = renderElement(headerRenderer, { cells, columns, headerIndex });
33 | }
34 |
35 | return (
36 |
37 | {cells}
38 |
39 | );
40 | };
41 |
42 | TableHeaderRow.defaultProps = {
43 | tagName: 'div',
44 | };
45 |
46 | TableHeaderRow.propTypes = {
47 | isScrolling: PropTypes.bool,
48 | className: PropTypes.string,
49 | style: PropTypes.object,
50 | columns: PropTypes.arrayOf(PropTypes.object).isRequired,
51 | headerIndex: PropTypes.number,
52 | cellRenderer: PropTypes.func,
53 | headerRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
54 | expandColumnKey: PropTypes.string,
55 | expandIcon: PropTypes.func,
56 | tagName: PropTypes.elementType,
57 | };
58 |
59 | export default TableHeaderRow;
60 |
--------------------------------------------------------------------------------
/src/TableRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { renderElement } from './utils';
5 |
6 | /**
7 | * Row component for BaseTable
8 | */
9 | class TableRow extends React.PureComponent {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | measured: false,
15 | };
16 |
17 | this._setRef = this._setRef.bind(this);
18 | this._handleExpand = this._handleExpand.bind(this);
19 | }
20 |
21 | componentDidMount() {
22 | this.props.estimatedRowHeight && this.props.rowIndex >= 0 && this._measureHeight(true);
23 | }
24 |
25 | componentDidUpdate(prevProps, prevState) {
26 | if (
27 | this.props.estimatedRowHeight &&
28 | this.props.rowIndex >= 0 &&
29 | // should not re-measure if it's updated after measured and reset
30 | !this.props.getIsResetting() &&
31 | this.state.measured &&
32 | prevState.measured
33 | ) {
34 | this.setState({ measured: false }, () => this._measureHeight());
35 | }
36 | }
37 |
38 | render() {
39 | /* eslint-disable no-unused-vars */
40 | const {
41 | isScrolling,
42 | className,
43 | style,
44 | columns,
45 | rowIndex,
46 | rowData,
47 | expandColumnKey,
48 | depth,
49 | rowEventHandlers,
50 | estimatedRowHeight,
51 | rowRenderer,
52 | cellRenderer,
53 | expandIconRenderer,
54 | tagName: Tag,
55 | // omit the following from rest
56 | rowKey,
57 | getIsResetting,
58 | onRowHover,
59 | onRowExpand,
60 | onRowHeightChange,
61 | ...rest
62 | } = this.props;
63 | /* eslint-enable no-unused-vars */
64 |
65 | const expandIcon = expandIconRenderer({ rowData, rowIndex, depth, onExpand: this._handleExpand });
66 | let cells = columns.map((column, columnIndex) =>
67 | cellRenderer({
68 | isScrolling,
69 | columns,
70 | column,
71 | columnIndex,
72 | rowData,
73 | rowIndex,
74 | expandIcon: column.key === expandColumnKey && expandIcon,
75 | })
76 | );
77 |
78 | if (rowRenderer) {
79 | cells = renderElement(rowRenderer, { isScrolling, cells, columns, rowData, rowIndex, depth });
80 | }
81 |
82 | const eventHandlers = this._getEventHandlers(rowEventHandlers);
83 |
84 | if (estimatedRowHeight && rowIndex >= 0) {
85 | const { height, ...otherStyles } = style;
86 | return (
87 |
94 | {cells}
95 |
96 | );
97 | }
98 |
99 | return (
100 |
101 | {cells}
102 |
103 | );
104 | }
105 |
106 | _setRef(ref) {
107 | this.ref = ref;
108 | }
109 |
110 | _handleExpand(expanded) {
111 | const { onRowExpand, rowData, rowIndex, rowKey } = this.props;
112 | onRowExpand && onRowExpand({ expanded, rowData, rowIndex, rowKey });
113 | }
114 |
115 | _measureHeight(initialMeasure) {
116 | if (!this.ref) return;
117 |
118 | const { style, rowKey, onRowHeightChange, rowIndex, columns } = this.props;
119 | const height = this.ref.getBoundingClientRect().height;
120 | this.setState({ measured: true }, () => {
121 | if (initialMeasure || height !== style.height)
122 | onRowHeightChange(rowKey, height, rowIndex, columns[0] && !columns[0].__placeholder__ && columns[0].frozen);
123 | });
124 | }
125 |
126 | _getEventHandlers(handlers = {}) {
127 | const { rowData, rowIndex, rowKey, onRowHover } = this.props;
128 | const eventHandlers = {};
129 | Object.keys(handlers).forEach(eventKey => {
130 | const callback = handlers[eventKey];
131 | if (typeof callback === 'function') {
132 | eventHandlers[eventKey] = event => {
133 | callback({ rowData, rowIndex, rowKey, event });
134 | };
135 | }
136 | });
137 |
138 | if (onRowHover) {
139 | const mouseEnterHandler = eventHandlers['onMouseEnter'];
140 | eventHandlers['onMouseEnter'] = event => {
141 | onRowHover({
142 | hovered: true,
143 | rowData,
144 | rowIndex,
145 | rowKey,
146 | event,
147 | });
148 | mouseEnterHandler && mouseEnterHandler(event);
149 | };
150 |
151 | const mouseLeaveHandler = eventHandlers['onMouseLeave'];
152 | eventHandlers['onMouseLeave'] = event => {
153 | onRowHover({
154 | hovered: false,
155 | rowData,
156 | rowIndex,
157 | rowKey,
158 | event,
159 | });
160 | mouseLeaveHandler && mouseLeaveHandler(event);
161 | };
162 | }
163 |
164 | return eventHandlers;
165 | }
166 | }
167 |
168 | TableRow.defaultProps = {
169 | tagName: 'div',
170 | };
171 |
172 | TableRow.propTypes = {
173 | isScrolling: PropTypes.bool,
174 | className: PropTypes.string,
175 | style: PropTypes.object,
176 | columns: PropTypes.arrayOf(PropTypes.object).isRequired,
177 | rowData: PropTypes.object.isRequired,
178 | rowIndex: PropTypes.number.isRequired,
179 | rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
180 | expandColumnKey: PropTypes.string,
181 | depth: PropTypes.number,
182 | rowEventHandlers: PropTypes.object,
183 | rowRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
184 | cellRenderer: PropTypes.func,
185 | expandIconRenderer: PropTypes.func,
186 | estimatedRowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
187 | getIsResetting: PropTypes.func,
188 | onRowHover: PropTypes.func,
189 | onRowExpand: PropTypes.func,
190 | onRowHeightChange: PropTypes.func,
191 | tagName: PropTypes.elementType,
192 | };
193 |
194 | export default TableRow;
195 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './BaseTable';
2 |
3 | export { default as Column, Alignment, FrozenDirection } from './Column';
4 | export { default as SortOrder } from './SortOrder';
5 | export { default as AutoResizer } from './AutoResizer';
6 | export { default as TableHeader } from './TableHeader';
7 | export { default as TableRow } from './TableRow';
8 |
9 | export {
10 | renderElement,
11 | normalizeColumns,
12 | isObjectEqual,
13 | callOrReturn,
14 | hasChildren,
15 | unflatten,
16 | flattenOnKeys,
17 | getScrollbarSize,
18 | getValue,
19 | } from './utils';
20 |
--------------------------------------------------------------------------------
/website/.eslintignore:
--------------------------------------------------------------------------------
1 | src/examples/
2 |
3 | react-base-table/
4 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # Project dependencies
2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
3 | .cache
4 |
5 | # Build directory
6 | /public
7 |
8 | # Ignore annoying DS files
9 | .DS_Store
10 | *.DS_Store
11 | **.DS_Store
12 |
13 | # npm
14 | node_modules
15 | npm-debug.log
16 | npm-debug.log.*
17 |
18 | # yarn
19 | yarn-error.log
20 | yarn-error.log.*
21 |
--------------------------------------------------------------------------------
/website/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "es5",
5 | "printWidth": 80
6 | }
7 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # react-base-table
2 |
3 | [BaseTable website](https://autodesk.github.io/react-base-table/)
4 |
5 | ## Start
6 |
7 | ```bash
8 | git clone https://github.com/Autodesk/react-base-table.git
9 | cd react-base-table
10 | cd website
11 | yarn # install dependencies
12 | ```
13 |
14 | use `npm install` if you don't get `yarn` installed
15 |
16 | ## Develop
17 |
18 | ```bash
19 | yarn start
20 | ```
21 |
22 | visit http://localhost:8000 after server started, change any file in `src` folder and the webpage will hot reload the changes synchronously
23 |
24 | ## Deploy
25 |
26 | ```bash
27 | yarn deploy
28 | ```
29 |
30 | then your changes will be automatically deployed to the Github Pages
31 |
--------------------------------------------------------------------------------
/website/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pathPrefix: '/react-base-table',
3 | siteMetadata: {
4 | title: 'BaseTable',
5 | description: 'BaseTable website',
6 | keywords: 'react, component, table, basetable',
7 | author: 'Neo Nie (nihgwu@live.com)',
8 | },
9 | plugins: [
10 | {
11 | resolve: `gatsby-plugin-google-analytics`,
12 | options: {
13 | trackingId: 'UA-138491668-1',
14 | },
15 | },
16 | {
17 | resolve: 'gatsby-plugin-manifest',
18 | options: {
19 | name: 'BaseTable',
20 | short_name: 'BaseTable',
21 | start_url: '/',
22 | background_color: '#fff',
23 | theme_color: '#fff',
24 | display: 'minimal-ui',
25 | icon: 'src/assets/favicon.png',
26 | },
27 | },
28 | {
29 | resolve: 'gatsby-plugin-nprogress',
30 | options: {
31 | color: 'tomato',
32 | },
33 | },
34 | 'gatsby-plugin-react-helmet',
35 | 'gatsby-plugin-remove-trailing-slashes',
36 | 'gatsby-plugin-catch-links',
37 | 'gatsby-plugin-lodash',
38 | {
39 | resolve: 'gatsby-plugin-styled-components',
40 | options: {
41 | displayName: false,
42 | },
43 | },
44 | {
45 | resolve: 'gatsby-source-filesystem',
46 | options: {
47 | name: 'examples',
48 | path: `${__dirname}/src/examples`,
49 | },
50 | },
51 | {
52 | resolve: 'gatsby-transformer-code',
53 | options: {
54 | name: 'examples',
55 | },
56 | },
57 | {
58 | resolve: 'gatsby-source-filesystem',
59 | options: {
60 | name: 'docs',
61 | path: `${__dirname}/../docs`,
62 | },
63 | },
64 | {
65 | resolve: 'gatsby-source-filesystem',
66 | options: {
67 | name: 'api',
68 | ignore: ['**/*.snap', '**/*.scss'],
69 | path: `${__dirname}/../src`,
70 | },
71 | },
72 | {
73 | resolve: 'gatsby-transformer-remark',
74 | options: {
75 | plugins: ['gatsby-remark-copy-linked-files'],
76 | },
77 | },
78 | 'gatsby-transformer-react-docgen',
79 | ],
80 | }
81 |
--------------------------------------------------------------------------------
/website/gatsby-node.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const _ = require('lodash')
3 |
4 | const siteConfig = require('./siteConfig')
5 |
6 | exports.onCreateWebpackConfig = ({ stage, getConfig, actions }) => {
7 | const config = getConfig()
8 |
9 | config.resolve.alias = {
10 | ...config.resolve.alias,
11 | assets: path.resolve(__dirname, 'src/assets'),
12 | components: path.resolve(__dirname, 'src/components'),
13 | utils: path.resolve(__dirname, 'src/utils'),
14 | siteConfig: path.resolve(__dirname, 'siteConfig'),
15 | 'react-base-table/package.json': path.resolve(__dirname, '../package.json'),
16 | 'react-base-table/styles.css': path.resolve(__dirname, '../styles.css'),
17 | 'react-base-table': path.resolve(__dirname, '../src'),
18 | }
19 |
20 | actions.replaceWebpackConfig(config)
21 | }
22 |
23 | exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => {
24 | const { createNodeField, createNode, createParentChildLink } = actions
25 | if (node.internal.type === 'MarkdownRemark') {
26 | let slug
27 | const fileNode = getNode(node.parent)
28 | if (!fileNode.relativePath) return
29 |
30 | const parsedFilePath = path.parse(fileNode.relativePath)
31 | if (parsedFilePath.name !== 'index' && parsedFilePath.dir !== '') {
32 | slug = `/${parsedFilePath.dir}/${parsedFilePath.name}`
33 | } else if (parsedFilePath.dir === '') {
34 | slug = `/${parsedFilePath.name}`
35 | } else {
36 | slug = `/${parsedFilePath.dir}`
37 | }
38 | slug = `/${fileNode.sourceInstanceName}${slug}`
39 |
40 | // Add slug as a field on the node.
41 | createNodeField({ node, name: 'slug', value: slug })
42 | } else if (
43 | node.internal.type === 'ComponentMetadata' &&
44 | node.methods.length
45 | ) {
46 | node.methods
47 | .filter(method => method.docblock)
48 | .map(method => {
49 | const methodNode = {
50 | id: createNodeId(`${node.id} >>> ${method.name}`),
51 | parent: node.id,
52 | children: [],
53 | name: method.name,
54 | params: method.params,
55 | description: method.description,
56 | internal: {
57 | type: `ComponentMethodExt`,
58 | mediaType: `text/markdown`,
59 | content: method.description,
60 | contentDigest: method.description,
61 | },
62 | }
63 |
64 | createNode(methodNode)
65 | createParentChildLink({ parent: node, child: methodNode })
66 | })
67 | }
68 | }
69 |
70 | exports.createPages = async ({ graphql, actions, getNode }) => {
71 | const { createPage } = actions
72 |
73 | const docPage = path.resolve('src/templates/doc.js')
74 | const apiPage = path.resolve('src/templates/api.js')
75 | const examplePage = path.resolve('src/templates/example.js')
76 |
77 | const result = await graphql(
78 | `
79 | {
80 | allMarkdownRemark {
81 | edges {
82 | node {
83 | fields {
84 | slug
85 | }
86 | }
87 | }
88 | }
89 | allComponentMetadata {
90 | edges {
91 | node {
92 | parent {
93 | id
94 | }
95 | displayName
96 | docblock
97 | }
98 | }
99 | }
100 | allRawCode {
101 | edges {
102 | node {
103 | name
104 | }
105 | }
106 | }
107 | }
108 | `
109 | )
110 | if (result.errors) {
111 | throw new Error(result.errors)
112 | }
113 |
114 | result.data.allMarkdownRemark.edges.forEach(edge => {
115 | const slug = _.get(edge, 'node.fields.slug')
116 | if (!slug || !slug.includes('docs')) return
117 | createPage({
118 | path: slug,
119 | component: docPage,
120 | context: {
121 | slug,
122 | },
123 | })
124 | })
125 |
126 | result.data.allComponentMetadata.edges.forEach(edge => {
127 | const node = edge.node
128 | const fileNode = getNode(node.parent.id)
129 | if (fileNode.sourceInstanceName !== 'api') return
130 | const { displayName: name, docblock } = node
131 | if (!docblock) return
132 | createPage({
133 | path: `/api/${name.toLowerCase()}`,
134 | component: apiPage,
135 | context: {
136 | name,
137 | },
138 | })
139 | })
140 |
141 | result.data.allRawCode.edges.forEach(edge => {
142 | const name = edge.node.name
143 | createPage({
144 | path: `/examples/${name}`,
145 | component: examplePage,
146 | context: {
147 | name,
148 | },
149 | })
150 | })
151 | }
152 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-base-table-website",
3 | "description": "BaseTable website",
4 | "version": "1.0.0",
5 | "author": "Neo Nie ",
6 | "homepage": "https://autodesk.github.io/react-base-table/",
7 | "dependencies": {
8 | "classnames": "^2.2.6",
9 | "clipboard": "^2.0.4",
10 | "faker": "^4.1.0",
11 | "gatsby": "^2.13.1",
12 | "gatsby-plugin-catch-links": "^2.1.0",
13 | "gatsby-plugin-google-analytics": "^2.1.1",
14 | "gatsby-plugin-lodash": "^3.1.0",
15 | "gatsby-plugin-manifest": "^2.2.1",
16 | "gatsby-plugin-nprogress": "^2.1.0",
17 | "gatsby-plugin-react-helmet": "^3.1.0",
18 | "gatsby-plugin-remove-trailing-slashes": "^2.1.0",
19 | "gatsby-plugin-styled-components": "^3.1.0",
20 | "gatsby-remark-copy-linked-files": "^2.1.0",
21 | "gatsby-source-filesystem": "^2.1.2",
22 | "gatsby-transformer-code": "^0.1.0",
23 | "gatsby-transformer-react-docgen": "^4.1.0",
24 | "gatsby-transformer-remark": "^2.6.0",
25 | "lz-string": "^1.4.4",
26 | "minireset.css": "^0.0.5",
27 | "prop-types": "^15.7.2",
28 | "react": "^16.8.5",
29 | "react-dom": "^16.8.5",
30 | "react-helmet": "^5.2.0",
31 | "react-inspector": "^2.3.1",
32 | "react-live-runner": "^0.7.3",
33 | "react-overlays": "^1.2.0",
34 | "react-sortable-hoc": "^1.9.1",
35 | "react-texty": "^0.1.0",
36 | "rehype-react": "^3.1.0",
37 | "seamless-immutable": "^7.1.4",
38 | "slugify": "^1.3.4",
39 | "styled-components": "^4.3.2"
40 | },
41 | "devDependencies": {
42 | "babel-plugin-styled-components": "^1.10.6",
43 | "gh-pages": "^2.0.1",
44 | "lint-staged": "^9.0.1",
45 | "prettier": "^1.18.2",
46 | "rimraf": "^2.6.3"
47 | },
48 | "browserslist": [
49 | "> 1%",
50 | "IE >= 9",
51 | "last 2 versions"
52 | ],
53 | "scripts": {
54 | "start": "gatsby develop",
55 | "build": "gatsby build",
56 | "clean": "rimraf .cache public",
57 | "deploy": "rimraf .cache public && gatsby build --prefix-paths && gh-pages -d public",
58 | "develop": "gatsby develop",
59 | "serve": "gatsby serve",
60 | "format": "prettier --write 'src/**/*.{js,css}'",
61 | "test": "echo \"Error: no test specified\" && exit 1"
62 | },
63 | "lint-staged": {
64 | "*.js": [
65 | "prettier --write",
66 | "git add"
67 | ],
68 | "*.css": [
69 | "prettier --write",
70 | "git add"
71 | ]
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/website/siteConfig.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | docs: [
3 | {
4 | title: 'Get Started',
5 | path: '/docs/get-started',
6 | },
7 | {
8 | title: 'Advance',
9 | path: '/docs/advance',
10 | },
11 | {
12 | title: 'Selection',
13 | path: '/docs/selection',
14 | },
15 | {
16 | title: 'Inline Editing',
17 | path: '/docs/inline-editing',
18 | },
19 | {
20 | title: 'Glossary',
21 | path: '/docs/glossary',
22 | },
23 | {
24 | title: 'FAQ',
25 | path: '/docs/faq',
26 | },
27 | ],
28 | api: [
29 | {
30 | title: 'BaseTable',
31 | path: '/api/basetable',
32 | },
33 | {
34 | title: 'Column',
35 | path: '/api/column',
36 | },
37 | {
38 | title: 'AutoResizer',
39 | path: '/api/autoresizer',
40 | },
41 | {
42 | title: 'ColumnResizer',
43 | path: '/api/columnresizer',
44 | },
45 | ],
46 | examples: [
47 | {
48 | title: 'Default',
49 | path: '/examples/default',
50 | },
51 | {
52 | title: 'JSX Column',
53 | path: '/examples/jsx-column',
54 | },
55 | {
56 | title: 'Flex Column(column width in ratio)',
57 | path: '/examples/flex-column',
58 | },
59 | {
60 | title: 'Custom Cell',
61 | path: '/examples/custom-cell',
62 | },
63 | {
64 | title: 'Dynamic Row Height',
65 | path: '/examples/dynamic-row-heights',
66 | },
67 | {
68 | title: 'Tooltip Cell',
69 | path: '/examples/tooltip-cell',
70 | },
71 | {
72 | title: 'Auto Resize',
73 | path: '/examples/auto-resize',
74 | },
75 | {
76 | title: 'Fixed Table',
77 | path: '/examples/fixed',
78 | },
79 | {
80 | title: 'Frozen Columns',
81 | path: '/examples/frozen-columns',
82 | },
83 | {
84 | title: 'Frozen Rows',
85 | path: '/examples/frozen-rows',
86 | },
87 | {
88 | title: 'Sticky Rows',
89 | path: '/examples/sticky-rows',
90 | },
91 | {
92 | title: '10000 Rows',
93 | path: '/examples/10000-rows',
94 | },
95 | {
96 | title: 'Disabled',
97 | path: '/examples/disabled',
98 | },
99 | {
100 | title: 'Resizable',
101 | path: '/examples/resizable',
102 | },
103 | {
104 | title: 'Hide Header',
105 | path: '/examples/hide-header',
106 | },
107 | {
108 | title: 'Draggable Rows',
109 | path: '/examples/draggable-rows',
110 | },
111 | {
112 | title: 'Draggable Rows with frozen columns',
113 | path: '/examples/draggable-rows-frozen',
114 | },
115 | {
116 | title: 'Multi Header',
117 | path: '/examples/multi-header',
118 | },
119 | {
120 | title: 'Infinite Loading',
121 | path: '/examples/infinite-loading',
122 | },
123 | {
124 | title: 'Infinite Loading with Loader',
125 | path: '/examples/infinite-loading-loader',
126 | },
127 | {
128 | title: 'Col Span',
129 | path: '/examples/col-span',
130 | },
131 | {
132 | title: 'Row Span',
133 | path: '/examples/row-span',
134 | },
135 | {
136 | title: 'Custom Components',
137 | path: '/examples/components',
138 | },
139 | {
140 | title: 'Custom Row',
141 | path: '/examples/row-renderer',
142 | },
143 | {
144 | title: 'Custom Header',
145 | path: '/examples/header-renderer',
146 | },
147 | {
148 | title: 'Custom Footer',
149 | path: '/examples/footer-renderer',
150 | },
151 | {
152 | title: 'Custom Empty',
153 | path: '/examples/empty-renderer',
154 | },
155 | {
156 | title: 'Custom Overlay',
157 | path: '/examples/overlay-renderer',
158 | },
159 | {
160 | title: 'Custom Width/Height',
161 | path: '/examples/width-height',
162 | },
163 | {
164 | title: 'Custom Scrollbar',
165 | path: '/examples/scrollbar',
166 | },
167 | {
168 | title: 'Max Height',
169 | path: '/examples/max-height',
170 | },
171 | {
172 | title: 'Custom Row/Header Height',
173 | path: '/examples/row-header-height',
174 | },
175 | {
176 | title: 'Row Event Handlers',
177 | path: '/examples/row-event-handlers',
178 | },
179 | {
180 | title: 'Sortable',
181 | path: '/examples/sortable',
182 | },
183 | {
184 | title: 'Multi Sort',
185 | path: '/examples/multi-sort',
186 | },
187 | {
188 | title: 'Selection',
189 | path: '/examples/selection',
190 | },
191 | {
192 | title: 'Expand - Uncontrolled',
193 | path: '/examples/expand-uncontrolled',
194 | },
195 | {
196 | title: 'Expand - Controlled',
197 | path: '/examples/expand-controlled',
198 | },
199 | {
200 | title: 'Detail View',
201 | path: '/examples/detail-view',
202 | },
203 | {
204 | title: 'Use IsScrolling',
205 | path: '/examples/is-scrolling',
206 | },
207 | {
208 | title: 'Custom RowKey',
209 | path: '/examples/row-key',
210 | },
211 | {
212 | title: 'Extra Props',
213 | path: '/examples/extra-props',
214 | },
215 | {
216 | title: 'ExpandIcon Props',
217 | path: '/examples/expand-icon-props',
218 | },
219 | {
220 | title: 'Tag Name',
221 | path: '/examples/tag-name',
222 | },
223 | {
224 | title: 'Inline Editing',
225 | path: '/examples/inline-editing',
226 | },
227 | {
228 | title: 'Scroll Methods',
229 | path: '/examples/scroll-to',
230 | },
231 | {
232 | title: 'Column Hovering',
233 | path: '/examples/column-hovering',
234 | },
235 | ],
236 | }
237 |
--------------------------------------------------------------------------------
/website/src/assets/external-url.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/website/src/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Autodesk/react-base-table/ef4f9e34488e5a149aa750d429a8adaf071275ae/website/src/assets/favicon.png
--------------------------------------------------------------------------------
/website/src/assets/mark-github.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/website/src/components/ActionPanel.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import Inspector from 'react-inspector'
4 |
5 | import CornerButton from './CornerButton'
6 |
7 | const Container = styled.div`
8 | position: relative;
9 | outline: 1px solid #edf0f2;
10 | padding: 15px 0;
11 | `
12 |
13 | const ActionsContainer = styled.div`
14 | max-height: 200px;
15 | overflow-y: auto;
16 | padding: 0 15px;
17 | `
18 |
19 | const ClearButton = styled(CornerButton)`
20 | background-color: transparent;
21 | `
22 |
23 | class ActionPanel extends React.Component {
24 | constructor(props) {
25 | super(props)
26 | this.props.channel.on(this.onAction)
27 | }
28 |
29 | state = {
30 | actions: [],
31 | }
32 |
33 | componentWillUnmount() {
34 | this.props.channel.off(this.onAction)
35 | }
36 |
37 | render() {
38 | const { actions } = this.state
39 | if (!actions.length) return null
40 | return (
41 |
42 |
43 | {actions.map((action, idx) => (
44 |
50 | ))}
51 |
52 | clear
53 |
54 | )
55 | }
56 |
57 | onAction = action => {
58 | this.setState(({ actions }) => ({
59 | actions: [action, ...actions.slice(0, 99)],
60 | }))
61 | }
62 |
63 | onClear = () => {
64 | this.setState({
65 | actions: [],
66 | })
67 | }
68 | }
69 |
70 | export default ActionPanel
71 |
--------------------------------------------------------------------------------
/website/src/components/Anchor.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import slugify from 'slugify'
4 |
5 | const Container = styled.div`
6 | position: relative;
7 | `
8 |
9 | const Span = styled.span`
10 | display: block;
11 | height: 70px;
12 | margin-top: -70px;
13 | visibility: hidden;
14 | `
15 |
16 | const Link = styled.a`
17 | position: absolute;
18 | left: -1.6rem;
19 |
20 | &::before {
21 | content: '#';
22 | visibility: hidden;
23 | }
24 |
25 | &:hover {
26 | &::before {
27 | visibility: visible;
28 | }
29 | }
30 | `
31 |
32 | const Anchor = ({ tagName = 'h2', children, title, link, ...rest }) => {
33 | if (!title && !children) return null
34 |
35 | const slug = link || slugify(title || children, { lower: true })
36 | return (
37 |
38 |
39 |
40 | {children || title}
41 |
42 | )
43 | }
44 |
45 | export default Anchor
46 |
--------------------------------------------------------------------------------
/website/src/components/CodeBlock.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { CodeBlock as Code } from 'react-live-runner'
4 |
5 | import CopyButton from './CopyButton'
6 |
7 | const Container = styled.div`
8 | position: relative;
9 | overflow: hidden;
10 | border-radius: 0.3rem;
11 | `
12 |
13 | const Scroll = styled.div`
14 | overflow: auto;
15 | `
16 |
17 | const StyledCode = styled(Code)`
18 | float: left;
19 | min-width: 100%;
20 | `
21 |
22 | const CodeBlock = ({ code = '', language = 'jsx', ...rest }) => (
23 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 |
31 | export default CodeBlock
32 |
--------------------------------------------------------------------------------
/website/src/components/CodeEditor.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, useMemo, useEffect } from 'react'
2 | import styled from 'styled-components'
3 | import { CodeEditor as Editor } from 'react-live-runner'
4 | import { debounce } from 'lodash'
5 |
6 | import CopyButton from './CopyButton'
7 |
8 | const Container = styled.div`
9 | position: relative;
10 | overflow: hidden;
11 | `
12 |
13 | const EditorContainer = styled.div`
14 | overflow: auto;
15 | height: 100%;
16 | `
17 |
18 | const StyledEditor = styled(Editor)`
19 | font-family: source-code-pro, Menlo, Monaco, Consolas, Courier New, monospace;
20 | font-size: 1.4rem;
21 | white-space: pre;
22 | background: #222;
23 | caret-color: #fff;
24 | min-width: 100%;
25 | min-height: 100%;
26 | float: left;
27 |
28 | & > textarea,
29 | & > pre {
30 | outline: none;
31 | white-space: pre !important;
32 | }
33 | `
34 |
35 | const CodeEditor = ({ sourceCode, language, onChange, ...rest }) => {
36 | const [code, setCode] = useState(sourceCode)
37 | const debouncedChange = useMemo(() => debounce(onChange, 300), [onChange])
38 | const handleChange = useCallback(code => {
39 | setCode(code)
40 | debouncedChange(code)
41 | }, [debouncedChange])
42 | useEffect(() => setCode(sourceCode), [sourceCode])
43 |
44 | return (
45 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 | }
53 |
54 | export default CodeEditor
55 |
--------------------------------------------------------------------------------
/website/src/components/CodePreview.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import styled from 'styled-components'
3 | import { useLiveRunner } from 'react-live-runner'
4 |
5 | import ActionPanel from './ActionPanel'
6 | import CodeEditor from './CodeEditor'
7 | import { createActionChannel } from 'utils/actionChannel'
8 | import baseScope from 'utils/baseScope'
9 |
10 | const Container = styled.div`
11 | height: 100%;
12 | `
13 |
14 | const StyledEditor = styled(CodeEditor)`
15 | height: 30rem;
16 | border-radius: 0.3rem;
17 | `
18 |
19 | const PreviewContainer = styled.div`
20 | min-height: 40rem;
21 | position: relative;
22 | display: flex;
23 | flex-direction: column;
24 | align-items: center;
25 | justify-content: center;
26 | overflow: auto;
27 | padding: 1rem;
28 | margin-bottom: 1rem;
29 | box-shadow: 0 0 0.8rem 0 lightsteelblue;
30 | `
31 |
32 | const Preview = styled.div`
33 | margin: auto;
34 | `
35 |
36 | const Error = styled.div`
37 | background: #fcc;
38 | position: absolute;
39 | top: 0;
40 | left: 0;
41 | min-width: 100%;
42 | margin: 0;
43 | padding: 1rem;
44 | color: #f00;
45 | white-space: pre-wrap;
46 | `
47 |
48 | const CodePreview = ({
49 | code: sourceCode,
50 | scope: _scope,
51 | language,
52 | type,
53 | editorHeight = 300,
54 | ...rest
55 | }) => {
56 | const { action, channel } = useMemo(createActionChannel, [])
57 | const scope = useMemo(() => ({ ...baseScope, action, ..._scope }), [
58 | action,
59 | _scope,
60 | ])
61 | const { element, error, onChange } = useLiveRunner({
62 | sourceCode,
63 | scope,
64 | type,
65 | })
66 |
67 | return (
68 |
69 |
70 | {error && {error} }
71 | {element}
72 |
73 |
74 |
80 |
81 | )
82 | }
83 |
84 | export default CodePreview
85 |
--------------------------------------------------------------------------------
/website/src/components/CopyButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import clipboard from 'clipboard'
3 |
4 | import CornerButton from './CornerButton'
5 |
6 | class CopyButton extends React.PureComponent {
7 | state = {
8 | text: this.props.text,
9 | }
10 |
11 | handleRef = ref => (this.ref = ref)
12 |
13 | onSuccess = () => {
14 | this.clearTimer()
15 | this.setState({ text: 'copied' }, () => {
16 | this.timer = setTimeout(() => {
17 | this.setState({ text: this.props.text })
18 | }, 300)
19 | })
20 | }
21 |
22 | onError = () => {
23 | this.clearTimer()
24 | this.setState({ text: 'failed' }, () => {
25 | this.timer = setTimeout(() => {
26 | this.setState({ text: this.props.text })
27 | }, 300)
28 | })
29 | }
30 |
31 | clearTimer = () => {
32 | this.timer && clearTimeout(this.timer)
33 | }
34 |
35 | componentDidMount() {
36 | this.clearTimer()
37 | this.clipboard = new clipboard(this.ref)
38 |
39 | this.clipboard.on('success', this.onSuccess)
40 | this.clipboard.on('error', this.onError)
41 | }
42 |
43 | componentWillUnmount() {
44 | this.clipboard && this.clipboard.destroy()
45 | }
46 |
47 | render() {
48 | const { content, ...rest } = this.props
49 | const { text } = this.state
50 | return (
51 |
56 | {text}
57 |
58 | )
59 | }
60 | }
61 |
62 | CopyButton.defaultProps = {
63 | text: 'copy',
64 | }
65 |
66 | export default CopyButton
67 |
--------------------------------------------------------------------------------
/website/src/components/CornerButton.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const CornerButton = styled.button`
4 | position: absolute;
5 | top: 0;
6 | right: 0;
7 | border-bottom-left-radius: 0.3em;
8 | padding: 0.2em 0.8em;
9 | outline: none;
10 | border: 0;
11 | opacity: 0.5;
12 | font-size: 1.4rem;
13 | color: #000;
14 | transition: opacity 0.15s;
15 |
16 | &:hover {
17 | opacity: 0.8;
18 | cursor: pointer;
19 | }
20 | `
21 |
22 | export default CornerButton
23 |
--------------------------------------------------------------------------------
/website/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'gatsby'
3 | import styled from 'styled-components'
4 | import pkg from 'react-base-table/package.json'
5 |
6 | import linkIcon from 'assets/mark-github.svg'
7 |
8 | const Container = styled.div`
9 | background-color: #182a3d;
10 | position: fixed;
11 | top: 0;
12 | width: 100%;
13 | z-index: 1001;
14 | `
15 |
16 | const Nav = styled.div`
17 | margin: 0 auto;
18 | max-width: 100rem;
19 | height: 5rem;
20 | padding: 0 2rem;
21 | display: flex;
22 | align-items: center;
23 | justify-content: space-between;
24 | `
25 |
26 | const Title = styled(Link)`
27 | text-decoration: none;
28 | font-size: 2.4rem;
29 | line-height: 1;
30 | padding: 1rem 0;
31 | &,
32 | &:hover,
33 | &:focus {
34 | color: #fff;
35 | }
36 | `
37 |
38 | const Spacer = styled.div`
39 | flex: 1;
40 | `
41 |
42 | const NavLink = styled(Link).attrs({
43 | partiallyActive: true,
44 | })`
45 | color: #bcc9d1;
46 | text-decoration: none;
47 | padding: 1rem;
48 | line-height: 1;
49 | &:hover {
50 | color: #fff;
51 | }
52 | &,
53 | &:focus {
54 | color: ${props =>
55 | props.pathname && props.pathname.includes(props.to) ? '#fff' : '#bcc9d1'};
56 | }
57 | &:last-child {
58 | padding-right: 0;
59 | display: inline-block;
60 | }
61 | `
62 |
63 | const ExternalLink = NavLink.withComponent('a')
64 |
65 | const LinkIcon = styled.img`
66 | width: 2rem;
67 | height: 2rem;
68 | `
69 |
70 | const Version = styled(ExternalLink)`
71 | font-size: 1.4rem;
72 | padding: 1rem;
73 | margin-top: 1rem;
74 | `
75 |
76 | const Header = ({ pathname }) => {
77 | return (
78 |
79 |
80 | BaseTable
81 |
85 | v{pkg.version}
86 |
87 |
88 |
89 | Docs
90 |
91 |
92 | API
93 |
94 |
95 | Examples
96 |
97 |
98 | Playground
99 |
100 |
101 |
102 |
103 |
104 |
105 | )
106 | }
107 |
108 | export default Header
109 |
--------------------------------------------------------------------------------
/website/src/components/Html.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import rehypeReact from 'rehype-react'
3 |
4 | import CodeBlock from './CodeBlock'
5 | import CodePreview from './CodePreview'
6 |
7 | const parseMeta = meta => {
8 | const options = {}
9 | if (!meta) return options
10 |
11 | const items = meta.split(/\s+/)
12 | items.forEach(item => {
13 | if (/^[\w-]+=?$/.test(item)) options[item] = true
14 | else if (/^[\w-]+=[^=]+$/.test(item)) {
15 | const [key, value] = item.split('=')
16 | let parsed = value
17 | if (value === 'true') parsed = true
18 | else if (value === 'false') parsed = false
19 | else if (/^\d+$/.test(value)) parsed = parseInt(value, 10)
20 | else if (/^\d*\.\d+$/.test(value)) parsed = parseFloat(value)
21 | else if (/^['"].*['"]$/.test(value))
22 | parsed = value.substr(1, value.length - 2)
23 | else if (/^{.*}$/.test(value)) {
24 | try {
25 | // eslint-disable-next-line no-eval
26 | parsed = eval(`(${value})`)
27 | } catch (err) {}
28 | }
29 | options[key] = parsed
30 | }
31 | })
32 | return options
33 | }
34 |
35 | const Pre = props => {
36 | if (!props.children[0]) return
37 |
38 | const { children, className } = props.children[0].props
39 | const language = className && className.split('-')[1]
40 | const code = children[0]
41 |
42 | const meta = parseMeta(props.children[0].props['data-meta'])
43 | const { live, ...rest } = meta
44 | const Component = live ? CodePreview : CodeBlock
45 | return (
46 |
52 | )
53 | }
54 |
55 | const renderAst = new rehypeReact({
56 | createElement: React.createElement,
57 | components: {
58 | pre: Pre,
59 | },
60 | }).Compiler
61 |
62 | const Html = ({ html, htmlAst, ...rest }) => {
63 | if (htmlAst) {
64 | return {renderAst(htmlAst)}
65 | }
66 |
67 | if (html) {
68 | return
69 | }
70 |
71 | return null
72 | }
73 |
74 | export default Html
75 |
--------------------------------------------------------------------------------
/website/src/components/Methods.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import Html from './Html'
5 | import Anchor from './Anchor'
6 |
7 | const Method = styled.div`
8 | margin-bottom: 1.6rem;
9 | `
10 |
11 | const Name = styled.div`
12 | font-weight: 600;
13 | `
14 |
15 | const Tag = styled.span`
16 | font-size: 0.8em;
17 | padding: 0.1em 0.4em;
18 | margin: 0 0.4em;
19 | border-radius: 0.2em;
20 | background-color: #daf0f9;
21 | color: #819099;
22 | `
23 |
24 | const Block = styled(Html)`
25 | color: #666;
26 | `
27 |
28 | const getSignature = params =>
29 | `(${params
30 | .map(x => `${x.name}${x.type ? `: ${x.type.name}` : ''}`)
31 | .join(', ')})`
32 |
33 | const Methods = ({ title = 'Methods', methods, ...rest }) => (
34 |
35 |
{title}
36 | {Array.isArray(methods) &&
37 | methods.map((method, idx) => (
38 |
39 |
40 | {method.name}
41 | {getSignature(method.params)}
42 |
43 |
44 |
45 | ))}
46 |
47 | )
48 |
49 | export default Methods
50 |
--------------------------------------------------------------------------------
/website/src/components/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Helmet from 'react-helmet'
3 | import styled, { css } from 'styled-components'
4 | import { StaticQuery, graphql } from 'gatsby'
5 |
6 | import Header from './Header'
7 | import Sidebar from './Sidebar'
8 |
9 | import '../styles/index.css'
10 | import 'react-base-table/styles.css'
11 |
12 | const pageMixin = css`
13 | margin: 0 auto;
14 | max-width: 100rem;
15 | `
16 |
17 | const Container = styled.div`
18 | position: relative;
19 | padding: 70px 20px 20px;
20 | ${props => !props.full && pageMixin};
21 | `
22 |
23 | const Content = styled.div`
24 | margin-left: 240px;
25 | `
26 |
27 | const Page = ({ title, location = {}, children, links, ...rest }) => (
28 | (
31 |
32 |
41 |
42 |
43 | {links ? (
44 |
45 |
46 | {children}
47 |
48 | ) : (
49 | children
50 | )}
51 |
52 |
53 | )}
54 | />
55 | )
56 |
57 | export default Page
58 |
59 | const detailsQuery = graphql`
60 | query {
61 | site {
62 | config: siteMetadata {
63 | title
64 | description
65 | keywords
66 | author
67 | }
68 | }
69 | }
70 | `
71 |
--------------------------------------------------------------------------------
/website/src/components/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { Link } from 'gatsby'
4 |
5 | const Container = styled.div`
6 | display: flex;
7 | align-items: center;
8 | justify-content: space-between;
9 | height: 5rem;
10 | margin-top: 2rem;
11 | border-top: 1px solid #edf0f2;
12 | `
13 |
14 | const StyledLink = styled(Link)`
15 | display: block;
16 | text-decoration: none;
17 | white-space: nowrap;
18 | text-overflow: ellipsis;
19 | overflow: hidden;
20 | color: #888;
21 |
22 | &:hover,
23 | &:active {
24 | color: #222;
25 | }
26 | `
27 |
28 | const Pagination = ({ links, link, ...rest }) => {
29 | const index = links.indexOf(link)
30 | if (index < 0) return null
31 | const prevLink = index === 0 ? null : links[index - 1]
32 | const nextLink = index === links.length - 1 ? null : links[index + 1]
33 |
34 | return (
35 |
36 |
37 | {prevLink && (
38 | ← {prevLink.title}
39 | )}
40 |
41 |
42 | {nextLink && (
43 | {nextLink.title}→
44 | )}
45 |
46 |
47 | )
48 | }
49 |
50 | export default Pagination
51 |
--------------------------------------------------------------------------------
/website/src/components/Playground.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useCallback, useState, useEffect } from 'react'
2 | import styled from 'styled-components'
3 | import { useLiveRunner } from 'react-live-runner'
4 | import CodeEditor from './CodeEditor'
5 | import CopyButton from './CopyButton'
6 |
7 | import baseScope from 'utils/baseScope'
8 | import { getCode, replaceState } from 'utils/urlHash'
9 |
10 | const Container = styled.div`
11 | position: relative;
12 | display: flex;
13 | box-shadow: 0 0 8px 0 lightsteelblue;
14 | height: 100%;
15 | `
16 |
17 | const StyledEditor = styled(CodeEditor)`
18 | flex: 0 1 60rem;
19 | `
20 |
21 | const PreviewContainer = styled.div`
22 | position: relative;
23 | flex: 1 1 60rem;
24 | display: flex;
25 | flex-direction: column;
26 | align-items: center;
27 | justify-content: center;
28 | background: #f3f3f3;
29 | overflow: auto;
30 | background: #fff;
31 | `
32 |
33 | const Preview = styled.div`
34 | margin: auto;
35 | `
36 |
37 | const Error = styled.div`
38 | background: #fcc;
39 | position: absolute;
40 | top: 0;
41 | left: 0;
42 | min-width: 100%;
43 | margin: 0;
44 | padding: 1rem;
45 | color: #f00;
46 | white-space: pre-wrap;
47 | `
48 |
49 | const Playground = ({ scope: _scope, language, type, ...rest }) => {
50 | const scope = useMemo(() => ({ ...baseScope, ..._scope }), [_scope])
51 | const [sourceCode, setSourceCode] = useState(getCode)
52 | const { element, error, onChange } = useLiveRunner({
53 | sourceCode,
54 | scope,
55 | type,
56 | })
57 | const handleChange = useCallback(
58 | code => {
59 | onChange(code)
60 | replaceState(code)
61 | },
62 | [onChange]
63 | )
64 |
65 | const canUseDOM = typeof document !== 'undefined'
66 | useEffect(() => {
67 | setSourceCode(getCode)
68 | }, [canUseDOM])
69 |
70 | return (
71 |
72 |
77 |
78 | {error && {error} }
79 | {element}
80 |
81 | {typeof document !== 'undefined' && (
82 |
83 | )}
84 |
85 | )
86 | }
87 |
88 | export default Playground
89 |
--------------------------------------------------------------------------------
/website/src/components/Props.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import Html from './Html'
5 | import Anchor from './Anchor'
6 |
7 | const Prop = styled.div`
8 | margin-bottom: 1.6rem;
9 | `
10 |
11 | const Name = styled.div`
12 | font-weight: 600;
13 | `
14 |
15 | const Tag = styled.span`
16 | font-size: 0.8em;
17 | padding: 0.1em 0.4em;
18 | margin: 0 0.4em;
19 | border-radius: 0.2em;
20 | background-color: #daf0f9;
21 | color: #819099;
22 | `
23 |
24 | const Required = styled(Tag)`
25 | background-color: #182a3d;
26 | color: #fff;
27 | margin: 0;
28 | `
29 |
30 | const DefaultValue = styled.span`
31 | color: #819099;
32 | `
33 |
34 | const Block = styled(Html)`
35 | color: #666;
36 | `
37 |
38 | const parseType = type => {
39 | if (!type) return 'unknown'
40 |
41 | if (type.name === 'enum') {
42 | if (typeof type.value === 'string') return type.value
43 | return type.value.map(x => x.value).join(' | ')
44 | }
45 |
46 | if (type.name === 'union') {
47 | return type.value.map(x => x.name).join(' | ')
48 | }
49 |
50 | return type.name
51 | }
52 |
53 | const Props = ({ title = 'Props', props, ...rest }) => {
54 | return (
55 |
56 |
{title}
57 | {Array.isArray(props) &&
58 | props.map(prop => (
59 |
60 |
61 | {prop.name}
62 | {parseType(prop.type)}
63 | {prop.defaultValue && (
64 |
65 | defaults to
66 | {prop.defaultValue.value}
67 |
68 | )}
69 | {prop.required && required }
70 |
71 | {prop.description && (
72 |
73 | )}
74 |
75 | ))}
76 |
77 | )
78 | }
79 |
80 | export default Props
81 |
--------------------------------------------------------------------------------
/website/src/components/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { Link } from 'gatsby'
4 |
5 | import linkIcon from 'assets/external-url.svg'
6 |
7 | const Container = styled.div`
8 | position: fixed;
9 | top: 7rem;
10 | bottom: 2rem;
11 | overflow-y: auto;
12 | width: 22rem;
13 | min-width: 22rem;
14 | padding-right: 2rem;
15 | border-right: 1px solid #edf0f2;
16 | `
17 |
18 | const Ul = styled.ul`
19 | padding-top: 1rem;
20 | `
21 |
22 | const Li = styled.li`
23 | padding-bottom: 1rem;
24 | `
25 |
26 | const StyledLink = styled(Link).attrs({
27 | activeStyle: {
28 | fontWeight: 700,
29 | borderRight: '3px solid #0696d7',
30 | },
31 | })`
32 | display: block;
33 | text-decoration: none;
34 | white-space: nowrap;
35 | text-overflow: ellipsis;
36 | overflow: hidden;
37 | color: #222;
38 | `
39 |
40 | const ExternalLink = StyledLink.withComponent('a')
41 |
42 | const LinkIcon = styled.img`
43 | width: 1.4rem;
44 | height: 1.4rem;
45 | margin-left: 0.4rem;
46 | `
47 |
48 | const Sidebar = ({ links }) => (
49 |
50 |
51 | {links.map(({ key, to, title, external }) => (
52 |
53 | {external ? (
54 |
55 | {title}
56 |
57 |
58 | ) : (
59 | {title}
60 | )}
61 |
62 | ))}
63 |
64 |
65 | )
66 |
67 | export default Sidebar
68 |
--------------------------------------------------------------------------------
/website/src/examples/10000-rows.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 10000)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | export default () =>
12 |
--------------------------------------------------------------------------------
/website/src/examples/auto-resize.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const Container = styled.div`
5 | width: calc(50vw + 220px);
6 | height: 50vh;
7 | `
8 |
9 | const Hint = styled.div`
10 | font-size: 16px;
11 | font-weight: 700;
12 | color: #336699;
13 | margin-bottom: 10px;
14 | `
15 |
16 | export default () => (
17 | <>
18 | Resize your browser and see
19 |
20 |
21 | {({ width, height }) => (
22 |
28 | )}
29 |
30 |
31 | >
32 | )
33 |
--------------------------------------------------------------------------------
/website/src/examples/col-span.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10, undefined, { resizable: true })
2 | const data = generateData(columns, 200)
3 |
4 | const spanIndex = 1
5 | columns[spanIndex].colSpan = ({ rowData, rowIndex }) => (rowIndex % 4) + 1
6 | columns[spanIndex].align = Column.Alignment.CENTER
7 |
8 | const rowRenderer = ({ rowData, rowIndex, cells, columns }) => {
9 | const span = columns[spanIndex].colSpan({ rowData, rowIndex })
10 | if (span > 1) {
11 | let width = cells[spanIndex].props.style.width
12 | for (let i = 1; i < span; i++) {
13 | width += cells[spanIndex + i].props.style.width
14 | cells[spanIndex + i] = null
15 | }
16 | const style = {
17 | ...cells[spanIndex].props.style,
18 | width,
19 | backgroundColor: 'lightgray',
20 | }
21 | cells[spanIndex] = React.cloneElement(cells[spanIndex], { style })
22 | }
23 | return cells
24 | }
25 |
26 | export default () => (
27 |
28 | )
29 |
--------------------------------------------------------------------------------
/website/src/examples/column-hovering.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 | const tableRef = React.createRef()
4 |
5 | const GlobalStyle = createGlobalStyle`
6 | .BaseTable.active-col-0 [data-col-idx="0"],
7 | .BaseTable.active-col-1 [data-col-idx="1"],
8 | .BaseTable.active-col-2 [data-col-idx="2"],
9 | .BaseTable.active-col-3 [data-col-idx="3"],
10 | .BaseTable.active-col-4 [data-col-idx="4"],
11 | .BaseTable.active-col-5 [data-col-idx="5"],
12 | .BaseTable.active-col-6 [data-col-idx="6"],
13 | .BaseTable.active-col-7 [data-col-idx="7"],
14 | .BaseTable.active-col-8 [data-col-idx="8"],
15 | .BaseTable.active-col-9 [data-col-idx="9"] {
16 | background: #f3f3f3;
17 | }
18 | `
19 |
20 | const cellProps = ({ columnIndex }) => ({
21 | 'data-col-idx': columnIndex,
22 | onMouseEnter: () => {
23 | const table = tableRef.current.getDOMNode()
24 | table.classList.add(`active-col-${columnIndex}`)
25 | },
26 | onMouseLeave: () => {
27 | const table = tableRef.current.getDOMNode()
28 | table.classList.remove(`active-col-${columnIndex}`)
29 | },
30 | })
31 |
32 | const headerCellProps = ({ columnIndex }) => ({
33 | 'data-col-idx': columnIndex,
34 | })
35 |
36 | export default () => (
37 | <>
38 |
39 |
46 | >
47 | )
48 |
--------------------------------------------------------------------------------
/website/src/examples/components.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | fixedColumns[0].format = 'checkbox'
12 | fixedColumns[1].format = 'contact'
13 |
14 | const Contact = styled.div`
15 | font-weight: 700;
16 | color: orange;
17 | `
18 |
19 | const stringRenderer = ({ className, cellData }) => (
20 | {cellData}
21 | )
22 | const checkboxRenderer = ({ rowIndex }) => (
23 |
24 | )
25 | const contactRenderer = ({ cellData }) => {cellData}
26 |
27 | const renderers = {
28 | string: stringRenderer,
29 | checkbox: checkboxRenderer,
30 | contact: contactRenderer,
31 | }
32 |
33 | const Cell = cellProps => {
34 | const format = cellProps.column.format || 'string'
35 | const renderer = renderers[format] || renderers.string
36 |
37 | return renderer(cellProps)
38 | }
39 |
40 | const components = {
41 | TableCell: Cell,
42 | }
43 |
44 | const expandColumnKey = 'column-1'
45 | const treeData = unflatten(data)
46 |
47 | export default () => (
48 |
56 | )
57 |
--------------------------------------------------------------------------------
/website/src/examples/custom-cell.js:
--------------------------------------------------------------------------------
1 | const dataGenerator = () => ({
2 | id: faker.random.uuid(),
3 | name: faker.name.findName(),
4 | gender: faker.random.boolean() ? 'male' : 'female',
5 | score: {
6 | math: faker.random.number(70) + 30,
7 | },
8 | birthday: faker.date.between(1995, 2005),
9 | attachments: faker.random.number(5),
10 | description: faker.lorem.sentence(),
11 | email: faker.internet.email(),
12 | country: faker.address.country(),
13 | address: {
14 | street: faker.address.streetAddress(),
15 | city: faker.address.city(),
16 | zipCode: faker.address.zipCode(),
17 | },
18 | })
19 |
20 | const GenderContainer = styled.div`
21 | background-color: ${props =>
22 | props.gender === 'male' ? 'lightblue' : 'pink'};
23 | color: white;
24 | border-radius: 3px;
25 | width: 20px;
26 | height: 20px;
27 | font-size: 16px;
28 | font-weight: bold;
29 | line-height: 20px;
30 | text-align: center;
31 | `
32 |
33 | const Gender = ({ gender }) => (
34 |
35 | {gender === 'male' ? '♂' : '♀'}
36 |
37 | )
38 |
39 | const Score = styled.span`
40 | color: ${props => (props.score >= 60 ? 'green' : 'red')};
41 | `
42 |
43 | const Attachment = styled.div`
44 | background-color: lightgray;
45 | width: 20px;
46 | height: 20px;
47 | line-height: 20px;
48 | text-align: center;
49 | border-radius: 4px;
50 | color: gray;
51 | `
52 |
53 | const defaultData = new Array(5000)
54 | .fill(0)
55 | .map(dataGenerator)
56 | .sort((a, b) => (a.name > b.name ? 1 : -1))
57 |
58 | const defaultSort = { key: 'name', order: SortOrder.ASC }
59 |
60 | export default class App extends React.Component {
61 | state = {
62 | data: defaultData,
63 | sortBy: defaultSort,
64 | }
65 |
66 | columns = [
67 | {
68 | key: 'name',
69 | title: 'Name',
70 | dataKey: 'name',
71 | width: 150,
72 | resizable: true,
73 | sortable: true,
74 | frozen: Column.FrozenDirection.LEFT,
75 | },
76 | {
77 | key: 'score',
78 | title: 'Score',
79 | dataKey: 'score.math',
80 | width: 60,
81 | align: Column.Alignment.CENTER,
82 | sortable: false,
83 | cellRenderer: ({ cellData: score }) => {score} ,
84 | },
85 | {
86 | key: 'gender',
87 | title: '♂♀',
88 | dataKey: 'gender',
89 | cellRenderer: ({ cellData: gender }) => ,
90 | width: 60,
91 | align: Column.Alignment.CENTER,
92 | sortable: true,
93 | },
94 | {
95 | key: 'birthday',
96 | title: 'Birthday',
97 | dataKey: 'birthday',
98 | dataGetter: ({ column, rowData }) =>
99 | rowData[column.dataKey].toLocaleDateString(),
100 | width: 100,
101 | align: Column.Alignment.RIGHT,
102 | sortable: true,
103 | },
104 | {
105 | key: 'attachments',
106 | title: 'Attachments',
107 | dataKey: 'attachments',
108 | width: 60,
109 | align: Column.Alignment.CENTER,
110 | headerRenderer: () => ? ,
111 | cellRenderer: ({ cellData }) => {cellData} ,
112 | },
113 | {
114 | key: 'description',
115 | title: 'Description',
116 | dataKey: 'description',
117 | width: 200,
118 | resizable: true,
119 | sortable: true,
120 | cellRenderer: ({ cellData }) => {cellData} ,
121 | },
122 | {
123 | key: 'email',
124 | title: 'Email',
125 | dataKey: 'email',
126 | width: 200,
127 | resizable: true,
128 | sortable: true,
129 | },
130 | {
131 | key: 'country',
132 | title: 'Country',
133 | dataKey: 'country',
134 | width: 100,
135 | resizable: true,
136 | sortable: true,
137 | },
138 | {
139 | key: 'address',
140 | title: 'Address',
141 | dataKey: 'address.street',
142 | width: 200,
143 | resizable: true,
144 | },
145 | {
146 | key: 'action',
147 | width: 100,
148 | align: Column.Alignment.CENTER,
149 | frozen: Column.FrozenDirection.RIGHT,
150 | cellRenderer: ({ rowData }) => (
151 | {
153 | this.setState({
154 | data: this.state.data.filter(x => x.id !== rowData.id),
155 | })
156 | }}
157 | >
158 | Remove
159 |
160 | ),
161 | },
162 | ]
163 |
164 | onColumnSort = sortBy => {
165 | const order = sortBy.order === SortOrder.ASC ? 1 : -1
166 | const data = [...this.state.data]
167 | data.sort((a, b) => (a[sortBy.key] > b[sortBy.key] ? order : -order))
168 | this.setState({
169 | sortBy,
170 | data,
171 | })
172 | }
173 |
174 | render() {
175 | const { data, sortBy } = this.state
176 | return (
177 |
184 | )
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/website/src/examples/default.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () =>
5 |
--------------------------------------------------------------------------------
/website/src/examples/detail-view.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | data.forEach(x => {
5 | x.children = [
6 | {
7 | id: `${x.id}-detail`,
8 | content: faker.lorem.paragraphs(),
9 | },
10 | ]
11 | })
12 |
13 | const GlobalStyle = createGlobalStyle`
14 | .BaseTable__row--depth-0 {
15 | height: 50px;
16 | }
17 |
18 | .BaseTable__row--depth-0 .BaseTable__row-cell-text {
19 | overflow: hidden;
20 | text-overflow: ellipsis;
21 | white-space: nowrap;
22 | }
23 | `
24 |
25 | const Row = styled.div`
26 | padding: 15px;
27 | `
28 | const rowRenderer = ({ rowData, cells }) => {
29 | if (rowData.content) return {rowData.content}
30 | return cells
31 | }
32 |
33 | export default () => (
34 | <>
35 |
36 |
43 | >
44 | )
45 |
--------------------------------------------------------------------------------
/website/src/examples/disabled.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () =>
5 |
--------------------------------------------------------------------------------
/website/src/examples/draggable-rows-frozen.js:
--------------------------------------------------------------------------------
1 | // import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc'
2 | const { sortableContainer, sortableElement, sortableHandle } = ReactSortableHoc
3 | const DraggableContainer = sortableContainer(({ children }) => children)
4 | const DraggableElement = sortableElement(({ children }) => children)
5 | const DraggableHandle = sortableHandle(({ children }) => children)
6 |
7 | const Handle = styled.div`
8 | flex: none;
9 | width: 7.5px;
10 | height: 100%;
11 |
12 | &::before {
13 | content: '';
14 | border-left: 4px dotted #ccc;
15 | display: block;
16 | height: 20px;
17 | margin: 15px 3px;
18 | }
19 |
20 | &:hover::before {
21 | border-color: #888;
22 | }
23 | `
24 |
25 | const Row = ({ key, index, children, ...rest }) => {
26 | // if the children's length is not equal the columns' length, then it's rendering row for frozen table
27 | if (children.length !== 10)
28 | return (
29 |
30 |
31 |
32 |
33 |
34 | {children}
35 |
36 |
37 | )
38 |
39 | return {children}
40 | }
41 |
42 | const rowProps = ({ rowIndex }) => ({
43 | tagName: Row,
44 | index: rowIndex,
45 | })
46 |
47 | class DraggableTable extends React.PureComponent {
48 | state = {
49 | data: this.props.data,
50 | }
51 |
52 | table = React.createRef()
53 |
54 | getContainer = () => {
55 | // for fixed table with frozen columns, the drag handle is in the left frozen table
56 | return this.table.current
57 | .getDOMNode()
58 | .querySelector('.BaseTable__table-frozen-left .BaseTable__body')
59 | }
60 |
61 | getHelperContainer = () => {
62 | return this.table.current
63 | .getDOMNode()
64 | .querySelector('.BaseTable__table-frozen-left')
65 | }
66 |
67 | rowProps = args => {
68 | // don't forget to passing the incoming rowProps
69 | const extraProps = callOrReturn(this.props.rowProps)
70 | return {
71 | ...extraProps,
72 | tagName: Row,
73 | index: args.rowIndex,
74 | }
75 | }
76 |
77 | handleSortEnd = ({ oldIndex, newIndex }) => {
78 | const data = [...this.state.data]
79 | const [removed] = data.splice(oldIndex, 1)
80 | data.splice(newIndex, 0, removed)
81 | this.setState({ data })
82 | }
83 |
84 | render() {
85 | return (
86 |
92 |
99 |
100 | )
101 | }
102 | }
103 |
104 | const Hint = styled.div`
105 | font-size: 16px;
106 | font-weight: 700;
107 | color: #336699;
108 | margin-bottom: 10px;
109 | `
110 |
111 | const columns = generateColumns(10)
112 | const data = generateData(columns, 200)
113 | columns[0].minWidth = 150
114 | columns[0].frozen = true
115 |
116 | export default () => (
117 | <>
118 | Drag the dots, only works in fixed mode(fixed=true)
119 |
120 | >
121 | )
122 |
--------------------------------------------------------------------------------
/website/src/examples/draggable-rows.js:
--------------------------------------------------------------------------------
1 | // import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc'
2 | const { sortableContainer, sortableElement, sortableHandle } = ReactSortableHoc
3 | const DraggableContainer = sortableContainer(({ children }) => children)
4 | const DraggableElement = sortableElement(({ children }) => children)
5 | const DraggableHandle = sortableHandle(({ children }) => children)
6 |
7 | const Handle = styled.div`
8 | flex: none;
9 | width: 7.5px;
10 | height: 100%;
11 |
12 | &::before {
13 | content: '';
14 | border-left: 4px dotted #ccc;
15 | display: block;
16 | height: 20px;
17 | margin: 15px 3px;
18 | }
19 |
20 | &:hover::before {
21 | border-color: #888;
22 | }
23 | `
24 |
25 | const Row = ({ key, index, children, ...rest }) => (
26 |
27 |
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 | )
35 |
36 | const rowProps = ({ rowIndex }) => ({
37 | tagName: Row,
38 | index: rowIndex,
39 | })
40 |
41 | class DraggableTable extends React.PureComponent {
42 | state = {
43 | data: this.props.data,
44 | }
45 |
46 | table = React.createRef()
47 |
48 | getContainer = () => {
49 | return this.table.current.getDOMNode().querySelector('.BaseTable__body')
50 | }
51 |
52 | getHelperContainer = () => {
53 | return this.table.current.getDOMNode().querySelector('.BaseTable__table')
54 | }
55 |
56 | rowProps = args => {
57 | // don't forget to passing the incoming rowProps
58 | const extraProps = callOrReturn(this.props.rowProps)
59 | return {
60 | ...extraProps,
61 | tagName: Row,
62 | index: args.rowIndex,
63 | }
64 | }
65 |
66 | handleSortEnd = ({ oldIndex, newIndex }) => {
67 | const data = [...this.state.data]
68 | const [removed] = data.splice(oldIndex, 1)
69 | data.splice(newIndex, 0, removed)
70 | this.setState({ data })
71 | }
72 |
73 | render() {
74 | return (
75 |
81 |
88 |
89 | )
90 | }
91 | }
92 |
93 | const Hint = styled.div`
94 | font-size: 16px;
95 | font-weight: 700;
96 | color: #336699;
97 | margin-bottom: 10px;
98 | `
99 |
100 | const columns = generateColumns(10)
101 | const data = generateData(columns, 200)
102 | columns[0].minWidth = 150
103 |
104 | export default () => (
105 | <>
106 | Drag the dots, only works in flex mode(fixed=false)
107 |
108 | >
109 | )
110 |
--------------------------------------------------------------------------------
/website/src/examples/dynamic-row-heights.js:
--------------------------------------------------------------------------------
1 | const dataGenerator = () => ({
2 | id: faker.random.uuid(),
3 | name: faker.name.findName(),
4 | gender: faker.random.boolean() ? 'male' : 'female',
5 | score: {
6 | math: faker.random.number(70) + 30,
7 | },
8 | birthday: faker.date.between(1995, 2005),
9 | attachments: faker.random.number(5),
10 | description: faker.lorem.sentence(),
11 | email: faker.internet.email(),
12 | country: faker.address.country(),
13 | address: {
14 | street: faker.address.streetAddress(),
15 | city: faker.address.city(),
16 | zipCode: faker.address.zipCode(),
17 | },
18 | })
19 |
20 | const GenderContainer = styled.div`
21 | background-color: ${props =>
22 | props.gender === 'male' ? 'lightblue' : 'pink'};
23 | color: white;
24 | border-radius: 3px;
25 | width: 20px;
26 | height: 20px;
27 | font-size: 16px;
28 | font-weight: bold;
29 | line-height: 20px;
30 | text-align: center;
31 | `
32 |
33 | const Gender = ({ gender }) => (
34 |
35 | {gender === 'male' ? '♂' : '♀'}
36 |
37 | )
38 |
39 | const Score = styled.span`
40 | color: ${props => (props.score >= 60 ? 'green' : 'red')};
41 | `
42 |
43 | const Attachment = styled.div`
44 | background-color: lightgray;
45 | width: 20px;
46 | height: 20px;
47 | line-height: 20px;
48 | text-align: center;
49 | border-radius: 4px;
50 | color: gray;
51 | `
52 |
53 | const defaultData = new Array(5000)
54 | .fill(0)
55 | .map(dataGenerator)
56 | .sort((a, b) => (a.name > b.name ? 1 : -1))
57 |
58 | const defaultSort = { key: 'name', order: SortOrder.ASC }
59 |
60 | export default class App extends React.Component {
61 | state = {
62 | data: defaultData,
63 | sortBy: defaultSort,
64 | }
65 |
66 | columns = [
67 | {
68 | key: 'name',
69 | title: 'Name',
70 | dataKey: 'name',
71 | width: 150,
72 | resizable: true,
73 | sortable: true,
74 | frozen: Column.FrozenDirection.LEFT,
75 | },
76 | {
77 | key: 'score',
78 | title: 'Score',
79 | dataKey: 'score.math',
80 | width: 60,
81 | align: Column.Alignment.CENTER,
82 | sortable: false,
83 | },
84 | {
85 | key: 'gender',
86 | title: '♂♀',
87 | dataKey: 'gender',
88 | cellRenderer: ({ cellData: gender }) => ,
89 | width: 60,
90 | align: Column.Alignment.CENTER,
91 | sortable: true,
92 | },
93 | {
94 | key: 'birthday',
95 | title: 'Birthday',
96 | dataKey: 'birthday',
97 | dataGetter: ({ column, rowData }) =>
98 | rowData[column.dataKey].toLocaleDateString(),
99 | width: 100,
100 | align: Column.Alignment.RIGHT,
101 | sortable: true,
102 | },
103 | {
104 | key: 'attachments',
105 | title: 'Attachments',
106 | dataKey: 'attachments',
107 | width: 60,
108 | align: Column.Alignment.CENTER,
109 | headerRenderer: () => ? ,
110 | cellRenderer: ({ cellData }) => {cellData} ,
111 | },
112 | {
113 | key: 'description',
114 | title: 'Description',
115 | dataKey: 'description',
116 | width: 200,
117 | resizable: true,
118 | sortable: true,
119 | },
120 | {
121 | key: 'email',
122 | title: 'Email',
123 | dataKey: 'email',
124 | width: 200,
125 | resizable: true,
126 | sortable: true,
127 | },
128 | {
129 | key: 'country',
130 | title: 'Country',
131 | dataKey: 'country',
132 | width: 100,
133 | resizable: true,
134 | sortable: true,
135 | },
136 | {
137 | key: 'address',
138 | title: 'Address',
139 | dataKey: 'address.street',
140 | width: 200,
141 | resizable: true,
142 | },
143 | {
144 | key: 'action',
145 | width: 100,
146 | align: Column.Alignment.CENTER,
147 | frozen: Column.FrozenDirection.RIGHT,
148 | cellRenderer: ({ rowData }) => (
149 | {
151 | this.setState({
152 | data: this.state.data.filter(x => x.id !== rowData.id),
153 | })
154 | }}
155 | >
156 | Remove
157 |
158 | ),
159 | },
160 | ]
161 |
162 | onColumnSort = sortBy => {
163 | const order = sortBy.order === SortOrder.ASC ? 1 : -1
164 | const data = [...this.state.data]
165 | data.sort((a, b) => (a[sortBy.key] > b[sortBy.key] ? order : -order))
166 | this.setState({
167 | sortBy,
168 | data,
169 | })
170 | }
171 |
172 | render() {
173 | const { data, sortBy } = this.state
174 | return (
175 | <>
176 | {
178 | this.setState({
179 | toggle: !this.state.toggle,
180 | })
181 | }}
182 | >
183 | Toggle columns
184 |
185 | {
187 | this.setState({
188 | data: [dataGenerator(), ...data],
189 | })
190 | }}
191 | >
192 | Add item to top
193 |
194 |
204 | >
205 | )
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/website/src/examples/empty-renderer.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 |
3 | const Empty = styled.div`
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | height: 100%;
8 | font-size: 16px;
9 | `
10 |
11 | export default () => (
12 | Table is empty}
16 | />
17 | )
18 |
--------------------------------------------------------------------------------
/website/src/examples/expand-controlled.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | const expandColumnKey = 'column-0'
12 |
13 | // add some sub items
14 | for (let i = 0; i < 5; i++) {
15 | data.push({
16 | ...data[0],
17 | id: `${data[0].id}-sub-${i}`,
18 | parentId: data[0].id,
19 | [expandColumnKey]: `Sub ${i}`,
20 | })
21 | data.push({
22 | ...data[2],
23 | id: `${data[2].id}-sub-${i}`,
24 | parentId: data[2].id,
25 | [expandColumnKey]: `Sub ${i}`,
26 | })
27 | data.push({
28 | ...data[2],
29 | id: `${data[2].id}-sub-sub-${i}`,
30 | parentId: `${data[2].id}-sub-${i}`,
31 | [expandColumnKey]: `Sub-Sub ${i}`,
32 | })
33 | }
34 |
35 | const treeData = unflatten(data)
36 |
37 | export default () => (
38 |
47 | )
48 |
--------------------------------------------------------------------------------
/website/src/examples/expand-icon-props.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | const expandColumnKey = 'column-0'
12 |
13 | // add some sub items
14 | for (let i = 0; i < 3; i++) {
15 | data.push({
16 | ...data[0],
17 | id: `${data[0].id}-sub-${i}`,
18 | parentId: data[0].id,
19 | [expandColumnKey]: `Sub ${i}`,
20 | })
21 | data.push({
22 | ...data[2],
23 | id: `${data[2].id}-sub-${i}`,
24 | parentId: data[2].id,
25 | [expandColumnKey]: `Sub ${i}`,
26 | })
27 | data.push({
28 | ...data[2],
29 | id: `${data[2].id}-sub-sub-${i}`,
30 | parentId: `${data[2].id}-sub-${i}`,
31 | [expandColumnKey]: `Sub-Sub ${i}`,
32 | })
33 | }
34 |
35 | const treeData = unflatten(data)
36 |
37 | const rotate = keyframes`
38 | from {
39 | transform: rotate(0deg);
40 | }
41 |
42 | to {
43 | transform: rotate(360deg);
44 | }
45 | `
46 |
47 | const Loader = styled.div`
48 | display: inline-block;
49 | border-radius: 100%;
50 | margin: 2px;
51 | border: 2px solid #0696d7;
52 | border-bottom-color: transparent;
53 | margin: 2px;
54 | width: 12px;
55 | height: 12px;
56 | animation: ${rotate} 0.75s linear infinite;
57 | margin-left: ${props => props.depth * 16}px;
58 | `
59 |
60 | const ExpandIcon = ({ expanding, ...rest }) =>
61 | expanding ? :
62 |
63 | const components = {
64 | ExpandIcon,
65 | }
66 |
67 | const expandIconProps = ({ rowData }) => ({
68 | expanding: !rowData.children || rowData.children.length === 0,
69 | })
70 |
71 | export default () => (
72 |
83 | )
84 |
--------------------------------------------------------------------------------
/website/src/examples/expand-uncontrolled.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | const expandColumnKey = 'column-0'
12 |
13 | // add some sub items
14 | for (let i = 0; i < 1000; i++) {
15 | data.push({
16 | ...data[0],
17 | id: `${data[0].id}-sub-${i}`,
18 | parentId: data[0].id,
19 | [expandColumnKey]: `Sub ${i}`,
20 | })
21 | data.push({
22 | ...data[2],
23 | id: `${data[2].id}-sub-${i}`,
24 | parentId: data[2].id,
25 | [expandColumnKey]: `Sub ${i}`,
26 | })
27 | data.push({
28 | ...data[2],
29 | id: `${data[2].id}-sub-sub-${i}`,
30 | parentId: `${data[2].id}-sub-${i}`,
31 | [expandColumnKey]: `Sub-Sub ${i}`,
32 | })
33 | }
34 |
35 | const treeData = unflatten(data)
36 |
37 | export default () => (
38 |
47 | )
48 |
--------------------------------------------------------------------------------
/website/src/examples/extra-props.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const Row = styled.a`
5 | color: black;
6 | &:hover {
7 | color: red;
8 | }
9 | `
10 |
11 | const rowProps = {
12 | tagName: Row,
13 | href: 'https://www.google.com',
14 | target: '_blank',
15 | }
16 | const cellProps = ({ rowIndex, columnIndex }) =>
17 | rowIndex % 2 === 0 && {
18 | tagName: 'button',
19 | onClick: e => {
20 | e.preventDefault()
21 | e.stopPropagation()
22 | alert(`You clicked row ${rowIndex} column ${columnIndex}`)
23 | },
24 | }
25 |
26 | export default () => (
27 |
33 | )
34 |
--------------------------------------------------------------------------------
/website/src/examples/fixed.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () =>
5 |
--------------------------------------------------------------------------------
/website/src/examples/flex-column.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () => (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | )
13 |
--------------------------------------------------------------------------------
/website/src/examples/footer-renderer.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | const Footer = styled.div`
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | height: 100%;
16 | font-size: 16px;
17 | `
18 |
19 | export default () => (
20 | Custom Footer}
26 | />
27 | )
28 |
--------------------------------------------------------------------------------
/website/src/examples/frozen-columns.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | export default () =>
12 |
--------------------------------------------------------------------------------
/website/src/examples/frozen-rows.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 | const frozenData = generateData(columns, 3, 'frozen-row-')
4 |
5 | const fixedColumns = columns.map((column, columnIndex) => {
6 | let frozen
7 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
8 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
9 | return { ...column, frozen }
10 | })
11 |
12 | const expandColumnKey = 'column-0'
13 |
14 | // add some sub items
15 | for (let i = 0; i < 3; i++) {
16 | data.push({
17 | ...data[0],
18 | id: `${data[0].id}-sub-${i}`,
19 | parentId: data[0].id,
20 | [expandColumnKey]: `Sub ${i}`,
21 | })
22 | data.push({
23 | ...data[2],
24 | id: `${data[2].id}-sub-${i}`,
25 | parentId: data[2].id,
26 | [expandColumnKey]: `Sub ${i}`,
27 | })
28 | data.push({
29 | ...data[2],
30 | id: `${data[2].id}-sub-sub-${i}`,
31 | parentId: `${data[2].id}-sub-${i}`,
32 | [expandColumnKey]: `Sub-Sub ${i}`,
33 | })
34 | }
35 |
36 | const treeData = unflatten(data)
37 |
38 | export default () => (
39 |
46 | )
47 |
--------------------------------------------------------------------------------
/website/src/examples/header-renderer.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const Row = styled.div`
5 | display: flex;
6 | align-items: center;
7 | width: 100%;
8 | height: 100%;
9 | `
10 |
11 | const CustomHeader = styled.div`
12 | display: flex;
13 | flex-grow: 1;
14 | padding: 0 7.5px;
15 | `
16 |
17 | columns[0].frozen = Column.FrozenDirection.LEFT
18 | columns[1].frozen = Column.FrozenDirection.LEFT
19 | columns[2].frozen = Column.FrozenDirection.RIGHT
20 |
21 | const headerRenderer = ({ cells, columns }) => {
22 | // frozen table's header
23 | if (columns.every(x => x.frozen)) return cells
24 |
25 | // scrollalbe table's header, as there are placeholders for the frozen cells
26 | // we have to keep them to make sure the custom content display in the right palce
27 | const leftPlaceholders = []
28 | const rightPlaceholders = []
29 | columns.forEach((column, idx) => {
30 | if (column.frozen === Column.FrozenDirection.RIGHT)
31 | rightPlaceholders.push(cells[idx])
32 | else if (column.frozen) leftPlaceholders.push(cells[idx])
33 | })
34 |
35 | return (
36 |
37 | {leftPlaceholders}
38 | This is a custom Header
39 | {rightPlaceholders}
40 |
41 | )
42 | }
43 |
44 | export default () => (
45 |
46 | )
47 |
--------------------------------------------------------------------------------
/website/src/examples/hide-header.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () =>
5 |
--------------------------------------------------------------------------------
/website/src/examples/infinite-loading-loader.js:
--------------------------------------------------------------------------------
1 | const TOTAL_SIZE = 1005
2 | const PAGE_SIZE = 50
3 |
4 | const columns = generateColumns(10)
5 | const DATA = generateData(columns, TOTAL_SIZE)
6 |
7 | const fixedColumns = columns.map((column, columnIndex) => {
8 | let frozen
9 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
10 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
11 | return { ...column, frozen }
12 | })
13 |
14 | const Empty = styled.div`
15 | height: 100%;
16 | display: flex;
17 | align-items: center;
18 | justify-content: center;
19 | `
20 |
21 | const rotate = keyframes`
22 | from {
23 | transform: rotate(0deg);
24 | }
25 |
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | `
30 |
31 | const Loader = styled.div`
32 | display: inline-block;
33 | border-radius: 100%;
34 | margin: 2px;
35 | border: 2px solid #0696d7;
36 | border-bottom-color: transparent;
37 | margin: 2px;
38 | width: 22px;
39 | height: 22px;
40 | animation: ${rotate} 0.75s linear infinite;
41 | `
42 |
43 | const Toolbar = styled.div`
44 | display: flex;
45 | align-items: center;
46 | justify-content: space-between;
47 | margin-bottom: 10px;
48 | `
49 |
50 | const Footer = styled.div`
51 | display: flex;
52 | align-items: center;
53 | justify-content: center;
54 | height: 100%;
55 | `
56 |
57 | export default class App extends React.Component {
58 | state = {
59 | data: [],
60 | loading: true,
61 | loadingMore: false,
62 | loadedAll: false,
63 | }
64 |
65 | fetchData(offset = 0, limit = PAGE_SIZE) {
66 | return delay(3000).then(() => {
67 | return DATA.slice(offset, offset + limit)
68 | })
69 | }
70 |
71 | loadData() {
72 | this.fetchData(0, Math.random() < 0.2 ? 0 : PAGE_SIZE).then(data => {
73 | if (!this._isMount) return
74 | this.setState({
75 | data,
76 | loading: false,
77 | loadedAll: data.length < PAGE_SIZE,
78 | })
79 | })
80 | }
81 |
82 | loadMore() {
83 | this.setState({ loadingMore: true })
84 | this.fetchData(this.state.data.length).then(data => {
85 | if (!this._isMount) return
86 | this.setState({
87 | data: [...this.state.data, ...data],
88 | loadingMore: false,
89 | loadedAll: data.length < PAGE_SIZE,
90 | })
91 | })
92 | }
93 |
94 | handleEndReached = args => {
95 | action('onEndReached')(args)
96 | const { loading, loadingMore, loadedAll } = this.state
97 | if (loading || loadingMore || loadedAll) return
98 | this.loadMore()
99 | }
100 |
101 | handleReload = () => {
102 | this.setState({
103 | data: [],
104 | loading: true,
105 | })
106 | this.loadData()
107 | }
108 |
109 | renderFooter = () => {
110 | if (!this.state.loadingMore) return null
111 | return (
112 |
115 | )
116 | }
117 |
118 | renderEmpty = () => {
119 | if (this.state.loading)
120 | return (
121 |
122 |
123 |
124 | )
125 | if (this.state.data.length === 0) return No data available
126 | }
127 |
128 | componentDidMount() {
129 | this._isMount = true
130 | this.loadData()
131 | }
132 |
133 | componentWillUnmount() {
134 | this._isMount = false
135 | }
136 |
137 | render() {
138 | const { data, loading, loadingMore, loadedAll } = this.state
139 | return (
140 | <>
141 |
142 | Loaded data length: {data.length}
143 | All data loaded: {loadedAll.toString()}
144 | Regenerate
145 |
146 |
157 | >
158 | )
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/website/src/examples/infinite-loading.js:
--------------------------------------------------------------------------------
1 | const TOTAL_SIZE = 1005
2 | const PAGE_SIZE = 50
3 |
4 | const columns = generateColumns(10)
5 | const DATA = generateData(columns, TOTAL_SIZE)
6 |
7 | const fixedColumns = columns.map((column, columnIndex) => {
8 | let frozen
9 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
10 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
11 | return { ...column, frozen }
12 | })
13 |
14 | const Empty = styled.div`
15 | height: 100%;
16 | display: flex;
17 | align-items: center;
18 | justify-content: center;
19 | `
20 |
21 | const rotate = keyframes`
22 | from {
23 | transform: rotate(0deg);
24 | }
25 |
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | `
30 |
31 | const Loader = styled.div`
32 | display: inline-block;
33 | border-radius: 100%;
34 | margin: 2px;
35 | border: 2px solid #0696d7;
36 | border-bottom-color: transparent;
37 | margin: 2px;
38 | width: ${props => (props.small ? 12 : 22)}px;
39 | height: ${props => (props.small ? 12 : 22)}px;
40 | animation: ${rotate} 0.75s linear infinite;
41 | `
42 |
43 | const LoadingLayer = styled.div`
44 | display: flex;
45 | align-items: center;
46 | justify-content: center;
47 | background-color: rgba(255, 255, 255, 0.3);
48 | margin: 0;
49 | width: 100%;
50 | height: 100%;
51 | `
52 |
53 | const LoadingMoreLayer = styled.div`
54 | pointer-events: none;
55 | background: rgba(32, 60, 94, 0.3);
56 | position: absolute;
57 | bottom: 30px;
58 | left: 50%;
59 | transform: translateX(-50%);
60 | padding: 5px 15px;
61 | border-radius: 10px;
62 | display: flex;
63 | align-items: center;
64 | `
65 |
66 | const LoadingMoreText = styled.span`
67 | color: #fff;
68 | margin-right: 5px;
69 | `
70 |
71 | const Toolbar = styled.div`
72 | display: flex;
73 | align-items: center;
74 | justify-content: space-between;
75 | margin-bottom: 10px;
76 | `
77 |
78 | const Footer = styled.div`
79 | display: flex;
80 | align-items: center;
81 | justify-content: center;
82 | height: 100%;
83 | `
84 |
85 | export default class App extends React.Component {
86 | state = {
87 | data: [],
88 | loading: true,
89 | loadingMore: false,
90 | loadedAll: false,
91 | }
92 |
93 | fetchData(offset = 0, limit = PAGE_SIZE) {
94 | return delay(3000).then(() => {
95 | return DATA.slice(offset, offset + limit)
96 | })
97 | }
98 |
99 | loadData() {
100 | this.fetchData(0, Math.random() < 0.2 ? 0 : PAGE_SIZE).then(data => {
101 | if (!this._isMount) return
102 | this.setState({
103 | data,
104 | loading: false,
105 | loadedAll: data.length < PAGE_SIZE,
106 | })
107 | })
108 | }
109 |
110 | loadMore() {
111 | this.setState({ loadingMore: true })
112 | this.fetchData(this.state.data.length).then(data => {
113 | if (!this._isMount) return
114 | this.setState({
115 | data: [...this.state.data, ...data],
116 | loadingMore: false,
117 | loadedAll: data.length < PAGE_SIZE,
118 | })
119 | })
120 | }
121 |
122 | handleEndReached = args => {
123 | action('onEndReached')(args)
124 | const { loading, loadingMore, loadedAll } = this.state
125 | if (loading || loadingMore || loadedAll) return
126 | this.loadMore()
127 | }
128 |
129 | handleReload = () => {
130 | this.setState({
131 | data: [],
132 | loading: true,
133 | })
134 | this.loadData()
135 | }
136 |
137 | renderEmpty = () => {
138 | if (this.state.loading) return null
139 | return No data available
140 | }
141 |
142 | renderOverlay = () => {
143 | const { loading, loadingMore } = this.state
144 |
145 | if (loadingMore)
146 | return (
147 |
148 | Loading More
149 |
150 |
151 | )
152 | if (loading)
153 | return (
154 |
155 |
156 |
157 | )
158 |
159 | return null
160 | }
161 |
162 | componentDidMount() {
163 | this._isMount = true
164 | this.loadData()
165 | }
166 |
167 | componentWillUnmount() {
168 | this._isMount = false
169 | }
170 |
171 | render() {
172 | const { data, loading, loadingMore, loadedAll } = this.state
173 | return (
174 | <>
175 |
176 | Loaded data length: {data.length}
177 | All data loaded: {loadedAll.toString()}
178 | Regenerate
179 |
180 |
191 | >
192 | )
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/website/src/examples/inline-editing.js:
--------------------------------------------------------------------------------
1 | // import { Overlay } from 'react-overlays'
2 | const { Overlay } = ReactOverlays
3 |
4 | const CellContainer = styled.div`
5 | display: flex;
6 | flex: 1 0 100%;
7 | align-items: center;
8 | height: 100%;
9 | overflow: hidden;
10 | margin: 0 -5px;
11 | padding: 5px;
12 | border: 1px dashed transparent;
13 | `
14 |
15 | const GlobalStyle = createGlobalStyle`
16 | .BaseTable__row:hover,
17 | .BaseTable__row--hover {
18 | ${CellContainer} {
19 | border: 1px dashed #ccc;
20 | }
21 | }
22 | `
23 |
24 | const Select = styled.select`
25 | width: 100%;
26 | height: 30px;
27 | margin-top: 10px;
28 | `
29 |
30 | class EditableCell extends React.PureComponent {
31 | state = {
32 | value: this.props.cellData,
33 | editing: false,
34 | }
35 |
36 | setTargetRef = ref => (this.targetRef = ref)
37 |
38 | getTargetRef = () => this.targetRef
39 |
40 | handleClick = () => this.setState({ editing: true })
41 |
42 | handleHide = () => this.setState({ editing: false })
43 |
44 | handleChange = e =>
45 | this.setState({
46 | value: e.target.value,
47 | editing: false,
48 | })
49 |
50 | render() {
51 | const { container, rowIndex, columnIndex } = this.props
52 | const { value, editing } = this.state
53 |
54 | return (
55 |
56 | {!editing && value}
57 | {editing && this.targetRef && (
58 |
66 | {({ props, placement }) => (
67 |
78 |
79 | Grapefruit
80 | Lime
81 | Coconut
82 | Mango
83 |
84 |
85 | )}
86 |
87 | )}
88 |
89 | )
90 | }
91 | }
92 |
93 | const columns = generateColumns(5)
94 | const data = generateData(columns, 100)
95 |
96 | columns[0].cellRenderer = EditableCell
97 | columns[0].width = 300
98 |
99 | export default () => (
100 | <>
101 |
102 |
103 | >
104 | )
105 |
--------------------------------------------------------------------------------
/website/src/examples/is-scrolling.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const Loading = styled.div`
5 | padding-left: 15px;
6 | color: gray;
7 | `
8 |
9 | const rowRenderer = ({ isScrolling, cells }) => {
10 | if (isScrolling) return Scrolling
11 | return cells
12 | }
13 |
14 | export default () => (
15 |
21 | )
22 |
--------------------------------------------------------------------------------
/website/src/examples/jsx-column.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () => (
5 |
6 | {columns.map(column => (
7 |
8 | ))}
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/website/src/examples/max-height.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 3)
3 | const frozenData = generateData(columns, 1, 'frozen-row-')
4 |
5 | const fixedColumns = columns.map((column, columnIndex) => {
6 | let frozen
7 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
8 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
9 | return { ...column, frozen }
10 | })
11 |
12 | const expandColumnKey = 'column-0'
13 |
14 | // add some sub items
15 | for (let i = 0; i < 3; i++) {
16 | data.push({
17 | ...data[0],
18 | id: `${data[0].id}-sub-${i}`,
19 | parentId: data[0].id,
20 | [expandColumnKey]: `Sub ${i}`,
21 | })
22 | data.push({
23 | ...data[2],
24 | id: `${data[2].id}-sub-${i}`,
25 | parentId: data[2].id,
26 | [expandColumnKey]: `Sub ${i}`,
27 | })
28 | data.push({
29 | ...data[2],
30 | id: `${data[2].id}-sub-sub-${i}`,
31 | parentId: `${data[2].id}-sub-${i}`,
32 | [expandColumnKey]: `Sub-Sub ${i}`,
33 | })
34 | }
35 |
36 | const treeData = unflatten(data)
37 |
38 | export default () => (
39 |
48 | )
49 |
--------------------------------------------------------------------------------
/website/src/examples/multi-header.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(15)
2 | const data = generateData(columns, 200)
3 |
4 | const GroupCell = styled.div`
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | height: 100%;
9 |
10 | &:not(:last-child) {
11 | border-right: 1px solid lightgray;
12 | }
13 | `
14 |
15 | const fixedColumns = columns.map((column, columnIndex) => {
16 | let frozen
17 | if (columnIndex < 3) frozen = Column.FrozenDirection.LEFT
18 | if (columnIndex > 12) frozen = Column.FrozenDirection.RIGHT
19 | return { ...column, frozen, width: 100 }
20 | })
21 |
22 | const headerRenderer = ({ cells, columns, headerIndex }) => {
23 | if (headerIndex === 2) return cells
24 |
25 | const groupCells = []
26 | let width = 0
27 | let idx = 0
28 |
29 | columns.forEach((column, columnIndex) => {
30 | // if there are frozen columns, there will be some placeholders for the frozen cells
31 | if (column[Table.PlaceholderKey]) groupCells.push(cells[columnIndex])
32 | else {
33 | width += cells[columnIndex].props.style.width
34 | idx++
35 |
36 | const nextColumn = columns[columnIndex + 1]
37 | if (
38 | columnIndex === columns.length - 1 ||
39 | nextColumn[Table.PlaceholderKey] ||
40 | idx === (headerIndex === 0 ? 4 : 2)
41 | ) {
42 | groupCells.push(
43 |
47 | Group width {width}
48 |
49 | )
50 | width = 0
51 | idx = 0
52 | }
53 | }
54 | })
55 | return groupCells
56 | }
57 |
58 | export default () => (
59 |
66 | )
67 |
--------------------------------------------------------------------------------
/website/src/examples/multi-sort.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10, undefined, { sortable: true })
2 | const data = generateData(columns, 200)
3 |
4 | const defaultSort = {
5 | 'column-0': SortOrder.ASC,
6 | 'column-1': SortOrder.DESC,
7 | 'column-2': SortOrder.ASC,
8 | }
9 |
10 | export default class App extends React.Component {
11 | state = {
12 | data,
13 | sortState: defaultSort,
14 | }
15 |
16 | onColumnSort = ({ key, order }) => {
17 | const { data, sortState } = this.state
18 | this.setState({
19 | // clear the sort state if the previous order is desc
20 | sortState: {
21 | ...sortState,
22 | [key]: sortState[key] === SortOrder.DESC ? null : order,
23 | },
24 | data: this.state.data.reverse(),
25 | })
26 | }
27 |
28 | render() {
29 | return (
30 |
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/website/src/examples/overlay-renderer.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | const Overlay = styled.div`
12 | background: lightgray;
13 | position: absolute;
14 | top: 50%;
15 | left: 50%;
16 | transform: translateX(-50%) translateY(-50%);
17 | padding: 5px 15px;
18 | border-radius: 10px;
19 | color: white;
20 | `
21 |
22 | export default () => (
23 |
29 | Custom Overlay
30 |
31 | }
32 | />
33 | )
34 |
--------------------------------------------------------------------------------
/website/src/examples/resizable.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 | const frozenData = generateData(columns, 1, 'frozen-row-')
4 |
5 | const fixedColumns = columns.map((column, columnIndex) => {
6 | let frozen
7 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
8 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
9 | return { ...column, frozen, resizable: true, maxWidth: 300 }
10 | })
11 |
12 | const expandColumnKey = 'column-0'
13 |
14 | // add some sub items
15 | for (let i = 0; i < 3; i++) {
16 | data.push({
17 | ...data[0],
18 | id: `${data[0].id}-sub-${i}`,
19 | parentId: data[0].id,
20 | [expandColumnKey]: `Sub ${i}`,
21 | })
22 | data.push({
23 | ...data[2],
24 | id: `${data[2].id}-sub-${i}`,
25 | parentId: data[2].id,
26 | [expandColumnKey]: `Sub ${i}`,
27 | })
28 | data.push({
29 | ...data[2],
30 | id: `${data[2].id}-sub-sub-${i}`,
31 | parentId: `${data[2].id}-sub-${i}`,
32 | [expandColumnKey]: `Sub-Sub ${i}`,
33 | })
34 | }
35 |
36 | const treeData = unflatten(data)
37 |
38 | export default () => (
39 |
48 | )
49 |
--------------------------------------------------------------------------------
/website/src/examples/row-event-handlers.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 | const frozenData = generateData(columns, 1, 'frozen-row-')
4 |
5 | const fixedColumns = columns.map((column, columnIndex) => {
6 | let frozen
7 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
8 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
9 | return { ...column, frozen }
10 | })
11 |
12 | const rowEventHandlers = {
13 | onClick: action('click'),
14 | onDoubleClick: action('double click'),
15 | onContextMenu: action('context menu'),
16 | onMouseEnter: action('mouse enter'),
17 | onMouseLeave: action('mouse leave'),
18 | }
19 |
20 | export default () => (
21 |
28 | )
29 |
--------------------------------------------------------------------------------
/website/src/examples/row-header-height.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () => (
5 |
6 | )
7 |
--------------------------------------------------------------------------------
/website/src/examples/row-key.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | const expandColumnKey = 'column-0'
12 |
13 | // add some sub items
14 | for (let i = 0; i < 3; i++) {
15 | data.push({
16 | ...data[0],
17 | ['column-0']: `${data[0]['column-0']}-sub-${i}`,
18 | parentId: data[0]['column-0'],
19 | [expandColumnKey]: `Sub ${i}`,
20 | })
21 | data.push({
22 | ...data[2],
23 | ['column-0']: `${data[2]['column-0']}-sub-${i}`,
24 | parentId: data[2]['column-0'],
25 | [expandColumnKey]: `Sub ${i}`,
26 | })
27 | data.push({
28 | ...data[2],
29 | ['column-0']: `${data[2]['column-0']}-sub-sub-${i}`,
30 | parentId: `${data[2]['column-0']}-sub-${i}`,
31 | [expandColumnKey]: `Sub-Sub ${i}`,
32 | })
33 | }
34 |
35 | const treeData = unflatten(data, null, 'column-0')
36 |
37 | export default () => (
38 |
47 | )
48 |
--------------------------------------------------------------------------------
/website/src/examples/row-renderer.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const Title = styled.h4`
5 | font-size: 16px;
6 | color: #819099;
7 | margin-top: 20px;
8 | margin-bottom: 10px;
9 | `
10 |
11 | const Row = styled.div`
12 | padding: 0 15px;
13 | `
14 |
15 | const rowRenderer = ({ rowData, ...rest }) => (
16 | {Object.values(rowData).join(' | ')}
17 | )
18 |
19 | class RowComponent extends React.Component {
20 | render() {
21 | const { rowData, ...rest } = this.props
22 | return {Object.values(rowData).join(' | ')}
23 | }
24 | }
25 |
26 | export default () => (
27 |
28 | Function as renderer
29 |
30 | Component as renderer
31 |
32 | Element as renderer
33 | } />
34 |
35 | )
36 |
--------------------------------------------------------------------------------
/website/src/examples/row-span.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10, undefined, { resizable: true })
2 | const data = generateData(columns, 200)
3 |
4 | const colSpanIndex = 1
5 | columns[colSpanIndex].colSpan = ({ rowData, rowIndex }) => (rowIndex % 4) + 1
6 | columns[colSpanIndex].align = Column.Alignment.CENTER
7 |
8 | const rowSpanIndex = 0
9 | columns[rowSpanIndex].rowSpan = ({ rowData, rowIndex }) =>
10 | rowIndex % 2 === 0 && rowIndex <= data.length - 2 ? 2 : 1
11 |
12 | const rowRenderer = ({ rowData, rowIndex, cells, columns }) => {
13 | const colSpan = columns[colSpanIndex].colSpan({ rowData, rowIndex })
14 | if (colSpan > 1) {
15 | let width = cells[colSpanIndex].props.style.width
16 | for (let i = 1; i < colSpan; i++) {
17 | width += cells[colSpanIndex + i].props.style.width
18 | cells[colSpanIndex + i] = null
19 | }
20 | const style = {
21 | ...cells[colSpanIndex].props.style,
22 | width,
23 | backgroundColor: 'lightgray',
24 | }
25 | cells[colSpanIndex] = React.cloneElement(cells[colSpanIndex], { style })
26 | }
27 |
28 | const rowSpan = columns[rowSpanIndex].rowSpan({ rowData, rowIndex })
29 | if (rowSpan > 1) {
30 | const cell = cells[rowSpanIndex]
31 | const style = {
32 | ...cell.props.style,
33 | backgroundColor: 'darkgray',
34 | height: rowSpan * 50 - 1,
35 | alignSelf: 'flex-start',
36 | zIndex: 1,
37 | }
38 | cells[rowSpanIndex] = React.cloneElement(cell, { style })
39 | }
40 | return cells
41 | }
42 |
43 | export default () => (
44 |
51 | )
52 |
--------------------------------------------------------------------------------
/website/src/examples/scroll-to.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 500)
3 |
4 | const Button = styled.button`
5 | padding: 4px 8px;
6 | margin: 10px;
7 | `
8 |
9 | export default class App extends React.Component {
10 | setRef = ref => (this.table = ref)
11 |
12 | render() {
13 | return (
14 | <>
15 | this.table.scrollToRow(100, 'auto')}>
16 | scrollToRow(100, 'auto')
17 |
18 | this.table.scrollToRow(200, 'start')}>
19 | scrollToRow(200, 'start')
20 |
21 | this.table.scrollToRow(300, 'center')}>
22 | scrollToRow(300, 'center')
23 |
24 | this.table.scrollToRow(400, 'end')}>
25 | scrollToRow(400, 'end')
26 |
27 | this.table.scrollToLeft(400)}>
28 | scrollToLeft(400)
29 |
30 | this.table.scrollToTop(400)}>
31 | scrollToTop(400)
32 |
33 |
35 | this.table.scrollToPosition({ scrollLeft: 200, scrollTop: 2000 })
36 | }
37 | >
38 | {'scrollToPosition({ scrollLeft: 200, scrollTop: 2000 })'}
39 |
40 |
41 | >
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/website/src/examples/scrollbar.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 | const frozenData = generateData(columns, 3, 'frozen-row-')
4 |
5 | const fixedColumns = columns.map((column, columnIndex) => {
6 | let frozen
7 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
8 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
9 | return { ...column, frozen }
10 | })
11 |
12 | const expandColumnKey = 'column-0'
13 |
14 | // add some sub items
15 | for (let i = 0; i < 3; i++) {
16 | data.push({
17 | ...data[0],
18 | id: `${data[0].id}-sub-${i}`,
19 | parentId: data[0].id,
20 | [expandColumnKey]: `Sub ${i}`,
21 | })
22 | data.push({
23 | ...data[2],
24 | id: `${data[2].id}-sub-${i}`,
25 | parentId: data[2].id,
26 | [expandColumnKey]: `Sub ${i}`,
27 | })
28 | data.push({
29 | ...data[2],
30 | id: `${data[2].id}-sub-sub-${i}`,
31 | parentId: `${data[2].id}-sub-${i}`,
32 | [expandColumnKey]: `Sub-Sub ${i}`,
33 | })
34 | }
35 |
36 | const treeData = unflatten(data)
37 |
38 | const StyledTable = styled(Table)`
39 | .BaseTable__table .BaseTable__body {
40 | ::-webkit-scrollbar {
41 | -webkit-appearance: none;
42 | background-color: #e3e3e3;
43 | }
44 |
45 | ::-webkit-scrollbar:vertical {
46 | width: 10px;
47 | }
48 |
49 | ::-webkit-scrollbar:horizontal {
50 | height: 10px;
51 | }
52 |
53 | ::-webkit-scrollbar-thumb {
54 | border-radius: 10px;
55 | border: 2px solid #e3e3e3;
56 | background-color: #999;
57 |
58 | &:hover {
59 | background-color: #666;
60 | }
61 | }
62 |
63 | ::-webkit-resizer {
64 | display: none;
65 | }
66 | }
67 | `
68 |
69 | const Tip = styled.div`
70 | font-weight: 600;
71 | `
72 |
73 | const getScrollbarSize = () => 10
74 |
75 | export default () => (
76 | <>
77 | works only on WebKit based browser
78 |
86 | >
87 | )
88 |
--------------------------------------------------------------------------------
/website/src/examples/selection.js:
--------------------------------------------------------------------------------
1 | const StyledTable = styled(BaseTable)`
2 | .row-selected {
3 | background-color: #e3e3e3;
4 | }
5 | `
6 |
7 | class SelectionCell extends React.PureComponent {
8 | _handleChange = e => {
9 | const { rowData, rowIndex, column } = this.props
10 | const { onChange } = column
11 |
12 | onChange({ selected: e.target.checked, rowData, rowIndex })
13 | }
14 |
15 | render() {
16 | const { rowData, column } = this.props
17 | const { selectedRowKeys, rowKey } = column
18 | const checked = selectedRowKeys.includes(rowData[rowKey])
19 |
20 | return (
21 |
22 | )
23 | }
24 | }
25 |
26 | class SelectableTable extends React.PureComponent {
27 | constructor(props) {
28 | super(props)
29 |
30 | const {
31 | selectedRowKeys,
32 | defaultSelectedRowKeys,
33 | expandedRowKeys,
34 | defaultExpandedRowKeys,
35 | } = props
36 | this.state = {
37 | selectedRowKeys:
38 | (selectedRowKeys !== undefined
39 | ? selectedRowKeys
40 | : defaultSelectedRowKeys) || [],
41 | expandedRowKeys:
42 | (expandedRowKeys !== undefined
43 | ? expandedRowKeys
44 | : defaultExpandedRowKeys) || [],
45 | }
46 | }
47 |
48 | /**
49 | * Set `selectedRowKeys` manually.
50 | * This method is available only if `selectedRowKeys` is uncontrolled.
51 | *
52 | * @param {array} selectedRowKeys
53 | */
54 | setSelectedRowKeys(selectedRowKeys) {
55 | // if `selectedRowKeys` is controlled
56 | if (this.props.selectedRowKeys !== undefined) return
57 |
58 | this.setState({
59 | selectedRowKeys: cloneArray(selectedRowKeys),
60 | })
61 | }
62 |
63 | /**
64 | * See BaseTable#setExpandedRowKeys
65 | */
66 | setExpandedRowKeys(expandedRowKeys) {
67 | // if `expandedRowKeys` is controlled
68 | if (this.props.expandedRowKeys !== undefined) return
69 |
70 | this.setState({
71 | expandedRowKeys: cloneArray(expandedRowKeys),
72 | })
73 | }
74 |
75 | /* some other custom methods and proxy methods */
76 |
77 | /**
78 | * Remove rowKeys from inner state manually, it's useful to purge dirty state after rows removed.
79 | * This method is available only if `selectedRowKeys` or `expandedRowKeys` is uncontrolled.
80 | *
81 | * @param {array} rowKeys
82 | */
83 | removeRowKeysFromState(rowKeys) {
84 | if (!Array.isArray(rowKeys)) return
85 |
86 | const state = {}
87 | if (
88 | this.props.selectedRowKeys === undefined &&
89 | this.state.selectedRowKeys.length > 0
90 | ) {
91 | state.selectedRowKeys = this.state.selectedRowKeys.filter(
92 | key => !rowKeys.includes(key)
93 | )
94 | }
95 | if (
96 | this.props.expandedRowKeys === undefined &&
97 | this.state.expandedRowKeys.length > 0
98 | ) {
99 | state.expandedRowKeys = this.state.expandedRowKeys.filter(
100 | key => !rowKeys.includes(key)
101 | )
102 | }
103 | if (state.selectedRowKeys || state.expandedRowKeys) {
104 | this.setState(state)
105 | }
106 | }
107 |
108 | _handleSelectChange = ({ selected, rowData, rowIndex }) => {
109 | const selectedRowKeys = [...this.state.selectedRowKeys]
110 | const key = rowData[this.props.rowKey]
111 |
112 | if (selected) {
113 | if (!selectedRowKeys.includes(key)) selectedRowKeys.push(key)
114 | } else {
115 | const index = selectedRowKeys.indexOf(key)
116 | if (index > -1) {
117 | selectedRowKeys.splice(index, 1)
118 | }
119 | }
120 |
121 | // if `selectedRowKeys` is uncontrolled, update internal state
122 | if (this.props.selectedRowKeys === undefined) {
123 | this.setState({ selectedRowKeys })
124 | }
125 | this.props.onRowSelect({ selected, rowData, rowIndex })
126 | this.props.onSelectedRowsChange(selectedRowKeys)
127 | }
128 |
129 | _rowClassName = ({ rowData, rowIndex }) => {
130 | const { rowClassName, rowKey } = this.props
131 | const { selectedRowKeys } = this.state
132 |
133 | const rowClass = rowClassName
134 | ? callOrReturn(rowClassName, { rowData, rowIndex })
135 | : ''
136 | const key = rowData[rowKey]
137 |
138 | return [rowClass, selectedRowKeys.includes(key) && 'row-selected']
139 | .filter(Boolean)
140 | .concat(' ')
141 | }
142 |
143 | render() {
144 | const {
145 | columns,
146 | children,
147 | selectable,
148 | selectionColumnProps,
149 | ...rest
150 | } = this.props
151 | const { selectedRowKeys } = this.state
152 |
153 | // you'd better memoize this operation
154 | let _columns = columns || normalizeColumns(children)
155 | if (selectable) {
156 | const selectionColumn = {
157 | width: 40,
158 | flexShrink: 0,
159 | resizable: false,
160 | frozen: Column.FrozenDirection.LEFT,
161 | cellRenderer: SelectionCell,
162 | ...selectionColumnProps,
163 | key: '__selection__',
164 | rowKey: this.props.rowKey,
165 | selectedRowKeys: selectedRowKeys,
166 | onChange: this._handleSelectChange,
167 | }
168 | _columns = [selectionColumn, ..._columns]
169 | }
170 |
171 | return (
172 |
177 | )
178 | }
179 | }
180 |
181 | SelectableTable.defaultProps = {
182 | ...BaseTable.defaultProps,
183 | onRowSelect: noop,
184 | onSelectedRowsChange: noop,
185 | }
186 |
187 | // Use case
188 | const columns = generateColumns(10)
189 | const data = generateData(columns, 200)
190 |
191 | export default () => (
192 |
201 | )
202 |
--------------------------------------------------------------------------------
/website/src/examples/sortable.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | for (let i = 0; i < 3; i++) columns[i].sortable = true
5 |
6 | const defaultSort = { key: 'column-0', order: SortOrder.ASC }
7 |
8 | export default class App extends React.Component {
9 | state = {
10 | data,
11 | sortBy: defaultSort,
12 | }
13 |
14 | onColumnSort = sortBy => {
15 | this.setState({
16 | sortBy,
17 | data: this.state.data.reverse(),
18 | })
19 | }
20 |
21 | render() {
22 | return (
23 |
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/website/src/examples/sticky-rows.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const fixedColumns = columns.map((column, columnIndex) => {
5 | let frozen
6 | if (columnIndex < 2) frozen = Column.FrozenDirection.LEFT
7 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
8 | return { ...column, frozen }
9 | })
10 |
11 | const GlobalStyle = createGlobalStyle`
12 | .sticky-row.BaseTable__row {
13 | background-color: #f3f3f3;
14 | }
15 | `
16 |
17 | export default () => {
18 | const [stickyIndex, setStickyIndex] = React.useState(0)
19 | const tableData = data.slice(1)
20 | const frozenData = data.slice(stickyIndex, stickyIndex + 1)
21 |
22 | const rowClassName = React.useCallback(({ rowIndex }) => {
23 | // we sliced the original data by 1, so we have to correct the index back
24 | if (rowIndex < 0 || (rowIndex + 1) % 5 === 0) return 'sticky-row'
25 | })
26 | const handleScroll = React.useCallback(({ scrollTop }) => {
27 | setStickyIndex(Math.floor(scrollTop / 250) * 5)
28 | })
29 | return (
30 | <>
31 |
32 |
40 | >
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/website/src/examples/tag-name.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | const move = keyframes`
5 | from {
6 | transform: translateX(-100%);
7 | }
8 |
9 | to {
10 | transform: translateX(100%);
11 | }
12 | `
13 |
14 | const InlineLoader = styled.div`
15 | overflow: hidden;
16 | height: 100%;
17 | width: 100%;
18 | position: relative;
19 | background-color: #eee;
20 |
21 | &::after {
22 | content: '';
23 | position: absolute;
24 | left: 0;
25 | right: 0;
26 | top: 0;
27 | bottom: 0;
28 | background-position: left top;
29 | background-repeat: no-repeat;
30 | background-image: linear-gradient(to right, transparent, #ccc, transparent);
31 | animation: ${move} 1.5s linear infinite;
32 | }
33 | `
34 |
35 | const CellLoader = styled(InlineLoader)`
36 | height: 16px !important;
37 | `
38 |
39 | const Cell = props => (
40 |
41 |
42 |
43 | )
44 |
45 | const RowLoader = styled(InlineLoader)`
46 | height: 16px !important;
47 | margin: 0 15px;
48 | `
49 |
50 | const Row = props => (
51 |
52 |
53 |
54 | )
55 |
56 | const cellProps = ({ rowIndex, columnIndex }) =>
57 | rowIndex % 3 === 1 && columnIndex > 0 && { tagName: Cell }
58 |
59 | const rowProps = ({ rowIndex }) => rowIndex % 3 === 2 && { tagName: Row }
60 |
61 | export default () => (
62 |
68 | )
69 |
--------------------------------------------------------------------------------
/website/src/examples/tooltip-cell.js:
--------------------------------------------------------------------------------
1 | // import Text from 'react-texty'
2 | const Text = ReactTexty
3 |
4 | const TableCell = ({ className, cellData }) => (
5 | {cellData}
6 | )
7 |
8 | const TableHeaderCell = ({ className, column }) => (
9 | {column.title}
10 | )
11 |
12 | const columns = generateColumns(10)
13 | const data = generateData(columns, 200)
14 | columns[3].title = 'No tooltip'
15 | columns[3].minWidth = 150
16 |
17 | export default () => (
18 |
23 | )
24 |
--------------------------------------------------------------------------------
/website/src/examples/width-height.js:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 200)
3 |
4 | export default () => (
5 |
6 | )
7 |
--------------------------------------------------------------------------------
/website/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Page from 'components/Page'
4 |
5 | export default () => (
6 |
7 | PAGE NOT FOUND
8 | You just hit a route that doesn't exist... the sadness.
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/website/src/pages/api.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect } from '@reach/router'
3 | import { withPrefix } from 'gatsby-link'
4 |
5 | import Page from 'components/Page'
6 |
7 | const API = () => (
8 |
9 |
14 |
15 | )
16 |
17 | export default API
18 |
--------------------------------------------------------------------------------
/website/src/pages/docs.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect } from '@reach/router'
3 | import { withPrefix } from 'gatsby-link'
4 |
5 | import Page from 'components/Page'
6 |
7 | const Docs = () => (
8 |
9 |
14 |
15 | )
16 |
17 | export default Docs
18 |
--------------------------------------------------------------------------------
/website/src/pages/examples.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect } from '@reach/router'
3 | import { withPrefix } from 'gatsby-link'
4 |
5 | import Page from 'components/Page'
6 |
7 | const Examples = () => (
8 |
9 |
14 |
15 | )
16 |
17 | export default Examples
18 |
--------------------------------------------------------------------------------
/website/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'gatsby'
3 | import styled from 'styled-components'
4 |
5 | import Page from 'components/Page'
6 |
7 | const Container = styled(Page).attrs({ full: true })`
8 | padding: 0;
9 | `
10 |
11 | const Hero = styled.div`
12 | background-color: #182a3d;
13 | color: #fff;
14 | padding: 10rem;
15 | text-align: center;
16 | `
17 |
18 | const Description = styled.p`
19 | color: #bcc9d1;
20 | max-width: 60rem;
21 | margin: 2rem auto 4rem;
22 | `
23 |
24 | const Content = styled.div`
25 | margin: 0 auto;
26 | max-width: 96rem;
27 | padding: 2rem;
28 | position: relative;
29 | `
30 |
31 | const StyledLink = styled(Link)`
32 | display: block;
33 | margin-top: 1rem;
34 | font-size: 1.8rem;
35 | font-weight: 500;
36 | `
37 |
38 | const ExternalLink = StyledLink.withComponent('a')
39 |
40 | const StartLink = styled(Link)`
41 | background-color: #0696d7;
42 | color: #fff;
43 | padding: 0.5em 1em;
44 | border-radius: 0.2em;
45 | &:hover {
46 | background-color: #fff;
47 | }
48 | `
49 |
50 | const ExampleLink = styled(StartLink)`
51 | background-color: transparent;
52 | &:hover {
53 | background-color: transparent;
54 | }
55 | `
56 |
57 | export default () => (
58 |
59 |
60 | BaseTable
61 |
62 | BaseTable is a react table component to display large data set with high
63 | performance and flexibility
64 |
65 | Get Started
66 | View Examples
67 |
68 |
69 | Docs
70 | API
71 | Examples
72 |
77 | Github
78 |
79 |
80 |
81 | )
82 |
--------------------------------------------------------------------------------
/website/src/pages/playground/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import Page from 'components/Page'
5 | import Playground from 'components/Playground'
6 |
7 | const Container = styled(Page).attrs({ full: true })`
8 | max-width: 100%;
9 | height: 100vh;
10 | `
11 |
12 | export default ({ location }) => (
13 |
14 |
15 |
16 | )
17 |
--------------------------------------------------------------------------------
/website/src/styles/index.css:
--------------------------------------------------------------------------------
1 | @import '~minireset.css';
2 | @import '~react-texty/styles.css';
3 |
4 | html {
5 | font-size: 62.5%;
6 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell,
7 | Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
8 | Noto Color Emoji;
9 | }
10 |
11 | body {
12 | -webkit-font-smoothing: antialiased;
13 | /* Currently ems cause chrome bug misinterpreting rems on body element */
14 | font-size: 1.6em;
15 | line-height: 1.6;
16 | color: #333;
17 | font-weight: 400;
18 | }
19 |
20 | h1,
21 | h2,
22 | h3,
23 | h4,
24 | h5,
25 | h6 {
26 | font-weight: 400;
27 | }
28 |
29 | h1 {
30 | font-size: 3rem;
31 | margin: 0 0 1.6rem;
32 | }
33 |
34 | h2 {
35 | font-size: 2.6rem;
36 | margin: 0 0 1.2rem;
37 | }
38 |
39 | h3 {
40 | font-size: 2.2rem;
41 | margin: 0 0 0.8rem;
42 | }
43 |
44 | h4 {
45 | font-size: 1.8rem;
46 | margin: 0 0 0.4rem;
47 | }
48 |
49 | h5,
50 | h6 {
51 | font-size: 1.6rem;
52 | margin: 0;
53 | }
54 |
55 | p {
56 | font-size: 1.6rem;
57 | margin: 0 0 1.2rem;
58 | }
59 |
60 | a {
61 | color: #0696d7;
62 | text-decoration: none;
63 | }
64 |
65 | a:hover,
66 | a:focus {
67 | color: #38abdf;
68 | }
69 |
70 | code,
71 | kbd,
72 | pre,
73 | samp {
74 | font-family: source-code-pro, Menlo, Monaco, Consolas, Courier New, monospace;
75 | }
76 |
77 | pre {
78 | font-size: 1.4rem;
79 | }
80 |
81 | :not(pre) > code {
82 | padding: 0.2em 0.4em;
83 | font-size: 85%;
84 | color: #c7254e;
85 | background-color: #f3f3f3;
86 | border-radius: 0.3em;
87 | }
88 |
89 | [data-texty-tooltip] {
90 | font-size: 1.2rem;
91 | padding: 0.2rem 0.8rem;
92 | }
93 |
--------------------------------------------------------------------------------
/website/src/templates/api.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { graphql } from 'gatsby'
3 |
4 | import Page from 'components/Page'
5 | import Html from 'components/Html'
6 | import Props from 'components/Props'
7 | import Methods from 'components/Methods'
8 | import Pagination from 'components/Pagination'
9 |
10 | import siteConfig from 'siteConfig'
11 |
12 | const links = siteConfig.api.map(item => ({
13 | key: item.title,
14 | title: item.title,
15 | to: item.path,
16 | }))
17 |
18 | class ApiTemplate extends React.Component {
19 | render() {
20 | const { data, pageContext, location } = this.props
21 | const metaData = data.componentMetadata
22 | const methods = metaData.childrenComponentMethodExt
23 | const name = pageContext.name
24 | const link = links.find(link => link.to === `/api/${name.toLowerCase()}`)
25 |
26 | return (
27 |
32 | {metaData.displayName}
33 | {metaData.description && (
34 |
35 | )}
36 |
37 | {methods.length > 0 && }
38 |
39 |
40 | )
41 | }
42 | }
43 |
44 | export default ApiTemplate
45 |
46 | export const pageQuery = graphql`
47 | query ApiByName($name: String!) {
48 | componentMetadata(displayName: { eq: $name }) {
49 | displayName
50 | description {
51 | childMarkdownRemark {
52 | htmlAst
53 | }
54 | }
55 | props {
56 | name
57 | type {
58 | name
59 | value
60 | raw
61 | }
62 | required
63 | description {
64 | childMarkdownRemark {
65 | htmlAst
66 | }
67 | }
68 | defaultValue {
69 | value
70 | }
71 | }
72 | childrenComponentMethodExt {
73 | name
74 | params {
75 | name
76 | type {
77 | name
78 | }
79 | }
80 | childMarkdownRemark {
81 | htmlAst
82 | }
83 | }
84 | }
85 | }
86 | `
87 |
--------------------------------------------------------------------------------
/website/src/templates/doc.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { graphql } from 'gatsby'
3 |
4 | import Html from 'components/Html'
5 | import Page from 'components/Page'
6 | import Pagination from 'components/Pagination'
7 |
8 | import siteConfig from 'siteConfig'
9 |
10 | const links = siteConfig.docs.map(item => ({
11 | key: item.title,
12 | title: item.title,
13 | to: item.path,
14 | }))
15 |
16 | class DocumentTemplate extends React.Component {
17 | render() {
18 | const { data, pageContext, location } = this.props
19 | const doc = data.markdownRemark
20 | const link = links.find(link => link.to === pageContext.slug)
21 |
22 | return (
23 |
24 |
25 |
26 |
27 | )
28 | }
29 | }
30 |
31 | export default DocumentTemplate
32 |
33 | export const pageQuery = graphql`
34 | query DocumentBySlug($slug: String!) {
35 | markdownRemark(fields: { slug: { eq: $slug } }) {
36 | htmlAst
37 | }
38 | }
39 | `
40 |
--------------------------------------------------------------------------------
/website/src/templates/example.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { graphql } from 'gatsby'
3 |
4 | import Page from 'components/Page'
5 | import CodePreview from 'components/CodePreview'
6 | import Pagination from 'components/Pagination'
7 |
8 | import siteConfig from 'siteConfig'
9 |
10 | const links = siteConfig.examples.map(item => ({
11 | key: item.title,
12 | title: item.title,
13 | to: item.path,
14 | }))
15 |
16 | class ComponentTemplate extends React.Component {
17 | render() {
18 | const { data, pageContext, location } = this.props
19 | const code = data.rawCode.content
20 | const name = pageContext.name
21 | const link = links.find(link => link.to === `/examples/${name}`)
22 | return (
23 |
24 | {link.title}
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default ComponentTemplate
33 |
34 | export const pageQuery = graphql`
35 | query ExampleByName($name: String!) {
36 | rawCode(name: { eq: $name }) {
37 | content
38 | }
39 | }
40 | `
41 |
--------------------------------------------------------------------------------
/website/src/utils/actionChannel.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from './eventEmitter'
2 |
3 | export const actionEmitter = new EventEmitter()
4 |
5 | export default class ActionChannel {
6 | constructor(channel) {
7 | if (!channel) throw new Error('"channel" is required for ActionChannel')
8 | this.channel = channel
9 | }
10 |
11 | on(handler) {
12 | actionEmitter.on(this.channel, handler)
13 | }
14 |
15 | off(handler) {
16 | actionEmitter.off(this.channel, handler)
17 | }
18 |
19 | emit(event) {
20 | actionEmitter.emit(this.channel, event)
21 | }
22 | }
23 |
24 | export const createAction = channel => {
25 | return name => args => actionEmitter.emit(channel, { name, args })
26 | }
27 |
28 | let sequence = 0
29 | export const createActionChannel = name => {
30 | const channel = name || `${Date.now()}-${sequence++}`
31 | return {
32 | action: createAction(channel),
33 | channel: new ActionChannel(channel),
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/website/src/utils/baseScope.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import styled, { css, keyframes, createGlobalStyle } from 'styled-components'
4 | import * as ReactSortableHoc from 'react-sortable-hoc'
5 | import * as ReactOverlays from 'react-overlays'
6 | import ReactTexty from 'react-texty'
7 | import faker from 'faker';
8 |
9 | import BaseTable, {
10 | Column,
11 | SortOrder,
12 | AutoResizer,
13 | normalizeColumns,
14 | callOrReturn,
15 | unflatten,
16 | TableHeader as BaseTableHeader,
17 | TableRow as BaseTableRow,
18 | } from 'react-base-table'
19 | import BaseTableExpandIcon from 'react-base-table/ExpandIcon'
20 |
21 | const generateColumns = (count = 10, prefix = 'column-', props) =>
22 | new Array(count).fill(0).map((column, columnIndex) => ({
23 | ...props,
24 | key: `${prefix}${columnIndex}`,
25 | dataKey: `${prefix}${columnIndex}`,
26 | title: `Column ${columnIndex}`,
27 | width: 150,
28 | }))
29 |
30 | const generateData = (columns, count = 200, prefix = 'row-') =>
31 | new Array(count).fill(0).map((row, rowIndex) => {
32 | return columns.reduce(
33 | (rowData, column, columnIndex) => {
34 | rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
35 | return rowData
36 | },
37 | {
38 | id: `${prefix}${rowIndex}`,
39 | parentId: null,
40 | }
41 | )
42 | })
43 |
44 | const noop = () => {}
45 | const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
46 | const action = message => args => console.log(message, args)
47 |
48 | const Table = React.forwardRef((props, ref) => (
49 |
50 | ))
51 | Table.Column = Column
52 | Table.PlaceholderKey = BaseTable.PlaceholderKey
53 |
54 | export default {
55 | React,
56 | ReactDOM,
57 |
58 | styled,
59 | css,
60 | keyframes,
61 | createGlobalStyle,
62 |
63 | ReactSortableHoc,
64 | ReactOverlays,
65 | ReactTexty,
66 | faker,
67 |
68 | BaseTable,
69 | Column,
70 | SortOrder,
71 | AutoResizer,
72 | normalizeColumns,
73 | callOrReturn,
74 | unflatten,
75 | BaseTableRow,
76 | BaseTableHeader,
77 | BaseTableExpandIcon,
78 |
79 | generateColumns,
80 | generateData,
81 | noop,
82 | delay,
83 | action,
84 | Table,
85 | }
86 |
--------------------------------------------------------------------------------
/website/src/utils/eventEmitter.js:
--------------------------------------------------------------------------------
1 | export default class EventEmitter {
2 | handlersMap = {}
3 |
4 | on(type, handler) {
5 | const handlers = this.handlersMap[type]
6 | if (!handlers) this.handlersMap[type] = [handler]
7 | else handlers.push(handler)
8 | }
9 |
10 | off(type, handler) {
11 | const handlers = this.handlersMap[type] || []
12 | const index = handlers.indexOf(handler)
13 | if (index >= 0) handlers.splice(index, 1)
14 | }
15 |
16 | emit(type, event) {
17 | const handlers = this.handlersMap[type] || []
18 | handlers.forEach(handler => handler(event))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/website/src/utils/sample.code:
--------------------------------------------------------------------------------
1 | const columns = generateColumns(10)
2 | const data = generateData(columns, 2000)
3 | const frozenData = generateData(columns, 1, 'frozen-row-')
4 |
5 | const fixedColumns = columns.map((column, columnIndex) => {
6 | let frozen
7 | if (columnIndex < 1) frozen = Column.FrozenDirection.LEFT
8 | if (columnIndex > 8) frozen = Column.FrozenDirection.RIGHT
9 | return { ...column, frozen }
10 | })
11 |
12 | const expandColumnKey = 'column-0'
13 |
14 | // add some sub items
15 | for (let i = 0; i < 100; i++) {
16 | data.push({
17 | ...data[0],
18 | id: `${data[0].id}-sub-${i}`,
19 | parentId: data[0].id,
20 | [expandColumnKey]: `Sub ${i}`,
21 | })
22 | data.push({
23 | ...data[2],
24 | id: `${data[2].id}-sub-${i}`,
25 | parentId: data[2].id,
26 | [expandColumnKey]: `Sub ${i}`,
27 | })
28 | data.push({
29 | ...data[2],
30 | id: `${data[2].id}-sub-sub-${i}`,
31 | parentId: `${data[2].id}-sub-${i}`,
32 | [expandColumnKey]: `Sub-Sub ${i}`,
33 | })
34 | }
35 |
36 | const treeData = unflatten(data)
37 |
38 | export default () => (
39 |
46 | )
47 |
--------------------------------------------------------------------------------
/website/src/utils/urlHash.js:
--------------------------------------------------------------------------------
1 | import LZString from 'lz-string'
2 |
3 | // eslint-disable-next-line
4 | const sampleCode = require('!raw-loader!./sample.code')
5 |
6 | export const getCode = () => {
7 | if (typeof document === 'undefined') return sampleCode
8 |
9 | const hash = document.location.hash.slice(1)
10 | if (!hash) return sampleCode
11 |
12 | return (
13 | LZString.decompressFromEncodedURIComponent(hash) || decodeURIComponent(hash)
14 | )
15 | }
16 |
17 | export const replaceState = code => {
18 | const hash = code ? LZString.compressToEncodedURIComponent(code) : ''
19 |
20 | if (
21 | typeof URL === 'function' &&
22 | typeof window.history === 'object' &&
23 | typeof window.history.replaceState === 'function'
24 | ) {
25 | const url = new URL(document.location)
26 | url.hash = hash
27 | window.history.replaceState(null, null, url)
28 | } else {
29 | document.location.hash = hash
30 | }
31 | }
32 |
--------------------------------------------------------------------------------