refactor(design): lock the border-radius ladder, one radius per role (#1607)

Seven radii were in circulation (4/5/6/8/12/16px + pill) with no rule for
which went where; one toolbar row on /transactions mixed four shape
languages. This locks a 4-tier ladder (design.md convention 16):

- pill: interactive toolbar controls (buttons, chips, pickers, segmented
  controls, toolbar search, count nubs)
- rounded-xl (12px): overlay tier: page panel, dialogs, slide-overs
- rounded-lg (8px): cards, form fields, popover/menu content, boxes
- rounded-sm (4px): nested leaves (menu items, checkboxes, kbd/code nubs)

Changes:
- New SegmentedControl primitive (pill-in-pill tablist, h-8) replaces the
  hand-rolled bg-muted/70 tablist copied across 11 files
- New ToolbarSearch primitive (pill, h-8) adopted on 9 page toolbars;
  dialog/picker searches keep the rounded-lg Input
- dialog.tsx 8px -> 12px, matching SettingsModal/slide-over/CommandPalette
- ContextPicker chips at the shared h-8 toolbar height
- ~300 rounded-md / bare rounded call sites remapped by role; auth icon
  tiles and the mobile nav sheet come down from 16px to 12px
- rounded-md, bare rounded, rounded-2xl and rounded-[Npx] are dead
  vocabulary, enforced by a new off-ladder-radius check in check:guards

Verified: lint 0 errors, 14422 unit tests pass, check:guards green, tsc
clean on all changed files, sandbox screenshots of transactions/
bookkeeping/granskning toolbars and the Ny verifikation dialog.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-14 08:55:37 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 18c20e68e6
commit 9686b54b41
130 changed files with 633 additions and 652 deletions
@@ -1113,7 +1113,7 @@ function OptionsStep({
{/* Years whose provider export failed: must be visible before
the user proceeds, otherwise an IB/UB gap slips through. */}
{failedYears.length > 0 && (
<div className="rounded-md border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
<div className="rounded-lg border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
<div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-600" />
<div className="space-y-1">
@@ -1263,7 +1263,7 @@ function OptionsStep({
</div>
{options.importSIEData && yearsToReplace.length > 0 && (
<div className="rounded-md border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
<div className="rounded-lg border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
<div className="flex items-start gap-2">
<RefreshCw className="mt-0.5 h-4 w-4 shrink-0 text-amber-600" />
<div className="space-y-1">
@@ -1435,7 +1435,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
<div className="border-t border-border px-4 pb-4 pt-3 space-y-3">
{/* Errors: shown prominently */}
{result.errors.length > 0 && (
<div className="rounded-md border border-destructive/20 bg-destructive/5 p-3">
<div className="rounded-lg border border-destructive/20 bg-destructive/5 p-3">
<div className="flex items-start gap-2">
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0 text-destructive" />
<div className="space-y-1.5">
@@ -1452,7 +1452,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
{/* Opening balance adjustment */}
{d?.openingBalance && (
<div className="rounded-md border border-border bg-muted/30 p-3">
<div className="rounded-lg border border-border bg-muted/30 p-3">
<div className="flex items-start gap-2">
<BookOpen className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
@@ -1493,7 +1493,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
{/* Skipped vouchers breakdown */}
{d?.skippedVouchers && d.skippedVouchers.total > 0 && (
<div className="rounded-md border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
<div className="rounded-lg border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
<div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
<div>
@@ -1537,7 +1537,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
{/* Migration adjustment info */}
{d?.migrationAdjustment?.created && (
<div className="rounded-md border border-border bg-muted/30 p-3">
<div className="rounded-lg border border-border bg-muted/30 p-3">
<div className="flex items-start gap-2">
<Info className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
@@ -1554,7 +1554,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
{/* Untransferred prior-year results — omföring av årets resultat saknas */}
{d?.untransferredResults && d.untransferredResults.length > 0 && (
<div className="rounded-md border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
<div className="rounded-lg border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
<div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
<div>
@@ -1591,7 +1591,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
)
if (remainingWarnings.length === 0) return null
return (
<div className="rounded-md border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
<div className="rounded-lg border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
<div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
<div className="space-y-1.5">
@@ -763,7 +763,7 @@ export default function BookDirectlyDialog({ open, onOpenChange, item, docUrl =
disabled={isSubmitting}
/>
</div>
<div className="max-h-56 overflow-y-auto rounded-md border">
<div className="max-h-56 overflow-y-auto rounded-lg border">
{isLoadingTransactions ? (
<div className="flex items-center justify-center py-8 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> Laddar…
@@ -252,7 +252,7 @@ export default function BulkBookInboxDialog({ open, onOpenChange, items, onSucce
</Select>
{vatTreatment === 'reverse_charge' && (
<div className="rounded-md border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
<div className="rounded-lg border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
<strong className="font-medium text-foreground">Kontrollera säljaren.</strong>{' '}
Omvänd skattskyldighet gäller bara köp från en <strong className="font-medium text-foreground">utländsk
säljare utan svenskt momsregistreringsnummer</strong>: t.ex. EU-tjänster, EU-varor,
@@ -272,7 +272,7 @@ export default function BulkBookInboxDialog({ open, onOpenChange, items, onSucce
)}
{isMixedCurrency && (
<div className="rounded-md border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
<div className="rounded-lg border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
<p className="font-medium text-foreground">{t('mixed_currency_totals_label')}</p>
<ul className="mt-2 space-y-1">
{underlagTotals.map(({ currency, total }) => (
@@ -1793,7 +1793,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
/>
)}
{isDragging && (
<div className="absolute inset-0 bg-primary/5 border-2 border-dashed border-primary rounded-md m-4 flex items-center justify-center pointer-events-none">
<div className="absolute inset-0 bg-primary/5 border-2 border-dashed border-primary rounded-lg m-4 flex items-center justify-center pointer-events-none">
<p className="text-sm font-medium text-primary">Släpp filen för att ladda upp</p>
</div>
)}
@@ -2235,7 +2235,7 @@ export function DocumentPreview({
<div className="h-full w-full p-4 flex items-start justify-center overflow-hidden">
{docMime?.startsWith('image/') ? (
// Image: frame hugs the image, capped at the parent's visible box.
<div className="max-h-full max-w-3xl bg-background rounded-md border overflow-hidden flex">
<div className="max-h-full max-w-3xl bg-background rounded-lg border overflow-hidden flex">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={docUrl}
@@ -2248,7 +2248,7 @@ export function DocumentPreview({
// with no tokens = opaque origin, no scripts, no forms, no popups.
// bg-white because mail HTML assumes a white canvas and would render
// transparent (unreadable in dark mode) otherwise.
<div className="h-full w-full max-w-3xl bg-background rounded-md border overflow-hidden">
<div className="h-full w-full max-w-3xl bg-background rounded-lg border overflow-hidden">
<iframe
src={docUrl}
sandbox=""
@@ -2258,7 +2258,7 @@ export function DocumentPreview({
</div>
) : (
// PDF: iframe needs explicit height, frame fills the available pane.
<div className="h-full w-full max-w-3xl bg-background rounded-md border overflow-hidden">
<div className="h-full w-full max-w-3xl bg-background rounded-lg border overflow-hidden">
<iframe src={docUrl} className="w-full h-full border-0" title="Underlag" />
</div>
)}
@@ -281,7 +281,7 @@ export function WhatsAppLinkPanel() {
{minted ? (
<div className="space-y-3">
<div className="flex items-center gap-3">
<span className="rounded-md border border-border bg-muted/50 px-3 py-1.5 font-mono text-base tracking-wider">
<span className="rounded-sm border border-border bg-muted/50 px-3 py-1.5 font-mono text-base tracking-wider">
{minted.code}
</span>
{minted.waLink ? (
@@ -242,7 +242,7 @@ export default function MockDataImportDialog<T>({
{/* ── Error ─────────────────────────────────────── */}
{error && (
<div className="flex items-start gap-2 p-3 rounded-md bg-destructive/5 text-destructive text-sm">
<div className="flex items-start gap-2 p-3 rounded-lg bg-destructive/5 text-destructive text-sm">
<AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
<span>{error}</span>
</div>
@@ -378,7 +378,7 @@ export default function MockDataImportDialog<T>({
<span>{fileName}</span>
</div>
<div className="flex items-center gap-2 p-3 rounded-md bg-success/10 text-success text-sm">
<div className="flex items-center gap-2 p-3 rounded-lg bg-success/10 text-success text-sm">
<Check className="h-4 w-4 shrink-0" />
<span>Giltig JSON: {jsonSummary}</span>
</div>