@onehat/ui 0.4.81 → 0.4.83

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.
Files changed (31) hide show
  1. package/package.json +7 -6
  2. package/src/Components/Container/Container.js +4 -4
  3. package/src/Components/Form/Field/Combo/Combo.js +88 -16
  4. package/src/Components/Form/Field/Combo/MeterTypesCombo.js +1 -0
  5. package/src/Components/Form/Field/Date.js +1 -1
  6. package/src/Components/Form/Field/Json.js +2 -1
  7. package/src/Components/Form/Field/Select/PageSizeSelect.js +6 -1
  8. package/src/Components/Form/Field/Select/Select.js +14 -38
  9. package/src/Components/Form/Field/Tag/Tag.js +234 -14
  10. package/src/Components/Form/Field/Tag/ValueBox.js +20 -1
  11. package/src/Components/Form/Form.js +26 -13
  12. package/src/Components/Grid/Grid.js +316 -106
  13. package/src/Components/Grid/GridHeaderRow.js +42 -22
  14. package/src/Components/Grid/GridRow.js +16 -6
  15. package/src/Components/Grid/RowHandle.js +16 -4
  16. package/src/Components/Hoc/Secondary/withSecondaryEditor.js +137 -43
  17. package/src/Components/Hoc/Secondary/withSecondarySideEditor.js +1 -1
  18. package/src/Components/Hoc/withData.js +7 -0
  19. package/src/Components/Hoc/withEditor.js +19 -4
  20. package/src/Components/Hoc/withPresetButtons.js +1 -1
  21. package/src/Components/Hoc/withSideEditor.js +1 -1
  22. package/src/Components/Icons/Join.js +10 -0
  23. package/src/Components/Layout/AsyncOperation.js +61 -14
  24. package/src/Components/Layout/CenterBox.js +1 -1
  25. package/src/Components/Screens/Manager.js +1 -1
  26. package/src/Components/Toolbar/Pagination.js +108 -106
  27. package/src/Components/Toolbar/PaginationToolbar.js +3 -1
  28. package/src/Components/Toolbar/Toolbar.js +10 -6
  29. package/src/Components/Tree/TreeNode.js +39 -9
  30. package/src/Components/Viewer/Viewer.js +7 -2
  31. package/src/Constants/Progress.js +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.81",
3
+ "version": "0.4.83",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -58,17 +58,18 @@
58
58
  "@k-renwick/colour-mixer": "^1.2.1",
59
59
  "@legendapp/motion": "^2.4.0",
60
60
  "@onehat/data": "^1.22.0",
61
- "clsx": "^2.1.1",
62
- "nativewind": "4.1.23",
63
- "normalize-css-color": "^1.0.2",
64
61
  "@react-native-community/slider": "^4.5.2",
65
62
  "@reduxjs/toolkit": "^2.6.1",
63
+ "clsx": "^2.1.1",
66
64
  "decimal.js": "^10.5.0",
67
65
  "inflector-js": "^1.0.1",
68
66
  "js-cookie": "^3.0.5",
67
+ "nativewind": "4.1.23",
68
+ "normalize-css-color": "^1.0.2",
69
69
  "react-hook-form": "^7.55.0",
70
70
  "react-native-progress": "^5.0.1",
71
71
  "react-redux": "^9.2.0",
72
+ "tailwind-scrollbar": "^3.1.0",
72
73
  "tailwindcss": "^3.4.17",
73
74
  "yup": "^1.6.1"
74
75
  },
@@ -79,10 +80,10 @@
79
80
  "react": "*",
80
81
  "react-color": "^2.19.3",
81
82
  "react-datetime": "^3.2.0",
82
- "react-dom": "*",
83
83
  "react-dnd": "^16.0.1",
84
84
  "react-dnd-html5-backend": "^16.0.1",
85
- "react-dnd-touch-backend":"16.0.1",
85
+ "react-dnd-touch-backend": "16.0.1",
86
+ "react-dom": "*",
86
87
  "react-draggable": "^4.4.5",
87
88
  "react-native": "*",
88
89
  "react-native-draggable": "^3.3.0",
