PromucFlow_constructor/app/client/src/layoutSystems/anvil/common/AnvilFlexComponent.tsx
Abhinav Jha e05313c943
feat: Anvil themeing and Anvil vertical alignment (#29907)
## Description
This PR adds the features of proper vertical alignment and themeing to
Anvil.
- A separate `Container` component is created for Anvil, that is used as
the layer on top of which the themeing tokens are applied.
- A default `min-height` is set using tokens for all widgets in Anvil. 
- Anvil now stops considering any `min-height` configurations provided
by the widgets. It is the widgets responsibility to take care of their
own heights, and Anvil will accommodate them -- no matter the height.
- Table widget's default height is now set to the min height that was
configured for it earlier.
- `AnvilFlexComponent` now has `overflow:visible` to allow the shadows
for zones and sections to not be cut-off.
- All widgets are aligned center vertically by default. This will apply
if they're smaller than the set `min-height`
- Zones and Sections have elevation styles applied suing the `Container`
component mentioned above.
- Zones and Sections don't have any styling property other than
`Background`, we'll add more as we understand more about the product.
   

> Conditional vertical margin applied to widgets.
> If in a row of widgets (.aligned-widget-row), one of the widgets has a
label ([data-field-label-wrapper]), then
> all widgets (.anvil-widget-wrapper) in the row other than the widget
with the label, will shift down using the
> margin-block-start property. This is to ensure that the widgets are
aligned vertically.
> The value of the margin-block-start property is calculated based on
the spacing tokens used by the labels in input
>     like components
> 

#### PR fixes following issue(s)
Fixes #29073 
Fixes #28591
Fixes #28592 
Fixes #28593


#### Media
![Screenshot 2023-12-28 at 3 55
05 AM](https://github.com/appsmithorg/appsmith/assets/103687/30b04bc7-62af-40af-9f4c-50774bec3fdf)


#### Type of change
- New feature (non-breaking change which adds functionality)
## Testing
#### How Has This Been Tested?
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress

#### Test Plan

#### Issues raised during DP testing
## Checklist:
#### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] 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
- [ ] 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
- [ ] 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced conditional vertical margins for widgets to ensure
alignment within rows.
- Added a new `Container` component for thematic elevation styles in
Anvil widgets.
- Implemented elevation style options and semantic background settings
for Section and Zone widgets.

- **Enhancements**
- Improved visual layout and alignment of AnvilFlexComponent with
updated styling properties.
- Added `className` properties to various layout components for enhanced
CSS targeting.

- **Style**
- Updated widget styles to accommodate new background and elevation
features.

- **Refactor**
	- Simplified padding logic in WDSParagraphWidget.
	- Streamlined dimensions calculation in WDSTableWidget.

- **Documentation**
- Renamed sections in property panes to better reflect background
styling options.

- **Chores**
- Added `Elevations` enum to manage elevation values consistently across
components.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2023-12-29 08:41:05 +05:30

188 lines
6.5 KiB
TypeScript

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 {
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 { 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";
import { usePositionObserver } from "layoutSystems/common/utils/LayoutElementPositionsObserver/usePositionObserver";
import { useWidgetBorderStyles } from "./hooks/useWidgetBorderStyles";
import { getAnvilWidgetDOMId } from "layoutSystems/common/utils/LayoutElementPositionsObserver/utils";
import type { AppState } from "@appsmith/reducers";
/**
* 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 function AnvilFlexComponent(props: AnvilFlexComponentProps) {
const isDropTarget = checkIsDropTarget(props.widgetType);
const isFocused = useSelector(isCurrentWidgetFocused(props.widgetId));
const isSelected = useSelector(isWidgetSelected(props.widgetId));
const isSnipingMode = useSelector(snipingModeSelector);
const isDragging = useSelector(
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
/** POSITIONS OBSERVER LOGIC */
// Create a ref so that this DOM node can be
// observed by the observer for changes in size
const ref = React.useRef<HTMLDivElement>(null);
usePositionObserver(
"widget",
{ widgetId: props.widgetId, layoutId: props.layoutId },
ref,
);
/** EO POSITIONS OBSERVER LOGIC */
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()} drop-target-${
props.layoutId
} row-index-${props.rowIndex} anvil-widget-wrapper`,
[
props.parentId,
props.widgetId,
props.widgetType,
props.widgetName,
props.layoutId,
props.rowIndex,
],
);
// 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: props.flexGrow ? props.flexGrow : isFillWidget ? 1 : 0,
flexShrink: isFillWidget ? 1 : 0,
flexBasis: isFillWidget ? "0%" : "auto",
height: "auto",
padding: "spacing-1",
width: "auto",
minHeight: { base: "var(--sizing-12)" },
alignItems: "center",
};
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?.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;
}, [isFillWidget, props.widgetSize, verticalAlignment, props.flexGrow]);
const borderStyles = useWidgetBorderStyles(props.widgetId);
const styleProps: CSSProperties = useMemo(() => {
return {
position: "relative",
// overflow is set to make sure widgets internal components/divs don't overflow this boundary causing scrolls
overflow: "visible",
opacity: (isDragging && isSelected) || !props.isVisible ? 0.5 : 1,
"&:hover": {
zIndex: onHoverZIndex,
},
...borderStyles,
};
}, [borderStyles, isDragging, isSelected, onHoverZIndex]);
return (
<Flex
{...flexProps}
className={className}
id={getAnvilWidgetDOMId(props.widgetId)}
ref={ref}
style={styleProps}
>
<div
className="w-full h-full"
onClick={stopEventPropagation}
onClickCapture={onClickFn}
>
{props.children}
</div>
</Flex>
);
}