@onehat/ui 0.4.26 → 0.4.27

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.26",
3
+ "version": "0.4.27",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -7,14 +7,13 @@ export default function SquareButton(props) {
7
7
  const {
8
8
  text,
9
9
  isActive = false,
10
- activeColor,
10
+ activeClassName,
11
11
  invertColorWhenActive = false,
12
12
  showText = true,
13
13
  disableInteractions = false,
14
14
  fontSize = '20px',
15
15
  ...propsToPass
16
16
  } = props,
17
- bg = isActive ? activeColor || '#56a6f8' : '#fff',
18
17
  color = invertColorWhenActive && isActive ? '#fff' : '#000';
19
18
 
20
19
  if (!props.icon) {
@@ -24,21 +23,26 @@ export default function SquareButton(props) {
24
23
  throw Error('text missing. If you want to hide the text, use showText={false}');
25
24
  }
26
25
 
26
+ let className = `
27
+ SquareButton
28
+ rounded-md
29
+ p-2
30
+ h-[100px]
31
+ w-[100px]
32
+ flex
33
+ flex-col
34
+ justify-center
35
+ items-center
36
+ bg-grey-200
37
+ hover:bg-grey-400
38
+ disabled:bg-grey-100
39
+ `;
40
+ if (isActive && activeClassName) {
41
+ className += ' ' + activeClassName;
42
+ }
43
+
27
44
  return <IconButton
28
- className={`
29
- SquareButton
30
- rounded-md
31
- p-2
32
- bg-[${bg}]
33
- hover:bg-[${bg}]
34
- disabled:bg-[${bg}]
35
- h-[100px]
36
- w-[100px]
37
- flex
38
- flex-col
39
- justify-center
40
- items-center
41
- `}
45
+ className={className}
42
46
  style={{
43
47
  // backgroundColor: bg,
44
48
  }}
@@ -417,7 +417,7 @@ function GridComponent(props) {
417
417
  if (canUser && !canUser(VIEW)) { // permissions
418
418
  return;
419
419
  }
420
- onView(true);
420
+ onView(!props.isEditorViewOnly);
421
421
  }
422
422
  } else {
423
423
  if (onEdit) {
@@ -1,4 +1,4 @@
1
- import { useEffect, useState, useRef, } from 'react';
1
+ import { forwardRef, useEffect, useState, useRef, } from 'react';
2
2
  import {
3
3
  ADD,
4
4
  EDIT,
@@ -11,6 +11,7 @@ import {
11
11
  EDITOR_MODE__ADD,
12
12
  EDITOR_MODE__EDIT,
13
13
  EDITOR_TYPE__SIDE,
14
+ EDITOR_TYPE__INLINE,
14
15
  } from '../../../Constants/Editor.js';
15
16
  import Button from '../../Buttons/Button.js';
16
17
  import UiGlobals from '../../../UiGlobals.js';
@@ -20,10 +21,10 @@ import _ from 'lodash';
20
21
  // This HOC will eventually get out of sync with that one, and may need to be updated.
21
22
 
22
23
  export default function withSecondaryEditor(WrappedComponent, isTree = false) {
23
- return (props) => {
24
+ return forwardRef((props, ref) => {
24
25
 
25
26
  if (props.secondaryDisableWithEditor) {
26
- return <WrappedComponent {...props} isTree={isTree} />;
27
+ return <WrappedComponent {...props} ref={ref} isTree={isTree} />;
27
28
  }
28
29
 
29
30
  let [secondaryEditorMode, secondarySetEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
@@ -52,6 +53,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
52
53
  secondaryNewEntityDisplayValue,
53
54
  secondaryNewEntityDisplayProperty, // in case the field to set for newEntityDisplayValue is different from model
54
55
  secondaryDefaultValues,
56
+ secondaryStayInEditModeOnSelectionChange = false,
55
57
 
56
58
  // withComponent
57
59
  self,
@@ -132,7 +134,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
132
134
  // NOTE: This is a hack to prevent adding a new record while the repository is still loading.
133
135
  // This can happen when the repository is still loading, and the user clicks the 'Add' button.
134
136
  setTimeout(() => {
135
- doAdd(e, values);
137
+ secondaryDoAdd(e, values);
136
138
  }, 500);
137
139
  return;
138
140
  }
@@ -169,7 +171,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
169
171
  if (!secondarySelection[0]) {
170
172
  throw Error('Must select a parent node.');
171
173
  }
172
- addValues.parentId = secondarySelection[0].id;
174
+ const parent = secondarySelection[0];
175
+ addValues.parentId = parent.id;
176
+ addValues.depth = parent.depth +1;
173
177
  } else {
174
178
  // Set repository to sort by id DESC and switch to page 1, so this new entity is guaranteed to show up on the current page, even after saving
175
179
  const currentSorter = SecondaryRepository.sorters[0];
@@ -200,10 +204,13 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
200
204
  setIsSaving(false);
201
205
  setSecondaryIsIgnoreNextSelectionChange(true);
202
206
  secondarySetSelection([entity]);
207
+ if (getListeners().onAfterAdd) {
208
+ await getListeners().onAfterAdd(entity);
209
+ }
203
210
  if (SecondaryRepository.isAutoSave) {
204
211
  // for isAutoSave Repositories, submit the handers right away
205
- if (getListeners().onAfterAdd) {
206
- await getListeners().onAfterAdd(entity);
212
+ if (getListeners().onAfterAddSave) {
213
+ await getListeners().onAfterAddSave(entity);
207
214
  }
208
215
  if (secondaryOnAdd) {
209
216
  await secondaryOnAdd(entity);
@@ -331,6 +338,13 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
331
338
  showPermissionsError(VIEW, secondaryModel);
332
339
  return;
333
340
  }
341
+ if (secondaryEditorType === EDITOR_TYPE__INLINE) {
342
+ alert('Cannot view in inline editor.');
343
+ return; // inline editor doesn't have a view mode
344
+ }
345
+
346
+ // check permissions for view
347
+
334
348
  if (secondarySelection.length !== 1) {
335
349
  return;
336
350
  }
@@ -350,6 +364,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
350
364
  showPermissionsError(DUPLICATE, secondaryModel);
351
365
  return;
352
366
  }
367
+
368
+ // check permissions for duplicate
369
+
370
+
353
371
  if (secondarySelection.length !== 1) {
354
372
  return;
355
373
  }
@@ -436,8 +454,8 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
436
454
  if (secondaryOnAdd) {
437
455
  await secondaryOnAdd(secondarySelection);
438
456
  }
439
- if (getListeners().onAfterAdd) {
440
- await getListeners().onAfterAdd(secondarySelection);
457
+ if (getListeners().onAfterAddSave) {
458
+ await getListeners().onAfterAddSave(secondarySelection);
441
459
  }
442
460
  setIsAdding(false);
443
461
  if (!canUser || canUser(EDIT, secondaryModel)) {
@@ -500,6 +518,16 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
500
518
  });
501
519
  },
502
520
  calculateEditorMode = (secondaryIsIgnoreNextSelectionChange = false) => {
521
+
522
+ let doStayInEditModeOnSelectionChange = secondaryStayInEditModeOnSelectionChange;
523
+ if (!_.isNil(UiGlobals.stayInEditModeOnSelectionChange)) {
524
+ // allow global override to for this property
525
+ doStayInEditModeOnSelectionChange = UiGlobals.stayInEditModeOnSelectionChange;
526
+ }
527
+ if (doStayInEditModeOnSelectionChange) {
528
+ secondaryIsIgnoreNextSelectionChange = true;
529
+ }
530
+
503
531
  // calculateEditorMode gets called only on selection changes
504
532
  let mode;
505
533
  if (secondaryEditorType === EDITOR_TYPE__SIDE && !_.isNil(UiGlobals.isSideEditorAlwaysEditMode) && UiGlobals.isSideEditorAlwaysEditMode) {
@@ -579,6 +607,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
579
607
 
580
608
  return <WrappedComponent
581
609
  {...props}
610
+ ref={ref}
582
611
  secondaryDisableWithEditor={false}
583
612
  secondaryCurrentRecord={secondaryCurrentRecord}
584
613
  secondarySetCurrentRecord={secondarySetCurrentRecord}
@@ -612,5 +641,5 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
612
641
  secondarySetSelection={secondarySetSelectionDecorated}
613
642
  isTree={isTree}
614
643
  />;
615
- };
644
+ });
616
645
  }
@@ -1,3 +1,4 @@
1
+ import { forwardRef } from 'react';
1
2
  import {
2
3
  EDITOR_TYPE__SIDE,
3
4
  } from '../../../Constants/Editor.js';
@@ -10,20 +11,21 @@ import _ from 'lodash';
10
11
 
11
12
 
12
13
  function withAdditionalProps(WrappedComponent) {
13
- return (props) => {
14
+ return forwardRef((props, ref) => {
14
15
  // provide the editorType to withEditor
15
16
  return <WrappedComponent
16
17
  editorType={EDITOR_TYPE__SIDE}
17
18
  {...props}
19
+ ref={ref}
18
20
  />;
19
- };
21
+ });
20
22
  }
21
23
 
22
24
  // NOTE: Effectivtly, the HOC composition is:
23
25
  // withAdditionalProps(withSecondaryEditor(withSecondarySideEditor))
24
26
 
25
27
  export default function withSecondarySideEditor(WrappedComponent, isTree = false) {
26
- return withAdditionalProps(withSecondaryEditor((props) => {
28
+ const SideEditor = forwardRef((props, ref) => {
27
29
  const {
28
30
  SecondaryEditor,
29
31
  secondaryEditorProps = {},
@@ -36,6 +38,7 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
36
38
  secondarySelectorId,
37
39
  secondarySelectorSelected,
38
40
  secondarySelectorSelectedField,
41
+ style,
39
42
 
40
43
  ...propsToPass
41
44
  } = props;
@@ -44,8 +47,23 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
44
47
  throw Error('SecondaryEditor is not defined');
45
48
  }
46
49
 
50
+ if (isResizable) {
51
+ secondaryEditorProps.w = 500;
52
+ secondaryEditorProps.isResizable = true;
53
+ } else {
54
+ secondaryEditorProps.flex = secondarySideFlex;
55
+ }
56
+
57
+ if (!secondaryEditorProps.className) {
58
+ secondaryEditorProps.className = '';
59
+ }
60
+ secondaryEditorProps.className += ' border-l-1 border-l-grey-300';
61
+
47
62
  return <Container
63
+ parent={self}
64
+ reference="SideEditor"
48
65
  center={<WrappedComponent
66
+ ref={ref}
49
67
  isTree={isTree}
50
68
  isSideEditor={true}
51
69
  {...props}
@@ -53,13 +71,11 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
53
71
  east={<Editor
54
72
  {...propsToPass}
55
73
  editorType={EDITOR_TYPE__SIDE}
56
- flex={secondarySideFlex}
57
- borderLeftWidth={1}
58
- borderLeftColor="#ccc"
59
74
  {...secondaryEditorProps}
60
75
  parent={self}
61
76
  reference="secondaryEditor"
62
77
  />}
63
78
  />;
64
- }));
79
+ });
80
+ return withAdditionalProps(withSecondaryEditor(SideEditor, isTree));
65
81
  }
@@ -1,3 +1,4 @@
1
+ import { forwardRef } from 'react';
1
2
  import {
2
3
  Modal, ModalBackdrop, ModalHeader, ModalContent, ModalCloseButton, ModalBody, ModalFooter,
3
4
  } from '@project-components/Gluestack';
@@ -13,20 +14,21 @@ import _ from 'lodash';
13
14
 
14
15
 
15
16
  function withAdditionalProps(WrappedComponent) {
16
- return (props) => {
17
+ return forwardRef((props, ref) => {
17
18
  // provide the editorType to withEditor
18
19
  return <WrappedComponent
19
20
  editorType={EDITOR_TYPE__WINDOWED}
20
21
  {...props}
22
+ ref={ref}
21
23
  />;
22
- };
24
+ });
23
25
  }
24
26
 
25
27
  // NOTE: Effectivtly, the HOC composition is:
26
28
  // withAdditionalProps(withSecondaryEditor(withSecondaryWindowedEditor))
27
29
 
28
30
  export default function withSecondaryWindowedEditor(WrappedComponent, isTree = false) {
29
- return withAdditionalProps(withSecondaryEditor((props) => {
31
+ const WindowedEditor = forwardRef((props, ref) => {
30
32
  const {
31
33
  secondaryIsEditorShown = false,
32
34
  secondarySetIsEditorShown,
@@ -41,6 +43,7 @@ export default function withSecondaryWindowedEditor(WrappedComponent, isTree = f
41
43
  secondarySelectorSelected,
42
44
  secondarySelectorSelectedField,
43
45
  h,
46
+ style,
44
47
 
45
48
  ...propsToPass
46
49
  } = props;
@@ -65,25 +68,28 @@ export default function withSecondaryWindowedEditor(WrappedComponent, isTree = f
65
68
  }
66
69
 
67
70
  return <>
68
- <WrappedComponent {...props} />
71
+ <WrappedComponent {...props} ref={ref} />
69
72
  {secondaryIsEditorShown &&
70
73
  <Modal
71
74
  isOpen={true}
72
75
  onClose={() => secondarySetIsEditorShown(false)}
76
+ className="withSecondaryEditor-Modal"
73
77
  >
74
- <ModalBackdrop />
75
- <ModalContent>
76
- <ModalBody>
77
- <SecondaryEditor
78
- editorType={EDITOR_TYPE__WINDOWED}
79
- {...propsToPass}
80
- {...secondaryEditorProps}
81
- parent={self}
82
- reference="secondaryEditor"
83
- />
84
- </ModalBody>
85
- </ModalContent>
78
+ <ModalBackdrop className="withSecondaryEditor-ModalBackdrop" />
79
+ <SecondaryEditor
80
+ editorType={EDITOR_TYPE__WINDOWED}
81
+ {...propsToPass}
82
+ {...secondaryEditorProps}
83
+ parent={self}
84
+ reference="secondaryEditor"
85
+ className={`
86
+ bg-white
87
+ shadow-lg
88
+ rounded-lg
89
+ `}
90
+ />
86
91
  </Modal>}
87
92
  </>;
88
- }, isTree));
93
+ });
94
+ return withAdditionalProps(withSecondaryEditor(WindowedEditor, isTree));
89
95
  }
@@ -163,13 +163,14 @@ export default function withModal(WrappedComponent) {
163
163
  <Panel
164
164
  title={title}
165
165
  isCollapsible={false}
166
- className="bg-white overflow-auto"
166
+ className="withModal-Panel bg-white"
167
167
  h={h > windowHeight ? windowHeight : h}
168
168
  w={w > windowWidth ? windowWidth : w}
169
169
  isWindow={true}
170
170
  disableAutoFlex={true}
171
171
  onClose={canClose ? hideModal : null}
172
172
  footer={footer}
173
+ isScrollable={true}
173
174
  >{modalBody}</Panel>
174
175
  }
175
176
  }
@@ -83,7 +83,13 @@ export default function withPdfButtons(WrappedComponent) {
83
83
 
84
84
  if (!_.isEmpty(ancillaryItems)) {
85
85
  const
86
- ancillaryItemsClone = _.cloneDeep(ancillaryItems),
86
+ ancillaryItemsClone = _.cloneDeepWith(ancillaryItems, (value) => {
87
+ // Exclude the 'parent' property from being cloned, as it would introduce an infinitely recursive loop
88
+ if (value && value.parent) {
89
+ const { parent, ...rest } = value;
90
+ return rest;
91
+ }
92
+ }),
87
93
  items = [];
88
94
  _.each(ancillaryItemsClone, (ancillaryItem) => { // clone, as we don't want to alter the item by reference
89
95
  let name;
@@ -3,7 +3,7 @@ import { Path, Svg } from 'react-native-svg';
3
3
 
4
4
  const SvgComponent = createIcon({
5
5
  Root: Svg,
6
- viewBox: '0 0 512 512',
6
+ viewBox: '0 0 101.44 83.8',
7
7
  path: <Path d="M58.92 4.5L99.9 68.86c4.12 6.47-.53 14.94-8.2 14.94H9.74c-7.67 0-12.32-8.47-8.2-14.94L42.52 4.5c3.82-6 12.58-6 16.4 0zm-8.2 68.21c3.24 0 5.34-2.34 5.34-5.46-.06-3.18-2.16-5.46-5.34-5.46s-5.4 2.28-5.4 5.46 2.16 5.46 5.4 5.46zm3.42-13.92l1.32-27.18h-9.54l1.38 27.18h6.84z" strokeWidth={0} />
8
8
  });
9
9
 
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  HStack,
3
+ Icon,
3
4
  Text,
4
5
  VStackNative,
5
6
  } from '@project-components/Gluestack';
@@ -98,7 +99,16 @@ function ManagerScreen(props) {
98
99
  className="max-h-screen overflow-hidden flex-1 w-full"
99
100
  >
100
101
  <HStack className="h-[80px] items-center border-b-[2px] border-b-[#ccc]">
101
- <Text {...textProps} className="pl-5 text-[26px] font-[700]">{title}</Text>
102
+ {props.icon ?
103
+ <Icon
104
+ as={props.icon}
105
+ className={`
106
+ ml-5
107
+ `}
108
+ size="xl"
109
+ color="#000"
110
+ /> : null}
111
+ <Text {...textProps} className="pl-4 text-[26px] font-[700]">{title}</Text>
102
112
  {allowSideBySide &&
103
113
  <>
104
114
  <IconButton
@@ -24,9 +24,11 @@ export function getDomNodes(selectors, options = {}) {
24
24
 
25
25
  /**
26
26
  * Builds selector string for data-testid attributes.
27
- * @argument {string | string[]} selectors - data-testid attribute values
28
- * If an array is given, these will be considered nested selectors.
27
+ * It leaves classname, id, and attribute selectors unchanged.
28
+ *
29
+ * If selectors is an array, these will be considered nested selectors.
29
30
  * e.g. ['parent', 'child'] will be converted to '[data-testid="parent"] [data-testid="child"]'
31
+ * @argument {string|string[]} selectors - data-testid attribute values
30
32
  * @return {string}
31
33
  */
32
34
  export function getTestIdSelectors(selectors, isGetFirst = false) {
@@ -34,16 +36,47 @@ export function getTestIdSelectors(selectors, isGetFirst = false) {
34
36
  selectors = [selectors];
35
37
  }
36
38
  const selectorParts = _.map(selectors, (selector) => {
37
- if (selector.match(/=/)) { // selector is something like [role="switch"], so don't use data-testid
38
- return selector;
39
- }
40
- if (selector.match(/^\./)) { // selector is something like .my-class, so don't use data-testid
41
- return selector;
39
+ if (!selector.match(/^\./) // className, like .my-class
40
+ && !selector.match(/^#/) // id, like @my-id
41
+ && !selector.match(/=/) // attribute, like [role="switch"]
42
+ ){
43
+ selector = '[data-testid="' + selector + '"]';
42
44
  }
43
- if (selector.match(/^#/)) { // selector is something like #my-id, so don't use data-testid
44
- return selector;
45
+ if (isGetFirst) {
46
+ selector += ':first';
45
47
  }
46
- return '[data-testid="' + selector + '"]' + (isGetFirst ? ':first' : '');
48
+ return selector;
47
49
  });
48
50
  return selectorParts.join(' ');
49
51
  }
52
+
53
+ export function drag(draggableSelectors, droppableSelectors, options = {}) {
54
+ if (typeof options.force === 'undefined') {
55
+ options.force = true;
56
+ }
57
+
58
+ // getDomNode(getTestIdSelectors(droppableSelectors)).then((node) => {
59
+ // const selectors = getTestIdSelectors(droppableSelectors);
60
+ // debugger;
61
+ // });
62
+
63
+ options = {
64
+ source: { // applies to the element being dragged
65
+ x: 10,
66
+ // y: 100,
67
+ position: 'left',
68
+ },
69
+ target: { // applies to the drop target
70
+ position: 'left',
71
+ x: 20,
72
+ },
73
+ force: true, // applied to both the source and target element
74
+ };
75
+
76
+
77
+ return getDomNode(draggableSelectors)
78
+ .drag(getTestIdSelectors(droppableSelectors), options)
79
+ .then((success) => {
80
+ debugger;
81
+ });
82
+ }
@@ -1,6 +1,6 @@
1
1
  html, body {
2
- /* -webkit-user-select: none;
3
- user-select: none; */
2
+ -webkit-user-select: none;
3
+ user-select: none;
4
4
  }
5
5
 
6
6
  /* to fix the inline editor */