1
0
mirror of https://github.com/flarum/core.git synced 2025-08-02 22:47:33 +02:00

fix(tags): tag text color contrast (#3653)

* add yiq calculator util
* use the new contast util to differentiate light/dark tags
* fix: invert logic
* feat: add tag-dark and tag-light less config
* fix: convert 3 chars hex to 6 chars hex
* fix: rename import
* fix: clarify util name
* fix: rename function
* fix: invert less variables when dark mode is enabled
* fix: TagTiles contrast
* refactor: simplify logic with a unique variable
* refactor: simplify logic with a unique variable
* feat: add text color variables not depending on the dark/light mode
* refactor: use isDark rather than getContrast
* refactor: change getContrast to isDark with for a more direct approach
* fix: adjust snippet description
* refactor: change getContrast to isDark with for a more direct approach
* fix: adjust snippet description
* fix: TagHero contrast
* fix: DiscussionHero contrast
* fix: newDiscussion contrast
* fix(newDiscussion): restore less rule when tag is not colored
* fix: TagTiles description
* fix: TagTiles last posted
* chore: change `var` to `let`
* refactor: keep it for backwards compatibility
* refactor: keep it for backwards compatibility
* Apply suggestions from code review
* fix: missed this when I was resolving
* fix: remove dist files from pull request
* Revert "Resolved merge conflict"
This reverts commit c7f0d14aa8, reversing
changes made to 6753dfc2af.
* fix: missed this when I was resolving
* fix
* Update isDark.ts
* chore: flexible contrast color fixing
* refactor(isDark): clarify the doc block
* fix(isDark): increase the yiq threshold
* typo
* fix: preserve design coloring through light and dark modes

Co-authored-by: David Wheatley <david@davwheat.dev>
Co-authored-by: Sami Mazouz <sychocouldy@gmail.com>
This commit is contained in:
ornanovitch
2023-01-17 20:45:03 +01:00
committed by GitHub
parent 6adae00f72
commit 4d292263b5
16 changed files with 75 additions and 30 deletions

View File

@@ -1,5 +1,7 @@
import extract from 'flarum/common/utils/extract'; import extract from 'flarum/common/utils/extract';
import Link from 'flarum/common/components/Link'; import Link from 'flarum/common/components/Link';
import classList from 'flarum/common/utils/classList';
import textContrastClass from 'flarum/common/helpers/textContrastClass';
import tagIcon from './tagIcon'; import tagIcon from './tagIcon';
export default function tagLabel(tag, attrs = {}) { export default function tagLabel(tag, attrs = {}) {
@@ -13,7 +15,7 @@ export default function tagLabel(tag, attrs = {}) {
const color = tag.color(); const color = tag.color();
if (color) { if (color) {
attrs.style['--tag-bg'] = color; attrs.style['--tag-bg'] = color;
attrs.className += ' colored'; attrs.className = classList(attrs.className, 'colored', textContrastClass(color));
} }
if (link) { if (link) {

View File

@@ -5,6 +5,7 @@ import IndexPage from 'flarum/forum/components/IndexPage';
import DiscussionListState from 'flarum/forum/states/DiscussionListState'; import DiscussionListState from 'flarum/forum/states/DiscussionListState';
import GlobalSearchState from 'flarum/forum/states/GlobalSearchState'; import GlobalSearchState from 'flarum/forum/states/GlobalSearchState';
import classList from 'flarum/common/utils/classList'; import classList from 'flarum/common/utils/classList';
import textContrastClass from 'flarum/common/helpers/textContrastClass';
import TagHero from './components/TagHero'; import TagHero from './components/TagHero';
import Tag from '../common/models/Tag'; import Tag from '../common/models/Tag';
@@ -90,7 +91,7 @@ export default function () {
const newDiscussion = items.get('newDiscussion') as Mithril.Vnode<ComponentAttrs, {}>; const newDiscussion = items.get('newDiscussion') as Mithril.Vnode<ComponentAttrs, {}>;
if (color) { if (color) {
newDiscussion.attrs.className = classList([newDiscussion.attrs.className, 'Button--tagColored']); newDiscussion.attrs.className = classList([newDiscussion.attrs.className, 'Button--tagColored', textContrastClass(color)]);
newDiscussion.attrs.style = { '--color': color }; newDiscussion.attrs.style = { '--color': color };
} }

View File

@@ -1,6 +1,8 @@
import { extend } from 'flarum/common/extend'; import { extend } from 'flarum/common/extend';
import DiscussionListItem from 'flarum/forum/components/DiscussionListItem'; import DiscussionListItem from 'flarum/forum/components/DiscussionListItem';
import DiscussionHero from 'flarum/forum/components/DiscussionHero'; import DiscussionHero from 'flarum/forum/components/DiscussionHero';
import textContrastClass from 'flarum/common/helpers/textContrastClass';
import classList from 'flarum/common/utils/classList';
import tagsLabel from '../common/helpers/tagsLabel'; import tagsLabel from '../common/helpers/tagsLabel';
import sortTags from '../common/utils/sortTags'; import sortTags from '../common/utils/sortTags';
@@ -23,7 +25,7 @@ export default function () {
const color = tags[0].color(); const color = tags[0].color();
if (color) { if (color) {
view.attrs.style = { '--hero-bg': color }; view.attrs.style = { '--hero-bg': color };
view.attrs.className += ' DiscussionHero--colored'; view.attrs.className = classList(view.attrs.className, 'DiscussionHero--colored', textContrastClass(color));
} }
} }
}); });

View File

@@ -1,5 +1,7 @@
import Component from 'flarum/common/Component'; import Component from 'flarum/common/Component';
import textContrastClass from 'flarum/common/helpers/textContrastClass';
import tagIcon from '../../common/helpers/tagIcon'; import tagIcon from '../../common/helpers/tagIcon';
import classList from 'flarum/common/utils/classList';
export default class TagHero extends Component { export default class TagHero extends Component {
view() { view() {
@@ -7,7 +9,10 @@ export default class TagHero extends Component {
const color = tag.color(); const color = tag.color();
return ( return (
<header className={'Hero TagHero' + (color ? ' TagHero--colored' : '')} style={color ? { '--hero-bg': color } : ''}> <header
className={classList('Hero', 'TagHero', { 'TagHero--colored': color }, textContrastClass(color))}
style={color ? { '--hero-bg': color } : ''}
>
<div className="container"> <div className="container">
<div className="containerNarrow"> <div className="containerNarrow">
<h2 className="Hero-title"> <h2 className="Hero-title">

View File

@@ -4,6 +4,8 @@ import Link from 'flarum/common/components/Link';
import LoadingIndicator from 'flarum/common/components/LoadingIndicator'; import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
import listItems from 'flarum/common/helpers/listItems'; import listItems from 'flarum/common/helpers/listItems';
import humanTime from 'flarum/common/helpers/humanTime'; import humanTime from 'flarum/common/helpers/humanTime';
import textContrastClass from 'flarum/common/helpers/textContrastClass';
import classList from 'flarum/common/utils/classList';
import tagIcon from '../../common/helpers/tagIcon'; import tagIcon from '../../common/helpers/tagIcon';
import tagLabel from '../../common/helpers/tagLabel'; import tagLabel from '../../common/helpers/tagLabel';
@@ -58,7 +60,7 @@ export default class TagsPage extends Page {
const children = sortTags(tag.children() || []); const children = sortTags(tag.children() || []);
return ( return (
<li className={'TagTile ' + (tag.color() ? 'colored' : '')} style={{ '--tag-bg': tag.color() }}> <li className={classList('TagTile', { colored: tag.color() }, textContrastClass(tag.color()))} style={{ '--tag-bg': tag.color() }}>
<Link className="TagTile-info" href={app.route.tag(tag)}> <Link className="TagTile-info" href={app.route.tag(tag)}>
{tag.icon() && tagIcon(tag, {}, { useColor: false })} {tag.icon() && tagIcon(tag, {}, { useColor: false })}
<h3 className="TagTile-name">{tag.name()}</h3> <h3 className="TagTile-name">{tag.name()}</h3>

View File

@@ -16,7 +16,7 @@
} }
&.colored { &.colored {
--tag-color: @body-bg; --tag-color: var(--contrast-color, var(--body-bg));
.TagLabel-text { .TagLabel-text {
color: var(--tag-color) !important; color: var(--tag-color) !important;
@@ -31,13 +31,13 @@
&.colored { &.colored {
--tag-color: var(--tag-bg); --tag-color: var(--tag-bg);
margin-right: 5px; margin-right: 5px;
background: @body-bg !important; background-color: var(--contrast-color, var(--body-bg));
} }
} }
} }
.DiscussionHero--colored { .DiscussionHero--colored {
&, a { &, a {
color: @body-bg; color: var(--contrast-color, var(--body-bg));
} }
} }
.TagsLabel { .TagsLabel {

View File

@@ -9,6 +9,7 @@
--button-primary-bg: var(--color); --button-primary-bg: var(--color);
--button-primary-bg-hover: var(--color); --button-primary-bg-hover: var(--color);
--button-primary-bg-active: var(--color); --button-primary-bg-active: var(--color);
--button-color: var(--contrast-color);
} }
.DiscussionHero { .DiscussionHero {
.item-title { .item-title {

View File

@@ -1,5 +1,5 @@
.TagHero { .TagHero {
&--colored { &--colored {
--hero-color: #fff; --hero-color: var(--body-bg);
} }
} }

View File

@@ -64,7 +64,7 @@
} }
&.colored { &.colored {
&, a { &, a {
color: @body-bg; color: var(--contrast-color, var(--body-bg));
} }
} }
} }
@@ -100,10 +100,7 @@
.TagTile-description { .TagTile-description {
font-size: 12px; font-size: 12px;
margin: 0 0 10px; margin: 0 0 10px;
opacity: 70%;
.TagTile.colored & {
color: fade(@body-bg, 70%);
}
} }
.TagTile-children { .TagTile-children {
font-weight: bold; font-weight: bold;
@@ -124,10 +121,7 @@
font-size: 12px; font-size: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.15); border-top: 1px solid rgba(0, 0, 0, 0.15);
margin: 0 20px; margin: 0 20px;
opacity: 70%;
.TagTile.colored & {
color: fade(@body-bg, 70%);
}
&:hover .TagTile-lastPostedDiscussion-title { &:hover .TagTile-lastPostedDiscussion-title {
text-decoration: underline; text-decoration: underline;

View File

@@ -81,6 +81,7 @@ import highlight from './helpers/highlight';
import username from './helpers/username'; import username from './helpers/username';
import userOnline from './helpers/userOnline'; import userOnline from './helpers/userOnline';
import listItems from './helpers/listItems'; import listItems from './helpers/listItems';
import textContrastClass from './helpers/textContrastClass';
import Fragment from './Fragment'; import Fragment from './Fragment';
import DefaultResolver from './resolvers/DefaultResolver'; import DefaultResolver from './resolvers/DefaultResolver';
import PaginatedListState from './states/PaginatedListState'; import PaginatedListState from './states/PaginatedListState';
@@ -175,6 +176,7 @@ export default {
'helpers/username': username, 'helpers/username': username,
'helpers/userOnline': userOnline, 'helpers/userOnline': userOnline,
'helpers/listItems': listItems, 'helpers/listItems': listItems,
'helpers/textContrastClass': textContrastClass,
'resolvers/DefaultResolver': DefaultResolver, 'resolvers/DefaultResolver': DefaultResolver,
'states/PaginatedListState': PaginatedListState, 'states/PaginatedListState': PaginatedListState,
'states/AlertManagerState': AlertManagerState, 'states/AlertManagerState': AlertManagerState,

View File

@@ -0,0 +1,5 @@
import isDark from '../utils/isDark';
export default function textContrastClass(hexcolor: string): string {
return isDark(hexcolor) ? 'text-contrast--light' : 'text-contrast--dark';
}

View File

@@ -1,11 +1,13 @@
/** /**
* The `isDark` utility converts a hex color to rgb, and then calcul a YIQ * The `isDark` utility converts a hex color to rgb, and then calculates a YIQ
* value in order to get the appropriate brightness value (is it dark or is it * value in order to get the appropriate brightness value. See
* light?) See https://www.w3.org/TR/AERT/#color-contrast for references. A YIQ * https://www.w3.org/TR/AERT/#color-contrast for references.
* value >= 128 is a light color. *
* A YIQ value >= 128 corresponds to a light color according to the W3C
* standards, but we use a custom threshold for each light and dark modes
* to preserve design consistency.
*/ */
export default function isDark(hexcolor: string): boolean {
export default function isDark(hexcolor: String) {
let hexnumbers = hexcolor.replace('#', ''); let hexnumbers = hexcolor.replace('#', '');
if (hexnumbers.length == 3) { if (hexnumbers.length == 3) {
@@ -17,5 +19,7 @@ export default function isDark(hexcolor: String) {
const b = parseInt(hexnumbers.substr(4, 2), 16); const b = parseInt(hexnumbers.substr(4, 2), 16);
const yiq = (r * 299 + g * 587 + b * 114) / 1000; const yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 128 ? false : true; const threshold = parseInt(window.getComputedStyle(document.body).getPropertyValue('--yiq-threshold'));
return yiq < threshold;
} }

View File

@@ -30,6 +30,19 @@
--error-color: @error-color; --error-color: @error-color;
// ---------------------------------
// UTILITIES
--text-on-dark: @text-on-dark;
--text-on-light: @text-on-light;
& when (@config-dark-mode = true) {
--yiq-threshold: 108;
}
& when (@config-dark-mode = false) {
--yiq-threshold: 138;
}
// --------------------------------- // ---------------------------------
// COMPONENTS // COMPONENTS

View File

@@ -159,3 +159,14 @@ blockquote ol:last-child {
white-space: nowrap; white-space: nowrap;
width: 1px; width: 1px;
} }
.text-contrast {
&--dark {
--contrast-color: var(--text-on-light);
color: var(--contrast-color);
}
&--light {
--contrast-color: var(--text-on-dark);
color: var(--contrast-color);
}
}

View File

@@ -15,6 +15,9 @@
@secondary-hue: hue(@secondary-color); @secondary-hue: hue(@secondary-color);
@secondary-sat: saturation(@secondary-color); @secondary-sat: saturation(@secondary-color);
@body-bg-light: #fff;
@body-bg-dark: hsl(@secondary-hue, min(20%, @secondary-sat), 10%);
// Derive the primary/secondary colors from the config variables. In dark mode, // Derive the primary/secondary colors from the config variables. In dark mode,
// we make the user-set colors a bit darker, otherwise they will stand out too // we make the user-set colors a bit darker, otherwise they will stand out too
// much. // much.
@@ -23,7 +26,7 @@
@primary-color: @config-primary-color; @primary-color: @config-primary-color;
@secondary-color: @config-secondary-color; @secondary-color: @config-secondary-color;
@body-bg: #fff; @body-bg: @body-bg-light;
@text-color: #111; @text-color: #111;
@link-color: saturate(@primary-color, 10%); @link-color: saturate(@primary-color, 10%);
@heading-color: @text-color; @heading-color: @text-color;
@@ -45,7 +48,7 @@
@primary-color: @config-primary-color; @primary-color: @config-primary-color;
@secondary-color: @config-secondary-color; @secondary-color: @config-secondary-color;
@body-bg: hsl(@secondary-hue, min(20%, @secondary-sat), 10%); @body-bg: @body-bg-dark;
@text-color: #ddd; @text-color: #ddd;
@link-color: saturate(@primary-color, 10%); @link-color: saturate(@primary-color, 10%);
@heading-color: @text-color; @heading-color: @text-color;
@@ -68,8 +71,8 @@
// of the parents element's background. This allow not to change the color // of the parents element's background. This allow not to change the color
// variable depending on the dark/light mode to get the same final color, and // variable depending on the dark/light mode to get the same final color, and
// thus to simplify the logic. // thus to simplify the logic.
@text-on-dark: #ddd; @text-on-dark: @body-bg-light;
@text-on-light: #111; @text-on-light: @body-bg-dark;
@hero-bg: @control-bg; @hero-bg: @control-bg;
@hero-color: @control-color; @hero-color: @control-color;

View File

@@ -2,7 +2,7 @@
margin-top: -1px; margin-top: -1px;
background: var(--hero-bg); background: var(--hero-bg);
text-align: center; text-align: center;
color: var(--hero-color); color: var(--contrast-color, var(--hero-color));
h2 { h2 {
margin: 0; margin: 0;