@@ -78,10 +78,10 @@ function Container(props) {
78
78
  id = props.id || props.self?.path,
79
79
  canResize = CURRENT_MODE === UI_MODE_WEB,
80
80
  [isReady, setIsReady] = useState(false),
81
- [localIsNorthCollapsed, setLocalIsNorthCollapsedRaw] = useState(north ? north.props.startsCollapsed : false),
82
- [localIsSouthCollapsed, setLocalIsSouthCollapsedRaw] = useState(south ? south.props.startsCollapsed : false),
83
- [localIsEastCollapsed, setLocalIsEastCollapsedRaw] = useState(east ? east.props.startsCollapsed : false),
84
- [localIsWestCollapsed, setLocalIsWestCollapsedRaw] = useState(west ? west.props.startsCollapsed : false),
81
+ [localIsNorthCollapsed, setLocalIsNorthCollapsedRaw] = useState(north ? !!north.props.startsCollapsed : false),
82
+ [localIsSouthCollapsed, setLocalIsSouthCollapsedRaw] = useState(south ? !!south.props.startsCollapsed : false),
83
+ [localIsEastCollapsed, setLocalIsEastCollapsedRaw] = useState(east ? !!east.props.startsCollapsed : false),
84
+ [localIsWestCollapsed, setLocalIsWestCollapsedRaw] = useState(west ? !!west.props.startsCollapsed : false),
85
85
  [northHeight, setNorthHeightRaw] = useState(north ? north.props.h : 0),
86
86
  [southHeight, setSouthHeightRaw] = useState(south ? south.props.h : 0),
87
87
  [eastWidth, setEastWidthRaw] = useState(east ? east.props.w : 0),
@@ -24,6 +24,7 @@ import testProps from '../../../../Functions/testProps.js';
24
24
  import UiGlobals from '../../../../UiGlobals.js';
25
25
  import Input from '../Input.js';
26
26
  import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
27
+ import useForceUpdate from '../../../../Hooks/useForceUpdate.js';
27
28
  import withAlert from '../../../Hoc/withAlert.js';
28
29
  import withComponent from '../../../Hoc/withComponent.js';
29
30
  import withData from '../../../Hoc/withData.js';
@@ -121,13 +122,16 @@ export const ComboComponent = forwardRef((props, ref) => {
121
122
  setValue,
122
123
  } = props,
123
124
  styles = UiGlobals.styles,
125
+ forceUpdate = useForceUpdate(),
124
126
  inputRef = useRef(),
125
127
  inputCloneRef = useRef(),
126
128
  triggerRef = useRef(),
127
129
  menuRef = useRef(),
128
130
  displayValueRef = useRef(),
129
131
  typingTimeout = useRef(),
130
- [isMenuShown, setIsMenuShown] = useState(false),
132
+ isMenuShown = useRef(false),
133
+ isGridLayoutRunWithRender = useRef(false),
134
+ [isMenuAbove, setIsMenuAbove] = useState(false),
131
135
  [isViewerShown, setIsViewerShown] = useState(false),
132
136
  [viewerSelection, setViewerSelection] = useState([]),
133
137
  [isRendered, setIsRendered] = useState(false),
@@ -140,15 +144,39 @@ export const ComboComponent = forwardRef((props, ref) => {
140
144
  [newEntityDisplayValue, setNewEntityDisplayValue] = useState(null),
141
145
  [filteredData, setFilteredData] = useState(data),
142
146
  [inputHeight, setInputHeight] = useState(0),
147
+ [menuRenderedHeight, setMenuRenderedHeight] = useState(0),
143
148
  [width, setWidth] = useState(0),
144
149
  [top, setTop] = useState(0),
145
150
  [left, setLeft] = useState(0),
151
+ getIsMenuShown = () => {
152
+ return isMenuShown.current;
153
+ },
154
+ setIsMenuShown = (bool) => {
155
+ isMenuShown.current = bool;
156
+
157
+ if (!bool) {
158
+ // The menu's onLayout runs every time there's a change in its size or position.
159
+ // We're only interested in the *first* time it runs with a rendered height.
160
+ // So if hiding the menu, reset isGridLayoutRunWithRender here and we'll set it to true
161
+ // the first time onLayout runs with a height.
162
+ setIsGridLayoutRunWithRender(false);
163
+ setIsMenuAbove(false); // reset this so the next time the menu opens, it starts below the input
164
+ }
165
+
166
+ forceUpdate();
167
+ },
168
+ getIsGridLayoutRunWithRender = () => {
169
+ return isGridLayoutRunWithRender.current;
170
+ },
171
+ setIsGridLayoutRunWithRender = (bool) => {
172
+ isGridLayoutRunWithRender.current = bool;
173
+ },
146
174
  onLayout = (e) => {
147
175
  setIsRendered(true);
148
176
  setContainerWidth(e.nativeEvent.layout.width);
149
177
  },
150
178
  showMenu = async () => {
151
- if (isMenuShown) {
179
+ if (getIsMenuShown()) {
152
180
  return;
153
181
  }
154
182
  if (CURRENT_MODE === UI_MODE_WEB && inputRef.current?.getBoundingClientRect) {
@@ -181,13 +209,13 @@ export const ComboComponent = forwardRef((props, ref) => {
181
209
  setIsMenuShown(true);
182
210
  },
183
211
  hideMenu = () => {
184
- if (!isMenuShown) {
212
+ if (!getIsMenuShown()) {
185
213
  return;
186
214
  }
187
215
  setIsMenuShown(false);
188
216
  },
189
217
  toggleMenu = () => {
190
- setIsMenuShown(!isMenuShown);
218
+ setIsMenuShown(!getIsMenuShown());
191
219
  },
192
220
  temporarilySetIsNavigatingViaKeyboard = () => {
193
221
  setIsNavigatingViaKeyboard(true);
@@ -359,14 +387,14 @@ export const ComboComponent = forwardRef((props, ref) => {
359
387
  if (reloadOnTrigger && Repository) {
360
388
  await Repository.reload();
361
389
  }
362
- if (isMenuShown) {
390
+ if (getIsMenuShown()) {
363
391
  hideMenu();
364
392
  } else {
365
393
  showMenu();
366
394
  }
367
395
  },
368
396
  onTriggerBlur = (e) => {
369
- if (!isMenuShown) {
397
+ if (!getIsMenuShown()) {
370
398
  return;
371
399
  }
372
400
 
@@ -409,6 +437,33 @@ export const ComboComponent = forwardRef((props, ref) => {
409
437
  onCheckButtonPress = () => {
410
438
  hideMenu();
411
439
  },
440
+ onGridLayout = (e) => {
441
+ // This method is to determine if we need to flip the grid above the input
442
+ // because the menu is partially offscreen
443
+
444
+ if (CURRENT_MODE !== UI_MODE_WEB || !e.nativeEvent.layout.height) {
445
+ return;
446
+ }
447
+
448
+ // we reach this point only if the grid has rendered with a height.
449
+
450
+ if (!getIsGridLayoutRunWithRender()) {
451
+ // we reach this point only on the *first* time onGridLayout runs with a height.
452
+ // determine if the menu is partially offscreen
453
+ const
454
+ menuRect = menuRef.current.getBoundingClientRect(),
455
+ inputRect = inputRef.current.getBoundingClientRect(),
456
+ menuOverflows = menuRect.bottom > window.innerHeight;
457
+ if (menuOverflows) {
458
+ // flip it
459
+ setIsMenuAbove(true);
460
+ } else {
461
+ setIsMenuAbove(false);
462
+ }
463
+ setMenuRenderedHeight(e.nativeEvent.layout.height);
464
+ setIsGridLayoutRunWithRender(true);
465
+ }
466
+ },
412
467
  isEventStillInComponent = (e) => {
413
468
  const {
414
469
  relatedTarget
@@ -518,7 +573,7 @@ export const ComboComponent = forwardRef((props, ref) => {
518
573
  setFilteredData(found);
519
574
  }
520
575
 
521
- if (!isMenuShown) {
576
+ if (!getIsMenuShown()) {
522
577
  showMenu();
523
578
  }
524
579
  setIsSearchMode(true);
@@ -734,7 +789,7 @@ export const ComboComponent = forwardRef((props, ref) => {
734
789
  </Pressable>;
735
790
  }
736
791
 
737
- if (isMenuShown) {
792
+ if (getIsMenuShown()) {
738
793
  const gridProps = _.pick(props, [
739
794
  'Editor',
740
795
  'model',
@@ -743,6 +798,9 @@ export const ComboComponent = forwardRef((props, ref) => {
743
798
  'idIx',
744
799
  'displayIx',
745
800
  // 'value',
801
+ 'disableAdd',
802
+ 'disableEdit',
803
+ 'disableDelete',
746
804
  'disableView',
747
805
  'disableCopy',
748
806
  'disableDuplicate',
@@ -761,8 +819,9 @@ export const ComboComponent = forwardRef((props, ref) => {
761
819
  if (!Repository) {
762
820
  gridProps.data = filteredData;
763
821
  }
764
- const WhichGrid = isEditor ? WindowedGridEditor : Grid;
765
- const gridStyle = {};
822
+ const
823
+ WhichGrid = isEditor ? WindowedGridEditor : Grid,
824
+ gridStyle = {};
766
825
  if (CURRENT_MODE === UI_MODE_WEB) {
767
826
  gridStyle.height = menuHeight || styles.FORM_COMBO_MENU_HEIGHT;
768
827
  }
@@ -773,6 +832,9 @@ export const ComboComponent = forwardRef((props, ref) => {
773
832
  if (CURRENT_MODE === UI_MODE_NATIVE) {
774
833
  gridClassName += ' h-[400px] max-h-[100%]';
775
834
  }
835
+ if (gridProps.className) {
836
+ gridClassName += ' ' + gridProps.className;
837
+ }
776
838
  grid = <WhichGrid
777
839
  showHeaders={false}
778
840
  showHovers={true}
@@ -783,6 +845,7 @@ export const ComboComponent = forwardRef((props, ref) => {
783
845
  disablePresetButtons={!isEditor}
784
846
  alternateRowBackgrounds={false}
785
847
  showSelectHandle={false}
848
+ onLayout={onGridLayout}
786
849
  onChangeSelection={(selection) => {
787
850
 
788
851
  if (Repository && selection[0]?.isPhantom) {
@@ -886,9 +949,9 @@ export const ComboComponent = forwardRef((props, ref) => {
886
949
  }}
887
950
  reference="grid"
888
951
  parent={self}
889
- className={gridClassName}
890
952
  style={gridStyle}
891
953
  {...gridProps}
954
+ className={gridClassName}
892
955
  {..._editor}
893
956
  />;
894
957
  if (CURRENT_MODE === UI_MODE_WEB) {
@@ -935,7 +998,7 @@ export const ComboComponent = forwardRef((props, ref) => {
935
998
  </Box>;
936
999
  }
937
1000
  dropdownMenu = <Popover
938
- isOpen={isMenuShown}
1001
+ isOpen={getIsMenuShown()}
939
1002
  onClose={() => {
940
1003
  hideMenu();
941
1004
  }}
@@ -959,15 +1022,24 @@ export const ComboComponent = forwardRef((props, ref) => {
959
1022
  'max-w-full',
960
1023
  )}
961
1024
  style={{
962
- top,
1025
+ // If flipped, position above input; otherwise, below
1026
+ top: isMenuAbove
1027
+ ? (top - menuRenderedHeight) // above
1028
+ : top, // below
963
1029
  left,
964
1030
  width,
965
- height: (menuHeight || styles.FORM_COMBO_MENU_HEIGHT) + inputHeight,
966
1031
  minWidth: 100,
967
1032
  }}
968
1033
  >
969
- {inputClone}
970
- {grid}
1034
+ {isMenuAbove ?
1035
+ <>
1036
+ {grid}
1037
+ {inputClone}
1038
+ </> :
1039
+ <>
1040
+ {inputClone}
1041
+ {grid}
1042
+ </>}
971
1043
  </Box>
972
1044
  </Popover>;
973
1045
  }
@@ -22,6 +22,7 @@ function MeterTypesCombo(props) {
22
22
  reference="MeterTypeCombo"
23
23
  data={data}
24
24
  disableDirectEntry={true}
25
+ menuHeight={100}
25
26
  {...props}
26
27
  />;
27
28
  }
@@ -601,7 +601,7 @@ export const DateElement = forwardRef((props, ref) => {
601
601
  styles.FORM_DATE_CLASSNAME,
602
602
  );
603
603
  if (props.className) {
604
- className += props.className;
604
+ className += ' ' + props.className;
605
605
  }
606
606
  if (minimizeForRow) {
607
607
  className += ' h-auto min-h-0 max-h-[50px]';
@@ -21,6 +21,7 @@ export function JsonElement(props) {
21
21
  tooltip = null,
22
22
  isDisabled = false,
23
23
  isViewOnly = false,
24
+ isCollapsed = true,
24
25
  tooltipPlacement = 'bottom',
25
26
  testID,
26
27
 
@@ -59,7 +60,7 @@ export function JsonElement(props) {
59
60
  editable={!isViewOnly}
60
61
  src={src}
61
62
  enableClipboard={false}
62
- collapsed={true}
63
+ collapsed={isCollapsed}
63
64
  onEdit={(obj) => {
64
65
  setValue(JSON.stringify(obj.updated_src));
65
66
  }}
@@ -12,7 +12,11 @@ export default function PageSizeSelect(props) {
12
12
  } = props;
13
13
 
14
14
  return useMemo(() => {
15
- return <HStack className="PageSizeSelect-HStack w-[70px]">
15
+ return <HStack
16
+ className={clsx(
17
+ 'PageSizeSelect-HStack',
18
+ )}
19
+ >
16
20
  <Select
17
21
  data={[
18
22
  // [ 1, '1', ],
@@ -26,6 +30,7 @@ export default function PageSizeSelect(props) {
26
30
  onChangeValue={(value) => Repository.setPageSize(value)}
27
31
  tooltip="Page Size"
28
32
  tooltipClassName="w-[70px]"
33
+ fixedWidth={false}
29
34
  />
30
35
  </HStack>;
31
36
  }, [
@@ -1,6 +1,7 @@
1
1
  import { forwardRef, useState, useEffect, useRef, } from 'react';
2
2
  import {
3
3
  Select, SelectBackdrop, SelectContent, SelectDragIndicator, SelectDragIndicatorWrapper, SelectInput, SelectIcon, SelectItem, SelectPortal, SelectTrigger,
4
+ Text,
4
5
  } from '@project-components/Gluestack';
5
6
  import clsx from 'clsx';
6
7
  import {
@@ -15,54 +16,22 @@ import CaretDown from '../../../Icons/CaretDown.js';
15
16
  import _ from 'lodash';
16
17
 
17
18
  const SelectElement = forwardRef((props, ref) => {
18
- let { // so localValue can be changed, if needed
19
+ const {
19
20
  data = [], // in format [ [ value, label, ], ... ]
20
21
  value,
21
22
  setValue,
22
23
  onKeyPress,
23
24
  placeholder,
24
25
  disableAutoFlex = false,
26
+ fixedWidth = true,
25
27
  ...propsToPass
26
28
  } = props,
27
29
  styles = UiGlobals.styles,
28
- // debouncedSetValueRef = useRef(),
29
- // [localValue, setLocalValue] = useState(value),
30
- // onKeyPressLocal = (e) => {
31
- // if (e.key === 'Enter') {
32
- // debouncedSetValueRef.current?.cancel();
33
- // setValue(localValue);
34
- // }
35
- // if (onKeyPress) {
36
- // onKeyPress(e, localValue);
37
- // }
38
- // },
30
+ style = props.style || {},
39
31
  items = data.map(([ value, label, ], key) => {
40
32
  return <SelectItem key={key} label={label} value={value} />;
41
33
  });
42
34
 
43
- // useEffect(() => {
44
-
45
- // // Set up debounce fn
46
- // // Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
47
- // debouncedSetValueRef.current?.cancel(); // Cancel any previous debounced fn
48
- // debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
49
-
50
- // }, [setValue]);
51
-
52
- // useEffect(() => {
53
-
54
- // if (value !== localValue) {
55
- // // Make local value conform to externally changed value
56
- // setLocalValue(value);
57
- // }
58
-
59
- // }, [value]);
60
-
61
- // if (localValue === null || typeof localValue === 'undefined') {
62
- // localValue = ''; // If the value is null or undefined, don't let this be an uncontrolled select
63
- // }
64
-
65
- const style = props.style || {};
66
35
  // auto-set width to flex if it's not already set another way
67
36
  if (!disableAutoFlex && !hasWidth(props) && !hasFlex(props)) {
68
37
  style.flex = 1;
@@ -70,13 +39,12 @@ const SelectElement = forwardRef((props, ref) => {
70
39
  let className = clsx(
71
40
  'Select',
72
41
  'min-h-[40px]',
73
- 'w-full',
74
42
  'text-left',
75
43
  'rounded-lg',
76
44
  styles.FORM_SELECT_CLASSNAME,
77
45
  );
78
46
  if (props.className) {
79
- className += props.className;
47
+ className += ' ' + props.className;
80
48
  }
81
49
 
82
50
  return <Select
@@ -89,7 +57,15 @@ const SelectElement = forwardRef((props, ref) => {
89
57
  style={style}
90
58
  >
91
59
  <SelectTrigger variant="outline" size="md" className="SelectTrigger" >
92
- <SelectInput placeholder={placeholder} className="SelectInput" />
60
+ {fixedWidth ?
61
+ <SelectInput
62
+ placeholder={placeholder}
63
+ className={clsx(
64
+ 'SelectInput',
65
+ )}
66
+ /> :
67
+ <Text className="SelectText p-2">{value}</Text>}
68
+
93
69
  <SelectIcon className="mr-3" as={CaretDown} />
94
70
  </SelectTrigger>
95
71
  <SelectPortal className="SelectPortal">