@onehat/ui 0.4.17 → 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.17",
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,12 +73,17 @@ 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;
77
81
  }
78
82
  return item[displayIx];
79
83
  },
84
+ getNodeContent = (item) => { // extracts model/data and decides what the row content should be
85
+ return null;
86
+ },
80
87
  getDisplayTextFromSearchResults = (item) => {
81
88
  return item.id
82
89
  },
@@ -195,8 +202,7 @@ function TreeComponent(props) {
195
202
  return;
196
203
  }
197
204
 
198
- const
199
- {
205
+ const {
200
206
  shiftKey,
201
207
  metaKey,
202
208
  } = e;
@@ -464,7 +470,7 @@ function TreeComponent(props) {
464
470
  </VStack>,
465
471
  onCancel: hideModal,
466
472
  });
467
- };
473
+ },
468
474
 
469
475
  // utilities
470
476
  getNodeData = (id) => {
@@ -505,6 +511,7 @@ function TreeComponent(props) {
505
511
  datum = {
506
512
  item: treeNode,
507
513
  text: getNodeText(treeNode),
514
+ content: getNodeContent ? getNodeContent(treeNode) : null,
508
515
  iconCollapsed: getNodeIcon(COLLAPSED, treeNode),
509
516
  iconExpanded: getNodeIcon(EXPANDED, treeNode),
510
517
  iconLeaf: getNodeIcon(LEAF, treeNode),
@@ -873,13 +880,13 @@ function TreeComponent(props) {
873
880
  getHeaderToolbarItems = () => {
874
881
  const
875
882
  buttons = [
876
- {
877
- key: 'searchBtn',
878
- text: 'Search tree',
879
- handler: () => onSearchTree(treeSearchValue),
880
- icon: MagnifyingGlass,
881
- isDisabled: !treeSearchValue.length,
882
- },
883
+ // {
884
+ // key: 'searchBtn',
885
+ // text: 'Search tree',
886
+ // handler: () => onSearchTree(treeSearchValue),
887
+ // icon: MagnifyingGlass,
888
+ // isDisabled: !treeSearchValue.length,
889
+ // },
883
890
  {
884
891
  key: 'collapseAllBtn',
885
892
  text: 'Collapse whole tree',
@@ -906,33 +913,41 @@ function TreeComponent(props) {
906
913
  isDisabled: false,
907
914
  });
908
915
  }
916
+ if (isNodeTextConfigurable && editDisplaySettings) {
917
+ buttons.push({
918
+ key: 'editNodeTextBtn',
919
+ text: 'Display Settings',
920
+ handler: () => editDisplaySettings(),
921
+ icon: Gear,
922
+ });
923
+ }
909
924
  const items = _.map(buttons, (config, ix) => getIconButtonFromConfig(config, ix, self));
910
925
 
911
- items.unshift(<Input // Add text input to beginning of header items
912
- key="searchNodes"
913
- className="flex-1"
914
- placeholder="Find tree node"
915
- onChangeText={(val) => setTreeSearchValue(val)}
916
- onKeyPress={(e) => {
917
- if (e.key === 'Enter') {
918
- onSearchTree(treeSearchValue);
919
- }
920
- }}
921
- value={treeSearchValue}
922
- autoSubmit={false}
923
- />);
924
-
925
- if (treeSearchValue.length) {
926
- // Add 'X' button to clear search
927
- items.unshift(getIconButtonFromConfig({
928
- key: 'xBtn',
929
- handler: () => {
930
- setHighlitedDatum(null);
931
- setTreeSearchValue('');
932
- },
933
- icon: Xmark,
934
- }, 0, self));
935
- }
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
+ // }
936
951
 
937
952
  return items;
938
953
  },
@@ -1306,6 +1321,8 @@ function TreeComponent(props) {
1306
1321
  self.reloadTree = reloadTree;
1307
1322
  self.expandPath = expandPath;
1308
1323
  self.scrollToNode = scrollToNode;
1324
+ self.buildAndSetTreeNodeData = buildAndSetTreeNodeData;
1325
+ self.forceUpdate = forceUpdate;
1309
1326
  }
1310
1327
 
1311
1328
  const
@@ -1360,7 +1377,7 @@ function TreeComponent(props) {
1360
1377
  className += ' ' + props.className;
1361
1378
  }
1362
1379
 
1363
- return <VStack
1380
+ return <VStackNative
1364
1381
  {...testProps(self)}
1365
1382
  className={className}
1366
1383
  >
@@ -1388,7 +1405,7 @@ function TreeComponent(props) {
1388
1405
 
1389
1406
  {treeFooterComponent}
1390
1407
 
1391
- </VStack>;
1408
+ </VStackNative>;
1392
1409
 
1393
1410
  }
1394
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,
@@ -35,6 +35,7 @@ export default function TreeNode(props) {
35
35
  hasChildren = item.hasChildren,
36
36
  depth = item.depth,
37
37
  text = datum.text,
38
+ content = datum.content,
38
39
  iconCollapsed = datum.iconCollapsed,
39
40
  iconExpanded = datum.iconExpanded,
40
41
  iconLeaf = datum.iconLeaf,
@@ -67,7 +68,6 @@ export default function TreeNode(props) {
67
68
  let className = `
68
69
  TreeNode
69
70
  items-center
70
- flex
71
71
  flex-1
72
72
  grow-1
73
73
  `;
@@ -75,7 +75,7 @@ export default function TreeNode(props) {
75
75
  className += ' ' + props.className;
76
76
  }
77
77
 
78
- return <HStack
78
+ return <HStackNative
79
79
  {...testProps('node' + (isSelected ? '-selected' : ''))}
80
80
  {...nodeProps}
81
81
  key={hash}
@@ -88,7 +88,7 @@ export default function TreeNode(props) {
88
88
 
89
89
  {isLoading ?
90
90
  <Spinner className="px-2" /> :
91
- (hasChildren && !isDragMode ?
91
+ (icon && hasChildren && !isDragMode ?
92
92
  <IconButton
93
93
  {...testProps('expandBtn')}
94
94
  icon={icon}
@@ -96,23 +96,25 @@ export default function TreeNode(props) {
96
96
  /> :
97
97
  <Icon as={icon} className="ml-4 mr-1" />)}
98
98
 
99
- <TextNative
100
- numberOfLines={1}
101
- ellipsizeMode="head"
102
- // {...propsToPass}
103
- className={`
104
- TreeNode-TextNative
105
- self-center
106
- overflow-hidden
107
- flex
108
- flex-1
109
- text-ellipsis
110
- ${styles.TREE_NODE_CLASSNAME}
111
- `}
112
- style={{ userSelect: 'none', }}
113
- >{text}</TextNative>
99
+ {text ? <TextNative
100
+ numberOfLines={1}
101
+ ellipsizeMode="head"
102
+ // {...propsToPass}
103
+ className={`
104
+ TreeNode-TextNative
105
+ self-center
106
+ overflow-hidden
107
+ flex
108
+ flex-1
109
+ text-ellipsis
110
+ ${styles.TREE_NODE_CLASSNAME}
111
+ `}
112
+ style={{ userSelect: 'none', }}
113
+ >{text}</TextNative> : null}
114
114
 
115
- </HStack>;
115
+ {content}
116
+
117
+ </HStackNative>;
116
118
  }, [
117
119
  nodeProps,
118
120
  bg,
@@ -127,6 +129,7 @@ export default function TreeNode(props) {
127
129
  hasChildren,
128
130
  depth,
129
131
  text,
132
+ content,
130
133
  onToggle,
131
134
  isLoading,
132
135
  ]);