@onehat/ui 0.4.18 → 0.4.20

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.18",
3
+ "version": "0.4.20",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -18,6 +18,7 @@ export default function BooleanCombo(props) {
18
18
 
19
19
  return <ArrayCombo
20
20
  data={data}
21
+ menuHeight={70}
21
22
  {...props}
22
23
  />;
23
24
  }
@@ -56,6 +56,7 @@ export function ComboComponent(props) {
56
56
  isDisabled = false,
57
57
  isInTag = false,
58
58
  minimizeForRow = false,
59
+ menuHeight,
59
60
  tooltipPlacement = 'bottom',
60
61
  placeholder,
61
62
  onRowPress,
@@ -690,7 +691,7 @@ export function ComboComponent(props) {
690
691
  const WhichGrid = isEditor ? WindowedGridEditor : Grid;
691
692
  const gridStyle = {};
692
693
  if (UiGlobals.mode === UI_MODE_WEB) {
693
- gridStyle.height = styles.FORM_COMBO_MENU_HEIGHT;
694
+ gridStyle.height = menuHeight || styles.FORM_COMBO_MENU_HEIGHT;
694
695
  }
695
696
  grid = <WhichGrid
696
697
  showHeaders={false}
@@ -882,7 +883,7 @@ export function ComboComponent(props) {
882
883
  top,
883
884
  left,
884
885
  width,
885
- height: styles.FORM_COMBO_MENU_HEIGHT + inputHeight,
886
+ height: (menuHeight || styles.FORM_COMBO_MENU_HEIGHT) + inputHeight,
886
887
  minWidth: 100,
887
888
  }}
888
889
  >
@@ -22,6 +22,7 @@ function TagComponent(props) {
22
22
  isViewOnly = false,
23
23
  isValueAlwaysArray,
24
24
  isValueAsStringifiedJson,
25
+ showEye = true,
25
26
  minimizeForRow = false,
26
27
  Editor,
27
28
  _combo = {},
@@ -231,6 +232,7 @@ function TagComponent(props) {
231
232
  text={val.text}
232
233
  onView={() => onView(val)}
233
234
  onDelete={!isViewOnly ? () => onDelete(val) : null}
235
+ showEye={showEye}
234
236
  />;
235
237
  });
236
238
 
@@ -14,6 +14,7 @@ export default function ValueBox(props) {
14
14
  text,
15
15
  onView,
16
16
  onDelete,
17
+ showEye,
17
18
  } = props,
18
19
  styles = UiGlobals.styles;
19
20
  return <HStackNative
@@ -30,25 +31,26 @@ export default function ValueBox(props) {
30
31
  ${!onDelete && 'pr-4'}
31
32
  `}
32
33
  >
33
- <IconButton
34
- {...testProps('eyeBtn')}
35
- icon={Eye}
36
- _icon={{
37
- size: styles.FORM_TAG_VALUEBOX_ICON_SIZE,
38
- className: 'text-grey-600',
39
- }}
40
- onPress={onView}
41
- className={`
42
- ValueBox-eyeBtn
43
- h-full
44
- ${styles.FORM_TAG_BTN_CLASSNAME}
45
- `}
46
- />
34
+ {showEye && <IconButton
35
+ {...testProps('eyeBtn')}
36
+ icon={Eye}
37
+ _icon={{
38
+ size: styles.FORM_TAG_VALUEBOX_ICON_SIZE,
39
+ className: 'text-grey-600',
40
+ }}
41
+ onPress={onView}
42
+ className={`
43
+ ValueBox-eyeBtn
44
+ h-full
45
+ ${styles.FORM_TAG_BTN_CLASSNAME}
46
+ `}
47
+ />}
47
48
  <Text
48
49
  className={`
49
50
  ValueBox-Text
50
51
  text-grey-600
51
52
  ${styles.FORM_TAG_VALUEBOX_CLASSNAME}
53
+ ${showEye ? 'ml-0' : 'ml-1'}
52
54
  ${onDelete ? 'mr-0' : 'mr-1'}
53
55
  `}
54
56
  >{text}</Text>
@@ -4,7 +4,7 @@ import {
4
4
  Icon,
5
5
  Pressable,
6
6
  Switch,
7
- Text,
7
+ TextNative,
8
8
  } from '@project-components/Gluestack';
9
9
  import UiGlobals from '../../../UiGlobals.js';
10
10
  import IconButton from '../../Buttons/IconButton.js';
@@ -96,14 +96,14 @@ const
96
96
  {...testProps('readoutBtn')}
97
97
  onPress={onToggle}
98
98
  >
99
- <Text
99
+ <TextNative
100
100
  {...testProps('readout')}
101
101
  className={`
102
102
  ml-1
103
103
  mr-2
104
104
  ${styles.FORM_TOGGLE_READOUT_CLASSNAME}
105
105
  `}
106
- >{_.isNil(value) ? 'N/A' : (!!value ? onText : offText)}</Text>
106
+ >{_.isNil(value) ? 'N/A' : (!!value ? onText : offText)}</TextNative>
107
107
  </Pressable>
108
108
  </HStack>;
109
109
  },
@@ -471,6 +471,7 @@ function Form(props) {
471
471
  isEditable = true,
472
472
  isEditingEnabledInPlainEditor,
473
473
  label,
474
+ labelWidth,
474
475
  items,
475
476
  onChange: onEditorChange,
476
477
  useSelectorId = false,
@@ -647,9 +648,12 @@ function Form(props) {
647
648
  if (defaults?.labelWidth) {
648
649
  style.width = defaults.labelWidth;
649
650
  }
651
+ if (labelWidth) {
652
+ style.width = labelWidth;
653
+ }
650
654
  if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
651
655
  if (!style.width) {
652
- style.width = '120px';
656
+ style.width = '160px';
653
657
  }
654
658
  element = <HStack className="Form-HStack1 w-full py-1">
655
659
  <Label style={style}>{label}</Label>
@@ -778,7 +782,7 @@ function Form(props) {
778
782
  if (message) {
779
783
  message = <Text className="text-[#f00]">{message}</Text>;
780
784
  }
781
- element = <VStack className="Form-VStack4 w-full pt-1">
785
+ element = <VStack className="Form-VStack4 flex-1 pt-1">
782
786
  {element}
783
787
  {message}
784
788
  </VStack>;
@@ -786,14 +790,14 @@ function Form(props) {
786
790
  if (item.additionalEditButtons) {
787
791
  const buttons = buildAdditionalButtons(item.additionalEditButtons, self, { fieldState, formSetValue, formGetValues, formState });
788
792
  if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
789
- element = <HStack className="Form-HStack5 w-full flex-wrap">
793
+ element = <HStack className="Form-HStack5 flex-1 flex-wrap">
790
794
  {element}
791
795
  {buttons}
792
796
  </HStack>;
793
797
  } else {
794
- element = <VStack className="Form-VStack6 w-full">
798
+ element = <VStack className="Form-VStack6 flex-1">
795
799
  {element}
796
- <HStack className="Form-HStack7-VStack w-full mt-1 flex-wrap">
800
+ <HStack className="Form-HStack7-VStack flex-1 mt-1 flex-wrap">
797
801
  {buttons}
798
802
  </HStack>
799
803
  </VStack>;
@@ -831,9 +835,12 @@ function Form(props) {
831
835
  if (defaults?.labelWidth) {
832
836
  style.width = defaults.labelWidth;
833
837
  }
838
+ if (labelWidth) {
839
+ style.width = labelWidth;
840
+ }
834
841
  if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
835
842
  if (!style.width) {
836
- style.width = '120px';
843
+ style.width = '160px';
837
844
  }
838
845
  element = <HStack className="Form-HStack8 w-full">
839
846
  <Label style={style}>
@@ -16,8 +16,10 @@ export default function Label(props) {
16
16
  }
17
17
  let textClassName = `
18
18
  Label-TextNative
19
- flex
19
+ inline-block
20
20
  text-ellipsis
21
+ text-base
22
+ w-full
21
23
  ${styles.FORM_LABEL_CLASSNAME}
22
24
  `;
23
25
  if (props._text?.className) {
@@ -12,6 +12,7 @@ import {
12
12
  UPLOAD_DOWNLOAD,
13
13
  } from '../../Constants/Commands.js';
14
14
  import UiGlobals from '../../UiGlobals.js';
15
+ import oneHatData from '@onehat/data';
15
16
  import _ from 'lodash';
16
17
 
17
18
  /**
@@ -22,11 +23,23 @@ import _ from 'lodash';
22
23
  export function checkPermission(permission) {
23
24
  const
24
25
  reduxState = UiGlobals.redux?.getState(),
25
- permissions = reduxState?.app?.permissions;
26
- if (!permissions) {
27
- return false;
26
+ permissions = reduxState?.app?.permissions || [];
27
+ let hasPermission = inArray(permission, permissions);
28
+ if (hasPermission) {
29
+ return true;
28
30
  }
29
- return inArray(permission, permissions);
31
+ // check for anonymous get
32
+ const matches = permission.match(/^view_(.*)$/);
33
+ if (matches) {
34
+ const
35
+ name = Inflector.camelize(matches[1]), // 'pm_events' -> 'PmEvents'
36
+ repository = oneHatData.getRepository(name),
37
+ allowAnonymousGet = repository?.schema.repository.allowAnonymousGet;
38
+ if (allowAnonymousGet) {
39
+ hasPermission = true;
40
+ }
41
+ }
42
+ return hasPermission;
30
43
  }
31
44
 
32
45
  /**
@@ -45,10 +45,14 @@ export default function Header(props) {
45
45
  self-center
46
46
  border
47
47
  border-grey-400
48
+ h-[20px]
49
+ w-[20px]
50
+ px-[2px]
51
+ py-[2px]
48
52
  ${styles.PANEL_HEADER_BG}
49
53
  `;
50
- if (collapseDirection !== HORIZONTAL) {
51
- closeClassName += ' mr-3';
54
+ if (collapseDirection === HORIZONTAL && isCollapsed) {
55
+ closeClassName += ' mb-1';
52
56
  }
53
57
  closeBtn = <IconButton
54
58
  onPress={onClose}
@@ -66,10 +70,14 @@ export default function Header(props) {
66
70
  self-center
67
71
  border
68
72
  border-grey-400
73
+ h-[20px]
74
+ w-[20px]
75
+ px-[2px]
76
+ py-[2px]
69
77
  ${styles.PANEL_HEADER_BG}
70
78
  `;
71
- if (collapseDirection !== HORIZONTAL) {
72
- collapseClassName += ' ml-3';
79
+ if (collapseDirection === HORIZONTAL && isCollapsed) {
80
+ collapseClassName += ' mb-1';
73
81
  }
74
82
  collapseBtn = <IconButton
75
83
  onPress={onToggleCollapse}
@@ -138,6 +146,7 @@ export default function Header(props) {
138
146
  </div>;
139
147
  }
140
148
  }
149
+ panelClassName += ' gap-2';
141
150
  if (closeBtn) {
142
151
  panelClassName += ' pl-[4px] pr-3';
143
152
  } else {
@@ -217,6 +226,7 @@ export default function Header(props) {
217
226
  </Pressable>;
218
227
  }
219
228
  }
229
+ panelClassName += ' gap-2';
220
230
  if (closeBtn) {
221
231
  panelClassName += ' pl-[4px] pr-3';
222
232
  } else {
@@ -112,8 +112,10 @@ function Panel(props) {
112
112
  if (isCollapsed) {
113
113
  if (collapseDirection === HORIZONTAL) {
114
114
  className += ' w-[33px]';
115
+ delete style.width;
115
116
  } else {
116
117
  className += ' h-[33px]';
118
+ delete style.height;
117
119
  }
118
120
  }
119
121
 
@@ -4,6 +4,7 @@ import {
4
4
  Pressable,
5
5
  ScrollView,
6
6
  VStack,
7
+ VStackNative,
7
8
  } from '@project-components/Gluestack';
8
9
  import {
9
10
  SELECTION_MODE_SINGLE,
@@ -49,6 +50,7 @@ import Collapse from '../Icons/Collapse.js';
49
50
  import Expand from '../Icons/Expand.js';
50
51
  import FolderClosed from '../Icons/FolderClosed.js';
51
52
  import FolderOpen from '../Icons/FolderOpen.js';
53
+ import Gear from '../Icons/Gear.js';
52
54
  import MagnifyingGlass from '../Icons/MagnifyingGlass.js';
53
55
  import NoReorderRows from '../Icons/NoReorderRows.js';
54
56
  import ReorderRows from '../Icons/ReorderRows.js';
@@ -71,6 +73,8 @@ function TreeComponent(props) {
71
73
  areRootsVisible = true,
72
74
  autoLoadRootNodes = true,
73
75
  extraParams = {}, // Additional params to send with each request ( e.g. { order: 'Categories.name ASC' })
76
+ isNodeTextConfigurable = false,
77
+ editDisplaySettings, // fn
74
78
  getNodeText = (item) => { // extracts model/data and decides what the row text should be
75
79
  if (Repository) {
76
80
  return item.displayValue;
@@ -198,8 +202,7 @@ function TreeComponent(props) {
198
202
  return;
199
203
  }
200
204
 
201
- const
202
- {
205
+ const {
203
206
  shiftKey,
204
207
  metaKey,
205
208
  } = e;
@@ -467,7 +470,7 @@ function TreeComponent(props) {
467
470
  </VStack>,
468
471
  onCancel: hideModal,
469
472
  });
470
- };
473
+ },
471
474
 
472
475
  // utilities
473
476
  getNodeData = (id) => {
@@ -508,7 +511,7 @@ function TreeComponent(props) {
508
511
  datum = {
509
512
  item: treeNode,
510
513
  text: getNodeText(treeNode),
511
- content: getNodeContent(treeNode),
514
+ content: getNodeContent ? getNodeContent(treeNode) : null,
512
515
  iconCollapsed: getNodeIcon(COLLAPSED, treeNode),
513
516
  iconExpanded: getNodeIcon(EXPANDED, treeNode),
514
517
  iconLeaf: getNodeIcon(LEAF, treeNode),
@@ -877,13 +880,13 @@ function TreeComponent(props) {
877
880
  getHeaderToolbarItems = () => {
878
881
  const
879
882
  buttons = [
880
- {
881
- key: 'searchBtn',
882
- text: 'Search tree',
883
- handler: () => onSearchTree(treeSearchValue),
884
- icon: MagnifyingGlass,
885
- isDisabled: !treeSearchValue.length,
886
- },
883
+ // {
884
+ // key: 'searchBtn',
885
+ // text: 'Search tree',
886
+ // handler: () => onSearchTree(treeSearchValue),
887
+ // icon: MagnifyingGlass,
888
+ // isDisabled: !treeSearchValue.length,
889
+ // },
887
890
  {
888
891
  key: 'collapseAllBtn',
889
892
  text: 'Collapse whole tree',
@@ -910,33 +913,41 @@ function TreeComponent(props) {
910
913
  isDisabled: false,
911
914
  });
912
915
  }
916
+ if (isNodeTextConfigurable && editDisplaySettings) {
917
+ buttons.push({
918
+ key: 'editNodeTextBtn',
919
+ text: 'Display Settings',
920
+ handler: () => editDisplaySettings(),
921
+ icon: Gear,
922
+ });
923
+ }
913
924
  const items = _.map(buttons, (config, ix) => getIconButtonFromConfig(config, ix, self));
914
925
 
915
- items.unshift(<Input // Add text input to beginning of header items
916
- key="searchNodes"
917
- className="flex-1"
918
- placeholder="Find tree node"
919
- onChangeText={(val) => setTreeSearchValue(val)}
920
- onKeyPress={(e) => {
921
- if (e.key === 'Enter') {
922
- onSearchTree(treeSearchValue);
923
- }
924
- }}
925
- value={treeSearchValue}
926
- autoSubmit={false}
927
- />);
928
-
929
- if (treeSearchValue.length) {
930
- // Add 'X' button to clear search
931
- items.unshift(getIconButtonFromConfig({
932
- key: 'xBtn',
933
- handler: () => {
934
- setHighlitedDatum(null);
935
- setTreeSearchValue('');
936
- },
937
- icon: Xmark,
938
- }, 0, self));
939
- }
926
+ // items.unshift(<Input // Add text input to beginning of header items
927
+ // key="searchNodes"
928
+ // className="flex-1"
929
+ // placeholder="Find tree node"
930
+ // onChangeText={(val) => setTreeSearchValue(val)}
931
+ // onKeyPress={(e) => {
932
+ // if (e.key === 'Enter') {
933
+ // onSearchTree(treeSearchValue);
934
+ // }
935
+ // }}
936
+ // value={treeSearchValue}
937
+ // autoSubmit={false}
938
+ // />);
939
+
940
+ // if (treeSearchValue.length) {
941
+ // // Add 'X' button to clear search
942
+ // items.unshift(getIconButtonFromConfig({
943
+ // key: 'xBtn',
944
+ // handler: () => {
945
+ // setHighlitedDatum(null);
946
+ // setTreeSearchValue('');
947
+ // },
948
+ // icon: Xmark,
949
+ // }, 0, self));
950
+ // }
940
951
 
941
952
  return items;
942
953
  },
@@ -1310,6 +1321,8 @@ function TreeComponent(props) {
1310
1321
  self.reloadTree = reloadTree;
1311
1322
  self.expandPath = expandPath;
1312
1323
  self.scrollToNode = scrollToNode;
1324
+ self.buildAndSetTreeNodeData = buildAndSetTreeNodeData;
1325
+ self.forceUpdate = forceUpdate;
1313
1326
  }
1314
1327
 
1315
1328
  const
@@ -1364,7 +1377,7 @@ function TreeComponent(props) {
1364
1377
  className += ' ' + props.className;
1365
1378
  }
1366
1379
 
1367
- return <VStack
1380
+ return <VStackNative
1368
1381
  {...testProps(self)}
1369
1382
  className={className}
1370
1383
  >
@@ -1392,7 +1405,7 @@ function TreeComponent(props) {
1392
1405
 
1393
1406
  {treeFooterComponent}
1394
1407
 
1395
- </VStack>;
1408
+ </VStackNative>;
1396
1409
 
1397
1410
  }
1398
1411
 
@@ -1,7 +1,7 @@
1
1
  import { useMemo, } from 'react';
2
2
  import {
3
3
  Box,
4
- HStack,
4
+ HStackNative,
5
5
  Icon,
6
6
  Spinner,
7
7
  TextNative,
@@ -68,7 +68,6 @@ export default function TreeNode(props) {
68
68
  let className = `
69
69
  TreeNode
70
70
  items-center
71
- flex
72
71
  flex-1
73
72
  grow-1
74
73
  `;
@@ -76,7 +75,7 @@ export default function TreeNode(props) {
76
75
  className += ' ' + props.className;
77
76
  }
78
77
 
79
- return <HStack
78
+ return <HStackNative
80
79
  {...testProps('node' + (isSelected ? '-selected' : ''))}
81
80
  {...nodeProps}
82
81
  key={hash}
@@ -89,7 +88,7 @@ export default function TreeNode(props) {
89
88
 
90
89
  {isLoading ?
91
90
  <Spinner className="px-2" /> :
92
- (hasChildren && !isDragMode ?
91
+ (icon && hasChildren && !isDragMode ?
93
92
  <IconButton
94
93
  {...testProps('expandBtn')}
95
94
  icon={icon}
@@ -115,7 +114,7 @@ export default function TreeNode(props) {
115
114
 
116
115
  {content}
117
116
 
118
- </HStack>;
117
+ </HStackNative>;
119
118
  }, [
120
119
  nodeProps,
121
120
  bg,