Allow admins to set brand color (#10097)
* Allow admins to set brand color * Remove extra line * Fix linting * Update app/controllers/admin/configs_controller.rb Co-authored-by: Michael Kohl <citizen428@dev.to> * Move wcag compare to own class * Remove unnecessary spacing Co-authored-by: Michael Kohl <citizen428@dev.to>
This commit is contained in:
parent
cc7d297371
commit
7b5bc89b7c
12 changed files with 124 additions and 2 deletions
1
Gemfile
1
Gemfile
|
|
@ -108,6 +108,7 @@ gem "uglifier", "~> 4.2" # Uglifier minifies JavaScript files
|
|||
gem "ulid", "~> 1.2" # Universally Unique Lexicographically Sortable Identifier implementation for Ruby
|
||||
gem "validate_url", "~> 1.0" # Library for validating urls in Rails
|
||||
gem "vault", "~> 0.15" # Used to store secrets
|
||||
gem "wcag_color_contrast", "~> 0.1" # Detect contrast of colors to determine readability and a11y.
|
||||
gem "webpacker", "~> 5.2.1" # Use webpack to manage app-like JavaScript modules in Rails
|
||||
|
||||
group :development do
|
||||
|
|
|
|||
|
|
@ -815,6 +815,7 @@ GEM
|
|||
equalizer (~> 0.0, >= 0.0.9)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
wcag_color_contrast (0.1.0)
|
||||
web-console (4.0.4)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
|
|
@ -995,6 +996,7 @@ DEPENDENCIES
|
|||
validate_url (~> 1.0)
|
||||
vault (~> 0.15)
|
||||
vcr (~> 6.0)
|
||||
wcag_color_contrast (~> 0.1)
|
||||
web-console (~> 4.0)
|
||||
webdrivers (~> 4.4)
|
||||
webmock (~> 3.8)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
--brand-github-bg: #24292e;
|
||||
--brand-github-color: #fff;
|
||||
--brand-github-bg-hover: #000;
|
||||
|
||||
--brand-facebook-bg: #4267B2;
|
||||
--brand-facebook-color: #fff;
|
||||
--brand-facebook-bg-hover: #476fbf;
|
||||
}
|
||||
|
||||
// Basic styling
|
||||
|
|
@ -261,6 +265,18 @@
|
|||
--color-inverted-hover: var(--brand-github-color);
|
||||
}
|
||||
|
||||
.crayons-btn--brand-facebook {
|
||||
--bg: var(--brand-facebook-bg);
|
||||
--bg-hover: var(--brand-facebook-bg-hover);
|
||||
--color: var(--brand-facebook-color);
|
||||
--color-hover: var(--brand-facebook-color);
|
||||
--bg-inverted: var(--brand-facebook-bg);
|
||||
--bg-inverted-hover: var(--brand-facebook-bg-hover);
|
||||
--color-inverted: var(--brand-facebook-color);
|
||||
--color-inverted-hover: var(--brand-facebook-color);
|
||||
}
|
||||
|
||||
|
||||
// Icon alone
|
||||
.crayons-btn--icon,
|
||||
.crayons-btn--icon-rounded {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ module Admin
|
|||
layout "admin"
|
||||
|
||||
before_action :extra_authorization_and_confirmation, only: [:create]
|
||||
before_action :validate_inputs, only: [:create]
|
||||
|
||||
def show
|
||||
@confirmation_text = confirmation_text
|
||||
|
|
@ -54,6 +55,7 @@ module Admin
|
|||
facebook_key
|
||||
facebook_secret
|
||||
allow_email_password_registration
|
||||
primary_brand_color_hex
|
||||
]
|
||||
|
||||
allowed_params = allowed_params |
|
||||
|
|
@ -87,6 +89,13 @@ module Admin
|
|||
raise_confirmation_mismatch_error if params.require(:confirmation) != confirmation_text
|
||||
end
|
||||
|
||||
def validate_inputs
|
||||
errors = []
|
||||
errors << "Brand color must be darker for accessibility." if brand_contrast_too_low
|
||||
errors << "Brand color must be be a 6 character hex (starting with #)." if brand_color_not_hex
|
||||
redirect_to admin_config_path, alert: "😭 #{errors.join(',')}" if errors.any?
|
||||
end
|
||||
|
||||
def clean_up_params
|
||||
config = params[:site_config]
|
||||
%i[sidebar_tags suggested_tags suggested_users].each do |param|
|
||||
|
|
@ -103,6 +112,17 @@ module Admin
|
|||
Rails.cache.delete_matched(ApplicationConfig["RELEASE_FOOTPRINT"]) # Delete all caches tied to this key.
|
||||
end
|
||||
|
||||
# Validations
|
||||
def brand_contrast_too_low
|
||||
hex = params[:site_config][:primary_brand_color_hex]
|
||||
hex.present? && Color::Accessibility.new(hex).low_contrast?
|
||||
end
|
||||
|
||||
def brand_color_not_hex
|
||||
hex = params[:site_config][:primary_brand_color_hex]
|
||||
hex.present? && !hex.match?(/\A#(\h{6}|\h{3})\z/)
|
||||
end
|
||||
|
||||
def campaign_params
|
||||
%i[
|
||||
campaign_featured_tags
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ module Constants
|
|||
description: "Determines which default feed the users sees (rich content, more minimal, etc.)",
|
||||
placeholder: "basic, rich, or compact"
|
||||
},
|
||||
primary_brand_color_hex: {
|
||||
description: "Determines background/border of buttons etc. Must be dark enough to contrast with white text.",
|
||||
placeholder: "#0a0a0a"
|
||||
},
|
||||
github_key: {
|
||||
description: "The \"Client ID\" portion of the GitHub Oauth Apps portal",
|
||||
placeholder: ""
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ class SiteConfig < RailsSettings::Base
|
|||
field :public, type: :boolean, default: 0
|
||||
# The default font for all users that have not chosen a custom font yet
|
||||
field :default_font, type: :string, default: "sans_serif"
|
||||
field :primary_brand_color_hex, type: :string, default: "#3b49df"
|
||||
|
||||
# Broadcast
|
||||
field :welcome_notifications_live_at, type: :date
|
||||
|
|
|
|||
11
app/services/color/accessibility.rb
Normal file
11
app/services/color/accessibility.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module Color
|
||||
class Accessibility
|
||||
def initialize(hex)
|
||||
@hex = hex.delete("#")
|
||||
end
|
||||
|
||||
def low_contrast?(compared_color = "ffffff", min_contrast = 4.5)
|
||||
WCAGColorContrast.ratio(@hex, compared_color.delete("#")) < min_contrast
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -931,7 +931,7 @@
|
|||
<div class="card mt-3">
|
||||
<%= render partial: "card_header",
|
||||
locals: {
|
||||
header: "User Experience",
|
||||
header: "User Experience and Brand",
|
||||
state: "collapse",
|
||||
target: "uxBodyContainer",
|
||||
expanded: "false"
|
||||
|
|
@ -956,6 +956,15 @@
|
|||
class: "form-control selectpicker" %>
|
||||
<div class="alert alert-info"><%= Constants::SiteConfig::DETAILS[:default_font][:description] %></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :primary_brand_color_hex %>
|
||||
<%= f.text_field :primary_brand_color_hex,
|
||||
class: "form-control",
|
||||
value: SiteConfig.primary_brand_color_hex,
|
||||
pattern: "^#+([a-fA-F0-9]{6})$",
|
||||
placeholder: Constants::SiteConfig::DETAILS[:primary_brand_color_hex][:placeholder] %>
|
||||
<div class="alert alert-info"><%= Constants::SiteConfig::DETAILS[:primary_brand_color_hex][:description] %></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= admin_config_label :public %>
|
||||
<%= f.check_box :public, checked: SiteConfig.public %>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,16 @@
|
|||
class="<%= SiteConfig.default_font.tr("_", "-") %>-article-body"
|
||||
data-pusher-key="<%= ApplicationConfig["PUSHER_KEY"] %>"
|
||||
data-app-name="<%= ApplicationConfig["APP_NAME"] %>">
|
||||
<div id="body-styles"></div>
|
||||
<div id="body-styles">
|
||||
<style>
|
||||
:root {
|
||||
--accent-brand: <%= SiteConfig.primary_brand_color_hex %>;
|
||||
--accent-brand-darker: <%= HexComparer.new([SiteConfig.primary_brand_color_hex]).brightness(0.85) %>;
|
||||
--accent-brand-lighter: <%= HexComparer.new([SiteConfig.primary_brand_color_hex]).brightness(1.1) %>;
|
||||
--accent-brand-a10: #{rgba(<%= SiteConfig.primary_brand_color_hex %>, 0.1)};
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<% if user_signed_in? %>
|
||||
<%= render "layouts/user_config" %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -589,6 +589,28 @@ RSpec.describe "/admin/config", type: :request do
|
|||
expect(SiteConfig.feed_style).to eq(feed_style)
|
||||
end
|
||||
|
||||
it "updates the brand color if proper hex" do
|
||||
hex = "#0a0a0a" # dark enough
|
||||
post "/admin/config", params: { site_config: { primary_brand_color_hex: hex },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.primary_brand_color_hex).to eq(hex)
|
||||
end
|
||||
|
||||
it "does not update brand color if hex not contrasting enough" do
|
||||
hex = "#bd746f" # not dark enough
|
||||
post "/admin/config", params: { site_config: { primary_brand_color_hex: hex },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.primary_brand_color_hex).not_to eq(hex)
|
||||
end
|
||||
|
||||
it "does not update brand color if hex not a hex with proper format" do
|
||||
hex = "0a0a0a" # dark enough, but not proper format
|
||||
post "/admin/config", params: { site_config: { primary_brand_color_hex: hex },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.primary_brand_color_hex).not_to eq(hex)
|
||||
end
|
||||
|
||||
|
||||
it "updates public to true" do
|
||||
is_public = true
|
||||
post "/admin/config", params: { site_config: { public: is_public },
|
||||
|
|
|
|||
27
spec/services/color/accessibility_spec.rb
Normal file
27
spec/services/color/accessibility_spec.rb
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe Color::Accessibility, type: :service do
|
||||
it "determines low contrast with default compared color" do
|
||||
expect(described_class.new("#8a9bb8").low_contrast?).to be true
|
||||
end
|
||||
|
||||
it "determines low contrast with compared color input" do
|
||||
expect(described_class.new("#041d4a").low_contrast?("#1a3669")).to be true
|
||||
end
|
||||
|
||||
it "determines low contrast with compared color and rate" do
|
||||
expect(described_class.new("#225CC9").low_contrast?("#ffffff", 10.0)).to be true
|
||||
end
|
||||
|
||||
it "determines sufficient contrast with default compared color" do
|
||||
expect(described_class.new("#041d4a").low_contrast?).to be false
|
||||
end
|
||||
|
||||
it "determines sufficient contrast with compared color input" do
|
||||
expect(described_class.new("#041d4a").low_contrast?("#e6eaf0")).to be false
|
||||
end
|
||||
|
||||
it "determines sufficient contrast with compared color and rate" do
|
||||
expect(described_class.new("#225CC9").low_contrast?("#ffffff", 1.0)).to be false
|
||||
end
|
||||
end
|
||||
BIN
vendor/cache/wcag_color_contrast-0.1.0.gem
vendored
Normal file
BIN
vendor/cache/wcag_color_contrast-0.1.0.gem
vendored
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue