diff --git a/Gemfile b/Gemfile index 50715b048..046c6f72c 100644 --- a/Gemfile +++ b/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 diff --git a/Gemfile.lock b/Gemfile.lock index f4c5ee119..a2f6da871 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/stylesheets/components/buttons.scss b/app/assets/stylesheets/components/buttons.scss index e1a2d1699..37f585369 100644 --- a/app/assets/stylesheets/components/buttons.scss +++ b/app/assets/stylesheets/components/buttons.scss @@ -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 { diff --git a/app/controllers/admin/configs_controller.rb b/app/controllers/admin/configs_controller.rb index dd9d5fb53..0d8d11a47 100644 --- a/app/controllers/admin/configs_controller.rb +++ b/app/controllers/admin/configs_controller.rb @@ -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 diff --git a/app/lib/constants/site_config.rb b/app/lib/constants/site_config.rb index a44af0be1..c4448ebef 100644 --- a/app/lib/constants/site_config.rb +++ b/app/lib/constants/site_config.rb @@ -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: "" diff --git a/app/models/site_config.rb b/app/models/site_config.rb index 764627dd1..dafee7215 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -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 diff --git a/app/services/color/accessibility.rb b/app/services/color/accessibility.rb new file mode 100644 index 000000000..a2c381e77 --- /dev/null +++ b/app/services/color/accessibility.rb @@ -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 diff --git a/app/views/admin/configs/show.html.erb b/app/views/admin/configs/show.html.erb index 05f1afd09..ba24a205b 100644 --- a/app/views/admin/configs/show.html.erb +++ b/app/views/admin/configs/show.html.erb @@ -931,7 +931,7 @@