@semcore/data-table 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/DataTable.tsx DELETED
@@ -1,444 +0,0 @@
1
- import React from 'react';
2
- import createComponent, { Component, PropGetterFn, Root, sstyled } from '@semcore/core';
3
- import { Box, IBoxProps, IFlexProps } from '@semcore/flex-box';
4
- import syncScroll from '@semcore/utils/lib/syncScroll';
5
- import { callAllEventHandlers } from '@semcore/utils/lib/assignProps';
6
- import fire from '@semcore/utils/lib/fire';
7
- import { flattenColumns } from './utils';
8
- import type {
9
- Column,
10
- NestedCells,
11
- PropsLayer,
12
- PseudoChildPropsGetter,
13
- RowData,
14
- SortDirection,
15
- } from './types';
16
- import Head from './Head';
17
- import Body from './Body';
18
-
19
- import style from './style/data-table.shadow.css';
20
-
21
- const REVERSED_SORT_DIRECTION: { [direction in SortDirection]: SortDirection } = {
22
- desc: 'asc',
23
- asc: 'desc',
24
- };
25
- const DEFAULT_SORT_DIRECTION: SortDirection = 'desc';
26
-
27
- const ROW_GROUP = Symbol('ROW_GROUP');
28
-
29
- const cssVarReg = /[:;]/g;
30
-
31
- const createCssVarForWidth = (name: string) => {
32
- return `--${name.replace(cssVarReg, '_')}_width`;
33
- };
34
-
35
- type AsProps = {
36
- use: 'primary' | 'secondary';
37
- sort: SortDirection[];
38
- data: RowData[];
39
- uniqueKey: string;
40
- };
41
-
42
- type HeadAsProps = {
43
- children: React.ReactChild;
44
- };
45
- type BodyAsProps = {
46
- children: React.ReactChild;
47
- };
48
-
49
- /* utils type */
50
- type CProps<Props, Ctx = {}, UCProps = {}> = Props & {
51
- children?: ((props: Props & Ctx, handlers: UCProps) => React.ReactNode) | React.ReactNode;
52
- };
53
- type ReturnEl = React.ReactElement | null;
54
- type ChildRenderFn<Props> = Props & {
55
- children?: (props: Props, column: DataTableData, index: number) => { [key: string]: unknown };
56
- };
57
- /* utils type */
58
-
59
- export type DataTableData = { [key: string]: unknown };
60
- export type DataTableSort = [string, 'desc' | 'asc'];
61
- export type DataTableTheme = 'muted' | 'info' | 'success' | 'warning' | 'danger';
62
- export type DataTableUse = 'primary' | 'secondary';
63
- export type DataTableRow = DataTableCell[];
64
- export type DataTableCell = {
65
- /** Name of column */
66
- name: string;
67
- /** Data of column */
68
- data: React.ReactNode;
69
- [key: string]: unknown;
70
- };
71
-
72
- export interface IDataTableProps extends IBoxProps {
73
- /** Theme for table
74
- * @default primary
75
- * */
76
- use?: DataTableUse;
77
- /** Data for table */
78
- data?: DataTableData[];
79
- /** Active sort object */
80
- sort?: DataTableSort;
81
- /** Handler call when will request change sort */
82
- onSortChange?: (sort: DataTableSort, e?: React.SyntheticEvent) => void;
83
- /** Field name in one data entity that is unique accross all set of data
84
- * @default id
85
- */
86
- uniqueKey?: string;
87
- }
88
-
89
- export interface IDataTableHeadProps extends IBoxProps {
90
- /** Sticky header table
91
- * @deprecated
92
- * */
93
- sticky?: boolean;
94
-
95
- /** Hidden header */
96
- hidden?: boolean;
97
- }
98
-
99
- export interface IDataTableColumnProps extends IFlexProps {
100
- /** Unique name column */
101
- name?: string;
102
- /** Enable sort for column also if you pass string you can set default sort */
103
- sortable?: boolean | 'desc' | 'asc';
104
- /** Enable resize for column
105
- * @ignore */
106
- resizable?: boolean;
107
- /** Fixed column on the left/right */
108
- fixed?: 'left' | 'right';
109
- }
110
-
111
- export interface IDataTableBodyProps extends IBoxProps {
112
- /** Rows table */
113
- rows?: DataTableRow[];
114
- /** When enabled, only visually acessable rows are rendered.
115
- * `tollerance` property controls how many rows outside of viewport are render.
116
- * `rowHeight` fixes the rows height if it known. If not provided, first row node height is measured.
117
- * @default { tollerance: 2 }
118
- */
119
- virtualScroll?: boolean | { tollerance?: number; rowHeight?: number };
120
- /**
121
- * Called every time user scrolls area
122
- */
123
- onScroll?: (event: React.SyntheticEvent<HTMLElement>) => void;
124
- }
125
-
126
- export interface IDataTableRowProps extends IBoxProps {
127
- /** Theme for row */
128
- theme?: DataTableTheme;
129
- /** Displays row as active/hover */
130
- active?: boolean;
131
- }
132
-
133
- export interface IDataTableCellProps extends IFlexProps {
134
- /** Unique name column or columns separated by / */
135
- name: string;
136
- /** Theme for cell */
137
- theme?: DataTableTheme;
138
- }
139
-
140
- class RootDefinitionTable extends Component<AsProps> {
141
- static displayName = 'DefinitionTable';
142
-
143
- static style = style;
144
-
145
- static defaultProps = {
146
- use: 'primary',
147
- uniqueKey: 'id',
148
- sort: [],
149
- data: [],
150
- } as AsProps;
151
-
152
- columns: Column[] = [];
153
-
154
- tableRef = React.createRef<HTMLElement>();
155
- scrollBodyRef: null | ReturnType<ReturnType<typeof syncScroll>> = null;
156
- scrollHeadRef: null | ReturnType<ReturnType<typeof syncScroll>> = null;
157
-
158
- constructor(props: AsProps) {
159
- super(props);
160
-
161
- const createRef = syncScroll();
162
- // first create body ref for master scroll
163
- this.scrollBodyRef = createRef('body');
164
- this.scrollHeadRef = createRef('head');
165
- }
166
-
167
- handlerSortClick = (name: string, event: React.MouseEvent) => {
168
- const column = this.columns.find((column) => column.name === name)!;
169
- return fire(
170
- this,
171
- 'onSortChange',
172
- [
173
- column.name,
174
- column.active ? REVERSED_SORT_DIRECTION[column.sortDirection] : column.sortDirection,
175
- ],
176
- event,
177
- );
178
- };
179
-
180
- handlerResize = () => {
181
- this.forceUpdate();
182
- };
183
-
184
- scrollToUp = () => {
185
- this.tableRef?.current?.scrollIntoView({
186
- block: 'nearest',
187
- inline: 'nearest',
188
- behavior: 'smooth',
189
- });
190
- };
191
-
192
- setVarStyle(columns: Column[]) {
193
- for (const column of columns) {
194
- if (Array.isArray(column.cssVar)) {
195
- for (const cssVar of column.cssVar) {
196
- this.tableRef.current?.style.setProperty(cssVar, `${column.width}px`);
197
- }
198
- } else {
199
- this.tableRef.current?.style.setProperty(column.cssVar, `${column.width}px`);
200
- }
201
- }
202
- }
203
-
204
- childrenToColumns(
205
- children: React.ReactNode,
206
- options: { fixed?: 'left' | 'right' } = { fixed: undefined },
207
- ) {
208
- const { sort } = this.asProps;
209
- const columnsChildren: Column[] = [];
210
- React.Children.forEach(children, (child) => {
211
- if (!React.isValidElement(child)) return;
212
- if (child.type !== DefinitionTable.Column) return;
213
-
214
- let {
215
- children,
216
- name,
217
- fixed = options.fixed,
218
- resizable,
219
- sortable,
220
- ...props
221
- } = child.props as Column['props'];
222
- const isGroup = !name;
223
- let columns: Column[] = [];
224
-
225
- if (isGroup) {
226
- columns = this.childrenToColumns(children, { fixed });
227
- name = flattenColumns(columns)
228
- .map(({ name }) => name)
229
- .join('/');
230
- if (!columns.length) return;
231
- children = React.Children.toArray(children).filter(
232
- (child) => !(React.isValidElement(child) && child.type === DefinitionTable.Column),
233
- );
234
- }
235
-
236
- const column = this.columns.find((column) => column.name === name);
237
- columnsChildren.push({
238
- get width() {
239
- return this.props.ref.current?.getBoundingClientRect().width || 0;
240
- },
241
- name,
242
- cssVar: createCssVarForWidth(name),
243
- fixed,
244
- resizable,
245
- active: sort[0] === name,
246
- sortable,
247
- sortDirection:
248
- sort[0] === name
249
- ? sort[1]
250
- : column?.sortDirection ||
251
- (typeof sortable == 'string' ? sortable : DEFAULT_SORT_DIRECTION),
252
- columns,
253
- props: {
254
- name,
255
- ref: column?.props?.ref || React.createRef(),
256
- children,
257
- ...props,
258
- },
259
- });
260
- });
261
- return columnsChildren;
262
- }
263
-
264
- getHeadProps(props: HeadAsProps) {
265
- const { use } = this.asProps;
266
- const columnsChildren = this.childrenToColumns(props.children);
267
- this.columns = flattenColumns(columnsChildren);
268
- return {
269
- $onSortClick: callAllEventHandlers(this.handlerSortClick, this.scrollToUp),
270
- columnsChildren,
271
- use,
272
- onResize: this.handlerResize,
273
- $scrollRef: this.scrollHeadRef,
274
- };
275
- }
276
-
277
- getBodyProps(props: BodyAsProps) {
278
- const { data, use, uniqueKey } = this.asProps;
279
-
280
- const cellPropsLayers: { [columnName: string]: PropsLayer[] } = {};
281
- const rowPropsLayers: PropsLayer[] = [];
282
-
283
- React.Children.forEach(props.children, (child) => {
284
- if (React.isValidElement(child)) {
285
- const { name, children, ...other } = child.props as {
286
- name?: string;
287
- children?: PseudoChildPropsGetter;
288
- } & { [propName: string]: unknown };
289
- if (child.type === DefinitionTable.Cell && name) {
290
- name.split('/').forEach((name) => {
291
- cellPropsLayers[name] = cellPropsLayers[name] || [];
292
- cellPropsLayers[name].push({
293
- ...other,
294
- childrenPropsGetter: children,
295
- });
296
- });
297
- }
298
- if (child.type === DefinitionTable.Row) {
299
- rowPropsLayers.push({
300
- ...other,
301
- childrenPropsGetter: children,
302
- });
303
- }
304
- }
305
- });
306
-
307
- return {
308
- columns: this.columns,
309
- rows: this.dataToRows(data, cellPropsLayers),
310
- uniqueKey,
311
- use,
312
- rowPropsLayers,
313
- $scrollRef: this.scrollBodyRef,
314
- };
315
- }
316
-
317
- dataToRows(data: RowData[], cellPropsLayers: { [columnName: string]: PropsLayer[] }) {
318
- const parseData = (data: RowData[], exclude: { [columnName: string]: true }) =>
319
- data.map((row) => {
320
- const groupByName: {
321
- [columnName: string]: {
322
- groupedColumns: string[];
323
- groupData: { [columnName: string]: unknown };
324
- };
325
- } = {};
326
- const groupedColumns: { [columnname: string]: true } = {};
327
- const ungroupedColumns: { [columnname: string]: true } = {};
328
- for (const rowKey in row) {
329
- const columnNames = rowKey.split('/');
330
- if (columnNames.length >= 2) {
331
- for (const column of columnNames) {
332
- groupByName[column] = {
333
- groupedColumns: columnNames,
334
- groupData: row[rowKey] as { [columnName: string]: unknown },
335
- };
336
- groupedColumns[rowKey] = true;
337
- }
338
- } else {
339
- ungroupedColumns[rowKey] = true;
340
- }
341
- }
342
- const rowsGroup = row[ROW_GROUP] || [];
343
- const rowsGroupedNames = Object.fromEntries(
344
- rowsGroup
345
- .map((subRow) => Object.keys(subRow))
346
- .flat()
347
- .map((key) => [key, true]),
348
- );
349
-
350
- let isGroup = false;
351
-
352
- const cells: NestedCells = this.columns
353
- .map((column) => {
354
- if (groupByName[column.name]) {
355
- const { groupedColumns, groupData } = groupByName[column.name];
356
- if (groupedColumns[0] === column.name) {
357
- return {
358
- name: groupedColumns.join('/'),
359
- cssVar: groupedColumns.map(createCssVarForWidth),
360
- fixed: column.fixed,
361
- data: groupData,
362
- cellPropsLayers: cellPropsLayers[column.name] || [],
363
- };
364
- }
365
- } else if (column.name in row) {
366
- return {
367
- name: column.name,
368
- cssVar: column.cssVar,
369
- fixed: column.fixed,
370
- data: row[column.name],
371
- cellPropsLayers: cellPropsLayers[column.name] || [],
372
- };
373
- } else if (!isGroup && rowsGroupedNames[column.name]) {
374
- // TODO: make it work not only with first group
375
- isGroup = true;
376
- return parseData(rowsGroup, {
377
- ...ungroupedColumns,
378
- ...groupedColumns,
379
- });
380
- } else if (!exclude[column.name] && !rowsGroupedNames[column.name]) {
381
- return {
382
- name: column.name,
383
- cssVar: column.cssVar,
384
- fixed: column.fixed,
385
- data: null,
386
- cellPropsLayers: cellPropsLayers[column.name] || [],
387
- };
388
- }
389
- })
390
- .filter((column) => column !== undefined)
391
- .map((column) => column!);
392
-
393
- cells.flatRowData = row;
394
- return cells;
395
- });
396
-
397
- return parseData(data, {});
398
- }
399
-
400
- componentDidUpdate() {
401
- this.setVarStyle(this.columns);
402
- }
403
-
404
- render() {
405
- const SDataTable = Root;
406
- const { Children, styles } = this.asProps;
407
-
408
- return sstyled(styles)(
409
- <SDataTable render={Box} __excludeProps={['data']} ref={this.tableRef}>
410
- <Children />
411
- </SDataTable>,
412
- );
413
- }
414
- }
415
-
416
- interface IDataTableCtx {
417
- getHeadProps: PropGetterFn;
418
- getBodyProps: PropGetterFn;
419
- }
420
-
421
- function ComponentDefinition() {
422
- return null;
423
- }
424
-
425
- const DefinitionTable = createComponent(
426
- RootDefinitionTable,
427
- {
428
- Head,
429
- Body,
430
- Column: ComponentDefinition,
431
- Cell: ComponentDefinition,
432
- Row: ComponentDefinition,
433
- },
434
- {},
435
- ) as (<T>(props: CProps<IDataTableProps & T, IDataTableCtx>) => ReturnEl) & {
436
- Head: <T>(props: IDataTableHeadProps & T) => ReturnEl;
437
- Body: <T>(props: IDataTableBodyProps & T) => ReturnEl;
438
- Column: <T>(props: IDataTableColumnProps & T) => ReturnEl;
439
- Cell: <T>(props: ChildRenderFn<IDataTableCellProps & T>) => ReturnEl;
440
- Row: <T>(props: ChildRenderFn<IDataTableRowProps & T>) => ReturnEl;
441
- };
442
-
443
- export { ROW_GROUP };
444
- export default DefinitionTable;
package/src/Head.tsx DELETED
@@ -1,142 +0,0 @@
1
- import React from 'react';
2
- import { Component, sstyled, Root } from '@semcore/core';
3
- import { Box, Flex } from '@semcore/flex-box';
4
- import ScrollArea from '@semcore/scroll-area';
5
- import SortDesc from '@semcore/icon/SortDesc/m';
6
- import SortAsc from '@semcore/icon/SortAsc/m';
7
- import { callAllEventHandlers } from '@semcore/utils/lib/assignProps';
8
- import { flattenColumns, getFixedStyle, getScrollOffsetValue } from './utils';
9
- import type { Column } from './types';
10
- import logger from '@semcore/utils/lib/logger';
11
- import 'resize-observer-polyfill';
12
-
13
- import scrollStyles from './style/scroll-area.shadow.css';
14
-
15
- const SORTING_ICON = {
16
- desc: SortDesc,
17
- asc: SortAsc,
18
- } as const;
19
-
20
- type AsProps = {
21
- $onSortClick: (name: string, event: React.MouseEvent | React.KeyboardEvent) => void;
22
- $scrollRef: (instance: unknown) => void;
23
- use: 'primary' | 'secondary';
24
- columnsChildren: Column[];
25
- onResize: ResizeObserverCallback;
26
- sticky: boolean;
27
- ['data-ui-name']: string;
28
- };
29
-
30
- class Head extends Component<AsProps> {
31
- columns: Column[] = [];
32
-
33
- static displayName: string;
34
-
35
- bindHandlerSortClick = (name: string) => (event: React.MouseEvent) => {
36
- this.asProps.$onSortClick(name, event);
37
- };
38
-
39
- bindHandlerKeyDown = (name: string) => (event: React.KeyboardEvent) => {
40
- if (event.code === 'Enter') {
41
- this.asProps.$onSortClick(name, event);
42
- }
43
- };
44
-
45
- renderColumns(columns: Column[], width: number) {
46
- return columns.map((column) => this.renderColumn(column, width));
47
- }
48
-
49
- renderColumn(column: Column, width: number) {
50
- const { styles, use, hidden } = this.asProps;
51
- const SColumn = Flex;
52
- const SHead = Box;
53
- const SSortIcon = SORTING_ICON[column.sortDirection];
54
- const isGroup = column.columns?.length > 0;
55
- const cSize = isGroup ? flattenColumns(column.columns).length : 1;
56
- const [name, value] = getFixedStyle(column, this.columns);
57
-
58
- const style = {
59
- flexBasis: column.props.flex === undefined && `${width * cSize}%`,
60
- ...column.props.style,
61
- };
62
-
63
- if (name !== undefined && value !== undefined) {
64
- style[name] = value;
65
- }
66
-
67
- return sstyled(styles)(
68
- <SColumn
69
- key={column.name}
70
- use={use}
71
- fixed={column.fixed}
72
- resizable={column.resizable}
73
- sortable={column.sortable}
74
- active={column.active}
75
- group={isGroup}
76
- tabIndex={column.sortable && 0}
77
- {...column.props}
78
- onClick={callAllEventHandlers(
79
- column.props.onClick,
80
- column.sortable ? this.bindHandlerSortClick(column.name) : undefined,
81
- )}
82
- onKeyDown={callAllEventHandlers(
83
- column.props.onKeyDown,
84
- column.sortable ? this.bindHandlerKeyDown(column.name) : undefined,
85
- )}
86
- style={style}
87
- hidden={hidden}
88
- >
89
- {isGroup ? (
90
- <>
91
- <SColumn groupHead use={use}>
92
- <div>{column.props.children}</div>
93
- </SColumn>
94
- <SHead>{this.renderColumns(column.columns, 100 / cSize)}</SHead>
95
- </>
96
- ) : (
97
- <>
98
- <div>{column.props.children}</div>
99
- {column.sortable ? <SSortIcon active={column.active} /> : null}
100
- </>
101
- )}
102
- </SColumn>,
103
- );
104
- }
105
-
106
- render() {
107
- const SHead = Root;
108
- const SHeadWrapper = Box;
109
- const { Children, styles, columnsChildren, onResize, $scrollRef, sticky } = this.asProps;
110
-
111
- this.columns = flattenColumns(columnsChildren);
112
-
113
- const [offsetLeftSum, offsetRightSum] = getScrollOffsetValue(this.columns);
114
-
115
- logger.warn(
116
- sticky,
117
- "'sticky' property is deprecated, use '<Sticky/>' wrapper",
118
- this.asProps['data-ui-name'] || Head.displayName,
119
- );
120
-
121
- return sstyled(styles)(
122
- <SHeadWrapper sticky={sticky}>
123
- <ScrollArea
124
- styles={scrollStyles}
125
- use:left={`${offsetLeftSum}px`}
126
- use:right={`${offsetRightSum}px`}
127
- shadow
128
- onResize={onResize}
129
- >
130
- <ScrollArea.Container ref={$scrollRef}>
131
- <SHead render={Box}>
132
- {this.renderColumns(columnsChildren, 100 / this.columns.length)}
133
- </SHead>
134
- </ScrollArea.Container>
135
- </ScrollArea>
136
- {Children.origin}
137
- </SHeadWrapper>,
138
- );
139
- }
140
- }
141
-
142
- export default Head;
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { default } from './DataTable';
2
- export * from './DataTable';