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:
parent
eaac70d9f7
commit
5b641b40a4
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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"][];
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import type { AnvilConfig } from "WidgetProvider/constants";
|
||||
|
||||
export const anvilConfig: AnvilConfig = {
|
||||
widgetSize: {
|
||||
maxHeight: {},
|
||||
maxWidth: {},
|
||||
minHeight: { base: "42px" },
|
||||
minWidth: { base: "130px" },
|
||||
},
|
||||
};
|
||||
|
|
@ -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]",
|
||||
};
|
||||
|
|
@ -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",
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export const featuresConfig = {
|
||||
dynamicHeight: {
|
||||
sectionIndex: 3,
|
||||
active: true,
|
||||
},
|
||||
};
|
||||
|
|
@ -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";
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { propertyPaneContentConfig } from "./contentConfig";
|
||||
export { propertyPaneStyleConfig } from "./styleConfig";
|
||||
|
|
@ -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 },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./defaultSelectedValuesValidation";
|
||||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -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 |
|
|
@ -0,0 +1,3 @@
|
|||
import { WDSCheckboxGroupWidget } from "./widget";
|
||||
|
||||
export { WDSCheckboxGroupWidget };
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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";
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user