diff --git a/app/client/cypress/fixtures/SwitchGroupWidgetDsl.json b/app/client/cypress/fixtures/SwitchGroupWidgetDsl.json new file mode 100644 index 0000000000..acc93d0818 --- /dev/null +++ b/app/client/cypress/fixtures/SwitchGroupWidgetDsl.json @@ -0,0 +1,65 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1206.35, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 710, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 45, + "minHeight": 690, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "SwitchGroup1", + "isCanvas": false, + "displayName": "Switch Group", + "iconSVG": "/static/media/icon.086a7201.svg", + "topRow": 11, + "bottomRow": 17, + "parentRowSpace": 10, + "type": "SWITCH_GROUP_WIDGET", + "hideCard": false, + "parentColumnSpace": 18.66171875, + "leftColumn": 6, + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "isDisabled": false, + "key": "4dddg71qr8", + "isRequired": false, + "rightColumn": 24, + "defaultSelectedValues": [ + "BLUE" + ], + "widgetId": "lexxd4wyb1", + "isVisible": true, + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "isInline": true + } + ] +} +} diff --git a/app/client/cypress/fixtures/newFormDsl.json b/app/client/cypress/fixtures/newFormDsl.json index bf53a552a9..fde4b70297 100644 --- a/app/client/cypress/fixtures/newFormDsl.json +++ b/app/client/cypress/fixtures/newFormDsl.json @@ -307,6 +307,45 @@ "bottomRow": 17, "parentId": "kipfh779n5", "widgetId": "zbzrlbiose" + }, + { + "widgetName": "SwitchGroup1", + "isCanvas": false, + "displayName": "Switch Group", + "iconSVG": "/static/media/icon.086a7201.svg", + "topRow": 8, + "bottomRow": 14, + "parentRowSpace": 10, + "type": "SWITCH_GROUP_WIDGET", + "hideCard": false, + "parentColumnSpace": 6.6856445312499995, + "leftColumn": 2, + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "isDisabled": false, + "key": "vrhzyvir7s", + "isRequired": false, + "rightColumn": 20, + "defaultSelectedValues": "BLUE", + "widgetId": "egtjazr1wh", + "isVisible": true, + "version": 1, + "parentId": "xbbqrz2ncf", + "renderMode": "CANVAS", + "isLoading": false, + "isInline": true } ], "widgetId": "e3tq9qwta6", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/SwitchGroup_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/SwitchGroup_spec.js new file mode 100644 index 0000000000..497326be87 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/SwitchGroup_spec.js @@ -0,0 +1,103 @@ +const commonlocators = require("../../../../locators/commonlocators.json"); +const formWidgetsPage = require("../../../../locators/FormWidgets.json"); +const modalWidgetPage = require("../../../../locators/ModalWidget.json"); +const publish = require("../../../../locators/publishWidgetspage.json"); +const dsl = require("../../../../fixtures/SwitchGroupWidgetDsl.json"); + +describe("Switch Group Widget Functionality", function() { + before(() => { + cy.addDsl(dsl); + }); + + beforeEach(() => { + cy.openPropertyPane("switchgroupwidget"); + }); + + afterEach(() => { + cy.goToEditFromPublish(); + }); + + it("Widget name changes", function() { + /** + * @param{Text} Random Text + * @param{RadioWidget}Mouseover + * @param{RadioPre Css} Assertion + */ + + cy.widgetText( + "switchgrouptest", + formWidgetsPage.switchGroupWidget, + formWidgetsPage.switchGroupInput, + ); + cy.closePropertyPane(); + }); + + it("Property: options", function() { + // Add a new option + const optionToAdd = { label: "Yellow", value: "YELLOW" }; + cy.get(".t--property-control-options .CodeMirror textarea") + .first() + .focus({ force: true }) + .type("{ctrl}{end}", { force: true }) + .type("{ctrl}{uparrow}", { force: true }) + .type("{end}", { force: true }) + .type(",{enter}") + .type(JSON.stringify(optionToAdd), { + parseSpecialCharSequences: false, + }); + // Assert + cy.get(formWidgetsPage.labelSwitchGroup) + .should("have.length", 4) + .eq(3) + .contains("Yellow"); + cy.closePropertyPane(); + }); + + it("Property: defaultSelectedValues", function() { + // Add a new option + const valueToAdd = "GREEN"; + cy.get(".t--property-control-defaultselectedvalues .CodeMirror textarea") + .first() + .focus({ force: true }) + .type("{ctrl}{end}", { force: true }) + .type("{ctrl}{uparrow}", { force: true }) + .type("{end}", { force: true }) + .type(",{enter}") + .type(`"${valueToAdd}"`); + // Assert + cy.get(`${formWidgetsPage.labelSwitchGroup} input:checked`) + .should("have.length", 2) + .eq(1) + .parent() + .contains("Green"); + cy.closePropertyPane(); + }); + + it("Property: isVisible === FALSE", function() { + cy.togglebarDisable(commonlocators.visibleCheckbox); + cy.PublishtheApp(); + cy.get(publish.switchGroupWidget + " " + "input").should("not.exist"); + }); + + it("Property: isVisible === TRUE", function() { + cy.togglebar(commonlocators.visibleCheckbox); + cy.PublishtheApp(); + cy.get(publish.switchGroupWidget + " " + "input") + .eq(0) + .should("exist"); + }); + + it("Property: onSelectionChange", function() { + // create an alert modal and verify its name + cy.createModal(this.data.ModalName); + cy.PublishtheApp(); + cy.get(publish.switchGroupWidget + " " + "label.bp3-switch") + .children() + .first() + .click({ force: true }); + cy.get(modalWidgetPage.modelTextField).should( + "have.text", + this.data.ModalName, + ); + }); +}); diff --git a/app/client/cypress/locators/FormWidgets.json b/app/client/cypress/locators/FormWidgets.json index 1f2b0bc8fc..b49525cffe 100644 --- a/app/client/cypress/locators/FormWidgets.json +++ b/app/client/cypress/locators/FormWidgets.json @@ -8,6 +8,7 @@ "dropdownSelectionType": ".t--property-control-selectiontype .bp3-popover-target", "radioWidget": ".t--draggable-radiogroupwidget", "checkboxGroupWidget": ".t--draggable-checkboxgroupwidget", + "switchGroupWidget": ".t--draggable-switchgroupwidget", "radioOnSelectionChangeDropdown": ".t--property-control-onselectionchange", "nextDayBtn": ".DayPicker-Day[aria-selected='true'] + div.DayPicker-Day", "datepickerWidget": ".t--draggable-datepickerwidget", @@ -28,6 +29,7 @@ "treeSelectInput": ".rc-tree-select-selection-search-input", "labelradio": ".t--draggable-radiogroupwidget label", "labelCheckboxGroup": ".t--draggable-checkboxgroupwidget label", + "labelSwitchGroup": ".t--draggable-switchgroupwidget label", "deleteradiovalue": ".t--property-control-options svg.remixicon-icon ", "inputRadio": ".t--draggable-radiogroupwidget input", "defaultSelect": ".t--property-control-defaultselectedvalue .CodeMirror-code", @@ -35,6 +37,7 @@ "formInner": ".t--draggable-formwidget span.t--widget-name", "radioInput": ".t--draggable-radiogroupwidget span.t--widget-name", "checkboxGroupInput": ".t--draggable-checkboxgroupwidget span.t--widget-name", + "switchGroupInput": ".t--draggable-switchgroupwidget span.t--widget-name", "radioAddButton": ".t--property-control-options button", "formD": "div[type='FORM_WIDGET']", "datepickerFooter": ".bp3-datepicker-footer span", diff --git a/app/client/cypress/locators/publishWidgetspage.json b/app/client/cypress/locators/publishWidgetspage.json index db8943f523..34c5fb661d 100644 --- a/app/client/cypress/locators/publishWidgetspage.json +++ b/app/client/cypress/locators/publishWidgetspage.json @@ -9,6 +9,7 @@ "switchwidget": ".t--widget-switchwidget", "radioWidget": ".t--widget-radiogroupwidget", "checkboxGroupWidget": ".t--widget-checkboxgroupwidget", + "switchGroupWidget": ".t--widget-switchgroupwidget", "formWidget": ".t--widget-formwidget", "imageWidget": ".t--widget-imagewidget", "dropdownWidget": ".t--widget-dropdownwidget", diff --git a/app/client/package.json b/app/client/package.json index 6f4e2113d2..4b0bfec42a 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -146,7 +146,7 @@ "tinycolor2": "^1.4.2", "toposort": "^2.0.2", "ts-loader": "^6.0.4", - "tslib": "^2.1.0", + "tslib": "^2.3.1", "typescript": "^4.1.3", "unescape-js": "^1.1.4", "url-search-params-polyfill": "^8.0.0", diff --git a/app/client/src/assets/icons/widget/switch-group.svg b/app/client/src/assets/icons/widget/switch-group.svg new file mode 100755 index 0000000000..fcaa891f23 --- /dev/null +++ b/app/client/src/assets/icons/widget/switch-group.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx index 1191abffad..b7368bdeb5 100644 --- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx +++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx @@ -90,6 +90,7 @@ export enum EventType { ON_LIST_PAGE_CHANGE = "ON_LIST_PAGE_CHANGE", ON_RECORDING_START = "ON_RECORDING_START", ON_RECORDING_COMPLETE = "ON_RECORDING_COMPLETE", + ON_SWITCH_GROUP_SELECTION_CHANGE = "ON_SWITCH_GROUP_SELECTION_CHANGE", } export interface PageAction { diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts index 25be69f731..ba079d7b7a 100644 --- a/app/client/src/constants/HelpConstants.ts +++ b/app/client/src/constants/HelpConstants.ts @@ -167,6 +167,10 @@ export const HelpMap: Record = { path: "/widget-reference/audio-recorder", searchKey: "Audio Recorder", }, + SWITCH_GROUP_WIDGET: { + path: "/widget-reference/switch-group", + searchKey: "Switch Group", + }, }; export const HelpBaseURL = "https://docs.appsmith.com"; diff --git a/app/client/src/icons/WidgetIcons.tsx b/app/client/src/icons/WidgetIcons.tsx index f779ef22f7..b0dbbbae45 100644 --- a/app/client/src/icons/WidgetIcons.tsx +++ b/app/client/src/icons/WidgetIcons.tsx @@ -34,6 +34,7 @@ import { ReactComponent as StatboxIcon } from "assets/icons/widget/statbox.svg"; import { ReactComponent as CheckboxGroupIcon } from "assets/icons/widget/checkbox-group.svg"; import { ReactComponent as AudioRecorderIcon } from "assets/icons/widget/audio-recorder.svg"; import { ReactComponent as ButtonGroupIcon } from "assets/icons/widget/button-group.svg"; +import { ReactComponent as SwitchGroupIcon } from "assets/icons/widget/switch-group.svg"; import styled from "styled-components"; import { Colors } from "constants/Colors"; @@ -225,6 +226,11 @@ export const WidgetIcons: { ), + SWITCH_GROUP_WIDGET: (props: IconProps) => ( + + + + ), }; export type WidgetIcon = typeof WidgetIcons[keyof typeof WidgetIcons]; diff --git a/app/client/src/utils/AppsmithUtils.test.ts b/app/client/src/utils/AppsmithUtils.test.ts new file mode 100644 index 0000000000..57fe7efb7b --- /dev/null +++ b/app/client/src/utils/AppsmithUtils.test.ts @@ -0,0 +1,13 @@ +import { getCamelCaseString } from "utils/AppsmithUtils"; + +describe("getCamelCaseString", () => { + it("Should return a string in camelCase", () => { + const inputs = ["abcd", "ab12cd", "开关", "😃 😃 😃"]; + const expected = ["abcd", "ab12Cd", "", ""]; + + inputs.forEach((input, index) => { + const result = getCamelCaseString(input); + expect(result).toStrictEqual(expected[index]); + }); + }); +}); diff --git a/app/client/src/utils/AppsmithUtils.tsx b/app/client/src/utils/AppsmithUtils.tsx index 9d1d5a2ad4..83b110785c 100644 --- a/app/client/src/utils/AppsmithUtils.tsx +++ b/app/client/src/utils/AppsmithUtils.tsx @@ -372,6 +372,26 @@ export const parseBlobUrl = (blobId: string) => { }; /** + * Convert a string into camelCase + * @param sourceString input string + * @returns camelCase string + */ +export const getCamelCaseString = (sourceString: string) => { + let out = ""; + // Split the input string to separate words using RegEx + const regEx = /[A-Z\xC0-\xD6\xD8-\xDE]?[a-z\xDF-\xF6\xF8-\xFF]+|[A-Z\xC0-\xD6\xD8-\xDE]+(?![a-z\xDF-\xF6\xF8-\xFF])|\d+/g; + const words = sourceString.match(regEx); + if (words) { + words.forEach(function(el, idx) { + const add = el.toLowerCase(); + out += idx === 0 ? add : add[0].toUpperCase() + add.slice(1); + }); + } + + return out; +}; + +/* * gets the page url * * Note: for edit mode, the page will have different url ( contains '/edit' at the end ) diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx index ba1fabdbee..bd3975dfe4 100644 --- a/app/client/src/utils/WidgetRegistry.tsx +++ b/app/client/src/utils/WidgetRegistry.tsx @@ -93,7 +93,6 @@ import FilePickerWidgetV2, { import AudioWidget, { CONFIG as AUDIO_WIDGET_CONFIG, } from "widgets/AudioWidget"; - import AudioRecorderWidget, { CONFIG as AUDIO_RECORDER_WIDGET_CONFIG, } from "widgets/AudioRecorderWidget"; @@ -103,14 +102,17 @@ import DocumentViewerWidget, { import ButtonGroupWidget, { CONFIG as BUTTON_GROUP_CONFIG, } from "widgets/ButtonGroupWidget"; - -import log from "loglevel"; import SingleSelectTreeWidget, { CONFIG as SINGLE_SELECT_TREE_WIDGET_CONFIG, } from "widgets/SingleSelectTreeWidget"; import MultiSelectTreeWidget, { CONFIG as MULTI_SELECT_TREE_WIDGET_CONFIG, } from "widgets/MultiSelectTreeWidget"; +import SwitchGroupWidget, { + CONFIG as SWITCH_GROUP_WIDGET_CONFIG, +} from "widgets/SwitchGroupWidget"; + +import log from "loglevel"; export const registerWidgets = () => { const start = performance.now(); @@ -154,6 +156,7 @@ export const registerWidgets = () => { registerWidget(ButtonGroupWidget, BUTTON_GROUP_CONFIG); registerWidget(MultiSelectTreeWidget, MULTI_SELECT_TREE_WIDGET_CONFIG); registerWidget(SingleSelectTreeWidget, SINGLE_SELECT_TREE_WIDGET_CONFIG); + registerWidget(SwitchGroupWidget, SWITCH_GROUP_WIDGET_CONFIG); registerWidget(AudioWidget, AUDIO_WIDGET_CONFIG); log.debug("Widget registration took: ", performance.now() - start, "ms"); diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 2263e985a6..89c116e1e8 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -422,6 +422,12 @@ export const entityDefinitions: Record = { dataURL: "string", rawBinary: "string", }, + SWITCH_GROUP_WIDGET: { + "!doc": + "Switch group widget allows users to create many switch components which can easily by used in a form", + "!url": "https://docs.appsmith.com/widget-reference/switch-group", + selectedValues: "[string]", + }, }; export const GLOBAL_DEFS = { diff --git a/app/client/src/widgets/SwitchGroupWidget/component/index.tsx b/app/client/src/widgets/SwitchGroupWidget/component/index.tsx new file mode 100644 index 0000000000..4bf8ee01b4 --- /dev/null +++ b/app/client/src/widgets/SwitchGroupWidget/component/index.tsx @@ -0,0 +1,143 @@ +import React from "react"; +import styled from "styled-components"; +import { Alignment, Switch } from "@blueprintjs/core"; + +import { ThemeProp } from "components/ads/common"; +import { BlueprintControlTransform } from "constants/DefaultTheme"; +import { Colors } from "constants/Colors"; + +export interface OptionProps { + label?: string; + value: string; +} + +export interface SwitchGroupContainerProps { + alignment: Alignment; + inline?: boolean; + optionCount: number; + valid?: boolean; +} + +export const SwitchGroupContainer = styled.div< + ThemeProp & SwitchGroupContainerProps +>` + display: ${({ inline }) => (inline ? "inline-flex" : "flex")}; + ${({ alignment, inline }) => ` + flex-direction: ${inline ? "row" : "column"}; + align-items: ${ + inline + ? "center" + : alignment === Alignment.LEFT + ? "flex-start" + : "flex-end" + }; + ${inline && "flex-wrap: wrap"}; + `} + justify-content: ${({ alignment, inline, optionCount }) => + optionCount > 1 + ? `space-between` + : inline + ? alignment === Alignment.LEFT + ? `flex-start` + : `flex-end` + : `center`}; + width: 100%; + height: 100%; + overflow: auto; + border: 1px solid transparent; + ${({ theme, valid }) => + !valid && + ` + border: 1px solid ${theme.colors.error}; + `} + padding: 2px 4px; + + ${BlueprintControlTransform} +`; + +export interface StyledSwitchProps { + disabled?: boolean; + inline?: boolean; + optionCount: number; + rowSpace: number; +} + +const StyledSwitch = styled(Switch)` + height: ${({ rowSpace }) => rowSpace}px; + + &.bp3-control.bp3-switch { + margin-top: ${({ inline, optionCount }) => + (inline || optionCount === 1) && `4px`}; + ${({ alignIndicator }) => + alignIndicator === Alignment.RIGHT && `margin-right: 0`}; + input:checked ~ .bp3-control-indicator, + &:hover input:checked ~ .bp3-control-indicator { + background-color: ${Colors.GREEN}; + } + } +`; + +function SwitchGroupComponent(props: SwitchGroupComponentProps) { + const { + alignment, + disabled, + inline, + onChange, + options, + rowSpace, + selected, + valid, + } = props; + + return ( + + {Array.isArray(options) && + options.length > 0 && + options.map((option: OptionProps) => ( + + ))} + + ); +} + +export interface Item { + widgetId: string; + id: string; + index: number; + isVisible?: boolean; + isDisabled?: boolean; + label?: string; + value: string; + alignIndicator?: Alignment; + defaultChecked?: boolean; + checked?: boolean; +} + +export interface SwitchGroupComponentProps { + alignment: Alignment; + disabled?: boolean; + inline?: boolean; + options: OptionProps[]; + onChange: (value: string) => React.FormEventHandler; + required?: boolean; + rowSpace: number; + selected: string[]; + valid?: boolean; +} + +export default SwitchGroupComponent; diff --git a/app/client/src/widgets/SwitchGroupWidget/icon.svg b/app/client/src/widgets/SwitchGroupWidget/icon.svg new file mode 100755 index 0000000000..fcaa891f23 --- /dev/null +++ b/app/client/src/widgets/SwitchGroupWidget/icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/client/src/widgets/SwitchGroupWidget/index.ts b/app/client/src/widgets/SwitchGroupWidget/index.ts new file mode 100644 index 0000000000..964f38f26f --- /dev/null +++ b/app/client/src/widgets/SwitchGroupWidget/index.ts @@ -0,0 +1,38 @@ +import { Alignment } from "@blueprintjs/core"; + +import Widget from "./widget"; +import IconSVG from "./icon.svg"; +import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; + +export const CONFIG = { + type: Widget.getWidgetType(), + name: "Switch Group", // The display name which will be made in uppercase and show in the widgets panel ( can have spaces ) + iconSVG: IconSVG, + needsMeta: true, // Defines if this widget adds any meta properties + isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets + defaults: { + widgetName: "SwitchGroup", + rows: 1.5 * GRID_DENSITY_MIGRATION_V1, + columns: 4.5 * GRID_DENSITY_MIGRATION_V1, + options: [ + { label: "Blue", value: "BLUE" }, + { label: "Green", value: "GREEN" }, + { label: "Red", value: "RED" }, + ], + defaultSelectedValues: ["BLUE"], + isDisabled: false, + isRequired: false, + isInline: true, + isVisible: true, + alignment: Alignment.LEFT, + version: 1, + }, + properties: { + derived: Widget.getDerivedPropertiesMap(), + default: Widget.getDefaultPropertiesMap(), + meta: Widget.getMetaPropertiesMap(), + config: Widget.getPropertyPaneConfig(), + }, +}; + +export default Widget; diff --git a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx new file mode 100644 index 0000000000..e8617fbc8f --- /dev/null +++ b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx @@ -0,0 +1,254 @@ +import React from "react"; +import { Alignment } from "@blueprintjs/core"; + +import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; +import { DerivedPropertiesMap } from "utils/WidgetFactory"; +import { ValidationTypes } from "constants/WidgetValidation"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; + +import SwitchGroupComponent, { OptionProps } from "../component"; + +class SwitchGroupWidget extends BaseWidget< + SwitchGroupWidgetProps, + WidgetState +> { + static getPropertyPaneConfig() { + return [ + { + sectionName: "General", + children: [ + { + helpText: + "Displays a list of options for a user to select. Values must be unique", + propertyName: "options", + label: "Options", + controlType: "INPUT_TEXT", + placeholderText: '[{ "label": "Option1", "value": "Option2" }]', + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.ARRAY, + params: { + children: { + type: ValidationTypes.OBJECT, + params: { + allowedKeys: [ + { + name: "label", + type: ValidationTypes.TEXT, + params: { + default: "", + required: true, + unique: true, + }, + }, + { + name: "value", + type: ValidationTypes.TEXT, + params: { + default: "", + required: true, + unique: true, + }, + }, + ], + }, + }, + }, + }, + evaluationSubstitutionType: + EvaluationSubstitutionType.SMART_SUBSTITUTE, + }, + { + helpText: + "Selects values of the options checked by default. Enter comma separated values for multiple selected", + propertyName: "defaultSelectedValues", + label: "Default Selected Values", + placeholderText: "Enter option values", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.ARRAY, + params: { + default: [], + children: { + type: ValidationTypes.TEXT, + }, + strict: true, + }, + }, + }, + { + propertyName: "isInline", + helpText: + "Whether switches are to be displayed inline horizontally", + label: "Inline", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, + { + propertyName: "isRequired", + label: "Required", + helpText: "Makes input to the widget mandatory", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, + { + 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 }, + }, + { + propertyName: "alignment", + helpText: "Sets the alignment of the widget", + label: "Alignment", + controlType: "DROP_DOWN", + isBindProperty: true, + isTriggerProperty: false, + options: [ + { + label: "Left", + value: Alignment.LEFT, + }, + { + label: "Right", + value: Alignment.RIGHT, + }, + ], + }, + ], + }, + { + sectionName: "Actions", + children: [ + { + helpText: + "Triggers an action when a switch state inside the group is changed", + propertyName: "onSelectionChange", + label: "onSelectionChange", + controlType: "ACTION_SELECTOR", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + ], + }, + ]; + } + + static getDefaultPropertiesMap(): Record { + return { + selectedValuesArray: "defaultSelectedValues", + }; + } + + static getMetaPropertiesMap(): Record { + return { + selectedValuesArray: undefined, + }; + } + + static getDerivedPropertiesMap(): DerivedPropertiesMap { + return { + isValid: `{{ this.isRequired ? !!this.selectedValues.length : true }}`, + selectedValues: `{{ + this.selectedValuesArray.filter( + selectedValue => this.options.map(option => option.value).includes(selectedValue) + ) + }}`, + }; + } + + static getWidgetType(): string { + return "SWITCH_GROUP_WIDGET"; + } + + getPageView() { + const { + alignment, + isDisabled, + isInline, + isRequired, + isValid, + options, + parentRowSpace, + selectedValues, + } = this.props; + + return ( + + ); + } + + private handleSwitchStateChange = (value: string) => { + return (event: React.FormEvent) => { + let { selectedValuesArray } = this.props; + const isChecked = (event.target as HTMLInputElement).checked; + if (isChecked) { + selectedValuesArray = [...selectedValuesArray, value]; + } else { + selectedValuesArray = selectedValuesArray.filter( + (item: string) => item !== value, + ); + } + + this.props.updateWidgetMetaProperty( + "selectedValuesArray", + selectedValuesArray, + { + triggerPropertyName: "onSelectionChange", + dynamicString: this.props.onSelectionChange, + event: { + type: EventType.ON_SWITCH_GROUP_SELECTION_CHANGE, + }, + }, + ); + }; + }; +} + +export interface SwitchGroupWidgetProps extends WidgetProps { + options: OptionProps[]; + defaultSelectedValues: string[]; + isInline: boolean; + isRequired?: boolean; + isValid?: boolean; + isDisabled?: boolean; + alignment: Alignment; + onSelectionChange?: boolean; +} + +export default SwitchGroupWidget; diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 4b1dae5729..5d8da2320a 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -16019,7 +16019,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" -tslib@^2.1.0: +tslib@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==