Implement CSS variables for admin color theming (#6409)

Co-authored-by: JNaftali <jmarantz@thelabnyc.com>
Co-authored-by: Thibaud Colas <thibaudcolas@gmail.com>
pull/6676/head
Joshua Marantz 2021-01-16 21:17:41 -05:00 zatwierdzone przez GitHub
rodzic 454002fbd7
commit 8e79c61564
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
15 zmienionych plików z 184 dodań i 25 usunięć

Wyświetl plik

@ -492,6 +492,7 @@ Contributors
* Bohreromir
* Fernando Cordeiro
* Matthias Rohmer
* Joshua Marantz
Translators
===========

Wyświetl plik

@ -8,3 +8,4 @@ No CSS should be produced by these files.
@import 'tools/mixins.fonts';
@import 'tools/mixins.general';
@import 'tools/mixins.grid';
@import 'tools/various.colors';

Wyświetl plik

@ -414,7 +414,6 @@ ul.listing {
&.bicolor {
background: $color-teal-darker;
border: solid 1px darken($color-teal-darker, 10%);
&:active {
color: $color-white;

Wyświetl plik

@ -37,7 +37,7 @@
&:hover {
color: $color-white;
border-top-color: darken($color-teal-darker, 8);
border-top-color: rgba(0, 0, 0, 0.35);
}
}

Wyświetl plik

@ -0,0 +1,8 @@
:root {
@include define-color('color-primary', #007d7e);
@include define-color('color-primary-darker', css-darken(css-adjust-hue(get-color('color-primary'), 1), 4%));
@include define-color('color-primary-dark', css-darken(css-adjust-hue(get-color('color-primary'), 1), 7%));
@include define-color('color-input-focus', css-lighten(css-desaturate(get-color('color-primary'), 40%), 72%));
@include define-color('color-input-focus-border', css-lighten(css-saturate(get-color('color-primary'), 12%), 10%));
}

Wyświetl plik

@ -28,9 +28,9 @@ $breakpoints: (
);
// colours
$color-teal: #007d7e;
$color-teal-darker: darken(adjust-hue($color-teal, 1), 4);
$color-teal-dark: darken(adjust-hue($color-teal, 1), 7);
$color-teal: var(--color-primary);
$color-teal-darker: var(--color-primary-darker);
$color-teal-dark: var(--color-primary-dark);
$color-blue: #71b2d4;
$color-red: #cd3238;
@ -59,8 +59,8 @@ $color-header-bg: $color-teal;
$color-fieldset-hover: $color-grey-5;
$color-input-border: $color-grey-4;
$color-input-focus: lighten(desaturate($color-teal, 40), 72);
$color-input-focus-border: lighten(saturate($color-teal, 12), 10);
$color-input-focus: var(--color-input-focus);
$color-input-focus-border: var(--color-input-focus-border);
$color-input-error-bg: lighten(saturate($color-red, 28), 45);
$color-button: $color-teal;

Wyświetl plik

@ -74,6 +74,7 @@ These are base styles for bare HTML elements.
@import 'elements/elements';
@import 'elements/typography';
@import 'elements/forms';
@import 'elements/root';
/* OBJECTS

Wyświetl plik

@ -0,0 +1,62 @@
// $color is either a color or an hsl tuple
@mixin define-color($name, $color) {
$h: null;
$s: null;
$l: null;
@if type-of($color) == color {
$h: hue($color) / 1deg; // Cast to unitless
$s: saturation($color);
$l: lightness($color);
} @else {
$h: nth($color, 1);
$s: nth($color, 2);
$l: nth($color, 3);
}
--#{$name}-hue: #{$h};
--#{$name}-saturation: #{$s};
--#{$name}-lightness: #{$l};
--#{$name}: hsl(#{ var(--#{$name}-hue), var(--#{$name}-saturation), var(--#{$name}-lightness) });
}
@function get-color($name) {
@return (var(--#{$name}-hue), var(--#{$name}-saturation), var(--#{$name}-lightness));
}
@function css-darken($hsl-tuple, $darken-by) {
$h: nth($hsl-tuple, 1);
$s: nth($hsl-tuple, 2);
$l: nth($hsl-tuple, 3);
@return ($h, $s, calc(#{$l} - #{$darken-by + 0%}));
}
@function css-lighten($hsl-tuple, $lighten-by) {
$h: nth($hsl-tuple, 1);
$s: nth($hsl-tuple, 2);
$l: nth($hsl-tuple, 3);
@return ($h, $s, calc(#{$l} + #{$lighten-by + 0%}));
}
@function css-saturate($hsl-tuple, $saturate-by) {
$h: nth($hsl-tuple, 1);
$s: nth($hsl-tuple, 2);
$l: nth($hsl-tuple, 3);
@return ($h, calc(#{$s} + #{$saturate-by + 0%}), $l);
}
@function css-desaturate($hsl-tuple, $desaturate-by) {
$h: nth($hsl-tuple, 1);
$s: nth($hsl-tuple, 2);
$l: nth($hsl-tuple, 3);
@return ($h, calc(#{$s} - #{$desaturate-by + 0%}), $l);
}
@function css-adjust-hue($hsl-tuple, $adjust-by) {
$h: nth($hsl-tuple, 1);
$s: nth($hsl-tuple, 2);
$l: nth($hsl-tuple, 3);
@return (calc(#{$h} + #{$adjust-by}), $s, $l);
}
@function css-transparentize($hsl-tuple, $alpha) {
$h: nth($hsl-tuple, 1);
$s: nth($hsl-tuple, 2);
$l: nth($hsl-tuple, 3);
@return ($h, $s, $l, $alpha);
}

Wyświetl plik

@ -2,7 +2,10 @@ $header-padding-vertical: 6px;
$action-font-size: 18px;
@import '../../../../node_modules/react-streamfield/src/scss/index';
@use '../../../../node_modules/react-streamfield/src/scss/index' with (
$teal: $color-teal,
$error-color: $color-red,
);
.c-sf-container {

Wyświetl plik

@ -91,6 +91,53 @@ To replace the welcome message on the dashboard, create a template file ``dashbo
{% block branding_welcome %}Welcome to Frank's Site{% endblock %}
.. _custom_user_interface_colors:
Custom user interface colors
============================
.. warning::
CSS variables are not supported in Internet Explorer, so the admin will appear with the default colors when viewed in that browser.
The default Wagtail colors conform to the WCAG2.1 AA level color contrast requirements. When customizing the admin colors you should test the contrast using tools like `Axe <https://www.deque.com/axe/browser-extensions/>`_.
To customize the primary color used in the admin user interface, inject a CSS file using the hook :ref:`insert_global_admin_css` and override the variables within the ``:root`` selector:
.. code-block:: text
:root {
--color-primary-hue: 25;
}
``color-primary`` is an `hsl color <https://en.wikipedia.org/wiki/HSL_and_HSV>`_ composed of 3 CSS variables - ``--color-primary-hue`` (0-360 with no unit), ``--color-primary-saturation`` (a percentage), and ``--color-primary-lightness`` (also a percentage). Separating the color into 3 allows us to calculate variations on the color to use alongside the primary color. If needed, you can also control those variations manually by setting ``hue``, ``saturation``, and ``lightness`` variables for the following colors: ``color-primary-darker``, ``color-primary-dark``, ``color-input-focus``, and ``color-input-focus-border``:
.. code-block:: text
:root {
--color-primary-hue: 25;
--color-primary-saturation: 100%;
--color-primary-lightness: 25%;
--color-primary-darker-hue: 24;
--color-primary-darker-saturation: 100%;
--color-primary-darker-lightness: 20%;
--color-primary-dark-hue: 23;
--color-primary-dark-saturation: 100%;
--color-primary-dark-lightness: 15%;
}
If instead you intend to set all available colors, you can use any valid css colors:
.. code-block:: text
:root {
--color-primary: mediumaquamarine;
--color-primary-darker: rebeccapurple;
--color-primary-dark: hsl(330, 100%, 70%);
--color-input-focus: rgb(204, 0, 102);
--color-input-focus-border: #4d0026;
}
Specifying a site or page in the branding
=========================================

Wyświetl plik

@ -21,6 +21,10 @@ In-place StreamField updating
StreamField values now formally support being updated in-place from Python code, allowing blocks to be inserted, modified and deleted rather than having to assign a new list of blocks to the field. For further details, see :ref:`modifying_streamfield_data`. This feature was developed by Matt Westcott.
Admin color themes
~~~~~~~~~~~~~~~~~~
Wagtails admin now uses CSS custom properties for its primary teal color. Applying brand colors for the whole user interface only takes a few lines of CSS, and third-party extensions can reuse Wagtails CSS variables to support the same degree of customization. Read on :ref:`custom_user_interface_colors`. This feature was developed by Joshua Marantz.
Other features
~~~~~~~~~~~~~~

Wyświetl plik

@ -4,6 +4,8 @@ var sass = require('gulp-dart-sass');
var postcss = require('gulp-postcss');
var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');
var postcssCustomProperties = require('postcss-custom-properties');
var postcssCalc = require('postcss-calc');
var sourcemaps = require('gulp-sourcemaps');
var size = require('gulp-size');
var config = require('../config');
@ -69,6 +71,8 @@ gulp.task('styles:sass', function () {
.pipe(postcss([
cssnano(cssnanoConfig),
autoprefixer(autoprefixerConfig),
postcssCustomProperties(),
postcssCalc(),
]))
.pipe(size({ title: 'Wagtail CSS' }))
.pipe(config.isProduction ? gutil.noop() : sourcemaps.write())

60
package-lock.json wygenerowano
Wyświetl plik

@ -3499,8 +3499,7 @@
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
},
"cssnano": {
"version": "4.1.10",
@ -7308,8 +7307,7 @@
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
"dev": true
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc="
},
"inflight": {
"version": "1.0.6",
@ -7654,6 +7652,12 @@
"unc-path-regex": "^0.1.2"
}
},
"is-url-superb": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz",
"integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==",
"dev": true
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -11778,7 +11782,6 @@
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
@ -11788,14 +11791,12 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
@ -11803,10 +11804,9 @@
}
},
"postcss-calc": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz",
"integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==",
"dev": true,
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz",
"integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==",
"requires": {
"postcss": "^7.0.27",
"postcss-selector-parser": "^6.0.2",
@ -11852,6 +11852,16 @@
}
}
},
"postcss-custom-properties": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-10.0.0.tgz",
"integrity": "sha512-55BPj5FudpCiPZzBaO+MOeqmwMDa+nV9/0QBJBfhZjYg6D9hE+rW9lpMBLTJoF4OTXnS5Po4yM1nMlgkPbCxFg==",
"dev": true,
"requires": {
"postcss": "^7.0.17",
"postcss-values-parser": "^4.0.0"
}
},
"postcss-discard-comments": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz",
@ -12355,7 +12365,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
"integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"indexes-of": "^1.0.1",
@ -12402,8 +12411,26 @@
"postcss-value-parser": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ=="
},
"postcss-values-parser": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-4.0.0.tgz",
"integrity": "sha512-R9x2D87FcbhwXUmoCXJR85M1BLII5suXRuXibGYyBJ7lVDEpRIdKZh4+8q5S+/+A4m0IoG1U5tFw39asyhX/Hw==",
"dev": true,
"requires": {
"color-name": "^1.1.4",
"is-url-superb": "^4.0.0",
"postcss": "^7.0.5"
},
"dependencies": {
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},
"prelude-ls": {
"version": "1.2.1",
@ -15389,8 +15416,7 @@
"uniq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
"dev": true
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8="
},
"uniqs": {
"version": "2.0.0",

Wyświetl plik

@ -73,6 +73,7 @@
"gulp-util": "~3.0.8",
"jest": "^26.6.0",
"npm-run-all": "^4.1.5",
"postcss-custom-properties": "^10.0.0",
"react-axe": "^3.1.0",
"react-test-renderer": "^16.13.1",
"redux-mock-store": "^1.3.0",
@ -89,6 +90,7 @@
"draftail": "^1.2.1",
"element-closest": "^2.0.2",
"focus-trap-react": "^3.1.0",
"postcss-calc": "^7.0.5",
"prop-types": "^15.6.2",
"react": "^16.4.0",
"react-dom": "^16.4.0",

Wyświetl plik

@ -1 +1,2 @@
@import 'wagtailadmin/scss/helpers';
@import '../../../../../../client/src/components/StreamField/StreamField';