PromucFlow_constructor/app/client/src/widgets/MultiSelectWidget/widget/index.tsx
Shivam kumar d2ed7f2309
chore: Move widget deprecation messages from propertyPaneView to individual Widget configurations (#33759)
@rajatagrawal 

## Description
- In this PR I have added **getEditorCallout** method which can also
used to return deprecataed messages.
- I have updated above method for these deprecated widgets:
  -   InputWidget
  -   DropdownWidget
  -   DatePickerWidget
  -   IconWidget
  -   FilePickerWidget
  -   MultiSelectWidget
  -   FormButtonWidget
  -   ProgressBarWidget
  -   CircularProgressWidget
  -   ListWidget

**OUTPUT:**
![Screenshot from 2024-05-23
17-08-08](https://github.com/zemoso-int/appsmith-from-the-business/assets/127818049/813adfaa-f6ee-446b-831a-2b96167d6b9f)
> [!TIP]  
> _Add a TL;DR when the description is longer than 500 words or
extremely technical (helps the content, marketing, and DevRel team)._
>
> _Please also include relevant motivation and context. List any
dependencies that are required for this change. Add links to Notion,
Figma or any other documents that might be relevant to the PR._


Fixes [26526](https://github.com/appsmithorg/appsmith/issues/26526)  
_or_  
Fixes `https://github.com/appsmithorg/appsmith/issues/26526`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

/ok-to-test tags=""

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!CAUTION]  
> If you modify the content in this section, you are likely to disrupt
the CI result for your PR.

<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No
2024-06-04 11:55:55 +05:30

645 lines
20 KiB
TypeScript

import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import type { ValidationResponse } from "constants/WidgetValidation";
import { ValidationTypes } from "constants/WidgetValidation";
import { isArray } from "lodash";
import React from "react";
import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
import BaseWidget from "widgets/BaseWidget";
import { Alignment } from "@blueprintjs/core";
import type {
AutocompletionDefinitions,
WidgetCallout,
} from "WidgetProvider/constants";
import { MinimumPopupWidthInPercentage } from "WidgetProvider/constants";
import { LabelPosition } from "components/constants";
import { Layers } from "constants/Layers";
import { WIDGET_TAGS, layoutConfigurations } from "constants/WidgetConstants";
import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants";
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import { buildDeprecationWidgetMessage } from "pages/Editor/utils";
import type { DraftValueType } from "rc-select/lib/Select";
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
import {
DefaultAutocompleteDefinitions,
isCompactMode,
} from "widgets/WidgetUtils";
import MultiSelectComponent from "../component";
import IconSVG from "../icon.svg";
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 MultiSelectWidget extends BaseWidget<
MultiSelectWidgetProps,
WidgetState
> {
static type = "MULTI_SELECT_WIDGET";
static getConfig() {
return {
name: "MultiSelect",
iconSVG: IconSVG,
needsMeta: true,
hideCard: true,
isDeprecated: true,
replacement: "MULTI_SELECT_WIDGET_V2",
tags: [WIDGET_TAGS.SELECT],
};
}
static getDefaults() {
return {
rows: 7,
columns: 20,
animateLoading: true,
labelText: "Label",
labelPosition: LabelPosition.Left,
labelAlignment: Alignment.LEFT,
labelWidth: 5,
options: [
{ label: "Blue", value: "BLUE" },
{ label: "Green", value: "GREEN" },
{ label: "Red", value: "RED" },
],
widgetName: "MultiSelect",
serverSideFiltering: false,
defaultOptionValue: ["GREEN"],
version: 1,
isRequired: false,
isDisabled: false,
placeholderText: "Select option(s)",
responsiveBehavior: ResponsiveBehavior.Fill,
minWidth: FILL_WIDGET_MIN_WIDTH,
};
}
static getMethods() {
return {
getEditorCallouts(): WidgetCallout[] {
return [
{
message: buildDeprecationWidgetMessage(
MultiSelectWidget.getConfig().name,
),
},
];
},
};
}
static getAutocompleteDefinitions(): AutocompletionDefinitions {
return {
"!doc":
"MultiSelect is used to capture user input/s from a specified list of permitted inputs. A MultiSelect captures multiple choices from a list of options",
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
isVisible: DefaultAutocompleteDefinitions.isVisible,
filterText: {
"!type": "string",
"!doc": "The filter text for Server side filtering",
},
selectedOptionValues: {
"!type": "[string]",
"!doc": "The array of values selected in a multi select dropdown",
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
},
selectedOptionLabels: {
"!type": "[string]",
"!doc":
"The array of selected option labels in a multi select dropdown",
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
},
isDisabled: "bool",
options: "[$__dropdownOption__$]",
};
}
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: '[{ "label": "Option1", "value": "Option2" }]',
isBindProperty: true,
isTriggerProperty: false,
isJSConvertible: false,
validation: {
type: ValidationTypes.ARRAY,
params: {
unique: ["value"],
children: {
type: ValidationTypes.OBJECT,
params: {
required: true,
allowedKeys: [
{
name: "label",
type: ValidationTypes.TEXT,
params: {
default: "",
required: true,
},
},
{
name: "value",
type: ValidationTypes.TEXT,
params: {
default: "",
},
},
],
},
},
},
},
evaluationSubstitutionType:
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Selects the option with value by default",
propertyName: "defaultOptionValue",
label: "Default value",
controlType: "INPUT_TEXT",
placeholderText: "[GREEN]",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.FUNCTION,
params: {
fn: defaultOptionValueValidation,
expected: {
type: "Array of values",
example: `['option1', 'option2']`,
autocompleteDataType: AutocompleteDataType.ARRAY,
},
},
},
},
{
helpText: "Sets a Placeholder Text",
propertyName: "placeholderText",
label: "Placeholder",
controlType: "INPUT_TEXT",
placeholderText: "Search",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "isRequired",
label: "Required",
helpText: "Makes input to the widget mandatory",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
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: "animateLoading",
label: "Animate loading",
controlType: "SWITCH",
helpText: "Controls the loading of the widget",
defaultValue: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
helpText: "Enables server side filtering of the data",
propertyName: "serverSideFiltering",
label: "Server side filtering",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
helpText:
"Controls the visibility of select all option in dropdown.",
propertyName: "allowSelectAll",
label: "Allow select all",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
{
sectionName: "Label",
children: [
{
helpText: "Sets the label text of the widget",
propertyName: "labelText",
label: "Text",
controlType: "INPUT_TEXT",
placeholderText: "Enter label text",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Sets the label position of the widget",
propertyName: "labelPosition",
label: "Position",
controlType: "DROP_DOWN",
options: [
{ label: "Left", value: LabelPosition.Left },
{ label: "Top", value: LabelPosition.Top },
{ label: "Auto", value: LabelPosition.Auto },
],
defaultValue: LabelPosition.Top,
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Sets the label alignment of the widget",
propertyName: "labelAlignment",
label: "Alignment",
controlType: "LABEL_ALIGNMENT_OPTIONS",
fullWidth: false,
options: [
{
startIcon: "align-left",
value: Alignment.LEFT,
},
{
startIcon: "align-right",
value: Alignment.RIGHT,
},
],
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
hidden: (props: MultiSelectWidgetProps) =>
props.labelPosition !== LabelPosition.Left,
dependencies: ["labelPosition"],
},
{
helpText:
"Sets the label width of the widget as the number of columns",
propertyName: "labelWidth",
label: "Width (in columns)",
controlType: "NUMERIC_INPUT",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
min: 0,
validation: {
type: ValidationTypes.NUMBER,
params: {
natural: true,
},
},
hidden: (props: MultiSelectWidgetProps) =>
props.labelPosition !== LabelPosition.Left,
dependencies: ["labelPosition"],
},
],
},
{
sectionName: "Styles",
children: [
{
propertyName: "accentColor",
label: "Accent color",
controlType: "COLOR_PICKER",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
invisible: true,
},
{
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",
defaultValue: "0.875rem",
options: [
{
label: "S",
value: "0.875rem",
subText: "0.875rem",
},
{
label: "M",
value: "1rem",
subText: "1rem",
},
{
label: "L",
value: "1.25rem",
subText: "1.25rem",
},
{
label: "XL",
value: "1.875rem",
subText: "1.875rem",
},
{
label: "XXL",
value: "3rem",
subText: "3rem",
},
{
label: "3XL",
value: "3.75rem",
subText: "3.75rem",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "labelStyle",
label: "Label Font Style",
controlType: "BUTTON_GROUP",
options: [
{
startIcon: "text-bold",
value: "BOLD",
},
{
startIcon: "text-italic",
value: "ITALIC",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
{
sectionName: "Actions",
children: [
{
helpText: "when a user selects an option",
propertyName: "onOptionChange",
label: "onOptionChange",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
{
helpText: "Trigger an action on change of filterText",
hidden: (props: MultiSelectWidgetProps) =>
!props.serverSideFiltering,
dependencies: ["serverSideFiltering"],
propertyName: "onFilterUpdate",
label: "onFilterUpdate",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
{
sectionName: "Styles",
children: [
{
propertyName: "backgroundColor",
helpText: "Sets the background color of the widget",
label: "Background color",
controlType: "COLOR_PICKER",
isBindProperty: false,
isTriggerProperty: false,
},
],
},
];
}
static getDerivedPropertiesMap() {
return {
selectedIndexArr: `{{ this.selectedOptionValues.map(o => _.findIndex(this.options, { value: o })) }}`,
selectedOptionLabels: `{{ this.selectedOptionValueArr.map((o) => { const index = _.findIndex(this.options, { value: o }); return this.options[index]?.label ?? this.options[index]?.value; }) }}`,
selectedOptionValues: `{{ this.selectedOptionValueArr.filter((o) => { const index = _.findIndex(this.options, { value: o }); return index > -1; }) }}`,
isValid: `{{this.isRequired ? !!this.selectedIndexArr && this.selectedIndexArr.length > 0 : true}}`,
};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {
selectedOptionValueArr: "defaultOptionValue",
filterText: "",
};
}
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedOptionValueArr: undefined,
filterText: "",
};
}
static getStylesheetConfig(): Stylesheet {
return {
accentColor: "{{appsmith.theme.colors.primaryColor}}",
borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
boxShadow: "none",
};
}
static getSetterConfig(): SetterConfig {
return {
__setters: {
setVisibility: {
path: "isVisible",
type: "boolean",
},
setDisabled: {
path: "isDisabled",
type: "boolean",
},
setRequired: {
path: "isRequired",
type: "boolean",
},
setSelectedOptions: {
path: "defaultOptionValue",
type: "array",
accessor: "selectedOptionValues",
},
},
};
}
getWidgetView() {
const options = isArray(this.props.options) ? this.props.options : [];
const values: string[] = isArray(this.props.selectedOptionValues)
? this.props.selectedOptionValues
: [];
const dropDownWidth =
(MinimumPopupWidthInPercentage / 100) *
(this.props.mainCanvasWidth ?? layoutConfigurations.MOBILE.maxWidth);
const { componentHeight, componentWidth } = this.props;
return (
<MultiSelectComponent
accentColor={this.props.accentColor}
allowSelectAll={this.props.allowSelectAll}
borderRadius={this.props.borderRadius}
boxShadow={this.props.boxShadow}
compactMode={isCompactMode(componentHeight)}
disabled={this.props.isDisabled ?? false}
dropDownWidth={dropDownWidth}
dropdownStyle={{
zIndex: Layers.dropdownModalWidget,
}}
isValid={this.props.isValid}
labelAlignment={this.props.labelAlignment}
labelPosition={this.props.labelPosition}
labelStyle={this.props.labelStyle}
labelText={this.props.labelText}
labelTextColor={this.props.labelTextColor}
labelTextSize={this.props.labelTextSize}
labelWidth={this.props.labelComponentWidth}
loading={this.props.isLoading}
onChange={this.onOptionChange}
onFilterChange={this.onFilterChange}
options={options}
placeholder={this.props.placeholderText as string}
serverSideFiltering={this.props.serverSideFiltering}
value={values}
widgetId={this.props.widgetId}
width={componentWidth}
/>
);
}
onOptionChange = (value: DraftValueType) => {
this.props.updateWidgetMetaProperty("selectedOptionValueArr", value, {
triggerPropertyName: "onOptionChange",
dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
// Empty filter after Selection
this.onFilterChange("");
};
onFilterChange = (value: string) => {
this.props.updateWidgetMetaProperty("filterText", value);
if (this.props.onFilterUpdate) {
super.executeAction({
triggerPropertyName: "onFilterUpdate",
dynamicString: this.props.onFilterUpdate,
event: {
type: EventType.ON_FILTER_UPDATE,
},
});
}
};
}
export interface DropdownOption {
label: string;
value: string;
disabled?: boolean;
}
export interface MultiSelectWidgetProps extends WidgetProps {
placeholderText?: string;
selectedIndex?: number;
selectedIndexArr?: number[];
selectedOption: DropdownOption;
options?: DropdownOption[];
onOptionChange: string;
onFilterChange: string;
defaultOptionValue: string | string[];
isRequired: boolean;
isLoading: boolean;
selectedOptionValueArr: string[];
filterText: string;
selectedOptionValues: string[];
selectedOptionLabels: string[];
serverSideFiltering: boolean;
onFilterUpdate: string;
backgroundColor: string;
borderRadius: string;
boxShadow?: string;
accentColor: string;
allowSelectAll?: boolean;
labelText: string;
labelPosition?: LabelPosition;
labelAlignment?: Alignment;
labelWidth?: number;
labelComponentWidth?: number;
}
export default MultiSelectWidget;