Skip to content

Commit 53d3cad

Browse files
committed
feat: grid with draggable items
1 parent f3d9ea1 commit 53d3cad

File tree

1 file changed

+40
-185
lines changed

1 file changed

+40
-185
lines changed

electron/renderer/components/grid/grid4.tsx

+40-185
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@
22
// https://github.com/alexkrkn/react-crop-video/
33
// https://www.youtube.com/watch?v=vDxZLN6FVqY
44

5-
import { animated, useSpring } from '@react-spring/web';
6-
import type { EventTypes, Handler, UserDragConfig } from '@use-gesture/react';
7-
import { useDrag } from '@use-gesture/react';
8-
import isNil from 'lodash-es/isNil.js';
9-
import type { ReactNode, RefObject } from 'react';
10-
import { useCallback, useMemo, useRef } from 'react';
5+
import type { ReactNode } from 'react';
6+
import { useState } from 'react';
7+
import { DraggableItem } from '../draggable/draggable-item.jsx';
118

129
export interface Grid4Props {
1310
/**
1411
* The dimension for the grid.
1512
*/
16-
dimensions: {
13+
boundary: {
1714
/**
1815
* The max height of the grid in pixels.
1916
*/
@@ -25,196 +22,54 @@ export interface Grid4Props {
2522
};
2623
}
2724

28-
// We need to invoke our pointer event handlers from different event listeners
29-
// that each have their own unique interface. Rather than force cast the events
30-
// to the desired interface, which may introduce a bug later, we'll create
31-
// a simplified interface that can be used from any event listener.
32-
export interface GridPointerEvent {
33-
clientX: number;
34-
clientY: number;
35-
}
36-
37-
const GridItem4: React.FC<Grid4Props> = (props: Grid4Props): ReactNode => {
38-
const { dimensions: gridDimensions } = props;
39-
40-
const [{ x, y, width, height }, api] = useSpring(() => ({
41-
x: 0,
42-
y: 0,
43-
width: 100,
44-
height: 100,
45-
}));
46-
47-
const draggableRef = useRef<HTMLDivElement>(null);
48-
const resizableRef = useRef<HTMLDivElement>(null);
49-
50-
const isEventTarget = useCallback(
51-
(
52-
eventOrTarget: Event | EventTarget | null | undefined,
53-
ref: RefObject<HTMLElement>
54-
) => {
55-
if (isNil(eventOrTarget)) {
56-
return false;
57-
}
58-
if ('target' in eventOrTarget) {
59-
return eventOrTarget.target === ref.current;
60-
}
61-
return eventOrTarget === ref.current;
62-
},
63-
[]
64-
);
65-
66-
const isDragging = useCallback(
67-
(eventOrTarget: Event | EventTarget | null | undefined): boolean => {
68-
return isEventTarget(eventOrTarget, draggableRef);
69-
},
70-
[isEventTarget]
71-
);
72-
73-
const isResizing = useCallback(
74-
(eventOrTarget: Event | EventTarget | null | undefined): boolean => {
75-
return isEventTarget(eventOrTarget, resizableRef);
76-
},
77-
[isEventTarget]
78-
);
79-
80-
const dragHandler: Handler<'drag', EventTypes['drag']> = useCallback(
81-
/**
82-
* Callback to invoke when a gesture event ends.
83-
* For example, when the user stops dragging or resizing.
84-
*/
85-
(state) => {
86-
// The cumulative displacements the pointer has moved relative to
87-
// the last vector returned by the `from` drag option function.
88-
const [dx, dy] = state.offset;
89-
90-
if (isResizing(state.event)) {
91-
// When resizing, the values are the new width and height dimensions.
92-
api.set({
93-
width: dx,
94-
height: dy,
95-
});
96-
} else if (isDragging(state.event)) {
97-
// When dragging, the values are the new x and y coordinates.
98-
api.set({
99-
x: dx,
100-
y: dy,
101-
});
102-
}
103-
},
104-
[api, isResizing, isDragging]
25+
export const Grid4: React.FC<Grid4Props> = (props: Grid4Props): ReactNode => {
26+
const { boundary } = props;
27+
28+
const [focusedItemId, setFocusedItemId] = useState<string>('');
29+
30+
const item1 = (
31+
<DraggableItem
32+
key="1"
33+
itemId="1"
34+
titleBarText="Item 1"
35+
isFocused={focusedItemId === '1'}
36+
onFocus={setFocusedItemId}
37+
onClose={() => {
38+
alert('Closed item 1');
39+
}}
40+
boundary={boundary}
41+
>
42+
<div>Content1</div>
43+
</DraggableItem>
10544
);
10645

107-
const dragOptions: UserDragConfig = useMemo(() => {
108-
return {
109-
/**
110-
* When a gesture event begins, specify the reference vector
111-
* from which to calculate the distance the pointer moves.
112-
*/
113-
from: (state) => {
114-
if (isResizing(state.target)) {
115-
return [width.get(), height.get()];
116-
}
117-
return [x.get(), y.get()];
118-
},
119-
/**
120-
* When a gesture event begins, specify the where the pointer can move.
121-
* The element will not be dragged or resized outside of these bounds.
122-
*/
123-
bounds: (state) => {
124-
const containerWidth = gridDimensions.width;
125-
const containerHeight = gridDimensions.height;
126-
if (isResizing(state?.event)) {
127-
return {
128-
top: 50, // min height
129-
left: 50, // min width
130-
right: containerWidth - x.get(),
131-
bottom: containerHeight - y.get(),
132-
};
133-
}
134-
return {
135-
top: 0,
136-
left: 0,
137-
right: containerWidth - width.get(),
138-
bottom: containerHeight - height.get(),
139-
};
140-
},
141-
};
142-
}, [x, y, width, height, gridDimensions, isResizing]);
143-
144-
const bind = useDrag(dragHandler, dragOptions);
145-
146-
return (
147-
<animated.div
148-
style={{
149-
position: 'absolute',
150-
x,
151-
y,
152-
width,
153-
height,
154-
backgroundColor: 'brown',
155-
overflow: 'hidden',
46+
const item2 = (
47+
<DraggableItem
48+
key="2"
49+
itemId="2"
50+
titleBarText="Item 2"
51+
isFocused={focusedItemId === '2'}
52+
onFocus={setFocusedItemId}
53+
onClose={() => {
54+
alert('Closed item 2');
15655
}}
157-
{...bind()}
56+
boundary={boundary}
15857
>
159-
<div
160-
ref={draggableRef}
161-
style={{
162-
width: '100%',
163-
height: '20px',
164-
cursor: 'move',
165-
backgroundColor: 'red',
166-
textAlign: 'center',
167-
}}
168-
>
169-
Drag Handle
170-
</div>
171-
172-
<div
173-
style={{
174-
width: '100%',
175-
height: '100%',
176-
overflowY: 'auto',
177-
overflowX: 'hidden',
178-
}}
179-
>
180-
This quick brown fox jumped over the fence.
181-
</div>
182-
183-
<div
184-
ref={resizableRef}
185-
style={{
186-
position: 'absolute',
187-
bottom: -4,
188-
right: -4,
189-
width: 10,
190-
height: 10,
191-
cursor: 'nwse-resize',
192-
backgroundColor: '#0097df',
193-
borderRadius: 4,
194-
}}
195-
></div>
196-
</animated.div>
58+
<div>Content2</div>
59+
</DraggableItem>
19760
);
198-
};
199-
200-
GridItem4.displayName = 'GridItem4';
201-
202-
export const Grid4: React.FC<Grid4Props> = (props: Grid4Props): ReactNode => {
203-
const gridDimensions = props.dimensions;
20461

20562
return (
20663
<div
20764
style={{
208-
position: 'relative',
209-
height: gridDimensions.height,
210-
width: gridDimensions.width,
21165
overflow: 'hidden',
66+
position: 'relative',
67+
height: boundary.height,
68+
width: boundary.width,
21269
}}
21370
>
214-
<GridItem4 dimensions={gridDimensions} />
215-
<GridItem4 dimensions={gridDimensions} />
71+
{item1}
72+
{item2}
21673
</div>
21774
);
21875
};
219-
220-
Grid4.displayName = 'Grid4';

0 commit comments

Comments
 (0)