@arcblock/ux 2.1.4 → 2.1.7

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.
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ exports.handleCSVDownload = handleCSVDownload;
8
+
9
+ var _find = _interopRequireDefault(require("lodash/find"));
10
+
11
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
+
13
+ function escapeDangerousCSVCharacters(data) {
14
+ if (typeof data === 'string') {
15
+ // Places single quote before the appearance of dangerous characters if they
16
+ // are the first in the data string.
17
+ // eslint-disable-next-line no-useless-escape
18
+ return data.replace(/^\+|^\-|^\=|^\@/g, "'$&");
19
+ }
20
+
21
+ return data;
22
+ }
23
+
24
+ function buildCSV(columns, data, options) {
25
+ const replaceDoubleQuoteInString = columnData => typeof columnData === 'string' ? columnData.replace(/"/g, '""') : columnData; // eslint-disable-next-line no-shadow
26
+
27
+
28
+ const buildHead = columns => {
29
+ return "".concat(columns.reduce((soFar, column) => column.download ? "".concat(soFar, "\"").concat(escapeDangerousCSVCharacters(replaceDoubleQuoteInString(column.label || column.name)), "\"").concat(options.downloadOptions.separator) : soFar, '').slice(0, -1), "\r\n");
30
+ };
31
+
32
+ const CSVHead = buildHead(columns); // eslint-disable-next-line no-shadow
33
+
34
+ const buildBody = data => {
35
+ if (!data.length) return '';
36
+ return data.reduce((soFar, row) => "".concat(soFar, "\"").concat(row.data.filter((_, index) => columns[index].download).map(columnData => escapeDangerousCSVCharacters(replaceDoubleQuoteInString(columnData))).join("\"".concat(options.downloadOptions.separator, "\"")), "\"\r\n"), '').trim();
37
+ };
38
+
39
+ const CSVBody = buildBody(data);
40
+ const csv = options.onDownload ? options.onDownload(buildHead, buildBody, columns, data) : "".concat(CSVHead).concat(CSVBody).trim();
41
+ return csv;
42
+ }
43
+
44
+ function downloadCSV(csv, filename) {
45
+ const blob = new Blob([csv], {
46
+ type: 'text/csv'
47
+ });
48
+ /* taken from react-csv */
49
+
50
+ if (navigator && navigator.msSaveOrOpenBlob) {
51
+ navigator.msSaveOrOpenBlob(blob, filename);
52
+ } else {
53
+ const dataURI = "data:text/csv;charset=utf-8,".concat(csv);
54
+ const URL = window.URL || window.webkitURL;
55
+ const downloadURI = typeof URL.createObjectURL === 'undefined' ? dataURI : URL.createObjectURL(blob);
56
+ const link = document.createElement('a');
57
+ link.setAttribute('href', downloadURI);
58
+ link.setAttribute('download', filename);
59
+ document.body.appendChild(link);
60
+ link.click();
61
+ document.body.removeChild(link);
62
+ }
63
+ } // eslint-disable-next-line no-shadow
64
+
65
+
66
+ function createCSVDownload(columns, data, options, downloadCSV) {
67
+ const csv = buildCSV(columns, data, options);
68
+
69
+ if (options.onDownload && csv === false) {
70
+ return;
71
+ }
72
+
73
+ downloadCSV(csv, options.downloadOptions.filename);
74
+ }
75
+
76
+ function handleCSVDownload(props) {
77
+ const {
78
+ data,
79
+ displayData,
80
+ columns,
81
+ options,
82
+ columnOrder
83
+ } = props;
84
+ let dataToDownload = [];
85
+ let columnsToDownload = [];
86
+ let columnOrderCopy = Array.isArray(columnOrder) ? columnOrder.slice(0) : [];
87
+
88
+ if (columnOrderCopy.length === 0) {
89
+ columnOrderCopy = columns.map((item, idx) => idx);
90
+ }
91
+
92
+ data.forEach(row => {
93
+ const newRow = {
94
+ index: row.index,
95
+ data: []
96
+ };
97
+ columnOrderCopy.forEach(idx => {
98
+ newRow.data.push(row.data[idx]);
99
+ });
100
+ dataToDownload.push(newRow);
101
+ });
102
+ columnOrderCopy.forEach(idx => {
103
+ columnsToDownload.push(columns[idx]);
104
+ });
105
+
106
+ if (options.downloadOptions && options.downloadOptions.filterOptions) {
107
+ // check rows first:
108
+ if (options.downloadOptions.filterOptions.useDisplayedRowsOnly) {
109
+ const filteredDataToDownload = displayData.map((row, index) => {
110
+ let i = -1; // Help to preserve sort order in custom render columns
111
+
112
+ row.index = index;
113
+ return {
114
+ data: row.data.map(column => {
115
+ i += 1; // if we have a custom render, which will appear as a react element, we must grab the actual value from data
116
+ // that matches the dataIndex and column
117
+ // TODO: Create a utility function for checking whether or not something is a react object
118
+
119
+ let val = typeof column === 'object' && column !== null && !Array.isArray(column) ? (0, _find.default)(data, d => d.index === row.dataIndex).data[i] : column;
120
+ val = typeof val === 'function' ? (0, _find.default)(data, d => d.index === row.dataIndex).data[i] : val;
121
+ return val;
122
+ })
123
+ };
124
+ });
125
+ dataToDownload = [];
126
+ filteredDataToDownload.forEach(row => {
127
+ const newRow = {
128
+ index: row.index,
129
+ data: []
130
+ };
131
+ columnOrderCopy.forEach(idx => {
132
+ newRow.data.push(row.data[idx]);
133
+ });
134
+ dataToDownload.push(newRow);
135
+ });
136
+ } // now, check columns:
137
+
138
+
139
+ if (options.downloadOptions.filterOptions.useDisplayedColumnsOnly) {
140
+ columnsToDownload = columnsToDownload.filter(_ => _.display === 'true');
141
+ dataToDownload = dataToDownload.map(row => {
142
+ row.data = row.data.filter((_, index) => columns[columnOrderCopy[index]].display === 'true');
143
+ return row;
144
+ });
145
+ }
146
+ }
147
+
148
+ createCSVDownload(columnsToDownload, dataToDownload, options, downloadCSV);
149
+ }
150
+
151
+ var _default = {
152
+ handleCSVDownload
153
+ };
154
+ exports.default = _default;
package/lib/index.js CHANGED
@@ -69,6 +69,12 @@ Object.defineProperty(exports, "CountDown", {
69
69
  return _CountDown.default;
70
70
  }
71
71
  });
