genesys-react-components 0.3.1 → 0.3.2-devengage-1573-implementing-tables.230
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/LICENSE +1 -1
- package/build/datatable/DataTable.d.ts +13 -0
- package/build/index.d.ts +13 -1
- package/build/index.js +6013 -32
- package/build/index.js.map +1 -1
- package/package.json +54 -53
- package/src/datatable/DataTable.scss +199 -0
- package/src/datatable/DataTable.tsx +431 -0
- package/src/index.ts +15 -1
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
import { GenesysDevIcon, GenesysDevIcons } from 'genesys-dev-icons';
|
|
3
|
+
import DxTextbox from '../dxtextbox/DxTextbox';
|
|
4
|
+
import DxToggle from '../dxtoggle/DxToggle';
|
|
5
|
+
import moment from 'moment';
|
|
6
|
+
import React, { useEffect, useState, ReactNode } from 'react';
|
|
7
|
+
import CopyButton from '../copybutton/CopyButton';
|
|
8
|
+
import { DataTableRow } from '..';
|
|
9
|
+
import './DataTable.scss';
|
|
10
|
+
|
|
11
|
+
interface IProps {
|
|
12
|
+
rows: DataTableRow[];
|
|
13
|
+
headerRow?: DataTableRow;
|
|
14
|
+
className?: string;
|
|
15
|
+
indentation?: number;
|
|
16
|
+
sortable?: boolean;
|
|
17
|
+
filterable?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ColumnFilterCollection {
|
|
21
|
+
[colId: string]: ColumnFilter;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ColumnFilter {
|
|
25
|
+
colId: number;
|
|
26
|
+
dataType: 'string' | 'number' | 'datetime';
|
|
27
|
+
filter: any;
|
|
28
|
+
filterModifier?: FilterModifierGtLt;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type FilterModifierGtLt = 'greaterthan' | 'lessthan';
|
|
32
|
+
|
|
33
|
+
interface ColumnSort {
|
|
34
|
+
colId?: number;
|
|
35
|
+
sort: 'none' | 'ascending' | 'descending';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ColumnTypeCollection {
|
|
39
|
+
[colId: string]: 'string' | 'number' | 'date';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface RawColumnTypeCollection {
|
|
43
|
+
[colId: string]: RawColumnTypeCount;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface RawColumnTypeCount {
|
|
47
|
+
colId: number;
|
|
48
|
+
number: number;
|
|
49
|
+
date: number;
|
|
50
|
+
string: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const TABLE_CLASS_REGEX = /(?:^|\s)table(?:$|\s)/i;
|
|
54
|
+
|
|
55
|
+
export default function DataTable(props: IProps) {
|
|
56
|
+
// filterRows filters the input rows using the configured filters
|
|
57
|
+
const filterRows = (): DataTableRow[] => {
|
|
58
|
+
// Return raw data if we don't have info to filter
|
|
59
|
+
if (!columnTypes || Object.keys(columnTypes).length === 0 || !filters || Object.keys(filters).length === 0) return parsedRows;
|
|
60
|
+
|
|
61
|
+
// Filter source rows
|
|
62
|
+
let newRows = [] as DataTableRow[];
|
|
63
|
+
let anyValidFilters = false;
|
|
64
|
+
parsedRows.forEach((row) => {
|
|
65
|
+
let filterMatch: boolean | undefined;
|
|
66
|
+
Object.keys(filters)
|
|
67
|
+
.map((i) => {
|
|
68
|
+
let ii = parseInt(i);
|
|
69
|
+
return ii;
|
|
70
|
+
})
|
|
71
|
+
// .map(parseInt)
|
|
72
|
+
.forEach((colId) => {
|
|
73
|
+
const filter = filters[colId];
|
|
74
|
+
if (!filter || filter.filter === '' || filter.filter === undefined) return;
|
|
75
|
+
switch (filter.dataType) {
|
|
76
|
+
case 'datetime': {
|
|
77
|
+
const m = filter.filter as moment.Moment | undefined;
|
|
78
|
+
const value = row.cells[colId].parsedContent as Date | undefined;
|
|
79
|
+
if (filterMatch === false || !moment.isMoment(m) || !m.isValid() || !value) return;
|
|
80
|
+
const datePoint = m.toDate();
|
|
81
|
+
filterMatch = filter.filterModifier === 'greaterthan' ? value > datePoint : value < datePoint;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'number': {
|
|
85
|
+
if (filter.filter === '' || filter.filter === undefined || !filter.filterModifier || filterMatch === false) return;
|
|
86
|
+
if (filter.filterModifier === 'greaterthan' && (row.cells[colId].parsedContent as number) >= filter.filter)
|
|
87
|
+
filterMatch = true;
|
|
88
|
+
else if (filter.filterModifier === 'lessthan' && (row.cells[colId].parsedContent as number) <= filter.filter)
|
|
89
|
+
filterMatch = true;
|
|
90
|
+
else if (filter.filterModifier !== undefined) filterMatch = false;
|
|
91
|
+
// Didn't hit a valid filter, take no action
|
|
92
|
+
if (filterMatch === undefined) return;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'string':
|
|
96
|
+
default: {
|
|
97
|
+
if (filter.filter === '' || filterMatch === false) return;
|
|
98
|
+
filterMatch = (row.cells[colId].parsedContent as string).includes(filter.filter);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
anyValidFilters = true;
|
|
102
|
+
});
|
|
103
|
+
if (filterMatch === true) newRows.push(row);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return anyValidFilters ? newRows : parsedRows;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// sortRows sorts the filtered rows using the configured sorting
|
|
110
|
+
const sortRows = (): DataTableRow[] => {
|
|
111
|
+
// Abort if we can't sort
|
|
112
|
+
if (!colsort || colsort.colId === undefined || filteredRows.length < 2 || !filteredRows[0].cells[colsort.colId]) return filteredRows;
|
|
113
|
+
|
|
114
|
+
// Unsort rows
|
|
115
|
+
if (colsort.sort === 'none') {
|
|
116
|
+
return filteredRows;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sort rows
|
|
120
|
+
const i = colsort.colId;
|
|
121
|
+
const isAscending = colsort.sort === 'ascending';
|
|
122
|
+
return [...filteredRows].sort((a, b) => {
|
|
123
|
+
if ((a.cells[i].parsedContent as number) < (b.cells[i].parsedContent as number)) return isAscending ? -1 : 1;
|
|
124
|
+
if ((a.cells[i].parsedContent as number) > (b.cells[i].parsedContent as number)) return isAscending ? 1 : -1;
|
|
125
|
+
return 0;
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const [parsedRows, setParsedRows] = useState([] as DataTableRow[]);
|
|
130
|
+
// Filtered set of rows (first pass)
|
|
131
|
+
const [filteredRows, setFilteredRows] = useState([] as DataTableRow[]);
|
|
132
|
+
// Sorted set of rows (second pass)
|
|
133
|
+
const [sortedRows, setSortedRows] = useState([] as DataTableRow[]);
|
|
134
|
+
// Rows to display in the table (third pass, paginated)
|
|
135
|
+
const [rows, setRows] = useState([] as DataTableRow[]);
|
|
136
|
+
|
|
137
|
+
const [filters, setFilters] = useState({} as ColumnFilterCollection);
|
|
138
|
+
const [colsort, setColsort] = useState({ sort: 'none' } as ColumnSort);
|
|
139
|
+
|
|
140
|
+
const [columnTypes, setColumnTypes] = useState({} as ColumnTypeCollection);
|
|
141
|
+
const [isFilterOpen, setIsFilterOpen] = useState(false);
|
|
142
|
+
|
|
143
|
+
// "Constructor"
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
// Infer column types
|
|
146
|
+
if (props.rows.length > 0 && props.rows[0].cells.length > 0) {
|
|
147
|
+
// Seed columns
|
|
148
|
+
const cellTypeData = {} as RawColumnTypeCollection;
|
|
149
|
+
props.rows[0].cells.forEach((cell, i) => (cellTypeData[i] = { colId: i, number: 0, date: 0, string: 0 }));
|
|
150
|
+
// Iterate rows and cells to infer and count types
|
|
151
|
+
props.rows.forEach((row) => {
|
|
152
|
+
row.cells.forEach((cell, i) => {
|
|
153
|
+
if (!cell || !cell.content || !cellTypeData[i]) return;
|
|
154
|
+
|
|
155
|
+
// Check number first (moment parses numbers as dates successfully)
|
|
156
|
+
// Passing a string to isNaN uses built-in type coersion logic that's different than Number.parseFloat()
|
|
157
|
+
if (!isNaN(cell.content as any) && !isNaN(parseFloat(cell.content))) {
|
|
158
|
+
cellTypeData[i].number++;
|
|
159
|
+
cell.parsedContent = parseFloat(cell.content);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check date
|
|
164
|
+
if (moment(cell.content, 'M/D/YYYY', true).isValid() || moment(cell.content, 'M-D-YYYY', true).isValid()) {
|
|
165
|
+
cellTypeData[i].date++;
|
|
166
|
+
cell.parsedContent = Date.parse(cell.content);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Default: string
|
|
171
|
+
cellTypeData[i].string++;
|
|
172
|
+
cell.parsedContent = cell.content.toLowerCase();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Assign column types
|
|
177
|
+
const newColumnTypes = {} as ColumnTypeCollection;
|
|
178
|
+
for (let i = 0; i < props.rows[0].cells.length; i++) {
|
|
179
|
+
const maxCount = Math.max(cellTypeData[i].date, cellTypeData[i].number, cellTypeData[i].string);
|
|
180
|
+
if (cellTypeData[i].date === maxCount) newColumnTypes[i] = 'date';
|
|
181
|
+
else if (cellTypeData[i].number === maxCount) newColumnTypes[i] = 'number';
|
|
182
|
+
else newColumnTypes[i] = 'string';
|
|
183
|
+
}
|
|
184
|
+
setColumnTypes(newColumnTypes);
|
|
185
|
+
setParsedRows(props.rows);
|
|
186
|
+
}
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
// Filter changed
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const r = filterRows();
|
|
192
|
+
setFilteredRows(r);
|
|
193
|
+
}, [filters, columnTypes, parsedRows]);
|
|
194
|
+
|
|
195
|
+
// Sort or filtered rows (source) changed
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
const r = sortRows();
|
|
198
|
+
setSortedRows(r);
|
|
199
|
+
}, [colsort, filteredRows]);
|
|
200
|
+
|
|
201
|
+
// sorted rows (source) changed
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
setRows([...sortedRows]);
|
|
204
|
+
}, [sortedRows]);
|
|
205
|
+
|
|
206
|
+
// Consolidation props to identify conditions for rendering
|
|
207
|
+
const isSortable = props.sortable || props.className?.includes('sortable') || props.className?.includes('sort-and-filter');
|
|
208
|
+
const isFilterable = props.filterable || props.className?.includes('filterable') || props.className?.includes('sort-and-filter');
|
|
209
|
+
|
|
210
|
+
// getSortCaret returns the FontAwesome glyph name to use for the column sort indicator based on the current sort configuration
|
|
211
|
+
const getSortCaret = (columnId: number): GenesysDevIcons => {
|
|
212
|
+
if (colsort.colId !== columnId || colsort.sort === 'none') return GenesysDevIcons.AppSort;
|
|
213
|
+
return colsort.sort === 'descending' ? GenesysDevIcons.AppSortDown : GenesysDevIcons.AppSortUp;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// The filterChanged functions are raised when the user updates a filter column
|
|
217
|
+
const stringFilterChanged = (colId: string, filterValue: string) => {
|
|
218
|
+
const newFilters = { ...filters };
|
|
219
|
+
newFilters[colId] = { colId: parseInt(colId), dataType: 'string', filter: filterValue.toLowerCase() };
|
|
220
|
+
setFilters(newFilters);
|
|
221
|
+
};
|
|
222
|
+
const numberFilterChanged = (colId: string, filterValue: string) => {
|
|
223
|
+
const newFilters = { ...filters };
|
|
224
|
+
const i = parseFloat(filterValue);
|
|
225
|
+
newFilters[colId] = { colId: parseInt(colId), dataType: 'number', filter: isNaN(i) ? undefined : i, filterModifier: 'lessthan' };
|
|
226
|
+
if (filters[colId]) newFilters[colId].filterModifier = filters[colId].filterModifier;
|
|
227
|
+
setFilters(newFilters);
|
|
228
|
+
};
|
|
229
|
+
const numberFilterModifierChanged = (colId: string, filterModifier: FilterModifierGtLt) => {
|
|
230
|
+
const newFilters = { ...filters };
|
|
231
|
+
if (!newFilters[colId]) newFilters[colId] = { colId: parseInt(colId), dataType: 'number', filter: undefined };
|
|
232
|
+
newFilters[colId].filterModifier = filterModifier;
|
|
233
|
+
setFilters(newFilters);
|
|
234
|
+
};
|
|
235
|
+
const dateFilterChanged = (colId: string, filterValue: string) => {
|
|
236
|
+
const newFilters = { ...filters };
|
|
237
|
+
newFilters[colId] = { colId: parseInt(colId), dataType: 'datetime', filter: moment(filterValue), filterModifier: 'lessthan' };
|
|
238
|
+
if (filters[colId]) newFilters[colId].filterModifier = filters[colId].filterModifier;
|
|
239
|
+
setFilters(newFilters);
|
|
240
|
+
};
|
|
241
|
+
const dateFilterModifierChanged = (colId: string, filterModifier: FilterModifierGtLt) => {
|
|
242
|
+
const newFilters = { ...filters };
|
|
243
|
+
if (!newFilters[colId]) newFilters[colId] = { colId: parseInt(colId), dataType: 'datetime', filter: undefined };
|
|
244
|
+
newFilters[colId].filterModifier = filterModifier;
|
|
245
|
+
setFilters(newFilters);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// sortChanged is raised when the user clicks a sortable column header
|
|
249
|
+
const sortChanged = (columnId: string) => {
|
|
250
|
+
const colId = parseInt(columnId);
|
|
251
|
+
const newSort = { ...colsort };
|
|
252
|
+
newSort.colId = colId;
|
|
253
|
+
// Unset column on invalid id
|
|
254
|
+
if (colId < 0 || (rows[0] && colId >= rows[0].cells.length)) newSort.colId = undefined;
|
|
255
|
+
|
|
256
|
+
// Update sort order
|
|
257
|
+
if (newSort.colId !== colsort.colId) {
|
|
258
|
+
// New sorts are always descending first
|
|
259
|
+
newSort.sort = 'ascending';
|
|
260
|
+
} else {
|
|
261
|
+
// Rotate sort order
|
|
262
|
+
switch (newSort.sort) {
|
|
263
|
+
case 'ascending': {
|
|
264
|
+
newSort.sort = 'descending';
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case 'descending': {
|
|
268
|
+
newSort.sort = 'none';
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
default: {
|
|
272
|
+
newSort.sort = 'ascending';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
setColsort(newSort);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/***** Setup complete, build the component *****/
|
|
281
|
+
|
|
282
|
+
// Build column headers
|
|
283
|
+
let columnHeaders;
|
|
284
|
+
if (props.headerRow) {
|
|
285
|
+
columnHeaders = (
|
|
286
|
+
<tr>
|
|
287
|
+
{props.headerRow.cells.map((cell, i) => (
|
|
288
|
+
<td
|
|
289
|
+
key={i}
|
|
290
|
+
align={cell?.align || 'left'}
|
|
291
|
+
className={colsort.colId === i && colsort.sort !== 'none' ? '' : 'unsorted'}
|
|
292
|
+
onClick={isSortable ? () => sortChanged(i.toString()) : undefined}
|
|
293
|
+
>
|
|
294
|
+
<div className={`header-container align-${cell?.align || 'left'}`}>
|
|
295
|
+
{cell?.content ? cell.renderedContent : null}
|
|
296
|
+
{filters[i] && filters[i].filter !== '' && filters[i].filter !== undefined ? (
|
|
297
|
+
<GenesysDevIcon icon={GenesysDevIcons.AppFilter} className="filter-active-icon" />
|
|
298
|
+
) : (
|
|
299
|
+
''
|
|
300
|
+
)}
|
|
301
|
+
{isSortable ? <GenesysDevIcon icon={getSortCaret(i)} className="sort-icon" /> : null}
|
|
302
|
+
</div>
|
|
303
|
+
</td>
|
|
304
|
+
))}
|
|
305
|
+
</tr>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Build filter row
|
|
310
|
+
let filterRow;
|
|
311
|
+
if (isFilterable && Object.keys(columnTypes).length > 0) {
|
|
312
|
+
filterRow = (
|
|
313
|
+
<React.Fragment>
|
|
314
|
+
<tr className="filter-spacer"></tr>
|
|
315
|
+
<tr className="filter-row">
|
|
316
|
+
{Object.keys(columnTypes).map((colId, i) => {
|
|
317
|
+
const columnType = columnTypes[colId];
|
|
318
|
+
switch (columnType) {
|
|
319
|
+
case 'date': {
|
|
320
|
+
return (
|
|
321
|
+
<td key={colId}>
|
|
322
|
+
<div className="sort-date">
|
|
323
|
+
<DxTextbox
|
|
324
|
+
className="date-filter"
|
|
325
|
+
label="Filter date"
|
|
326
|
+
inputType="date"
|
|
327
|
+
onChange={(value) => dateFilterChanged(colId, value)}
|
|
328
|
+
initialValue={moment.isMoment(filters[i]?.filter) ? filters[i]?.filter.format('YYYY-MM-DD') : undefined}
|
|
329
|
+
/>
|
|
330
|
+
<DxToggle
|
|
331
|
+
label="Comparison"
|
|
332
|
+
falseIcon={GenesysDevIcons.AppChevronLeft}
|
|
333
|
+
trueIcon={GenesysDevIcons.AppChevronRight}
|
|
334
|
+
initialValue={filters[i]?.filterModifier === 'greaterthan'}
|
|
335
|
+
onChange={(value) => dateFilterModifierChanged(colId, value === false ? 'lessthan' : 'greaterthan')}
|
|
336
|
+
/>
|
|
337
|
+
</div>
|
|
338
|
+
</td>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
case 'number': {
|
|
342
|
+
return (
|
|
343
|
+
<td key={colId}>
|
|
344
|
+
<div className="sort-numeric">
|
|
345
|
+
<DxTextbox
|
|
346
|
+
label="Value"
|
|
347
|
+
inputType="decimal"
|
|
348
|
+
onChange={(value) => numberFilterChanged(colId, value)}
|
|
349
|
+
placeholder={props.headerRow?.cells[i]?.content}
|
|
350
|
+
initialValue={filters[i]?.filter}
|
|
351
|
+
/>
|
|
352
|
+
<DxToggle
|
|
353
|
+
label="Comparison"
|
|
354
|
+
falseIcon={GenesysDevIcons.AppChevronLeft}
|
|
355
|
+
trueIcon={GenesysDevIcons.AppChevronRight}
|
|
356
|
+
initialValue={filters[i]?.filterModifier === 'greaterthan'}
|
|
357
|
+
onChange={(value) => numberFilterModifierChanged(colId, value === false ? 'lessthan' : 'greaterthan')}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
</td>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
case 'string':
|
|
364
|
+
default: {
|
|
365
|
+
return (
|
|
366
|
+
<td key={colId}>
|
|
367
|
+
<DxTextbox
|
|
368
|
+
label="Filter text"
|
|
369
|
+
inputType="text"
|
|
370
|
+
icon={GenesysDevIcons.AppSearch}
|
|
371
|
+
placeholder={props.headerRow?.cells[i]?.content}
|
|
372
|
+
onChange={(value) => stringFilterChanged(colId, value)}
|
|
373
|
+
clearButton={true}
|
|
374
|
+
initialValue={filters[i]?.filter}
|
|
375
|
+
/>
|
|
376
|
+
</td>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
})}
|
|
381
|
+
</tr>
|
|
382
|
+
</React.Fragment>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Build optional table header
|
|
387
|
+
let thead;
|
|
388
|
+
if (columnHeaders || filterRow) {
|
|
389
|
+
thead = (
|
|
390
|
+
<thead>
|
|
391
|
+
{columnHeaders}
|
|
392
|
+
{isFilterOpen ? filterRow : undefined}
|
|
393
|
+
</thead>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Make sure classes always has "table"; sometimes it will be provided, sometimes not
|
|
398
|
+
let tableClassName = props.className || '';
|
|
399
|
+
if (tableClassName?.match(TABLE_CLASS_REGEX) === null) {
|
|
400
|
+
tableClassName = 'table ' + tableClassName.trim();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<div className={`table-container${isSortable ? ' sortable' : ''}${isFilterable ? ' filterable' : ''}`}>
|
|
405
|
+
<div className="filter-container">
|
|
406
|
+
<div className="filter-toggle" style={{ visibility: isFilterable ? 'visible' : 'hidden' }}>
|
|
407
|
+
<GenesysDevIcon icon={GenesysDevIcons.AppFilter} onClick={() => setIsFilterOpen(!isFilterOpen)} />
|
|
408
|
+
</div>
|
|
409
|
+
<table className={tableClassName} cellSpacing="0">
|
|
410
|
+
{thead}
|
|
411
|
+
<tbody>
|
|
412
|
+
{rows.map((row, i) => (
|
|
413
|
+
<tr key={i}>
|
|
414
|
+
{row.cells.map((cell, ii) => (
|
|
415
|
+
<td key={ii} align={cell?.align || 'left'}>
|
|
416
|
+
{cell?.content ? (
|
|
417
|
+
<div className={`align-${cell?.align || 'left'}`}>
|
|
418
|
+
{cell.renderedContent}
|
|
419
|
+
{cell.copyButton ? <CopyButton copyText={cell.content} /> : undefined}
|
|
420
|
+
</div>
|
|
421
|
+
) : null}
|
|
422
|
+
</td>
|
|
423
|
+
))}
|
|
424
|
+
</tr>
|
|
425
|
+
))}
|
|
426
|
+
</tbody>
|
|
427
|
+
</table>
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
);
|
|
431
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import AlertBlock from './alertblock/AlertBlock';
|
|
|
13
13
|
import LoadingPlaceholder from './loadingplaceholder/LoadingPlaceholder';
|
|
14
14
|
import Tooltip from './tooltip/Tooltip';
|
|
15
15
|
import CopyButton from './copybutton/CopyButton';
|
|
16
|
-
|
|
16
|
+
import DataTable from './datatable/DataTable';
|
|
17
17
|
export {
|
|
18
18
|
DxAccordion,
|
|
19
19
|
DxAccordionGroup,
|
|
@@ -29,6 +29,7 @@ export {
|
|
|
29
29
|
CopyButton,
|
|
30
30
|
LoadingPlaceholder,
|
|
31
31
|
AlertBlock,
|
|
32
|
+
DataTable,
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
export interface StringChangedCallback {
|
|
@@ -140,3 +141,16 @@ export interface DxTabPanelProps {
|
|
|
140
141
|
children: React.ReactNode;
|
|
141
142
|
className?: string;
|
|
142
143
|
}
|
|
144
|
+
|
|
145
|
+
export interface DataTableRow {
|
|
146
|
+
cells: DataTableCell[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface DataTableCell {
|
|
150
|
+
raw?: string;
|
|
151
|
+
renderedContent: React.ReactNode;
|
|
152
|
+
content: string;
|
|
153
|
+
parsedContent?: string | number | Date;
|
|
154
|
+
align?: 'left' | 'center' | 'right';
|
|
155
|
+
copyButton?: boolean;
|
|
156
|
+
}
|