diff --git a/README.md b/README.md index 6bad8d1..67806d0 100644 --- a/README.md +++ b/README.md @@ -382,6 +382,8 @@ Three things changed shape and are worth knowing if you already set variables: | `$skinBorderColor` / `$skinBorderColorDark` | ![#e0e4e9](docs/swatches/e0e4e9.svg) `#e0e4e9` / ![#404750](docs/swatches/404750.svg) `#404750` | borders / grid lines | | `$skinInputBgColor` / `$skinInputBgColorDark` | ![#ffffff](docs/swatches/ffffff.svg) `#ffffff` / ![#1e2227](docs/swatches/1e2227.svg) `#1e2227` | form control background | | `$skinInputBorderColor` / `$skinInputBorderColorDark` | ![#c9ced4](docs/swatches/c9ced4.svg) `#c9ced4` / ![#4d555f](docs/swatches/4d555f.svg) `#4d555f` | form control border | +| `$skinErrorTextColor` / `$skinErrorTextColorDark` | ![#932419](docs/swatches/932419.svg) `#932419` / ![#f49b9b](docs/swatches/f49b9b.svg) `#f49b9b` | validation messages | +| `$skinErrorBgColor` / `$skinErrorBgColorDark` | ![#fae6e4](docs/swatches/fae6e4.svg) `#fae6e4` / ![#3a2224](docs/swatches/3a2224.svg) `#3a2224` | error summary above a form | #### Header menu diff --git a/app/assets/stylesheets/wigu/active_admin_theme.scss b/app/assets/stylesheets/wigu/active_admin_theme.scss index 7928648..7d1984f 100644 --- a/app/assets/stylesheets/wigu/active_admin_theme.scss +++ b/app/assets/stylesheets/wigu/active_admin_theme.scss @@ -123,6 +123,11 @@ $skinLinkColorDark: #7cc0ec!default; // match); dark uses a lightened red, since the default red is too dark there. $skinDeleteLinkColor: $skinLinkColor!default; $skinDeleteLinkColorDark: #f49b9b!default; +// Validation errors: message text and summary block fill. +$skinErrorTextColor: #932419!default; +$skinErrorTextColorDark: #f49b9b!default; +$skinErrorBgColor: #fae6e4!default; +$skinErrorBgColorDark: #3a2224!default; // Tabs: fill and label colours per mode. yeti-web sets all four to make the // inactive tabs recede to the page background and the active one read as raised. $skinTabInactiveColor: #f7f9fb!default; @@ -186,6 +191,8 @@ $skinStatusTagErrorColor: #ce483b!default; // error / errored / red --aa-active-tab-text: #{$skinActiveTabTextColorDark}; --aa-inactive-tab-text: #{$skinInactiveTabTextColorDark}; --aa-delete-link: #{$skinDeleteLinkColorDark}; + --aa-error: #{$skinErrorTextColorDark}; + --aa-error-bg: #{$skinErrorBgColorDark}; } :root { color-scheme: light; @@ -217,6 +224,8 @@ $skinStatusTagErrorColor: #ce483b!default; // error / errored / red --aa-active-tab-text: #{$skinActiveTabTextColor}; --aa-inactive-tab-text: #{$skinInactiveTabTextColor}; --aa-delete-link: #{$skinDeleteLinkColor}; + --aa-error: #{$skinErrorTextColor}; + --aa-error-bg: #{$skinErrorBgColor}; // Bridge for the companion active_admin_datetimepicker plugin: feed its // --aa-datetimepicker-* inputs from this theme's palette, so the xdsoft picker @@ -337,6 +346,10 @@ $logout-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' skinLinkColorDark: $skinLinkColorDark, skinDeleteLinkColor: $skinDeleteLinkColor, skinDeleteLinkColorDark: $skinDeleteLinkColorDark, + skinErrorTextColor: $skinErrorTextColor, + skinErrorTextColorDark: $skinErrorTextColorDark, + skinErrorBgColor: $skinErrorBgColor, + skinErrorBgColorDark: $skinErrorBgColorDark, skinTabInactiveColor: $skinTabInactiveColor, skinTabInactiveColorDark: $skinTabInactiveColorDark, skinActiveTabTextColor: $skinActiveTabTextColor, @@ -1242,8 +1255,16 @@ body.active_admin { form fieldset > ol > li fieldset.fragments ol { margin-left: 20%; } -form fieldset ol > li.has_many_container h3 { - color: #8494a8; +form .has_many_container h3 { + color: var(--aa-text-muted); +} +form fieldset > ol > li p.inline-errors, +form fieldset > ol > li ul.errors, +form ul.errors { + color: var(--aa-error); +} +form ul.errors { + background: var(--aa-error-bg); } form input[type=submit], form input[type=button], form button, a.button, a:link.button, a:visited.button { background-image: none; diff --git a/docs/swatches/3a2224.svg b/docs/swatches/3a2224.svg new file mode 100644 index 0000000..bc35653 --- /dev/null +++ b/docs/swatches/3a2224.svg @@ -0,0 +1 @@ + diff --git a/docs/swatches/932419.svg b/docs/swatches/932419.svg new file mode 100644 index 0000000..84b4577 --- /dev/null +++ b/docs/swatches/932419.svg @@ -0,0 +1 @@ + diff --git a/docs/swatches/fae6e4.svg b/docs/swatches/fae6e4.svg new file mode 100644 index 0000000..ad19344 --- /dev/null +++ b/docs/swatches/fae6e4.svg @@ -0,0 +1 @@ + diff --git a/test/css_check.rb b/test/css_check.rb index 4afdbac..c1f0f84 100644 --- a/test/css_check.rb +++ b/test/css_check.rb @@ -61,6 +61,8 @@ module CssCheck "$skinStatusTagWarnColor: none" => '$skinStatusTagWarnColor: none;', "$skinStatusTagErrorColor: none" => '$skinStatusTagErrorColor: none;', "$skinStatusTagTextColor as a length" => '$skinStatusTagTextColor: 10px;', + "$skinErrorTextColor: none" => '$skinErrorTextColor: none;', + "$skinErrorBgColorDark: none" => '$skinErrorBgColorDark: none;', }.freeze # The variables table in the README is the public contract people configure @@ -242,6 +244,37 @@ def self.status_tag_labels_are_readable problems end + # The same bar for the other text the theme paints on a fill it also owns. + def self.message_pairs + { + "error_on_panel" => ["$skinErrorTextColor", "$skinSurfaceColor"], + "error_on_panel_dark" => ["$skinErrorTextColorDark", "$skinSurfaceColorDark"], + "error_summary" => ["$skinErrorTextColor", "$skinErrorBgColor"], + "error_summary_dark" => ["$skinErrorTextColorDark", "$skinErrorBgColorDark"], + "has_many_heading" => ["$skinTextMutedColor", "$skinPageBgColor"], + "has_many_heading_dark" => ["$skinTextMutedColorDark", "$skinPageBgColorDark"] + } + end + + def self.messages_are_readable + pairs = message_pairs + probe = pairs.flat_map { |name, (text, fill)| [["#{name}_text", text], ["#{name}_fill", fill]] }.map do |name, colour| + ".css-check-#{name} { r: red(#{colour}); g: green(#{colour}); b: blue(#{colour}); a: alpha(#{colour}); }" + end + colours = compile("", probe.join("\n")) + .scan(/\.css-check-(\w+)\s*\{\s*r:\s*([\d.]+);\s*g:\s*([\d.]+);\s*b:\s*([\d.]+);\s*a:\s*([\d.]+);?\s*\}/) + .to_h { |name, r, g, b, a| [name, { rgb: [r, g, b].map { |v| v.to_f.round }, alpha: a.to_f }] } + + pairs.keys.filter_map do |name| + text, fill = colours.fetch("#{name}_text"), colours.fetch("#{name}_fill") + next "#{name}: translucent, so the ratio cannot be measured" if text[:alpha] < 1 || fill[:alpha] < 1 + + ratio = contrast(text[:rgb], fill[:rgb]) + next if ratio >= LABEL_MINIMUM + "#{name}: #{hex(text[:rgb])} on #{hex(fill[:rgb])} is #{format("%.3f", ratio)}:1, under #{LABEL_MINIMUM}" + end + end + def self.hex(rgb) format("#%02x%02x%02x", *rgb) end @@ -317,6 +350,7 @@ def self.run # before the second ran — so a run could report one problem while holding # three, and each fix revealed the next. failures.concat(status_tag_labels_are_readable) + failures.concat(messages_are_readable) failures.concat(readme_table_matches_declarations) unless failures.empty?