feat: Tree Select widget (#6271)

* feat: Tree Select

* feat: styling multiselect

* fix: selected values

* fix: remove console statement

* fix: popup position

* fix: selection types

* fix: Form validation using TreeSelect

* feat: Add Label to TreeSelect

* fix: styling

* fix: Dropdown search

* fix: Add Entity Definitions

* fix: Entity Definition

* Feat: Add clear icon

* fix: validation

* fix: options validation

* fix: Styling issues

* fix: build error

* Fix: Separate Tree Select widget

* fix: issues and add validation

* fix: Options Validation

* fix: issues with build

* fix: yarn

* fix: changes

* fix

* Fix: select component

* fix: PR issues

* fix: merge conflicts

* fix: issues

* fix: all issues

* test: added test

* fix: failing test
This commit is contained in:
Tolulope Adetula 2021-09-17 10:08:35 +01:00 committed by GitHub
parent 146f536190
commit 15b26f823e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 3590 additions and 39 deletions

View File

@ -0,0 +1,128 @@
{
"dsl":{
"widgetName":"MainContainer",
"backgroundColor":"none",
"rightColumn":1034.3999999999999,
"snapColumns":64,
"detachFromLayout":true,
"widgetId":"0",
"topRow":0,
"bottomRow":1990,
"containerStyle":"none",
"snapRows":125,
"parentRowSpace":1,
"type":"CANVAS_WIDGET",
"canExtend":true,
"version":38,
"minHeight":2000,
"parentColumnSpace":1,
"dynamicTriggerPathList":[],
"dynamicBindingPathList":[],
"leftColumn":0,
"children":[
{
"widgetName":"MultiSelectTree1",
"displayName":"Multi Select Tree",
"iconSVG":"/static/media/icon.f264210c.svg",
"labelText":"Label",
"topRow":38,
"bottomRow":44.88,
"parentRowSpace":10,
"type":"MULTI_SELECT_TREE_WIDGET",
"hideCard":false,
"mode":"SHOW_ALL",
"defaultOptionValue":[],
"parentColumnSpace":15.974999999999998,
"leftColumn":18,
"options":[
{
"label":"Blue",
"value":"BLUE",
"children":[
{
"label":"Dark Blue",
"value":"DARK BLUE"
},
{
"label":"Light Blue",
"value":"LIGHT BLUE"
}
]
},
{
"label":"Green",
"value":"GREEN"
},
{
"label":"Red",
"value":"RED"
}
],
"placeholderText":"select option(s)",
"isDisabled":false,
"key":"1zu067mn51",
"isRequired":false,
"rightColumn":34,
"widgetId":"zvm3vcs5gp",
"isVisible":true,
"version":1,
"expandAll":false,
"parentId":"0",
"renderMode":"CANVAS",
"isLoading":false,
"allowClear":false
},
{
"widgetName":"SingleSelectTree1",
"displayName":"Single Select Tree",
"iconSVG":"/static/media/icon.f815ebe3.svg",
"labelText":"Label",
"topRow":58,
"bottomRow":64.8,
"parentRowSpace":10,
"type":"SINGLE_SELECT_TREE_WIDGET",
"hideCard":false,
"defaultOptionValue":"BLUE",
"parentColumnSpace":15.974999999999998,
"leftColumn":17,
"options":[
{
"label":"Blue",
"value":"BLUE",
"children":[
{
"label":"Dark Blue",
"value":"DARK BLUE"
},
{
"label":"Light Blue",
"value":"LIGHT BLUE"
}
]
},
{
"label":"Green",
"value":"GREEN"
},
{
"label":"Red",
"value":"RED"
}
],
"placeholderText":"select option",
"isDisabled":false,
"key":"cul8w70bzs",
"isRequired":false,
"rightColumn":33,
"widgetId":"0zloh94nd4",
"isVisible":true,
"version":1,
"expandAll":false,
"parentId":"0",
"renderMode":"CANVAS",
"isLoading":false,
"allowClear":false
}
]
}
}

View File

@ -0,0 +1,28 @@
const dsl = require("../../../../fixtures/TreeSelectDsl.json");
describe("Tree Select Widget", function() {
beforeEach(() => {
cy.addDsl(dsl);
});
it("Open Existing MultiSelectTree from created Widgets list", () => {
cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Widgets)").click({
multiple: true,
});
cy.get(
".bp3-icon-caret-right ~ .t--entity-name:contains(MultiSelectTree1)",
).click({
multiple: true,
});
});
it("Open Existing SingleSelectTree from created Widgets list", () => {
cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Widgets)").click({
multiple: true,
});
cy.get(
".bp3-icon-caret-right ~ .t--entity-name:contains(SingleSelectTree1)",
).click({
multiple: true,
});
});
});

View File

