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
+16 -1
View File
@@ -54,6 +54,7 @@ Decided during the 2026-07 concept work (dev_docs/ui_migration_plan.md); they ap
13. **Overlays**: centered modal for create/confirm (template, assistant, confirmations, settings); right slide-over for reviewing an object (Granskning detail). Both with veil, Esc, and click-outside.
14. **Manual base, AI as opt-in.** Base flows work without AI; AI entry points are clearly labeled discrete choices (e.g. "Skapa med assistenten") and no AI suggestion posts without Granskning.
15. **Settings speak "Fönster"** (founder-chosen 2026-07-25, applies to every settings tab). Settings content is flat hairline rows, never boxed cards: `SettingsGroup` / `SettingsRow` / `SettingsSectionHeader` from `components/settings/SettingsRows.tsx`. Toggles are switches, not checkboxes. Saving is a sticky bar that fades in only when the form is dirty (`components/settings/SettingsFormWrapper.tsx`); no always-visible save button. The settings surface is a 920x680 modal (`components/settings/SettingsModal.tsx`); switching tabs inside it updates the URL shallowly via `history.replaceState` (`SettingsRail.tsx`), never `router.replace`, which would remount the modal.
16. **One radius per role (the radius ladder, 2026-08-13).** Four tiers, tied to what a thing IS: pills for interactive toolbar controls (buttons, chips, pickers, segmented controls, toolbar search, count badges), `rounded-xl` (12px) for the overlay/panel tier (page panel, dialogs, slide-overs, command palette), `rounded-lg` (8px) for surfaces on the panel (cards, form fields, textareas, popover/dropdown/menu content, bordered boxes), `rounded-sm` (4px) for leaf elements nested inside 8px surfaces (menu items, checkboxes, kbd/code nubs, skeleton text lines). Toolbar controls share one height: `h-8`. `rounded-md`, bare `rounded`, `rounded-2xl` and arbitrary `rounded-[Npx]` are dead vocabulary, enforced by `check:guards` (off-ladder-radius). Nesting is concentric: pill-in-pill by construction, `rounded-sm` items inside `rounded-lg` content with ~4px inset.
## Design System Tokens
@@ -72,6 +73,17 @@ Decided during the 2026-07 concept work (dev_docs/ui_migration_plan.md); they ap
Compact metric cards (e.g. dashboard tiles, salary KPI row) use `p-4`. Detail cards use `p-6`. Never mix `p-5`.
**Radius ladder** (convention 16; enforced by `check:guards` off-ladder-radius). Radius communicates role, and every role has exactly one:
| Tier | Class | Use for |
|---|---|---|
| pill | `rounded-full` | Buttons, chips, badges, context pickers, segmented controls, toolbar search, count nubs, dots, avatars |
| 12px | `rounded-xl` | Page panel, dialogs, slide-overs, command palette, hero surfaces |
| 8px | `rounded-lg` | Cards, form inputs, textareas, popover/dropdown/menu content, bordered boxes, toasts |
| 4px | `rounded-sm` | Menu items, checkboxes, kbd/code/account-number nubs, skeleton text lines, small nested leaves |
**Forbidden radii:** `rounded-md`, bare `rounded`, `rounded-2xl`+, `rounded-[Npx]`. A search field is a pill in a toolbar (`ToolbarSearch`) and `rounded-lg` in a form or dialog (`Input`): the row it sits in decides, and mixing shapes in one row is the bug the ladder exists to prevent.
**Layout (frame layout).**
- The dashboard wrapper is `bg-frame` (`--frame: 40 18% 96%` light, `0 0% 5%` dark). `<main>` is the page panel: `bg-background rounded-xl border border-border`, 10px margin against the frame, own inner scroll (`md:h-[calc(100vh-20px)] md:overflow-y-auto`). Defined once as `MAIN_PANEL_CLASS` in `app/(dashboard)/layout.tsx`; never restyle per page.
- The panel is the desktop scroll container: `position: sticky` binds to it automatically; never assume `window` scroll on dashboard pages. Scroll reset on navigation lives in `MainContainer`.
@@ -91,6 +103,8 @@ Compact metric cards (e.g. dashboard tiles, salary KPI row) use `p-4`. Detail ca
| No-data state | `components/ui/empty-state.tsx` `EmptyState` | Don't hand-roll `<div className="flex flex-col items-center py-12">…</div>`. Preset variants exist (`EmptyInvoices`, `EmptyCustomers`, `EmptyTransactions`, etc.). |
| Loading placeholder | `components/ui/skeleton.tsx` `<Skeleton>` | Don't hand-roll `bg-muted rounded animate-pulse` divs. |
| Inline help / formulas | `components/ui/info-tooltip.tsx` `InfoTooltip` | Hover-revealed; don't use always-visible info buttons. |
| View/mode switcher in a toolbar | `components/ui/segmented-control.tsx` `SegmentedControl` | Pill-in-pill tablist at the shared `h-8` toolbar height; `options` take an optional `count` for the standard count chip. Never hand-roll the `bg-muted/70` tablist div. |
| Search in a page toolbar | `components/ui/toolbar-search.tsx` `ToolbarSearch` | Pill search at `h-8`, matching the chips and pickers beside it. Searches inside dialogs/pickers keep the regular `Input`. |
| Fiscal year picker | `components/common/FyPicker.tsx` | The chip-dropdown context picker of convention 8 (wraps `ContextPicker`). `FiscalYearSelector` is the legacy pre-frame control: don't add new uses. Never use a raw `<select>` for fiscal periods. |
| Settings rows / save | `components/settings/SettingsRows.tsx`, `SettingsFormWrapper.tsx` | Fönster language (convention 15): flat hairline rows, dirty-only sticky save bar. Don't hand-roll settings cards or per-field save buttons. |
@@ -125,6 +139,7 @@ Never render raw `{x.invoice_date}` directly: always route through `formatDate()
- `active:scale-[...]` on buttons. Buttons do not bounce.
- `bg-gradient-to-*` on page or card backgrounds. Flat surfaces only.
- `font-medium` on display elements (`font-display`, h1/h2/h3, CardTitle, PageHeader title). Hedvig is single-weight by design.
- `rounded-xl` (12px) on cards. Cards are `rounded-lg` (8px). `rounded-xl` survives on the page panel (frame layout) and prominent hero-style surfaces only.
- `rounded-xl` (12px) on cards. Cards are `rounded-lg` (8px). `rounded-xl` is the overlay/panel tier: page panel, dialogs, slide-overs, hero surfaces.
- Off-ladder radii: `rounded-md`, bare `rounded`, `rounded-2xl`, `rounded-[Npx]`. See the radius ladder above; `check:guards` fails on any of them in `app/` or `components/`.
- Overriding the Button pill radius per call site (`rounded-lg`, `rounded-md` on a `<Button>`). Buttons are pills app-wide; the radius lives in `components/ui/button.tsx` alone.
- Opacity-suffixed border classes (`border-border/30`, `border-border/60`) on cards and primary surfaces. Use full-opacity `border-border`: the new border token is calibrated for that.
+3
View File
@@ -978,3 +978,6 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-14] PR #1608 round-2 compliance finding (VMB class-3/4 VAT hole): account_override now books GROSS with no auto-VAT line unless the caller stated explicit VAT intent (vat_treatment or vat_amount). Deliberate divergence from v1 REST, which keeps the category-default standard_25 on overrides: MCP callers are agents, and a forgotten flag must under-deduct (lawful), never fabricate an ingående-moms deduction on a margin-scheme account. The review's class-appropriateness suggestion (block e.g. expense onto 27xx) was noted but not implemented: the approval gate is the guard, and a class matrix would block legitimate balance-sheet bookings v1 REST supports today.
[2026-08-14] Skatteverket connections are per (user, company): the stacked UNIQUE(user_id) + UNIQUE(company_id) pair is replaced by UNIQUE(user_id, company_id), every token read/write is company-scoped, and /skattekonto/saldo returns 401 NOT_CONNECTED when the active company has no row. A multi-company operator connects each company separately; legacy NULL-company rows are ignored by scoped reads and just require a reconnect.
[2026-08-14] Pristine empty-state gates key on all-years emptiness (a count probe), never on the scoped list result: a default fiscal-year selection is a scope, not a filter, and it made the bookkeeping start card unreachable on brand-new companies.
[2026-08-13] Radius ladder locked (convention 16): 4 tiers by role (pill toolbar controls / rounded-xl overlays / rounded-lg surfaces / rounded-sm leaves); rounded-md, bare rounded, rounded-2xl and rounded-[Npx] retired app-wide, hard-failed by check:guards off-ladder-radius. Before: 7 radii in circulation with no rule; one toolbar row on /transactions mixed 4 shape languages.
[2026-08-13] Toolbar shape language: pills won over "inputs stay rectangles". Search in a page toolbar is a pill (ToolbarSearch, h-8) matching chips/pickers/buttons beside it; the same search inside a dialog or form keeps rounded-lg Input. Rationale: convention 8 already made pickers pill chips and convention 3 made buttons pills, so the rectangle search/segmented were the odd ones out; one row = one shape reads as trust.
[2026-08-13] Dialogs promoted 8px -> 12px (rounded-xl): SettingsModal, slide-over and CommandPalette were already 12px, so dialog.tsx was the overlay-tier outlier, not the rule.
+2 -2
View File
@@ -371,7 +371,7 @@ export function LoginClient({ initialMethod }: { initialMethod: LoginMethod | nu
<div className="min-h-dvh flex flex-col items-center justify-center bg-frame p-4">
<div className="w-full max-w-sm animate-slide-up space-y-8">
<div className="flex justify-center">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<Mail className="h-7 w-7 text-primary" />
</div>
</div>
@@ -432,7 +432,7 @@ export function LoginClient({ initialMethod }: { initialMethod: LoginMethod | nu
<div className="w-full max-w-sm animate-slide-up">
<div className="text-center mb-10">
<div className="flex justify-center mb-4">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<KeyRound className="h-7 w-7 text-primary" />
</div>
</div>
+3 -3
View File
@@ -179,7 +179,7 @@ function MfaEnrollContent() {
<div className="w-full max-w-sm animate-slide-up">
<div className="text-center mb-10">
<div className="flex justify-center mb-4">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<ShieldCheck className="h-7 w-7 text-primary" />
</div>
</div>
@@ -233,7 +233,7 @@ function MfaEnrollContent() {
<div className="w-full max-w-sm animate-slide-up">
<div className="text-center mb-8">
<div className="flex justify-center mb-4">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<ShieldCheck className="h-7 w-7 text-primary" />
</div>
</div>
@@ -258,7 +258,7 @@ function MfaEnrollContent() {
Kan du inte skanna? Ange denna nyckel manuellt:
</p>
<div className="flex items-center gap-2">
<code className="flex-1 rounded-md border bg-muted/50 px-3 py-2 text-xs font-mono text-center break-all select-all">
<code className="flex-1 rounded-sm border bg-muted/50 px-3 py-2 text-xs font-mono text-center break-all select-all">
{secret}
</code>
<Button
+1 -1
View File
@@ -174,7 +174,7 @@ function MfaVerifyContent() {
<div className="w-full max-w-sm animate-slide-up">
<div className="text-center mb-10">
<div className="flex justify-center mb-4">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<ShieldCheck className="h-7 w-7 text-primary" />
</div>
</div>
+2 -2
View File
@@ -387,7 +387,7 @@ function RegisterPageContent() {
<div className="min-h-dvh flex flex-col items-center justify-center bg-frame p-4">
<div className="w-full max-w-sm animate-slide-up space-y-8">
<div className="flex justify-center">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<Mail className="h-7 w-7 text-primary" />
</div>
</div>
@@ -440,7 +440,7 @@ function RegisterPageContent() {
<div className="min-h-dvh flex flex-col items-center justify-center bg-frame p-4">
<div className="w-full max-w-sm animate-slide-up space-y-8">
<div className="flex justify-center">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<Mail className="h-7 w-7 text-primary" />
</div>
</div>
+1 -1
View File
@@ -212,7 +212,7 @@ function ResetPasswordInner() {
<div className="w-full max-w-sm animate-slide-up">
<div className="text-center mb-10">
<div className="flex justify-center mb-4">
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
<KeyRound className="h-7 w-7 text-primary" />
</div>
</div>
+11 -14
View File
@@ -7,13 +7,13 @@ import { useSearchParams, useRouter, usePathname } from 'next/navigation'
import { createClient } from '@/lib/supabase/client'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Skeleton } from '@/components/ui/skeleton'
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
import { useToast } from '@/components/ui/use-toast'
import { getErrorMessage, type ErrorLocale } from '@/lib/errors/get-error-message'
import { Plus, Search, Package, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
import { Plus, Package, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
import { ActivateAccountsDialog } from '@/components/bookkeeping/ActivateAccountsDialog'
import {
useSubmitWithAccountActivation,
@@ -315,18 +315,15 @@ function ArticlesPageInner() {
{/* Toolbar: search, plus the currency scope far right (convention 8) */}
<div className="flex flex-wrap items-center gap-2">
<div className="relative min-w-[190px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setVisibleCount(INITIAL_VISIBLE_ROWS)
}}
className="h-9 pl-10"
/>
</div>
<ToolbarSearch
containerClassName="min-w-[190px]"
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setVisibleCount(INITIAL_VISIBLE_ROWS)
}}
/>
{/* A single-currency register needs no scope: the chip would be a
control with one meaningful position. */}
{availableCurrencies.length > 1 && (
+4 -4
View File
@@ -325,7 +325,7 @@ export default function DisposeAssetPage({ params }: { params: Promise<{ id: str
<SelectContent>{VAT_TREATMENTS.map((value) => <SelectItem key={value} value={value}>{t(`vat_${value}`)}</SelectItem>)}</SelectContent>
</Select>
</Field>
<div className="rounded-md bg-secondary/40 p-3 text-xs">
<div className="rounded-lg bg-secondary/40 p-3 text-xs">
<SummaryRow label={t('gross')} value={formatCurrency(proceedsNumber)} />
<SummaryRow label={t('vat')} value={formatCurrency(vatAmount)} />
<SummaryRow label={t('net')} value={formatCurrency(netProceeds)} strong />
@@ -351,20 +351,20 @@ export default function DisposeAssetPage({ params }: { params: Promise<{ id: str
</div>
)}
{jamkningAssessment && (
<div className="rounded-md border border-border bg-secondary/40 p-3 text-sm">
<div className="rounded-lg border border-border bg-secondary/40 p-3 text-sm">
<SummaryRow label={t('adjustment_direction')} value={t(`direction_${jamkningAssessment.direction}`)} />
<SummaryRow label={t('adjustment_amount')} value={formatCurrency(jamkningAssessment.amount)} strong />
{jamkningAssessment.capped && <p className="mt-2 text-xs text-muted-foreground">{t('adjustment_capped')}</p>}
</div>
)}
{disposalType === 'business_transfer' && (
<div className="flex items-center gap-3 rounded-md border border-border p-3">
<div className="flex items-center gap-3 rounded-lg border border-border p-3">
<Switch id="businessTransfer" checked={businessTransferConfirmed} onCheckedChange={setBusinessTransferConfirmed} />
<Label htmlFor="businessTransfer" className="cursor-pointer">{t('business_transfer_confirm')}</Label>
</div>
)}
{disposalType === 'business_transfer' && transferNeedsDocument && (
<div className="flex items-center gap-3 rounded-md border border-border p-3">
<div className="flex items-center gap-3 rounded-lg border border-border p-3">
<Switch id="adjustmentDocument" checked={adjustmentDocumentConfirmed} onCheckedChange={setAdjustmentDocumentConfirmed} />
<Label htmlFor="adjustmentDocument" className="cursor-pointer">{t('adjustment_document_confirm')}</Label>
</div>
+3 -3
View File
@@ -842,7 +842,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
<button
type="button"
onClick={() => setRetagLine(line as unknown as RetagLine)}
className="p-1 rounded text-muted-foreground/50 hover:text-foreground hover:bg-secondary/60 transition-colors"
className="p-1 rounded-sm text-muted-foreground/50 hover:text-foreground hover:bg-secondary/60 transition-colors"
aria-label="Ändra dimensioner"
title="Ändra dimensioner (påverkar endast internredovisningen)"
>
@@ -932,7 +932,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
<button
type="button"
onClick={() => setRetagLine(line as unknown as RetagLine)}
className="p-1 rounded text-muted-foreground/50 hover:text-foreground transition-colors"
className="p-1 rounded-sm text-muted-foreground/50 hover:text-foreground transition-colors"
aria-label="Ändra dimensioner"
>
<Pencil className="h-3.5 w-3.5" />
@@ -996,7 +996,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
<li key={`${ref.type}-${ref.id}`}>
<Link
href={ref.type === 'invoice' ? `/invoices/${ref.id}` : `/supplier-invoices/${ref.id}`}
className="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50 hover:bg-secondary/60 transition-colors"
className="flex items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50 hover:bg-secondary/60 transition-colors"
>
<FileText className="h-4 w-4 text-muted-foreground shrink-0" />
<span className="truncate">
@@ -14,6 +14,7 @@ import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'
import { EmptyState } from '@/components/ui/empty-state'
import { ConfirmDialog } from '@/components/ui/confirm-dialog'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS, RowFoldout } from '@/components/ui/dry-table'
import { useToast } from '@/components/ui/use-toast'
import { useCanWrite } from '@/lib/hooks/use-can-write'
@@ -211,35 +212,15 @@ export default function AccrualSchedulesPage() {
) : null}
{/* Toolbar: status seg (concept scene 33) */}
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
{(
[
{ key: 'active', label: 'Aktiva', count: activeCount },
{ key: 'completed', label: 'Avslutade', count: null },
{ key: 'all', label: 'Alla', count: null },
] as const
).map(({ key, label, count }) => (
<button
key={key}
type="button"
role="tab"
aria-selected={statusFilter === key}
onClick={() => setStatusFilter(key)}
className={`inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
statusFilter === key
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{label}
{count !== null && count > 0 && (
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
{count}
</span>
)}
</button>
))}
</div>
<SegmentedControl
value={statusFilter}
onChange={setStatusFilter}
options={[
{ value: 'active', label: 'Aktiva', count: activeCount ?? undefined },
{ value: 'completed', label: 'Avslutade' },
{ value: 'all', label: 'Alla' },
]}
/>
{isLoading ? (
<div className="space-y-3">
@@ -522,7 +522,7 @@ function VacationStep({
</CardHeader>
<CardContent>
{proposal ? (
<div className="flex items-start justify-between gap-4 rounded-md border border-border p-4">
<div className="flex items-start justify-between gap-4 rounded-lg border border-border p-4">
<div className="flex-1 space-y-2">
<p className="text-sm font-medium">{proposal.label}</p>
<p className="text-xs text-muted-foreground">{proposal.description}</p>
@@ -590,7 +590,7 @@ function AuditStep({
</Label>
</div>
{state.enabled && (
<div className="grid grid-cols-2 gap-4 rounded-md border border-border p-4">
<div className="grid grid-cols-2 gap-4 rounded-lg border border-border p-4">
<div className="space-y-1">
<Label className="text-xs">Belopp (kr)</Label>
<Input
@@ -605,7 +605,7 @@ function AuditStep({
<div className="space-y-1">
<Label className="text-xs">Konto</Label>
<select
className="border border-border rounded-md h-9 text-sm px-2 w-full bg-background"
className="border border-border rounded-lg h-9 text-sm px-2 w-full bg-background"
value={state.liabilityAccount}
onChange={(e) =>
onChange({ ...state, liabilityAccount: e.target.value as '2991' | '2992' })
@@ -667,7 +667,7 @@ function AutoStep({
return (
<div
key={key}
className="flex items-start gap-3 rounded-md border border-border p-3"
className="flex items-start gap-3 rounded-lg border border-border p-3"
>
<Checkbox
id={`auto-${key}`}
@@ -808,7 +808,7 @@ function ManualEntryEditor({
template.side === 'deferred_revenue' ? 'Intäktskonto' : 'Kostnadskonto'
return (
<div className="rounded-md border border-border p-3 space-y-3">
<div className="rounded-lg border border-border p-3 space-y-3">
<div className="flex items-center justify-between">
<p className="text-sm font-medium">{template.name}</p>
<Button variant="ghost" size="sm" onClick={onRemove} className="h-7 px-2" aria-label="Ta bort">
@@ -989,7 +989,7 @@ function ReviewStep({
function ReviewLine({ label, amount, note }: { label: string; amount: number; note?: string }) {
return (
<div className="flex items-center justify-between gap-4 rounded-md border border-border px-3 py-2">
<div className="flex items-center justify-between gap-4 rounded-lg border border-border px-3 py-2">
<div className="min-w-0">
<p className="text-sm font-medium truncate">{label}</p>
{note && <p className="text-xs text-muted-foreground">{note}</p>}
+10 -14
View File
@@ -6,13 +6,13 @@ import { useLocale, useTranslations } from 'next-intl'
import { useSearchParams, useRouter, usePathname } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Skeleton } from '@/components/ui/skeleton'
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
import { useToast } from '@/components/ui/use-toast'
import { getErrorMessage, type ErrorLocale } from '@/lib/errors/get-error-message'
import { Plus, Search, Users, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
import { Plus, Users, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
import { EmptyCustomers, EmptyState } from '@/components/ui/empty-state'
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
import { cn } from '@/lib/utils'
@@ -293,18 +293,14 @@ function CustomersPageInner() {
{/* Toolbar: search (concept) */}
<div className="flex flex-wrap items-center gap-2">
<div className="relative min-w-[220px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setVisibleCount(INITIAL_VISIBLE_ROWS)
}}
className="h-9 pl-10"
/>
</div>
<ToolbarSearch
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setVisibleCount(INITIAL_VISIBLE_ROWS)
}}
/>
</div>
{isLoading ? (
+12 -26
View File
@@ -10,6 +10,7 @@ import { Badge } from '@/components/ui/badge'
import { PageHeader } from '@/components/ui/page-header'
import { HelpPopover } from '@/components/ui/help-popover'
import { AttnLine } from '@/components/ui/attn-line'
import { SegmentedControl } from '@/components/ui/segmented-control'
import {
Dialog,
DialogContent,
@@ -2048,7 +2049,7 @@ function CSVDataImportWizard() {
onClick={() => setEntity(opt.value)}
aria-pressed={selected}
className={cn(
'relative h-9 rounded-md border px-4 text-sm font-medium transition-colors',
'relative h-9 rounded-lg border px-4 text-sm font-medium transition-colors',
selected
? 'border-foreground bg-foreground text-background'
: 'border-border bg-card text-foreground hover:border-foreground/30 hover:bg-muted',
@@ -2203,29 +2204,14 @@ export default function ImportPage() {
{isSandbox && <AttnLine>{t('sandbox_disabled')}</AttnLine>}
{/* Importera / Exportera as separate tabs (house seg), like before */}
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
{(
[
{ key: 'import', label: t('tab_import') },
{ key: 'export', label: t('tab_export') },
] as const
).map(({ key, label }) => (
<button
key={key}
type="button"
role="tab"
aria-selected={view === key}
onClick={() => handleViewChange(key)}
className={`rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
view === key
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{label}
</button>
))}
</div>
<SegmentedControl
value={view}
onChange={handleViewChange}
options={[
{ value: 'import', label: t('tab_import') },
{ value: 'export', label: t('tab_export') },
]}
/>
{view === 'import' ? (
<div>
@@ -2370,7 +2356,7 @@ export default function ImportPage() {
<label className="flex cursor-pointer items-start gap-2 text-sm text-muted-foreground">
<input
type="checkbox"
className="mt-0.5 h-4 w-4 rounded border-border"
className="mt-0.5 h-4 w-4 rounded-sm border-border"
checked={exportExcludeClosing}
onChange={(e) => setExportExcludeClosing(e.target.checked)}
/>
@@ -2551,7 +2537,7 @@ function ImportRow({
// with the inverse lift in dark mode.
function LogoChip({ src, name, mono = false }: { src: string; name: string; mono?: boolean }) {
return (
<span className="flex items-center gap-2 rounded border border-border bg-muted/30 px-2 py-1">
<span className="flex items-center gap-2 rounded-sm border border-border bg-muted/30 px-2 py-1">
<img
src={src}
alt=""
+1 -1
View File
@@ -1580,7 +1580,7 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
return (
<li
key={p.id}
className="flex items-center justify-between gap-3 px-2 py-2 text-sm transition-colors hover:bg-secondary/60 rounded"
className="flex items-center justify-between gap-3 px-2 py-2 text-sm transition-colors hover:bg-secondary/60 rounded-sm"
>
<span className="tabular-nums text-muted-foreground">
{formatDate(p.payment_date)}
+10 -14
View File
@@ -12,7 +12,7 @@ import { Badge } from '@/components/ui/badge'
import { Checkbox } from '@/components/ui/checkbox'
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
import { RowStatus, type RowStatusDescriptor } from '@/components/ui/row-status'
import { Input } from '@/components/ui/input'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { Skeleton } from '@/components/ui/skeleton'
import { Dialog, DialogContent, DialogTitle, DialogVeil } from '@/components/ui/dialog'
import { DataListEmpty } from '@/components/ui/data-list'
@@ -39,7 +39,6 @@ import {
ArrowUp,
ArrowUpDown,
Plus,
Search,
ReceiptText,
Repeat,
FileInput,
@@ -620,18 +619,15 @@ export default function InvoicesPage() {
annotation: tabCounts[tab] > 0 ? String(tabCounts[tab]) : undefined,
}))}
/>
<div className="relative min-w-[190px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
resetPaging()
}}
className="h-9 pl-10"
/>
</div>
<ToolbarSearch
containerClassName="min-w-[190px]"
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
resetPaging()
}}
/>
<div className="ml-auto">
<FyPicker
value={fyPeriodId}
+3 -3
View File
@@ -31,7 +31,7 @@ export default function DashboardLoading() {
full-width list, so that shape stays there. */}
<aside className="hidden md:flex md:w-12 shrink-0 flex-col items-center border-r border-border bg-card/40 py-3 gap-2">
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-8 w-8 rounded-md" />
<Skeleton className="h-8 w-8" />
</aside>
<aside className="flex w-full flex-col border-r border-border bg-card/40 md:hidden shrink-0">
<div className="space-y-3 border-b border-border px-5 py-4">
@@ -42,7 +42,7 @@ export default function DashboardLoading() {
<Skeleton className="h-2.5 w-20" />
</div>
</div>
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full" />
</div>
<div className="space-y-1 p-3">
{['w-40', 'w-48', 'w-36', 'w-44'].map((w, i) => (
@@ -80,7 +80,7 @@ export default function DashboardLoading() {
key={i}
className="flex items-start gap-3 border-b border-border px-1 py-3.5"
>
<Skeleton className="mt-px h-[15px] w-[15px] shrink-0 rounded" />
<Skeleton className="mt-px h-[15px] w-[15px] shrink-0" />
<Skeleton className={`h-3.5 ${w}`} />
<Skeleton className="ml-auto h-5 w-7 shrink-0 rounded-full" />
</div>
+13 -27
View File
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { DataListEmpty, DataListLoading } from '@/components/ui/data-list'
import { ContextPicker } from '@/components/common/ContextPicker'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { HOVER_REVEAL_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
import {
SlideOver,
@@ -229,7 +230,7 @@ function useAccountNamesSource(): Record<string, string> {
function PeriodLockBanner({ period }: { period: PeriodStatusShape }) {
const lockedThrough = period.lock_date ? formatDate(period.lock_date) : null
return (
<div className="flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm">
<div className="flex items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm">
<Lock className="h-4 w-4 text-destructive mt-0.5 shrink-0" />
<div className="flex-1">
<p className="font-medium text-destructive">
@@ -704,7 +705,7 @@ export default function PendingOperationsPage() {
</div>
{conversationFilter && (
<div className="flex items-center justify-between rounded-md border bg-muted/30 px-3 py-2 text-sm">
<div className="flex items-center justify-between rounded-lg border bg-muted/30 px-3 py-2 text-sm">
<div className="flex items-center gap-2">
<MessageSquare className="h-4 w-4 text-muted-foreground" />
<span>
@@ -733,30 +734,15 @@ export default function PendingOperationsPage() {
{/* Toolbar (concept): status seg left, source picker (convention 8)
far right. The count chip rides only on Väntar: it is the queue. */}
<div className="flex flex-wrap items-center gap-2">
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
{SEG_TABS.map(({ tab, labelKey }) => (
<button
key={tab}
type="button"
role="tab"
aria-selected={activeTab === tab}
onClick={() => setActiveTab(tab)}
className={cn(
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
activeTab === tab
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
{t(labelKey)}
{tab === 'pending' && (pendingCount ?? 0) > 0 && (
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
{pendingCount}
</span>
)}
</button>
))}
</div>
<SegmentedControl
value={activeTab}
onChange={setActiveTab}
options={SEG_TABS.map(({ tab, labelKey }) => ({
value: tab,
label: t(labelKey),
count: tab === 'pending' ? pendingCount ?? 0 : undefined,
}))}
/>
<div className="ml-auto">
<ContextPicker
value={sourceFilter}
@@ -1190,7 +1176,7 @@ export default function PendingOperationsPage() {
>
<div className="space-y-3 text-sm">
<p>{t('bulk_confirm_intro')}</p>
<ul className="space-y-1 rounded-md border bg-muted/30 px-3 py-2">
<ul className="space-y-1 rounded-lg border bg-muted/30 px-3 py-2">
{selectedBreakdown.map(({ type, count }) => (
<li key={type} className="flex justify-between font-mono tabular-nums">
<span className="font-sans">{bulkActionLabel(type, count, t)}</span>
@@ -163,7 +163,7 @@ export default function SalaryRunEmployeeDetailPage({
>
<ArrowLeft className="mr-1 h-3.5 w-3.5" /> {t('back_to_run')}
</Link>
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive">
{error ?? t('error_load_employee')}
</div>
</div>
@@ -321,7 +321,7 @@ export default function SalaryRunEmployeeDetailPage({
function SummaryCard({ label, value, accent, overridden }: { label: string; value: number; accent?: boolean; overridden?: boolean }) {
const t = useTranslations('salary_run_employee')
return (
<div className={cn('rounded-md border bg-card p-3', accent && 'ring-1 ring-primary/40')}>
<div className={cn('rounded-lg border bg-card p-3', accent && 'ring-1 ring-primary/40')}>
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
{label}
{/* The override is an exception, so it is a chip, not a second ring:
@@ -337,7 +337,7 @@ function SummaryCard({ label, value, accent, overridden }: { label: string; valu
function AbsenceCount({ label, days }: { label: string; days: number }) {
const t = useTranslations('salary_run_employee')
return (
<div className="rounded-md border bg-muted/30 px-3 py-2">
<div className="rounded-lg border bg-muted/30 px-3 py-2">
<div className="text-[11px] text-muted-foreground">{label}</div>
<div className="text-sm font-medium tabular-nums">{t('days_count', { days })}</div>
</div>
+1 -1
View File
@@ -896,7 +896,7 @@ export default function SalaryRunPage({ params }: { params: Promise<{ id: string
</DialogDescription>
</DialogHeader>
{approveOverride && approveOverride.length > 0 && (
<div className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
<div className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-warning" />
<ul className="space-y-1">
{approveOverride.map((reason, i) => (
+1 -1
View File
@@ -888,7 +888,7 @@ function MatchDialog({
)}
{!loading && candidates && candidates.length > 0 && (
<div className="max-h-[420px] overflow-y-auto rounded-md border">
<div className="max-h-[420px] overflow-y-auto rounded-lg border">
<Table>
<TableHeader>
<TableRow>
@@ -1080,7 +1080,7 @@ export default function SupplierInvoiceDetailPage() {
onChange={(e) => updateEditLine(i, { description: e.target.value })}
placeholder="Beskrivning"
/>
<div className="inline-flex rounded-md border bg-background overflow-hidden h-9">
<div className="inline-flex rounded-lg border bg-background overflow-hidden h-9">
<button
type="button"
onClick={() => updateEditLine(i, { side: 'debit' })}
@@ -1197,7 +1197,7 @@ export default function SupplierInvoiceDetailPage() {
? t('duplicate_payment_description_one')
: t('duplicate_payment_description_many')}
</p>
<div className="space-y-2 rounded-md border bg-muted/30 p-3">
<div className="space-y-2 rounded-lg border bg-muted/30 p-3">
{duplicateCandidates?.map((c) => (
<div key={c.id} className="flex items-center justify-between gap-3 text-sm">
<div className="min-w-0">
+8 -11
View File
@@ -8,13 +8,13 @@ import { Skeleton } from '@/components/ui/skeleton'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { Input } from '@/components/ui/input'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { DataListEmpty } from '@/components/ui/data-list'
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
import { FyPicker } from '@/components/common/FyPicker'
import { ContextPicker } from '@/components/common/ContextPicker'
import { HelpPopover } from '@/components/ui/help-popover'
import { Plus, FileInput, Lock, Search } from 'lucide-react'
import { Plus, FileInput, Lock } from 'lucide-react'
import Link from 'next/link'
import { DialogLoadingSkeleton } from '@/components/ui/dialog-loading-skeleton'
import { useCanWrite } from '@/lib/hooks/use-can-write'
@@ -316,15 +316,12 @@ export default function SupplierInvoicesPage() {
: undefined,
}))}
/>
<div className="relative min-w-[190px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="h-9 pl-10"
/>
</div>
<ToolbarSearch
containerClassName="min-w-[190px]"
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<div className="ml-auto flex items-center gap-4">
<Link href="/supplier-invoices/payment-files" className={QUIET_LINK_CLASS}>
{t('payment_files_link')}
+10 -14
View File
@@ -6,14 +6,14 @@ import { useRouter } from 'next/navigation'
import { useTranslations } from 'next-intl'
import { createClient } from '@/lib/supabase/client'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { EmptyState } from '@/components/ui/empty-state'
import { Skeleton } from '@/components/ui/skeleton'
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
import { useToast } from '@/components/ui/use-toast'
import { Plus, Search, Building2, Lock } from 'lucide-react'
import { Plus, Building2, Lock } from 'lucide-react'
import Link from 'next/link'
import { cn } from '@/lib/utils'
import { useCompany } from '@/contexts/CompanyContext'
@@ -170,18 +170,14 @@ export default function SuppliersPage() {
{/* Toolbar: search (concept) */}
<div className="flex flex-wrap items-center gap-2">
<div className="relative min-w-[220px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setVisibleCount(INITIAL_VISIBLE_ROWS)
}}
className="h-9 pl-10"
/>
</div>
<ToolbarSearch
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setVisibleCount(INITIAL_VISIBLE_ROWS)
}}
/>
</div>
{isLoading ? (
+28 -73
View File
@@ -14,10 +14,11 @@ import { ToastAction } from '@/components/ui/toast'
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog'
import { DataList, DataListEmpty } from '@/components/ui/data-list'
import { Input } from '@/components/ui/input'
import { Skeleton } from '@/components/ui/skeleton'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { TH_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
import { Loader2, Search } from 'lucide-react'
import { Loader2 } from 'lucide-react'
import TransactionStatusBar from '@/components/transactions/TransactionStatusBar'
import BankSyncStatusChip from '@/components/transactions/BankSyncStatusChip'
import { ContextPicker, type ContextPickerItem } from '@/components/common/ContextPicker'
@@ -3177,71 +3178,25 @@ export default function TransactionsPage() {
{/* Toolbar (concept order): [Att bokföra/Alla-seg] [sök] [Välj flera]
... [source ContextPicker far right] */}
<div className="flex flex-wrap items-center gap-2">
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
<button
type="button"
role="tab"
aria-selected={mode === 'inbox'}
onClick={() => setMode('inbox')}
className={`inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
mode === 'inbox'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{t('mode_inbox')}
{inboxBadgeCount > 0 && (
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
{inboxBadgeCount}
</span>
)}
</button>
<button
type="button"
role="tab"
aria-selected={mode === 'history'}
onClick={() => setMode('history')}
className={`rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
mode === 'history'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{t('mode_all')}
</button>
{/* Review tab exists only while suggestions do (or while the user is
standing in it after emptying the list): a permanent third tab
would advertise a migrator surface most companies never need. */}
{(suggestionItems.length > 0 || mode === 'review') && (
<button
type="button"
role="tab"
aria-selected={mode === 'review'}
onClick={() => setMode('review')}
className={`inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
mode === 'review'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{t('mode_review')}
{suggestionItems.length > 0 && (
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
{suggestionItems.length}
</span>
)}
</button>
)}
</div>
<div className="relative min-w-[220px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Sök transaktioner…"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="h-9 pl-10"
/>
</div>
<SegmentedControl
value={mode}
onChange={setMode}
options={[
{ value: 'inbox', label: t('mode_inbox'), count: inboxBadgeCount },
{ value: 'history', label: t('mode_all') },
// Review tab exists only while suggestions do (or while the user is
// standing in it after emptying the list): a permanent third tab
// would advertise a migrator surface most companies never need.
...(suggestionItems.length > 0 || mode === 'review'
? [{ value: 'review' as const, label: t('mode_review'), count: suggestionItems.length }]
: []),
]}
/>
<ToolbarSearch
placeholder="Sök transaktioner…"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
{/* Far-right context group, shared by both view modes: period scope
(rakenskapsar chip) and the account chooser (concept scene 10).
Approved deviation from convention 8's single chip: booking is
@@ -3276,12 +3231,12 @@ export default function TransactionsPage() {
<DataList className="stagger-enter">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-center gap-3 px-4 py-3">
<Skeleton className="h-5 w-5 rounded" />
<Skeleton className="h-5 w-5" />
<div className="flex-1 space-y-2">
<Skeleton className="h-4 w-48 rounded" />
<Skeleton className="h-3 w-24 rounded" />
<Skeleton className="h-4 w-48" />
<Skeleton className="h-3 w-24" />
</div>
<Skeleton className="h-5 w-20 rounded" />
<Skeleton className="h-5 w-20" />
</div>
))}
</DataList>
@@ -3857,7 +3812,7 @@ export default function TransactionsPage() {
fakturan istället för att bokföra direkt på leverantörsskuldskontot, annars skapas en
dubblerad verifikation som måste stornas (BFL 5 kap 5 §).
</p>
<div className="space-y-2 rounded-md border bg-muted/30 p-3">
<div className="space-y-2 rounded-lg border bg-muted/30 p-3">
{siMatchSuggestion?.candidates.map((c) => (
<div key={c.supplier_invoice_id} className="flex items-center justify-between gap-3 text-sm">
<div className="min-w-0">
@@ -3915,7 +3870,7 @@ export default function TransactionsPage() {
istället för att bokföra direkt mot kundfordringskontot, annars skapas en dubblerad
verifikation som måste stornas (BFL 5 kap 5 §).
</p>
<div className="space-y-2 rounded-md border bg-muted/30 p-3">
<div className="space-y-2 rounded-lg border bg-muted/30 p-3">
{ciMatchSuggestion?.candidates.map((c) => (
<div key={c.invoice_id} className="flex items-center justify-between gap-3 text-sm">
<div className="min-w-0">
+1 -1
View File
@@ -78,7 +78,7 @@ export default function GlobalError({
</p>
<button
onClick={() => window.location.reload()}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90"
className="rounded-full bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90"
>
Försök igen
</button>
+1 -1
View File
@@ -134,7 +134,7 @@ export default async function PayslipPage({
<a
href={`/api/payslip/${token}/pdf`}
className="inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors duration-150 hover:bg-primary/90"
className="inline-flex h-10 w-full items-center justify-center rounded-full bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors duration-150 hover:bg-primary/90"
>
Ladda ner PDF (lönespecifikation)
</a>
+1 -1
View File
@@ -329,7 +329,7 @@ export function LedgerGraph({ deep, companyName }: { deep: DeepLedgerContext; co
<button
type="button"
onClick={() => setRunKey((k) => k + 1)}
className="inline-flex shrink-0 items-center gap-2 rounded-md border px-3 py-1.5 text-xs transition-colors"
className="inline-flex shrink-0 items-center gap-2 rounded-full border px-3 py-1.5 text-xs transition-colors"
style={{ borderColor: HAIR_STRONG, color: MUTED }}
>
<RotateCw className="h-3.5 w-3.5" />
+2 -2
View File
@@ -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">
+3 -3
View File
@@ -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>
) : (
+8 -8
View File
@@ -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"
>
+4 -4
View File
@@ -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.
+1 -1
View File
@@ -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" />
+9 -9
View File
@@ -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>
+3 -3
View File
@@ -301,7 +301,7 @@ export default function AccountCombobox({ value, accounts, onChange, onCommit, o
<div
ref={listRef}
className={cn(
'absolute z-50 top-full left-0 mt-1 max-h-[300px] overflow-y-auto rounded-md border border-input bg-card shadow-md',
'absolute z-50 top-full left-0 mt-1 max-h-[300px] overflow-y-auto rounded-lg border border-input bg-card shadow-md',
listWidthClass,
)}
>
@@ -348,7 +348,7 @@ export default function AccountCombobox({ value, accounts, onChange, onCommit, o
{isOpen && !disabled && search.trim() && flatList.length === 0 && (
<div
className={cn(
'absolute z-50 top-full left-0 mt-1 rounded-md border border-input bg-card shadow-md p-3',
'absolute z-50 top-full left-0 mt-1 rounded-lg border border-input bg-card shadow-md p-3',
listWidthClass,
)}
>
@@ -367,7 +367,7 @@ export default function AccountCombobox({ value, accounts, onChange, onCommit, o
{onCreateAccount && (
<button
type="button"
className="mt-2 flex w-full items-center gap-2 rounded-md border border-input bg-card px-2 py-1.5 text-left text-sm hover:bg-muted/50"
className="mt-2 flex w-full items-center gap-2 rounded-sm border border-input bg-card px-2 py-1.5 text-left text-sm hover:bg-muted/50"
onMouseDown={(e) => {
e.preventDefault()
setIsOpen(false)
@@ -108,7 +108,7 @@ export default function AccrualPeriodControl({
const showK2Hint = shouldShowK2AccrualHint({ amount, currency, exchangeRate })
return (
<div className="rounded-md border bg-muted/30 p-3 space-y-3">
<div className="rounded-lg border bg-muted/30 p-3 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{t('panel_title')}
@@ -114,7 +114,7 @@ export function ActivateAccountsDialog({
)}
{!loading && knownRows.length > 0 && (
<ul className="divide-y divide-border rounded-md border">
<ul className="divide-y divide-border rounded-lg border">
{knownRows.map((r) => (
<li key={r.account_number} className="flex items-baseline gap-3 px-3 py-2">
<span className="font-mono text-foreground w-14 shrink-0">{r.account_number}</span>
@@ -130,7 +130,7 @@ export function ActivateAccountsDialog({
)}
{!loading && unknownRows.length > 0 && (
<div className="rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-attn">
<div className="rounded-lg border border-border bg-muted/30 px-3 py-2 text-xs text-attn">
<p className="font-medium">Finns inte i BAS-katalogen:</p>
<p className="mt-1 font-mono">{unknownRows.map((r) => r.account_number).join(', ')}</p>
<p className="mt-1 text-attn/80">
@@ -71,7 +71,7 @@ export function AttachDocumentPreview({ data, params }: AttachDocumentPreviewPro
</div>
{willOverwrite && existingIsAccounting && (
<div className="flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
<div className="flex items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
<div>
<p className="font-medium">Ersätter räkenskapsinformation</p>
@@ -85,7 +85,7 @@ export function AttachDocumentPreview({ data, params }: AttachDocumentPreviewPro
</div>
)}
{willOverwrite && !existingIsAccounting && (
<div className="rounded-md border border-border bg-muted/30 p-2 text-xs text-muted-foreground">
<div className="rounded-lg border border-border bg-muted/30 p-2 text-xs text-muted-foreground">
Ersätter befintligt dokument{existingDocName ? `: ${existingDocName}` : ''}.
</div>
)}
@@ -4,7 +4,8 @@ import { Fragment, useState, useEffect, useCallback, useMemo, useRef } from 'rea
import { useTranslations } from 'next-intl'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { Switch } from '@/components/ui/switch'
import { Skeleton } from '@/components/ui/skeleton'
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
@@ -18,7 +19,6 @@ import { AddAccountDialog } from './AddAccountDialog'
import { EditAccountDialog } from './EditAccountDialog'
import { PruneAccountsDialog } from './PruneAccountsDialog'
import {
Search,
ChevronRight,
Plus,
Pencil,
@@ -466,46 +466,20 @@ export default function ChartOfAccountsManager() {
{/* Toolbar: seg + search; the K2 filter rides far right in catalog view */}
<div className="flex flex-wrap items-center gap-2">
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
<button
type="button"
role="tab"
aria-selected={view === 'my-accounts'}
onClick={() => switchView('my-accounts')}
className={cn(
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
view === 'my-accounts'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
{t('tab_my_accounts')}
<span className="font-normal text-muted-foreground tabular-nums">{accounts.length}</span>
</button>
<button
type="button"
role="tab"
aria-selected={view === 'bas-catalog'}
onClick={() => switchView('bas-catalog')}
className={cn(
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
view === 'bas-catalog'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
{t('tab_bas_catalog')}
</button>
</div>
<div className="relative min-w-[190px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t('search_placeholder')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="h-9 pl-10"
/>
</div>
<SegmentedControl
value={view}
onChange={switchView}
options={[
{ value: 'my-accounts', label: t('tab_my_accounts'), count: accounts.length },
{ value: 'bas-catalog', label: t('tab_bas_catalog') },
]}
/>
<ToolbarSearch
placeholder={t('search_placeholder')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
containerClassName="min-w-[190px] max-w-xs flex-1"
/>
{view === 'my-accounts' && (
<label className="ml-auto flex items-center gap-2 text-sm">
<Switch
+1 -1
View File
@@ -72,7 +72,7 @@ export default function CorrectionChain({ currentEntryId, chain }: Props) {
href={`/bookkeeping/${entry.id}`}
className="block"
>
<div className={`relative pl-7 py-2 rounded-md transition-colors hover:bg-muted/50 ${isCurrent ? 'bg-muted/30' : ''} ${isCancelled ? 'opacity-60' : ''}`}>
<div className={`relative pl-7 py-2 rounded-sm transition-colors hover:bg-muted/50 ${isCurrent ? 'bg-muted/30' : ''} ${isCancelled ? 'opacity-60' : ''}`}>
{/* Timeline dot */}
<div className={`absolute left-0.5 top-[18px] h-3 w-3 rounded-full border-2 border-background ${isCancelled ? 'bg-muted-foreground' : role.color}`} />
@@ -288,7 +288,7 @@ export default function DocumentUploadZone({
{files.map((file, index) => (
<div
key={file.uploadKey}
className="flex min-w-0 items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50"
className="flex min-w-0 items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50"
>
{isImageType(file.file.type) ? (
<ImageIcon className="h-4 w-4 text-muted-foreground shrink-0" />
@@ -140,7 +140,7 @@ export default function DocumentViewerPane({
<div className="min-h-0 flex-1 overflow-auto bg-background">
{loadingMeta ? (
<div className="p-3">
<Skeleton className="h-full min-h-[40vh] w-full rounded-md" />
<Skeleton className="h-full min-h-[40vh] w-full" />
</div>
) : showAsPdf ? (
<object
+1 -1
View File
@@ -538,7 +538,7 @@ export function EditAccountDialog({ open, onOpenChange, account, onSaved }: Edit
</div>
{account.is_system_account && (
<p className="text-xs text-muted-foreground bg-muted rounded p-2">
<p className="text-xs text-muted-foreground bg-muted rounded-sm p-2">
Detta är ett systemkonto och kan inte tas bort.
</p>
)}
@@ -216,7 +216,7 @@ export default function InboxDocumentPicker({ open, onClose, journalEntryId, onL
onClick={() => void handlePick(it)}
disabled={!!linkingId}
className={cn(
'flex-1 min-w-0 text-left flex items-center gap-3 py-3 px-2 -ml-2 rounded transition-colors hover:bg-secondary/60',
'flex-1 min-w-0 text-left flex items-center gap-3 py-3 px-2 -ml-2 rounded-sm transition-colors hover:bg-secondary/60',
linkingId && !isLinking && 'opacity-50',
)}
>
@@ -307,7 +307,7 @@ export default function JournalEntryAttachments({
const isReplacing = replacingDocId === doc.id
return (
<div key={doc.id}>
<div className="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50">
<div className="flex items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50">
{isPreviewable(doc.mime_type) ? (
<button
onClick={() => handlePreviewToggle(doc)}
+1 -1
View File
@@ -1792,7 +1792,7 @@ export default function JournalEntryForm({
</Button>
{dimPopoverRow === index && (
<div
className="absolute right-0 top-full z-50 mt-1 w-64 rounded-md border bg-card p-3 shadow-md"
className="absolute right-0 top-full z-50 mt-1 w-64 rounded-lg border bg-card p-3 shadow-md"
onKeyDown={(e) => {
// The comboboxes preventDefault their own Escape
// (closing their dropdown): only an unhandled
+15 -40
View File
@@ -11,6 +11,8 @@ import {
} from '@/components/ui/data-list'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
@@ -990,51 +992,24 @@ export default function JournalEntryList({ pristineSlot }: { pristineSlot?: Reac
<div className="flex flex-wrap items-center gap-2">
{/* Verifikat vs Utkast. Drafts live in their own view with a count badge so
they don't sink to the last page of the committed list. */}
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
<button
type="button"
role="tab"
aria-selected={listMode === 'committed'}
onClick={() => switchMode('committed')}
className={cn(
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
listMode === 'committed'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
{t('mode_vouchers')}
</button>
<button
type="button"
role="tab"
aria-selected={listMode === 'drafts'}
onClick={() => switchMode('drafts')}
className={cn(
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
listMode === 'drafts'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
{t('mode_drafts')}
{draftCount > 0 && (
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
{draftCount}
</span>
)}
</button>
</div>
<SegmentedControl
value={listMode}
onChange={switchMode}
options={[
{ value: 'committed', label: t('mode_vouchers') },
{ value: 'drafts', label: t('mode_drafts'), count: draftCount },
]}
/>
<div className="relative flex-1 sm:flex-none sm:w-[280px]">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
<Input
<ToolbarSearch
type="text"
inputMode="search"
placeholder={t('search_placeholder')}
aria-label={t('search_placeholder')}
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
className="h-8 pl-8 pr-7 text-xs"
containerClassName="min-w-0 max-w-none"
className="pr-7"
/>
{searchInput && (
<button
@@ -1506,7 +1481,7 @@ export default function JournalEntryList({ pristineSlot }: { pristineSlot?: Reac
e.stopPropagation()
setPreviewEntryId(entry.id)
}}
className="inline-flex items-center gap-0.5 rounded text-muted-foreground transition-colors duration-150 hover:text-foreground"
className="inline-flex items-center gap-0.5 rounded-sm text-muted-foreground transition-colors duration-150 hover:text-foreground"
>
<Paperclip className="h-3.5 w-3.5" />
<span className="text-xs tabular-nums">{attachmentCounts[entry.id]}</span>
@@ -1553,7 +1528,7 @@ export default function JournalEntryList({ pristineSlot }: { pristineSlot?: Reac
// p-2 grows the tap target to 30px without
// changing row height (the row is ~40px from
// the description cell).
'inline-flex items-center rounded p-2 text-muted-foreground transition-opacity duration-150 hover:text-foreground',
'inline-flex items-center rounded-sm p-2 text-muted-foreground transition-opacity duration-150 hover:text-foreground',
// Quiet at rest on desktop, but the table has no
// mobile card to fall back on, so touch keeps the
// icon visible.
@@ -202,7 +202,7 @@ export default function TemplateBookDialog({ open, onOpenChange, onCreated }: Pr
key={tpl.id}
type="button"
onClick={() => setSelected(tpl)}
className="flex w-full items-center gap-2.5 rounded-md px-2.5 py-2.5 text-left transition-colors hover:bg-secondary/60"
className="flex w-full items-center gap-2.5 rounded-sm px-2.5 py-2.5 text-left transition-colors hover:bg-secondary/60"
>
<span className="min-w-0 flex-1">
<span className="block truncate text-[13px] text-foreground">
@@ -313,7 +313,7 @@ export function CreateAssetDialog({ open, onOpenChange, onCreated }: CreateAsset
</p>
</div>
{isK3 && (
<div className="space-y-3 rounded-md border border-border bg-muted/20 p-4">
<div className="space-y-3 rounded-lg border border-border bg-muted/20 p-4">
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<Label className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
@@ -340,7 +340,7 @@ export function CreateAssetDialog({ open, onOpenChange, onCreated }: CreateAsset
{componentRows.map((row, idx) => (
<div
key={row.id}
className="grid grid-cols-12 items-end gap-2 rounded-md border border-border bg-background p-2"
className="grid grid-cols-12 items-end gap-2 rounded-lg border border-border bg-background p-2"
>
<div className="col-span-12 sm:col-span-4 space-y-1">
<Label
@@ -464,14 +464,14 @@ export function CreateAssetDialog({ open, onOpenChange, onCreated }: CreateAsset
)}
</div>
)}
<div className="rounded-md border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
<div className="rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
<strong className="text-foreground">Tips:</strong> Anskaffningen måste redan vara
bokförd (debet på 1xxx-kontot mot t.ex. 1930/2440): registret bokför inte
själva köpet. Det här registret styr enbart de planenliga avskrivningarna under
bokslutet.
</div>
{error && (
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
{error}
</div>
)}
@@ -191,7 +191,7 @@ export function EditAssetDialog({ asset, open, onOpenChange, onSaved }: EditAsse
</div>
{basisLocked && (
<div className="flex items-start gap-2 rounded-md border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
<div className="flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
<Lock className="mt-0.5 h-3.5 w-3.5 shrink-0" />
<span>
Anskaffningsdatum, anskaffningsvärde och kategori är låsta eftersom avskrivningar
@@ -216,7 +216,7 @@ export function EditAssetDialog({ asset, open, onOpenChange, onSaved }: EditAsse
</div>
{error && (
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
{error}
</div>
)}
@@ -302,7 +302,7 @@ function ManualEntryEditor({
onRemove: () => void
}) {
return (
<div className="rounded-md border border-border p-3 space-y-3">
<div className="rounded-lg border border-border p-3 space-y-3">
<div className="flex items-center justify-between">
<p className="text-sm font-medium">
{entry.kind === 'audit_fee' && 'Revisions-/bokslutsarvode'}
@@ -329,7 +329,7 @@ function ManualEntryEditor({
<div className="space-y-1">
<Label className="text-xs">Konto</Label>
<select
className="border border-border rounded-md h-8 text-sm px-2 w-full bg-background"
className="border border-border rounded-lg h-8 text-sm px-2 w-full bg-background"
value={entry.liabilityAccount}
onChange={(e) =>
onChange({ liabilityAccount: e.target.value as '2991' | '2992' })
@@ -584,7 +584,7 @@ function TaxDepreciationCard({
</div>
{dirty && !preview && (
<p className="rounded-md border border-border bg-muted/20 p-3 text-sm text-muted-foreground">
<p className="rounded-lg border border-border bg-muted/20 p-3 text-sm text-muted-foreground">
Inställningarna är ändrade. Beräkna förslaget för att se rätt gränsbelopp innan du
sparar.
</p>
@@ -592,7 +592,7 @@ function TaxDepreciationCard({
{result && (
<>
<div className="grid grid-cols-2 gap-x-6 gap-y-3 rounded-md border border-border bg-muted/20 p-4 text-sm sm:grid-cols-3">
<div className="grid grid-cols-2 gap-x-6 gap-y-3 rounded-lg border border-border bg-muted/20 p-4 text-sm sm:grid-cols-3">
<TaxValue label="Ingående värde" value={result.openingTaxValue} />
<TaxValue label="Årets anskaffningar" value={result.additions} />
<TaxValue label="Årets avyttringar" value={-result.disposals} />
@@ -639,7 +639,7 @@ function TaxDepreciationCard({
</div>
)}
{dirty && method === 'rakenskapsenlig' && (
<div className="flex items-start gap-2 rounded-md border border-border p-3">
<div className="flex items-start gap-2 rounded-lg border border-border p-3">
<Checkbox
id="tax-book-conformity"
checked={bookConformityConfirmed}
@@ -213,7 +213,7 @@ export function EfDeclarationSection({
<div className="space-y-1">
<Label className="text-xs">Egenavgifter: kategori</Label>
<select
className="border border-border rounded-md h-9 text-sm px-2 w-full bg-background"
className="border border-border rounded-lg h-9 text-sm px-2 w-full bg-background"
value={overrides.category}
onChange={(e) => update('category', e.target.value as EgenavgiftCategory)}
>
+2 -2
View File
@@ -97,7 +97,7 @@ export function ContextPicker({
aria-haspopup="listbox"
aria-label={ariaLabel}
className={cn(
'inline-flex items-center gap-1.5 whitespace-nowrap rounded-full border border-border px-3 py-[5px] text-[13px]',
'inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-full border border-border px-3 text-[13px]',
'text-foreground transition-colors duration-150',
disabled
? 'opacity-50 cursor-not-allowed'
@@ -130,7 +130,7 @@ export function ContextPicker({
setOpen(false)
}}
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',
item.disabled
? 'text-muted-foreground/40 cursor-not-allowed'
: item.id === value
+1 -1
View File
@@ -195,7 +195,7 @@ export function ReportDateRange({
type="button"
onClick={() => handlePreset(p)}
className={cn(
'px-3 py-1.5 text-xs rounded-md border transition-colors duration-150',
'px-3 py-1.5 text-xs rounded-full border transition-colors duration-150',
active
? 'bg-secondary border-border text-foreground'
: 'bg-transparent border-border text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
+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',
+1 -1
View File
@@ -81,7 +81,7 @@ export function DeadlineGroupCard({ deadlines, onEdit, onRequestToggle }: Deadli
key={deadline.id}
onClick={() => onEdit?.(deadline)}
className={cn(
'group flex items-center gap-3 rounded-md py-1.5 -mx-2 px-2 transition-colors duration-150',
'group flex items-center gap-3 rounded-sm py-1.5 -mx-2 px-2 transition-colors duration-150',
onEdit && 'cursor-pointer hover:bg-secondary/35',
)}
>
+11 -25
View File
@@ -6,6 +6,7 @@ import { Deadline, DeadlineType } from '@/types'
import { DeadlineRow, deadlineDateLabel } from './DeadlineRow'
import { DeadlineGroupCard, isSkattekontoDeadline } from './DeadlineGroupCard'
import { ConfirmDialog } from '@/components/ui/confirm-dialog'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { isDeadlineOverdue, parseDate, startOfDay } from '@/lib/calendar/utils'
type TypeSegment = 'all' | 'tax' | 'invoicing' | 'own'
@@ -136,31 +137,16 @@ export function DeadlineList({
return (
<div className="space-y-6">
{/* Toolbar: the type seg (concept: Alla / Skatt / Fakturering / Egna) */}
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
{(
[
{ key: 'all', label: t('seg_all') },
{ key: 'tax', label: t('seg_tax') },
{ key: 'invoicing', label: t('seg_invoicing') },
{ key: 'own', label: t('seg_own') },
] as const
).map(({ key, label }) => (
<button
key={key}
type="button"
role="tab"
aria-selected={segment === key}
onClick={() => setSegment(key)}
className={`rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
segment === key
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{label}
</button>
))}
</div>
<SegmentedControl
value={segment}
onChange={setSegment}
options={[
{ value: 'all', label: t('seg_all') },
{ value: 'tax', label: t('seg_tax') },
{ value: 'invoicing', label: t('seg_invoicing') },
{ value: 'own', label: t('seg_own') },
]}
/>
<div className="gap-8 lg:grid lg:grid-cols-[minmax(0,1fr)_240px] lg:items-start">
{/* The thread */}
+1 -1
View File
@@ -130,7 +130,7 @@ export function DeadlineRow({ deadline, onEdit, onRequestToggle }: DeadlineRowPr
<div
onClick={() => onEdit?.(deadline)}
className={cn(
'group flex items-start gap-3 rounded-md py-3 -mx-2 px-2 transition-colors duration-150',
'group flex items-start gap-3 rounded-sm py-3 -mx-2 px-2 transition-colors duration-150',
onEdit && 'cursor-pointer hover:bg-secondary/35',
completed && 'opacity-60',
)}
+1 -1
View File
@@ -265,7 +265,7 @@ export default function DimensionCombobox({
{isOpen && !disabled && (
<div
ref={listRef}
className="absolute z-50 top-full left-0 mt-1 min-w-[16rem] w-[max(100%,20rem)] max-h-[300px] overflow-y-auto rounded-md border border-input bg-card shadow-md"
className="absolute z-50 top-full left-0 mt-1 min-w-[16rem] w-[max(100%,20rem)] max-h-[300px] overflow-y-auto rounded-lg border border-input bg-card shadow-md"
>
{loadState === 'loading' && (
<div className="flex items-center gap-2 px-2 py-2 text-sm text-muted-foreground">
+26 -34
View File
@@ -6,6 +6,8 @@ import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { ToolbarSearch } from '@/components/ui/toolbar-search'
import { Skeleton } from '@/components/ui/skeleton'
import { Switch } from '@/components/ui/switch'
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
@@ -368,40 +370,30 @@ export default function DimensionsManager() {
muted value count, search, and the actions far right. */}
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2">
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
{dimensions.map((dim) => (
<button
key={dim.id}
type="button"
role="tab"
aria-selected={activeDimId === dim.id}
onClick={() => {
setActiveDimId(dim.id)
setSearchTerm('')
}}
className={cn(
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
activeDimId === dim.id
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
{dim.name}
{dim.values.length > 0 && (
<span className="text-muted-foreground tabular-nums">{dim.values.length}</span>
)}
</button>
))}
</div>
<div className="relative min-w-[180px] max-w-xs flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="h-9 pl-10"
/>
</div>
<SegmentedControl
value={activeDimId ?? ''}
onChange={(id) => {
setActiveDimId(id)
setSearchTerm('')
}}
options={dimensions.map((dim) => ({
value: dim.id,
label: (
<>
{dim.name}
{dim.values.length > 0 && (
<span className="text-muted-foreground tabular-nums">{dim.values.length}</span>
)}
</>
),
}))}
/>
<ToolbarSearch
placeholder={t('search_placeholder')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
containerClassName="min-w-[180px] max-w-xs flex-1"
/>
<div className="ml-auto flex items-center gap-4">
<button
type="button"
+1 -1
View File
@@ -72,7 +72,7 @@ export function DocsLayout({ currentPath, children }: DocsLayoutProps) {
<Link
href={link.href}
className={cn(
'block text-sm px-3 py-1.5 rounded-md transition-colors',
'block text-sm px-3 py-1.5 rounded-sm transition-colors',
isActive
? 'bg-secondary text-foreground font-medium'
: 'text-foreground/75 hover:bg-secondary/60 hover:text-foreground',
@@ -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>
+3 -3
View File
@@ -140,7 +140,7 @@ export default function ArticlesEditStep({
)}
{/* Table */}
<div className="overflow-x-auto rounded-md border">
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
<tr className="border-b">
@@ -244,7 +244,7 @@ export default function ArticlesEditStep({
{row.duplicate_match ? (
<span
className={cn(
'text-[11px] font-medium px-1.5 py-0.5 rounded',
'text-[11px] font-medium px-1.5 py-0.5 rounded-full',
updateDuplicates
? 'bg-muted text-warning'
: 'bg-muted text-muted-foreground',
@@ -254,7 +254,7 @@ export default function ArticlesEditStep({
{updateDuplicates ? 'Uppdateras' : 'Hoppas över'}
</span>
) : (
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded bg-success/15 text-success">
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded-full bg-success/15 text-success">
Ny
</span>
)}
@@ -186,7 +186,7 @@ export default function BankFileColumnMappingStep({
</CardHeader>
<CardContent className="space-y-6">
{/* Header row toggle */}
<div className="flex items-center justify-between rounded-md border p-4">
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<Label htmlFor="has-header">Har filen rubrikrad?</Label>
<p className="text-xs text-muted-foreground">
@@ -382,7 +382,7 @@ export default function BankFileColumnMappingStep({
</CardDescription>
</CardHeader>
<CardContent>
<div className="rounded-md border max-h-64 overflow-x-auto overflow-y-auto">
<div className="rounded-lg border max-h-64 overflow-x-auto overflow-y-auto">
<Table>
<TableHeader>
<TableRow>
+1 -1
View File
@@ -163,7 +163,7 @@ export default function BankFilePreviewStep({
</CardDescription>
</CardHeader>
<CardContent>
<div className="rounded-md border max-h-96 overflow-x-auto overflow-y-auto">
<div className="rounded-lg border max-h-96 overflow-x-auto overflow-y-auto">
<Table>
<TableHeader>
<TableRow>
+1 -1
View File
@@ -58,7 +58,7 @@ export default function BankFileResultStep({
</CardHeader>
{!isSuccess && result.first_error && (
<CardContent>
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm">
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm">
<p className="font-medium text-destructive">Databasfel</p>
<p className="mt-1 font-mono text-xs text-muted-foreground break-all">
{result.first_error.message}
+3 -3
View File
@@ -119,7 +119,7 @@ export default function CustomersEditStep({
)}
{/* Table */}
<div className="overflow-x-auto rounded-md border">
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
<tr className="border-b">
@@ -183,7 +183,7 @@ export default function CustomersEditStep({
{row.duplicate_match ? (
<span
className={cn(
'text-[11px] font-medium px-1.5 py-0.5 rounded',
'text-[11px] font-medium px-1.5 py-0.5 rounded-full',
updateDuplicates
? 'bg-muted text-warning'
: 'bg-muted text-muted-foreground',
@@ -193,7 +193,7 @@ export default function CustomersEditStep({
{updateDuplicates ? 'Uppdateras' : 'Hoppas över'}
</span>
) : (
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded bg-success/15 text-success">
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded-full bg-success/15 text-success">
Ny
</span>
)}
@@ -195,7 +195,7 @@ export default function OpeningBalanceColumnMappingStep({
{previewRows.length > 0 && (
<div className="space-y-2">
<Label className="text-muted-foreground">Förhandsgranskning (5 första raderna)</Label>
<div className="overflow-x-auto rounded-md border">
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
<tr className="border-b">
@@ -239,7 +239,7 @@ export default function OpeningBalanceRowEditor({
return (
<div className="space-y-4">
{/* Table */}
<div className="overflow-x-auto rounded-md border">
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
<tr className="border-b">
@@ -284,7 +284,7 @@ export default function OpeningBalanceRowEditor({
{activeAutocomplete === row.id && autocompleteResults.length > 0 && (
<div
ref={autocompleteRef}
className="absolute z-50 top-full left-3 mt-1 w-72 max-h-48 overflow-y-auto rounded-md border bg-popover shadow-md"
className="absolute z-50 top-full left-3 mt-1 w-72 max-h-48 overflow-y-auto rounded-lg border bg-popover shadow-md"
>
{autocompleteResults.map((item) => (
<button
@@ -86,7 +86,7 @@ export default function RegisterColumnMappingStep<K extends string>({
{previewRows.length > 0 && (
<div className="space-y-2">
<Label className="text-muted-foreground">Förhandsgranskning (5 första raderna)</Label>
<div className="overflow-x-auto rounded-md border">
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
<tr className="border-b">
+3 -3
View File
@@ -109,7 +109,7 @@ export default function SuppliersEditStep({
</div>
)}
<div className="overflow-x-auto rounded-md border">
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
<tr className="border-b">
@@ -173,7 +173,7 @@ export default function SuppliersEditStep({
{row.duplicate_match ? (
<span
className={cn(
'text-[11px] font-medium px-1.5 py-0.5 rounded',
'text-[11px] font-medium px-1.5 py-0.5 rounded-full',
updateDuplicates
? 'bg-muted text-warning'
: 'bg-muted text-muted-foreground',
@@ -183,7 +183,7 @@ export default function SuppliersEditStep({
{updateDuplicates ? 'Uppdateras' : 'Hoppas över'}
</span>
) : (
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded bg-success/15 text-success">
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded-full bg-success/15 text-success">
Ny
</span>
)}
+2 -2
View File
@@ -345,7 +345,7 @@ export default function TransactionMatchPicker({
{/* Underlag reference: what we're matching against, so a currency or
amount mismatch with a candidate is obvious at a glance. */}
{(total != null || supplier) && (
<div className="rounded-md border bg-muted/30 px-3 py-2 text-xs flex items-center gap-x-3 gap-y-1 flex-wrap">
<div className="rounded-lg border bg-muted/30 px-3 py-2 text-xs flex items-center gap-x-3 gap-y-1 flex-wrap">
<span className="text-muted-foreground shrink-0">Underlag</span>
{supplier && <span className="font-medium truncate">{supplier}</span>}
{total != null && (
@@ -420,7 +420,7 @@ export default function TransactionMatchPicker({
onClick={() => void handlePick(c.id)}
disabled={!!matchingId}
className={cn(
'w-full text-left flex items-center gap-3 py-3 hover:bg-muted/50 transition-colors px-2 -mx-2 rounded',
'w-full text-left flex items-center gap-3 py-3 hover:bg-muted/50 transition-colors px-2 -mx-2 rounded-sm',
matchingId && !isMatching && 'opacity-50',
)}
>
+1 -1
View File
@@ -441,7 +441,7 @@ export default function RotRutPayoutDialog({
)}
{visibleCandidates.length > MAX_CASES_PER_FILE && (
<div className="flex items-start gap-2 rounded-md border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
<div className="flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-attn" />
<span>{t('rot_rut_max_cases_help', { count: MAX_CASES_PER_FILE })}</span>
</div>
+2 -2
View File
@@ -166,7 +166,7 @@ export function KPISettingsDialog({ preferences, onSave, saving }: KPISettingsDi
<p className="font-medium text-foreground/80 mb-0.5">
{t('settings_formula_label')}
</p>
<p className="font-mono text-[11px] bg-muted/50 rounded px-2 py-1">
<p className="font-mono text-[11px] bg-muted/50 rounded-sm px-2 py-1">
{t(`def_${def.id}_formula`)}
</p>
</div>
@@ -189,7 +189,7 @@ export function KPISettingsDialog({ preferences, onSave, saving }: KPISettingsDi
setAccountOverride(def.id, e.target.value)
}
placeholder={def.defaultAccounts.join(', ')}
className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs font-mono tabular-nums placeholder:text-muted-foreground/50"
className="w-full rounded-lg border border-input bg-background px-2.5 py-1.5 text-xs font-mono tabular-nums placeholder:text-muted-foreground/50"
/>
<p className="mt-1 text-[10px] text-muted-foreground/70">
{t('settings_account_hint', { example: def.defaultAccounts.slice(0, 3).join(', ') })}
+1 -1
View File
@@ -504,7 +504,7 @@ function LogoMark({ src, name, mono = false }: { src: string; name: string; mono
return (
<span
className={cn(
'flex h-6 w-6 items-center justify-center overflow-hidden rounded-md border border-border',
'flex h-6 w-6 items-center justify-center overflow-hidden rounded-sm border border-border',
!mono && 'bg-white',
)}
title={name}
+2 -2
View File
@@ -66,7 +66,7 @@ export function DeclarationRutaRow({
aria-expanded={expanded}
aria-controls={panelId}
aria-label={expanded ? `Dölj konton för ${code}` : `Visa konton för ${code}`}
className="mr-1 inline-flex h-6 w-6 items-center justify-center rounded align-middle hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
className="mr-1 inline-flex h-6 w-6 items-center justify-center rounded-sm align-middle hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
onClick={(e) => {
e.stopPropagation()
setExpanded((v) => !v)
@@ -79,7 +79,7 @@ export function DeclarationRutaRow({
)}
</button>
)}
<span className="font-mono text-xs bg-muted px-1 rounded mr-2">{code}</span>
<span className="font-mono text-xs bg-muted px-1 rounded-sm mr-2">{code}</span>
{label}
{hasAccounts && (
<span className="text-xs text-muted-foreground ml-2">
+1 -1
View File
@@ -87,7 +87,7 @@ export function ReportLibrary({
type="button"
onClick={() => setQuery('')}
aria-label={t('search_clear')}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-sm p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
>
<X className="h-3.5 w-3.5" aria-hidden="true" />
</button>
+2 -2
View File
@@ -97,7 +97,7 @@ export function ReportRowExpansion({
aria-expanded={expanded}
aria-controls={`expansion-${rowId}`}
aria-label={expanded ? 'Dölj verifikat' : 'Visa verifikat'}
className="inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
className="inline-flex h-6 w-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
>
{expanded ? (
<ChevronDown className="h-4 w-4" />
@@ -142,7 +142,7 @@ export function useReportRowExpansion(fetcher: ReportSourceFetcher, rowId: strin
aria-expanded={expanded}
aria-controls={`expansion-${rowId}`}
aria-label={expanded ? 'Dölj verifikat' : 'Visa verifikat'}
className="inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
className="inline-flex h-6 w-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
>
{expanded ? (
<ChevronDown className="h-4 w-4" />
+12 -33
View File
@@ -14,6 +14,7 @@ import { Label } from '@/components/ui/label'
import { Badge } from '@/components/ui/badge'
import { AlertCircle, Check, ChevronDown, ChevronRight, ExternalLink, FileCode, FileDown, FileText, Percent } from 'lucide-react'
import { Skeleton } from '@/components/ui/skeleton'
import { SegmentedControl } from '@/components/ui/segmented-control'
import { EmptyState } from '@/components/ui/empty-state'
import { FyPicker } from '@/components/common/FyPicker'
import { mostRecentEndedVatPeriod } from '@/lib/vat/period-defaults'
@@ -209,36 +210,14 @@ export function TrialBalanceView({ periodId, onNavigateToAccount }: { periodId:
<div className="flex items-center justify-between">
<CardTitle>Saldobalans</CardTitle>
<div className="flex items-center gap-3">
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
<button
type="button"
role="tab"
aria-selected={viewMode === 'simplified'}
onClick={() => setViewMode('simplified')}
className={cn(
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
viewMode === 'simplified'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
Förenklad
</button>
<button
type="button"
role="tab"
aria-selected={viewMode === 'detailed'}
onClick={() => setViewMode('detailed')}
className={cn(
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
viewMode === 'detailed'
? 'border border-border bg-card font-medium text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
Detaljerad
</button>
</div>
<SegmentedControl
value={viewMode}
onChange={setViewMode}
options={[
{ value: 'simplified', label: 'Förenklad' },
{ value: 'detailed', label: 'Detaljerad' },
]}
/>
{data.isBalanced ? (
<span className="text-sm text-muted-foreground">Balanserad</span>
) : (
@@ -2374,7 +2353,7 @@ function VatRutaRow({
<expansion.Toggle />
</span>
)}
<span className="font-mono text-xs bg-muted px-1 rounded mr-2">{ruta}</span>
<span className="font-mono text-xs bg-muted px-1 rounded-sm mr-2">{ruta}</span>
{label}
</td>
<td className="py-2 text-right tabular-nums">{noVat ? `${formatAmount(baseAmount)} kr` : `${formatAmount(amount)} kr`}</td>
@@ -3490,8 +3469,8 @@ export function DimensionPnlView({ periodId, dateRange }: { periodId: string; da
onClick={() => setDimNo(String(d.sie_dim_no))}
className={
active
? 'px-3 py-1.5 text-xs rounded-md border transition-colors duration-150 bg-secondary border-border text-foreground'
: 'px-3 py-1.5 text-xs rounded-md border transition-colors duration-150 bg-transparent border-border text-muted-foreground hover:bg-secondary/60 hover:text-foreground'
? 'px-3 py-1.5 text-xs rounded-full border transition-colors duration-150 bg-secondary border-border text-foreground'
: 'px-3 py-1.5 text-xs rounded-full border transition-colors duration-150 bg-transparent border-border text-muted-foreground hover:bg-secondary/60 hover:text-foreground'
}
>
{d.name}
+12 -12
View File
@@ -841,7 +841,7 @@ export function AGIPanel(props: AGIPanelProps) {
<ShieldAlert className="mt-0.5 h-4 w-4 shrink-0" />
<p>
{t('disabled_before')}
<code className="mx-1 rounded bg-muted px-1 py-0.5 text-xs">SKATTEVERKET_ENABLED</code>
<code className="mx-1 rounded-sm bg-muted px-1 py-0.5 text-xs">SKATTEVERKET_ENABLED</code>
{t('disabled_after')}
</p>
</div>
@@ -923,7 +923,7 @@ export function AGIPanel(props: AGIPanelProps) {
type="button"
onClick={handleDisconnect}
disabled={actionLoading === 'disconnect' || connecting}
className="inline-flex items-center gap-1 rounded border border-border px-1.5 py-0.5 text-[11px] font-normal text-muted-foreground transition-colors hover:border-destructive/50 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-50"
className="inline-flex items-center gap-1 rounded-full border border-border px-1.5 py-0.5 text-[11px] font-normal text-muted-foreground transition-colors hover:border-destructive/50 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-50"
title={t('disconnect_title')}
>
{actionLoading === 'disconnect' ? (
@@ -945,7 +945,7 @@ export function AGIPanel(props: AGIPanelProps) {
receipt a later correction has replaced) still gets the card, just
without a number: better than showing another declaration's. */}
{isSigned && (
<div className="rounded-md border border-border bg-muted/30 p-4">
<div className="rounded-lg border border-border bg-muted/30 p-4">
<div className="flex items-start gap-3">
<CheckCircle2 className="mt-0.5 h-5 w-5 shrink-0 text-success" />
<div className="space-y-1">
@@ -979,7 +979,7 @@ export function AGIPanel(props: AGIPanelProps) {
refresh token or has burned through its 10-refresh budget. The
only fix is a fresh BankID round-trip. */}
{(status?.expired === true || status?.canRefresh === false) && !readOnly && (
<div className="rounded-md border border-border bg-muted/30 p-3">
<div className="rounded-lg border border-border bg-muted/30 p-3">
<p className="text-sm font-medium">{t('expired_banner_title')}</p>
<p className="mt-1 text-xs text-muted-foreground">
{t('expired_banner_description')}
@@ -996,7 +996,7 @@ export function AGIPanel(props: AGIPanelProps) {
after some users had already connected, so their stored token
grants moms/skattekonto but not AGI. */}
{missingAgdScope && !readOnly && (
<div className="rounded-md border border-border bg-muted/30 p-3">
<div className="rounded-lg border border-border bg-muted/30 p-3">
<p className="text-sm font-medium">
{t('missing_scope_title')}
</p>
@@ -1046,7 +1046,7 @@ export function AGIPanel(props: AGIPanelProps) {
treatment so the user understands they must fix errors before
BankID signing is even possible. */}
{submission?.signeringslank && awaitingSigning && !draftIsStale && (
<div className="rounded-md border border-border bg-muted/30 p-3">
<div className="rounded-lg border border-border bg-muted/30 p-3">
<p className="text-sm font-medium">
<InfoTooltip variant="help" content={t('granskningsunderlag_gloss')}>
{t('draft_locked_title')}
@@ -1072,7 +1072,7 @@ export function AGIPanel(props: AGIPanelProps) {
it would file the old amounts. The "Lås upp" button below releases
the SKV lock; the user then re-submits the freshly generated XML. */}
{awaitingSigning && draftIsStale && (
<div className="rounded-md border border-border bg-muted/30 p-3">
<div className="rounded-lg border border-border bg-muted/30 p-3">
<p className="text-sm font-medium">{t('stale_draft_title')}</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{t('stale_draft_description', {
@@ -1096,7 +1096,7 @@ export function AGIPanel(props: AGIPanelProps) {
link would be permanently unreachable even though the extension
persisted it. */}
{submission?.signeringslank && underlagRejected && (
<div className="rounded-md border border-destructive/40 bg-destructive/5 p-3">
<div className="rounded-lg border border-destructive/40 bg-destructive/5 p-3">
<p className="text-sm font-medium text-destructive">
{t('incorrect_data_title')}
</p>
@@ -1115,7 +1115,7 @@ export function AGIPanel(props: AGIPanelProps) {
)}
{kontroller.length > 0 && (
<div className="space-y-1 rounded-md border bg-muted/30 p-3">
<div className="space-y-1 rounded-lg border bg-muted/30 p-3">
{kontroller.map((k, i) => (
<div
key={i}
@@ -1144,7 +1144,7 @@ export function AGIPanel(props: AGIPanelProps) {
status?.expired === true ||
status?.canRefresh === false
return (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive">
<AlertCircle className="mr-1 inline h-3.5 w-3.5" />
{error}
{sessionExpired && !readOnly && (
@@ -1159,7 +1159,7 @@ export function AGIPanel(props: AGIPanelProps) {
)
})()}
{success && !error && (
<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">
<CheckCircle2 className="mr-1 inline h-3.5 w-3.5 text-success" />
{success}
</div>
@@ -1204,7 +1204,7 @@ export function AGIPanel(props: AGIPanelProps) {
</div>
{chain && (
<ol className="space-y-1.5 rounded-md border border-border bg-muted/30 p-3">
<ol className="space-y-1.5 rounded-lg border border-border bg-muted/30 p-3">
{CHAIN_STEPS.map(step => {
const state = chainStepState(step)
return (
+1 -1
View File
@@ -182,7 +182,7 @@ export function EmployeeBenefitsPanel({ employeeId, canWrite }: { employeeId: st
)}
{adding && (
<div className="space-y-4 rounded-md border bg-muted/30 p-4">
<div className="space-y-4 rounded-lg border bg-muted/30 p-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="benefit_type">{t('benefits_type')}</Label>
+4 -4
View File
@@ -123,7 +123,7 @@ export default function EmployeeTaxCard({
checked={sido}
onChange={(e) => setSido(e.target.checked)}
disabled={disabled}
className="rounded border-border"
className="rounded-sm border-border"
/>
<InfoTooltip content={t('tax_sidoinkomst_tooltip')}>
{t('tax_sidoinkomst_label')}
@@ -185,7 +185,7 @@ export default function EmployeeTaxCard({
disabled={disabled}
/>
) : tableNumber ? (
<div className="flex items-baseline gap-2 rounded-md border border-input px-3 py-2">
<div className="flex items-baseline gap-2 rounded-lg border border-input px-3 py-2">
<span className="font-sans text-xl tabular-nums">{tableNumber}</span>
{(municipality || rate != null) && (
<span className="text-xs text-muted-foreground">
@@ -195,7 +195,7 @@ export default function EmployeeTaxCard({
)}
</div>
) : (
<p className="rounded-md border border-dashed border-input px-3 py-2 text-sm text-muted-foreground">
<p className="rounded-lg border border-dashed border-input px-3 py-2 text-sm text-muted-foreground">
{t('tax_table_pick_municipality')}
</p>
)}
@@ -249,7 +249,7 @@ export default function EmployeeTaxCard({
</div>
</>
) : (
<p className="rounded-md border border-dashed border-input px-3 py-3 text-sm text-muted-foreground">
<p className="rounded-lg border border-dashed border-input px-3 py-3 text-sm text-muted-foreground">
{sido
? t('tax_no_table_sidoinkomst')
: t('tax_no_table_f_skatt')}
+1 -1
View File
@@ -165,7 +165,7 @@ export default function MunicipalityCombobox({
{isOpen && filtered.length > 0 && (
<div
ref={listRef}
className="absolute z-50 top-full left-0 mt-1 w-full max-h-[300px] overflow-y-auto rounded-md border border-input bg-card shadow-md"
className="absolute z-50 top-full left-0 mt-1 w-full max-h-[300px] overflow-y-auto rounded-lg border border-input bg-card shadow-md"
>
{filtered.map((k, i) => {
const isHighlighted = i === highlightedIndex
+4 -4
View File
@@ -154,14 +154,14 @@ export function PaymentFilePanel({
file downloads fine and only fails at upload, on the pay date.
Say the delivery precondition up front instead. */}
{format === 'pain001' && (
<div className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
<div className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
<AlertTriangle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
<span className="text-muted-foreground">{t('pain001_agreement_warning')}</span>
</div>
)}
{format === 'bg_lb' && (
<div className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
<div className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
<AlertTriangle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
<span className="text-muted-foreground">
{t('sunset_warning')}{' '}
@@ -197,7 +197,7 @@ export function PaymentFilePanel({
</button>
{showInstructions && (
<div className="space-y-2 rounded-md border bg-muted/30 p-3 text-xs">
<div className="space-y-2 rounded-lg border bg-muted/30 p-3 text-xs">
{sortedBanks.map((bank) => (
<div key={bank}>
<strong className="text-foreground">
@@ -215,7 +215,7 @@ export function PaymentFilePanel({
</div>
)}
<div className="flex items-start gap-2 rounded-md border border-dashed bg-muted/20 p-3 text-xs text-muted-foreground">
<div className="flex items-start gap-2 rounded-lg border border-dashed bg-muted/20 p-3 text-xs text-muted-foreground">
<Info className="h-3.5 w-3.5 mt-0.5 shrink-0" />
<span>{t('open_payments_note')}</span>
</div>
+8 -8
View File
@@ -353,7 +353,7 @@ export function SalaryCalendar({
}
return (
<div className="rounded-md border bg-card">
<div className="rounded-lg border bg-card">
{/* Header */}
<div className="flex flex-wrap items-center justify-between gap-2 border-b px-3 py-2">
<div className="flex items-center gap-2">
@@ -727,7 +727,7 @@ function BulkWorkedDialog({
</div>
{conflicts.length > 0 && (
<div className="space-y-1 rounded-md border border-border bg-muted/30 p-2 text-xs">
<div className="space-y-1 rounded-lg border border-border bg-muted/30 p-2 text-xs">
<div className="font-medium text-attn">
{t('conflicts_worked', { count: conflicts.length })}
</div>
@@ -738,7 +738,7 @@ function BulkWorkedDialog({
)}
{error && (
<div className="rounded-md bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
<div className="rounded-lg bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
)}
</div>
@@ -880,7 +880,7 @@ function BulkAbsenceDialog({
</div>
{conflicts.length > 0 && (
<div className="space-y-1 rounded-md border border-border bg-muted/30 p-2 text-xs">
<div className="space-y-1 rounded-lg border border-border bg-muted/30 p-2 text-xs">
<div className="font-medium text-attn">
{t('conflicts_absence', { count: conflicts.length })}
</div>
@@ -891,7 +891,7 @@ function BulkAbsenceDialog({
)}
{error && (
<div className="rounded-md bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
<div className="rounded-lg bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
)}
</div>
@@ -995,7 +995,7 @@ function DayInspectorDialog({
<div className="space-y-2">
{isHourly && worked && (
<div className="flex items-center justify-between rounded-md border bg-secondary/50 px-3 py-2">
<div className="flex items-center justify-between rounded-lg border bg-secondary/50 px-3 py-2">
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<div>
@@ -1020,7 +1020,7 @@ function DayInspectorDialog({
const meta = TYPE_META[a.absence_type]
const Icon = meta.icon
return (
<div key={a.id} className={cn('flex items-center justify-between rounded-md border px-3 py-2', meta.pillClass)}>
<div key={a.id} className={cn('flex items-center justify-between rounded-lg border px-3 py-2', meta.pillClass)}>
<div className="flex items-center gap-2 text-sm">
<Icon className="h-4 w-4" />
<div>
@@ -1049,7 +1049,7 @@ function DayInspectorDialog({
)}
{error && (
<div className="rounded-md bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
<div className="rounded-lg bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
)}
</div>
+1 -1
View File
@@ -141,7 +141,7 @@ export function RunHeader({ run, canWrite, actionLoading, employeeCount, onDelet
<DialogDescription className="space-y-3 pt-2 text-left">
<span className="block">{t('correct_dialog_body', { period: periodLabel })}</span>
{run.agi_generated_at && (
<span className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
<span className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
<AlertTriangle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
<span>{t('correct_dialog_agi_warning')}</span>
</span>
+1 -1
View File
@@ -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',
+1 -1
View File
@@ -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 ' +
+6 -6
View File
@@ -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>
+2 -2
View File
@@ -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 ? (

Some files were not shown because too many files have changed in this diff Show More