feat: Switch Group widget (#7590)

* Feat: Switch Group Widget

-- The first MVP of the widget

* feat: Switch Group Widget

-- Follow the same config and implementation as the other group widgets

* feat: Switch Group Widget

-- Elaborate the help text for defaultSelectedValues

* feat: Switch Group Widget

-- Add a widget icon

* feat: Switch Group Widget

-- Remove unnecessary property control at ItemsControl.tsx
-- Refactor some code snippets for higher code quality
-- Add basic cypress test cases

* fix: icon for the widget optimised & replaced

* feat: Switch Group Widget

-- Add a unit test for defaultSelectedValuesValidation

* feat: Switch Group Widget

-- Make the validation type for defaultSelectedValues property to ValidationTypes.ARRAY
-- Remove original validation function and its unit test

* feat: Switch Group Widget

-- Fix on typo
-- Fix on formatting issue
-- Change the help text for isRequired property

* feat: Switch Group Widget

-- Revert help text for isRequired property to the original one

* feat: Switch Group Widget

-- Set strict property to true on defaultSelectedValues

* feat: Switch group widget

-- Refactor utility function, getCamelCaseString
-- Add the corresponding test case

* feat: Switch group widget

-- Implement options property as a plain JS field
-- Reimplement update logic for selectedValues when options changes
-- Add a new utility function for checking equality of object arrays
-- Add a unit test for the above function
-- Rewrite the corresponding Cypress test cases

* feat: Switch Group Widget

-- Remove isArrayEqual utility function and directly use functions from lodash

* feat: Swtich Group Widget

-- Make selectedValues as a derived property

* feat: Switch Group Widget

-- Replace the widget icon

* feat: Switch Group Widget

-- Rewrite a test case for onSelectionChange property

* feat: Switch Group Widget

-- Remove redundant calls for openPropertyPane

* feat: Switch Group Widget

-- Remove closePropertyPane call from afterEach hook

* feat: Switch Group Widget

-- Change the selector for every switch element in onSelectionChange test case

* feat: Switch Group Widget

-- Fix on failed Cypress test case, adding closePropertyPane command to onSelectionChange

* feat: Switch Group Widget

-- Remove template literal from a selector

* feat: Switch Group Widget

-- Make click on onSelectionChange test case forced

* feat: Switch Group Widget

-- Fix on crash issue when editing on Options property

* feat: Switch Group Widget

-- Add the widget icon to show in entity explorer

* feat: Switch Group Widget

-- Fix on blue color on mouse down
-- Add a new property for alignment

Co-authored-by: somangshu <somangshu.goswami1508@gmail.com>
This commit is contained in:
Paul Li 2021-12-09 20:02:47 +08:00 committed by GitHub
parent 3e51c1173c
commit 54579a4e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 722 additions and 5 deletions

View File

@ -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
}
]
}
}

View File

@ -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",

View File

@ -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,
);
});
});

View File

@ -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",

View File

@ -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",

View File

@ -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",

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">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 6C11 4.89543 10.1046 4 9 4H6C4.89543 4 4 4.89543 4 6C4 7.10457 4.89543 8 6 8H9C10.1046 8 11 7.10457 11 6ZM9 7C9.55228 7 10 6.55228 10 6C10 5.44772 9.55228 5 9 5C8.44772 5 8 5.44772 8 6C8 6.55228 8.44772 7 9 7Z" fill="#4B4848"/>
<path d="M12.9998 5.80005C12.9998 5.38584 13.3355 5.05005 13.7498 5.05005L19.2498 5.05005C19.664 5.05005 19.9998 5.38584 19.9998 5.80005C19.9998 6.21426 19.664 6.55005 19.2498 6.55005L13.7498 6.55005C13.3355 6.55005 12.9998 6.21426 12.9998 5.80005Z" fill="#4B4848"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 12C11 10.8954 10.1046 10 9 10H6C4.89543 10 4 10.8954 4 12C4 13.1046 4.89543 14 6 14H9C10.1046 14 11 13.1046 11 12ZM9 13C9.55228 13 10 12.5523 10 12C10 11.4477 9.55228 11 9 11C8.44772 11 8 11.4477 8 12C8 12.5523 8.44772 13 9 13Z" fill="#4B4848"/>
<path d="M12.9998 11.8C12.9998 11.3858 13.3355 11.05 13.7498 11.05L19.2498 11.05C19.664 11.05 19.9998 11.3858 19.9998 11.8C19.9998 12.2143 19.664 12.55 19.2498 12.55L13.7498 12.55C13.3355 12.55 12.9998 12.2143 12.9998 11.8Z" fill="#4B4848"/>
<circle cx="6" cy="18" r="1" fill="#4B4848"/>
<path d="M4.25 18C4.25 17.0335 5.0335 16.25 6 16.25H9C9.9665 16.25 10.75 17.0335 10.75 18C10.75 18.9665 9.9665 19.75 9 19.75H6C5.0335 19.75 4.25 18.9665 4.25 18Z" stroke="#4B4848" stroke-width="0.5"/>
<path d="M13 17.8C13 17.3858 13.3358 17.05 13.75 17.05L19.25 17.05C19.6642 17.05 20 17.3858 20 17.8C20 18.2143 19.6642 18.55 19.25 18.55L13.75 18.55C13.3358 18.55 13 18.2143 13 17.8Z" fill="#4B4848"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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 {

View File

@ -167,6 +167,10 @@ export const HelpMap: Record<string, { path: string; searchKey: string }> = {
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";

View File

@ -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: {
<ButtonGroupIcon />
</StyledIconWrapper>
),
SWITCH_GROUP_WIDGET: (props: IconProps) => (
<StyledIconWrapper {...props}>
<SwitchGroupIcon />
</StyledIconWrapper>
),
};
export type WidgetIcon = typeof WidgetIcons[keyof typeof WidgetIcons];

View File

@ -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]);
});
});
});

