feat: disabledWhenInvalid in ButtonGroupWidget (#38656)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: rahul.barwal@appsmith.com <rahul.barwal@appsmith.com>
This commit is contained in:
devin-ai-integration[bot] 2025-01-16 17:23:35 +05:30 committed by GitHub
parent 4e7a2a013d
commit b3e5e431b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 142 additions and 6 deletions

View File

@ -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<string, GroupButtonProps>,
): 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 {

View File

@ -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(<ButtonGroupWidget {...props} />);
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(<ButtonGroupWidget {...props} />);
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(<ButtonGroupWidget {...props} />);
const buttons = container.querySelectorAll("button");
buttons.forEach((button) => {
expect(button.hasAttribute("disabled")).toBe(false);
});
});
});

View File

@ -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;