feat: RTL support for input, select and multi-select widget (#28522)

#### PR fixes following issue(s)
Fixes https://github.com/appsmithorg/appsmith/issues/28469

#### Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video
>
>
#### Type of change
> Please delete options that are not relevant.
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- Chore (housekeeping or task changes that don't impact user perception)
- This change requires a documentation update
>
>
>
## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [x] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
balajisoundar 2023-11-03 16:18:13 +05:30 committed by GitHub
parent 0ec86d92ad
commit dcaa46050d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 477 additions and 64 deletions

View File

@ -0,0 +1,51 @@
import { featureFlagIntercept } from "../../../../../support/Objects/FeatureFlags";
import {
agHelper,
draggableWidgets,
entityExplorer,
propPane,
} from "../../../../../support/Objects/ObjectsCore";
describe("Select Widget", () => {
before(() => {
entityExplorer.DragNDropWidget(draggableWidgets.INPUT_V2);
});
it("1. Test RTL support", () => {
featureFlagIntercept({
license_widget_rtl_support_enabled: false,
});
agHelper.AssertElementAbsence(".t--property-control-enablertl");
featureFlagIntercept({
license_widget_rtl_support_enabled: true,
});
agHelper.RefreshPage();
agHelper.Sleep(2000);
agHelper.AssertElementExist(".t--property-control-enablertl");
propPane.TogglePropertyState("Enable RTL", "On");
agHelper.AssertElementExist(
".t--widget-inputwidgetv2 .bp3-input-group.rtl",
);
agHelper.AssertElementExist(
".t--widget-inputwidgetv2 .label-container[dir='rtl']",
);
propPane.TogglePropertyState("Enable RTL", "Off");
agHelper.AssertElementAbsence(
".t--widget-inputwidgetv2 .bp3-input-group.rtl",
);
agHelper.AssertElementAbsence(
".t--widget-inputwidgetv2 .label-container[dir='rtl']",
);
});
});

View File

@ -0,0 +1,71 @@
import { featureFlagIntercept } from "../../../../../support/Objects/FeatureFlags";
import {
agHelper,
draggableWidgets,
entityExplorer,
propPane,
} from "../../../../../support/Objects/ObjectsCore";
describe("Select Widget", () => {
before(() => {
entityExplorer.DragNDropWidget(draggableWidgets.MULTISELECT);
});
it("1. Test RTL support", () => {
featureFlagIntercept({
license_widget_rtl_support_enabled: false,
});
agHelper.AssertElementAbsence(".t--property-control-enablertl");
featureFlagIntercept({
license_widget_rtl_support_enabled: true,
});
agHelper.RefreshPage();
agHelper.Sleep(2000);
agHelper.AssertElementExist(".t--property-control-enablertl");
propPane.TogglePropertyState("Enable RTL", "On");
agHelper
.GetElement(".t--widget-multiselectwidgetv2 .rc-select-selector")
.click();
agHelper
.GetElement(
".t--widget-multiselectwidgetv2 [data-testid='multiselect-container']",
)
.should("have.css", "direction", "rtl");
agHelper
.GetElement(".t--widget-multiselectwidgetv2 .rc-select-selector")
.should("have.css", "direction", "rtl");
agHelper
.GetElement(".multi-select-dropdown input.bp3-input")
.should("have.css", "direction", "rtl");
agHelper.GetElement(".rc-select-dropdown [dir='rtl']").should("exist");
propPane.TogglePropertyState("Enable RTL", "Off");
agHelper
.GetElement(
".t--widget-multiselectwidgetv2 [data-testid='multiselect-container']",
)
.should("have.css", "direction", "ltr");
agHelper
.GetElement(".t--widget-multiselectwidgetv2 .rc-select-selector")
.should("have.css", "direction", "ltr");
agHelper
.GetElement(".multi-select-dropdown input.bp3-input")
.should("have.css", "direction", "ltr");
agHelper.GetElement(".rc-select-dropdown [dir='rtl']").should("not.exist");
});
});

View File

@ -0,0 +1,69 @@
import { featureFlagIntercept } from "../../../../../support/Objects/FeatureFlags";
import {
agHelper,
draggableWidgets,
entityExplorer,
propPane,
} from "../../../../../support/Objects/ObjectsCore";
describe("Select Widget", () => {
before(() => {
entityExplorer.DragNDropWidget(draggableWidgets.SELECT);
});
it("1. Test RTL support", () => {
featureFlagIntercept({
license_widget_rtl_support_enabled: false,
});
agHelper.AssertElementAbsence(".t--property-control-enablertl");
featureFlagIntercept({
license_widget_rtl_support_enabled: true,
});
agHelper.RefreshPage();
agHelper.Sleep(2000);
agHelper.AssertElementExist(".t--property-control-enablertl");
propPane.TogglePropertyState("Enable RTL", "On");
agHelper.GetElement(".t--widget-selectwidget .select-button").click();
agHelper
.GetElement(".t--widget-selectwidget .label-container")
.should("have.css", "direction", "rtl");
agHelper
.GetElement(".t--widget-selectwidget .select-button")
.should("have.css", "direction", "rtl");
agHelper
.GetElement(".select-popover-wrapper input.bp3-input")
.should("have.css", "direction", "rtl");
agHelper
.GetElement(".select-popover-wrapper .menu-virtual-list")
.should("have.css", "direction", "rtl");
propPane.TogglePropertyState("Enable RTL", "Off");
agHelper
.GetElement(".t--widget-selectwidget .label-container")
.should("have.css", "direction", "ltr");
agHelper
.GetElement(".t--widget-selectwidget .select-button")
.should("have.css", "direction", "ltr");
agHelper
.GetElement(".select-popover-wrapper input.bp3-input")
.should("have.css", "direction", "ltr");
agHelper
.GetElement(".select-popover-wrapper .menu-virtual-list")
.should("have.css", "direction", "ltr");
});
});

View File

@ -28,6 +28,7 @@ export const FEATURE_FLAG = {
ab_show_templates_instead_of_blank_canvas_enabled:
"ab_show_templates_instead_of_blank_canvas_enabled",
release_app_sidebar_enabled: "release_app_sidebar_enabled",
license_widget_rtl_support_enabled: "license_widget_rtl_support_enabled",
} as const;
export type FeatureFlag = keyof typeof FEATURE_FLAG;
@ -56,6 +57,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
release_anvil_enabled: false,
ab_show_templates_instead_of_blank_canvas_enabled: false,
release_app_sidebar_enabled: false,
license_widget_rtl_support_enabled: false,
};
export const AB_TESTING_EVENT_KEYS = {

View File

@ -101,6 +101,38 @@ const InputComponentWrapper = styled((props) => (
line-height: 16px;
}
}
${(props) =>
props.inputType === "PASSWORD" &&
`
.password-input {
height: 100%;
width: 36px;
cursor: pointer;
color:
${
props.disabled
? "var(--wds-color-icon-disabled)"
: "var(--wds-color-icon)"
};
justify-content: center;
height: 100%;
svg {
width: 20px;
height: 20px;
}
&:hover {
background-color: var(--wds-color-bg-hover);
}
}
`}
&.rtl {
input {
direction: rtl;
}
}
}
&&&& {
@ -207,34 +239,6 @@ const InputComponentWrapper = styled((props) => (
return "var(--wds-color-text-light)";
}};
}
${(props) =>
props.inputType === "PASSWORD" &&
`
& + .bp3-input-action {
height: 100%;
width: 36px;
cursor: pointer;
.password-input {
color:
${
props.disabled
? "var(--wds-color-icon-disabled)"
: "var(--wds-color-icon)"
};
justify-content: center;
height: 100%;
svg {
width: 20px;
height: 20px;
}
&:hover {
background-color: var(--wds-color-bg-hover);
}
}
}
`}
}
& .${Classes.INPUT_GROUP} {
@ -333,6 +337,12 @@ const StyledNumericInput = styled(NumericInput)`
}
}
}
&.rtl {
input {
direction: rtl;
}
}
`;
const TextInputWrapper = styled.div<{
@ -471,13 +481,6 @@ class BaseInputComponent extends React.Component<
this.props.onValueChange(valueAsString);
};
getLeftIcon = () => {
if (this.props.iconName && this.props.iconAlign === "left") {
return this.props.iconName;
}
return this.props.leftIcon;
};
getType(inputType: InputHTMLType = "TEXT") {
switch (inputType) {
case "PASSWORD":
@ -534,7 +537,10 @@ class BaseInputComponent extends React.Component<
allowNumericCharactersOnly
autoFocus={this.props.autoFocus}
buttonPosition={this.props.buttonPosition}
className={this.props.isLoading ? "bp3-skeleton" : Classes.FILL}
className={
(this.props.isLoading ? "bp3-skeleton" : Classes.FILL) +
(this.props.rtl ? " rtl" : "")
}
disabled={this.props.disabled}
inputRef={(el) => {
if (this.props.inputRef && el) {
@ -564,6 +570,7 @@ class BaseInputComponent extends React.Component<
autoFocus={this.props.autoFocus}
autoResize={!!this.props.isDynamicHeightEnabled}
className={this.props.isLoading ? "bp3-skeleton" : ""}
dir={this.props.rtl ? "rtl" : "ltr"}
disabled={this.props.disabled}
maxLength={this.props.maxChars}
onBlur={() => this.setFocusState(false)}
@ -585,15 +592,14 @@ class BaseInputComponent extends React.Component<
<InputGroup
autoComplete={this.props.autoComplete}
autoFocus={this.props.autoFocus}
className={this.props.isLoading ? "bp3-skeleton" : ""}
className={
(this.props.isLoading ? "bp3-skeleton" : "") +
(this.props.rtl ? " rtl" : "")
}
disabled={this.props.disabled}
inputRef={this.props.inputRef as IRef<HTMLInputElement>}
intent={this.props.intent}
leftIcon={
this.props.iconName && this.props.iconAlign === "left"
? this.props.iconName
: this.props.leftIcon
}
leftIcon={this.getLeftIcon()}
maxLength={this.props.maxChars}
onBlur={() => this.setFocusState(false)}
onChange={this.onTextChange}
@ -601,24 +607,47 @@ class BaseInputComponent extends React.Component<
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
placeholder={this.props.placeholder}
rightElement={
this.props.inputType === "PASSWORD" ? (
<Icon
className="password-input"
name={this.state.showPassword ? "eye-off" : "eye-on"}
onClick={() => {
this.setState({ showPassword: !this.state.showPassword });
}}
/>
) : this.props.iconName && this.props.iconAlign === "right" ? (
<Tag icon={this.props.iconName} />
) : undefined
}
rightElement={this.getRightIcon()}
spellCheck={this.props.spellCheck}
type={this.getType(this.props.inputHTMLType)}
value={this.props.value}
/>
);
private getLeftIcon = () => {
if (this.props.inputType === "PASSWORD" && this.props.rtl) {
return (
<Icon
className="password-input"
name={this.state.showPassword ? "eye-off" : "eye-on"}
onClick={() => {
this.setState({ showPassword: !this.state.showPassword });
}}
/>
);
} else if (this.props.iconName && this.props.iconAlign === "left") {
return this.props.iconName;
} else {
return this.props.leftIcon;
}
};
private getRightIcon = () => {
if (this.props.inputType === "PASSWORD" && !this.props.rtl) {
return (
<Icon
className="password-input"
name={this.state.showPassword ? "eye-off" : "eye-on"}
onClick={() => {
this.setState({ showPassword: !this.state.showPassword });
}}
/>
);
} else if (this.props.iconName && this.props.iconAlign === "right") {
return <Tag icon={this.props.iconName} />;
}
};
private renderInputComponent = (
inputHTMLType: InputHTMLType = "TEXT",
isTextArea: boolean,
@ -693,6 +722,7 @@ class BaseInputComponent extends React.Component<
isDynamicHeightEnabled={isDynamicHeightEnabled}
loading={isLoading}
position={labelPosition}
rtl={this.props.rtl}
text={label}
width={labelWidth}
/>
@ -789,6 +819,7 @@ export interface BaseInputComponentProps extends ComponentProps {
errorTooltipBoundary?: string;
shouldUseLocale?: boolean;
buttonPosition?: NumberInputStepButtonPosition;
rtl?: boolean;
}
export default BaseInputComponent;

View File

@ -22,6 +22,7 @@ import type {
WidgetBaseConfiguration,
WidgetDefaultProps,
} from "WidgetProvider/constants";
import type { PropertyPaneConfig } from "constants/PropertyControlConstants";
class BaseInputWidget<
T extends BaseInputWidgetProps,
@ -66,7 +67,9 @@ class BaseInputWidget<
};
}
static getPropertyPaneContentConfig() {
static getPropertyPaneContentConfig(
generalProperties: PropertyPaneConfig[] = [],
) {
return [
{
sectionName: "Label",
@ -317,6 +320,7 @@ class BaseInputWidget<
return props.type !== "PHONE_INPUT_WIDGET";
},
},
...generalProperties,
],
},
{

View File

@ -76,6 +76,7 @@ class InputComponent extends React.Component<InputComponentProps> {
onKeyDown={this.props.onKeyDown}
onValueChange={this.props.onValueChange}
placeholder={this.props.placeholder}
rtl={this.props.rtl}
showError={this.props.showError}
spellCheck={this.props.spellCheck}
stepSize={1}
@ -96,6 +97,7 @@ export interface InputComponentProps extends BaseInputComponentProps {
boxShadow?: string;
accentColor?: string;
autoComplete?: string;
rtl?: boolean;
}
export default InputComponent;

View File

@ -49,6 +49,7 @@ import type {
} from "WidgetProvider/constants";
import { WIDGET_TAGS } from "constants/WidgetConstants";
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
export function defaultValueValidation(
value: any,
@ -536,7 +537,23 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
],
},
],
super.getPropertyPaneContentConfig(),
super.getPropertyPaneContentConfig([
{
propertyName: "rtl",
label: "Enable RTL",
helpText: "Enables right to left text direction",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
hidden: () => {
return !super.getFeatureFlag(
FEATURE_FLAG.license_widget_rtl_support_enabled,
);
},
},
]),
);
}
@ -850,6 +867,7 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
onKeyDown={this.handleKeyDown}
onValueChange={this.onValueChange}
placeholder={this.props.placeholderText}
rtl={this.props.rtl}
showError={!!this.props.isFocused}
spellCheck={!!this.props.isSpellCheck}
stepSize={1}
@ -869,6 +887,7 @@ export interface InputWidgetProps extends BaseInputWidgetProps {
maxNum?: number;
minNum?: number;
inputText: string;
rtl?: boolean;
}
export default InputWidget;

View File

@ -391,6 +391,7 @@ export const MultiSelectContainer = styled.div<{
borderRadius: string;
boxShadow?: string;
accentColor?: string;
rtl?: boolean;
}>`
${labelLayoutStyles}
@ -674,7 +675,60 @@ export const MultiSelectContainer = styled.div<{
: `border: 1px solid var(--wds-color-border-danger); box-shadow: 0px 0px 0px 2px var(--wds-color-border-danger-focus-light) !important;`}
}
}
${(props) =>
props.rtl &&
`
&&& {
direction: rtl;
.rc-select .rc-select-selector {
padding-right: 10px;
padding-left: 36px;
}
.rc-select-arrow {
right: auto;
left: 0;
}
}
`}
`;
export const RTLStyles = createGlobalStyle<{ dropdownContainer: string }>`
${(props) => `
.${props.dropdownContainer} {
&&& {
.rc-select-item {
.rc-select-item-option-state {
.bp3-checkbox {
right: -13px;
padding-left: 24px;
}
}
}
label.all-options .bp3-control-indicator {
margin-left: 12px;
margin-right: 13px !important;
}
.bp3-input-group {
.bp3-icon-search {
left: auto;
right: 0px;
}
input {
padding-right: 34px;
padding-left: 8px !important;
}
}
}
}
`}
`;
export const StyledCheckbox = styled(Checkbox)<{
accentColor?: string;
}>`

View File

@ -15,6 +15,7 @@ import MenuItemCheckBox, {
MultiSelectContainer,
StyledCheckbox,
InputContainer,
RTLStyles,
} from "./index.styled";
import type { RenderMode, TextSize } from "constants/WidgetConstants";
import type { Alignment } from "@blueprintjs/core";
@ -68,6 +69,7 @@ export interface MultiSelectProps
onDropdownClose?: () => void;
renderMode?: RenderMode;
isDynamicHeightEnabled?: boolean;
rtl?: boolean;
}
const DEBOUNCE_TIMEOUT = 1000;
@ -101,6 +103,7 @@ function MultiSelectComponent({
options,
placeholder,
renderMode,
rtl,
serverSideFiltering,
value,
widgetId,
@ -238,7 +241,7 @@ function MultiSelectComponent({
(
menu: React.ReactElement<any, string | React.JSXElementConstructor<any>>,
) => (
<>
<div dir={rtl ? "rtl" : "ltr"}>
<BackDrop />
{isFilterable ? (
<InputGroup
@ -267,7 +270,7 @@ function MultiSelectComponent({
) : null}
{menu}
</div>
</>
</div>
),
[
isSelectAll,
@ -277,6 +280,7 @@ function MultiSelectComponent({
isFilterable,
filter,
onQueryChange,
rtl,
],
);
@ -290,7 +294,11 @@ function MultiSelectComponent({
isValid={isValid}
labelPosition={labelPosition}
ref={_menu as React.RefObject<HTMLDivElement>}
rtl={rtl}
>
{rtl ? (
<RTLStyles dropdownContainer={`multi-select-dropdown-${widgetId}`} />
) : null}
<DropdownStyles
accentColor={accentColor}
borderRadius={borderRadius}
@ -325,7 +333,7 @@ function MultiSelectComponent({
// autoFocus
defaultActiveFirstOption={false}
disabled={disabled}
dropdownClassName={`multi-select-dropdown multiselect-popover-width-${widgetId}`}
dropdownClassName={`multi-select-dropdown multiselect-popover-width-${widgetId} multi-select-dropdown-${widgetId}`}
dropdownRender={dropdownRender}
dropdownStyle={dropdownStyle}
getPopupContainer={getPopupContainer}

View File

@ -47,6 +47,7 @@ import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import { DynamicHeight } from "utils/WidgetFeatures";
import IconSVG from "../icon.svg";
import { WIDGET_TAGS, layoutConfigurations } from "constants/WidgetConstants";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
class MultiSelectWidget extends BaseWidget<
MultiSelectWidgetProps,
@ -551,6 +552,21 @@ class MultiSelectWidget extends BaseWidget<
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "rtl",
label: "Enable RTL",
helpText: "Enables right to left text direction",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
hidden: () => {
return !super.getFeatureFlag(
FEATURE_FLAG.license_widget_rtl_support_enabled,
);
},
},
],
},
{
@ -838,6 +854,7 @@ class MultiSelectWidget extends BaseWidget<
options={options}
placeholder={this.props.placeholderText as string}
renderMode={this.props.renderMode}
rtl={this.props.rtl}
serverSideFiltering={this.props.serverSideFiltering}
value={values}
widgetId={this.props.widgetId}
@ -945,6 +962,7 @@ export interface MultiSelectWidgetProps extends WidgetProps {
labelWidth?: number;
isDirty?: boolean;
labelComponentWidth?: number;
rtl?: boolean;
}
export default MultiSelectWidget;

View File

@ -5,6 +5,7 @@ import { Colors } from "constants/Colors";
import { isEmptyOrNill } from "../../../utils/helpers";
import { StyledDiv } from "./index.styled";
import { CLASSNAMES } from "../constants";
export interface SelectButtonProps {
disabled?: boolean;
@ -31,7 +32,7 @@ function SelectButton(props: SelectButtonProps) {
return (
<Button
className="select-button"
className={CLASSNAMES.selectButton}
data-testid="selectbutton.btn.main"
disabled={disabled}
onClick={togglePopoverVisibility}

View File

@ -14,6 +14,7 @@ import {
} from "widgets/components/LabelWithTooltip";
import { lightenColor } from "widgets/WidgetUtils";
import { CommonSelectFilterStyle } from "widgets/MultiSelectWidgetV2/component/index.styled";
import { CLASSNAMES } from "../constants";
export const StyledDiv = styled.div`
display: flex;
@ -220,6 +221,8 @@ ${({ dropDownWidth, id }) => `
export const DropdownContainer = styled.div<{
compactMode: boolean;
labelPosition?: LabelPosition;
dropdownPopoverContainer?: string;
rtl?: boolean;
}>`
${BlueprintCSSTransform}
${labelLayoutStyles}
@ -247,6 +250,49 @@ export const DropdownContainer = styled.div<{
}};
}
}
${(props) =>
props.rtl
? `
.${LABEL_CONTAINER_CLASS} {
direction: rtl;
}
.${CLASSNAMES.selectButton} {
direction: rtl;
.bp3-button-text {
display: block;
direction: rtl;
text-align: right;
margin: 0;
}
}
`
: ``}
`;
export const RTLStyleContainer = createGlobalStyle<{
dropdownPopoverContainer: string;
}>`
${(props) => `
.${props.dropdownPopoverContainer} {
.bp3-input-group {
.bp3-icon-search {
left: auto !important;
right: 0;
}
input {
direction: rtl;
padding-left: 10px !important;
padding-right: 34px !important;
}
}
}
`}
`;
export const MenuItem = styled.div<{

View File

@ -18,6 +18,7 @@ import {
DropdownStyles,
DropdownContainer,
MenuItem,
RTLStyleContainer,
} from "./index.styled";
import { WidgetContainerDiff } from "widgets/WidgetUtils";
import type { LabelPosition } from "components/constants";
@ -240,9 +241,11 @@ class SelectComponent extends React.Component<
{renderItem(items[itemProps.index], itemProps.index)}
</div>
);
return (
<FixedSizeList
className="menu-virtual-list"
direction={this.props.rtl ? "rtl" : "ltr"}
height={MAX_RENDER_MENU_ITEMS_HEIGHT}
initialScrollOffset={scrollOffset}
itemCount={items.length}
@ -328,7 +331,13 @@ class SelectComponent extends React.Component<
compactMode={compactMode}
data-testid="select-container"
labelPosition={labelPosition}
rtl={this.props.rtl}
>
{this.props.rtl ? (
<RTLStyleContainer
dropdownPopoverContainer={`select-popover-wrapper-${this.props.widgetId}`}
/>
) : null}
<DropdownStyles
accentColor={accentColor}
borderRadius={borderRadius}
@ -404,7 +413,7 @@ class SelectComponent extends React.Component<
enabled: false,
},
},
popoverClassName: `select-popover-wrapper select-popover-width-${this.props.widgetId}`,
popoverClassName: `select-popover-wrapper select-popover-width-${this.props.widgetId} select-popover-wrapper-${this.props.widgetId}`,
}}
query={this.props.filterText}
resetOnClose={this.props.resetFilterTextOnClose}
@ -466,6 +475,7 @@ export interface SelectComponentProps extends ComponentProps {
onClose?: () => void;
hideCancelIcon?: boolean;
resetFilterTextOnClose?: boolean;
rtl?: boolean;
}
export default React.memo(SelectComponent);

View File

@ -22,3 +22,7 @@ export const getOptionLabelValueExpressionPrefix = (widget: WidgetProps) =>
`{{${widget.widgetName}.sourceData.map((item) => (`;
export const optionLabelValueExpressionSuffix = `))}}`;
export const CLASSNAMES = {
selectButton: "select-button",
};

View File

@ -53,6 +53,7 @@ import type {
} from "WidgetProvider/constants";
import IconSVG from "../icon.svg";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
constructor(props: SelectWidgetProps) {
@ -556,6 +557,21 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "rtl",
label: "Enable RTL",
helpText: "Enables right to left text direction",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
hidden: () => {
return !super.getFeatureFlag(
FEATURE_FLAG.license_widget_rtl_support_enabled,
);
},
},
],
},
{
@ -828,6 +844,7 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
options={options}
placeholder={this.props.placeholderText}
resetFilterTextOnClose={!this.props.serverSideFiltering}
rtl={this.props.rtl}
selectedIndex={selectedIndex > -1 ? selectedIndex : undefined}
serverSideFiltering={this.props.serverSideFiltering}
value={this.props.selectedOptionValue}
@ -928,6 +945,7 @@ export interface SelectWidgetProps extends WidgetProps {
isDirty?: boolean;
filterText: string;
labelComponentWidth?: number;
rtl?: boolean;
}
export default SelectWidget;

View File

@ -29,6 +29,7 @@ export interface LabelWithTooltipProps {
text: string;
width?: number;
isDynamicHeightEnabled?: boolean;
rtl?: boolean;
}
export interface LabelContainerProps {
@ -50,6 +51,7 @@ export interface StyledLabelProps {
$hasHelpText: boolean;
position?: LabelPosition;
$isDynamicHeightEnabled?: boolean;
rtl?: boolean;
}
interface TooltipIconProps {
@ -164,7 +166,7 @@ export const StyledTooltip = styled(Tooltip)`
export const StyledLabel = styled(Label)<StyledLabelProps>`
&&& {
${({ $compact, $hasHelpText, position }) => {
${({ $compact, $hasHelpText, position, rtl }) => {
if (!position && !$compact) return;
if (
position === LabelPosition.Left ||
@ -173,7 +175,7 @@ export const StyledLabel = styled(Label)<StyledLabelProps>`
return `margin-bottom: 0px; margin-right: ${LABEL_DEFAULT_GAP}`;
return `margin-bottom: ${LABEL_DEFAULT_GAP}; ${
$hasHelpText
? `margin-right: ${LABEL_DEFAULT_GAP}`
? `margin-${rtl ? "left" : "right"}: ${LABEL_DEFAULT_GAP}`
: "margin-right: 0px"
}`;
}};
@ -238,6 +240,7 @@ const LabelWithTooltip = React.forwardRef<
loading,
optionCount,
position,
rtl,
text,
width,
} = props;
@ -262,6 +265,7 @@ const LabelWithTooltip = React.forwardRef<
className={LABEL_CONTAINER_CLASS}
compact={compact}
data-testid={LABEL_CONTAINER_CLASS}
dir={rtl ? "rtl" : "ltr"}
inline={inline}
isDynamicHeightEnabled={isDynamicHeightEnabled}
optionCount={optionCount}
@ -290,6 +294,7 @@ const LabelWithTooltip = React.forwardRef<
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
position={position}
rtl={rtl}
>
{text}
</StyledLabel>