Files
sure/app/assets/tailwind/sure-design-system/components.css
Guillem Arias Fauste 5abf9cb537 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.
2026-06-03 12:02:50 +02:00

163 lines
4.7 KiB
CSS

@layer components {
/* Forms */
.form-field {
@apply flex flex-col gap-1 relative px-3 py-2 rounded-md border bg-container border-secondary shadow-xs w-full;
@apply focus-within:border-secondary focus-within:shadow-none focus-within:ring-4 focus-within:ring-alpha-black-200;
@apply transition-all duration-300;
@variant theme-dark {
@apply focus-within:ring-alpha-white-300;
}
/* Add styles for multiple select within form fields */
select[multiple] {
@apply py-2 pr-2 space-y-0.5 overflow-y-auto;
option {
@apply py-2 rounded-md;
}
option:checked {
@apply after:content-['\2713'] bg-container-inset after:text-gray-500 after:ml-2;
}
option:active,
option:focus {
@apply bg-container-inset;
}
}
}
/* New form field structure components */
.form-field__header {
@apply flex items-center justify-between gap-2;
}
.form-field__body {
@apply flex flex-col gap-1;
}
.form-field__actions {
@apply flex items-center gap-1;
}
.form-field__label {
@apply block text-xs text-secondary peer-disabled:text-subdued;
}
.form-field__input {
@apply text-primary border-none bg-container text-sm opacity-100 w-full p-0;
@apply focus:opacity-100 focus:outline-hidden focus:ring-0;
@apply placeholder-shown:opacity-50;
@apply disabled:text-subdued;
@apply text-ellipsis overflow-hidden whitespace-nowrap;
@apply transition-opacity duration-300;
@apply placeholder:text-subdued;
@variant theme-dark {
&::-webkit-calendar-picker-indicator {
filter: invert(1);
cursor: pointer;
}
}
}
textarea.form-field__input {
@apply whitespace-normal overflow-auto;
text-overflow: clip;
}
.form-field__input--multiselect-trigger {
@apply whitespace-normal overflow-visible;
text-overflow: clip;
}
select.form-field__input,
button.form-field__input {
@apply pr-10 appearance-none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right -0.15rem center;
background-repeat: no-repeat;
background-size: 1.25rem 1.25rem;
text-align: left;
}
.form-field__radio {
@apply text-primary;
}
.form-field__submit {
@apply cursor-pointer rounded-lg bg-surface p-3 text-center text-white hover:bg-surface-hover;
}
/* Checkboxes */
.checkbox {
&[type='checkbox'] {
@apply rounded-sm;
@apply transition-colors duration-300;
}
}
.checkbox--light {
&[type='checkbox'] {
@apply border-alpha-black-200 checked:bg-gray-900 checked:ring-gray-900 focus:ring-gray-900 focus-visible:ring-gray-900 checked:hover:bg-gray-300 hover:bg-gray-300;
}
&[type='checkbox']:disabled {
@apply cursor-not-allowed opacity-80 bg-gray-50 border-gray-200 checked:bg-gray-400 checked:ring-gray-400;
}
@variant theme-dark {
&[type='checkbox'] {
@apply ring-gray-900 border-alpha-white-300;
background-color: transparent;
}
&[type='checkbox']:disabled {
@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='%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");
}
}
}
.checkbox--dark {
&[type='checkbox'] {
@apply ring-gray-900 checked:text-white;
}
&[type='checkbox']:disabled {
@apply cursor-not-allowed opacity-80 ring-gray-600;
}
&[type='checkbox']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23111827' 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");
}
}
/* Tooltips */
.tooltip {
@apply hidden absolute;
}
.qrcode svg path {
fill: var(--color-black);
@variant theme-dark {
fill: var(--color-white);
}
}
}