mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 01:16:38 -06:00
FEATURE: WCAG compliant color schemes (#10882)
Co-authored-by: Kris <kris.aubuchon@discourse.org>
This commit is contained in:
parent
d68ad82a9e
commit
5763309953
307
app/assets/stylesheets/wcag.scss
Normal file
307
app/assets/stylesheets/wcag.scss
Normal file
@ -0,0 +1,307 @@
|
||||
// Overrides for WCAG color schemes only
|
||||
|
||||
// Global
|
||||
|
||||
::placeholder {
|
||||
color: var(--primary-medium);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.discourse-no-touch {
|
||||
.btn-default,
|
||||
.btn-icon {
|
||||
&.btn-default {
|
||||
.d-icon {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
}
|
||||
&:hover,
|
||||
&.btn-hover {
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-primary .d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
.btn-icon.ok,
|
||||
.btn-icon.cancel,
|
||||
.btn-danger {
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
.btn-flat.delete.d-hover {
|
||||
background: var(--danger);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-pills > li > a:not(.active):hover {
|
||||
background: var(--tertiary-low);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
// Composer
|
||||
|
||||
#reply-control .reply-to .reply-details .d-icon {
|
||||
opacity: 1;
|
||||
color: var(--primary-low-mid);
|
||||
}
|
||||
|
||||
.d-editor-button-bar {
|
||||
.btn-icon.btn-default .d-icon {
|
||||
color: var(--primary-low-mid);
|
||||
.discourse-no-touch & {
|
||||
&:hover {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Site header
|
||||
|
||||
.menu-panel li a.widget-link:hover,
|
||||
.menu-panel li a.widget-link:focus,
|
||||
.menu-panel li.heading a.widget-link:hover,
|
||||
.menu-panel li.heading a.widget-link:focus {
|
||||
color: var(--primary);
|
||||
background-color: var(--highlight-medium);
|
||||
.d-icon {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-panel .panel-body-bottom .btn:hover {
|
||||
.d-icon {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.d-header-icons .d-icon {
|
||||
color: var(--primary-low-mid);
|
||||
}
|
||||
|
||||
.d-header-icons .icon:hover .d-icon,
|
||||
.d-header-icons .icon:focus .d-icon {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
|
||||
.d-header-icons .unread-notifications {
|
||||
background: var(--tertiary);
|
||||
}
|
||||
|
||||
// Topic list
|
||||
|
||||
table th {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
|
||||
.coldmap {
|
||||
&-high {
|
||||
color: #6c77cc !important;
|
||||
}
|
||||
|
||||
&-med {
|
||||
color: #548eaa !important;
|
||||
}
|
||||
|
||||
&-low {
|
||||
color: #32a1a5 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.heatmap-high,
|
||||
.heatmap-high a {
|
||||
color: #dc3249 !important;
|
||||
}
|
||||
.heatmap-med,
|
||||
.heatmap-med a {
|
||||
color: #ae5b54 !important;
|
||||
}
|
||||
.heatmap-low,
|
||||
.heatmap-low a {
|
||||
color: #8f6d5b !important;
|
||||
}
|
||||
|
||||
.badge-notification {
|
||||
background: var(--primary-medium);
|
||||
}
|
||||
|
||||
.badge-notification.new-posts,
|
||||
.badge-notification.unread-posts {
|
||||
background: var(--tertiary);
|
||||
}
|
||||
.select-kit.dropdown-select-box.period-chooser
|
||||
.period-chooser-header
|
||||
h2.selected-name
|
||||
.top-date-string,
|
||||
.select-kit.dropdown-select-box.period-chooser
|
||||
.period-chooser-row
|
||||
.top-date-string {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
|
||||
// Posts
|
||||
|
||||
.discourse-no-touch .topic-body .actions .fade-out {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.topic-body .reply-to-tab {
|
||||
color: var(--primary-medium);
|
||||
.d-icon {
|
||||
color: var(--primary-low-mid);
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-container .topic-timeline {
|
||||
.timeline-scrollarea {
|
||||
border-color: var(--primary-low-mid);
|
||||
}
|
||||
.timeline-handle {
|
||||
background: var(--primary-low-mid);
|
||||
}
|
||||
}
|
||||
|
||||
.topic-map h4 {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.quote-controls,
|
||||
.quote-controls .d-icon {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
a,
|
||||
a:visited {
|
||||
color: var(--tertiary);
|
||||
}
|
||||
}
|
||||
.meta .d-icon + .filename,
|
||||
.meta .informations {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
#topic-footer-buttons .bookmark.bookmarked:hover .d-icon-bookmark {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.gap {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
|
||||
.badge-notification.clicks {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
|
||||
.topic-map {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// Post controls
|
||||
|
||||
nav.post-controls {
|
||||
// this is a bit tedious
|
||||
a,
|
||||
button {
|
||||
color: var(--primary-medium);
|
||||
.d-icon {
|
||||
color: var(--primary-low-mid);
|
||||
}
|
||||
.discourse-no-touch & {
|
||||
&:hover {
|
||||
color: var(--secondary);
|
||||
background: var(--primary-medium);
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
background: var(--primary-medium);
|
||||
}
|
||||
}
|
||||
.discourse-no-touch & {
|
||||
.double-button:hover {
|
||||
button {
|
||||
background: var(--primary-medium);
|
||||
color: var(--secondary);
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
&.has-like {
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
button.bookmark.bookmarked.d-hover .d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
.double-button button.button-count + .toggle-like.d-hover {
|
||||
background: var(--primary-medium);
|
||||
.d-icon {
|
||||
color: var(--love-low);
|
||||
}
|
||||
}
|
||||
.discourse-no-touch & {
|
||||
.double-button button.button-count.d-hover {
|
||||
background: var(--primary-medium);
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
button.create {
|
||||
.d-icon {
|
||||
color: var(--primary-low-mid);
|
||||
}
|
||||
&.d-hover {
|
||||
color: var(--secondary);
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
.actions a,
|
||||
.actions button {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
}
|
||||
|
||||
nav.post-controls
|
||||
.actions
|
||||
.double-button
|
||||
button.button-count
|
||||
+ .toggle-like.d-hover {
|
||||
background: var(--primary-medium);
|
||||
}
|
||||
|
||||
.topic-admin-menu-button-container,
|
||||
.timeline-controls {
|
||||
.btn .d-icon {
|
||||
// admin wrenches
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
}
|
||||
|
||||
// Categories
|
||||
|
||||
.list-cell,
|
||||
.table-heading,
|
||||
.category-list td,
|
||||
.category-list th {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
|
||||
// Admin
|
||||
|
||||
.admin-controls {
|
||||
.nav-pills > li > a:not(.active):hover {
|
||||
background: var(--primary-medium);
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
@ -94,6 +94,37 @@ class ColorScheme < ActiveRecord::Base
|
||||
"danger" => '6c3e63',
|
||||
"success" => 'd9b2bb',
|
||||
"love" => 'd9b2bb'
|
||||
},
|
||||
"WCAG": {
|
||||
"primary" => '000000',
|
||||
"primary-medium" => '696969',
|
||||
"primary-low-mid" => '909090',
|
||||
"secondary" => 'ffffff',
|
||||
"tertiary" => '3369FF',
|
||||
"quaternary" => '3369FF',
|
||||
"header_background" => 'ffffff',
|
||||
"header_primary" => '000000',
|
||||
"highlight" => '3369FF',
|
||||
"highlight-high" => '0036E6',
|
||||
"highlight-medium" => 'e0e9ff',
|
||||
"highlight-low" => 'e0e9ff',
|
||||
"danger" => 'BB1122',
|
||||
"success" => '3d854d',
|
||||
"love" => '9D256B'
|
||||
},
|
||||
"WCAG Dark": {
|
||||
"primary" => 'ffffff',
|
||||
"primary-medium" => '999999',
|
||||
"primary-low-mid" => '888888',
|
||||
"secondary" => '0c0c0c',
|
||||
"tertiary" => '759AFF',
|
||||
"quaternary" => '759AFF',
|
||||
"header_background" => '000000',
|
||||
"header_primary" => 'ffffff',
|
||||
"highlight" => '3369FF',
|
||||
"danger" => 'BB1122',
|
||||
"success" => '3d854d',
|
||||
"love" => '9D256B'
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,6 +338,10 @@ class ColorScheme < ActiveRecord::Base
|
||||
primary_b > secondary_b
|
||||
end
|
||||
|
||||
def is_wcag?
|
||||
base_scheme_id&.start_with?('WCAG')
|
||||
end
|
||||
|
||||
# Equivalent to dc-color-brightness() in variables.scss
|
||||
def brightness(color)
|
||||
rgb = color.scan(/../).map { |c| c.to_i(16) }
|
||||
|
@ -4058,6 +4058,12 @@ en:
|
||||
primary:
|
||||
name: "primary"
|
||||
description: "Most text, icons, and borders."
|
||||
primary-medium:
|
||||
name: "primary-medium"
|
||||
description: ""
|
||||
primary-low-mid:
|
||||
name: "primary-low-mid"
|
||||
description: ""
|
||||
secondary:
|
||||
name: "secondary"
|
||||
description: "The main background color, and text color of some buttons."
|
||||
@ -4076,6 +4082,15 @@ en:
|
||||
highlight:
|
||||
name: "highlight"
|
||||
description: "The background color of highlighted elements on the page, such as posts and topics."
|
||||
highlight-high:
|
||||
name: "highlight-high"
|
||||
description: ""
|
||||
highlight-medium:
|
||||
name: "highlight-medium"
|
||||
description: ""
|
||||
highlight-low:
|
||||
name: "highlight-low"
|
||||
description: ""
|
||||
danger:
|
||||
name: "danger"
|
||||
description: "Highlight color for actions like deleting posts and topics."
|
||||
|
@ -3923,6 +3923,10 @@ en:
|
||||
latte: "Latte"
|
||||
summer: "Summer"
|
||||
dark_rose: "Dark Rose"
|
||||
wcag: "WCAG Light"
|
||||
wcag_theme_name: "WCAG Light"
|
||||
wcag_dark: "WCAG Dark"
|
||||
wcag_dark_theme_name: "WCAG Dark"
|
||||
default_theme_name: "Default"
|
||||
light_theme_name: "Light"
|
||||
dark_theme_name: "Dark"
|
||||
|
@ -4,9 +4,16 @@
|
||||
if !Theme.exists?
|
||||
STDERR.puts "> Seeding theme and color schemes"
|
||||
|
||||
name = I18n.t("color_schemes.dark_theme_name")
|
||||
dark_scheme = ColorScheme.find_by(base_scheme_id: "Dark")
|
||||
dark_scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: "Dark", user_selectable: true)
|
||||
color_schemes = [
|
||||
{ name: I18n.t("color_schemes.dark"), base_scheme_id: "Dark" },
|
||||
{ name: I18n.t("color_schemes.wcag"), base_scheme_id: "WCAG" },
|
||||
{ name: I18n.t("color_schemes.wcag_dark"), base_scheme_id: "WCAG Dark" }
|
||||
]
|
||||
|
||||
color_schemes.each do |cs|
|
||||
scheme = ColorScheme.find_by(base_scheme_id: cs[:base_scheme_id])
|
||||
scheme ||= ColorScheme.create_from_base(name: cs[:name], via_wizard: true, base_scheme_id: cs[:base_scheme_id], user_selectable: true)
|
||||
end
|
||||
|
||||
name = I18n.t('color_schemes.default_theme_name')
|
||||
default_theme = Theme.create!(name: name, user_id: -1)
|
||||
|
@ -22,6 +22,7 @@ module Stylesheet
|
||||
|
||||
if asset.to_s == Stylesheet::Manager::COLOR_SCHEME_STYLESHEET
|
||||
file += Stylesheet::Importer.import_color_definitions(options[:theme_id])
|
||||
file += Stylesheet::Importer.import_wcag_overrides(options[:color_scheme_id])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -191,6 +191,13 @@ module Stylesheet
|
||||
contents
|
||||
end
|
||||
|
||||
def self.import_wcag_overrides(color_scheme_id)
|
||||
if color_scheme_id && ColorScheme.find_by_id(color_scheme_id)&.is_wcag?
|
||||
return "@import \"wcag\";"
|
||||
end
|
||||
""
|
||||
end
|
||||
|
||||
def initialize(options)
|
||||
@theme = options[:theme]
|
||||
@theme_id = options[:theme_id]
|
||||
|
@ -187,6 +187,17 @@ describe Stylesheet::Importer do
|
||||
styles = Stylesheet::Importer.import_color_definitions(nil)
|
||||
expect(styles).to include(scss)
|
||||
end
|
||||
end
|
||||
|
||||
context "#import_wcag_overrides" do
|
||||
it "should do nothing on a regular scheme" do
|
||||
scheme = ColorScheme.create_from_base(name: 'Regular')
|
||||
expect(Stylesheet::Importer.import_wcag_overrides(scheme.id)).to eq("")
|
||||
end
|
||||
|
||||
it "should include WCAG overrides for WCAG based scheme" do
|
||||
scheme = ColorScheme.create_from_base(name: 'WCAG New', base_scheme_id: "WCAG Dark")
|
||||
expect(Stylesheet::Importer.import_wcag_overrides(scheme.id)).to eq("@import \"wcag\";")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -102,4 +102,14 @@ describe ColorScheme do
|
||||
expect(scheme.is_dark?).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe "is_wcag?" do
|
||||
it "works as expected" do
|
||||
expect(ColorScheme.create_from_base(name: 'Nope').is_wcag?).to eq(nil)
|
||||
expect(ColorScheme.create_from_base(name: 'Nah', base_scheme_id: "Dark").is_wcag?).to eq(false)
|
||||
|
||||
expect(ColorScheme.create_from_base(name: 'Yup', base_scheme_id: "WCAG").is_wcag?).to eq(true)
|
||||
expect(ColorScheme.create_from_base(name: 'Yup', base_scheme_id: "WCAG Dark").is_wcag?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -32,14 +32,19 @@ describe SiteSerializer do
|
||||
it "includes user-selectable color schemes" do
|
||||
# it includes seeded color schemes
|
||||
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
|
||||
expect(serialized[:user_color_schemes].count).to eq(1)
|
||||
expect(serialized[:user_color_schemes].count).to eq(3)
|
||||
|
||||
dark_scheme = ColorScheme.create_from_base(name: "ADarkScheme", base_scheme_id: "Dark")
|
||||
scheme_names = serialized[:user_color_schemes].map { |x| x[:name] }
|
||||
expect(scheme_names).to include(I18n.t("color_schemes.dark"))
|
||||
expect(scheme_names).to include(I18n.t("color_schemes.wcag"))
|
||||
expect(scheme_names).to include(I18n.t("color_schemes.wcag_dark"))
|
||||
|
||||
dark_scheme = ColorScheme.create_from_base(name: "AnotherDarkScheme", base_scheme_id: "Dark")
|
||||
dark_scheme.user_selectable = true
|
||||
dark_scheme.save!
|
||||
|
||||
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
|
||||
expect(serialized[:user_color_schemes].count).to eq(2)
|
||||
expect(serialized[:user_color_schemes].count).to eq(4)
|
||||
expect(serialized[:user_color_schemes][0][:is_dark]).to eq(true)
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user