diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js index a35162eca6..478b99e9ac 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js @@ -106,4 +106,25 @@ describe("Text Widget color/font/alignment Functionality", function() { }); cy.get(commonlocators.headingTextStyle).scrollIntoView({ duration: 2000 }); }); + + it("Test border width, color and verity", function() { + cy.openPropertyPane("textwidget"); + + cy.testJsontext("borderwidth", "10"); + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}'] div`, + ) + .should("have.css", "border-width") + .and("eq", "10px"); + + cy.get(widgetsPage.boadercolorPicker) + .first() + .click({ force: true }); + cy.xpath(widgetsPage.yellowColor).click(); + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}'] div`, + ) + .should("have.css", "border-color") + .and("eq", "rgb(255, 193, 61)"); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Container_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Container_spec.js index 883257100f..fb4001c63a 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Container_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Container_spec.js @@ -1,4 +1,5 @@ const commonlocators = require("../../../../locators/commonlocators.json"); +const publish = require("../../../../locators/publishWidgetspage.json"); const widgetsPage = require("../../../../locators/Widgets.json"); const dsl = require("../../../../fixtures/containerdsl.json"); @@ -20,7 +21,19 @@ describe("Container Widget Functionality", function() { commonlocators.containerInnerText, ); /** - * @param{Text} Random Colour + * @param{Text} Random Border Colour + */ + cy.get(widgetsPage.boadercolorPicker) + .first() + .click({ force: true }); + cy.xpath(widgetsPage.yellowColor).click(); + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}'] div`, + ) + .should("have.css", "border-color") + .and("eq", "rgb(255, 193, 61)"); + /** + * @param{Text} Random Background Colour */ cy.get(widgetsPage.backgroundcolorPicker) .first() @@ -46,6 +59,73 @@ describe("Container Widget Functionality", function() { .should("have.css", "background-color") .and("eq", "rgb(3, 179, 101)"); }); + + it("Test border width and verity", function() { + cy.get(publish.backToEditor).click(); + cy.openPropertyPane("containerwidget"); + + cy.testJsontext("borderwidth", "10"); + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}'] div`, + ) + .should("have.css", "border-width") + .and("eq", "10px"); + }); + + it("Test border radius and verity", function() { + cy.testJsontext("borderradius", "10"); + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}'] div`, + ) + .should("have.css", "border-radius") + .and("eq", "10px"); + // should have overflow : hidden to show border edges + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}'] div`, + ) + .should("have.css", "overflow") + .and("eq", "hidden"); + // wrapper should have same border radius + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}']`, + ) + .should("have.css", "border-radius") + .and("eq", "10px"); + }); + + it("Test Box shadow and verity", function() { + cy.get(widgetsPage.boxShadow) + .children() + .eq(3) + .click({ force: true }); + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}']`, + ) + .should("have.css", "box-shadow") + .and("eq", "rgba(0, 0, 0, 0.5) 0px 1px 3px 0px"); + + // change shadow color and check box-shadow again + cy.get(widgetsPage.boxShadowColorPicker) + .first() + .click({ force: true }); + cy.xpath(widgetsPage.blueColor).click(); + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}']`, + ) + .should("have.css", "box-shadow") + .and("eq", "rgb(51, 102, 255) 0px 1px 3px 0px"); + }); + + it("Test overflow of widget boundaries", function() { + cy.testJsontext("borderwidth", "500"); + // prevent overflow of widget boundaries + cy.get( + `div[data-testid='container-wrapper-${dsl.dsl.children[0].widgetId}']`, + ) + .should("have.css", "overflow") + .and("eq", "hidden"); + }); + afterEach(() => { // put your clean up code if any }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PropertyPane/Draggable_PropertyPane_Widgets_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PropertyPane/Draggable_PropertyPane_Widgets_spec.js index 58a61d16b1..d00d7782fa 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PropertyPane/Draggable_PropertyPane_Widgets_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PropertyPane/Draggable_PropertyPane_Widgets_spec.js @@ -49,7 +49,7 @@ describe("Widget property pane draggable", function() { cy.openPropertyPane(testWidget); cy.get("[data-cy=t--property-pane-drag-handle]") .trigger("mousedown", { which: 1 }) - .trigger("mousemove", { clientX: 400, clientY: 500 }) + .trigger("mousemove", { clientX: 0, clientY: 0 }) .trigger("mouseup", { force: true }); cy.get("[data-cy=t--property-pane-drag-handle]").then((oldPorpPane) => { const oldPropPanePosition = oldPorpPane[0].getBoundingClientRect(); diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json index ed8b9ba662..0464f47ab9 100644 --- a/app/client/cypress/locators/Widgets.json +++ b/app/client/cypress/locators/Widgets.json @@ -39,6 +39,7 @@ "checkboxInput": ".t--draggable-checkboxwidget span.t--widget-name", "checkboxLabel": ".t--draggable-checkboxwidget label", "containerD": "div[type='CONTAINER_WIDGET']", + "containerWrapper": "div[data-testid='container-wrapper']", "defaultInput": ".t--property-control-defaulttext .CodeMirror-code", "placeholder": ".t--property-control-placeholder .CodeMirror-code", "inputLabelControl": ".t--property-control-label .CodeMirror-code", @@ -81,8 +82,13 @@ "verticalCenter": ".t--icon-tab-CENTER", "verticalBottom": ".t--icon-tab-BOTTOM", "textColor": ".t--property-control-textcolor input", - "backgroundcolorPicker": ".t--property-control-backgroundcolor input", + "boadercolorPicker": ".t--property-control-bordercolour input", + "boxShadowColorPicker": ".t--property-control-shadowcolor input", + "boxShadow": ".t--property-control-boxshadow .bp3-button-group", + "backgroundcolorPicker": ".t--property-control-backgroundcolour input", "greenColor": "//div[@color='#03b365']", + "yellowColor": "//div[@color='#FFC13D']", + "blueColor": "//div[@color='#3366FF']", "toggleJsColor": ".t--property-control-textcolor .t--js-toggle", "backgroundColor": ".t--property-control-cellbackground input", "toggleJsBcgColor": ".t--property-control-cellbackground .t--js-toggle", diff --git a/app/client/src/components/designSystems/appsmith/WidgetStyleContainer.tsx b/app/client/src/components/designSystems/appsmith/WidgetStyleContainer.tsx new file mode 100644 index 0000000000..f18f624b97 --- /dev/null +++ b/app/client/src/components/designSystems/appsmith/WidgetStyleContainer.tsx @@ -0,0 +1,90 @@ +import React, { ReactNode } from "react"; +import styled from "styled-components"; +import { ContainerStyle } from "widgets/ContainerWidget/component"; +import { Color } from "constants/Colors"; +import { Theme } from "constants/DefaultTheme"; + +export enum BoxShadowTypes { + NONE = "NONE", + VARIANT1 = "VARIANT1", + VARIANT2 = "VARIANT2", + VARIANT3 = "VARIANT3", + VARIANT4 = "VARIANT4", + VARIANT5 = "VARIANT5", +} + +export type BoxShadow = keyof typeof BoxShadowTypes; + +export interface WidgetStyleContainerProps { + widgetId: string; + containerStyle?: ContainerStyle; + children?: ReactNode; + borderColor?: Color; + borderWidth?: number; + borderRadius?: number; + boxShadow?: BoxShadow; + boxShadowColor?: string; +} + +// get box shadow style string based on boxShadow and boxShadowColor +const getBoxShadow = ({ + boxShadow, + boxShadowColor, + theme, +}: { + boxShadow?: BoxShadow; + boxShadowColor?: string; + theme: Theme; +}) => { + switch (boxShadow) { + case BoxShadowTypes.VARIANT1: + return `0px 0px 4px 3px ${boxShadowColor || + theme.colors.button.boxShadow.default.variant1}`; + case BoxShadowTypes.VARIANT2: + return `3px 3px 4px ${boxShadowColor || + theme.colors.button.boxShadow.default.variant2}`; + case BoxShadowTypes.VARIANT3: + return `0px 1px 3px ${boxShadowColor || + theme.colors.button.boxShadow.default.variant3}`; + case BoxShadowTypes.VARIANT4: + return `2px 2px 0px ${boxShadowColor || + theme.colors.button.boxShadow.default.variant4}`; + case BoxShadowTypes.VARIANT5: + return `-2px -2px 0px ${boxShadowColor || + theme.colors.button.boxShadow.default.variant5}`; + default: + return "none"; + } +}; + +const WidgetStyle = styled.div` + height: 100%; + width: 100%; + overflow: hidden; + border-radius: ${(props) => props.borderRadius}px; + box-shadow: ${(props) => getBoxShadow(props)} !important; + & > div { + ${(props) => + props.containerStyle !== "none" + ? ` + border-width: ${props.borderWidth}px; + border-radius: ${props.borderRadius}px; + border-color: ${props.borderColor || "transparent"}; + border-style: solid;` + : ""} + height: 100%; + width: 100%; + overflow: hidden; + } +}`; + +// wrapper component for apply styles on any widget boundary +function WidgetStyleContainer(props: WidgetStyleContainerProps) { + return ( + +
{props.children}
+
+ ); +} + +export default WidgetStyleContainer; diff --git a/app/client/src/utils/migrations/ContainerWidget.ts b/app/client/src/utils/migrations/ContainerWidget.ts new file mode 100644 index 0000000000..e8a189bbaa --- /dev/null +++ b/app/client/src/utils/migrations/ContainerWidget.ts @@ -0,0 +1,25 @@ +import { WidgetProps } from "widgets/BaseWidget"; +import { ContainerWidgetProps } from "widgets/ContainerWidget/widget"; +import WidgetFactory from "utils/WidgetFactory"; + +const WidgetTypes = WidgetFactory.widgetTypes; + +export const migrateContainerAndFormWidgetStyleProperties = ( + currentDSL: ContainerWidgetProps, +) => { + currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { + if ( + child.type === WidgetTypes.CONTAINER_WIDGET || + child.type === WidgetTypes.FORM_WIDGET + ) { + if (!("borderWidth" in child)) { + child.borderWidth = "0"; + child.borderRadius = "0"; + } + } else if (child.children && child.children.length > 0) { + child = migrateContainerAndFormWidgetStyleProperties(child); + } + return child; + }); + return currentDSL; +}; diff --git a/app/client/src/widgets/ContainerWidget/component/index.tsx b/app/client/src/widgets/ContainerWidget/component/index.tsx index 744894f5ec..13d06a617e 100644 --- a/app/client/src/widgets/ContainerWidget/component/index.tsx +++ b/app/client/src/widgets/ContainerWidget/component/index.tsx @@ -5,6 +5,10 @@ import { invisible } from "constants/DefaultTheme"; import { Color } from "constants/Colors"; import { generateClassName, getCanvasClassName } from "utils/generators"; import { useCanvasMinHeightUpdateHook } from "utils/hooks/useCanvasMinHeightUpdateHook"; +import WidgetStyleContainer, { + WidgetStyleContainerProps, +} from "components/designSystems/appsmith/WidgetStyleContainer"; +import { pick } from "lodash"; import { ComponentProps } from "widgets/BaseComponent"; const scrollContents = css` @@ -16,12 +20,6 @@ const StyledContainerComponent = styled.div< ref: RefObject; } >` - ${(props) => - props.containerStyle !== "none" - ? ` - box-shadow: 0px 0px 0px 1px #E7E7E7; - border-radius: 0;` - : ""} height: 100%; width: 100%; background: ${(props) => props.backgroundColor}; @@ -70,25 +68,38 @@ function ContainerComponent(props: ContainerComponentProps) { } }, [props.shouldScrollContents]); return ( - - {props.children} - + + {props.children} + + ); } export type ContainerStyle = "border" | "card" | "rounded-border" | "none"; -export interface ContainerComponentProps extends ComponentProps { - containerStyle?: ContainerStyle; +export interface ContainerComponentProps + extends ComponentProps, + WidgetStyleContainerProps { children?: ReactNode; className?: string; backgroundColor?: Color; diff --git a/app/client/src/widgets/ContainerWidget/index.ts b/app/client/src/widgets/ContainerWidget/index.ts index 44f40b1a09..cc2ee472d2 100644 --- a/app/client/src/widgets/ContainerWidget/index.ts +++ b/app/client/src/widgets/ContainerWidget/index.ts @@ -1,6 +1,7 @@ import Widget from "./widget"; import IconSVG from "./icon.svg"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; +import { ButtonBoxShadowTypes } from "components/constants"; export const CONFIG = { type: Widget.getWidgetType(), @@ -13,6 +14,10 @@ export const CONFIG = { columns: 8 * GRID_DENSITY_MIGRATION_V1, widgetName: "Container", containerStyle: "card", + borderColor: "transparent", + borderWidth: "0", + borderRadius: "0", + boxShadow: ButtonBoxShadowTypes.NONE, children: [], blueprint: { view: [ diff --git a/app/client/src/widgets/ContainerWidget/widget/index.tsx b/app/client/src/widgets/ContainerWidget/widget/index.tsx index 09378fd0c4..9f1028fdcb 100644 --- a/app/client/src/widgets/ContainerWidget/widget/index.tsx +++ b/app/client/src/widgets/ContainerWidget/widget/index.tsx @@ -33,17 +33,6 @@ class ContainerWidget extends BaseWidget< { sectionName: "General", children: [ - { - helpText: "Use a html color name, HEX, RGB or RGBA value", - placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)", - propertyName: "backgroundColor", - label: "Background Color", - controlType: "COLOR_PICKER", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: false, - validation: { type: ValidationTypes.TEXT }, - }, { helpText: "Controls the visibility of the widget", propertyName: "isVisible", @@ -63,6 +52,82 @@ class ContainerWidget extends BaseWidget< }, ], }, + { + sectionName: "Styles", + children: [ + { + helpText: "Use a html color name, HEX, RGB or RGBA value", + placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)", + propertyName: "backgroundColor", + label: "Background Colour", + controlType: "COLOR_PICKER", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, + { + helpText: "Use a html color name, HEX, RGB or RGBA value", + placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)", + propertyName: "borderColor", + label: "Border Colour", + controlType: "COLOR_PICKER", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, + { + helpText: "Enter value for border width", + propertyName: "borderWidth", + label: "Border Width", + placeholderText: "Enter value in px", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.NUMBER }, + }, + { + helpText: "Enter value for border radius", + propertyName: "borderRadius", + label: "Border Radius", + placeholderText: "Enter value in px", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.NUMBER }, + }, + { + propertyName: "boxShadow", + label: "Box Shadow", + helpText: + "Enables you to cast a drop shadow from the frame of the widget", + controlType: "BOX_SHADOW_OPTIONS", + isBindProperty: false, + isTriggerProperty: false, + validation: { + type: ValidationTypes.TEXT, + params: { + allowedValues: [ + "NONE", + "VARIANT1", + "VARIANT2", + "VARIANT3", + "VARIANT4", + "VARIANT5", + ], + }, + }, + }, + { + propertyName: "boxShadowColor", + helpText: "Sets the shadow color of the widget", + label: "Shadow Color", + controlType: "COLOR_PICKER", + isBindProperty: false, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, + ], + }, ]; } diff --git a/app/client/src/widgets/FormWidget/widget/index.tsx b/app/client/src/widgets/FormWidget/widget/index.tsx index 35f174b98c..c2950fa7f6 100644 --- a/app/client/src/widgets/FormWidget/widget/index.tsx +++ b/app/client/src/widgets/FormWidget/widget/index.tsx @@ -6,46 +6,8 @@ import ContainerWidget, { ContainerWidgetProps, } from "widgets/ContainerWidget/widget"; import { ContainerComponentProps } from "widgets/ContainerWidget/component"; -import { ValidationTypes } from "constants/WidgetValidation"; class FormWidget extends ContainerWidget { - static getPropertyPaneConfig() { - return [ - { - sectionName: "General", - children: [ - { - propertyName: "backgroundColor", - label: "Background Color", - helpText: "Use a html color name, HEX, RGB or RGBA value", - placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)", - controlType: "COLOR_PICKER", - isBindProperty: true, - isJSConvertible: true, - isTriggerProperty: false, - validation: { type: ValidationTypes.TEXT }, - }, - { - helpText: "Controls the visibility of the widget", - propertyName: "isVisible", - label: "Visible", - controlType: "SWITCH", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: false, - validation: { type: ValidationTypes.BOOLEAN }, - }, - { - propertyName: "shouldScrollContents", - label: "Scroll Contents", - controlType: "SWITCH", - isBindProperty: false, - isTriggerProperty: false, - }, - ], - }, - ]; - } checkInvalidChildren = (children: WidgetProps[]): boolean => { return some(children, (child) => { if ("children" in child) { diff --git a/app/client/src/widgets/TextWidget/widget/index.tsx b/app/client/src/widgets/TextWidget/widget/index.tsx index 9a1b4a111e..004cbc79e6 100644 --- a/app/client/src/widgets/TextWidget/widget/index.tsx +++ b/app/client/src/widgets/TextWidget/widget/index.tsx @@ -1,4 +1,8 @@ import React from "react"; +import { pick } from "lodash"; +import WidgetStyleContainer, { + WidgetStyleContainerProps, +} from "components/designSystems/appsmith/WidgetStyleContainer"; import { TextSize } from "constants/WidgetConstants"; @@ -68,6 +72,27 @@ class TextWidget extends BaseWidget { }, }, }, + { + helpText: "Use a html color name, HEX, RGB or RGBA value", + placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)", + propertyName: "borderColor", + label: "Border Colour", + controlType: "COLOR_PICKER", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, + { + helpText: + "Enter value for border width which can also use as margin", + propertyName: "borderWidth", + label: "Border Width", + placeholderText: "Enter value in px", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.NUMBER }, + }, { propertyName: "fontSize", label: "Text Size", @@ -157,18 +182,27 @@ class TextWidget extends BaseWidget { getPageView() { return ( - + + + ); } @@ -191,7 +225,10 @@ export interface TextStyles { textAlign?: TextAlign; } -export interface TextWidgetProps extends WidgetProps, TextStyles { +export interface TextWidgetProps + extends WidgetProps, + TextStyles, + WidgetStyleContainerProps { text?: string; isLoading: boolean; shouldScroll: boolean;