feat: WDS button group widget integration (#28205)

## Description
Adds WDSButtonGroup Widget.

#### PR fixes following issue(s)
Fixes #27748

#### Type of change
- Chore (housekeeping or task changes that don't impact user perception)

## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [ ] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## 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

---------

Co-authored-by: Pawan Kumar <pawan.stardust@gmail.com>
This commit is contained in:
Dhruvik Neharia 2023-10-30 17:32:13 +05:30 committed by GitHub
parent 4fee5ed92e
commit 69350fc033
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 575 additions and 7 deletions

View File

@ -119,7 +119,7 @@
*-----------------------------------------------------------------------------
*/
&[aria-disabled] {
cursor: default;
cursor: not-allowed;
opacity: var(--opacity-disabled);
}

View File

@ -1,10 +1,15 @@
import type React from "react";
import type { ButtonProps } from "../../Button";
export const BUTTON_GROUP_ORIENTATIONS = {
vertical: "vertical",
horizontal: "horizontal",
};
export interface ButtonGroupProps
extends Pick<ButtonProps, "variant" | "color"> {
children?: React.ReactNode;
orientation?: "vertical" | "horizontal";
orientation?: keyof typeof BUTTON_GROUP_ORIENTATIONS;
}
export type ButtonGroupItemProps = Omit<ButtonProps, "variant" | "color">;

View File

@ -166,17 +166,31 @@ class ButtonListControl extends BaseControl<ControlProps, State> {
id: newGroupButtonId,
index: groupButtonsArray.length,
label: newGroupButtonLabel,
menuItems: {},
buttonType: "SIMPLE",
placement: ButtonPlacementTypes.CENTER,
widgetId: generateReactKey(),
isDisabled: false,
isVisible: true,
buttonColor:
this.props.widgetProperties.childStylesheet.button.buttonColor,
},
};
if (this.props.widgetProperties.type === "BUTTON_GROUP_WIDGET") {
/**
* These properties are required for "BUTTON_GROUP_WIDGET" but not for
* "WDS_BUTTON_GROUP_WIDGET"
*/
const optionalButtonGroupItemProperties = {
menuItems: {},
buttonType: "SIMPLE",
placement: ButtonPlacementTypes.CENTER,
buttonColor:
this.props.widgetProperties.childStylesheet.button.buttonColor,
};
groupButtons[newGroupButtonId] = {
...groupButtons[newGroupButtonId],
...optionalButtonGroupItemProperties,
};
}
this.updateProperty(this.props.propertyName, groupButtons);
};

View File

@ -5,4 +5,5 @@ export const WDS_V2_WIDGET_MAP = {
ICON_BUTTON_WIDGET: "WDS_ICON_BUTTON_WIDGET",
TEXT_WIDGET: "WDS_TEXT_WIDGET",
TABLE_WIDGET_V2: "WDS_TABLE_WIDGET",
BUTTON_GROUP_WIDGET: "WDS_BUTTON_GROUP_WIDGET",
};

View File

