mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 13:06:56 +08:00
FEATURE: auto contrast text color for categories (#32015)
This change removes the foreground color category setting to simplify the category creation and edit process for admins. Instead we determine the highest contrasting color (either white or black) based on the background color. Contrast algorithm is based on: https://www.w3.org/TR/AERT/#color-contrast We also implement the value transformer as part of this change, which allows overriding the category text color.
This commit is contained in:
@ -63,7 +63,7 @@ export default class DStyles extends Component {
|
||||
css.push(
|
||||
`.badge-category[data-category-id="${category.id}"] { ` +
|
||||
`--category-badge-color: var(--category-${category.id}-color); ` +
|
||||
`--category-badge-text-color: #${category.text_color}; ` +
|
||||
`--category-badge-text-color: #${category.textColor}; ` +
|
||||
`}`
|
||||
);
|
||||
|
||||
|
@ -27,7 +27,6 @@ export default class EditCategoryGeneral extends Component {
|
||||
customizeTextContentLink = getURL(
|
||||
"/admin/customize/site_texts?q=uncategorized"
|
||||
);
|
||||
foregroundColors = ["FFFFFF", "000000"];
|
||||
|
||||
get styleTypes() {
|
||||
return Object.keys(CATEGORY_STYLE_TYPES).map((key) => ({
|
||||
@ -306,31 +305,6 @@ export default class EditCategoryGeneral extends Component {
|
||||
</field.Custom>
|
||||
</@form.Field>
|
||||
{{/unless}}
|
||||
|
||||
<@form.Field
|
||||
@name="text_color"
|
||||
@title={{i18n "category.foreground_color"}}
|
||||
@format="full"
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<div class="category-color-editor">
|
||||
<div class="colorpicker-wrapper edit-text-color">
|
||||
<ColorInput
|
||||
@hexValue={{readonly field.value}}
|
||||
@ariaLabelledby="foreground-color-label"
|
||||
@onChangeColor={{fn this.updateColor field}}
|
||||
/>
|
||||
<ColorPicker
|
||||
@colors={{this.foregroundColors}}
|
||||
@value={{readonly field.value}}
|
||||
@ariaLabel={{i18n "category.predefined_colors"}}
|
||||
@onSelectColor={{fn this.updateColor field}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</field.Custom>
|
||||
</@form.Field>
|
||||
</@form.Section>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -40,6 +40,7 @@ export default class EditCategoryTabsController extends Controller {
|
||||
expandedMenu = false;
|
||||
parentParams = null;
|
||||
validators = [];
|
||||
textColors = ["000000", "FFFFFF"];
|
||||
|
||||
@and("showTooltip", "model.cannot_delete_reason") showDeleteReason;
|
||||
|
||||
@ -120,6 +121,8 @@ export default class EditCategoryTabsController extends Controller {
|
||||
}
|
||||
|
||||
this.model.setProperties(transientData);
|
||||
this.setTextColor(this.model.color);
|
||||
|
||||
this.set("saving", true);
|
||||
|
||||
this.model
|
||||
@ -184,4 +187,15 @@ export default class EditCategoryTabsController extends Controller {
|
||||
goBack() {
|
||||
DiscourseURL.routeTo(this.model.url);
|
||||
}
|
||||
|
||||
@action
|
||||
setTextColor(backgroundColor) {
|
||||
const r = parseInt(backgroundColor.substr(0, 2), 16);
|
||||
const g = parseInt(backgroundColor.substr(2, 2), 16);
|
||||
const b = parseInt(backgroundColor.substr(4, 2), 16);
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
const color = brightness >= 128 ? this.textColors[0] : this.textColors[1];
|
||||
|
||||
this.model.set("text_color", color);
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,16 @@ export default function categoryVariables(category) {
|
||||
vars += `--category-badge-color: #${category.color};`;
|
||||
}
|
||||
|
||||
if (category.text_color) {
|
||||
vars += `--category-badge-text-color: #${category.text_color};`;
|
||||
if (category.textColor) {
|
||||
vars += `--category-badge-text-color: #${category.textColor};`;
|
||||
}
|
||||
|
||||
if (category.parentCategory?.color) {
|
||||
vars += `--parent-category-badge-color: #${category.parentCategory.color};`;
|
||||
}
|
||||
|
||||
if (category.parentCategory?.text_color) {
|
||||
vars += `--parent-category-badge-text-color: #${category.parentCategory.text_color};`;
|
||||
if (category.parentCategory?.textColor) {
|
||||
vars += `--parent-category-badge-text-color: #${category.parentCategory.textColor};`;
|
||||
}
|
||||
|
||||
return htmlSafe(vars);
|
||||
|
@ -15,6 +15,7 @@ export const VALUE_TRANSFORMERS = Object.freeze([
|
||||
"category-available-views",
|
||||
"category-description-text",
|
||||
"category-display-name",
|
||||
"category-text-color",
|
||||
"composer-service-cannot-submit-post",
|
||||
"header-notifications-avatar-size",
|
||||
"home-logo-href",
|
||||
|
@ -497,6 +497,16 @@ export default class Category extends RestModel {
|
||||
});
|
||||
}
|
||||
|
||||
get textColor() {
|
||||
return applyValueTransformer(
|
||||
"category-text-color",
|
||||
this.get("text_color"),
|
||||
{
|
||||
category: this,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@computed("parent_category_id", "site.categories.[]")
|
||||
get parentCategory() {
|
||||
if (this.parent_category_id) {
|
||||
|
@ -26,8 +26,6 @@ acceptance("Category Edit", function (needs) {
|
||||
await fillIn("input.category-name", "testing");
|
||||
assert.dom(".category-style .badge-category__name").hasText("testing");
|
||||
|
||||
await fillIn(".edit-text-color input", "ff0000");
|
||||
|
||||
await click(".edit-category-topic-template a");
|
||||
await fillIn(".d-editor-input", "this is the new topic template");
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { click, currentURL, fillIn, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import sinon from "sinon";
|
||||
import { cloneJSON } from "discourse/lib/object";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { fixturesByUrl } from "discourse/tests/helpers/create-pretender";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import { i18n } from "discourse-i18n";
|
||||
@ -99,6 +101,59 @@ acceptance("Category New", function (needs) {
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Category text color", function (needs) {
|
||||
needs.user();
|
||||
needs.pretender((server, helper) => {
|
||||
const category = cloneJSON(fixturesByUrl["/c/11/show.json"]).category;
|
||||
|
||||
server.get("/c/testing/find_by_slug.json", () => {
|
||||
return helper.response(200, {
|
||||
category: {
|
||||
...category,
|
||||
color: "EEEEEE",
|
||||
text_color: "000000",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("Category text color is set based on contrast", async function (assert) {
|
||||
await visit("/new-category");
|
||||
|
||||
let previewTextColor = document
|
||||
.querySelector(".category-style .badge-category__wrapper")
|
||||
.style.getPropertyValue("--category-badge-text-color")
|
||||
.trim();
|
||||
|
||||
assert.strictEqual(
|
||||
previewTextColor,
|
||||
"#FFFFFF",
|
||||
"has the default text color"
|
||||
);
|
||||
|
||||
await fillIn("input.category-name", "testing");
|
||||
await fillIn(".category-color-editor .hex-input", "EEEEEE");
|
||||
await click("#save-category");
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
"/c/testing/edit/general",
|
||||
"it transitions to the category edit route"
|
||||
);
|
||||
|
||||
previewTextColor = document
|
||||
.querySelector(".category-style .badge-category__wrapper")
|
||||
.style.getPropertyValue("--category-badge-text-color")
|
||||
.trim();
|
||||
|
||||
assert.strictEqual(
|
||||
previewTextColor,
|
||||
"#000000",
|
||||
"sets the contrast text color"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("New category preview", function (needs) {
|
||||
needs.user({ admin: true });
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
acceptance("category-text-color transformer", function () {
|
||||
test("applying a value transformation", async function (assert) {
|
||||
withPluginApi("1.34.0", (api) => {
|
||||
api.registerValueTransformer("category-text-color", () => "FF0000");
|
||||
});
|
||||
|
||||
await visit("/");
|
||||
|
||||
const element = document.querySelector(
|
||||
"[data-topic-id='11994'] .badge-category__wrapper"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
window
|
||||
.getComputedStyle(element)
|
||||
.getPropertyValue("--category-badge-text-color"),
|
||||
"#FF0000",
|
||||
"it transforms the category text color"
|
||||
);
|
||||
});
|
||||
});
|
@ -4147,7 +4147,6 @@ en:
|
||||
background_image_dark: "Dark Category Background Image"
|
||||
style: "Styles"
|
||||
background_color: "Color"
|
||||
foreground_color: "Foreground color"
|
||||
styles:
|
||||
type: "Style"
|
||||
icon: "Icon"
|
||||
|
Reference in New Issue
Block a user