Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,13 @@ set variables:
| `$skinTabInactiveColor` / `$skinTabInactiveColorDark` | `#f7f9fb` / `#161a1e` | inactive tab fill |
| `$skinActiveTabTextColor` / `$skinActiveTabTextColorDark` | `$skinMainSecondColor` / `#7cc0ec` | selected tab label |
| `$skinInactiveTabTextColor` / `$skinInactiveTabTextColorDark` | `#5e6469` / `#b0b8c2` | inactive tab label |
| `$skinTableHeaderTextColor` / `$skinTableHeaderTextColorDark` | `#5e6469` / `#dde2e8` | index-table column header text |
| `$skinStatusTagTextColor` | `#ffffff` | status tag label; `#000000` passes WCAG AA on every fill |
| `$skinTableHeaderTextColor` / `$skinTableHeaderTextColorDark` | `$skinTextColor` / `#dde2e8` | index-table column header text; the body text colour, so headings read as strongly as the rows |
| `$skinStatusTagTextColor` | `#ffffff` | label inside every status tag |
| `$skinStatusTagNeutralColor` | `#707681` | unclassified tags: `No`, protocol tags |
| `$skinStatusTagOkColor` | `#5e7e63` | `ok` `published` `complete` `completed` `green` `yes` |
| `$skinStatusTagNoticeColor` | `#3874d2` | `notice` `blue` |
| `$skinStatusTagWarnColor` | `#9e6c15` | `warn` `warning` `orange` |
| `$skinStatusTagErrorColor` | `#ce483b` | `error` `errored` `red` |
| `$skinTabPaddingY` | `8px` | tab height |
| `$skinTabPaddingX` | `15px` | tab label horizontal padding (text → border) |

Expand Down
72 changes: 61 additions & 11 deletions app/assets/stylesheets/wigu/active_admin_theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,20 @@ $skinActiveTabTextColorDark: #7cc0ec!default;
$skinInactiveTabTextColor: #5e6469!default;
$skinInactiveTabTextColorDark: #b0b8c2!default;
// Index-table column header text, one colour for sortable and plain headers.
$skinTableHeaderTextColor: #5e6469!default;
$skinTableHeaderTextColor: $skinTextColor!default;
$skinTableHeaderTextColorDark: #dde2e8!default;
// Status tag label, the same on every filled tag in both modes.

// Status tags. The label is the same on every filled tag in both modes. The
// fills below are dark enough to carry a white one: every one of the five is
// at least 4.53 against it. The mid-tone fills this palette replaces ran from
// 2.35 to 3.78, under the 4.5 WCAG AA asks of text this small, which is why
// they moved rather than the label.
$skinStatusTagTextColor: #ffffff!default;
Comment thread
Copilot marked this conversation as resolved.
$skinStatusTagNeutralColor: #707681!default; // "No", protocol tags, anything unclassified
$skinStatusTagOkColor: #5e7e63!default; // ok / published / complete / green / yes
$skinStatusTagNoticeColor: #3874d2!default; // notice / blue
$skinStatusTagWarnColor: #9e6c15!default; // warn / warning / orange
$skinStatusTagErrorColor: #ce483b!default; // error / errored / red

