@arcblock/ux 2.1.7 → 2.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.1.7",
3
+ "version": "2.1.10",
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": "a54ef3a9335aae1bc7404b5f4a8c9c508b6ff81f",
55
+ "gitHead": "f31615cda7024bde33cc8b4be093ab8e0baf46a1",
56
56
  "dependencies": {
57
- "@arcblock/icons": "^2.1.7",
58
- "@arcblock/react-hooks": "^2.1.7",
57
+ "@arcblock/icons": "^2.1.10",
58
+ "@arcblock/react-hooks": "^2.1.10",
59
59
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
60
60
  "@emotion/react": "^11.9.0",
61
61
  "@emotion/styled": "^11.8.1",
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, isValidElement } from 'react';
1
+ import React, { useState, useRef, useEffect, isValidElement } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { TableFilter, TableViewCol } from 'mui-datatables';
4
4
  import styled from 'styled-components';
@@ -17,8 +17,10 @@ import ListItemIcon from '@mui/material/ListItemIcon';
17
17
  import ListItemText from '@mui/material/ListItemText';
18
18
  import useMediaQuery from '@mui/material/useMediaQuery';
19
19
  import { useTheme } from '@mui/material/styles';
20
+ import LinearProgress from '@mui/material/LinearProgress';
20
21
  import { handleCSVDownload } from './utils';
21
22
  import TableSearch from './TableSearch';
23
+ import { useDatatableContext } from './DatatableContext';
22
24
 
