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>
68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { ExternalLink } from 'lucide-react'
|
|
import { useTranslations } from 'next-intl'
|
|
import { cn } from '@/lib/utils'
|
|
import { HOVER_REVEAL_CLASS } from '@/components/ui/dry-table'
|
|
|
|
/**
|
|
* Escape hatch out of a list without losing it.
|
|
*
|
|
* Working a filtered list (kontoavstämning, granskning) means opening one
|
|
* record, fixing it, and coming back to the same list. Navigating in place
|
|
* throws away the filter, the page and the scroll position, so the only way
|
|
* to keep them is to open the record in a second tab. Most of our row links
|
|
* are plain anchors, so cmd-click already works, but nothing on screen ever
|
|
* says so and a few call sites are buttons where it does not work at all.
|
|
*
|
|
* Sits next to the record's own link rather than replacing it: the primary
|
|
* click keeps navigating in place, which is what people expect. Hover-revealed
|
|
* per the row-control convention, and always visible on coarse pointers.
|
|
*/
|
|
export function OpenInNewTab({
|
|
href,
|
|
label,
|
|
className,
|
|
}: {
|
|
href: string
|
|
/** Overrides the default "Öppna i ny flik" for a more specific target. */
|
|
label?: string
|
|
className?: string
|
|
}) {
|
|
const t = useTranslations('common')
|
|
const text = label ?? t('open_in_new_tab')
|
|
|
|
return (
|
|
<Link
|
|
href={href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
// Named by the visually-hidden text below rather than aria-label, so the
|
|
// title renders a tooltip for sighted users without screen readers
|
|
// announcing the same string twice.
|
|
title={text}
|
|
onClick={(e) => e.stopPropagation()}
|
|
// Rows that own this control are themselves interactive: JournalEntryList
|
|
// gives its <tr> an Enter/Space handler that calls preventDefault() and
|
|
// expands the row. Without this, Enter on a focused link would expand the
|
|
// row instead of opening the voucher, so the control would be usable with
|
|
// a mouse but not a keyboard.
|
|
onKeyDown={(e) => e.stopPropagation()}
|
|
className={cn(
|
|
HOVER_REVEAL_CLASS,
|
|
'relative inline-flex shrink-0 items-center rounded-sm p-1 text-muted-foreground',
|
|
// The icon stays 14px so the row keeps its density, but the pointer
|
|
// target is padded out to the 40px the design rules require.
|
|
'before:absolute before:left-1/2 before:top-1/2 before:h-10 before:w-10',
|
|
'before:-translate-x-1/2 before:-translate-y-1/2 before:content-[""]',
|
|
'transition-colors duration-150 hover:text-foreground',
|
|
className,
|
|
)}
|
|
>
|
|
<ExternalLink className="h-3.5 w-3.5" aria-hidden="true" />
|
|
<span className="sr-only">{text}</span>
|
|
</Link>
|
|
)
|
|
}
|