Files
accounted/components/ui/detail-section.tsx
T
a977a67063 refactor(register): customer, supplier and article detail as documents, not card piles (#1624)
The three register detail pages rendered a handful of facts as a grid of
sparse bordered cards floating in an empty page. They now read as one
flowing document: serif entity name over a quiet type/status kicker,
quiet ghost actions top right (delete turns terracotta only on hover),
and hairline-kickered definition sections (KONTAKT, KUNDUPPGIFTER,
PRIS, BOKFORING, BETALNINGSUPPGIFTER, FAKTUROR) with aligned label/value
rows in a constrained column (max-w-2xl, supplier max-w-3xl for its
invoice table). Sections land with the standard stagger-enter.

New shared primitive components/ui/detail-section.tsx (DetailSection,
DefRow, DefEmpty) carries the grammar. Empty values render a muted
en dash for facts that matter (email, phone, expense account) and are
omitted row-wise otherwise; a section with nothing to say is omitted.

Behavior preserved: edit/deactivate/delete flows, confirm dialogs,
personal-number reveal + AttnLine, account-activation retry, viewer
lock states, routing and all existing i18n keys. New def_* label keys
added line-wise to both sv.json and en.json; customer invoice_count
gained ICU plural (1 faktura, not 1 fakturor).

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:28:45 +02:00

67 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { cn } from '@/lib/utils'
/**
* Register-detail document grammar (customers, suppliers, articles).
*
* A detail page is one flowing document, not a pile of cards: each group of
* facts is introduced by an uppercase hairline kicker and set as aligned
* label/value rows. The kicker's hairline is the only rule; groups are
* separated by whitespace, never borders (Living Paper, design.md).
*/
export function DetailSection({
kicker,
aside,
children,
className,
}: {
kicker: string
/** Optional right-aligned element on the kicker line: a count, a quiet action. */
aside?: React.ReactNode
children: React.ReactNode
className?: string
}) {
return (
<section className={className}>
<div className="flex items-baseline justify-between gap-4 border-b border-border pb-2">
<h2 className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
{kicker}
</h2>
{aside}
</div>
<div className="mt-3">{children}</div>
</section>
)
}
export function DefRow({
label,
children,
className,
}: {
label: string
children: React.ReactNode
className?: string
}) {
return (
<div
className={cn(
'grid grid-cols-[8rem_1fr] gap-x-6 py-2 text-sm sm:grid-cols-[10rem_1fr]',
className,
)}
>
<div className="text-muted-foreground">{label}</div>
<div className="min-w-0">{children}</div>
</div>
)
}
/**
* Muted placeholder for a value that matters but is not filled in. The en dash
* is the literal rendered value (same vocabulary as the orders list), which is
* why it is allowed to be a dash at all.
*/
export function DefEmpty() {
return <span className="text-muted-foreground">{'–'}</span>
}