23
25
  function useMobile() {
24
26
  const theme = useTheme();
@@ -30,6 +32,8 @@ export default function CustomToolbar(props) {
30
32
  const moreBtn = useRef(null);
31
33
  const isMobile = useMobile();
32
34
  const toolbarId = useRef(Math.random().toString(32).slice(2));
35
+ const [searchOpened, setSearchOpened] = useState(false);
36
+ const { customButtons, loading, disabled } = useDatatableContext();
33
37
 
34
38
  const {
35
39
  data,
@@ -47,9 +51,10 @@ export default function CustomToolbar(props) {
47
51
  searchText,
48
52
  searchTextUpdate,
49
53
  searchClose,
50
- customButtons,
51
54
  } = props;
52
55
 
56
+ const customToolbarEle = options.customToolbar ? options.customToolbar(props) : '';
57
+
53
58
  const { search, downloadCsv, print, viewColumns, filterTable } = options.textLabels.toolbar;
54
59
 
55
60
  const hideSearch = options.search === false || options.search === 'false';
@@ -58,6 +63,12 @@ export default function CustomToolbar(props) {
58
63
  const TableFilterComponent = components.TableFilter || TableFilter;
59
64
  const TableViewColComponent = components.TableViewCol || TableViewCol;
60
65
 
66
+ useEffect(() => {
67
+ if (loading || disabled) {
68
+ setAllPopsEl({});
69
+ }
70
+ }, [loading, disabled]);
71
+
61
72
  const printArea = func => {
62
73
  return (
63
74
  <ReactToPrint content={() => props.tableRef()}>
@@ -70,6 +81,7 @@ export default function CustomToolbar(props) {
70
81
 
71
82
  const defaultButtons = [];
72
83
 
84
+ // download/viewColumns/filter button behaviours, rendered using custom button logic
73
85
  if (!(options.download === false || options.download === 'false')) {
74
86
  defaultButtons.push({
75
87
  Icon: DownloadIcon,
@@ -127,6 +139,8 @@ export default function CustomToolbar(props) {
127
139
  const allPops = [];
128
140
  const [allPopsEl, setAllPopsEl] = useState({});
129
141
 
142
+ // Large screens show the toolbar buttons directly, small screens show the drop-down menu style buttons
143
+ // The right-hand button of the form toolbar in desktop mode
130
144
  const toolbarButtons = [...defaultButtons, ...customButtons].map((e, index) => {
131
145
  if (isValidElement(e)) {
132
146
  return e;
@@ -136,6 +150,7 @@ export default function CustomToolbar(props) {
136
150
 
137
151
  if (e.Icon) {
138
152
  const { Icon, popRender } = e;
153
+ // When popRender is present, clicking the button will bubble up the content returned by the popRender
139
154
  if (popRender) {
140
155
  allPops.push(
141
156
  <Popover
@@ -166,6 +181,7 @@ export default function CustomToolbar(props) {
166
181
  }
167
182
 
168
183
  if (popRender) {
184
+ // On the large screen, the bubble is positioned at the corresponding button
169
185
  setAllPopsEl({
170
186
  [popId]: document.getElementById(`btn-${popId}`),
171
187
  });
@@ -180,6 +196,7 @@ export default function CustomToolbar(props) {
180
196
  return e;
181
197
  });
182
198
 
199
+ // The toolbar menu in the mobile to replace toolbarButtons
183
200
  const menuItems = [...defaultButtons, ...customButtons].map((e, index) => {
184
201
  const popId = getPopId(index);
185
202
 
@@ -210,6 +227,7 @@ export default function CustomToolbar(props) {
210
227
  }
211
228
 
212
229
  if (e.popRender) {
230
+ // On the small screen, the bubbles are positioned at the three dot buttons
213
231
  setAllPopsEl({
214
232
  [popId]: moreBtn.current,
215
233
  });
@@ -223,54 +241,57 @@ export default function CustomToolbar(props) {
223
241
  return (
224
242
  <div>
225
243
  <Container>
226
- <div className="custom-toobar-title">
244
+ <div
245
+ className={`custom-toobar-title ${
246
+ isMobile && searchOpened && isValidElement(title) ? 'toobar-title-hidden' : ''
247
+ }`}>
227
248
  <div className="custom-toobar-title-inner">
228
249
  <span>{title}</span>
229
250
  </div>
230
251
  </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>
252
+ <div className={`custom-toobar-btns ${loading || disabled ? 'toobar-btns-disabled' : ''}`}>
253
+ {!hideSearch && (
254
+ <TableSearch
255
+ search={search}
256
+ options={options}
257
+ searchText={searchText}
258
+ searchTextUpdate={searchTextUpdate}
259
+ searchClose={searchClose}
260
+ onSearchOpen={setSearchOpened}
261
+ />
262
+ )}
263
+ {!showMore && (
264
+ <>
265
+ {!hidePrint &&
266
+ printArea(({ handlePrint }) => (
267
+ <span>
268
+ <Tooltip title={print}>
269
+ <IconButton
270
+ data-testid={`${print}-iconButton`}
271
+ aria-label={print}
272
+ disabled={options.print === 'disabled'}
273
+ onClick={handlePrint}>
274
+ <PrintIcon />
275
+ </IconButton>
276
+ </Tooltip>
277
+ </span>
278
+ ))}
279
+
280
+ {toolbarButtons}
281
+ </>
282
+ )}
283
+ {showMore && (
284
+ <IconButton
285
+ ref={moreBtn}
286
+ aria-haspopup="true"
287
+ aria-expanded={menuIconEl ? 'true' : undefined}
288
+ onClick={event => setMenuIconEl(event.currentTarget)}
289
+ style={{ flexShrink: 0 }}>
290
+ <MoreVertIcon />
291
+ </IconButton>
292
+ )}
273
293
  </div>
294
+ {customToolbarEle}
274
295
  </Container>
275
296
 
276
297
  <Menu
@@ -298,6 +319,7 @@ export default function CustomToolbar(props) {
298
319
  {allPops.map((e, index) => (
299
320
  <div key={getPopId(index)}>{e}</div>
300
321
  ))}
322
+ {loading && <LinearProgress />}
301
323
  </div>
302
324
  );
303
325
  }
@@ -314,12 +336,11 @@ CustomToolbar.propTypes = {
314
336
  updateFilterByType: PropTypes.func.isRequired,
315
337
  toggleViewColumn: PropTypes.func.isRequired,
316
338
  updateColumns: PropTypes.func.isRequired,
317
- title: PropTypes.string,
339
+ title: PropTypes.any,
318
340
  searchText: PropTypes.any,
319
341
  searchTextUpdate: PropTypes.func.isRequired,
320
342
  searchClose: PropTypes.func.isRequired,
321
343
  tableRef: PropTypes.func.isRequired,
322
- customButtons: PropTypes.array.isRequired,
323
344
  };
324
345
 
325
346
  CustomToolbar.defaultProps = {
@@ -335,37 +356,51 @@ const Container = styled.div`
335
356
  display: flex;
336
357
  align-items: center;
337
358
  height: 56px;
338
- .custom-toobar {
339
- &-title {
340
- position: relative;
341
- flex: 1;
342
- font-size: 18px;
343
- font-weight: 800;
359
+ .custom-toobar-title {
360
+ position: relative;
361
+ flex: 1;
362
+ font-size: 18px;
363
+ font-weight: 800;
364
+ height: 56px;
365
+ transition: all ease 0.3s;
366
+ &-inner {
367
+ line-height: 56px;
368
+ width: 100%;
344
369
  height: 56px;
345
- &-inner {
346
- line-height: 56px;
347
- width: 100%;
348
- height: 56px;
370
+ position: absolute;
371
+ left: 0;
372
+ top: 0;
373
+ span {
374
+ display: inline-block;
375
+ max-width: 100%;
376
+ white-space: nowrap;
377
+ text-overflow: ellipsis;
378
+ overflow: hidden;
379
+ }
380
+ }
381
+ }
382
+ .custom-toobar-btns {
383
+ display: flex;
384
+ justify-content: center;
385
+ align-items: center;
386
+ &.toobar-btns-disabled {
387
+ position: relative;
388
+ opacity: 0.5;
389
+ &:after {
349
390
  position: absolute;
391
+ display: block;
392
+ z-index: 2;
393
+ width: 100%;
394
+ height: 100%;
350
395
  left: 0;
351
396
  top: 0;
352
- span {
353
- display: inline-block;
354
- max-width: 100%;
355
- white-space: nowrap;
356
- text-overflow: ellipsis;
357
- overflow: hidden;
358
- }
397
+ content: '';
398
+ cursor: not-allowed;
359
399
  }
360
400
  }
361
- &-right {
362
- display: flex;
363
- margin-left: auto;
364
- }
365
- &-btns {
366
- display: flex;
367
- justify-content: center;
368
- align-items: center;
369
- }
401
+ }
402
+ .toobar-title-hidden {
403
+ opacity: 0;
404
+ cursor: none;
370
405
  }
371
406
  `;
@@ -0,0 +1,32 @@
1
+ import React, { createContext, useContext, useState } from 'react';
2
+
3
+ const DatatableContext = createContext({});
4
+
5
+ const { Provider } = DatatableContext;
6
+
7
+ // eslint-disable-next-line react/prop-types
8
+ const DatatableProvide = ({ children }) => {
9
+ const [customButtons, setCustomButtons] = useState([]);
10
+ const [loading, setLoading] = useState(false);
11
+ const [disabled, setDisabled] = useState(false);
12
+ const [filterLabel, setFilterLabel] = useState('Filter');
13
+
14
+ const value = {
15
+ customButtons,
16
+ setCustomButtons,
17
+ filterLabel,
18
+ setFilterLabel,
19
+ loading,
20
+ setLoading,
21
+ disabled,
22
+ setDisabled,
23
+ };
24
+
25
+ return <Provider value={value}>{children}</Provider>;
26
+ };
27
+
28
+ function useDatatableContext() {
29
+ return useContext(DatatableContext);
30
+ }
31
+
32
+ export { DatatableProvide, useDatatableContext };
@@ -13,7 +13,6 @@ export default function TableSearch({
13
13
  searchText,
14
14
  searchTextUpdate,
15
15
  searchClose,
16
- isMobile,
17
16
  onSearchOpen,
18
17
  }) {
19
18
  const [inputMode, setInputMode] = useState(false);
@@ -36,7 +35,7 @@ export default function TableSearch({
36
35
  return (
37
36
  <Container>
38
37
  {inputMode ? (
39
- <div className="toolbar-search-icon-placeholder-">
38
+ <div className="toolbar-search-icon-placeholder">
40
39
  <SearchIcon />
41
40
  </div>
42
41
  ) : (
@@ -51,10 +50,7 @@ export default function TableSearch({
51
50
  </Tooltip>
52
51
  )}
53
52
 
54
- <div
55
- className={`toolbar-search-area ${inputMode ? 'toolbar-btn-show' : ''} ${
56
- isMobile ? 'small-textfield' : ''
57
- }`}>
53
+ <div className={`toolbar-search-area ${inputMode ? 'toolbar-btn-show' : ''}`}>
58
54
  {inputMode && (
59
55
  <TextField
60
56
  variant="standard"
@@ -81,7 +77,6 @@ TableSearch.propTypes = {
81
77
  options: PropTypes.object.isRequired,
82
78
  searchTextUpdate: PropTypes.func.isRequired,
83
79
  searchClose: PropTypes.func.isRequired,
84
- isMobile: PropTypes.bool.isRequired,
85
80
  };
86
81
 
87
82
  TableSearch.defaultProps = {
@@ -104,6 +99,14 @@ const Container = styled.div`
104
99
  &.toolbar-btn-show {
105
100
  width: 260px;
106
101
  padding-left: 8px;
102
+
103
+ ${props => props.theme.breakpoints.down('md')} {
104
+ width: 200px;
105
+ }
106
+
107
+ ${props => props.theme.breakpoints.down('sm')} {
108
+ width: 180px;
109
+ }
107
110
  &.small-textfield {
108
111
  width: 200px;
109
112
  }
@@ -1,34 +1,105 @@
1
- import React, { useRef } from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import MUIDataTable, { TableFilterList } from 'mui-datatables';
3
+ import MUIDataTable, { TableFilterList, TableFooter } from 'mui-datatables';
4
4
  import styled from 'styled-components';
5
+ import isObject from 'lodash/isObject';
6
+ import cloneDeep from 'lodash/cloneDeep';
5
7
  import CustomToolbar from './CustomToolbar';
8
+ import { DatatableProvide, useDatatableContext } from './DatatableContext';
6
9
 
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
- };
10
+ export default function Datatable({ ...props }) {
11
+ return (
12
+ <DatatableProvide>
13
+ <ReDatatable {...props} />
14
+ </DatatableProvide>
15
+ );
16
+ }
21
17
 
22
- WrapFilterList.propTypes = {
23
- filterList: PropTypes.array,
24
- };
18
+ /**
19
+ * @param {Object} props.options The options of mui-datatable,detail see https://github.com/gregnb/mui-datatables/tree/b8d2eee6af4589d254b40918e5d7e70b1ee4baca
20
+ * @param {Array} props.customButtons Custom buttons for toolbar
21
+ * @param {Function} props.onChange When onChange is present, serverSide mode is activated by default https://github.com/gregnb/mui-datatables/tree/b8d2eee6af4589d254b40918e5d7e70b1ee4baca#remote-data
22
+ * @param {Boolean} props.loading For dynamic data, usually used with onChange
23
+ * @returns
24
+ */
25
+ function ReDatatable({
26
+ data: originData,
27
+ columns: originColumns,
28
+ locale,
29
+ options,
30
+ style,
31
+ customButtons,
32
+ onChange,
33
+ loading,
34
+ disabled,
35
+ ...rest
36
+ }) {
37
+ const container = useRef(null);
38
+ const oldState = useRef(null);
39
+ const { setCustomButtons, setFilterLabel, setLoading, setDisabled } = useDatatableContext();
25
40
 
26
- WrapFilterList.defaultProps = {
27
- filterList: [],
28
- };
41
+ const disabledCellStyle = {
42
+ cursor: 'not-allowed',
43
+ pointerEvents: 'none',
44
+ };
29
45
 
30
- export default function Datatable({ locale, options, style, customButtons, ...rest }) {
31
- const container = useRef(null);
46
+ const keys = [];
47
+
48
+ // Convert Columns fields to object sets to support the width function
49
+ const columns = originColumns.map(e => {
50
+ let tempObj;
51
+
52
+ if (!isObject(e)) {
53
+ tempObj = {
54
+ label: e,
55
+ name: e,
56
+ };
57
+ } else {
58
+ tempObj = cloneDeep(e);
59
+ }
60
+ keys.push(tempObj.name);
61
+
62
+ if (!tempObj.options) {
63
+ tempObj.options = {};
64
+ }
65
+
66
+ const { setCellHeaderProps } = tempObj.options;
67
+ tempObj.options.setCellHeaderProps = columnMeta => {
68
+ let cellProps = {};
69
+
70
+ // Complementing width while inheriting old setCellHeaderProps
71
+ if (setCellHeaderProps && !setCellHeaderProps.__innerFunc) {
72
+ cellProps = setCellHeaderProps(columnMeta) || {};
73
+ }
74
+
75
+ if (loading || disabled) {
76
+ cellProps = { ...cellProps, style: disabledCellStyle };
77
+ }
78
+
79
+ if (tempObj.width) {
80
+ cellProps.width = tempObj.width;
81
+ }
82
+
83
+ return cellProps;
84
+ };
85
+
86
+ // Prevent memory xie caused by recursive forwarding of setCellHeaderProps functions
87
+ tempObj.options.setCellHeaderProps.__innerFunc = 1;
88
+
89
+ return tempObj;
90
+ });
91
+
92
+ // Fixing object-type structures
93
+ const data = originData.map(e => {
94
+ if (!Array.isArray(e) && isObject(e)) {
95
+ return keys.map(key => e[key]);
96
+ }
97
+ return e;
98
+ });
99
+
100
+ useEffect(() => setCustomButtons(customButtons || []), [customButtons]);
101
+ useEffect(() => setLoading(loading), [loading]);
102
+ useEffect(() => setDisabled(disabled), [disabled]);
32
103
 
33
104
  let textLabels = {
34
105
  body: { noMatch: 'Sorry, no matching records found', toolTip: 'Sort' },
@@ -74,42 +145,78 @@ export default function Datatable({ locale, options, style, customButtons, ...re
74
145
  };
75
146
  }
76
147
 
148
+ useEffect(() => setFilterLabel(textLabels.filter.title), [textLabels.filter.title]);
149
+
77
150
  const opts = {
78
151
  selectableRows: 'none',
79
152
  textLabels,
153
+ rowsPerPage: 10,
154
+ rowsPerPageOptions: [10, 20, 50],
80
155
  ...options,
81
156
  };
82
157
 
83
- const WrapCustomToolBar = props => {
84
- return <CustomToolbar {...props} customButtons={customButtons || []} />;
85
- };
158
+ if (onChange) {
159
+ Object.assign(opts, {
160
+ serverSide: true,
161
+ // Wrap the more friendly onChange callback by listening to onTableChange,
162
+ // which will only be triggered when the table key state changes
163
+ onTableChange: (action, tableState) => {
164
+ if (action === 'propsUpdate') {
165
+ return;
166
+ }
167
+ const state = {
168
+ count: tableState.count,
169
+ page: tableState.page,
170
+ rowsPerPage: tableState.rowsPerPage,
171
+ searchText: tableState.searchText,
172
+ sortOrder: tableState.sortOrder, //
173
+ filterList: tableState.filterList,
174
+ };
175
+ const stateStr = JSON.stringify(state);
176
+ if (stateStr === oldState.current) {
177
+ return;
178
+ }
179
+ oldState.current = stateStr;
180
+ onChange(state, action);
181
+ },
182
+ });
183
+ }
86
184
 
87
185
  const props = {
88
186
  options: opts,
89
187
  ...rest,
90
188
  components: {
91
- TableToolbar: WrapCustomToolBar,
189
+ TableToolbar: CustomToolbar,
190
+ TableFooter: WrapTableFooter,
92
191
  TableFilterList: WrapFilterList,
93
192
  },
94
193
  };
95
194
 
96
- Datatable.propTypes = {
195
+ ReDatatable.propTypes = {
196
+ data: PropTypes.array.isRequired,
197
+ columns: PropTypes.array.isRequired,
97
198
  options: PropTypes.object,
98
199
  style: PropTypes.object,
99
200
  locale: PropTypes.string,
201
+ loading: PropTypes.bool,
202
+ disabled: PropTypes.bool,
100
203
  customButtons: PropTypes.array,
204
+ onChange: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
101
205
  };
102
206
 
103
- Datatable.defaultProps = {
207
+ ReDatatable.defaultProps = {
104
208
  options: {},
105
209
  style: {},
106
210
  locale: 'en',
211
+ loading: false,
212
+ disabled: false,
107
213
  customButtons: [],
214
+ onChange: '',
108
215
  };
109
216
 
110
217
  return (
111
218
  <TableContainer ref={container} style={style}>
112
- <MUIDataTable {...props} />
219
+ <MUIDataTable data={data} columns={columns} {...props} />
113
220
  </TableContainer>
114
221
  );
115
222
  }
@@ -143,3 +250,65 @@ const FilterLine = styled.div`
143
250
  font-size: 14px;
144
251
  }
145
252
  `;
253
+
254
+ const WrapFilterList = props => {
255
+ const { filterLabel } = useDatatableContext();
256
+ const hasFilter = !!props.filterList.filter(e => e.length).length;
257
+ if (hasFilter) {
258
+ return (
259
+ <FilterLine>
260
+ {hasFilter && <div className="toolbar-filter-title">{filterLabel}</div>}
261
+ <div className="toolbar-filter-content">
262
+ <TableFilterList {...props} />
263
+ </div>
264
+ </FilterLine>
265
+ );
266
+ }
267
+ return '';
268
+ };
269
+
270
+ WrapFilterList.propTypes = {
271
+ filterList: PropTypes.array,
272
+ };
273
+
274
+ WrapFilterList.defaultProps = {
275
+ filterList: [],
276
+ };
277
+
278
+ const WrapTableFooter = props => {
279
+ const { loading, disabled } = useDatatableContext();
280
+
281
+ return (
282
+ <FooterContainer>
283
+ <div className={`datatable-footer ${loading || disabled ? 'datatable-footer-disabled' : ''}`}>
284
+ <TableFooter {...props} />
285
+ </div>
286
+ </FooterContainer>
287
+ );
288
+ };
289
+
290
+ const FooterContainer = styled.div`
291
+ display: flex;
292
+ align-items: center;
293
+ .datatable-footer {
294
+ position: relative;
295
+ margin-left: auto;
296
+ &.datatable-footer-disabled {
297
+ position: relative;
298
+ .MuiTablePagination-root {
299
+ opacity: 0.6;
300
+ }
301
+ &:after {
302
+ position: absolute;
303
+ display: block;
304
+ z-index: 2;
305
+ width: 100%;
306
+ height: 100%;
307
+ left: 0;
308
+ top: 0;
309
+ content: '';
310
+ cursor: not-allowed;
311
+ }
312
+ }
313
+ }
314
+ `;