//DARK-MODE PALETTE---------------------------------------------------------------------------------------------------//
// Semantic CSS custom properties for runtime light/dark switching. Light values
Expand Down Expand Up @@ -304,7 +314,12 @@ $theme-icon-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/s
skinInactiveTabTextColorDark: $skinInactiveTabTextColorDark,
skinTableHeaderTextColor: $skinTableHeaderTextColor,
skinTableHeaderTextColorDark: $skinTableHeaderTextColorDark,
skinStatusTagTextColor: $skinStatusTagTextColor
skinStatusTagTextColor: $skinStatusTagTextColor,
skinStatusTagNeutralColor: $skinStatusTagNeutralColor,
skinStatusTagOkColor: $skinStatusTagOkColor,
skinStatusTagNoticeColor: $skinStatusTagNoticeColor,
skinStatusTagWarnColor: $skinStatusTagWarnColor,
skinStatusTagErrorColor: $skinStatusTagErrorColor
) {
@if type-of($value) != color {
@error "$#{$name} must be a color (use `transparent`, not `none`), got `#{$value}`.";
Expand Down Expand Up @@ -879,6 +894,42 @@ body.active_admin {
font-weight: bold!important;
}
}
// ActiveAdmin puts the sort arrow inside the heading link, on the left, as a
// background image cleared with `padding-left: 13px`. That indents the label
// of every sortable column by 13px while the data below starts at the cell's
// own padding, so a heading never lines up with its column. The link is also
// `display: block`, so moving the image to the right edge would park the
// arrow at the far side of the column instead of beside the text.
//
// Drawn as a pseudo-element instead: it follows the label immediately, the
// link keeps its full width so the whole cell stays clickable, and the
// marker takes currentColor — the stock sprite is a fixed grey PNG that
// cannot follow the text into dark mode.
th.sortable a {
padding-left: 0;
background-image: none;

&:after {
content: "";
display: inline-block;
margin-left: 6px;
vertical-align: middle;
border: 4px solid transparent;
border-top-color: currentColor;
// 0.6, not lower: the marker is the only thing separating a sortable
// heading from a plain one, so WCAG 1.4.11 asks 3:1 of it. Against the
// header fill it gives 3.40 light and 4.22 dark; at 0.4 it was 2.13
// and 2.73.
opacity: 0.6;
}
}
th.sorted-asc a:after {
border-top-color: transparent;
border-bottom-color: currentColor;
margin-bottom: 4px;
opacity: 1;
}
th.sorted-desc a:after { opacity: 1; }
// Right edge = a single 1px line on the last-column cells (header th, body
// td, footer cells) coloured like the table border, since the table itself
// no longer draws a right border.
Expand Down Expand Up @@ -1418,9 +1469,8 @@ input[type='radio'] {
@mixin status-tag-colors($c) {
background: $c;
border-color: mix($c, #000000, 84%); // = darken($c) — outline in the fill colour
// The five fills below are mid-tone: white lands between 2.35 and 3.78
// against them, under the 4.5 WCAG AA asks for text this small. Set
// $skinStatusTagTextColor: #000000 for at least 5.56 on every one of them.
// The fills are dark enough to carry this label: every one of the five is at
// least 4.53 against white. Lighten one and the label needs to go with it.
color: $skinStatusTagTextColor;
}

Expand Down Expand Up @@ -1453,11 +1503,11 @@ input[type='radio'] {
border-radius: 2px;
border: 1px solid;

@include status-tag-colors(#8a909a); // neutral default (e.g. "No", protocol tags)
&.ok, &.published, &.complete, &.completed, &.green, &.yes { @include status-tag-colors(#8daa92); }
&.notice, &.blue { @include status-tag-colors(#6090db); }
&.warn, &.warning, &.orange { @include status-tag-colors(#e29b20); }
&.error, &.errored, &.red { @include status-tag-colors(#d45f53); }
@include status-tag-colors($skinStatusTagNeutralColor);
&.ok, &.published, &.complete, &.completed, &.green, &.yes { @include status-tag-colors($skinStatusTagOkColor); }
&.notice, &.blue { @include status-tag-colors($skinStatusTagNoticeColor); }
&.warn, &.warning, &.orange { @include status-tag-colors($skinStatusTagWarnColor); }
&.error, &.errored, &.red { @include status-tag-colors($skinStatusTagErrorColor); }
&.empty, &.unknown, &.none {
background: none;
border: 0; // placeholder/empty tags stay unobtrusive (no outline)
Expand Down
Binary file modified img/dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/inputs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/switch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 56 additions & 12 deletions test/css_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module CssCheck
"black status tag labels" => '$skinStatusTagTextColor: #000000;',
"repainted palette" => '$skinPageBgColor: #fafafa; $skinSurfaceColor: #ffffff;
$skinTextColor: #202020; $skinLinkColor: #0b5;',
"status tags recoloured" => '$skinStatusTagOkColor: #1f7a3a; $skinStatusTagTextColor: #f5f5f5;',
}.freeze

# Wrong-typed overrides. All of these are legal SassScript, so without the
Expand All @@ -47,6 +48,7 @@ module CssCheck
"$skinLinkColorDark: none" => '$skinLinkColorDark: none;',
"$skinPanelHeaderColor as a length" => '$skinPanelHeaderColor: 10px;',
"$skinStatusTagTextColor: none" => '$skinStatusTagTextColor: none;',
"$skinStatusTagOkColor: none" => '$skinStatusTagOkColor: none;',
}.freeze

# The variables table in the README is the public contract people configure
Expand All @@ -56,28 +58,64 @@ module CssCheck
def self.readme_table_matches_declarations
scss = File.read(File.join(STYLESHEETS, "wigu/active_admin_theme.scss"))
declared = {}
duplicates = []
scss.scan(/(\$skin[A-Za-z0-9]+)\s*:\s*(.+?)!default/) do |name, value|
# Sass keeps the first !default and ignores the rest, so a second
# declaration is dead code that drifts from the live one in silence.
duplicates << name if declared.key?(name)
# `if($x == null, 4.5px, $x)` documents as the fallback it falls back to.
declared[name] ||= value.strip.sub(/\Aif\(\$\w+ == null, (.+?), \$\w+\)\z/, '\\1')
end

readme = File.read(File.expand_path("../README.md", __dir__))
rows = readme.scan(/^\|\s*`(\$skin[A-Za-z0-9]+)`(?:\s*\/\s*`(\$skin[A-Za-z0-9]+)`)?\s*\|\s*([^|]*?)\s*\|/)

rows.flat_map do |light, dark, documented|
parts = documented.split("/").map { |part| part.strip.delete("`") }
pairs = [[light, parts[0]]]
pairs << [dark, parts[1]] if dark
pairs.filter_map do |name, value|
next if value.nil? || value.empty?
actual = declared[name]
next if actual && actual.casecmp?(value)
"#{name}: README says `#{value}`, the stylesheet declares `#{actual || "nothing"}`"
documented = {}
listed_twice = []
rows.each do |light, dark, values|
parts = values.split("/").map { |part| part.strip.delete("`") }
# An empty cell is not documentation. Recording it anyway would put the
# name in `documented`, which exempts it from the undocumented check below
# while the mismatch check skips it for having no value — a blank default
# would pass on both sides.
[[light, parts[0]], [dark, parts[1]]].each do |name, value|
next if name.nil? || value.nil? || value.empty?
listed_twice << name if documented.key?(name)
documented[name] = value
end
end
@readme_variable_rows = declared.size

mismatched = documented.filter_map do |name, value|
actual = declared[name]
next if actual && actual.casecmp?(value)
"#{name}: README says `#{value}`, the stylesheet declares `#{actual || "nothing"}`"
end

# Both directions: comparing only the documented names would let a new
# variable ship undocumented while the success line still claimed the table
# matched every declaration.
undocumented = (declared.keys - documented.keys).map do |name|
"#{name}: declared in the stylesheet, absent from the README table"
end
duplicated = duplicates.uniq.map do |name|
"#{name}: declared more than once; Sass keeps the first !default and drops the rest"
end
# The last row wins when a name is listed twice, so the table can agree with
# the stylesheet while a reader meets the stale row first.
redocumented = listed_twice.uniq.map do |name|
"#{name}: listed more than once in the README table"
end

mismatched + undocumented + duplicated + redocumented
end

DECLARED_ROWS = 53

# Reported in the success line. Counted from the declarations themselves, so
# it cannot drift the way a hand-maintained constant does.
def self.readme_variable_rows
@readme_variable_rows || 0
end

def self.load_paths
activeadmin = Gem::Specification.find_by_name("activeadmin").gem_dir
Expand Down Expand Up @@ -124,7 +162,13 @@ def self.run
# A block-level item (flex, block, grid) breaks that row and stacks the
# username, theme switch and logout on top of each other.
utility = compile(GOOD["defaults"]).scan(/^[^{}]*#utility_nav\s*>\s*li[^{}\s,]*\s*\{[^}]*\}/m)
blocky = utility.select { |rule| rule =~ /^\s*display:\s*(?:flex|block|grid)\s*;/ }
# The body, not the start of a line: sassc happens to put the first
# declaration on its own line, so matching from `^` only works while
# `display` is written first in the stylesheet. Reorder the two lines in the
# source and the guard goes blind.
blocky = utility.select do |rule|
rule[/\{(.*)\}/m, 1].to_s.split(";").any? { |d| d.strip =~ /\Adisplay:\s*(?:flex|block|grid)\z/ }
end
unless blocky.empty?
failures << "utility nav: #{blocky.size} item rule(s) make the li block-level and break the inline row: " \
"#{blocky.map { |rule| rule[/\A[^{]*/].strip }.join(", ")}"
Expand All @@ -138,7 +182,7 @@ def self.run
end

puts "css_check: #{GOOD.size} overrides compile clean, #{BAD.size} bad ones rejected, " \
"README table matches #{DECLARED_ROWS} declarations"
"README table matches #{readme_variable_rows} declarations"
else
failures.each { |failure| warn "css_check: #{failure}" }
abort "css_check: #{failures.size} problem(s)"
Expand Down
Loading