@onehat/ui 0.3.68 → 0.3.70

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.68",
3
+ "version": "0.3.70",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -24,6 +24,7 @@ const
24
24
  } = props,
25
25
  [low, setLow] = useState(''),
26
26
  [high, setHigh] = useState(''),
27
+ [isReady, setIsReady] = useState(false),
27
28
  onChangeLow = (value) => {
28
29
  setLow(value);
29
30
  const newValue = {
@@ -59,9 +60,16 @@ const
59
60
  if (value.high !== high) {
60
61
  setHigh(value.high);
61
62
  }
63
+ if (!isReady) {
64
+ setIsReady(true);
65
+ }
62
66
 
63
67
  }, [value]);
64
68
 
69
+ if (!isReady) {
70
+ return null;
71
+ }
72
+
65
73
  return <Row
66
74
  justifyContent="center"
67
75
  alignItems="center"
@@ -72,7 +80,6 @@ const
72
80
  value={low}
73
81
  onChangeValue={onChangeLow}
74
82
  mode={mode}
75
- startingValue={null}
76
83
  // minValue={minValue}
77
84
  // maxValue={maxValue}
78
85
  tooltip={(tooltip ? tooltip + ' ' : '') + 'Low'}
@@ -82,7 +89,6 @@ const
82
89
  value={high}
83
90
  onChangeValue={onChangeHigh}
84
91
  mode={mode}
85
- startingValue={null}
86
92
  // minValue={minValue}
87
93
  // maxValue={maxValue}