@ -0,0 +1,47 @@
const dsl = require("../../../../fixtures/TreeSelectDsl.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const commonlocators = require("../../../../locators/commonlocators.json");
describe("MultiSelectTree Widget Functionality", function() {
before(() => {
cy.addDsl(dsl);
});
it("Selects value with enter in default value", () => {
cy.openPropertyPane("multiselecttreewidget");
cy.testJsontext("defaultvalue", "RED\n");
cy.get(formWidgetsPage.multiselecttreeWidget)
.find(".rc-tree-select-selection-item-content")
.first()
.should("have.text", "Red");
});
it(" To Validate Options", function() {
cy.get(formWidgetsPage.treeSelectInput)
.first()
.click({ force: true });
cy.get(formWidgetsPage.treeSelectInput)
.first()
.type("light");
cy.treeSelectDropdown("Light Blue");
});
it("To Unchecked Visible Widget", function() {
cy.togglebarDisable(commonlocators.visibleCheckbox);
cy.PublishtheApp();
cy.get(
publish.multiselecttreewidget + " " + ".rc-tree-select-multiple",
).should("not.exist");
cy.get(publish.backToEditor).click();
});
it(" To Check Visible Widget", function() {
cy.openPropertyPane("multiselecttreewidget");
cy.togglebar(commonlocators.visibleCheckbox);
cy.PublishtheApp();
cy.get(
publish.multiselecttreewidget + " " + ".rc-tree-select-multiple",
).should("be.visible");
cy.get(publish.backToEditor).click();
});
});
afterEach(() => {
// put your clean up code if any
});

View File

@ -0,0 +1,47 @@
const dsl = require("../../../../fixtures/TreeSelectDsl.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const commonlocators = require("../../../../locators/commonlocators.json");
describe("MultiSelectTree Widget Functionality", function() {
before(() => {
cy.addDsl(dsl);
});
it("Selects value with enter in default value", () => {
cy.openPropertyPane("singleselecttreewidget");
cy.testJsontext("defaultvalue", "RED\n");
cy.get(formWidgetsPage.singleselecttreeWidget)
.find(".rc-tree-select-selection-item")
.first()
.should("have.text", "Red");
});
it(" To Validate Options", function() {
cy.get(formWidgetsPage.treeSelectInput)
.last()
.click({ force: true });
cy.get(formWidgetsPage.treeSelectInput)
.last()
.type("light");
cy.treeSelectDropdown("Light Blue");
});
it("To Unchecked Visible Widget", function() {
cy.togglebarDisable(commonlocators.visibleCheckbox);
cy.PublishtheApp();
cy.get(
publish.singleselecttreewidget + " " + ".rc-tree-select-single",
).should("not.exist");
cy.get(publish.backToEditor).click();
});
it(" To Check Visible Widget", function() {
cy.openPropertyPane("singleselecttreewidget");
cy.togglebar(commonlocators.visibleCheckbox);
cy.PublishtheApp();
cy.get(
publish.singleselecttreewidget + " " + ".rc-tree-select-single",
).should("be.visible");
cy.get(publish.backToEditor).click();
});
});
afterEach(() => {
// put your clean up code if any
});

View File

@ -2,6 +2,8 @@
"checkboxWidget": ".t--draggable-checkboxwidget",
"dropdownWidget": ".t--draggable-dropdownwidget",
"multiselectWidget": ".t--draggable-multiselectwidget",
"multiselecttreeWidget": ".t--draggable-multiselecttreewidget",
"singleselecttreeWidget": ".t--draggable-singleselecttreewidget",
"dropdownSelectionType": ".t--property-control-selectiontype .bp3-popover-target",
"radioWidget": ".t--draggable-radiogroupwidget",
"checkboxGroupWidget": ".t--draggable-checkboxgroupwidget",
@ -22,6 +24,7 @@
"labelvalue": ".t--draggable-dropdownwidget label",
"dropdownInput": ".bp3-tag-input-values",
"mulitiselectInput": ".rc-select-selection-search-input",
"treeSelectInput": ".rc-tree-select-selection-search-input",
"labelradio": ".t--draggable-radiogroupwidget label",
"labelCheckboxGroup": ".t--draggable-checkboxgroupwidget label",
"deleteradiovalue": ".t--property-control-options mask",

View File

@ -13,6 +13,8 @@
"imageWidget": ".t--widget-imagewidget",
"dropdownWidget": ".t--widget-dropdownwidget",
"multiselectwidget": ".t--widget-multiselectwidget",
"multiselecttreewidget": ".t--widget-multiselecttreewidget",
"singleselecttreewidget": ".t--widget-singleselecttreewidget",
"tabWidget": ".t--widget-tabswidget",
"chartWidget": ".t--widget-chartwidget",
"horizontalTab": ".t--widget-chartwidget g[class*='-scrollContainer'] rect",

View File

@ -1805,6 +1805,14 @@ Cypress.Commands.add("dropdownMultiSelectDynamic", (text) => {
.click({ force: true })
.should("have.text", text);
});
Cypress.Commands.add("treeSelectDropdown", (text) => {
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(".tree-select-dropdown")
.contains(text)
.click({ force: true })
.should("have.text", text);
});
Cypress.Commands.add("dropdownDynamicUpdated", (text) => {
// eslint-disable-next-line cypress/no-unnecessary-waiting

View File

@ -103,6 +103,7 @@
"prismjs": "^1.24.0",
"rc-pagination": "^3.1.3",
"rc-select": "^12.1.10",
"rc-tree-select": "^4.4.0-alpha.2",
"re-reselect": "^3.4.0",
"react": "^16.12.0",
"react-base-table": "^1.9.1",

View File

@ -0,0 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="0.75" y="0.75" width="18.5" height="6.5" stroke="#EAEAEA" stroke-width="1.5"/><path d="M7.99985 10.05L14.9998 10.05V11.55L7.99985 11.55L7.99985 10.05Z" fill="#EAEAEA"/><path d="M7.99985 14.75H13.9998V16.25L7.99985 16.25L7.99985 14.75Z" fill="#EAEAEA"/><path d="M5.99985 10.5L2.99985 10.5L4.49985 12L5.99985 10.5Z" fill="#EAEAEA"/><path d="M6 15L3 15L4.5 16.5L6 15Z" fill="#EAEAEA"/><path fill-rule="evenodd" clip-rule="evenodd" d="M0 6.5V20H18V6.5H0ZM1.5 18.5V8H16.5V18.5H1.5Z" fill="#EAEAEA"/><path d="M3 3.25H11V4.75H3V3.25Z" fill="#EAEAEA"/><path d="M12 3.25H17V4.75H12V3.25Z" fill="#EAEAEA"/></svg>

After

Width:  |  Height:  |  Size: 707 B

View File

@ -0,0 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="0.75" y="0.75" width="18.5" height="6.5" stroke="#EAEAEA" stroke-width="1.5"/><path d="M7.99985 10.05L14.9998 10.05V11.55L7.99985 11.55L7.99985 10.05Z" fill="#EAEAEA"/><path d="M7.99985 14.75H13.9998V16.25L7.99985 16.25L7.99985 14.75Z" fill="#EAEAEA"/><path d="M5.99985 10.5L2.99985 10.5L4.49985 12L5.99985 10.5Z" fill="#EAEAEA"/><path d="M6 15L3 15L4.5 16.5L6 15Z" fill="#EAEAEA"/><path fill-rule="evenodd" clip-rule="evenodd" d="M0 6.5V20H18V6.5H0ZM1.5 18.5V8H16.5V18.5H1.5Z" fill="#EAEAEA"/><path d="M3 3.19995H17V4.69995H3V3.19995Z" fill="#EAEAEA"/></svg>

After

Width:  |  Height:  |  Size: 664 B

View File

@ -139,6 +139,14 @@ export const HelpMap: Record<string, { path: string; searchKey: string }> = {
path: "/widget-reference/menu-button",
searchKey: "Menu Button",
},
TREE_MULTI_SELECT_WIDGET: {
path: "/widget-reference/tree-multi-select",
searchKey: "Tree Multi Select",
},
TREE_SINGLE_SELECT_WIDGET: {
path: "/widget-reference/tree-single-select",
searchKey: "Tree Single Select",
},
ICON_BUTTON_WIDGET: {
path: "/widget-reference/icon-button",
searchKey: "Icon Button",

View File

@ -10,6 +10,7 @@ export enum ValidationTypes {
OBJECT = "OBJECT",
ARRAY = "ARRAY",
OBJECT_ARRAY = "OBJECT_ARRAY",
NESTED_OBJECT_ARRAY = "NESTED_OBJECT_ARRAY",
DATE_ISO_STRING = "DATE_ISO_STRING",
IMAGE_URL = "IMAGE_URL",
FUNCTION = "FUNCTION",

View File

@ -27,6 +27,8 @@ import { ReactComponent as RatingIcon } from "assets/icons/widget/rating.svg";
import { ReactComponent as EmbedIcon } from "assets/icons/widget/embed.svg";
import { ReactComponent as DividerIcon } from "assets/icons/widget/divider.svg";
import { ReactComponent as MenuButtonIcon } from "assets/icons/widget/menu-button.svg";
import { ReactComponent as MultiTreeSelectIcon } from "assets/icons/widget/multi-tree-select.svg";
import { ReactComponent as SingleTreeSelectIcon } from "assets/icons/widget/single-tree-select.svg";
import { ReactComponent as IconButtonIcon } from "assets/icons/widget/icon-button.svg";
import { ReactComponent as StatboxIcon } from "assets/icons/widget/statbox.svg";
import { ReactComponent as CheckboxGroupIcon } from "assets/icons/widget/checkbox-group.svg";
@ -177,6 +179,16 @@ export const WidgetIcons: {
<MenuButtonIcon />
</IconWrapper>
),
TREE_SINGLE_SELECT_WIDGET: (props: IconProps) => (
<IconWrapper {...props}>
<SingleTreeSelectIcon />
</IconWrapper>
),
TREE_MULTI_SELECT_WIDGET: (props: IconProps) => (
<IconWrapper {...props}>
<MultiTreeSelectIcon />
</IconWrapper>
),
ICON_BUTTON_WIDGET: (props: IconProps) => (
<IconWrapper {...props}>
<IconButtonIcon />

View File

@ -96,6 +96,12 @@ import AudioRecorderWidget, {
} from "widgets/AudioRecorderWidget";
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";
export const registerWidgets = () => {
const start = performance.now();
@ -135,5 +141,8 @@ export const registerWidgets = () => {
registerWidget(FilePickerWidgetV2, FILEPICKER_WIDGET_V2_CONFIG);
registerWidget(StatboxWidget, STATBOX_WIDGET_CONFIG);
registerWidget(AudioRecorderWidget, AUDIO_RECORDER_WIDGET_CONFIG);
registerWidget(MultiSelectTreeWidget, MULTI_SELECT_TREE_WIDGET_CONFIG);
registerWidget(SingleSelectTreeWidget, SINGLE_SELECT_TREE_WIDGET_CONFIG);
log.debug("Widget registration took: ", performance.now() - start, "ms");
};

View File

@ -314,6 +314,45 @@ export const entityDefinitions: Record<string, unknown> = {
isVisible: isVisible,
label: "string",
},
//TODO: fix this after development
SINGLE_SELECT_TREE_WIDGET: {
"!doc":
"Single Select Tree is used to capture user input from a specified list of permitted inputs/Nested Inputs.",
"!url": "https://docs.appsmith.com/widget-reference/treeselect",
isVisible: isVisible,
selectedOptionValue: {
"!type": "string",
"!doc": "The value selected in a tree select dropdown",
"!url": "https://docs.appsmith.com/widget-reference/treeselect",
},
selectedOptionLabel: {
"!type": "string",
"!doc": "The selected option label in a tree select dropdown",
"!url": "https://docs.appsmith.com/widget-reference/treeselect",
},
isDisabled: "bool",
isValid: "bool",
options: "[dropdownOption]",
},
MULTI_SELECT_TREE_WIDGET: {
"!doc":
"Multi Select Tree is used to capture user inputs from a specified list of permitted inputs/Nested Inputs. A Tree Select can capture a single choice as well as multiple choices",
"!url": "https://docs.appsmith.com/widget-reference/treeselect",
isVisible: isVisible,
selectedOptionValues: {
"!type": "[string]",
"!doc": "The array of values selected in a tree select dropdown",
"!url": "https://docs.appsmith.com/widget-reference/treeselect",
},
selectedOptionLabels: {
"!type": "[string]",
"!doc": "The array of selected option labels in a tree select dropdown",
"!url": "https://docs.appsmith.com/widget-reference/treeselect",
},
isDisabled: "bool",
isValid: "bool",
options: "[dropdownOption]",
},
ICON_BUTTON_WIDGET: {
"!doc":
"Icon button widget is just an icon, along with all other button properties.",

View File

@ -114,6 +114,7 @@ export function getExpectedValue(
autocompleteDataType: AutocompleteDataType.OBJECT,
};
case ValidationTypes.ARRAY:
case ValidationTypes.NESTED_OBJECT_ARRAY:
if (config.params?.allowedValues) {
const allowed = config.params?.allowedValues.join("' | '");
return {

View File

@ -272,7 +272,6 @@ const BaseButton = styled(Button)<ThemeProp & BaseStyleProps>`
}
`}
border-radius: ${({ borderRadius }) =>
borderRadius === ButtonBorderRadiusTypes.ROUNDED ? "5px" : 0};

View File

@ -0,0 +1,891 @@
import React from "react";
import { Checkbox, Classes, Label } from "@blueprintjs/core";
import styled, { keyframes } from "styled-components";
import { Colors } from "constants/Colors";
import { createGlobalStyle } from "constants/DefaultTheme";
import {
FontStyleTypes,
TextSize,
TEXT_SIZES,
} from "constants/WidgetConstants";
export const menuItemSelectedIcon = (props: { isSelected: boolean }) => {
return <StyledCheckbox checked={props.isSelected} />;
};
export const TextLabelWrapper = styled.div<{
compactMode: boolean;
}>`
${(props) =>
props.compactMode ? "&&& {margin-right: 5px;}" : "width: 100%;"}
display: flex;
`;
export const StyledLabel = styled(Label)<{
$compactMode: boolean;
$labelText?: string;
$labelTextColor?: string;
$labelTextSize?: TextSize;
$labelStyle?: string;
}>`
overflow-y: hidden;
text-overflow: ellipsis;
width: ${(props) => (props.$compactMode ? "auto" : "100%")};
text-align: left;
color: ${(props) => props.$labelTextColor || "inherit"};
font-size: ${(props) =>
props.$labelTextSize ? TEXT_SIZES[props.$labelTextSize] : "14px"};
font-weight: ${(props) =>
props?.$labelStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"};
font-style: ${(props) =>
props?.$labelStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""};
`;
const rcSelectDropdownSlideUpIn = keyframes`
0% {
opacity: 0;
transform-origin: 0% 0%;
}
100% {
opacity: 1;
transform-origin: 0% 0%;
}
`;
const rcSelectDropdownSlideUpOut = keyframes`
0% {
opacity: 1;
transform-origin: 0% 0%;
}
100% {
opacity: 0;
transform-origin: 0% 0%;
}
`;
export const DropdownStyles = createGlobalStyle`
.rc-tree-select-dropdown-hidden {
display: none;
}
.rc-tree-select-item-group {
color: #999;
font-weight: bold;
font-size: 80%;
}
.rc-tree-select-item-option {
position: relative;
display: flex;
flex-direction: row-reverse;
.rc-tree-select-item-option-state {
pointer-events: all;
margin-right: 10px;
}
}
.rc-tree-select-item-option-grouped {
padding-left: 24px;
}
.rc-tree-select-item-option-content {
flex: 1 1 0;
}
.rc-tree-select-item-option-active {
background: rgb(233, 250, 243);
}
.rc-tree-select-item-option-selected {
background: rgb(233, 250, 243);
}
.rc-tree-select-item-option-disabled {
color: #999;
}
.rc-tree-select-item-empty {
text-align: center;
color: #999;
}
.rc-tree-select-selection__choice-zoom {
transition: all 0s;
}
.rc-tree-select-selection__choice-zoom-appear {
opacity: 0;
}
.rc-tree-select-selection__choice-zoom-appear.rc-tree-select-selection__choice-zoom-appear-active {
opacity: 1;
}
.rc-tree-select-selection__choice-zoom-leave {
opacity: 1;
}
.rc-tree-select-selection__choice-zoom-leave.rc-tree-select-selection__choice-zoom-leave-active {
opacity: 0;
}
.rc-tree-select-dropdown-slide-up-enter {
animation-duration: 0s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-tree-select-dropdown-slide-up-appear {
animation-duration: 0s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-tree-select-dropdown-slide-up-leave {
animation-duration: 0s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-bottomLeft {
animation-name: ${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-bottomLeft {
animation-name:${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-bottomLeft {
animation-name: ${rcSelectDropdownSlideUpOut};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-topLeft {
animation-name: ${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-topLeft {
animation-name: ${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-topLeft {
animation-name: ${rcSelectDropdownSlideUpOut};
animation-play-state: running;
}
.tree-select-dropdown.single-tree-select-dropdown {
.rc-tree-select-tree
.rc-tree-select-tree-treenode.rc-tree-select-tree-treenode-disabled
span.rc-tree-select-tree-iconEle {
cursor: not-allowed;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle {
position: relative;
cursor: pointer;
margin-left: 5px;
top: 0;
left: 0;
display: inline-block;
width: 16px;
height: 16px;
direction: ltr;
background-color: #fff;
border: 1px solid #E8E8E8;
border-radius: 100%;
border-collapse: separate;
transition: all .3s;
:after{
position: absolute;
top: 50%;
left: 52%;
display: table;
width: 10px;
height: 10px;
border: none;
border-top: 0;
border-left: 0;
transform: rotate(
45deg
) scale(0) translate(-50%,-50%);
opacity: 0;
transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
content: " ";
}
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
.rc-tree-select-tree-node-selected
span.rc-tree-select-tree-iconEle {
:after{
width: 10px;
height: 10px;
transform: translate(-50%,-50%) scale(1);
background: rgb(3, 179, 101) !important;
opacity: 1;
content: " ";
border-radius: 100%;
}
}
}
.tree-select-dropdown {
min-height: 100px;
min-width: 250px !important;
position: absolute;
background: #fff;
width: 100%;
border-radius: 0px;
margin-top: 10px;
padding: 12px;
background: white;
box-shadow: 0 0 2px rgb(0 0 0 / 20%) !important;
&&&& .${Classes.ALIGN_LEFT} {
font-size: 16px;
padding-bottom: 10px;
margin-left: 16px ;
.${Classes.CONTROL_INDICATOR} {
margin-right: 20px;
}
}
&&&& .${Classes.CONTROL} .${Classes.CONTROL_INDICATOR} {
background: white;
box-shadow: none;
border-width: 2px;
border-style: solid;
border-color: ${Colors.GEYSER};
&::before {
width: auto;
height: 1em;
}
}
.${Classes.CONTROL} input:checked ~ .${Classes.CONTROL_INDICATOR} {
background: rgb(3, 179, 101) !important;
color: rgb(255, 255, 255);
border-color: rgb(3, 179, 101) !important;
box-shadow: none;
outline: none !important;
}
.rc-tree-select-item {
font-size: 16px;
line-height: 1.5;
padding: 5px 16px;
align-items: center;
cursor: pointer;
}
.rc-tree-select-item-option-state {
.bp3-control.bp3-checkbox {
margin-bottom: 0;
}
}
.rc-tree-select-tree {
margin: 0;
border: 1px solid transparent;
}
.rc-tree-select-tree-focused:not(.rc-tree-select-tree-active-focused) {
border-color: cyan;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode {
margin: 0;
padding: 0;
line-height: 24px;
white-space: nowrap;
list-style: none;
outline: 0;
padding: 0 5px;
height: 34px;
align-items: center;
display: flex !important;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode .draggable {
color: #333;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
-khtml-user-drag: element;
-webkit-user-drag: element;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.drop-container
> .draggable::after {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
box-shadow: inset 0 0 0 2px red;
content: "";
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.drop-container
~ .rc-tree-select-tree-treenode {
border-left: 2px solid chocolate;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode.drop-target {
background-color: yellowgreen;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.drop-target
~ .rc-tree-select-tree-treenode {
border-left: none;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.filter-node
> .rc-tree-select-tree-node-content-wrapper {
color: #182026 !important;
font-weight: bold !important;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode ul {
margin: 0;
padding: 0 0 0 18px;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
.rc-tree-select-tree-node-content-wrapper {
position: relative;
display: inline-flex;
align-items: center;
height: 34px;
margin: 0;
padding: 0;
text-decoration: none;
vertical-align: top;
cursor: pointer;
flex: 1
}
.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner:after {
position: absolute;
display: table;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(
45deg
) scale(1) translate(-50%,-50%);
opacity: 1;
transition: all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
content: " ";
}
.rc-tree-select-tree-checkbox-inner:after {
position: absolute;
top: 50%;
left: 22%;
display: table;
width: 5.71428571px;
height: 9.14285714px;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(
45deg
) scale(0) translate(-50%,-50%);
opacity: 0;
transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
content: " ";
}
.rc-tree-select-tree-checkbox-indeterminate .rc-tree-select-tree-checkbox-inner:after {
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background-color: rgb(3, 179, 101) !important;
border: 0;
transform: translate(-50%,-50%) scale(1);
opacity: 1;
content: " ";
}
.rc-tree-select-tree-checkbox:hover:after, .rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox:after {
visibility: visible;
}
.rc-tree-select-tree-checkbox {
top: initial;
margin: 4px 8px 0 0;
}
.rc-tree-select-tree-checkbox {
box-sizing: border-box;
margin: 0;
padding: 0;
color: #000000d9;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: "tnum";
position: relative;
top: 0;
line-height: 1;
white-space: nowrap;
outline: none;
cursor: pointer;
margin-left: 3px;
}
.rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox-input:focus+.rc-tree-select-tree-checkbox-inner {
border-color: rgb(3, 179, 101) !important;
}
.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner {
border-color: rgb(3, 179, 101) !important;
background: rgb(3, 179, 101) !important;
}
.rc-tree-select-tree-checkbox-inner {
position: relative;
top: 0;
left: 0;
display: inline-block;
width: 16px;
height: 16px;
direction: ltr;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 0px;
border-collapse: separate;
transition: all .3s;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree.select-tree-checkbox-checked {
.rc-tree-select-tree-checkbox-inner {
border-color: rgb(3, 179, 101) !important;
background: rgb(3, 179, 101) !important;
}
}
.single-tree-select-dropdown
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle {
width: 20px;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle {
display: inline-block;
width: 0px;
height: 16px;
margin-right: 2px;
line-height: 16px;
vertical-align: -0.125em;
background-color: transparent;
background-image: none;
background-repeat: no-repeat;
background-attachment: scroll;
border: 0 none;
outline: none;
cursor: pointer;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-icon__customize,
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-checkbox.rc-tree-select-tree-icon__customize,
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle.rc-tree-select-tree-icon__customize {
background-image: none;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-icon_loading {
margin-right: 2px;
vertical-align: top;
background: none;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher-noop {
cursor: auto;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_open {
background-position: -93px -56px;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_close {
background-position: -75px -56px;
}
.rc-tree-select-tree:not(.rc-tree-select-tree-show-line)
.rc-tree-select-tree-treenode
.rc-tree-select-tree-switcher-noop {
background: none;
}
.rc-tree-select-tree.rc-tree-select-tree-show-line
.rc-tree-select-tree-treenode:not(:last-child)
> ul {
background: none;
}
.rc-tree-select-tree.rc-tree-select-tree-show-line
.rc-tree-select-tree-treenode:not(:last-child)
> .rc-tree-select-tree-switcher-noop {
background-position: -56px -18px;
}
.rc-tree-select-tree.rc-tree-select-tree-show-line
.rc-tree-select-tree-treenode:last-child
> .rc-tree-select-tree-switcher-noop {
background-position: -56px -36px;
}
.rc-tree-select-tree-child-tree {
display: none;
}
.rc-tree-select-tree-child-tree-open {
display: block;
}
.rc-tree-select-tree-treenode-disabled
> span:not(.rc-tree-select-tree-switcher),
.rc-tree-select-tree-treenode-disabled > a,
.rc-tree-select-tree-treenode-disabled > a span {
color: #767676;
cursor: not-allowed;
}
.rc-tree-select-tree-treenode-active {
background: rgb(233, 250, 243);
}
.rc-tree-select-tree-treenode:hover {
background: rgb(233, 250, 243);
}
.rc-tree-select-tree-node-selected {
background-color: none;
box-shadow: 0 0 0 0 #ffb951;
opacity: 1;
}
.rc-tree-select-tree-icon__open {
margin-right: 2px;
vertical-align: top;
background-position: -110px -16px;
}
.rc-tree-select-tree-icon__close {
margin-right: 2px;
vertical-align: top;
background-position: -110px 0;
}
.rc-tree-select-tree-icon__docu {
margin-right: 2px;
vertical-align: top;
background-position: -110px -32px;
}
.rc-tree-select-tree-icon__customize {
margin-right: 2px;
vertical-align: top;
}
.rc-tree-select-tree-title {
display: inline-block;
margin-left: 10px;
font-size: 16px !important;
}
.rc-tree-select-tree-indent {
display: inline-block;
vertical-align: bottom;
height: 0;
}
.rc-tree-select-tree-indent-unit {
width: 25px;
display: inline-block;
}
}
`;
export const TreeSelectContainer = styled.div<{
compactMode: boolean;
allowClear: boolean;
}>`
display: flex;
flex-direction: ${(props) => (props.compactMode ? "row" : "column")};
align-items: ${(props) => (props.compactMode ? "center" : "left")};
label.tree-select-label {
margin-bottom: ${(props) => (props.compactMode ? "0px" : "5px")};
margin-right: ${(props) => (props.compactMode ? "10px" : "0px")};
}
.rc-tree-select {
display: inline-block;
font-size: 12px;
width: 100%;
height: 100%;
position: relative;
cursor: pointer;
flex: 1 1;
.rc-tree-select-selection-placeholder {
pointer-events: none;
position: absolute;
top: 50%;
right: 11px;
left: 11px;
transform: translateY(-50%);
transition: all 0.3s;
flex: 1;
overflow: hidden;
color: #bfbfbf;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
font-size: 14px;
}
.rc-tree-select-selection-search-input {
appearance: none;
&::-webkit-search-cancel-button {
display: none;
appearance: none;
}
}
.rc-tree-select-selection-overflow-item-suffix {
position: relative !important;
left: 0px !important;
}
}
.rc-tree-select-disabled {
cursor: not-allowed;
input {
cursor: not-allowed;
}
.rc-tree-select-selector {
opacity: 0.3;
}
}
.rc-tree-select-show-arrow.rc-tree-select-loading {
.rc-tree-select-arrow-icon {
&::after {
box-sizing: border-box;
width: 12px;
height: 12px;
border-radius: 100%;
border: 2px solid #999;
border-top-color: transparent;
border-bottom-color: transparent;
transform: none;
margin-top: 4px;
animation: rcSelectLoadingIcon 0.5s infinite;
}
}
}
.rc-tree-select-single .rc-tree-select-selector {
display: flex;
flex-wrap: wrap;
padding: 1px;
padding-right: 20px;
box-shadow: none;
border: 1px solid rgb(231, 231, 231);
border-radius: 0px;
width: 100%;
transition: border-color 0.15s ease-in-out 0s,
box-shadow 0.15s ease-in-out 0s;
background-color: white;
height: 100%;
.rc-tree-select-selection-search {
width: 100%;
height: 100%;
input {
width: 100%;
appearance: none;
&::-webkit-search-cancel-button {
display: none;
appearance: none;
}
font-family: system-ui;
height: 100%;
border: none;
}
}
.rc-tree-select-selection-item {
pointer-events: none;
position: absolute;
top: 50%;
right: 11px;
left: 11px;
transform: translateY(-50%);
transition: all 0.3s;
flex: 1;
overflow: hidden;
color: #231f20;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
font-size: 14px;
}
}
.rc-tree-select-multiple {
.rc-tree-select-selector {
display: flex;
flex-wrap: wrap;
padding: 1px;
box-shadow: none;
border: 1px solid rgb(231, 231, 231);
border-radius: 0px;
width: 100%;
transition: border-color 0.15s ease-in-out 0s,
box-shadow 0.15s ease-in-out 0s;
background-color: white;
.rc-tree-select-selection-item {
background: none;
border: 1px solid rgb(208, 215, 221);
border-radius: 2px;
margin: 3px 2px;
max-width: 273.926px;
height: 24px;
color: #182026;
overflow-wrap: break-word;
display: inline-flex;
flex-direction: row;
align-items: center;
box-shadow: none;
font-size: 12px;
line-height: 16px;
min-height: 20px;
min-width: 20px;
padding: 2px 6px;
position: relative;
}
.rc-tree-select-selection-item-disabled {
cursor: not-allowed;
opacity: 0.5;
}
.rc-tree-select-selection-overflow {
display: flex;
flex-wrap: wrap;
width: 100%;
align-content: center;
}
.rc-tree-select-selection-overflow-item {
flex: none;
max-width: 100%;
}
.rc-tree-select-selection-search {
position: relative;
max-width: 100%;
margin-bottom: 2px;
height: 100%;
display: flex;
align-items: center;
}
.rc-tree-select-selection-search-input {
padding: 1px;
font-family: system-ui;
width: 5px;
margin: 0px;
display: flex;
height: 26px;
flex: 1 1 0%;
border: none;
outline: none;
width: 100%;
}
.rc-tree-select-selection-search-mirror {
padding: 1px;
font-family: system-ui;
width: 5px;
margin: 0px;
display: flex;
height: 26px;
flex: 1 1 0%;
position: absolute;
z-index: 999;
white-space: nowrap;
position: none;
left: 0;
top: 0;
visibility: hidden;
}
}
}
.rc-tree-select-selection-item-content {
flex-grow: 1;
flex-shrink: 1;
margin-right: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
font-size: 12px;
line-height: 16px;
}
.rc-tree-select-allow-clear {
.rc-tree-select-clear {
position: absolute;
right: 20px;
right: 25px;
top: -1px;
height: 100%;
display: flex;
align-items: center;
z-index: -1;
.rc-tree-select-clear-icon {
font-size: 18px;
font-weight: bold;
}
}
}
.rc-tree-select-allow-clear.rc-tree-select-focused {
.rc-tree-select-clear {
z-index: 1;
}
}
.rc-tree-select-show-arrow.rc-tree-select-multiple {
.rc-tree-select-selector {
padding-right: ${({ allowClear }) => (allowClear ? "40px" : "20px")};
box-shadow: none;
border: 1px solid rgb(231, 231, 231);
border-radius: 0px;
height: inherit;
width: 100%;
transition: border-color 0.15s ease-in-out 0s,
box-shadow 0.15s ease-in-out 0s;
}
}
.rc-tree-select-show-arrow {
.rc-tree-select-arrow {
pointer-events: none;
position: absolute;
right: 5px;
top: 0;
height: 100%;
display: flex;
align-items: center;
}
.rc-tree-select-arrow-icon {
&::after {
content: "";
border: 5px solid transparent;
width: 0;
height: 0;
display: inline-block;
border-top-color: #999;
transform: translateY(5px);
}
}
}
.rc-tree-select-show-arrow.rc-tree-select-focused {
.rc-tree-select-selector {
border: 1px solid rgb(128, 189, 255);
outline: 0px;
box-shadow: rgba(0, 123, 255, 0.25) 0px 0px 0px 0.1rem;
}
}
`;
export const StyledCheckbox = styled(Checkbox)`
&&.${Classes.CHECKBOX}.${Classes.CONTROL} {
margin: 0;
}
`;
export const inputIcon = (): JSX.Element => (
<svg data-icon="chevron-down" height="16" viewBox="0 0 16 16" width="16">
<desc>chevron-down</desc>
<path
d="M12 5c-.28 0-.53.11-.71.29L8 8.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42l4 4c.18.18.43.29.71.29s.53-.11.71-.29l4-4A1.003 1.003 0 0012 5z"
fillRule="evenodd"
/>
</svg>
);

View File

@ -0,0 +1,191 @@
import React, {
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import TreeSelect, { TreeSelectProps as SelectProps } from "rc-tree-select";
import {
TreeSelectContainer,
DropdownStyles,
inputIcon,
StyledLabel,
TextLabelWrapper,
} from "./index.styled";
import "rc-tree-select/assets/index.less";
import { DefaultValueType } from "rc-tree-select/lib/interface";
import { TreeNodeProps } from "rc-tree-select/lib/TreeNode";
import { CheckedStrategy } from "rc-tree-select/lib/utils/strategyUtil";
import {
CANVAS_CLASSNAME,
MODAL_PORTAL_CLASSNAME,
TextSize,
} from "constants/WidgetConstants";
import { Classes } from "@blueprintjs/core";
export interface TreeSelectProps
extends Required<
Pick<
SelectProps,
| "disabled"
| "options"
| "placeholder"
| "loading"
| "dropdownStyle"
| "allowClear"
>
> {
value?: DefaultValueType;
onChange: (value?: DefaultValueType, labelList?: ReactNode[]) => void;
expandAll: boolean;
mode: CheckedStrategy;
labelText?: string;
labelTextColor?: string;
labelTextSize?: TextSize;
labelStyle?: string;
compactMode: boolean;
}
const getSvg = (style = {}) => (
<i
style={{
cursor: "pointer",
backgroundColor: "transparent",
display: "inline-flex",
width: "10px",
}}
>
<svg
fill="none"
height="10"
style={{ verticalAlign: "-.125em", ...style }}
viewBox="0 0 10 10"
width="10"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
d="M2.5 9L7.5 5L2.5 1L2.5 9Z"
fill="#090707"
ill-rule="evenodd"
/>
</svg>
</i>
);
const switcherIcon = (treeNode: TreeNodeProps) => {
if (treeNode.isLeaf) {
return (
<i
style={{
cursor: "pointer",
backgroundColor: "white",
display: "inline-flex",
width: "10px",
}}
/>
);
}
return getSvg({ transform: `rotate(${treeNode.expanded ? 90 : 0}deg)` });
};
function MultiTreeSelectComponent({
allowClear,
compactMode,
disabled,
dropdownStyle,
expandAll,
labelStyle,
labelText,
labelTextColor,
labelTextSize,
loading,
mode,
onChange,
options,
placeholder,
value,
}: TreeSelectProps): JSX.Element {
const [key, setKey] = useState(Math.random());
const _menu = useRef<HTMLElement | null>(null);
// treeDefaultExpandAll is uncontrolled after first render,
// using this to force render to respond to changes in expandAll
useEffect(() => {
setKey(Math.random());
}, [expandAll]);
const getDropdownPosition = useCallback(() => {
const node = _menu.current;
if (Boolean(node?.closest(`.${MODAL_PORTAL_CLASSNAME}`))) {
return document.querySelector(
`.${MODAL_PORTAL_CLASSNAME}`,
) as HTMLElement;
}
return document.querySelector(`.${CANVAS_CLASSNAME}`) as HTMLElement;
}, []);
const onClear = useCallback(() => onChange([], []), []);
return (
<TreeSelectContainer
allowClear={allowClear}
compactMode={compactMode}
ref={_menu as React.RefObject<HTMLDivElement>}
>
<DropdownStyles />
{labelText && (
<TextLabelWrapper compactMode={compactMode}>
<StyledLabel
$compactMode={compactMode}
$labelStyle={labelStyle}
$labelText={labelText}
$labelTextColor={labelTextColor}
$labelTextSize={labelTextSize}
className={`tree-select-label ${
loading ? Classes.SKELETON : Classes.TEXT_OVERFLOW_ELLIPSIS
}`}
>
{labelText}
</StyledLabel>
</TextLabelWrapper>
)}
<TreeSelect
allowClear={allowClear}
animation="slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
className="rc-tree-select"
disabled={disabled}
dropdownClassName="tree-select-dropdown"
dropdownStyle={dropdownStyle}
getPopupContainer={getDropdownPosition}
inputIcon={inputIcon}
key={key}
loading={loading}
maxTagCount={"responsive"}
maxTagPlaceholder={(e) => `+${e.length} more`}
multiple
notFoundContent="No item Found"
onChange={onChange}
onClear={onClear}
placeholder={placeholder}
showArrow
showCheckedStrategy={mode}
showSearch
style={{ width: "100%" }}
switcherIcon={switcherIcon}
transitionName="rc-tree-select-dropdown-slide-up"
treeCheckable={
<span className={`rc-tree-select-tree-checkbox-inner`} />
}
treeData={options}
treeDefaultExpandAll={expandAll}
treeIcon
treeNodeFilterProp="label"
value={value}
/>
</TreeSelectContainer>
);
}
export default MultiTreeSelectComponent;

View File

@ -0,0 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="0.75" y="0.75" width="18.5" height="6.5" stroke="#EAEAEA" stroke-width="1.5"/><path d="M7.99985 10.05L14.9998 10.05V11.55L7.99985 11.55L7.99985 10.05Z" fill="#EAEAEA"/><path d="M7.99985 14.75H13.9998V16.25L7.99985 16.25L7.99985 14.75Z" fill="#EAEAEA"/><path d="M5.99985 10.5L2.99985 10.5L4.49985 12L5.99985 10.5Z" fill="#EAEAEA"/><path d="M6 15L3 15L4.5 16.5L6 15Z" fill="#EAEAEA"/><path fill-rule="evenodd" clip-rule="evenodd" d="M0 6.5V20H18V6.5H0ZM1.5 18.5V8H16.5V18.5H1.5Z" fill="#EAEAEA"/><path d="M3 3.25H11V4.75H3V3.25Z" fill="#EAEAEA"/><path d="M12 3.25H17V4.75H12V3.25Z" fill="#EAEAEA"/></svg>

After

Width:  |  Height:  |  Size: 707 B

View File

@ -0,0 +1,51 @@
import Widget from "./widget";
import IconSVG from "./icon.svg";
import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
export const CONFIG = {
type: Widget.getWidgetType(),
name: "Multi Select Tree",
iconSVG: IconSVG,
needsMeta: true,
defaults: {
rows: 1.72 * GRID_DENSITY_MIGRATION_V1,
columns: 4 * GRID_DENSITY_MIGRATION_V1,
mode: "SHOW_ALL",
options: [
{
label: "Blue",
value: "BLUE",
children: [
{
label: "Dark Blue",
value: "DARK BLUE",
},
{
label: "Light Blue",
value: "LIGHT BLUE",
},
],
},
{ label: "Green", value: "GREEN" },
{ label: "Red", value: "RED" },
],
widgetName: "MultiSelectTree",
defaultOptionValue: ["GREEN"],
version: 1,
isVisible: true,
isRequired: false,
isDisabled: false,
allowClear: false,
expandAll: false,
placeholderText: "select option(s)",
labelText: "Label",
},
properties: {
derived: Widget.getDerivedPropertiesMap(),
default: Widget.getDefaultPropertiesMap(),
meta: Widget.getMetaPropertiesMap(),
config: Widget.getPropertyPaneConfig(),
},
};
export default Widget;

View File

@ -0,0 +1,465 @@
import React, { ReactNode } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { TextSize, WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { isArray, findIndex } from "lodash";
import {
ValidationResponse,
ValidationTypes,
} from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { DefaultValueType } from "rc-select/lib/interface/generator";
import { Layers } from "constants/Layers";
import { CheckedStrategy } from "rc-tree-select/lib/utils/strategyUtil";
import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
import MultiTreeSelectComponent from "../component";
function defaultOptionValueValidation(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,
};
}
class MultiSelectTreeWidget extends BaseWidget<
MultiSelectTreeWidgetProps,
WidgetState
> {
static getPropertyPaneConfig() {
return [
{
sectionName: "General",
children: [
{
helpText: "Mode to Display options",
propertyName: "mode",
label: "Mode",
controlType: "DROP_DOWN",
options: [
{
label: "Display only parent items",
value: "SHOW_PARENT",
},
{
label: "Display only child items",
value: "SHOW_CHILD",
},
{
label: "Display all items",
value: "SHOW_ALL",
},
],
isBindProperty: false,
isTriggerProperty: false,
},
{
helpText:
"Allows users to select multiple options. Values must be unique",
propertyName: "options",
label: "Options",
controlType: "INPUT_TEXT",
placeholderText: "Enter option value",
isBindProperty: true,
isTriggerProperty: false,
isJSConvertible: false,
validation: {
type: ValidationTypes.NESTED_OBJECT_ARRAY,
params: {
unique: ["value"],
default: [],
children: {
type: ValidationTypes.OBJECT,
params: {
allowedKeys: [
{
name: "label",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "value",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "children",
type: ValidationTypes.ARRAY,
required: false,
params: {
children: {
type: ValidationTypes.OBJECT,
params: {
allowedKeys: [
{
name: "label",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "value",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
],
},
},
},
},
],
},
},
},
},
evaluationSubstitutionType:
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Selects the option with value by default",
propertyName: "defaultOptionValue",
label: "Default Value",
controlType: "INPUT_TEXT",
placeholderText: "Enter option value",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.FUNCTION,
params: {
fn: defaultOptionValueValidation,
expected: {
type: "Array of values",
example: `['value1', 'value2']`,
autocompleteDataType: AutocompleteDataType.ARRAY,
},
},
},
},
{
helpText: "Label Text",
propertyName: "labelText",
label: "Label Text",
controlType: "INPUT_TEXT",
placeholderText: "Enter Label text",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Input Place Holder",
propertyName: "placeholderText",
label: "Placeholder",
controlType: "INPUT_TEXT",
placeholderText: "Enter placeholder text",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Controls the visibility of the widget",
propertyName: "isVisible",
label: "Visible",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "isDisabled",
label: "Disabled",
helpText: "Disables input to this widget",
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: "allowClear",
label: "Clear all Selections",
helpText: "Enables Icon to clear all Selections",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "expandAll",
label: "Expand all by default",
helpText: "Expand All nested options",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
{
sectionName: "Styles",
children: [
{
propertyName: "labelTextColor",
label: "Label Text Color",
controlType: "COLOR_PICKER",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "labelTextSize",
label: "Label Text Size",
controlType: "DROP_DOWN",
options: [
{
label: "Heading 1",
value: "HEADING1",
subText: "24px",
icon: "HEADING_ONE",
},
{
label: "Heading 2",
value: "HEADING2",
subText: "18px",
icon: "HEADING_TWO",
},
{
label: "Heading 3",
value: "HEADING3",
subText: "16px",
icon: "HEADING_THREE",
},
{
label: "Paragraph",
value: "PARAGRAPH",
subText: "14px",
icon: "PARAGRAPH",
},
{
label: "Paragraph 2",
value: "PARAGRAPH2",
subText: "12px",
icon: "PARAGRAPH_TWO",
},
],
isBindProperty: false,
isTriggerProperty: false,
},
{
propertyName: "labelStyle",
label: "Label Font Style",
controlType: "BUTTON_TABS",
options: [
{
icon: "BOLD_FONT",
value: "BOLD",
},
{
icon: "ITALICS_FONT",
value: "ITALIC",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
{
sectionName: "Actions",
children: [
{
helpText: "Triggers an action when a user selects an option",
propertyName: "onOptionChange",
label: "onOptionChange",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
];
}
static getDerivedPropertiesMap() {
return {
selectedOptionLabels: `{{ this.selectedLabel }}`,
selectedOptionValues:
'{{ this.selectedOptionValueArr.filter((o) => JSON.stringify(this.options).match(new RegExp(`"value":"${o}"`, "g")) )}}',
isValid: `{{ this.isRequired ? this.selectedOptionValues?.length > 0 : true}}`,
};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {
selectedOptionValueArr: "defaultOptionValue",
selectedLabel: "defaultOptionValue",
};
}
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedOptionValueArr: undefined,
selectedLabel: [],
};
}
getPageView() {
const options =
isArray(this.props.options) &&
!this.props.__evaluation__?.errors.options.length
? this.props.options
: [];
const values = isArray(this.props.selectedOptionValueArr)
? this.props.selectedOptionValueArr
: [];
const filteredValue = this.filterValues(values);
return (
<MultiTreeSelectComponent
allowClear={this.props.allowClear}
compactMode={
!(
(this.props.bottomRow - this.props.topRow) /
GRID_DENSITY_MIGRATION_V1 >
1
)
}
disabled={this.props.isDisabled ?? false}
dropdownStyle={{
zIndex: Layers.dropdownModalWidget,
}}
expandAll={this.props.expandAll}
labelStyle={this.props.labelStyle}
labelText={this.props.labelText}
labelTextColor={this.props.labelTextColor}
labelTextSize={this.props.labelTextSize}
loading={this.props.isLoading}
mode={this.props.mode}
onChange={this.onOptionChange}
options={options}
placeholder={this.props.placeholderText as string}
value={filteredValue}
/>
);
}
onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => {
this.props.updateWidgetMetaProperty("selectedLabel", labelList, {
triggerPropertyName: "onOptionChange",
dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
this.props.updateWidgetMetaProperty("selectedOptionValueArr", value, {
triggerPropertyName: "onOptionChange",
dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
};
flat(array: DropdownOption[]) {
let result: { value: string }[] = [];
array.forEach((a) => {
result.push({ value: a.value });
if (Array.isArray(a.children)) {
result = result.concat(this.flat(a.children));
}
});
return result;
}
filterValues(values: string[] | undefined) {
const options = this.props.options
? this.flat(this.props.options as DropdownOption[])
: [];
if (isArray(values)) {
return values.filter((o) => {
const index = findIndex(options, { value: o });
return index > -1;
});
}
}
static getWidgetType(): WidgetType {
return "MULTI_SELECT_TREE_WIDGET";
}
}
export interface DropdownOption {
label: string;
value: string;
disabled?: boolean;
children?: DropdownOption[];
}
export interface MultiSelectTreeWidgetProps extends WidgetProps {
placeholderText?: string;
selectedIndexArr?: number[];
options?: DropdownOption[];
onOptionChange: string;
defaultOptionValue: string[];
isRequired: boolean;
isLoading: boolean;
allowClear: boolean;
labelText?: string;
selectedLabel: string[];
selectedOptionValueArr: string[];
selectedOptionValues: string[];
selectedOptionLabels: string[];
expandAll: boolean;
mode: CheckedStrategy;
labelTextColor?: string;
labelTextSize?: TextSize;
labelStyle?: string;
}
export default MultiSelectTreeWidget;

View File

@ -48,13 +48,13 @@ function MultiSelectComponent({
const [isSelectAll, setIsSelectAll] = useState(false);
const _menu = useRef<HTMLElement | null>(null);
const getDropdownPosition = useCallback((node: HTMLElement | null) => {
const getDropdownPosition = useCallback(() => {
const node = _menu.current;
if (Boolean(node?.closest(`.${MODAL_PORTAL_CLASSNAME}`))) {
return document.querySelector(
`.${MODAL_PORTAL_CLASSNAME}`,
) as HTMLElement;
}
// TODO: Use generateClassName func.
return document.querySelector(`.${CANVAS_CLASSNAME}`) as HTMLElement;
}, []);
@ -129,7 +129,7 @@ function MultiSelectComponent({
dropdownRender={dropdownRender}
dropdownStyle={dropdownStyle}
filterOption={serverSideFiltering ? false : filterOption}
getPopupContainer={() => getDropdownPosition(_menu.current)}
getPopupContainer={getDropdownPosition}
inputIcon={inputIcon}
loading={loading}
maxTagCount={"responsive"}

View File

@ -0,0 +1,888 @@
import React from "react";
import { Checkbox, Classes, Label } from "@blueprintjs/core";
import styled, { keyframes } from "styled-components";
import { Colors } from "constants/Colors";
import { createGlobalStyle } from "constants/DefaultTheme";
import {
FontStyleTypes,
TextSize,
TEXT_SIZES,
} from "constants/WidgetConstants";
export const menuItemSelectedIcon = (props: { isSelected: boolean }) => {
return <StyledCheckbox checked={props.isSelected} />;
};
export const TextLabelWrapper = styled.div<{
compactMode: boolean;
}>`
${(props) =>
props.compactMode ? "&&& {margin-right: 5px;}" : "width: 100%;"}
display: flex;
`;
export const StyledLabel = styled(Label)<{
$compactMode: boolean;
$labelText?: string;
$labelTextColor?: string;
$labelTextSize?: TextSize;
$labelStyle?: string;
}>`
overflow-y: hidden;
text-overflow: ellipsis;
width: ${(props) => (props.$compactMode ? "auto" : "100%")};
text-align: left;
color: ${(props) => props.$labelTextColor || "inherit"};
font-size: ${(props) =>
props.$labelTextSize ? TEXT_SIZES[props.$labelTextSize] : "14px"};
font-weight: ${(props) =>
props?.$labelStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"};
font-style: ${(props) =>
props?.$labelStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""};
`;
const rcSelectDropdownSlideUpIn = keyframes`
0% {
opacity: 0;
transform-origin: 0% 0%;
}
100% {
opacity: 1;
transform-origin: 0% 0%;
}
`;
const rcSelectDropdownSlideUpOut = keyframes`
0% {
opacity: 1;
transform-origin: 0% 0%;
}
100% {
opacity: 0;
transform-origin: 0% 0%;
}
`;
export const DropdownStyles = createGlobalStyle`
.rc-tree-select-dropdown-hidden {
display: none;
}
.rc-tree-select-item-group {
color: #999;
font-weight: bold;
font-size: 80%;
}
.rc-tree-select-item-option {
position: relative;
display: flex;
flex-direction: row-reverse;
.rc-tree-select-item-option-state {
pointer-events: all;
margin-right: 10px;
}
}
.rc-tree-select-item-option-grouped {
padding-left: 24px;
}
.rc-tree-select-item-option-content {
flex: 1 1 0;
}
.rc-tree-select-item-option-active {
background: rgb(233, 250, 243);
}
.rc-tree-select-item-option-selected {
background: rgb(233, 250, 243);
}
.rc-tree-select-item-option-disabled {
color: #999;
}
.rc-tree-select-item-empty {
text-align: center;
color: #999;
}
.rc-tree-select-selection__choice-zoom {
transition: all 0s;
}
.rc-tree-select-selection__choice-zoom-appear {
opacity: 0;
}
.rc-tree-select-selection__choice-zoom-appear.rc-tree-select-selection__choice-zoom-appear-active {
opacity: 1;
}
.rc-tree-select-selection__choice-zoom-leave {
opacity: 1;
}
.rc-tree-select-selection__choice-zoom-leave.rc-tree-select-selection__choice-zoom-leave-active {
opacity: 0;
}
.rc-tree-select-dropdown-slide-up-enter {
animation-duration: 0s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-tree-select-dropdown-slide-up-appear {
animation-duration: 0s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-tree-select-dropdown-slide-up-leave {
animation-duration: 0s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-bottomLeft {
animation-name: ${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-bottomLeft {
animation-name:${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-bottomLeft {
animation-name: ${rcSelectDropdownSlideUpOut};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-topLeft {
animation-name: ${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-topLeft {
animation-name: ${rcSelectDropdownSlideUpIn};
animation-play-state: running;
}
.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-topLeft {
animation-name: ${rcSelectDropdownSlideUpOut};
animation-play-state: running;
}
.tree-select-dropdown.single-tree-select-dropdown {
.rc-tree-select-tree
.rc-tree-select-tree-treenode.rc-tree-select-tree-treenode-disabled
span.rc-tree-select-tree-iconEle {
cursor: not-allowed;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle {
position: relative;
cursor: pointer;
margin-left: 5px;
top: 0;
left: 0;
display: inline-block;
width: 16px;
height: 16px;
direction: ltr;
background-color: #fff;
border: 1px solid #E8E8E8;
border-radius: 100%;
border-collapse: separate;
transition: all .3s;
:after{
position: absolute;
top: 50%;
left: 52%;
display: table;
width: 10px;
height: 10px;
border: none;
border-top: 0;
border-left: 0;
transform: rotate(
45deg
) scale(0) translate(-50%,-50%);
opacity: 0;
transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
content: " ";
}
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
.rc-tree-select-tree-node-selected
span.rc-tree-select-tree-iconEle {
:after{
width: 10px;
height: 10px;
transform: translate(-50%,-50%) scale(1);
background: rgb(3, 179, 101) !important;
opacity: 1;
content: " ";
border-radius: 100%;
}
}
}
.tree-select-dropdown {
min-height: 100px;
min-width: 250px !important;
position: absolute;
background: #fff;
width: 100%;
border-radius: 0px;
margin-top: 10px;
padding: 12px;
background: white;
box-shadow: 0 0 2px rgb(0 0 0 / 20%) !important;
&&&& .${Classes.ALIGN_LEFT} {
font-size: 16px;
padding-bottom: 10px;
margin-left: 16px ;
.${Classes.CONTROL_INDICATOR} {
margin-right: 20px;
}
}
&&&& .${Classes.CONTROL} .${Classes.CONTROL_INDICATOR} {
background: white;
box-shadow: none;
border-width: 2px;
border-style: solid;
border-color: ${Colors.GEYSER};
&::before {
width: auto;
height: 1em;
}
}
.${Classes.CONTROL} input:checked ~ .${Classes.CONTROL_INDICATOR} {
background: rgb(3, 179, 101) !important;
color: rgb(255, 255, 255);
border-color: rgb(3, 179, 101) !important;
box-shadow: none;
outline: none !important;
}
.rc-tree-select-item {
font-size: 16px;
line-height: 1.5;
padding: 5px 16px;
align-items: center;
cursor: pointer;
}
.rc-tree-select-item-option-state {
.bp3-control.bp3-checkbox {
margin-bottom: 0;
}
}
.rc-tree-select-tree {
margin: 0;
border: 1px solid transparent;
}
.rc-tree-select-tree-focused:not(.rc-tree-select-tree-active-focused) {
border-color: cyan;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode {
margin: 0;
padding: 0;
line-height: 24px;
white-space: nowrap;
list-style: none;
outline: 0;
padding: 0 5px;
height: 34px;
align-items: center;
display: flex !important;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode .draggable {
color: #333;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
-khtml-user-drag: element;
-webkit-user-drag: element;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.drop-container
> .draggable::after {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
box-shadow: inset 0 0 0 2px red;
content: "";
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.drop-container
~ .rc-tree-select-tree-treenode {
border-left: 2px solid chocolate;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode.drop-target {
background-color: yellowgreen;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.drop-target
~ .rc-tree-select-tree-treenode {
border-left: none;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode.filter-node
> .rc-tree-select-tree-node-content-wrapper {
color: #182026 !important;
font-weight: bold !important;
}
.rc-tree-select-tree .rc-tree-select-tree-treenode ul {
margin: 0;
padding: 0 0 0 18px;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
.rc-tree-select-tree-node-content-wrapper {
position: relative;
display: inline-flex;
align-items: center;
height: 34px;
margin: 0;
padding: 0;
text-decoration: none;
vertical-align: top;
cursor: pointer;
flex: 1
}
.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner:after {
position: absolute;
display: table;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(
45deg
) scale(1) translate(-50%,-50%);
opacity: 1;
transition: all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
content: " ";
}
.rc-tree-select-tree-checkbox-inner:after {
position: absolute;
top: 50%;
left: 22%;
display: table;
width: 5.71428571px;
height: 9.14285714px;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(
45deg
) scale(0) translate(-50%,-50%);
opacity: 0;
transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
content: " ";
}
.rc-tree-select-tree-checkbox-indeterminate .rc-tree-select-tree-checkbox-inner:after {
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background-color: rgb(3, 179, 101) !important;
border: 0;
transform: translate(-50%,-50%) scale(1);
opacity: 1;
content: " ";
}
.rc-tree-select-tree-checkbox:hover:after, .rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox:after {
visibility: visible;
}
.rc-tree-select-tree-checkbox {
top: initial;
margin: 4px 8px 0 0;
}
.rc-tree-select-tree-checkbox {
box-sizing: border-box;
margin: 0;
padding: 0;
color: #000000d9;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: "tnum";
position: relative;
top: 0;
line-height: 1;
white-space: nowrap;
outline: none;
cursor: pointer;
margin-left: 3px;
}
.rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox-input:focus+.rc-tree-select-tree-checkbox-inner {
border-color: rgb(3, 179, 101) !important;
}
.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner {
border-color: rgb(3, 179, 101) !important;
background: rgb(3, 179, 101) !important;
}
.rc-tree-select-tree-checkbox-inner {
position: relative;
top: 0;
left: 0;
display: inline-block;
width: 16px;
height: 16px;
direction: ltr;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 0px;
border-collapse: separate;
transition: all .3s;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree.select-tree-checkbox-checked {
.rc-tree-select-tree-checkbox-inner {
border-color: rgb(3, 179, 101) !important;
background: rgb(3, 179, 101) !important;
}
}
.single-tree-select-dropdown
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle {
width: 20px;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle {
display: inline-block;
width: 0px;
height: 16px;
margin-right: 2px;
line-height: 16px;
vertical-align: -0.125em;
background-color: transparent;
background-image: none;
background-repeat: no-repeat;
background-attachment: scroll;
border: 0 none;
outline: none;
cursor: pointer;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-icon__customize,
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-checkbox.rc-tree-select-tree-icon__customize,
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-iconEle.rc-tree-select-tree-icon__customize {
background-image: none;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-icon_loading {
margin-right: 2px;
vertical-align: top;
background: none;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher-noop {
cursor: auto;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_open {
background-position: -93px -56px;
}
.rc-tree-select-tree
.rc-tree-select-tree-treenode
span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_close {
background-position: -75px -56px;
}
.rc-tree-select-tree:not(.rc-tree-select-tree-show-line)
.rc-tree-select-tree-treenode
.rc-tree-select-tree-switcher-noop {
background: none;
}
.rc-tree-select-tree.rc-tree-select-tree-show-line
.rc-tree-select-tree-treenode:not(:last-child)
> ul {
background: none;
}
.rc-tree-select-tree.rc-tree-select-tree-show-line
.rc-tree-select-tree-treenode:not(:last-child)
> .rc-tree-select-tree-switcher-noop {
background-position: -56px -18px;
}
.rc-tree-select-tree.rc-tree-select-tree-show-line
.rc-tree-select-tree-treenode:last-child
> .rc-tree-select-tree-switcher-noop {
background-position: -56px -36px;
}
.rc-tree-select-tree-child-tree {
display: none;
}
.rc-tree-select-tree-child-tree-open {
display: block;
}
.rc-tree-select-tree-treenode-disabled
> span:not(.rc-tree-select-tree-switcher),
.rc-tree-select-tree-treenode-disabled > a,
.rc-tree-select-tree-treenode-disabled > a span {
color: #767676;
cursor: not-allowed;
}
.rc-tree-select-tree-treenode-active {
background: rgb(233, 250, 243);
}
.rc-tree-select-tree-treenode:hover {
background: rgb(233, 250, 243);
}
.rc-tree-select-tree-node-selected {
background-color: none;
box-shadow: 0 0 0 0 #ffb951;
opacity: 1;
}
.rc-tree-select-tree-icon__open {
margin-right: 2px;
vertical-align: top;
background-position: -110px -16px;
}
.rc-tree-select-tree-icon__close {
margin-right: 2px;
vertical-align: top;
background-position: -110px 0;
}
.rc-tree-select-tree-icon__docu {
margin-right: 2px;
vertical-align: top;
background-position: -110px -32px;
}
.rc-tree-select-tree-icon__customize {
margin-right: 2px;
vertical-align: top;
}
.rc-tree-select-tree-title {
display: inline-block;
margin-left: 10px;
font-size: 16px !important;
}
.rc-tree-select-tree-indent {
display: inline-block;
vertical-align: bottom;
height: 0;
}
.rc-tree-select-tree-indent-unit {
width: 25px;
display: inline-block;
}
}
`;
export const TreeSelectContainer = styled.div<{ compactMode: boolean }>`
display: flex;
flex-direction: ${(props) => (props.compactMode ? "row" : "column")};
align-items: ${(props) => (props.compactMode ? "center" : "left")};
label.tree-select-label {
margin-bottom: ${(props) => (props.compactMode ? "0px" : "5px")};
margin-right: ${(props) => (props.compactMode ? "10px" : "0px")};
}
.rc-tree-select {
display: inline-block;
font-size: 12px;
width: 100%;
height: 100%;
position: relative;
cursor: pointer;
flex: 1 1;
.rc-tree-select-selection-placeholder {
pointer-events: none;
position: absolute;
top: 50%;
right: 11px;
left: 11px;
transform: translateY(-50%);
transition: all 0.3s;
flex: 1;
overflow: hidden;
color: #bfbfbf;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
font-size: 14px;
}
.rc-tree-select-selection-search-input {
appearance: none;
&::-webkit-search-cancel-button {
display: none;
appearance: none;
}
}
.rc-tree-select-selection-overflow-item-suffix {
position: relative !important;
left: 0px !important;
}
}
.rc-tree-select-disabled {
cursor: not-allowed;
input {
cursor: not-allowed;
}
.rc-tree-select-selector {
opacity: 0.3;
}
}
.rc-tree-select-show-arrow.rc-tree-select-loading {
.rc-tree-select-arrow-icon {
&::after {
box-sizing: border-box;
width: 12px;
height: 12px;
border-radius: 100%;
border: 2px solid #999;
border-top-color: transparent;
border-bottom-color: transparent;
transform: none;
margin-top: 4px;
animation: rcSelectLoadingIcon 0.5s infinite;
}
}
}
.rc-tree-select-single .rc-tree-select-selector {
display: flex;
flex-wrap: wrap;
padding: 1px;
padding-right: 20px;
box-shadow: none;
border: 1px solid rgb(231, 231, 231);
border-radius: 0px;
width: 100%;
transition: border-color 0.15s ease-in-out 0s,
box-shadow 0.15s ease-in-out 0s;
background-color: white;
height: 100%;
.rc-tree-select-selection-search {
width: 100%;
height: 100%;
input {
width: 100%;
appearance: none;
&::-webkit-search-cancel-button {
display: none;
appearance: none;
}
font-family: system-ui;
height: 100%;
border: none;
}
}
.rc-tree-select-selection-item {
pointer-events: none;
position: absolute;
top: 50%;
right: 11px;
left: 11px;
transform: translateY(-50%);
transition: all 0.3s;
flex: 1;
overflow: hidden;
color: #231f20;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
font-size: 14px;
}
}
.rc-tree-select-multiple {
.rc-tree-select-selector {
padding-right: 20px;
display: flex;
flex-wrap: wrap;
padding: 1px;
box-shadow: none;
border: 1px solid rgb(231, 231, 231);
border-radius: 0px;
width: 100%;
transition: border-color 0.15s ease-in-out 0s,
box-shadow 0.15s ease-in-out 0s;
background-color: white;
.rc-tree-select-selection-item {
background: none;
border: 1px solid rgb(208, 215, 221);
border-radius: 2px;
margin: 3px 2px;
max-width: 273.926px;
height: 24px;
color: #182026;
overflow-wrap: break-word;
display: inline-flex;
flex-direction: row;
align-items: center;
box-shadow: none;
font-size: 12px;
line-height: 16px;
min-height: 20px;
min-width: 20px;
padding: 2px 6px;
position: relative;
}
.rc-tree-select-selection-item-disabled {
cursor: not-allowed;
opacity: 0.5;
}
.rc-tree-select-selection-overflow {
display: flex;
flex-wrap: wrap;
width: 100%;
align-content: center;
}
.rc-tree-select-selection-overflow-item {
flex: none;
max-width: 100%;
}
.rc-tree-select-selection-search {
position: relative;
max-width: 100%;
margin-bottom: 2px;
height: 100%;
display: flex;
align-items: center;
}
.rc-tree-select-selection-search-input {
padding: 1px;
font-family: system-ui;
width: 5px;
margin: 0px;
display: flex;
height: 26px;
flex: 1 1 0%;
border: none;
outline: none;
width: 100%;
}
.rc-tree-select-selection-search-mirror {
padding: 1px;
font-family: system-ui;
width: 5px;
margin: 0px;
display: flex;
height: 26px;
flex: 1 1 0%;
position: absolute;
z-index: 999;
white-space: nowrap;
position: none;
left: 0;
top: 0;
visibility: hidden;
}
}
}
.rc-tree-select-selection-item-content {
flex-grow: 1;
flex-shrink: 1;
margin-right: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
font-size: 12px;
line-height: 16px;
}
.rc-tree-select-allow-clear {
.rc-tree-select-clear {
position: absolute;
right: 20px;
right: 25px;
top: -1px;
height: 100%;
display: flex;
align-items: center;
z-index: -1;
.rc-tree-select-clear-icon {
font-size: 18px;
font-weight: bold;
}
}
}
.rc-tree-select-allow-clear.rc-tree-select-focused {
.rc-tree-select-clear {
z-index: 1;
}
}
.rc-tree-select-show-arrow.rc-tree-select-multiple {
.rc-tree-select-selector {
padding-right: 20px;
box-shadow: none;
border: 1px solid rgb(231, 231, 231);
border-radius: 0px;
height: inherit;
width: 100%;
transition: border-color 0.15s ease-in-out 0s,
box-shadow 0.15s ease-in-out 0s;
}
}
.rc-tree-select-show-arrow {
.rc-tree-select-arrow {
pointer-events: none;
position: absolute;
right: 5px;
top: 0;
height: 100%;
display: flex;
align-items: center;
}
.rc-tree-select-arrow-icon {
&::after {
content: "";
border: 5px solid transparent;
width: 0;
height: 0;
display: inline-block;
border-top-color: #999;
transform: translateY(5px);
}
}
}
.rc-tree-select-show-arrow.rc-tree-select-focused {
.rc-tree-select-selector {
border: 1px solid rgb(128, 189, 255);
outline: 0px;
box-shadow: rgba(0, 123, 255, 0.25) 0px 0px 0px 0.1rem;
}
}
`;
export const StyledCheckbox = styled(Checkbox)`
&&.${Classes.CHECKBOX}.${Classes.CONTROL} {
margin: 0;
}
`;
export const inputIcon = (): JSX.Element => (
<svg data-icon="chevron-down" height="16" viewBox="0 0 16 16" width="16">
<desc>chevron-down</desc>
<path
d="M12 5c-.28 0-.53.11-.71.29L8 8.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42l4 4c.18.18.43.29.71.29s.53-.11.71-.29l4-4A1.003 1.003 0 0012 5z"
fillRule="evenodd"
/>
</svg>
);

View File

@ -0,0 +1,182 @@
import React, {
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import TreeSelect, { TreeSelectProps as SelectProps } from "rc-tree-select";
import {
TreeSelectContainer,
DropdownStyles,
inputIcon,
StyledLabel,
TextLabelWrapper,
} from "./index.styled";
import "rc-tree-select/assets/index.less";
import { DefaultValueType } from "rc-tree-select/lib/interface";
import { TreeNodeProps } from "rc-tree-select/lib/TreeNode";
import {
CANVAS_CLASSNAME,
MODAL_PORTAL_CLASSNAME,
TextSize,
} from "constants/WidgetConstants";
import { Classes } from "@blueprintjs/core";
export interface TreeSelectProps
extends Required<
Pick<
SelectProps,
| "disabled"
| "options"
| "placeholder"
| "loading"
| "dropdownStyle"
| "allowClear"
>
> {
value?: DefaultValueType;
onChange: (value?: DefaultValueType, labelList?: ReactNode[]) => void;
expandAll: boolean;
labelText?: string;
labelTextColor?: string;
labelTextSize?: TextSize;
labelStyle?: string;
compactMode: boolean;
}
const getSvg = (style = {}) => (
<i
style={{
cursor: "pointer",
backgroundColor: "transparent",
display: "inline-flex",
width: "10px",
}}
>
<svg
fill="none"
height="10"
style={{ verticalAlign: "-.125em", ...style }}
viewBox="0 0 10 10"
width="10"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
d="M2.5 9L7.5 5L2.5 1L2.5 9Z"
fill="#090707"
ill-rule="evenodd"
/>
</svg>
</i>
);
const switcherIcon = (treeNode: TreeNodeProps) => {
if (treeNode.isLeaf) {
return (
<i
style={{
cursor: "pointer",
backgroundColor: "white",
display: "inline-flex",
width: "10px",
}}
/>
);
}
return getSvg({ transform: `rotate(${treeNode.expanded ? 90 : 0}deg)` });
};
function SingleSelectTreeComponent({
allowClear,
compactMode,
disabled,
dropdownStyle,
expandAll,
labelStyle,
labelText,
labelTextColor,
labelTextSize,
loading,
onChange,
options,
placeholder,
value,
}: TreeSelectProps): JSX.Element {
const [key, setKey] = useState(Math.random());
const _menu = useRef<HTMLElement | null>(null);
// treeDefaultExpandAll is uncontrolled after first render,
// using this to force render to respond to changes in expandAll
useEffect(() => {
setKey(Math.random());
}, [expandAll]);
const getDropdownPosition = useCallback(() => {
const node = _menu.current;
if (Boolean(node?.closest(`.${MODAL_PORTAL_CLASSNAME}`))) {
return document.querySelector(
`.${MODAL_PORTAL_CLASSNAME}`,
) as HTMLElement;
}
return document.querySelector(`.${CANVAS_CLASSNAME}`) as HTMLElement;
}, []);
const onClear = useCallback(() => onChange([], []), []);
return (
<TreeSelectContainer
compactMode={compactMode}
ref={_menu as React.RefObject<HTMLDivElement>}
>
<DropdownStyles />
{labelText && (
<TextLabelWrapper compactMode={compactMode}>
<StyledLabel
$compactMode={compactMode}
$labelStyle={labelStyle}
$labelText={labelText}
$labelTextColor={labelTextColor}
$labelTextSize={labelTextSize}
className={`tree-select-label ${
loading ? Classes.SKELETON : Classes.TEXT_OVERFLOW_ELLIPSIS
}`}
>
{labelText}
</StyledLabel>
</TextLabelWrapper>
)}
<TreeSelect
allowClear={allowClear}
animation="slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
className="rc-tree-select"
disabled={disabled}
dropdownClassName="tree-select-dropdown single-tree-select-dropdown"
dropdownStyle={dropdownStyle}
getPopupContainer={getDropdownPosition}
inputIcon={inputIcon}
key={key}
loading={loading}
maxTagCount={"responsive"}
maxTagPlaceholder={(e) => `+${e.length} more`}
notFoundContent="No item Found"
onChange={onChange}
onClear={onClear}
placeholder={placeholder}
showArrow
showSearch
style={{ width: "100%" }}
switcherIcon={switcherIcon}
transitionName="rc-tree-select-dropdown-slide-up"
treeData={options}
treeDefaultExpandAll={expandAll}
treeIcon
treeNodeFilterProp="label"
value={value}
/>
</TreeSelectContainer>
);
}
export default SingleSelectTreeComponent;

View File

@ -0,0 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="0.75" y="0.75" width="18.5" height="6.5" stroke="#EAEAEA" stroke-width="1.5"/><path d="M7.99985 10.05L14.9998 10.05V11.55L7.99985 11.55L7.99985 10.05Z" fill="#EAEAEA"/><path d="M7.99985 14.75H13.9998V16.25L7.99985 16.25L7.99985 14.75Z" fill="#EAEAEA"/><path d="M5.99985 10.5L2.99985 10.5L4.49985 12L5.99985 10.5Z" fill="#EAEAEA"/><path d="M6 15L3 15L4.5 16.5L6 15Z" fill="#EAEAEA"/><path fill-rule="evenodd" clip-rule="evenodd" d="M0 6.5V20H18V6.5H0ZM1.5 18.5V8H16.5V18.5H1.5Z" fill="#EAEAEA"/><path d="M3 3.19995H17V4.69995H3V3.19995Z" fill="#EAEAEA"/></svg>

After

Width:  |  Height:  |  Size: 664 B

View File

@ -0,0 +1,50 @@
import Widget from "./widget";
import IconSVG from "./icon.svg";
import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
export const CONFIG = {
type: Widget.getWidgetType(),
name: "Single Select Tree",
iconSVG: IconSVG,
needsMeta: true,
defaults: {
rows: 1.7 * GRID_DENSITY_MIGRATION_V1,
columns: 4 * GRID_DENSITY_MIGRATION_V1,
options: [
{
label: "Blue",
value: "BLUE",
children: [
{
label: "Dark Blue",
value: "DARK BLUE",
},
{
label: "Light Blue",
value: "LIGHT BLUE",
},
],
},
{ label: "Green", value: "GREEN" },
{ label: "Red", value: "RED" },
],
widgetName: "SingleSelectTree",
defaultOptionValue: "BLUE",
version: 1,
isVisible: true,
isRequired: false,
isDisabled: false,
allowClear: false,
expandAll: false,
placeholderText: "select option",
labelText: "Label",
},
properties: {
derived: Widget.getDerivedPropertiesMap(),
default: Widget.getDefaultPropertiesMap(),
meta: Widget.getMetaPropertiesMap(),
config: Widget.getPropertyPaneConfig(),
},
};
export default Widget;

View File

@ -0,0 +1,425 @@
import React, { ReactNode } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { TextSize, WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { isArray, findIndex } from "lodash";
import {
ValidationResponse,
ValidationTypes,
} from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { DefaultValueType } from "rc-select/lib/interface/generator";
import { Layers } from "constants/Layers";
import { isString } from "../../../utils/helpers";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
import SingleSelectTreeComponent from "../component";
function defaultOptionValueValidation(value: unknown): ValidationResponse {
if (typeof value === "string") return { isValid: true, parsed: value.trim() };
if (value === undefined || value === null)
return {
isValid: false,
parsed: "",
message: "This value does not evaluate to type: string",
};
return { isValid: true, parsed: value };
}
class SingleSelectTreeWidget extends BaseWidget<
SingleSelectTreeWidgetProps,
WidgetState
> {
static getPropertyPaneConfig() {
return [
{
sectionName: "General",
children: [
{
helpText:
"Allows users to select multiple options. Values must be unique",
propertyName: "options",
label: "Options",
controlType: "INPUT_TEXT",
placeholderText: "Enter option value",
isBindProperty: true,
isTriggerProperty: false,
isJSConvertible: false,
validation: {
type: ValidationTypes.NESTED_OBJECT_ARRAY,
params: {
unique: ["value"],
default: [],
children: {
type: ValidationTypes.OBJECT,
params: {
allowedKeys: [
{
name: "label",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "value",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "children",
type: ValidationTypes.ARRAY,
required: false,
params: {
children: {
type: ValidationTypes.OBJECT,
params: {
allowedKeys: [
{
name: "label",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "value",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
],
},
},
},
},
],
},
},
},
},
evaluationSubstitutionType:
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Selects the option with value by default",
propertyName: "defaultOptionValue",
label: "Default Value",
controlType: "INPUT_TEXT",
placeholderText: "Enter option value",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.FUNCTION,
params: {
fn: defaultOptionValueValidation,
expected: {
type: "value",
example: `value1`,
autocompleteDataType: AutocompleteDataType.STRING,
},
},
},
},
{
helpText: "Label Text",
propertyName: "labelText",
label: "Label Text",
controlType: "INPUT_TEXT",
placeholderText: "Enter Label text",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Input Place Holder",
propertyName: "placeholderText",
label: "Placeholder",
controlType: "INPUT_TEXT",
placeholderText: "Enter placeholder text",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Controls the visibility of the widget",
propertyName: "isVisible",
label: "Visible",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "isDisabled",
label: "Disabled",
helpText: "Disables input to this widget",
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: "allowClear",
label: "Clear all Selections",
helpText: "Enables Icon to clear all Selections",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "expandAll",
label: "Expand all by default",
helpText: "Expand All nested options",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
{
sectionName: "Styles",
children: [
{
propertyName: "labelTextColor",
label: "Label Text Color",
controlType: "COLOR_PICKER",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "labelTextSize",
label: "Label Text Size",
controlType: "DROP_DOWN",
options: [
{
label: "Heading 1",
value: "HEADING1",
subText: "24px",
icon: "HEADING_ONE",
},
{
label: "Heading 2",
value: "HEADING2",
subText: "18px",
icon: "HEADING_TWO",
},
{
label: "Heading 3",
value: "HEADING3",
subText: "16px",
icon: "HEADING_THREE",
},
{
label: "Paragraph",
value: "PARAGRAPH",
subText: "14px",
icon: "PARAGRAPH",
},
{
label: "Paragraph 2",
value: "PARAGRAPH2",
subText: "12px",
icon: "PARAGRAPH_TWO",
},
],
isBindProperty: false,
isTriggerProperty: false,
},
{
propertyName: "labelStyle",
label: "Label Font Style",
controlType: "BUTTON_TABS",
options: [
{
icon: "BOLD_FONT",
value: "BOLD",
},
{
icon: "ITALICS_FONT",
value: "ITALIC",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
{
sectionName: "Actions",
children: [
{
helpText: "Triggers an action when a user selects an option",
propertyName: "onOptionChange",
label: "onOptionChange",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
];
}
static getDerivedPropertiesMap() {
return {
selectedOptionLabel: `{{ this.selectedLabel[0] }}`,
selectedOptionValue:
'{{ JSON.stringify(this.options).match(new RegExp(`"value":"${this.selectedOption}"`), "g") ? this.selectedOption : undefined }}',
isValid: `{{this.isRequired ? !!this.selectedOptionValue?.length : true}}`,
};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {
selectedOption: "defaultOptionValue",
selectedLabel: "defaultOptionValue",
};
}
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedOption: undefined,
selectedOptionValueArr: undefined,
selectedLabel: [],
};
}
getPageView() {
const options =
isArray(this.props.options) &&
!this.props.__evaluation__?.errors.options.length
? this.props.options
: [];
const values: string | undefined = isString(this.props.selectedOption)
? this.props.selectedOption
: undefined;
const filteredValue = this.filterValues(values);
return (
<SingleSelectTreeComponent
allowClear={this.props.allowClear}
compactMode={
!(
(this.props.bottomRow - this.props.topRow) /
GRID_DENSITY_MIGRATION_V1 >
1
)
}
disabled={this.props.isDisabled ?? false}
dropdownStyle={{
zIndex: Layers.dropdownModalWidget,
}}
expandAll={this.props.expandAll}
labelStyle={this.props.labelStyle}
labelText={this.props.labelText}
labelTextColor={this.props.labelTextColor}
labelTextSize={this.props.labelTextSize}
loading={this.props.isLoading}
onChange={this.onOptionChange}
options={options}
placeholder={this.props.placeholderText as string}
value={filteredValue}
/>
);
}
onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => {
this.props.updateWidgetMetaProperty("selectedLabel", labelList, {
triggerPropertyName: "onOptionChange",
dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
this.props.updateWidgetMetaProperty("selectedOption", value, {
triggerPropertyName: "onOptionChange",
dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
};
flat(array: DropdownOption[]) {
let result: { value: string }[] = [];
array.forEach((a) => {
result.push({ value: a.value });
if (Array.isArray(a.children)) {
result = result.concat(this.flat(a.children));
}
});
return result;
}
filterValues(values: string | undefined) {
const options = this.props.options ? this.flat(this.props.options) : [];
if (isString(values)) {
const index = findIndex(options, { value: values as string });
return index > -1 ? values : undefined;
}
}
static getWidgetType(): WidgetType {
return "SINGLE_SELECT_TREE_WIDGET";
}
}
export interface DropdownOption {
label: string;
value: string;
disabled?: boolean;
children?: DropdownOption[];
}
export interface SingleSelectTreeWidgetProps extends WidgetProps {
placeholderText?: string;
selectedIndex?: number;
options?: DropdownOption[];
onOptionChange: string;
defaultOptionValue: string;
isRequired: boolean;
isLoading: boolean;
allowClear: boolean;
labelText?: string;
selectedLabel: string[];
selectedOption: string;
selectedOptionValue: string;
selectedOptionLabel: string;
expandAll: boolean;
labelTextColor?: string;
labelTextSize?: TextSize;
labelStyle?: string;
}
export default SingleSelectTreeWidget;

View File

@ -22,6 +22,16 @@ import evaluate from "./evaluate";
import getIsSafeURL from "utils/validation/getIsSafeURL";
export const UNDEFINED_VALIDATION = "UNDEFINED_VALIDATION";
const flat = (array: Record<string, any>[], uniqueParam: string) => {
let result: { value: string }[] = [];
array.forEach((a) => {
result.push({ value: a[uniqueParam] });
if (Array.isArray(a.children)) {
result = result.concat(flat(a.children, uniqueParam));
}
});
return result;
};
function validatePlainObject(
config: ValidationConfig,
value: Record<string, unknown>,
@ -242,6 +252,7 @@ export function getExpectedType(config?: ValidationConfig): string | undefined {
}
return type;
case ValidationTypes.ARRAY:
case ValidationTypes.NESTED_OBJECT_ARRAY:
if (config.params?.allowedValues) {
const allowed = config.params?.allowedValues.join("' | '");
return `Array<'${allowed}'>`;
@ -626,6 +637,42 @@ export const VALIDATORS: Record<ValidationTypes, Validator> = {
}
return invalidResponse;
},
[ValidationTypes.NESTED_OBJECT_ARRAY]: (
config: ValidationConfig,
value: unknown,
props: Record<string, unknown>,
): ValidationResponse => {
let response: ValidationResponse = {
isValid: false,
parsed: config.params?.default || [],
message: `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
};
response = VALIDATORS.ARRAY(config, value, props);
if (!response.isValid) {
return response;
}
// Check if all values and children values are unique
if (config.params?.unique && response.parsed.length) {
if (isArray(config.params?.unique)) {
for (const param of config.params?.unique) {
const flattenedArray = flat(response.parsed, param);
const shouldBeUnique = flattenedArray.map((entry) =>
get(entry, param, ""),
);
if (uniq(shouldBeUnique).length !== flattenedArray.length) {
response = {
...response,
isValid: false,
message: `Array entry path:${param} must be unique. Duplicate values found`,
};
}
}
}
}
return response;
},
[ValidationTypes.DATE_ISO_STRING]: (
config: ValidationConfig,
value: unknown,

View File

@ -2,7 +2,7 @@
// Import this named export into your test file:
export const mockExecute = jest.fn().mockImplementation((src, data) => {
let finalSource = "let ";
Object.keys(data).forEach(key => {
Object.keys(data).forEach((key) => {
finalSource += ` ${key} = ${JSON.stringify(data[key])}, `;
});
finalSource = finalSource.substring(0, finalSource.length - 2) + ";";

View File

@ -4,7 +4,7 @@ import { WidgetProps } from "widgets/BaseWidget";
export const ContainerFactory = Factory.Sync.makeFactory<WidgetProps>({
backgroundColor: "#FFFFFF",
widgetName: Factory.each((i) => `Container${(i+1)}` ),
widgetName: Factory.each((i) => `Container${i + 1}`),
type: "CONTAINER_WIDGET",
containerStyle: "card",
isVisible: true,

View File

@ -3,37 +3,39 @@ import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const RadiogroupFactory = Factory.Sync.makeFactory<WidgetProps>({
rightColumn: 16,
topRow: 3,
bottomRow: 5,
parentRowSpace: 38,
isVisible: true,
label: "Test Radio",
type: "RADIO_GROUP_WIDGET",
isLoading: false,
defaultOptionValue: "1",
parentColumnSpace: 34.6875,
leftColumn: 12,
dynamicTriggerPathList: [{
key: "onSelectionChange"
}],
onSelectionChange: "{{navigateTo()}}",
options: [
rightColumn: 16,
topRow: 3,
bottomRow: 5,
parentRowSpace: 38,
isVisible: true,
label: "Test Radio",
type: "RADIO_GROUP_WIDGET",
isLoading: false,
defaultOptionValue: "1",
parentColumnSpace: 34.6875,
leftColumn: 12,
dynamicTriggerPathList: [
{
id: "1",
label: "jarvis",
value: "1"
key: "onSelectionChange",
},
],
onSelectionChange: "{{navigateTo()}}",
options: [
{
id: "1",
label: "jarvis",
value: "1",
},
{
id: "2",
label: "marvel",
value: "2"
id: "2",
label: "marvel",
value: "2",
},
{
label: "iron",
value: "4"
}
],
label: "iron",
value: "4",
},
],
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `RadioGroup${i + 1}`),
widgetId: generateReactKey(),

View File

@ -3,7 +3,7 @@ import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const TextFactory = Factory.Sync.makeFactory<WidgetProps>({
widgetName: Factory.each((i) => `Text${(i+1)}`),
widgetName: Factory.each((i) => `Text${i + 1}`),
rightColumn: 12,
onClick: "",
isDefaultClickDisabled: true,

View File

@ -4,10 +4,10 @@ import React from "react";
import { useSelector } from "react-redux";
import { mockGetCanvasWidgetDsl, useMockDsl } from "./testCommon";
export const MockCanvas = () => {
export function MockCanvas() {
const dsl = useSelector(mockGetCanvasWidgetDsl);
return <Canvas dsl={dsl}></Canvas>;
};
return <Canvas dsl={dsl} />;
}
export function UpdatedMainContainer({ dsl }: any) {
useMockDsl(dsl);
return <MainContainer />;

View File

@ -14789,7 +14789,7 @@ rc-resize-observer@^1.0.0:
rc-util "^5.0.0"
resize-observer-polyfill "^1.5.1"
rc-select@^12.1.10:
rc-select@^12.0.0, rc-select@^12.1.10:
version "12.1.13"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-12.1.13.tgz#c33560ccb9339d30695b52458f55efc35af35273"
integrity sha512-cPI+aesP6dgCAaey4t4upDbEukJe+XN0DK6oO/6flcCX5o28o7KNZD7JAiVtC/6fCwqwI/kSs7S/43dvHmBl+A==
@ -14802,6 +14802,28 @@ rc-select@^12.1.10:
rc-util "^5.9.8"
rc-virtual-list "^3.2.0"
rc-tree-select@^4.4.0-alpha.2:
version "4.4.0-alpha.2"
resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-4.4.0-alpha.2.tgz#b96019bd401084076bedac4e49ea50321709b273"
integrity sha512-8PdODhXpNK13z5u3P0uWb8+ghDtpkQ+ImhxgTXZxBj1KSSi0fB1Ey/mHFLP/R3r72vmwMRGkfkpbW2G6ZRtipw==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
rc-select "^12.0.0"
rc-tree "^4.0.0"
rc-util "^5.0.5"
rc-tree@^4.0.0:
version "4.2.2"
resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-4.2.2.tgz#4429187cbbfbecbe989714a607e3de8b3ab7763f"
integrity sha512-V1hkJt092VrOVjNyfj5IYbZKRMHxWihZarvA5hPL/eqm7o2+0SNkeidFYm7LVVBrAKBpOpa0l8xt04uiqOd+6w==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
rc-motion "^2.0.1"
rc-util "^5.0.0"
rc-virtual-list "^3.0.1"
rc-trigger@^5.0.4:
version "5.2.9"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.9.tgz#795a787d2b038347dcde27b89a4a5cec8fc40f3e"
@ -14813,7 +14835,7 @@ rc-trigger@^5.0.4:
rc-motion "^2.0.0"
rc-util "^5.5.0"
rc-util@^5.0.0, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.9.8:
rc-util@^5.0.0, rc-util@^5.0.5, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.9.8:
version "5.13.2"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.13.2.tgz#a8a0bb77743351841ba8bed6393e03b8d2f685c8"
integrity sha512-eYc71XXGlp96RMzg01Mhq/T3BL6OOVTDSS0urFEuvpi+e7slhJRhaHGCKy2hqJm18m9ff7VoRoptplKu60dYog==
@ -14822,7 +14844,7 @@ rc-util@^5.0.0, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0,
react-is "^16.12.0"
shallowequal "^1.1.0"
rc-virtual-list@^3.2.0:
rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.3.0.tgz#2f95a6ddbbf63d78b28662b57f1e69f7472762fe"
integrity sha512-lVXpGWC6yMdwV2SHo6kc63WlqjCnb3eO72V726KA2/wh9KA6wi/swcdR3zAowuA8hJxG/lRANmY5kpLZ+Pz3iQ==