diff --git a/frontend/src/plan-schedule-plot.ts b/frontend/src/plan-schedule-plot.ts index a270e3f48..e7ed219eb 100644 --- a/frontend/src/plan-schedule-plot.ts +++ b/frontend/src/plan-schedule-plot.ts @@ -25,8 +25,10 @@ function staircase(points: readonly SchedulePlotPoint[], frame: SchedulePlotFram export function renderSchedulePlot(points: readonly SchedulePlotPoint[], frame: SchedulePlotFrame, selected: number, interactive: boolean): string { const { width } = frame, count = points.length, x = (index: number): number => schedulePlotX(points[index]!.date, frame); const path = staircase(points, frame); - return ` - ${[0, 20, 40, 60, 80, 100].map(value => `${value}`).join('')} + // The read-only plot has no Start/End handle row, so it ends below the date labels. + const height = interactive ? 370 : 310; + return ` + ${[0, 20, 40, 60, 80, 100].map(value => `${value}%`).join('')} ${points.map((point, index) => ` ${interactive ? `
` : ""} diff --git a/frontend/src/plans.ts b/frontend/src/plans.ts index 41d7a88fb..3d883c9f7 100644 --- a/frontend/src/plans.ts +++ b/frontend/src/plans.ts @@ -7,7 +7,7 @@ import * as state from './state'; import { buildCommitmentSummary, withIntro } from './commitmentSummary'; import { formatDate, formatTerm, getStatusBadge, escapeHtml, escapeHtmlAttr, formatCurrency, CURRENCY_DEFAULT_DIGITS, NO_VALUE, providerBadgeHtml } from './utils'; import { showToast } from './toast'; -import { iconSvg } from './icons'; +import { createIcon, iconSvg } from './icons'; import { confirmDialog } from './confirmDialog'; import type { PlansResponse, LocalPlan, SavePlanData } from './types'; import { viewPlanHistory } from './history'; @@ -1287,13 +1287,23 @@ function renderPlanAccountChips(): void { if (!container) return; container.textContent = ''; planSelectedAccounts.forEach(acct => { + const label = `${acct.name} (${acct.external_id})`; const chip = document.createElement('span'); chip.className = 'account-chip'; - chip.textContent = `${acct.name} (${acct.external_id})`; + // A pill that never wraps mid-name: a long name is truncated with an + // ellipsis and shown in full on hover. + const name = document.createElement('span'); + name.className = 'account-chip-name'; + name.textContent = label; + name.title = label; + chip.appendChild(name); const removeBtn = document.createElement('button'); removeBtn.type = 'button'; - removeBtn.textContent = '\u00d7'; + removeBtn.className = 'account-chip-remove'; + removeBtn.setAttribute('aria-label', `Remove ${label}`); + removeBtn.title = 'Remove'; + removeBtn.appendChild(createIcon('x')); removeBtn.addEventListener('click', () => { planSelectedAccounts = planSelectedAccounts.filter(a => a.id !== acct.id); renderPlanAccountChips(); diff --git a/frontend/src/styles/plans.css b/frontend/src/styles/plans.css index da59788e0..5a363b564 100644 --- a/frontend/src/styles/plans.css +++ b/frontend/src/styles/plans.css @@ -178,16 +178,20 @@ .ramp-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 1rem; - margin-top: 0.5rem; + gap: var(--cudly-sp-3); + margin-top: var(--cudly-sp-2); } -.ramp-option { +/* label.ramp-option outranks `.modal-content label { display: block }`, + * which otherwise ran the title and description together on one line. */ +label.ramp-option { display: flex; flex-direction: column; - padding: 1rem; - border: 2px solid var(--cudly-border-strong); - border-radius: 8px; + gap: var(--cudly-sp-1); + margin: 0; + padding: var(--cudly-sp-3) var(--cudly-sp-4); + border: 1px solid var(--cudly-border-strong); + border-radius: var(--cudly-r-md); cursor: pointer; transition: border-color var(--cudly-dur) var(--cudly-ease), background-color var(--cudly-dur) var(--cudly-ease), box-shadow var(--cudly-dur) var(--cudly-ease); } @@ -217,16 +221,28 @@ .ramp-option:has(input:checked) { border-color: var(--cudly-primary); background: var(--cudly-info-bg); + /* A second pixel of the accent, inside the border, so selection reads + * clearly without the card changing size. */ + box-shadow: inset 0 0 0 1px var(--cudly-primary); +} + +.ramp-option:has(input:checked:focus-visible) { + box-shadow: inset 0 0 0 1px var(--cudly-primary), var(--cudly-focus-ring); } .ramp-label { + font-size: var(--cudly-fs-sm); font-weight: var(--cudly-fw-semibold); color: var(--cudly-text); - margin-bottom: 0.25rem; +} + +.ramp-option:has(input:checked) .ramp-label { + color: var(--cudly-primary); } .ramp-desc { - font-size: 0.8rem; + font-size: var(--cudly-fs-xs); + line-height: 1.4; color: var(--cudly-text-muted); } @@ -280,5 +296,88 @@ #plan-editor-panel .editor-help { margin-bottom: 16px; } #plan-editor-panel .editor-status { font-weight: var(--cudly-fw-semibold); min-height: 1.5em; } #plan-editor-panel .schedule-preview-scroll { min-height: 372px; } +/* The read-only plot is shorter (no Start/End handle row). */ +#plan-editor-panel .schedule-preview-scroll:has(> svg[role="img"]) { min-height: 312px; } #plan-editor-panel .schedule-preview-scroll[hidden] { display: none; } #plan-editor-panel [data-editor="changes"] { margin-top: 16px; border-top: 1px solid var(--cudly-border); } + +/* ---- Plan editor: selected-account pills (ui/modernize-modals) ---------- */ +.selected-accounts { + display: flex; + flex-wrap: wrap; + gap: var(--cudly-sp-2); +} + +.selected-accounts:not(:empty) { + margin-top: var(--cudly-sp-3); +} + +.account-chip { + display: inline-flex; + align-items: center; + gap: var(--cudly-sp-1); + max-width: 100%; + padding: 0.125rem var(--cudly-sp-1) 0.125rem var(--cudly-sp-3); + font-size: var(--cudly-fs-sm); + color: var(--cudly-text); + background: var(--cudly-info-bg); + border: 1px solid var(--cudly-border); + border-radius: var(--cudly-r-full); +} + +.account-chip-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +button.account-chip-remove { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.25rem; + height: 1.25rem; + padding: 0; + border: none; + border-radius: var(--cudly-r-full); + background: transparent; + color: var(--cudly-text-muted); + font-size: var(--cudly-fs-xs); +} + +button.account-chip-remove:hover { + background: var(--cudly-border); + color: var(--cudly-text); +} + +.plan-accounts-search { + position: relative; +} + +.account-suggestions { + position: absolute; + z-index: 3; + left: 0; + right: 0; + max-height: 14rem; + overflow-y: auto; + margin-top: var(--cudly-sp-1); + padding: var(--cudly-sp-1) 0; + background: var(--cudly-surface); + border: 1px solid var(--cudly-border); + border-radius: var(--cudly-r-md); + box-shadow: var(--cudly-shadow-lg); +} + +.account-suggestion-item { + padding: var(--cudly-sp-2) var(--cudly-sp-3); + font-size: var(--cudly-fs-sm); + cursor: pointer; +} + +.account-suggestion-item:hover { + background: var(--cudly-surface-muted); +} +/* ---- end plan editor pills ---------------------------------------------- */