@onehat/ui 0.4.100 → 0.4.102

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.100",
3
+ "version": "0.4.102",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -100,6 +100,8 @@ export const ComboComponent = forwardRef((props, ref) => {
100
100
  onGridSave, // to hook into when menu saves (ComboEditor only)
101
101
  onGridDelete, // to hook into when menu deletes (ComboEditor only)
102
102
  onSubmit, // when Combo is used in a Tag, call this when the user submits the Combo value (i.e. presses Enter or clicks a row)
103
+ displayProperty, // override for Repository.schema.model.displayProperty
104
+ valueProperty, // override for Repository.schema.model.idProperty
103
105
  newEntityDisplayProperty,
104
106
  testID,
105
107
 
@@ -242,7 +244,7 @@ export const ComboComponent = forwardRef((props, ref) => {
242
244
  displayValue = _.each(value, (id) => {
243
245
  const entity = Repository.getById(id);
244
246
  if (entity) {
245
- displayValue.push(entity.displayValue);
247
+ displayValue.push((displayProperty ? entity?.[displayProperty] : entity?.displayValue) || '');
246
248
  }
247
249
  });
248
250
  }
@@ -272,7 +274,7 @@ export const ComboComponent = forwardRef((props, ref) => {
272
274
  }
273
275
  }
274
276
  }
275
- displayValue = entity?.displayValue || '';
277
+ displayValue = (displayProperty ? entity?.[displayProperty] : entity?.displayValue) || '';
276
278
  }
277
279
  } else {
278
280
  const item = _.find(data, (datum) => datum[idIx] === value);
@@ -318,7 +320,11 @@ export const ComboComponent = forwardRef((props, ref) => {
318
320
 
319
321
  let id = null;
320
322
  if (gridSelection.length) {
321
- id = Repository ? gridSelection[0].id : gridSelection[0][idIx];
323
+ if (Repository) {
324
+ id = valueProperty ? gridSelection[0][valueProperty] : gridSelection[0].id;
325
+ } else {
326
+ id = gridSelection[0][idIx];
327
+ }
322
328
  }
323
329
  if (id !== value) {
324
330
  setValue(id);
@@ -863,7 +869,7 @@ export const ComboComponent = forwardRef((props, ref) => {
863
869
  return;
864
870
  }
865
871
 
866
- setValue(selection[0] ? selection[0].id : null);
872
+ setValue(selection[0] ? (valueProperty ? selection[0][valueProperty] : selection[0].id) : null);
867
873
 
868
874
  } else {
869
875
 
@@ -545,10 +545,16 @@ function TagComponent(props) {
545
545
 
546
546
  function withAdditionalProps(WrappedComponent) {
547
547
  return (props) => {
548
+ const tooltipTriggerClassName = clsx(
549
+ 'w-full',
550
+ 'flex-1',
551
+ props.tooltipTriggerClassName,
552
+ );
548
553
  return <WrappedComponent
549
554
  isValueAlwaysArray={true}
550
555
  isValueAsStringifiedJson={true}
551
556
  {...props}
557
+ tooltipTriggerClassName={tooltipTriggerClassName}
552
558
  />;
553
559
  };
554
560
  }
@@ -147,6 +147,7 @@ function GridComponent(props) {
147
147
  showHeaders = true,
148
148
  showHovers = true,
149
149
  showSelectHandle = true,
150
+ isRowTextSelectable, // if false, user can't select text in rows (e.g. to copy/paste)
150
151
  isRowSelectable = true,
151
152
  isRowHoverable = true,
152
153
  isDisabled = false,
@@ -201,6 +202,7 @@ function GridComponent(props) {
201
202
  },
202
203
  dragPreviewOptions, // optional object for drag preview positioning options
203
204
  areRowsDragSource = false,
205
+ areRowsDragFromHandleOnly,
204
206
  rowDragSourceType,
205
207
  getRowDragSourceItem,
206
208
  areRowsDropTarget = false,
@@ -641,10 +643,23 @@ function GridComponent(props) {
641
643
 
642
644
  const userHasPermissionToDrag = (!canUser || canUser(EDIT));
643
645
  if (userHasPermissionToDrag) {
646
+
647
+ // Determine whether dragging should only be allowed from a handle, based on global default and grid prop override
648
+ let dragFromRowHandleOnly = true;
649
+ if (!_.isNil(UiGlobals.gridAreRowsDragFromHandleOnly)) {
650
+ // allow global default
651
+ dragFromRowHandleOnly = UiGlobals.gridAreRowsDragFromHandleOnly;
652
+ }
653
+ if (!_.isNil(areRowsDragFromHandleOnly)) {
654
+ // allow grid props override
655
+ dragFromRowHandleOnly = areRowsDragFromHandleOnly;
656
+ }
657
+
644
658
  // assign event handlers
645
659
  if (canRowsReorder && isReorderMode) {
646
660
  WhichRow = DragSourceGridRow;
647
661
  rowReorderProps.isDragSource = true;
662
+ rowReorderProps.isDragFromHandleOnly = dragFromRowHandleOnly;
648
663
  rowReorderProps.dragSourceType = 'row';
649
664
  const dragIx = showHeaders ? index - 1 : index;
650
665
  rowReorderProps.dragSourceItem = {
@@ -656,6 +671,14 @@ function GridComponent(props) {
656
671
  onRowReorderDrag(dragState, dragIx);
657
672
  },
658
673
  };
674
+ // Add custom drag preview options
675
+ if (dragPreviewOptions) {
676
+ rowReorderProps.dragPreviewOptions = dragPreviewOptions;
677
+ }
678
+ // Add drag preview rendering
679
+ rowReorderProps.getDragProxy = getCustomDragProxy ?
680
+ (dragItem) => getCustomDragProxy(item, getSelection()) :
681
+ null; // Let GlobalDragProxy handle the default case
659
682
  rowReorderProps.onDragEnd = onRowReorderEnd;
660
683
  rowCanDrag = true;
661
684
  } else {
@@ -664,6 +687,7 @@ function GridComponent(props) {
664
687
  WhichRow = DragSourceGridRow;
665
688
  rowDragProps.isDragSource = true;
666
689
  rowDragProps.dragSourceType = rowDragSourceType;
690
+ rowDragProps.isDragFromHandleOnly = dragFromRowHandleOnly;
667
691
  if (getRowDragSourceItem) {
668
692
  rowDragProps.dragSourceItem = getRowDragSourceItem(item, getSelection, isInSelection, rowDragSourceType);
669
693
  // Ensure all drag items have a component reference
@@ -766,6 +790,7 @@ function GridComponent(props) {
766
790
  areCellsScrollable={areCellsScrollable}
767
791
  showHovers={showHovers}
768
792
  showRowHandle={showRowHandle}
793
+ isRowTextSelectable={isRowTextSelectable}
769
794
  rowCanSelect={rowCanSelect}
770
795
  rowCanDrag={rowCanDrag}
771
796
  index={index}
@@ -38,6 +38,7 @@ const GridRow = forwardRef((props, ref) => {
38
38
  rowProps,
39
39
  hideNavColumn,
40
40
  showRowHandle,
41
+ isRowTextSelectable,
41
42
  areCellsScrollable,
42
43
  rowCanSelect,
43
44
  rowCanDrag,
@@ -53,6 +54,7 @@ const GridRow = forwardRef((props, ref) => {
53
54
  isInlineEditorShown,
54
55
  isDraggable = false, // withDraggable
55
56
  isDragSource = false, // withDnd
57
+ isDragFromHandleOnly = true,
56
58
  isOver = false, // drop target
57
59
  canDrop,
58
60
  draggedItem,
@@ -124,7 +126,24 @@ const GridRow = forwardRef((props, ref) => {
124
126
  }
125
127
  const
126
128
  visibleColumns = _.filter(columnsConfig, (config) => !config.isHidden),
127
- isOnlyOneVisibleColumn = visibleColumns.length === 1;
129
+ isOnlyOneVisibleColumn = visibleColumns.length === 1,
130
+ canSelectTextOnRow = isRowTextSelectable === false ? false : isDragFromHandleOnly,
131
+ shouldUseTextCursor = showRowHandle && canSelectTextOnRow,
132
+ rowShouldHaveDragRef = !isDragFromHandleOnly && (isDragSource || isDraggable) && !!dragSourceRef;
133
+ const setRowRef = (node) => {
134
+ if (typeof ref === 'function') {
135
+ ref(node);
136
+ } else if (ref) {
137
+ ref.current = node;
138
+ }
139
+ if (rowShouldHaveDragRef) {
140
+ if (typeof dragSourceRef === 'function') {
141
+ dragSourceRef(node);
142
+ } else if (dragSourceRef) {
143
+ dragSourceRef.current = node;
144
+ }
145
+ }
146
+ };
128
147
 
129
148
  const renderColumns = (item) => {
130
149
  if (_.isArray(columnsConfig)) {
@@ -136,7 +155,7 @@ const GridRow = forwardRef((props, ref) => {
136
155
  const
137
156
  propsToPass = columnProps[key] || {},
138
157
  colStyle = {},
139
- whichCursor = showRowHandle ? 'cursor-text' : 'cursor-pointer'; // when using rowSelectHandle, indicate that the row text is selectable, otherwise indicate that the row itself is selectable
158
+ whichCursor = shouldUseTextCursor ? 'cursor-text' : 'cursor-pointer';
140
159
  let colClassName = clsx(
141
160
  'GridRow-column',
142
161
  'p-1',
@@ -144,6 +163,7 @@ const GridRow = forwardRef((props, ref) => {
144
163
  'border-r-black-100',
145
164
  'block',
146
165
  areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
166
+ canSelectTextOnRow ? null : 'select-none',
147
167
  whichCursor,
148
168
  styles.GRID_ROW_MAX_HEIGHT_EXTRA,
149
169
  );
@@ -395,7 +415,7 @@ const GridRow = forwardRef((props, ref) => {
395
415
  let rowContents = <>
396
416
  {showRowHandle &&
397
417
  <RowHandle
398
- ref={dragSourceRef}
418
+ ref={isDragFromHandleOnly ? dragSourceRef : undefined}
399
419
  isDragSource={isDragSource}
400
420
  isDraggable={isDraggable}
401
421
  canSelect={rowCanSelect}
@@ -448,6 +468,7 @@ const GridRow = forwardRef((props, ref) => {
448
468
  let rowClassName = clsx(
449
469
  'GridRow-HStackNative',
450
470
  'items-center',
471
+ canSelectTextOnRow ? null : 'select-none',
451
472
  );
452
473
  if (isOnlyOneVisibleColumn) {
453
474
  rowClassName += ' w-full';
@@ -459,7 +480,7 @@ const GridRow = forwardRef((props, ref) => {
459
480
  rowClassName += ' border-4 border-[#0ff]';
460
481
  }
461
482
  let row = <HStackNative
462
- ref={ref}
483
+ ref={rowShouldHaveDragRef ? setRowRef : ref}
463
484
  {...testProps('Row ' + (isSelected ? 'row-selected' : ''))}
464
485
  {...rowProps}
465
486
  key={hash}
@@ -496,8 +517,13 @@ const GridRow = forwardRef((props, ref) => {
496
517
  dragPreviewRef,
497
518
  dropTargetRef,
498
519
  showRowHandle,
520
+ isRowTextSelectable,
499
521
  rowCanSelect,
500
522
  rowCanDrag,
523
+ isDragFromHandleOnly,
524
+ isDragSource,
525
+ isDraggable,
526
+ dragSourceRef,
501
527
  ]);
502
528
  });
503
529
 
@@ -222,15 +222,18 @@ export function GlobalDragProxy() {
222
222
  const {
223
223
  isDragging,
224
224
  item,
225
- currentOffset,
225
+ clientOffset,
226
+ sourceClientOffset,
226
227
  } = useDragLayer((monitor) => {
227
228
  return {
228
229
  isDragging: monitor.isDragging(),
229
230
  item: monitor.getItem(),
230
- currentOffset: monitor.getSourceClientOffset(),
231
+ clientOffset: monitor.getClientOffset(),
232
+ sourceClientOffset: monitor.getSourceClientOffset(),
231
233
  };
232
234
  });
233
235
 
236
+ const currentOffset = clientOffset || sourceClientOffset;
234
237
  if (!isDragging || !currentOffset || CURRENT_MODE !== UI_MODE_WEB) { // Native uses a native drag layer, so we don't need to render a custom proxy
235
238
  return null;
236
239
  }
@@ -0,0 +1,11 @@
1
+ import { createIcon } from "../Gluestack/icon";
2
+ // Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.
3
+ import { Path, Svg } from 'react-native-svg';
4
+
5
+ const SvgComponent = createIcon({
6
+ Root: Svg,
7
+ viewBox: '0 0 640 640',
8
+ path: <Path d="M256 160v64h128v-64c0-35.3-28.7-64-64-64s-64 28.7-64 64zm-64 64v-64c0-70.7 57.3-128 128-128s128 57.3 128 128v64c35.3 0 64 28.7 64 64v224c0 35.3-28.7 64-64 64H192c-35.3 0-64-28.7-64-64V288c0-35.3 28.7-64 64-64z" />,
9
+ });
10
+
11
+ export default SvgComponent
@@ -8,44 +8,63 @@ import {
8
8
  import clsx from 'clsx';
9
9
  import ChartPie from '../Icons/ChartPie.js';
10
10
  import ScreenHeader from '../Layout/ScreenHeader.js';
11
+ import TabBar from '../Tab/TabBar.js';
12
+ import _ from 'lodash';
11
13
 
12
14
  const CONTAINER_THRESHOLD = 1100;
13
15
 
14
16
  export default function ReportsManager(props) {
15
17
  const {
16
18
  reports = [],
19
+ reportTabs,
20
+ initialReportTabIx = 0,
21
+ id,
22
+ self,
17
23
  isActive = false,
18
24
  } = props,
19
25
  [containerWidth, setContainerWidth] = useState(),
20
26
  onLayout = (e) => {
21
27
  setContainerWidth(e.nativeEvent.layout.width);
28
+ },
29
+ renderReportLayout = (reportsToRender = []) => {
30
+ if (!containerWidth) {
31
+ return null;
32
+ }
33
+
34
+ if (containerWidth >= CONTAINER_THRESHOLD) {
35
+ const
36
+ reportsPerColumn = Math.ceil(reportsToRender.length / 2),
37
+ col1Reports = reportsToRender.slice(0, reportsPerColumn),
38
+ col2Reports = reportsToRender.slice(reportsPerColumn);
39
+ return <HStack className="gap-3">
40
+ <VStack className="flex-1">
41
+ {col1Reports}
42
+ </VStack>
43
+ <VStack className="flex-1">
44
+ {col2Reports}
45
+ </VStack>
46
+ </HStack>;
47
+ }
48
+
49
+ return reportsToRender;
22
50
  };
23
51
 
24
52
  if (!isActive) {
25
53
  return null;
26
54
  }
27
55
 
28
- let reportElements = [];
29
- if (containerWidth) {
30
- if (containerWidth >= CONTAINER_THRESHOLD) {
31
- // two column layout
32
- const
33
- reportsPerColumn = Math.ceil(reports.length / 2),
34
- col1Reports = reports.slice(0, reportsPerColumn),
35
- col2Reports = reports.slice(reportsPerColumn);
36
- reportElements = <HStack className="gap-3">
37
- <VStack className="flex-1">
38
- {col1Reports}
39
- </VStack>
40
- <VStack className="flex-1">
41
- {col2Reports}
42
- </VStack>
43
- </HStack>;
44
- } else {
45
- // one column layout
46
- reportElements = reports;
47
- }
48
- }
56
+ const
57
+ hasReportTabs = _.isArray(reportTabs) && reportTabs.length > 0,
58
+ tabBarId = `${id || self?.path || 'ReportsManager'}-reportTabs`,
59
+ reportElements = renderReportLayout(reports),
60
+ tabBarTabs = hasReportTabs ? _.map(reportTabs, (tab, ix) => ({
61
+ ...tab,
62
+ content: <ScrollView className="flex-1 w-full" key={`reportTabContent-${ix}`}>
63
+ <VStackNative className="w-full p-4" onLayout={onLayout}>
64
+ {renderReportLayout(tab.reports || [])}
65
+ </VStackNative>
66
+ </ScrollView>,
67
+ })) : [];
49
68
 
50
69
  return <VStack
51
70
  className="overflow-hidden flex-1 w-full"
@@ -54,10 +73,16 @@ export default function ReportsManager(props) {
54
73
  title="Reports"
55
74
  icon={ChartPie}
56
75
  />
57
- <ScrollView className="flex-1 w-full">
58
- <VStackNative className="w-full p-4" onLayout={onLayout}>
59
- {containerWidth && reportElements}
60
- </VStackNative>
61
- </ScrollView>
76
+ {hasReportTabs ?
77
+ <TabBar
78
+ id={tabBarId}
79
+ initialTabIx={initialReportTabIx}
80
+ tabs={tabBarTabs}
81
+ /> :
82
+ <ScrollView className="flex-1 w-full">
83
+ <VStackNative className="w-full p-4" onLayout={onLayout}>
84
+ {containerWidth && reportElements}
85
+ </VStackNative>
86
+ </ScrollView>}
62
87
  </VStack>;
63
88
  }
package/src/UiGlobals.js CHANGED
@@ -13,6 +13,7 @@ const Globals = {
13
13
  doubleClickingGridRowOpensEditorInViewMode: false,
14
14
  disableSavedColumnsConfig: true,
15
15
  autoSubmitDelay: 500,
16
+ // gridAreRowsDragFromHandleOnly: true,
16
17
  // stayInEditModeOnSelectionChange: true,
17
18
  // isSideEditorAlwaysEditMode: true,
18
19