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:
parent
0ec86d92ad
commit
dcaa46050d
|
|
@ -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']",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}>`
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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<{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -22,3 +22,7 @@ export const getOptionLabelValueExpressionPrefix = (widget: WidgetProps) =>
|
|||
`{{${widget.widgetName}.sourceData.map((item) => (`;
|
||||
|
||||
export const optionLabelValueExpressionSuffix = `))}}`;
|
||||
|
||||
export const CLASSNAMES = {
|
||||
selectButton: "select-button",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user