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:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
18c20e68e6
commit
9686b54b41
@@ -389,7 +389,7 @@ export function AgentMemoryPanel() {
|
||||
onClick={() => patch(row.id, { is_pinned: !row.is_pinned })}
|
||||
disabled={isBusy}
|
||||
className={cn(
|
||||
'mt-0.5 shrink-0 rounded-md p-1.5 transition-colors duration-150',
|
||||
'mt-0.5 shrink-0 rounded-sm p-1.5 transition-colors duration-150',
|
||||
row.is_pinned
|
||||
? 'bg-secondary text-foreground'
|
||||
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
|
||||
|
||||
@@ -50,7 +50,7 @@ const PROSE =
|
||||
'prose-h1:text-lg prose-h2:text-base prose-h3:text-sm prose-p:my-2 prose-p:leading-6 ' +
|
||||
'prose-strong:font-semibold prose-strong:text-foreground prose-ul:my-2 prose-li:my-0.5 ' +
|
||||
'prose-a:text-foreground prose-a:underline prose-a:underline-offset-2 ' +
|
||||
'prose-code:bg-secondary prose-code:rounded prose-code:px-1 prose-code:py-0.5 prose-code:text-xs ' +
|
||||
'prose-code:bg-secondary prose-code:rounded-sm prose-code:px-1 prose-code:py-0.5 prose-code:text-xs ' +
|
||||
'prose-code:before:content-none prose-code:after:content-none ' +
|
||||
'prose-table:my-2 prose-table:text-xs [&_table]:w-full ' +
|
||||
'[&_th]:border-b [&_th]:border-border [&_th]:py-1.5 [&_th]:px-2 [&_th]:text-left [&_th]:font-medium ' +
|
||||
|
||||
@@ -192,7 +192,7 @@ function CopyBlock({ text, copyAriaLabel }: { text: string; copyAriaLabel: strin
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<pre className="select-all rounded-md bg-muted p-4 pr-12 text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all">
|
||||
<pre className="select-all rounded-lg bg-muted p-4 pr-12 text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all">
|
||||
{text}
|
||||
</pre>
|
||||
<Button
|
||||
@@ -240,7 +240,7 @@ function ScopeCard({
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'flex min-h-[68px] cursor-pointer flex-col gap-1 rounded-md border p-2 transition-colors',
|
||||
'flex min-h-[68px] cursor-pointer flex-col gap-1 rounded-lg border p-2 transition-colors',
|
||||
checked
|
||||
? 'border-border bg-secondary'
|
||||
: 'border-border hover:bg-secondary/60'
|
||||
@@ -580,7 +580,7 @@ export function ApiKeysPanel() {
|
||||
|
||||
{/* Create key dialog */}
|
||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||
<DialogContent className="max-w-[calc(100vw-2rem)] rounded-2xl p-4 sm:max-w-3xl sm:p-6">
|
||||
<DialogContent className="max-w-[calc(100vw-2rem)] rounded-xl p-4 sm:max-w-3xl sm:p-6">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('create_dialog_title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -600,7 +600,7 @@ export function ApiKeysPanel() {
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t('mode_label')}</Label>
|
||||
<div className="inline-flex rounded-md border p-0.5" role="radiogroup" aria-label={t('mode_label')}>
|
||||
<div className="inline-flex rounded-full border p-0.5" role="radiogroup" aria-label={t('mode_label')}>
|
||||
{(['live', 'test'] as const).map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
@@ -609,7 +609,7 @@ export function ApiKeysPanel() {
|
||||
aria-checked={newKeyMode === m}
|
||||
onClick={() => setNewKeyMode(m)}
|
||||
className={cn(
|
||||
'rounded-[5px] px-3 py-1.5 text-xs transition-colors',
|
||||
'rounded-full px-3 py-1.5 text-xs transition-colors',
|
||||
newKeyMode === m
|
||||
? 'bg-secondary text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
@@ -683,7 +683,7 @@ export function ApiKeysPanel() {
|
||||
{hasSodConflict && (
|
||||
<div
|
||||
role="alert"
|
||||
className="flex items-start gap-2 rounded-md border border-border bg-muted/30 p-3 text-xs text-foreground"
|
||||
className="flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-foreground"
|
||||
>
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-warning" />
|
||||
<p className="leading-snug">{t('sod_warning')}</p>
|
||||
|
||||
@@ -277,7 +277,7 @@ export function BackupDownloadForm() {
|
||||
id="backup-period"
|
||||
value={selectedPeriodId}
|
||||
onChange={(e) => setSelectedPeriodId(e.target.value)}
|
||||
className="flex h-10 w-full max-w-xs rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
className="flex h-10 w-full max-w-xs rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
disabled={periods === null || periods.length === 0}
|
||||
>
|
||||
{/* The confirmed-empty option only after a confirmed empty
|
||||
@@ -308,7 +308,7 @@ export function BackupDownloadForm() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3 text-sm">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3 text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Info className="h-3.5 w-3.5" />
|
||||
{isLoadingEstimate ? (
|
||||
|
||||
@@ -155,7 +155,7 @@ export function BankNameCombobox({ defaultValue = '', value: controlledValue, on
|
||||
ref={listRef}
|
||||
id="bank-name-listbox"
|
||||
role="listbox"
|
||||
className="absolute z-50 mt-1 max-h-56 w-full overflow-auto rounded-md border border-border bg-popover shadow-md"
|
||||
className="absolute z-50 mt-1 max-h-56 w-full overflow-auto rounded-lg border border-border bg-popover shadow-md"
|
||||
>
|
||||
{filtered.map((bank, i) => (
|
||||
<li
|
||||
@@ -176,7 +176,7 @@ export function BankNameCombobox({ defaultValue = '', value: controlledValue, on
|
||||
<img
|
||||
src={bank.logo}
|
||||
alt=""
|
||||
className="h-5 w-5 flex-shrink-0 rounded object-contain"
|
||||
className="h-5 w-5 flex-shrink-0 rounded-sm object-contain"
|
||||
/>
|
||||
) : (
|
||||
<svg
|
||||
|
||||
@@ -150,7 +150,7 @@ export function InvoiceEmailTextsSettings({ settings, onUpdate }: InvoiceEmailTe
|
||||
<p>
|
||||
{t('placeholders_help')}{' '}
|
||||
{INVOICE_EMAIL_PLACEHOLDER_KEYS.map((key) => (
|
||||
<code key={key} className="mr-1 rounded bg-muted px-1 text-xs">{`{${key}}`}</code>
|
||||
<code key={key} className="mr-1 rounded-sm bg-muted px-1 text-xs">{`{${key}}`}</code>
|
||||
))}
|
||||
</p>
|
||||
<p>{t('firstname_note')}</p>
|
||||
|
||||
@@ -177,7 +177,7 @@ export function OAuthClientsPanel() {
|
||||
<DialogDescription>
|
||||
{t.rich('register_dialog_description', {
|
||||
bold: (chunks) => <span className="font-medium">{chunks}</span>,
|
||||
code: (chunks) => <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">{chunks}</code>,
|
||||
code: (chunks) => <code className="rounded-sm bg-muted px-1 py-0.5 font-mono text-[11px]">{chunks}</code>,
|
||||
})}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -242,7 +242,7 @@ export function SettingsSeg<T extends string>({
|
||||
aria-pressed={o.value === value}
|
||||
onClick={() => onChange(o.value)}
|
||||
className={cn(
|
||||
'rounded-md px-3 py-1 text-xs transition-colors duration-150 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
'rounded-sm px-3 py-1 text-xs transition-colors duration-150 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
o.value === value
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
|
||||
@@ -237,7 +237,7 @@ export function TemplateForm({
|
||||
{lines.map((line, i) => {
|
||||
const showRatioInput = showRatio && line.type === 'business'
|
||||
return (
|
||||
<div key={i} className="rounded-md border border-border p-2 space-y-1.5">
|
||||
<div key={i} className="rounded-lg border border-border p-2 space-y-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={line.account}
|
||||
|
||||
@@ -115,7 +115,7 @@ export function BankingSettingsContent() {
|
||||
setIsAccessDenied(false)
|
||||
setShowHbPoaHint(false)
|
||||
}}
|
||||
className="shrink-0 rounded-md p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
className="shrink-0 rounded-sm p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
aria-label={t('dismiss_aria')}
|
||||
>
|
||||
<span className="text-lg leading-none">×</span>
|
||||
|
||||
Reference in New Issue
Block a user