Skip to content

Commit b3e5e43

Browse files
feat: disabledWhenInvalid in ButtonGroupWidget (#38656)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]>
1 parent 4e7a2a0 commit b3e5e43

File tree

3 files changed

+142
-6
lines changed

3 files changed

+142
-6
lines changed

app/client/src/widgets/ButtonGroupWidget/component/index.tsx

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { RefObject } from "react";
22
import React, { createRef } from "react";
33
import { sortBy } from "lodash";
4+
import { objectKeys } from "@appsmith/utils";
45
import {
56
Alignment,
67
Icon,
@@ -45,7 +46,7 @@ interface ButtonData {
4546
const getButtonData = (
4647
groupButtons: Record<string, GroupButtonProps>,
4748
): ButtonData[] => {
48-
const buttonData = Object.keys(groupButtons).reduce(
49+
const buttonData = objectKeys(groupButtons).reduce(
4950
(acc: ButtonData[], id) => {
5051
return [
5152
...acc,
@@ -344,7 +345,7 @@ interface PopoverContentProps {
344345
function PopoverContent(props: PopoverContentProps) {
345346
const { buttonId, menuItems, onItemClicked } = props;
346347

347-
let items = Object.keys(menuItems)
348+
let items = objectKeys(menuItems)
348349
.map((itemKey) => menuItems[itemKey])
349350
.filter((item) => item.isVisible === true);
350351

@@ -490,7 +491,7 @@ class ButtonGroupComponent extends React.Component<
490491

491492
// Get widths of menu buttons
492493
getMenuButtonWidths = () =>
493-
Object.keys(this.props.groupButtons).reduce((acc, id) => {
494+
objectKeys(this.props.groupButtons).reduce((acc, id) => {
494495
if (this.props.groupButtons[id].buttonType === "MENU") {
495496
return {
496497
...acc,
@@ -503,7 +504,7 @@ class ButtonGroupComponent extends React.Component<
503504

504505
// Create refs of menu buttons
505506
createMenuButtonRefs = () =>
506-
Object.keys(this.props.groupButtons).reduce((acc, id) => {
507+
objectKeys(this.props.groupButtons).reduce((acc, id) => {
507508
if (this.props.groupButtons[id].buttonType === "MENU") {
508509
return {
509510
...acc,
@@ -540,14 +541,15 @@ class ButtonGroupComponent extends React.Component<
540541
buttonVariant,
541542
groupButtons,
542543
isDisabled,
544+
isFormValid,
543545
minPopoverWidth,
544546
orientation,
545547
widgetId,
546548
} = this.props;
547549
const { loadedBtnId } = this.state;
548550
const isHorizontal = orientation === "horizontal";
549551

550-
let items = Object.keys(groupButtons)
552+
let items = objectKeys(groupButtons)
551553
.map((itemKey) => groupButtons[itemKey])
552554
.filter((item) => item.isVisible === true);
553555

@@ -574,7 +576,11 @@ class ButtonGroupComponent extends React.Component<
574576
{items.map((button) => {
575577
const isLoading = button.id === loadedBtnId;
576578
const isButtonDisabled =
577-
button.isDisabled || isDisabled || !!loadedBtnId || isLoading;
579+
button.isDisabled ||
580+
isDisabled ||
581+
!!loadedBtnId ||
582+
isLoading ||
583+
(button.disabledWhenInvalid && isFormValid === false);
578584

579585
if (button.buttonType === "MENU" && !isButtonDisabled) {
580586
const { menuItems } = button;
@@ -703,6 +709,7 @@ interface GroupButtonProps {
703709
index: number;
704710
isVisible?: boolean;
705711
isDisabled?: boolean;
712+
disabledWhenInvalid?: boolean;
706713
label?: string;
707714
buttonType?: string;
708715
buttonColor?: string;
@@ -718,6 +725,7 @@ interface GroupButtonProps {
718725
index: number;
719726
isVisible?: boolean;
720727
isDisabled?: boolean;
728+
disabledWhenInvalid?: boolean;
721729
label?: string;
722730
backgroundColor?: string;
723731
textColor?: string;
@@ -746,6 +754,7 @@ export interface ButtonGroupComponentProps {
746754
widgetId: string;
747755
buttonMinWidth?: number;
748756
minHeight?: number;
757+
isFormValid?: boolean;
749758
}
750759

751760
export interface ButtonGroupComponentState {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { render } from "@testing-library/react";
2+
import React from "react";
3+
import ButtonGroupWidget from "../index";
4+
import { RenderModes } from "constants/WidgetConstants";
5+
import type { ButtonGroupWidgetProps } from "../index";
6+
import { klona } from "klona";
7+
8+
describe("ButtonGroupWidget disabledWhenInvalid", () => {
9+
const defaultProps: ButtonGroupWidgetProps = {
10+
widgetId: "test-button-group",
11+
renderMode: RenderModes.CANVAS,
12+
version: 1,
13+
parentColumnSpace: 1,
14+
parentRowSpace: 1,
15+
leftColumn: 0,
16+
rightColumn: 0,
17+
topRow: 0,
18+
bottomRow: 0,
19+
isLoading: false,
20+
orientation: "horizontal",
21+
isDisabled: false,
22+
buttonVariant: "PRIMARY",
23+
type: "BUTTON_GROUP_WIDGET",
24+
widgetName: "ButtonGroup1",
25+
groupButtons: {
26+
groupButton1: {
27+
label: "Test Button 1",
28+
id: "groupButton1",
29+
widgetId: "",
30+
buttonType: "SIMPLE",
31+
placement: "CENTER",
32+
isVisible: true,
33+
isDisabled: false,
34+
disabledWhenInvalid: true,
35+
index: 0,
36+
menuItems: {},
37+
},
38+
groupButton2: {
39+
label: "Test Button 2",
40+
id: "groupButton2",
41+
widgetId: "",
42+
buttonType: "SIMPLE",
43+
placement: "CENTER",
44+
isVisible: true,
45+
isDisabled: false,
46+
disabledWhenInvalid: true,
47+
index: 1,
48+
menuItems: {},
49+
},
50+
},
51+
};
52+
53+
it("disables buttons when disabledWhenInvalid is true and form is invalid", () => {
54+
const props = klona(defaultProps);
55+
56+
props.isFormValid = false;
57+
58+
const { container } = render(<ButtonGroupWidget {...props} />);
59+
const buttons = container.querySelectorAll("button");
60+
61+
buttons.forEach((button) => {
62+
expect(button.hasAttribute("disabled")).toBe(true);
63+
});
64+
});
65+
66+
it("enables buttons when disabledWhenInvalid is true but form is valid", () => {
67+
const props = klona(defaultProps);
68+
69+
props.isFormValid = true;
70+
71+
const { container } = render(<ButtonGroupWidget {...props} />);
72+
const buttons = container.querySelectorAll("button");
73+
74+
buttons.forEach((button) => {
75+
expect(button.hasAttribute("disabled")).toBe(false);
76+
});
77+
});
78+
79+
it("enables buttons when disabledWhenInvalid is false regardless of form validity", () => {
80+
const props = klona(defaultProps);
81+
82+
props.groupButtons = {
83+
...defaultProps.groupButtons,
84+
groupButton1: {
85+
...defaultProps.groupButtons.groupButton1,
86+
disabledWhenInvalid: false,
87+
},
88+
groupButton2: {
89+
...defaultProps.groupButtons.groupButton2,
90+
disabledWhenInvalid: false,
91+
},
92+
};
93+
94+
const { container } = render(<ButtonGroupWidget {...props} />);
95+
const buttons = container.querySelectorAll("button");
96+
97+
buttons.forEach((button) => {
98+
expect(button.hasAttribute("disabled")).toBe(false);
99+
});
100+
});
101+
});

app/client/src/widgets/ButtonGroupWidget/widget/index.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class ButtonGroupWidget extends BaseWidget<
6565
placement: "CENTER",
6666
isVisible: true,
6767
isDisabled: false,
68+
disabledWhenInvalid: false,
6869
index: 0,
6970
menuItems: {},
7071
},
@@ -77,6 +78,7 @@ class ButtonGroupWidget extends BaseWidget<
7778
widgetId: "",
7879
isVisible: true,
7980
isDisabled: false,
81+
disabledWhenInvalid: false,
8082
index: 1,
8183
menuItems: {},
8284
},
@@ -89,6 +91,7 @@ class ButtonGroupWidget extends BaseWidget<
8991
widgetId: "",
9092
isVisible: true,
9193
isDisabled: false,
94+
disabledWhenInvalid: false,
9295
index: 2,
9396
menuItems: {
9497
menuItem1: {
@@ -99,6 +102,7 @@ class ButtonGroupWidget extends BaseWidget<
99102
onClick: "",
100103
isVisible: true,
101104
isDisabled: false,
105+
disabledWhenInvalid: false,
102106
index: 0,
103107
},
104108
menuItem2: {
@@ -109,6 +113,7 @@ class ButtonGroupWidget extends BaseWidget<
109113
onClick: "",
110114
isVisible: true,
111115
isDisabled: false,
116+
disabledWhenInvalid: false,
112117
index: 1,
113118
},
114119
menuItem3: {
@@ -123,6 +128,7 @@ class ButtonGroupWidget extends BaseWidget<
123128
onClick: "",
124129
isVisible: true,
125130
isDisabled: false,
131+
disabledWhenInvalid: false,
126132
index: 2,
127133
},
128134
},
@@ -517,6 +523,22 @@ class ButtonGroupWidget extends BaseWidget<
517523
},
518524
],
519525
},
526+
{
527+
sectionName: "Form settings",
528+
children: [
529+
{
530+
propertyName: "disabledWhenInvalid",
531+
label: "Disabled invalid forms",
532+
helpText:
533+
"Disables this button if the form is invalid, if this button exists directly within a Form widget",
534+
controlType: "SWITCH",
535+
isJSConvertible: true,
536+
isBindProperty: true,
537+
isTriggerProperty: false,
538+
validation: { type: ValidationTypes.BOOLEAN },
539+
},
540+
],
541+
},
520542
{
521543
sectionName: "Events",
522544
hidden: (
@@ -825,6 +847,7 @@ class ButtonGroupWidget extends BaseWidget<
825847
buttonVariant={this.props.buttonVariant}
826848
groupButtons={this.props.groupButtons}
827849
isDisabled={this.props.isDisabled}
850+
isFormValid={this.props.isFormValid}
828851
minHeight={this.isAutoLayoutMode ? this.props.minHeight : undefined}
829852
minPopoverWidth={minPopoverWidth}
830853
orientation={this.props.orientation}
@@ -839,6 +862,7 @@ class ButtonGroupWidget extends BaseWidget<
839862
export interface ButtonGroupWidgetProps extends WidgetProps {
840863
orientation: string;
841864
isDisabled: boolean;
865+
isFormValid?: boolean;
842866
borderRadius?: string;
843867
boxShadow?: string;
844868
buttonVariant: ButtonVariant;
@@ -850,6 +874,7 @@ export interface ButtonGroupWidgetProps extends WidgetProps {
850874
index: number;
851875
isVisible?: boolean;
852876
isDisabled?: boolean;
877+
disabledWhenInvalid?: boolean;
853878
label?: string;
854879
buttonType?: string;
855880
buttonColor?: string;
@@ -865,6 +890,7 @@ export interface ButtonGroupWidgetProps extends WidgetProps {
865890
index: number;
866891
isVisible?: boolean;
867892
isDisabled?: boolean;
893+
disabledWhenInvalid?: boolean;
868894
label?: string;
869895
backgroundColor?: string;
870896
textColor?: string;

0 commit comments

Comments
 (0)