From 4e9ac336d370e17839c5e58a0a7f6d3f45decf4b Mon Sep 17 00:00:00 2001 From: nandinisaha13 Date: Tue, 19 Dec 2023 16:31:37 +0530 Subject: [PATCH 01/64] add automation for deltion of component from inspector --- cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js | 8 +++++++- cypress-tests/cypress/support/utils/inspector.js | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js b/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js index f4ec0c005a..1de78f91b2 100644 --- a/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js +++ b/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js @@ -3,7 +3,7 @@ import { verifyMultipleComponentValuesFromInspector, verifyComponentValueFromInspector, } from "Support/utils/commonWidget"; -import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector"; +import { verifyNodeData, openNode, verifyValue, deleteComponentFromInspector } from "Support/utils/inspector"; import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { addNewPage } from "Support/utils/multipage"; import { @@ -170,4 +170,10 @@ describe("Editor- Inspector", () => { cy.notVisible(commonWidgetSelector.draggableWidget("button1")); cy.apiDeleteApp(); }); + it("should verify deletion of component from inspector", () => { + cy.dragAndDropWidget("button", 500, 500); + cy.get(commonWidgetSelector.sidebarinspector).click(); + deleteComponentFromInspector("button1"); + + }); }); diff --git a/cypress-tests/cypress/support/utils/inspector.js b/cypress-tests/cypress/support/utils/inspector.js index 786c99f850..8f4a5ea7a1 100644 --- a/cypress-tests/cypress/support/utils/inspector.js +++ b/cypress-tests/cypress/support/utils/inspector.js @@ -32,3 +32,8 @@ export const verifyValue = (node, type, children, index = 0) => { .eq(index) .verifyVisibleElement("have.text", type); }; +export const deleteComponentFromInspector = (node) => { + cy.get('[data-cy="inspector-node-components"] > .node-key').click(); + cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).click(); + cy.get(cy.get('[style="height: 13px; width: 13px;"] > img').click()); +} From e7cabe2fea96fbc2f9cd62e03d3926d4aaa92d7e Mon Sep 17 00:00:00 2001 From: Nandini <41302917+Nandinisaha13@users.noreply.github.com> Date: Thu, 28 Dec 2023 11:53:10 +0530 Subject: [PATCH 02/64] Add check for link column type (#7476) Co-authored-by: Nandini --- .../e2e/editor/widget/tableRegression.cy.js | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js b/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js index 09a4e6c7b7..6110a2aeb2 100644 --- a/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js @@ -333,12 +333,37 @@ describe("Table", () => { ); }); + it("should verify column options", () => { const data = {}; data.widgetName = fake.widgetName; openEditorSidebar(tableText.defaultWidgetName); editAndVerifyWidgetName(data.widgetName, []); + openEditorSidebar(data.widgetName); + cy.wait(500); + addAndOpenColumnOption("Fake-Link", `link`); + verifyAndEnterColumnOptionInput("key", "name"); + cy.get('[data-cy="dropdown-link-target"] >>:eq(0)') + .click() + .find("input") + .type(`{selectAll}{backspace}new window{enter}`); + + cy.forceClickOnCanvas(); + cy.get('[data-cy="linksarah-cell-3"]').contains('a', 'Sarah').should('have.attr', 'target', '_blank'); + + openEditorSidebar(data.widgetName); + cy.get('[data-cy*="column-Fake-Link"]').click(); + + + cy.get('[data-cy="dropdown-link-target"] >>:eq(0)') + .click() + .find("input") + .type(`{selectAll}{backspace}same window{enter}`); + cy.forceClickOnCanvas(); + cy.get('[data-cy="linksarah-cell-3"]').contains('a', 'Sarah').should('have.attr', 'target', '_self'); + + //String/default openEditorSidebar(data.widgetName); cy.wait(500); @@ -790,8 +815,7 @@ describe("Table", () => { .should( "have.css", "color", - `rgba(${data.color[0]}, ${data.color[1]}, ${data.color[2]}, ${ - data.color[3] / 100 + `rgba(${data.color[0]}, ${data.color[1]}, ${data.color[2]}, ${data.color[3] / 100 })` ); }); From 41b73531e56b685c75eba3221f0ecb14b6022f2e Mon Sep 17 00:00:00 2001 From: nandinisaha13 Date: Thu, 4 Jan 2024 12:37:57 +0530 Subject: [PATCH 03/64] fix:resolve comments --- cypress-tests/cypress/support/utils/inspector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress-tests/cypress/support/utils/inspector.js b/cypress-tests/cypress/support/utils/inspector.js index 8f4a5ea7a1..f5804eea6e 100644 --- a/cypress-tests/cypress/support/utils/inspector.js +++ b/cypress-tests/cypress/support/utils/inspector.js @@ -35,5 +35,5 @@ export const verifyValue = (node, type, children, index = 0) => { export const deleteComponentFromInspector = (node) => { cy.get('[data-cy="inspector-node-components"] > .node-key').click(); cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).click(); - cy.get(cy.get('[style="height: 13px; width: 13px;"] > img').click()); + cy.get('[style="height: 13px; width: 13px;"] > img').click(); } From 2f5dadc3ffeb52cbb5fe34fc2b1b786892730ff5 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Thu, 4 Jan 2024 12:46:02 +0530 Subject: [PATCH 04/64] Added automation for table server side pagination (#8482) * Add apiCommands * Add test cases * Add command logs --- cypress-tests/cypress/commands/apiCommands.js | 68 ++++++++++++---- .../e2e/editor/widget/tableRegression.cy.js | 81 +++++++++++++++++-- 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 9783d215d7..1fec7f0ce3 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -133,23 +133,6 @@ Cypress.Commands.add( } ); -// cy.apiLogin(); -// cy.apiCreateApp(); -// cy.apiCreateGDS( -// "http://localhost:3000/api/v2/data_sources", -// "aaaaaadish", -// "postgresql", -// [ -// { key: "host", value: "localhost" }, -// { key: "port", value: 5432 }, -// { key: "database", value: "" }, -// { key: "username", value: "dev@tooljet.io" }, -// { key: "password", value: "password", encrypted: true }, -// { key: "ssl_enabled", value: true, encrypted: false }, -// { key: "ssl_certificate", value: "none", encrypted: false }, -// ] -// ); - Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => { cy.getCookie("tj_auth_token").then((cookie) => { cy.request( @@ -212,6 +195,7 @@ Cypress.Commands.add("userInviteApi", (userName, userEmail) => { }); }); }); + Cypress.Commands.add("addQueryApi", (queryName, query, dataQueryId) => { cy.getCookie("tj_auth_token").then((cookie) => { const headers = { @@ -236,3 +220,53 @@ Cypress.Commands.add("addQueryApi", (queryName, query, dataQueryId) => { }); }); }); + +Cypress.Commands.add( + "apiAddQueryToApp", + (queryName, options, dsName, dsKind) => { + cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { + const authToken = `tj_auth_token=${cookie.value}`; + const workspaceId = Cypress.env("workspaceId"); + const appId = Cypress.env("appId"); + + cy.request({ + method: "GET", + url: `http://localhost:3000/api/apps/${appId}`, + headers: { + "Tj-Workspace-Id": workspaceId, + Cookie: `${authToken}; app_id=${appId}`, + }, + body: {}, + }).then((appResponse) => { + const editingVersionId = appResponse.body.editing_version.id; + Cypress.env("version-id", editingVersionId); + + cy.request({ + method: "POST", + url: "http://localhost:3000/api/data_queries", + headers: { + "Content-Type": "application/json", + Cookie: authToken, + "tj-workspace-id": workspaceId, + }, + body: { + app_id: appId, + app_version_id: editingVersionId, + name: queryName, + kind: dsKind, + options: options, + data_source_id: Cypress.env(`${dsName}-id`), + plugin_id: null, + }, + }).then((queryResponse) => { + expect(queryResponse.status).to.equal(201); + Cypress.log({ + name: "Created queery", + displayName: "QUERY CREATED", + message: `: ${queryName}: ${dsKind}`, + }); + }); + }); + }); + } +); diff --git a/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js b/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js index 6110a2aeb2..4dfcc8dbee 100644 --- a/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js @@ -52,6 +52,7 @@ import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector"; import { textInputText } from "Texts/textInput"; import { deleteDownloadsFolder } from "Support/utils/common"; import { resizeQueryPanel } from "Support/utils/dataSource"; +import { waitForQueryAction } from "Support/utils/queries"; describe("Table", () => { beforeEach(() => { @@ -333,7 +334,6 @@ describe("Table", () => { ); }); - it("should verify column options", () => { const data = {}; data.widgetName = fake.widgetName; @@ -350,19 +350,21 @@ describe("Table", () => { .type(`{selectAll}{backspace}new window{enter}`); cy.forceClickOnCanvas(); - cy.get('[data-cy="linksarah-cell-3"]').contains('a', 'Sarah').should('have.attr', 'target', '_blank'); + cy.get('[data-cy="linksarah-cell-3"]') + .contains("a", "Sarah") + .should("have.attr", "target", "_blank"); openEditorSidebar(data.widgetName); cy.get('[data-cy*="column-Fake-Link"]').click(); - cy.get('[data-cy="dropdown-link-target"] >>:eq(0)') .click() .find("input") .type(`{selectAll}{backspace}same window{enter}`); cy.forceClickOnCanvas(); - cy.get('[data-cy="linksarah-cell-3"]').contains('a', 'Sarah').should('have.attr', 'target', '_self'); - + cy.get('[data-cy="linksarah-cell-3"]') + .contains("a", "Sarah") + .should("have.attr", "target", "_self"); //String/default openEditorSidebar(data.widgetName); @@ -815,7 +817,8 @@ describe("Table", () => { .should( "have.css", "color", - `rgba(${data.color[0]}, ${data.color[1]}, ${data.color[2]}, ${data.color[3] / 100 + `rgba(${data.color[0]}, ${data.color[1]}, ${data.color[2]}, ${ + data.color[3] / 100 })` ); }); @@ -1283,4 +1286,70 @@ describe("Table", () => { .eq(0) .verifyVisibleElement("have.value", "Jack"); }); + + it("should verify server-side paginaion", () => { + let dsName = fake.companyName; + cy.apiCreateGDS( + "http://localhost:3000/api/v2/data_sources", + `cypress-${dsName}-postgresql`, + "postgresql", + [ + { key: "host", value: Cypress.env("pg_host") }, + { key: "port", value: 5432 }, + { key: "database", value: Cypress.env("pg_user") }, + { key: "username", value: Cypress.env("pg_user") }, + { key: "password", value: Cypress.env("pg_password"), encrypted: true }, + { key: "ssl_enabled", value: false, encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + ] + ); + cy.apiAddQueryToApp( + "q112", + { + mode: "sql", + transformationLanguage: "javascript", + enableTransformation: false, + query: `SELECT * + FROM server_side_pagination + ORDER BY id + LIMIT 10 OFFSET {{(components.table1.pageIndex-1)*10}};`, + }, + `cypress-${dsName}-postgresql`, + "postgresql" + ); + cy.reload(); + openEditorSidebar(tableText.defaultWidgetName); + cy.get("[data-state=off]:eq(3)").click(); + cy.get( + '[data-cy="total-records-server-side-input-field"]' + ).clearAndTypeOnCodeMirror("50"); + + selectEvent("Page changed", "Run Query", 1); + cy.get('[data-cy="query-selection-field"]') + .click() + .find("input") + .type(`{selectAll}{backspace}q112{enter}`); + cy.get('[data-cy="table-data-input-field"]').clearAndTypeOnCodeMirror( + `{{queries.q112.data` + ); + cy.get('[data-cy="pagination-button-to-next"]').click(); + waitForQueryAction("run"); + cy.get('[data-cy="11-cell-0"]').verifyVisibleElement("have.text", "11"); + cy.get('[data-cy="pagination-button-to-previous"]').click(); + waitForQueryAction("run"); + cy.get('[data-cy="1-cell-0"]').verifyVisibleElement("have.text", "1"); + + cy.openInCurrentTab(commonWidgetSelector.previewButton); + cy.wait(4000); + cy.get('[data-cy="pagination-button-to-next"]').click(); + cy.get('[data-cy="11-cell-0"]', { timeout: 10000 }).verifyVisibleElement( + "have.text", + "11" + ); + cy.get('[data-cy="pagination-button-to-previous"]').click(); + cy.get('[data-cy="1-cell-0"]', { timeout: 10000 }).verifyVisibleElement( + "have.text", + "1" + ); + }); }); From 4b0a2490c64544f45f2d62460f21fdba891ea10d Mon Sep 17 00:00:00 2001 From: nandinisaha13 Date: Thu, 4 Jan 2024 14:23:25 +0530 Subject: [PATCH 05/64] fixes: verifying deletion toast & verifying component not exist on version creation --- .../cypress/e2e/editor/inspectorHappypath.cy.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js b/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js index 1de78f91b2..7080e361d0 100644 --- a/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js +++ b/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js @@ -12,8 +12,16 @@ import { addSupportCSAData, } from "Support/utils/events"; import { multipageSelector } from "Selectors/multipage"; +import { + navigateToCreateNewVersionModal +} from "Support/utils/version"; +import { createNewVersion } from "Support/utils/exportImport"; + describe("Editor- Inspector", () => { + let currentVersion = ""; + let newVersion = []; + let versionFrom = ""; beforeEach(() => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-App`); @@ -170,10 +178,17 @@ describe("Editor- Inspector", () => { cy.notVisible(commonWidgetSelector.draggableWidget("button1")); cy.apiDeleteApp(); }); - it("should verify deletion of component from inspector", () => { + it.only("should verify deletion of component from inspector", () => { cy.dragAndDropWidget("button", 500, 500); cy.get(commonWidgetSelector.sidebarinspector).click(); deleteComponentFromInspector("button1"); + cy.verifyToastMessage( + `[class=go3958317564]`, + "Component deleted! (โŒ˜ + Z to undo)" + ); + navigateToCreateNewVersionModal((currentVersion = "v1")); + createNewVersion((newVersion = ["v2"]), (versionFrom = "v1")); + cy.notVisible(commonWidgetSelector.draggableWidget("button1")); }); }); From 40f210d97152ddf13f108a54d5efecd1798e26ba Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Fri, 5 Jan 2024 13:26:32 +0530 Subject: [PATCH 06/64] Added automation for Page deletion and disable. (#8476) * Add utils * Add it block --- .../editor/multipage/multipageHappypath.cy.js | 24 +++++++++++++++++++ .../cypress/support/utils/multipage.js | 21 ++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/cypress-tests/cypress/e2e/editor/multipage/multipageHappypath.cy.js b/cypress-tests/cypress/e2e/editor/multipage/multipageHappypath.cy.js index ccf9060476..102390388d 100644 --- a/cypress-tests/cypress/e2e/editor/multipage/multipageHappypath.cy.js +++ b/cypress-tests/cypress/e2e/editor/multipage/multipageHappypath.cy.js @@ -36,6 +36,7 @@ import { modifyPageHandle, clearSearch, searchPage, + disableOrEnablePage, } from "Support/utils/multipage"; describe("Multipage", () => { @@ -285,4 +286,27 @@ describe("Multipage", () => { cy.get(multipageSelector.homePageLabel).click(); }); + it("should verify the disable/delete functions of multipage", () => { + cy.get(multipageSelector.sidebarPageButton).click(); + cy.get(multipageSelector.pagesPinIcon).click(); + addNewPage("pageOne"); + addNewPage("pageTwo"); + addNewPage("pageThree"); + + detetePage("pageOne"); + disableOrEnablePage("pageTwo"); + cy.get( + '[data-cy="pages-name-pagetwo"] > .color-slate09' + ).verifyVisibleElement("have.text", "Disabled"); + cy.openInCurrentTab(commonWidgetSelector.previewButton); + cy.notVisible(`[data-cy="pages-name-pageone}"]`); + cy.notVisible(`[data-cy="pages-name-pagetwo}"]`); + cy.wait(1000); + cy.url().should("contain", "/home?"); + cy.url().then((url) => { + cy.visit(url.replace("home", "pagetwo")); + }); + cy.wait(1000); + cy.url().should("contain", "/home?"); + }); }); diff --git a/cypress-tests/cypress/support/utils/multipage.js b/cypress-tests/cypress/support/utils/multipage.js index 1e62a3b01e..8821054af0 100644 --- a/cypress-tests/cypress/support/utils/multipage.js +++ b/cypress-tests/cypress/support/utils/multipage.js @@ -1,4 +1,5 @@ import { multipageSelector } from "Selectors/multipage"; +import { commonSelectors } from "../../constants/selectors/common"; export const searchPage = (pageName) => { cy.get('[title="Search"]').click(); @@ -21,10 +22,17 @@ export const modifyPageHandle = (pageName, handle) => { }; export const detetePage = (pageName) => { - cy.get(`[data-cy="pages-name-${pageName.toLowerCase()}"]`).click(); - cy.get(multipageSelector.pageMenuIcon).click(); + cy.get(`[data-cy="pages-name-${pageName.toLowerCase()}"]`) + .click() + .parent() + .find(multipageSelector.pageMenuIcon) + .click(); cy.get(multipageSelector.deletePageOptionButton).click(); cy.get(multipageSelector.modalConfirmButton).click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + `${pageName} page deleted.` + ); cy.notVisible(`[data-cy="pages-name-${pageName.toLowerCase()}"]`); }; @@ -73,3 +81,12 @@ export const hideOrUnhidePageMenu = () => { cy.get(multipageSelector.pagesMenuIcon).click(); cy.get(multipageSelector.disableMenuToggle).click(); }; + +export const disableOrEnablePage = (pageName, option = "disable") => { + cy.get(`[data-cy="pages-name-${pageName.toLowerCase()}"]`) + .click() + .parent() + .find(multipageSelector.pageMenuIcon) + .click(); + cy.get(`[data-cy="${option}-option-button"]`).click(); +}; From fb60b531a936af11cc3a97e9527c81ec171ff141 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Fri, 5 Jan 2024 13:27:55 +0530 Subject: [PATCH 07/64] Add automation to verify chaining of queries. (#8489) * Add apiCommands * Add test cases * Add command logs * Modify apiCommands with ds null value * Add utils * Add testcases as chaining of queries * Add duplication test cases --- cypress-tests/cypress/commands/apiCommands.js | 2 +- .../editor/queries/chainingOfQueries.cy.js | 176 ++++++++++++++++++ .../cypress/support/utils/queries.js | 19 +- 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 cypress-tests/cypress/e2e/editor/queries/chainingOfQueries.cy.js diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 1fec7f0ce3..eb0488d29d 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -255,7 +255,7 @@ Cypress.Commands.add( name: queryName, kind: dsKind, options: options, - data_source_id: Cypress.env(`${dsName}-id`), + data_source_id: dsName != null ? Cypress.env(`${dsName}-id`) : null, plugin_id: null, }, }).then((queryResponse) => { diff --git a/cypress-tests/cypress/e2e/editor/queries/chainingOfQueries.cy.js b/cypress-tests/cypress/e2e/editor/queries/chainingOfQueries.cy.js new file mode 100644 index 0000000000..9f485b7c41 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/queries/chainingOfQueries.cy.js @@ -0,0 +1,176 @@ +import { fake } from "Fixtures/fake"; +import { commonSelectors, commonWidgetSelector } from "Selectors/common"; +import { openEditorSidebar } from "Support/utils/commonWidget"; +import { selectEvent } from "Support/utils/events"; +import { randomString } from "Support/utils/textInput"; +import { buttonText } from "Texts/button"; + +import { addSuccessNotification, chainQuery } from "Support/utils/queries"; + +import { resizeQueryPanel } from "Support/utils/dataSource"; + +describe("Chaining of queries", () => { + beforeEach(() => { + cy.apiLogin(); + cy.apiCreateApp(`${fake.companyName}-App`); + cy.openApp(); + cy.viewport(1800, 1800); + cy.dragAndDropWidget("Button"); + resizeQueryPanel("80"); + }); + + it("should verify the chainig of runjs, restapi, runpy, tooljetdb and postgres", () => { + const data = {}; + let dsName = fake.companyName; + data.customText = randomString(12); + cy.apiAddQueryToApp( + "runjs", + { code: "return true", hasParamSupport: true, parameters: [] }, + null, + "runjs" + ); + cy.apiAddQueryToApp( + "runpy", + { code: "True", hasParamSupport: true, parameters: [] }, + null, + "runpy" + ); + cy.apiAddQueryToApp( + "restapi", + { + method: "get", + url: "https://gorest.co.in/public/v2/users", + url_params: [["", ""]], + }, + null, + "restapi" + ); + cy.apiAddQueryToApp( + "tjdb", + { + operation: "", + transformationLanguage: "javascript", + enableTransformation: false, + }, + null, + "tooljetdb" + ); + + cy.apiCreateGDS( + "http://localhost:3000/api/v2/data_sources", + `cypress-${dsName}-postgresql`, + "postgresql", + [ + { key: "host", value: Cypress.env("pg_host") }, + { key: "port", value: 5432 }, + { key: "database", value: Cypress.env("pg_user") }, + { key: "username", value: Cypress.env("pg_user") }, + { key: "password", value: Cypress.env("pg_password"), encrypted: true }, + { key: "ssl_enabled", value: false, encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + ] + ); + cy.apiAddQueryToApp( + "psql", + { + mode: "sql", + transformationLanguage: "javascript", + enableTransformation: false, + query: `SELECT * FROM server_side_pagination`, + }, + `cypress-${dsName}-postgresql`, + "postgresql" + ); + cy.reload(); + resizeQueryPanel("80"); + chainQuery("psql", "runjs"); + addSuccessNotification("psql"); + chainQuery("runjs", "runpy"); + addSuccessNotification("runjs"); + chainQuery("runpy", "restapi"); + addSuccessNotification("runpy"); + chainQuery("restapi", "tjdb"); + addSuccessNotification("restapi"); + + openEditorSidebar(buttonText.defaultWidgetName); + selectEvent("On Click", "Run Query", 1, `[data-cy="add-event-handler"]`, 1); + cy.wait(500); + cy.get('[data-cy="query-selection-field"]') + .click() + .find("input") + .type(`{selectAll}{backspace}psql{enter}`); + cy.forceClickOnCanvas(); + + cy.get(commonWidgetSelector.draggableWidget("button1")).click(); + cy.verifyToastMessage(commonSelectors.toastMessage, "psql"); + cy.verifyToastMessage(commonSelectors.toastMessage, "runjs"); + cy.verifyToastMessage(commonSelectors.toastMessage, "runpy"); + cy.verifyToastMessage(commonSelectors.toastMessage, "restapi"); + cy.verifyToastMessage(commonSelectors.toastMessage, "Invalid operation"); + }); + + it("should verify query duplication", () => { + const data = {}; + let dsName = fake.companyName; + data.customText = randomString(12); + cy.apiAddQueryToApp( + "runjs", + { code: "return true", hasParamSupport: true, parameters: [] }, + null, + "runjs" + ); + cy.apiAddQueryToApp( + "runpy", + { code: "True", hasParamSupport: true, parameters: [] }, + null, + "runpy" + ); + + cy.reload(); + resizeQueryPanel("80"); + addSuccessNotification("runpy"); + chainQuery("runjs", "runpy"); + addSuccessNotification("runjs"); + + openEditorSidebar(buttonText.defaultWidgetName); + selectEvent("On Click", "Run Query", 1, `[data-cy="add-event-handler"]`, 1); + cy.wait(500); + cy.get('[data-cy="query-selection-field"]') + .click() + .find("input") + .type(`{selectAll}{backspace}runjs{enter}`); + cy.forceClickOnCanvas(); + + cy.get(commonWidgetSelector.draggableWidget("button1")).click(); + cy.verifyToastMessage(commonSelectors.toastMessage, "runjs"); + cy.verifyToastMessage(commonSelectors.toastMessage, "runpy"); + cy.get('[data-cy="list-query-runjs"]') + .trigger("mouseover") + .parent() + .parent() + .find('[data-cy="copy-icon"]') + .eq(0) + .invoke("show") + .click({ force: true }); + cy.get('[data-cy="list-query-runjs_copy"]').verifyVisibleElement( + "have.text", + "runjs_copy " + ); + cy.get('[data-cy="notification-on-success-toggle-switch"]').should( + "have.value", + "on" + ); + cy.get('[data-cy="success-message-input-field"]').should( + "contain.text", + "runjs" + ); + cy.get(".query-definition-pane-wrapper").within(() => { + cy.get('[data-cy="event-handler-card"]').eq(0).click(); + cy.wait(500); + }); + cy.get( + `[data-cy="action-selection"] > .select-search > .react-select__control > .react-select__value-container > ` + ).should("have.text", "Run Query"); + cy.get('[data-cy="query-selection-field"]').should("have.text", "runpy"); + }); +}); diff --git a/cypress-tests/cypress/support/utils/queries.js b/cypress-tests/cypress/support/utils/queries.js index 69912228a6..433ddff5de 100644 --- a/cypress-tests/cypress/support/utils/queries.js +++ b/cypress-tests/cypress/support/utils/queries.js @@ -1,4 +1,5 @@ import { postgreSqlSelector } from "Selectors/postgreSql"; +import { selectEvent } from "Support/utils/events"; export const selectQueryFromLandingPage = (dbName, label) => { cy.get( @@ -11,7 +12,7 @@ export const selectQueryFromLandingPage = (dbName, label) => { export const deleteQuery = (queryName) => { cy.get(`[data-cy="list-query-${queryName}"]`).realHover(); - cy.get(`[data-cy="elete-query-${queryName}"]`).click(); + cy.get(`[data-cy="delete-query-${queryName}"]`).click(); }; export const query = (action) => { @@ -43,3 +44,19 @@ export const waitForQueryAction = (action) => { "button-loading" ); }; + +export const chainQuery = (currentQuery, trigger) => { + cy.get(`[data-cy="list-query-${currentQuery}"]`).click(); + selectEvent("Query Success", "Run Query"); + cy.get('[data-cy="query-selection-field"]') + .click() + .find("input") + .type(`{selectAll}{backspace}${trigger}{enter}`); +}; + +export const addSuccessNotification = (notification) => { + changeQueryToggles("notification-on-success"); + cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror( + notification + ); +}; From 211357ce903cd945141e9d72072e843eb9d7f4d8 Mon Sep 17 00:00:00 2001 From: nandinisaha13 Date: Fri, 5 Jan 2024 14:40:16 +0530 Subject: [PATCH 08/64] added specs for deletion of compponent from right side panel & keyboard action --- .../e2e/editor/inspectorHappypath.cy.js | 2 +- .../e2e/editor/widget/buttonHappyPath.cy.js | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js b/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js index 7080e361d0..4c3b69465f 100644 --- a/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js +++ b/cypress-tests/cypress/e2e/editor/inspectorHappypath.cy.js @@ -178,7 +178,7 @@ describe("Editor- Inspector", () => { cy.notVisible(commonWidgetSelector.draggableWidget("button1")); cy.apiDeleteApp(); }); - it.only("should verify deletion of component from inspector", () => { + it("should verify deletion of component from inspector", () => { cy.dragAndDropWidget("button", 500, 500); cy.get(commonWidgetSelector.sidebarinspector).click(); deleteComponentFromInspector("button1"); diff --git a/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js index a136c7482b..31cd75ee45 100644 --- a/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/buttonHappyPath.cy.js @@ -443,4 +443,28 @@ describe("Editor- Test Button widget", () => { ).should("not.be.visible"); cy.apiDeleteApp(); }); + it("Should verify deletion of button component from right side panel", () => { + cy.get('.col-2').click(); + cy.get('.list-item-popover-body > :nth-child(3)').click(); + cy.get('[data-cy="yes-button"]').click(); + cy.verifyToastMessage( + `[class=go3958317564]`, + "Component deleted! (โŒ˜ + Z to undo)" + ); + cy.notVisible(commonWidgetSelector.draggableWidget("button1")); + cy.reload(); + cy.notVisible(commonWidgetSelector.draggableWidget("button1")); + + }); + it("Should delete button via keyboard action", () => { + cy.realPress("Backspace"); + cy.get('[data-cy="yes-button"]').click(); + cy.verifyToastMessage( + `[class=go3958317564]`, + "Component deleted! (โŒ˜ + Z to undo)" + ); + cy.notVisible(commonWidgetSelector.draggableWidget("button1")); + cy.reload(); + cy.notVisible(commonWidgetSelector.draggableWidget("button1")); + }); }); From 291a955fecbacbfb56ca7089e418d08707cf4f6b Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Mon, 8 Jan 2024 16:20:39 +0530 Subject: [PATCH 09/64] Added automation to verify Copy-Paste and duplication of components (#8522) * Update data-cy * Add utils * Add spec * Remove unused imports --- .../componentDuplicationHappypath.cy.js | 110 ++++++++++++++++++ cypress-tests/cypress/support/utils/button.js | 105 ++++++++++++++++- frontend/src/Editor/Inspector/Inspector.jsx | 3 +- 3 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 cypress-tests/cypress/e2e/editor/widget/componentDuplicationHappypath.cy.js diff --git a/cypress-tests/cypress/e2e/editor/widget/componentDuplicationHappypath.cy.js b/cypress-tests/cypress/e2e/editor/widget/componentDuplicationHappypath.cy.js new file mode 100644 index 0000000000..991b933518 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/widget/componentDuplicationHappypath.cy.js @@ -0,0 +1,110 @@ +import { fake } from "Fixtures/fake"; +import { commonSelectors, commonWidgetSelector } from "Selectors/common"; +import { buttonText } from "Texts/button"; + +import { addBasicData, verifyBasicData } from "Support/utils/button"; + +import { openEditorSidebar } from "Support/utils/commonWidget"; + +describe("Editor- component duplication", () => { + const data = {}; + beforeEach(() => { + cy.apiLogin(); + cy.apiCreateApp(`${fake.companyName}-App`); + cy.openApp(); + cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 500); + + data.appName = `${fake.companyName}-App`; + data.alertMessage = fake.randomSentence; + data.widgetName = fake.widgetName; + data.customMessage = fake.randomSentence; + data.backgroundColor = fake.randomRgba; + data.textColor = fake.randomRgba; + data.loaderColor = fake.randomRgba; + data.boxShadowColor = fake.randomRgba; + data.boxShadowParam = fake.boxShadowParam; + data.tooltipText = fake.randomSentence; + }); + + it("should verify duplication using copy and paste", () => { + addBasicData(data); + cy.forceClickOnCanvas(); + openEditorSidebar("button1"); + cy.realPress(["Control", "c"]); + cy.moveComponent("button1", 200, 200); + cy.verifyToastMessage( + commonSelectors.toastMessage, + "Component copied successfully" + ); + cy.forceClickOnCanvas(); + + cy.get('[data-cy="real-canvas"]').realPress(["Control", "v"]); + + verifyBasicData("button2", data); + + cy.reload(); + cy.wait(2500); + verifyBasicData("button2", data); + }); + it("should verify componen paste to container", () => { + addBasicData(data); + cy.forceClickOnCanvas(); + openEditorSidebar("button1"); + cy.realPress(["Control", "c"]); + cy.moveComponent("button1", 200, 90); + cy.dragAndDropWidget("Container", 300, 200); + cy.resizeWidget("container1", 800, 500); + cy.verifyToastMessage( + commonSelectors.toastMessage, + "Component copied successfully", + false + ); + cy.forceClickOnCanvas(); + openEditorSidebar("container1"); + cy.get(`${commonWidgetSelector.draggableWidget("container1")}>`) + .click({ force: true }) + .within(() => { + cy.realPress(["Control", "v"]); + cy.wait(1000); + cy.get(commonWidgetSelector.draggableWidget("button2")).should( + "be.visible" + ); + }); + verifyBasicData("button2", data); + }); + + it("should verify duplication using right side panel", () => { + addBasicData(data); + cy.forceClickOnCanvas(); + openEditorSidebar("button1"); + cy.get('[data-cy="component-inspector-options"]>').click(); + cy.get('[data-cy="component-inspector-duplicate-button"]').click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + "Component cloned succesfully" + ); + cy.moveComponent("button1", 200, 200); + cy.forceClickOnCanvas(); + cy.wait(1000); + verifyBasicData("button2", data); + }); + + it("should verify duplication using keyboard", () => { + addBasicData(data); + cy.forceClickOnCanvas(); + openEditorSidebar("button1"); + cy.realPress(["Control", "d"]); + cy.verifyToastMessage( + commonSelectors.toastMessage, + "Component cloned succesfully" + ); + cy.moveComponent("button1", 200, 200); + cy.forceClickOnCanvas(); + cy.wait(1000); + verifyBasicData("button2", data); + + cy.reload(); + cy.wait(2500); + verifyBasicData("button2", data); + }); +}); diff --git a/cypress-tests/cypress/support/utils/button.js b/cypress-tests/cypress/support/utils/button.js index 2662b7a839..449c3e6042 100644 --- a/cypress-tests/cypress/support/utils/button.js +++ b/cypress-tests/cypress/support/utils/button.js @@ -1,6 +1,18 @@ -import { commonWidgetSelector } from "Selectors/common"; +import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { openAccordion, openEditorSidebar } from "Support/utils/commonWidget"; +import { buttonText } from "Texts/button"; import { commonWidgetText } from "Texts/common"; +import { + addDefaultEventHandler, + selectColourFromColourPicker, + verifyAndModifyParameter, + verifyBoxShadowCss, + verifyLoaderColor, + verifyPropertiesGeneralAccordion, + verifyStylesGeneralAccordion, + verifyTooltip, + verifyWidgetColorCss, +} from "Support/utils/commonWidget"; export const verifyControlComponentAction = (widgetName, value) => { cy.forceClickOnCanvas(); @@ -24,10 +36,97 @@ export const verifyControlComponentAction = (widgetName, value) => { .clearAndTypeOnCodeMirror(value); cy.forceClickOnCanvas(); cy.waitForAutoSave(); - cy.get(commonWidgetSelector.draggableWidget(widgetName)).click(); +}; +export const addBasicData = (data) => { + openEditorSidebar(buttonText.defaultWidgetName); + verifyAndModifyParameter(buttonText.buttonTextLabel, data.widgetName); + + openAccordion(commonWidgetText.accordionEvents); + addDefaultEventHandler(data.alertMessage); + + verifyPropertiesGeneralAccordion( + buttonText.defaultWidgetName, + data.tooltipText + ); + + openEditorSidebar(buttonText.defaultWidgetName); + cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); + selectColourFromColourPicker( + buttonText.backgroundColor, + data.backgroundColor + ); + + cy.forceClickOnCanvas(); + openEditorSidebar(buttonText.defaultWidgetName); + cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); + selectColourFromColourPicker(buttonText.textColor, data.textColor, 1); + + cy.forceClickOnCanvas(); + openEditorSidebar(buttonText.defaultWidgetName); + cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); + selectColourFromColourPicker(buttonText.loaderColor, data.loaderColor, 2); + + cy.forceClickOnCanvas(); + openEditorSidebar(buttonText.defaultWidgetName); + cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); + + cy.get( + commonWidgetSelector.parameterInputField( + commonWidgetText.parameterBorderRadius + ) + ) + .first() + .clear() + .type(buttonText.borderRadiusInput); + + verifyStylesGeneralAccordion( + buttonText.defaultWidgetName, + data.boxShadowParam, + data.colourHex, + data.boxShadowColor, + 4 + ); + + verifyControlComponentAction( + buttonText.defaultWidgetName, + data.customMessage + ); + + cy.waitForAutoSave(); +}; + +export const verifyBasicData = (widgetName, data) => { + cy.get(commonWidgetSelector.draggableWidget(widgetName)).verifyVisibleElement( + "have.text", + data.widgetName + ); + cy.wait(1500); + cy.get(commonWidgetSelector.draggableWidget(widgetName)).click({ + force: true, + }); + cy.wait(500); + + cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage); cy.get(commonWidgetSelector.draggableWidget("textinput1")).should( "have.value", - value + data.customMessage ); + + verifyTooltip( + commonWidgetSelector.draggableWidget(widgetName), + data.tooltipText + ); + + verifyWidgetColorCss(widgetName, "background-color", data.backgroundColor); + verifyWidgetColorCss(widgetName, "color", data.textColor); + verifyLoaderColor(widgetName, data.loaderColor); + + cy.get(commonWidgetSelector.draggableWidget(widgetName)).should( + "have.css", + "border-radius", + "20px" + ); + + verifyBoxShadowCss(widgetName, data.boxShadowColor, data.boxShadowParam); }; diff --git a/frontend/src/Editor/Inspector/Inspector.jsx b/frontend/src/Editor/Inspector/Inspector.jsx index 37ab887a1c..683c93389b 100644 --- a/frontend/src/Editor/Inspector/Inspector.jsx +++ b/frontend/src/Editor/Inspector/Inspector.jsx @@ -370,7 +370,7 @@ export const Inspector = ({ /> -
+
{INSPECTOR_HEADER_OPTIONS.map((option) => (
{ From 1141f710ba0e1e9e3ecdb79de7bcf1a90bc7c7bc Mon Sep 17 00:00:00 2001 From: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:58:47 +0530 Subject: [PATCH 10/64] Add null check in modal when clicked outside (#8582) --- frontend/src/Editor/Components/Modal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Editor/Components/Modal.jsx b/frontend/src/Editor/Components/Modal.jsx index eef8675710..0891bc05a4 100644 --- a/frontend/src/Editor/Components/Modal.jsx +++ b/frontend/src/Editor/Components/Modal.jsx @@ -158,7 +158,7 @@ export const Modal = function Modal({ useEffect(() => { if (closeOnClickingOutside) { const handleClickOutside = (event) => { - const modalRef = parentRef.current.parentElement.parentElement.parentElement; + const modalRef = parentRef?.current?.parentElement?.parentElement?.parentElement; if (modalRef && modalRef === event.target) { hideModal(); From c643a8128f43530dd5aeb032e16f18093c7a6c5f Mon Sep 17 00:00:00 2001 From: Arpit Date: Thu, 1 Feb 2024 11:38:44 +0530 Subject: [PATCH 11/64] fixes: app versioning with kannban children and import export apps (#8407) --- .../src/services/app_import_export.service.ts | 29 +++++++++++++++++++ server/src/services/apps.service.ts | 16 ++++++++++ 2 files changed, 45 insertions(+) diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index d7d2cefcf8..1e064ec67d 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -712,6 +712,11 @@ export class AppImportExportService { const mappedParentId = newComponentIdsMap[_parentId]; parentId = `${mappedParentId}-${childTabId}`; + } else if (isChildOfKanbanModal(component, pageComponents, parentId, true)) { + const _parentId = component?.parent?.split('-').slice(0, -1).join('-'); + const mappedParentId = newComponentIdsMap[_parentId]; + + parentId = `${mappedParentId}-modal`; } else { if (component.parent && !newComponentIdsMap[parentId]) { skipComponent = true; @@ -1694,6 +1699,11 @@ function transformComponentData( const mappedParentId = componentsMapping[_parentId]; parentId = `${mappedParentId}-${childTabId}`; + } else if (isChildOfKanbanModal(component, allComponents, parentId, true)) { + const _parentId = component?.parent?.split('-').slice(0, -1).join('-'); + const mappedParentId = componentsMapping[_parentId]; + + parentId = `${mappedParentId}-modal`; } else { if (component.parent && !componentsMapping[parentId]) { skipComponent = true; @@ -1748,3 +1758,22 @@ const isChildOfTabsOrCalendar = ( return false; }; + +const isChildOfKanbanModal = ( + component, + allComponents = [], + componentParentId = undefined, + isNormalizedAppDefinitionSchema: boolean +) => { + if (!componentParentId || !componentParentId.includes('modal')) return false; + + const parentId = component?.parent?.split('-').slice(0, -1).join('-'); + + const parentComponent = allComponents.find((comp) => comp.id === parentId); + + if (!isNormalizedAppDefinitionSchema) { + return parentComponent.component.component === 'Kanban'; + } + + return parentComponent.type === 'Kanban'; +}; diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index 87b239ee38..663e335098 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -477,6 +477,17 @@ export class AppsService { return false; }; + const isChildOfKanbanModal = (componentParentId: string, allComponents = []) => { + if (!componentParentId.includes('modal')) return false; + + if (componentParentId) { + const parentId = componentParentId.split('-').slice(0, -1).join('-'); + const isParentKandban = allComponents.find((comp) => comp.id === parentId)?.type === 'Kanban'; + + return isParentKandban; + } + }; + for (const page of pages) { const savedPage = await manager.save( manager.create(Page, { @@ -574,6 +585,11 @@ export class AppsService { const mappedParentId = oldComponentToNewComponentMapping[_parentId]; parentId = `${mappedParentId}-${childTabId}`; + } else if (isChildOfKanbanModal(component.parent, page.components)) { + const _parentId = component?.parent?.split('-').slice(0, -1).join('-'); + const mappedParentId = oldComponentToNewComponentMapping[_parentId]; + + parentId = `${mappedParentId}-modal`; } else { parentId = oldComponentToNewComponentMapping[parentId]; } From d36cc44920faade68f82d88eebdfe414346a80a2 Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Tue, 6 Feb 2024 10:27:31 +0530 Subject: [PATCH 12/64] test: Add automation for text-input revamp. (#8671) * init textinput revamp * updated styles panel * bugfix * updates * fix :: accordion * fix :: styling * add box shadow , additional property,tooltip * fix conditional render for styles * feat :: fixed order of each property and styles * feat :: styling input * bugfix * feat :: add option to add icon * add option to add icon * adding option to toggle visibility * updated password input with new design * chnaging component location * bugfix * style fixes * fix :: added loader * updated :: few detailing * few bugfixes * fix :: for form widget label * fixes * added option to add icon color * including label field for password input * fix for label * fix * test fix backward compatibility for height * updates * revert * adding key for distinguishing older and newer widgets * testing * test * test * update * update * migration testing * limit vertical resizing in textinput * testing * throw test * test * adding check for label length * fixing edge cases * removing resize * backward compatibility height * backward compatibility * number input review fixes * added exposed items * fixing csa * ui fixes * fix height compatibility * feat :: csa for all inputs and exposed variables * backward compatibility fixes and validation fixes * fixes :: textinput positioning of loader and icon * fix :: password input * cleanup and fixes * fixes * cleanup * fixes * review fixes * review fixes * typo fix * fix padding * review fixes styles component panel * fix naming * fix padding * fix :: icons position * updates * cleanup * updates events , csa * backward compatibility * clean * feat :: change validation from properties * ui fixes * icon name * removed 'px' text from tooltip * fixes placeholder * few updates :: removing label in form * ui in form * update :: number input validation behaviour * testing fixes * added side handlers * removing unwanted fx * disabling fx for padding field * ordering change * fix * label issue + restricted side handler * fix :: box shadow bug * on change event doesnt propagate exposed vars correctly * adding debounce for slider value change * fix :: for modal ooen bug during onfocus event * test slider * Add common utils * Modify helpers * Add text input spec * Add utils for field validation * Minor spec updates * Fix for password basic automation cases --------- Co-authored-by: stepinfwd --- cypress-tests/cypress/commands/commands.js | 54 +- .../cypress/constants/texts/button.js | 2 +- .../cypress/constants/texts/common.js | 1 + .../editor/widget/numberInputHappyPath.cy.js | 275 ++++--- .../editor/widget/textInputHappyPath.cy.js | 153 ++-- cypress-tests/cypress/fixtures/fake.js | 5 +- .../cypress/support/utils/commonWidget.js | 21 +- .../support/utils/editor/inputFieldUtils.js | 121 +++ .../cypress/support/utils/textInput.js | 4 +- docs/src/components/DocsCard/DocsCardList.jsx | 1 - frontend/assets/translations/en.json | 1 + frontend/src/Editor/Box.jsx | 19 +- .../src/Editor/CodeBuilder/CodeHinter.jsx | 78 +- .../Editor/CodeBuilder/Elements/Checkbox.jsx | 25 + .../src/Editor/CodeBuilder/Elements/Icon.jsx | 122 +++ .../src/Editor/CodeBuilder/Elements/Input.jsx | 22 + .../Editor/CodeBuilder/Elements/Slider.jsx | 49 ++ .../Editor/CodeBuilder/Elements/Switch.jsx | 21 + .../CodeBuilder/Elements/Visibility.jsx | 21 + .../src/Editor/CodeBuilder/TypeMapping.js | 6 + frontend/src/Editor/Components/Form/Form.jsx | 3 + .../src/Editor/Components/Form/FormUtils.js | 5 + frontend/src/Editor/Components/Modal.jsx | 2 + .../src/Editor/Components/NumberInput.jsx | 452 +++++++++-- .../src/Editor/Components/PasswordInput.jsx | 387 +++++++++- .../src/Editor/Components/Table/Table.jsx | 1 - .../Editor/Components/Table/columns/index.jsx | 1 - frontend/src/Editor/Components/TextInput.jsx | 320 ++++++-- .../src/Editor/Components/numberinput.scss | 39 + frontend/src/Editor/DraggableBox.jsx | 118 ++- .../Inspector/Components/DefaultComponent.jsx | 49 +- .../src/Editor/Inspector/Elements/Code.jsx | 24 +- .../Inspector/Elements/Components/ToolTip.jsx | 3 +- frontend/src/Editor/Inspector/Inspector.jsx | 100 ++- frontend/src/Editor/Inspector/Utils.js | 71 ++ frontend/src/Editor/Viewer.jsx | 5 - .../src/Editor/WidgetManager/widgetConfig.js | 729 +++++++++++++++--- frontend/src/ToolJetUI/Loader/Loader.jsx | 47 ++ frontend/src/ToolJetUI/Loader/Loader.scss | 4 + .../src/ToolJetUI/SwitchGroup/ToggleGroup.jsx | 1 - .../ToolJetUI/SwitchGroup/ToggleGroupItem.jsx | 17 +- .../ToolJetUI/SwitchGroup/toggleGroup.scss | 33 +- frontend/src/ToolJetUI/Tabs/tabs.scss | 11 +- frontend/src/_helpers/utils.js | 13 +- frontend/src/_styles/designtheme.scss | 23 +- frontend/src/_styles/theme.scss | 198 +++-- frontend/src/_ui/Accordion/AccordionItem.js | 32 +- frontend/src/_ui/CustomInput/CustomInput.scss | 19 + frontend/src/_ui/CustomInput/index.js | 25 + .../Icon/solidIcons/AlignLeftinspector.jsx | 18 + .../Icon/solidIcons/AlignRightinspector.jsx | 18 + .../src/_ui/Icon/solidIcons/CheveronDown.jsx | 5 +- .../src/_ui/Icon/solidIcons/CheveronUp.jsx | 5 +- frontend/src/_ui/Icon/solidIcons/Eye1.jsx | 3 +- .../src/_ui/Icon/solidIcons/EyeDisable.jsx | 3 +- frontend/src/_ui/Icon/solidIcons/index.js | 7 +- .../1701335703893-TextInputMaxHeight.ts | 29 + ...05162272372-numberInputMinMaxValidation.ts | 41 + 58 files changed, 3177 insertions(+), 685 deletions(-) create mode 100644 cypress-tests/cypress/support/utils/editor/inputFieldUtils.js create mode 100644 frontend/src/Editor/CodeBuilder/Elements/Checkbox.jsx create mode 100644 frontend/src/Editor/CodeBuilder/Elements/Icon.jsx create mode 100644 frontend/src/Editor/CodeBuilder/Elements/Input.jsx create mode 100644 frontend/src/Editor/CodeBuilder/Elements/Slider.jsx create mode 100644 frontend/src/Editor/CodeBuilder/Elements/Switch.jsx create mode 100644 frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx create mode 100644 frontend/src/Editor/Components/numberinput.scss create mode 100644 frontend/src/ToolJetUI/Loader/Loader.jsx create mode 100644 frontend/src/ToolJetUI/Loader/Loader.scss create mode 100644 frontend/src/_ui/CustomInput/CustomInput.scss create mode 100644 frontend/src/_ui/CustomInput/index.js create mode 100644 frontend/src/_ui/Icon/solidIcons/AlignLeftinspector.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/AlignRightinspector.jsx create mode 100644 server/migrations/1701335703893-TextInputMaxHeight.ts create mode 100644 server/migrations/1705162272372-numberInputMinMaxValidation.ts diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index 3f7b01d2f1..b944c664e1 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -115,9 +115,9 @@ Cypress.Commands.add( .last() .click() .type(createBackspaceText(text), { delay: 0 }), - { - delay: 0, - }; + { + delay: 0, + }; }); if (!Array.isArray(value)) { cy.wrap(subject).last().type(value, { @@ -193,9 +193,9 @@ Cypress.Commands.add( .invoke("text") .then((text) => { cy.wrap(subject).realType(createBackspaceText(text)), - { - delay: 0, - }; + { + delay: 0, + }; }); } ); @@ -366,19 +366,31 @@ Cypress.Commands.add("getPosition", (componentName) => { Cypress.Commands.add("defaultWorkspaceLogin", () => { cy.apiLogin(); - cy.intercept('GET', "http://localhost:3000/api/library_apps/").as("library_apps") - cy.visit('/my-workspace'); - cy.get(commonSelectors.homePageLogo, { timeout: 10000 }) - cy.wait("@library_apps") -}) - -Cypress.Commands.add('visitSlug', ({ actualUrl, currentUrl = 'http://localhost:8082/error/unknown' }) => { - cy.visit(actualUrl); - cy.wait(3000); - - cy.url().then((url) => { - if (url === currentUrl) { - cy.visit(actualUrl); - } - }); + cy.intercept("GET", "http://localhost:3000/api/library_apps/").as( + "library_apps" + ); + cy.visit("/my-workspace"); + cy.get(commonSelectors.homePageLogo, { timeout: 10000 }); + cy.wait("@library_apps"); }); + +Cypress.Commands.add( + "visitSlug", + ({ actualUrl, currentUrl = "http://localhost:8082/error/unknown" }) => { + cy.visit(actualUrl); + cy.wait(3000); + + cy.url().then((url) => { + if (url === currentUrl) { + cy.visit(actualUrl); + } + }); + } +); + +Cypress.Commands.add( + "verifyCssProperty", + (selector, property, expectedValue) => { + cy.get(selector).should("have.css", property).and("eq", expectedValue); + } +); diff --git a/cypress-tests/cypress/constants/texts/button.js b/cypress-tests/cypress/constants/texts/button.js index bc1aebacd0..eb7bc76ed2 100644 --- a/cypress-tests/cypress/constants/texts/button.js +++ b/cypress-tests/cypress/constants/texts/button.js @@ -4,7 +4,7 @@ export const buttonText = { buttonTextLabel: "Button text", loadingState: "Loading state", buttonDocumentationLink: "Read documentation for Button", - backgroundColor: "Background Color", + backgroundColor: "BG color", textColor: "Text color", loaderColor: "Loader color", defaultBackgroundColor: "#375FCF", diff --git a/cypress-tests/cypress/constants/texts/common.js b/cypress-tests/cypress/constants/texts/common.js index 1faff84416..35c4143a98 100644 --- a/cypress-tests/cypress/constants/texts/common.js +++ b/cypress-tests/cypress/constants/texts/common.js @@ -204,6 +204,7 @@ export const commonWidgetText = { parameterOptionvalues: "Option values", boxShadowColor: "Box shadow Color", boxShadowFxValue: "-5px 6px 5px 8px #ee121240", + loadingState: "Loading state", codeMirrorLabelTrue: "{{true}}", codeMirrorLabelFalse: "{{false}}", diff --git a/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js index 2d42455b65..9df7fb564c 100644 --- a/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js @@ -1,30 +1,42 @@ import { fake } from "Fixtures/fake"; -import { commonWidgetText } from "Texts/common"; import { commonSelectors, commonWidgetSelector } from "Selectors/common"; +import { commonWidgetText } from "Texts/common"; import { numberInputText } from "Texts/numberInput"; import { - openAccordion, - verifyAndModifyParameter, - openEditorSidebar, - verifyAndModifyToggleFx, - verifyComponentValueFromInspector, - verifyBoxShadowCss, - verifyLayout, - verifyTooltip, - editAndVerifyWidgetName, addTextWidgetToVerifyValue, + editAndVerifyWidgetName, + fillBoxShadowParams, + openAccordion, + openEditorSidebar, + randomNumber, + selectColourFromColourPicker, + verifyAndModifyParameter, + verifyBoxShadowCss, + verifyComponentValueFromInspector, + verifyLayout, verifyPropertiesGeneralAccordion, verifyStylesGeneralAccordion, - randomNumber, - fillBoxShadowParams, - selectColourFromColourPicker, + verifyTooltip, } from "Support/utils/commonWidget"; +import { + addAllInputFieldColors, + addAndVerifyAdditionalActions, + addValidations, + verifyInputFieldColors, +} from "Support/utils/editor/inputFieldUtils"; + +import { + addDefaultEventHandler, + closeAccordions, +} from "Support/utils/commonWidget"; +import { verifyControlComponentAction } from "Support/utils/textInput"; +import { widgetValue } from "Texts/common"; describe("Number Input", () => { beforeEach(() => { cy.apiLogin(); - cy.apiCreateApp(); + cy.apiCreateApp(`${fake.companyName}-numberInput-App`); cy.openApp(); cy.dragAndDropWidget("Number Input"); }); @@ -32,164 +44,191 @@ describe("Number Input", () => { cy.apiDeleteApp(); }); - it("should verify the properties of the number input widget", () => { + it.only("should verify the properties of the text input widget", () => { const data = {}; - data.appName = `${fake.companyName}-App`; data.widgetName = fake.widgetName; data.tooltipText = fake.randomSentence; - data.randomNumber = `${randomNumber(10, 99)}`; - data.minimumvalue = `${randomNumber(5, 10)}`; - data.maximumValue = `${randomNumber(90, 99)}`; + data.minimumLength = randomNumber(1, 4); + data.maximumLength = randomNumber(8, 10); + data.customText = randomNumber(12); + data.customNumber = randomNumber(12); openEditorSidebar(numberInputText.defaultWidgetName); - editAndVerifyWidgetName(data.widgetName); - cy.get( - commonWidgetSelector.draggableWidget(data.widgetName) - ).verifyVisibleElement("have.value", "99"); - - openEditorSidebar(data.widgetName); - openAccordion(commonWidgetText.accordionProperties, [ - "Events", + closeAccordions([ "Properties", - "Layout", + "Validation", + "Additional Actions", + "Devices", + "Events", + ]); + editAndVerifyWidgetName(data.widgetName, [ + "Properties", + "Validation", + "Additional Actions", + "Devices", + "Events", + ]); + openAccordion(commonWidgetText.accordionProperties, [ + "Properties", + "Validation", + "Additional Actions", + "Devices", + "Events", ]); verifyAndModifyParameter( commonWidgetText.labelDefaultValue, - data.randomNumber + data.customText ); cy.forceClickOnCanvas(); cy.get( commonWidgetSelector.draggableWidget(data.widgetName) - ).verifyVisibleElement("have.value", data.randomNumber); + ).verifyVisibleElement("have.value", data.customText); - verifyComponentValueFromInspector(data.widgetName, data.randomNumber); + verifyComponentValueFromInspector(data.widgetName, data.customText); cy.forceClickOnCanvas(); + cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear(); + data.customText = fake.randomSentence; openEditorSidebar(data.widgetName); openAccordion(commonWidgetText.accordionProperties, [ - "Events", "Properties", - "Layout", - ]); - verifyAndModifyParameter( - commonWidgetText.labelMinimumValue, - data.minimumvalue - ); - cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); - cy.clearAndType( - commonWidgetSelector.draggableWidget(data.widgetName), - randomNumber(1, 4) - ); - cy.forceClickOnCanvas(); - cy.get( - commonWidgetSelector.draggableWidget(data.widgetName) - ).verifyVisibleElement("have.value", `${data.minimumvalue}`); - - openEditorSidebar(data.widgetName); - openAccordion(commonWidgetText.accordionProperties, [ + "Validation", + "Additional Actions", + "Devices", "Events", - "Properties", - "Layout", - ]); - verifyAndModifyParameter( - commonWidgetText.labelMaximumValue, - `${data.maximumValue}` - ); - cy.clearAndType( - commonWidgetSelector.draggableWidget(data.widgetName), - randomNumber(100, 110) - ); - cy.forceClickOnCanvas; - cy.get( - commonWidgetSelector.draggableWidget(data.widgetName) - ).verifyVisibleElement("have.value", `${data.maximumValue}`); - - openEditorSidebar(data.widgetName); - openAccordion(commonWidgetText.accordionProperties, [ - "Events", - "Properties", - "Layout", ]); verifyAndModifyParameter( commonWidgetText.labelPlaceHolder, - data.randomNumber + data.customText ); - cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); + cy.forceClickOnCanvas(); cy.get(commonWidgetSelector.draggableWidget(data.widgetName)) .invoke("attr", "placeholder") - .should("contain", data.randomNumber); + .should("contain", data.customText); - verifyPropertiesGeneralAccordion(data.widgetName, data.tooltipText); + openEditorSidebar(data.widgetName); + openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]); + addDefaultEventHandler(widgetValue(data.widgetName)); + cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}"); + cy.log("---------------------"); - // verifyLayout(data.widgetName); + cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).type( + `${data.customNumber}{Enter}` + ); + cy.verifyToastMessage(commonSelectors.toastMessage, data.customNumber); + cy.forceClickOnCanvas(); - // cy.get(commonWidgetSelector.changeLayoutButton).click(); - // cy.get( - // commonWidgetSelector.parameterTogglebutton( - // commonWidgetText.parameterShowOnDesktop - // ) - // ).click(); + // cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear(); + + addValidations(data.widgetName, data, "Min value", "Max value"); + + cy.clearAndType( + commonWidgetSelector.draggableWidget(data.widgetName), + data.customText + ); + cy.forceClickOnCanvas(); + cy.get( + commonWidgetSelector.validationFeedbackMessage(data.widgetName) + ).verifyVisibleElement("have.text", commonWidgetText.regexValidationError); + + cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear(); + cy.get( + commonWidgetSelector.parameterInputField(commonWidgetText.labelRegex) + ).clearCodeMirror(); + + cy.forceClickOnCanvas(); + cy.get( + commonWidgetSelector.validationFeedbackMessage(data.widgetName) + ).verifyVisibleElement( + "have.text", + commonWidgetText.minLengthValidationError(data.minimumLength) + ); + + cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear(); + cy.get( + commonWidgetSelector.parameterInputField(commonWidgetText.labelMinLength) + ).clearCodeMirror(); + + cy.forceClickOnCanvas(); + cy.clearAndType( + commonWidgetSelector.draggableWidget(data.widgetName), + data.customText + ); + cy.get( + commonWidgetSelector.validationFeedbackMessage(data.widgetName) + ).verifyVisibleElement( + "have.text", + commonWidgetText.maxLengthValidationError(data.maximumLength) + ); + cy.forceClickOnCanvas(); + cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear(); + cy.get( + commonWidgetSelector.validationFeedbackMessage(data.widgetName) + ).verifyVisibleElement("have.text", data.customText); + + cy.get( + commonWidgetSelector.accordion(commonWidgetText.accordionProperties) + ).click(); + cy.get( + commonWidgetSelector.accordion(commonWidgetText.accordionValidation) + ).click(); + addAndVerifyAdditionalActions(data.widgetName, data.tooltipText); + + openEditorSidebar(data.widgetName); + cy.get( + commonWidgetSelector.accordion(commonWidgetText.accordionProperties) + ).click(); + cy.get( + commonWidgetSelector.accordion(commonWidgetText.accordionValidation) + ).click(); + verifyLayout(data.widgetName, "Devices"); + + cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click(); + cy.get( + commonWidgetSelector.parameterTogglebutton( + commonWidgetText.parameterShowOnDesktop + ) + ).click(); cy.get(commonWidgetSelector.widgetDocumentationLink).should( "have.text", - numberInputText.numberInputDocumentationLink + numberInputText.textInputDocumentationLink ); + data.customText = fake.firstName; + verifyControlComponentAction(data.widgetName, data.customText); }); - it("should verify the styles of the number input widget", () => { + it("should verify the styles of the text input widget", () => { const data = {}; data.appName = `${fake.companyName}-App`; data.colourHex = fake.randomRgbaHex; data.boxShadowColor = fake.randomRgba; data.boxShadowParam = fake.boxShadowParam; + data.bgColor = fake.randomRgba; + data.borderColor = fake.randomRgba; + data.textColor = fake.randomRgba; + data.errorTextColor = fake.randomRgba; + data.iconColor = fake.randomRgba; openEditorSidebar(numberInputText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); + addAllInputFieldColors(data); - verifyAndModifyToggleFx( - commonWidgetText.parameterVisibility, - commonWidgetText.codeMirrorLabelTrue - ); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).should("not.be.visible"); - - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterVisibility - ) - ).click(); - - verifyAndModifyToggleFx( - commonWidgetText.parameterDisable, - commonWidgetText.codeMirrorLabelFalse - ); - cy.waitForAutoSave(); - cy.get( - commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) - ).should("have.attr", "disabled"); - - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterDisable - ) - ).click(); - - verifyAndModifyParameter( - commonWidgetText.parameterBorderRadius, - commonWidgetText.borderRadiusInput - ); + cy.clearAndType('[data-cy="border-radius-input"]', "20"); + cy.get('[data-cy="icon-visibility-button"]').click(); cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); cy.get( commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName) ).should("have.css", "border-radius", "20px"); + verifyInputFieldColors("textinput1", data); + verifyStylesGeneralAccordion( numberInputText.defaultWidgetName, data.boxShadowParam, data.colourHex, data.boxShadowColor, - 3 + 4 ); }); diff --git a/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js index a246504684..651fa3f5aa 100644 --- a/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/textInputHappyPath.cy.js @@ -1,39 +1,43 @@ import { fake } from "Fixtures/fake"; -import { textInputText } from "Texts/textInput"; -import { commonWidgetText, widgetValue, customValidation } from "Texts/common"; import { commonSelectors, commonWidgetSelector } from "Selectors/common"; -import { buttonText } from "Texts/button"; import { - verifyControlComponentAction, - randomString, -} from "Support/utils/textInput"; -import { - openAccordion, - verifyAndModifyParameter, - openEditorSidebar, - verifyAndModifyToggleFx, addDefaultEventHandler, - verifyComponentValueFromInspector, - selectColourFromColourPicker, - verifyBoxShadowCss, - verifyLayout, - verifyTooltip, + closeAccordions, editAndVerifyWidgetName, + openAccordion, + openEditorSidebar, + randomNumber, + verifyAndModifyParameter, + verifyBoxShadowCss, + verifyComponentValueFromInspector, + verifyLayout, verifyPropertiesGeneralAccordion, verifyStylesGeneralAccordion, - randomNumber, - closeAccordions, + verifyTooltip, } from "Support/utils/commonWidget"; import { + addAllInputFieldColors, + addAndVerifyAdditionalActions, + addValidations, + verifyInputFieldColors, +} from "Support/utils/editor/inputFieldUtils"; +import { + addSupportCSAData, selectCSA, selectEvent, - addSupportCSAData, } from "Support/utils/events"; +import { + randomString, + verifyControlComponentAction, +} from "Support/utils/textInput"; +import { buttonText } from "Texts/button"; +import { commonWidgetText, customValidation, widgetValue } from "Texts/common"; +import { textInputText } from "Texts/textInput"; describe("Text Input", () => { beforeEach(() => { cy.apiLogin(); - cy.apiCreateApp(); + cy.apiCreateApp(`${fake.companyName}-Textinput-App`); cy.openApp(); cy.dragAndDropWidget("Text Input", 500, 500); }); @@ -43,7 +47,6 @@ describe("Text Input", () => { it("should verify the properties of the text input widget", () => { const data = {}; - data.appName = `${fake.companyName}-App`; data.widgetName = fake.widgetName; data.tooltipText = fake.randomSentence; data.minimumLength = randomNumber(1, 4); @@ -51,12 +54,25 @@ describe("Text Input", () => { data.customText = randomString(12); openEditorSidebar(textInputText.defaultWidgetName); - closeAccordions(["Validation", "General", "Properties", "Layout"]); - editAndVerifyWidgetName(data.widgetName); - openAccordion(commonWidgetText.accordionProperties, [ - "Validation", - "General", + closeAccordions([ "Properties", + "Validation", + "Additional Actions", + "Devices", + "Events", + ]); + editAndVerifyWidgetName(data.widgetName, [ + "Properties", + "Validation", + "Additional Actions", + "Devices", + "Events", + ]); + openAccordion(commonWidgetText.accordionProperties, [ + "Properties", + "Validation", + "Additional Actions", + "Devices", "Events", ]); verifyAndModifyParameter( @@ -75,10 +91,11 @@ describe("Text Input", () => { data.customText = fake.randomSentence; openEditorSidebar(data.widgetName); openAccordion(commonWidgetText.accordionProperties, [ - "Validation", - "General", - "Events", "Properties", + "Validation", + "Additional Actions", + "Devices", + "Events", ]); verifyAndModifyParameter( commonWidgetText.labelPlaceHolder, @@ -90,7 +107,7 @@ describe("Text Input", () => { .should("contain", data.customText); openEditorSidebar(data.widgetName); - openAccordion(commonWidgetText.accordionEvents, ["Validation", "Layout"]); + openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]); addDefaultEventHandler(widgetValue(data.widgetName)); cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}"); @@ -102,12 +119,9 @@ describe("Text Input", () => { cy.forceClickOnCanvas(); cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear(); - openEditorSidebar(data.widgetName); - openAccordion(commonWidgetText.accordionValidation); - verifyAndModifyParameter( - commonWidgetText.labelRegex, - commonWidgetText.regularExpression - ); + + addValidations(data.widgetName, data); + cy.clearAndType( commonWidgetSelector.draggableWidget(data.widgetName), data.customText @@ -121,10 +135,7 @@ describe("Text Input", () => { cy.get( commonWidgetSelector.parameterInputField(commonWidgetText.labelRegex) ).clearCodeMirror(); - verifyAndModifyParameter( - commonWidgetText.labelMinLength, - data.minimumLength - ); + cy.forceClickOnCanvas(); cy.get( commonWidgetSelector.validationFeedbackMessage(data.widgetName) @@ -137,10 +148,7 @@ describe("Text Input", () => { cy.get( commonWidgetSelector.parameterInputField(commonWidgetText.labelMinLength) ).clearCodeMirror(); - verifyAndModifyParameter( - commonWidgetText.labelMaxLength, - data.maximumLength - ); + cy.forceClickOnCanvas(); cy.clearAndType( commonWidgetSelector.draggableWidget(data.widgetName), @@ -152,11 +160,6 @@ describe("Text Input", () => { "have.text", commonWidgetText.maxLengthValidationError(data.maximumLength) ); - - verifyAndModifyParameter( - commonWidgetText.labelcustomValidadtion, - customValidation(data.widgetName, data.customText) - ); cy.forceClickOnCanvas(); cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear(); cy.get( @@ -169,7 +172,7 @@ describe("Text Input", () => { cy.get( commonWidgetSelector.accordion(commonWidgetText.accordionValidation) ).click(); - verifyPropertiesGeneralAccordion(data.widgetName, data.tooltipText); + addAndVerifyAdditionalActions(data.widgetName, data.tooltipText); openEditorSidebar(data.widgetName); cy.get( @@ -178,7 +181,7 @@ describe("Text Input", () => { cy.get( commonWidgetSelector.accordion(commonWidgetText.accordionValidation) ).click(); - verifyLayout(data.widgetName); + verifyLayout(data.widgetName, "Devices"); cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click(); cy.get( @@ -194,56 +197,32 @@ describe("Text Input", () => { data.customText = fake.firstName; verifyControlComponentAction(data.widgetName, data.customText); }); - it("should verify the styles of the text input widget", () => { + it.only("should verify the styles of the text input widget", () => { const data = {}; data.appName = `${fake.companyName}-App`; data.colourHex = fake.randomRgbaHex; data.boxShadowColor = fake.randomRgba; data.boxShadowParam = fake.boxShadowParam; + data.bgColor = fake.randomRgba; + data.borderColor = fake.randomRgba; + data.textColor = fake.randomRgba; + data.errorTextColor = fake.randomRgba; + data.iconColor = fake.randomRgba; openEditorSidebar(textInputText.defaultWidgetName); cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click(); + addAllInputFieldColors(data); - verifyAndModifyToggleFx( - commonWidgetText.parameterVisibility, - commonWidgetText.codeMirrorLabelTrue - ); - cy.get( - commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName) - ).should("not.be.visible"); - - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterVisibility - ) - ).click(); - - verifyAndModifyToggleFx( - commonWidgetText.parameterDisable, - commonWidgetText.codeMirrorLabelFalse - ); - cy.waitForAutoSave(); - cy.get("[data-cy='draggable-widget-textinput1']") - .parent('[class="text-input true"]') - .invoke("attr", "data-disabled") - .and("contain", "true"); - - cy.get( - commonWidgetSelector.parameterTogglebutton( - commonWidgetText.parameterDisable - ) - ).click(); - - verifyAndModifyParameter( - commonWidgetText.parameterBorderRadius, - commonWidgetText.borderRadiusInput - ); + cy.clearAndType('[data-cy="border-radius-input"]', "20"); + cy.get('[data-cy="icon-visibility-button"]').click(); cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click(); cy.get( commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName) ).should("have.css", "border-radius", "20px"); + verifyInputFieldColors("textinput1", data); + verifyStylesGeneralAccordion( textInputText.defaultWidgetName, data.boxShadowParam, @@ -278,7 +257,7 @@ describe("Text Input", () => { data.customText ); - openAccordion(commonWidgetText.accordionEvents, ["Validation", "Layout"]); + openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]); addDefaultEventHandler(widgetValue(textInputText.defaultWidgetName)); cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}"); diff --git a/cypress-tests/cypress/fixtures/fake.js b/cypress-tests/cypress/fixtures/fake.js index f05ec39b4e..5a903e6526 100644 --- a/cypress-tests/cypress/fixtures/fake.js +++ b/cypress-tests/cypress/fixtures/fake.js @@ -29,7 +29,10 @@ function randomSentence() { function randomRgba() { let rgba = faker.color.rgb({ format: "decimal", includeAlpha: true }); - rgba[rgba.length - 1] = rgba[rgba.length - 1].toPrecision(2) * 100; + let alpha = rgba[rgba.length - 1].toPrecision(2) * 100; + + alpha = Math.min(Math.max(alpha, 20), 80); + rgba[rgba.length - 1] = alpha; return rgba; } diff --git a/cypress-tests/cypress/support/utils/commonWidget.js b/cypress-tests/cypress/support/utils/commonWidget.js index a846fcb32e..37319f65a8 100644 --- a/cypress-tests/cypress/support/utils/commonWidget.js +++ b/cypress-tests/cypress/support/utils/commonWidget.js @@ -1,10 +1,6 @@ import { faker } from "@faker-js/faker"; -import { commonSelectors, commonWidgetSelector } from "Selectors/common"; -import { - commonWidgetText, - commonText, - codeMirrorInputLabel, -} from "Texts/common"; +import { commonWidgetSelector } from "Selectors/common"; +import { codeMirrorInputLabel, commonWidgetText } from "Texts/common"; export const openAccordion = ( accordionName, @@ -131,7 +127,7 @@ export const selectColourFromColourPicker = ( index = 0, parent = commonWidgetSelector.colourPickerParent ) => { - cy.get(commonWidgetSelector.stylePicker(paramName)).click(); + cy.get(commonWidgetSelector.stylePicker(paramName)).last().click(); cy.get(parent) .eq(index) .then(() => { @@ -274,9 +270,12 @@ export const verifyLoaderColor = (widgetName, color) => { }); }; -export const verifyLayout = (widgetName) => { +export const verifyLayout = ( + widgetName, + layout = commonWidgetText.accordionLayout +) => { openEditorSidebar(widgetName); - openAccordion(commonWidgetText.accordionLayout); + openAccordion(layout); verifyAndModifyToggleFx( commonWidgetText.parameterShowOnDesktop, commonWidgetText.codeMirrorLabelTrue @@ -354,9 +353,7 @@ export const verifyTooltip = (widgetSelector, message) => { .trigger("mouseover", { timeout: 2000 }) .trigger("mouseover") .then(() => { - cy.get(commonWidgetSelector.tooltipLabel) - .last() - .should("have.text", message); + cy.get(".tooltip-inner").last().should("have.text", message); }); }; diff --git a/cypress-tests/cypress/support/utils/editor/inputFieldUtils.js b/cypress-tests/cypress/support/utils/editor/inputFieldUtils.js new file mode 100644 index 0000000000..afb827dc9a --- /dev/null +++ b/cypress-tests/cypress/support/utils/editor/inputFieldUtils.js @@ -0,0 +1,121 @@ +import { commonWidgetSelector } from "Selectors/common"; +import { + addAndVerifyTooltip, + openAccordion, + openEditorSidebar, + selectColourFromColourPicker, + verifyAndModifyParameter, + verifyAndModifyToggleFx, + verifyWidgetColorCss, +} from "Support/utils/commonWidget"; +import { commonWidgetText, customValidation } from "Texts/common"; +import { textInputText } from "Texts/textInput"; + +export const addValidations = ( + widgetName, + data, + min = commonWidgetText.labelMinLength, + max = commonWidgetText.labelMaxLength +) => { + openEditorSidebar(widgetName); + openAccordion(commonWidgetText.accordionValidation); + verifyAndModifyParameter( + commonWidgetText.labelRegex, + commonWidgetText.regularExpression + ); + verifyAndModifyParameter(min, data.minimumLength); + verifyAndModifyParameter(max, data.maximumLength); + verifyAndModifyParameter( + commonWidgetText.labelcustomValidadtion, + customValidation(data.widgetName, data.customText) + ); + verifyAndModifyToggleFx("Make this field mandatory", ""); +}; + +export const addAndVerifyAdditionalActions = (widgetName, tooltipText) => { + openEditorSidebar(widgetName); + openAccordion("Additional Actions"); + verifyAndModifyToggleFx( + commonWidgetText.parameterVisibility, + commonWidgetText.codeMirrorLabelTrue + ); + cy.get(commonWidgetSelector.draggableWidget(widgetName)).should( + "not.be.visible" + ); + + cy.get( + commonWidgetSelector.parameterTogglebutton( + commonWidgetText.parameterVisibility + ) + ).click(); + + verifyAndModifyToggleFx( + commonWidgetText.parameterDisable, + commonWidgetText.codeMirrorLabelFalse + ); + cy.waitForAutoSave(); + cy.get(commonWidgetSelector.draggableWidget(widgetName)).should( + "have.attr", + "disabled" + ); + + cy.get( + commonWidgetSelector.parameterTogglebutton( + commonWidgetText.parameterDisable + ) + ).click(); + + verifyAndModifyToggleFx( + commonWidgetText.loadingState, + commonWidgetText.codeMirrorLabelFalse + ); + cy.get(commonWidgetSelector.draggableWidget(widgetName)) + .parent() + .within(() => { + cy.get(".tj-widget-loader").should("be.visible"); + }); + + cy.get( + commonWidgetSelector.parameterTogglebutton(commonWidgetText.loadingState) + ).click(); + + addAndVerifyTooltip( + commonWidgetSelector.draggableWidget(widgetName), + tooltipText + ); +}; + +export const addAllInputFieldColors = (data) => { + selectColourFromColourPicker("BG color", data.bgColor); + selectColourFromColourPicker("Border color", data.borderColor); + selectColourFromColourPicker("Text color", data.textColor); + selectColourFromColourPicker("Error text color", data.errorTextColor); + selectColourFromColourPicker("Icon color", data.iconColor); +}; + +export const verifyInputFieldColors = (selectorInput, data) => { + verifyWidgetColorCss(selectorInput, "color", data.textColor); + verifyWidgetColorCss(selectorInput, "border-color", data.borderColor); + verifyWidgetColorCss(selectorInput, "background-color", data.bgColor); + openEditorSidebar(textInputText.defaultWidgetName); + cy.get('[data-cy="make-this-field-mandatory-toggle-button"]').click(); + cy.get(commonWidgetSelector.draggableWidget("textinput1")).clear(); + cy.forceClickOnCanvas(); + cy.verifyCssProperty( + '[data-cy="textinput1-invalid-feedback"]', + "color", + `rgba(${data.errorTextColor[0]}, ${data.errorTextColor[1]}, ${ + data.errorTextColor[2] + }, ${data.errorTextColor[3] / 100})` + ); + + cy.get(commonWidgetSelector.draggableWidget("textinput1")) + .siblings("svg") + .should( + "have.css", + "stroke", + `rgba(${data.iconColor[0]}, ${data.iconColor[1]}, ${data.iconColor[2]}, ${ + data.iconColor[3] / 100 + })` + ); +}; diff --git a/cypress-tests/cypress/support/utils/textInput.js b/cypress-tests/cypress/support/utils/textInput.js index f47b82b769..9b534e02b7 100644 --- a/cypress-tests/cypress/support/utils/textInput.js +++ b/cypress-tests/cypress/support/utils/textInput.js @@ -1,15 +1,15 @@ +import { faker } from "@faker-js/faker"; import { commonWidgetSelector } from "Selectors/common"; import { openAccordion, openEditorSidebar } from "Support/utils/commonWidget"; import { buttonText } from "Texts/button"; import { commonWidgetText } from "Texts/common"; -import { faker } from "@faker-js/faker"; export const verifyControlComponentAction = (widgetName, value) => { cy.forceClickOnCanvas(); cy.dragAndDropWidget("button", 340, 90); openEditorSidebar(widgetName); - openAccordion(commonWidgetText.accordionEvents, ["Validation", "Layout"]); + openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]); cy.get(commonWidgetSelector.addMoreEventHandlerLink).click(); cy.get(commonWidgetSelector.eventHandlerCard).eq(1).click(); diff --git a/docs/src/components/DocsCard/DocsCardList.jsx b/docs/src/components/DocsCard/DocsCardList.jsx index 01132dff18..196e8e286b 100644 --- a/docs/src/components/DocsCard/DocsCardList.jsx +++ b/docs/src/components/DocsCard/DocsCardList.jsx @@ -3,7 +3,6 @@ import { DocsCard } from './'; import styles from './DocsCard.css' export const DocsCardList = ({ list }) => { - console.log('list', list); return (
{list.map(item => )} diff --git a/frontend/assets/translations/en.json b/frontend/assets/translations/en.json index ab9e646116..0a3439e3ca 100644 --- a/frontend/assets/translations/en.json +++ b/frontend/assets/translations/en.json @@ -491,6 +491,7 @@ "properties": "Properties", "events": "Events", "layout": "Layout", + "devices":"Devices", "styles": "Styles", "general": "General", "validation": "Validation", diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index c68b9a4c3f..a380155039 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -68,9 +68,8 @@ import { EditorContext } from '@/Editor/Context/EditorContextWrapper'; import { useTranslation } from 'react-i18next'; import { useCurrentState } from '@/_stores/currentStateStore'; import { useAppInfo } from '@/_stores/appDataStore'; -import WidgetIcon from '@/../assets/images/icons/widgets'; -const AllComponents = { +export const AllComponents = { Button, Image, Text, @@ -149,14 +148,16 @@ export const Box = memo( sideBarDebugger, readOnly, childComponents, + isResizing, + adjustHeightBasedOnAlignment, }) => { const { t } = useTranslation(); const backgroundColor = yellow ? 'yellow' : ''; const currentState = useCurrentState(); - let styles = { height: '100%', - padding: '1px', + // paddingRight: '1px', + // paddingLeft: '1px', }; if (inCanvas) { @@ -279,6 +280,7 @@ export const Box = memo( ...{ validationObject: component.definition.validation, currentState }, customResolveObjects: customResolvables, }); + const shouldAddBoxShadow = ['TextInput', 'PasswordInput', 'NumberInput']; return ( onComponentOptionChanged(component, variable, value, id)} setExposedVariables={(variableSet) => onComponentOptionsChanged(component, Object.entries(variableSet), id) @@ -338,6 +345,8 @@ export const Box = memo( resetComponent={() => setResetStatus(true)} childComponents={childComponents} dataCy={`draggable-widget-${String(component.name).toLowerCase()}`} + isResizing={isResizing} + adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment} > ) : ( <> diff --git a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx index 8c6b1e7063..5eb663d7a3 100644 --- a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx +++ b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx @@ -35,7 +35,14 @@ import cx from 'classnames'; import { Alert } from '@/_ui/Alert/Alert'; import { useCurrentState } from '@/_stores/currentStateStore'; import ClientServerSwitch from './Elements/ClientServerSwitch'; +import Switch from './Elements/Switch'; +import Checkbox from './Elements/Checkbox'; +import Slider from './Elements/Slider'; +import { Input } from './Elements/Input'; +import { Icon } from './Elements/Icon'; +import { Visibility } from './Elements/Visibility'; import { validateProperty } from '../component-properties-validation'; + const HIDDEN_CODE_HINTER_LABELS = ['Table data', 'Column data']; const AllElements = { @@ -47,11 +54,18 @@ const AllElements = { Number, BoxShadow, ClientServerSwitch, + Slider, + Switch, + Input, + Checkbox, + Icon, + Visibility, }; export function CodeHinter({ initialValue, onChange, + onVisibilityChange, mode, theme, lineNumbers, @@ -77,7 +91,9 @@ export function CodeHinter({ callgpt = () => null, isCopilotEnabled = false, currentState: _currentState, - verticalLine = true, + isIcon = false, + paramUpdated, + staticText, }) { const darkMode = localStorage.getItem('darkMode') === 'true'; const options = { @@ -376,23 +392,27 @@ export function CodeHinter({ className === 'query-hinter' || className === 'custom-component' || undefined ? '' : 'code-hinter'; const ElementToRender = AllElements[TypeMapping[type]]; - const [forceCodeBox, setForceCodeBox] = useState(fxActive); const codeShow = (type ?? 'code') === 'code' || forceCodeBox; cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : cyLabel; - return ( -
-
- {paramLabel === 'Type' &&
} +
+
{paramLabel && !HIDDEN_CODE_HINTER_LABELS.includes(paramLabel) && (
)} @@ -402,21 +422,23 @@ export function CodeHinter({ className="d-flex align-items-center" >
- {paramLabel !== 'Type' && ( - { - if (codeShow) { - setForceCodeBox(false); - onFxPress(false); - } else { - setForceCodeBox(true); - onFxPress(true); - } - }} - dataCy={cyLabel} - /> - )} + {paramLabel !== 'Type' && + paramLabel !== ' ' && + paramLabel !== 'Padding' && ( //add some key if these extends + { + if (codeShow) { + setForceCodeBox(false); + onFxPress(false); + } else { + setForceCodeBox(true); + onFxPress(true); + } + }} + dataCy={cyLabel} + /> + )}
{!codeShow && ( { + if (value !== currentValue) { + onVisibilityChange(value); + setCurrentValue(value); + } + }} paramName={paramName} paramLabel={paramLabel} forceCodeBox={() => { @@ -435,6 +463,9 @@ export function CodeHinter({ }} meta={fieldMeta} cyLabel={cyLabel} + isIcon={isIcon} + staticText={staticText} + component={component} /> )}
@@ -442,11 +473,10 @@ export function CodeHinter({
-
+ { + setIsChecked(!isChecked); // Toggle the checkbox state + onChange(!isChecked); + }} + value={isChecked} + style={{ height: '16px', width: '16px' }} + /> + + Auto width + +
+ ); +} + +export default Checkbox; diff --git a/frontend/src/Editor/CodeBuilder/Elements/Icon.jsx b/frontend/src/Editor/CodeBuilder/Elements/Icon.jsx new file mode 100644 index 0000000000..32eee4670d --- /dev/null +++ b/frontend/src/Editor/CodeBuilder/Elements/Icon.jsx @@ -0,0 +1,122 @@ +import React, { useRef, useState } from 'react'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import Popover from 'react-bootstrap/Popover'; +import { SearchBox } from '@/_components/SearchBox'; +// eslint-disable-next-line import/no-unresolved +import * as Icons from '@tabler/icons-react'; +import { VirtuosoGrid } from 'react-virtuoso'; +import { Visibility } from './Visibility'; + +export const Icon = ({ value, onChange, onVisibilityChange, component }) => { + const [searchText, setSearchText] = useState(''); + const [showPopOver, setPopOverVisibility] = useState(false); + const iconList = useRef(Object.keys(Icons)); + const darkMode = localStorage.getItem('darkMode') === 'true'; + + const searchIcon = (text) => { + if (searchText === text) return; + setSearchText(text); + }; + + const filteredIcons = + searchText === '' + ? iconList.current + : iconList.current.filter((icon) => icon?.toLowerCase().includes(searchText ? searchText.toLowerCase() : '')); + + const onIconSelect = (icon) => { + onChange(icon); + }; + + const eventPopover = () => { + return ( + + + + + +
+ { + { + if (filteredIcons[index] === undefined || filteredIcons[index] === 'createReactComponent') + return null; + // eslint-disable-next-line import/namespace + const IconElement = Icons[filteredIcons[index]]; + return ( +
{ + onIconSelect(filteredIcons[index]); + setPopOverVisibility(false); + }} + > + +
+ ); + }} + /> + } +
+
+
+ ); + }; + + function RenderIconPicker() { + // eslint-disable-next-line import/namespace + const IconElement = Icons?.[value] ?? Icons?.['IconHome2']; + + return ( + <> +
+
+
+ setPopOverVisibility(showing)} + rootClose={true} + overlay={eventPopover()} + > +
+
+ +
+
+ {value} +
+ +
+
+
+
+
+ + ); + } + return <>{RenderIconPicker()}; +}; diff --git a/frontend/src/Editor/CodeBuilder/Elements/Input.jsx b/frontend/src/Editor/CodeBuilder/Elements/Input.jsx new file mode 100644 index 0000000000..55af97393f --- /dev/null +++ b/frontend/src/Editor/CodeBuilder/Elements/Input.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export const Input = ({ value, onChange, cyLabel, staticText }) => { + return ( +
+ { + onChange(e.target.value); + }} + /> + +
+ ); +}; diff --git a/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx b/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx new file mode 100644 index 0000000000..834dbda82b --- /dev/null +++ b/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx @@ -0,0 +1,49 @@ +import React, { useState, useEffect } from 'react'; +import CustomInput from '@/_ui/CustomInput'; +import throttle from 'lodash/throttle'; + +function Slider1({ value, onChange, component }) { + const [sliderValue, setSliderValue] = useState(value ? value : 33); // Initial value of the slider + + const handleSliderChange = (event) => { + setSliderValue(event.target.value); + onChange(`{{${event.target.value}}}`); + }; + + // Throttle function to handle input changes + const onInputChange = throttle((e) => { + let inputValue = parseInt(e.target.value, 10) || 0; + inputValue = Math.min(inputValue, 100); + setSliderValue(inputValue); + onChange(`{{${inputValue}}}`); + }, 300); + + useEffect(() => { + return () => { + // Clear the throttle timeout when the component unmounts + onInputChange.cancel(); + }; + }, [onInputChange]); + + return ( +
+ + +
+ ); +} + +export default Slider1; diff --git a/frontend/src/Editor/CodeBuilder/Elements/Switch.jsx b/frontend/src/Editor/CodeBuilder/Elements/Switch.jsx new file mode 100644 index 0000000000..dc46f754c5 --- /dev/null +++ b/frontend/src/Editor/CodeBuilder/Elements/Switch.jsx @@ -0,0 +1,21 @@ +import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup'; +import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem'; +import React from 'react'; + +const Switch = ({ value, onChange, cyLabel, meta, paramName, isIcon }) => { + const options = meta?.options; + const defaultValue = value; + return ( +
+ + {options.map((option) => ( + + {option.displayName} + + ))} + +
+ ); +}; + +export default Switch; diff --git a/frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx b/frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx new file mode 100644 index 0000000000..2ca6d65be0 --- /dev/null +++ b/frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; + +export const Visibility = ({ value, onVisibilityChange, component }) => { + return ( +
{ + e.stopPropagation(); + onVisibilityChange(!component.component.definition.styles?.iconVisibility?.value); + }} + > + +
+ ); +}; diff --git a/frontend/src/Editor/CodeBuilder/TypeMapping.js b/frontend/src/Editor/CodeBuilder/TypeMapping.js index 04f2a70f13..8acda15ba5 100644 --- a/frontend/src/Editor/CodeBuilder/TypeMapping.js +++ b/frontend/src/Editor/CodeBuilder/TypeMapping.js @@ -10,4 +10,10 @@ export const TypeMapping = { number: 'Number', boxShadow: 'BoxShadow', clientServerSwitch: 'ClientServerSwitch', + checkbox: 'Checkbox', + slider: 'Slider', + switch: 'Switch', + input: 'Input', + icon: 'Icon', + visibility: 'Visibility', }; diff --git a/frontend/src/Editor/Components/Form/Form.jsx b/frontend/src/Editor/Components/Form/Form.jsx index 18cbd0146e..58181929cc 100644 --- a/frontend/src/Editor/Components/Form/Form.jsx +++ b/frontend/src/Editor/Components/Form/Form.jsx @@ -29,6 +29,7 @@ export const Form = function Form(props) { onEvent, dataCy, paramUpdated, + adjustHeightBasedOnAlignment, } = props; const { events: allAppEvents } = useAppInfo(); @@ -293,6 +294,8 @@ export const Form = function Form(props) { allComponents={containerProps.allComponents} sideBarDebugger={containerProps.sideBarDebugger} childComponents={childComponents} + adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment} + height={item.defaultSize.height} />
); diff --git a/frontend/src/Editor/Components/Form/FormUtils.js b/frontend/src/Editor/Components/Form/FormUtils.js index 440e6e568a..f382438ebf 100644 --- a/frontend/src/Editor/Components/Form/FormUtils.js +++ b/frontend/src/Editor/Components/Form/FormUtils.js @@ -55,6 +55,7 @@ export function generateUIComponents(JSONSchema, advanced) { uiComponentsDraft[index * 2 + 1]['definition']['properties']['value']['value'] = value?.value; if (value?.placeholder) uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder; + if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; break; case 'DropDown': if (value?.styles?.disabled) @@ -155,7 +156,9 @@ export function generateUIComponents(JSONSchema, advanced) { uiComponentsDraft[index * 2 + 1]['definition']['properties']['minValue']['value'] = value?.minValue; if (value?.placeholder) uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder; + if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; break; + case 'PasswordInput': if (value?.styles?.backgroundColor) uiComponentsDraft[index * 2 + 1]['definition']['styles']['backgroundColor']['value'] = @@ -184,6 +187,8 @@ export function generateUIComponents(JSONSchema, advanced) { uiComponentsDraft[index * 2 + 1]['definition']['validation']['regex']['value'] = value?.validation?.regex; if (value?.placeholder) uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder; + if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; + break; case 'Datepicker': if (value?.styles?.borderRadius) diff --git a/frontend/src/Editor/Components/Modal.jsx b/frontend/src/Editor/Components/Modal.jsx index 0891bc05a4..ba2dbb4ab0 100644 --- a/frontend/src/Editor/Components/Modal.jsx +++ b/frontend/src/Editor/Components/Modal.jsx @@ -65,6 +65,8 @@ export const Modal = function Modal({ const canShowModal = exposedVariables.show ?? false; setShowModal(exposedVariables.show ?? false); fireEvent(canShowModal ? 'onOpen' : 'onClose'); + const inpuRef = document.getElementsByClassName('tj-text-input-widget')[0]; + inpuRef.blur(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [exposedVariables.show]); diff --git a/frontend/src/Editor/Components/NumberInput.jsx b/frontend/src/Editor/Components/NumberInput.jsx index f544cc6638..fd1b46db47 100644 --- a/frontend/src/Editor/Components/NumberInput.jsx +++ b/frontend/src/Editor/Components/NumberInput.jsx @@ -1,20 +1,64 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; +import './numberinput.scss'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ToolTip } from '@/_components/ToolTip'; +import * as Icons from '@tabler/icons-react'; +import Loader from '@/ToolJetUI/Loader/Loader'; +import { resolveReferences } from '@/_helpers/utils'; +import { useCurrentState } from '@/_stores/currentStateStore'; export const NumberInput = function NumberInput({ height, properties, + validate, styles, setExposedVariable, - darkMode, fireEvent, + component, + darkMode, dataCy, + isResizing, + adjustHeightBasedOnAlignment, }) { - const { visibility, borderRadius, borderColor, backgroundColor, boxShadow } = styles; + const { loadingState, tooltip, disabledState, label, placeholder } = properties; + const { + padding, + borderRadius, + borderColor, + backgroundColor, + boxShadow, + width, + alignment, + direction, + color, + auto, + errTextColor, + iconColor, + } = styles; const textColor = darkMode && ['#232e3c', '#000000ff'].includes(styles.textColor) ? '#fff' : styles.textColor; + const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState) ?? false; + const minValue = resolveReferences(component?.definition?.validation?.minValue?.value, currentState) ?? null; + const maxValue = resolveReferences(component?.definition?.validation?.maxValue?.value, currentState) ?? null; + const [visibility, setVisibility] = useState(properties.visibility); + const [loading, setLoading] = useState(loadingState); + const [showValidationError, setShowValidationError] = useState(false); const [value, setValue] = React.useState(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces))); + const { isValid, validationError } = validate(value); + const [isFocused, setIsFocused] = useState(false); + const inputRef = useRef(null); + const currentState = useCurrentState(); + const [disable, setDisable] = useState(disabledState || loadingState); + const labelRef = useRef(); + + useEffect(() => { + if (alignment == 'top' && label?.length > 0) { + adjustHeightBasedOnAlignment(true); + } else adjustHeightBasedOnAlignment(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [alignment, label?.length]); useEffect(() => { setValue(Number(parseFloat(value).toFixed(properties.decimalPlaces))); @@ -26,27 +70,12 @@ export const NumberInput = function NumberInput({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [properties.value]); - const handleChange = (e) => { - if ( - !isNaN(parseFloat(properties.minValue)) && - !isNaN(parseFloat(properties.maxValue)) && - parseFloat(properties.minValue) > parseFloat(properties.maxValue) - ) { - setValue(Number(parseFloat(properties.maxValue))); - } else if ( - !isNaN(parseFloat(properties.maxValue)) && - parseFloat(e.target.value) > parseFloat(properties.maxValue) - ) { - setValue(Number(parseFloat(properties.maxValue))); - } else { - setValue(Number(parseFloat(e.target.value))); - } - fireEvent('onChange'); - }; const handleBlur = (e) => { - if (!isNaN(parseFloat(properties.minValue)) && parseFloat(e.target.value) < parseFloat(properties.minValue)) { - setValue(Number(parseFloat(properties.minValue))); - } else setValue(Number(parseFloat(e.target.value ? e.target.value : 0).toFixed(properties.decimalPlaces))); + setValue(Number(parseFloat(e.target.value).toFixed(properties.decimalPlaces))); + setShowValidationError(true); + e.stopPropagation(); + fireEvent('onBlur'); + setIsFocused(false); }; useEffect(() => { @@ -56,40 +85,361 @@ export const NumberInput = function NumberInput({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); + useEffect(() => { + setExposedVariable('isMandatory', isMandatory); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isMandatory]); + + useEffect(() => { + setExposedVariable('isLoading', loading); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loading]); + + useEffect(() => { + setExposedVariable('setLoading', async function (loading) { + setLoading(loading); + setExposedVariable('isLoading', loading); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.loadingState]); + + useEffect(() => { + setExposedVariable('isVisible', visibility); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visibility]); + + useEffect(() => { + setExposedVariable('setVisibility', async function (state) { + setVisibility(state); + setExposedVariable('isVisible', state); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.visibility]); + + useEffect(() => { + setExposedVariable('setDisable', async function (disable) { + setDisable(disable); + setExposedVariable('isDisabled', disable); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disabledState]); + + useEffect(() => { + setExposedVariable('isDisabled', disable); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disable]); + useEffect(() => { + if (labelRef.current) { + const width = labelRef.current.offsetWidth; + padding == 'default' ? setLabelWidth(width + 7) : setLabelWidth(width + 5); + } else setLabelWidth(0); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + isResizing, + width, + auto, + defaultAlignment, + component?.definition?.styles?.iconVisibility?.value, + label?.length, + isMandatory, + padding, + direction, + alignment, + ]); + + useEffect(() => { + setExposedVariable('isValid', isValid); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isValid]); + const computedStyles = { - height, - display: visibility ? '' : 'none', + height: height == 40 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height - 4 : height, borderRadius: `${borderRadius}px`, - borderColor, - color: textColor, - backgroundColor: darkMode && ['#ffffff', '#ffffffff'].includes(backgroundColor) ? '#000000' : backgroundColor, - boxShadow, + color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor, + borderColor: isFocused + ? '#3E63DD' + : ['#D7DBDF'].includes(borderColor) + ? darkMode + ? '#4C5155' + : '#D7DBDF' + : borderColor, + backgroundColor: darkMode && ['#fff'].includes(backgroundColor) ? '#313538' : backgroundColor, + boxShadow: + boxShadow !== '0px 0px 0px 0px #00000040' ? boxShadow : isFocused ? '0px 0px 0px 1px #3E63DD4D' : boxShadow, + padding: styles.iconVisibility + ? padding == 'default' + ? '3px 5px 3px 29px' + : '3px 5px 3px 29px' + : '3px 5px 3px 5px', + }; + + const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; + const [labelWidth, setLabelWidth] = useState(0); + + const iconName = styles.icon; // Replace with the name of the icon you want + // eslint-disable-next-line import/namespace + const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName]; + // eslint-disable-next-line import/namespace + + const handleChange = (e) => { + setValue(Number(parseFloat(e.target.value))); + if (e.target.value == '') { + setValue(null); + setExposedVariable('value', null).then(fireEvent('onChange')); + } + if (!isNaN(Number(parseFloat(e.target.value)))) { + setExposedVariable('value', Number(parseFloat(e.target.value))).then(fireEvent('onChange')); + } + }; + useEffect(() => { + disable !== disabledState && setDisable(disabledState); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disabledState]); + useEffect(() => { + visibility !== properties.visibility && setVisibility(properties.visibility); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.visibility]); + useEffect(() => { + loading !== loadingState && setLoading(loadingState); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loadingState]); + + const handleIncrement = (e) => { + e.preventDefault(); // Prevent the default button behavior (form submission, page reload) + + const newValue = (value || 0) + 1; + setValue(newValue); + if (!isNaN(newValue)) { + setExposedVariable('value', newValue).then(fireEvent('onChange')); + } + }; + const handleDecrement = (e) => { + e.preventDefault(); + const newValue = (value || 0) - 1; + setValue(newValue); + if (!isNaN(newValue)) { + setExposedVariable('value', newValue).then(fireEvent('onChange')); + } + }; + useEffect(() => { + setExposedVariable('setFocus', async function () { + inputRef.current.focus(); + }); + setExposedVariable('setBlur', async function () { + inputRef.current.blur(); + }); + setExposedVariable('setText', async function (text) { + if (text) { + const newValue = Number(parseFloat(text)); + setValue(newValue); + setExposedVariable('value', text).then(fireEvent('onChange')); + } + }); + + setExposedVariable('clear', async function () { + setValue(''); + setExposedVariable('value', '').then(fireEvent('onChange')); + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderInput = () => { + const loaderStyle = { + right: alignment == 'top' ? `33px` : direction == 'left' ? `33px` : `${labelWidth + 35}px`, + top: alignment == 'side' ? '' : `53%`, + transform: alignment == 'top' && label?.length == 0 && 'translateY(-50%)', + }; + + return ( + <> +
+ {label && width > 0 && ( + + )} + {component?.definition?.styles?.iconVisibility?.value && !isResizing && ( + 0 && width > 0 ? 'calc(50% + 10px)' : '50%' + }`, + transform: ' translateY(-50%)', + color: iconColor, + }} + stroke={1.5} + /> + )} + { + if (e.key === 'Enter') { + setValue(e.target.value); + setExposedVariable('value', e.target.value); + fireEvent('onEnterPressed'); + } + }} + onFocus={(e) => { + setIsFocused(true); + e.stopPropagation(); + setTimeout(() => { + fireEvent('onFocus'); + }, 0); + }} + /> + {!isResizing && ( + <> +
handleIncrement(e)}> + 0 && width > 0 + ? padding == 'default' + ? '23px' + : '21px' + : padding == 'default' + ? '3px' + : '1px', + right: + labelWidth == 0 + ? padding == 'default' + ? '3px' + : '0px' + : alignment == 'side' && direction === 'right' + ? `${labelWidth + 5}px` + : padding == 'default' + ? '3px' + : '1px', + borderLeft: darkMode ? '1px solid #313538' : '1px solid #D7D7D7', + borderBottom: darkMode ? '.5px solid #313538' : '0.5px solid #D7D7D7', + borderTopRightRadius: borderRadius - 1, + backgroundColor: !darkMode ? 'white' : 'black', + }} + className="numberinput-up-arrow arrow" + name="cheveronup" + > +
+ +
handleDecrement(e)}> + +
+ + )} + + {loading && } +
+ {showValidationError && visibility && ( +
+ {showValidationError && validationError} +
+ )} + + ); }; return ( <> - {!properties.loadingState && ( - - )} - {properties.loadingState === true && ( -
-
-
-
-
+ {properties?.tooltip?.length > 0 ? ( + +
{renderInput()}
+
+ ) : ( +
{renderInput()}
)} ); diff --git a/frontend/src/Editor/Components/PasswordInput.jsx b/frontend/src/Editor/Components/PasswordInput.jsx index f526e966bc..7ec8aefb02 100644 --- a/frontend/src/Editor/Components/PasswordInput.jsx +++ b/frontend/src/Editor/Components/PasswordInput.jsx @@ -1,61 +1,372 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { resolveReferences } from '@/_helpers/utils'; +import { useCurrentState } from '@/_stores/currentStateStore'; +import { ToolTip } from '@/_components/ToolTip'; +import * as Icons from '@tabler/icons-react'; +import Loader from '@/ToolJetUI/Loader/Loader'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; -export const PasswordInput = ({ +export const PasswordInput = function PasswordInput({ height, validate, properties, styles, setExposedVariable, - darkMode, - component, fireEvent, + component, + darkMode, dataCy, -}) => { - const { visibility, disabledState, borderRadius, backgroundColor, boxShadow } = styles; + isResizing, + adjustHeightBasedOnAlignment, +}) { + const textInputRef = useRef(); + const labelRef = useRef(); - const placeholder = properties.placeholder; + const { loadingState, tooltip, disabledState, label, placeholder } = properties; + const { + padding, + borderRadius, + borderColor, + backgroundColor, + textColor, + boxShadow, + width, + alignment, + direction, + color, + auto, + errTextColor, + iconColor, + } = styles; - const [passwordValue, setPasswordValue] = useState(''); + const [disable, setDisable] = useState(disabledState || loadingState); + const [passwordValue, setPasswordValue] = useState(properties.value); + const [visibility, setVisibility] = useState(properties.visibility); const { isValid, validationError } = validate(passwordValue); const [showValidationError, setShowValidationError] = useState(false); + const currentState = useCurrentState(); + const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState); + const [elementWidth, setElementWidth] = useState(0); + const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; + const [iconVisibility, setIconVisibility] = useState(false); + const [loading, setLoading] = useState(loadingState); + const [isFocused, setIsFocused] = useState(false); const computedStyles = { - height, - display: visibility ? '' : 'none', + height: height == 40 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height - 4 : height, borderRadius: `${borderRadius}px`, - color: darkMode && '#fff', - borderColor: darkMode && '#DADCDE', - backgroundColor: darkMode && ['#ffffff'].includes(backgroundColor) ? '#232e3c' : backgroundColor, - boxShadow: boxShadow, + color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor, + borderColor: isFocused + ? '#3E63DD' + : ['#D7DBDF'].includes(borderColor) + ? darkMode + ? '#4C5155' + : '#D7DBDF' + : borderColor, + backgroundColor: darkMode && ['#fff'].includes(backgroundColor) ? '#313538' : backgroundColor, + boxShadow: + boxShadow !== '0px 0px 0px 0px #00000040' ? boxShadow : isFocused ? '0px 0px 0px 1px #3E63DD4D' : boxShadow, + padding: styles.iconVisibility + ? padding == 'default' + ? '3px 24px 3px 29px' + : '3px 24px 3px 27px' + : '3px 24px 3px 10px', + }; + const loaderStyle = { + right: + direction === 'right' && defaultAlignment === 'side' + ? `${elementWidth + 5}px` + : padding == 'default' + ? '12px' + : '10px', + top: `${defaultAlignment === 'top' ? `53%` : ''}`, + transform: alignment == 'top' && label?.length == 0 && 'translateY(-50%)', }; - React.useEffect(() => { + useEffect(() => { + if (labelRef.current) { + const width = labelRef.current.offsetWidth; + padding == 'default' ? setElementWidth(width + 17) : setElementWidth(width + 15); + } else padding == 'default' ? setElementWidth(7) : setElementWidth(5); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + isResizing, + width, + auto, + defaultAlignment, + component?.definition?.styles?.iconVisibility?.value, + label?.length, + isMandatory, + padding, + direction, + alignment, + isMandatory, + ]); + + useEffect(() => { + disable !== disabledState && setDisable(disabledState); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disabledState]); + + useEffect(() => { + visibility !== properties.visibility && setVisibility(properties.visibility); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.visibility]); + + useEffect(() => { + loading !== loadingState && setLoading(loadingState); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loadingState]); + + useEffect(() => { setExposedVariable('isValid', isValid); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [passwordValue, isValid]); + }, [isValid]); + + useEffect(() => { + setPasswordValue(properties.value); + setExposedVariable('value', properties?.value ?? ''); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.value]); + + useEffect(() => { + setExposedVariable('setFocus', async function () { + textInputRef.current.focus(); + }); + setExposedVariable('setBlur', async function () { + textInputRef.current.blur(); + }); + setExposedVariable('disable', async function (value) { + setDisable(value); + }); + setExposedVariable('visibility', async function (value) { + setVisibility(value); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setExposedVariable('setText', async function (text) { + setPasswordValue(text); + setExposedVariable('value', text).then(fireEvent('onChange')); + }); + setExposedVariable('clear', async function () { + setPasswordValue(''); + setExposedVariable('value', '').then(fireEvent('onChange')); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [setPasswordValue]); + + const iconName = styles.icon; // Replace with the name of the icon you want + // eslint-disable-next-line import/namespace + const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName]; + // eslint-disable-next-line import/namespace + + useEffect(() => { + if (alignment == 'top' && label?.length > 0) adjustHeightBasedOnAlignment(true); + else adjustHeightBasedOnAlignment(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [alignment, label?.length]); + + useEffect(() => { + setExposedVariable('isMandatory', isMandatory); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isMandatory]); + + useEffect(() => { + setExposedVariable('isLoading', loading); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loading]); + + useEffect(() => { + setExposedVariable('setLoading', async function (loading) { + setLoading(loading); + setExposedVariable('isLoading', loading); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.loadingState]); + + useEffect(() => { + setExposedVariable('isVisible', visibility); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visibility]); + + useEffect(() => { + setExposedVariable('setVisibility', async function (state) { + setVisibility(state); + setExposedVariable('isVisible', state); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.visibility]); + + useEffect(() => { + setExposedVariable('setDisable', async function (disable) { + setDisable(disable); + setExposedVariable('isDisabled', disable); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disabledState]); + + useEffect(() => { + setExposedVariable('isDisabled', disable); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disable]); + + const renderInput = () => ( + <> +
+ {label && width > 0 && ( + + )} + {component?.definition?.styles?.iconVisibility?.value && !isResizing && ( + 0 && width > 0 ? 'calc(50% + 10px)' : '50%' + }`, + transform: ' translateY(-50%)', + color: iconColor, + }} + stroke={1.5} + /> + )} + {!loading && !isResizing && ( +
{ + setIconVisibility(!iconVisibility); + }} + style={{ + width: '16px', + height: '16px', + position: 'absolute', + right: + alignment == 'top' + ? padding == 'none' + ? `11px` + : '13px' + : direction == 'left' + ? padding == 'none' + ? `11px` + : '13px' + : `${elementWidth + 5}px`, + top: alignment == 'side' ? '50%' : label?.length > 0 && width > 0 ? `calc(50% + 10px)` : '50%', + transform: ' translateY(-50%)', + display: 'flex', + }} + stroke={1.5} + > + +
+ )} + { + if (e.key === 'Enter') { + setPasswordValue(e.target.value); + setExposedVariable('value', e.target.value); + fireEvent('onEnterPressed'); + } + }} + onChange={(e) => { + setPasswordValue(e.target.value); + setExposedVariable('value', e.target.value); + fireEvent('onChange'); + }} + onBlur={(e) => { + setIsFocused(false); + setShowValidationError(true); + e.stopPropagation(); + fireEvent('onBlur'); + }} + onFocus={(e) => { + setIsFocused(true); + e.stopPropagation(); + setTimeout(() => { + fireEvent('onFocus'); + }, 0); + }} + type={!iconVisibility ? 'password' : 'text'} + placeholder={placeholder} + style={computedStyles} + value={passwordValue} + data-cy={dataCy} + disabled={disable || loading} + /> + {loading && } +
+ {showValidationError && visibility && ( +
+ {showValidationError && validationError} +
+ )} + + ); return ( -
- { - setPasswordValue(e.target.value); - setExposedVariable('value', e.target.value); - fireEvent('onChange'); - setShowValidationError(true); - }} - type={'password'} - className={`form-control ${!isValid && showValidationError ? 'is-invalid' : ''} validation-without-icon ${ - darkMode && 'dark-theme-placeholder' - }`} - placeholder={placeholder} - value={passwordValue} - style={computedStyles} - data-cy={dataCy} - /> -
- {showValidationError && validationError} -
-
+ <> + {tooltip?.length > 0 ? ( + +
{renderInput()}
+
+ ) : ( +
{renderInput()}
+ )} + ); }; diff --git a/frontend/src/Editor/Components/Table/Table.jsx b/frontend/src/Editor/Components/Table/Table.jsx index a8b16087f4..2b440b4137 100644 --- a/frontend/src/Editor/Components/Table/Table.jsx +++ b/frontend/src/Editor/Components/Table/Table.jsx @@ -1013,7 +1013,6 @@ export function Table({ )} {showFilterButton && !loadingState && (
- {''} { + if (labelRef.current) { + const width = labelRef.current.offsetWidth; + padding == 'default' ? setElementWidth(width + 17) : setElementWidth(width + 15); + } else padding == 'default' ? setElementWidth(7) : setElementWidth(5); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + isResizing, + width, + auto, + defaultAlignment, + component?.definition?.styles?.iconVisibility?.value, + label?.length, + isMandatory, + padding, + direction, + alignment, + ]); useEffect(() => { - disable !== styles.disabledState && setDisable(styles.disabledState); + disable !== disabledState && setDisable(disabledState); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [styles.disabledState]); + }, [disabledState]); useEffect(() => { - visibility !== styles.visibility && setVisibility(styles.visibility); + visibility !== properties.visibility && setVisibility(properties.visibility); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [styles.visibility]); + }, [properties.visibility]); + + useEffect(() => { + loading !== loadingState && setLoading(loadingState); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loadingState]); useEffect(() => { setExposedVariable('isValid', isValid); @@ -84,48 +160,190 @@ export const TextInput = function TextInput({ setExposedVariables(exposedVariables); // eslint-disable-next-line react-hooks/exhaustive-deps }, [setValue]); + const iconName = styles.icon; // Replace with the name of the icon you want + // eslint-disable-next-line import/namespace + const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName]; + // eslint-disable-next-line import/namespace - return ( -
- { - if (e.key == 'Enter') { + useEffect(() => { + if (alignment == 'top' && label?.length > 0) adjustHeightBasedOnAlignment(true); + else adjustHeightBasedOnAlignment(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [alignment, label?.length]); + + useEffect(() => { + setExposedVariable('isMandatory', isMandatory); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isMandatory]); + + useEffect(() => { + setExposedVariable('isLoading', loading); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loading]); + + useEffect(() => { + setExposedVariable('setLoading', async function (loading) { + setLoading(loading); + setExposedVariable('isLoading', loading); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.loadingState]); + + useEffect(() => { + setExposedVariable('isVisible', visibility); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visibility]); + + useEffect(() => { + setExposedVariable('setVisibility', async function (state) { + setVisibility(state); + setExposedVariable('isVisible', state); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [properties.visibility]); + + useEffect(() => { + setExposedVariable('setDisable', async function (disable) { + setDisable(disable); + setExposedVariable('isDisabled', disable); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disabledState]); + + useEffect(() => { + setExposedVariable('isDisabled', disable); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disable]); + + const renderInput = () => ( + <> +
+ {label && width > 0 && ( + + )} + {component?.definition?.styles?.iconVisibility?.value && !isResizing && ( + 0 && width > 0 ? 'calc(50% + 10px)' : '50%' + }`, + transform: ' translateY(-50%)', + color: iconColor, + }} + stroke={1.5} + /> + )} + { + if (e.key === 'Enter') { + setValue(e.target.value); + setExposedVariable('value', e.target.value); + fireEvent('onEnterPressed'); + } + }} + onChange={(e) => { setValue(e.target.value); setExposedVariable('value', e.target.value); - fireEvent('onEnterPressed'); - } - }} - onChange={(e) => { - setValue(e.target.value); - setExposedVariable('value', e.target.value); - fireEvent('onChange'); - }} - onBlur={(e) => { - setShowValidationError(true); - e.stopPropagation(); - fireEvent('onBlur'); - }} - onFocus={(e) => { - e.stopPropagation(); - fireEvent('onFocus'); - }} - type="text" - className={`form-control ${!isValid ? 'is-invalid' : ''} validation-without-icon ${ - darkMode && 'dark-theme-placeholder' - }`} - placeholder={properties.placeholder} - style={computedStyles} - value={value} - data-cy={dataCy} - /> -
- {showValidationError && validationError} + fireEvent('onChange'); + }} + onBlur={(e) => { + setShowValidationError(true); + setIsFocused(false); + e.stopPropagation(); + fireEvent('onBlur'); + setIsFocused(false); + }} + onFocus={(e) => { + setIsFocused(true); + e.stopPropagation(); + + setTimeout(() => { + fireEvent('onFocus'); + }, 0); + }} + type="text" + placeholder={placeholder} + style={computedStyles} + value={value} + data-cy={dataCy} + disabled={disable || loading} + /> + {loading && }
-
+ {showValidationError && visibility && ( +
+ {showValidationError && validationError} +
+ )} + + ); + + return ( + <> + {properties?.tooltip?.length > 0 ? ( + +
{renderInput()}
+
+ ) : ( +
{renderInput()}
+ )} + ); }; diff --git a/frontend/src/Editor/Components/numberinput.scss b/frontend/src/Editor/Components/numberinput.scss new file mode 100644 index 0000000000..d86c7270d5 --- /dev/null +++ b/frontend/src/Editor/Components/numberinput.scss @@ -0,0 +1,39 @@ +.arrow { + cursor: pointer; + user-select: none; + position: absolute; + font-size: 12px; +} + +.numberinput-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} + +.input-number::-webkit-inner-spin-button, +.input-number::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.input-number { + align-items: center; + -webkit-appearance: none !important; + /* Remove clear button on Safari */ + -moz-appearance: textfield !important; + /* Remove clear button on Firefox */ + appearance: none !important; + /* Remove clear button on modern browsers */ +} + +.numberinput-down-arrow { + position: absolute; + width: 20px; +} + +.numberinput-up-arrow { + position: absolute; + width: 20px; +} \ No newline at end of file diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index 5eee44657c..0501386302 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -24,33 +24,6 @@ const resizerClasses = { topLeft: 'top-left', }; -const resizerStyles = { - topRight: { - width: '8px', - height: '8px', - right: '-4px', - top: '-4px', - }, - bottomRight: { - width: '8px', - height: '8px', - right: '-4px', - bottom: '-4px', - }, - bottomLeft: { - width: '8px', - height: '8px', - left: '-4px', - bottom: '-4px', - }, - topLeft: { - width: '8px', - height: '8px', - left: '-4px', - top: '-4px', - }, -}; - function computeWidth(currentLayoutOptions) { return `${currentLayoutOptions?.width}%`; } @@ -121,6 +94,66 @@ export const DraggableBox = React.memo( shallow ); const currentState = useCurrentState(); + const [calculatedHeight, setCalculatedHeight] = useState(layoutData?.height); + + const resizerStyles = { + topRight: { + width: '8px', + height: '8px', + right: '-4px', + top: '-4px', + }, + bottomRight: { + width: '8px', + height: '8px', + right: '-4px', + bottom: '-4px', + }, + right: { + position: 'absolute', + height: '20px', + width: '5px', + right: '-3px', + background: '#4368E3', + borderRadius: '8px', + top: '50%', + transform: 'translateY(-50%)', + display: + (mode === 'edit' && !readOnly && mouseOver) || isResizing || isDragging2 || isSelectedComponent + ? isVerticalResizingAllowed() + ? 'none' + : 'block' + : 'none', + }, + left: { + position: 'absolute', + height: '20px', + width: '5px', + left: '-3px', + background: '#4368E3', + borderRadius: '8px', + top: '50%', + transform: 'translateY(-50%)', + display: + (mode === 'edit' && !readOnly && mouseOver) || isResizing || isDragging2 || isSelectedComponent + ? isVerticalResizingAllowed() + ? 'none' + : 'block' + : 'none', + }, + bottomLeft: { + width: '8px', + height: '8px', + left: '-4px', + bottom: '-4px', + }, + topLeft: { + width: '8px', + height: '8px', + left: '-4px', + top: '-4px', + }, + }; const [{ isDragging }, drag, preview] = useDrag( () => ({ type: ItemTypes.BOX, @@ -203,7 +236,21 @@ export const DraggableBox = React.memo( if (selectionInProgress) return; setHoveredComponent(id); }; + function isVerticalResizingAllowed() { + // Return true if vertical resizing is allowed, false otherwise + return ( + mode === 'edit' && + component.component !== 'TextInput' && + component.component !== 'PasswordInput' && + component.component !== 'NumberInput' && + !readOnly + ); + } + const adjustHeightBasedOnAlignment = (increase) => { + if (increase) return setCalculatedHeight(layoutData?.height + 20); + else return setCalculatedHeight(layoutData?.height); + }; return (
{ setDragging(false); @@ -301,7 +357,7 @@ export const DraggableBox = React.memo( component={component} id={id} width={width} - height={layoutData.height - 4} + height={layoutData.height} mode={mode} changeCanDrag={changeCanDrag} inCanvas={inCanvas} @@ -320,6 +376,8 @@ export const DraggableBox = React.memo( allComponents={allComponents} sideBarDebugger={sideBarDebugger} childComponents={childComponents} + isResizing={isResizing} + adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment} />
diff --git a/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx b/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx index 344554ec12..7115bafd3e 100644 --- a/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx +++ b/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx @@ -5,6 +5,8 @@ import { renderElement } from '../Utils'; // eslint-disable-next-line import/no-unresolved import i18next from 'i18next'; import { resolveReferences } from '@/_helpers/utils'; +import { AllComponents } from '@/Editor/Box'; +const SHOW_ADDITIONAL_ACTIONS = ['Text', 'TextInput', 'DropDown', 'NumberInput', 'PasswordInput']; export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => { const { @@ -19,9 +21,17 @@ export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => { pages, } = restProps; - const properties = Object.keys(componentMeta.properties); const events = Object.keys(componentMeta.events); const validations = Object.keys(componentMeta.validation || {}); + let properties = []; + let additionalActions = []; + for (const [key] of Object.entries(componentMeta?.properties)) { + if (componentMeta?.properties[key]?.section === 'additionalActions') { + additionalActions.push(key); + } else { + properties.push(key); + } + } const accordionItems = baseComponentProperties( properties, @@ -37,7 +47,8 @@ export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => { components, validations, darkMode, - pages + pages, + additionalActions ); return ; @@ -57,14 +68,18 @@ export const baseComponentProperties = ( allComponents, validations, darkMode, - pages + pages, + additionalActions ) => { // Add widget title to section key to filter that property section from specified widgets' settings const accordionFilters = { Properties: [], Events: [], Validation: [], - General: ['Modal'], + 'Additional Actions': Object.keys(AllComponents).filter( + (component) => !SHOW_ADDITIONAL_ACTIONS.includes(component) + ), + General: ['Modal', 'TextInput', 'PasswordInput', 'NumberInput'], Layout: [], }; if (component.component.component === 'Listview') { @@ -113,7 +128,6 @@ export const baseComponentProperties = ( ), }); } - if (validations.length > 0) { items.push({ title: `${i18next.t('widget.common.validation', 'Validation')}`, @@ -127,7 +141,8 @@ export const baseComponentProperties = ( 'validation', currentState, allComponents, - darkMode + darkMode, + componentMeta.validation?.[property]?.placeholder ) ), }); @@ -153,7 +168,27 @@ export const baseComponentProperties = ( }); items.push({ - title: `${i18next.t('widget.common.layout', 'Layout')}`, + title: `${i18next.t('widget.common.additionalActions', 'Additional Actions')}`, + isOpen: true, + children: additionalActions?.map((property) => { + const paramType = property === 'Tooltip' ? 'general' : 'properties'; + return renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + property, + paramType, + currentState, + allComponents, + darkMode, + componentMeta.properties?.[property]?.placeholder + ); + }), + }); + + items.push({ + title: `${i18next.t('widget.common.devices', 'Devices')}`, isOpen: true, children: ( <> diff --git a/frontend/src/Editor/Inspector/Elements/Code.jsx b/frontend/src/Editor/Inspector/Elements/Code.jsx index 986586bef5..106f2f6c7e 100644 --- a/frontend/src/Editor/Inspector/Elements/Code.jsx +++ b/frontend/src/Editor/Inspector/Elements/Code.jsx @@ -18,6 +18,8 @@ export const Code = ({ fxActive, component, verticalLine, + accordian, + placeholder, }) => { const currentState = useCurrentState(); @@ -50,8 +52,8 @@ export const Code = ({ }; let initialValue = !_.isEmpty(definition) ? definition.value : getDefinitionForNewProps(param.name); - const paramMeta = componentMeta[paramType][param.name]; - const displayName = paramMeta.displayName || param.name; + const paramMeta = accordian ? componentMeta[paramType]?.[param.name] : componentMeta[paramType][param.name]; + const displayName = paramMeta?.displayName || param?.name; /* following block is written for cellSize Prop to support backward compatibility, @@ -75,13 +77,18 @@ export const Code = ({ onChange(param, 'value', value, paramType); } - const options = paramMeta.options || {}; + function onVisibilityChange(value) { + onChange({ name: 'iconVisibility' }, 'value', value, 'styles'); + } + + const options = paramMeta?.options || {}; const getfieldName = React.useMemo(() => { return param.name; }, [param]); + return ( -
+
handleCodeChanged(value)} + onVisibilityChange={(value) => onVisibilityChange(value)} componentName={`component/${componentName}::${getfieldName}`} - type={paramMeta.type} + type={paramMeta?.type} paramName={param.name} - paramLabel={displayName} + paramLabel={paramMeta?.showLabel !== false ? displayName : ' '} fieldMeta={paramMeta} onFxPress={onFxPress} fxActive={CLIENT_SERVER_TOGGLE_FIELDS.includes(param.name) ? false : fxActive} // Client Server Toggle don't support Fx component={component} verticalLine={verticalLine} + isIcon={paramMeta?.isIcon} + staticText={paramMeta?.staticText} + placeholder={placeholder} + bold={true} />
); diff --git a/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx b/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx index b16fc27dd7..218bbe5e24 100644 --- a/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx +++ b/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx @@ -7,7 +7,7 @@ const tooltipStyle = { textDecorationStyle: 'dashed', }; -export const ToolTip = ({ label, meta, labelClass }) => { +export const ToolTip = ({ label, meta, labelClass, bold = false }) => { function renderTooltip(props) { return ( @@ -31,6 +31,7 @@ export const ToolTip = ({ label, meta, labelClass }) => { .toLowerCase() .replace(/\s+/g, '-')}`} className={labelClass || 'form-label'} + style={{ fontWeight: bold ? 500 : 400 }} > {label} diff --git a/frontend/src/Editor/Inspector/Inspector.jsx b/frontend/src/Editor/Inspector/Inspector.jsx index c34ec7a029..a168649d99 100644 --- a/frontend/src/Editor/Inspector/Inspector.jsx +++ b/frontend/src/Editor/Inspector/Inspector.jsx @@ -3,7 +3,7 @@ import { componentTypes } from '../WidgetManager/components'; import { Table } from './Components/Table/Table.jsx'; import { Chart } from './Components/Chart'; import { Form } from './Components/Form'; -import { renderElement } from './Utils'; +import { renderElement, renderCustomStyles } from './Utils'; import { toast } from 'react-hot-toast'; import { validateQueryName, convertToKebabCase, resolveReferences } from '@/_helpers/utils'; import { ConfirmDialog } from '@/_components'; @@ -83,6 +83,8 @@ export const Inspector = ({ const [inputRef, setInputFocus] = useFocus(); const [showHeaderActionsMenu, setShowHeaderActionsMenu] = useState(false); + const shouldAddBoxShadow = ['TextInput', 'PasswordInput', 'NumberInput']; + const { isVersionReleased } = useAppVersionStore( (state) => ({ isVersionReleased: state.isVersionReleased, @@ -310,7 +312,14 @@ export const Inspector = ({ ); const stylesTab = (
-
+
- {buildGeneralStyle()} + {!shouldAddBoxShadow.includes(component.component.component) && buildGeneralStyle()}
); @@ -353,7 +362,11 @@ export const Inspector = ({
setSelectedComponents(EMPTY_ARRAY)}> - +
@@ -455,10 +468,39 @@ const widgetsWithStyleConditions = { ], }, }; +const styleGroupedComponentTypes = ['TextInput', 'NumberInput', 'PasswordInput']; const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQueries, currentState, allComponents }) => { - return Object.keys(componentMeta.styles).map((style) => { - const conditionWidget = widgetsWithStyleConditions[component?.component?.component] ?? null; + // Initialize an object to group properties by "accordian" + const groupedProperties = {}; + if ( + component.component.component === 'TextInput' || + component.component.component === 'PasswordInput' || + component.component.component === 'NumberInput' + ) { + // Iterate over the properties in componentMeta.styles + for (const key in componentMeta.styles) { + const property = componentMeta.styles[key]; + const accordian = property.accordian; + + // Check if the "accordian" key exists in groupedProperties + if (!groupedProperties[accordian]) { + groupedProperties[accordian] = {}; // Create an empty object for the "accordian" key if it doesn't exist + } + + // Add the property to the corresponding "accordian" object + groupedProperties[accordian][key] = property; + } + } + + return Object.keys( + component.component.component === 'TextInput' || + component.component.component === 'PasswordInput' || + component.component.component === 'NumberInput' + ? groupedProperties + : componentMeta.styles + ).map((style) => { + const conditionWidget = widgetsWithStyleConditions[component.component.component] ?? null; const condition = conditionWidget?.conditions.find((condition) => condition.property) ?? {}; if (conditionWidget && conditionWidget.conditions.find((condition) => condition.conditionStyles.includes(style))) { @@ -478,16 +520,42 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie ); } - return renderElement( - component, - componentMeta, - paramUpdated, - dataQueries, - style, - 'styles', - currentState, - allComponents - ); + const items = []; + + if ( + component.component.component === 'TextInput' || + component.component.component === 'PasswordInput' || + component.component.component === 'NumberInput' + ) { + items.push({ + title: `${style}`, + children: Object.entries(groupedProperties[style]).map(([key, value]) => ({ + ...renderCustomStyles( + component, + componentMeta, + paramUpdated, + dataQueries, + key, + 'styles', + currentState, + allComponents, + value.accordian + ), + })), + }); + return ; + } else { + return renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + style, + 'styles', + currentState, + allComponents + ); + } }); }; diff --git a/frontend/src/Editor/Inspector/Utils.js b/frontend/src/Editor/Inspector/Utils.js index 6ffba6b90f..56b36741e3 100644 --- a/frontend/src/Editor/Inspector/Utils.js +++ b/frontend/src/Editor/Inspector/Utils.js @@ -17,6 +17,75 @@ export function renderQuerySelector(component, dataQueries, eventOptionUpdated, /> ); } +export function renderCustomStyles( + component, + componentMeta, + paramUpdated, + dataQueries, + param, + paramType, + currentState, + components = {}, + accordian, + darkMode = false, + verticalLine = true, + placeholder = '' +) { + const componentConfig = component.component; + const componentDefinition = componentConfig.definition; + const paramTypeDefinition = componentDefinition[paramType] || {}; + const definition = paramTypeDefinition[param] || {}; + const meta = componentMeta[paramType]?.[accordian]?.[param]; + + if ( + componentConfig.component == 'DropDown' || + componentConfig.component == 'Form' || + componentConfig.component == 'Listview' || + componentConfig.component == 'TextInput' || + componentConfig.component == 'NumberInput' || + componentConfig.component == 'PasswordInput' + ) { + const paramTypeConfig = componentMeta[paramType] || {}; + const paramConfig = paramTypeConfig[param] || {}; + const { conditionallyRender = null } = paramConfig; + + if (conditionallyRender) { + const { key, value } = conditionallyRender; + if (paramTypeDefinition?.[key] ?? value) { + const resolvedValue = paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key], currentState); + + if (resolvedValue?.value !== value) { + return; + } + } + } + } + + return ( + <> + { + paramUpdated({ name: param, ...component.component.properties[param] }, 'fxActive', active, paramType); + }} + component={component} + verticalLine={verticalLine} + accordian={accordian} + placeholder={placeholder} + /> + + ); +} export function renderElement( component, @@ -28,6 +97,7 @@ export function renderElement( currentState, components = {}, darkMode = false, + placeholder = '', verticalLine = true ) { const componentConfig = component.component; @@ -72,6 +142,7 @@ export function renderElement( }} component={component} verticalLine={verticalLine} + placeholder={placeholder} /> ); } diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index 14bb42475f..abb16bb2ae 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -242,16 +242,11 @@ class ViewerComponent extends React.Component { variablesResult = constants; } - console.log('--org constant 2.0', { variablesResult }); - if (variablesResult && Array.isArray(variablesResult)) { variablesResult.map((constant) => { const constantValue = constant.values.find((value) => value.environmentName === 'production')['value']; orgConstants[constant.name] = constantValue; }); - - // console.log('--org constant 2.0', { orgConstants }); - return { constants: orgConstants, }; diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index aade72827c..0c180fef94 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -252,7 +252,7 @@ export const widgets = [ }, actionButtonBackgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, }, @@ -577,7 +577,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, defaultValue: false, @@ -1107,9 +1107,10 @@ export const widgets = [ height: 30, width: 25, }, - properties: ['placeholder'], + properties: ['placeholder', 'label'], defaultValue: { placeholder: 'Enter your name', + label: '', }, }, { @@ -1120,11 +1121,10 @@ export const widgets = [ height: 30, width: 25, }, - properties: ['value'], - styles: ['borderColor'], + properties: ['value', 'label'], defaultValue: { value: 24, - borderColor: '#dadcde', + label: '', }, }, { @@ -1186,7 +1186,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, }, @@ -1266,14 +1266,26 @@ export const widgets = [ description: 'User text input field', component: 'TextInput', defaultSize: { - width: 6, - height: 30, + width: 10, + height: 40, }, others: { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { + label: { + type: 'code', + displayName: 'Label', + validation: { schema: { type: 'string' } }, + }, + placeholder: { + type: 'code', + displayName: 'Placeholder', + validation: { + schema: { type: 'string' }, + }, + }, value: { type: 'code', displayName: 'Default value', @@ -1283,19 +1295,42 @@ export const widgets = [ }, }, }, - placeholder: { + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', + }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', + }, + tooltip: { type: 'code', - displayName: 'Placeholder', - validation: { - schema: { type: 'string' }, - }, + displayName: 'Tooltip', + validation: { schema: { type: 'string' } }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', }, }, validation: { - regex: { type: 'code', displayName: 'Regex' }, - minLength: { type: 'code', displayName: 'Min length' }, - maxLength: { type: 'code', displayName: 'Max length' }, - customRule: { type: 'code', displayName: 'Custom validation' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, }, events: { onChange: { displayName: 'On change' }, @@ -1304,36 +1339,124 @@ export const widgets = [ onBlur: { displayName: 'On blur' }, }, styles: { - textColor: { + color: { type: 'color', displayName: 'Text color', validation: { schema: { type: 'string' } }, + accordian: 'label', }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' } }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' } }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left' }, + { displayName: 'alignrightinspector', value: 'right' }, + ], + accordian: 'label', + }, + width: { + type: 'slider', + displayName: 'Width', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' } }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + }, + backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' } }, + accordian: 'field', }, + borderColor: { type: 'color', displayName: 'Border color', validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text Color', + validation: { schema: { type: 'string' } }, + accordian: 'field', }, errTextColor: { type: 'color', displayName: 'Error text color', validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' } }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + visibility: false, }, borderRadius: { - type: 'code', + type: 'input', displayName: 'Border radius', validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, - visibility: { type: 'toggle', displayName: 'Visibility', validation: { schema: { type: 'boolean' } } }, - disabledState: { type: 'toggle', displayName: 'Disable', validation: { schema: { type: 'boolean' } } }, }, exposedVariables: { value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, }, actions: [ { @@ -1355,39 +1478,69 @@ export const widgets = [ }, { handle: 'disable', - displayName: 'Disable', + displayName: 'Disable(deprecated)', params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, { handle: 'visibility', - displayName: 'Visibility', + displayName: 'Visibility(deprecated)', params: [{ handle: 'visibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, + { + handle: 'setVisibility', + displayName: 'setVisibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'setDisable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'setLoading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, ], definition: { validation: { + mandatory: { value: false }, regex: { value: '' }, minLength: { value: null }, maxLength: { value: null }, customRule: { value: null }, }, + others: { showOnDesktop: { value: '{{true}}' }, showOnMobile: { value: '{{false}}' }, }, properties: { - value: { value: '' }, + value: { value: 'Hello World๐Ÿ‘‹' }, + label: { value: 'Label' }, placeholder: { value: 'Enter your input' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, }, events: [], styles: { - textColor: { value: '#000' }, - borderColor: { value: '#dadcde' }, - errTextColor: { value: '#ff0000' }, - borderRadius: { value: '{{4}}' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, + textColor: { value: '#11181C' }, + borderColor: { value: '#D7DBDF' }, + errTextColor: { value: '#DB4324' }, + borderRadius: { value: '{{6}}' }, backgroundColor: { value: '#fff' }, + iconColor: { value: '#C1C8CD' }, + direction: { value: 'left' }, + width: { value: '33' }, + alignment: { value: 'side' }, + color: { value: '#11181C' }, + auto: { value: true }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconHome2' }, + iconVisibility: { value: false }, }, }, }, @@ -1397,14 +1550,19 @@ export const widgets = [ description: 'Numeric input field', component: 'NumberInput', defaultSize: { - width: 4, - height: 30, + width: 10, + height: 40, }, others: { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { + label: { + type: 'code', + displayName: 'Label', + validation: { schema: { type: 'string' } }, + }, value: { type: 'code', displayName: 'Default value', @@ -1412,20 +1570,6 @@ export const widgets = [ schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, }, }, - minValue: { - type: 'code', - displayName: 'Minimum value', - validation: { - schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, - }, - }, - maxValue: { - type: 'code', - displayName: 'Maximum value', - validation: { - schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, - }, - }, placeholder: { type: 'code', displayName: 'Placeholder', @@ -1433,13 +1577,6 @@ export const widgets = [ schema: { type: 'string' }, }, }, - loadingState: { - type: 'toggle', - displayName: 'Loading state', - validation: { - schema: { type: 'boolean' }, - }, - }, decimalPlaces: { type: 'code', displayName: 'Decimal places', @@ -1447,73 +1584,244 @@ export const widgets = [ schema: { type: 'number' }, }, }, - }, - events: { - onChange: { displayName: 'On change' }, - }, - styles: { + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', + }, visibility: { type: 'toggle', displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - }, + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', }, disabledState: { type: 'toggle', displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - }, + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', }, - borderRadius: { + tooltip: { type: 'code', - displayName: 'Border radius', - validation: { - schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + displayName: 'Tooltip', + validation: { schema: { type: 'string' } }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + events: { + onChange: { displayName: 'On change' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + onEnterPressed: { displayName: 'On enter pressed' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text color', + validation: { schema: { type: 'string' } }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' } }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' } }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left' }, + { displayName: 'alignrightinspector', value: 'right' }, + ], + accordian: 'label', + }, + width: { + type: 'slider', + displayName: 'Width', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', }, }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' } }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + }, + backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', + validation: { schema: { type: 'string' } }, + accordian: 'field', }, + borderColor: { type: 'color', displayName: 'Border color', - validation: { - schema: { type: 'string' }, - }, + validation: { schema: { type: 'string' } }, + accordian: 'field', }, textColor: { type: 'color', displayName: 'Text Color', validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' } }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + visibility: false, + }, + borderRadius: { + type: 'input', + displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, }, + actions: [ + { + handle: 'setText', + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: '100' }], + }, + { + handle: 'clear', + displayName: 'Clear', + }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'setVisibility', + displayName: 'setVisibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'setDisable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'setLoading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + ], exposedVariables: { value: 99, + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, + }, + validation: { + regex: { type: 'code', displayName: 'Regex', placeholder: '^d+$' }, + minValue: { type: 'code', displayName: 'Min value', placeholder: 'Enter min value' }, + maxValue: { type: 'code', displayName: 'Max value', placeholder: 'Enter max value' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, }, definition: { others: { showOnDesktop: { value: '{{true}}' }, showOnMobile: { value: '{{false}}' }, }, + validation: { + mandatory: { value: false }, + regex: { value: '' }, + minValue: { value: '' }, + maxValue: { value: '' }, + customRule: { value: null }, + }, properties: { value: { value: '99' }, + label: { value: 'Label' }, maxValue: { value: '' }, minValue: { value: '' }, placeholder: { value: '0' }, decimalPlaces: { value: '{{2}}' }, + tooltip: { value: '' }, + visibility: { value: '{{true}}' }, loadingState: { value: '{{false}}' }, + disabledState: { value: '{{false}}' }, }, events: [], styles: { - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, - borderRadius: { value: '{{4}}' }, - backgroundColor: { value: '#ffffffff' }, - borderColor: { value: '#fff' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + borderColor: { value: '#D7DBDF' }, + errTextColor: { value: '#DB4324' }, textColor: { value: '#232e3c' }, + iconColor: { value: '#C1C8CD' }, + direction: { value: 'left' }, + width: { value: '33' }, + alignment: { value: 'side' }, + color: { value: '#11181C' }, + auto: { value: true }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconHome2' }, + iconVisibility: { value: false }, }, }, }, @@ -1523,14 +1831,19 @@ export const widgets = [ description: 'Secure text input', component: 'PasswordInput', defaultSize: { - width: 4, - height: 30, + width: 10, + height: 40, }, others: { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { + label: { + type: 'code', + displayName: 'Label', + validation: { schema: { type: 'string' } }, + }, placeholder: { type: 'code', displayName: 'Placeholder', @@ -1538,49 +1851,217 @@ export const widgets = [ schema: { type: 'string' }, }, }, - }, - validation: { - regex: { type: 'code', displayName: 'Regex' }, - minLength: { type: 'code', displayName: 'Min length' }, - maxLength: { type: 'code', displayName: 'Max length' }, - customRule: { type: 'code', displayName: 'Custom validation' }, - }, - events: { - onChange: { displayName: 'On change' }, - }, - styles: { + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + }, + }, + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', + }, visibility: { type: 'toggle', displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - }, + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', }, disabledState: { type: 'toggle', displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - }, + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', }, - borderRadius: { + tooltip: { type: 'code', - displayName: 'Border radius', - validation: { - schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + displayName: 'Tooltip', + validation: { schema: { type: 'string' } }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + regex: { + type: 'code', + displayName: 'Regex', + placeholder: '^(?=.*[a-z])(?=.*[A-Z])(?=.*d)[a-zA-Zd]{8,}$', + }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + }, + events: { + onChange: { displayName: 'On change' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + onEnterPressed: { displayName: 'On enter pressed' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text color', + validation: { schema: { type: 'string' } }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' } }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' } }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left' }, + { displayName: 'alignrightinspector', value: 'right' }, + ], + accordian: 'label', + }, + width: { + type: 'slider', + displayName: 'Width', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', }, }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' } }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + }, + backgroundColor: { type: 'color', - displayName: 'Background color', - validation: { - schema: { type: 'string' }, - }, + displayName: 'BG color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + + borderColor: { + type: 'color', + displayName: 'Border color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text Color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' } }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' } }, + accordian: 'field', + visibility: false, + }, + borderRadius: { + type: 'input', + displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'field', + }, + + padding: { + type: 'switch', + displayName: 'Padding', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, }, exposedVariables: { value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, }, + actions: [ + { + handle: 'setText', + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New Text' }], + }, + { + handle: 'clear', + displayName: 'Clear', + }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'setVisibility', + displayName: 'setVisibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'setDisable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'setLoading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + ], definition: { others: { showOnDesktop: { value: '{{true}}' }, @@ -1588,8 +2069,15 @@ export const widgets = [ }, properties: { placeholder: { value: 'password' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, + label: { value: 'Label' }, + value: { value: 'Hello world' }, }, validation: { + mandatory: { value: false }, regex: { value: '' }, minLength: { value: null }, maxLength: { value: null }, @@ -1597,10 +2085,21 @@ export const widgets = [ }, events: [], styles: { - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, - borderRadius: { value: '{{4}}' }, - backgroundColor: { value: '#ffffff' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + borderColor: { value: '#D7DBDF' }, + errTextColor: { value: '#DB4324' }, + textColor: { value: '#11181C' }, + iconColor: { value: '#C1C8CD' }, + direction: { value: 'left' }, + width: { value: '33' }, + alignment: { value: 'side' }, + color: { value: '#11181C' }, + auto: { value: true }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconLock' }, + iconVisibility: { value: true }, }, }, }, @@ -2278,7 +2777,7 @@ export const widgets = [ }, backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, }, @@ -2425,7 +2924,7 @@ export const widgets = [ }, backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, }, @@ -2516,7 +3015,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, }, @@ -4121,7 +4620,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, }, @@ -5018,7 +5517,7 @@ ReactDOM.render(, document.body);`, styles: { backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'BG color', validation: { schema: { type: 'string' }, }, diff --git a/frontend/src/ToolJetUI/Loader/Loader.jsx b/frontend/src/ToolJetUI/Loader/Loader.jsx new file mode 100644 index 0000000000..ad2b77b30e --- /dev/null +++ b/frontend/src/ToolJetUI/Loader/Loader.jsx @@ -0,0 +1,47 @@ +// Loader.js +import React from 'react'; +import './Loader.scss'; // Import a CSS file for styling + +const Loader = ({ width, style }) => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +export default Loader; diff --git a/frontend/src/ToolJetUI/Loader/Loader.scss b/frontend/src/ToolJetUI/Loader/Loader.scss new file mode 100644 index 0000000000..71095c199d --- /dev/null +++ b/frontend/src/ToolJetUI/Loader/Loader.scss @@ -0,0 +1,4 @@ +.tj-widget-loader { + position: absolute; + display: flex; +} \ No newline at end of file diff --git a/frontend/src/ToolJetUI/SwitchGroup/ToggleGroup.jsx b/frontend/src/ToolJetUI/SwitchGroup/ToggleGroup.jsx index 8e60835c6b..e4ef0da815 100644 --- a/frontend/src/ToolJetUI/SwitchGroup/ToggleGroup.jsx +++ b/frontend/src/ToolJetUI/SwitchGroup/ToggleGroup.jsx @@ -8,7 +8,6 @@ const ToggleGroup1 = ({ children, className, onValueChange, defaultValue, ...res React.useEffect(() => { setValue(defaultValue); }, [defaultValue]); - return ( { +const ToggleGroupItem = ({ children, value, isIcon, ...restProps }) => { return ( -
{children}
+
+ {!isIcon ? ( + children + ) : ( + + )} +
); }; diff --git a/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss b/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss index c2256dff4a..3e14396ea9 100644 --- a/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss +++ b/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss @@ -1,13 +1,15 @@ .ToggleGroup { display: inline-flex; - background-color: var(--slate4); + background-color: var(--slate4); border-radius: 6px; box-shadow: 0 2px 10px var(--black-a7); + width: 142px; + // padding: 1px; } .ToggleGroupItem { all: unset; - background-color: var(--slate4); + background-color: var(--slate4); color: var(--slate9); min-height: 28px; display: flex; @@ -18,25 +20,50 @@ justify-content: center; padding: 2px 6px; border-radius: 6px; + width: 67px; + + font-size: 10px; + } + .ToggleGroupItem:first-child { margin-left: 0; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } + .ToggleGroupItem:last-child { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } + .ToggleGroupItem[data-state='on'] { padding: 2px 2px; + .toggle-item { - background-color: var(--slate1); + background-color: white; color: var(--indigo9); border-radius: 6px; padding: 4px; + height: 28px; + font-size: 10px; + width: 100%; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + + svg { + path { + fill: var(--indigo9) !important; + + } + + } } } + .ToggleGroupItem:focus { position: relative; } \ No newline at end of file diff --git a/frontend/src/ToolJetUI/Tabs/tabs.scss b/frontend/src/ToolJetUI/Tabs/tabs.scss index 2418083d17..523016cd92 100644 --- a/frontend/src/ToolJetUI/Tabs/tabs.scss +++ b/frontend/src/ToolJetUI/Tabs/tabs.scss @@ -1,12 +1,18 @@ - .tj-tabs { .nav-tabs { border-bottom: 0px !important; + height: 36px; } + + .nav-item { font-size: 12px !important; line-height: 20px !important; + + button { + height: 36px; + } } .nav-item .nav-link { @@ -19,5 +25,4 @@ border-width: 2px !important; color: var(--indigo9) !important; } -} - +} \ No newline at end of file diff --git a/frontend/src/_helpers/utils.js b/frontend/src/_helpers/utils.js index 697134f3fa..9d706403c9 100644 --- a/frontend/src/_helpers/utils.js +++ b/frontend/src/_helpers/utils.js @@ -342,7 +342,7 @@ export function validateWidget({ validationObject, widgetValue, currentState, cu const minValue = validationObject?.minValue?.value; const maxValue = validationObject?.maxValue?.value; const customRule = validationObject?.customRule?.value; - + const mandatory = validationObject?.mandatory?.value; const validationRegex = resolveWidgetFieldValue(regex, currentState, '', customResolveObjects); const re = new RegExp(validationRegex, 'g'); @@ -373,7 +373,7 @@ export function validateWidget({ validationObject, widgetValue, currentState, cu const resolvedMinValue = resolveWidgetFieldValue(minValue, currentState, undefined, customResolveObjects); if (resolvedMinValue !== undefined) { - if (widgetValue === undefined || widgetValue < parseInt(resolvedMinValue)) { + if (widgetValue === undefined || widgetValue < parseFloat(resolvedMinValue)) { return { isValid: false, validationError: `Minimum value is ${resolvedMinValue}`, @@ -383,7 +383,7 @@ export function validateWidget({ validationObject, widgetValue, currentState, cu const resolvedMaxValue = resolveWidgetFieldValue(maxValue, currentState, undefined, customResolveObjects); if (resolvedMaxValue !== undefined) { - if (widgetValue === undefined || widgetValue > parseInt(resolvedMaxValue)) { + if (widgetValue === undefined || widgetValue > parseFloat(resolvedMaxValue)) { return { isValid: false, validationError: `Maximum value is ${resolvedMaxValue}`, @@ -396,6 +396,13 @@ export function validateWidget({ validationObject, widgetValue, currentState, cu return { isValid: false, validationError: resolvedCustomRule }; } + const resolvedMandatory = resolveWidgetFieldValue(mandatory, currentState, false, customResolveObjects); + + if (resolvedMandatory == true) { + if (!widgetValue) { + return { isValid: false, validationError: `Field cannot be empty` }; + } + } return { isValid, validationError, diff --git a/frontend/src/_styles/designtheme.scss b/frontend/src/_styles/designtheme.scss index d11f2aa61a..2f0721bb3a 100644 --- a/frontend/src/_styles/designtheme.scss +++ b/frontend/src/_styles/designtheme.scss @@ -64,8 +64,15 @@ :root { --base: #FCFCFD; --base-black: #18181A; - --text-black-000 : #000000; - --slate12:#313739; + --text-black-000: #000000; + --slate12: #313739; + + --tj-text-input-widget-border-default: #D7DBDF; + --tj-text-input-widget-field-default: #FFFFFF; + --tj-text-input-widget-hover: #F1F3F5; + --tj-text-input-widget-error: #DB4324; + --tj-text-input-widget-disabled: #DFE3E6; + --tj-text-input-widget-border-clicked: #3E63DD; } .dark-theme { @@ -73,5 +80,15 @@ --base: #1f2936; --base-black: #FBFCFD; --text-black-000: #ffffff; - --slate1:#1f2936; + --slate1: #1f2936; + + --tj-text-input-widget-border-default: #4C5155; + --tj-text-input-widget-field-default: #313538; + --tj-text-input-widget-hover: #292D2F; + --tj-text-input-widget-error: #EC5E41; + --tj-text-input-widget-disabled: #2B2F3199; + --tj-text-input-widget-border-clicked: #3E63DD; + + + } \ No newline at end of file diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 5dd04c86bf..46856a5245 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -286,7 +286,7 @@ button { font-weight: 500 !important; box-shadow: none !important; color: var(--slate12) !important; - padding: 20px 16px !important; + padding: 16px 16px !important; } .accordion-button::after { @@ -302,7 +302,7 @@ button { } .accordion-body { - padding: 6px 16px 20px 16px !important; + padding: 4px 16px 16px 16px !important; .form-label { font-weight: 400; @@ -330,7 +330,7 @@ button { .resizer-select, .resizer-active { - border: solid 1px $primary !important; + outline: solid 1px $primary !important; .top-right, .top-left, @@ -743,7 +743,7 @@ button { margin: 0px auto; .resizer { - border: solid 1px transparent; + // border: solid 1px red; } } } @@ -830,7 +830,7 @@ button { .list-group.list-group-transparent.dark .all-apps-link, .list-group-item-action.dark.active { - background-color: $dark-background !important; + background-color: $dark-background !important; } } @@ -1128,6 +1128,7 @@ button { } } + .template-library-modal { font-weight: 500; @@ -1284,7 +1285,7 @@ button { height: 80%; padding: 0 10px; background-color: var(--base) !important; - + .container-fluid { height: 100%; padding: 0; @@ -1462,6 +1463,7 @@ button { /* IE and Edge */ scrollbar-width: none; /* Firefox */ + border-top: 1px solid var(--slate5) !important; } /* Hide scrollbar for Chrome, Safari and Opera */ @@ -1469,7 +1471,7 @@ button { display: none; } - .accordion { + .accordion:last-child { margin-bottom: 45px !important; } @@ -1576,7 +1578,7 @@ button { .select-search-dark input { width: 224px !important; height: 32px !important; - border-radius: $border-radius !important; + border-radius: $border-radius !important; } } @@ -1587,7 +1589,7 @@ button { .select-search__value input, .select-search-dark input { height: 32px !important; - border-radius: $border-radius !important; + border-radius: $border-radius !important; } } @@ -1648,7 +1650,7 @@ button { -webkit-appearance: none; -moz-appearance: none; appearance: none; - border-radius: $border-radius !important; + border-radius: $border-radius !important; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @@ -2445,8 +2447,6 @@ body { } .code-hinter { - height: 36px; - .form-control { .CodeMirror { font-family: "Roboto", sans-serif; @@ -2691,7 +2691,7 @@ hr { .CodeMirror-placeholder { color: #9e9e9e !important; font-size: 0.7rem !important; - margin-top: 2px !important; + // margin-top: 2px !important; font-size: 12px !important; } @@ -2840,14 +2840,14 @@ input:focus-visible { bottom: 0; background: var(--indigo3); width: 18.75rem; // 300px - z-index: 1; + z-index: 999; padding: 12px 18px; display: flex; justify-content: space-between; cursor: pointer; .widget-documentation-link-text { - margin-left: 13px; + margin-left: 10px; font-weight: 500; color: var(--slate12); } @@ -3694,7 +3694,7 @@ input[type="text"] { .nav-tabs .nav-link.active { font-weight: 400 !important; - color: $primary !important; + color: $primary !important; } .empty { @@ -4220,7 +4220,7 @@ input[type="text"] { .tabs-inspector.dark { .nav-link.active { - border-bottom: 1px solid $primary !important; + border-bottom: 1px solid $primary !important; } } @@ -4469,7 +4469,7 @@ input[type="text"] { } input { - border-radius: $border-radius !important; + border-radius: $border-radius !important; padding-left: 1.75rem !important; } } @@ -4638,20 +4638,20 @@ input[type="text"] { } .modal-content.home-modal-component.dark { - background-color: $bg-dark-light !important; - color: $white !important; + background-color: $bg-dark-light !important; + color: $white !important; .modal-title { - color: $white !important; + color: $white !important; } .tj-version-wrap-sub-footer { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; border-top: 1px solid #3A3F42 !important; p { - color: $white !important; + color: $white !important; } } @@ -4662,8 +4662,8 @@ input[type="text"] { } .modal-header { - background-color: $bg-dark-light !important; - color: $white !important; + background-color: $bg-dark-light !important; + color: $white !important; border-bottom: 2px solid #3A3F42 !important; } @@ -4672,32 +4672,22 @@ input[type="text"] { } .form-control { - border-color: $border-grey-dark !important; + border-color: $border-grey-dark !important; color: inherit; } input { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; } .form-select { - background-color: $bg-dark !important; - color: $white !important; - border-color: $border-grey-dark !important; + background-color: $bg-dark !important; + color: $white !important; + border-color: $border-grey-dark !important; } .text-muted { - color: $white !important; - } -} - -.inspector-align-buttons { - .ToggleGroupItem { - padding: 2px 10px !important; - } - - .ToggleGroupItem[data-state='on'] { - padding: 2px 9px !important; + color: $white !important; } } @@ -4956,10 +4946,6 @@ input[type="text"] { // comment styles ::override .editor-sidebar { - .nav-tabs { - border-bottom: 1px solid var(--slate5) !important; - } - .nav-tabs .nav-link.active { background-color: transparent !important; } @@ -4979,6 +4965,7 @@ input[type="text"] { margin: 0; display: flex; align-items: center; + height: 36px; } } @@ -4998,7 +4985,7 @@ div#driver-page-overlay { } .dark-theme-walkthrough#driver-popover-item { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; border-color: rgba(101, 109, 119, 0.16) !important; .driver-popover-title { @@ -5006,7 +4993,7 @@ div#driver-page-overlay { } .driver-popover-tip { - border-color: transparent transparent transparent $bg-dark-light !important; + border-color: transparent transparent transparent $bg-dark-light !important; } .driver-popover-description { @@ -5038,7 +5025,7 @@ div#driver-page-overlay { .driver-next-btn, .driver-prev-btn { - color: $primary !important; + color: $primary !important; } .driver-disabled { @@ -5511,7 +5498,7 @@ div#driver-page-overlay { } .selected-node { - border-color: $primary-light !important; + border-color: $primary-light !important; } .json-tree-icon-container .selected-node>svg:first-child { @@ -5602,7 +5589,7 @@ div#driver-page-overlay { } .selected-node { - border-color: $primary-light !important; + border-color: $primary-light !important; } .selected-node .group-object-container .badge { @@ -5920,7 +5907,7 @@ div#driver-page-overlay { //Kanban board .kanban-container.dark-themed { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; .kanban-column { .card-header { @@ -5966,7 +5953,7 @@ div#driver-page-overlay { } .dnd-card.card.card-dark { - background-color: $bg-dark !important; + background-color: $bg-dark !important; } } @@ -6312,6 +6299,7 @@ input.hide-input-arrows { background-color: #232e3c; border-bottom: 1px solid #324156; } + .popover-body { background-color: #232e3c; border-radius: 6px; @@ -10267,6 +10255,7 @@ tbody { border: 1px solid var(--indigo9) !important; box-shadow: none !important; } + &.input-error-border { border-color: #DB4324 !important; } @@ -10697,7 +10686,7 @@ tbody { #global-settings-popover.theme-dark { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; border: 1px solid #2B2F31; .global-popover-text { @@ -10705,7 +10694,7 @@ tbody { } .maximum-canvas-width-input-select { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; border: 1px solid #324156; color: $white; } @@ -11095,11 +11084,13 @@ tbody { .field-name { color: var(--slate-12) !important; } + input.slug-input { background: #1f2936 !important; color: #f4f6fa !important; border-color: #324156 !important; - } + } + .applink-text { background-color: #2b394b !important; } @@ -11113,7 +11104,7 @@ tbody { } .input-with-icon { - .form-control{ + .form-control { background-color: #1f2936 !important; border-color: #3E4B5A !important; color: #fff !important; @@ -11123,7 +11114,7 @@ tbody { } -.dark-theme{ +.dark-theme { .manage-app-users-footer { .default-secondary-button { background-color: var(--indigo9); @@ -11136,9 +11127,9 @@ tbody { word-break: break-all; } -.workspace-folder-modal{ +.workspace-folder-modal { .tj-text-input.dark { - background: #202425; + background: #202425; border-color: var(--slate7) !important; } } @@ -11147,7 +11138,7 @@ tbody { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - width: 150px; + width: 150px; } .app-slug-container, @@ -11179,7 +11170,7 @@ tbody { &:hover { box-shadow: none; } - + &:active { border: 1px solid #D7DBDF; box-shadow: none; @@ -11188,6 +11179,7 @@ tbody { .input-with-icon { flex: none; + .icon-container { right: 20px; top: calc(50% - 13px); @@ -11202,7 +11194,7 @@ tbody { color: #3D9A50; } - + .workspace-spinner { color: #889096 !important; width: 16px; @@ -11214,6 +11206,7 @@ tbody { color: var(--indigo9); } } + .confirm-dialogue-modal { background: var(--base); } @@ -11543,10 +11536,10 @@ tbody { border-color: var(--tomato9) !important; } - .icon-container { + .icon-container { right: 12px; - top: calc(50% - 11px); - + top: calc(50% - 11px); + .spinner-border { width: 20px; height: 20px; @@ -11563,6 +11556,7 @@ tbody { .manage-app-users-footer { padding-bottom: 20px; margin-top: 18px; + .default-secondary-button { width: auto !important; padding: 18px; @@ -11841,13 +11835,15 @@ tbody { .CodeMirror { background: var(--base); + font-size: 12px; } .color-picker-input { - border: solid 1px #333c48; + position: relative; height: 36px; background-color: var(--slate1); border: 1px solid var(--slate7); + border-radius: 6px; &:hover { background-color: var(--slate4); @@ -11857,6 +11853,7 @@ tbody { } #popover-basic-2 { + .sketch-picker { left: 7px; width: 170px !important; @@ -11875,6 +11872,10 @@ tbody { gap: 6px } +.custom-gap-2 { + gap: 2px +} + // ToolJet Database buttons .ghost-black-operation { @@ -11906,3 +11907,68 @@ tbody { } } +.tj-text-input-widget { + border: 1px solid var(--tj-text-input-widget-border-default); + background-color: var(--tj-text-input-widget-field-default); + width: 100%; + + &.is-invalid { + border: 1px solid var(--tj-text-input-widget-error) !important; // For example, a red border for invalid input + } + + &:focus { + // box-shadow: 0px 0px 0px 4px #DFE3E6 !important; + outline: none !important; + border: 1px solid var(--tj-text-input-widget-border-clicked); + + } + + &:active { + box-shadow: 0px 0px 0px 1px rgba(62, 99, 221, 0.30); + border: 1px solid var(--tj-text-input-widget-border-clicked) !important; + outline: none !important; + } + + &::placeholder { + color: #7E868C; + } +} + +.icon-style-container { + width: 142px; + height: 32px; + display: flex; + align-items: center; + border-radius: 6px; + padding: 2px; +} + +.visibility-eye { + position: absolute; + top: 50%; + right: -5px; + transform: translate(-50%, -50%); +} + +.label-hinter-margin { + margin-bottom: 4px; +} + +.accordion-header { + height: 52px; +} + +.code-hinter { + min-height: 32px !important; +} + +.CodeMirror-sizer { + // height: 30px !important; + display: flex; + align-items: center; + min-height: 32px !important; +} + +.CodeMirror-placeholder { + min-width: 265px !important; +} \ No newline at end of file diff --git a/frontend/src/_ui/Accordion/AccordionItem.js b/frontend/src/_ui/Accordion/AccordionItem.js index 545e2c996e..2e518f1c63 100644 --- a/frontend/src/_ui/Accordion/AccordionItem.js +++ b/frontend/src/_ui/Accordion/AccordionItem.js @@ -1,8 +1,34 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import cx from 'classnames'; const AccordionItem = ({ open = true, index, title, children }) => { const [show, setShow] = React.useState(open); + const [newChildren, setNewChildren] = React.useState([]); + + useEffect(() => { + setNewChildren(removeEmptyItems(children)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [children]); + + function isNotEmpty(item) { + if (Array.isArray(item)) { + return item.some(isNotEmpty); // Check if any element in the array is not empty + } else if (typeof item === 'object') { + return Object.keys(item).some((key) => isNotEmpty(item[key])); // Check if any key in the object is not empty + } else { + return Boolean(item); // Check if the item itself is truthy + } + } + + function removeEmptyItems(input) { + if (Array.isArray(input)) { + return input.filter(isNotEmpty); + } else if (typeof input === 'object') { + return isNotEmpty(input) ? input : null; + } else { + return input; + } + } return (

setShow(!show)} className="accordion-header" id={`heading-${index}`}> @@ -14,7 +40,7 @@ const AccordionItem = ({ open = true, index, title, children }) => { aria-expanded="false" data-cy={`widget-accordion-${title.toLowerCase()}`} > - {title} + {title}

{ className={cx('accordion-collapse collapse', { show })} data-bs-parent="#accordion-example" > -
{children}
+
{newChildren}
); diff --git a/frontend/src/_ui/CustomInput/CustomInput.scss b/frontend/src/_ui/CustomInput/CustomInput.scss new file mode 100644 index 0000000000..95fddc0436 --- /dev/null +++ b/frontend/src/_ui/CustomInput/CustomInput.scss @@ -0,0 +1,19 @@ +.form-text { + position: relative; + margin: 0px !important; + height: 32px; + + + input { + height: 32px; + margin: 0px !important; + + } +} + +.static-value { + position: absolute; + right: 10px; + color: var(--slate9); + top: 6px; +} \ No newline at end of file diff --git a/frontend/src/_ui/CustomInput/index.js b/frontend/src/_ui/CustomInput/index.js new file mode 100644 index 0000000000..683fa636b7 --- /dev/null +++ b/frontend/src/_ui/CustomInput/index.js @@ -0,0 +1,25 @@ +import React from 'react'; +import './CustomInput.scss'; + +function index({ value, disabled, staticText, onInputChange }) { + return ( +
+ { + onInputChange(e); + }} + /> + +
+ ); +} + +export default index; diff --git a/frontend/src/_ui/Icon/solidIcons/AlignLeftinspector.jsx b/frontend/src/_ui/Icon/solidIcons/AlignLeftinspector.jsx new file mode 100644 index 0000000000..24d65152e8 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/AlignLeftinspector.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const AlignLeftinspector = ({ fill = '', width = '25', className = '', viewBox = '0 0 25 25' }) => { + return ( + + + + + ); +}; + +export default AlignLeftinspector; diff --git a/frontend/src/_ui/Icon/solidIcons/AlignRightinspector.jsx b/frontend/src/_ui/Icon/solidIcons/AlignRightinspector.jsx new file mode 100644 index 0000000000..31e722cea1 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/AlignRightinspector.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const AlignRightinspector = ({ fill = '', width = '25', className = '', viewBox = '0 0 25 25' }) => { + return ( + + + + + ); +}; + +export default AlignRightinspector; diff --git a/frontend/src/_ui/Icon/solidIcons/CheveronDown.jsx b/frontend/src/_ui/Icon/solidIcons/CheveronDown.jsx index b7da5d7dfe..a0214dd498 100644 --- a/frontend/src/_ui/Icon/solidIcons/CheveronDown.jsx +++ b/frontend/src/_ui/Icon/solidIcons/CheveronDown.jsx @@ -1,12 +1,13 @@ import React from 'react'; -const CheveronDown = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24' }) => ( +const CheveronDown = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24', style, height }) => ( ( +const CheveronUp = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24', style, height }) => ( ( +const Eye1 = ({ style, fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( ( +const EyeDisable = ({ style, fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( { switch (props.name) { case 'addrectangle': return ; + case 'alignleftinspector': + return ; + case 'alignrightinspector': + return ; case 'alignright': return ; case 'apps': diff --git a/server/migrations/1701335703893-TextInputMaxHeight.ts b/server/migrations/1701335703893-TextInputMaxHeight.ts new file mode 100644 index 0000000000..92c17d4de9 --- /dev/null +++ b/server/migrations/1701335703893-TextInputMaxHeight.ts @@ -0,0 +1,29 @@ +import { Component } from 'src/entities/component.entity'; +import { In, MigrationInterface, QueryRunner } from 'typeorm'; + +export class TextInputMaxHeight1701335703893 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const componentTypes = ['TextInput', 'NumberInput', 'PasswordInput']; + const entityManager = queryRunner.manager; + + const components = await entityManager.find(Component, { + where: { type: In(componentTypes) }, + order: { createdAt: 'ASC' }, + }); + + for (const component of components) { + // Ensure properties is always an object + const properties = component.properties || {}; + // Check if properties.label is not present, then assign it as null + if (properties.label == undefined || null) { + properties.label = ''; + } + + // Update the component in the database with the modified properties + await entityManager.update(Component, component.id, { properties }); + } + } + + // implemented in this example + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/migrations/1705162272372-numberInputMinMaxValidation.ts b/server/migrations/1705162272372-numberInputMinMaxValidation.ts new file mode 100644 index 0000000000..68f1b863ab --- /dev/null +++ b/server/migrations/1705162272372-numberInputMinMaxValidation.ts @@ -0,0 +1,41 @@ +import { Component } from 'src/entities/component.entity'; +import { In, MigrationInterface, QueryRunner } from 'typeorm'; + +export class NumberInputMinMaxValidation1705162272372 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // Array of component types to be enhanced + const componentTypes = ['NumberInput']; + + // Obtaining the TypeORM EntityManager from the QueryRunner + const entityManager = queryRunner.manager; + + // Retrieving components of specified types from the database + const components = await entityManager.find(Component, { + where: { type: In(componentTypes) }, // Filtering by component types + order: { createdAt: 'ASC' }, // Ordering components by creation date in ascending order + }); + + // Iterating through each retrieved component + for (const component of components) { + // Extracting properties and validation from the component + const properties = component.properties; + const validation = component.validation; + + // Moving 'minValue' from properties to validation + if (properties.minValue) { + validation.minValue = properties.minValue; + delete properties.minValue; // Removing 'minValue' from properties + } + + // Moving 'minValue' from properties to validation + if (properties.maxValue) { + validation.maxValue = properties.maxValue; + delete properties.maxValue; // Removing 'minValue' from properties + } + // Updating the component in the database with the modified properties and validation + await entityManager.update(Component, component.id, { properties, validation }); + } + } + + public async down(queryRunner: QueryRunner): Promise {} +} From a8403046290ce3078a91dc2d4e444c2aa65c892c Mon Sep 17 00:00:00 2001 From: Sherfin Shamsudeen Date: Tue, 6 Feb 2024 10:36:26 +0530 Subject: [PATCH 13/64] feat: Getter functions for query data and variables (#8241) * Introduce getVariable and getPageVariable actions * Introduce getData, getRawData and getLoadingState for queries --- frontend/src/Editor/CodeBuilder/utils.js | 2 ++ frontend/src/_helpers/appUtils.js | 26 ++++++++++++++++ frontend/src/_helpers/utils.js | 38 ++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/CodeBuilder/utils.js b/frontend/src/Editor/CodeBuilder/utils.js index 9044cba833..7851d8e367 100644 --- a/frontend/src/Editor/CodeBuilder/utils.js +++ b/frontend/src/Editor/CodeBuilder/utils.js @@ -71,6 +71,7 @@ export function getSuggestionKeys(refState, refSource) { const actions = [ 'runQuery', 'setVariable', + 'getVariable', 'unSetVariable', 'showAlert', 'logout', @@ -81,6 +82,7 @@ export function getSuggestionKeys(refState, refSource) { 'goToApp', 'generateFile', 'setPageVariable', + 'getPageVariable', 'unsetPageVariable', 'switchPage', ]; diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 45046e7edc..6ac5313204 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -143,6 +143,18 @@ async function executeRunPycode(_ref, code, query, isPreview, mode) { currentState.queries[key] = { ...currentState.queries[key], run: () => actions.runQuery(key), + + getData: () => { + return getCurrentState().queries[key].data; + }, + + getRawData: () => { + return getCurrentState().queries[key].rawData; + }, + + getloadingState: () => { + return getCurrentState().queries[key].isLoading; + }, }; } @@ -536,6 +548,12 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { }); } + case 'get-custom-variable': { + const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); + const customAppVariables = { ...getCurrentState().variables }; + return customAppVariables[key]; + } + case 'unset-custom-variable': { const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); const customAppVariables = { ...getCurrentState().variables }; @@ -560,6 +578,14 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { }); } + case 'get-page-variable': { + const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); + const customPageVariables = { + ...getCurrentState().page.variables, + }; + return customPageVariables[key]; + } + case 'unset-page-variable': { const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); const customPageVariables = _.omit(getCurrentState().page.variables, key); diff --git a/frontend/src/_helpers/utils.js b/frontend/src/_helpers/utils.js index 9d706403c9..a216e9ef62 100644 --- a/frontend/src/_helpers/utils.js +++ b/frontend/src/_helpers/utils.js @@ -470,6 +470,18 @@ export async function executeMultilineJS( query.options.parameters?.forEach((arg) => (processedParams[arg.name] = params[arg.name])); return actions.runQuery(key, processedParams); }, + + getData: () => { + return getCurrentState().queries[key].data; + }, + + getRawData: () => { + return getCurrentState().queries[key].rawData; + }, + + getloadingState: () => { + return getCurrentState().queries[key].isLoading; + }, }; } @@ -605,9 +617,9 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => { ); } - if (isPreview) { - return previewQuery(_ref, query, true, processedParams); - } + // if (isPreview) { + // return previewQuery(_ref, query, true, processedParams); + // } const event = { actionId: 'run-query', @@ -630,6 +642,16 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => { } }; + const getVariable = (key = '') => { + if (key) { + const event = { + actionId: 'get-custom-variable', + key, + }; + return executeAction(_ref, event, mode, {}); + } + }; + const unSetVariable = (key = '') => { if (key) { const event = { @@ -735,6 +757,14 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => { return executeAction(_ref, event, mode, {}); }; + const getPageVariable = (key = '') => { + const event = { + actionId: 'get-page-variable', + key, + }; + return executeAction(_ref, event, mode, {}); + }; + const unsetPageVariable = (key = '') => { const event = { actionId: 'unset-page-variable', @@ -773,6 +803,7 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => { return { runQuery, setVariable, + getVariable, unSetVariable, showAlert, logout, @@ -783,6 +814,7 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => { goToApp, generateFile, setPageVariable, + getPageVariable, unsetPageVariable, switchPage, }; From 30386b31917fadb13dfe1cce51ac623cf6be770c Mon Sep 17 00:00:00 2001 From: Manish Kushare <37823141+manishkushare@users.noreply.github.com> Date: Tue, 6 Feb 2024 10:37:13 +0530 Subject: [PATCH 14/64] Fix : All fx code blocks are getting active when fxActive is true for a table column (#8664) * bug fixed - all fx code blocks are getting active when fxActive is true * removed consoles * added error handling mechanism for fxActiveFields created a function to improve readability of onFxPress callback function --- .../Table/ProgramaticallyHandleProperties.jsx | 53 ++++++++++++++++++- .../Inspector/Components/Table/Table.jsx | 2 +- .../src/Editor/WidgetManager/widgetConfig.js | 3 ++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx b/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx index 695376bb8b..9b1ff4af85 100644 --- a/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx +++ b/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx @@ -1,3 +1,4 @@ +import { resolveReferences } from '@/_helpers/utils'; import React from 'react'; import { CodeHinter } from '../../../CodeBuilder/CodeHinter'; @@ -13,6 +14,7 @@ export const ProgramaticallyHandleProperties = ({ paramMeta, // eslint-disable-next-line no-unused-vars paramType, + currentState, }) => { const getValueBasedOnProperty = (property, props) => { switch (property) { @@ -45,6 +47,52 @@ export const ProgramaticallyHandleProperties = ({ const initialValue = getInitialValue(property, definition); const options = {}; + + const calcFxActiveState = (props, property) => { + const fxActiveFieldsPresent = props?.hasOwnProperty('fxActiveFields'); + if (!fxActiveFieldsPresent) { + return props?.fxActive ?? false; + } + const fxActiveFields = props.fxActiveFields; + const propertyPresentInFxActiveFields = fxActiveFields.length >= 1 && fxActiveFields.includes(property); + return propertyPresentInFxActiveFields; + }; + + const calculateFxActiveFields = (active, props, property) => { + const fxActiveFieldsPropExists = props?.hasOwnProperty('fxActiveFields') ?? false; + //to support backward compatibility, when fxActive is true for a particular column, we are passing all possible combinations which should render codehinter + const fxActive = + props?.fxActive && resolveReferences(props.fxActive, currentState) + ? ['isEditable', 'columnVisibility', 'linkTarget'] + : []; + + const checkFxActiveFieldIsArrray = (fxActiveFieldsProperty) => { + // adding error handling mechanism for fxActiveFieldsProperty , if props.fxActiveFields is array , then return props.fxActiveFields or else return [], this will make sure, fxActiveFields wil always be array + return Array.isArray(fxActiveFieldsProperty) ? fxActiveFieldsProperty : []; + }; + + const fxActiveFields = fxActiveFieldsPropExists ? checkFxActiveFieldIsArrray(props.fxActiveFields) : fxActive; + + const findIndexOfPropertyInFxActiveFields = (property, fxActiveFields) => { + // checking if particular property is already present in the fxActiveFields or not + const index = fxActiveFields.findIndex((prop) => prop === property); + return index; + }; + + const indexOfProp = findIndexOfPropertyInFxActiveFields(property, fxActiveFields); + + // removing or addding property for which we want to render codehinter + if (active && indexOfProp === -1) { + //if active is true and property does not present in the fxActiveField before hand, if both conditions are satisfied, we add it to the array of fxActiveField. + fxActiveFields.push(property); + } else if (!active && indexOfProp !== -1) { + // if active is falsy and particular property is present in the fxActiveField , we remove that particular property from the array + fxActiveFields.splice(indexOfProp, 1); + } + + return fxActiveFields; + }; + return (
e.stopPropagation()}> { - callbackFunction(index, 'fxActive', active); + const resultFxActiveFields = calculateFxActiveFields(active, props, property); + callbackFunction(index, 'fxActiveFields', resultFxActiveFields); }} - fxActive={props?.fxActive ?? false} + fxActive={calcFxActiveState(props, property)} component={component.component} className={options.className} /> diff --git a/frontend/src/Editor/Inspector/Components/Table/Table.jsx b/frontend/src/Editor/Inspector/Components/Table/Table.jsx index c7ccdcafc7..8a1136d6c2 100644 --- a/frontend/src/Editor/Inspector/Components/Table/Table.jsx +++ b/frontend/src/Editor/Inspector/Components/Table/Table.jsx @@ -908,7 +908,7 @@ class TableComponent extends React.Component { addNewColumn = () => { const columns = this.props.component.component.definition.properties.columns; const newValue = columns.value; - newValue.push({ name: this.generateNewColumnName(columns.value), id: uuidv4() }); + newValue.push({ name: this.generateNewColumnName(columns.value), id: uuidv4(), fxActiveFields: [] }); this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties', true); }; diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index 0c180fef94..900d6b7a29 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -505,16 +505,19 @@ export const widgets = [ name: 'id', id: 'e3ecbf7fa52c4d7210a93edb8f43776267a489bad52bd108be9588f790126737', autogenerated: true, + fxActiveFields: [], }, { name: 'name', id: '5d2a3744a006388aadd012fcc15cc0dbcb5f9130e0fbb64c558561c97118754a', autogenerated: true, + fxActiveFields: [], }, { name: 'email', id: 'afc9a5091750a1bd4760e38760de3b4be11a43452ae8ae07ce2eebc569fe9a7f', autogenerated: true, + fxActiveFields: [], }, ], }, From 4a21eeb9f68685b8b1c0e08ec8e1fb4d5b19e08d Mon Sep 17 00:00:00 2001 From: Sherfin Shamsudeen Date: Tue, 6 Feb 2024 10:38:44 +0530 Subject: [PATCH 15/64] feat: Add transformations feature to table columns (#8287) * Add transformations feature to table columns * Make table transformations listen to current state * Move column transformation to below "key" in column properties * Properly exposed currentData and currentPageData of table alongwith transformations --- frontend/assets/translations/en.json | 5 ++- .../src/Editor/CodeBuilder/CodeHinter.jsx | 2 +- .../src/Editor/Components/Table/Table.jsx | 37 ++++++++++++++++++- .../Inspector/Components/Table/Table.jsx | 19 ++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/frontend/assets/translations/en.json b/frontend/assets/translations/en.json index 0a3439e3ca..528093d072 100644 --- a/frontend/assets/translations/en.json +++ b/frontend/assets/translations/en.json @@ -503,8 +503,9 @@ "commonProperties": { "visibility": "Visibility", "disable": "Disable", - "borderRadius": "Border radius", - "boxShadow": "Box shadow", + "borderRadius": "Border Radius", + "transformation": "Transformation", + "boxShadow": "Box Shadow", "tooltip": "Tooltip", "showOnDesktop": "Show on desktop", "showOnMobile": "Show on mobile", diff --git a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx index 5eb663d7a3..7046551c16 100644 --- a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx +++ b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx @@ -480,7 +480,7 @@ export function CodeHinter({
column.transformation && column.transformation != '{{cellValue}}') + .map((column) => ({ + key: column.key ? column.key : column.name, + transformation: column.transformation, + })); + + tableData = useMemo(() => { + return tableData.map((row) => ({ + ...row, + ...Object.fromEntries( + transformations.map((t) => [ + t.key, + resolveReferences(t.transformation, currentState, row[t.key], { cellValue: row[t.key], rowData: row }), + ]) + ), + })); + }, [JSON.stringify([transformations, currentState])]); + + useEffect(() => { + setExposedVariables({ + currentData: tableData, + updatedData: tableData, + }); + }, [JSON.stringify(tableData)]); + const columnDataForAddNewRows = generateColumnsData({ columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value, columnSizes, @@ -512,7 +540,7 @@ export function Table({ } } return _.isEmpty(updatedDataReference.current) ? tableData : updatedDataReference.current; - }, [tableData.length, component.definition.properties.data.value, JSON.stringify(properties.data)]); + }, [tableData.length, component.definition.properties.data.value, JSON.stringify([properties.data, tableData])]); useEffect(() => { if ( @@ -883,6 +911,11 @@ export function Table({ //hack : in the initial render, data is undefined since, upon feeding data to the table from some query, query inside current state is {}. Hence we added data in the dependency array, now question is should we add data or rows? }, [JSON.stringify(defaultSelectedRow), JSON.stringify(data)]); + const pageData = page.map((row) => row.original); + useEffect(() => { + setExposedVariable('currentPageData', pageData); + }, [JSON.stringify(pageData)]); + function downlaodPopover() { const options = [ { dataCy: 'option-download-CSV', text: 'Download as CSV', value: 'csv' }, diff --git a/frontend/src/Editor/Inspector/Components/Table/Table.jsx b/frontend/src/Editor/Inspector/Components/Table/Table.jsx index 8a1136d6c2..83f7d7ae78 100644 --- a/frontend/src/Editor/Inspector/Components/Table/Table.jsx +++ b/frontend/src/Editor/Inspector/Components/Table/Table.jsx @@ -268,6 +268,25 @@ class TableComponent extends React.Component { }} />
+ +
+ + this.onColumnItemChange(index, 'transformation', value)} + componentName={this.getPopoverFieldSource(column.columnType, 'transformation')} + popOverCallback={(showing) => { + this.setColumnPopoverRootCloseBlocker('transformation', showing); + }} + enablePreview={false} + /> +
+
@@ -1005,7 +1013,7 @@ export const EventManager = ({ const renderAddHandlerBtn = () => { return ( - + {t('editor.inspector.eventManager.addHandler', 'New event handler')} ); diff --git a/frontend/src/Editor/Inspector/ManageEventButton.jsx b/frontend/src/Editor/Inspector/ManageEventButton.jsx index ba512baf5e..d1270466b0 100644 --- a/frontend/src/Editor/Inspector/ManageEventButton.jsx +++ b/frontend/src/Editor/Inspector/ManageEventButton.jsx @@ -4,8 +4,16 @@ import AddRectangle from '@/_ui/Icon/solidIcons/AddRectangle'; import Trash from '@/_ui/Icon/solidIcons/Trash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import SortableList from '@/_components/SortableList'; +import { Spinner } from 'react-bootstrap'; -const ManageEventButton = ({ eventDisplayName = 'Upon events', actionName, index, removeHandler }) => { +const ManageEventButton = ({ + eventDisplayName = 'Upon events', + actionName, + index, + removeHandler, + actionsUpdatedLoader, + eventsUpdatedLoader, +}) => { const [isHovered, setIsHovered] = useState(false); return ( @@ -25,40 +33,48 @@ const ManageEventButton = ({ eventDisplayName = 'Upon events', actionName, index style={{ padding: '6px 12px 6px 8px', width: '100%' }} >
- {eventDisplayName} + {!eventsUpdatedLoader ? ( + eventDisplayName + ) : ( + + )}
-
- - {actionName ? actionName : 'Select action'} - - {!actionName && } - + {!actionsUpdatedLoader ? ( +
+ + {actionName ? actionName : 'Select action'} + + {!actionName && } - {isHovered && ( - { - event.stopPropagation(); - }} - > -
{ - e.stopPropagation(); - removeHandler(index); + + {isHovered && ( + { + event.stopPropagation(); }} > -
- +
{ + e.stopPropagation(); + removeHandler(index); + }} + > +
+ +
-
-
- )} + + )} +
- -
+
+ ) : ( + + )}
diff --git a/frontend/src/ToolJetUI/Buttons/AddNewButton/AddNewButton.jsx b/frontend/src/ToolJetUI/Buttons/AddNewButton/AddNewButton.jsx index bbbdfbdb88..301991a677 100644 --- a/frontend/src/ToolJetUI/Buttons/AddNewButton/AddNewButton.jsx +++ b/frontend/src/ToolJetUI/Buttons/AddNewButton/AddNewButton.jsx @@ -2,7 +2,7 @@ import React from 'react'; import './addNewButton.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; -const AddNewButton = ({ children, dataCy, onClick, className = '' }) => { +const AddNewButton = ({ children, dataCy, onClick, className = '', isLoading }) => { return ( { leftIcon="plusrectangle" fill={'var(--indigo9)'} iconWidth={16} + isLoading={isLoading} > {children} diff --git a/frontend/src/_stores/appDataStore.js b/frontend/src/_stores/appDataStore.js index c12f2f9036..f21352fdf7 100644 --- a/frontend/src/_stores/appDataStore.js +++ b/frontend/src/_stores/appDataStore.js @@ -25,6 +25,9 @@ const initialState = { appId: null, areOthersOnSameVersionAndPage: false, appVersionPreviewLink: null, + eventsUpdatedLoader: false, + eventsCreatedLoader: false, + actionsUpdatedLoader: false, }; export const useAppDataStore = create( @@ -62,14 +65,21 @@ export const useAppDataStore = create( .finally(() => resolve()); }); }, - updateAppVersionEventHandlers: async (events, updateType = 'update') => { + updateAppVersionEventHandlers: async (events, updateType = 'update', param) => { useAppDataStore.getState().actions.setIsSaving(true); + if (param === 'actionId') { + set({ actionsUpdatedLoader: true }); + } + if (param === 'eventId') { + set({ eventsUpdatedLoader: true }); + } const appId = get().appId; const versionId = get().currentVersionId; const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, events, updateType); useAppDataStore.getState().actions.setIsSaving(false); + set({ eventsUpdatedLoader: false, actionsUpdatedLoader: false }); const updatedEvents = get().events; updatedEvents.forEach((e, index) => { @@ -84,12 +94,16 @@ export const useAppDataStore = create( createAppVersionEventHandlers: async (event) => { useAppDataStore.getState().actions.setIsSaving(true); + set({ eventsCreatedLoader: true }); + const appId = get().appId; const versionId = get().currentVersionId; const updatedEvents = get().events; const response = await appVersionService.createAppVersionEventHandler(appId, versionId, event); useAppDataStore.getState().actions.setIsSaving(false); + set({ eventsCreatedLoader: false }); + updatedEvents.push(response); set(() => ({ events: updatedEvents })); From 80506decb4ca485122f182b52c934c4ebfa8b781 Mon Sep 17 00:00:00 2001 From: Kiran Ashok Date: Tue, 6 Feb 2024 11:42:05 +0530 Subject: [PATCH 20/64] feat: Textinput , Passwordinput , Numberinput revamp (#8079) * init textinput revamp * updated styles panel * bugfix * updates * fix :: accordion * fix :: styling * add box shadow , additional property,tooltip * fix conditional render for styles * feat :: fixed order of each property and styles * feat :: styling input * bugfix * feat :: add option to add icon * add option to add icon * adding option to toggle visibility * updated password input with new design * chnaging component location * bugfix * style fixes * fix :: added loader * updated :: few detailing * few bugfixes * fix :: for form widget label * fixes * added option to add icon color * including label field for password input * fix for label * fix * test fix backward compatibility for height * updates * revert * adding key for distinguishing older and newer widgets * testing * test * test * update * update * migration testing * limit vertical resizing in textinput * testing * throw test * test * adding check for label length * fixing edge cases * removing resize * backward compatibility height * backward compatibility * number input review fixes * added exposed items * fixing csa * ui fixes * fix height compatibility * feat :: csa for all inputs and exposed variables * backward compatibility fixes and validation fixes * fixes :: textinput positioning of loader and icon * fix :: password input * cleanup and fixes * fixes * cleanup * fixes * review fixes * review fixes * typo fix * fix padding * review fixes styles component panel * fix naming * fix padding * fix :: icons position * updates * cleanup * updates events , csa * backward compatibility * clean * feat :: change validation from properties * ui fixes * icon name * removed 'px' text from tooltip * fixes placeholder * few updates :: removing label in form * ui in form * update :: number input validation behaviour * testing fixes * added side handlers * removing unwanted fx * disabling fx for padding field * ordering change * fix * label issue + restricted side handler * fix :: box shadow bug * on change event doesnt propagate exposed vars correctly * adding debounce for slider value change * fix :: for modal ooen bug during onfocus event * test slider * fix :: bugs regarding state update in checbox , slider , slider bug * update slider with radix slider * bugfix * fix auto widh bug * updae margin * few fixes renamed style ppts * stylefix * fix :: config handled not getting focused on hover of the component * typo * fix :: side dragging got disabled * changing everything to sentence case * removing unwanted exposed vars * expose labels * typo * fix mandatory position * fix :: for warning icon for deprecated csa's * fix :: when switching layouts box size is not updated --- frontend/package-lock.json | 36 +++++- frontend/package.json | 1 + frontend/src/Editor/Box.jsx | 4 +- .../src/Editor/CodeBuilder/CodeHinter.jsx | 4 +- .../Editor/CodeBuilder/Elements/Checkbox.jsx | 10 +- .../Editor/CodeBuilder/Elements/Slider.jsx | 57 +++++----- .../Editor/CodeBuilder/Elements/Slider.scss | 45 ++++++++ .../src/Editor/Components/NumberInput.jsx | 7 +- .../src/Editor/Components/PasswordInput.jsx | 13 +-- frontend/src/Editor/Components/TextInput.jsx | 9 +- frontend/src/Editor/ConfigHandle.jsx | 3 +- frontend/src/Editor/DraggableBox.jsx | 40 ++++--- .../Editor/LeftSidebar/SidebarInspector.jsx | 53 ++++++++- .../src/Editor/WidgetManager/widgetConfig.js | 105 +++++++++--------- .../ToolJetUI/SwitchGroup/toggleGroup.scss | 17 ++- frontend/src/_styles/designtheme.scss | 11 +- frontend/src/_styles/theme.scss | 15 +-- 17 files changed, 291 insertions(+), 139 deletions(-) create mode 100644 frontend/src/Editor/CodeBuilder/Elements/Slider.scss diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b40087fb96..eb027471d3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "@emoji-mart/react": "^1.1.1", "@radix-ui/colors": "^0.1.8", "@radix-ui/react-popover": "^1.0.3", + "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-toggle-group": "^1.0.4", "@react-google-maps/api": "^2.18.1", "@sentry/react": "^7.60.0", @@ -4927,7 +4928,6 @@ }, "node_modules/@radix-ui/number": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" @@ -5468,6 +5468,39 @@ } } }, + "node_modules/@radix-ui/react-slider": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.1.2.tgz", + "integrity": "sha512-NKs15MJylfzVsCagVSWKhGGLNR1W9qWs+HtgbmjjVUB3B9+lb3PYoXxVju3kOrpf0VKyVCtZp+iTwVoqpa1Chw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.1", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-use-size": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", @@ -5640,7 +5673,6 @@ }, "node_modules/@radix-ui/react-use-previous": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.10" diff --git a/frontend/package.json b/frontend/package.json index 466d8c39cc..1555e7d9d0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "@emoji-mart/react": "^1.1.1", "@radix-ui/colors": "^0.1.8", "@radix-ui/react-popover": "^1.0.3", + "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-toggle-group": "^1.0.4", "@react-google-maps/api": "^2.18.1", "@sentry/react": "^7.60.0", diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index a380155039..19d5579bba 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -150,14 +150,13 @@ export const Box = memo( childComponents, isResizing, adjustHeightBasedOnAlignment, + currentLayout, }) => { const { t } = useTranslation(); const backgroundColor = yellow ? 'yellow' : ''; const currentState = useCurrentState(); let styles = { height: '100%', - // paddingRight: '1px', - // paddingLeft: '1px', }; if (inCanvas) { @@ -347,6 +346,7 @@ export const Box = memo( dataCy={`draggable-widget-${String(component.name).toLowerCase()}`} isResizing={isResizing} adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment} + currentLayout={currentLayout} > ) : ( <> diff --git a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx index 7046551c16..0b1766189c 100644 --- a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx +++ b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx @@ -424,7 +424,8 @@ export function CodeHinter({
{paramLabel !== 'Type' && paramLabel !== ' ' && - paramLabel !== 'Padding' && ( //add some key if these extends + paramLabel !== 'Padding' && + paramLabel !== 'Width' && ( //add some key if these extends { @@ -489,6 +490,7 @@ export function CodeHinter({ maxHeight: '320px', overflow: 'auto', fontSize: ' .875rem', + maxWidth: paramLabel == 'Tooltip' && '190px', }} data-cy={`${cyLabel}-input-field`} > diff --git a/frontend/src/Editor/CodeBuilder/Elements/Checkbox.jsx b/frontend/src/Editor/CodeBuilder/Elements/Checkbox.jsx index c9136b5e18..919f73d7ed 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/Checkbox.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/Checkbox.jsx @@ -1,10 +1,14 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; function Checkbox({ value, onChange }) { const [isChecked, setIsChecked] = useState(value); // Initial state of the checkbox + useEffect(() => { + setIsChecked(value); + }, [value]); + return ( -
+
- + Auto width
diff --git a/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx b/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx index 834dbda82b..ea8933cd64 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx @@ -1,47 +1,54 @@ import React, { useState, useEffect } from 'react'; import CustomInput from '@/_ui/CustomInput'; -import throttle from 'lodash/throttle'; +// eslint-disable-next-line import/no-unresolved +import * as Slider from '@radix-ui/react-slider'; +import './Slider.scss'; function Slider1({ value, onChange, component }) { const [sliderValue, setSliderValue] = useState(value ? value : 33); // Initial value of the slider - const handleSliderChange = (event) => { - setSliderValue(event.target.value); - onChange(`{{${event.target.value}}}`); + useEffect(() => { + setSliderValue(value); + }, [value]); + + const handleSliderChange = (value) => { + setSliderValue(value); + onChange(value); }; // Throttle function to handle input changes - const onInputChange = throttle((e) => { + const onInputChange = (e) => { let inputValue = parseInt(e.target.value, 10) || 0; inputValue = Math.min(inputValue, 100); setSliderValue(inputValue); - onChange(`{{${inputValue}}}`); - }, 300); - - useEffect(() => { - return () => { - // Clear the throttle timeout when the component unmounts - onInputChange.cancel(); - }; - }, [onInputChange]); + onChange(inputValue); + }; return ( -
+
- +
+ + + + + + +
); } diff --git a/frontend/src/Editor/CodeBuilder/Elements/Slider.scss b/frontend/src/Editor/CodeBuilder/Elements/Slider.scss new file mode 100644 index 0000000000..c91b9a6843 --- /dev/null +++ b/frontend/src/Editor/CodeBuilder/Elements/Slider.scss @@ -0,0 +1,45 @@ +.SliderRoot { + position: relative; + display: flex; + align-items: center; + user-select: none; + touch-action: none; + width: 142px; + height: 20px; +} + +.SliderTrack { + background-color: #E8ECF0; + position: relative; + flex-grow: 1; + border-radius: 9999px; + height: 6px; +} + +.SliderRange { + position: absolute; + background-color: #4368E3; + border-radius: 9999px; + height: 100%; +} + +.SliderThumb { + display: block; + width: 12px; + height: 12px; + background-color: white; + border-radius: 10px; + border: 1px solid #C1C8CD; +} + +.SliderThumb:hover { + background-color: #fff; +} + +.SliderThumb:focus { + outline: none; +} + +.SliderRange[data-disabled] { + background-color: #C1C8CD; +} \ No newline at end of file diff --git a/frontend/src/Editor/Components/NumberInput.jsx b/frontend/src/Editor/Components/NumberInput.jsx index fd1b46db47..28b6af9012 100644 --- a/frontend/src/Editor/Components/NumberInput.jsx +++ b/frontend/src/Editor/Components/NumberInput.jsx @@ -19,6 +19,7 @@ export const NumberInput = function NumberInput({ dataCy, isResizing, adjustHeightBasedOnAlignment, + currentLayout, }) { const { loadingState, tooltip, disabledState, label, placeholder } = properties; const { @@ -53,12 +54,16 @@ export const NumberInput = function NumberInput({ const [disable, setDisable] = useState(disabledState || loadingState); const labelRef = useRef(); + useEffect(() => { + setExposedVariable('label', label); + }, [label]); + useEffect(() => { if (alignment == 'top' && label?.length > 0) { adjustHeightBasedOnAlignment(true); } else adjustHeightBasedOnAlignment(false); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alignment, label?.length]); + }, [alignment, label?.length, currentLayout]); useEffect(() => { setValue(Number(parseFloat(value).toFixed(properties.decimalPlaces))); diff --git a/frontend/src/Editor/Components/PasswordInput.jsx b/frontend/src/Editor/Components/PasswordInput.jsx index 7ec8aefb02..3c171bf475 100644 --- a/frontend/src/Editor/Components/PasswordInput.jsx +++ b/frontend/src/Editor/Components/PasswordInput.jsx @@ -18,6 +18,7 @@ export const PasswordInput = function PasswordInput({ dataCy, isResizing, adjustHeightBasedOnAlignment, + currentLayout, }) { const textInputRef = useRef(); const labelRef = useRef(); @@ -83,6 +84,10 @@ export const PasswordInput = function PasswordInput({ transform: alignment == 'top' && label?.length == 0 && 'translateY(-50%)', }; + useEffect(() => { + setExposedVariable('label', label); + }, [label]); + useEffect(() => { if (labelRef.current) { const width = labelRef.current.offsetWidth; @@ -137,12 +142,6 @@ export const PasswordInput = function PasswordInput({ setExposedVariable('setBlur', async function () { textInputRef.current.blur(); }); - setExposedVariable('disable', async function (value) { - setDisable(value); - }); - setExposedVariable('visibility', async function (value) { - setVisibility(value); - }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -167,7 +166,7 @@ export const PasswordInput = function PasswordInput({ if (alignment == 'top' && label?.length > 0) adjustHeightBasedOnAlignment(true); else adjustHeightBasedOnAlignment(false); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alignment, label?.length]); + }, [alignment, label?.length, currentLayout]); useEffect(() => { setExposedVariable('isMandatory', isMandatory); diff --git a/frontend/src/Editor/Components/TextInput.jsx b/frontend/src/Editor/Components/TextInput.jsx index a2e013d1e0..a389b1c927 100644 --- a/frontend/src/Editor/Components/TextInput.jsx +++ b/frontend/src/Editor/Components/TextInput.jsx @@ -18,6 +18,7 @@ export const TextInput = function TextInput({ dataCy, isResizing, adjustHeightBasedOnAlignment, + currentLayout, }) { const textInputRef = useRef(); const labelRef = useRef(); @@ -61,7 +62,7 @@ export const TextInput = function TextInput({ ? '#4C5155' : '#D7DBDF' : borderColor, - backgroundColor: isFocused ? 'red' : darkMode && ['#fff'].includes(backgroundColor) ? '#313538' : backgroundColor, + backgroundColor: darkMode && ['#fff'].includes(backgroundColor) ? '#313538' : backgroundColor, boxShadow: boxShadow !== '0px 0px 0px 0px #00000040' ? boxShadow : isFocused ? '0px 0px 0px 1px #3E63DD4D' : boxShadow, padding: styles.iconVisibility @@ -100,6 +101,10 @@ export const TextInput = function TextInput({ alignment, ]); + useEffect(() => { + setExposedVariable('label', label); + }, [label]); + useEffect(() => { disable !== disabledState && setDisable(disabledState); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -169,7 +174,7 @@ export const TextInput = function TextInput({ if (alignment == 'top' && label?.length > 0) adjustHeightBasedOnAlignment(true); else adjustHeightBasedOnAlignment(false); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alignment, label?.length]); + }, [alignment, label?.length, currentLayout]); useEffect(() => { setExposedVariable('isMandatory', isMandatory); diff --git a/frontend/src/Editor/ConfigHandle.jsx b/frontend/src/Editor/ConfigHandle.jsx index f3d4fd5955..c5045bbbec 100644 --- a/frontend/src/Editor/ConfigHandle.jsx +++ b/frontend/src/Editor/ConfigHandle.jsx @@ -13,13 +13,14 @@ export const ConfigHandle = function ConfigHandle({ customClassName = '', configWidgetHandlerForModalComponent = false, isVersionReleased, + isVerticalResizingAllowed, }) { return (
({ type: ItemTypes.BOX, @@ -344,6 +346,7 @@ export const DraggableBox = React.memo( widgetHeight={layoutData.height} isMultipleComponentsSelected={isMultipleComponentsSelected} configWidgetHandlerForModalComponent={configWidgetHandlerForModalComponent} + isVerticalResizingAllowed={isVerticalResizingAllowed} /> )} {/* Adding a sentry's error boundary to differentiate between our generic error boundary and one from editor's component */} @@ -378,6 +381,7 @@ export const DraggableBox = React.memo( childComponents={childComponents} isResizing={isResizing} adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment} + currentLayout={currentLayout} />
diff --git a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx index 47af5dedda..54a87a7231 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx @@ -4,7 +4,7 @@ import JSONTreeViewer from '@/_ui/JSONTreeViewer'; import _, { isEmpty } from 'lodash'; import { toast } from 'react-hot-toast'; import { getSvgIcon } from '@/_helpers/appUtils'; - +import Icon from '@/_ui/Icon/solidIcons/index'; import { useGlobalDataSources } from '@/_stores/dataSourcesStore'; import { useDataQueries } from '@/_stores/dataQueriesStore'; import { useCurrentState } from '@/_stores/currentStateStore'; @@ -126,9 +126,58 @@ export const LeftSidebarInspector = ({ }; } }); + const exposedVariablesIcon = Object.entries(currentState['components']) + .map(([key, value]) => { + const component = componentDefinitions[value.id]?.component ?? {}; + const componentExposedVariables = value; - const iconsList = useMemo(() => [...queryIcons, ...componentIcons], [queryIcons, componentIcons]); + if (!_.isEmpty(component) && component.component === 'TextInput') { + const icons = []; + if (componentExposedVariables.disable) { + icons.push({ + iconName: 'disable', + jsx: () => , + className: 'component-icon', + tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', + isInfoIcon: true, + }); + } + + if (componentExposedVariables.visibility) { + icons.push({ + iconName: 'visibility', + jsx: () => , + className: 'component-icon', + tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', + isInfoIcon: true, + }); + } + + return icons; + } + + if (!_.isEmpty(component) && component.component === 'Text' && componentExposedVariables?.visibility) { + return [ + { + iconName: 'visibility', + jsx: () => , + className: 'component-icon', + tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', + isInfoIcon: true, + }, + ]; + } + + return []; + }) + .flat() + .filter((value) => value !== undefined); // Remove undefined values + + const iconsList = useMemo( + () => [...queryIcons, ...componentIcons, ...exposedVariablesIcon], + [queryIcons, componentIcons, exposedVariablesIcon] + ); const handleRemoveComponent = (component) => { removeComponent(component.id); }; diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index c9271beec5..8a8515baa3 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -252,7 +252,7 @@ export const widgets = [ }, actionButtonBackgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, }, @@ -581,7 +581,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, defaultValue: false, @@ -1190,7 +1190,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, }, @@ -1326,6 +1326,7 @@ export const widgets = [ }, }, validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, @@ -1334,18 +1335,17 @@ export const widgets = [ displayName: 'Custom validation', placeholder: `{{components.text2.text=='yes'&&'valid'}}`, }, - mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, }, events: { onChange: { displayName: 'On change' }, - onEnterPressed: { displayName: 'On Enter Pressed' }, + onEnterPressed: { displayName: 'On enter pressed' }, onFocus: { displayName: 'On focus' }, onBlur: { displayName: 'On blur' }, }, styles: { color: { type: 'color', - displayName: 'Text color', + displayName: 'Text', validation: { schema: { type: 'string' } }, accordian: 'label', }, @@ -1374,7 +1374,7 @@ export const widgets = [ width: { type: 'slider', displayName: 'Width', - validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + validation: { schema: { type: 'number' } }, accordian: 'label', conditionallyRender: { key: 'alignment', @@ -1395,26 +1395,26 @@ export const widgets = [ backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background', validation: { schema: { type: 'string' } }, accordian: 'field', }, borderColor: { type: 'color', - displayName: 'Border color', + displayName: 'Border', validation: { schema: { type: 'string' } }, accordian: 'field', }, textColor: { type: 'color', - displayName: 'Text Color', + displayName: 'Text', validation: { schema: { type: 'string' } }, accordian: 'field', }, errTextColor: { type: 'color', - displayName: 'Error text color', + displayName: 'Error text', validation: { schema: { type: 'string' } }, accordian: 'field', }, @@ -1431,6 +1431,7 @@ export const widgets = [ validation: { schema: { type: 'string' } }, accordian: 'field', visibility: false, + showLabel: false, }, borderRadius: { type: 'input', @@ -1466,7 +1467,7 @@ export const widgets = [ { handle: 'setText', displayName: 'Set text', - params: [{ handle: 'text', displayName: 'text', defaultValue: 'New Text' }], + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], }, { handle: 'clear', @@ -1492,23 +1493,23 @@ export const widgets = [ }, { handle: 'setVisibility', - displayName: 'setVisibility', + displayName: 'Set visibility', params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, { handle: 'setDisable', - displayName: 'setDisable', + displayName: 'Set disable', params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, { handle: 'setLoading', - displayName: 'setLoading', + displayName: 'Set loading', params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, ], definition: { validation: { - mandatory: { value: false }, + mandatory: { value: '{{false}}' }, regex: { value: '' }, minLength: { value: null }, maxLength: { value: null }, @@ -1537,10 +1538,10 @@ export const widgets = [ backgroundColor: { value: '#fff' }, iconColor: { value: '#C1C8CD' }, direction: { value: 'left' }, - width: { value: '33' }, + width: { value: '{{33}}' }, alignment: { value: 'side' }, color: { value: '#11181C' }, - auto: { value: true }, + auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, icon: { value: 'IconHome2' }, @@ -1623,7 +1624,7 @@ export const widgets = [ styles: { color: { type: 'color', - displayName: 'Text color', + displayName: 'Text', validation: { schema: { type: 'string' } }, accordian: 'label', }, @@ -1652,7 +1653,7 @@ export const widgets = [ width: { type: 'slider', displayName: 'Width', - validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + validation: { schema: { type: 'number' } }, accordian: 'label', conditionallyRender: { key: 'alignment', @@ -1673,26 +1674,26 @@ export const widgets = [ backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background', validation: { schema: { type: 'string' } }, accordian: 'field', }, borderColor: { type: 'color', - displayName: 'Border color', + displayName: 'Border', validation: { schema: { type: 'string' } }, accordian: 'field', }, textColor: { type: 'color', - displayName: 'Text Color', + displayName: 'Text', validation: { schema: { type: 'string' } }, accordian: 'field', }, errTextColor: { type: 'color', - displayName: 'Error text color', + displayName: 'Error text', validation: { schema: { type: 'string' } }, accordian: 'field', }, @@ -1709,6 +1710,7 @@ export const widgets = [ validation: { schema: { type: 'string' } }, accordian: 'field', visibility: false, + showLabel: false, }, borderRadius: { type: 'input', @@ -1753,17 +1755,17 @@ export const widgets = [ }, { handle: 'setVisibility', - displayName: 'setVisibility', + displayName: 'Set visibility', params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, { handle: 'setDisable', - displayName: 'setDisable', + displayName: 'Set disable', params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, { handle: 'setLoading', - displayName: 'setLoading', + displayName: 'Set loading', params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, ], @@ -1775,6 +1777,7 @@ export const widgets = [ isLoading: false, }, validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, regex: { type: 'code', displayName: 'Regex', placeholder: '^d+$' }, minValue: { type: 'code', displayName: 'Min value', placeholder: 'Enter min value' }, maxValue: { type: 'code', displayName: 'Max value', placeholder: 'Enter max value' }, @@ -1783,7 +1786,6 @@ export const widgets = [ displayName: 'Custom validation', placeholder: `{{components.text2.text=='yes'&&'valid'}}`, }, - mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, }, definition: { others: { @@ -1791,7 +1793,7 @@ export const widgets = [ showOnMobile: { value: '{{false}}' }, }, validation: { - mandatory: { value: false }, + mandatory: { value: '{{false}}' }, regex: { value: '' }, minValue: { value: '' }, maxValue: { value: '' }, @@ -1818,10 +1820,10 @@ export const widgets = [ textColor: { value: '#232e3c' }, iconColor: { value: '#C1C8CD' }, direction: { value: 'left' }, - width: { value: '33' }, + width: { value: '{{33}}' }, alignment: { value: 'side' }, color: { value: '#11181C' }, - auto: { value: true }, + auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, icon: { value: 'IconHome2' }, @@ -1891,6 +1893,7 @@ export const widgets = [ }, }, validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, regex: { type: 'code', displayName: 'Regex', @@ -1903,7 +1906,6 @@ export const widgets = [ displayName: 'Custom validation', placeholder: `{{components.text2.text=='yes'&&'valid'}}`, }, - mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, }, events: { onChange: { displayName: 'On change' }, @@ -1914,7 +1916,7 @@ export const widgets = [ styles: { color: { type: 'color', - displayName: 'Text color', + displayName: 'Text', validation: { schema: { type: 'string' } }, accordian: 'label', }, @@ -1943,7 +1945,7 @@ export const widgets = [ width: { type: 'slider', displayName: 'Width', - validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + validation: { schema: { type: 'number' } }, accordian: 'label', conditionallyRender: { key: 'alignment', @@ -1964,26 +1966,26 @@ export const widgets = [ backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background', validation: { schema: { type: 'string' } }, accordian: 'field', }, borderColor: { type: 'color', - displayName: 'Border color', + displayName: 'Border', validation: { schema: { type: 'string' } }, accordian: 'field', }, textColor: { type: 'color', - displayName: 'Text Color', + displayName: 'Text', validation: { schema: { type: 'string' } }, accordian: 'field', }, errTextColor: { type: 'color', - displayName: 'Error text color', + displayName: 'Error text', validation: { schema: { type: 'string' } }, accordian: 'field', }, @@ -2000,6 +2002,7 @@ export const widgets = [ validation: { schema: { type: 'string' } }, accordian: 'field', visibility: false, + showLabel: false, }, borderRadius: { type: 'input', @@ -2009,7 +2012,7 @@ export const widgets = [ }, boxShadow: { type: 'boxShadow', - displayName: 'Box Shadow', + displayName: 'Box shadow', validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, accordian: 'field', }, @@ -2027,7 +2030,7 @@ export const widgets = [ }, exposedVariables: { value: '', - isMandatory: false, + mandatory: { value: '{{false}}' }, isVisible: true, isDisabled: false, isLoading: false, @@ -2052,17 +2055,17 @@ export const widgets = [ }, { handle: 'setVisibility', - displayName: 'setVisibility', + displayName: 'Set visibility', params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, { handle: 'setDisable', - displayName: 'setDisable', + displayName: 'Set disable', params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, { handle: 'setLoading', - displayName: 'setLoading', + displayName: 'Set loading', params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], }, ], @@ -2072,7 +2075,7 @@ export const widgets = [ showOnMobile: { value: '{{false}}' }, }, properties: { - placeholder: { value: 'password' }, + placeholder: { value: 'Password' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, loadingState: { value: '{{false}}' }, @@ -2096,10 +2099,10 @@ export const widgets = [ textColor: { value: '#11181C' }, iconColor: { value: '#C1C8CD' }, direction: { value: 'left' }, - width: { value: '33' }, + width: { value: '{{33}}' }, alignment: { value: 'side' }, color: { value: '#11181C' }, - auto: { value: true }, + auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, icon: { value: 'IconLock' }, @@ -2781,7 +2784,7 @@ export const widgets = [ }, backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, }, @@ -2928,7 +2931,7 @@ export const widgets = [ }, backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, }, @@ -3019,7 +3022,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, }, @@ -4624,7 +4627,7 @@ export const widgets = [ styles: { backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, }, @@ -5521,7 +5524,7 @@ ReactDOM.render(, document.body);`, styles: { backgroundColor: { type: 'color', - displayName: 'BG color', + displayName: 'Background color', validation: { schema: { type: 'string' }, }, diff --git a/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss b/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss index 3e14396ea9..7e676ad592 100644 --- a/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss +++ b/frontend/src/ToolJetUI/SwitchGroup/toggleGroup.scss @@ -1,16 +1,14 @@ .ToggleGroup { display: inline-flex; - background-color: var(--slate4); + background-color: var(--controls-switch-tag); border-radius: 6px; box-shadow: 0 2px 10px var(--black-a7); width: 142px; - // padding: 1px; } .ToggleGroupItem { all: unset; - background-color: var(--slate4); - color: var(--slate9); + color: var(--text-disabled); min-height: 28px; display: flex; font-size: 12px; @@ -41,9 +39,10 @@ padding: 2px 2px; .toggle-item { - background-color: white; - color: var(--indigo9); - border-radius: 6px; + border-radius: 5px; + background: var(--controls-switch-tab); + box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.10); + color: var(--text-default); padding: 4px; height: 28px; font-size: 10px; @@ -56,10 +55,8 @@ svg { path { - fill: var(--indigo9) !important; - + fill: var(--text-default) !important; } - } } } diff --git a/frontend/src/_styles/designtheme.scss b/frontend/src/_styles/designtheme.scss index 2f0721bb3a..00ad06e84a 100644 --- a/frontend/src/_styles/designtheme.scss +++ b/frontend/src/_styles/designtheme.scss @@ -73,6 +73,12 @@ --tj-text-input-widget-error: #DB4324; --tj-text-input-widget-disabled: #DFE3E6; --tj-text-input-widget-border-clicked: #3E63DD; + --controls-switch-tab: #fff; + --controls-switch-tag: #CCD1D54D; + --text-disabled: #889099; + --text-default: #1B1F24; + + } .dark-theme { @@ -89,6 +95,9 @@ --tj-text-input-widget-disabled: #2B2F3199; --tj-text-input-widget-border-clicked: #3E63DD; - + --controls-switch-tab: #2B3036; + --controls-switch-tag: #121518; + --text-disabled: #6D757D; + --text-default: #FAFCFF; } \ No newline at end of file diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 46856a5245..193b762b81 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -2519,7 +2519,7 @@ hr { .codehinter-default-input { font-family: "Roboto", sans-serif; - padding: 0.0475rem 0rem !important; + // padding: 0.0475rem 0rem !important; display: block; width: 100%; font-size: 0.875rem; @@ -4894,6 +4894,7 @@ input[type="text"] { .CodeMirror-lines { height: 32px !important; + padding: 7px 0px !important; } } @@ -8003,7 +8004,6 @@ tbody { .CodeMirror-lines { margin-top: 0px !important; min-height: 32px !important; - padding: 0 !important; } } } @@ -11958,17 +11958,6 @@ tbody { height: 52px; } -.code-hinter { - min-height: 32px !important; -} - -.CodeMirror-sizer { - // height: 30px !important; - display: flex; - align-items: center; - min-height: 32px !important; -} - .CodeMirror-placeholder { min-width: 265px !important; } \ No newline at end of file From c1c3f8564101f8288b73e5959bc8808eec1630c9 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:51:41 +0530 Subject: [PATCH 21/64] feat: Add setFilters and clearFilters CSA in table (#8637) * Add setFilters and clearFilters CSA in table * Remove console log * Add this CSA in component actions * Resolve code comments --- .../src/Editor/Components/Table/Filter.jsx | 86 ++++++++++++++----- .../src/Editor/Components/Table/Table.jsx | 28 +++--- .../src/Editor/WidgetManager/widgetConfig.js | 9 ++ 3 files changed, 88 insertions(+), 35 deletions(-) diff --git a/frontend/src/Editor/Components/Table/Filter.jsx b/frontend/src/Editor/Components/Table/Filter.jsx index 1878dbf6bd..b4f8ff99ec 100644 --- a/frontend/src/Editor/Components/Table/Filter.jsx +++ b/frontend/src/Editor/Components/Table/Filter.jsx @@ -1,16 +1,31 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import Select from '@/_ui/Select'; import defaultStyles from '@/_ui/Select/styles'; import { useTranslation } from 'react-i18next'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; -import _ from 'lodash'; +import _, { isArray } from 'lodash'; // eslint-disable-next-line import/no-unresolved import { diff as deepDiff } from 'deep-object-diff'; +const FILTER_OPTIONS = [ + { name: 'contains', value: 'contains' }, + { name: 'does not contains', value: 'doesNotContains' }, + { name: 'matches', value: 'matches' }, + { name: 'does not match', value: 'nl' }, + { name: 'equals', value: 'equals' }, + { name: 'does not equal', value: 'ne' }, + { name: 'is empty', value: 'isEmpty' }, + { name: 'is not empty', value: 'isNotEmpty' }, + { name: 'greater than', value: 'gt' }, + { name: 'less than', value: 'lt' }, + { name: 'greater than or equals', value: 'gte' }, + { name: 'less than or equals', value: 'lte' }, +]; + export function Filter(props) { const { t } = useTranslation(); - const { mergeToFilterDetails, filterDetails, setAllFilters, fireEvent, darkMode } = props; + const { mergeToFilterDetails, filterDetails, setAllFilters, fireEvent, darkMode, setExposedVariable } = props; const { filters } = filterDetails; const [activeFilters, set] = React.useState(filters); @@ -37,7 +52,6 @@ export function Filter(props) { if (value === 'isEmpty' || value === 'isNotEmpty') { newFilters[index].value.value = ''; } - mergeToFilterDetails({ filters: newFilters, }); @@ -82,7 +96,7 @@ export function Filter(props) { setTimeout(() => fireEvent('onFilterChanged'), 0); } - React.useEffect(() => { + useEffect(() => { if (filters.length > 0) { const tableFilters = JSON.parse(JSON.stringify(filters)); const shouldFire = findFilterDiff(activeFilters, tableFilters); @@ -93,6 +107,45 @@ export function Filter(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(filters)]); + useEffect(() => { + // Add CSA to set filters + setExposedVariable('setFilters', async function (_filters) { + if (!isArray(_filters)) return; + const filterArr = []; + _filters.forEach((_filter) => { + const { column = '', value = '', condition = '' } = _filter; + const columnId = props.columns.find((col) => col.name === column)?.value; + const isCorrectCondition = FILTER_OPTIONS.some((option) => option.value === condition); + if (columnId && isCorrectCondition) { + const filterObj = { + id: columnId, + value: { + column, + condition, + value, + }, + }; + filterArr.push(filterObj); + } + }); + if (filterArr.length) { + setAllFilters(filterArr.filter((filter) => filter.id !== '')); + mergeToFilterDetails({ + filters: filterArr, + }); + } + }); + + // Add CSA to clear filters + setExposedVariable('clearFilters', async function (_filters) { + setAllFilters([]); + mergeToFilterDetails({ + filters: [], + }); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(props.columns)]); + // eslint-disable-next-line react-hooks/exhaustive-deps const debounceFn = React.useCallback( _.debounce(() => { @@ -110,6 +163,10 @@ export function Filter(props) { }; }; + if (!filterDetails.filtersVisible) { + return null; + } + return (
@@ -150,20 +207,7 @@ export function Filter(props) {
{ + onChange(e.target.value); + }} + autoComplete="off" + /> + +
+ ); +}; diff --git a/frontend/src/Editor/CodeBuilder/Elements/Switch.jsx b/frontend/src/Editor/CodeBuilder/Elements/Switch.jsx index dc46f754c5..526a17f95d 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/Switch.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/Switch.jsx @@ -1,16 +1,22 @@ import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup'; import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem'; import React from 'react'; +import cx from 'classnames'; const Switch = ({ value, onChange, cyLabel, meta, paramName, isIcon }) => { const options = meta?.options; const defaultValue = value; return ( -
- +
+ {options.map((option) => ( - - {option.displayName} + + {isIcon ? option?.iconName ?? '' : option?.displayName} ))} diff --git a/frontend/src/Editor/CodeBuilder/TypeMapping.js b/frontend/src/Editor/CodeBuilder/TypeMapping.js index 8acda15ba5..5283e2f066 100644 --- a/frontend/src/Editor/CodeBuilder/TypeMapping.js +++ b/frontend/src/Editor/CodeBuilder/TypeMapping.js @@ -16,4 +16,5 @@ export const TypeMapping = { input: 'Input', icon: 'Icon', visibility: 'Visibility', + numberInput: 'NumberInput', }; diff --git a/frontend/src/Editor/Components/NumberInput.jsx b/frontend/src/Editor/Components/NumberInput.jsx index 28b6af9012..6afa68610b 100644 --- a/frontend/src/Editor/Components/NumberInput.jsx +++ b/frontend/src/Editor/Components/NumberInput.jsx @@ -437,15 +437,5 @@ export const NumberInput = function NumberInput({ ); }; - return ( - <> - {properties?.tooltip?.length > 0 ? ( - -
{renderInput()}
-
- ) : ( -
{renderInput()}
- )} - - ); + return
{renderInput()}
; }; diff --git a/frontend/src/Editor/Components/PasswordInput.jsx b/frontend/src/Editor/Components/PasswordInput.jsx index 3c171bf475..76d961d668 100644 --- a/frontend/src/Editor/Components/PasswordInput.jsx +++ b/frontend/src/Editor/Components/PasswordInput.jsx @@ -357,15 +357,5 @@ export const PasswordInput = function PasswordInput({ ); - return ( - <> - {tooltip?.length > 0 ? ( - -
{renderInput()}
-
- ) : ( -
{renderInput()}
- )} - - ); + return
{renderInput()}
; }; diff --git a/frontend/src/Editor/Components/Text.jsx b/frontend/src/Editor/Components/Text.jsx index 9c9db33bc0..ba69cb72ea 100644 --- a/frontend/src/Editor/Components/Text.jsx +++ b/frontend/src/Editor/Components/Text.jsx @@ -1,15 +1,14 @@ import React, { useState, useEffect } from 'react'; import DOMPurify from 'dompurify'; +import Markdown from 'react-markdown'; -export const Text = function Text({ - height, - properties, - styles, - darkMode, - setExposedVariable, - setExposedVariables, - dataCy, -}) { +const VERTICAL_ALIGNMENT_VS_CSS_VALUE = { + top: 'flex-start', + center: 'center', + bottom: 'flex-end', +}; + +export const Text = function Text({ height, properties, fireEvent, styles, darkMode, setExposedVariable, dataCy }) { let { textSize, textColor, @@ -24,50 +23,84 @@ export const Text = function Text({ letterSpacing, wordSpacing, fontVariant, - disabledState, boxShadow, + verticalAlignment, + borderColor, + borderRadius, + isScrollRequired, } = styles; - const { loadingState } = properties; + const { loadingState, textFormat, disabledState } = properties; const [text, setText] = useState(() => computeText()); - const [visibility, setVisibility] = useState(styles.visibility); + const [visibility, setVisibility] = useState(properties.visibility); + const [isLoading, setLoading] = useState(loadingState); + const [isDisabled, setIsDisabled] = useState(disabledState); const color = ['#000', '#000000'].includes(textColor) ? (darkMode ? '#fff' : '#000') : textColor; useEffect(() => { - if (visibility !== styles.visibility) setVisibility(styles.visibility); + if (visibility !== properties.visibility) setVisibility(properties.visibility); + if (isLoading !== loadingState) setLoading(loadingState); + if (isDisabled !== disabledState) setIsDisabled(disabledState); + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [styles.visibility]); + }, [properties.visibility, loadingState, disabledState]); useEffect(() => { const text = computeText(); setText(text); setExposedVariable('text', text); - const exposedVariables = { - text: text, - setText: async function (text) { - setText(text); - setExposedVariable('text', text); - }, - visibility: async function (value) { - setVisibility(value); - }, - }; - setExposedVariables(exposedVariables); + setExposedVariable('setText', async function (text) { + setText(text); + setExposedVariable('text', text); + }); + setExposedVariable('clear', async function (text) { + setText(''); + setExposedVariable('text', ''); + }); + setExposedVariable('isVisible', properties.visibility); + setExposedVariable('isLoading', loadingState); + setExposedVariable('isDisabled', disabledState); + + setExposedVariable('visibility', async function (value) { + setVisibility(value); + }); + + setExposedVariable('setVisibility', async function (value) { + setVisibility(value); + }); + + setExposedVariable('setLoading', async function (value) { + setLoading(value); + }); + + setExposedVariable('setDisable', async function (value) { + setIsDisabled(value); + }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [properties.text, setText, setVisibility]); + }, [ + properties.text, + setText, + setVisibility, + properties.visibility, + loadingState, + disabledState, + setIsDisabled, + setLoading, + ]); function computeText() { return properties.text === 0 || properties.text === false ? properties.text?.toString() : properties.text; } + const handleClick = () => { + fireEvent('onClick'); + }; const computedStyles = { - backgroundColor, + height: `${height}px`, + backgroundColor: darkMode && ['#edeff5'].includes(backgroundColor) ? '#2f3c4c' : backgroundColor, color, - height, display: visibility ? 'flex' : 'none', - alignItems: 'center', - textAlign, fontWeight: fontWeight ? fontWeight : fontWeight === '0' ? 0 : 'normal', lineHeight: lineHeight ?? 1.5, textDecoration: decoration ?? 'none', @@ -78,18 +111,45 @@ export const Text = function Text({ letterSpacing: `${letterSpacing}px` ?? '0px', wordSpacing: `${wordSpacing}px` ?? '0px', boxShadow, + border: '1px solid', + borderColor: darkMode && ['#f2f2f5'].includes(borderColor) ? '#2f3c4c' : borderColor ? borderColor : 'transparent', + borderRadius: borderRadius ? `${borderRadius}px` : '0px', + fontSize: `${textSize}px`, + }; + + const commonStyles = { + width: '100%', + height: '100%', + overflowY: isScrollRequired == 'enabled' ? 'auto' : 'hidden', + display: 'flex', + flexDirection: 'column', + justifyContent: VERTICAL_ALIGNMENT_VS_CSS_VALUE[verticalAlignment], + textAlign, }; return ( -
- {!loadingState && ( -
- )} - {loadingState === true && ( -
+
{ + fireEvent('onHover'); + }} + onClick={handleClick} + > + {!isLoading && + (textFormat === 'markdown' ? ( +
+ {text} +
+ ) : ( +
+
+
+ ))} + {isLoading && ( +
diff --git a/frontend/src/Editor/Components/TextInput.jsx b/frontend/src/Editor/Components/TextInput.jsx index a389b1c927..289e1eb3f5 100644 --- a/frontend/src/Editor/Components/TextInput.jsx +++ b/frontend/src/Editor/Components/TextInput.jsx @@ -340,15 +340,5 @@ export const TextInput = function TextInput({ ); - return ( - <> - {properties?.tooltip?.length > 0 ? ( - -
{renderInput()}
-
- ) : ( -
{renderInput()}
- )} - - ); + return <>{renderInput()}; }; diff --git a/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx b/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx index 7115bafd3e..bfdc2997f5 100644 --- a/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx +++ b/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx @@ -6,7 +6,12 @@ import { renderElement } from '../Utils'; import i18next from 'i18next'; import { resolveReferences } from '@/_helpers/utils'; import { AllComponents } from '@/Editor/Box'; + const SHOW_ADDITIONAL_ACTIONS = ['Text', 'TextInput', 'DropDown', 'NumberInput', 'PasswordInput']; +const PROPERTIES_VS_ACCORDION_TITLE = { + Text: 'Data', + TextInput: 'Data', +}; export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => { const { @@ -79,7 +84,7 @@ export const baseComponentProperties = ( 'Additional Actions': Object.keys(AllComponents).filter( (component) => !SHOW_ADDITIONAL_ACTIONS.includes(component) ), - General: ['Modal', 'TextInput', 'PasswordInput', 'NumberInput'], + General: ['Modal', 'TextInput', 'PasswordInput', 'NumberInput', 'Text'], Layout: [], }; if (component.component.component === 'Listview') { @@ -90,7 +95,9 @@ export const baseComponentProperties = ( let items = []; if (properties.length > 0) { items.push({ - title: `${i18next.t('widget.common.properties', 'Properties')}`, + title: + PROPERTIES_VS_ACCORDION_TITLE[component?.component?.component] ?? + `${i18next.t('widget.common.properties', 'Properties')}`, children: properties.map((property) => renderElement( component, diff --git a/frontend/src/Editor/Inspector/Elements/Code.jsx b/frontend/src/Editor/Inspector/Elements/Code.jsx index 35617b042a..356bd5c50a 100644 --- a/frontend/src/Editor/Inspector/Elements/Code.jsx +++ b/frontend/src/Editor/Inspector/Elements/Code.jsx @@ -33,6 +33,13 @@ export const Code = ({ if (isPaginationEnabled) return '{{true}}'; return '{{false}}'; } + // Following condition is needed to support older Text component not having textFormat switch + if (component?.component?.component === 'Text' && param === 'textFormat') { + const doTextFormatAlreadyExist = component?.component?.definition?.properties?.textFormat; + if (!doTextFormatAlreadyExist) { + return 'html'; + } + } if (['showAddNewRowButton', 'allowSelection', 'defaultSelectedRow'].includes(param)) { if (param === 'allowSelection') { const highlightSelectedRow = component?.component?.definition?.properties?.highlightSelectedRow?.value ?? false; @@ -114,6 +121,7 @@ export const Code = ({ isIcon={paramMeta?.isIcon} staticText={paramMeta?.staticText} placeholder={placeholder} + inspectorTab={paramType} bold={true} />
diff --git a/frontend/src/Editor/Inspector/Inspector.jsx b/frontend/src/Editor/Inspector/Inspector.jsx index a168649d99..936f2014aa 100644 --- a/frontend/src/Editor/Inspector/Inspector.jsx +++ b/frontend/src/Editor/Inspector/Inspector.jsx @@ -83,7 +83,7 @@ export const Inspector = ({ const [inputRef, setInputFocus] = useFocus(); const [showHeaderActionsMenu, setShowHeaderActionsMenu] = useState(false); - const shouldAddBoxShadow = ['TextInput', 'PasswordInput', 'NumberInput']; + const shouldAddBoxShadow = ['TextInput', 'PasswordInput', 'NumberInput', 'Text']; const { isVersionReleased } = useAppVersionStore( (state) => ({ @@ -317,6 +317,7 @@ export const Inspector = ({ component.component.component !== 'TextInput' && component.component.component !== 'PasswordInput' && component.component.component !== 'NumberInput' && + component.component.component !== 'Text' && 'p-3' } > @@ -476,7 +477,8 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie if ( component.component.component === 'TextInput' || component.component.component === 'PasswordInput' || - component.component.component === 'NumberInput' + component.component.component === 'NumberInput' || + component.component.component === 'Text' ) { // Iterate over the properties in componentMeta.styles for (const key in componentMeta.styles) { @@ -496,7 +498,8 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie return Object.keys( component.component.component === 'TextInput' || component.component.component === 'PasswordInput' || - component.component.component === 'NumberInput' + component.component.component === 'NumberInput' || + component.component.component === 'Text' ? groupedProperties : componentMeta.styles ).map((style) => { @@ -525,7 +528,8 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie if ( component.component.component === 'TextInput' || component.component.component === 'PasswordInput' || - component.component.component === 'NumberInput' + component.component.component === 'NumberInput' || + component.component.component === 'Text' ) { items.push({ title: `${style}`, diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index 8f27a9490c..0f1b3521b4 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -1095,12 +1095,47 @@ export const widgets = [ width: 17, }, properties: ['text'], - styles: ['fontWeight', 'textSize', 'textColor'], + styles: [ + 'textSize', + 'fontWeight', + 'fontStyle', + 'textColor', + 'isScrollRequired', + 'lineHeight', + 'textIndent', + 'textAlign', + 'verticalAlignment', + 'decoration', + 'transformation', + 'letterSpacing', + 'wordSpacing', + 'fontVariant', + 'backgroundColor', + 'borderColor', + 'borderRadius', + 'boxShadow', + 'padding', + ], defaultValue: { text: 'User Details', fontWeight: 'bold', - textSize: 20, + textSize: 18, textColor: '#000', + backgroundColor: '#fff00000', + textAlign: 'left', + decoration: 'none', + transformation: 'none', + fontStyle: 'normal', + lineHeight: 1.5, + textIndent: '0', + letterSpacing: '0', + wordSpacing: '0', + fontVariant: 'normal', + verticalAlignment: 'top', + padding: 'default', + boxShadow: '0px 0px 0px 0px #00000090', + borderRadius: '0', + isScrollRequired: 'enabled', }, }, { @@ -1111,8 +1146,47 @@ export const widgets = [ height: 30, }, properties: ['text'], + styles: [ + 'textSize', + 'fontWeight', + 'fontStyle', + 'textColor', + 'isScrollRequired', + 'lineHeight', + 'textIndent', + 'textAlign', + 'verticalAlignment', + 'decoration', + 'transformation', + 'letterSpacing', + 'wordSpacing', + 'fontVariant', + 'backgroundColor', + 'borderColor', + 'borderRadius', + 'boxShadow', + 'padding', + ], defaultValue: { text: 'Name', + fontWeight: 'normal', + textSize: 14, + textColor: '#000', + backgroundColor: '#fff00000', + textAlign: 'left', + decoration: 'none', + transformation: 'none', + fontStyle: 'normal', + lineHeight: 1.5, + textIndent: '0', + letterSpacing: '0', + wordSpacing: '0', + fontVariant: 'normal', + verticalAlignment: 'top', + padding: 'default', + boxShadow: '0px 0px 0px 0px #00000090', + borderRadius: '0', + isScrollRequired: 'enabled', }, }, { @@ -1123,8 +1197,47 @@ export const widgets = [ height: 30, }, properties: ['text'], + styles: [ + 'textSize', + 'fontWeight', + 'fontStyle', + 'textColor', + 'isScrollRequired', + 'lineHeight', + 'textIndent', + 'textAlign', + 'verticalAlignment', + 'decoration', + 'transformation', + 'letterSpacing', + 'wordSpacing', + 'fontVariant', + 'backgroundColor', + 'borderColor', + 'borderRadius', + 'boxShadow', + 'padding', + ], defaultValue: { text: 'Age', + fontWeight: 'normal', + textSize: 14, + textColor: '#000', + backgroundColor: '#fff00000', + textAlign: 'left', + decoration: 'none', + transformation: 'none', + fontStyle: 'normal', + lineHeight: 1.5, + textIndent: '0', + letterSpacing: '0', + wordSpacing: '0', + fontVariant: 'normal', + verticalAlignment: 'top', + padding: 'default', + boxShadow: '0px 0px 0px 0px #00000090', + borderRadius: '0', + isScrollRequired: 'enabled', }, }, { @@ -1390,8 +1503,8 @@ export const widgets = [ showLabel: false, isIcon: true, options: [ - { displayName: 'alignleftinspector', value: 'left' }, - { displayName: 'alignrightinspector', value: 'right' }, + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, ], accordian: 'label', }, @@ -1669,8 +1782,8 @@ export const widgets = [ showLabel: false, isIcon: true, options: [ - { displayName: 'alignleftinspector', value: 'left' }, - { displayName: 'alignrightinspector', value: 'right' }, + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, ], accordian: 'label', }, @@ -1961,8 +2074,8 @@ export const widgets = [ showLabel: false, isIcon: true, options: [ - { displayName: 'alignleftinspector', value: 'left' }, - { displayName: 'alignrightinspector', value: 'right' }, + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, ], accordian: 'label', }, @@ -2724,9 +2837,21 @@ export const widgets = [ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { + textFormat: { + type: 'switch', + displayName: 'Text Format', + options: [ + { displayName: 'Plain text', value: 'plainText' }, + { displayName: 'Markdown', value: 'markdown' }, + { displayName: 'HTML', value: 'html' }, + ], + isFxNotRequired: true, + defaultValue: { value: 'plainText' }, + fullWidth: true, + }, text: { type: 'code', - displayName: 'Text', + displayName: 'TextComponentTextInput', // Keeping this name unique so that we can filter it in Codehinter validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, }, @@ -2737,58 +2862,137 @@ export const widgets = [ validation: { schema: { type: 'boolean' }, }, + section: 'additionalActions', + }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { + schema: { type: 'boolean' }, + }, + section: 'additionalActions', + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + validation: { + schema: { type: 'boolean' }, + }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' } }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', }, }, defaultSize: { width: 6, - height: 30, + height: 40, + }, + events: { + onClick: { displayName: 'On click' }, + onHover: { displayName: 'On hover' }, }, - events: [], styles: { + textSize: { + type: 'numberInput', + displayName: 'Size', + validation: { + schema: [{ type: 'string' }, { type: 'number' }], + }, + accordian: 'Text', + }, fontWeight: { type: 'select', - displayName: 'Font weight', + displayName: 'Weight', options: [ { name: 'normal', value: 'normal' }, { name: 'bold', value: 'bold' }, { name: 'lighter', value: 'lighter' }, { name: 'bolder', value: 'bolder' }, ], - }, - decoration: { - type: 'select', - displayName: 'Text decoration', - options: [ - { name: 'none', value: 'none' }, - { name: 'overline', value: 'overline' }, - { name: 'line-through', value: 'line-through' }, - { name: 'underline', value: 'underline' }, - { name: 'overline underline', value: 'overline underline' }, - ], - }, - transformation: { - type: 'select', - displayName: 'Text transformation', - options: [ - { name: 'none', value: 'none' }, - { name: 'uppercase', value: 'uppercase' }, - { name: 'lowercase', value: 'lowercase' }, - { name: 'capitalize', value: 'capitalize' }, - ], + accordian: 'Text', }, fontStyle: { - type: 'select', - displayName: 'Font style', + type: 'switch', + displayName: 'Style', options: [ - { name: 'normal', value: 'normal' }, - { name: 'italic', value: 'italic' }, - { name: 'oblique', value: 'oblique' }, + { displayName: 'Normal', value: 'normal', iconName: 'minus' }, + { displayName: 'Oblique', value: 'oblique', iconName: 'oblique' }, + { displayName: 'Italic', value: 'italic', iconName: 'italic' }, ], + isIcon: true, + accordian: 'Text', }, - lineHeight: { type: 'number', displayName: 'Line height' }, - textIndent: { type: 'number', displayName: 'Text indent' }, - letterSpacing: { type: 'number', displayName: 'Letter spacing' }, - wordSpacing: { type: 'number', displayName: 'Word spacing' }, + textColor: { + type: 'color', + displayName: 'Color', + validation: { + schema: { type: 'string' }, + }, + accordian: 'Text', + }, + isScrollRequired: { + type: 'switch', + displayName: 'Scroll', + options: [ + { displayName: 'Enable', value: 'enabled' }, + { displayName: 'Disable', value: 'disabled' }, + ], + accordian: 'Text', + }, + lineHeight: { type: 'numberInput', displayName: 'Line height', accordian: 'Text' }, + textIndent: { type: 'numberInput', displayName: 'Text indent', accordian: 'Text' }, + textAlign: { + type: 'alignButtons', + displayName: 'Alignment', + validation: { + schema: { type: 'string' }, + }, + accordian: 'Text', + }, + verticalAlignment: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' } }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignverticallytop', value: 'top', iconName: 'alignverticallytop' }, + { displayName: 'alignverticallycenter', value: 'center', iconName: 'alignverticallycenter' }, + { displayName: 'alignverticallybottom', value: 'bottom', iconName: 'alignverticallybottom' }, + ], + accordian: 'Text', + }, + decoration: { + type: 'switch', + displayName: 'Decoration', + isIcon: true, + options: [ + { displayName: 'none', value: 'none', iconName: 'minus' }, + { displayName: 'underline', value: 'underline', iconName: 'underline' }, + { displayName: 'overline', value: 'overline', iconName: 'overline' }, + { displayName: 'line-through', value: 'line-through', iconName: 'linethrough' }, + ], + accordian: 'Text', + }, + transformation: { + type: 'switch', + displayName: 'Transformation', + isIcon: true, + options: [ + { displayName: 'none', value: 'none', iconName: 'minus' }, + { displayName: 'uppercase', value: 'uppercase', iconName: 'uppercase' }, + { displayName: 'lowercase', value: 'lowercase', iconName: 'lowercase' }, + { displayName: 'capitalize', value: 'capitalize', iconName: 'capitalize' }, + ], + accordian: 'Text', + }, + letterSpacing: { type: 'numberInput', displayName: 'Letter spacing', accordian: 'Text' }, + wordSpacing: { type: 'numberInput', displayName: 'Word spacing', accordian: 'Text' }, fontVariant: { type: 'select', displayName: 'Font variant', @@ -2798,48 +3002,48 @@ export const widgets = [ { name: 'initial', value: 'initial' }, { name: 'inherit', value: 'inherit' }, ], + accordian: 'Text', }, - textSize: { - type: 'number', - displayName: 'Text size', - validation: { - schema: { type: 'number' }, - }, - }, + backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'Background', validation: { schema: { type: 'string' }, }, + accordian: 'Container', + colorPickerPosition: 'top', }, - textColor: { + borderColor: { type: 'color', - displayName: 'Text color', + displayName: 'Border', validation: { schema: { type: 'string' }, }, + accordian: 'Container', + colorPickerPosition: 'top', }, - textAlign: { - type: 'alignButtons', - displayName: 'Align text', - validation: { - schema: { type: 'string' }, - }, + borderRadius: { + type: 'numberInput', + displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'Container', }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box shadow', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, + accordian: 'Container', }, - disabledState: { - type: 'toggle', - displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { schema: { type: 'string' } }, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'Container', }, }, exposedVariables: { @@ -2848,13 +3052,27 @@ export const widgets = [ actions: [ { handle: 'setText', - displayName: 'Set Text', + displayName: 'Set text', params: [{ handle: 'text', displayName: 'Text', defaultValue: 'New text' }], }, { - handle: 'visibility', - displayName: 'Set Visibility', - params: [{ handle: 'visibility', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }], + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }], + }, + { + handle: 'clear', + displayName: 'Clear', + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }], }, ], definition: { @@ -2863,8 +3081,11 @@ export const widgets = [ showOnMobile: { value: '{{false}}' }, }, properties: { - text: { value: 'Hello, there!' }, + textFormat: { value: 'plainText' }, + text: { value: 'Hello World๐Ÿ‘‹' }, loadingState: { value: `{{false}}` }, + disabledState: { value: '{{false}}' }, + visibility: { value: '{{true}}' }, }, events: [], styles: { @@ -2881,8 +3102,12 @@ export const widgets = [ letterSpacing: { value: 0 }, wordSpacing: { value: 0 }, fontVariant: { value: 'normal' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, + verticalAlignment: { value: 'top' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000090' }, + borderColor: { value: '' }, + borderRadius: { value: 6 }, + isScrollRequired: { value: 'enabled' }, }, }, }, diff --git a/frontend/src/ToolJetUI/Loader/Loader.jsx b/frontend/src/ToolJetUI/Loader/Loader.jsx index ad2b77b30e..c3833b341d 100644 --- a/frontend/src/ToolJetUI/Loader/Loader.jsx +++ b/frontend/src/ToolJetUI/Loader/Loader.jsx @@ -1,34 +1,45 @@ -// Loader.js import React from 'react'; import './Loader.scss'; // Import a CSS file for styling const Loader = ({ width, style }) => { + const viewBoxSize = 240; // Increase the viewBox size as needed + return ( -
+
- - + + - - + + - - - + + + - + { + return ( + + + + ); +}; + +export default AlignVerticallyBottom; diff --git a/frontend/src/_ui/Icon/solidIcons/AlignVerticallyCenter.jsx b/frontend/src/_ui/Icon/solidIcons/AlignVerticallyCenter.jsx new file mode 100644 index 0000000000..05ac6013a9 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/AlignVerticallyCenter.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const AlignVerticallyCenter = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => { + return ( + + + + ); +}; + +export default AlignVerticallyCenter; diff --git a/frontend/src/_ui/Icon/solidIcons/AlignVerticallyTop.jsx b/frontend/src/_ui/Icon/solidIcons/AlignVerticallyTop.jsx new file mode 100644 index 0000000000..49cde7d410 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/AlignVerticallyTop.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const AlignVerticallyTop = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => { + return ( + + + + ); +}; + +export default AlignVerticallyTop; diff --git a/frontend/src/_ui/Icon/solidIcons/Capitalize.jsx b/frontend/src/_ui/Icon/solidIcons/Capitalize.jsx new file mode 100644 index 0000000000..173a0582bf --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Capitalize.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Capitalize = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Capitalize; diff --git a/frontend/src/_ui/Icon/solidIcons/Italic.jsx b/frontend/src/_ui/Icon/solidIcons/Italic.jsx new file mode 100644 index 0000000000..7b0cda6ef4 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Italic.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Italic = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Italic; diff --git a/frontend/src/_ui/Icon/solidIcons/Linethrough.jsx b/frontend/src/_ui/Icon/solidIcons/Linethrough.jsx new file mode 100644 index 0000000000..d47f786944 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Linethrough.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Linethrough = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Linethrough; diff --git a/frontend/src/_ui/Icon/solidIcons/Lowercase.jsx b/frontend/src/_ui/Icon/solidIcons/Lowercase.jsx new file mode 100644 index 0000000000..2c2f27b1b5 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Lowercase.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Lowercase = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Lowercase; diff --git a/frontend/src/_ui/Icon/solidIcons/Oblique.jsx b/frontend/src/_ui/Icon/solidIcons/Oblique.jsx new file mode 100644 index 0000000000..74f775c329 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Oblique.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const Oblique = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Oblique; diff --git a/frontend/src/_ui/Icon/solidIcons/Overline.jsx b/frontend/src/_ui/Icon/solidIcons/Overline.jsx new file mode 100644 index 0000000000..6d5461e11b --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Overline.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Overline = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Overline; diff --git a/frontend/src/_ui/Icon/solidIcons/Underline.jsx b/frontend/src/_ui/Icon/solidIcons/Underline.jsx new file mode 100644 index 0000000000..37e074012f --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Underline.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Underline = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Underline; diff --git a/frontend/src/_ui/Icon/solidIcons/Uppercase.jsx b/frontend/src/_ui/Icon/solidIcons/Uppercase.jsx new file mode 100644 index 0000000000..1a267ec2b2 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Uppercase.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Uppercase = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + +); + +export default Uppercase; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 327e8e4e71..74cb3e7dca 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -145,6 +145,17 @@ import WorkspaceConstants from './WorkspaceConstants.jsx'; import ArrowBackDown from './ArrowBackDown.jsx'; import AlignRightinspector from './AlignRightinspector.jsx'; import AlignLeftinspector from './AlignLeftinspector.jsx'; +import AlignVerticallyTop from './AlignVerticallyTop.jsx'; +import AlignVerticallyBottom from './AlignVerticallyBottom.jsx'; +import AlignVerticallyCenter from './AlignVerticallyCenter.jsx'; +import Italic from './Italic.jsx'; +import Underline from './Underline.jsx'; +import Overline from './Overline.jsx'; +import Linethrough from './Linethrough.jsx'; +import Uppercase from './Uppercase.jsx'; +import Lowercase from './Lowercase.jsx'; +import Capitalize from './Capitalize.jsx'; +import Oblique from './Oblique.jsx'; const Icon = (props) => { switch (props.name) { @@ -154,6 +165,12 @@ const Icon = (props) => { return ; case 'alignrightinspector': return ; + case 'alignverticallytop': + return ; + case 'alignverticallybottom': + return ; + case 'alignverticallycenter': + return ; case 'alignright': return ; case 'apps': @@ -268,6 +285,8 @@ const Icon = (props) => { return ; case 'interactive': return ; + case 'italic': + return ; case 'layers': return ; case 'leftarrow': @@ -440,6 +459,20 @@ const Icon = (props) => { return ; case 'cross': return ; + case 'underline': + return ; + case 'overline': + return ; + case 'linethrough': + return ; + case 'uppercase': + return ; + case 'lowercase': + return ; + case 'capitalize': + return ; + case 'oblique': + return ; default: return ; } diff --git a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx index 5b1b32a27d..a4d79da943 100644 --- a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx +++ b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx @@ -34,6 +34,7 @@ export const JSONNode = ({ data, ...restProps }) => { actionsList, fontSize, inspectorTree, + renderCurrentNodeInfoIcon, } = restProps; const [expandable, set] = React.useState(() => @@ -94,6 +95,7 @@ export const JSONNode = ({ data, ...restProps }) => { let $VALUE = null; let $NODEType = null; let $NODEIcon = null; + let $NODEInfoIcon = null; const checkSelectedNode = (_selectedNode, _currentNode, parent, toExpand) => { if (selectedNode?.parent && parent) { @@ -135,6 +137,7 @@ export const JSONNode = ({ data, ...restProps }) => { if (toUseNodeIcons && currentNode) { $NODEIcon = renderNodeIcons(currentNode); + $NODEInfoIcon = renderCurrentNodeInfoIcon(currentNode); } switch (typeofCurrentNode) { @@ -264,7 +267,14 @@ export const JSONNode = ({ data, ...restProps }) => { onMouseLeave={() => updateHoveredNode(null)} > {(inspectorTree || toShowNodeIndicator) && ( -
+
+ {$NODEInfoIcon && $NODEInfoIcon} { - const icon = this.props.iconsList.filter((icon) => icon?.iconName === node)[0]; + const icon = this.props.iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; if (icon && icon?.iconPath) { return ( +
{icon.jsx()}
+ + ); + } return icon.jsx(); } }; + renderCurrentNodeInfoIcon = (node) => { + const icon = this.props.iconsList.filter((icon) => icon?.iconName === node)[0]; + if (icon?.isInfoIcon) { + if (icon && icon?.iconPath) { + return ( + + ); + } + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx()}
+
+ ); + } + return icon.jsx(); + } + } else return null; + }; + updateSelectedNode = (node, path) => { if (node) { this.setState({ @@ -236,6 +269,7 @@ export class JSONTreeViewer extends React.Component { getAbsoluteNodePath={this.getAbsoluteNodePath} fontSize={this.props.fontSize ?? '12px'} inspectorTree={this.props.treeType === 'inspector'} + renderCurrentNodeInfoIcon={this.renderCurrentNodeInfoIcon} />
diff --git a/server/data-migrations/1698663642825-MoveStatesFromStylesToProperties.ts b/server/data-migrations/1698663642825-MoveStatesFromStylesToProperties.ts new file mode 100644 index 0000000000..3b7b6c97e4 --- /dev/null +++ b/server/data-migrations/1698663642825-MoveStatesFromStylesToProperties.ts @@ -0,0 +1,51 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { AppVersion } from '../src/entities/app_version.entity'; + +//This will need to be updated after App definition changes are merged +export class MoveStatesFromStylesToProperties1698663642825 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const entityManager = queryRunner.manager; + const appVersions = await entityManager.find(AppVersion, { + select: ['id', 'definition'], + }); + + for (const version of appVersions) { + const definition = version['definition']; + if (definition) { + for (const pageId of Object.keys(definition.pages)) { + let components = { ...definition?.pages[pageId]?.components }; + + for (const componentId of Object.keys(components)) { + const _component = components[componentId]; + if (_component.component.component === 'Text' || _component.component.component === 'TextInput') { + if (_component?.component?.definition?.styles?.visibility) { + _component.component.definition.properties.visibility = + _component?.component?.definition.styles.visibility; + } + if (_component?.component?.definition?.styles?.disabledState) { + _component.component.definition.properties.disabledState = + _component.component?.definition.styles.disabledState; + } + if (_component?.component?.definition?.generalStyles?.boxShadow) { + _component.component.definition.styles.boxShadow = + _component.component?.definition?.generalStyles?.boxShadow; + } + delete _component.component.definition.styles.visibility; + delete _component.component.definition.styles.disabledState; + delete _component?.component?.definition?.generalStyles?.boxShadow; + components = { + ...components, + [componentId]: { ..._component }, + }; + } + } + definition.pages[pageId].components = { ...components }; + version.definition = definition; + await entityManager.update(AppVersion, { id: version.id }, { definition }); + } + } + } + } + + public async down(queryRunner: QueryRunner): Promise {} +} From c13610df1c5c55f8cecdf3792a044fc31a385164 Mon Sep 17 00:00:00 2001 From: Arpit Date: Wed, 7 Feb 2024 14:45:48 +0530 Subject: [PATCH 29/64] fix: query details are not available in the current state before the query is run (#8717) * Restored query details to current state. * use getCurrentState instead of getting the entire state of the current state store, as we dont want to expose the actions --- frontend/src/_stores/dataQueriesStore.js | 51 ++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/frontend/src/_stores/dataQueriesStore.js b/frontend/src/_stores/dataQueriesStore.js index 3651d367f4..06e90c5161 100644 --- a/frontend/src/_stores/dataQueriesStore.js +++ b/frontend/src/_stores/dataQueriesStore.js @@ -11,7 +11,7 @@ import { toast } from 'react-hot-toast'; import _, { isEmpty, throttle } from 'lodash'; import { useEditorStore } from './editorStore'; import { shallow } from 'zustand/shallow'; -import { useCurrentStateStore } from './currentStateStore'; +import { getCurrentState, useCurrentStateStore } from './currentStateStore'; const initialState = { dataQueries: [], @@ -47,7 +47,8 @@ export const useDataQueriesStore = create( const currentQueries = useCurrentStateStore.getState().queries; data.data_queries.forEach(({ id, name, options }) => { - updatedQueries[name] = _.merge(currentQueries[name], { id: id }); + updatedQueries[name] = _.merge(currentQueries[name], { id: id, isLoading: false, data: [], rawData: [] }); + if (options && options?.requestConfirmation && options?.runOnPageLoad) { queryConfirmationList.push({ queryId: id, queryName: name }); } @@ -58,7 +59,7 @@ export const useDataQueriesStore = create( } useCurrentStateStore.getState().actions.setCurrentState({ - ...useCurrentStateStore.getState(), + ...getCurrentState(), queries: updatedQueries, }); } @@ -93,6 +94,18 @@ export const useDataQueriesStore = create( isDeletingQueryInProcess: false, dataQueries: state.dataQueries.filter((query) => query.id !== queryId), })); + + const currentQueries = useCurrentStateStore.getState().queries; + + useCurrentStateStore.getState().actions.setCurrentState({ + ...getCurrentState(), + queries: Object.keys(currentQueries).reduce((acc, key) => { + if (currentQueries[key].id !== queryId) { + acc[key] = currentQueries[key]; + } + return acc; + }, {}), + }); }) .catch(() => { set({ @@ -178,6 +191,21 @@ export const useDataQueriesStore = create( get().actions.saveData({ ...get().queuedActions.saveData, id: data.id }); set({ queuedActions: { ...get().queuedActions, saveData: undefined } }); } + + const currentQueries = useCurrentStateStore.getState().queries; + + useCurrentStateStore.getState().actions.setCurrentState({ + ...getCurrentState(), + queries: { + ...currentQueries, + [data.name]: { + id: data.id, + isLoading: false, + data: [], + rawData: [], + }, + }, + }); }) .catch((error) => { set((state) => ({ @@ -215,6 +243,23 @@ export const useDataQueriesStore = create( }), })); useQueryPanelStore.getState().actions.setSelectedQuery(id); + + const currentQueries = useCurrentStateStore.getState().queries; + + const queryName = Object.keys(currentQueries).find((key) => currentQueries[key].id === id); + + const { [queryName]: _, ...rest } = currentQueries; + + useCurrentStateStore.getState().actions.setCurrentState({ + ...getCurrentState(), + queries: { + ...rest, + [newName]: { + ...currentQueries[queryName], + name: newName, + }, + }, + }); }) .finally(() => useAppDataStore.getState().actions.setIsSaving(false)); }, From 53ad5fd60f3215d3f85c9540ee48efb8be8bbaab Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> Date: Thu, 8 Feb 2024 08:50:14 +0530 Subject: [PATCH 30/64] feat: Added a scrollbar on pages menu in viewer (#8715) * Added a scrollbar on pages menu in viewer * Add overflow * Fix last page not visible --------- Co-authored-by: Nakul Nagargade --- frontend/src/Editor/Viewer.jsx | 9 ++++++++- frontend/src/Editor/Viewer/ViewerSidebarNavigation.jsx | 10 ++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index 8d1a1d03c0..bcc9070a5f 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -651,6 +651,7 @@ class ViewerComponent extends React.Component {
{appDefinition?.showViewerNavigation && (
{ +const APP_HEADER_HEIGHT = 47; + +export const ViewerSidebarNavigation = ({ isMobileDevice, pages, currentPageId, switchPage, darkMode, showHeader }) => { if (isMobileDevice) { return null; } return (
From 5a3ec54459e1915d7341666fc47defec437c1dd6 Mon Sep 17 00:00:00 2001 From: Kiran Ashok Date: Thu, 8 Feb 2024 08:54:13 +0530 Subject: [PATCH 31/64] fix: Modal crashing , fixes in form with custom schema (#8719) * fix :: for modal crashing , custom form showing labels for input * removing validation as there is no fx * alignment and fixing resize issue in form with custom schema * fix : center modal --- frontend/src/Editor/Components/Form/FormUtils.js | 8 ++++---- frontend/src/Editor/Components/Modal.jsx | 11 ++++++++--- frontend/src/Editor/DraggableBox.jsx | 9 ++++----- frontend/src/Editor/WidgetManager/widgetConfig.js | 3 --- frontend/src/_ui/JSONTreeViewer/JSONNode.jsx | 1 + 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frontend/src/Editor/Components/Form/FormUtils.js b/frontend/src/Editor/Components/Form/FormUtils.js index f382438ebf..5cb133e03f 100644 --- a/frontend/src/Editor/Components/Form/FormUtils.js +++ b/frontend/src/Editor/Components/Form/FormUtils.js @@ -55,7 +55,7 @@ export function generateUIComponents(JSONSchema, advanced) { uiComponentsDraft[index * 2 + 1]['definition']['properties']['value']['value'] = value?.value; if (value?.placeholder) uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder; - if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; + uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; //removing default label in all components with default label to match previous versions break; case 'DropDown': if (value?.styles?.disabled) @@ -156,7 +156,8 @@ export function generateUIComponents(JSONSchema, advanced) { uiComponentsDraft[index * 2 + 1]['definition']['properties']['minValue']['value'] = value?.minValue; if (value?.placeholder) uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder; - if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; + uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; + break; case 'PasswordInput': @@ -187,8 +188,7 @@ export function generateUIComponents(JSONSchema, advanced) { uiComponentsDraft[index * 2 + 1]['definition']['validation']['regex']['value'] = value?.validation?.regex; if (value?.placeholder) uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder; - if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; - + uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; break; case 'Datepicker': if (value?.styles?.borderRadius) diff --git a/frontend/src/Editor/Components/Modal.jsx b/frontend/src/Editor/Components/Modal.jsx index ba2dbb4ab0..0f2a2cec73 100644 --- a/frontend/src/Editor/Components/Modal.jsx +++ b/frontend/src/Editor/Components/Modal.jsx @@ -65,8 +65,8 @@ export const Modal = function Modal({ const canShowModal = exposedVariables.show ?? false; setShowModal(exposedVariables.show ?? false); fireEvent(canShowModal ? 'onOpen' : 'onClose'); - const inpuRef = document.getElementsByClassName('tj-text-input-widget')[0]; - inpuRef.blur(); + const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0]; + inputRef?.blur(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [exposedVariables.show]); @@ -176,7 +176,12 @@ export const Modal = function Modal({ }, [closeOnClickingOutside, parentRef]); return ( -
+
{useDefaultButton && (
diff --git a/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx b/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx index bc731aeab3..ea8933cd64 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/Slider.jsx @@ -4,7 +4,7 @@ import CustomInput from '@/_ui/CustomInput'; import * as Slider from '@radix-ui/react-slider'; import './Slider.scss'; -function Slider1({ value, onChange, disabled }) { +function Slider1({ value, onChange, component }) { const [sliderValue, setSliderValue] = useState(value ? value : 33); // Initial value of the slider useEffect(() => { @@ -27,13 +27,11 @@ function Slider1({ value, onChange, disabled }) { return (
-
diff --git a/frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx b/frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx index ef5a564d17..73d7b2d88d 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/Visibility.jsx @@ -6,10 +6,10 @@ export const Visibility = ({ value, onVisibilityChange, component }) => {
{ e.stopPropagation(); - onVisibilityChange(!component?.component?.definition?.styles?.iconVisibility?.value); + onVisibilityChange(!component.component.definition.styles?.iconVisibility?.value); }} > { - const selectProps = props.selectProps; - // eslint-disable-next-line import/namespace - const IconElement = Icons[selectProps?.icon] == undefined ? Icons['IconHome2'] : Icons[selectProps?.icon]; - return ( - - {selectProps?.doShowIcon && ( - - )} - - {React.Children.map(children, (child) => { - return child ? ( - child - ) : props.hasValue ? ( - - {selectProps?.getOptionLabel(props?.getValue()[0])} - - ) : ( - - {selectProps.placeholder} - - ); - })} - - - ); -}; - -const Option = (props) => { - // Hack around https://github.com/JedWatson/react-select/pull/3705 - const firstOption = props.options[0]; - const isFirstOption = props.label === firstOption.label; - return ( - -
- - {props.label} - - {props.isSelected && ( - - - - )} -
-
- ); -}; +import _ from 'lodash'; +import React, { useState, useEffect } from 'react'; +import Select from 'react-select'; export const DropDown = function DropDown({ height, @@ -82,63 +15,33 @@ export const DropDown = function DropDown({ component, exposedVariables, dataCy, - adjustHeightBasedOnAlignment, - currentLayout, }) { - let { - label, - value, - advanced, - schema, - placeholder, - display_values, - values, - dropdownLoadingState, - disabledState, - optionVisibility, - optionDisable, - } = properties; - const { - selectedTextColor, - fieldBorderRadius, - justifyContent, - boxShadow, - labelColor, - alignment, - direction, - fieldBorderColor, - fieldBackgroundColor, - labelWidth, - icon, - iconVisibility, - errTextColor, - labelAutoWidth, - iconColor, - padding, - } = styles; + let { label, value, advanced, schema, placeholder, display_values, values } = properties; + const { selectedTextColor, borderRadius, visibility, disabledState, justifyContent, boxShadow } = styles; const [currentValue, setCurrentValue] = useState(() => (advanced ? findDefaultItem(schema) : value)); const { value: exposedValue } = exposedVariables; - const currentState = useCurrentState(); - const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState); - const validationData = validate(currentValue); + const [showValidationError, setShowValidationError] = useState(false); + + const validationData = validate(value); const { isValid, validationError } = validationData; - const ref = React.useRef(null); - const selectref = React.useRef(null); - const [visibility, setVisibility] = useState(properties.visibility); - const [isDropdownLoading, setIsDropdownLoading] = useState(dropdownLoadingState); - const [isDropdownDisabled, setIsDropdownDisabled] = useState(disabledState); - const [isFocused, setIsFocused] = useState(false); - const [inputValue, setInputValue] = useState(''); - // We are substracting 4px because of 2px padding each in top bottom - const _height = padding === 'default' ? `${height - 4}px` : `${height}px`; function findDefaultItem(schema) { const foundItem = schema?.find((item) => item?.default === true); return !hasVisibleFalse(foundItem?.value) ? foundItem?.value : undefined; } - const selectOptions = useMemo(() => { - let _selectOptions = advanced + if (advanced) { + values = schema?.map((item) => item?.value); + display_values = schema?.map((item) => item?.label); + value = findDefaultItem(schema); + } else if (!_.isArray(values)) { + values = []; + } + + let selectOptions = []; + + try { + selectOptions = advanced ? [ ...schema .filter((data) => data.visible) @@ -148,26 +51,94 @@ export const DropDown = function DropDown({ })), ] : [ - ...values - .map((value, index) => { - if (optionVisibility[index] !== false) { - return { label: display_values[index], value: value, isDisabled: optionDisable[index] }; - } - }) - .filter((option) => option), + ...values.map((value, index) => { + return { label: display_values[index], value: value }; + }), ]; + } catch (err) { + console.log(err); + } - return _selectOptions; - }, [advanced, schema, display_values, values, optionDisable, optionVisibility]); + const setExposedItem = (value, index, onSelectFired = false) => { + setCurrentValue(value); + onSelectFired ? setExposedVariable('value', value).then(fireEvent('onSelect')) : setExposedVariable('value', value); + setExposedVariable('selectedOptionLabel', index === undefined ? undefined : display_values?.[index]); + }; function selectOption(value) { - const val = selectOptions.filter((option) => !option.isDisabled)?.find((option) => option.value === value); - if (val) { - setCurrentValue(value); - fireEvent('onSelect'); + let index = null; + index = values?.indexOf(value); + + if (values?.includes(value)) { + setExposedItem(value, index, true); + } else { + setExposedItem(undefined, undefined, true); } } + useEffect(() => { + setExposedVariable('selectOption', async function (value) { + selectOption(value); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(values), setCurrentValue, JSON.stringify(display_values)]); + + useEffect(() => { + setExposedVariable('isValid', isValid); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isValid]); + + useEffect(() => { + let newValue = undefined; + let index = null; + if (values?.includes(value)) { + newValue = value; + index = values?.indexOf(value); + } + setExposedItem(newValue, index); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value, JSON.stringify(values)]); + + useEffect(() => { + let index = null; + if (exposedValue !== currentValue) { + setExposedVariable('value', currentValue); + } + index = values?.indexOf(currentValue); + setExposedVariable('selectedOptionLabel', display_values?.[index]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentValue, JSON.stringify(display_values), JSON.stringify(values)]); + + useEffect(() => { + let newValue = undefined; + let index = null; + + if (values?.includes(currentValue)) newValue = currentValue; + else if (values?.includes(value)) newValue = value; + index = values?.indexOf(newValue); + setExposedItem(newValue, index); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(values)]); + + useEffect(() => { + setExposedVariable('label', label); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [label]); + + useEffect(() => { + if (advanced) { + setExposedVariable( + 'optionLabels', + schema?.filter((item) => item?.visible)?.map((item) => item.label) + ); + if (hasVisibleFalse(currentValue)) { + setCurrentValue(findDefaultItem(schema)); + } + } else setExposedVariable('optionLabels', display_values); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(schema), advanced, JSON.stringify(display_values), currentValue]); + function hasVisibleFalse(value) { for (let i = 0; i < schema?.length; i++) { if (schema[i].value === value && schema[i].visible === false) { @@ -179,164 +150,31 @@ export const DropDown = function DropDown({ const onSearchTextChange = (searchText, actionProps) => { if (actionProps.action === 'input-change') { - setInputValue(searchText); + setExposedVariable('searchText', searchText); fireEvent('onSearchTextChanged'); } }; - const handleOutsideClick = (e) => { - let menu = ref.current.querySelector('.select__menu'); - if (!ref.current.contains(e.target) || !menu || !menu.contains(e.target)) { - setIsFocused(false); - setInputValue(''); - } - }; - - useEffect(() => { - if (advanced) { - setCurrentValue(findDefaultItem(schema)); - } else setCurrentValue(value); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [advanced, value, JSON.stringify(schema)]); - - useEffect(() => { - if (alignment == 'top' && label) adjustHeightBasedOnAlignment(true); - else adjustHeightBasedOnAlignment(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alignment, label, currentLayout]); - - useEffect(() => { - document.addEventListener('mousedown', handleOutsideClick); - return () => { - document.removeEventListener('mousedown', handleOutsideClick); - }; - }, []); - - // Exposed variables UseEffect's - useEffect(() => { - if (visibility !== properties.visibility) setVisibility(properties.visibility); - if (isDropdownLoading !== dropdownLoadingState) setIsDropdownLoading(dropdownLoadingState); - if (isDropdownDisabled !== disabledState) setIsDropdownDisabled(disabledState); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [properties.visibility, dropdownLoadingState, disabledState]); - - useEffect(() => { - setExposedVariable('selectOption', async function (value) { - selectOption(value); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(selectOptions)]); - - useEffect(() => { - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); - - useEffect(() => { - if (exposedValue !== currentValue) { - setExposedVariable('value', currentValue); - } - const _selectedOptionLabel = selectOptions.find((option) => option.value === currentValue)?.label; - setExposedVariable('selectedOptionLabel', _selectedOptionLabel); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentValue, JSON.stringify(display_values), JSON.stringify(values), JSON.stringify(selectOptions)]); - - useEffect(() => { - setExposedVariable('label', label); - setExposedVariable('searchText', inputValue); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [label, inputValue]); - - useEffect(() => { - if (advanced) { - setExposedVariable( - 'optionLabels', - schema?.filter((item) => item?.visible)?.map((item) => item.label) - ); - } else setExposedVariable('optionLabels', display_values); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(schema), advanced, JSON.stringify(display_values), currentValue]); - - useEffect(() => { - setExposedVariable('isVisible', properties.visibility); - setExposedVariable('isLoading', dropdownLoadingState); - setExposedVariable('isDisabled', disabledState); - setExposedVariable('isMandatory', isMandatory); - - setExposedVariable('clear', async function () { - setCurrentValue(null); - }); - setExposedVariable('setVisibility', async function (value) { - setVisibility(value); - }); - setExposedVariable('setLoading', async function (value) { - setIsDropdownLoading(value); - }); - setExposedVariable('setDisable', async function (value) { - setIsDropdownDisabled(value); - }); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [properties.visibility, dropdownLoadingState, disabledState, isMandatory]); - - useEffect(() => { - const _options = selectOptions?.map((selectOption) => ({ label: selectOption?.label, value: selectOption?.value })); - setExposedVariable('options', _options); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(selectOptions)]); - const customStyles = { - container: (base) => ({ - ...base, - width: '100%', + control: (provided, state) => ({ + ...provided, + background: darkMode ? 'rgb(31,40,55)' : 'white', + minHeight: height, + height: height, + boxShadow: state.isFocused ? boxShadow : boxShadow, + borderRadius: Number.parseFloat(borderRadius), }), - control: (provided, state) => { - return { - ...provided, - minHeight: _height, - height: _height, - boxShadow: state.isFocused ? boxShadow : boxShadow, - borderRadius: Number.parseFloat(fieldBorderRadius), - borderColor: !isValid - ? 'var(--tj-text-input-widget-error)' - : state.isFocused - ? '#3E63DD' - : ['#D7DBDF'].includes(fieldBorderColor) - ? darkMode - ? '#4C5155' - : '#D7DBDF' - : fieldBorderColor, - backgroundColor: - darkMode && ['#fff'].includes(fieldBackgroundColor) - ? '#313538' - : state.isDisabled - ? '#F1F3F5' - : fieldBackgroundColor, - '&:hover': { - borderColor: '#6A727C', - }, - }; - }, + valueContainer: (provided, _state) => ({ ...provided, - height: _height, + height: height, padding: '0 6px', justifyContent, - display: 'flex', - gap: '0.13rem', }), singleValue: (provided, _state) => ({ ...provided, - color: darkMode && selectedTextColor === '#11181C' ? '#ECEDEE' : selectedTextColor, - maxWidth: - ref?.current?.offsetWidth - - (iconVisibility ? INDICATOR_CONTAINER_WIDTH + ICON_WIDTH : INDICATOR_CONTAINER_WIDTH), - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', + color: disabledState ? 'grey' : selectedTextColor ? selectedTextColor : darkMode ? 'white' : 'black', }), input: (provided, _state) => ({ @@ -349,151 +187,83 @@ export const DropDown = function DropDown({ }), indicatorsContainer: (provided, _state) => ({ ...provided, - height: _height, + height: height, }), - clearIndicator: (provided, _state) => ({ + option: (provided, state) => { + const styles = darkMode + ? { + color: state.isDisabled ? '#88909698' : 'white', + backgroundColor: state.value === currentValue ? '#3650AF' : 'rgb(31,40,55)', + ':hover': { + backgroundColor: state.isDisabled ? 'transparent' : state.value === currentValue ? '#1F2E64' : '#323C4B', + }, + maxWidth: 'auto', + minWidth: 'max-content', + } + : { + backgroundColor: state.value === currentValue ? '#7A95FB' : 'white', + color: state.isDisabled ? '#88909694' : state.value === currentValue ? 'white' : 'black', + ':hover': { + backgroundColor: state.isDisabled ? 'transparent' : state.value === currentValue ? '#3650AF' : '#d8dce9', + }, + maxWidth: 'auto', + minWidth: 'max-content', + }; + return { + ...provided, + justifyContent, + height: 'auto', + display: 'flex', + flexDirection: 'rows', + alignItems: 'center', + ...styles, + }; + }, + menu: (provided, _state) => ({ ...provided, - padding: '0px', - }), - dropdownIndicator: (provided, _state) => ({ - ...provided, - padding: '0px', - }), - option: (provided) => ({ - ...provided, - backgroundColor: darkMode && ['#fff'].includes(fieldBackgroundColor) ? '#313538' : fieldBackgroundColor, - color: darkMode && ['#11181C'].includes(selectedTextColor) ? '#ECEDEE' : selectedTextColor, - '&:hover': { - backgroundColor: '#ACB2B9', - color: 'white', - }, - }), - menuList: (provided) => ({ - ...provided, - padding: '2px', - // this is needed otherwise :active state doesn't look nice, gap is required - display: 'flex', - flexDirection: 'column', - gap: '4px !important', - overflowY: 'auto', - }), - menu: (provided) => ({ - ...provided, - marginTop: '5px', - backgroundColor: darkMode && ['#fff'].includes(fieldBackgroundColor) ? '#313538' : fieldBackgroundColor, + backgroundColor: darkMode ? 'rgb(31,40,55)' : 'white', }), }; - - const labelStyles = { - [direction === 'alignRight' ? 'marginLeft' : 'marginRight']: label ? '1rem' : '0.001rem', - color: darkMode && labelColor === '#11181C' ? '#ECEDEE' : labelColor, - justifyContent: direction === 'alignRight' ? 'flex-end' : 'flex-start', - }; - return ( <>
{ onComponentClick(id, component, event); - // This following line is needed because sometimes after clicking on canvas then also dropdown remains selected - useEditorStore.getState().actions.setHoveredComponent(''); }} data-cy={dataCy} > -
-