Skip to content

Commit d3b5213

Browse files
committed
Finalized the draggable implementation
1 parent 93be92c commit d3b5213

File tree

4 files changed

+132
-51
lines changed

4 files changed

+132
-51
lines changed

src/ui/Dialog/Dialog.stories.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,54 @@ export function PreventScrollingBehind () {
161161
</>
162162
)
163163
}
164+
165+
export function Draggable () {
166+
const [dialogOpen, setIsDialogOpen] = useState(false)
167+
const [dialog2Open, setDialog2Open] = useState(false)
168+
169+
return (
170+
<>
171+
<div>
172+
<Button onClick={() => { setIsDialogOpen(!dialogOpen) }}>{`${!dialogOpen ? 'Åpne' : 'Lukk'} modal`}</Button>
173+
</div>
174+
<p />
175+
176+
<Dialog
177+
isOpen={dialogOpen}
178+
onDismiss={() => { setIsDialogOpen(false) }}
179+
showCloseButton
180+
draggable
181+
contained
182+
width='60%'
183+
height='50%'
184+
>
185+
<DialogTitle>This is the first dialog</DialogTitle>
186+
<DialogBody>
187+
Click and drag the top part of the dialog to move it.<br />
188+
This dialog is <b>contained</b> and is not allowed to move outside it's parents bounds
189+
</DialogBody>
190+
<DialogActions>
191+
<Button size='small' onClick={() => setDialog2Open(true)}>Open nested dialog</Button>
192+
<Button size='small' type='secondary' onClick={() => { setIsDialogOpen(false) }}>Close</Button>
193+
</DialogActions>
194+
</Dialog>
195+
<Dialog
196+
isOpen={dialog2Open}
197+
onDismiss={() => setDialog2Open(false)}
198+
width='50%'
199+
draggable
200+
height='30%'
201+
>
202+
<DialogTitle>
203+
Draggable nested dialog
204+
</DialogTitle>
205+
<DialogBody>
206+
This dialog is also draggable, but it is not <b>contained</b>
207+
</DialogBody>
208+
<DialogActions>
209+
<Button size='small' onClick={() => setDialog2Open(false)}>Close</Button>
210+
</DialogActions>
211+
</Dialog>
212+
</>
213+
)
214+
}

src/ui/Dialog/index.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import './styles.scss'
88
import ScrollLock from 'react-scrolllock'
99
import { Draggable } from '../Draggable'
1010

