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 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.
@@ -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,qBAkF5C"}
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 */
@@ -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;AA4IjB;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,GAAE,cAAmB,GAAG,iBAAiB,CAiT1E"}
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"}
@@ -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
- const minDistance = Math.min(distanceToLeft, distanceToRight, distanceToTop, distanceToBottom);
11
- if (minDistance === distanceToLeft)
12
- return 'left';
13
- if (minDistance === distanceToRight)
14
- return 'right';
15
- if (minDistance === distanceToTop)
16
- return 'top';
17
- return 'bottom';
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: window.innerWidth - 60, y: window.innerHeight - 60 });
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 }, { width: window.innerWidth, height: window.innerHeight }, popupGap);
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 = { width: window.innerWidth, height: window.innerHeight };
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 = { width: window.innerWidth, height: window.innerHeight };
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 = { width: window.innerWidth, height: window.innerHeight };
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.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",