Skip to content

Commit

Permalink
feat: add select widget (#35849)
Browse files Browse the repository at this point in the history
## Description


https://github.com/user-attachments/assets/7516bcd8-7746-485a-a49d-bd19b22833d0




Fixes #35824

> [!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="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/10663955895>
> Commit: 58d40f7
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10663955895&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 02 Sep 2024 10:15:26 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
- Introduced the `WDSSelectWidget`, enhancing widget options within the
application.
- Added configuration files to support autocomplete, validation, and
property management for the new select widget.
- Added new icons and thumbnails for input fields, improving UI
component availability.

- **Improvements**
  - Streamlined the `Select` component for better usability and clarity.
- Enhanced type definitions for better type safety and clarity in widget
configuration.
- Improved SVG structures for icons and thumbnails, enhancing visual
representation.
- Adjusted styles for the `Select` component, ensuring consistent sizing
and improved icon alignment.

- **Documentation**
- Added central export for configuration modules, simplifying access to
widget settings.
- Updated stories to include new icons and thumbnails, enriching
documentation resources.

- **Bug Fixes**
- Improved validation logic for default options and custom options to
ensure data integrity.

- **Chores**
- Reorganized import statements for better readability and
maintainability across widget files.
  - Updated TypeScript configuration for improved module handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: vadim <[email protected]>
Co-authored-by: Pawan Kumar <[email protected]>
  • Loading branch information
3 people authored Sep 2, 2024
1 parent e501413 commit bd8c0de
Show file tree
Hide file tree
Showing 26 changed files with 1,029 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React, { useRef } from "react";
import clsx from "clsx";
import { Icon, Label, Popover, Spinner, Text } from "@appsmith/wds";
import { getTypographyClassName } from "@appsmith/wds-theming";
import clsx from "clsx";
import React, { useRef } from "react";
import {
Button,
ListBox,
FieldError,
Select as HeadlessSelect,
ListBox,
SelectValue,
FieldError,
} from "react-aria-components";
import { Text, Icon, Spinner, Popover, Label } from "@appsmith/wds";
import { ListBoxItem } from "./ListBoxItem";
import styles from "./styles.module.css";
import type { SelectProps } from "./types";

export const Select = <T extends object>(props: SelectProps<T>) => {
export const Select = (props: SelectProps) => {
const {
contextualHelp,
description,
Expand Down Expand Up @@ -53,7 +53,15 @@ export const Select = <T extends object>(props: SelectProps<T>) => {
styles.fieldValue,
getTypographyClassName("body"),
)}
/>
>
{({ defaultChildren, isPlaceholder }) => {
if (isPlaceholder) {
return props.placeholder;
}

return defaultChildren;
}}
</SelectValue>
{!Boolean(isLoading) && <Icon name="chevron-down" />}
{Boolean(isLoading) && <Spinner />}
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./Select";
export type { SelectProps } from "./types";
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
display: flex;
position: relative;
padding: 0;
height: var(--sizing-9);
border: none;
align-items: center;
border-radius: var(--border-radius-elevation-3);
Expand Down Expand Up @@ -43,7 +44,7 @@

.textField [data-icon] {
position: absolute;
right: var(--inner-spacing-2);
right: var(--inner-spacing-1);
}

.necessityIndicator {
Expand All @@ -66,6 +67,10 @@
flex: 1;
}

.fieldValue[data-placeholder] {
color: var(--color-fg-neutral-subtle);
}

.fieldValue [data-icon] {
display: none;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type {
} from "react-aria-components";
import type { IconProps, SIZES } from "@appsmith/wds";

export interface SelectProps<T extends object>
extends Omit<SpectrumSelectProps<T>, "slot"> {
export interface SelectProps
extends Omit<SpectrumSelectProps<SelectItem>, "slot"> {
/** Item objects in the collection. */
items: Iterable<SelectItem>;
items: SelectItem[];
/** The content to display as the label. */
label?: string;
/** The content to display as the description. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ export function KeyValueComponent(props: KeyValueComponentProps) {
/>
<StyledBox />
<Button
// At least one pair must be present
isDisabled={renderPairs.length <= 1}
isIconButton
kind="tertiary"
onClick={(e: React.MouseEvent) => {
Expand Down
14 changes: 10 additions & 4 deletions app/client/src/constants/PropertyControlConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,18 @@ export interface PanelConfig {
}

export interface PropertyPaneControlConfig {
// unique id to identify the property. It is added automatically with generateReactKey()
id?: string;
// label is used to display the name of the property
label: string;
// unique name of the property
propertyName: string;
// Serves in the tooltip
helpText?: string;
//Dynamic text serves below the property pane inputs
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
helperText?: ((props: any) => React.ReactNode) | React.ReactNode;
// Dynamic text serves below the property pane inputs
helperText?: ((props: unknown) => React.ReactNode) | React.ReactNode;
// used to tell if the property is a JS convertible property.
// If true, It will show the little JS icon button next to the property name
isJSConvertible?: boolean;
customJSControl?: string;
controlType: ControlType;
Expand All @@ -80,6 +83,7 @@ export interface PropertyPaneControlConfig {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
props: any,
) => UpdateWidgetPropertyPayload[];
// Function that is called when the property is updated, it is mainly used to update other properties
updateHook?: (
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -101,10 +105,12 @@ export interface PropertyPaneControlConfig {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
additionalAutoComplete?: (props: any) => AdditionalDynamicDataTree;
evaluationSubstitutionType?: EvaluationSubstitutionType;
// all the properties that current property is dependent on. All the properties passed here comes into widgetProperties
dependencies?: string[];
dynamicDependencies?: (widget: WidgetProps) => string[];
evaluatedDependencies?: string[]; // dependencies to be picked from the __evaluated__ object
expected?: CodeEditorExpected;
// Used to get value of the property from stylesheet config. Used in app theming v1 ( Not needed in anvil )
getStylesheetValue?: (
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
20 changes: 20 additions & 0 deletions app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "utils/WidgetSizeUtils";
import { klona } from "klona";
import type { UpdateCanvasPayload } from "actions/pageActions";
import type { SetWidgetDynamicPropertyPayload } from "../../actions/controlActions";

/* This type is an object whose keys are widgetIds and values are arrays with property paths
and property values
Expand Down Expand Up @@ -138,6 +139,25 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
[ReduxActionTypes.RESET_EDITOR_REQUEST]: () => {
return klona(initialState);
},
[ReduxActionTypes.SET_WIDGET_DYNAMIC_PROPERTY]: (
state: CanvasWidgetsReduxState,
action: ReduxAction<SetWidgetDynamicPropertyPayload>,
) => {
const { isDynamic, propertyPath, widgetId } = action.payload;
const widget = state[widgetId];

// When options JS mode is disabled, reset the optionLabel and optionValue to standard values
if (
widget.type === "WDS_SELECT_WIDGET" &&
propertyPath === "options" &&
!isDynamic
) {
set(state, `${widgetId}.optionLabel`, "label");
set(state, `${widgetId}.optionValue`, "value");
}

return state;
},
});

export interface CanvasWidgetsReduxState {
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/widgets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { WDSEmailInputWidget } from "./wds/WDSEmailInputWidget";
import { WDSPasswordInputWidget } from "./wds/WDSPasswordInputWidget";
import { WDSNumberInputWidget } from "./wds/WDSNumberInputWidget";
import { WDSMultilineInputWidget } from "./wds/WDSMultilineInputWidget";
import { WDSSelectWidget } from "./wds/WDSSelectWidget";

const LegacyWidgets = [
CanvasWidget,
Expand Down Expand Up @@ -181,6 +182,7 @@ const WDSWidgets = [
WDSPasswordInputWidget,
WDSNumberInputWidget,
WDSMultilineInputWidget,
WDSSelectWidget,
];

const Widgets = [
Expand Down
11 changes: 11 additions & 0 deletions app/client/src/widgets/wds/WDSSelectWidget/config/anvilConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { AnvilConfig } from "WidgetProvider/constants";

export const anvilConfig: AnvilConfig = {
isLargeWidget: false,
widgetSize: {
minWidth: {
base: "100%",
"180px": "sizing-30",
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";

export const autocompleteConfig = {
"!doc":
"Select widget lets the user choose one option from a dropdown list. It is similar to a SingleSelect Dropdown in its functionality",
"!url": "https://docs.appsmith.com/widget-reference/radio",
isVisible: DefaultAutocompleteDefinitions.isVisible,
options: "[$__dropdownOption__$]",
selectedOptionValue: "string",
isRequired: "bool",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import type { WidgetDefaultProps } from "WidgetProvider/constants";

export const defaultsConfig = {
animateLoading: true,
label: "Label",
options: [
{ label: "Option 1", value: "1" },
{ label: "Option 2", value: "2" },
{ label: "Option 3", value: "3" },
],
defaultOptionValue: "",
isRequired: false,
isDisabled: false,
isVisible: true,
isInline: false,
widgetName: "Select",
version: 1,
responsiveBehavior: ResponsiveBehavior.Fill,
} as unknown as WidgetDefaultProps;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const featuresConfig = {
dynamicHeight: {
sectionIndex: 3,
active: true,
},
};
7 changes: 7 additions & 0 deletions app/client/src/widgets/wds/WDSSelectWidget/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from "./propertyPaneConfig";
export { metaConfig } from "./metaConfig";
export { anvilConfig } from "./anvilConfig";
export { defaultsConfig } from "./defaultsConfig";
export { settersConfig } from "./settersConfig";
export { methodsConfig } from "./methodsConfig";
export { autocompleteConfig } from "./autocompleteConfig";
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { WIDGET_TAGS } from "constants/WidgetConstants";

export const metaConfig = {
name: "Select",
tags: [WIDGET_TAGS.SELECT],
needsMeta: true,
searchTags: ["choice", "option", "choose", "pick", "select", "dropdown"],
};
21 changes: 21 additions & 0 deletions app/client/src/widgets/wds/WDSSelectWidget/config/methodsConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type {
PropertyUpdates,
SnipingModeProperty,
} from "WidgetProvider/constants";
import { RadioGroupIcon, SelectThumbnail } from "appsmith-icons";

export const methodsConfig = {
getSnipingModeUpdates: (
propValueMap: SnipingModeProperty,
): PropertyUpdates[] => {
return [
{
propertyPath: "options",
propertyValue: propValueMap.data,
isDynamicPropertyPath: true,
},
];
},
IconCmp: RadioGroupIcon,
ThumbnailCmp: SelectThumbnail,
};
Loading

0 comments on commit bd8c0de

Please sign in to comment.