88
94
  tooltip={(tooltip ? tooltip + ' ' : '') + 'High'}
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useEffect, useRef, } from 'react';
2
2
  import {
3
+ Button,
3
4
  Modal,
4
5
  Popover,
5
6
  Pressable,
@@ -21,6 +22,7 @@ import emptyFn from '../../../../Functions/emptyFn.js';
21
22
  import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
22
23
  import IconButton from '../../../Buttons/IconButton.js';
23
24
  import CaretDown from '../../../Icons/CaretDown.js';
25
+ import Check from '../../../Icons/Check.js';
24
26
  import Xmark from '../../../Icons/Xmark.js';
25
27
  import _ from 'lodash';
26
28
 
@@ -70,6 +72,7 @@ export function ComboComponent(props) {
70
72
  [gridSelection, setGridSelection] = useState(null),
71
73
  [textInputValue, setTextInputValue] = useState(''),
72
74
  [newEntityDisplayValue, setNewEntityDisplayValue] = useState(null),
75
+ [filteredData, setFilteredData] = useState(data),
73
76
  [width, setWidth] = useState(0),
74
77
  [top, setTop] = useState(0),
75
78
  [left, setLeft] = useState(0),
@@ -129,6 +132,7 @@ export function ComboComponent(props) {
129
132
  displayValue = [];
130
133
  if (Repository) {
131
134
  if (!Repository.isLoaded) {
135
+ debugger;
132
136
  throw Error('Not yet implemented'); // Would a Combo ever have multiple remote selections? Shouldn't that be a Tag field??
133
137
  }
134
138
  if (Repository.isLoading) {
@@ -168,9 +172,9 @@ export function ComboComponent(props) {
168
172
  }
169
173
 
170
174
  displayValueRef.current = displayValue;
171
- resetInputTextValue();
175
+ resetTextInputValue();
172
176
  },
173
- resetInputTextValue = () => {
177
+ resetTextInputValue = () => {
174
178
  setTextInputValue(getDisplayValue());
175
179
  },
176
180
  onInputKeyPress = (e, inputValue) => {
@@ -180,7 +184,7 @@ export function ComboComponent(props) {
180
184
  switch(e.key) {
181
185
  case 'Escape':
182
186
  setIsSearchMode(false);
183
- resetInputTextValue();
187
+ resetTextInputValue();
184
188
  hideMenu();
185
189
  break;
186
190
  case 'Enter':
@@ -245,8 +249,8 @@ export function ComboComponent(props) {
245
249
  }, 300);
246
250
  },
247
251
  onInputFocus = (e) => {
248
- if (inputRef.current.select) {
249
- inputRef.current.select();
252
+ if (inputRef.current?.select) {
253
+ inputRef.current?.select();
250
254
  }
251
255
  },
252
256
  onInputBlur = (e) => {
@@ -256,7 +260,7 @@ export function ComboComponent(props) {
256
260
  }
257
261
 
258
262
  setIsSearchMode(false);
259
- resetInputTextValue();
263
+ resetTextInputValue();
260
264
  hideMenu();
261
265
  },
262
266
  onTriggerPress = (e) => {
@@ -277,7 +281,7 @@ export function ComboComponent(props) {
277
281
  }
278
282
 
279
283
  setIsSearchMode(false);
280
- resetInputTextValue();
284
+ resetTextInputValue();
281
285
  hideMenu();
282
286
  },
283
287
  onClearBtn = () => {
@@ -303,7 +307,7 @@ export function ComboComponent(props) {
303
307
  }
304
308
 
305
309
  // clear filter
306
- if (Repository.isRemote) {
310
+ if (Repository.isRemote || Repository.remote) {
307
311
  let searchField = 'q';
308
312
  const searchValue = null;
309
313
 
@@ -316,18 +320,20 @@ export function ComboComponent(props) {
316
320
  searchField = displayFieldName + ' LIKE';
317
321
  }
318
322
 
319
- Repository.clear();
320
- await Repository.filter(searchField, searchValue);
321
- if (!this.isAutoLoad) {
322
- await Repository.reload();
323
+ if (Repository.hasFilter(searchField)) {
324
+ Repository.clear();
325
+ await Repository.filter(searchField, searchValue);
326
+ if (!this.isAutoLoad) {
327
+ await Repository.reload();
328
+ }
323
329
  }
324
330
 
325
331
  } else {
326
- throw Error('Not yet implemented');
332
+ Repository.clear();
327
333
  }
328
334
 
329
335
  } else {
330
- // throw Error('Not yet implemented');
336
+ setFilteredData(data);
331
337
  }
332
338
  },
333
339
  searchForMatches = async (value) => {
@@ -339,21 +345,26 @@ export function ComboComponent(props) {
339
345
 
340
346
  let found;
341
347
  if (Repository) {
348
+
349
+ if (_.isEmpty(value) && Repository.hasFilters) {
350
+ Repository.clearFilters();
351
+ return;
352
+ }
353
+
342
354
  if (Repository.isLoading) {
343
355
  await Repository.waitUntilDoneLoading();
344
356
  }
345
357
 
346
358
  // Set filter
347
- let filter = {};
348
359
  if (Repository.isRemote) {
349
- let searchField = 'q';
350
- const searchValue = _.isEmpty(value) ? null : value + '%';
351
-
352
- // Check to see if displayField is a real field
353
360
  const
354
361
  schema = Repository.getSchema(),
355
362
  displayFieldName = schema.model.displayProperty,
356
- displayFieldDef = schema.getPropertyDefinition(displayFieldName);
363
+ displayFieldDef = schema.getPropertyDefinition(displayFieldName),
364
+ searchValue = _.isEmpty(value) ? null : value + '%';
365
+ let searchField = 'q';
366
+
367
+ // Verify displayField is a real field
357
368
  if (!displayFieldDef.isVirtual) {
358
369
  searchField = displayFieldName + ' LIKE';
359
370
  }
@@ -362,39 +373,28 @@ export function ComboComponent(props) {
362
373
  if (!this.isAutoLoad) {
363
374
  await Repository.reload();
364
375
  }
365
-
366
376
  } else {
367
- throw Error('Not yet implemented');
368
-
369
- // Fuzzy search with getBy filter function
370
- filter = (entity) => {
377
+ // local filter
378
+ Repository.filter((entity) => {
371
379
  const
372
380
  displayValue = entity.displayValue,
373
381
  regex = new RegExp('^' + value);
374
382
  return displayValue.match(regex);
375
- };
376
- Repository.filter(filter);
383
+ });
377
384
  }
378
385
 
379
386
  setNewEntityDisplayValue(value); // capture the search query so we can tell Grid what to use for a new entity's displayValue
380
387
 
381
388
  } else {
382
-
383
- throw Error('Not yet implemented'); // NOTE: When implementing this, also implement clearGridFilters
384
-
385
389
  // Search through data
386
- found = _.find(data, (item) => {
390
+ const regex = new RegExp('^' + value);
391
+ found = _.filter(data, (item) => {
387
392
  if (_.isString(item[displayIx]) && _.isString(value)) {
388
- return item[displayIx].toLowerCase() === value.toLowerCase();
393
+ return item[displayIx].match(regex);
389
394
  }
390
- return item[displayIx] === value;
395
+ return item[displayIx] == value; // equality, not identity
391
396
  });
392
- // if (found) {
393
- // const
394
- // newSelection = [found];
395
-
396
- // setTextInputValue(newTextValue);
397
- // }
397
+ setFilteredData(found);
398
398
  }
399
399
  };
400
400
 
@@ -429,6 +429,7 @@ export function ComboComponent(props) {
429
429
 
430
430
  let xButton = null,
431
431
  inputAndTrigger = null,
432
+ checkBtn = null,
432
433
  grid = null,
433
434
  dropdownMenu = null,
434
435
  assembledComponents = null;
@@ -588,7 +589,7 @@ export function ComboComponent(props) {
588
589
  'Editor',
589
590
  'model',
590
591
  'Repository',
591
- 'data',
592
+ // 'data',
592
593
  'idIx',
593
594
  'displayIx',
594
595
  'value',
@@ -614,6 +615,7 @@ export function ComboComponent(props) {
614
615
  }}
615
616
  autoAdjustPageSizeToHeight={false}
616
617
  {...gridProps}
618
+ data={filteredData}
617
619
  reference="dropdownGrid"
618
620
  parent={self}
619
621
  h={UiGlobals.mode === UI_MODE_WEB ? styles.FORM_COMBO_MENU_HEIGHT + 'px' : null}
@@ -638,7 +640,7 @@ export function ComboComponent(props) {
638
640
  // when user selected the record matching the current value, kill search mode
639
641
  if (selection[0]?.id === value) {
640
642
  setIsSearchMode(false);
641
- resetInputTextValue();
643
+ resetTextInputValue();
642
644
  if (hideMenuOnSelection) {
643
645
  hideMenu();
644
646
  }
@@ -657,7 +659,7 @@ export function ComboComponent(props) {
657
659
  // when user selected the record matching the current value, kill search mode
658
660
  if (selection[0] && selection[0][idIx] === value) {
659
661
  setIsSearchMode(false);
660
- resetInputTextValue();
662
+ resetTextInputValue();
661
663
  if (hideMenuOnSelection) {
662
664
  hideMenu();
663
665
  }
@@ -735,6 +737,27 @@ export function ComboComponent(props) {
735
737
  </Popover>;
736
738
  }
737
739
  if (UiGlobals.mode === UI_MODE_REACT_NATIVE) {
740
+ if (isEditor) {
741
+ // in RN, an editor has no way to accept the selection of the grid, so we need to add a check button to do this
742
+ const isCheckBtnDisabled = _.isEmpty(value);
743
+ checkBtn = <IconButton
744
+ _icon={{
745
+ as: Check,
746
+ color: isCheckBtnDisabled ? 'disabled' : 'trueGray.600',
747
+ size: 'sm',
748
+ }}
749
+ isDisabled={isCheckBtnDisabled}
750
+ onPress={acceptSelection}
751
+ h="100%"
752
+ borderWidth={1}
753
+ borderColor="#bbb"
754
+ borderRadius="md"
755
+ bg={styles.FORM_COMBO_TRIGGER_BG}
756
+ _hover={{
757
+ bg: styles.FORM_COMBO_TRIGGER_HOVER_BG,
758
+ }}
759
+ />;
760
+ }
738
761
  const inputAndTriggerClone = // for RN, this is the actual input and trigger, as we need them to appear up above in the modal
739
762
  <Row h={10}>
740
763
  {disableDirectEntry ?
@@ -798,6 +821,7 @@ export function ComboComponent(props) {
798
821
  bg: styles.FORM_COMBO_TRIGGER_HOVER_BG,
799
822
  }}
800
823
  />
824
+ {checkBtn}
801
825
  </Row>;
802
826
  dropdownMenu = <Modal
803
827
  isOpen={true}
@@ -811,6 +835,7 @@ export function ComboComponent(props) {
811
835
  >
812
836
  {inputAndTriggerClone}
813
837
  {grid}
838
+ <Button mt={2} onPress={() => setIsMenuShown(false)}>Close</Button>
814
839
  </Modal>;
815
840
  }
816
841
  }
@@ -1,6 +1,8 @@
1
1
  import React, { useState, useEffect, useRef, } from 'react';
2
2
  import {
3
+ Box,
3
4
  Icon,
5
+ Modal,
4
6
  Popover,
5
7
  Pressable,
6
8
  Row,
@@ -13,6 +15,7 @@ import {
13
15
  DATETIME,
14
16
  } from '../../../Constants/Date.js';
15
17
  import {
18
+ UI_MODE_REACT_NATIVE,
16
19
  UI_MODE_WEB,
17
20
  } from '../../../Constants/UiModes.js';
18
21
  import UiGlobals from '../../../UiGlobals.js';
@@ -20,24 +23,36 @@ import Formatters from '@onehat/data/src/Util/Formatters.js';
20
23
  import Parsers from '@onehat/data/src/Util/Parsers.js';
21
24
  import Input from '../Field/Input.js';
22
25
  import IconButton from '../../Buttons/IconButton.js';
26
+ import Xmark from '../../Icons/Xmark.js';
23
27
  import withComponent from '../../Hoc/withComponent.js';
24
28
  import withValue from '../../Hoc/withValue.js';
25
29
  import emptyFn from '../../../Functions/emptyFn.js';
26
30
  import Calendar from '../../Icons/Calendar.js';
27
31
  import getComponentFromType from '../../../Functions/getComponentFromType.js';
32
+ import moment from 'moment';
28
33
  import _ from 'lodash';
29
34
 
30
35
 
31
36
  export function DateElement(props) {
32
37
  const {
33
- placeholderText,
34
- value,
35
- setValue,
36
38
  format,
37
39
  mode = DATE,
38
- tooltip = 'Choose a date.',
39
- tooltipPlacement = 'bottom',
40
+
41
+ additionalButtons,
42
+ tooltipRef = null,
43
+ tooltip = null,
44
+ menuMinWidth = 150,
45
+ disableDirectEntry = false,
46
+ hideMenuOnSelection = true,
47
+ showXButton = false,
48
+ _input = {},
40
49
  isDisabled = false,
50
+ tooltipPlacement = 'bottom',
51
+ placeholder = 'Choose a date.',
52
+
53
+ // withValue
54
+ value,
55
+ setValue,
41
56
  } = props,
42
57
  styles = UiGlobals.styles,
43
58
  Datetime = getComponentFromType('Datetime'),
@@ -46,6 +61,7 @@ export function DateElement(props) {
46
61
  pickerRef = useRef(),
47
62
  [isPickerShown, setIsPickerShown] = useState(false),
48
63
  [isRendered, setIsRendered] = useState(false),
64
+ [textInputValue, setTextInputValue] = useState(value),
49
65
  [isTranslateX, setIsTranslateX] = useState(false),
50
66
  [isTranslateY, setIsTranslateY] = useState(false),
51
67
  [top, setTop] = useState(0),
@@ -83,7 +99,16 @@ export function DateElement(props) {
83
99
  }
84
100
  setIsPickerShown(false);
85
101
  },
86
- onInputKeyPress = (e) => {
102
+ togglePicker = () => {
103
+ setIsPickerShown(!isPickerShown);
104
+ },
105
+ onInputKeyPress = (e, inputValue) => {
106
+ if (disableDirectEntry) {
107
+ return;
108
+ }
109
+ if (UiGlobals.mode !== UI_MODE_WEB) {
110
+ return;
111
+ }
87
112
  switch(e.key) {
88
113
  case 'Escape':
89
114
  case 'Enter':
@@ -93,13 +118,16 @@ export function DateElement(props) {
93
118
  }
94
119
  },
95
120
  onInputBlur = (e) => {
96
- const {
97
- relatedTarget
98
- } = e;
99
- if (!relatedTarget ||
100
- (!inputRef.current.contains(relatedTarget) && triggerRef.current !== relatedTarget && (!pickerRef.current || !pickerRef.current.contains(relatedTarget)))) {
101
- // hidePicker();
102
- }
121
+ // if (UiGlobals.mode !== UI_MODE_WEB) {
122
+ // return;
123
+ // }
124
+ // const {
125
+ // relatedTarget
126
+ // } = e;
127
+ // if (!relatedTarget ||
128
+ // (!inputRef.current.contains(relatedTarget) && triggerRef.current !== relatedTarget && (!pickerRef.current || !pickerRef.current.contains(relatedTarget)))) {
129
+ // // hidePicker();
130
+ // }
103
131
  },
104
132
  onInputClick = (e) => {
105
133
  if (!isRendered) {
@@ -107,9 +135,13 @@ export function DateElement(props) {
107
135
  }
108
136
  showPicker();
109
137
  },
110
- onInputSetValue = (value) => {
111
- if (value === '') {
138
+ onInputChangeText = (value) => {
139
+ if (disableDirectEntry) {
140
+ return;
141
+ }
142
+ if (_.isEmpty(value)) {
112
143
  setValue(null);
144
+ setTextInputValue('');
113
145
  return;
114
146
  }
115
147
  switch(mode) {
@@ -133,7 +165,20 @@ export function DateElement(props) {
133
165
  break;
134
166
  default:
135
167
  }
136
- setValue(value);
168
+
169
+ if (value !== 'Invalid date') {
170
+ setValue(value);
171
+ }
172
+
173
+ setTextInputValue(value);
174
+ if (!isPickerShown) {
175
+ showPicker();
176
+ }
177
+ },
178
+ onInputFocus = (e) => {
179
+ if (inputRef.current?.select) {
180
+ inputRef.current?.select();
181
+ }
137
182
  },
138
183
  onTriggerPress = (e) => {
139
184
  if (!isRendered) {
@@ -144,10 +189,10 @@ export function DateElement(props) {
144
189
  } else {
145
190
  showPicker();
146
191
  }
147
- inputRef.current.focus();
192
+ inputRef.current?.focus();
148
193
  },
149
194
  onTriggerBlur = (e) => {
150
- if (!isPickerShown) {
195
+ if (!isPickerShown || UiGlobals.mode !== UI_MODE_WEB) {
151
196
  return;
152
197
  }
153
198
  const {
@@ -172,28 +217,15 @@ export function DateElement(props) {
172
217
  break;
173
218
  default:
174
219
  }
175
- setValue(value);
176
- };
177
220
 
178
-
179
- // place the picker in a convenient spot
180
- const
181
- translateParts = [],
182
- translateProps = {};
183
- if (isTranslateX) {
184
- translateParts.push('translateX(-100%)');
185
- }
186
- if (isTranslateY) {
187
- translateParts.push('translateY(-100%)');
188
- }
189
- if (!_.isEmpty(translateParts)) {
190
- translateProps.style = {
191
- transform: translateParts.join(' '),
221
+ if (moment && moment?.isValid()) {
222
+ setValue(value);
223
+ setTextInputValue(value);
224
+ }
192
225
  };
193
- }
194
-
226
+
195
227
  // Format the display date/time/datetime
196
- let title = placeholderText,
228
+ let title = placeholder,
197
229
  pickerValue = null,
198
230
  height = 300,
199
231
  width = 300;
@@ -226,147 +258,348 @@ export function DateElement(props) {
226
258
  pickerValue = pickerValue.toDate();
227
259
  }
228
260
 
229
- // Web version
230
- return <Tooltip label={tooltip} placement={tooltipPlacement}>
231
- <Row flex={1} h="100%" alignItems="center" onLayout={() => setIsRendered(true)}>
232
- <IconButton
233
- ref={triggerRef}
234
- icon={<Icon as={Calendar} color={styles.FORM_DATE_ICON_COLOR} />}
235
- onPress={onTriggerPress}
236
- onBlur={onTriggerBlur}
237
- h={10}
238
- w={10}
261
+ let xButton = null,
262
+ inputAndTrigger = null,
263
+ grid = null,
264
+ dropdownMenu = null,
265
+ assembledComponents = null;
266
+
267
+ if (showXButton && !_.isNil(value)) {
268
+ xButton = <IconButton
269
+ _icon={{
270
+ as: Xmark,
271
+ color: 'trueGray.600',
272
+ size: 'sm',
273
+ }}
239
274
  isDisabled={isDisabled}
240
- borderTopLeftRadius={6}
241
- borderBottomLeftRadius={6}
242
- borderTopRightRadius={0}
243
- borderBottomRightRadius={0}
244
- bg={styles.FORM_DATE_ICON_BG}
275
+ onPress={onClearBtn}
276
+ h="100%"
277
+ bg={styles.FORM_COMBO_TRIGGER_BG}
245
278
  _hover={{
246
- bg: styles.FORM_DATE_ICON_BG_HOVER,
279
+ bg: styles.FORM_COMBO_TRIGGER_HOVER_BG,
247
280
  }}
248
- />
249
- <Input
250
- ref={inputRef}
251
- value={title}
252
- setValue={onInputSetValue}
253
- onKeyPress={onInputKeyPress}
254
- onBlur={onInputBlur}
255
- onClick={onInputClick}
256
- flex={1}
257
- h="100%"
258
- p={2}
259
- borderWidth={1}
260
- borderColor="trueGray.300"
261
- borderLeftWidth={0}
262
- borderTopLeftRadius={0}
263
- borderBottomLeftRadius={0}
264
- borderTopRightRadius={6}
265
- borderBottomRightRadius={6}
266
- fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
267
- bg={styles.FORM_DATE_INPUT_BG}
268
- _focus={{
269
- bg: styles.FORM_DATE_INPUT_FOCUS_BG,
281
+ />;
282
+ }
283
+
284
+ if (UiGlobals.mode === UI_MODE_WEB) {
285
+ inputAndTrigger = <>
286
+ <IconButton
287
+ ref={triggerRef}
288
+ _icon={{
289
+ as: Calendar,
290
+ color: styles.FORM_DATE_ICON_COLOR,
291
+ size: 'sm',
292
+ }}
293
+ onPress={onTriggerPress}
294
+ onBlur={onTriggerBlur}
295
+ h={10}
296
+ w={10}
297
+ isDisabled={isDisabled}
298
+ borderWidth={1}
299
+ borderColor="#bbb"
300
+ borderLeftRadius="md"
301
+ borderRighttRadius={0}
302
+ bg={styles.FORM_DATE_ICON_BG}
303
+ _hover={{
304
+ bg: styles.FORM_DATE_ICON_BG_HOVER,
305
+ }}
306
+ />
307
+ {disableDirectEntry ?
308
+ <Pressable
309
+ onPress={togglePicker}
310
+ flex={1}
311
+ h="100%"
312
+ >
313
+ <Text
314
+ ref={inputRef}
315
+ flex={1}
316
+ h="100%"
317
+ numberOfLines={1}
318
+ ellipsizeMode="head"
319
+ m={0}
320
+ p={2}
321
+ borderWidth={1}
322
+ borderColor="trueGray.400"
323
+ borderLeftWidth={0}
324
+ borderLeftRadius={0}
325
+ borderRightRadius="md"
326
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
327
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
328
+ bg={styles.FORM_DATE_INPUT_BG}
329
+ _focus={{
330
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
331
+ }}
332
+ >{_.isEmpty(textInputValue) ? placeholder : textInputValue}</Text>
333
+ </Pressable> :
334
+ <Input
335
+ ref={inputRef}
336
+ value={textInputValue}
337
+ // setValue={onInputSetValue}
338
+ onChangeValue={onInputChangeText}
339
+ onKeyPress={onInputKeyPress}
340
+ onBlur={onInputBlur}
341
+ onFocus={onInputFocus}
342
+ autoSubmit={true}
343
+ isDisabled={isDisabled}
344
+ // onLayout={(e) => {
345
+ // const {
346
+ // height,
347
+ // width,
348
+ // } = e.nativeEvent.layout;
349
+ // setWidth(Math.round(width));
350
+ // setTop(Math.round(height));
351
+ // }}
352
+ flex={1}
353
+ h="100%"
354
+ m={0}
355
+ autoSubmitDelay={1000}
356
+ borderTopRightRadius={0}
357
+ borderBottomRightRadius={0}
358
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
359
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
360
+ bg={styles.FORM_DATE_INPUT_BG}
361
+ _focus={{
362
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
363
+ }}
364
+ placeholder={placeholder}
365
+ {..._input}
366
+ />}
367
+ </>;
368
+ }
369
+
370
+ if (UiGlobals.mode === UI_MODE_REACT_NATIVE) {
371
+ // This input and trigger are for show
372
+ // The just show the current value and open the menu
373
+ inputAndTrigger = <>
374
+ <IconButton
375
+ ref={triggerRef}
376
+ _icon={{
377
+ as: Calendar,
378
+ color: styles.FORM_DATE_ICON_COLOR,
379
+ size: 'sm',
380
+ }}
381
+ isDisabled={isDisabled}
382
+ onPress={onTriggerPress}
383
+ onBlur={onTriggerBlur}
384
+ h="100%"
385
+ w={10}
386
+ borderWidth={1}
387
+ borderColor="#bbb"
388
+ borderLeftRadius="md"
389
+ borderRightWidth={0}
390
+ borderRighttRadius={0}
391
+ bg={styles.FORM_DATE_ICON_BG}
392
+ _hover={{
393
+ bg: styles.FORM_DATE_ICON_BG_HOVER,
394
+ }}
395
+ />
396
+ <Pressable
397
+ onPress={togglePicker}
398
+ flex={1}
399
+ >
400
+ <Text
401
+ flex={1}
402
+ h="100%"
403
+ numberOfLines={1}
404
+ ellipsizeMode="head"
405
+ m={0}
406
+ p={2}
407
+ borderWidth={1}
408
+ borderColor="trueGray.400"
409
+ borderLeftWidth={0}
410
+ borderLeftRadius={0}
411
+ borderRightRadius="md"
412
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
413
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
414
+ bg={styles.FORM_DATE_INPUT_BG}
415
+ _focus={{
416
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
417
+ }}
418
+ >{_.isEmpty(textInputValue) ? placeholder : textInputValue}</Text>
419
+ </Pressable>
420
+ </>;
421
+ }
422
+
423
+ if (isPickerShown) {
424
+ if (UiGlobals.mode === UI_MODE_WEB) {
425
+
426
+ // place the picker in a convenient spot
427
+ const
428
+ translateParts = [],
429
+ translateProps = {};
430
+ if (isTranslateX) {
431
+ translateParts.push('translateX(-100%)');
432
+ }
433
+ if (isTranslateY) {
434
+ translateParts.push('translateY(-100%)');
435
+ }
436
+ if (!_.isEmpty(translateParts)) {
437
+ translateProps.style = {
438
+ transform: translateParts.join(' '),
439
+ };
440
+ }
441
+ dropdownMenu = <Popover
442
+ isOpen={isPickerShown}
443
+ onClose={() => {
444
+ hidePicker();
445
+ }}
446
+ trigger={emptyFn}
447
+ trapFocus={false}
448
+ placement={'auto'}
449
+ {...props}
450
+ >
451
+ <Popover.Content
452
+ position="absolute"
453
+ top={top + 'px'}
454
+ left={left + 'px'}
455
+ w={width + 'px'}
456
+ minWidth={menuMinWidth}
457
+ overflow="auto"
458
+ bg="#fff"
459
+ {...translateProps}
460
+ >
461
+ <Popover.Body
462
+ ref={pickerRef}
463
+ borderWidth={1}
464
+ borderColor='trueGray.400'
465
+ borderTopWidth={0}
466
+ p={0}
467
+ >
468
+ <Datetime
469
+ open={true}
470
+ input={false}
471
+ closeOnClickOutside={false}
472
+ value={pickerValue}
473
+ dateFormat={mode === DATE || mode === DATETIME ? 'YYYY-MM-DD' : false}
474
+ timeFormat={mode === TIME || mode === DATETIME ? 'HH:mm:ss' : false}
475
+ onChange={onPickerChange}
476
+ />
477
+ </Popover.Body>
478
+ </Popover.Content>
479
+ </Popover>;
480
+ }
481
+ if (UiGlobals.mode === UI_MODE_REACT_NATIVE) {
482
+ const inputAndTriggerClone = // for RN, this is the actual input and trigger, as we need them to appear up above in the modal
483
+ <Row h={10}>
484
+ <IconButton
485
+ _icon={{
486
+ as: Calendar,
487
+ color: styles.FORM_DATE_ICON_COLOR,
488
+ size: 'sm',
270
489
  }}
271
490
  isDisabled={isDisabled}
272
- numberOfLines={1}
273
- ellipsizeMode="head"
274
- onLayout={(e) => {
275
- // On web, this is not needed, but on RN it might be, so leave it in for now
276
- const {
277
- height,
278
- top,
279
- left,
280
- } = e.nativeEvent.layout;
281
- setTop(top + height);
282
- setLeft(left);
491
+ onPress={() => hidePicker()}
492
+ h="100%"
493
+ w={10}
494
+ borderWidth={1}
495
+ borderColor="#bbb"
496
+ borderLeftRadius="md"
497
+ borderRightWidth={0}
498
+ borderRighttRadius={0}
499
+ bg={styles.FORM_DATE_ICON_BG}
500
+ _hover={{
501
+ bg: styles.FORM_DATE_ICON_BG_HOVER,
283
502
  }}
284
- w={props.w || null}
285
503
  />
286
- {/* <Pressable
287
- flex={1}
288
- h="100%"
289
- onPress={showPicker}
290
- >
504
+ {disableDirectEntry ?
291
505
  <Text
506
+ ref={inputRef}
292
507
  flex={1}
293
508
  h="100%"
294
- ml={1}
295
- p={2}
296
- fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
297
- borderWidth={1}
298
- borderColor="trueGray.300"
299
- borderRadius={4}
300
509
  numberOfLines={1}
301
510
  ellipsizeMode="head"
302
- >{title}</Text>
303
- </Pressable> */}
304
- <Popover
305
- isOpen={isPickerShown}
306
- onClose={() => {
307
- hidePicker();
308
- }}
309
- trigger={emptyFn}
310
- trapFocus={false}
311
- placement={'auto'}
312
- {...props}
313
- >
314
- <Popover.Content
315
- position="absolute"
316
- top={top + 'px'}
317
- left={left + 'px'}
318
- w={width}
319
- h={height}
320
- {...translateProps}
321
- >
322
- <Popover.Body
323
- ref={pickerRef}
324
- p={0}
511
+ m={0}
512
+ p={2}
513
+ borderWidth={1}
514
+ borderColor="trueGray.400"
515
+ borderLeftWidth={0}
516
+ borderLeftRadius={0}
517
+ borderRightRadius="md"
518
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
519
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
520
+ bg={styles.FORM_DATE_INPUT_BG}
521
+ _focus={{
522
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
523
+ }}
524
+ >{textInputValue}</Text> :
525
+ <Input
526
+ ref={inputRef}
527
+ value={textInputValue}
528
+ autoSubmit={true}
529
+ isDisabled={isDisabled}
530
+ onChangeValue={onInputChangeText}
531
+ onKeyPress={onInputKeyPress}
532
+ onFocus={onInputFocus}
533
+ onBlur={onInputBlur}
534
+ flex={1}
535
+ h="100%"
536
+ m={0}
537
+ autoSubmitDelay={1000}
538
+ borderTopRightRadius={0}
539
+ borderBottomRightRadius={0}
540
+ fontSize={styles.FORM_DATE_READOUT_FONTSIZE}
541
+ color={_.isEmpty(textInputValue) ? 'trueGray.400' : '#000'}
542
+ bg={styles.FORM_DATE_INPUT_BG}
543
+ _focus={{
544
+ bg: styles.FORM_DATE_INPUT_FOCUS_BG,
545
+ }}
546
+ placeholder={placeholder}
547
+ {..._input}
548
+ />}
549
+ </Row>;
550
+ dropdownMenu = <Modal
551
+ isOpen={true}
552
+ safeAreaTop={true}
553
+ onClose={() => setIsPickerShown(false)}
554
+ mt="auto"
555
+ mb="auto"
556
+ w="100%"
557
+ h={400}
558
+ p={5}
325
559
  >
326
- <Datetime
560
+ {inputAndTriggerClone}
561
+ {/* <Datetime
327
562
  open={true}
328
563
  input={false}
564
+ mode={mode === DATE ? 'date' : mode === TIME ? 'time' : mode === DATETIME ? 'datetime' : null}
329
565
  closeOnClickOutside={false}
330
566
  value={pickerValue}
331
567
  dateFormat={mode === DATE || mode === DATETIME ? 'YYYY-MM-DD' : false}
332
568
  timeFormat={mode === TIME || mode === DATETIME ? 'HH:mm:ss' : false}
333
569
  onChange={onPickerChange}
334
- />
335
- </Popover.Body>
336
- </Popover.Content>
337
- </Popover>
338
- </Row>
339
- </Tooltip>;
570
+ /> */}
571
+ <Box bg="#fff">
572
+ <Datetime
573
+ selectedStartDate={moment(value).toDate()}
574
+ onDateChange={onPickerChange}
575
+ todayBackgroundColor="#eee"
576
+ selectedDayColor="#f00"
577
+ selectedDayTextColor="#fff"
578
+ />
579
+ </Box>
580
+ </Modal>;
581
+ }
582
+ }
340
583
 
341
- // React Native v1
342
- // return <Row>
343
- // <Icon as={Calendar} />
344
- // <Text>{title}</Text>
345
- // {isPickerShown && <DateTimePicker
346
- // value={value}
347
- // mode={mode}
348
- // display="default"
349
- // onChange={(e, value) => {
350
- // setValue(value);
351
- // }}
352
- // {...propsToPass}
353
- // />}
354
- // </Row>;
584
+ const refProps = {};
585
+ if (tooltipRef) {
586
+ refProps.ref = tooltipRef;
587
+ }
588
+ assembledComponents = <Row {...refProps} justifyContent="center" alignItems="center" h={styles.FORM_COMBO_HEIGHT} flex={1} onLayout={() => setIsRendered(true)}>
589
+ {xButton}
590
+ {inputAndTrigger}
591
+ {additionalButtons}
592
+ {dropdownMenu}
593
+ </Row>;
355
594
 
356
- // React Native v2
357
- // return <Box>
358
- // <Button
359
- // leftIcon={<Icon as={Calendar} />}
360
- // onPress={showPicker}
361
- // >{title}</Button>
362
- // <DateTimePickerModal
363
- // isPickerShown={isPickerShown}
364
- // mode="date"
365
- // onConfirm={handleConfirm}
366
- // onCancel={hidePicker}
367
- // {...propsToPass}
368
- // />
369
- // </Box>;
595
+ if (tooltip) {
596
+ assembledComponents = <Tooltip label={tooltip} placement={tooltipPlacement}>
597
+ {assembledComponents}
598
+ </Tooltip>;
599
+ }
600
+
601
+ return assembledComponents;
602
+
370
603
  };
371
604
 
372
605
  export default withComponent(withValue(DateElement));
@@ -265,48 +265,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
265
265
  setIsEditorShown(true);
266
266
  },
267
267
  onRemoteDuplicate = async () => {
268
-
269
- // Call /duplicate on server
270
- const
271
- Model = Repository.getSchema().name,
268
+ const
272
269
  entity = selection[0],
273
- id = entity.id;
274
- const result = await Repository._send('POST', Model + '/duplicate', { id });
275
- if (!result) {
276
- return;
277
- }
278
- const {
279
- root,
280
- success,
281
- total,
282
- message
283
- } = Repository._processServerResponse(result);
284
-
285
- if (!success) {
286
- throw Error(message);
287
- }
288
-
289
- const duplicateId = root.id;
290
-
291
- // TODO: I don't like this.
292
- // Currently, we filter the repository by only the new Entity, then select the entity for editing.
293
- // There is a 2-second delay between filtering and being able to select, and this is unacceptable.
294
- // Why do we filter for just the new entity? Because it's not guaranteed to show up in the grid based on sorting.
295
- // Can't we just manually add this record to the repository at the top and then edit it?
296
-
297
- // Filter the grid with only the duplicate's ID, and open it for editing.
298
- self.filterById(duplicateId, () => { // because of the way useFilters is made, we have to use a callback, not await a Promise.
299
-
300
- // Select the only node
301
- const duplicateEntity = Repository.getById(duplicateId);
302
- setTimeout(() => {
303
- setSelection([duplicateEntity]);
304
-
305
- onEdit();
306
- }, 2000); // we need this delay!
307
-
308
- });
270
+ duplicateEntity = await Repository.remoteDuplicate(entity);
309
271
 
272
+ setSelection([duplicateEntity]);
273
+ onEdit();
310
274
  },
311
275
  onEditorSave = async (data, e) => {
312
276
  const
@@ -2,14 +2,14 @@ import {
2
2
  Row,
3
3
  Spinner,
4
4
  } from 'native-base';
5
- // import ScreenContainer from '../ScreenContainer.js';
5
+ import ScreenContainer from '../Container/ScreenContainer.js';
6
6
 
7
7
  export default function Loading(props) {
8
- // if (props.isScreen) {
9
- // return <ScreenContainer {...props}>
10
- // <Spinner flex={1} color="primary.500" />
11
- // </ScreenContainer>;
12
- // }
8
+ if (props.isScreen) {
9
+ return <ScreenContainer {...props}>
10
+ <Spinner flex={1} color="primary.500" />
11
+ </ScreenContainer>;
12
+ }
13
13
  return <Row justifyContent="center" minHeight={100} {...props}>
14
14
  <Spinner flex={1} color="primary.500" />
15
15
  </Row>;
@@ -1,10 +1,10 @@
1
1
  import * as ImageManipulator from 'expo-image-manipulator';
2
2
 
3
- export default async function processImage(uri) {
3
+ export default async function processImage(uri, width = 1000) {
4
4
  const file = await ImageManipulator.manipulateAsync(uri,
5
5
  [{
6
6
  resize: {
7
- width: 1000
7
+ width,
8
8
  }
9
9
  }],
10
10
  {
@@ -2,6 +2,7 @@ import UiGlobals from '../UiGlobals.js';
2
2
  import Datetime from '../PlatformImports/ReactNative/Datetime';
3
3
  import Draggable from '../PlatformImports/ReactNative/Draggable';
4
4
  import ScreenContainer from '../Components/Container/ScreenContainer';
5
+ import useWindowSize from '../PlatformImports/ReactNative/useWindowSize.js';
5
6
  import _ from 'lodash';
6
7
 
7
8
  export default function registerReactNativeComponents() {
@@ -9,5 +10,6 @@ export default function registerReactNativeComponents() {
9
10
  Datetime,
10
11
  Draggable,
11
12
  ScreenContainer,
13
+ useWindowSize,
12
14
  });
13
15
  }
@@ -4,6 +4,7 @@ import Attachments from '../PlatformImports/Web/Attachments.js';
4
4
  import Datetime from '../PlatformImports/Web/Datetime.js';
5
5
  import Draggable from '../PlatformImports/Web/Draggable.js';
6
6
  // import File from '../PlatformImports/Web/Attachments.js';
7
+ import useWindowSize from '../PlatformImports/Web/useWindowSize.js';
7
8
  import _ from 'lodash';
8
9
 
9
10
  export default function registerWebComponents() {
@@ -13,5 +14,6 @@ export default function registerWebComponents() {
13
14
  Datetime,
14
15
  Draggable,
15
16
  // File,
17
+ useWindowSize,
16
18
  });
17
19
  }
@@ -1,11 +1,13 @@
1
- import useWindowSize from './useWindowSize.js';
1
+ import getComponentFromType from '../Functions/getComponentFromType.js';
2
2
 
3
3
  // This hook takes the submitted window size and adjusts it
4
4
  // to fit the actual screen size
5
5
 
6
6
  export default function(width, height, percentage = 1) {
7
7
 
8
- const windowSize = useWindowSize();
8
+ const
9
+ useWindowSize = getComponentFromType('useWindowSize'),
10
+ windowSize = useWindowSize();
9
11
 
10
12
  if (width > windowSize.width) {
11
13
  width = windowSize.width * percentage;
@@ -1,4 +1,5 @@
1
1
  // import DateTimePickerModal from 'react-native-modal-datetime-picker'; // https://github.com/mmazzarolo/react-native-modal-datetime-picker
2
- import Datetime from '@react-native-community/datetimepicker'; // https://github.com/react-native-datetimepicker/datetimepicker
2
+ // import Datetime from '@react-native-community/datetimepicker'; // https://github.com/react-native-datetimepicker/datetimepicker
3
+ import CalendarPicker from 'react-native-calendar-picker'; // https://www.npmjs.com/package/react-native-calendar-picker
3
4
 
4
- export default Datetime.default || Datetime;
5
+ export default CalendarPicker.default || CalendarPicker;
@@ -0,0 +1,4 @@
1
+ import { useWindowDimensions } from 'react-native';
2
+
3
+ const useWindowSize = useWindowDimensions;
4
+ export default useWindowSize;
@@ -3,7 +3,6 @@
3
3
  import { useLayoutEffect, useState } from 'react';
4
4
  import _ from 'lodash';
5
5
 
6
- // For web only!
7
6
  export default function useWindowSize() {
8
7
  const [windowSize, setWindowSize] = useState({
9
8
  width: window?.innerWidth || 0,