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:
parent
3e51c1173c
commit
54579a4e6c
65
app/client/cypress/fixtures/SwitchGroupWidgetDsl.json
Normal file
65
app/client/cypress/fixtures/SwitchGroupWidgetDsl.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
9
app/client/src/assets/icons/widget/switch-group.svg
Executable file
9
app/client/src/assets/icons/widget/switch-group.svg
Executable 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 |
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
13
app/client/src/utils/AppsmithUtils.test.ts
Normal file
13
app/client/src/utils/AppsmithUtils.test.ts
Normal 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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
143
app/client/src/widgets/SwitchGroupWidget/component/index.tsx
Normal file
143
app/client/src/widgets/SwitchGroupWidget/component/index.tsx
Normal 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;
|
||||
9
app/client/src/widgets/SwitchGroupWidget/icon.svg
Executable file
9
app/client/src/widgets/SwitchGroupWidget/icon.svg
Executable 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 |
38
app/client/src/widgets/SwitchGroupWidget/index.ts
Normal file
38
app/client/src/widgets/SwitchGroupWidget/index.ts
Normal 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;
|
||||
254
app/client/src/widgets/SwitchGroupWidget/widget/index.tsx
Normal file
254
app/client/src/widgets/SwitchGroupWidget/widget/index.tsx
Normal 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;
|
||||
|
|
@ -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==
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user