View File

@ -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 )

View File

@ -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");

View File

@ -422,6 +422,12 @@ export const entityDefinitions: Record<string, unknown> = {
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 = {

View File

@ -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)<ThemeProp & StyledSwitchProps>`
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 (
<SwitchGroupContainer
alignment={alignment}
inline={inline}
optionCount={(options || []).length}
valid={valid}
>
{Array.isArray(options) &&
options.length > 0 &&
options.map((option: OptionProps) => (
<StyledSwitch
alignIndicator={alignment}
checked={selected.includes(option.value)}
disabled={disabled}
inline={inline}
key={option.value}
label={option.label}
onChange={onChange(option.value)}
optionCount={options.length}
rowSpace={rowSpace}
/>
))}
</SwitchGroupContainer>
);
}
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<HTMLInputElement>;
required?: boolean;
rowSpace: number;
selected: string[];
valid?: boolean;
}
export default SwitchGroupComponent;

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">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 6C11 4.89543 10.1046 4 9 4H6C4.89543 4 4 4.89543 4 6C4 7.10457 4.89543 8 6 8H9C10.1046 8 11 7.10457 11 6ZM9 7C9.55228 7 10 6.55228 10 6C10 5.44772 9.55228 5 9 5C8.44772 5 8 5.44772 8 6C8 6.55228 8.44772 7 9 7Z" fill="#4B4848"/>
<path d="M12.9998 5.80005C12.9998 5.38584 13.3355 5.05005 13.7498 5.05005L19.2498 5.05005C19.664 5.05005 19.9998 5.38584 19.9998 5.80005C19.9998 6.21426 19.664 6.55005 19.2498 6.55005L13.7498 6.55005C13.3355 6.55005 12.9998 6.21426 12.9998 5.80005Z" fill="#4B4848"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 12C11 10.8954 10.1046 10 9 10H6C4.89543 10 4 10.8954 4 12C4 13.1046 4.89543 14 6 14H9C10.1046 14 11 13.1046 11 12ZM9 13C9.55228 13 10 12.5523 10 12C10 11.4477 9.55228 11 9 11C8.44772 11 8 11.4477 8 12C8 12.5523 8.44772 13 9 13Z" fill="#4B4848"/>
<path d="M12.9998 11.8C12.9998 11.3858 13.3355 11.05 13.7498 11.05L19.2498 11.05C19.664 11.05 19.9998 11.3858 19.9998 11.8C19.9998 12.2143 19.664 12.55 19.2498 12.55L13.7498 12.55C13.3355 12.55 12.9998 12.2143 12.9998 11.8Z" fill="#4B4848"/>
<circle cx="6" cy="18" r="1" fill="#4B4848"/>
<path d="M4.25 18C4.25 17.0335 5.0335 16.25 6 16.25H9C9.9665 16.25 10.75 17.0335 10.75 18C10.75 18.9665 9.9665 19.75 9 19.75H6C5.0335 19.75 4.25 18.9665 4.25 18Z" stroke="#4B4848" stroke-width="0.5"/>
<path d="M13 17.8C13 17.3858 13.3358 17.05 13.75 17.05L19.25 17.05C19.6642 17.05 20 17.3858 20 17.8C20 18.2143 19.6642 18.55 19.25 18.55L13.75 18.55C13.3358 18.55 13 18.2143 13 17.8Z" fill="#4B4848"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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;

View File

@ -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<string, string> {
return {
selectedValuesArray: "defaultSelectedValues",
};
}
static getMetaPropertiesMap(): Record<string, any> {
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 (
<SwitchGroupComponent
alignment={alignment}
disabled={isDisabled}
inline={isInline}
onChange={this.handleSwitchStateChange}
options={options}
required={isRequired}
rowSpace={parentRowSpace}
selected={selectedValues}
valid={isValid}
/>
);
}
private handleSwitchStateChange = (value: string) => {
return (event: React.FormEvent<HTMLElement>) => {
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;

View File

@ -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==