## 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  #### 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 -->
188 lines
6.5 KiB
TypeScript
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>
|
|
);
|
|
}
|