PromucFlow_constructor/app/client/src/layoutSystems/anvil/common/AnvilFlexComponent.tsx

168 lines
5.7 KiB
TypeScript
Raw Normal View History

chore: Create layout system structure for Anvil and AnvilFlexComponent. (#27178) ## Description 1. Add new ```appPositioningType``` : ANVIL. 2. Create new code path and folder structure for Anvil layout system. 3. Move common pieces of functionalities between autoLayout and anvil to anvil folder structure (e.g. ```CanvasResizer```). 4. Create ```AnvilFlexComponent```. 5. Use WDS Flex component in AnvilFlexComponent. 6. Pass min max size props in a data structure that is supported by container queries in the Flex component. e.g. min-width: { base: "120px", "480px": "200px" } 7. Supply the following flex properties (flex-grow flex-shrink flex-basis) to widgets depending on their ```responsiveBehvaiour```: a) Fill: ```flex: 1 1 0%;``` b) Hug: ```flex: 0 0 auto;``` #### PR fixes following issue(s) Fixes # (issue number) 1. [#26987](https://github.com/appsmithorg/appsmith/issues/26987) 2. [#26609](https://github.com/appsmithorg/appsmith/issues/26609) 3. [#26611](https://github.com/appsmithorg/appsmith/issues/26611) #### Type of change - New feature (non-breaking change which adds functionality) ## 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 - [ ] Cypress ## 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 - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [x] 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 --------- Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> Co-authored-by: Aswath K <aswath.sana@gmail.com> Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
2023-10-02 19:41:05 +00:00
import React, { useCallback, useEffect, useMemo, useState } from "react";
import type { CSSProperties, MouseEvent } from "react";
import { Flex } from "@design-system/widgets";
import { useSelector } from "react-redux";
import { snipingModeSelector } from "selectors/editorSelectors";
import { useClickToSelectWidget } from "utils/hooks/useClickToSelectWidget";
import { usePositionedContainerZIndex } from "utils/hooks/usePositionedContainerZIndex";
import {
getIsResizing,
isCurrentWidgetFocused,
isWidgetSelected,
} from "selectors/widgetSelectors";
import { widgetTypeClassname } from "widgets/WidgetUtils";
import {
FlexVerticalAlignment,
ResponsiveBehavior,
} from "layoutSystems/common/utils/constants";
import type { FlexProps } from "@design-system/widgets/src/components/Flex/src/types";
import { WIDGET_PADDING } from "constants/WidgetConstants";
import { checkIsDropTarget } from "WidgetProvider/factory/helpers";
import type { AnvilFlexComponentProps } from "../utils/types";
import {
getResponsiveMinWidth,
validateResponsiveProp,
} from "../utils/widgetUtils";
import WidgetFactory from "WidgetProvider/factory";
import type { WidgetProps } from "widgets/BaseWidget";
import type { WidgetConfigProps } from "WidgetProvider/constants";
/**
* Adds following functionalities to the widget:
* 1. Click handler to select the widget and open property pane.
* 2. Widget size based on responsiveBehavior:
* 2a. Hug widgets will stick to the size provided to them. (flex: 0 0 auto;)
* 2b. Fill widgets will automatically take up all available width in the parent container. (flex: 1 1 0%;)
* 3. Widgets can optionally have auto width or height which is dictated by the props.
*
* Uses Flex component provided by WDS.
* @param props | AnvilFlexComponentProps
* @returns Widget
*/
export const AnvilFlexComponent = (props: AnvilFlexComponentProps) => {
const isDropTarget = checkIsDropTarget(props.widgetType);
const isFocused = useSelector(isCurrentWidgetFocused(props.widgetId));
const isResizing = useSelector(getIsResizing);
const isSelected = useSelector(isWidgetSelected(props.widgetId));
const isSnipingMode = useSelector(snipingModeSelector);
const isCurrentWidgetResizing = isResizing && isSelected;
const [isFillWidget, setIsFillWidget] = useState<boolean>(false);
const [verticalAlignment, setVerticalAlignment] =
useState<FlexVerticalAlignment>(FlexVerticalAlignment.Top);
const clickToSelectWidget = useClickToSelectWidget(props.widgetId);
const onClickFn = useCallback(
(e) => {
clickToSelectWidget(e);
},
[props.widgetId, clickToSelectWidget],
);
const stopEventPropagation = (e: MouseEvent<HTMLElement>) => {
!isSnipingMode && e.stopPropagation();
};
useEffect(() => {
const widgetConfig:
| (Partial<WidgetProps> & WidgetConfigProps & { type: string })
| undefined = WidgetFactory.getConfig(props.widgetType);
if (!widgetConfig) return;
setIsFillWidget(
widgetConfig?.responsiveBehavior === ResponsiveBehavior.Fill,
);
setVerticalAlignment(
widgetConfig?.flexVerticalAlignment || FlexVerticalAlignment.Top,
);
}, [props.widgetType]);
const { onHoverZIndex } = usePositionedContainerZIndex(
isDropTarget,
props.widgetId,
isFocused,
isSelected,
);
const className = useMemo(
() =>
`anvil-layout-parent-${props.parentId} anvil-layout-child-${
props.widgetId
} ${widgetTypeClassname(
props.widgetType,
)} t--widget-${props.widgetName.toLowerCase()}`,
[props.parentId, props.widgetId, props.widgetType, props.widgetName],
);
// Memoize flex props to be passed to the WDS Flex component.
// If the widget is being resized => update width and height to auto.
const flexProps: FlexProps = useMemo(() => {
const data: FlexProps = {
alignSelf: verticalAlignment || FlexVerticalAlignment.Top,
flexGrow: isFillWidget ? 1 : 0,
flexShrink: isFillWidget ? 1 : 0,
flexBasis: isFillWidget ? "0%" : "auto",
height:
props.hasAutoHeight || isCurrentWidgetResizing
? "auto"
: `${props.componentHeight}px`,
padding: WIDGET_PADDING + "px",
width:
isFillWidget || props.hasAutoWidth || isCurrentWidgetResizing
? "auto"
: `${props.componentWidth}px`,
};
if (props?.widgetSize) {
// adding min max limits only if they are available, as WDS Flex doesn't handle undefined values.
if (validateResponsiveProp(props.widgetSize?.maxHeight)) {
data.maxHeight = props.widgetSize.maxHeight;
}
if (validateResponsiveProp(props.widgetSize?.maxWidth)) {
data.maxWidth = props.widgetSize.maxWidth;
}
if (validateResponsiveProp(props.widgetSize?.minHeight)) {
data.minHeight = props.widgetSize.minHeight;
}
if (validateResponsiveProp(props.widgetSize?.minWidth)) {
// Setting a base of 100% for Fill widgets to ensure that they expand on smaller sizes.
data.minWidth = getResponsiveMinWidth(
props.widgetSize?.minWidth,
isFillWidget,
);
}
}
return data;
}, [
isCurrentWidgetResizing,
isFillWidget,
props.componentHeight,
props.hasAutoHeight,
props.hasAutoWidth,
props.componentWidth,
props.widgetSize,
verticalAlignment,
]);
const styleProps: CSSProperties = useMemo(() => {
return {
position: "relative",
"&:hover": {
zIndex: onHoverZIndex,
},
};
}, [onHoverZIndex]);
return (
<Flex {...flexProps} className={className} style={styleProps}>
<div
className="w-full h-full"
onClick={stopEventPropagation}
onClickCapture={onClickFn}
>
{props.children}
</div>
</Flex>
);
};