@onehat/ui 0.4.65 → 0.4.67

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.65",
3
+ "version": "0.4.67",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -6,6 +6,7 @@ import {
6
6
  import Animated, {
7
7
  useSharedValue,
8
8
  useAnimatedStyle,
9
+ useDerivedValue,
9
10
  withTiming,
10
11
  } from 'react-native-reanimated';
11
12
  import IconButton from '../Buttons/IconButton.js';
@@ -33,6 +34,15 @@ export default function DynamicFab(props) {
33
34
  }, []),
34
35
  buttonSpacing = 45,
35
36
  verticalOffset = 50; // to shift the entire expanded group up
37
+ buttonAnimatedStyle = useAnimatedStyle(() => {
38
+ return {
39
+ opacity: withTiming(isExpanded.value, { duration: 200 }),
40
+ pointerEvents: isExpanded.value ? 'auto' : 'none', // Disable interaction when collapsed
41
+ };
42
+ }),
43
+ isExpandedForRender = useDerivedValue(() => { // Use useDerivedValue to safely read the shared value during render
44
+ return isExpanded.value > 0;
45
+ });
36
46
 
37
47
  let className = `
38
48
  DynamicFab
@@ -55,18 +65,12 @@ export default function DynamicFab(props) {
55
65
  onPress,
56
66
  key,
57
67
  ...btnConfigToPass
58
- } = btnConfig,
59
- animatedStyle = useAnimatedStyle(() => {
60
- return {
61
- opacity: withTiming(isExpanded.value, { duration: 200 }),
62
- pointerEvents: isExpanded.value ? 'auto' : 'none', // Disable interaction when collapsed
63
- };
64
- });
68
+ } = btnConfig;
65
69
 
66
70
  return <Animated.View
67
71
  key={ix}
68
72
  style={[
69
- animatedStyle,
73
+ buttonAnimatedStyle,
70
74
  {
71
75
  position: 'absolute',
72
76
  bottom: buttonSpacing * (ix + 1) + verticalOffset, // Static vertical positioning
@@ -101,8 +105,10 @@ export default function DynamicFab(props) {
101
105
  tooltipClassName={tooltipClassName}
102
106
  tooltipTriggerClassName={tooltipTriggerClassName}
103
107
  >
104
- <FabIcon as={isExpanded.value ? Xmark : icon || EllipsisVertical} />
108
+ <Animated.View>
109
+ <FabIcon as={isExpandedForRender.value ? Xmark : icon || EllipsisVertical} />
110
+ </Animated.View>
105
111
  {label ? <FabLabel>{label}</FabLabel> : null}
106
112
  </FabWithTooltip>
107
113
  </VStack>;
108
- };
114
+ }
@@ -162,9 +162,9 @@ function Form(props) {
162
162
  let skipAll = false;
163
163
  if (record?.isDestroyed) {
164
164
  skipAll = true; // if record is destroyed, skip render, but allow hooks to still be called
165
- if (self?.parent?.parent?.setIsEditorShown) {
166
- self.parent.parent.setIsEditorShown(false); // close the editor
167
- }
165
+ // if (self?.parent?.parent?.setIsEditorShown) {
166
+ // self.parent.parent.setIsEditorShown(false); // close the editor
167
+ // }
168
168
  }
169
169
  const
170
170
  isMultiple = _.isArray(record),
@@ -363,7 +363,7 @@ function Form(props) {
363
363
  style.width = boxW;
364
364
  }
365
365
  elements.push(<Box
366
- key={ix}
366
+ key={fieldName + '-' + ix}
367
367
  className={columnClassName}
368
368
  style={style}
369
369
  >{element}</Box>);
@@ -467,7 +467,7 @@ function Form(props) {
467
467
  `}
468
468
  /> : null;
469
469
  return <HStack
