From db11c3e6c829dbc4f103b58767cd2ca187a24b30 Mon Sep 17 00:00:00 2001 From: Geido <60598000+geido@users.noreply.github.com> Date: Wed, 25 Aug 2021 16:11:16 +0300 Subject: [PATCH] feat: Draggable and Resizable Modal (#16394) * Implement resizable prop * Implement resizableConfig * Implement fluid Syntax Highlighter * Implement draggable * Destroy on close * Implement draggableConfig * Enhance with footer calculation * Add new line * Make whole header draggable trigger --- superset-frontend/package.json | 1 + .../src/components/Modal/Modal.stories.tsx | 2 + .../src/components/Modal/Modal.tsx | 148 +++++++++++++++++- .../ModalTrigger/ModalTrigger.stories.tsx | 4 + .../src/components/ModalTrigger/index.jsx | 10 ++ .../ExploreAdditionalActionsMenu/index.jsx | 2 + .../components/controls/ViewQueryModal.tsx | 16 +- 7 files changed, 175 insertions(+), 8 deletions(-) diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 4911500cde..70a80b0df2 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -147,6 +147,7 @@ "react-dnd": "^11.1.3", "react-dnd-html5-backend": "^11.1.3", "react-dom": "^16.13.0", + "react-draggable": "^4.4.3", "react-gravatar": "^2.6.1", "react-hot-loader": "^4.12.20", "react-js-cron": "^1.2.0", diff --git a/superset-frontend/src/components/Modal/Modal.stories.tsx b/superset-frontend/src/components/Modal/Modal.stories.tsx index 4a7b06eaee..b3557c5a8b 100644 --- a/superset-frontend/src/components/Modal/Modal.stories.tsx +++ b/superset-frontend/src/components/Modal/Modal.stories.tsx @@ -34,6 +34,8 @@ InteractiveModal.args = { primaryButtonType: 'danger', show: true, title: "I'm a modal!", + resizable: false, + draggable: false, }; InteractiveModal.argTypes = { diff --git a/superset-frontend/src/components/Modal/Modal.tsx b/superset-frontend/src/components/Modal/Modal.tsx index 3dbdb62a3c..a885538204 100644 --- a/superset-frontend/src/components/Modal/Modal.tsx +++ b/superset-frontend/src/components/Modal/Modal.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useRef, useState } from 'react'; import { isNil } from 'lodash'; import { styled, t } from '@superset-ui/core'; import { css } from '@emotion/react'; @@ -25,6 +25,13 @@ import { ModalProps as AntdModalProps, } from 'src/common/components'; import Button from 'src/components/Button'; +import { Resizable, ResizableProps } from 're-resizable'; +import Draggable, { + DraggableBounds, + DraggableData, + DraggableEvent, + DraggableProps, +} from 'react-draggable'; export interface ModalProps { className?: string; @@ -46,6 +53,10 @@ export interface ModalProps { wrapProps?: object; height?: string; closable?: boolean; + resizable?: boolean; + resizableConfig?: ResizableProps; + draggable?: boolean; + draggableConfig?: DraggableProps; destroyOnClose?: boolean; } @@ -54,8 +65,19 @@ interface StyledModalProps { responsive?: boolean; height?: string; hideFooter?: boolean; + draggable?: boolean; + resizable?: boolean; } +const MODAL_HEADER_HEIGHT = 55; +const MODAL_MIN_CONTENT_HEIGHT = 54; +const MODAL_FOOTER_HEIGHT = 65; + +const RESIZABLE_MIN_HEIGHT = MODAL_HEADER_HEIGHT + MODAL_MIN_CONTENT_HEIGHT; +const RESIZABLE_MIN_WIDTH = '380px'; +const RESIZABLE_MAX_HEIGHT = '100vh'; +const RESIZABLE_MAX_WIDTH = '100vw'; + const BaseModal = (props: AntdModalProps) => ( // Removes mask animation. Fixed in 4.6.0. // https://github.com/ant-design/ant-design/issues/27192 @@ -101,9 +123,8 @@ export const StyledModal = styled(BaseModal)` .ant-modal-body { padding: ${({ theme }) => theme.gridUnit * 4}px; overflow: auto; - ${({ height }) => height && `height: ${height};`} + ${({ resizable, height }) => !resizable && height && `height: ${height};`} } - .ant-modal-footer { border-top: ${({ theme }) => theme.gridUnit / 4}px solid ${({ theme }) => theme.colors.grayscale.light2}; @@ -129,6 +150,44 @@ export const StyledModal = styled(BaseModal)` &.no-content-padding .ant-modal-body { padding: 0; } + + ${({ draggable, theme }) => + draggable && + ` + .ant-modal-header { + padding: 0; + .draggable-trigger { + cursor: move; + padding: ${theme.gridUnit * 4}px; + width: 100%; + } + } + `}; + + ${({ resizable, hideFooter }) => + resizable && + ` + .resizable { + pointer-events: all; + + .resizable-wrapper { + height: 100%; + } + + .ant-modal-content { + height: 100%; + + .ant-modal-body { + /* 100% - header height - footer height */ + height: ${ + hideFooter + ? `calc(100% - ${MODAL_HEADER_HEIGHT}px);` + : `calc(100% - ${MODAL_HEADER_HEIGHT}px - ${MODAL_FOOTER_HEIGHT}px);` + } + } + } + } + `} `; const CustomModal = ({ @@ -148,8 +207,33 @@ const CustomModal = ({ footer, hideFooter, wrapProps, + draggable = false, + resizable = false, + resizableConfig = { + maxHeight: RESIZABLE_MAX_HEIGHT, + maxWidth: RESIZABLE_MAX_WIDTH, + minHeight: hideFooter + ? RESIZABLE_MIN_HEIGHT + : RESIZABLE_MIN_HEIGHT + MODAL_FOOTER_HEIGHT, + minWidth: RESIZABLE_MIN_WIDTH, + enable: { + bottom: true, + bottomLeft: false, + bottomRight: true, + left: false, + top: false, + topLeft: false, + topRight: false, + right: true, + }, + }, + draggableConfig, + destroyOnClose, ...rest }: ModalProps) => { + const draggableRef = useRef(null); + const [bounds, setBounds] = useState(); + const [dragDisabled, setDragDisabled] = useState(true); const modalFooter = isNil(footer) ? [