feat: WDS Checkbox Group Integration (#28528)

## Description
Adds WDSCheckboxGroup Widget.

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

#### Media
<video
src="https://github.com/appsmithorg/appsmith/assets/22471214/78ed3ff5-0b60-4ba1-8a91-1b31f7e30334"></video>

#### Type of change
> Please delete options that are not relevant.
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- Chore (housekeeping or task changes that don't impact user perception)
- This change requires a documentation update
>
>
>
## 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
- [x] 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
- [x] My code follows the style guidelines of this project
- [x] 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
- [x] 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
- [x] 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
This commit is contained in:
Dhruvik Neharia 2023-11-06 14:09:22 +05:30 committed by GitHub
parent eaac70d9f7
commit 5b641b40a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 527 additions and 0 deletions

View File

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

View File

@ -65,6 +65,7 @@ import { WDSTextWidget } from "./wds/WDSTextWidget";
import type BaseWidget from "./BaseWidget";
import { WDSTableWidget } from "./wds/WDSTableWidget";
import { WDSButtonGroupWidget } from "./wds/WDSButtonGroupWidget";
import { WDSCheckboxGroupWidget } from "./wds/WDSCheckboxGroupWidget";
const Widgets = [
CanvasWidget,
@ -123,6 +124,7 @@ const Widgets = [
WDSTextWidget,
WDSTableWidget,
WDSButtonGroupWidget,
WDSCheckboxGroupWidget,
//Deprecated Widgets
InputWidget,

View File

@ -0,0 +1,30 @@
import React from "react";
import { CheckboxGroup, Checkbox } from "@design-system/widgets";
import type { CheckboxGroupComponentProps } from "./types";
import type { OptionProps } from "../widget/types";
export const CheckboxGroupComponent = (props: CheckboxGroupComponentProps) => {
return (
<CheckboxGroup
isDisabled={props.isDisabled}
isRequired={props.isRequired}
label={props.labelText}
onChange={props.onChange}
orientation={props.orientation}
value={props.selectedValues}
>
{props.options.map((option: OptionProps, index: number) => {
return (
<Checkbox
key={`${props.widgetId}-checkbox-${index}`}
labelPosition={props.labelPosition}
validationState={props.isValid ? "valid" : "invalid"}
value={option.value}
>
{option.label}
</Checkbox>
);
})}
</CheckboxGroup>
);
};

View File

@ -0,0 +1,19 @@
import type { CheckboxGroupWidgetProps, OptionProps } from "../widget/types";
export interface CheckboxGroupComponentProps
extends Pick<
CheckboxGroupWidgetProps,
| "defaultSelectedValues"
| "isDisabled"
| "isRequired"
| "isValid"
| "labelPosition"
| "labelText"
| "onCheckChange"
| "options"
| "orientation"
| "widgetId"
> {
onChange: (value: string[]) => void;
selectedValues: OptionProps["value"][];
}

View File

@ -0,0 +1,10 @@
import type { AnvilConfig } from "WidgetProvider/constants";
export const anvilConfig: AnvilConfig = {
widgetSize: {
maxHeight: {},
maxWidth: {},
minHeight: { base: "42px" },
minWidth: { base: "130px" },
},
};

View File

@ -0,0 +1,12 @@
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
export const autocompleteConfig = {
"!doc":
"Checkbox group widget allows users to easily configure multiple checkboxes together.",
"!url": "https://docs.appsmith.com/widget-reference/checkbox-group",
isVisible: DefaultAutocompleteDefinitions.isVisible,
isDisabled: "bool",
isValid: "bool",
options: "[$__dropdownOption__$]",
selectedValues: "[string]",
};

View File

@ -0,0 +1,19 @@
export const defaultsConfig = {
rows: 10,
columns: 20,
animateLoading: true,
options: [
{ label: "Blue", value: "BLUE" },
{ label: "Green", value: "GREEN" },
{ label: "Red", value: "RED" },
],
defaultSelectedValues: ["BLUE"],
isDisabled: false,
isRequired: false,
isVisible: true,
labelPosition: "left",
labelText: "Label",
orientation: "vertical",
version: 1,
widgetName: "CheckboxGroup",
};

View File

@ -0,0 +1,6 @@
export const featuresConfig = {
dynamicHeight: {
sectionIndex: 3,
active: true,
},
};

View File

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

View File

@ -0,0 +1,9 @@
import IconSVG from "../icon.svg";
import { WIDGET_TAGS } from "constants/WidgetConstants";
export const metaConfig = {
name: "Checkbox Group",
iconSVG: IconSVG,
tags: [WIDGET_TAGS.TOGGLES],
needsMeta: true,
};

View File

@ -0,0 +1,172 @@
import { ValidationTypes } from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { defaultSelectedValuesValidation } from "./validations";
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
export const propertyPaneContentConfig = [
{
sectionName: "Data",
children: [
{
helpText: "Displays a list of unique checkbox options",
propertyName: "options",
label: "Options",
controlType: "OPTION_INPUT",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.ARRAY,
params: {
default: [],
unique: ["value"],
children: {
type: ValidationTypes.OBJECT,
params: {
required: true,
allowedKeys: [
{
name: "label",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "value",
type: ValidationTypes.TEXT,
params: {
default: "",
},
},
],
},
},
},
},
evaluationSubstitutionType: EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Sets the values of the options checked by default",
propertyName: "defaultSelectedValues",
label: "Default selected values",
placeholderText: '["apple", "orange"]',
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.FUNCTION,
params: {
fn: defaultSelectedValuesValidation,
expected: {
type: "String or Array<string>",
example: `apple | ["apple", "orange"]`,
autocompleteDataType: AutocompleteDataType.STRING,
},
},
},
},
],
},
{
sectionName: "Label",
children: [
{
helpText: "Sets the label text of the widget",
propertyName: "labelText",
label: "Text",
controlType: "INPUT_TEXT",
placeholderText: "Enter label text",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Sets the label position of the widget",
propertyName: "labelPosition",
label: "Position",
controlType: "ICON_TABS",
fullWidth: true,
options: [
{ label: "Left", value: "left" },
{ label: "Right", value: "right" },
],
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
{
sectionName: "Validations",
children: [
{
propertyName: "isRequired",
label: "Required",
helpText: "Makes input to the widget mandatory",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.BOOLEAN,
},
},
],
},
{
sectionName: "General",
children: [
{
propertyName: "isVisible",
label: "Visible",
helpText: "Controls the visibility of the widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.BOOLEAN,
},
},
{
propertyName: "isDisabled",
label: "Disabled",
controlType: "SWITCH",
helpText: "Disables input to this widget",
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 },
},
],
},
{
sectionName: "Events",
children: [
{
helpText: "When the check state is changed",
propertyName: "onCheckChange",
label: "onCheckChange",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
];

View File

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

View File

@ -0,0 +1,30 @@
import { ValidationTypes } from "constants/WidgetValidation";
export const propertyPaneStyleConfig = [
{
sectionName: "General",
children: [
{
helpText: "Controls widget orientation",
propertyName: "orientation",
label: "Orientation",
controlType: "ICON_TABS",
fullWidth: true,
options: [
{
label: "Horizontal",
value: "horizontal",
},
{
label: "Vertical",
value: "vertical",
},
],
defaultValue: "vertical",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
];

View File

@ -0,0 +1,30 @@
import type { ValidationResponse } from "constants/WidgetValidation";
export function defaultSelectedValuesValidation(
value: unknown,
): ValidationResponse {
let values: string[] = [];
if (typeof value === "string") {
try {
values = JSON.parse(value);
if (!Array.isArray(values)) {
throw new Error();
}
} catch {
values = value.length ? value.split(",") : [];
if (values.length > 0) {
values = values.map((_v: string) => _v.trim());
}
}
}
if (Array.isArray(value)) {
values = Array.from(new Set(value));
}
return {
isValid: true,
parsed: values,
};
}

View File

@ -0,0 +1 @@
export * from "./defaultSelectedValuesValidation";

View File

@ -0,0 +1,21 @@
export const settersConfig = {
__setters: {
setVisibility: {
path: "isVisible",
type: "boolean",
},
setDisabled: {
path: "isDisabled",
type: "boolean",
},
setRequired: {
path: "isRequired",
type: "boolean",
},
setSelectedOptions: {
path: "defaultSelectedValues",
type: "array",
accessor: "selectedValues",
},
},
};

View File

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 13.7513L2.15063 12.6007L3.30123 13.7513L6.17779 10.8748L7.3284 12.0253L3.30123 16.0526L1 13.7513Z" fill="#4C5664"/>
<path d="M1 4.87655L2.15063 3.72594L3.30123 4.87655L6.17779 2L7.3284 3.15063L3.30123 7.17781L1 4.87655Z" fill="#4C5664"/>
<path d="M22.9998 3.53413H9.57617V6.21887H22.9998V3.53413Z" fill="#4C5664"/>
<path d="M22.9998 11.5883H9.57617V14.273H22.9998V11.5883Z" fill="#4C5664"/>
<path d="M22.9998 19.6426H9.57617V22.3273H22.9998V19.6426Z" fill="#4C5664"/>
</svg>

After

Width:  |  Height:  |  Size: 583 B

View File

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

View File

@ -0,0 +1,127 @@
import React from "react";
import type { SetterConfig } from "entities/AppTheming";
import type { DerivedPropertiesMap } from "WidgetProvider/factory";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { xor } from "lodash";
import BaseWidget from "widgets/BaseWidget";
import type { CheckboxGroupWidgetProps, OptionProps } from "./types";
import type { WidgetState } from "widgets/BaseWidget";
import type { AnvilConfig } from "WidgetProvider/constants";
import { CheckboxGroupComponent } from "../component";
import {
anvilConfig,
autocompleteConfig,
defaultsConfig,
featuresConfig,
metaConfig,
propertyPaneContentConfig,
propertyPaneStyleConfig,
settersConfig,
} from "./../config";
class WDSCheckboxGroupWidget extends BaseWidget<
CheckboxGroupWidgetProps,
WidgetState
> {
static type = "WDS_CHECKBOX_GROUP_WIDGET";
static getConfig() {
return metaConfig;
}
static getFeatures() {
return featuresConfig;
}
static getDefaults() {
return defaultsConfig;
}
static getAutoLayoutConfig() {
return {};
}
static getAnvilConfig(): AnvilConfig | null {
return anvilConfig;
}
static getAutocompleteDefinitions() {
return autocompleteConfig;
}
static getSetterConfig(): SetterConfig {
return settersConfig;
}
static getPropertyPaneContentConfig() {
return propertyPaneContentConfig;
}
static getPropertyPaneStyleConfig() {
return propertyPaneStyleConfig;
}
static getDefaultPropertiesMap(): Record<string, string> {
return {
selectedValues: "defaultSelectedValues",
};
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {
value: `{{this.selectedValues}}`,
isValid: `{{ this.isRequired ? !!this.selectedValues.length : true }}`,
};
}
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedValues: undefined,
isDirty: false,
};
}
componentDidUpdate(prevProps: CheckboxGroupWidgetProps) {
if (
xor(this.props.defaultSelectedValues, prevProps.defaultSelectedValues)
.length > 0 &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
handleChange = (selectedValues: OptionProps["value"][]) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("selectedValues", selectedValues, {
triggerPropertyName: "onCheckChange",
dynamicString: this.props.onCheckChange,
event: {
type: EventType.ON_CHECKBOX_GROUP_SELECTION_CHANGE,
},
});
};
getWidgetView() {
return (
<CheckboxGroupComponent
defaultSelectedValues={this.props.defaultSelectedValues}
isDisabled={this.props.isDisabled}
isRequired={this.props.isRequired}
isValid={this.props.isValid}
labelPosition={this.props.labelPosition}
labelText={this.props.labelText}
onChange={this.handleChange}
options={this.props.options}
orientation={this.props.orientation}
selectedValues={this.props.selectedValues}
widgetId={this.props.widgetId}
/>
);
}
}
export { WDSCheckboxGroupWidget };

View File

@ -0,0 +1,19 @@
import type { WidgetProps } from "widgets/BaseWidget";
export interface OptionProps {
label?: string;
value: string;
}
export interface CheckboxGroupWidgetProps extends WidgetProps {
defaultSelectedValues?: OptionProps["value"][];
isDisabled: boolean;
isRequired?: boolean;
isValid?: boolean;
isVisible: boolean;
labelPosition: "left" | "right";
labelText?: string;
onCheckChange?: string;
options: OptionProps[];
orientation: "vertical" | "horizontal";
}