fix(fonts): semantic typography utilities per handoff (7 roles)
Заменяет blanket override из MR !45 на типизированную type scale per design_handoff_ordinis_mdm/README.md "Scale" section. Семь semantic @utility в styles.css: text-title-xl 22/600 — modal title, section header text-title-lg 17/600 — page title в editor text-title-md 15/600 — dictionary card title text-body 13/400 — workhorse: body, buttons, tabs, inputs text-cell 12.5/500 — table cell text text-mono 11/500 — Mono: IDs, dates, FK refs (font-mono baked in) text-cap 10.5/600 — Tektur UPPERCASE caption (font/uppercase baked in) Audit phases: P1: добавил 7 утилит, font/uppercase/tracking baked где надо P2: 43 deterministic codemods (font-mono+text-2xs/[11px] → text-mono, [15/17/22]px+font-semibold → text-title-*, [12.5]px → text-cell, [10.5]px+uppercase+tracking → text-cap) P3: 64 text-sm → text-body (handoff workhorse), 84 text-2xs context-aware (TableCell → text-cell, bare → text-cell, плюс cleanup caps в backticks template literals который Phase 2 пропустил), 25 text-xs (6 → text-mono когда с font-mono, 19 → text-cell), 8 titles text-base/lg → text-title-* P4: убрал --text-2xs override (no users), оставил --text-sm: 13px scoped к @nstart/ui passthrough (см. comment в styles.css — убирается в Stage 3.9) Stats: text-body: 69 | text-cell: 99 | text-mono: 50 | text-cap: 42 text-title-xl: 5 | text-title-lg: 5 | text-title-md: 7 text-sm/text-2xs/text-xs в src/: 0 (только в styles.css комментариях) Поведение всех 277 typography usages теперь явно соответствует handoff — каждое место осознанно выбрано под роль, не плажирующий override.
This commit is contained in:
@@ -308,7 +308,7 @@ function ActiveFilterChips({ filters, onRemove }: ActiveFilterChipsProps) {
|
||||
type="button"
|
||||
onClick={() => onRemove(f.key)}
|
||||
className={[
|
||||
'inline-flex items-center gap-1.5 px-2 py-1 rounded-sm border text-2xs',
|
||||
'inline-flex items-center gap-1.5 px-2 py-1 rounded-sm border text-cell',
|
||||
'bg-white border-line hover:border-carbon/40 hover:bg-line/30',
|
||||
'transition-colors group',
|
||||
].join(' ')}
|
||||
@@ -469,7 +469,7 @@ function AuditRow({ row }: { row: AuditEntry }) {
|
||||
<>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<span className="text-2xs tabular-nums">{timeLabel}</span>
|
||||
<span className="text-cell tabular-nums">{timeLabel}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={actionVariant}>
|
||||
@@ -478,7 +478,7 @@ function AuditRow({ row }: { row: AuditEntry }) {
|
||||
</TableCell>
|
||||
<TableCell>{row.dictionaryName ?? '—'}</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-mono text-xs">{row.businessKey ?? '—'}</span>
|
||||
<span className=" text-mono">{row.businessKey ?? '—'}</span>
|
||||
</TableCell>
|
||||
<TableCell>{row.userId ?? 'anonymous'}</TableCell>
|
||||
<TableCell>
|
||||
@@ -486,7 +486,7 @@ function AuditRow({ row }: { row: AuditEntry }) {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
className="font-mono text-2xs text-ink-2"
|
||||
className="text-mono text-ink-2"
|
||||
title={row.traceId ?? ''}
|
||||
>
|
||||
{row.traceId ? row.traceId.slice(0, 8) : '—'}
|
||||
@@ -509,7 +509,7 @@ function AuditRow({ row }: { row: AuditEntry }) {
|
||||
<div className="text-cap text-ink-2 mb-1">
|
||||
{t('audit.diff.before')}
|
||||
</div>
|
||||
<pre className="text-2xs font-mono whitespace-pre-wrap break-all bg-white border border-line rounded p-2 max-h-60 overflow-auto">
|
||||
<pre className="text-mono whitespace-pre-wrap break-all bg-white border border-line rounded p-2 max-h-60 overflow-auto">
|
||||
{row.payloadBefore
|
||||
? JSON.stringify(row.payloadBefore, null, 2)
|
||||
: '—'}
|
||||
@@ -519,14 +519,14 @@ function AuditRow({ row }: { row: AuditEntry }) {
|
||||
<div className="text-cap text-ink-2 mb-1">
|
||||
{t('audit.diff.after')}
|
||||
</div>
|
||||
<pre className="text-2xs font-mono whitespace-pre-wrap break-all bg-white border border-line rounded p-2 max-h-60 overflow-auto">
|
||||
<pre className="text-mono whitespace-pre-wrap break-all bg-white border border-line rounded p-2 max-h-60 overflow-auto">
|
||||
{row.payloadAfter
|
||||
? JSON.stringify(row.payloadAfter, null, 2)
|
||||
: '—'}
|
||||
</pre>
|
||||
</div>
|
||||
{(row.ipAddress || row.userAgent || row.requestId) && (
|
||||
<div className="md:col-span-2 text-2xs text-ink-2 flex flex-wrap gap-x-4 gap-y-1 pt-1">
|
||||
<div className="md:col-span-2 text-cell text-ink-2 flex flex-wrap gap-x-4 gap-y-1 pt-1">
|
||||
{row.ipAddress && <span>IP: {row.ipAddress}</span>}
|
||||
{row.requestId && <span>req: {row.requestId}</span>}
|
||||
{row.userAgent && (
|
||||
@@ -562,7 +562,7 @@ function Pagination({
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="flex items-center justify-between flex-wrap gap-3">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 text-body">
|
||||
<span className="text-ink-2">{t('audit.page.size')}</span>
|
||||
<SingleSelect
|
||||
options={PAGE_SIZE_OPTIONS}
|
||||
@@ -570,7 +570,7 @@ function Pagination({
|
||||
onChange={(id) => onSizeChange(Number(id))}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<div className="flex items-center gap-3 text-body">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
|
||||
@@ -506,7 +506,7 @@ function DictionaryDetail() {
|
||||
)}
|
||||
<PageHeader
|
||||
breadcrumb={
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<div className="flex items-center gap-3 text-body">
|
||||
<Link to="/dictionaries" className="text-ink-2 hover:text-accent">
|
||||
← {t('nav.dictionaries')}
|
||||
</Link>
|
||||
@@ -630,8 +630,8 @@ function DictionaryDetail() {
|
||||
)}
|
||||
|
||||
{aoi && (
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-accent/30 bg-accent/4 text-sm">
|
||||
<span className="font-mono text-xs text-ink">
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-accent/30 bg-accent/4 text-body">
|
||||
<span className=" text-mono text-ink">
|
||||
{aoi.kind === 'bbox'
|
||||
? t('aoi.activeBbox', { value: aoi.bboxCsv })
|
||||
: t('aoi.activePolygon', {
|
||||
@@ -640,7 +640,7 @@ function DictionaryDetail() {
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-accent hover:underline"
|
||||
className="text-cell text-accent hover:underline"
|
||||
onClick={handleAoiClear}
|
||||
>
|
||||
{t('aoi.clear')}
|
||||
@@ -667,16 +667,16 @@ function DictionaryDetail() {
|
||||
|
||||
{/* Active state — ambient banner если ?at= задан, даже если picker закрыт. */}
|
||||
{timeTravelAt && !timeTravelOpen && (
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-warn bg-warn-bg text-sm">
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-warn bg-warn-bg text-body">
|
||||
<ClockCounterClockwiseIcon weight="bold" size={16} className="text-warn shrink-0" />
|
||||
<span className="font-mono text-xs text-ink">
|
||||
<span className=" text-mono text-ink">
|
||||
{t('timeTravel.viewing', {
|
||||
date: new Date(timeTravelAt).toLocaleString(),
|
||||
})}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-accent hover:underline ml-auto"
|
||||
className="text-cell text-accent hover:underline ml-auto"
|
||||
onClick={() => setTimeTravelAt(undefined)}
|
||||
>
|
||||
{t('timeTravel.clear')}
|
||||
@@ -717,7 +717,7 @@ function DictionaryDetail() {
|
||||
onClick={() => toggleScope(s)}
|
||||
aria-pressed={active}
|
||||
className={[
|
||||
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border text-2xs uppercase tracking-[0.10em] font-display transition-colors',
|
||||
'text-cap inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border transition-colors',
|
||||
active
|
||||
? 'border-accent bg-accent/8 text-accent'
|
||||
: 'border-line bg-white text-ink-2 hover:border-carbon/40',
|
||||
@@ -733,7 +733,7 @@ function DictionaryDetail() {
|
||||
})}
|
||||
</div>
|
||||
{filtersActive && (
|
||||
<div className="flex items-center gap-2 text-xs text-ink-2">
|
||||
<div className="flex items-center gap-2 text-cell text-ink-2">
|
||||
<span>
|
||||
{t('dict.filter.matched', { count: filteredCount, total: totalRecords })}
|
||||
</span>
|
||||
@@ -800,7 +800,7 @@ function DictionaryDetail() {
|
||||
)}
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-mono text-2xs">{r.businessKey}</span>
|
||||
<span className="text-mono">{r.businessKey}</span>
|
||||
{pendingByKey.has(r.businessKey) ? (
|
||||
<span title={t('dict.pendingReview.tooltip')}>
|
||||
<Badge variant="warning">{t('dict.pendingReview.label')}</Badge>
|
||||
@@ -819,7 +819,7 @@ function DictionaryDetail() {
|
||||
if (v == null || v === '') {
|
||||
return (
|
||||
<TableCell key={c.key}>
|
||||
<span className="text-2xs text-mute/70">—</span>
|
||||
<span className="text-cell text-mute/70">—</span>
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
@@ -828,7 +828,7 @@ function DictionaryDetail() {
|
||||
<TableCell key={c.key}>
|
||||
<span
|
||||
className={[
|
||||
'inline-block px-1.5 py-0.5 rounded-sm font-mono text-2xs',
|
||||
'inline-block px-1.5 py-0.5 rounded-sm font-mono text-cell',
|
||||
c.isFk
|
||||
? 'bg-accent/8 text-accent'
|
||||
: 'bg-carbon/8 text-ink',
|
||||
@@ -843,7 +843,7 @@ function DictionaryDetail() {
|
||||
<Badge variant="info">{r.dataScope}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-2xs text-mute">
|
||||
<span className="text-cell text-mute">
|
||||
{new Date(r.validFrom).toLocaleDateString()}
|
||||
</span>
|
||||
</TableCell>
|
||||
@@ -970,7 +970,7 @@ function DictionaryDetail() {
|
||||
>
|
||||
{edit.kind === 'close-confirm' && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-ink">
|
||||
<p className="text-body text-ink">
|
||||
{t('dict.confirmClose.body', { key: edit.record.businessKey })}
|
||||
</p>
|
||||
<TextInput
|
||||
@@ -1093,13 +1093,13 @@ function BulkSelectionToolbar({
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium tabular-nums">
|
||||
<span className="text-body font-medium tabular-nums">
|
||||
{t('dict.bulk.selectedCount', { count, total: totalVisible })}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClear}
|
||||
className="inline-flex items-center gap-1 text-2xs underline-offset-2 hover:underline opacity-80 hover:opacity-100"
|
||||
className="inline-flex items-center gap-1 text-cell underline-offset-2 hover:underline opacity-80 hover:opacity-100"
|
||||
>
|
||||
<XIcon size={12} weight="bold" />
|
||||
{t('dict.bulk.clearSelection')}
|
||||
@@ -1150,10 +1150,10 @@ function RecordsPagination({
|
||||
const to = Math.min((page + 1) * pageSize, totalCount)
|
||||
return (
|
||||
<div className="flex items-center justify-between flex-wrap gap-3 px-2">
|
||||
<span className="text-2xs text-ink-2 tabular-nums">
|
||||
<span className="text-cell text-ink-2 tabular-nums">
|
||||
{t('dict.page.range', { from, to, total: totalCount })}
|
||||
</span>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<div className="flex items-center gap-3 text-body">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
@@ -1163,7 +1163,7 @@ function RecordsPagination({
|
||||
>
|
||||
{t('dict.page.prev')}
|
||||
</Button>
|
||||
<span className="tabular-nums text-2xs text-ink-2">
|
||||
<span className="tabular-nums text-cell text-ink-2">
|
||||
{t('dict.page.of', { cur: page + 1, total: totalPages })}
|
||||
</span>
|
||||
<Button
|
||||
@@ -1209,19 +1209,19 @@ function BulkCloseDialog({
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
{result.closed.length > 0 && (
|
||||
<div className="flex items-center gap-2 text-sm text-soyuz-green">
|
||||
<div className="flex items-center gap-2 text-body text-soyuz-green">
|
||||
<CheckCircleIcon size={18} weight="fill" />
|
||||
<span>{t('dict.bulk.resultClosed', { count: result.closed.length })}</span>
|
||||
</div>
|
||||
)}
|
||||
{result.skipped.length > 0 && (
|
||||
<div className="flex items-center gap-2 text-sm text-saturn">
|
||||
<div className="flex items-center gap-2 text-body text-saturn">
|
||||
<WarningIcon size={18} weight="fill" />
|
||||
<span>{t('dict.bulk.resultSkipped', { count: result.skipped.length })}</span>
|
||||
</div>
|
||||
)}
|
||||
{result.errors.length > 0 && (
|
||||
<div className="flex items-center gap-2 text-sm text-mars">
|
||||
<div className="flex items-center gap-2 text-body text-mars">
|
||||
<XCircleIcon size={18} weight="fill" />
|
||||
<span>{t('dict.bulk.resultErrors', { count: result.errors.length })}</span>
|
||||
</div>
|
||||
@@ -1230,7 +1230,7 @@ function BulkCloseDialog({
|
||||
|
||||
{(result.skipped.length > 0 || result.errors.length > 0) && (
|
||||
<div className="border border-line rounded p-3 max-h-48 overflow-auto bg-line/30">
|
||||
<ul className="text-2xs font-mono space-y-1">
|
||||
<ul className="text-mono space-y-1">
|
||||
{result.skipped.map((s) => (
|
||||
<li key={`s-${s.businessKey}`} className="text-saturn">
|
||||
· {s.businessKey} — {s.reason}
|
||||
@@ -1256,7 +1256,7 @@ function BulkCloseDialog({
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-ink">{t('dict.bulk.confirmBody', { count })}</p>
|
||||
<p className="text-body text-ink">{t('dict.bulk.confirmBody', { count })}</p>
|
||||
<TextInput
|
||||
label={t('dict.confirmClose.reason')}
|
||||
placeholder={t('dict.confirmClose.reasonPlaceholder')}
|
||||
@@ -1361,7 +1361,7 @@ function EditorTabBar({
|
||||
onClick={() => onChange(tab.id)}
|
||||
aria-pressed={isActive}
|
||||
className={[
|
||||
'px-3.5 h-10 text-sm transition-colors -mb-px border-b-2',
|
||||
'px-3.5 h-10 text-body transition-colors -mb-px border-b-2',
|
||||
isActive
|
||||
? 'border-accent text-ink font-semibold'
|
||||
: 'border-transparent text-ink-2 hover:text-ink',
|
||||
@@ -1382,14 +1382,14 @@ function FieldsTabContent({ detail }: { detail: { schemaJson: import('@/api/clie
|
||||
const entries = Object.entries(props)
|
||||
if (entries.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-mute py-8 text-center">
|
||||
<div className="text-body text-mute py-8 text-center">
|
||||
Schema без properties.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="rounded-lg border border-line overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<table className="w-full text-body">
|
||||
<thead className="text-cap bg-surface-2 font-display text-mute">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2">name</th>
|
||||
@@ -1404,15 +1404,15 @@ function FieldsTabContent({ detail }: { detail: { schemaJson: import('@/api/clie
|
||||
<tbody>
|
||||
{entries.map(([key, prop]) => (
|
||||
<tr key={key} className="border-t border-line-2 hover:bg-surface-2/50">
|
||||
<td className="px-3 py-1.5 font-mono text-[12.5px]">{key}</td>
|
||||
<td className="text-cell px-3 py-1.5 font-mono">{key}</td>
|
||||
<td className="px-3 py-1.5 text-ink-2">{prop.type ?? '—'}</td>
|
||||
<td className="px-3 py-1.5">{required.has(key) ? '✓' : ''}</td>
|
||||
<td className="px-3 py-1.5">{prop['x-unique'] ? '✓' : ''}</td>
|
||||
<td className="px-3 py-1.5">{prop['x-localized'] ? '✓' : ''}</td>
|
||||
<td className="px-3 py-1.5 font-mono text-[11px] text-accent">
|
||||
<td className="text-mono px-3 py-1.5 text-accent">
|
||||
{prop['x-references'] ?? ''}
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-ink-2 text-xs max-w-[300px] truncate">
|
||||
<td className="px-3 py-1.5 text-ink-2 text-cell max-w-[300px] truncate">
|
||||
{prop.description ?? ''}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1437,13 +1437,13 @@ function JsonTabContent({ detail }: { detail: { schemaJson: import('@/api/client
|
||||
function EventsTabContent({ dictName }: { dictName: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-line bg-surface p-6 text-center space-y-3">
|
||||
<p className="text-sm text-ink-2">
|
||||
<p className="text-body text-ink-2">
|
||||
События для справочника <span className="font-mono">{dictName}</span> — в общем аудит-логе.
|
||||
</p>
|
||||
<Link
|
||||
to="/audit"
|
||||
search={{ dict: dictName }}
|
||||
className="inline-flex items-center gap-1 text-accent hover:underline text-sm"
|
||||
className="inline-flex items-center gap-1 text-accent hover:underline text-body"
|
||||
>
|
||||
Открыть /audit с фильтром →
|
||||
</Link>
|
||||
@@ -1454,7 +1454,7 @@ function EventsTabContent({ dictName }: { dictName: string }) {
|
||||
/** History tab — placeholder (Stage 3.x followup, требует backend changelog endpoint). */
|
||||
function HistoryTabContent() {
|
||||
return (
|
||||
<div className="rounded-lg border border-dashed border-line bg-surface-2/30 p-8 text-center text-sm text-mute">
|
||||
<div className="rounded-lg border border-dashed border-line bg-surface-2/30 p-8 text-center text-body text-mute">
|
||||
Timeline изменений справочника — скоро. Требует backend endpoint для schema versions.
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -273,7 +273,7 @@ function DictionariesPage() {
|
||||
onClick={() => toggleScope(scope)}
|
||||
aria-pressed={selected}
|
||||
title={t(`dict.list.section.${scope}`)}
|
||||
className={`px-2 py-1 rounded-full text-2xs uppercase tracking-[0.10em] flex items-center gap-1 border transition focus:outline-none focus:ring-2 focus:ring-accent/40 font-mono ${
|
||||
className={`text-cap px-2 py-1 rounded-full flex items-center gap-1 border transition focus:outline-none focus:ring-2 focus:ring-accent/40 font-mono ${
|
||||
selected
|
||||
? 'border-accent bg-accent-bg text-accent'
|
||||
: 'border-line hover:border-accent/60 text-ink'
|
||||
@@ -303,7 +303,7 @@ function DictionariesPage() {
|
||||
type="button"
|
||||
onClick={() => setBundle(undefined)}
|
||||
aria-pressed={!bundleFilter}
|
||||
className={`px-2 py-1 rounded-sm text-2xs uppercase tracking-[0.10em] border transition focus:outline-none focus:ring-2 focus:ring-accent/40 ${
|
||||
className={`text-cap px-2 py-1 rounded-sm border transition focus:outline-none focus:ring-2 focus:ring-accent/40 ${
|
||||
!bundleFilter
|
||||
? 'border-accent bg-accent-bg text-accent'
|
||||
: 'border-line hover:border-accent/60 text-ink'
|
||||
@@ -321,7 +321,7 @@ function DictionariesPage() {
|
||||
type="button"
|
||||
onClick={() => setBundle(selected ? undefined : bundle)}
|
||||
aria-pressed={selected}
|
||||
className={`px-2 py-1 rounded-sm text-2xs uppercase tracking-[0.10em] flex items-center gap-1 border transition focus:outline-none focus:ring-2 focus:ring-accent/40 font-mono ${
|
||||
className={`text-cap px-2 py-1 rounded-sm flex items-center gap-1 border transition focus:outline-none focus:ring-2 focus:ring-accent/40 font-mono ${
|
||||
selected
|
||||
? 'border-accent bg-accent-bg text-accent'
|
||||
: 'border-line hover:border-accent/60 text-ink'
|
||||
@@ -342,7 +342,7 @@ function DictionariesPage() {
|
||||
onClick={() => setSearch({ deps: withDepsOnly ? undefined : '1' })}
|
||||
aria-pressed={withDepsOnly}
|
||||
title={t('dict.list.deps.only')}
|
||||
className={`px-2 py-1 rounded-sm text-2xs uppercase tracking-[0.10em] border transition focus:outline-none focus:ring-2 focus:ring-accent/40 ${
|
||||
className={`text-cap px-2 py-1 rounded-sm border transition focus:outline-none focus:ring-2 focus:ring-accent/40 ${
|
||||
withDepsOnly
|
||||
? 'border-accent bg-accent-bg text-accent'
|
||||
: 'border-line hover:border-accent/60 text-ink'
|
||||
@@ -355,7 +355,7 @@ function DictionariesPage() {
|
||||
{/* Empty state */}
|
||||
{filtered.length === 0 ? (
|
||||
<div className="border border-line rounded-lg p-12 text-center bg-surface-2">
|
||||
<p className="font-sans text-lg text-accent mb-2">
|
||||
<p className="font-sans text-title-md text-accent mb-2">
|
||||
{t('dict.list.search.empty')}
|
||||
</p>
|
||||
{filtersActive && (
|
||||
@@ -369,7 +369,7 @@ function DictionariesPage() {
|
||||
Array.from(grouped.entries()).map(([bundle, items]) => (
|
||||
<section key={bundle} className="space-y-3">
|
||||
<h2 className="flex items-baseline gap-3 pb-2 border-b border-line">
|
||||
<span className="text-sm uppercase tracking-[0.10em] text-accent font-mono font-medium">
|
||||
<span className="text-body uppercase tracking-[0.10em] text-accent font-mono font-medium">
|
||||
{bundle}
|
||||
</span>
|
||||
<span className="text-cap text-mute">
|
||||
@@ -467,10 +467,10 @@ const DictCard = ({ d, t }: { d: DictionaryDefinition; t: TFunc }) => {
|
||||
{/* Body */}
|
||||
<div className="px-3.5 py-3 min-w-0">
|
||||
<div className="flex items-baseline gap-2.5 flex-wrap">
|
||||
<h3 className="text-[15px] font-semibold text-navy truncate">
|
||||
<h3 className="text-title-md text-navy truncate">
|
||||
{d.displayName ?? d.name}
|
||||
</h3>
|
||||
<span className="font-mono text-[11px] text-mute truncate">{d.name}</span>
|
||||
<span className="text-mono text-mute truncate">{d.name}</span>
|
||||
{d.approvalRequired && (
|
||||
<Badge variant="warning">{t('dict.list.approval')}</Badge>
|
||||
)}
|
||||
@@ -494,7 +494,7 @@ const DictCard = ({ d, t }: { d: DictionaryDefinition; t: TFunc }) => {
|
||||
/>
|
||||
))}
|
||||
{refBy.length > MAX_REFBY_CHIPS && (
|
||||
<span className="font-mono text-[11px] text-mute">
|
||||
<span className="text-mono text-mute">
|
||||
+{refBy.length - MAX_REFBY_CHIPS}
|
||||
</span>
|
||||
)}
|
||||
@@ -505,7 +505,7 @@ const DictCard = ({ d, t }: { d: DictionaryDefinition; t: TFunc }) => {
|
||||
{/* Right rail: scope badge + version */}
|
||||
<div className="px-3.5 py-3 flex flex-col items-end gap-1.5 shrink-0">
|
||||
<Badge variant="info">{d.scope}</Badge>
|
||||
<span className="font-mono text-[11px] text-mute whitespace-nowrap">
|
||||
<span className="text-mono text-mute whitespace-nowrap">
|
||||
v{d.schemaVersion}
|
||||
</span>
|
||||
{typeof d.recordCount === 'number' && (
|
||||
|
||||
@@ -82,7 +82,7 @@ function MyDraftsPage() {
|
||||
{drafts.data && drafts.data.totalElements > 0 && (
|
||||
<Panel>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<p className="text-2xs text-mute">
|
||||
<p className="text-cell text-mute">
|
||||
{t('myDrafts.total', { count: drafts.data.totalElements })}
|
||||
</p>
|
||||
</div>
|
||||
@@ -102,25 +102,25 @@ function MyDraftsPage() {
|
||||
<TableBody>
|
||||
{drafts.data.items.map((d) => (
|
||||
<TableRow key={d.id}>
|
||||
<TableCell className="font-mono text-2xs">{d.businessKey}</TableCell>
|
||||
<TableCell className="text-mono">{d.businessKey}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={operationVariant(d.operation)}>{d.operation}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={statusVariant(d.status)}>{d.status}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-2xs text-ink-2">
|
||||
<TableCell className="text-cell text-ink-2">
|
||||
{new Date(d.submittedAt).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="text-2xs text-ink-2">
|
||||
<TableCell className="text-cell text-ink-2">
|
||||
{d.reviewedAt
|
||||
? new Date(d.reviewedAt).toLocaleString()
|
||||
: '—'}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-2xs">
|
||||
<TableCell className="text-mono">
|
||||
{d.reviewerId ?? '—'}
|
||||
</TableCell>
|
||||
<TableCell className="text-2xs text-ink max-w-md">
|
||||
<TableCell className="text-cell text-ink max-w-md">
|
||||
{d.reviewComment ?? d.makerComment ?? '—'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -134,7 +134,7 @@ function MyDraftsPage() {
|
||||
{t('myDrafts.action.withdraw')}
|
||||
</Button>
|
||||
) : (
|
||||
<span className="text-2xs text-mute/70">—</span>
|
||||
<span className="text-cell text-mute/70">—</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -90,29 +90,29 @@ function OutboxPage() {
|
||||
{dlq.data.content.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>
|
||||
<span className="font-mono text-2xs">{row.id}</span>
|
||||
<span className="text-mono">{row.id}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="warning">{row.eventType}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{row.dictionaryName ?? '—'}</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-mono text-2xs">{row.aggregateId}</span>
|
||||
<span className="text-mono">{row.aggregateId}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-mono text-2xs">{row.kafkaTopic}</span>
|
||||
<span className="text-mono">{row.kafkaTopic}</span>
|
||||
</TableCell>
|
||||
<TableCell>{row.attemptCount}</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
className="text-2xs text-ink-2 truncate max-w-md inline-block align-middle"
|
||||
className="text-cell text-ink-2 truncate max-w-md inline-block align-middle"
|
||||
title={row.lastError ?? ''}
|
||||
>
|
||||
{row.lastError ?? '—'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-2xs tabular-nums">
|
||||
<span className="text-cell tabular-nums">
|
||||
{new Date(row.dlqAt).toLocaleString()}
|
||||
</span>
|
||||
</TableCell>
|
||||
@@ -120,7 +120,7 @@ function OutboxPage() {
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex items-center justify-end gap-3 text-sm">
|
||||
<div className="flex items-center justify-end gap-3 text-body">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
|
||||
@@ -160,7 +160,7 @@ function ReviewsPage() {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<ul className="space-y-1 text-2xs">
|
||||
<ul className="space-y-1 text-cell">
|
||||
{bulkResult.approved.length > 0 && (
|
||||
<li>
|
||||
{t('reviews.bulk.approvedCount', { count: bulkResult.approved.length })}
|
||||
@@ -196,7 +196,7 @@ function ReviewsPage() {
|
||||
{queue.data && queue.data.totalElements > 0 && (
|
||||
<Panel>
|
||||
<div className="flex items-center justify-between mb-3 gap-3">
|
||||
<p className="text-2xs text-mute">
|
||||
<p className="text-cell text-mute">
|
||||
{t('reviews.queueTotal', { count: queue.data.totalElements })}
|
||||
{anySelected ? (
|
||||
<span className="ml-2 text-accent font-medium">
|
||||
@@ -264,17 +264,17 @@ function ReviewsPage() {
|
||||
<Link
|
||||
to="/dictionaries/$name"
|
||||
params={{ name: d.dictionaryId }}
|
||||
className="text-accent hover:underline font-mono text-2xs"
|
||||
className="text-mono text-accent hover:underline"
|
||||
>
|
||||
{d.dictionaryId.slice(0, 8)}…
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-2xs">{d.businessKey}</TableCell>
|
||||
<TableCell className="text-mono">{d.businessKey}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={operationVariant(d.operation)}>{d.operation}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-2xs">{d.makerId}</TableCell>
|
||||
<TableCell className="text-2xs text-ink-2">
|
||||
<TableCell className="text-mono">{d.makerId}</TableCell>
|
||||
<TableCell className="text-cell text-ink-2">
|
||||
{new Date(d.submittedAt).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -304,7 +304,7 @@ function ReviewsPage() {
|
||||
title={t('reviews.bulk.rejectModalTitle', { count: selection.size })}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<p className="text-2xs text-ink-2">
|
||||
<p className="text-cell text-ink-2">
|
||||
{t('reviews.bulk.rejectModalDescription')}
|
||||
</p>
|
||||
<TextArea
|
||||
@@ -400,7 +400,7 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
|
||||
|
||||
{draft && (
|
||||
<>
|
||||
<div className="flex flex-wrap items-center gap-2 text-2xs">
|
||||
<div className="flex flex-wrap items-center gap-2 text-cell">
|
||||
<Badge variant={operationVariant(draft.operation)}>{draft.operation}</Badge>
|
||||
<span className="font-mono">{draft.businessKey}</span>
|
||||
<span className="text-mute">
|
||||
@@ -413,7 +413,7 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
|
||||
</div>
|
||||
|
||||
{draft.makerComment && (
|
||||
<div className="px-3 py-2 rounded-sm border border-line bg-line/30 text-2xs">
|
||||
<div className="px-3 py-2 rounded-sm border border-line bg-line/30 text-cell">
|
||||
<span className="text-mute uppercase tracking-[0.10em] font-display">
|
||||
{t('reviews.drawer.makerComment')}:
|
||||
</span>{' '}
|
||||
@@ -428,12 +428,12 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
|
||||
</p>
|
||||
{liveQ.isLoading && <LoadingBlock size="sm" label={t('loading')} />}
|
||||
{liveQ.data === null && (
|
||||
<p className="text-2xs text-mute italic">
|
||||
<p className="text-cell text-mute italic">
|
||||
{t('reviews.drawer.noLive')}
|
||||
</p>
|
||||
)}
|
||||
{liveQ.data && (
|
||||
<pre className="bg-line/30 rounded p-2 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<pre className="text-mono bg-line/30 rounded p-2 overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(liveQ.data.data, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
@@ -443,11 +443,11 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
|
||||
{t('reviews.drawer.proposed')}
|
||||
</p>
|
||||
{draft.operation === 'CLOSE' ? (
|
||||
<p className="text-2xs text-aurora italic">
|
||||
<p className="text-cell text-aurora italic">
|
||||
{t('reviews.drawer.closeNote')}
|
||||
</p>
|
||||
) : (
|
||||
<pre className="bg-accent/4 rounded p-2 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<pre className="text-mono bg-accent/4 rounded p-2 overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(draft.data, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
|
||||
@@ -70,7 +70,7 @@ function SearchPage() {
|
||||
</form>
|
||||
|
||||
{tooShort && (
|
||||
<p className="text-2xs text-mute">{t('search.tooShort')}</p>
|
||||
<p className="text-cell text-mute">{t('search.tooShort')}</p>
|
||||
)}
|
||||
|
||||
{hasQuery && result.isLoading && (
|
||||
@@ -89,7 +89,7 @@ function SearchPage() {
|
||||
|
||||
{hasQuery && result.data && result.data.total > 0 && (
|
||||
<>
|
||||
<p className="text-2xs text-mute">
|
||||
<p className="text-cell text-mute">
|
||||
{t('search.totalHits', { count: result.data.total })}
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
@@ -100,7 +100,7 @@ function SearchPage() {
|
||||
to="/dictionaries/$name"
|
||||
params={{ name: g.dictName }}
|
||||
search={{ q: urlSearch.q }}
|
||||
className="text-sm font-sans text-accent hover:underline"
|
||||
className="text-body font-sans text-accent hover:underline"
|
||||
>
|
||||
{g.dictDisplayName ?? g.dictName}
|
||||
</Link>
|
||||
@@ -110,7 +110,7 @@ function SearchPage() {
|
||||
{g.items.map((it) => (
|
||||
<li
|
||||
key={`${g.dictName}-${it.businessKey}`}
|
||||
className="flex items-center justify-between gap-3 py-2 text-2xs"
|
||||
className="flex items-center justify-between gap-3 py-2 text-cell"
|
||||
>
|
||||
<Link
|
||||
to="/dictionaries/$name"
|
||||
@@ -136,7 +136,7 @@ function SearchPage() {
|
||||
)}
|
||||
|
||||
{!hasQuery && !tooShort && (
|
||||
<p className="text-2xs text-mute">{t('search.intro')}</p>
|
||||
<p className="text-cell text-mute">{t('search.intro')}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -115,7 +115,7 @@ function WebhookDetailPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
breadcrumb={
|
||||
<Link to="/webhooks" className="text-sm text-ink-2 hover:text-accent">
|
||||
<Link to="/webhooks" className="text-body text-ink-2 hover:text-accent">
|
||||
← {t('nav.webhooks')}
|
||||
</Link>
|
||||
}
|
||||
@@ -156,7 +156,7 @@ function WebhookDetailPage() {
|
||||
<Panel>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 p-4">
|
||||
<InfoRow label={t('webhooks.field.url')}>
|
||||
<code className="font-mono text-2xs break-all">{data.url}</code>
|
||||
<code className="text-mono break-all">{data.url}</code>
|
||||
</InfoRow>
|
||||
<InfoRow label={t('webhooks.col.status')}>
|
||||
<Badge variant={data.active ? 'success' : 'neutral'}>
|
||||
@@ -177,28 +177,28 @@ function WebhookDetailPage() {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-2xs text-mute">{t('webhooks.allScopes')}</span>
|
||||
<span className="text-cell text-mute">{t('webhooks.allScopes')}</span>
|
||||
)}
|
||||
</InfoRow>
|
||||
<InfoRow label={t('webhooks.field.dictionaryFilter')}>
|
||||
<span className="text-2xs">
|
||||
<span className="text-cell">
|
||||
{data.dictionaryFilter && data.dictionaryFilter.length > 0
|
||||
? data.dictionaryFilter.join(', ')
|
||||
: t('webhooks.allDicts')}
|
||||
</span>
|
||||
</InfoRow>
|
||||
<InfoRow label={t('webhooks.field.eventTypeFilter')}>
|
||||
<span className="text-2xs">
|
||||
<span className="text-cell">
|
||||
{data.eventTypeFilter && data.eventTypeFilter.length > 0
|
||||
? data.eventTypeFilter.join(', ')
|
||||
: t('webhooks.allEvents')}
|
||||
</span>
|
||||
</InfoRow>
|
||||
<InfoRow label="HMAC secret">
|
||||
<code className="font-mono text-2xs">{data.hmacSecret}</code>
|
||||
<code className="text-mono">{data.hmacSecret}</code>
|
||||
</InfoRow>
|
||||
<InfoRow label={t('webhooks.field.createdBy')}>
|
||||
<span className="text-2xs">
|
||||
<span className="text-cell">
|
||||
{data.createdBy} · {new Date(data.createdAt).toLocaleString()}
|
||||
</span>
|
||||
</InfoRow>
|
||||
@@ -207,7 +207,7 @@ function WebhookDetailPage() {
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="font-sans text-base text-accent">
|
||||
<h2 className="font-sans text-title-md text-accent">
|
||||
{t('webhooks.deliveries.title')}
|
||||
</h2>
|
||||
<IconButton
|
||||
@@ -232,7 +232,7 @@ function WebhookDetailPage() {
|
||||
onClick={() => handleStatusToggle(s)}
|
||||
aria-pressed={active}
|
||||
className={[
|
||||
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border text-2xs uppercase tracking-[0.10em] font-display transition-colors',
|
||||
'text-cap inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border transition-colors',
|
||||
active
|
||||
? 'border-accent bg-accent/8 text-accent'
|
||||
: 'border-line bg-white text-ink-2 hover:border-carbon/40',
|
||||
@@ -249,7 +249,7 @@ function WebhookDetailPage() {
|
||||
setPage(0)
|
||||
setStatusFilter(undefined)
|
||||
}}
|
||||
className="text-xs text-accent hover:underline ml-2"
|
||||
className="text-cell text-accent hover:underline ml-2"
|
||||
>
|
||||
{t('webhooks.deliveries.filter.clear')}
|
||||
</button>
|
||||
@@ -282,7 +282,7 @@ function WebhookDetailPage() {
|
||||
return (
|
||||
<TableRow key={d.id}>
|
||||
<TableCell>
|
||||
<span className="font-mono text-2xs">{d.outboxEventId}</span>
|
||||
<span className="text-mono">{d.outboxEventId}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={STATUS_BADGE[d.status] ?? 'neutral'}>
|
||||
@@ -290,22 +290,22 @@ function WebhookDetailPage() {
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-mono text-2xs">{d.attemptCount}</span>
|
||||
<span className="text-mono">{d.attemptCount}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-2xs text-ink-2">
|
||||
<span className="text-cell text-ink-2">
|
||||
{d.lastAttemptAt
|
||||
? new Date(d.lastAttemptAt).toLocaleString()
|
||||
: '—'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-mono text-2xs">
|
||||
<span className="text-mono">
|
||||
{d.lastStatusCode ?? '—'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-2xs text-ink-2 line-clamp-2 max-w-md inline-block">
|
||||
<span className="text-cell text-ink-2 line-clamp-2 max-w-md inline-block">
|
||||
{d.lastError ?? '—'}
|
||||
</span>
|
||||
</TableCell>
|
||||
@@ -337,7 +337,7 @@ function WebhookDetailPage() {
|
||||
>
|
||||
←
|
||||
</Button>
|
||||
<span className="text-sm text-ink-2 self-center">
|
||||
<span className="text-body text-ink-2 self-center">
|
||||
{page + 1} / {deliveries.data.totalPages}
|
||||
</span>
|
||||
<Button
|
||||
@@ -384,14 +384,14 @@ function TestResultAlert({
|
||||
action={
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-ink-2 hover:text-ink"
|
||||
className="text-cell text-ink-2 hover:text-ink"
|
||||
onClick={onDismiss}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<div className="space-y-1 text-sm">
|
||||
<div className="space-y-1 text-body">
|
||||
{result.httpStatus != null && (
|
||||
<div>
|
||||
<span className="text-cap text-mute mr-2">
|
||||
@@ -409,7 +409,7 @@ function TestResultAlert({
|
||||
</div>
|
||||
)}
|
||||
{result.message && (
|
||||
<div className="font-mono text-2xs text-ink whitespace-pre-wrap break-all">
|
||||
<div className="text-mono text-ink whitespace-pre-wrap break-all">
|
||||
{result.message}
|
||||
</div>
|
||||
)}
|
||||
@@ -446,14 +446,14 @@ function RotateRevealModal({
|
||||
className="bg-white rounded-lg p-6 max-w-xl w-full space-y-4"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h3 className="font-sans text-lg text-accent">
|
||||
<h3 className="font-sans text-title-lg text-accent">
|
||||
{t('webhooks.secret.rotatedTitle')}
|
||||
</h3>
|
||||
<Alert variant="warning" title={t('webhooks.secret.warningTitle')}>
|
||||
{t('webhooks.secret.rotatedWarning')}
|
||||
</Alert>
|
||||
<div className="bg-carbon/5 border border-line rounded-md p-3">
|
||||
<code className="font-mono text-2xs break-all">{secret}</code>
|
||||
<code className="text-mono break-all">{secret}</code>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button variant="primary" onClick={onClose}>
|
||||
|
||||
@@ -118,7 +118,7 @@ function WebhooksPage() {
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-mono text-2xs truncate max-w-xs inline-block align-middle">
|
||||
<span className="text-mono truncate max-w-xs inline-block align-middle">
|
||||
{sub.url}
|
||||
</span>
|
||||
</TableCell>
|
||||
@@ -127,12 +127,12 @@ function WebhooksPage() {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{sub.dictionaryFilter && sub.dictionaryFilter.length > 0 ? (
|
||||
<span className="text-2xs text-ink">
|
||||
<span className="text-cell text-ink">
|
||||
{sub.dictionaryFilter.slice(0, 3).join(', ')}
|
||||
{sub.dictionaryFilter.length > 3 && ` +${sub.dictionaryFilter.length - 3}`}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-2xs text-mute">{t('webhooks.allDicts')}</span>
|
||||
<span className="text-cell text-mute">{t('webhooks.allDicts')}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -175,7 +175,7 @@ function WebhooksPage() {
|
||||
function ScopeChips({ scopes }: { scopes?: DataScope[] | null }) {
|
||||
const { t } = useTranslation()
|
||||
if (!scopes || scopes.length === 0) {
|
||||
return <span className="text-2xs text-mute">{t('webhooks.allScopes')}</span>
|
||||
return <span className="text-cell text-mute">{t('webhooks.allScopes')}</span>
|
||||
}
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
@@ -368,7 +368,7 @@ function SecretRevealModal({
|
||||
{t('webhooks.secret.warning')}
|
||||
</Alert>
|
||||
<div className="bg-carbon/5 border border-line rounded-md p-3">
|
||||
<code className="font-mono text-2xs break-all">{revealed.secret}</code>
|
||||
<code className="text-mono break-all">{revealed.secret}</code>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user