From f6b9abdacf027b3b42a140f96e04edb3c3c2cac4 Mon Sep 17 00:00:00 2001 From: rashmi rai Date: Mon, 27 Dec 2021 17:34:45 +0530 Subject: [PATCH] feat: adding ads component for UQI (#8777) --- .gitignore | 2 + .../ApiPaneTests/API_Edit_spec.js | 2 +- .../BindApi_withPageload_Input_spec.js | 1 - .../Replay/Replay_Editor_spec.js | 1 - .../ActionExecution/Action_PageOnLoad_spec.js | 4 +- .../ServerSideTests/QueryPane/Mongo_Spec.js | 1 - .../ServerSideTests/QueryPane/S3_spec.js | 130 ++--- .../cypress/locators/DatasourcesEditor.json | 4 +- .../cypress/locators/apiWidgetslocator.json | 4 +- app/client/cypress/support/commands.js | 30 +- .../src/assets/icons/form/help-outline.svg | 3 + app/client/src/components/ads/Checkbox.tsx | 4 +- app/client/src/components/ads/Dropdown.tsx | 256 +++++++-- app/client/src/components/ads/TextInput.tsx | 3 +- app/client/src/components/ads/common.tsx | 1 + .../CodeEditor/styledComponents.ts | 4 +- .../form/fields/StyledFormComponents.tsx | 147 ++++++ .../components/formControls/BaseControl.tsx | 10 + .../formControls/CheckboxControl.tsx | 47 +- .../formControls/DropDownControl.tsx | 148 ++---- .../formControls/DynamicInputTextControl.tsx | 16 +- .../formControls/DynamicTextFieldControl.tsx | 3 - .../formControls/FieldArrayControl.tsx | 18 +- .../formControls/FilePickerControl.tsx | 147 +++--- .../formControls/FixedKeyInputControl.tsx | 13 +- .../formControls/InputTextControl.tsx | 70 +-- .../formControls/KeyValueArrayControl.tsx | 207 +++++--- .../formControls/KeyValueInputControl.tsx | 50 +- .../components/formControls/SwitchControl.tsx | 46 +- .../formControls/WhereClauseControl.tsx | 41 +- app/client/src/icons/FormIcons.tsx | 6 + .../RestAPIDatasourceForm.tsx | 489 +++++++++++------- app/client/src/pages/Editor/FormControl.tsx | 147 +++++- .../components/DataSourceOption.tsx | 22 +- .../GeneratePageForm/GeneratePageForm.tsx | 2 +- .../gitSync/components/OptionSelector.tsx | 2 +- app/client/src/utils/FormControlRegistry.tsx | 19 +- 37 files changed, 1329 insertions(+), 771 deletions(-) create mode 100644 app/client/src/assets/icons/form/help-outline.svg create mode 100644 app/client/src/components/editorComponents/form/fields/StyledFormComponents.tsx diff --git a/.gitignore b/.gitignore index ee1d723ac9..b3cb968bc7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ .env .vscode/* +app/client/cypress.env.json + # to ignore the node_modeules folder node_modules # to ignore the package-lock.json file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js index 9eeee1fa41..97668db1dd 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js @@ -38,7 +38,7 @@ describe("API Panel Test Functionality", function() { cy.log("Creation of FirstAPI Action successful"); cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods); cy.get(apiwidget.settings).click({ force: true }); - cy.get(apiwidget.confirmBeforeExecute).click(); + cy.get(apiwidget.confirmBeforeExecute).click({ force: true }); cy.get(apiwidget.runQueryButton).click(); cy.get(".bp3-dialog") .find("button") diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js index 18c141b2da..be501f8802 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js @@ -1,6 +1,5 @@ const testdata = require("../../../../fixtures/testdata.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); -const commonlocators = require("../../../../locators/commonlocators.json"); const dsl = require("../../../../fixtures/MultipleInput.json"); const widgetsPage = require("../../../../locators/Widgets.json"); const publish = require("../../../../locators/publishWidgetspage.json"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Replay/Replay_Editor_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Replay/Replay_Editor_spec.js index e9fc7c4050..a1f6ad57a7 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Replay/Replay_Editor_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Replay/Replay_Editor_spec.js @@ -4,7 +4,6 @@ const datasource = require("../../../../locators/DatasourcesEditor.json"); const datasourceEditor = require("../../../../locators/DatasourcesEditor.json"); const datasourceFormData = require("../../../../fixtures/datasources.json"); const queryLocators = require("../../../../locators/QueryEditor.json"); -const jsEditorLocators = require("../../../../locators/JSEditor.json"); describe("Undo/Redo functionality", function() { const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl"; diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ActionExecution/Action_PageOnLoad_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ActionExecution/Action_PageOnLoad_spec.js index 700b1e808f..bd1198452f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ActionExecution/Action_PageOnLoad_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ActionExecution/Action_PageOnLoad_spec.js @@ -29,12 +29,13 @@ describe("API Panel Test Functionality", function() { }); it("Shows which action failed on action fail.", function() { + cy.wait(2000); cy.NavigateToAPI_Panel(); cy.CreateAPI("PageLoadApi2"); cy.enterDatasourceAndPath("https://abc.com", "users"); cy.WaitAutoSave(); cy.get("li:contains('Settings')").click({ force: true }); - cy.get("[data-cy=executeOnLoad]").click({ force: true }); + cy.get("[name=executeOnLoad]").click({ force: true }); cy.wait("@setExecuteOnLoad"); @@ -44,6 +45,7 @@ describe("API Panel Test Functionality", function() { cy.wait("@updateLayout"); cy.reload(); + cy.wait(3000); cy.get(commonlocators.toastMsg).contains( `The action "PageLoadApi2" has failed.`, ); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/Mongo_Spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/Mongo_Spec.js index 38302ab97a..53d0caef79 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/Mongo_Spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/Mongo_Spec.js @@ -35,7 +35,6 @@ describe("Create a query with a mongo datasource, run, save and then delete the // ); cy.validateNSelectDropdown("Commands", "Find Document(s)", "Raw"); - cy.get(queryLocators.templateMenu).click(); cy.typeValueNValidate('{"find": "listingsAndReviews","limit": 10}'); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js index 3af052daa7..9e4b6d31ea 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js @@ -258,81 +258,81 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications", cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method }); - // it("5. Validate Read file command, Verify possible error msgs, run & delete the query", () => { - // cy.NavigateToActiveDSQueryPane(datasourceName); - // cy.setQueryTimeout(30000); - // cy.validateNSelectDropdown("Commands", "List files in bucket", "Read file"); + it("5. Validate Read file command, Verify possible error msgs, run & delete the query", () => { + cy.NavigateToActiveDSQueryPane(datasourceName); + cy.setQueryTimeout(30000); + cy.validateNSelectDropdown("Commands", "List files in bucket", "Read file"); - // cy.onlyQueryRun(); - // cy.wait("@postExecute").should(({ response }) => { - // expect(response.body.data.isExecutionSuccess).to.eq(false); - // expect(response.body.data.body).to.contains( - // "Mandatory parameter 'Bucket Name' is missing.", - // ); - // }); - // cy.typeValueNValidate("AutoTest", "Bucket Name"); + cy.onlyQueryRun(); + cy.wait("@postExecute").should(({ response }) => { + expect(response.body.data.isExecutionSuccess).to.eq(false); + expect(response.body.data.body).to.contains( + "Mandatory parameter 'Bucket Name' is missing.", + ); + }); + cy.typeValueNValidate("AutoTest", "Bucket Name"); - // cy.onlyQueryRun(); - // cy.wait("@postExecute").then(({ response }) => { - // expect(response.body.data.isExecutionSuccess).to.eq(false); - // expect(response.body.data.body).to.contains( - // "Required parameter 'File Path' is missing.", - // ); - // }); - // cy.typeValueNValidate("Auto", "File Path"); + cy.onlyQueryRun(); + cy.wait("@postExecute").then(({ response }) => { + expect(response.body.data.isExecutionSuccess).to.eq(false); + expect(response.body.data.body).to.contains( + "Required parameter 'File Path' is missing.", + ); + }); + cy.typeValueNValidate("Auto", "File Path"); - // cy.onlyQueryRun(); - // cy.wait("@postExecute").then(({ response }) => { - // expect(response.body.data.isExecutionSuccess).to.eq(false); - // expect(response.body.data.body.split("(")[0].trim()).to.be.oneOf([ - // "The specified bucket does not exist", - // "The specified bucket is not valid.", - // ]); - // }); + cy.onlyQueryRun(); + cy.wait("@postExecute").then(({ response }) => { + expect(response.body.data.isExecutionSuccess).to.eq(false); + expect(response.body.data.body.split("(")[0].trim()).to.be.oneOf([ + "The specified bucket does not exist", + "The specified bucket is not valid.", + ]); + }); - // cy.typeValueNValidate("assets-test.appsmith.com", "Bucket Name"); + cy.typeValueNValidate("assets-test.appsmith.com", "Bucket Name"); - // cy.onlyQueryRun(); - // cy.wait("@postExecute").then(({ response }) => { - // expect(response.body.data.isExecutionSuccess).to.eq(false); - // expect(response.body.data.body).to.contain( - // "The specified key does not exist.", - // ); - // }); + cy.onlyQueryRun(); + cy.wait("@postExecute").then(({ response }) => { + expect(response.body.data.isExecutionSuccess).to.eq(false); + expect(response.body.data.body).to.contain( + "The specified key does not exist.", + ); + }); - // cy.typeValueNValidate("Autofile", "File Path"); + cy.typeValueNValidate("Autofile", "File Path"); - // cy.onlyQueryRun(); - // cy.wait("@postExecute").then(({ response }) => { - // expect(response.body.data.isExecutionSuccess).to.eq(false); - // expect(response.body.data.body).to.contain( - // "The specified key does not exist.", - // ); - // }); + cy.onlyQueryRun(); + cy.wait("@postExecute").then(({ response }) => { + expect(response.body.data.isExecutionSuccess).to.eq(false); + expect(response.body.data.body).to.contain( + "The specified key does not exist.", + ); + }); - // cy.typeValueNValidate("AutoFile", "File Path"); + cy.typeValueNValidate("AutoFile", "File Path"); - // //Commenting below since below dropdown is removed from Read - // //cy.validateNSelectDropdown("File Data Type", "Base64", "Text / Binary"); + //Commenting below since below dropdown is removed from Read + //cy.validateNSelectDropdown("File Data Type", "Base64", "Text / Binary"); - // cy.onlyQueryRun(); - // cy.wait("@postExecute").then(({ response }) => { - // expect(response.body.data.isExecutionSuccess).to.eq(true); - // expect(response.body.data.body.fileData).to.not.eq( - // "Hi, this is Automation script adding File!", - // ); - // }); + cy.onlyQueryRun(); + cy.wait("@postExecute").then(({ response }) => { + expect(response.body.data.isExecutionSuccess).to.eq(true); + expect(response.body.data.body.fileData).to.not.eq( + "Hi, this is Automation script adding File!", + ); + }); - // cy.validateNSelectDropdown("Base64 Encode File - Yes/No", "Yes", "No"); - // cy.onlyQueryRun(); - // cy.wait("@postExecute").then(({ response }) => { - // expect(response.body.data.isExecutionSuccess).to.eq(true); - // expect(response.body.data.body.fileData).to.eq( - // "Hi, this is Automation script adding File!", - // ); - // }); - // cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method - // }); + cy.validateNSelectDropdown("Base64 Encode File - Yes/No", "Yes", "No"); + cy.onlyQueryRun(); + cy.wait("@postExecute").then(({ response }) => { + expect(response.body.data.isExecutionSuccess).to.eq(true); + expect(response.body.data.body.fileData).to.eq( + "Hi, this is Automation script adding File!", + ); + }); + cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method + }); it("6. Validate Delete file command for new file, Verify possible error msgs, run & delete the query", () => { cy.NavigateToActiveDSQueryPane(datasourceName); @@ -564,7 +564,7 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications", cy.wait(3000); //waiting for deletion to complete! - else next case fails }); - it("11. Deletes the datasource", () => { + it("13. Deletes the datasource", () => { cy.NavigateToQueryEditor(); cy.NavigateToActiveTab(); cy.contains(".t--datasource-name", datasourceName).click({ force: true }); diff --git a/app/client/cypress/locators/DatasourcesEditor.json b/app/client/cypress/locators/DatasourcesEditor.json index 2d23656567..d098db303f 100644 --- a/app/client/cypress/locators/DatasourcesEditor.json +++ b/app/client/cypress/locators/DatasourcesEditor.json @@ -38,7 +38,7 @@ "Redshift": ".t--plugin-name:contains('Redshift')", "AmazonS3": ".t--plugin-name:contains('S3')", "authType": "[data-cy=authType]", - "OAuth2": "//div[contains(@class,'option') and text()='OAuth 2.0']", + "OAuth2": ".t--dropdown-option:contains('OAuth 2.0')", "accessTokenUrl": "[data-cy='authentication.accessTokenUrl'] input", "clienID": "[data-cy='authentication.clientId'] input", "clientSecret": "[data-cy='authentication.clientSecret'] input", @@ -47,7 +47,7 @@ "serviceAccCredential": "[data-cy='datasourceConfiguration.authentication.password'] input", "grantType": "[data-cy='authentication.grantType']", "authorizationURL":"[data-cy='authentication.authorizationUrl'] input", - "authorisecode": "//div[contains(@class,'option') and text()='Authorization Code']", + "authorisecode": ".t--dropdown-option:contains('Authorization Code')", "saveAndAuthorize": "button:contains('Save and Authorize')", "basic": "//div[contains(@class,'option') and text()='Basic']", "basicUsername": "input[name='authentication.username']", diff --git a/app/client/cypress/locators/apiWidgetslocator.json b/app/client/cypress/locators/apiWidgetslocator.json index 8091cea4c3..1bfb88441f 100644 --- a/app/client/cypress/locators/apiWidgetslocator.json +++ b/app/client/cypress/locators/apiWidgetslocator.json @@ -52,12 +52,12 @@ "actionlist": ".action div div", "settings": "li:contains('Settings')", "headers": "li:contains('Headers')", - "onPageLoad": "[data-cy=executeOnLoad]", + "onPageLoad": "[name=executeOnLoad]", "renameEntity": ".single-select >div:contains('Edit Name')", "paramsTab": "//li//span[text()='Params']", "paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0", "paramValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0", "multipartTypeDropdown":"button:contains('Type')", - "confirmBeforeExecute": "[data-cy=confirmBeforeExecute]", + "confirmBeforeExecute": "[name=confirmBeforeExecute]", "runQueryButton": ".t--apiFormRunBtn" } diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index efd38ce7cf..10a7c20666 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -390,9 +390,9 @@ Cypress.Commands.add( "addOauthAuthDetails", (accessTokenUrl, clientId, clientSecret, authURL) => { cy.get(datasource.authType).click(); - cy.xpath(datasource.OAuth2).click(); + cy.get(datasource.OAuth2).click(); cy.get(datasource.grantType).click(); - cy.xpath(datasource.authorisecode).click(); + cy.get(datasource.authorisecode).click(); cy.get(datasource.accessTokenUrl).type(accessTokenUrl); cy.get(datasource.clienID).type(clientId); cy.get(datasource.clientSecret).type(clientSecret); @@ -3369,16 +3369,18 @@ Cypress.Commands.add( "validateNSelectDropdown", (ddTitle, currentValue, newValue) => { let toChange = false; - cy.xpath('//div[contains(text(),"' + currentValue + '")]').should( + cy.xpath('//span[contains(text(),"' + currentValue + '")]').should( "exist", currentValue + " dropdown value not present", ); if (newValue) toChange = true; if (toChange) { cy.xpath( - "//p[text()='" + ddTitle + "']/following-sibling::div/div", + "//p[text()='" + + ddTitle + + "']/parent::label/following-sibling::div/div/div", ).click(); //to expand the dropdown - cy.xpath('//div[contains(text(),"' + newValue + '")]') + cy.xpath('//span[contains(text(),"' + newValue + '")]') .last() .click({ force: true }); //to select the new value } @@ -3387,19 +3389,17 @@ Cypress.Commands.add( Cypress.Commands.add("typeValueNValidate", (valueToType, fieldName = "") => { if (fieldName) { - cy.xpath("//p[text()='" + fieldName + "']/following-sibling::div").then( - ($field) => { - cy.updateCodeInput($field, valueToType); - }, - ); + cy.xpath( + "//p[text()='" + fieldName + "']/parent::label/following-sibling::div", + ).then(($field) => { + cy.updateCodeInput($field, valueToType); + }); } else { cy.xpath("//div[@class='CodeEditorTarget']").then(($field) => { cy.updateCodeInput($field, valueToType); }); } - cy.EvaluateCurrentValue(valueToType); - // cy.xpath("//p[text()='" + fieldName + "']/following-sibling::div//div[@class='CodeMirror-code']//span/span").should((fieldValue) => { // textF = fieldValue.innerText // fieldValue.innerText = "" @@ -3446,26 +3446,22 @@ Cypress.Commands.add( (fieldName = "", currentValue = "") => { let toValidate = false; if (currentValue) toValidate = true; - if (fieldName) { cy.xpath( "//p[text()='" + fieldName + - "']/following-sibling::div//div[@class='CodeMirror-code']", + "']/parent::label/following-sibling::div//div[@class='CodeMirror-code']", ).click(); } else { cy.xpath("//div[@class='CodeMirror-code']").click(); } - cy.wait(2000); const val = cy .get(commonlocators.evaluatedCurrentValue) .first() .should("be.visible") .invoke("text"); - if (toValidate) expect(val).to.eq(currentValue); - return val; }, ); diff --git a/app/client/src/assets/icons/form/help-outline.svg b/app/client/src/assets/icons/form/help-outline.svg new file mode 100644 index 0000000000..bdd8aa1db9 --- /dev/null +++ b/app/client/src/assets/icons/form/help-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/components/ads/Checkbox.tsx b/app/client/src/components/ads/Checkbox.tsx index 37ee3d1441..40d95bb7ee 100644 --- a/app/client/src/components/ads/Checkbox.tsx +++ b/app/client/src/components/ads/Checkbox.tsx @@ -12,9 +12,10 @@ export type CheckboxProps = CommonComponentProps & { info?: string; backgroundColor?: string; fill?: boolean; + name?: string; }; -const Checkmark = styled.span<{ +export const Checkmark = styled.span<{ disabled?: boolean; isChecked?: boolean; info?: string; @@ -137,6 +138,7 @@ function Checkbox(props: CheckboxProps) { ) => onChangeHandler(e.target.checked) } diff --git a/app/client/src/components/ads/Dropdown.tsx b/app/client/src/components/ads/Dropdown.tsx index 6fa8a7a247..20f3447a0d 100644 --- a/app/client/src/components/ads/Dropdown.tsx +++ b/app/client/src/components/ads/Dropdown.tsx @@ -14,6 +14,8 @@ import styled from "constants/DefaultTheme"; import SearchComponent from "components/designSystems/appsmith/SearchComponent"; import { Colors } from "constants/Colors"; import Spinner from "./Spinner"; +import { ReactComponent as Check } from "assets/icons/control/checkmark.svg"; +import { ReactComponent as Close } from "assets/icons/control/remove.svg"; import { replayHighlightClass } from "globalStyles/portals"; import Tooltip from "components/ads/Tooltip"; import { isEllipsisActive } from "utils/helpers"; @@ -44,7 +46,7 @@ export interface DropdownSearchProps { export interface RenderDropdownOptionType { index?: number; - option: DropdownOption; + option: DropdownOption | DropdownOption[]; optionClickHandler?: (dropdownOption: DropdownOption) => void; isSelectedNode?: boolean; extraProps?: any; @@ -63,8 +65,9 @@ type RenderOption = ({ export type DropdownProps = CommonComponentProps & DropdownSearchProps & { options: DropdownOption[]; - selected: DropdownOption; + selected: DropdownOption | DropdownOption[]; onSelect?: DropdownOnSelect; + isMultiSelect?: boolean; width?: string; height?: string; showLabelOnly?: boolean; @@ -91,16 +94,19 @@ export type DropdownProps = CommonComponentProps & fillOptions?: boolean; dontUsePortal?: boolean; hideSubText?: boolean; + removeSelectedOption?: DropdownOnSelect; boundary?: PopperBoundary; defaultIcon?: IconName; truncateOption?: boolean; // enabled wrapping and adding tooltip on option item of dropdown menu }; export interface DefaultDropDownValueNodeProps { - selected: DropdownOption; + selected: DropdownOption | DropdownOption[]; showLabelOnly?: boolean; + isMultiSelect?: boolean; isOpen?: boolean; hasError?: boolean; renderNode?: RenderOption; + selectedOptionClickHandler?: (option: DropdownOption) => void; placeholder?: string; showDropIcon?: boolean; optionWidth: string; @@ -108,13 +114,13 @@ export interface DefaultDropDownValueNodeProps { } export interface RenderDropdownOptionType { - option: DropdownOption; + option: DropdownOption | DropdownOption[]; optionClickHandler?: (dropdownOption: DropdownOption) => void; } export const DropdownContainer = styled.div<{ width: string; height?: string }>` width: ${(props) => props.width}; - height: ${(props) => props.height || `38px`}; + min-height: ${(props) => props.height}; position: relative; span.bp3-popover-target { display: inline-block; @@ -146,6 +152,41 @@ const DropdownTriggerWrapper = styled.div<{ } `; +const StyledCheckmark = styled(Check)` + width: 14px; + height: 14px; + position: absolute; + top: -1px; + left: -1px; +`; + +const StyledClose = styled(Close)` + width: 24px; + height: 24px; + padding: 3px; + padding-right: 10px; + &:hover { + background-color: #ebebeb; + } +`; +const SquareBox = styled.div<{ + backgroundColor?: string; + borderColor?: string; +}>` + width: 14px; + height: 14px; + box-sizing: border-box; + position: relative; + margin-right: 10px; + background-color: ${(props) => + props.backgroundColor ? props.backgroundColor : "transparent"}; + border: ${(props) => + props.borderColor + ? `1.8px solid ${props.borderColor}` + : "1.8px solid #A9A7A7"}; + border-width: 1.8px; +`; + const Selected = styled.div<{ isOpen: boolean; disabled?: boolean; @@ -154,6 +195,7 @@ const Selected = styled.div<{ hasError?: boolean; selected?: boolean; isLoading?: boolean; + isMultiSelect?: boolean; }>` padding: ${(props) => props.theme.spaces[2]}px ${(props) => props.theme.spaces[3]}px; @@ -170,7 +212,7 @@ const Selected = styled.div<{ align-items: center; justify-content: space-between; width: 100%; - height: ${(props) => props.height}; + min-height: ${(props) => props.height}; cursor: ${(props) => props.disabled || props.isLoading ? "not-allowed" : "pointer"}; ${(props) => @@ -209,11 +251,11 @@ const Selected = styled.div<{ } &:hover { background: ${(props) => - props.hasError - ? Colors.FAIR_PINK - : props.theme.colors.dropdown.hovered.bg}; - border: 1px solid var(--appsmith-input-focus-border-color); - } + !props.isMultiSelect + ? props.hasError + ? Colors.FAIR_PINK + : props.theme.colors.dropdown.hovered.bg + : Colors.WHITE} `; const DropdownSelect = styled.div``; @@ -284,7 +326,6 @@ const OptionWrapper = styled.div<{ align-items: center; min-height: 36px; background-color: ${(props) => (props.selected ? Colors.GREEN_3 : null)}; - &&& svg { rect { fill: ${(props) => props.theme.colors.dropdownIconBg}; @@ -384,7 +425,7 @@ const SelectedDropDownHolder = styled.div` display: flex; align-items: center; min-width: 0; - width: 100%; + max-width: 100%; overflow: hidden; & ${Text} { @@ -403,7 +444,6 @@ const SelectedIcon = styled(Icon)` svg { height: 18px; width: 18px; - rect { fill: ${(props) => props.theme.colors.dropdownIconBg}; rx: 0; @@ -479,26 +519,64 @@ function TooltipWrappedText( function DefaultDropDownValueNode({ hasError, hideSubText, + isMultiSelect, optionWidth, placeholder, renderNode, selected, + selectedOptionClickHandler, showDropIcon, showLabelOnly, }: DefaultDropDownValueNodeProps) { - const LabelText = selected - ? showLabelOnly - ? selected.label - : selected.value - : placeholder - ? placeholder - : "Please select a option."; + const LabelText = + !Array.isArray(selected) && selected + ? showLabelOnly + ? selected.label + : selected.value + : placeholder + ? placeholder + : "Please select a option."; + function Label() { - return hasError ? ( - {LabelText} - ) : ( - {LabelText} - ); + if (isMultiSelect && Array.isArray(selected) && selected.length) { + return ( +
+ {selected?.map((s: DropdownOption) => { + return ( +
+ + {s.label} + + { + event.stopPropagation(); + if (selectedOptionClickHandler) { + selectedOptionClickHandler(s as DropdownOption); + } + }} + /> +
+ ); + })} +
+ ); + } else + return hasError ? ( + {LabelText} + ) : ( + + {LabelText} + + ); } return ( @@ -510,29 +588,33 @@ function DefaultDropDownValueNode({ hasError, optionWidth, }) + ) : isMultiSelect && Array.isArray(selected) && selected.length ? ( +