diff --git a/app/client/src/widgets/ButtonGroupWidget/component/index.tsx b/app/client/src/widgets/ButtonGroupWidget/component/index.tsx index c20c6a40bd37..8b9a1f3b9dcf 100644 --- a/app/client/src/widgets/ButtonGroupWidget/component/index.tsx +++ b/app/client/src/widgets/ButtonGroupWidget/component/index.tsx @@ -1,6 +1,7 @@ import type { RefObject } from "react"; import React, { createRef } from "react"; import { sortBy } from "lodash"; +import { objectKeys } from "@appsmith/utils"; import { Alignment, Icon, @@ -45,7 +46,7 @@ interface ButtonData { const getButtonData = ( groupButtons: Record, ): ButtonData[] => { - const buttonData = Object.keys(groupButtons).reduce( + const buttonData = objectKeys(groupButtons).reduce( (acc: ButtonData[], id) => { return [ ...acc, @@ -344,7 +345,7 @@ interface PopoverContentProps { function PopoverContent(props: PopoverContentProps) { const { buttonId, menuItems, onItemClicked } = props; - let items = Object.keys(menuItems) + let items = objectKeys(menuItems) .map((itemKey) => menuItems[itemKey]) .filter((item) => item.isVisible === true); @@ -490,7 +491,7 @@ class ButtonGroupComponent extends React.Component< // Get widths of menu buttons getMenuButtonWidths = () => - Object.keys(this.props.groupButtons).reduce((acc, id) => { + objectKeys(this.props.groupButtons).reduce((acc, id) => { if (this.props.groupButtons[id].buttonType === "MENU") { return { ...acc, @@ -503,7 +504,7 @@ class ButtonGroupComponent extends React.Component< // Create refs of menu buttons createMenuButtonRefs = () => - Object.keys(this.props.groupButtons).reduce((acc, id) => { + objectKeys(this.props.groupButtons).reduce((acc, id) => { if (this.props.groupButtons[id].buttonType === "MENU") { return { ...acc, @@ -540,6 +541,7 @@ class ButtonGroupComponent extends React.Component< buttonVariant, groupButtons, isDisabled, + isFormValid, minPopoverWidth, orientation, widgetId, @@ -547,7 +549,7 @@ class ButtonGroupComponent extends React.Component< const { loadedBtnId } = this.state; const isHorizontal = orientation === "horizontal"; - let items = Object.keys(groupButtons) + let items = objectKeys(groupButtons) .map((itemKey) => groupButtons[itemKey]) .filter((item) => item.isVisible === true); @@ -574,7 +576,11 @@ class ButtonGroupComponent extends React.Component< {items.map((button) => { const isLoading = button.id === loadedBtnId; const isButtonDisabled = - button.isDisabled || isDisabled || !!loadedBtnId || isLoading; + button.isDisabled || + isDisabled || + !!loadedBtnId || + isLoading || + (button.disabledWhenInvalid && isFormValid === false); if (button.buttonType === "MENU" && !isButtonDisabled) { const { menuItems } = button; @@ -703,6 +709,7 @@ interface GroupButtonProps { index: number; isVisible?: boolean; isDisabled?: boolean; + disabledWhenInvalid?: boolean; label?: string; buttonType?: string; buttonColor?: string; @@ -718,6 +725,7 @@ interface GroupButtonProps { index: number; isVisible?: boolean; isDisabled?: boolean; + disabledWhenInvalid?: boolean; label?: string; backgroundColor?: string; textColor?: string; @@ -746,6 +754,7 @@ export interface ButtonGroupComponentProps { widgetId: string; buttonMinWidth?: number; minHeight?: number; + isFormValid?: boolean; } export interface ButtonGroupComponentState { diff --git a/app/client/src/widgets/ButtonGroupWidget/widget/__tests__/ButtonGroupWidget.test.tsx b/app/client/src/widgets/ButtonGroupWidget/widget/__tests__/ButtonGroupWidget.test.tsx new file mode 100644 index 000000000000..ae37b9fc48e8 --- /dev/null +++ b/app/client/src/widgets/ButtonGroupWidget/widget/__tests__/ButtonGroupWidget.test.tsx @@ -0,0 +1,101 @@ +import { render } from "@testing-library/react"; +import React from "react"; +import ButtonGroupWidget from "../index"; +import { RenderModes } from "constants/WidgetConstants"; +import type { ButtonGroupWidgetProps } from "../index"; +import { klona } from "klona"; + +describe("ButtonGroupWidget disabledWhenInvalid", () => { + const defaultProps: ButtonGroupWidgetProps = { + widgetId: "test-button-group", + renderMode: RenderModes.CANVAS, + version: 1, + parentColumnSpace: 1, + parentRowSpace: 1, + leftColumn: 0, + rightColumn: 0, + topRow: 0, + bottomRow: 0, + isLoading: false, + orientation: "horizontal", + isDisabled: false, + buttonVariant: "PRIMARY", + type: "BUTTON_GROUP_WIDGET", + widgetName: "ButtonGroup1", + groupButtons: { + groupButton1: { + label: "Test Button 1", + id: "groupButton1", + widgetId: "", + buttonType: "SIMPLE", + placement: "CENTER", + isVisible: true, + isDisabled: false, + disabledWhenInvalid: true, + index: 0, + menuItems: {}, + }, + groupButton2: { + label: "Test Button 2", + id: "groupButton2", + widgetId: "", + buttonType: "SIMPLE", + placement: "CENTER", + isVisible: true, + isDisabled: false, + disabledWhenInvalid: true, + index: 1, + menuItems: {}, + }, + }, + }; + + it("disables buttons when disabledWhenInvalid is true and form is invalid", () => { + const props = klona(defaultProps); + + props.isFormValid = false; + + const { container } = render(); + const buttons = container.querySelectorAll("button"); + + buttons.forEach((button) => { + expect(button.hasAttribute("disabled")).toBe(true); + }); + }); + + it("enables buttons when disabledWhenInvalid is true but form is valid", () => { + const props = klona(defaultProps); + + props.isFormValid = true; + + const { container } = render(); + const buttons = container.querySelectorAll("button"); + + buttons.forEach((button) => { + expect(button.hasAttribute("disabled")).toBe(false); + }); + }); + + it("enables buttons when disabledWhenInvalid is false regardless of form validity", () => { + const props = klona(defaultProps); + + props.groupButtons = { + ...defaultProps.groupButtons, + groupButton1: { + ...defaultProps.groupButtons.groupButton1, + disabledWhenInvalid: false, + }, + groupButton2: { + ...defaultProps.groupButtons.groupButton2, + disabledWhenInvalid: false, + }, + }; + + const { container } = render(); + const buttons = container.querySelectorAll("button"); + + buttons.forEach((button) => { + expect(button.hasAttribute("disabled")).toBe(false); + }); + }); +}); diff --git a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx index ace670fff3d1..b7f347a60bfe 100644 --- a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx @@ -65,6 +65,7 @@ class ButtonGroupWidget extends BaseWidget< placement: "CENTER", isVisible: true, isDisabled: false, + disabledWhenInvalid: false, index: 0, menuItems: {}, }, @@ -77,6 +78,7 @@ class ButtonGroupWidget extends BaseWidget< widgetId: "", isVisible: true, isDisabled: false, + disabledWhenInvalid: false, index: 1, menuItems: {}, }, @@ -89,6 +91,7 @@ class ButtonGroupWidget extends BaseWidget< widgetId: "", isVisible: true, isDisabled: false, + disabledWhenInvalid: false, index: 2, menuItems: { menuItem1: { @@ -99,6 +102,7 @@ class ButtonGroupWidget extends BaseWidget< onClick: "", isVisible: true, isDisabled: false, + disabledWhenInvalid: false, index: 0, }, menuItem2: { @@ -109,6 +113,7 @@ class ButtonGroupWidget extends BaseWidget< onClick: "", isVisible: true, isDisabled: false, + disabledWhenInvalid: false, index: 1, }, menuItem3: { @@ -123,6 +128,7 @@ class ButtonGroupWidget extends BaseWidget< onClick: "", isVisible: true, isDisabled: false, + disabledWhenInvalid: false, index: 2, }, }, @@ -517,6 +523,22 @@ class ButtonGroupWidget extends BaseWidget< }, ], }, + { + sectionName: "Form settings", + children: [ + { + propertyName: "disabledWhenInvalid", + label: "Disabled invalid forms", + helpText: + "Disables this button if the form is invalid, if this button exists directly within a Form widget", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, + ], + }, { sectionName: "Events", hidden: ( @@ -825,6 +847,7 @@ class ButtonGroupWidget extends BaseWidget< buttonVariant={this.props.buttonVariant} groupButtons={this.props.groupButtons} isDisabled={this.props.isDisabled} + isFormValid={this.props.isFormValid} minHeight={this.isAutoLayoutMode ? this.props.minHeight : undefined} minPopoverWidth={minPopoverWidth} orientation={this.props.orientation} @@ -839,6 +862,7 @@ class ButtonGroupWidget extends BaseWidget< export interface ButtonGroupWidgetProps extends WidgetProps { orientation: string; isDisabled: boolean; + isFormValid?: boolean; borderRadius?: string; boxShadow?: string; buttonVariant: ButtonVariant; @@ -850,6 +874,7 @@ export interface ButtonGroupWidgetProps extends WidgetProps { index: number; isVisible?: boolean; isDisabled?: boolean; + disabledWhenInvalid?: boolean; label?: string; buttonType?: string; buttonColor?: string; @@ -865,6 +890,7 @@ export interface ButtonGroupWidgetProps extends WidgetProps { index: number; isVisible?: boolean; isDisabled?: boolean; + disabledWhenInvalid?: boolean; label?: string; backgroundColor?: string; textColor?: string;