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
@@ -978,7 +978,7 @@ function MessageBubble({
|
||||
{isUser ? (
|
||||
message.text || (streamingTail ? <Cursor /> : '')
|
||||
) : message.text ? (
|
||||
<div className="prose prose-sm max-w-none text-foreground [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 prose-headings:font-display prose-headings:font-normal prose-headings:tracking-tight prose-h2:text-base prose-h2:mt-3 prose-h2:mb-2 prose-h3:text-sm prose-h3:mt-3 prose-h3:mb-1 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-blockquote:border-l-2 prose-blockquote:border-foreground/30 prose-blockquote:not-italic prose-blockquote:text-muted-foreground prose-blockquote:pl-3 prose-blockquote:my-2 prose-code:bg-secondary prose-code:rounded prose-code:px-1 prose-code:py-0.5 prose-code:text-xs prose-code:before:content-none prose-code:after:content-none prose-a:text-foreground prose-a:underline prose-a:underline-offset-2 prose-pre:bg-secondary prose-pre:text-foreground prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-pre:my-2 prose-pre:p-3 prose-pre:text-xs prose-pre:leading-relaxed prose-pre:overflow-x-auto [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-foreground [&_pre_code]:text-xs prose-table:my-2 prose-table:text-xs prose-table:border-collapse [&_table]:w-full [&_th]:border-b [&_th]:border-border [&_th]:py-1.5 [&_th]:px-2 [&_th]:text-left [&_th]:font-medium [&_th]:text-muted-foreground [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-[10px] [&_td]:border-b [&_td]:border-border [&_td]:py-1.5 [&_td]:px-2 [&_td]:align-top [&_tbody_tr:last-child_td]:border-b-0">
|
||||
<div className="prose prose-sm max-w-none text-foreground [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 prose-headings:font-display prose-headings:font-normal prose-headings:tracking-tight prose-h2:text-base prose-h2:mt-3 prose-h2:mb-2 prose-h3:text-sm prose-h3:mt-3 prose-h3:mb-1 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-blockquote:border-l-2 prose-blockquote:border-foreground/30 prose-blockquote:not-italic prose-blockquote:text-muted-foreground prose-blockquote:pl-3 prose-blockquote:my-2 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-a:text-foreground prose-a:underline prose-a:underline-offset-2 prose-pre:bg-secondary prose-pre:text-foreground prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-pre:my-2 prose-pre:p-3 prose-pre:text-xs prose-pre:leading-relaxed prose-pre:overflow-x-auto [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-foreground [&_pre_code]:text-xs prose-table:my-2 prose-table:text-xs prose-table:border-collapse [&_table]:w-full [&_th]:border-b [&_th]:border-border [&_th]:py-1.5 [&_th]:px-2 [&_th]:text-left [&_th]:font-medium [&_th]:text-muted-foreground [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-[10px] [&_td]:border-b [&_td]:border-border [&_td]:py-1.5 [&_td]:px-2 [&_td]:align-top [&_tbody_tr:last-child_td]:border-b-0">
|
||||
{markdownLoaded ? (
|
||||
<MarkdownMessage text={message.text} />
|
||||
) : (
|
||||
@@ -1124,7 +1124,7 @@ function MessageActions({
|
||||
}
|
||||
|
||||
const btn =
|
||||
'inline-flex items-center gap-1.5 rounded-md px-1.5 py-1 text-[11px] text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors'
|
||||
'inline-flex items-center gap-1.5 rounded-sm px-1.5 py-1 text-[11px] text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors'
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5 opacity-0 focus-within:opacity-100 group-hover/msg:opacity-100 transition-opacity">
|
||||
|
||||
@@ -80,14 +80,14 @@ export default function AgentSessionList({ activeConversationId, onSelect }: Pro
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Sök konversationer…"
|
||||
className="w-full rounded-md border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
{query.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setQuery('')}
|
||||
aria-label="Rensa sökning"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -135,7 +135,7 @@ export default function AgentSessionList({ activeConversationId, onSelect }: Pro
|
||||
placeholder="Namnge konversationen…"
|
||||
maxLength={200}
|
||||
aria-label="Nytt namn på konversationen"
|
||||
className="w-full rounded-md border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -617,7 +617,7 @@ export default function AgentSheet({
|
||||
>
|
||||
<button
|
||||
onClick={() => setView('chat')}
|
||||
className="h-9 w-9 -ml-1 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 -ml-1 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Tillbaka"
|
||||
title="Tillbaka"
|
||||
>
|
||||
@@ -626,7 +626,7 @@ export default function AgentSheet({
|
||||
<h2 className="font-display text-lg tracking-tight truncate">Konversationer</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-auto h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="ml-auto h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Stäng"
|
||||
title="Avsluta sessionen"
|
||||
>
|
||||
@@ -646,7 +646,7 @@ export default function AgentSheet({
|
||||
{!isSandbox && (
|
||||
<button
|
||||
onClick={() => setView('list')}
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Tidigare konversationer"
|
||||
title="Tidigare konversationer"
|
||||
>
|
||||
@@ -671,7 +671,7 @@ export default function AgentSheet({
|
||||
{!isSandbox && (
|
||||
<button
|
||||
onClick={toggleFloating}
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label={floating ? 'Docka mot högerkanten' : 'Frigör panelen'}
|
||||
title={
|
||||
floating
|
||||
@@ -693,7 +693,7 @@ export default function AgentSheet({
|
||||
{!isSandbox && !floating && (
|
||||
<button
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label={expanded ? 'Förminska' : 'Förstora'}
|
||||
title={expanded ? 'Förminska' : 'Förstora'}
|
||||
>
|
||||
@@ -706,7 +706,7 @@ export default function AgentSheet({
|
||||
{activeConversationId && !isSandbox && (
|
||||
<button
|
||||
onClick={onRestart}
|
||||
className="h-9 inline-flex items-center gap-2 rounded-md px-2 text-xs font-medium text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 inline-flex items-center gap-2 rounded-sm px-2 text-xs font-medium text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Rensa: börja en ny konversation"
|
||||
title="Rensa: börja en ny konversation"
|
||||
>
|
||||
@@ -716,7 +716,7 @@ export default function AgentSheet({
|
||||
)}
|
||||
<button
|
||||
onClick={handleCollapse}
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Minimera"
|
||||
title="Minimera: behåll sessionen"
|
||||
>
|
||||
@@ -724,7 +724,7 @@ export default function AgentSheet({
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Stäng"
|
||||
title="Avsluta sessionen"
|
||||
>
|
||||
|
||||
@@ -352,7 +352,7 @@ export default function ApprovalCard({
|
||||
</div>
|
||||
|
||||
{preview != null && typeof preview === 'object' && (
|
||||
<div className="rounded-md border border-border bg-muted/30 px-3 py-2">
|
||||
<div className="rounded-lg border border-border bg-muted/30 px-3 py-2">
|
||||
<OperationPreview
|
||||
op={{
|
||||
operation_type: previewOperationType,
|
||||
@@ -374,7 +374,7 @@ export default function ApprovalCard({
|
||||
value={confirmText}
|
||||
onChange={(e) => setConfirmText(e.target.value)}
|
||||
disabled={isBusy}
|
||||
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
autoComplete="off"
|
||||
aria-label="Bekräfta med ordet godkänn"
|
||||
/>
|
||||
@@ -384,7 +384,7 @@ export default function ApprovalCard({
|
||||
{errorMessage && <p className="text-xs text-destructive">{errorMessage}</p>}
|
||||
|
||||
{showRejectForm ? (
|
||||
<div className="space-y-2 rounded-md border border-border bg-muted/30 px-3 py-2">
|
||||
<div className="space-y-2 rounded-lg border border-border bg-muted/30 px-3 py-2">
|
||||
<p className="text-xs font-medium">Vad är fel?</p>
|
||||
<Select
|
||||
value={rejectCategory}
|
||||
@@ -448,7 +448,7 @@ export default function ApprovalCard({
|
||||
</div>
|
||||
</div>
|
||||
) : accountsToActivate ? (
|
||||
<div className="space-y-2 rounded-md border border-border bg-muted/30 px-3 py-2">
|
||||
<div className="space-y-2 rounded-lg border border-border bg-muted/30 px-3 py-2">
|
||||
<p className="text-xs leading-5">
|
||||
Bokningen använder konton som inte är aktiva i din kontoplan:{' '}
|
||||
<strong className="tabular-nums">{accountsToActivate.join(', ')}</strong>. Aktivera dem för att godkänna bokningen.
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function ChatConversationView({
|
||||
visible so a back button would be redundant. */}
|
||||
<Link
|
||||
href="/chat"
|
||||
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors -ml-1"
|
||||
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors -ml-1"
|
||||
aria-label="Tillbaka till konversationer"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
onClick={toggleCollapsed}
|
||||
aria-label="Visa konversationer"
|
||||
title="Visa konversationer"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
>
|
||||
<PanelLeftOpen className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -91,7 +91,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
onClick={() => openAgentSheet({ intentId: 'general.help' })}
|
||||
aria-label="Ny konversation"
|
||||
title="Ny konversation"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors text-lg"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors text-lg"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
@@ -123,7 +123,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
onClick={toggleCollapsed}
|
||||
aria-label="Dölj konversationer"
|
||||
title="Dölj konversationer"
|
||||
className="hidden md:inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
className="hidden md:inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
>
|
||||
<PanelLeftClose className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -141,14 +141,14 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Sök…"
|
||||
className="w-full rounded-md border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
{query.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setQuery('')}
|
||||
aria-label="Rensa sökning"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -191,7 +191,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
placeholder="Namnge konversationen…"
|
||||
maxLength={200}
|
||||
aria-label="Nytt namn på konversationen"
|
||||
className="w-full rounded-md border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -230,7 +230,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
}}
|
||||
title="Byt namn"
|
||||
aria-label="Byt namn på konversation"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
>
|
||||
<Pencil className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -243,7 +243,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
title={c.pinned ? 'Avfäst' : 'Fäst'}
|
||||
aria-label={c.pinned ? 'Avfäst konversation' : 'Fäst konversation'}
|
||||
className={cn(
|
||||
'inline-flex h-8 w-8 items-center justify-center rounded transition-colors',
|
||||
'inline-flex h-8 w-8 items-center justify-center rounded-sm transition-colors',
|
||||
c.pinned
|
||||
? 'text-foreground'
|
||||
: 'text-muted-foreground/50 hover:text-foreground hover:bg-secondary',
|
||||
@@ -263,7 +263,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
}}
|
||||
title="Arkivera"
|
||||
aria-label="Arkivera konversation"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
>
|
||||
<Archive className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user