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
+2 -2
View File
@@ -275,7 +275,7 @@ export default function AttGoraSection({
)}
>
<div className="overflow-hidden pb-1">
<div className="flex items-center gap-3 rounded bg-secondary/40 px-3 py-2">
<div className="flex items-center gap-3 rounded-sm bg-secondary/40 px-3 py-2">
<div className="flex-1 min-w-0">
<p className="text-sm truncate">
{match.transaction_description}
@@ -302,7 +302,7 @@ export default function AttGoraSection({
href={`/transactions?highlight=${match.transaction_id}`}
aria-label={t('suggested_view')}
title={t('suggested_view')}
className="shrink-0 h-10 w-10 inline-flex items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
className="shrink-0 h-10 w-10 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
>
<Eye className="h-4 w-4" />
</Link>
+2 -2
View File
@@ -152,7 +152,7 @@ export default function CompanySwitcher() {
onClick={() => handleSwitch(c.id)}
disabled={isPending}
className={cn(
'flex items-center gap-2 w-full px-2.5 py-2 text-left text-[13px] leading-snug transition-colors rounded-md md:whitespace-nowrap',
'flex items-center gap-2 w-full px-2.5 py-2 text-left text-[13px] leading-snug transition-colors rounded-sm md:whitespace-nowrap',
c.id === company?.id
? 'text-foreground bg-muted/40'
: 'text-muted-foreground hover:text-foreground hover:bg-muted/40',
@@ -184,7 +184,7 @@ export default function CompanySwitcher() {
<Link
href="/select-company?choose=1"
onClick={() => setOpen(false)}
className="flex items-center gap-2 px-2.5 py-2 text-[13px] text-muted-foreground hover:text-foreground hover:bg-muted/40 rounded-md transition-colors md:whitespace-nowrap"
className="flex items-center gap-2 px-2.5 py-2 text-[13px] text-muted-foreground hover:text-foreground hover:bg-muted/40 rounded-sm transition-colors md:whitespace-nowrap"
>
<Plus className="h-3.5 w-3.5" />
{t('add_company')}
+3 -3
View File
@@ -738,7 +738,7 @@ export default function DashboardNav({ companyName: _companyName, entityType, pa
alt=""
width={26}
height={26}
className="h-[26px] w-[26px] rounded-md"
className="h-[26px] w-[26px] rounded-lg"
/>
</Link>
<button
@@ -1010,7 +1010,7 @@ export default function DashboardNav({ companyName: _companyName, entityType, pa
{/* Bottom sheet */}
<div
className={cn(
"md:hidden fixed inset-x-0 bottom-0 z-50 bg-card rounded-t-2xl border-t border-border/40 overflow-y-auto overscroll-contain",
"md:hidden fixed inset-x-0 bottom-0 z-50 bg-card rounded-t-xl border-t border-border/40 overflow-y-auto overscroll-contain",
isClosing
? "animate-out slide-out-to-bottom duration-200"
: "animate-in slide-in-from-bottom duration-300"
@@ -1020,7 +1020,7 @@ export default function DashboardNav({ companyName: _companyName, entityType, pa
aria-label={tNav('navigation_menu')}
>
{/* Drag handle */}
<div className="flex justify-center pt-3 pb-1 sticky top-0 bg-card rounded-t-2xl">
<div className="flex justify-center pt-3 pb-1 sticky top-0 bg-card rounded-t-xl">
<div className="w-8 h-1 rounded-full bg-muted-foreground/25" />
</div>
+2 -2
View File
@@ -26,13 +26,13 @@ export function SandboxBanner() {
</span>
<button
onClick={handleCreateAccount}
className="shrink-0 rounded-md bg-foreground/10 px-3 py-0.5 text-xs font-semibold hover:bg-foreground/15 transition-colors"
className="shrink-0 rounded-full bg-foreground/10 px-3 py-0.5 text-xs font-semibold hover:bg-foreground/15 transition-colors"
>
Skapa konto
</button>
<button
onClick={() => setDismissed(true)}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 hover:bg-foreground/10 transition-colors"
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-sm p-1 hover:bg-foreground/10 transition-colors"
aria-label="Stäng"
>
<X className="h-3.5 w-3.5" />
+3 -3
View File
@@ -63,7 +63,7 @@ function CompanyMark({ name }: { name: string }) {
return (
<span
aria-hidden="true"
className="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-[4px] bg-secondary text-[9px] font-semibold uppercase leading-none text-foreground"
className="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-sm bg-secondary text-[9px] font-semibold uppercase leading-none text-foreground"
>
{name.trim().charAt(0) || '?'}
</span>
@@ -193,7 +193,7 @@ export default function UserMenu({
)
const menuRow =
'flex w-full items-center gap-2.5 rounded-md px-2.5 py-2 text-left text-[13px] ' +
'flex w-full items-center gap-2.5 rounded-sm px-2.5 py-2 text-left text-[13px] ' +
'text-muted-foreground hover:text-foreground hover:bg-secondary/60 ' +
'transition-colors duration-150 cursor-pointer'
@@ -293,7 +293,7 @@ export default function UserMenu({
role="option"
aria-selected={c.id === company?.id}
className={cn(
'flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-[13px] leading-snug transition-colors',
'flex w-full items-center gap-2 rounded-sm px-2.5 py-2 text-left text-[13px] leading-snug transition-colors',
c.id === company?.id
? 'bg-secondary/60 text-foreground'
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',