470
- key={ix}
470
+ key={fieldName + '-HStack-' + ix}
471
471
  className={`
472
472
  Form-HStack1
473
473
  flex-${flex}
@@ -624,7 +624,7 @@ function Form(props) {
624
624
  itemDefaultsToPass = itemDefaults;
625
625
  }
626
626
  return <Element
627
- key={ix}
627
+ key={'column-Element-' + type + '-' + ix}
628
628
  title={title}
629
629
  {...defaultsToPass}
630
630
  {...itemDefaultsToPass}
@@ -697,7 +697,7 @@ function Form(props) {
697
697
  </VStack>;
698
698
  }
699
699
  }
700
- return <HStack key={ix} className="Form-HStack3 w-full px-2 pb-1">{element}</HStack>;
700
+ return <HStack key={'Form-HStack3-' + ix} className="Form-HStack3 w-full px-2 pb-1">{element}</HStack>;
701
701
  }
702
702
 
703
703
 
@@ -908,7 +908,7 @@ function Form(props) {
908
908
  `}
909
909
  /> : null;
910
910
  return <HStack
911
- key={ix}
911
+ key={'Controller-HStack-' + ix}
912
912
  className={`
913
913
  Form-HStack11
914
914
  min-h-[50px]
@@ -930,6 +930,7 @@ function Form(props) {
930
930
 
931
931
  // add the "scroll to top" button
932
932
  getAncillaryButtons().push({
933
+ key: 'scrollToTop',
933
934
  icon: ArrowUp,
934
935
  reference: 'scrollToTop',
935
936
  onPress: () => scrollToAncillaryItem(0),
@@ -938,14 +939,15 @@ function Form(props) {
938
939
 
939
940
  _.each(ancillaryItems, (item, ix) => {
940
941
  let {
941
- type,
942
- title = null,
943
- description = null,
944
- icon,
945
- selectorId,
946
- selectorSelectedField,
947
- ...itemPropsToPass
948
- } = item;
942
+ type,
943
+ title = null,
944
+ description = null,
945
+ icon,
946
+ selectorId,
947
+ selectorSelectedField,
948
+ ...itemPropsToPass
949
+ } = item,
950
+ titleElement;
949
951
  if (isMultiple && type !== 'Attachments') {
950
952
  return;
951
953
  }
@@ -953,6 +955,7 @@ function Form(props) {
953
955
  // NOTE: this assumes that if one Ancillary item has an icon, they all do.
954
956
  // If they don't, the ix will be wrong!
955
957
  getAncillaryButtons().push({
958
+ key: 'ancillaryBtn-' + ix,
956
959
  icon,
957
960
  onPress: () => scrollToAncillaryItem(ix +1), // offset for the "scroll to top" button
958
961
  tooltip: title,
@@ -977,15 +980,15 @@ function Form(props) {
977
980
  if (record?.displayValue) {
978
981
  title += ' for ' + record.displayValue;
979
982
  }
980
- title = <Text
981
- className={`
982
- Form-Ancillary-Title
983
- font-bold
984
- ${styles.FORM_ANCILLARY_TITLE_CLASSNAME}
985
- `}
986
- >{title}</Text>;
983
+ titleElement = <Text
984
+ className={`
985
+ Form-Ancillary-Title
986
+ font-bold
987
+ ${styles.FORM_ANCILLARY_TITLE_CLASSNAME}
988
+ `}
989
+ >{title}</Text>;
987
990
  if (icon) {
988
- title = <HStack className="items-center"><Icon as={icon} className="w-[32px] h-[32px] mr-2" />{title}</HStack>
991
+ titleElement = <HStack className="items-center"><Icon as={icon} className="w-[32px] h-[32px] mr-2" />{titleElement}</HStack>
989
992
  }
990
993
  }
991
994
  if (description) {
@@ -1006,7 +1009,7 @@ function Form(props) {
1006
1009
  my-3
1007
1010
  `}
1008
1011
  >
1009
- {title}
1012
+ {titleElement}
1010
1013
  {description}
1011
1014
  {element}
1012
1015
  </VStack>);
