mirror of
https://github.com/we-promise/sure.git
synced 2026-06-05 18:59:04 +00:00
fix(ds): dark-mode token parity — contrast & state fixes (#2139)
* fix(ds): dark-mode token parity — text, borders, checkbox, toggle, over-budget badge Dark-mode contrast/parity pass (design-review epic #2134, follow-up to #1736): - text-subdued (dark): gray-500 -> gray-400; faint/eyebrow text ~3.6:1 -> ~6.7:1 (clears AA), staying below text-secondary so hierarchy holds. - border-primary/secondary/subdued (dark): +1 alpha-white step each; restores group-card edges and in-card row hairlines (border-primary 1.86:1 -> 2.8:1). Alpha keeps them surface-relative across any dark bg. - Dark checkbox: unchecked was a solid white square (read as already-selected) — now a transparent outlined box; checked/indeterminate use a white fill with a #171717 glyph (was #808080, ~2:1); added an explicit indeterminate dash; disabled muted to gray-700. - Toggle (light off-state): track gray-100 -> gray-300 plus a thumb shadow; the white-thumb-on-white-track invisible off-state now reads (dark off-track was already hardened to gray-700). - Over-budget badge: text-red-500 -> text-destructive (theme-aware, matching the on-track/near-limit siblings); in-situ dark contrast 4.18:1 -> 4.55:1 (AA). - Correct invalid icon color keys (red/yellow/green -> destructive/warning/success) on the three budget status badges. Verified in-browser, light+dark: isolated checkbox states, /accounts card borders, /reports muted text, toggle off-state, /budgets over-budget badge (in-situ 4.55:1). * fix(ds): reconcile destructive color + fix filled-pill contrast Continues the dark-parity pass (#2134): - Reconcile destructive: border-destructive and button-bg-destructive were red-500 while the destructive text/icon token was red-600. Unify on red-600 (light) / red-400 (dark) across text, border, and button — the text token can't drop to red-500 (3.96:1 on white, fails AA), so border/button move up instead. White-on-destructive-button 3.96:1 -> 4.36:1 (AA-large); hover red-600 -> red-700. - DS::Pill filled style: deepen the fill tone-500 -> tone-700. White label text on tone-500 failed AA on nearly every tone (amber 2.35:1, green 2.62:1, red 3.95:1); tone-700 clears it (amber 5.43, green 4.30, red 5.86, others 6.4-12) in both themes and removes the dark-surface glare. Date-input calendar glyph in dark verified already-correct (existing invert(1) rules; color-scheme is normal, so no conflict) — no change needed. Verified in-browser: real .button-bg-destructive (red-600) + filled pills, all tones, light and dark. * fix(ds): destructive button consumes the reconciled red-600 Follow-up to the destructive reconcile in this branch: DS::Buttonish's destructive variant still used raw bg-red-500 / hover:bg-red-600, so destructive *buttons* didn't match the reconciled destructive text/border (red-600). Align to red-600 / hover red-700 (light); dark unchanged (red-400 / red-500). White-on-red-600 = 4.37:1 (AA-large), consistent with the rest of the destructive family. * refactor(ds): tokenize budget-category badge + bar backgrounds Status-badge foregrounds already used semantic tokens (text-destructive/ warning/success) but backgrounds + progress-bar fills stayed on the raw palette (bg-red-500/10, bg-yellow-500, ...). Switch to the matching semantic tokens (bg-destructive/10, bg-warning, bg-success) — same value in light, now theme-aware in dark. Mirrors DS::Alert. Addresses CodeRabbit/Codex on #2139.
This commit is contained in:
committed by
GitHub
parent
a83619eda5
commit
5abf9cb537
@@ -25,7 +25,7 @@
|
||||
--color-container-inset: var(--color-gray-50);
|
||||
--color-container-inset-hover: var(--color-gray-100);
|
||||
--color-nav-indicator: var(--color-black);
|
||||
--color-toggle-track: var(--color-gray-100);
|
||||
--color-toggle-track: var(--color-gray-300);
|
||||
--color-destructive-subtle: var(--color-red-200);
|
||||
--color-gray-25: #FAFAFA;
|
||||
--color-gray-50: #F7F7F7;
|
||||
@@ -294,7 +294,7 @@
|
||||
@apply text-gray-400;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-500;
|
||||
@apply text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@
|
||||
@apply border-alpha-black-300;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-400;
|
||||
@apply border-alpha-white-500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@
|
||||
@apply border-alpha-black-200;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-300;
|
||||
@apply border-alpha-white-400;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@
|
||||
@apply border-alpha-black-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-100;
|
||||
@apply border-alpha-white-200;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@
|
||||
}
|
||||
|
||||
@utility border-destructive {
|
||||
@apply border-red-500;
|
||||
@apply border-red-600;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-red-400;
|
||||
@@ -447,7 +447,7 @@
|
||||
}
|
||||
|
||||
@utility button-bg-destructive {
|
||||
@apply bg-red-500;
|
||||
@apply bg-red-600;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-red-400;
|
||||
@@ -455,7 +455,7 @@
|
||||
}
|
||||
|
||||
@utility button-bg-destructive-hover {
|
||||
@apply bg-red-600;
|
||||
@apply bg-red-700;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-red-500;
|
||||
|
||||
@@ -109,18 +109,27 @@
|
||||
|
||||
@variant theme-dark {
|
||||
&[type='checkbox'] {
|
||||
@apply ring-gray-900 checked:text-white;
|
||||
background-color: var(--color-gray-100);
|
||||
@apply ring-gray-900 border-alpha-white-300;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&[type='checkbox']:disabled {
|
||||
@apply cursor-not-allowed opacity-80;
|
||||
background-color: var(--color-gray-600);
|
||||
@apply cursor-not-allowed opacity-80 border-transparent;
|
||||
background-color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
&[type='checkbox']:checked,
|
||||
&[type='checkbox']:indeterminate {
|
||||
@apply border-transparent;
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
&[type='checkbox']:checked {
|
||||
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23808080' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
||||
background-color: var(--color-gray-100);
|
||||
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23171717' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
&[type='checkbox']:indeterminate {
|
||||
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23171717' xmlns='http://www.w3.org/2000/svg'%3e%3crect x='3.5' y='7' width='9' height='2' rx='1'/%3e%3c/svg%3e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class DS::Buttonish < DesignSystemComponent
|
||||
icon_classes: "text-primary"
|
||||
},
|
||||
destructive: {
|
||||
container_classes: "text-inverse bg-red-500 theme-dark:bg-red-400 hover:bg-red-600 theme-dark:hover:bg-red-500 disabled:bg-red-200 theme-dark:disabled:bg-red-600",
|
||||
container_classes: "text-inverse bg-red-600 theme-dark:bg-red-400 hover:bg-red-700 theme-dark:hover:bg-red-500 disabled:bg-red-200 theme-dark:disabled:bg-red-600",
|
||||
icon_classes: "text-inverse"
|
||||
},
|
||||
outline: {
|
||||
|
||||
@@ -93,8 +93,13 @@ class DS::Pill < DesignSystemComponent
|
||||
p = palette
|
||||
case style
|
||||
when :filled
|
||||
# Filled = solid / high-emphasis. The tone-500 fill fails white-label AA on
|
||||
# the brighter tones (amber 2.4:1, green 2.6:1, red 4.0:1) and glares on dark
|
||||
# surfaces. Deepen to tone-700 — every `fill` is a `*-500`, so derive -700 —
|
||||
# so the white label clears AA on every tone in both themes.
|
||||
strong_fill = p[:fill].sub("-500)", "-700)")
|
||||
<<~CSS.strip.gsub(/\s+/, " ")
|
||||
background-color: #{p[:fill]};
|
||||
background-color: #{strong_fill};
|
||||
color: var(--color-white);
|
||||
border-color: transparent;
|
||||
CSS
|
||||
|
||||
@@ -24,7 +24,7 @@ class DS::Toggle < DesignSystemComponent
|
||||
# `prefers-reduced-motion`; reduced-motion users get a snap.
|
||||
"motion-safe:transition-colors motion-safe:duration-300",
|
||||
"after:content-[''] after:block after:bg-white after:absolute after:rounded-full",
|
||||
"after:top-0.5 after:left-0.5 after:w-4 after:h-4",
|
||||
"after:top-0.5 after:left-0.5 after:w-4 after:h-4 after:shadow-sm",
|
||||
"motion-safe:after:transition-transform motion-safe:after:duration-300 motion-safe:after:ease-in-out",
|
||||
"peer-checked:bg-success peer-checked:after:translate-x-4",
|
||||
# Focus ring driven from the sr-only input via `peer-focus-visible:`.
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<%# Progress Bar %>
|
||||
<div class="mb-3">
|
||||
<div class="h-1.5 bg-container-inset rounded-full overflow-hidden">
|
||||
<% bar_color = budget_category.over_budget? ? "bg-red-500" : (budget_category.near_limit? ? "bg-yellow-500" : "bg-green-500") %>
|
||||
<% bar_color = budget_category.over_budget? ? "bg-destructive" : (budget_category.near_limit? ? "bg-warning" : "bg-success") %>
|
||||
<div class="h-full <%= bar_color %> rounded-full transition-all duration-500"
|
||||
style="inline-size: <%= budget_category.bar_width_percent %>%"></div>
|
||||
</div>
|
||||
@@ -67,8 +67,7 @@
|
||||
<div class="w-full sm:w-auto text-xs text-subdued">
|
||||
<%= t("reports.budget_performance.suggested_daily",
|
||||
amount: daily_info[:amount].format,
|
||||
days: daily_info[:days_remaining])
|
||||
%>
|
||||
days: daily_info[:days_remaining]) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"container-inset": { "$value": "{color.gray.50}", "$type": "color", "$extensions": { "sure.dark": "{color.gray.800}" } },
|
||||
"container-inset-hover": { "$value": "{color.gray.100}", "$type": "color", "$extensions": { "sure.dark": "{color.gray.700}" } },
|
||||
"nav-indicator": { "$value": "{color.black}", "$type": "color", "$extensions": { "sure.dark": "{color.white}" } },
|
||||
"toggle-track": { "$value": "{color.gray.100}", "$type": "color", "$extensions": { "sure.dark": "{color.gray.700}" } },
|
||||
"toggle-track": { "$value": "{color.gray.300}", "$type": "color", "$extensions": { "sure.dark": "{color.gray.700}" } },
|
||||
"destructive-subtle": { "$value": "{color.red.200}", "$type": "color", "$extensions": { "sure.dark": "{color.red.800}" } },
|
||||
|
||||
"gray": {
|
||||
@@ -285,7 +285,7 @@
|
||||
"text-primary": { "$type": "utility", "$value": "{color.gray.900}", "$extensions": { "sure.utility": { "prefix": "text" }, "sure.dark": "{color.white}" } },
|
||||
"text-inverse": { "$type": "utility", "$value": "{color.white}", "$extensions": { "sure.utility": { "prefix": "text" }, "sure.dark": "{color.gray.900}" } },
|
||||
"text-secondary": { "$type": "utility", "$value": "{color.gray.500}", "$extensions": { "sure.utility": { "prefix": "text" }, "sure.dark": "{color.gray.300}" } },
|
||||
"text-subdued": { "$type": "utility", "$value": "{color.gray.400}", "$extensions": { "sure.utility": { "prefix": "text" }, "sure.dark": "{color.gray.500}" } },
|
||||
"text-subdued": { "$type": "utility", "$value": "{color.gray.400}", "$extensions": { "sure.utility": { "prefix": "text" }, "sure.dark": "{color.gray.400}" } },
|
||||
|
||||
"shadow-border-xs": {
|
||||
"$type": "utility",
|
||||
@@ -328,12 +328,12 @@
|
||||
}
|
||||
},
|
||||
|
||||
"border-primary": { "$type": "utility", "$value": "{color.alpha-black.300}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.alpha-white.400}" } },
|
||||
"border-secondary": { "$type": "utility", "$value": "{color.alpha-black.200}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.alpha-white.300}" } },
|
||||
"border-primary": { "$type": "utility", "$value": "{color.alpha-black.300}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.alpha-white.500}" } },
|
||||
"border-secondary": { "$type": "utility", "$value": "{color.alpha-black.200}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.alpha-white.400}" } },
|
||||
"border-divider": { "$type": "utility", "$value": "border-tertiary" },
|
||||
"border-subdued": { "$type": "utility", "$value": "{color.alpha-black.50}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.alpha-white.100}" } },
|
||||
"border-subdued": { "$type": "utility", "$value": "{color.alpha-black.50}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.alpha-white.200}" } },
|
||||
"border-solid": { "$type": "utility", "$value": "{color.black}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.white}" } },
|
||||
"border-destructive": { "$type": "utility", "$value": "{color.red.500}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.red.400}" } },
|
||||
"border-destructive": { "$type": "utility", "$value": "{color.red.600}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.red.400}" } },
|
||||
"border-inverse": { "$type": "utility", "$value": "{color.alpha-white.200}", "$extensions": { "sure.utility": { "prefix": "border" }, "sure.dark": "{color.alpha-black.300}" } },
|
||||
|
||||
"button-bg-primary": { "$type": "utility", "$value": "{color.gray.900}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.white}" } },
|
||||
@@ -343,8 +343,8 @@
|
||||
"button-bg-secondary-strong": { "$type": "utility", "$value": "{color.gray.200}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.gray.700}" } },
|
||||
"button-bg-secondary-strong-hover": { "$type": "utility", "$value": "{color.gray.300}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.gray.600}" } },
|
||||
"button-bg-disabled": { "$type": "utility", "$value": "{color.gray.50}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.gray.700}" } },
|
||||
"button-bg-destructive": { "$type": "utility", "$value": "{color.red.500}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.red.400}" } },
|
||||
"button-bg-destructive-hover": { "$type": "utility", "$value": "{color.red.600}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.red.500}" } },
|
||||
"button-bg-destructive": { "$type": "utility", "$value": "{color.red.600}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.red.400}" } },
|
||||
"button-bg-destructive-hover": { "$type": "utility", "$value": "{color.red.700}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.red.500}" } },
|
||||
"button-bg-ghost-hover": { "$type": "utility", "$value": "{color.gray.50}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "bg-gray-800 text-inverse" } },
|
||||
"button-bg-outline-hover": { "$type": "utility", "$value": "{color.gray.100}", "$extensions": { "sure.utility": { "prefix": "bg" }, "sure.dark": "{color.gray.700}" } },
|
||||
|
||||
|
||||
Reference in New Issue
Block a user