@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,127 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import IconButton from '@mui/material/IconButton';
4
+ import Tooltip from '@mui/material/Tooltip';
5
+ import SearchIcon from '@mui/icons-material/Search';
6
+ import TextField from '@mui/material/TextField';
7
+ import ClearIcon from '@mui/icons-material/Clear';
8
+ import styled from 'styled-components';
9
+
10
+ export default function TableSearch({
11
+ search,
12
+ options,
13
+ searchText,
14
+ searchTextUpdate,
15
+ searchClose,
16
+ isMobile,
17
+ onSearchOpen,
18
+ }) {
19
+ const [inputMode, setInputMode] = useState(false);
20
+
21
+ const clickSearchIcon = () => {
22
+ setInputMode(true);
23
+ onSearchOpen(true);
24
+ };
25
+
26
+ const onChange = event => {
27
+ searchTextUpdate(event.currentTarget.value);
28
+ };
29
+
30
+ const clickClose = () => {
31
+ setInputMode(false);
32
+ searchClose();
33
+ onSearchOpen(false);
34
+ };
35
+
36
+ return (
37
+ <Container>
38
+ {inputMode ? (
39
+ <div className="toolbar-search-icon-placeholder-">
40
+ <SearchIcon />
41
+ </div>
42
+ ) : (
43
+ <Tooltip title={search} disableFocusListener>
44
+ <IconButton
45
+ aria-label={search}
46
+ data-testid={`${search}-iconButton`}
47
+ disabled={options.search === 'disabled'}
48
+ onClick={clickSearchIcon}>
49
+ <SearchIcon />
50
+ </IconButton>
51
+ </Tooltip>
52
+ )}
53
+
54
+ <div
55
+ className={`toolbar-search-area ${inputMode ? 'toolbar-btn-show' : ''} ${
56
+ isMobile ? 'small-textfield' : ''
57
+ }`}>
58
+ {inputMode && (
59
+ <TextField
60
+ variant="standard"
61
+ spacing={2}
62
+ onChange={onChange}
63
+ value={searchText || ''}
64
+ autoFocus
65
+ />
66
+ )}
67
+ </div>
68
+ <div className={`toolbar-search-close ${inputMode ? 'toolbar-btn-show' : ''}`}>
69
+ <IconButton onClick={clickClose}>
70
+ <ClearIcon />
71
+ </IconButton>
72
+ </div>
73
+ </Container>
74
+ );
75
+ }
76
+
77
+ TableSearch.propTypes = {
78
+ search: PropTypes.string,
79
+ searchText: PropTypes.string,
80
+ onSearchOpen: PropTypes.func,
81
+ options: PropTypes.object.isRequired,
82
+ searchTextUpdate: PropTypes.func.isRequired,
83
+ searchClose: PropTypes.func.isRequired,
84
+ isMobile: PropTypes.bool.isRequired,
85
+ };
86
+
87
+ TableSearch.defaultProps = {
88
+ search: '',
89
+ searchText: '',
90
+ onSearchOpen: () => {},
91
+ };
92
+
93
+ const Container = styled.div`
94
+ display: flex;
95
+ align-items: center;
96
+ .toolbar-search-area {
97
+ width: 0;
98
+ transition: all ease 0.3s;
99
+ overflow: hidden;
100
+ .MuiFormControl-root {
101
+ width: inherit;
102
+ margin: 0 12px;
103
+ }
104
+ &.toolbar-btn-show {
105
+ width: 260px;
106
+ padding-left: 8px;
107
+ &.small-textfield {
108
+ width: 200px;
109
+ }
110
+ }
111
+ }
112
+ .toolbar-search-close {
113
+ width: 0;
114
+ transition: all ease 0.3s;
115
+ overflow: hidden;
116
+ &.toolbar-btn-show {
117
+ width: 40px;
118
+ }
119
+ }
120
+ .toolbar-search-icon-placeholder {
121
+ display: flex;
122
+ justify-content: center;
123
+ align-items: center;
124
+ width: 40px;
125
+ height: 40px;
126
+ }
127
+ `;
@@ -0,0 +1,145 @@
1
+ import React, { useRef } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import MUIDataTable, { TableFilterList } from 'mui-datatables';
4
+ import styled from 'styled-components';
5
+ import CustomToolbar from './CustomToolbar';
6
+
7
+ const WrapFilterList = props => {
8
+ const hasFilter = !!props.filterList.filter(e => e.length).length;
9
+ if (hasFilter) {
10
+ return (
11
+ <FilterLine>
12
+ {hasFilter && <div className="toolbar-filter-title">Filter</div>}
13
+ <div className="toolbar-filter-content">
14
+ <TableFilterList {...props} />
15
+ </div>
16
+ </FilterLine>
17
+ );
18
+ }
19
+ return '';
20
+ };
21
+
22
+ WrapFilterList.propTypes = {
23
+ filterList: PropTypes.array,
24
+ };
25
+
26
+ WrapFilterList.defaultProps = {
27
+ filterList: [],
28
+ };
29
+
30
+ export default function Datatable({ locale, options, style, customButtons, ...rest }) {
31
+ const container = useRef(null);
32
+
33
+ let textLabels = {
34
+ body: { noMatch: 'Sorry, no matching records found', toolTip: 'Sort' },
35
+ pagination: {
36
+ next: 'Next Page',
37
+ previous: 'Previous Page',
38
+ rowsPerPage: 'Rows per page:',
39
+ displayRows: 'of',
40
+ jumpToPage: 'Jump to Page:',
41
+ },
42
+ toolbar: {
43
+ search: 'Search',
44
+ downloadCsv: 'Download CSV',
45
+ print: 'Print',
46
+ viewColumns: 'View Columns',
47
+ filterTable: 'Filter Table',
48
+ },
49
+ filter: { all: 'All', title: 'FILTERS', reset: 'RESET' },
50
+ viewColumns: { title: 'Show Columns', titleAria: 'Show/Hide Table Columns' },
51
+ selectedRows: { text: 'row(s) selected', delete: 'Delete', deleteAria: 'Delete Selected Rows' },
52
+ };
53
+
54
+ if (locale === 'zh') {
55
+ textLabels = {
56
+ body: { noMatch: '对不起,没有找到匹配的记录', toolTip: '排序' },
57
+ pagination: {
58
+ next: '下一页',
59
+ previous: '上一页',
60
+ rowsPerPage: '每页行数',
61
+ displayRows: '/',
62
+ jumpToPage: '跳转到页面:',
63
+ },
64
+ toolbar: {
65
+ search: '搜索',
66
+ downloadCsv: '下载CSV',
67
+ print: '打印',
68
+ viewColumns: '查看列',
69
+ filterTable: '筛选表格',
70
+ },
71
+ filter: { all: '全部', title: '筛选器', reset: '重置' },
72
+ viewColumns: { title: '显示的列', titleAria: '显示/隐藏 表格的列' },
73
+ selectedRows: { text: '个已选项目', delete: '删除', deleteAria: '删除所选项目' },
74
+ };
75
+ }
76
+
77
+ const opts = {
78
+ selectableRows: 'none',
79
+ textLabels,
80
+ ...options,
81
+ };
82
+
83
+ const WrapCustomToolBar = props => {
84
+ return <CustomToolbar {...props} customButtons={customButtons || []} />;
85
+ };
86
+
87
+ const props = {
88
+ options: opts,
89
+ ...rest,
90
+ components: {
91
+ TableToolbar: WrapCustomToolBar,
92
+ TableFilterList: WrapFilterList,
93
+ },
94
+ };
95
+
96
+ Datatable.propTypes = {
97
+ options: PropTypes.object,
98
+ style: PropTypes.object,
99
+ locale: PropTypes.string,
100
+ customButtons: PropTypes.array,
101
+ };
102
+
103
+ Datatable.defaultProps = {
104
+ options: {},
105
+ style: {},
106
+ locale: 'en',
107
+ customButtons: [],
108
+ };
109
+
110
+ return (
111
+ <TableContainer ref={container} style={style}>
112
+ <MUIDataTable {...props} />
113
+ </TableContainer>
114
+ );
115
+ }
116
+
117
+ const TableContainer = styled.div`
118
+ height: 100%;
119
+ > .MuiPaper-root {
120
+ display: flex;
121
+ flex-direction: column;
122
+ height: 100%;
123
+ box-shadow: none;
124
+ }
125
+ ${props => props.theme.breakpoints.down('md')} {
126
+ [class*='MUIDataTableBody-emptyTitle'] {
127
+ padding-left: 16px;
128
+ width: 200%;
129
+ margin-left: -100%;
130
+ text-align: center;
131
+ }
132
+ }
133
+ `;
134
+
135
+ const FilterLine = styled.div`
136
+ display: flex;
137
+ align-items: center;
138
+ .toolbar-filter-content {
139
+ margin-bottom: 8px;
140
+ }
141
+ .toolbar-filter-title {
142
+ font-weight: 700;
143
+ font-size: 14px;
144
+ }
145
+ `;
@@ -0,0 +1,165 @@
1
+ import find from 'lodash/find';
2
+
3
+ function escapeDangerousCSVCharacters(data) {
4
+ if (typeof data === 'string') {
5
+ // Places single quote before the appearance of dangerous characters if they
6
+ // are the first in the data string.
7
+ // eslint-disable-next-line no-useless-escape
8
+ return data.replace(/^\+|^\-|^\=|^\@/g, "'$&");
9
+ }
10
+
11
+ return data;
12
+ }
13
+
14
+ function buildCSV(columns, data, options) {
15
+ const replaceDoubleQuoteInString = columnData =>
16
+ typeof columnData === 'string' ? columnData.replace(/"/g, '""') : columnData;
17
+
18
+ // eslint-disable-next-line no-shadow
19
+ const buildHead = columns => {
20
+ return `${columns
21
+ .reduce(
22
+ (soFar, column) =>
23
+ column.download
24
+ ? `${soFar}"${escapeDangerousCSVCharacters(
25
+ replaceDoubleQuoteInString(column.label || column.name)
26
+ )}"${options.downloadOptions.separator}`
27
+ : soFar,
28
+ ''
29
+ )
30
+ .slice(0, -1)}\r\n`;
31
+ };
32
+
33
+ const CSVHead = buildHead(columns);
34
+
35
+ // eslint-disable-next-line no-shadow
36
+ const buildBody = data => {
37
+ if (!data.length) return '';
38
+ return data
39
+ .reduce(
40
+ (soFar, row) =>
41
+ `${soFar}"${row.data
42
+ .filter((_, index) => columns[index].download)
43
+ .map(columnData => escapeDangerousCSVCharacters(replaceDoubleQuoteInString(columnData)))
44
+ .join(`"${options.downloadOptions.separator}"`)}"\r\n`,
45
+ ''
46
+ )
47
+ .trim();
48
+ };
49
+ const CSVBody = buildBody(data);
50
+
51
+ const csv = options.onDownload
52
+ ? options.onDownload(buildHead, buildBody, columns, data)
53
+ : `${CSVHead}${CSVBody}`.trim();
54
+
55
+ return csv;
56
+ }
57
+
58
+ function downloadCSV(csv, filename) {
59
+ const blob = new Blob([csv], { type: 'text/csv' });
60
+
61
+ /* taken from react-csv */
62
+ if (navigator && navigator.msSaveOrOpenBlob) {
63
+ navigator.msSaveOrOpenBlob(blob, filename);
64
+ } else {
65
+ const dataURI = `data:text/csv;charset=utf-8,${csv}`;
66
+
67
+ const URL = window.URL || window.webkitURL;
68
+ const downloadURI =
69
+ typeof URL.createObjectURL === 'undefined' ? dataURI : URL.createObjectURL(blob);
70
+
71
+ const link = document.createElement('a');
72
+ link.setAttribute('href', downloadURI);
73
+ link.setAttribute('download', filename);
74
+ document.body.appendChild(link);
75
+ link.click();
76
+ document.body.removeChild(link);
77
+ }
78
+ }
79
+
80
+ // eslint-disable-next-line no-shadow
81
+ function createCSVDownload(columns, data, options, downloadCSV) {
82
+ const csv = buildCSV(columns, data, options);
83
+
84
+ if (options.onDownload && csv === false) {
85
+ return;
86
+ }
87
+
88
+ downloadCSV(csv, options.downloadOptions.filename);
89
+ }
90
+
91
+ export function handleCSVDownload(props) {
92
+ const { data, displayData, columns, options, columnOrder } = props;
93
+ let dataToDownload = [];
94
+ let columnsToDownload = [];
95
+ let columnOrderCopy = Array.isArray(columnOrder) ? columnOrder.slice(0) : [];
96
+
97
+ if (columnOrderCopy.length === 0) {
98
+ columnOrderCopy = columns.map((item, idx) => idx);
99
+ }
100
+
101
+ data.forEach(row => {
102
+ const newRow = { index: row.index, data: [] };
103
+ columnOrderCopy.forEach(idx => {
104
+ newRow.data.push(row.data[idx]);
105
+ });
106
+ dataToDownload.push(newRow);
107
+ });
108
+
109
+ columnOrderCopy.forEach(idx => {
110
+ columnsToDownload.push(columns[idx]);
111
+ });
112
+
113
+ if (options.downloadOptions && options.downloadOptions.filterOptions) {
114
+ // check rows first:
115
+ if (options.downloadOptions.filterOptions.useDisplayedRowsOnly) {
116
+ const filteredDataToDownload = displayData.map((row, index) => {
117
+ let i = -1;
118
+
119
+ // Help to preserve sort order in custom render columns
120
+ row.index = index;
121
+
122
+ return {
123
+ data: row.data.map(column => {
124
+ i += 1;
125
+
126
+ // if we have a custom render, which will appear as a react element, we must grab the actual value from data
127
+ // that matches the dataIndex and column
128
+ // TODO: Create a utility function for checking whether or not something is a react object
129
+ let val =
130
+ typeof column === 'object' && column !== null && !Array.isArray(column)
131
+ ? find(data, d => d.index === row.dataIndex).data[i]
132
+ : column;
133
+ val =
134
+ typeof val === 'function' ? find(data, d => d.index === row.dataIndex).data[i] : val;
135
+ return val;
136
+ }),
137
+ };
138
+ });
139
+
140
+ dataToDownload = [];
141
+ filteredDataToDownload.forEach(row => {
142
+ const newRow = { index: row.index, data: [] };
143
+ columnOrderCopy.forEach(idx => {
144
+ newRow.data.push(row.data[idx]);
145
+ });
146
+ dataToDownload.push(newRow);
147
+ });
148
+ }
149
+
150
+ // now, check columns:
151
+ if (options.downloadOptions.filterOptions.useDisplayedColumnsOnly) {
152
+ columnsToDownload = columnsToDownload.filter(_ => _.display === 'true');
153
+
154
+ dataToDownload = dataToDownload.map(row => {
155
+ row.data = row.data.filter(
156
+ (_, index) => columns[columnOrderCopy[index]].display === 'true'
157
+ );
158
+ return row;
159
+ });
160
+ }
161
+ }
162
+ createCSVDownload(columnsToDownload, dataToDownload, options, downloadCSV);
163
+ }
164
+
165
+ export default { handleCSVDownload };
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ import Icon from './Icon';
15
15
  import LocaleSelector from './Locale/selector';
16
16
  import Logo from './Logo';
17
17
  import RelativeTime from './RelativeTime';
18
+ import Datatable from './Datatable';
18
19
  import Tabs from './Tabs';
19
20
  import Tag from './Tag';
20
21
  import Terminal from './Terminal';
@@ -50,6 +51,7 @@ export {
50
51
  Logo,
51
52
  Tabs,
52
53
  RelativeTime,
54
+ Datatable,
53
55
  Tag,
54
56
  Terminal,
55
57
  TerminalPlayer,