diff --git a/.eslintignore b/.eslintignore index ffd57a46d..82e735037 100755 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/package-lock.json b/package-lock.json index de97d1a0e..194e77c32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.22.0-pre-0", + "version": "1.22.0-alpha-11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.22.0-pre-0", + "version": "1.22.0-alpha-11", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index c18a3d755..b36090676 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.22.0-pre-0", + "version": "1.22.0-alpha-11", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Common/DraggableWrapper/DraggableWrapper.tsx b/src/Common/DraggableWrapper/DraggableWrapper.tsx index e1c89467a..49db62f71 100644 --- a/src/Common/DraggableWrapper/DraggableWrapper.tsx +++ b/src/Common/DraggableWrapper/DraggableWrapper.tsx @@ -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, + boundaryGap = { x: 16, y: 16 }, childDivProps = {}, - layoutFixDelta = 0, -}: DraggableWrapperProps) { +}: DraggableWrapperProps) => { const windowSize = useWindowSize() const nodeRef = useRef(null) - const [position, setPosition] = useState({ - 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 => { // if this return x: 0, y: 0 then it will be top left corner of parentDiv const parentRect = parentRef?.current?.getBoundingClientRect() ?? @@ -60,73 +61,62 @@ 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) { + // since the parent itself overflows, we use windowSize for calculations + return { x, y: windowSize.height - boundaryGap.y - nodeRefHeight } + } + // y = parentRect.bottom will place the widget at the extreme bottom of the parent, + // therefore need to offset it to the top by boundary and its own height + const y = parentRect.bottom - nodeRefHeight - boundaryGap.y return { x, y } } case DraggablePositionVariant.SCREEN_BOTTOM_RIGHT: { - const x = windowSize.width - parentRect.left - nodeRefWidth - boundaryGap - const y = windowSize.height - parentRect.top - nodeRefHeight - boundaryGap + // x = windowSize.width will place the widget at the extreme right, + // therefore need to offset it to the left by boundary and its own width + const x = windowSize.width - nodeRefWidth - boundaryGap.x + // y = windowSize.height will place the widget at the extreme bottom, + // therefore need to offset it to the top by boundary and its own height + const y = windowSize.height - nodeRefHeight - boundaryGap.y 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 + // we need to first place the start of the widget at (windowSize.width / 2) + // followed by moving it half of its own width to the left such that center of widget + // aligns with the central axis of the screen + const x = (windowSize.width - nodeRefWidth) / 2 + // y = windowSize.height will place the widget at the extreme bottom, + // therefore need to offset it to the top by boundary and its own height + const y = windowSize.height - nodeRefHeight - boundaryGap.y 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
- +
) } + +export default DraggableWrapper diff --git a/src/Common/DraggableWrapper/types.ts b/src/Common/DraggableWrapper/types.ts index 91b9f5f82..f1a7d5b9f 100644 --- a/src/Common/DraggableWrapper/types.ts +++ b/src/Common/DraggableWrapper/types.ts @@ -18,6 +18,7 @@ import { HTMLAttributes, ReactNode, RefObject } from 'react' export enum DraggablePositionVariant { PARENT_BOTTOM_CENTER = 'PARENT_BOTTOM_CENTER', + PARENT_BOTTOM_RIGHT = 'PARENT_BOTTOM_RIGHT', SCREEN_BOTTOM_CENTER = 'SCREEN_BOTTOM_CENTER', SCREEN_BOTTOM_RIGHT = 'SCREEN_BOTTOM_RIGHT', // Can add more based on requirement @@ -35,13 +36,8 @@ export interface DraggableWrapperProps { */ dragSelector: string parentRef?: RefObject - boundaryGap?: number + boundaryGap?: Record<'x' | 'y', number> childDivProps?: HTMLAttributes - /** - * Delta for fixing the scrollable layout positioning - * @deprecated - */ - layoutFixDelta?: number } /** diff --git a/src/Shared/Components/FloatingVariablesSuggestions/FloatingVariablesSuggestions.tsx b/src/Shared/Components/FloatingVariablesSuggestions/FloatingVariablesSuggestions.tsx index f685c4a07..44bad9c51 100644 --- a/src/Shared/Components/FloatingVariablesSuggestions/FloatingVariablesSuggestions.tsx +++ b/src/Shared/Components/FloatingVariablesSuggestions/FloatingVariablesSuggestions.tsx @@ -14,17 +14,16 @@ * limitations under the License. */ -import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import Draggable from 'react-draggable' +import React, { memo, useCallback, useState } from 'react' import Tippy from '@tippyjs/react' import { ReactComponent as ICDrag } from '@Icons/ic-drag.svg' +import { DraggablePositionVariant, DraggableWrapper } from '@Common/DraggableWrapper' import { useAsync } from '@Common/Helper' -import { useWindowSize } from '@Common/Hooks' import { ALLOW_ACTION_OUTSIDE_FOCUS_TRAP } from '@Shared/constants' import { Icon } from '../Icon' -import { SUGGESTIONS_SIZE } from './constants' +import { DRAG_SELECTOR } from './constants' import { getScopedVariables } from './service' import Suggestions from './Suggestions' import { FloatingVariablesSuggestionsProps } from './types' @@ -35,98 +34,26 @@ import { FloatingVariablesSuggestionsProps } from './types' * @param appId - To fetch the scoped variables * @param envId - (Optional) * @param clusterId - (Optional) - * @param bounds - (Optional) To set the bounds of the suggestions * @param hideObjectVariables - (Optional) To hide the object/array variables, default is true * @returns */ const FloatingVariablesSuggestions = ({ - zIndex, appId, envId, clusterId, - bounds, hideObjectVariables = true, showValueOnHover = true, isTemplateView, + boundaryGap, }: FloatingVariablesSuggestionsProps) => { const [isActive, setIsActive] = useState(false) - const [collapsedPosition, setCollapsedPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }) - const [expandedPosition, setExpandedPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }) const [loadingScopedVariables, variablesData, error, reloadScopedVariables] = useAsync( () => getScopedVariables(appId, envId, clusterId, { hideObjectVariables, isTemplateView }), [appId, envId, clusterId], ) - const windowSize = useWindowSize() - // In case of StrictMode, we get error findDOMNode is deprecated in StrictMode - // So we use useRef to get the DOM node - const nodeRef = useRef(null) - - // nodeRef.current is dependency even though its a ref as initially its null and we need to get the - // first value that it gets and after that is not going to trigger again - const initialPosition = useMemo(() => { - const initialPositionData = nodeRef.current?.getBoundingClientRect() || { - x: 0, - y: 0, - } - return { x: initialPositionData.x, y: initialPositionData.y } - }, [nodeRef.current]) - - // The size of the active state can expand say in case user expands SuggestionsInfo and the widget is at bottom of screen - useEffect(() => { - const resizeObserver = new ResizeObserver((entries) => { - if (entries?.length > 0 && isActive) { - const { height } = entries[0].contentRect - if (initialPosition.y + expandedPosition.y + height > windowSize.height) { - setExpandedPosition({ - x: expandedPosition.x, - y: windowSize.height - height - initialPosition.y, - }) - } - } - }) - resizeObserver.observe(nodeRef.current) - return () => { - resizeObserver.disconnect() - } - }, [isActive, expandedPosition, windowSize, initialPosition]) - const handleActivation = () => { - const currentPosInScreen = { - x: initialPosition.x + collapsedPosition.x, - y: initialPosition.y + collapsedPosition.y, - } - - setExpandedPosition({ - x: collapsedPosition.x, - y: collapsedPosition.y, - }) - - if (currentPosInScreen.y > windowSize.height - SUGGESTIONS_SIZE.height) { - setExpandedPosition({ - x: collapsedPosition.x, - y: windowSize.height - SUGGESTIONS_SIZE.height - initialPosition.y, - }) - } - - if (currentPosInScreen.x > windowSize.width - SUGGESTIONS_SIZE.width) { - setExpandedPosition({ - x: windowSize.width - SUGGESTIONS_SIZE.width - initialPosition.x, - y: collapsedPosition.y, - }) - } - - if ( - currentPosInScreen.x > windowSize.width - SUGGESTIONS_SIZE.width && - currentPosInScreen.y > windowSize.height - SUGGESTIONS_SIZE.height - ) { - setExpandedPosition({ - x: windowSize.width - SUGGESTIONS_SIZE.width - initialPosition.x, - y: windowSize.height - SUGGESTIONS_SIZE.height - initialPosition.y, - }) - } - setIsActive(true) } @@ -136,103 +63,66 @@ const FloatingVariablesSuggestions = ({ setIsActive(false) }, []) - // e will be unused, but we need to pass it as a parameter since Draggable expects it - const handleCollapsedDrag = (e, data: { x: number; y: number }) => { - const currentPosInScreen = { - x: initialPosition.x + data.x, - y: initialPosition.y + data.y, - } - if ( - currentPosInScreen.y < 0 || - currentPosInScreen.x < 0 || - currentPosInScreen.x + (nodeRef.current?.getBoundingClientRect().width || 0) > windowSize.width || - currentPosInScreen.y + (nodeRef.current?.getBoundingClientRect().height || 0) > windowSize.height - ) { - return - } - - setCollapsedPosition(data) - } - - const handleExpandedDrag = (e, data: { x: number; y: number }) => { - const currentPosInScreen = { - x: initialPosition.x + data.x, - y: initialPosition.y + data.y, - } - if ( - currentPosInScreen.y < 0 || - currentPosInScreen.x < 0 || - currentPosInScreen.x + (nodeRef.current?.getBoundingClientRect().width || 0) > windowSize.width || - currentPosInScreen.y + (nodeRef.current?.getBoundingClientRect().height || 0) > windowSize.height - ) { - return - } - setExpandedPosition(data) - // Only Need to retain the collapsed position if the user has not dragged the suggestions, so need to update - setCollapsedPosition(data) - } - - if (!isActive) { - return ( - -
+
+ - - - - - -
- - ) - } - return ( - -
- + + + +
+ +
+ +
+ +
+ +
+
-
+ ) } diff --git a/src/Shared/Components/FloatingVariablesSuggestions/Suggestions.tsx b/src/Shared/Components/FloatingVariablesSuggestions/Suggestions.tsx index 5dd91db7e..1e0057f3c 100644 --- a/src/Shared/Components/FloatingVariablesSuggestions/Suggestions.tsx +++ b/src/Shared/Components/FloatingVariablesSuggestions/Suggestions.tsx @@ -24,7 +24,7 @@ import { ALLOW_ACTION_OUTSIDE_FOCUS_TRAP, ComponentSizeType } from '@Shared/cons import { Button, ButtonStyleType, ButtonVariantType } from '../Button' import { Icon } from '../Icon' -import { NO_DEFINED_DESCRIPTION, NO_DEFINED_VALUE } from './constants' +import { DRAG_SELECTOR, NO_DEFINED_DESCRIPTION, NO_DEFINED_VALUE } from './constants' import SuggestionItem from './SuggestionItem' import SuggestionsInfo from './SuggestionsInfo' import { ScopedVariableType, SuggestionsProps } from './types' @@ -75,7 +75,9 @@ const Suggestions = ({ const renderHeader = (): JSX.Element => (
-
+

