linagora / esn-frontend-common-libs

Common ground for OpenPaaS frontend (https://open-paas.org)
Other
4 stars 12 forks source link

Use CSS variables to get back theme colors #51

Closed MichaelBailly closed 4 years ago

MichaelBailly commented 4 years ago

Details: https://ci.linagora.com/linagora/lgs/openpaas/esn/-/wikis/Week-by-Week-Activity#theming-css-variables Ask @romaricmourgues to woth with him

itswillta commented 4 years ago

This is what I imagine what will happen when we use CSS Variables:

  1. On the frontend side, each SPA will have to call to the /api/themes/:domainId endpoint to get the theme of the current domain.
  2. After getting all the theme variables from the endpoint, we append a style block to define the CSS Variables, e.g.:
:root {
  --primary-color: #223c50;
  --accent-color: #401b32;
  --body-bg-color: #f7f7f7;
  --text-primary-color: #ffffff;
  --text-color: #333333;
}
  1. If all the CSS files that particular SPA uses have already been reconfigured to use CSS Variables instead of LESS Variables, e.g.:
@media (min-width: @screen-md-min) {
  .calendar-sidebar-title {
    border-left: 5px solid var(--primary-color);
    margin-bottom: 5px;
  }
}

then all the CSS styles will use the correct CSS Variables that are defined in the second step.

The most time-consuming should be the third step because you'll have to do that with every LESS file of every single SPA that previously uses LESS Variables, but I suppose we don't have any other way, right?

However, if we implement theming this way, the users are likely to face the flash of "unthemed" content (similar to the flash of unstyled content - FOUC), where they will see the default theme for a brief moment and then the current theme after the request to get the current theme has succeeded. I'll try and see how bad it is, and maybe we can resort to displaying a loading screen while waiting for the current theme. We can also mitigate it by using some sort of cache (e.g. using Local Storage), so most of the time the users will only experience a flash of unthemed content the first time they load the app.

RomaricMourgues commented 4 years ago

Hi, what we do on Twake is loading the theme on the first app call, and we don't display the app while the theme is not loaded. I don't know if you also get languages from the backend but it's the same process (wait for it to display page).

About replacing all less variables to css variables, you can also put css variable as value of less variables like this:

@primary: var(--black);
//Sass $primary: var(--black);

On Twake we had a single file with all less variables, we duplicated the less variables, transformed them into css variables with a regex, and update the remaining less variables values (the one we copied) to use the new css variables as values.

:root {
  --primary: #837dff;
}
@primary:        var(--primary);
//Sass $primary: var(--primary);

Of course it depends on your architecture and the number of variable you have. (and a big search-and-replace using regex can also work I suppose)

(Edit this is for Sass not Less, I'm fixing this) (Edit 2, ok fixed it should work like this, and may be the whole process is a bit obvious in the end and not adapted in your case :) )

chamerling commented 4 years ago

Hello,

The way to avoid 'unthemed' issue is to do everything we need during the loading phase. As you can see, in each SPA, there is the OP loader ("Application is loading" message). In the code, this message is displayed until we get back what we call the session: The user data + the domain data. We can easily add steps here, one will be the theme data.

Le jeu. 16 juil. 2020 à 08:17, Romaric Mourgues notifications@github.com a écrit :

Hi, what we do on Twake is loading the theme on the first app call, and we don't display the app while the theme is not loaded. I don't know if you also get languages from the backend but it's the same process (wait for it to display page).

About replacing all less variables to css variables, you can also put css variable as value of less variables like this:

$black: var(--black);

On Twake we had a single file with all less variables, we duplicated the less variables, transformed them into css variables with a regex, and update the remaining less variables values (the one we copied) to use the new css variables as values.

:root { --primary: #837dff; } $primary: var(--primary);

Of course it depends on your architecture and the number of variable you have. (and a big search-and-replace using regex can also work I suppose)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/OpenPaaS-Suite/esn/issues/7#issuecomment-659183892, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACARU372NPCPZFYZWZ4HU3R32LN7ANCNFSM4OUJ5JFA .

itswillta commented 4 years ago

Yes. But here's the problem: LESS functions (e.g. darken, lighten) don't understand CSS Variables.

:root {
  --primary-color: #ff0000;
}

@primaryColor: var(--primary-color); // valid

.whatever {
  color: @primaryColor; // valid
}

.trouble {
  color: darken(@primaryColor, 10%); // invalid
}

From what I read in its documentation, those functions mainly revolve around HSL/HSLA color. So we'll need to convert HEX values to HSL/HSLA. Hopefully, if you have a better way, please converse with me. For now I'll just go with converting HEX to HSLA.

itswillta commented 4 years ago

Well I take it back. LESS functions just don't work with CSS Variables (https://github.com/badlydrawnrob/print-first-css/issues/29). Since there are like thousands of places where LESS functions are used, it's not quite possible to manually convert them all into something that can work with CSS Variables. So I'll need to find another solution.

Edit: Investigating into using LESS plugins that can override those default functions to work with CSS Variables.

itswillta commented 4 years ago

Mh... Even using LESS plugins can only solve half of the problem. The other half of the problem is that we need to also convert the other LESS variables that depend on the "root" theme variables (primary color, accent color, body background color, text primary color and text color) at runtime (on the client side).

You can look at the example below and the comments to understand what the problem is:

:root {
  --primary-color: #223c50;
}

// This should work without any problem. This will appear on the client side as something like `var(--primary-color, #2196F3)`
@primaryColor: var(--primary-color, lighten(@m-blue, 20%));

// This line won't work without the plugin that I'm writing.
@subHeaderBackground: darken(@primaryColor, 4%);
// However, with the plugin, I'm going to get it transformed into this:
// @subHeaderBackground: var(--primary-color-darken-4-percent);

As you can see, on the client side, I'll have to actually define --primary-color-darken-4-percent based on its name (getting the primary color and then darkening it by 4%).

However, I find this approach a little bit brute-forcing. There'll probably be hundreds of CSS variables such as --primary-color-darken-4-percent, --accent-color-lighten-10-percent, --text-primary-color-fade-5-percent, etc. Let's name this solution Solution No.1.

Edit: Hold on, I might have actually found a better way to do this, but I'll have to test it out first. The idea is something like this:

:root {
  --primary-color-h: 0;
  --primary-color-s: 100%;
  --primary-color-l: 50%;
}

@primaryColor: hsl(var(--primary-color-h), var(--primary-color-s), var(--primary-color-l));

@subHeaderBackground: darken(@primaryColor, 4%); 
// The above will be transformed into this: 
// @subHeaderBackground: hsl(var(--primary-color-h), var(--primary-color-s), calc(var(--primary-color-l) - 4%))

@subHeaderBackground2: lighten(@subHeaderBackground, 5%);
// The above will be transformed into this: 
// @subHeaderBackground2: hsl(var(--primary-color-h), var(--primary-color-s), calc(var(--primary-color-l) + 1%))

Let's name this solution Solution No.2.

RomaricMourgues commented 4 years ago

Interesting solution No.2, on Twake we used the solution No.1 but we have less customisation and less colors variables.

If I can suggest a third solution, may be this is already your idea of implementation of the solution no.2:

  1. The server send a limited number of colors (like primary-color and secondary-color), in hexa (or rgb)

  2. On the frontend, and for each color, you generate with JS primary-color-a, primary-color-b, and primary-color-c. I'm sure it shouldn't be too hard to convert hexa and rgb to hsl (https://gist.github.com/mjackson/5311256).

If you can do that, you have the best of the two sides, a limited number of colors and variable from the server (and better structured than separating the 3 parts everytime) and you can use the darken/lighten feature.

itswillta commented 4 years ago

Yes the solution you suggest is what I have in mind to implement Solution No.2. The most time-consuming part now will be to write a plugin to automatically transform all those darken/lighten/fade functions accordingly, because doing it manually is near impossible. Hopefully there won't be another problem arising when I actually get to the implementation details.

itswillta commented 4 years ago

OK, so the plugin worked. Now I just have to pull all of them altogether.

Screenshot from 2020-07-17 14-54-44