11-
export function Dialog ({ isOpen, title, className, persistent, width, height, draggable, showCloseButton, onDismiss, onCloseBtnClick, onClickOutside, onPressEscape, style, ...props }) {
11+
export function Dialog ({ isOpen, title, className, persistent, width, height, draggable, contained, showCloseButton, onDismiss, onCloseBtnClick, onClickOutside, onPressEscape, style, ...props }) {
1212
// Set an unique ID for the dialog
1313
const [id] = useState(`${nanoid()}`)
1414

1515
const [clickStartedInsideDialog, setClickStartedInsideDialog] = useState(undefined)
16+
const [isDragging, setIsDragging] = useState(false)
1617

1718
// onCreated lifecycle-hook
1819
useEffect(() => {
@@ -36,6 +37,7 @@ export function Dialog ({ isOpen, title, className, persistent, width, height, d
3637

3738
function handleMouseUp (e) {
3839
setClickStartedInsideDialog(false)
40+
setIsDragging(false)
3941
}
4042

4143
// Register eventhandlers
@@ -53,13 +55,20 @@ export function Dialog ({ isOpen, title, className, persistent, width, height, d
5355
_s.width = width || '100%'
5456
_s.height = height || '100%'
5557
} else {
56-
_s.width = '100%';
57-
_s.height = '100%';
58+
_s.width = '100%'
59+
_s.height = '100%'
5860
}
5961

6062
return _s
6163
}, [draggable, width, height])
6264

65+
const dialogClasses = useMemo(() => {
66+
let classes = 'dialog'
67+
if (isDragging) classes += ' dialog-no-select'
68+
69+
return classes
70+
}, [isDragging])
71+
6372
// Plays the shaking animation-effect when the dialog is persistent
6473
function shakeDialogBox () {
6574
const dialog = document.getElementById(`dialog-${id}`)
@@ -93,17 +102,17 @@ export function Dialog ({ isOpen, title, className, persistent, width, height, d
93102
isOpen === true &&
94103
<ScrollLock isActive={isOpen}>
95104
<div id={`dialog-backdrop-${id}`} className={`dialog-backdrop ${className}`} onMouseUp={(e) => handleBackdropClick(e)}>
96-
<Draggable active={draggable} width={width} height={height}>
105+
<Draggable active={draggable && isDragging} contained={contained} width={width} height={height}>
97106
<div
98107
id={`dialog-${id}`}
99-
className='dialog'
108+
className={dialogClasses}
100109
aria-label='dialog'
101110
aria-modal='true'
102111
role='dialog'
103-
style={style}
104-
onMouseDown={(e) => { setClickStartedInsideDialog(true) }}
112+
style={dialogStyle}
113+
onMouseDown={() => { setClickStartedInsideDialog(true) }}
105114
>
106-
<div className='dialog-drag-area'/>
115+
<div className='dialog-drag-area' onMouseDown={() => setIsDragging(true)} />
107116
{!persistent && showCloseButton &&
108117
<button className='dialog-close-btn' onClick={(e) => { handleCloseBtnClick(); e.preventDefault() }} aria-label='Lukk'>
109118
<CloseIcon alt='' />
@@ -144,6 +153,7 @@ export function DialogActions ({ children, style }) {
144153
Dialog.propTypes = {
145154
children: PropTypes.any,
146155
className: PropTypes.string,
156+
contained: PropTypes.bool,
147157
draggable: PropTypes.bool,
148158
height: PropTypes.string,
149159
isOpen: PropTypes.bool.isRequired,

src/ui/Dialog/styles.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,15 @@
4545
position: absolute;
4646
top: 0;
4747
left: 0;
48-
right: 20%;
48+
right: 0;
4949
height: $baseUnit * 4;
5050
cursor: move;
5151
}
5252

53+
.dialog-no-select {
54+
user-select: none!important;
55+
}
56+
5357
.dialog-title {
5458
font-weight: 500;
5559
font-size: x-large;

src/ui/Draggable/index.js

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,93 @@
11
import React, { useEffect, useMemo, useRef, useState } from 'react'
2+
import PropTypes from 'prop-types'
23
import './styles.scss'
34

4-
export function Draggable ({ children, active, width, height }) {
5+
export function Draggable ({ children, active, contained, width, height }) {
56
const [isDragging, setIsDragging] = useState(false)
67
const [coordinates, setCoordinates] = useState(undefined)
8+
const [mouseSelfDelta, setMouseSelfDelta] = useState(undefined)
79
const thisRef = useRef(undefined)
810

911
const parsedStyle = useMemo(() => {
10-
let _s = { width, height };
11-
if(coordinates?.x && coordinates?.y) {
12+
const _s = { width, height }
13+
if (coordinates?.x && coordinates?.y) {
1214
_s.top = `${coordinates.y}px`
1315
_s.left = `${coordinates.x}px`
1416
}
15-
console.log('Style', _s);
16-
return _s;
17+
return _s
1718
}, [width, height, coordinates])
1819

1920
useEffect(() => {
21+
/**
22+
*
23+
* @param {MouseEvent} e
24+
* @returns
25+
*/
2026
function handleMouseMove (e) {
21-
if(!isDragging) return;
22-
console.log('Moving', e)
23-
console.log(`Client - ${e.clientX}:${e.clientY}`)
24-
console.log(`Layer - ${e.layerX}:${e.layerY}`)
25-
console.log(`Offset - ${e.offsetX}:${e.offsetY}`)
26-
console.log(`Page - ${e.pageX}:${e.pageY}`)
27-
console.log(`Screen - ${e.screenX}:${e.screenY}`)
28-
console.log('Ref', thisRef?.current)
29-
30-
31-
if(thisRef?.current) {
32-
console.dir(thisRef.current)
33-
console.log(`Client - ${e.clientX}:${e.clientY}`)
34-
console.log(`Dialog - ${thisRef.current.offsetLeft} : ${thisRef.current.offsetTop} `)
35-
36-
let deltaX = e.clientX - (thisRef.current.offsetLeft / 2);
37-
let deltaY = e.clientY - (thisRef.current.offsetTop / 2);
38-
39-
// deltaX = Math.max(deltaX, 0);
40-
// deltaY = Math.min(1080, deltaY);
27+
if (!active || !isDragging) return
28+
29+
if (thisRef?.current) {
30+
let x = e.clientX - mouseSelfDelta.x
31+
let y = e.clientY - mouseSelfDelta.y
32+
33+
const width = thisRef.current.clientWidth
34+
const height = thisRef.current.clientHeight
35+
const bboxRight = x + thisRef.current.clientWidth
36+
const bboxBottom = y + height
37+
const parentWidth = thisRef.current.parentNode.clientWidth
38+
const parentHeight = thisRef.current.parentNode.clientHeight
39+
40+
if (contained) {
41+
if (x <= 1) x = 1
42+
else if (bboxRight >= parentWidth) x = parentWidth - width
43+
if (y <= 1) y = 1
44+
else if (bboxBottom >= parentHeight) y = parentHeight - height
45+
} else {
46+
if (x === 0) x = -1
47+
if (y === 0) y = -1
48+
}
4149

4250
setCoordinates({
43-
x: deltaX,
44-
y: deltaY
51+
x,
52+
y
4553
})
4654
}
4755
}
48-
console.log('Registering event listerner');
56+
4957
document.addEventListener('mousemove', handleMouseMove)
5058

5159
return () => {
52-
console.log('Unregistering')
5360
document.removeEventListener('mousemove', handleMouseMove)
5461
}
55-
}, [isDragging])
62+
}, [active, isDragging])
5663

5764
useEffect(() => {
58-
function handleMouseUp() {
59-
setIsDragging(false);
65+
function handleMouseUp () {
66+
setIsDragging(false)
6067
}
6168

6269
document.addEventListener('mouseup', handleMouseUp)
6370

6471
return () => {
65-
document.removeEventListener('mouseup', handleMouseUp);
72+
document.removeEventListener('mouseup', handleMouseUp)
6673
}
6774
})
6875

69-
useEffect(() => {
76+
function handleDragStart (e) {
7077
if (thisRef) {
71-
setCoordinates(thisRef)
78+
setIsDragging(true)
79+
setMouseSelfDelta({
80+
x: e.clientX - thisRef.current.offsetLeft,
81+
y: e.clientY - thisRef.current.offsetTop
82+
})
83+
setCoordinates({
84+
x: thisRef.current.offsetLeft,
85+
y: thisRef.current.offsetTop
86+
})
7287
}
73-
console.log(coordinates)
74-
}, [thisRef])
75-
76-
function handleDragStart (e) {
77-
console.log('Drag started', e)
78-
setIsDragging(true)
7988
}
8089

8190
function handleDragEnd (e) {
82-
console.log('Drag stopped')
8391
setIsDragging(false)
8492
}
8593

@@ -89,3 +97,11 @@ export function Draggable ({ children, active, width, height }) {
8997
</div>
9098
)
9199
}
100+
101+
Draggable.propTypes = {
102+
active: PropTypes.bool,
103+
children: PropTypes.node,
104+
contained: PropTypes.bool,
105+
height: PropTypes.string,
106+
width: PropTypes.string
107+
}

0 commit comments

Comments
 (0)