@ -64,6 +64,7 @@ import { WDSIconButtonWidget } from "./wds/WDSIconButtonWidget";
import { WDSTextWidget } from "./wds/WDSTextWidget";
import type BaseWidget from "./BaseWidget";
import { WDSTableWidget } from "./wds/WDSTableWidget";
import { WDSButtonGroupWidget } from "./wds/WDSButtonGroupWidget";
const Widgets = [
CanvasWidget,
@ -121,6 +122,7 @@ const Widgets = [
WDSIconButtonWidget,
WDSTextWidget,
WDSTableWidget,
WDSButtonGroupWidget,
//Deprecated Widgets
InputWidget,

View File

@ -0,0 +1,71 @@
import React, { useState } from "react";
import { ButtonGroup, ButtonGroupItem } from "@design-system/widgets";
import type {
ButtonGroupComponentProps,
ButtonGroupItemComponentProps,
} from "./types";
import { Icon as BIcon } from "@blueprintjs/core";
import { sortBy } from "lodash";
export const ButtonGroupComponent = (props: ButtonGroupComponentProps) => {
const [loadingButtonIds, setLoadingButtonIds] = useState<
Array<ButtonGroupItemComponentProps["id"]>
>([]);
const sortedButtons = sortBy(
Object.keys(props.buttonsList)
.map((key) => props.buttonsList[key])
.filter((button) => {
return button.isVisible === true;
}),
["index"],
);
return (
<ButtonGroup
color={props.color}
orientation={props.orientation}
variant={props.variant}
>
{sortedButtons.map((button: ButtonGroupItemComponentProps) => {
const icon =
button.iconName &&
(() => {
return <BIcon icon={button.iconName} />;
});
const handleActionComplete = () => {
const newLoadingButtonIds = [...loadingButtonIds];
const index = newLoadingButtonIds.indexOf(button.id);
if (index > -1) {
newLoadingButtonIds.splice(index, 1);
}
setLoadingButtonIds(newLoadingButtonIds);
};
const onButtonClick = () => {
if (button.onClick) {
setLoadingButtonIds([...loadingButtonIds, button.id]);
props.onButtonClick(button.onClick, handleActionComplete);
}
};
return (
<ButtonGroupItem
icon={icon}
iconPosition={button.iconAlign}
isDisabled={button.isDisabled}
isLoading={loadingButtonIds.includes(button.id)}
key={button.id}
onPress={onButtonClick}
>
{button.label}
</ButtonGroupItem>
);
})}
</ButtonGroup>
);
};

View File

@ -0,0 +1,28 @@
import type { IconName } from "@blueprintjs/icons";
import type { ButtonGroupProps, ButtonProps } from "@design-system/widgets";
import type { ButtonsList } from "../widget/types";
import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants";
export interface ButtonGroupComponentProps {
color?: ButtonGroupProps["color"];
variant?: ButtonGroupProps["variant"];
orientation: ButtonGroupProps["orientation"];
buttonsList: ButtonsList;
onButtonClick: (
onClick: string,
callback: (result: ExecutionResult) => void,
) => void;
}
export interface ButtonGroupItemComponentProps {
label?: string;
isVisible?: boolean;
isLoading?: boolean;
isDisabled?: boolean;
widgetId: string;
id: string;
index: number;
iconName?: IconName;
iconAlign?: ButtonProps["iconPosition"];
onClick?: string;
}

View File

@ -0,0 +1,8 @@
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
export const autocompleteConfig = {
"!doc":
"The Button group widget represents a set of buttons in a group. Group can have simple buttons or menu buttons with drop-down items.",
"!url": "https://docs.appsmith.com/widget-reference/button-group",
isVisible: DefaultAutocompleteDefinitions.isVisible,
};

View File

@ -0,0 +1,61 @@
import {
BUTTON_GROUP_ORIENTATIONS,
BUTTON_VARIANTS,
COLORS,
} from "@design-system/widgets";
import { IconNames } from "@blueprintjs/icons";
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import {
BUTTON_MIN_WIDTH,
FILL_WIDGET_MIN_WIDTH,
} from "constants/minWidthConstants";
export const defaultsConfig = {
rows: 4,
columns: 24,
widgetName: "ButtonGroup",
orientation: BUTTON_GROUP_ORIENTATIONS.horizontal,
buttonVariant: BUTTON_VARIANTS.filled,
buttonColor: COLORS.accent,
isDisabled: false,
isVisible: true,
version: 1,
animateLoading: true,
responsiveBehavior: ResponsiveBehavior.Fill,
minWidth: FILL_WIDGET_MIN_WIDTH,
buttonsList: {
button1: {
label: "Favorite",
isVisible: true,
isDisabled: false,
widgetId: "",
id: "button1",
index: 0,
iconName: IconNames.HEART,
iconAlign: "start",
minWidth: BUTTON_MIN_WIDTH,
},
button2: {
label: "Add",
isVisible: true,
isDisabled: false,
widgetId: "",
id: "button2",
index: 1,
iconName: IconNames.ADD,
iconAlign: "start",
minWidth: BUTTON_MIN_WIDTH,
},
button3: {
label: "More",
isVisible: true,
isDisabled: false,
widgetId: "",
id: "button3",
index: 2,
iconName: IconNames.MORE,
iconAlign: "start",
minWidth: BUTTON_MIN_WIDTH,
},
},
};

View File

@ -0,0 +1,5 @@
export * from "./propertyPaneConfig";
export { autocompleteConfig } from "./autocompleteConfig";
export { defaultsConfig } from "./defaultsConfig";
export { metaConfig } from "./metaConfig";
export { settersConfig } from "./settersConfig";

View File

@ -0,0 +1,11 @@
import IconSVG from "../icon.svg";
import { WIDGET_TAGS } from "constants/WidgetConstants";
export const metaConfig = {
name: "Button Group",
iconSVG: IconSVG,
needsMeta: false,
isCanvas: false,
searchTags: ["click", "submit"],
tags: [WIDGET_TAGS.BUTTONS],
};

View File

@ -0,0 +1,159 @@
import { ValidationTypes } from "constants/WidgetValidation";
export const propertyPaneContentConfig = [
{
sectionName: "Data",
children: [
{
helpText: "Group Buttons",
propertyName: "buttonsList",
controlType: "GROUP_BUTTONS",
label: "Buttons",
isBindProperty: false,
isTriggerProperty: false,
dependencies: ["childStylesheet"],
panelConfig: {
editableTitle: true,
titlePropertyName: "label",
panelIdPropertyName: "id",
updateHook: (
props: any,
propertyPath: string,
propertyValue: string,
) => {
return [
{
propertyPath,
propertyValue,
},
];
},
contentChildren: [
{
sectionName: "Label",
children: [
{
propertyName: "label",
helpText: "Sets the label of the button",
label: "Text",
controlType: "INPUT_TEXT",
placeholderText: "Enter label",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
{
sectionName: "General",
children: [
{
propertyName: "isVisible",
helpText: "Controls the visibility of the widget",
label: "Visible",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "isDisabled",
helpText: "Disables input to the widget",
label: "Disabled",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
{
sectionName: "Events",
children: [
{
helpText: "when the button is clicked",
propertyName: "onClick",
label: "onClick",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
],
styleChildren: [
{
sectionName: "Icon",
children: [
{
propertyName: "iconName",
label: "Icon",
helpText: "Sets the icon to be used for a button",
controlType: "ICON_SELECT",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "iconAlign",
label: "Position",
helpText: "Sets the icon alignment of the button",
controlType: "ICON_TABS",
fullWidth: false,
options: [
{
startIcon: "skip-left-line",
value: "start",
},
{
startIcon: "skip-right-line",
value: "end",
},
],
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: ["start", "end"],
},
},
},
],
},
],
},
},
],
},
{
sectionName: "General",
children: [
{
helpText: "Controls the visibility of the widget",
propertyName: "isVisible",
label: "Visible",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "animateLoading",
label: "Animate loading",
controlType: "SWITCH",
helpText: "Controls the loading of the widget",
defaultValue: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
];

View File

@ -0,0 +1,2 @@
export { propertyPaneContentConfig } from "./contentConfig";
export { propertyPaneStyleConfig } from "./styleConfig";

View File

@ -0,0 +1,78 @@
import {
BUTTON_GROUP_ORIENTATIONS,
BUTTON_VARIANTS,
COLORS,
} from "@design-system/widgets";
import { ValidationTypes } from "constants/WidgetValidation";
import { capitalize } from "lodash";
export const propertyPaneStyleConfig = [
{
sectionName: "General",
children: [
{
propertyName: "buttonVariant",
label: "Button variant",
controlType: "ICON_TABS",
fullWidth: true,
helpText: "Sets the variant of the button",
options: Object.values(BUTTON_VARIANTS).map((variant) => ({
label: capitalize(variant),
value: variant,
})),
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: Object.values(BUTTON_VARIANTS),
default: BUTTON_VARIANTS.filled,
},
},
},
{
propertyName: "buttonColor",
label: "Button color",
controlType: "DROP_DOWN",
fullWidth: true,
helpText: "Sets the semantic color of the button",
options: Object.values(COLORS).map((semantic) => ({
label: capitalize(semantic),
value: semantic,
})),
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: Object.values(COLORS),
default: COLORS.accent,
},
},
},
{
helpText: "Controls widget orientation",
propertyName: "orientation",
label: "Orientation",
controlType: "ICON_TABS",
fullWidth: true,
options: [
{
label: capitalize(BUTTON_GROUP_ORIENTATIONS.horizontal),
value: BUTTON_GROUP_ORIENTATIONS.horizontal,
},
{
label: capitalize(BUTTON_GROUP_ORIENTATIONS.vertical),
value: BUTTON_GROUP_ORIENTATIONS.vertical,
},
],
defaultValue: BUTTON_GROUP_ORIENTATIONS.horizontal,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
];

View File

@ -0,0 +1,12 @@
export const settersConfig = {
__setters: {
setVisibility: {
path: "isVisible",
type: "boolean",
},
setDisabled: {
path: "isDisabled",
type: "boolean",
},
},
};

View File

@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_35_3375" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="8" width="22" height="9">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 9.375C1 8.61561 1.61561 8 2.375 8H7.53125V16.9375H2.375C1.61561 16.9375 1 16.3219 1 15.5625V9.375ZM8.5625 16.9375V8H15.4375V16.9375H8.5625ZM16.4688 16.9375H21.625C22.3844 16.9375 23 16.3219 23 15.5625V9.375C23 8.61561 22.3844 8 21.625 8H16.4688V16.9375Z" fill="white"/>
</mask>
<g mask="url(#mask0_35_3375)">
<path d="M7.53125 8H8.21875V7.3125H7.53125V8ZM7.53125 16.9375V17.625H8.21875V16.9375H7.53125ZM8.5625 8V7.3125H7.875V8H8.5625ZM8.5625 16.9375H7.875V17.625H8.5625V16.9375ZM15.4375 8H16.125V7.3125H15.4375V8ZM15.4375 16.9375V17.625H16.125V16.9375H15.4375ZM16.4688 16.9375H15.7812V17.625H16.4688V16.9375ZM16.4688 8V7.3125H15.7812V8H16.4688ZM2.375 7.3125C1.23591 7.3125 0.3125 8.23591 0.3125 9.375H1.6875C1.6875 8.99531 1.99531 8.6875 2.375 8.6875V7.3125ZM7.53125 7.3125H2.375V8.6875H7.53125V7.3125ZM8.21875 16.9375V8H6.84375V16.9375H8.21875ZM2.375 17.625H7.53125V16.25H2.375V17.625ZM0.3125 15.5625C0.3125 16.7016 1.23591 17.625 2.375 17.625V16.25C1.99531 16.25 1.6875 15.9422 1.6875 15.5625H0.3125ZM0.3125 9.375V15.5625H1.6875V9.375H0.3125ZM7.875 8V16.9375H9.25V8H7.875ZM15.4375 7.3125H8.5625V8.6875H15.4375V7.3125ZM16.125 16.9375V8H14.75V16.9375H16.125ZM8.5625 17.625H15.4375V16.25H8.5625V17.625ZM16.4688 17.625H21.625V16.25H16.4688V17.625ZM21.625 17.625C22.7641 17.625 23.6875 16.7016 23.6875 15.5625H22.3125C22.3125 15.9422 22.0047 16.25 21.625 16.25V17.625ZM23.6875 15.5625V9.375H22.3125V15.5625H23.6875ZM23.6875 9.375C23.6875 8.23591 22.7641 7.3125 21.625 7.3125V8.6875C22.0047 8.6875 22.3125 8.99531 22.3125 9.375H23.6875ZM21.625 7.3125H16.4688V8.6875H21.625V7.3125ZM15.7812 8V16.9375H17.1562V8H15.7812Z" fill="#4C5664"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 9.375C1 8.61561 1.61561 8 2.375 8H7.53125V16.9375H2.375C1.61561 16.9375 1 16.3219 1 15.5625V9.375ZM8.5625 16.9375V8H15.4375V16.9375H8.5625ZM16.4688 16.9375H21.625C22.3844 16.9375 23 16.3219 23 15.5625V9.375C23 8.61561 22.3844 8 21.625 8H16.4688V16.9375Z" fill="#4C5664"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,3 @@
import { WDSButtonGroupWidget } from "./widget";
export { WDSButtonGroupWidget };

View File

@ -0,0 +1,86 @@
import React from "react";
import type { SetterConfig } from "entities/AppTheming";
import type { WidgetState } from "widgets/BaseWidget";
import BaseWidget from "widgets/BaseWidget";
import {
metaConfig,
defaultsConfig,
autocompleteConfig,
propertyPaneContentConfig,
propertyPaneStyleConfig,
settersConfig,
} from "./../config";
import type { ButtonGroupWidgetProps } from "./types";
import { ButtonGroupComponent } from "../component";
import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
class WDSButtonGroupWidget extends BaseWidget<
ButtonGroupWidgetProps,
WidgetState
> {
constructor(props: ButtonGroupWidgetProps) {
super(props);
}
static type = "WDS_BUTTON_GROUP_WIDGET";
static getConfig() {
return metaConfig;
}
static getDefaults() {
return defaultsConfig;
}
static getAutoLayoutConfig() {
return {};
}
static getAutocompleteDefinitions() {
return autocompleteConfig;
}
static getPropertyPaneContentConfig() {
return propertyPaneContentConfig;
}
static getPropertyPaneStyleConfig() {
return propertyPaneStyleConfig;
}
static getSetterConfig(): SetterConfig {
return settersConfig;
}
onButtonClick = (
onClick: string,
callback: (result: ExecutionResult) => void,
) => {
super.executeAction({
triggerPropertyName: "onClick",
dynamicString: onClick,
event: {
type: EventType.ON_CLICK,
callback: callback,
},
});
return;
};
getWidgetView() {
return (
<ButtonGroupComponent
buttonsList={this.props.buttonsList}
color={this.props.buttonColor}
key={this.props.widgetId}
onButtonClick={this.onButtonClick}
orientation={this.props.orientation}
variant={this.props.buttonVariant}
/>
);
}
}
export { WDSButtonGroupWidget };

View File

@ -0,0 +1,13 @@
import type { ButtonGroupProps } from "@design-system/widgets";
import type { WidgetProps } from "widgets/BaseWidget";
import type { ButtonGroupItemComponentProps } from "../component/types";
export type ButtonsList = Record<string, ButtonGroupItemComponentProps>;
export interface ButtonGroupWidgetProps extends WidgetProps {
buttonColor: ButtonGroupProps["color"];
buttonVariant: ButtonGroupProps["variant"];
orientation: ButtonGroupProps["orientation"];
isVisible: boolean;
buttonsList: ButtonsList;
}