Scoped variables

diff --git a/src/Shared/Components/FloatingVariablesSuggestions/constants.ts b/src/Shared/Components/FloatingVariablesSuggestions/constants.ts index d2c1d6c5c..eaa74d7d8 100644 --- a/src/Shared/Components/FloatingVariablesSuggestions/constants.ts +++ b/src/Shared/Components/FloatingVariablesSuggestions/constants.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -export const SUGGESTIONS_SIZE = { - width: 356, - height: 504, -} - export const NO_DEFINED_DESCRIPTION = 'No Defined Description' export const NO_DEFINED_VALUE = 'No Defined Value' export const SUGGESTIONS_INFO_TITLE = 'What is scoped variable?' +export const DRAG_SELECTOR = 'handle-drag' diff --git a/src/Shared/Components/FloatingVariablesSuggestions/types.ts b/src/Shared/Components/FloatingVariablesSuggestions/types.ts index 1c5230fd3..c33e8ebae 100644 --- a/src/Shared/Components/FloatingVariablesSuggestions/types.ts +++ b/src/Shared/Components/FloatingVariablesSuggestions/types.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { DraggableBounds } from 'react-draggable' - +import { DraggableWrapperProps } from '@Common/DraggableWrapper' import { AppConfigProps } from '@Pages/index' export interface ScopedVariableType { @@ -27,12 +26,12 @@ export interface ScopedVariableType { isRedacted: boolean } -export interface FloatingVariablesSuggestionsProps extends Required> { - zIndex: number +export interface FloatingVariablesSuggestionsProps + extends Required>, + Pick { appId?: string envId?: string clusterId?: string - bounds?: DraggableBounds | string | false /** * This will hide the variables with object/array values if set to true * @default true