@onehat/ui 0.4.35 → 0.4.38

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": "@onehat/ui",
3
- "version": "0.4.35",
3
+ "version": "0.4.38",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -56,6 +56,7 @@ export function ComboComponent(props) {
56
56
  isDisabled = false,
57
57
  isInTag = false,
58
58
  minimizeForRow = false,
59
+ reloadOnTrigger = false,
59
60
  menuHeight,
60
61
  tooltipPlacement = 'bottom',
61
62
  placeholder,
@@ -308,11 +309,14 @@ export function ComboComponent(props) {
308
309
  resetTextInputValue();
309
310
  hideMenu();
310
311
  },
311
- onTriggerPress = (e) => {
312
+ onTriggerPress = async (e) => {
312
313
  if (!isRendered) {
313
314
  return;
314
315
  }
315
316
  clearGridFilters();
317
+ if (reloadOnTrigger && Repository) {
318
+ await Repository.reload();
319
+ }
316
320
  if (isMenuShown) {
317
321
  hideMenu();
318
322
  } else {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * COPYRIGHT NOTICE
3
+ * This file is categorized as "Custom Source Code"
4
+ * and is subject to the terms and conditions defined in the
5
+ * "LICENSE.txt" file, which is part of this source code package.
6
+ */
7
+
8
+ import ArrayCombo from './ArrayCombo.js';
9
+ import {
10
+ METER_TYPES__HOURS,
11
+ METER_TYPES__MILES,
12
+ } from '../../../../constants/MeterTypes.js';
13
+
14
+ const data = [
15
+ [METER_TYPES__HOURS, 'Hours'],
16
+ [METER_TYPES__MILES, 'Miles'],
17
+ ];
18
+ function MeterTypeCombo(props) {
19
+ return <ArrayCombo
20
+ reference="MeterTypeCombo"
21
+ data={data}
22
+ disableDirectEntry={true}
23
+ {...props}
24
+ />;
25
+ }
26
+
27
+ export default MeterTypeCombo;
@@ -72,6 +72,7 @@ function TagComponent(props) {
72
72
  editorType={EDITOR_TYPE__WINDOWED}
73
73
  parent={self}
74
74
  reference="viewer"
75
+ Repository={repository}
75
76
  isEditorViewOnly={true}
76
77
  selection={[record]}
77
78
  onEditorClose={hideModal}
@@ -80,6 +80,7 @@ function Form(props) {
80
80
  checkIsEditingDisabled = true,
81
81
  disableLabels = false,
82
82
  disableDirtyIcon = false,
83
+ alwaysShowCancelButton = false,
83
84
  onBack,
84
85
  onReset,
85
86
  onInit,
@@ -567,7 +568,7 @@ function Form(props) {
567
568
  return buildFromItem(item, ix, {...defaults, ...itemDefaults});
568
569
  });
569
570
 
570
- let elementClassName = 'Form-ElementFromItem';
571
+ let elementClassName = 'Form-ElementFromItem gap-2';
571
572
  const defaultsClassName = defaults.className;
572
573
  if (defaultsClassName) {
573
574
  elementClassName += ' ' + defaultsClassName;
@@ -785,7 +786,7 @@ function Form(props) {
785
786
  if (item.additionalEditButtons) {
786
787
  const buttons = buildAdditionalButtons(item.additionalEditButtons, self, { fieldState, formSetValue, formGetValues, formState });
787
788
  if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
788
- element = <HStack className="Form-HStack5 flex-1 flex-wrap items-center">
789
+ element = <HStack className="Form-HStack5 flex-1 flex-wrap items-center gap-2">
789
790
  {element}
790
791
  {buttons}
791
792
  </HStack>;
@@ -1147,7 +1148,7 @@ function Form(props) {
1147
1148
  </Toolbar>;
1148
1149
  }
1149
1150
  if (getEditorMode() === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
1150
- formButtons.push(<Toolbar key="additionalButtonsToolbar" className="justify-end flex-wrap">
1151
+ formButtons.push(<Toolbar key="additionalButtonsToolbar" className="justify-end flex-wrap gap-2">
1151
1152
  {additionalButtons}
1152
1153
  </Toolbar>)
1153
1154
  }
@@ -1170,27 +1171,31 @@ function Form(props) {
1170
1171
  showResetBtn = true;
1171
1172
  }
1172
1173
  // determine whether we should show the close or cancel button
1173
- if (editorType !== EDITOR_TYPE__SIDE) {
1174
- if (isEditorViewOnly) {
1175
- showCloseBtn = true;
1176
- } else {
1177
- // if (editorType === EDITOR_TYPE__WINDOWED && onCancel) {
1178
- // showCancelBtn = true;
1179
- // }
1180
- if (formState.isDirty || isPhantom) {
1181
- if (isSingle && onCancel) {
1182
- showCancelBtn = true;
1183
- }
1174
+ if (alwaysShowCancelButton) {
1175
+ showCancelBtn = true;
1176
+ } else {
1177
+ if (editorType !== EDITOR_TYPE__SIDE) {
1178
+ if (isEditorViewOnly) {
1179
+ showCloseBtn = true;
1184
1180
  } else {
1185
- if (onClose) {
1186
- showCloseBtn = true;
1181
+ // if (editorType === EDITOR_TYPE__WINDOWED && onCancel) {
1182
+ // showCancelBtn = true;
1183
+ // }
1184
+ if (formState.isDirty || isPhantom) {
1185
+ if (isSingle && onCancel) {
1186
+ showCancelBtn = true;
1187
+ }
1188
+ } else {
1189
+ if (onClose) {
1190
+ showCloseBtn = true;
1191
+ }
1187
1192
  }
1188
1193
  }
1189
- }
1190
- } else {
1191
- // side editor only
1192
- if (isPhantom && isSingle && onCancel) {
1193
- showCancelBtn = true;
1194
+ } else {
1195
+ // side editor only
1196
+ if (isPhantom && isSingle && onCancel) {
1197
+ showCancelBtn = true;
1198
+ }
1194
1199
  }
1195
1200
  }
1196
1201
  if (!isEditorViewOnly && onSave) {
@@ -1267,11 +1272,16 @@ function Form(props) {
1267
1272
  />}
1268
1273
 
1269
1274
  {additionalFooterButtons && _.map(additionalFooterButtons, (props) => {
1275
+ let isDisabled = false;
1276
+ if (props.disableOnInvalid) {
1277
+ isDisabled = !formState.isValid;
1278
+ }
1270
1279
  return <Button
1271
1280
  {...testProps('additionalFooterBtn-' + props.key)}
1272
1281
  {...props}
1273
1282
  onPress={(e) => handleSubmit(props.onPress, onSubmitError)(e)}
1274
1283
  text={props.text}
1284
+ isDisabled={isDisabled}
1275
1285
  />;
1276
1286
  })}
1277
1287
  </>;
@@ -763,8 +763,18 @@ function GridComponent(props) {
763
763
  const
764
764
  headerHeight = showHeaders ? 50 : 0,
765
765
  footerHeight = !disablePagination ? 50 : 0,
766
- height = containerHeight - headerHeight - footerHeight,
767
- rowsPerContainer = Math.floor(height / defaultRowHeight);
766
+ height = containerHeight - headerHeight - footerHeight;
767
+
768
+ const rowsPerContainer = Math.floor(height / defaultRowHeight);
769
+
770
+ // // Get the total height of all rows
771
+ // const rows = gridRef.current._listRef._scrollRef.childNodes[0].childNodes;
772
+ // let totalRowHeight = 0;
773
+ // rows.forEach((row) => {
774
+ // totalRowHeight += row.getBoundingClientRect().height;
775
+ // });
776
+ // const rowsPerContainer = Math.floor(height / (totalRowHeight / rows.length));
777
+
768
778
  let pageSize = rowsPerContainer;
769
779
  if (showHeaders) {
770
780
  pageSize--;
@@ -83,13 +83,17 @@ function GridRow(props) {
83
83
  if (config.isHidden) {
84
84
  return null;
85
85
  }
86
- const propsToPass = columnProps[key] || {};
87
- const colStyle = {};
86
+ const
87
+ propsToPass = columnProps[key] || {},
88
+ colStyle = {};
88
89
  let colClassName = `
89
90
  GridRow-column
90
91
  p-1
91
92
  justify-center
92
93
  border-r-black-100
94
+ block
95
+ max-h-[40px]
96
+ overflow-scroll
93
97
  `;
94
98
  if (isOnlyOneVisibleColumn) {
95
99
  colClassName = ' w-full';
@@ -152,9 +156,10 @@ function GridRow(props) {
152
156
  if (config.fieldName) {
153
157
 
154
158
  if (item?.properties && item.properties[config.fieldName]) {
155
- const property = item.properties[config.fieldName];
159
+ const
160
+ property = item.properties[config.fieldName],
161
+ type = property?.viewerType?.type;
156
162
  value = property.displayValue;
157
- const type = property?.viewerType?.type;
158
163
 
159
164
  if (type) {
160
165
  const Element = getComponentFromType(type);
@@ -165,6 +170,24 @@ function GridRow(props) {
165
170
  if (config.getCellProps) {
166
171
  _.assign(elementProps, config.getCellProps(item));
167
172
  }
173
+ let elementClassName = `
174
+ GridRow-Element
175
+ self-center
176
+ text-ellipsis
177
+ px-2
178
+ py-3
179
+ block
180
+ max-h-[40px]
181
+ overflow-scroll
182
+ ${colClassName}
183
+ ${styles.GRID_CELL_CLASSNAME}
184
+ `;
185
+ if (config.className) {
186
+ elementClassName += ' ' + config.className;
187
+ }
188
+ if (type.match(/(Tag|TagEditor)$/)) {
189
+ elementClassName += ' max-h-[80px]';
190
+ }
168
191
  return <Element
169
192
  {...testProps('cell-' + config.fieldName)}
170
193
  value={value}
@@ -175,15 +198,7 @@ function GridRow(props) {
175
198
  ...colStyle,
176
199
  }}
177
200
  minimizeForRow={true}
178
- className={`
179
- GridRow-Element
180
- self-center
181
- text-ellipsis
182
- px-2
183
- py-3
184
- ${colClassName}
185
- ${styles.GRID_CELL_CLASSNAME}
186
- `}
201
+ className={elementClassName}
187
202
  numberOfLines={1}
188
203
  ellipsizeMode="head"
189
204
  {...propsToPass}
@@ -213,6 +228,22 @@ function GridRow(props) {
213
228
  if (config.getCellProps) {
214
229
  _.assign(elementProps, config.getCellProps(item));
215
230
  }
231
+ let textClassName = `
232
+ GridRow-TextNative
233
+ self-center
234
+ overflow-hidden
235
+ text-ellipsis
236
+ truncate
237
+ whitespace-nowrap
238
+ overflow-hidden
239
+ ${colClassName}
240
+ ${styles.GRID_CELL_CLASSNAME}
241
+ ${styles.GRID_CELL_PX}
242
+ ${styles.GRID_CELL_PY}
243
+ `;
244
+ if (config.className) {
245
+ textClassName += ' ' + config.className;
246
+ }
216
247
  return <TextNative
217
248
  {...testProps('cell-' + config.fieldName)}
218
249
  key={key}
@@ -222,19 +253,7 @@ function GridRow(props) {
222
253
  }}
223
254
  numberOfLines={1}
224
255
  ellipsizeMode="head"
225
- className={`
226
- GridRow-TextNative
227
- self-center
228
- overflow-hidden
229
- text-ellipsis
230
- truncate
231
- whitespace-nowrap
232
- overflow-hidden
233
- ${colClassName}
234
- ${styles.GRID_CELL_CLASSNAME}
235
- ${styles.GRID_CELL_PX}
236
- ${styles.GRID_CELL_PY}
237
- `}
256
+ className={textClassName}
238
257
  {...elementProps}
239
258
  {...propsToPass}
240
259
  >{value}</TextNative>;
@@ -33,6 +33,7 @@ export default function withModal(WrappedComponent) {
33
33
  [okBtnLabel, setOkBtnLabel] = useState(),
34
34
  [onYes, setOnYes] = useState(),
35
35
  [onNo, setOnNo] = useState(),
36
+ [onCancel, setOnCancel] = useState(),
36
37
  [customButtons, setCustomButtons] = useState(),
37
38
  [body, setBody] = useState(),
38
39
  [whichModal, setWhichModal] = useState(),
@@ -40,9 +41,6 @@ export default function withModal(WrappedComponent) {
40
41
  autoFocusRef = useRef(null),
41
42
  cancelRef = useRef(null),
42
43
  [windowWidth, windowHeight] = useAdjustedWindowSize(w, h),
43
- onCancel = () => {
44
- hideModal();
45
- },
46
44
  hideModal = () => {
47
45
  setIsModalShown(false);
48
46
  },
@@ -52,6 +50,7 @@ export default function withModal(WrappedComponent) {
52
50
  body = null,
53
51
  canClose = false,
54
52
  includeCancel = false,
53
+ onCancel = null,
55
54
  onOk = null,
56
55
  okBtnLabel = null,
57
56
  onYes = null,
@@ -76,6 +75,7 @@ export default function withModal(WrappedComponent) {
76
75
  setBody(body);
77
76
  setCanClose(canClose);
78
77
  setIncludeCancel(includeCancel);
78
+ setOnCancel(() => onCancel);
79
79
  setOnOk(onOk ? () => onOk : null);
80
80
  setOkBtnLabel(okBtnLabel || 'OK');
81
81
  setOnYes(onYes ? () => onYes : null);
@@ -101,7 +101,7 @@ export default function withModal(WrappedComponent) {
101
101
  buttons.push(<Button
102
102
  {...testProps('cancelBtn')}
103
103
  key="cancelBtn"
104
- onPress={onCancel}
104
+ onPress={onCancel || hideModal}
105
105
  colorScheme="coolGray"
106
106
  ref={cancelRef}
107
107
  className="mr-2"
@@ -180,7 +180,7 @@ export default function withModal(WrappedComponent) {
180
180
  {...props}
181
181
  disableWithModal={false}
182
182
  showModal={showModal}
183
- hideModal={onCancel}
183
+ hideModal={onCancel || hideModal}
184
184
  updateModalBody={updateModalBody}
185
185
  isModalShown={isModalShown}
186
186
  whichModal={whichModal}
@@ -33,12 +33,9 @@ function Report(props) {
33
33
  title,
34
34
  description,
35
35
  reportId,
36
- // icon,
37
36
  disablePdf = false,
38
37
  disableExcel = false,
39
- includePresets = false,
40
38
  showReportHeaders = true,
41
- h = '300px',
42
39
  } = props,
43
40
  buttons = [];
44
41
 
@@ -68,6 +65,7 @@ function Report(props) {
68
65
  reportType: REPORT_TYPES__EXCEL,
69
66
  showReportHeaders,
70
67
  }),
68
+ disabledOnInvalid: true,
71
69
  });
72
70
  }
73
71
  if (!disablePdf) {
@@ -82,6 +80,7 @@ function Report(props) {
82
80
  reportType: REPORT_TYPES__PDF,
83
81
  showReportHeaders,
84
82
  }),
83
+ disableOnInvalid: true,
85
84
  });
86
85
  }
87
86
  return <VStackNative
@@ -0,0 +1,23 @@
1
+ import {
2
+ Text,
3
+ } from '@project-components/Gluestack';
4
+ import UiGlobals from '../../UiGlobals';
5
+
6
+ export default function MeterTypeText(props) {
7
+ const styles = UiGlobals.styles;
8
+
9
+ let className = `
10
+ Text
11
+ flex-1
12
+ px-3
13
+ py-2
14
+ ${styles.FORM_TEXT_CLASSNAME}
15
+ `;
16
+ if (props.className) {
17
+ className += ' ' + props.className;
18
+ }
19
+ return <Text
20
+ {...props}
21
+ className={className}
22
+ >{props.value ? 'Hours' : 'Miles'}</Text>;
23
+ };
@@ -0,0 +1,98 @@
1
+ import {
2
+ Linking,
3
+ } from 'react-native';
4
+ import {
5
+ BoxNative,
6
+ Text,
7
+ TextNative,
8
+ } from '@project-components/Gluestack';
9
+ import {
10
+ UI_MODE_WEB,
11
+ } from '../../Constants/UiModes.js';
12
+ import UiGlobals from '../../UiGlobals.js';
13
+ import withComponent from '../Hoc/withComponent.js';
14
+ import _ from 'lodash';
15
+
16
+ function TextWithLinksElement(props) {
17
+ const {
18
+ value: text,
19
+ } = props,
20
+ styles = UiGlobals.styles,
21
+ openLink = (url) => {
22
+ Linking.openURL(url);
23
+ },
24
+ extractLinks = (text) => {
25
+
26
+ if (_.isNil(text) || _.isEmpty(text)) {
27
+ return [];
28
+ }
29
+ const
30
+ regex = /\b(?:https?|ftp):\/\/\S+/g,
31
+ links = text.match(regex) || [];
32
+
33
+ return links.map((link, ix) => ({
34
+ link,
35
+ key: `link_${ix}`,
36
+ }));
37
+ },
38
+ renderTextWithLinks = () => {
39
+ const links = extractLinks(text);
40
+ let modifiedText = text;
41
+
42
+ if (_.isNil(modifiedText) || _.isEmpty(modifiedText)) {
43
+ return null;
44
+ }
45
+
46
+ links.forEach(({ link, key }) => {
47
+ modifiedText = modifiedText.replace(link, key);
48
+ });
49
+
50
+ const
51
+ textClassName = `
52
+ TextWithLinks-Text
53
+ text-base
54
+ overflow-hidden
55
+ `,
56
+ textSegments = modifiedText.split(/(link_\d+)/);
57
+ if (textSegments.length === 1) {
58
+ return <Text className={textClassName}>{modifiedText}</Text>;
59
+ }
60
+
61
+ return textSegments.map((segment, ix) => {
62
+ const foundLink = links.find(({ key }) => segment === key);
63
+ let ret = <Text key={ix} className={textClassName}>{segment}</Text>;
64
+
65
+ if (foundLink) {
66
+ ret = <TextNative
67
+ key={foundLink.key}
68
+ className={`
69
+ text-blue-600
70
+ ${textClassName}
71
+ `}
72
+ onPress={() => openLink(foundLink.link)}
73
+ >{foundLink.link}</TextNative>;
74
+ }
75
+ return ret;
76
+ });
77
+ };
78
+
79
+ const elementProps = {};
80
+ if (UiGlobals.mode === UI_MODE_WEB) {
81
+ elementProps.textOverflow = 'ellipsis';
82
+ }
83
+ let className = `
84
+ overflow-auto
85
+ min-h-[40px]
86
+ px-3
87
+ py-2
88
+ `;
89
+ if (props.className) {
90
+ className += ` ${props.className}`;
91
+ }
92
+ return <BoxNative
93
+ className={className}
94
+ {...props}
95
+ >{renderTextWithLinks()}</BoxNative>;
96
+ };
97
+
98
+ export default withComponent(TextWithLinksElement);
@@ -234,7 +234,12 @@ function Viewer(props) {
234
234
  }
235
235
  }
236
236
 
237
- let elementClassName = 'Viewer-field-' + name;
237
+ let elementClassName = `
238
+ Viewer-field
239
+ basis-auto
240
+ grow
241
+ shrink
242
+ `;
238
243
  const defaultsClassName = defaults.className;
239
244
  if (defaultsClassName) {
240
245
  elementClassName += ' ' + defaultsClassName;
@@ -218,6 +218,8 @@ import Input from './Form/Field/Input.js';
218
218
  import IntervalsCombo from './Form/Field/Combo/IntervalsCombo.js';
219
219
  import Json from './Form/Field/Json.js';
220
220
  import Label from './Form/Label.js';
221
+ import MeterTypeCombo from './Form/Field/Combo/MeterTypeCombo.js';
222
+ import MeterTypeText from './Viewer/MeterTypeText.js';
221
223
  import MonthsCombo from './Form/Field/Combo/MonthsCombo.js';
222
224
  import Number from './Form/Field/Number.js';
223
225
  import NumberRange from './Filter/NumberRange.js';
@@ -231,6 +233,7 @@ import TabPanel from './Panel/TabPanel.js';
231
233
  import Tag from './Form/Field/Tag/Tag.js';
232
234
  import TextArea from './Form/Field/TextArea.js';
233
235
  import Text from './Form/Field/Text.js';
236
+ import TextWithLinks from './Viewer/TextWithLinks.js';
234
237
  import TimezonesCombo from './Form/Field/Combo/TimezonesCombo.js';
235
238
  import Toggle from './Form/Field/Toggle.js';
236
239
  import Toolbar from './Toolbar/Toolbar.js';
@@ -458,6 +461,8 @@ const components = {
458
461
  IntervalsCombo,
459
462
  Json,
460
463
  Label,
464
+ MeterTypeCombo,
465
+ MeterTypeText,
461
466
  MonthsCombo,
462
467
  Number,
463
468
  NumberRange,
@@ -471,6 +476,7 @@ const components = {
471
476
  Tag,
472
477
  Text,
473
478
  TextArea,
479
+ TextWithLinks,
474
480
  TimezonesCombo,
475
481
  Toggle,
476
482
  Toolbar,
@@ -0,0 +1,2 @@
1
+ export const METER_TYPES__HOURS = 1;
2
+ export const METER_TYPES__MILES = 2;
@@ -21,7 +21,6 @@ export default function buildAdditionalButtons(configs, self, handlerArgs = {})
21
21
  text,
22
22
  icon,
23
23
  isDisabled,
24
- className: 'ml-2',
25
24
  tooltip,
26
25
  color,
27
26
  };
@@ -45,17 +45,16 @@ function FileCardCustom(props) {
45
45
  uploadStatus,
46
46
  } = props,
47
47
  isDownloading = uploadStatus && inArray(uploadStatus, ['preparing', 'uploading', 'success']);
48
- return (
49
- <Pressable
50
- onPress={() => {
51
- downloadInBackground(downloadUrl);
52
- }}
53
- className="px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700">
54
- {isDownloading && <Spinner className="mr-2" />}
55
- <Text>{filename}</Text>
56
- {onDelete && <IconButton ml={1} icon={Xmark} onPress={() => onDelete(id)} />}
57
- </Pressable>
58
- );
48
+ return <Pressable
49
+ onPress={() => {
50
+ downloadInBackground(downloadUrl);
51
+ }}
52
+ className="px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700"
53
+ >
54
+ {isDownloading && <Spinner className="mr-2" />}
55
+ <Text>{filename}</Text>
56
+ {onDelete && <IconButton ml={1} icon={Xmark} onPress={() => onDelete(id)} />}
57
+ </Pressable>;
59
58
  }
60
59
 
61
60
 
@@ -183,8 +182,9 @@ function AttachmentsElement(props) {
183
182
  }
184
183
  },
185
184
  onFileDelete = (id) => {
185
+ const file = _.find(files, { id });
186
186
  if (confirmBeforeDelete) {
187
- confirm('Are you sure you want to delete the file?', () => doDelete(id));
187
+ confirm('Are you sure you want to delete the file "' + file.name + '"?', () => doDelete(id));
188
188
  } else {
189
189
  doDelete(id);
190
190
  }
@@ -282,12 +282,19 @@ function AttachmentsElement(props) {
282
282
  if (canCrud) {
283
283
  _fileMosaic.onDelete = onFileDelete;
284
284
  }
285
- let className = 'w-full p-1 bg-white rounded-[5px]';
285
+ let className = `
286
+ AttachmentsElement
287
+ w-full
288
+ h-full
289
+ p-1
290
+ rounded-[5px]
291
+ `;
286
292
  if (props.className) {
287
293
  className += ' ' + props.className;
288
294
  }
289
295
  let content = <VStack className={className}>
290
- <HStack className="flex-wrap">
296
+ <HStack className="AttachmentsElement-HStack flex-wrap">
297
+ {files.length === 0 && <Text className="text-grey-600 italic">No files</Text>}
291
298
  {files.map((file) => {
292
299
  return <Box
293
300
  key={file.id}
@@ -312,7 +319,7 @@ function AttachmentsElement(props) {
312
319
  {Repository.total <= collapsedMax ? null :
313
320
  <Button
314
321
  onPress={toggleShowAll}
315
- className="mt-2"
322
+ className="AttachmentsElement-toggleShowAll mt-2"
316
323
  text={'Show ' + (showAll ? ' Less' : ' All ' + Repository.total)}
317
324
  _text={{
318
325
  className: `