chore: add color validation + native color picker (#25355)

## Description
1. Reduced the number of default colors. Because the amount of suggested
options was too much: very little difference between shades and
sometimes with hues too. By removing half of them, we allow builders
make better choices faster. The transparent color has also been removed.
2. Added validation of color values because HTML colors are remarkably
easy to get wrong, because they allow so many different values and now
we support and validate all these guys
    - `hex` - `#bada55`
    - `name` - `LightGoldenrodYellow`
    - `special name` - `currentColor`
    - `rgb` - `rgb(0 0 0)`
    - `rgba` - `rgba(0, 0, 0, .45)`
    - `hsl` - `hsl(4.71239rad, 60%, 70%)`
    - `hsla` - `hsla(180deg 100% 50% / .8)`
    - `hwb` - `hwb(180deg 0% 0% / 100%)`
    - `lab` - `lab(2000.1337% -8.6911 -159.131231 / .987189732)`
    - `lch` - `lch(54.292% 106.839 40.853)`
<img width="283" alt="Снимок экрана 2023-08-02 в 17 58 07"
src="https://github.com/appsmithorg/appsmith/assets/11555074/a8fef365-506d-432e-85ad-cdb550de1f60">
    
3. Added support for a Full color picker. Now we can easily switch
between modes and builders can easily choose any colors.
<img width="259" alt="Снимок экрана 2023-08-02 в 17 43 34"
src="https://github.com/appsmithorg/appsmith/assets/11555074/be09cd92-7c69-43eb-812a-0b1fe3ac9ef6">


#### PR fixes following issue(s)
Fixes  #22996

#### Media

https://www.loom.com/share/098e0116e49744e7b10689d4a18ab664?sid=15405577-160e-4b48-bfef-bc8dcfa97efe

#### Type of change
- New feature (non-breaking change which adds functionality)

## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [x] Manual
- [x] Jest
- [x] Cypress

## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed

---------

Co-authored-by: Valera Melnikov <melnikov.vv@greendatasoft.ru>
This commit is contained in:
Valera Melnikov 2023-08-07 11:55:10 +03:00 committed by GitHub
parent 75eea5b87d
commit 8be4936ca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 188 additions and 205 deletions

View File

@ -29,7 +29,7 @@ describe("Entity explorer Drag and Drop widgets testcases", function () {
cy.selectColor("backgroundcolor"); cy.selectColor("backgroundcolor");
cy.get(formWidgetsPage.formD) cy.get(formWidgetsPage.formD)
.should("have.css", "background-color") .should("have.css", "background-color")
.and("eq", "rgb(126, 34, 206)"); .and("eq", "rgb(219, 234, 254)");
/** /**
* @param{toggleButton Css} Assert to be checked * @param{toggleButton Css} Assert to be checked
*/ */

View File

@ -174,7 +174,7 @@ describe("Undo/Redo functionality", function () {
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500); cy.wait(500);
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.readTextDataValidateCSS("color", "rgb(126, 34, 206)"); cy.readTextDataValidateCSS("color", "rgb(219, 234, 254)");
cy.get("body").click({ force: true }).type(`{${modifierKey}}z`); cy.get("body").click({ force: true }).type(`{${modifierKey}}z`);
entityExplorer.NavigateToSwitcher("Explorer"); entityExplorer.NavigateToSwitcher("Explorer");
entityExplorer.SelectEntityByName("Text1"); entityExplorer.SelectEntityByName("Text1");
@ -191,7 +191,7 @@ describe("Undo/Redo functionality", function () {
cy.get(widgetsPage.textColor) cy.get(widgetsPage.textColor)
.first() .first()
.invoke("attr", "value") .invoke("attr", "value")
.should("contain", "#7e22ce"); .should("contain", "#dbeafe");
}); });
it("8. checks undo/redo for option control for radio button", function () { it("8. checks undo/redo for option control for radio button", function () {

View File

@ -364,7 +364,7 @@ describe("App Theming funtionality", function () {
.find(".t--theme-card > main > main") .find(".t--theme-card > main > main")
.invoke("css", "background-color") .invoke("css", "background-color")
.then((backgroudColor) => { .then((backgroudColor) => {
expect(backgroudColor).to.eq("rgb(131, 24, 67)"); expect(backgroudColor).to.eq("rgb(236, 72, 153)");
}); });
//Check if the saved theme is present under 'Yours Themes' section with Trash button //Check if the saved theme is present under 'Yours Themes' section with Trash button
@ -669,7 +669,7 @@ describe("App Theming funtionality", function () {
.eq(0) .eq(0)
.invoke("css", "background-color") .invoke("css", "background-color")
.then((backgroudColor) => { .then((backgroudColor) => {
expect(backgroudColor).to.eq("rgb(126, 34, 206)"); expect(backgroudColor).to.eq("rgb(219, 234, 254)");
}); });
cy.contains("Applied theme") cy.contains("Applied theme")
@ -680,7 +680,7 @@ describe("App Theming funtionality", function () {
.eq(1) .eq(1)
.invoke("css", "background-color") .invoke("css", "background-color")
.then((backgroudColor) => { .then((backgroudColor) => {
expect(backgroudColor).to.eq("rgb(253, 224, 71)"); expect(backgroudColor).to.eq("rgb(29, 78, 216)");
}); });
//#endregion //#endregion
@ -696,17 +696,17 @@ describe("App Theming funtionality", function () {
cy.xpath("//div[@id='root']//section/parent::div").should( cy.xpath("//div[@id='root']//section/parent::div").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(253, 224, 71)", "rgb(29, 78, 216)",
); //Background Color ); //Background Color
cy.get(widgetsPage.widgetBtn).should( cy.get(widgetsPage.widgetBtn).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); //Widget Color ); //Widget Color
cy.get(publish.iconWidgetBtn).should( cy.get(publish.iconWidgetBtn).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); //Widget Color ); //Widget Color
cy.get(widgetsPage.widgetBtn).should("have.css", "border-radius", "24px"); //Border Radius cy.get(widgetsPage.widgetBtn).should("have.css", "border-radius", "24px"); //Border Radius
@ -738,12 +738,12 @@ describe("App Theming funtionality", function () {
cy.get(".t--widget-button1 button").should( cy.get(".t--widget-button1 button").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); //old widgets still conforming to theme color ); //old widgets still conforming to theme color
cy.get(widgetsPage.iconWidgetBtn).should( cy.get(widgetsPage.iconWidgetBtn).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
}); });
@ -789,20 +789,20 @@ describe("App Theming funtionality", function () {
cy.get(".t--widget-buttonwidget:nth-child(4) button").should( cy.get(".t--widget-buttonwidget:nth-child(4) button").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(134, 239, 172)", //rgb(134, 239, 172) "rgb(190, 24, 93)",
); //new widget with its own color ); //new widget with its own color
////old widgets still conforming to theme color ////old widgets still conforming to theme color
cy.get(".t--widget-buttonwidget button").should( cy.get(".t--widget-buttonwidget button").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
cy.get(publish.iconWidgetBtn).should( cy.get(publish.iconWidgetBtn).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
//Verify Border radius //Verify Border radius
@ -845,7 +845,7 @@ describe("App Theming funtionality", function () {
cy.get(".t--widget-button2 button").should( cy.get(".t--widget-button2 button").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); //verify widget reverted to theme color ); //verify widget reverted to theme color
cy.get(".t--property-control-borderradius .reset-button").then(($elem) => { cy.get(".t--property-control-borderradius .reset-button").then(($elem) => {
$elem[0].removeAttribute("display: none"); $elem[0].removeAttribute("display: none");
@ -866,12 +866,12 @@ describe("App Theming funtionality", function () {
cy.xpath("//div[@id='root']//section/parent::div").should( cy.xpath("//div[@id='root']//section/parent::div").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(253, 224, 71)", "rgb(29, 78, 216)",
); //Background Color ); //Background Color
cy.get(".t--widget-button1 button").should( cy.get(".t--widget-button1 button").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); //Widget Color ); //Widget Color
cy.get("body").then(($ele) => { cy.get("body").then(($ele) => {
if ($ele.find(widgetsPage.widgetBtn).length <= 1) { if ($ele.find(widgetsPage.widgetBtn).length <= 1) {
@ -882,12 +882,12 @@ describe("App Theming funtionality", function () {
cy.get(".t--widget-button2 button").should( cy.get(".t--widget-button2 button").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); //Widget Color ); //Widget Color
cy.get(publish.iconWidgetBtn).should( cy.get(publish.iconWidgetBtn).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); //Widget Color ); //Widget Color
cy.get(".t--widget-button1 button").should( cy.get(".t--widget-button1 button").should(
@ -1004,7 +1004,7 @@ describe("App Theming funtionality", function () {
cy.get(".t--widget-button1 button").should( cy.get(".t--widget-button1 button").should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(252, 165, 165)", "rgb(161, 98, 7)",
); //new widget with its own color ); //new widget with its own color
////old widgets still conforming to theme color ////old widgets still conforming to theme color

View File

@ -93,7 +93,7 @@ describe("Theme validation usecases", function () {
cy.get(themelocator.inputColor).clear({ force: true }); cy.get(themelocator.inputColor).clear({ force: true });
cy.wait(2000); cy.wait(2000);
theme.ChangeThemeColor(16, "Background"); theme.ChangeThemeColor(16, "Background");
cy.get(themelocator.inputColor).should("have.value", "#dc2626"); //Red cy.get(themelocator.inputColor).should("have.value", "#86efac"); //Red
cy.wait(2000); cy.wait(2000);
cy.get(themelocator.inputColor).eq(0).click({ force: true }); cy.get(themelocator.inputColor).eq(0).click({ force: true });
@ -107,7 +107,7 @@ describe("Theme validation usecases", function () {
cy.get(themelocator.inputColor).clear({ force: true }); cy.get(themelocator.inputColor).clear({ force: true });
cy.wait(2000); cy.wait(2000);
theme.ChangeThemeColor(9, "Primary"); theme.ChangeThemeColor(9, "Primary");
cy.get(themelocator.inputColor).should("have.value", "#18181b"); //Black cy.get(themelocator.inputColor).should("have.value", "#7f1d1d"); //Black
cy.wait(2000); cy.wait(2000);
cy.contains("Color").click({ force: true }); cy.contains("Color").click({ force: true });
appSettings.ClosePane(); appSettings.ClosePane();

View File

@ -190,13 +190,13 @@ describe("List Widget Functionality", function () {
agHelper.AssertCSS( agHelper.AssertCSS(
locators._listWidget, locators._listWidget,
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
// Verify List Item Background Color // Verify List Item Background Color
agHelper.AssertCSS( agHelper.AssertCSS(
locators._itemContainerWidget, locators._itemContainerWidget,
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
deployMode.NavigateBacktoEditor(); deployMode.NavigateBacktoEditor();
}); });

View File

@ -37,13 +37,13 @@ describe("Container Widget Functionality", function () {
cy.get(widgetsPage.listWidget).should( cy.get(widgetsPage.listWidget).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
// Verify List Item Background Color // Verify List Item Background Color
cy.get(widgetsPage.itemContainerWidget).should( cy.get(widgetsPage.itemContainerWidget).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
_.deployMode.NavigateBacktoEditor(); _.deployMode.NavigateBacktoEditor();
}); });

View File

@ -16,7 +16,7 @@ describe("Table Widget property pane feature validation", function () {
cy.wait(500); cy.wait(500);
cy.wait("@updateLayout"); cy.wait("@updateLayout");
// Verify the text color is green // Verify the text color is green
cy.readTabledataValidateCSS("1", "0", "color", "rgb(126, 34, 206)"); cy.readTabledataValidateCSS("1", "0", "color", "rgb(219, 234, 254)");
// Change the text color and enter purple in input field // Change the text color and enter purple in input field
cy.get(widgetsPage.textColor) cy.get(widgetsPage.textColor)
.scrollIntoView() .scrollIntoView()
@ -41,13 +41,15 @@ describe("Table Widget property pane feature validation", function () {
"1", "1",
"1", "1",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
_.deployMode.NavigateBacktoEditor(); _.deployMode.NavigateBacktoEditor();
cy.openPropertyPane("tablewidget"); cy.openPropertyPane("tablewidget");
// Change the cell background color and enter purple in input field // Change the cell background color and enter purple in input field
cy.get(`${widgetsPage.cellBackground_tablev1} input`) cy.get(
`${widgetsPage.cellBackground_tablev1} [data-testid='t--color-picker-input']`,
)
.clear({ force: true }) .clear({ force: true })
.type("purple", { force: true }); .type("purple", { force: true });
cy.wait("@updateLayout"); cy.wait("@updateLayout");

View File

@ -28,23 +28,23 @@ describe("Table Widget empty row color validation", function () {
"1", "1",
"0", "0",
"background-color", "background-color",
"rgb(99, 102, 241)", "rgb(185, 28, 28)",
); );
// Verify the cell background color of second column // Verify the cell background color of second column
cy.readTabledataValidateCSS( cy.readTabledataValidateCSS(
"1", "1",
"1", "1",
"background-color", "background-color",
"rgb(30, 58, 138)", "rgb(113, 113, 122)",
); );
//Test 2. Validate empty row background //Test 2. Validate empty row background
// first cell of first row should be transparent // first cell of first row should be transparent
cy.get( cy.get(
".t--widget-tablewidget .tbody div[data-testid='empty-row-0-cell-0']", ".t--widget-tablewidget .tbody div[data-testid='empty-row-0-cell-0']",
).should("have.css", "background-color", "rgb(99, 102, 241)"); ).should("have.css", "background-color", "rgb(185, 28, 28)");
// second cell of first row should be transparent // second cell of first row should be transparent
cy.get( cy.get(
".t--widget-tablewidget .tbody div[data-testid='empty-row-0-cell-1']", ".t--widget-tablewidget .tbody div[data-testid='empty-row-0-cell-1']",
).should("have.css", "background-color", "rgb(30, 58, 138)"); ).should("have.css", "background-color", "rgb(113, 113, 122)");
}); });
}); });

View File

@ -171,12 +171,12 @@ describe("Table Widget property pane feature validation", function () {
it("6. Test to validate text color and text background", function () { it("6. Test to validate text color and text background", function () {
cy.openPropertyPane("tablewidget"); cy.openPropertyPane("tablewidget");
// Changing text color to rgb(126, 34, 206) and validate // Changing text color to rgb(219, 234, 254) and validate
cy.selectColor("textcolor"); cy.selectColor("textcolor");
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(5000); cy.wait(5000);
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.readTabledataValidateCSS("1", "0", "color", "rgb(126, 34, 206)"); cy.readTabledataValidateCSS("1", "0", "color", "rgb(219, 234, 254)");
// Changing text color to PURPLE and validate using JS // Changing text color to PURPLE and validate using JS
cy.get(widgetsPage.toggleJsColor).click({ force: true }); cy.get(widgetsPage.toggleJsColor).click({ force: true });
@ -185,13 +185,13 @@ describe("Table Widget property pane feature validation", function () {
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)"); cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)");
cy.get(commonlocators.editPropBackButton).click(); cy.get(commonlocators.editPropBackButton).click();
// Changing Cell backgroud color to rgb(126, 34, 206) and validate // Changing Cell backgroud color to rgb(219, 234, 254) and validate
cy.selectColor("cellbackgroundcolor"); cy.selectColor("cellbackgroundcolor");
cy.readTabledataValidateCSS( cy.readTabledataValidateCSS(
"0", "0",
"0", "0",
"background", "background",
"rgb(126, 34, 206) none repeat scroll 0% 0% / auto padding-box border-box", "rgb(219, 234, 254) none repeat scroll 0% 0% / auto padding-box border-box",
true, true,
); );
// Changing Cell backgroud color to PURPLE and validate using JS // Changing Cell backgroud color to PURPLE and validate using JS

View File

@ -325,20 +325,4 @@ describe("Table Widget property pane feature validation", function () {
cy.wait(500); cy.wait(500);
cy.get("[data-testid='t--property-pane-back-btn']").click({ force: true }); cy.get("[data-testid='t--property-pane-back-btn']").click({ force: true });
}); });
it("7. Table widget test on button when transparent", () => {
cy.openPropertyPane("tablewidget");
// Open column details of "id".
cy.editColumn("id");
// Changing column "Button" color to transparent
cy.get(widgetsPage.buttonColor).click({ force: true });
cy.wait(2000);
cy.get(widgetsPage.transparent).click({ force: true });
cy.get(".td[data-colindex=5][data-rowindex=0] .bp3-button").should(
"have.css",
"background-color",
"rgba(0, 0, 0, 0)",
);
});
}); });

View File

@ -24,7 +24,7 @@ describe("Table Widget V2 property pane feature validation", function () {
cy.wait(500); cy.wait(500);
cy.wait("@updateLayout"); cy.wait("@updateLayout");
// Verify the text color is green // Verify the text color is green
cy.readTableV2dataValidateCSS("1", "0", "color", "rgb(126, 34, 206)"); cy.readTableV2dataValidateCSS("1", "0", "color", "rgb(219, 234, 254)");
// Change the text color and enter purple in input field // Change the text color and enter purple in input field
cy.get(widgetsPage.textColor) cy.get(widgetsPage.textColor)
.scrollIntoView() .scrollIntoView()
@ -49,13 +49,15 @@ describe("Table Widget V2 property pane feature validation", function () {
"1", "1",
"1", "1",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
_.deployMode.NavigateBacktoEditor(); _.deployMode.NavigateBacktoEditor();
cy.openPropertyPane("tablewidgetv2"); cy.openPropertyPane("tablewidgetv2");
cy.moveToStyleTab(); cy.moveToStyleTab();
// Change the cell background color and enter purple in input field // Change the cell background color and enter purple in input field
cy.get(`.t--property-control-cellbackgroundcolor input`) cy.get(
`.t--property-control-cellbackgroundcolor [data-testid='t--color-picker-input']`,
)
.clear({ force: true }) .clear({ force: true })
.type("purple", { force: true }); .type("purple", { force: true });
cy.wait("@updateLayout"); cy.wait("@updateLayout");

View File

@ -18,12 +18,12 @@ describe("Table Widget V2 property pane feature validation", function () {
cy.openPropertyPane("tablewidgetv2"); cy.openPropertyPane("tablewidgetv2");
cy.editColumn("id"); cy.editColumn("id");
cy.moveToStyleTab(); cy.moveToStyleTab();
// Changing text color to rgb(126, 34, 206) and validate // Changing text color to rgb(219, 234, 254) and validate
cy.selectColor("textcolor"); cy.selectColor("textcolor");
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(5000); cy.wait(5000);
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.readTableV2dataValidateCSS("1", "0", "color", "rgb(126, 34, 206)"); cy.readTableV2dataValidateCSS("1", "0", "color", "rgb(219, 234, 254)");
// Changing text color to PURPLE and validate using JS // Changing text color to PURPLE and validate using JS
cy.get(widgetsPage.toggleJsColor).click(); cy.get(widgetsPage.toggleJsColor).click();
@ -31,13 +31,13 @@ describe("Table Widget V2 property pane feature validation", function () {
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.readTableV2dataValidateCSS("1", "0", "color", "rgb(128, 0, 128)"); cy.readTableV2dataValidateCSS("1", "0", "color", "rgb(128, 0, 128)");
// Changing Cell backgroud color to rgb(126, 34, 206) and validate // Changing Cell backgroud color to rgb(219, 234, 254) and validate
cy.selectColor("cellbackground"); cy.selectColor("cellbackground");
cy.readTableV2dataValidateCSS( cy.readTableV2dataValidateCSS(
"0", "0",
"0", "0",
"background", "background",
"rgb(113, 30, 184) none repeat scroll 0% 0% / auto padding-box border-box", "rgb(194, 220, 253) none repeat scroll 0% 0% / auto padding-box border-box",
true, true,
); );
// Changing Cell backgroud color to PURPLE and validate using JS // Changing Cell backgroud color to PURPLE and validate using JS

View File

@ -289,20 +289,4 @@ describe("Table Widget V2 property pane feature validation", function () {
cy.wait(500); cy.wait(500);
cy.get("[data-testid='t--property-pane-back-btn']").click({ force: true }); cy.get("[data-testid='t--property-pane-back-btn']").click({ force: true });
}); });
it("8. Table widget test on button when transparent", () => {
cy.openPropertyPane("tablewidgetv2");
// Open column details of "id".
cy.editColumn("id");
// Changing column "Button" color to transparent
cy.moveToStyleTab();
cy.get(widgetsPage.buttonColor).click({ force: true });
cy.wait(2000);
cy.get(widgetsPage.transparent).click({ force: true });
cy.get(".td[data-colindex=5][data-rowindex=0] .bp3-button").should(
"have.css",
"background-color",
"rgba(0, 0, 0, 0)",
);
});
}); });

View File

@ -21,7 +21,7 @@ describe("Text Widget Cell Background and Text Size Validation", function () {
cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should( cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
//Toggle to JS mode //Toggle to JS mode

View File

@ -85,7 +85,7 @@ describe("Text Widget color/font/alignment Functionality", function () {
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500); cy.wait(500);
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.readTextDataValidateCSS("color", "rgb(126, 34, 206)"); cy.readTextDataValidateCSS("color", "rgb(219, 234, 254)");
cy.get(widgetsPage.textColor) cy.get(widgetsPage.textColor)
.clear({ force: true }) .clear({ force: true })
.type("purple", { force: true }); .type("purple", { force: true });
@ -105,7 +105,7 @@ describe("Text Widget color/font/alignment Functionality", function () {
cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should( cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should(
"have.css", "have.css",
"background-color", "background-color",
"rgb(126, 34, 206)", "rgb(219, 234, 254)",
); );
//Toggle JS check with cell background: //Toggle JS check with cell background:

View File

@ -21,7 +21,7 @@ describe("Json & JsonB Datatype tests", function () {
agHelper.AddDsl("Datatypes/JsonDTdsl"); agHelper.AddDsl("Datatypes/JsonDTdsl");
entityExplorer.NavigateToSwitcher("Widgets"); entityExplorer.NavigateToSwitcher("Widgets");
appSettings.OpenPaneAndChangeThemeColors(33, 39); appSettings.OpenPaneAndChangeThemeColors(16, 20);
}); });
beforeEach(() => { beforeEach(() => {

View File

@ -4,7 +4,7 @@
"popover": ".rc-tooltip-inner", "popover": ".rc-tooltip-inner",
"shadow": ".t--theme-appBoxShadow", "shadow": ".t--theme-appBoxShadow",
"color": ".t--property-pane-sidebar .bp3-popover-target .cursor-pointer", "color": ".t--property-pane-sidebar .bp3-popover-target .cursor-pointer",
"inputColor": ".t--colorpicker-v2-popover input", "inputColor": ".t--colorpicker-v2-popover [data-testid='t--color-picker-input']",
"colorPicker": "[data-testid='color-picker']", "colorPicker": "[data-testid='color-picker']",
"greenColor": "[style='background-color: rgb(21, 128, 61);']", "greenColor": "[style='background-color: rgb(21, 128, 61);']",
"fontsSelected": ".leading-normal", "fontsSelected": ".leading-normal",

View File

@ -8,7 +8,7 @@
"inputPropsDataType": ".t--property-control-datatype input", "inputPropsDataType": ".t--property-control-datatype input",
"inputdatatypeplaceholder": ".t--property-control-placeholder", "inputdatatypeplaceholder": ".t--property-control-placeholder",
"buttonWidget": ".t--draggable-buttonwidget", "buttonWidget": ".t--draggable-buttonwidget",
"buttonColor": ".t--property-control-buttoncolor input", "buttonColor": ".t--property-control-buttoncolor [data-testid='t--color-picker-input']",
"checkboxWidget": ".t--draggable-checkboxwidget", "checkboxWidget": ".t--draggable-checkboxwidget",
"buttonStyleDropdown": ".t--property-control-buttonstyle [name='downArrow']", "buttonStyleDropdown": ".t--property-control-buttonstyle [name='downArrow']",
"buttonBackground": ".sc-ecQjpJ > div > .bp3-button", "buttonBackground": ".sc-ecQjpJ > div > .bp3-button",
@ -38,7 +38,7 @@
"requiredjs": ".t--property-control-required input", "requiredjs": ".t--property-control-required input",
"visible": ".t--property-control-visible input", "visible": ".t--property-control-visible input",
"disable": ".t--property-control-disabled", "disable": ".t--property-control-disabled",
"menuColor": ".t--property-control-menucolor input", "menuColor": ".t--property-control-menucolor [data-testid='t--color-picker-input']",
"menubar": ".bp3-menu", "menubar": ".bp3-menu",
"menupop": ".bp3-popover", "menupop": ".bp3-popover",
"defaultcheck": ".t--property-control-defaultstate input", "defaultcheck": ".t--property-control-defaultstate input",
@ -94,17 +94,16 @@
"verticalTop": "[data-value='TOP']", "verticalTop": "[data-value='TOP']",
"verticalCenter": "[data-value='CENTER']", "verticalCenter": "[data-value='CENTER']",
"verticalBottom": "[data-value='BOTTOM']", "verticalBottom": "[data-value='BOTTOM']",
"textColor": ".t--property-control-textcolor input", "textColor": ".t--property-control-textcolor [data-testid='t--color-picker-input']",
"boadercolorPicker": ".t--property-control-bordercolour input", "boadercolorPicker": ".t--property-control-bordercolour input",
"boxShadowColorPicker": ".t--property-control-shadowcolor input", "boxShadowColorPicker": ".t--property-control-shadowcolor input",
"boxShadow": ".t--property-control-boxshadow .bp3-button-group", "boxShadow": ".t--property-control-boxshadow .bp3-button-group",
"inputStepArrows": ".bp3-button-group", "inputStepArrows": ".bp3-button-group",
"backgroundcolorPicker": ".t--property-control-backgroundcolour input", "backgroundcolorPicker": ".t--property-control-backgroundcolour input",
"backgroundcolorPickerNew": ".t--property-control-backgroundcolor input", "backgroundcolorPickerNew": ".t--property-control-backgroundcolor [data-testid='t--color-picker-input']",
"greenColorHex": "#03b365", "greenColorHex": "#03b365",
"yellowColorHex": "#FFC13D", "yellowColorHex": "#FFC13D",
"greenColor": "//div[@color='#03b365']", "greenColor": "//div[@color='#03b365']",
"transparent": ".diagnol-cross",
"yellowColor": "//div[@color='#FFC13D']", "yellowColor": "//div[@color='#FFC13D']",
"blueColor": "//div[@color='#3366FF']", "blueColor": "//div[@color='#3366FF']",
"toggleJsColor": ".t--property-control-textcolor .t--js-toggle", "toggleJsColor": ".t--property-control-textcolor .t--js-toggle",

View File

@ -10,7 +10,7 @@ export class ThemeSettings {
"']//ancestor::div[@class= 'space-y-1 group']", "']//ancestor::div[@class= 'space-y-1 group']",
_colorPickerV2Popover: ".t--colorpicker-v2-popover", _colorPickerV2Popover: ".t--colorpicker-v2-popover",
_colorPickerV2Color: _colorPickerV2Color:
"//h3[text()='All Colors']/following-sibling::div//div[contains(@class,'t--colorpicker-v2-color')]", "[data-testid='t--all-colors'] .t--colorpicker-v2-color",
_colorRingPrimary: "[data-testid='theme-primaryColor']", _colorRingPrimary: "[data-testid='theme-primaryColor']",
_colorRingBackground: "[data-testid='theme-backgroundColor']", _colorRingBackground: "[data-testid='theme-backgroundColor']",
_colorInput: (option: string) => _colorInput: (option: string) =>

View File

@ -207,6 +207,7 @@
"unescape-js": "^1.1.4", "unescape-js": "^1.1.4",
"url-search-params-polyfill": "^8.0.0", "url-search-params-polyfill": "^8.0.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"validate-color": "^2.2.4",
"webfontloader": "^1.6.28", "webfontloader": "^1.6.28",
"webpack-retry-chunk-load-plugin": "^3.1.1", "webpack-retry-chunk-load-plugin": "^3.1.1",
"yjs": "^13.5.12", "yjs": "^13.5.12",

View File

@ -56,13 +56,6 @@ and .Toastify__toast-container--bottom-center classes, which messes with the pla
opacity: 0.5; opacity: 0.5;
} }
.diagnol-cross {
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' preserveAspectRatio='none' viewBox='0 0 100 100'><path d='M0 99 L99 0 L100 1 L1 100' fill='red' /></svg>");
background-repeat: no-repeat;
background-position: center center;
background-size: 100% 100%, auto;
}
.hidden-scrollbar { .hidden-scrollbar {
-ms-overflow-style: none; /* for Internet Explorer, Edge */ -ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */ scrollbar-width: none; /* for Firefox */

View File

@ -1901,3 +1901,6 @@ export const DEFAULT_CAMERA_LABEL_DESCRIPTION = () =>
"Default choice for mobile users. Not applicable for other devices"; "Default choice for mobile users. Not applicable for other devices";
export const FRONT_CAMERA_LABEL = () => "Front (Selfie)"; export const FRONT_CAMERA_LABEL = () => "Front (Selfie)";
export const BACK_CAMERA_LABEL = () => "Back (Rear)"; export const BACK_CAMERA_LABEL = () => "Back (Rear)";
// Color picker
export const FULL_COLOR_PICKER_LABEL = () => "Full color picker";

View File

@ -111,6 +111,7 @@ describe("<ColorPicker /> - Keyboard Navigation", () => {
userEvent.tab(); userEvent.tab();
userEvent.keyboard("{Enter}"); userEvent.keyboard("{Enter}");
userEvent.tab();
userEvent.tab(); userEvent.tab();
expect( expect(
document.querySelectorAll("[tabindex='0'].t--colorpicker-v2-color")[0], document.querySelectorAll("[tabindex='0'].t--colorpicker-v2-color")[0],
@ -133,6 +134,7 @@ describe("<ColorPicker /> - Keyboard Navigation", () => {
userEvent.tab(); userEvent.tab();
userEvent.keyboard("{Enter}"); userEvent.keyboard("{Enter}");
userEvent.tab();
userEvent.tab(); userEvent.tab();
userEvent.tab(); userEvent.tab();
@ -153,6 +155,7 @@ describe("<ColorPicker /> - Keyboard Navigation", () => {
userEvent.tab(); userEvent.tab();
userEvent.keyboard("{Enter}"); userEvent.keyboard("{Enter}");
userEvent.tab();
userEvent.tab(); userEvent.tab();
userEvent.tab(); userEvent.tab();
@ -180,6 +183,7 @@ describe("<ColorPicker /> - Keyboard Navigation", () => {
userEvent.tab(); userEvent.tab();
userEvent.keyboard("{Enter}"); userEvent.keyboard("{Enter}");
userEvent.tab();
userEvent.tab(); userEvent.tab();
userEvent.tab(); userEvent.tab();
@ -199,6 +203,7 @@ describe("<ColorPicker /> - Keyboard Navigation", () => {
userEvent.tab(); userEvent.tab();
userEvent.keyboard("{Enter}"); userEvent.keyboard("{Enter}");
userEvent.tab();
userEvent.tab(); userEvent.tab();
userEvent.tab(); userEvent.tab();

View File

@ -6,7 +6,7 @@ import React, {
useCallback, useCallback,
} from "react"; } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Icon } from "design-system"; import { Icon, Switch } from "design-system";
import { import {
Popover, Popover,
InputGroup, InputGroup,
@ -21,11 +21,15 @@ import {
getThemePropertyBinding, getThemePropertyBinding,
} from "constants/ThemeConstants"; } from "constants/ThemeConstants";
import { getWidgets } from "sagas/selectors"; import { getWidgets } from "sagas/selectors";
import { extractColorsFromString } from "utils/helpers"; import { extractColorsFromString, isValidColor } from "utils/helpers";
import { TAILWIND_COLORS } from "constants/ThemeConstants"; import { TAILWIND_COLORS } from "constants/ThemeConstants";
import useDSEvent from "utils/hooks/useDSEvent"; import useDSEvent from "utils/hooks/useDSEvent";
import { DSEventTypes } from "utils/AppsmithUtils"; import { DSEventTypes } from "utils/AppsmithUtils";
import { getBrandColors } from "@appsmith/selectors/tenantSelectors"; import { getBrandColors } from "@appsmith/selectors/tenantSelectors";
import {
createMessage,
FULL_COLOR_PICKER_LABEL,
} from "@appsmith/constants/messages";
const FocusTrap = require("focus-trap-react"); const FocusTrap = require("focus-trap-react");
@ -46,6 +50,7 @@ interface ColorPickerProps {
isOpen?: boolean; isOpen?: boolean;
placeholderText?: string; placeholderText?: string;
portalContainer?: HTMLElement; portalContainer?: HTMLElement;
onPopupClosed?: () => void;
} }
/** /**
@ -73,7 +78,10 @@ const ColorPickerIconContainer = styled.div`
z-index: 1; z-index: 1;
`; `;
const StyledInputGroup = styled(InputGroup)` const StyledInputGroup = styled(InputGroup)<{
$isValid?: boolean;
$isFullColorPicker?: boolean;
}>`
.${Classes.INPUT} { .${Classes.INPUT} {
box-shadow: none; box-shadow: none;
border: 1px solid var(--ads-v2-color-border); border: 1px solid var(--ads-v2-color-border);
@ -83,23 +91,30 @@ const StyledInputGroup = styled(InputGroup)`
} }
} }
&&& input { &&& input {
padding-left: 36px; padding: ${({ $isFullColorPicker }) =>
$isFullColorPicker ? "0px 2px" : "0 10px 0 36px"};
height: 36px; height: 36px;
border: 1px solid var(--ads-v2-color-border); border: ${({ $isValid }) =>
$isValid
? "1px solid var(--ads-v2-color-border)"
: "1px solid var(--ads-v2-color-border-error)"};
background: ${(props) => background: ${(props) =>
props.theme.colors.propertyPane.multiDropdownBoxHoverBg}; props.theme.colors.propertyPane.multiDropdownBoxHoverBg};
color: ${(props) => props.theme.colors.propertyPane.label}; color: ${(props) => props.theme.colors.propertyPane.label};
&:hover {
border-color: var(--ads-v2-color-border-emphasis);
}
&:focus { &:focus {
border: 1px solid var(--ads-v2-color-border-emphasis);
outline: var(--ads-v2-border-width-outline) solid outline: var(--ads-v2-border-width-outline) solid
var(--ads-v2-color-outline); var(--ads-v2-color-outline);
outline-offset: var(--ads-v2-offset-outline); outline-offset: var(--ads-v2-offset-outline);
} }
&:hover,
&:focus {
border-color: ${({ $isValid }) =>
$isValid
? "var(--ads-v2-color-border-emphasis)"
: "var(--ads-v2-color-border-error)"};
}
} }
`; `;
@ -117,7 +132,6 @@ interface ColorPickerPopupProps {
const PopupContainer = styled.div` const PopupContainer = styled.div`
padding: 0.75rem; padding: 0.75rem;
width: 18rem;
border-radius: var(--ads-v2-border-radius); border-radius: var(--ads-v2-border-radius);
border: 1px solid var(--ads-v2-color-border); border: 1px solid var(--ads-v2-color-border);
`; `;
@ -126,10 +140,9 @@ function ColorPickerPopup(props: ColorPickerPopupProps) {
const themeColors = useSelector(getSelectedAppThemeProperties).colors; const themeColors = useSelector(getSelectedAppThemeProperties).colors;
const brandColors = useSelector(getBrandColors); const brandColors = useSelector(getBrandColors);
const widgets = useSelector(getWidgets); const widgets = useSelector(getWidgets);
const DSLStringified = JSON.stringify(widgets);
const applicationColors = useMemo(() => { const applicationColors = useMemo(() => {
return extractColorsFromString(DSLStringified); return extractColorsFromString(widgets);
}, [DSLStringified]); }, []);
const { const {
changeColor, changeColor,
color, color,
@ -169,7 +182,7 @@ function ColorPickerPopup(props: ColorPickerPopupProps) {
<h2 className="pb-2 font-semibold border-b">Color Styles</h2> <h2 className="pb-2 font-semibold border-b">Color Styles</h2>
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-xs">Theme Colors</h3> <h3 className="text-xs">Theme Colors</h3>
<div className="grid grid-cols-10 gap-2"> <div className="grid grid-cols-5 gap-2">
{Object.keys(themeColors).map((colorKey, colorIndex) => ( {Object.keys(themeColors).map((colorKey, colorIndex) => (
<div <div
className={`${COLOR_BOX_CLASSES} ${ className={`${COLOR_BOX_CLASSES} ${
@ -199,7 +212,7 @@ function ColorPickerPopup(props: ColorPickerPopupProps) {
{brandColors && Object.keys(brandColors).length > 0 && ( {brandColors && Object.keys(brandColors).length > 0 && (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-xs">Brand Colors</h3> <h3 className="text-xs">Brand Colors</h3>
<div className="grid grid-cols-10 gap-2"> <div className="grid grid-cols-5 gap-2">
{Object.keys(brandColors).map( {Object.keys(brandColors).map(
(colorKey: string, colorIndex: number) => ( (colorKey: string, colorIndex: number) => (
<div <div
@ -223,7 +236,7 @@ function ColorPickerPopup(props: ColorPickerPopupProps) {
{showApplicationColors && applicationColors.length > 0 && ( {showApplicationColors && applicationColors.length > 0 && (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-xs">Application Colors</h3> <h3 className="text-xs">Application Colors</h3>
<div className="grid grid-cols-10 gap-2"> <div className="grid grid-cols-5 gap-2">
{Object.values(applicationColors).map( {Object.values(applicationColors).map(
(colorCode: string, colorIndex) => ( (colorCode: string, colorIndex) => (
<div <div
@ -246,8 +259,15 @@ function ColorPickerPopup(props: ColorPickerPopupProps) {
)} )}
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-xs">All Colors</h3> {(showThemeColors ||
<div className="grid grid-cols-10 gap-2 t--tailwind-colors"> (brandColors && Object.keys(brandColors).length > 0) ||
(showApplicationColors && applicationColors.length > 0)) && (
<h3 className="text-xs">All Colors</h3>
)}
<div
className="grid grid-cols-5 gap-2 t--tailwind-colors"
data-testid="t--all-colors"
>
{Object.keys(TAILWIND_COLORS).map((colorKey, rowIndex) => {Object.keys(TAILWIND_COLORS).map((colorKey, rowIndex) =>
Object.keys(get(TAILWIND_COLORS, `${colorKey}`)).map( Object.keys(get(TAILWIND_COLORS, `${colorKey}`)).map(
(singleColorKey, colIndex) => ( (singleColorKey, colIndex) => (
@ -275,27 +295,6 @@ function ColorPickerPopup(props: ColorPickerPopupProps) {
), ),
), ),
)} )}
<div
className={`${COLOR_BOX_CLASSES} ${
color === "#fff" ? "ring-1" : ""
}`}
onClick={(e) => {
setColor("#fff");
changeColor("#fff", !e.isTrusted);
}}
tabIndex={-1}
/>
<div
className={`${COLOR_BOX_CLASSES} diagnol-cross ${
color === "transparent" ? "ring-1" : ""
}`}
onClick={(e) => {
setColor("transparent");
changeColor("transparent", !e.isTrusted);
}}
tabIndex={-1}
/>
</div> </div>
</section> </section>
</PopupContainer> </PopupContainer>
@ -329,7 +328,7 @@ interface LeftIconProps {
} }
function LeftIcon(props: LeftIconProps) { function LeftIcon(props: LeftIconProps) {
return props.color ? ( return isValidColor(props.color) ? (
<ColorIcon <ColorIcon
className="rounded-full cursor-pointer" className="rounded-full cursor-pointer"
color={props.color} color={props.color}
@ -365,6 +364,8 @@ const ColorPickerComponent = React.forwardRef(
props.evaluatedColorValue || props.color, props.evaluatedColorValue || props.color,
); );
const [isFullColorPicker, setFullColorPicker] = React.useState(false);
const debouncedOnChange = React.useCallback( const debouncedOnChange = React.useCallback(
debounce((color: string, isUpdatedViaKeyboard: boolean) => { debounce((color: string, isUpdatedViaKeyboard: boolean) => {
props.changeColor(color, isUpdatedViaKeyboard); props.changeColor(color, isUpdatedViaKeyboard);
@ -417,7 +418,6 @@ const ColorPickerComponent = React.forwardRef(
} }
break; break;
case "Enter": case "Enter":
case " ":
emitKeyPressEvent(e.key); emitKeyPressEvent(e.key);
(document.activeElement as any)?.click(); (document.activeElement as any)?.click();
setTimeout(() => { setTimeout(() => {
@ -530,7 +530,9 @@ const ColorPickerComponent = React.forwardRef(
const handleChangeColor = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChangeColor = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value; const value = event.target.value;
debouncedOnChange(value, true); if (isValidColor(value)) {
debouncedOnChange(value, true);
}
setColor(value); setColor(value);
}; };
@ -544,9 +546,20 @@ const ColorPickerComponent = React.forwardRef(
const handleInputClick = () => { const handleInputClick = () => {
isClick.current = true; isClick.current = true;
if (isFullColorPicker && isOpen) {
setIsOpen(false);
}
};
const handleFullColorPickerClick = (value: boolean) => {
setFullColorPicker(value);
setIsOpen(false);
}; };
const handleOnInteraction = (nextOpenState: boolean) => { const handleOnInteraction = (nextOpenState: boolean) => {
if (isFullColorPicker && !isOpen) return;
if (isOpen !== nextOpenState) { if (isOpen !== nextOpenState) {
if (isClick.current) setIsOpen(true); if (isClick.current) setIsOpen(true);
else setIsOpen(nextOpenState); else setIsOpen(nextOpenState);
@ -567,19 +580,26 @@ const ColorPickerComponent = React.forwardRef(
isOpen={isOpen} isOpen={isOpen}
minimal minimal
modifiers={POPOVER_MODFIER} modifiers={POPOVER_MODFIER}
onClosed={props.onPopupClosed}
onInteraction={handleOnInteraction} onInteraction={handleOnInteraction}
popoverClassName="color-picker-input" popoverClassName="color-picker-input"
portalContainer={props.portalContainer} portalContainer={props.portalContainer}
> >
<StyledInputGroup <StyledInputGroup
$isFullColorPicker={isFullColorPicker}
$isValid={isValidColor(color)}
autoFocus={props.autoFocus} autoFocus={props.autoFocus}
data-testid="t--color-picker-input"
inputRef={inputGroupRef} inputRef={inputGroupRef}
leftIcon={ leftIcon={
<LeftIcon color={color} handleInputClick={handleInputClick} /> !isFullColorPicker ? (
<LeftIcon color={color} handleInputClick={handleInputClick} />
) : null
} }
onChange={handleChangeColor} onChange={handleChangeColor}
onClick={handleInputClick} onClick={handleInputClick}
placeholder={placeholderText || "enter color name or hex"} placeholder={placeholderText || "enter color name or hex"}
type={isFullColorPicker ? "color" : "text"}
value={color} value={color}
/> />
@ -593,6 +613,14 @@ const ColorPickerComponent = React.forwardRef(
showThemeColors={props.showThemeColors} showThemeColors={props.showThemeColors}
/> />
</Popover> </Popover>
<div className="mt-2">
<Switch
isSelected={isFullColorPicker}
onChange={handleFullColorPickerClick}
>
{createMessage(FULL_COLOR_PICKER_LABEL)}
</Switch>
</div>
</div> </div>
); );
}, },

View File

@ -13,101 +13,54 @@ export type TailwindColors = {
export const TAILWIND_COLORS: TailwindColors = { export const TAILWIND_COLORS: TailwindColors = {
gray: { gray: {
50: "#fafafa",
100: "#f4f4f5", 100: "#f4f4f5",
200: "#e4e4e7",
300: "#d4d4d8", 300: "#d4d4d8",
400: "#a1a1aa",
500: "#71717a", 500: "#71717a",
600: "#52525b",
700: "#3f3f46", 700: "#3f3f46",
800: "#27272a",
900: "#18181b", 900: "#18181b",
}, },
red: { red: {
50: "#fef2f2",
100: "#fee2e2", 100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5", 300: "#fca5a5",
400: "#f87171",
500: "#ef4444", 500: "#ef4444",
600: "#dc2626",
700: "#b91c1c", 700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d", 900: "#7f1d1d",
}, },
yellow: { yellow: {
50: "#fefce8",
100: "#fef9c3", 100: "#fef9c3",
200: "#fef08a",
300: "#fde047", 300: "#fde047",
400: "#facc15",
500: "#eab308", 500: "#eab308",
600: "#ca8a04",
700: "#a16207", 700: "#a16207",
800: "#854d0e",
900: "#713f12", 900: "#713f12",
}, },
green: { green: {
50: "#f0fdf4",
100: "#dcfce7", 100: "#dcfce7",
200: "#bbf7d0",
300: "#86efac", 300: "#86efac",
400: "#4ade80",
500: "#22c55e", 500: "#22c55e",
600: "#16a34a",
700: "#15803d", 700: "#15803d",
800: "#166534",
900: "#14532d", 900: "#14532d",
}, },
blue: { blue: {
50: "#eff6ff",
100: "#dbeafe", 100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd", 300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6", 500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8", 700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a", 900: "#1e3a8a",
}, },
indigo: {
50: "#eef2ff",
100: "#e0e7ff",
200: "#c7d2fe",
300: "#a5b4fc",
400: "#818cf8",
500: "#6366f1",
600: "#4f46e5",
700: "#4338ca",
800: "#3730a3",
900: "#312e81",
},
purple: { purple: {
50: "#faf5ff",
100: "#f3e8ff", 100: "#f3e8ff",
200: "#e9d5ff",
300: "#d8b4fe", 300: "#d8b4fe",
400: "#c084fc",
500: "#a855f7", 500: "#a855f7",
600: "#9333ea",
700: "#7e22ce", 700: "#7e22ce",
800: "#6b21a8",
900: "#581c87", 900: "#581c87",
}, },
pink: { pink: {
50: "#fdf2f8",
100: "#fce7f3", 100: "#fce7f3",
200: "#fbcfe8",
300: "#f9a8d4", 300: "#f9a8d4",
400: "#f472b6",
500: "#ec4899", 500: "#ec4899",
600: "#db2777",
700: "#be185d", 700: "#be185d",
800: "#9d174d",
900: "#831843", 900: "#831843",
}, },
}; };

View File

@ -80,6 +80,7 @@ function ThemeColorControl(props: ThemeColorControlProps) {
color={userDefinedColors[selectedColor]} color={userDefinedColors[selectedColor]}
isOpen={autoFocus} isOpen={autoFocus}
key={selectedColor} key={selectedColor}
onPopupClosed={() => setAutoFocus(false)}
portalContainer={ portalContainer={
document.getElementById("app-settings-portal") || undefined document.getElementById("app-settings-portal") || undefined
} }

View File

@ -1,6 +1,7 @@
import { RenderModes } from "constants/WidgetConstants"; import { RenderModes } from "constants/WidgetConstants";
import { ValidationTypes } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import type { CanvasWidgetsReduxState } from "../reducers/entityReducers/canvasWidgetsReducer";
import { AutocompleteDataType } from "./autocomplete/AutocompleteDataType"; import { AutocompleteDataType } from "./autocomplete/AutocompleteDataType";
import { import {
flattenObject, flattenObject,
@ -550,20 +551,30 @@ describe("#captureInvalidDynamicBindingPath", () => {
describe("#extractColorsFromString", () => { describe("#extractColorsFromString", () => {
it("Check if the extractColorsFromString returns rgb, rgb, hex color strings", () => { it("Check if the extractColorsFromString returns rgb, rgb, hex color strings", () => {
const borderWithHex = `2px solid ${Colors.GREEN}`; const widgets = {
const borderWithRgb = "2px solid rgb(0,0,0)"; 0: { color: `${Colors.GREEN}` },
const borderWithRgba = `2px solid ${Colors.BOX_SHADOW_DEFAULT_VARIANT1}`; 1: { color: "rgb(0,0,0)" },
2: { color: `${Colors.BOX_SHADOW_DEFAULT_VARIANT1}` },
3: { color: `LightGoldenrodYellow` },
4: { color: `lch(54.292% 106.839 40.853)` },
} as unknown as CanvasWidgetsReduxState;
//Check Hex value //Check Hex value
expect(extractColorsFromString(borderWithHex)[0]).toEqual("#03b365"); expect(extractColorsFromString(widgets)[0]).toEqual("#03B365");
//Check rgba value
expect(extractColorsFromString(borderWithRgba)[0]).toEqual(
"rgba(0, 0, 0, 0.25)",
);
//Check rgb //Check rgb
expect(extractColorsFromString(borderWithRgb)[0]).toEqual("rgb(0,0,0)"); expect(extractColorsFromString(widgets)[1]).toEqual("rgb(0,0,0)");
//Check rgba value
expect(extractColorsFromString(widgets)[2]).toEqual("rgba(0, 0, 0, 0.25)");
//Check name value
expect(extractColorsFromString(widgets)[3]).toEqual("LightGoldenrodYellow");
//Check lch value
expect(extractColorsFromString(widgets)[4]).toEqual(
"lch(54.292% 106.839 40.853)",
);
}); });
}); });

View File

@ -35,6 +35,7 @@ import type { ContainerWidgetProps } from "widgets/ContainerWidget/widget";
import type { WidgetProps } from "widgets/BaseWidget"; import type { WidgetProps } from "widgets/BaseWidget";
import { getContainerIdForCanvas } from "sagas/WidgetOperationUtils"; import { getContainerIdForCanvas } from "sagas/WidgetOperationUtils";
import scrollIntoView from "scroll-into-view-if-needed"; import scrollIntoView from "scroll-into-view-if-needed";
import validateColor from "validate-color";
export const snapToGrid = ( export const snapToGrid = (
columnWidth: number, columnWidth: number,
@ -757,28 +758,36 @@ export function getLogToSentryFromResponse(response?: ApiResponse) {
return response && response?.responseMeta?.status >= 500; return response && response?.responseMeta?.status >= 500;
} }
const BLACKLIST_COLORS = ["#ffffff"];
const HEX_REGEX = /#[0-9a-fA-F]{6}/gi;
const RGB_REGEX = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/gi;
/** /**
* extract colors from string * extract colors from string
* *
* @param text
* @returns * @returns
* @param widgets
*/ */
export function extractColorsFromString(text: string) { export function extractColorsFromString(widgets: CanvasWidgetsReduxState) {
const colors = new Set(); const colors = new Set();
[...(text.match(RGB_REGEX) || []), ...(text.match(HEX_REGEX) || [])] Object.values(widgets).forEach((widget) => {
.filter((d) => BLACKLIST_COLORS.indexOf(d.toLowerCase()) === -1) Object.values(widget).forEach((widgetProp) => {
.forEach((color) => { if (isString(widgetProp) && validateColor(widgetProp)) {
colors.add(color.toLowerCase()); colors.add(widgetProp);
}
}); });
});
return Array.from(colors) as Array<string>; return Array.from(colors) as Array<string>;
} }
/**
* validate color string
*
* @returns {boolean} true if string is valid color or includes url
* @param color
*/
export function isValidColor(color: string) {
return color?.includes("url") || validateColor(color);
}
/* /*
* Function to merge property pane config of a widget * Function to merge property pane config of a widget
* *

View File

@ -9589,6 +9589,7 @@ __metadata:
unescape-js: ^1.1.4 unescape-js: ^1.1.4
url-search-params-polyfill: ^8.0.0 url-search-params-polyfill: ^8.0.0
uuid: ^9.0.0 uuid: ^9.0.0
validate-color: ^2.2.4
webfontloader: ^1.6.28 webfontloader: ^1.6.28
webpack-merge: ^5.8.0 webpack-merge: ^5.8.0
webpack-retry-chunk-load-plugin: ^3.1.1 webpack-retry-chunk-load-plugin: ^3.1.1
@ -28934,6 +28935,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"validate-color@npm:^2.2.4":
version: 2.2.4
resolution: "validate-color@npm:2.2.4"
checksum: ac9109e6347797300fd0a0b08d623acaf3cc295cc2077b9041119582c632eb56dc78e94b8f7f929754508974a8a0b63d296995106a20b237b3be5e89ff2c80f1
languageName: node
linkType: hard
"validate-npm-package-license@npm:^3.0.1": "validate-npm-package-license@npm:^3.0.1":
version: 3.0.4 version: 3.0.4
resolution: "validate-npm-package-license@npm:3.0.4" resolution: "validate-npm-package-license@npm:3.0.4"