72
+ Object.defineProperty(exports, "Datatable", {
73
+ enumerable: true,
74
+ get: function get() {
75
+ return _Datatable.default;
76
+ }
77
+ });
72
78
  Object.defineProperty(exports, "Earth", {
73
79
  enumerable: true,
74
80
  get: function get() {
@@ -230,6 +236,8 @@ var _Logo = _interopRequireDefault(require("./Logo"));
230
236
 
231
237
  var _RelativeTime = _interopRequireDefault(require("./RelativeTime"));
232
238
 
239
+ var _Datatable = _interopRequireDefault(require("./Datatable"));
240
+
233
241
  var _Tabs = _interopRequireDefault(require("./Tabs"));
234
242
 
235
243
  var _Tag = _interopRequireDefault(require("./Tag"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.1.4",
3
+ "version": "2.1.7",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -52,10 +52,10 @@
52
52
  "react": ">=18.1.0",
53
53
  "react-ga": "^2.7.0"
54
54
  },
55
- "gitHead": "5a92264deb34d879813be444bc26fa3f0cef0672",
55
+ "gitHead": "a54ef3a9335aae1bc7404b5f4a8c9c508b6ff81f",
56
56
  "dependencies": {
57
- "@arcblock/icons": "^2.1.4",
58
- "@arcblock/react-hooks": "^2.1.4",
57
+ "@arcblock/icons": "^2.1.7",
58
+ "@arcblock/react-hooks": "^2.1.7",
59
59
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
60
60
  "@emotion/react": "^11.9.0",
61
61
  "@emotion/styled": "^11.8.1",
@@ -75,6 +75,7 @@
75
75
  "js-cookie": "^2.2.0",
76
76
  "lodash": "^4.17.21",
77
77
  "mdi-material-ui": "^7.2.0",
78
+ "mui-datatables": "^4.2.2",
78
79
  "notistack": "^2.0.5",
79
80
  "react-cookie-consent": "^6.4.1",
80
81
  "react-helmet": "^6.1.0",
@@ -12,7 +12,8 @@ import Img from '../Img';
12
12
 
13
13
  const Div = styled.div`
14
14
  &.arcblock-blocklet {
15
- padding: 16px 16px 0 16px;
15
+ padding: ${props => props.theme.spacing(2)} ${props => props.theme.spacing(2)} 0
16
+ ${props => props.theme.spacing(2)};
16
17
  }
17
18
  .arcblock-blocklet__content {
18
19
  cursor: pointer;
@@ -24,7 +25,7 @@ const Div = styled.div`
24
25
  .arcblock-blocklet__cover {
25
26
  width: 64px;
26
27
  height: 64px;
27
- margin-right: 16px;
28
+ margin-right: ${props => props.theme.spacing(2)};
28
29
  overflow: hidden;
29
30
  border-radius: 12px;
30
31
  /* see: https://stackoverflow.com/questions/49066011/overflow-hidden-with-border-radius-not-working-on-safari */
@@ -83,7 +84,7 @@ const Div = styled.div`
83
84
  flex: 1;
84
85
  overflow: hidden;
85
86
  border-bottom: 1px solid ${props => props.theme.palette.divider};
86
- padding-bottom: 24px;
87
+ padding-bottom: ${props => props.theme.spacing(2)};
87
88
  }
88
89
  .arcblock-blocklet__text {
89
90
  height: 57px;
@@ -94,14 +95,12 @@ const Div = styled.div`
94
95
  margin: 0;
95
96
  font-size: 16px;
96
97
  font-weight: 500;
97
- line-height: 19px;
98
- max-height: 19px;
99
98
  overflow: hidden;
100
99
  text-overflow: ellipsis;
101
100
  white-space: nowrap;
102
101
  }
103
102
  .arcblock-blocklet__describe {
104
- margin-top: 4px;
103
+ margin-top: ${props => props.theme.spacing(0.5)};
105
104
  color: ${props => props.theme.palette.grey[600]};
106
105
  font-size: 14px;
107
106
  font-weight: 500;
@@ -112,7 +111,7 @@ const Div = styled.div`
112
111
  display: -webkit-box;
113
112
  -webkit-line-clamp: 2;
114
113
  -webkit-box-orient: vertical;
115
- word-break: break-all;
114
+ word-break: break-word;
116
115
  }
117
116
  .arcblock-blocklet__version {
118
117
  color: ${props => props.theme.palette.grey[600]};
@@ -0,0 +1,371 @@
1
+ import React, { useState, useRef, isValidElement } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { TableFilter, TableViewCol } from 'mui-datatables';
4
+ import styled from 'styled-components';
5
+ import ReactToPrint, { PrintContextConsumer } from 'react-to-print';
6
+ import IconButton from '@mui/material/IconButton';
7
+ import Tooltip from '@mui/material/Tooltip';
8
+ import DownloadIcon from '@mui/icons-material/CloudDownload';
9
+ import PrintIcon from '@mui/icons-material/Print';
10
+ import ViewColumnIcon from '@mui/icons-material/ViewColumn';
11
+ import FilterIcon from '@mui/icons-material/FilterList';
12
+ import Popover from '@mui/material/Popover';
13
+ import MoreVertIcon from '@mui/icons-material/MoreVert';
14
+ import Menu from '@mui/material/Menu';
15
+ import MenuItem from '@mui/material/MenuItem';
16
+ import ListItemIcon from '@mui/material/ListItemIcon';
17
+ import ListItemText from '@mui/material/ListItemText';
18
+ import useMediaQuery from '@mui/material/useMediaQuery';
19
+ import { useTheme } from '@mui/material/styles';
20
+ import { handleCSVDownload } from './utils';
21
+ import TableSearch from './TableSearch';
22
+
23
+ function useMobile() {
24
+ const theme = useTheme();
25
+ return useMediaQuery(theme.breakpoints.down('sm'));
26
+ }
27
+
28
+ export default function CustomToolbar(props) {
29
+ const [menuIconEl, setMenuIconEl] = useState(null);
30
+ const moreBtn = useRef(null);
31
+ const isMobile = useMobile();
32
+ const toolbarId = useRef(Math.random().toString(32).slice(2));
33
+
34
+ const {
35
+ data,
36
+ options,
37
+ components,
38
+ columns,
39
+ filterList,
40
+ filterData,
41
+ filterUpdate,
42
+ resetFilters,
43
+ updateFilterByType,
44
+ toggleViewColumn,
45
+ updateColumns,
46
+ title,
47
+ searchText,
48
+ searchTextUpdate,
49
+ searchClose,
50
+ customButtons,
51
+ } = props;
52
+
53
+ const { search, downloadCsv, print, viewColumns, filterTable } = options.textLabels.toolbar;
54
+
55
+ const hideSearch = options.search === false || options.search === 'false';
56
+ const hidePrint = options.print === false || options.print === 'false';
57
+
58
+ const TableFilterComponent = components.TableFilter || TableFilter;
59
+ const TableViewColComponent = components.TableViewCol || TableViewCol;
60
+
61
+ const printArea = func => {
62
+ return (
63
+ <ReactToPrint content={() => props.tableRef()}>
64
+ <PrintContextConsumer>{func}</PrintContextConsumer>
65
+ </ReactToPrint>
66
+ );
67
+ };
68
+
69
+ const getPopId = key => `toolbar-pop-${toolbarId.current}-${key}`;
70
+
71
+ const defaultButtons = [];
72
+
73
+ if (!(options.download === false || options.download === 'false')) {
74
+ defaultButtons.push({
75
+ Icon: DownloadIcon,
76
+ title: downloadCsv,
77
+ onClick: () => {
78
+ handleCSVDownload(props);
79
+ },
80
+ });
81
+ }
82
+
83
+ if (!(options.viewColumns === false || options.viewColumns === 'false')) {
84
+ defaultButtons.push({
85
+ Icon: ViewColumnIcon,
86
+ title: viewColumns,
87
+ popRender() {
88
+ return (
89
+ <TableViewColComponent
90
+ data={data}
91
+ columns={columns}
92
+ options={options}
93
+ onColumnUpdate={toggleViewColumn}
94
+ updateColumns={updateColumns}
95
+ components={components}
96
+ />
97
+ );
98
+ },
99
+ });
100
+ }
101
+
102
+ if (!(options.filter === false || options.filter === 'false')) {
103
+ defaultButtons.push({
104
+ Icon: FilterIcon,
105
+ title: filterTable,
106
+ popRender() {
107
+ return (
108
+ <TableFilterComponent
109
+ customFooter={options.customFilterDialogFooter}
110
+ columns={columns}
111
+ options={options}
112
+ filterList={filterList}
113
+ filterData={filterData}
114
+ onFilterUpdate={filterUpdate}
115
+ onFilterReset={resetFilters}
116
+ updateFilterByType={updateFilterByType}
117
+ components={components}
118
+ />
119
+ );
120
+ },
121
+ });
122
+ }
123
+
124
+ const showMore =
125
+ [!hidePrint, ...defaultButtons, ...customButtons].filter(e => !!e).length > 1 && isMobile;
126
+
127
+ const allPops = [];
128
+ const [allPopsEl, setAllPopsEl] = useState({});
129
+
130
+ const toolbarButtons = [...defaultButtons, ...customButtons].map((e, index) => {
131
+ if (isValidElement(e)) {
132
+ return e;
133
+ }
134
+
135
+ const popId = getPopId(index);
136
+
137
+ if (e.Icon) {
138
+ const { Icon, popRender } = e;
139
+ if (popRender) {
140
+ allPops.push(
141
+ <Popover
142
+ open={!!allPopsEl[popId]}
143
+ anchorEl={() => allPopsEl[popId]}
144
+ onClose={() => {
145
+ setAllPopsEl({});
146
+ }}
147
+ key={popId}
148
+ anchorOrigin={{
149
+ vertical: 'bottom',
150
+ horizontal: 'right',
151
+ }}>
152
+ <div>{popRender()}</div>
153
+ </Popover>
154
+ );
155
+ }
156
+
157
+ return (
158
+ <Tooltip title={e.title} key={popId}>
159
+ <IconButton
160
+ data-testid={`${e.title}-iconButton`}
161
+ id={`btn-${popId}`}
162
+ aria-label={e.title}
163
+ onClick={() => {
164
+ if (e.onClick) {
165
+ e.onClick();
166
+ }
167
+
168
+ if (popRender) {
169
+ setAllPopsEl({
170
+ [popId]: document.getElementById(`btn-${popId}`),
171
+ });
172
+ }
173
+ }}>
174
+ <Icon />
175
+ </IconButton>
176
+ </Tooltip>
177
+ );
178
+ }
179
+
180
+ return e;
181
+ });
182
+
183
+ const menuItems = [...defaultButtons, ...customButtons].map((e, index) => {
184
+ const popId = getPopId(index);
185
+
186
+ let content;
187
+
188
+ if (isValidElement(e)) {
189
+ content = e;
190
+ } else if (e.Icon) {
191
+ const { Icon } = e;
192
+
193
+ content = (
194
+ <>
195
+ <ListItemIcon>
196
+ <Icon fontSize="small" />
197
+ </ListItemIcon>
198
+ <ListItemText>{e.title}</ListItemText>
199
+ </>
200
+ );
201
+ }
202
+
203
+ return (
204
+ <MenuItem
205
+ key={popId}
206
+ onClick={() => {
207
+ setMenuIconEl(null);
208
+ if (e.onClick) {
209
+ e.onClick();
210
+ }
211
+
212
+ if (e.popRender) {
213
+ setAllPopsEl({
214
+ [popId]: moreBtn.current,
215
+ });
216
+ }
217
+ }}>
218
+ {content}
219
+ </MenuItem>
220
+ );
221
+ });
222
+
223
+ return (
224
+ <div>
225
+ <Container>
226
+ <div className="custom-toobar-title">
227
+ <div className="custom-toobar-title-inner">
228
+ <span>{title}</span>
229
+ </div>
230
+ </div>
231
+ <div className="custom-toobar-right">
232
+ <div className="custom-toobar-btns">
233
+ {!hideSearch && (
234
+ <TableSearch
235
+ search={search}
236
+ options={options}
237
+ searchText={searchText}
238
+ searchTextUpdate={searchTextUpdate}
239
+ searchClose={searchClose}
240
+ isMobile={isMobile}
241
+ />
242
+ )}
243
+ {!showMore && (
244
+ <>
245
+ {!hidePrint &&
246
+ printArea(({ handlePrint }) => (
247
+ <span>
248
+ <Tooltip title={print}>
249
+ <IconButton
250
+ data-testid={`${print}-iconButton`}
251
+ aria-label={print}
252
+ disabled={options.print === 'disabled'}
253
+ onClick={handlePrint}>
254
+ <PrintIcon />
255
+ </IconButton>
256
+ </Tooltip>
257
+ </span>
258
+ ))}
259
+
260
+ {toolbarButtons}
261
+ </>
262
+ )}
263
+ {showMore && (
264
+ <IconButton
265
+ ref={moreBtn}
266
+ aria-haspopup="true"
267
+ aria-expanded={menuIconEl ? 'true' : undefined}
268
+ onClick={event => setMenuIconEl(event.currentTarget)}>
269
+ <MoreVertIcon />
270
+ </IconButton>
271
+ )}
272
+ </div>
273
+ </div>
274
+ </Container>
275
+
276
+ <Menu
277
+ anchorEl={menuIconEl}
278
+ open={!!menuIconEl}
279
+ onClose={() => setMenuIconEl(null)}
280
+ MenuListProps={{
281
+ 'aria-labelledby': 'more-button',
282
+ }}>
283
+ {!hidePrint &&
284
+ printArea(({ handlePrint }) => (
285
+ <MenuItem
286
+ onClick={() => {
287
+ setMenuIconEl(null);
288
+ handlePrint();
289
+ }}>
290
+ <ListItemIcon>
291
+ <PrintIcon fontSize="small" />
292
+ </ListItemIcon>
293
+ <ListItemText>{print}</ListItemText>
294
+ </MenuItem>
295
+ ))}
296
+ {menuItems}
297
+ </Menu>
298
+ {allPops.map((e, index) => (
299
+ <div key={getPopId(index)}>{e}</div>
300
+ ))}
301
+ </div>
302
+ );
303
+ }
304
+
305
+ CustomToolbar.propTypes = {
306
+ data: PropTypes.array,
307
+ options: PropTypes.object.isRequired,
308
+ components: PropTypes.object,
309
+ columns: PropTypes.array.isRequired,
310
+ filterList: PropTypes.array,
311
+ filterData: PropTypes.array,
312
+ filterUpdate: PropTypes.func.isRequired,
313
+ resetFilters: PropTypes.func.isRequired,
314
+ updateFilterByType: PropTypes.func.isRequired,
315
+ toggleViewColumn: PropTypes.func.isRequired,
316
+ updateColumns: PropTypes.func.isRequired,
317
+ title: PropTypes.string,
318
+ searchText: PropTypes.any,
319
+ searchTextUpdate: PropTypes.func.isRequired,
320
+ searchClose: PropTypes.func.isRequired,
321
+ tableRef: PropTypes.func.isRequired,
322
+ customButtons: PropTypes.array.isRequired,
323
+ };
324
+
325
+ CustomToolbar.defaultProps = {
326
+ data: [],
327
+ components: {},
328
+ filterList: [],
329
+ filterData: [],
330
+ title: '',
331
+ searchText: null,
332
+ };
333
+
334
+ const Container = styled.div`
335
+ display: flex;
336
+ align-items: center;
337
+ height: 56px;
338
+ .custom-toobar {
339
+ &-title {
340
+ position: relative;
341
+ flex: 1;
342
+ font-size: 18px;
343
+ font-weight: 800;
344
+ height: 56px;
345
+ &-inner {
346
+ line-height: 56px;
347
+ width: 100%;
348
+ height: 56px;
349
+ position: absolute;
350
+ left: 0;
351
+ top: 0;
352
+ span {
353
+ display: inline-block;
354
+ max-width: 100%;
355
+ white-space: nowrap;
356
+ text-overflow: ellipsis;
357
+ overflow: hidden;
358
+ }
359
+ }
360
+ }
361
+ &-right {
362
+ display: flex;
363
+ margin-left: auto;
364
+ }
365
+ &-btns {
366
+ display: flex;
367
+ justify-content: center;
368
+ align-items: center;
369
+ }
370
+ }
371
+ `;