Redesign the setup flow and consolidate the CSS into one primitives layer
Onboarding asks explicitly for one or two parents and shows one card per parent for balances; the balance form is per parent with inline validation; the partner can be filled from the FK deduction or added from it. Settings and Dashboard reuse the same form. CSS: bare buttons carry no colour any more; buttons, cards, choice cards, fields, alerts, badges, eyebrow and modal chrome are defined once in primitives.css. Media queries live only in responsive.css. A style guard test blocks hex, !important and stray media queries. Bilingual fixes: calendar selects size by content, header no longer wraps. Vitest is capped at four workers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+111
-301
@@ -8,9 +8,21 @@
|
||||
margin: 0 0 var(--sp-4) 0;
|
||||
}
|
||||
|
||||
/* `auto-fit` only ever governs the desktop case (>1024px) — the tablet/
|
||||
* mobile breakpoints in responsive.css force an explicit 2-column/1-column
|
||||
* layout regardless of this basis. At desktop widths the container is
|
||||
* capped by `--maxw` (960px) minus the fixed chrome around it (`layout-main`
|
||||
* + `.dashboard__more-details` padding), landing at ~864px regardless of
|
||||
* how much wider the viewport itself gets — so the basis only needs to let
|
||||
* FOUR cards (the two-parent "expiring at 4" case) fit that one width: four
|
||||
* columns at 13rem plus three 16px gaps (880px) doesn't quite fit 864px and
|
||||
* `auto-fit` drops to 3, stranding the 4th card alone on its own dangling
|
||||
* row. 12.5rem keeps that same one-row-of-four for the two-parent overview
|
||||
* metrics/expiring-at-4 lists while still comfortably wrapping to 2 columns
|
||||
* for anything narrower than that. */
|
||||
.overview-metrics__list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
|
||||
gap: var(--sp-4);
|
||||
}
|
||||
|
||||
@@ -25,6 +37,22 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* The "all parents" combined total — a footer row spanning every column
|
||||
* instead of just another grid cell, so it never leaves a dangling gap
|
||||
* when the per-parent metrics above it don't fill a whole row. Tinted so
|
||||
* it reads as "the sum of the above", not one more figure among equals. */
|
||||
.overview-metrics__item--summary {
|
||||
grid-column: 1 / -1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: var(--sp-3);
|
||||
background-color: var(--c-accent-weak);
|
||||
}
|
||||
|
||||
.overview-metrics__item--summary .overview-metrics__value {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.overview-metrics__value {
|
||||
display: block;
|
||||
font-size: var(--fs-2xl);
|
||||
@@ -76,7 +104,7 @@
|
||||
}
|
||||
|
||||
.days-breakdown__table tbody tr:hover {
|
||||
background-color: rgba(47, 111, 79, 0.02);
|
||||
background-color: var(--c-row-hover);
|
||||
}
|
||||
|
||||
.days-breakdown__table tbody tr:last-child td {
|
||||
@@ -131,7 +159,7 @@
|
||||
}
|
||||
|
||||
.parental-leave-breakdown__table tbody tr:hover {
|
||||
background-color: rgba(47, 111, 79, 0.02);
|
||||
background-color: var(--c-row-hover);
|
||||
}
|
||||
|
||||
.parental-leave-breakdown__table tbody tr:last-child td {
|
||||
@@ -174,6 +202,11 @@
|
||||
background-color: var(--c-danger-weak);
|
||||
}
|
||||
|
||||
.dashboard-hero--reassure {
|
||||
border-left-color: var(--c-success);
|
||||
background-color: var(--c-success-weak);
|
||||
}
|
||||
|
||||
.dashboard-hero__message {
|
||||
margin: 0;
|
||||
font-size: var(--fs-2xl);
|
||||
@@ -191,15 +224,29 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard__estimate-badge {
|
||||
display: inline-block;
|
||||
margin: var(--sp-3) 0;
|
||||
padding: var(--sp-1) var(--sp-3);
|
||||
border-radius: var(--radius-full);
|
||||
/* "Ange manuellt" for a two-parent workspace asks which parent first —
|
||||
* see `Dashboard.tsx`'s `handleOpenManualFetch`. `.card` gives it the same
|
||||
* chrome as the rest of the dashboard's boxed content instead of a bare,
|
||||
* floating row of buttons. */
|
||||
.dashboard__balance-picker {
|
||||
margin-top: var(--sp-2);
|
||||
}
|
||||
|
||||
.dashboard__balance-picker-label {
|
||||
margin: 0 0 var(--sp-3) 0;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
background-color: var(--c-warning-weak);
|
||||
color: var(--c-warning);
|
||||
font-weight: 500;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.dashboard__balance-picker-actions {
|
||||
display: flex;
|
||||
gap: var(--sp-3);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard__estimate-badge {
|
||||
margin: var(--sp-3) 0;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
@@ -275,6 +322,37 @@
|
||||
margin-bottom: var(--sp-1);
|
||||
}
|
||||
|
||||
.year-timeline__caption {
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--c-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.year-timeline__legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-4);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.year-timeline__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-1);
|
||||
}
|
||||
|
||||
.year-timeline__legend-swatch {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: var(--radius-full);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.year-timeline__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -345,52 +423,6 @@
|
||||
margin-top: var(--sp-4);
|
||||
}
|
||||
|
||||
.plan-fold__error {
|
||||
color: var(--c-danger);
|
||||
}
|
||||
|
||||
/* Button Styles */
|
||||
button {
|
||||
background-color: var(--c-accent);
|
||||
color: var(--c-surface);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background-color: var(--c-accent-hover);
|
||||
}
|
||||
|
||||
button:active:not(:disabled) {
|
||||
background-color: var(--c-accent-active);
|
||||
}
|
||||
|
||||
/* Secondary button */
|
||||
.button--secondary,
|
||||
button.secondary {
|
||||
background-color: transparent;
|
||||
color: var(--c-accent);
|
||||
border: 1px solid var(--c-accent);
|
||||
}
|
||||
|
||||
.button--secondary:hover:not(:disabled),
|
||||
button.secondary:hover:not(:disabled) {
|
||||
background-color: var(--c-accent-weak);
|
||||
}
|
||||
|
||||
/* Ghost button */
|
||||
.button--ghost,
|
||||
button.ghost {
|
||||
background-color: transparent;
|
||||
color: var(--c-text);
|
||||
border: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
.button--ghost:hover:not(:disabled),
|
||||
button.ghost:hover:not(:disabled) {
|
||||
background-color: var(--c-bg);
|
||||
border-color: var(--c-text);
|
||||
}
|
||||
|
||||
/* Form styles */
|
||||
.form-group {
|
||||
margin-bottom: var(--sp-5);
|
||||
@@ -432,24 +464,8 @@ button.ghost:hover:not(:disabled) {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Settings Page Messages */
|
||||
.settings-page__message {
|
||||
padding: var(--sp-4);
|
||||
border-radius: var(--radius);
|
||||
.settings-page > .alert {
|
||||
margin-bottom: var(--sp-4);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.settings-page__message--success {
|
||||
background-color: var(--c-success-weak);
|
||||
color: var(--c-success);
|
||||
border-left: 4px solid var(--c-success);
|
||||
}
|
||||
|
||||
.settings-page__message--error {
|
||||
background-color: var(--c-danger-weak);
|
||||
color: var(--c-danger);
|
||||
border-left: 4px solid var(--c-danger);
|
||||
}
|
||||
|
||||
.settings-page__help {
|
||||
@@ -483,11 +499,11 @@ button.ghost:hover:not(:disabled) {
|
||||
gap: var(--sp-3);
|
||||
}
|
||||
|
||||
/* Box/border/focus chrome comes from `.field__control` in primitives.css —
|
||||
* this only adds the flex sizing `.field__control`'s own rule doesn't know
|
||||
* about (it sits in an inline row next to a button, not a stacked `.field`). */
|
||||
.settings-page__partner-input {
|
||||
flex: 1;
|
||||
padding: var(--sp-3) var(--sp-3);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.settings-page__member-list {
|
||||
@@ -507,11 +523,6 @@ button.ghost:hover:not(:disabled) {
|
||||
padding: var(--sp-2) 0;
|
||||
}
|
||||
|
||||
.settings-page__you-badge {
|
||||
color: var(--c-text-muted);
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.settings-page__remove-confirm {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -520,46 +531,6 @@ button.ghost:hover:not(:disabled) {
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.settings-page__button:not(:disabled):hover {
|
||||
background-color: var(--c-accent-hover);
|
||||
}
|
||||
|
||||
.settings-page__button:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.settings-page__button--secondary {
|
||||
background-color: transparent;
|
||||
color: var(--c-text);
|
||||
border: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
.settings-page__button--secondary:not(:disabled):hover {
|
||||
background-color: var(--c-bg);
|
||||
}
|
||||
|
||||
.settings-page__button--secondary:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.settings-page__button--danger {
|
||||
background-color: var(--c-danger-weak);
|
||||
color: var(--c-danger);
|
||||
border: 1px solid var(--c-danger);
|
||||
}
|
||||
|
||||
.settings-page__button--danger:not(:disabled):hover {
|
||||
background-color: var(--c-danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.settings-page__button--danger:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Vacation Days Table (multi-year) */
|
||||
.vacation-days-table {
|
||||
width: 100%;
|
||||
@@ -592,14 +563,7 @@ button.ghost:hover:not(:disabled) {
|
||||
}
|
||||
|
||||
.vacation-days-table__assumed-badge {
|
||||
display: inline-block;
|
||||
margin-left: var(--sp-2);
|
||||
padding: var(--sp-1) var(--sp-2);
|
||||
border-radius: 999px;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 400;
|
||||
background-color: var(--c-warning-weak);
|
||||
color: var(--c-warning);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
@@ -607,36 +571,11 @@ button.ghost:hover:not(:disabled) {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.vacation-days-table__save {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vacation-days-table__save:not(:disabled):hover {
|
||||
background-color: var(--c-accent-hover);
|
||||
}
|
||||
|
||||
.vacation-days-table__save:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.vacation-days-table__empty {
|
||||
color: var(--c-text-muted);
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
/* Planning Mode Selector */
|
||||
.planning-mode-selector__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
margin-bottom: var(--sp-2);
|
||||
}
|
||||
|
||||
.planning-mode-selector__option:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Fieldsets */
|
||||
fieldset {
|
||||
border: 1px solid var(--c-border);
|
||||
@@ -652,18 +591,20 @@ legend {
|
||||
margin-left: calc(var(--sp-2) * -1);
|
||||
}
|
||||
|
||||
/* Input Components */
|
||||
/* Input Components
|
||||
*
|
||||
* `ChildBirthdayInput` now builds its fields from `.field`/`.field__label`/
|
||||
* `.field__control` (primitives.css) directly rather than its own
|
||||
* `.child-birthday-input__*` set — that set is gone from here accordingly. */
|
||||
.vacation-days-input,
|
||||
.planning-goal-selector,
|
||||
.parent-profile-input,
|
||||
.child-birthday-input {
|
||||
.parent-profile-input {
|
||||
margin-bottom: var(--sp-5);
|
||||
}
|
||||
|
||||
.vacation-days-input__label,
|
||||
.planning-goal-selector__label,
|
||||
.parent-profile-input__label,
|
||||
.child-birthday-input__label {
|
||||
.parent-profile-input__label {
|
||||
display: block;
|
||||
margin-bottom: var(--sp-2);
|
||||
font-weight: 500;
|
||||
@@ -673,15 +614,13 @@ legend {
|
||||
|
||||
.vacation-days-input__field,
|
||||
.planning-goal-selector__select,
|
||||
.parent-profile-input__field,
|
||||
.child-birthday-input__field {
|
||||
.parent-profile-input__field {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.vacation-days-input__range,
|
||||
.parent-profile-input__help,
|
||||
.child-birthday-input__help {
|
||||
.parent-profile-input__help {
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--c-text-muted);
|
||||
margin-top: var(--sp-2);
|
||||
@@ -772,33 +711,13 @@ legend {
|
||||
margin-bottom: var(--sp-3);
|
||||
}
|
||||
|
||||
.day-overrides-input__remove-button {
|
||||
width: 100%;
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
font-size: var(--fs-sm);
|
||||
background-color: var(--c-danger-weak);
|
||||
color: var(--c-danger);
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.day-overrides-input__remove-button:hover:not(:disabled) {
|
||||
background-color: var(--c-danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.day-overrides-input__remove-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Plain section, not a bordered card — matches the rest of Settings (see
|
||||
* layout.css's "sections separate by whitespace and a hairline rule, not a
|
||||
* stack of bordered/shadowed cards" comment). Only a top hairline separates
|
||||
* it from the list of existing exceptions above. */
|
||||
.day-overrides-input__form {
|
||||
padding: var(--sp-4);
|
||||
background-color: var(--c-bg);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius);
|
||||
padding-top: var(--sp-4);
|
||||
border-top: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
.day-overrides-input__form h3 {
|
||||
@@ -808,24 +727,8 @@ legend {
|
||||
}
|
||||
|
||||
.day-overrides-input__form-group {
|
||||
margin-bottom: var(--sp-4);
|
||||
}
|
||||
|
||||
.day-overrides-input__form-group label {
|
||||
display: block;
|
||||
margin-bottom: var(--sp-2);
|
||||
font-weight: 500;
|
||||
color: var(--c-text);
|
||||
font-size: var(--fs-base);
|
||||
}
|
||||
|
||||
.day-overrides-input__form-input {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--fs-base);
|
||||
margin-bottom: var(--sp-4);
|
||||
}
|
||||
|
||||
.day-overrides-input__radio-group {
|
||||
@@ -863,20 +766,6 @@ legend {
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.day-overrides-input__submit-button {
|
||||
width: 100%;
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.day-overrides-input__submit-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Red Days List (§3.8 — statutory red days + workspace overrides, per year) */
|
||||
.red-days-list__pagination {
|
||||
@@ -886,20 +775,6 @@ legend {
|
||||
margin-bottom: var(--sp-4);
|
||||
}
|
||||
|
||||
.red-days-list__nav-button {
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius);
|
||||
background-color: var(--c-surface);
|
||||
color: var(--c-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.red-days-list__nav-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.red-days-list__range {
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -943,22 +818,6 @@ legend {
|
||||
color: var(--c-text);
|
||||
}
|
||||
|
||||
.red-days-list__override-badge {
|
||||
padding: var(--sp-1) var(--sp-2);
|
||||
border-radius: 999px;
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.red-days-list__override-badge--working {
|
||||
background-color: var(--c-danger-weak);
|
||||
color: var(--c-danger);
|
||||
}
|
||||
|
||||
.red-days-list__override-badge--free {
|
||||
background-color: var(--c-success-weak);
|
||||
color: var(--c-success);
|
||||
}
|
||||
|
||||
.red-days-list__effective {
|
||||
margin-left: auto;
|
||||
font-size: var(--fs-sm);
|
||||
@@ -1016,32 +875,13 @@ legend {
|
||||
color: var(--c-text);
|
||||
}
|
||||
|
||||
/* Sized down from the standard --control-h .btn--icon square — this dismiss
|
||||
* glyph sits inline in a card header, not in a toolbar. */
|
||||
.tip-card__dismiss {
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
color: var(--c-text-muted);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--fs-base);
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.tip-card__dismiss:hover {
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
color: var(--c-text);
|
||||
}
|
||||
|
||||
.tip-card__dismiss:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.tip-card__body {
|
||||
@@ -1049,33 +889,3 @@ legend {
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
[data-testid='metrics-list'] {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea,
|
||||
.vacation-days-input__field,
|
||||
.planning-goal-selector__select,
|
||||
.parent-profile-input__field,
|
||||
.child-birthday-input__field {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.days-breakdown__table {
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.days-breakdown__table th,
|
||||
.days-breakdown__table td {
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user