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 commitc7f0d14aa8
, reversing changes made to6753dfc2af
. * 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:
@@ -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) {
|
||||||
|
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -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">
|
||||||
|
@@ -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>
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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 {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
.TagHero {
|
.TagHero {
|
||||||
&--colored {
|
&--colored {
|
||||||
--hero-color: #fff;
|
--hero-color: var(--body-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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,
|
||||||
|
@@ -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';
|
||||||
|
}
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user