@@ -1369,13 +1372,15 @@ function Form(props) {
1369
1372
  text={submitBtnLabel || 'Submit'}
1370
1373
  />}
1371
1374
 
1372
- {additionalFooterButtons && _.map(additionalFooterButtons, (props) => {
1375
+ {additionalFooterButtons && _.map(additionalFooterButtons, (props, ix) => {
1373
1376
  let isDisabled = false;
1374
1377
  if (props.disableOnInvalid) {
1375
1378
  isDisabled = !formState.isValid;
1376
1379
  }
1380
+ const key = 'additionalFooterBtn-' + ix;
1377
1381
  return <Button
1378
- {...testProps('additionalFooterBtn-' + props.key)}
1382
+ {...testProps(key)}
1383
+ key={key}
1379
1384
  {...props}
1380
1385
  onPress={(e) => handleSubmit(props.onPress, onSubmitError)(e)}
1381
1386
  icon={props.icon || null}
@@ -133,16 +133,11 @@ function GridComponent(props) {
133
133
  showHeaders = true,
134
134
  showHovers = true,
135
135
  showSelectHandle = true,
136
+ isRowSelectable = true,
137
+ isRowHoverable = true,
136
138
  canColumnsSort = true,
137
139
  canColumnsReorder = true,
138
140
  canColumnsResize = true,
139
- canRowsReorder = false,
140
- areRowsDragSource = false,
141
- rowDragSourceType,
142
- getRowDragSourceItem,
143
- areRowsDropTarget = false,
144
- dropTargetAccept,
145
- onRowDrop,
146
141
  allowToggleSelection = false, // i.e. single click with no shift key toggles the selection of the item clicked on
147
142
  disableBottomToolbar = false,
148
143
  disablePagination = false,
@@ -173,6 +168,18 @@ function GridComponent(props) {
173
168
  noSelectorMeansNoResults = false,
174
169
  disableSelectorSelected = false,
175
170
 
171
+ // DND
172
+ canRowsReorder = false,
173
+ canRowAcceptDrop, // optional fn to customize whether each node can accept a dropped item: (targetItem, draggedItem) => boolean
174
+ getCustomDragProxy, // optional fn to render custom drag preview: (item, selection) => ReactElement
175
+ dragPreviewOptions, // optional object for drag preview positioning options
176
+ areRowsDragSource = false,
177
+ rowDragSourceType,
178
+ getRowDragSourceItem,
179
+ areRowsDropTarget = false,
180
+ dropTargetAccept,
181
+ onRowDrop,
182
+
176
183
  // withComponent
177
184
  self,
178
185
 
@@ -473,7 +480,13 @@ function GridComponent(props) {
473
480
  onContextMenu(item, e, newSelection);
474
481
  }
475
482
  }}
476
- className="Pressable Row flex-row grow">
483
+ className={`
484
+ Pressable
485
+ Row
486
+ flex-row
487
+ grow
488
+ `}
489
+ >
477
490
  {({
478
491
  hovered,
479
492
  focused,
@@ -505,12 +518,11 @@ function GridComponent(props) {
505
518
  }
506
519
  return headerRow;
507
520
  }
508
-
509
521
  const
510
522
  rowReorderProps = {},
511
523
  rowDragProps = {};
512
524
  let WhichRow = GridRow;
513
- if (CURRENT_MODE === UI_MODE_WEB) { // DND is currrently web-only TODO: implement for RN
525
+ if (CURRENT_MODE === UI_MODE_WEB) { // DND is currently web-only TODO: implement for RN
514
526
  // Create a method that gets an always-current copy of the selection ids
515
527
  dragSelectionRef.current = selection;
516
528
  const getSelection = () => dragSelectionRef.current;
@@ -542,10 +554,21 @@ function GridComponent(props) {
542
554
  } else {
543
555
  rowDragProps.dragSourceItem = {
544
556
  id: item.id,
557
+ item,
545
558
  getSelection,
546
559
  type: rowDragSourceType,
547
560
  };
548
561
  }
562
+
563
+ // Add custom drag preview options
564
+ if (dragPreviewOptions) {
565
+ rowDragProps.dragPreviewOptions = dragPreviewOptions;
566
+ }
567
+
568
+ // Add drag preview rendering
569
+ rowDragProps.getDragProxy = getCustomDragProxy ?
570
+ (dragItem) => getCustomDragProxy(item, getSelection()) :
571
+ null; // Let GlobalDragProxy handle the default case
549
572
  }
550
573
  if (areRowsDropTarget) {
551
574
  WhichRow = DropTargetGridRow;
@@ -555,6 +578,14 @@ function GridComponent(props) {
555
578
  // NOTE: item is sometimes getting destroyed, but it still as the id, so you can still use it
556
579
  onRowDrop(item, droppedItem); // item is what it was dropped on; droppedItem is the dragSourceItem defined above
557
580
  };
581
+ rowDragProps.canDrop = (droppedItem, monitor) => {
582
+ // Check if the drop operation would be valid based on business rules
583
+ if (canRowAcceptDrop && typeof canRowAcceptDrop === 'function') {
584
+ return canRowAcceptDrop(item, droppedItem);
585
+ }
586
+ // Default: allow all drops
587
+ return true;
588
+ };
558
589
  }
559
590
  if (areRowsDragSource && areRowsDropTarget) {
560
591
  WhichRow = DragSourceDropTargetGridRow;
@@ -569,6 +600,8 @@ function GridComponent(props) {
569
600
  fields={fields}
570
601
  rowProps={rowProps}
571
602
  hideNavColumn={hideNavColumn}
603
+ isRowSelectable={isRowSelectable}
604
+ isRowHoverable={isRowHoverable}
572
605
  isSelected={isSelected}
573
606
  isHovered={hovered}
574
607
  showHovers={showHovers}
@@ -20,7 +20,7 @@ import UiGlobals from '../../UiGlobals.js';
20
20
  import useBlocking from '../../Hooks/useBlocking.js';
21
21
  import testProps from '../../Functions/testProps.js';
22
22
  import AngleRight from '../Icons/AngleRight.js';
23
- import ArrowPointer from '../Icons/ArrowPointer.js';
23
+ import Arcs from '../Icons/Arcs.js';
24
24
  import HeaderReorderHandle from './HeaderReorderHandle.js';
25
25
  import HeaderResizeHandle from './HeaderResizeHandle.js';
26
26
  import HeaderColumnSelectorHandle from './HeaderColumnSelectorHandle.js';
@@ -469,13 +469,13 @@ export default function GridHeaderRow(props) {
469
469
  key="RowSelectHandle"
470
470
  className="Spacer-RowSelectHandle px-2 items-center justify-center flex-none w-[40px]"
471
471
  >
472
- <Icon as={ArrowPointer} className={`ArrowPointer w-[20px] h-[20px] text-[#aaa]`} />
472
+ <Icon as={Arcs} className={`Arcs w-[20px] h-[20px] text-[#aaa]`} />
473
473
  </Box>);
474
474
  }
475
475
  if (areRowsDragSource) {
476
476
  headerColumns.unshift(<Box
477
477
  key="spacer"
478
- className="Spacer w-[3px]"
478
+ className="Spacer w-[7px]"
479
479
  />);
480
480
  }
481
481
  if (!hideNavColumn) {
@@ -1,4 +1,4 @@
1
- import { useMemo, } from 'react';
1
+ import { useMemo, useEffect, } from 'react';
2
2
  import {
3
3
  Box,
4
4
  HStack,
@@ -29,6 +29,8 @@ function GridRow(props) {
29
29
  rowProps,
30
30
  hideNavColumn,
31
31
  showSelectHandle,
32
+ isRowSelectable,
33
+ isRowHoverable,
32
34
  isSelected,
33
35
  isHovered,
34
36
  bg,
@@ -41,14 +43,28 @@ function GridRow(props) {
41
43
  isDraggable = false, // withDraggable
42
44
  isDragSource = false, // withDnd
43
45
  isOver = false, // drop target
46
+ canDrop,
47
+ draggedItem,
48
+ validateDrop, // same as canDrop (for visual feedback)
49
+ getDragProxy,
44
50
  dragSourceRef,
51
+ dragPreviewRef,
45
52
  dropTargetRef,
53
+ ...propsToPass
46
54
  } = props,
47
55
  styles = UiGlobals.styles;
48
56
 
49
57
  if (item.isDestroyed) {
50
58
  return null;
51
59
  }
60
+
61
+ // Hide the default drag preview only when using custom drag proxy (and only on web)
62
+ useEffect(() => {
63
+ if (dragPreviewRef && typeof dragPreviewRef === 'function' && getDragProxy && CURRENT_MODE === UI_MODE_WEB) {
64
+ // Only suppress default drag preview when we have a custom one and we're on web
65
+ dragPreviewRef(getEmptyImage(), { captureDraggingState: true });
66
+ }
67
+ }, [dragPreviewRef, getDragProxy]);
52
68
 
53
69
  const
54
70
  isPhantom = item.isPhantom,
@@ -57,13 +73,22 @@ function GridRow(props) {
57
73
 
58
74
  let bg = rowProps.bg || props.bg || styles.GRID_ROW_BG,
59
75
  mixWith;
60
- if (isSelected) {
76
+
77
+ // TODO: Finish Drop styling
78
+
79
+ // Use custom validation for enhanced visual feedback, fallback to React DnD's canDrop
80
+ let actualCanDrop = canDrop;
81
+ if (isOver && draggedItem && validateDrop) {
82
+ actualCanDrop = validateDrop(draggedItem);
83
+ }
84
+
85
+ if (isRowSelectable && isSelected) {
61
86
  if (showHovers && isHovered) {
62
87
  mixWith = styles.GRID_ROW_SELECTED_BG_HOVER;
63
88
  } else {
64
89
  mixWith = styles.GRID_ROW_SELECTED_BG;
65
90
  }
66
- } else if (showHovers && isHovered) {
91
+ } else if (isRowHoverable && showHovers && isHovered) {
67
92
  mixWith = styles.GRID_ROW_BG_HOVER;
68
93
  } else if (alternateRowBackgrounds && index % alternatingInterval === 0) { // i.e. every second line, or every third line
69
94
  mixWith = styles.GRID_ROW_ALTERNATE_BG;
@@ -87,7 +112,8 @@ function GridRow(props) {
87
112
  }
88
113
  const
89
114
  propsToPass = columnProps[key] || {},
90
- colStyle = {};
115
+ colStyle = {},
116
+ whichCursor = showSelectHandle ? 'cursor-text' : 'cursor-pointer'; // when using rowSelectHandle, indicate that the row text is selectable, otherwise indicate that the row itself is selectable
91
117
  let colClassName = `
92
118
  GridRow-column
93
119
  p-1
@@ -95,6 +121,7 @@ function GridRow(props) {
95
121
  border-r-black-100
96
122
  block
97
123
  overflow-auto
124
+ ${whichCursor}
98
125
  ${styles.GRID_ROW_MAX_HEIGHT_EXTRA}
99
126
  `;
100
127
  if (isOnlyOneVisibleColumn) {
@@ -364,7 +391,11 @@ function GridRow(props) {
364
391
  isHovered,
365
392
  isOver,
366
393
  index,
394
+ canDrop,
395
+ draggedItem,
396
+ validateDrop,
367
397
  dragSourceRef,
398
+ dragPreviewRef,
368
399
  dropTargetRef,
369
400
  ]);
370
401
  }
@@ -7,7 +7,7 @@ import GripVertical from '../Icons/GripVertical.js';
7
7
 
8
8
  function RowDragHandle(props) { return <VStack
9
9
  style={styles.ewResize}
10
- className="RowDragHandle bg-grey-100 w-[3px] items-center justify-center select-none"
10
+ className="RowDragHandle bg-grey-100 w-[7px] items-center justify-center select-none"
11
11
  >
12
12
  <Icon
13
13
  as={GripVertical}
@@ -2,14 +2,14 @@ import {
2
2
  Icon,
3
3
  VStack,
4
4
  } from '@project-components/Gluestack';
5
- import ArrowPointer from '../Icons/ArrowPointer.js';
5
+ import Arcs from '../Icons/Arcs.js';
6
6
 
7
7
  function RowSelectHandle(props) {
8
8
  return <VStack
9
- className="RowSelectHandle w-[40px] px-2 items-center justify-center select-none cursor-grab"
9
+ className="RowSelectHandle w-[40px] px-2 items-center justify-center select-none cursor-pointer"
10
10
  >
11
11
  <Icon
12
- as={ArrowPointer}
12
+ as={Arcs}
13
13
  size="xs"
14
14
  className="w-[20px] h-[20px] text-[#ddd]" />
15
15
  </VStack>;
@@ -63,6 +63,10 @@ function withAlert(WrappedComponent) {
63
63
  text-[18px]
64
64
  flex-none
65
65
  mr-2
66
+ w-full
67
+ break-words
68
+ whitespace-normal
69
+ overflow-wrap-anywhere
66
70
  `}>{message}</Text>
67
71
  </Box>
68
72
  </HStack>;
@@ -1,6 +1,13 @@
1
1
  import { forwardRef, useEffect, useRef, } from 'react';
2
2
  import { useDrag, useDrop, useDragLayer } from 'react-dnd'; // https://react-dnd.github.io/react-dnd/about don't forget the wrapping <DndProvider /> as shown here: https://react-dnd.github.io/react-dnd/docs/api/dnd-provider
3
-
3
+ import {
4
+ Box,
5
+ } from '@project-components/Gluestack';
6
+ import {
7
+ UI_MODE_WEB,
8
+ UI_MODE_NATIVE,
9
+ CURRENT_MODE,
10
+ } from '../../Constants/UiModes.js';
4
11
 
5
12
  // This HOC allows components to be dragged and dropped onto another component.
6
13
  // It can't contrain the movement of the preview item, because react-dnd uses
@@ -32,6 +39,7 @@ export function withDragSource(WrappedComponent) {
32
39
  onDragEnd = null,
33
40
  canDrag = null,
34
41
  isDragging = null,
42
+ getDragProxy,
35
43
  dragCollect = (monitor, props2) => { // Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, monitor and props. Read the overview for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
36
44
  // monitor fn determines which props from dnd state get passed
37
45
  return {
@@ -54,7 +62,10 @@ export function withDragSource(WrappedComponent) {
54
62
 
55
63
  return {
56
64
  type: dragSourceType, // Required. This must be either a string or a symbol. Only the drop targets registered for the same type will react to this item.
57
- item: dragSourceItem, // Required (object or function).
65
+ item: {
66
+ ...dragSourceItem,
67
+ getDragProxy,
68
+ }, // Required (object or function).
58
69
  // When an object, it is a plain JavaScript object describing the data being dragged. This is the only information available to the drop targets about the drag source so it's important to pick the minimal data they need to know. You may be tempted to put a complex reference here, but you should try very hard to avoid doing this because it couples the drag sources and drop targets. It's a good idea to use something like { id }.
59
70
  // When a function, it is fired at the beginning of the drag operation and returns an object representing the drag operation (see first bullet). If null is returned, the drag operation is cancelled.
60
71
  previewOptions: dragPreviewOptions, // Optional. A plain JavaScript object describing drag preview options.
@@ -139,6 +150,7 @@ export function withDropTarget(WrappedComponent) {
139
150
  return {
140
151
  canDrop: !!monitor.canDrop(),
141
152
  isOver: !!monitor.isOver(),
153
+ draggedItem: monitor.getItem(), // Pass the dragged item so TreeNode can evaluate custom logic
142
154
  };
143
155
  },
144
156
  } = props,
@@ -171,6 +183,7 @@ export function withDropTarget(WrappedComponent) {
171
183
  {
172
184
  canDrop: stateCanDrop,
173
185
  isOver,
186
+ draggedItem,
174
187
  // didDrop,
175
188
  // clientOffset,
176
189
  // differenceFromInitialOffset,
@@ -187,64 +200,48 @@ export function withDropTarget(WrappedComponent) {
187
200
  ref={ref}
188
201
  isOver={isOver}
189
202
  dropTargetRef={localTargetRef}
203
+ draggedItem={draggedItem} // Pass the dragged item
190
204
  {...props}
191
205
  />;
192
206
  });
193
207
  }
194
208
 
195
209
 
196
- // export function CustomDragLayer(props) {
197
-
198
- // // if (CURRENT_MODE !== UI_MODE_WEB) {
199
- // // throw Error('CustomDragLayer only works in web mode');
200
- // // }
201
-
202
- // const
203
- // {
204
- // onDrag,
205
- // axis = null,
206
- // } = props,
207
- // layer = useDragLayer((monitor) => ({
208
- // isDragging: monitor.isDragging(),
209
- // item: monitor.getItem(),
210
- // currentOffset: monitor.getSourceClientOffset(),
211
- // })),
212
- // { isDragging, item, currentOffset } = layer;
213
-
214
- // useEffect(() => {
215
- // if (layer.isDragging) {
216
- // onDrag(layer);
217
- // }
218
- // }, [layer]);
219
-
220
- // return null;
221
-
222
- // if (!isDragging || !currentOffset) {
223
- // return null;
224
- // }
225
-
226
- // let transform;
227
- // if (axis === 'x') {
228
- // transform = `translate(${currentOffset.x}px, 0)`;
229
- // } else if (axis === 'y') {
230
- // transform = `translate(0, ${currentOffset.y}px)`;
231
- // } else {
232
- // transform = `translate(${currentOffset.x}px, ${currentOffset.y}px)`;
233
- // }
234
-
235
- // return (
236
- // <div id="dragLayer" style={{
237
- // background: '#f00',
238
- // position: 'fixed',
239
- // pointerEvents: 'none',
240
- // zIndex: 10000,
241
- // width: '200px',
242
- // height: '10px',
243
- // left: 0,
244
- // top: 0,
245
- // transform,
246
- // }}>
247
- // {children}
248
- // </div>
249
- // );
250
- // }
210
+ export function GlobalDragProxy() {
211
+ const {
212
+ isDragging,
213
+ item,
214
+ currentOffset,
215
+ } = useDragLayer((monitor) => ({
216
+ isDragging: monitor.isDragging(),
217
+ item: monitor.getItem(),
218
+ currentOffset: monitor.getSourceClientOffset(),
219
+ }));
220
+
221
+ if (!isDragging || !currentOffset || CURRENT_MODE !== UI_MODE_WEB) { // Native uses a native drag layer, so we don't need to render a custom proxy
222
+ return null;
223
+ }
224
+
225
+ const getDragProxy = item?.getDragProxy;
226
+ if (!getDragProxy) {
227
+ // Only render a custom proxy if one is provided - let React DnD handle default case
228
+ return null;
229
+ }
230
+
231
+ let proxyContent = null;
232
+ try {
233
+ proxyContent = getDragProxy(item);
234
+ } catch (error) {
235
+ console.warn('Failed to render custom drag proxy:', error);
236
+ return null; // use default React DnD proxy
237
+ }
238
+
239
+ return <Box
240
+ className="fixed pointer-events-none z-[10000] left-0 top-0"
241
+ style={{
242
+ transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
243
+ }}
244
+ >
245
+ {proxyContent}
246
+ </Box>;
247
+ }