From 8094152e8ffbc3dd4fe354e0e2670d89892b3382 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Fri, 9 Oct 2026 13:25:25 +0200 Subject: [PATCH] fix(frontend): define the button system and make the default button quiet The bare button was a solid grey fill and .btn-danger, .btn-link, .btn-warning and .secondary had no CSS. The default and .btn are now an outlined button; only primary, success and destructive actions are filled. .btn-danger and .btn-warning are outlined and fill on hover, .btn-link is a text action for table rows, .secondary aliases .btn-secondary, and disabled buttons are dimmed. The generic hover uses :where() so component classes keep their own background. Refs #695 Co-Authored-By: Claude Sonnet 5.5 --- frontend/src/__tests__/css.test.ts | 2 +- frontend/src/styles/components.css | 94 +++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/frontend/src/__tests__/css.test.ts b/frontend/src/__tests__/css.test.ts index f56c9846..4bd8e531 100644 --- a/frontend/src/__tests__/css.test.ts +++ b/frontend/src/__tests__/css.test.ts @@ -157,7 +157,7 @@ describe('CSS Styles', () => { describe('Interactive Elements', () => { test('has hover states for buttons', () => { - expect(css).toMatch(/button:hover/); + expect(css).toMatch(/button\)?:hover/); }); test('has hover states for table rows', () => { diff --git a/frontend/src/styles/components.css b/frontend/src/styles/components.css index d9f1fe6f..628df806 100644 --- a/frontend/src/styles/components.css +++ b/frontend/src/styles/components.css @@ -113,72 +113,95 @@ margin-top: 1rem; } -/* Buttons */ +/* Buttons: the default is a quiet outlined button. Only primary and + * destructive actions are filled, so the filled colour always means + * "this is the main or the dangerous action". */ button { padding: 0.5rem 1rem; - border: none; + border: 1px solid var(--cudly-border-strong); border-radius: 4px; - background: var(--cudly-text-muted); - color: white; + background: var(--cudly-surface); + color: var(--cudly-text); cursor: pointer; font-size: 0.875rem; - transition: background 0.2s; + transition: background 0.2s, border-color 0.2s; } -button:hover { - background: var(--cudly-text-muted); +/* :where() keeps this at class-level specificity so component classes that + * set their own background are not overridden on hover. */ +:where(button):hover { + background: var(--cudly-surface-muted); + border-color: var(--cudly-text-subtle); +} + +button:disabled { + opacity: 0.55; + cursor: not-allowed; } button.primary { background: var(--cudly-primary); + border-color: var(--cudly-primary); + color: white; } button.primary:hover { background: var(--cudly-primary-hover); + border-color: var(--cudly-primary-hover); } button.success { background: var(--cudly-success); + border-color: var(--cudly-success); + color: white; } button.success:hover { background: #2d9048; + border-color: #2d9048; } button.danger { background: var(--cudly-error); + border-color: var(--cudly-error); + color: white; } button.danger:hover { background: #d33426; + border-color: #d33426; } /* Secondary button style */ -.btn-secondary { +.btn-secondary, +button.secondary { background: var(--cudly-bg); color: var(--cudly-text); border: 1px solid var(--cudly-border-strong); } -.btn-secondary:hover { +.btn-secondary:hover, +button.secondary:hover { background: #e8e8e8; border-color: #bbb; } -/* Base btn class */ +/* Base btn class: same quiet outlined look as the bare button, so it can + * be combined with the variant classes below. */ .btn { padding: 0.5rem 1rem; - border: none; + border: 1px solid var(--cudly-border-strong); border-radius: 4px; - background: var(--cudly-text-muted); - color: white; + background: var(--cudly-surface); + color: var(--cudly-text); cursor: pointer; font-size: 0.875rem; - transition: background 0.2s; + transition: background 0.2s, border-color 0.2s; } .btn:hover { - background: var(--cudly-text-muted); + background: var(--cudly-surface-muted); + border-color: var(--cudly-text-subtle); } .btn-small { @@ -241,6 +264,47 @@ button.danger:hover { color: white; } +/* Variants used in markup next to .btn / .btn-small. Outlined by default; + * they fill on hover so the destructive action reads as such without being + * the loudest element in a row of actions. */ +.btn-danger { + background: var(--cudly-error-bg); + color: var(--cudly-error-fg); + border: 1px solid var(--cudly-error); +} + +.btn-danger:hover { + background: var(--cudly-error); + border-color: var(--cudly-error); + color: white; +} + +.btn-warning { + background: var(--cudly-warn-bg); + color: var(--cudly-warn-fg); + border: 1px solid var(--cudly-warn); +} + +.btn-warning:hover { + background: var(--cudly-warn); + border-color: var(--cudly-warn); + color: var(--cudly-text); +} + +/* Link-style action inside table rows (Approve, Cancel, Retry ...). */ +.btn-link { + padding: 0.25rem 0.5rem; + background: transparent; + color: var(--cudly-primary); + border: none; + text-decoration: none; +} + +.btn-link:hover { + background: var(--cudly-info-bg); + text-decoration: underline; +} + /* Canonical top-level button variants. Existing .btn + .btn-small classes * keep their current appearance; these new variants give callers a single * semantic class (primary / destructive / tertiary) without needing to