react-edge-dock 1.0.0 → 1.0.2
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/README.md +71 -0
- package/dist/EdgeDock.d.ts.map +1 -1
- package/dist/EdgeDock.js +3 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/useEdgeDock.d.ts.map +1 -1
- package/dist/useEdgeDock.js +55 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ A zero-dependency React + TypeScript library for customizable draggable edge-doc
|
|
|
12
12
|
- 🎮 Multiple docking modes (free, auto, manual)
|
|
13
13
|
- 💡 Smart popup positioning
|
|
14
14
|
- ⚡ Performance optimized with transform: translate3d
|
|
15
|
+
- 🌐 **SSR compatible (Next.js, Remix, etc.)**
|
|
16
|
+
- 📏 **Configurable edge offset/margin**
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -29,6 +31,7 @@ function App() {
|
|
|
29
31
|
<EdgeDock
|
|
30
32
|
dockMode="auto"
|
|
31
33
|
animation={true}
|
|
34
|
+
edgeOffset={16} // 16px gap from screen edge
|
|
32
35
|
button={<button>🚀</button>}
|
|
33
36
|
popup={<div>Your content here</div>}
|
|
34
37
|
/>
|
|
@@ -36,6 +39,74 @@ function App() {
|
|
|
36
39
|
}
|
|
37
40
|
```
|
|
38
41
|
|
|
42
|
+
## Usage in Next.js
|
|
43
|
+
|
|
44
|
+
Works out of the box with Next.js App Router and Pages Router:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
'use client'; // For App Router
|
|
48
|
+
|
|
49
|
+
import { EdgeDock } from 'react-edge-dock';
|
|
50
|
+
|
|
51
|
+
export default function MyComponent() {
|
|
52
|
+
return (
|
|
53
|
+
<EdgeDock
|
|
54
|
+
dockMode="auto"
|
|
55
|
+
edgeOffset={20}
|
|
56
|
+
button={<button>Menu</button>}
|
|
57
|
+
popup={<div>Navigation</div>}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
- `dockMode`: `"free"` | `"auto"` | `"manual"` - Docking behavior
|
|
66
|
+
- `dockEdge`: `"left"` | `"right"` | `"top"` | `"bottom"` - Fixed edge (manual mode)
|
|
67
|
+
- `allowedEdges`: `DockEdge[]` - Restrict docking to specific edges (e.g., `['left', 'right']` for horizontal only)
|
|
68
|
+
- `edgeOffset`: `number` - Gap from screen edge in pixels (default: 0)
|
|
69
|
+
- `animation`: `boolean` - Enable snap animations
|
|
70
|
+
- `popupGap`: `number` - Gap between button and popup
|
|
71
|
+
- `position`: `{ x: number; y: number }` - Initial/controlled position
|
|
72
|
+
- `zIndex`: `number` - z-index for the dock
|
|
73
|
+
- `onDockChange`: Callback when dock state changes
|
|
74
|
+
- `isPopupOpen` / `onPopupChange`: Controlled popup state
|
|
75
|
+
|
|
76
|
+
## Examples
|
|
77
|
+
|
|
78
|
+
### Restrict to horizontal edges only (left/right)
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<EdgeDock
|
|
82
|
+
dockMode="auto"
|
|
83
|
+
allowedEdges={['left', 'right']}
|
|
84
|
+
edgeOffset={16}
|
|
85
|
+
button={<button>📱</button>}
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Restrict to vertical edges only (top/bottom)
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<EdgeDock
|
|
93
|
+
dockMode="auto"
|
|
94
|
+
allowedEdges={['top', 'bottom']}
|
|
95
|
+
button={<button>⬆️</button>}
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Manual edge with offset
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<EdgeDock
|
|
103
|
+
dockMode="manual"
|
|
104
|
+
dockEdge="right"
|
|
105
|
+
edgeOffset={20}
|
|
106
|
+
button={<button>➡️</button>}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
39
110
|
## API
|
|
40
111
|
|
|
41
112
|
See the [example.tsx](./example.tsx) file for more detailed usage examples.
|
package/dist/EdgeDock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeDock.d.ts","sourceRoot":"","sources":["../src/EdgeDock.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"EdgeDock.d.ts","sourceRoot":"","sources":["../src/EdgeDock.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,qBAsF5C"}
|
package/dist/EdgeDock.js
CHANGED
|
@@ -14,13 +14,15 @@ import { useEdgeDock } from './useEdgeDock';
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
export function EdgeDock(props) {
|
|
17
|
-
const { button, popup, className, style, dockMode, dockEdge, position, animation, popupGap, zIndex, onDockChange, isPopupOpen, onPopupChange, } = props;
|
|
17
|
+
const { button, popup, className, style, dockMode, dockEdge, allowedEdges, position, animation, popupGap, edgeOffset, zIndex, onDockChange, isPopupOpen, onPopupChange, } = props;
|
|
18
18
|
const { state, buttonRef, popupRef, closePopup, buttonStyles, popupStyles, buttonProps, } = useEdgeDock({
|
|
19
19
|
dockMode,
|
|
20
20
|
dockEdge,
|
|
21
|
+
allowedEdges,
|
|
21
22
|
position,
|
|
22
23
|
animation,
|
|
23
24
|
popupGap,
|
|
25
|
+
edgeOffset,
|
|
24
26
|
zIndex,
|
|
25
27
|
onDockChange,
|
|
26
28
|
isPopupOpen,
|
package/dist/types.d.ts
CHANGED
|
@@ -35,12 +35,16 @@ export interface EdgeDockConfig {
|
|
|
35
35
|
dockMode?: DockMode;
|
|
36
36
|
/** Fixed edge for manual dock mode */
|
|
37
37
|
dockEdge?: DockEdge;
|
|
38
|
+
/** Restrict which edges are allowed for docking (only works in auto mode) */
|
|
39
|
+
allowedEdges?: DockEdge[];
|
|
38
40
|
/** Initial or controlled position */
|
|
39
41
|
position?: Position;
|
|
40
42
|
/** Enable snap animations */
|
|
41
43
|
animation?: boolean;
|
|
42
44
|
/** Gap between button and popup in pixels */
|
|
43
45
|
popupGap?: number;
|
|
46
|
+
/** Offset from edge when docked (in pixels) */
|
|
47
|
+
edgeOffset?: number;
|
|
44
48
|
/** z-index for the dock container */
|
|
45
49
|
zIndex?: number;
|
|
46
50
|
/** Callback when dock state changes */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEdgeDock.d.ts","sourceRoot":"","sources":["../src/useEdgeDock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAId,iBAAiB,EAGlB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"useEdgeDock.d.ts","sourceRoot":"","sources":["../src/useEdgeDock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAId,iBAAiB,EAGlB,MAAM,SAAS,CAAC;AAwKjB;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,GAAE,cAAmB,GAAG,iBAAiB,CAwT1E"}
|
package/dist/useEdgeDock.js
CHANGED
|
@@ -1,47 +1,66 @@
|
|
|
1
1
|
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Check if code is running in browser (SSR-safe)
|
|
4
|
+
*/
|
|
5
|
+
const isBrowser = typeof window !== 'undefined';
|
|
6
|
+
/**
|
|
7
|
+
* Get viewport dimensions safely (SSR-compatible)
|
|
8
|
+
*/
|
|
9
|
+
function getViewport() {
|
|
10
|
+
if (!isBrowser) {
|
|
11
|
+
return { width: 1920, height: 1080 }; // Default fallback for SSR
|
|
12
|
+
}
|
|
13
|
+
return { width: window.innerWidth, height: window.innerHeight };
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* Calculate which edge is closest to the given position
|
|
4
17
|
*/
|
|
5
|
-
function getClosestEdge(pos, viewport) {
|
|
18
|
+
function getClosestEdge(pos, viewport, allowedEdges) {
|
|
6
19
|
const distanceToLeft = pos.x;
|
|
7
20
|
const distanceToRight = viewport.width - pos.x;
|
|
8
21
|
const distanceToTop = pos.y;
|
|
9
22
|
const distanceToBottom = viewport.height - pos.y;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
// Create map of edges with their distances
|
|
24
|
+
const edgeDistances = [
|
|
25
|
+
{ edge: 'left', distance: distanceToLeft },
|
|
26
|
+
{ edge: 'right', distance: distanceToRight },
|
|
27
|
+
{ edge: 'top', distance: distanceToTop },
|
|
28
|
+
{ edge: 'bottom', distance: distanceToBottom },
|
|
29
|
+
];
|
|
30
|
+
// Filter by allowed edges if specified
|
|
31
|
+
const validEdges = allowedEdges
|
|
32
|
+
? edgeDistances.filter(({ edge }) => allowedEdges.includes(edge))
|
|
33
|
+
: edgeDistances;
|
|
34
|
+
// Find the closest edge
|
|
35
|
+
const closest = validEdges.reduce((min, current) => current.distance < min.distance ? current : min);
|
|
36
|
+
return closest.edge;
|
|
18
37
|
}
|
|
19
38
|
/**
|
|
20
39
|
* Snap position to edge based on dock mode
|
|
21
40
|
*/
|
|
22
|
-
function snapToEdge(pos, edge, viewport, buttonDimensions) {
|
|
41
|
+
function snapToEdge(pos, edge, viewport, buttonDimensions, offset = 0) {
|
|
23
42
|
const halfWidth = buttonDimensions.width / 2;
|
|
24
43
|
const halfHeight = buttonDimensions.height / 2;
|
|
25
44
|
switch (edge) {
|
|
26
45
|
case 'left':
|
|
27
46
|
return {
|
|
28
|
-
x: halfWidth,
|
|
47
|
+
x: halfWidth + offset,
|
|
29
48
|
y: Math.max(halfHeight, Math.min(viewport.height - halfHeight, pos.y)),
|
|
30
49
|
};
|
|
31
50
|
case 'right':
|
|
32
51
|
return {
|
|
33
|
-
x: viewport.width - halfWidth,
|
|
52
|
+
x: viewport.width - halfWidth - offset,
|
|
34
53
|
y: Math.max(halfHeight, Math.min(viewport.height - halfHeight, pos.y)),
|
|
35
54
|
};
|
|
36
55
|
case 'top':
|
|
37
56
|
return {
|
|
38
57
|
x: Math.max(halfWidth, Math.min(viewport.width - halfWidth, pos.x)),
|
|
39
|
-
y: halfHeight,
|
|
58
|
+
y: halfHeight + offset,
|
|
40
59
|
};
|
|
41
60
|
case 'bottom':
|
|
42
61
|
return {
|
|
43
62
|
x: Math.max(halfWidth, Math.min(viewport.width - halfWidth, pos.x)),
|
|
44
|
-
y: viewport.height - halfHeight,
|
|
63
|
+
y: viewport.height - halfHeight - offset,
|
|
45
64
|
};
|
|
46
65
|
}
|
|
47
66
|
}
|
|
@@ -109,11 +128,13 @@ function calculatePopupPosition(buttonPos, buttonDimensions, popupDimensions, vi
|
|
|
109
128
|
* Main hook for edge dock functionality
|
|
110
129
|
*/
|
|
111
130
|
export function useEdgeDock(config = {}) {
|
|
112
|
-
const { dockMode = 'auto', dockEdge, position: controlledPosition, animation = true, popupGap = 12, zIndex = 9999, onDockChange, isPopupOpen: controlledPopupOpen, onPopupChange, } = config;
|
|
131
|
+
const { dockMode = 'auto', dockEdge, allowedEdges, position: controlledPosition, animation = true, popupGap = 12, edgeOffset = 0, zIndex = 9999, onDockChange, isPopupOpen: controlledPopupOpen, onPopupChange, } = config;
|
|
113
132
|
const buttonRef = useRef(null);
|
|
114
133
|
const popupRef = useRef(null);
|
|
134
|
+
// Get initial viewport safely
|
|
135
|
+
const initialViewport = getViewport();
|
|
115
136
|
// Internal state
|
|
116
|
-
const [position, setPositionInternal] = useState(controlledPosition || { x:
|
|
137
|
+
const [position, setPositionInternal] = useState(controlledPosition || { x: initialViewport.width - 60, y: initialViewport.height - 60 });
|
|
117
138
|
const [dockedEdge, setDockedEdge] = useState(null);
|
|
118
139
|
const [isDragging, setIsDragging] = useState(false);
|
|
119
140
|
const [isPopupOpenInternal, setIsPopupOpenInternal] = useState(false);
|
|
@@ -148,7 +169,7 @@ export function useEdgeDock(config = {}) {
|
|
|
148
169
|
if (isPopupOpen && buttonRef.current && popupRef.current) {
|
|
149
170
|
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
150
171
|
const popupRect = popupRef.current.getBoundingClientRect();
|
|
151
|
-
const result = calculatePopupPosition(position, { width: buttonRect.width, height: buttonRect.height }, { width: popupRect.width, height: popupRect.height },
|
|
172
|
+
const result = calculatePopupPosition(position, { width: buttonRect.width, height: buttonRect.height }, { width: popupRect.width, height: popupRect.height }, getViewport(), popupGap);
|
|
152
173
|
setPopupPosition(result.position);
|
|
153
174
|
setPopupOrigin(result.origin);
|
|
154
175
|
}
|
|
@@ -164,23 +185,23 @@ export function useEdgeDock(config = {}) {
|
|
|
164
185
|
if (!buttonRef.current)
|
|
165
186
|
return;
|
|
166
187
|
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
167
|
-
const viewport =
|
|
188
|
+
const viewport = getViewport();
|
|
168
189
|
const buttonDimensions = { width: buttonRect.width, height: buttonRect.height };
|
|
169
190
|
let finalPos = constrainToViewport(newPos, viewport, buttonDimensions);
|
|
170
191
|
if (dockMode === 'auto') {
|
|
171
|
-
const edge = getClosestEdge(finalPos, viewport);
|
|
172
|
-
finalPos = snapToEdge(finalPos, edge, viewport, buttonDimensions);
|
|
192
|
+
const edge = getClosestEdge(finalPos, viewport, allowedEdges);
|
|
193
|
+
finalPos = snapToEdge(finalPos, edge, viewport, buttonDimensions, edgeOffset);
|
|
173
194
|
setDockedEdge(edge);
|
|
174
195
|
}
|
|
175
196
|
else if (dockMode === 'manual' && dockEdge) {
|
|
176
|
-
finalPos = snapToEdge(finalPos, dockEdge, viewport, buttonDimensions);
|
|
197
|
+
finalPos = snapToEdge(finalPos, dockEdge, viewport, buttonDimensions, edgeOffset);
|
|
177
198
|
setDockedEdge(dockEdge);
|
|
178
199
|
}
|
|
179
200
|
else {
|
|
180
201
|
setDockedEdge(null);
|
|
181
202
|
}
|
|
182
203
|
setPositionInternal(finalPos);
|
|
183
|
-
}, [dockMode, dockEdge]);
|
|
204
|
+
}, [dockMode, dockEdge, allowedEdges, edgeOffset]);
|
|
184
205
|
// Toggle popup
|
|
185
206
|
const togglePopup = useCallback(() => {
|
|
186
207
|
const newState = !isPopupOpen;
|
|
@@ -258,16 +279,16 @@ export function useEdgeDock(config = {}) {
|
|
|
258
279
|
// Snap to edge if needed
|
|
259
280
|
if (buttonRef.current) {
|
|
260
281
|
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
261
|
-
const viewport =
|
|
282
|
+
const viewport = getViewport();
|
|
262
283
|
const buttonDimensions = { width: buttonRect.width, height: buttonRect.height };
|
|
263
284
|
let finalPos = position;
|
|
264
285
|
if (dockMode === 'auto') {
|
|
265
|
-
const edge = getClosestEdge(position, viewport);
|
|
266
|
-
finalPos = snapToEdge(position, edge, viewport, buttonDimensions);
|
|
286
|
+
const edge = getClosestEdge(position, viewport, allowedEdges);
|
|
287
|
+
finalPos = snapToEdge(position, edge, viewport, buttonDimensions, edgeOffset);
|
|
267
288
|
setDockedEdge(edge);
|
|
268
289
|
}
|
|
269
290
|
else if (dockMode === 'manual' && dockEdge) {
|
|
270
|
-
finalPos = snapToEdge(position, dockEdge, viewport, buttonDimensions);
|
|
291
|
+
finalPos = snapToEdge(position, dockEdge, viewport, buttonDimensions, edgeOffset);
|
|
271
292
|
setDockedEdge(dockEdge);
|
|
272
293
|
}
|
|
273
294
|
setPositionInternal(finalPos);
|
|
@@ -279,7 +300,7 @@ export function useEdgeDock(config = {}) {
|
|
|
279
300
|
document.removeEventListener('pointermove', handlePointerMove);
|
|
280
301
|
document.removeEventListener('pointerup', handlePointerUp);
|
|
281
302
|
};
|
|
282
|
-
}, [position, dockMode, dockEdge, animation]);
|
|
303
|
+
}, [position, dockMode, dockEdge, animation, edgeOffset, allowedEdges]);
|
|
283
304
|
// Click handler (only trigger if not dragged)
|
|
284
305
|
const handleClick = useCallback((e) => {
|
|
285
306
|
if (dragStateRef.current.hasMoved) {
|
|
@@ -291,26 +312,28 @@ export function useEdgeDock(config = {}) {
|
|
|
291
312
|
}, [togglePopup]);
|
|
292
313
|
// Handle window resize
|
|
293
314
|
useEffect(() => {
|
|
315
|
+
if (!isBrowser)
|
|
316
|
+
return;
|
|
294
317
|
const handleResize = () => {
|
|
295
318
|
if (buttonRef.current) {
|
|
296
319
|
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
297
|
-
const viewport =
|
|
320
|
+
const viewport = getViewport();
|
|
298
321
|
const buttonDimensions = { width: buttonRect.width, height: buttonRect.height };
|
|
299
322
|
let newPos = constrainToViewport(position, viewport, buttonDimensions);
|
|
300
323
|
if (dockMode === 'auto') {
|
|
301
|
-
const edge = getClosestEdge(newPos, viewport);
|
|
302
|
-
newPos = snapToEdge(newPos, edge, viewport, buttonDimensions);
|
|
324
|
+
const edge = getClosestEdge(newPos, viewport, allowedEdges);
|
|
325
|
+
newPos = snapToEdge(newPos, edge, viewport, buttonDimensions, edgeOffset);
|
|
303
326
|
setDockedEdge(edge);
|
|
304
327
|
}
|
|
305
328
|
else if (dockMode === 'manual' && dockEdge) {
|
|
306
|
-
newPos = snapToEdge(newPos, dockEdge, viewport, buttonDimensions);
|
|
329
|
+
newPos = snapToEdge(newPos, dockEdge, viewport, buttonDimensions, edgeOffset);
|
|
307
330
|
}
|
|
308
331
|
setPositionInternal(newPos);
|
|
309
332
|
}
|
|
310
333
|
};
|
|
311
334
|
window.addEventListener('resize', handleResize);
|
|
312
335
|
return () => window.removeEventListener('resize', handleResize);
|
|
313
|
-
}, [position, dockMode, dockEdge]);
|
|
336
|
+
}, [position, dockMode, dockEdge, edgeOffset, allowedEdges]);
|
|
314
337
|
// Button styles
|
|
315
338
|
const buttonStyles = {
|
|
316
339
|
position: 'fixed',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-edge-dock",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A zero-dependency React TypeScript library for customizable draggable edge-docked floating buttons with popup support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|