Skip to content
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ src/Common/DebouncedSearch/__tests__/DebouncedSearch.test.tsx
src/Common/DevtronProgressing/DevtronProgressing.tsx
src/Common/Dialogs/DialogForm.tsx
src/Common/DraggableWrapper/DraggableButton.tsx
src/Common/DraggableWrapper/DraggableWrapper.tsx
src/Common/Drawer/Drawer.tsx
src/Common/Grid/Grid.tsx
src/Common/Helper.tsx
Expand Down
92 changes: 36 additions & 56 deletions src/Common/DraggableWrapper/DraggableWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,37 @@
*/

import { useEffect, useRef, useState } from 'react'
import Draggable, { ControlPosition, DraggableData } from 'react-draggable'
import { DraggableWrapperProps, DraggablePositionVariant } from './types'
import { useWindowSize } from '../Hooks'
import Draggable, { ControlPosition } from 'react-draggable'

import { DEVTRON_BASE_MAIN_ID } from '@Shared/constants'

import { MAX_Z_INDEX } from '../Constants'
import { useWindowSize } from '../Hooks'
import { DraggablePositionVariant, DraggableWrapperProps } from './types'

/**
* TODO: import it as lazy, after it is supported in common
* 1. If using react select please use menuPlacement='auto'
* 2. dragSelector will be used to identify the grabbable button that will grab the div to drag
* 3. parentRef is the reference point from which we will derive the base top:0 ,left: 0 position
* 3. The wrapper is positioned at the viewport's top-left (top: 0, left: 0) using fixed positioning; parentRef is an optional
* reference that may be used for position calculations but is not the base origin for the coordinate system.
*/
export default function DraggableWrapper({
const DraggableWrapper = ({
children,
zIndex = MAX_Z_INDEX,
positionVariant,
dragSelector,
parentRef,
boundaryGap = 16,
childDivProps = {},
layoutFixDelta = 0,
}: DraggableWrapperProps) {
}: DraggableWrapperProps) => {
const windowSize = useWindowSize()
const nodeRef = useRef<HTMLDivElement>(null)

const [position, setPosition] = useState<ControlPosition>({
x: 0,
y: 0,
})
// letting the dom render the element without displaying it so that we know it's dimensions
const [initialRenderDone, setInitialRenderDone] = useState(false)

const getDefaultPosition = (positionVariant: DraggablePositionVariant): ControlPosition => {
const getDefaultPosition = (): ControlPosition => {
Comment thread
AbhishekA1509 marked this conversation as resolved.
// if this return x: 0, y: 0 then it will be top left corner of parentDiv
Comment thread
AbhishekA1509 marked this conversation as resolved.
const parentRect =
parentRef?.current?.getBoundingClientRect() ??
Expand All @@ -60,73 +61,50 @@ export default function DraggableWrapper({

switch (positionVariant) {
case DraggablePositionVariant.PARENT_BOTTOM_CENTER: {
// currently at parentRect.x and need to start to the center of its width and half of node should lie on left of center and other half on right
const x = (parentRect.width - nodeRefWidth) / 2
// TODO (v3): Temp fix. Revisit
const parentRectTop = parentRect.top > 0 ? parentRect.top : layoutFixDelta
// currently at parentRect.y now parent height can be greater than windowSize.height so taking min
// subtracting parentRect.top since window height already contains that
const baseY =
parentRect.height > windowSize.height ? windowSize.height - parentRectTop : parentRect.height
const y = baseY - nodeRefHeight - boundaryGap
// center div to middle of the parent rect and then add the left offset of the parent rect
const x = (parentRect.width - nodeRefWidth) / 2 + parentRect.left
if (parentRect.height > windowSize.height) {
return { x, y: windowSize.height - boundaryGap - nodeRefHeight }
}
const y = parentRect.bottom - nodeRefHeight - boundaryGap
return { x, y }
}
case DraggablePositionVariant.SCREEN_BOTTOM_RIGHT: {
const x = windowSize.width - parentRect.left - nodeRefWidth - boundaryGap
const y = windowSize.height - parentRect.top - nodeRefHeight - boundaryGap
const x = windowSize.width - nodeRefWidth - boundaryGap
const y = windowSize.height - nodeRefHeight - boundaryGap

return { x, y }
}
// Add more cases for other variants if needed
default: {
// Since need node to be in center of screen so subtracting width/2 by left of parentRect it will start the node from center but want node's midpoint at center so subtracting node's width from it.
const x = windowSize.width / 2 - parentRect.left - nodeRefWidth / 2
// subtracting top since windowSize already contains that
const y = windowSize.height - parentRect.top - nodeRefHeight - boundaryGap
const x = (windowSize.width - nodeRefWidth) / 2
Comment thread
whoami-amrit marked this conversation as resolved.
const y = windowSize.height - nodeRefHeight - boundaryGap
Comment thread
whoami-amrit marked this conversation as resolved.
Outdated

return { x, y }
}
}
}

// On change of windowSize we will reset the position to default
useEffect(() => {
const defaultPosition = getDefaultPosition(positionVariant)
setPosition(defaultPosition)
}, [nodeRef, positionVariant, windowSize])

// Would be called on drag and will not update the state if the new position is out of window screen
function handlePositionChange(e, data: DraggableData) {
const offsetX = parentRef?.current?.getBoundingClientRect().left ?? 0
const offsetY = parentRef?.current?.getBoundingClientRect().top ?? 0

const nodeRefHeight = nodeRef.current?.getBoundingClientRect().height ?? 0
const nodeRefWidth = nodeRef.current?.getBoundingClientRect().width ?? 0

if (
offsetX + data.x + nodeRefWidth + boundaryGap > windowSize.width ||
offsetY + data.y + nodeRefHeight + boundaryGap > windowSize.height ||
offsetX + data.x < 0 ||
offsetY + data.y < 0
) {
return
}

setPosition({
x: data.x,
y: data.y,
})
}
// make the element visible after the initial render
setInitialRenderDone(true)
}, [])

return (
// Since we are using position fixed so we need to disable click on the div so that it does not interfere with the click of other elements
<div
className="dc__position-fixed dc__disable-click"
className={`dc__position-fixed dc__disable-click dc__top-0 dc__left-0 ${initialRenderDone ? '' : 'dc__visibility-hidden'}`}
style={{
zIndex,
}}
>
<Draggable handle={dragSelector} nodeRef={nodeRef} position={position} onDrag={handlePositionChange}>
<Draggable
key={`${windowSize.height}-${windowSize.width}-${initialRenderDone}`}
handle={dragSelector}
defaultPosition={getDefaultPosition()}
bounds={`#${DEVTRON_BASE_MAIN_ID}`}
nodeRef={nodeRef}
>
<div
ref={nodeRef}
{...childDivProps}
Expand All @@ -141,3 +119,5 @@ export default function DraggableWrapper({
</div>
)
}

export default DraggableWrapper
5 changes: 0 additions & 5 deletions src/Common/DraggableWrapper/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ export interface DraggableWrapperProps {
parentRef?: RefObject<HTMLDivElement>
boundaryGap?: number
childDivProps?: HTMLAttributes<HTMLDivElement>
/**
* Delta for fixing the scrollable layout positioning
* @deprecated
*/
layoutFixDelta?: number
}

/**
Expand Down