@onehat/ui 0.3.58 → 0.3.59

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.3.58",
3
+ "version": "0.3.59",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -20,6 +20,7 @@ import emptyFn from '../../../../Functions/emptyFn.js';
20
20
  import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
21
21
  import IconButton from '../../../Buttons/IconButton.js';
22
22
  import CaretDown from '../../../Icons/CaretDown.js';
23
+ import Xmark from '../../../Icons/Xmark.js';
23
24
  import _ from 'lodash';
24
25
 
25
26
  export function ComboComponent(props) {
@@ -31,6 +32,7 @@ export function ComboComponent(props) {
31
32
  menuMinWidth = 150,
32
33
  disableDirectEntry = false,
33
34
  hideMenuOnSelection = true,
35
+ showXButton = false,
34
36
  _input = {},
35
37
  isEditor = false,
36
38
  isDisabled = false,
@@ -231,6 +233,10 @@ export function ComboComponent(props) {
231
233
  resetInputTextValue();
232
234
  hideMenu();
233
235
  },
236
+ onClearBtn = () => {
237
+ setTextInputValue('');
238
+ setValue(null);
239
+ }
234
240
  isEventStillInComponent = (e) => {
235
241
  const {
236
242
  relatedTarget
@@ -276,7 +282,7 @@ export function ComboComponent(props) {
276
282
  setSavedSearch(null);
277
283
 
278
284
  } else {
279
- throw Error('Not yet implemented');
285
+ // throw Error('Not yet implemented');
280
286
  }
281
287
  },
282
288
  searchForMatches = async (value) => {
@@ -329,7 +335,8 @@ export function ComboComponent(props) {
329
335
  setNewEntityDisplayValue(value); // capture the search query so we can tell Grid what to use for a new entity's displayValue
330
336
 
331
337
  } else {
332
- throw Error('Not yet implemented');
338
+
339
+ throw Error('Not yet implemented'); // NOTE: When implementing this, also implement clearGridFilters
333
340
 
334
341
  // Search through data
335
342
  found = _.find(data, (item) => {
@@ -390,7 +397,7 @@ export function ComboComponent(props) {
390
397
  }
391
398
  displayValue = entity?.displayValue || '';
392
399
  } else {
393
- const item = _.find(data, (datum) => datum[idIx] === id);
400
+ const item = _.find(data, (datum) => datum[idIx] === value);
394
401
  displayValue = (item && item[displayIx]) || '';
395
402
  }
396
403
  }
@@ -424,6 +431,10 @@ export function ComboComponent(props) {
424
431
  return null;
425
432
  }
426
433
 
434
+ if (self) {
435
+ self.clear = onClearBtn;
436
+ }
437
+
427
438
  const refProps = {};
428
439
  if (tooltipRef) {
429
440
  refProps.ref = tooltipRef;
@@ -446,6 +457,21 @@ export function ComboComponent(props) {
446
457
  const WhichGrid = isEditor ? WindowedGridEditor : Grid;
447
458
 
448
459
  let comboComponent = <Row {...refProps} justifyContent="center" alignItems="center" h={styles.FORM_COMBO_HEIGHT} flex={1} onLayout={() => setIsRendered(true)}>
460
+ {showXButton && !_.isNil(value) &&
461
+ <IconButton
462
+ _icon={{
463
+ as: Xmark,
464
+ color: 'trueGray.600',
465
+ size: 'sm',
466
+ }}
467
+ isDisabled={isDisabled}
468
+ onPress={onClearBtn}
469
+ h="100%"
470
+ bg={styles.FORM_COMBO_TRIGGER_BG}
471
+ _hover={{
472
+ bg: styles.FORM_COMBO_TRIGGER_HOVER_BG,
473
+ }}
474
+ />}
449
475
  {disableDirectEntry ?
450
476
  <Pressable
451
477
  onPress={toggleMenu}
@@ -590,29 +616,53 @@ export function ComboComponent(props) {
590
616
  newEntityDisplayValue={newEntityDisplayValue}
591
617
  disablePresetButtons={!isEditor}
592
618
  onChangeSelection={(selection) => {
593
- if (selection[0]?.isPhantom) {
619
+
620
+ if (Repository && selection[0]?.isPhantom) {
594
621
  // do nothing
595
622
  return;
596
623
  }
597
624
 
598
625
  setGridSelection(selection);
599
626
 
600
- // When we first open the menu, we try to match the selection to the value, ignore this
601
- if (selection[0]?.displayValue === getDisplayValue()) {
602
- return;
603
- }
627
+ if (Repository) {
604
628
 
605
- // when user selected the record matching the current value, kill search mode
606
- if (selection[0]?.id === value) {
607
- setIsSearchMode(false);
608
- resetInputTextValue();
609
- if (hideMenuOnSelection) {
610
- hideMenu();
629
+ // When we first open the menu, we try to match the selection to the value, ignore this
630
+ if (selection[0]?.displayValue === getDisplayValue()) {
631
+ return;
611
632
  }
612
- return;
613
- }
614
633
 
615
- setValue(selection[0]?.id);
634
+ // when user selected the record matching the current value, kill search mode
635
+ if (selection[0]?.id === value) {
636
+ setIsSearchMode(false);
637
+ resetInputTextValue();
638
+ if (hideMenuOnSelection) {
639
+ hideMenu();
640
+ }
641
+ return;
642
+ }
643
+
644
+ setValue(selection[0] ? selection[0].id : null);
645
+
646
+ } else {
647
+
648
+ // When we first open the menu, we try to match the selection to the value, ignore this
649
+ if (selection[0] && selection[0][displayIx] === getDisplayValue()) {
650
+ return;
651
+ }
652
+
653
+ // when user selected the record matching the current value, kill search mode
654
+ if (selection[0] && selection[0][idIx] === value) {
655
+ setIsSearchMode(false);
656
+ resetInputTextValue();
657
+ if (hideMenuOnSelection) {
658
+ hideMenu();
659
+ }
660
+ return;
661
+ }
662
+
663
+ setValue(selection[0] ? selection[0][idIx] : null);
664
+
665
+ }
616
666
 
617
667
  if (_.isEmpty(selection)) {
618
668
  return;
@@ -1,9 +1,8 @@
1
- import { useState, } from 'react';
1
+ import { useState, useRef, } from 'react';
2
2
  import {
3
3
  Column,
4
4
  Modal,
5
5
  Row,
6
- Text,
7
6
  } from 'native-base';
8
7
  import {
9
8
  EDITOR_TYPE__WINDOWED,
@@ -12,49 +11,11 @@ import withAlert from '../../../Hoc/withAlert.js';
12
11
  import withComponent from '../../../Hoc/withComponent.js';
13
12
  import withData from '../../../Hoc/withData.js';
14
13
  import withValue from '../../../Hoc/withValue.js';
15
- import IconButton from '../../../Buttons/IconButton.js';
16
- import Eye from '../../../Icons/Eye.js';
17
- import Xmark from '../../../Icons/Xmark.js';
14
+ import ValueBox from './ValueBox.js';
18
15
  import Combo, { ComboEditor } from '../Combo/Combo.js';
19
16
  import _ from 'lodash';
20
17
 
21
18
 
22
- function ValueBox(props) {
23
- const {
24
- text,
25
- onView,
26
- onDelete,
27
- } = props;
28
- return <Row
29
- borderWidth={1}
30
- borderColor="trueGray.400"
31
- borderRadius="md"
32
- mr={1}
33
- bg="trueGray.200"
34
- alignItems="center"
35
- >
36
- <IconButton
37
- _icon={{
38
- as: Eye,
39
- color: 'trueGray.600',
40
- size: 'sm',
41
- }}
42
- onPress={onView}
43
- h="100%"
44
- />
45
- <Text color="trueGray.600" mr={onDelete ? 0 : 2}>{text}</Text>
46
- {onDelete &&
47
- <IconButton
48
- _icon={{
49
- as: Xmark,
50
- color: 'trueGray.600',
51
- size: 'sm',
52
- }}
53
- onPress={onDelete}
54
- h="100%"
55
- />}
56
- </Row>;
57
- }
58
19
 
59
20
  function TagComponent(props) {
60
21
 
@@ -67,6 +28,9 @@ function TagComponent(props) {
67
28
  // parent Form
68
29
  onChangeValue,
69
30
 
31
+ // withAlert
32
+ alert,
33
+
70
34
  // withComponent
71
35
  self,
72
36
 
@@ -75,8 +39,15 @@ function TagComponent(props) {
75
39
  setValue,
76
40
  ...propsToPass // break connection between Tag and Combo props
77
41
  } = props,
42
+ ignoreNextComboValueChangeRef = useRef(false),
78
43
  [isViewerShown, setIsViewerShown] = useState(false),
79
44
  [viewerSelection, setViewerSelection] = useState(false),
45
+ getIgnoreNextComboValueChange = () => {
46
+ return ignoreNextComboValueChangeRef.current;
47
+ },
48
+ setIgnoreNextComboValueChange = (bool) => {
49
+ ignoreNextComboValueChangeRef.current = bool;
50
+ },
80
51
  onViewerClose = () => setIsViewerShown(false),
81
52
  onView = async (item, e) => {
82
53
  const
@@ -101,27 +72,63 @@ function TagComponent(props) {
101
72
  setViewerSelection([record]);
102
73
  setIsViewerShown(true);
103
74
  },
104
- onAdd = (item, e) => {
75
+ clearComboValue = () => {
76
+ setIgnoreNextComboValueChange(true); // we're clearing out the value of the underlying Combo, so ignore it when this combo submits the new value change
77
+ self.children.combo.clear();
78
+ },
79
+ onChangeComboValue = (comboValue) => {
80
+ if (getIgnoreNextComboValueChange()) {
81
+ setIgnoreNextComboValueChange(false);
82
+ return;
83
+ }
84
+
105
85
  // make sure value doesn't already exist
106
86
  let exists = false;
107
87
  _.each(value, (val) => {
108
- if (val.id === item.getId()) {
88
+ if (val.id === comboValue) {
109
89
  exists = true;
110
90
  return false; // break
111
91
  }
112
92
  });
113
93
  if (exists) {
94
+ clearComboValue();
114
95
  alert('Value already exists!');
115
96
  return;
116
97
  }
117
98
 
99
+ // The value we get from combo is a simple int
100
+ // Convert this to id and displayValue from either Repository or data array.
101
+ const
102
+ Repository = props.Repository,
103
+ data = props.data,
104
+ idIx = props.idIx,
105
+ displayIx = props.displayIx,
106
+ id = comboValue;
107
+ let item,
108
+ displayValue;
109
+ if (Repository) {
110
+ item = Repository.getById(id);
111
+ if (!item) {
112
+ throw Error('item not found');
113
+ }
114
+ displayValue = item.displayValue;
115
+ } else {
116
+ item = _.find(data, (datum) => datum[idIx] === id);
117
+ if (!item) {
118
+ throw Error('item not found');
119
+ }
120
+ displayValue = item[displayIx];
121
+ }
122
+
123
+
118
124
  // add new value
119
125
  const newValue = _.clone(value); // so we trigger a re-render
120
126
  newValue.push({
121
- id: item.getId(),
122
- text: item.getDisplayValue(),
127
+ id,
128
+ text: displayValue,
123
129
  })
124
130
  setValue(newValue);
131
+ clearComboValue();
125
132
  },
126
133
  onDelete = (val) => {
127
134
  // Remove from value array
@@ -174,7 +181,9 @@ function TagComponent(props) {
174
181
  <WhichCombo
175
182
  Repository={props.Repository}
176
183
  Editor={props.Editor}
177
- onRowPress={onAdd}
184
+ onChangeValue={onChangeComboValue}
185
+ parent={self}
186
+ reference="combo"
178
187
  />}
179
188
  </Column>
180
189
  {isViewerShown &&
@@ -0,0 +1,45 @@
1
+ import {
2
+ Row,
3
+ Text,
4
+ } from 'native-base';
5
+ import IconButton from '../../../Buttons/IconButton.js';
6
+ import Eye from '../../../Icons/Eye.js';
7
+ import Xmark from '../../../Icons/Xmark.js';
8
+ import _ from 'lodash';
9
+
10
+ export default function ValueBox(props) {
11
+ const {
12
+ text,
13
+ onView,
14
+ onDelete,
15
+ } = props;
16
+ return <Row
17
+ borderWidth={1}
18
+ borderColor="trueGray.400"
19
+ borderRadius="md"
20
+ mr={1}
21
+ bg="trueGray.200"
22
+ alignItems="center"
23
+ >
24
+ <IconButton
25
+ _icon={{
26
+ as: Eye,
27
+ color: 'trueGray.600',
28
+ size: 'sm',
29
+ }}
30
+ onPress={onView}
31
+ h="100%"
32
+ />
33
+ <Text color="trueGray.600" mr={onDelete ? 0 : 2}>{text}</Text>
34
+ {onDelete &&
35
+ <IconButton
36
+ _icon={{
37
+ as: Xmark,
38
+ color: 'trueGray.600',
39
+ size: 'sm',
40
+ }}
41
+ onPress={onDelete}
42
+ h="100%"
43
+ />}
44
+ </Row>;
45
+ }
@@ -322,13 +322,17 @@ function Form(props) {
322
322
  type = 'Text';
323
323
  }
324
324
  }
325
+ const isCombo = type?.match && type.match(/Combo/);
325
326
  if (item.hasOwnProperty('autoLoad')) {
326
327
  editorTypeProps.autoLoad = item.autoLoad;
327
328
  } else {
328
- if (type?.match && type.match(/Combo$/) && Repository?.isRemote && !Repository?.isLoaded) {
329
+ if (isCombo && Repository?.isRemote && !Repository?.isLoaded) {
329
330
  editorTypeProps.autoLoad = true;
330
331
  }
331
332
  }
333
+ if (isCombo) {
334
+ editorTypeProps.showXButton = true;
335
+ }
332
336
  const Element = getComponentFromType(type);
333
337
  let children;
334
338
 
@@ -779,7 +783,12 @@ function Form(props) {
779
783
  }
780
784
  reset();
781
785
  }}
782
- icon={<Rotate color="#fff" />}
786
+ icon={Rotate}
787
+ _icon={{
788
+ color: !formState.isDirty ? 'trueGray.400' : '#000',
789
+ }}
790
+ isDisabled={!formState.isDirty}
791
+ mr={2}
783
792
  />}
784
793
 
785
794
  {showCancelBtn &&
@@ -27,6 +27,7 @@ export default function withAlert(WrappedComponent) {
27
27
  [isAlertShown, setIsAlertShown] = useState(false),
28
28
  [title, setTitle] = useState(''),
29
29
  [message, setMessage] = useState(''),
30
+ [canClose, setCanClose] = useState(true),
30
31
  [includeCancel, setIncludeCancel] = useState(false),
31
32
  [okCallback, setOkCallback] = useState(),
32
33
  [yesCallback, setYesCallback] = useState(),
@@ -35,7 +36,7 @@ export default function withAlert(WrappedComponent) {
35
36
  [mode, setMode] = useState(ALERT_MODE_OK),
36
37
  autoFocusRef = useRef(null),
37
38
  cancelRef = useRef(null),
38
- onAlert = (arg1, okCallback, includeCancel = false) => {
39
+ onAlert = (arg1, okCallback, includeCancel = false, canClose = true) => {
39
40
  clearAll();
40
41
  if (_.isString(arg1)) {
41
42
  setMode(ALERT_MODE_OK);
@@ -43,6 +44,7 @@ export default function withAlert(WrappedComponent) {
43
44
  setMessage(arg1);
44
45
  setOkCallback(() => okCallback);
45
46
  setIncludeCancel(includeCancel);
47
+ setCanClose(canClose);
46
48
  } else if (_.isPlainObject(arg1)) {
47
49
  // custom
48
50
  const {
@@ -50,12 +52,14 @@ export default function withAlert(WrappedComponent) {
50
52
  message,
51
53
  buttons,
52
54
  includeCancel,
55
+ canClose,
53
56
  } = arg1;
54
57
  setMode(ALERT_MODE_CUSTOM);
55
58
  setTitle(title);
56
59
  setMessage(message);
57
60
  setCustomButtons(buttons);
58
61
  setIncludeCancel(includeCancel);
62
+ setCanClose(canClose);
59
63
  }
60
64
  showAlert();
61
65
  },
@@ -161,7 +165,7 @@ export default function withAlert(WrappedComponent) {
161
165
  onClose={() => setIsAlertShown(false)}
162
166
  >
163
167
  <AlertDialog.Content>
164
- <AlertDialog.CloseButton />
168
+ {canClose && <AlertDialog.CloseButton />}
165
169
  <AlertDialog.Header>{title}</AlertDialog.Header>
166
170
  <AlertDialog.Body>
167
171
  <Row>
@@ -536,7 +536,9 @@ export default function withFilters(WrappedComponent) {
536
536
  },
537
537
  ]}
538
538
  onCancel={(e) => {
539
- // Just close the modal
539
+ setIsFilterSelectorShown(false);
540
+ }}
541
+ onClose={(e) => {
540
542
  setIsFilterSelectorShown(false);
541
543
  }}
542
544
  onSave={(data, e) => {
@@ -28,6 +28,9 @@ export default function withValue(WrappedComponent) {
28
28
  isValueAlwaysArray = false,
29
29
  isValueAsStringifiedJson = false,
30
30
 
31
+ // withComponent
32
+ self,
33
+
31
34
  // withData
32
35
  Repository,
33
36
  idIx,
@@ -120,6 +123,11 @@ export default function withValue(WrappedComponent) {
120
123
  }, []);
121
124
  }
122
125
 
126
+ if (self) {
127
+ self.setValue = setValue;
128
+ self.value = getLocalValue();
129
+ }
130
+
123
131
 
124
132
  // Convert localValue to normal JS primitives for field components
125
133
  let convertedValue = getLocalValue();
@@ -0,0 +1,14 @@
1
+ // Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.
2
+ import * as React from "react"
3
+ import Svg, { Path } from "react-native-svg"
4
+ import { Icon } from 'native-base';
5
+
6
+ function SvgComponent(props) {
7
+ return (
8
+ <Icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" {...props}>
9
+ <Path d="M512 32H160c-35.35 0-64 28.65-64 64v224c0 35.35 28.65 64 64 64h352c35.35 0 64-28.65 64-64V96c0-35.35-28.7-64-64-64zm16 288c0 8.822-7.178 16-16 16h-16L386.7 175.1c-3-4.4-8-7.1-13.4-7.1a15.978 15.978 0 00-13.31 7.125l-62.74 94.11L274.9 238.6c-3-4.2-7.8-6.6-12.9-6.6a16.007 16.007 0 00-12.93 6.574L176 336h-16c-8.822 0-16-7.178-16-16V96c0-8.822 7.178-16 16-16h352c8.822 0 16 7.178 16 16v224zM224 112c-17.67 0-32 14.33-32 32s14.33 32 32 32c17.68 0 32-14.33 32-32s-14.3-32-32-32zm232 368H120C53.83 480 0 426.2 0 360V120c0-13.2 10.75-24 24-24s24 10.8 24 24v240c0 39.7 32.3 72 72 72h336c13.25 0 24 10.75 24 24s-10.7 24-24 24z" />
10
+ </Icon>
11
+ )
12
+ }
13
+
14
+ export default SvgComponent
@@ -0,0 +1,3 @@
1
+ export default function delay(ms = 1000) {
2
+ return new Promise(resolve => setTimeout(resolve, ms));
3
+ }
@@ -0,0 +1,18 @@
1
+ export default function isVideo(mimetype) {
2
+ switch(mimetype) {
3
+ case 'video/quicktime':
4
+ case 'video/mp4':
5
+ case 'video/mpeg':
6
+ case 'video/ogg':
7
+ case 'video/webm':
8
+ case 'video/mp2t':
9
+ case 'video/3gpp':
10
+ case 'video/3gpp2':
11
+ case 'video/x-msvideo':
12
+ case 'video/x-ms-wmv':
13
+ case 'video/x-flv':
14
+ case 'application/x-mpegURL':
15
+ return true;
16
+ }
17
+ return false;
18
+ }