From f14b40cef69da23a236a3a02492139b51ed314e8 Mon Sep 17 00:00:00 2001 From: Valera Melnikov Date: Tue, 6 Feb 2024 10:26:47 +0300 Subject: [PATCH] fix: fix modal position and styles (#30805) ## Description Fixes for the modal widget: 1. Added support for clickOutside. The modal is closed only by clicking on the backdrop overlay. A click on the widget name has been added to the exceptions for close event. 2. Fixed the positioning of the modal. Now it is located in the center of the provider. 3. For the correct positioning of the modal, it was necessary to make fixes for canvas height. The fixes also affect fixed and autolayout. #### PR fixes following issue(s) Fixes #30788 Also fixed this https://www.notion.so/appsmith/Canvas-gets-cut-off-on-preview-mode-525b95f26c6e4644bf5ab7389c02e434 #### Media https://github.com/appsmithorg/appsmith/assets/11555074/6db9ecde-595b-4fa4-a21b-9ed08930d58f #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - Chore (housekeeping or task changes that don't impact user perception) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [x] Jest - [x] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **New Features** - Popover and Modal components now support dismissing by clicking outside. Custom logic and selectors can be specified to control this behavior. - **Enhancements** - Simplified logic for closing Modals by directly setting open state. - Enhanced Modal styling options, allowing for better customization of width and height. - ErrorBoundary component now supports custom styles. - **Refactor** - Removed redundant code and unused properties across various components and layout systems. - Simplified state management and styling adjustments in editor components. - **Style** - Updated Modal component styles for improved layout and responsiveness. - **Chores** - Codebase cleanup including removal of unused classes, variables, and adjustments for more efficient layout rendering. --- .../src/components/Popover/src/types.ts | 4 ++ .../src/components/Popover/src/usePopover.ts | 13 ++++- .../src/components/Modal/src/ModalFooter.tsx | 9 +--- .../src/components/Modal/src/ModalHeader.tsx | 9 +--- .../components/Modal/src/styles.module.css | 10 ++-- .../widgets/src/components/Modal/src/types.ts | 7 ++- .../editorComponents/ErrorBoundry.tsx | 9 +++- .../src/layoutSystems/anvil/canvas/styles.css | 5 -- .../widgetComponent/AnvilWidgetComponent.tsx | 3 +- .../anvil/utils/AnvilDSLTransformer.ts | 1 - .../anvil/utils/layouts/renderUtils.tsx | 1 - .../MainContainerResizer.tsx | 11 ++--- app/client/src/pages/AppViewer/AppPage.tsx | 5 +- app/client/src/pages/Editor/Canvas.tsx | 25 +++++----- .../WidgetsEditor/MainContainerWrapper.tsx | 47 ++++--------------- .../src/pages/Editor/WidgetsEditor/index.tsx | 26 ++++++++-- .../wds/WDSModalWidget/widget/index.tsx | 22 ++++----- 17 files changed, 99 insertions(+), 108 deletions(-) diff --git a/app/client/packages/design-system/headless/src/components/Popover/src/types.ts b/app/client/packages/design-system/headless/src/components/Popover/src/types.ts index 214fcac64d..f7e40e1eeb 100644 --- a/app/client/packages/design-system/headless/src/components/Popover/src/types.ts +++ b/app/client/packages/design-system/headless/src/components/Popover/src/types.ts @@ -47,6 +47,10 @@ export interface PopoverProps { triggerRef?: MutableRefObject; /** Which element to initially focus. Can be either a number (tabbable index as specified by the order) or a ref. */ initialFocus?: number | MutableRefObject; + /** Determines whether clickOutside is work or not. + * @default false + */ + dismissClickOutside?: boolean; } export interface PopoverContentProps { diff --git a/app/client/packages/design-system/headless/src/components/Popover/src/usePopover.ts b/app/client/packages/design-system/headless/src/components/Popover/src/usePopover.ts index 8cce903f3d..674ae60427 100644 --- a/app/client/packages/design-system/headless/src/components/Popover/src/usePopover.ts +++ b/app/client/packages/design-system/headless/src/components/Popover/src/usePopover.ts @@ -17,6 +17,7 @@ const DEFAULT_POPOVER_OFFSET = 10; export function usePopover({ defaultOpen = false, + dismissClickOutside = false, duration = 0, initialFocus, isOpen: controlledOpen, @@ -56,7 +57,17 @@ export function usePopover({ const click = useClick(context, { enabled: controlledOpen == null, }); - const dismiss = useDismiss(context); + const dismiss = useDismiss(context, { + escapeKey: !dismissClickOutside, + outsidePress: (event) => { + if (dismissClickOutside) return false; + + // By default, click to close popup only work inside the provider + return Boolean( + (event?.target as HTMLElement).closest("[data-theme-provider]"), + ); + }, + }); const role = useRole(context); const interactions = useInteractions([click, dismiss, role]); diff --git a/app/client/packages/design-system/widgets/src/components/Modal/src/ModalFooter.tsx b/app/client/packages/design-system/widgets/src/components/Modal/src/ModalFooter.tsx index 55d0f45304..020c0df2d2 100644 --- a/app/client/packages/design-system/widgets/src/components/Modal/src/ModalFooter.tsx +++ b/app/client/packages/design-system/widgets/src/components/Modal/src/ModalFooter.tsx @@ -7,7 +7,7 @@ import type { ModalFooterProps } from "./types"; export const ModalFooter = (props: ModalFooterProps) => { const { closeText = "Close", onSubmit, submitText = "Submit" } = props; - const { onClose, setOpen } = usePopoverContext(); + const { setOpen } = usePopoverContext(); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async () => { @@ -19,14 +19,9 @@ export const ModalFooter = (props: ModalFooterProps) => { } }; - const closeHandler = () => { - onClose && onClose(); - setOpen(false); - }; - return ( - diff --git a/app/client/packages/design-system/widgets/src/components/Modal/src/ModalHeader.tsx b/app/client/packages/design-system/widgets/src/components/Modal/src/ModalHeader.tsx index 7f9071493b..2b50fa3e5f 100644 --- a/app/client/packages/design-system/widgets/src/components/Modal/src/ModalHeader.tsx +++ b/app/client/packages/design-system/widgets/src/components/Modal/src/ModalHeader.tsx @@ -10,7 +10,7 @@ import type { ModalHeaderProps } from "./types"; export const ModalHeader = (props: ModalHeaderProps) => { const { title } = props; - const { onClose, setLabelId, setOpen } = usePopoverContext(); + const { setLabelId, setOpen } = usePopoverContext(); const id = useId(); // Only sets `aria-labelledby` on the Dialog root element @@ -20,17 +20,12 @@ export const ModalHeader = (props: ModalHeaderProps) => { return () => setLabelId(undefined); }, [id, setLabelId]); - const closeHandler = () => { - onClose && onClose(); - setOpen(false); - }; - return ( {title} - + setOpen(false)} variant="ghost" /> ); }; diff --git a/app/client/packages/design-system/widgets/src/components/Modal/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Modal/src/styles.module.css index 2f1ea60afd..b460d819b4 100644 --- a/app/client/packages/design-system/widgets/src/components/Modal/src/styles.module.css +++ b/app/client/packages/design-system/widgets/src/components/Modal/src/styles.module.css @@ -1,18 +1,18 @@ .overlay { - /* Redefining the overlay positioning so that the dialog is centered relative to the provider, not the viewport */ - position: absolute !important; background: var(--color-bg-neutral-opacity); display: grid; place-items: center; z-index: var(--z-index-99); + max-width: var(--provider-width); + margin: 0 auto; } .content { background: var(--color-bg); border-radius: var(--border-radius-1); box-shadow: var(--box-shadow-1); - max-width: calc(100vw - var(--outer-spacing-6)); - max-height: calc(100vh - var(--outer-spacing-6)); + max-width: calc(var(--provider-width) - var(--outer-spacing-8)); + max-height: calc(var(--provider-width) - var(--outer-spacing-8)); outline: none; display: flex; flex-direction: column; @@ -38,7 +38,7 @@ } [data-size="large"] .content { - width: calc(var(--provider-width) - var(--outer-spacing-6)); + width: calc(var(--provider-width) - var(--outer-spacing-8)); height: 100%; } diff --git a/app/client/packages/design-system/widgets/src/components/Modal/src/types.ts b/app/client/packages/design-system/widgets/src/components/Modal/src/types.ts index 7761bfdac1..11277f435e 100644 --- a/app/client/packages/design-system/widgets/src/components/Modal/src/types.ts +++ b/app/client/packages/design-system/widgets/src/components/Modal/src/types.ts @@ -8,7 +8,12 @@ import type { SIZES } from "../../../shared"; export interface ModalProps extends Pick< PopoverProps, - "isOpen" | "setOpen" | "onClose" | "triggerRef" | "initialFocus" + | "isOpen" + | "setOpen" + | "onClose" + | "triggerRef" + | "initialFocus" + | "dismissClickOutside" >, Pick { /** Size of the Modal diff --git a/app/client/src/components/editorComponents/ErrorBoundry.tsx b/app/client/src/components/editorComponents/ErrorBoundry.tsx index 99acb2bff2..9783ddc4ae 100644 --- a/app/client/src/components/editorComponents/ErrorBoundry.tsx +++ b/app/client/src/components/editorComponents/ErrorBoundry.tsx @@ -1,11 +1,13 @@ -import type { ReactNode } from "react"; import React from "react"; import styled from "styled-components"; import * as Sentry from "@sentry/react"; import * as log from "loglevel"; +import type { ReactNode, CSSProperties } from "react"; + interface Props { children: ReactNode; + style?: CSSProperties; } interface State { hasError: boolean; @@ -39,7 +41,10 @@ class ErrorBoundary extends React.Component { render() { return ( - + {this.state.hasError ? (

Oops, Something went wrong. diff --git a/app/client/src/layoutSystems/anvil/canvas/styles.css b/app/client/src/layoutSystems/anvil/canvas/styles.css index bb836d6a5d..c8fd2927c9 100644 --- a/app/client/src/layoutSystems/anvil/canvas/styles.css +++ b/app/client/src/layoutSystems/anvil/canvas/styles.css @@ -3,8 +3,3 @@ width: 100%; height: 100%; } - -.main-anvil-canvas { - height: calc(100% - 1rem); - overflow-y: auto; -} diff --git a/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx b/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx index 79176cc842..7bb702df19 100644 --- a/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx +++ b/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx @@ -22,7 +22,8 @@ export const AnvilWidgetComponent = (props: BaseWidgetProps) => { if (!detachFromLayout) return props.children; return ( - + // delete style as soon as we switch to Anvil layout completely + {props.children} diff --git a/app/client/src/layoutSystems/anvil/utils/AnvilDSLTransformer.ts b/app/client/src/layoutSystems/anvil/utils/AnvilDSLTransformer.ts index 64585681cb..3d033fb242 100644 --- a/app/client/src/layoutSystems/anvil/utils/AnvilDSLTransformer.ts +++ b/app/client/src/layoutSystems/anvil/utils/AnvilDSLTransformer.ts @@ -22,7 +22,6 @@ export function anvilDSLTransformer(dsl: DSLWidget) { layoutStyle: { border: "none", height: "100%", - minHeight: "var(--canvas-height)", padding: "spacing-1", }, isDropTarget: true, diff --git a/app/client/src/layoutSystems/anvil/utils/layouts/renderUtils.tsx b/app/client/src/layoutSystems/anvil/utils/layouts/renderUtils.tsx index 175bade339..bba5a3c9ea 100644 --- a/app/client/src/layoutSystems/anvil/utils/layouts/renderUtils.tsx +++ b/app/client/src/layoutSystems/anvil/utils/layouts/renderUtils.tsx @@ -125,7 +125,6 @@ export function renderWidgetsInAlignedRow( > = { alignSelf: "stretch", canvasId, - columnGap: "4px", direction: "row", flexBasis: { base: "auto", [`${MOBILE_BREAKPOINT}px`]: "0%" }, flexGrow: 1, diff --git a/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx b/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx index 4f2576015e..01c428e9c3 100644 --- a/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx +++ b/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx @@ -13,13 +13,12 @@ const CanvasResizerIcon = importSvg( ); const AutoLayoutCanvasResizer = styled.div` - position: sticky; + position: relative; z-index: var(--on-canvas-ui-z-index); cursor: col-resize; user-select: none; -webkit-user-select: none; width: 2px; - height: 100%; display: flex; background: var(--ads-v2-color-border); align-items: center; @@ -59,12 +58,9 @@ const AutoLayoutCanvasResizer = styled.div` export function MainContainerResizer({ currentPageId, enableMainCanvasResizer, - heightWithTopMargin, isPageInitiated, isPreview, - shouldHaveTopMargin, }: { - heightWithTopMargin: string; isPageInitiated: boolean; shouldHaveTopMargin: boolean; isPreview: boolean; @@ -74,6 +70,7 @@ export function MainContainerResizer({ const appLayout = useSelector(getCurrentApplicationLayout); const ref = useRef(null); const dispatch = useDispatch(); + const topHeaderHeight = "48px"; useEffect(() => { const ele: HTMLElement | null = document.getElementById(CANVAS_VIEWPORT); @@ -174,8 +171,8 @@ export function MainContainerResizer({ }} ref={ref} style={{ - top: "100%", - height: shouldHaveTopMargin ? heightWithTopMargin : "100vh", + top: isPreview ? topHeaderHeight : "0", + height: isPreview ? `calc(100% - ${topHeaderHeight})` : "100%", }} >

diff --git a/app/client/src/pages/AppViewer/AppPage.tsx b/app/client/src/pages/AppViewer/AppPage.tsx index 71e3fdbba2..8fe9bf7abf 100644 --- a/app/client/src/pages/AppViewer/AppPage.tsx +++ b/app/client/src/pages/AppViewer/AppPage.tsx @@ -71,10 +71,7 @@ export function AppPage(props: AppPageProps) { > {props.widgetsStructure.widgetId && - renderAppsmithCanvas({ - ...props.widgetsStructure, - classList: isAnvilLayout ? ["main-anvil-canvas"] : [], - } as WidgetProps)} + renderAppsmithCanvas(props.widgetsStructure as WidgetProps)} ); diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 29a7e30ce5..11a6adb116 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -18,8 +18,6 @@ import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettings import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants"; import { renderAppsmithCanvas } from "layoutSystems/CanvasFactory"; import type { WidgetProps } from "widgets/BaseWidget"; -import { LayoutSystemTypes } from "layoutSystems/types"; -import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; import { getAppThemeSettings } from "@appsmith/selectors/applicationSelectors"; interface CanvasProps { @@ -28,11 +26,17 @@ interface CanvasProps { enableMainCanvasResizer?: boolean; } +const StyledWDSThemeProvider = styled(WDSThemeProvider)` + min-height: 100%; + display: flex; +`; + const Wrapper = styled.section<{ background: string; width: number; $enableMainCanvasResizer: boolean; }>` + flex: 1; background: ${({ background }) => background}; width: ${({ $enableMainCanvasResizer, width }) => $enableMainCanvasResizer ? `100%` : `${width}px`}; @@ -45,7 +49,6 @@ const Canvas = (props: CanvasProps) => { ); const selectedTheme = useSelector(getSelectedAppTheme); const isWDSEnabled = useFeatureFlag("ab_wds_enabled"); - const layoutSystemType: LayoutSystemTypes = useSelector(getLayoutSystemType); const themeSetting = useSelector(getAppThemeSettings); const themeProps = { @@ -82,14 +85,12 @@ const Canvas = (props: CanvasProps) => { : `mx-auto`; const paddingBottomClass = props.enableMainCanvasResizer ? "" : "pb-52"; - const height = layoutSystemType === LayoutSystemTypes.ANVIL ? "h-full" : ""; - const renderChildren = () => { return ( { width={canvasWidth} > {props.widgetsStructure.widgetId && - renderAppsmithCanvas({ - ...props.widgetsStructure, - classList: - layoutSystemType === LayoutSystemTypes.ANVIL - ? ["main-anvil-canvas"] - : [], - } as WidgetProps)} + renderAppsmithCanvas(props.widgetsStructure as WidgetProps)} ); }; @@ -112,7 +107,9 @@ const Canvas = (props: CanvasProps) => { try { if (isWDSEnabled) { return ( - {renderChildren()} + + {renderChildren()} + ); } diff --git a/app/client/src/pages/Editor/WidgetsEditor/MainContainerWrapper.tsx b/app/client/src/pages/Editor/WidgetsEditor/MainContainerWrapper.tsx index 89d76302da..1654602467 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/MainContainerWrapper.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/MainContainerWrapper.tsx @@ -21,12 +21,10 @@ import { getAppThemeIsChanging, getSelectedAppTheme, } from "selectors/appThemingSelectors"; -import { getCurrentThemeDetails } from "selectors/themeSelectors"; import { getCanvasWidgetsStructure } from "@appsmith/selectors/entitiesSelector"; -import { - AUTOLAYOUT_RESIZER_WIDTH_BUFFER, - useDynamicAppLayout, -} from "utils/hooks/useDynamicAppLayout"; +import { useDynamicAppLayout } from "utils/hooks/useDynamicAppLayout"; +import { LayoutSystemTypes } from "../../../layoutSystems/types"; +import { getLayoutSystemType } from "../../../selectors/layoutSystemSelectors"; import Canvas from "../Canvas"; import type { AppState } from "@appsmith/reducers"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; @@ -51,14 +49,8 @@ const Wrapper = styled.section<{ isPreviewingNavigation?: boolean; isAppSettingsPaneWithNavigationTabOpen?: boolean; navigationHeight?: number; - $heightWithTopMargin: string; }>` - /* Create a custom variable that will allow us to measure the height of the canvas down the road */ - --canvas-height: ${(props) => props.$heightWithTopMargin}; - width: ${({ $enableMainCanvasResizer }) => - $enableMainCanvasResizer - ? `calc(100% - ${AUTOLAYOUT_RESIZER_WIDTH_BUFFER}px)` - : `100%`}; + width: 100%; position: relative; overflow-x: auto; overflow-y: auto; @@ -131,7 +123,6 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) { const isFetchingPage = useSelector(getIsFetchingPage); const widgetsStructure = useSelector(getCanvasWidgetsStructure, equal); const pages = useSelector(getViewModePageList); - const theme = useSelector(getCurrentThemeDetails); const selectedTheme = useSelector(getSelectedAppTheme); const shouldHaveTopMargin = !(isPreviewMode || isProtectedMode) || @@ -145,6 +136,9 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) { const isWDSV2Enabled = useFeatureFlag("ab_wds_enabled"); const { canShowResizer, enableMainContainerResizer } = useMainContainerResizer(); + const layoutSystemType: LayoutSystemTypes = useSelector(getLayoutSystemType); + const isAnvilLayout = layoutSystemType === LayoutSystemTypes.ANVIL; + const headerHeight = "40px"; useEffect(() => { return () => { @@ -181,31 +175,10 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) { const isPreviewingNavigation = isPreviewMode || isProtectedMode || isAppSettingsPaneWithNavigationTabOpen; - /** - * calculating exact height to not allow scroll at this component, - * calculating total height of the canvas minus - * - 1. navigation height - * - 1.1 height for top + stacked or top + inline nav style is calculated - * - 1.2 in case of sidebar nav, height is 0 - * - 2. top bar (header with preview/share/deploy buttons) - * - 3. bottom bar (footer with debug/logs buttons) - */ - const topMargin = shouldShowSnapShotBanner ? "4rem" : "0rem"; - const bottomBarHeight = - isPreviewMode || isProtectedMode ? "0px" : theme.bottomBarHeight; - const smallHeaderHeight = showCanvasTopSection - ? theme.smallHeaderHeight - : "0px"; - const scrollBarHeight = - isPreviewMode || isProtectedMode || isPreviewingNavigation ? "8px" : "40px"; - // calculating exact height to not allow scroll at this component, - // calculating total height minus margin on top, top bar and bottom bar and scrollbar height at the bottom - const heightWithTopMargin = `calc(100vh - 2rem - ${topMargin} - ${smallHeaderHeight} - ${bottomBarHeight} - ${scrollBarHeight} - ${navigationHeight}px)`; return ( <> { if (navigationPreviewRef?.current) { const { offsetHeight } = navigationPreviewRef.current; @@ -176,7 +186,7 @@ function WidgetsEditor() { PerformanceTracker.stopTracking(); return ( -
+
{shouldShowSnapShotBanner && ( -
+ -
+ )} { static type = "WDS_MODAL_WIDGET"; + constructor(props: ModalWidgetProps) { + super(props); + + this.state = { + isVisible: this.getModalVisibility(), + }; + } + static getConfig() { return config.metaConfig; } @@ -117,16 +119,14 @@ class WDSModalWidget extends BaseWidget { return ( this.setState({ isVisible: val })} size={this.props.size} > {this.props.showHeader && } - + {this.props.showFooter && (