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:
@@ -184,7 +184,7 @@ export const SchemaDrivenForm = ({
|
||||
</div>
|
||||
)}
|
||||
{idSource && (
|
||||
<div className="sm:col-span-2 text-2xs text-mute px-2">
|
||||
<div className="sm:col-span-2 text-cell text-mute px-2">
|
||||
{t('form.idSourceNote', { field: idSource })}
|
||||
</div>
|
||||
)}
|
||||
@@ -337,7 +337,7 @@ const FieldBody = ({
|
||||
name={`data.${fieldKey}.${loc}` as `data.${string}`}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs font-mono text-ink-2 normal-case tracking-normal whitespace-nowrap shrink-0 w-14">
|
||||
<span className="text-mono text-ink-2 normal-case tracking-normal whitespace-nowrap shrink-0 w-14">
|
||||
{loc === defaultLocale ? <strong>{loc}</strong> : loc}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -599,8 +599,8 @@ const DateTimeField = ({ label, value, defaultTime, onChange, hint, error }: Dat
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{hint && !error && <p className="text-2xs text-mute mt-1">{hint}</p>}
|
||||
{error && <p className="text-2xs text-mars mt-1">{error}</p>}
|
||||
{hint && !error && <p className="text-cell text-mute mt-1">{hint}</p>}
|
||||
{error && <p className="text-cell text-mars mt-1">{error}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -797,7 +797,7 @@ const ReferenceSelectField = ({
|
||||
// appearance-none убирает OS default стрелку.
|
||||
// py-2 (8px) match'ит TextInput высоту. pr-10 — место под адорнмент.
|
||||
className={[
|
||||
'w-full appearance-none border rounded-sm pl-3 pr-10 py-2 text-sm font-display',
|
||||
'w-full appearance-none border rounded-sm pl-3 pr-10 py-2 text-body font-display',
|
||||
'bg-white text-black transition-colors',
|
||||
'focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent',
|
||||
errorMsg ? 'border-mars' : 'border-line hover:border-carbon/40',
|
||||
|
||||
@@ -163,7 +163,7 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
type="button"
|
||||
onClick={() => setDrawMode(active ? 'idle' : mode)}
|
||||
className={[
|
||||
'px-3 py-1.5 text-xs font-display rounded-sm border transition-colors',
|
||||
'px-3 py-1.5 text-cell font-display rounded-sm border transition-colors',
|
||||
active
|
||||
? 'border-accent bg-accent text-white'
|
||||
: 'border-line bg-white text-ink hover:border-carbon/40',
|
||||
@@ -183,7 +183,7 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
panelClassName="max-h-[calc(100vh-2rem)] my-4 flex flex-col"
|
||||
bodyClassName="flex-1 flex flex-col gap-3 overflow-hidden"
|
||||
>
|
||||
<p className="text-sm text-ink-2">{t('aoi.dialog.help')}</p>
|
||||
<p className="text-body text-ink-2">{t('aoi.dialog.help')}</p>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{toolbarBtn('rectangle', t('aoi.dialog.drawRectangle'))}
|
||||
@@ -192,12 +192,12 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
disabled={!shape}
|
||||
className="px-3 py-1.5 text-xs font-display rounded-sm border border-line bg-white text-ink hover:border-carbon/40 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
className="px-3 py-1.5 text-cell font-display rounded-sm border border-line bg-white text-ink hover:border-carbon/40 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
{t('aoi.dialog.clear')}
|
||||
</button>
|
||||
{drawMode === 'polygon' && (
|
||||
<span className="text-2xs text-mute">
|
||||
<span className="text-cell text-mute">
|
||||
{t('aoi.dialog.polygonHint')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -89,11 +89,11 @@ export function DictionaryGraph() {
|
||||
}
|
||||
|
||||
if (dictsQuery.error) {
|
||||
return <div className="text-sm text-pink">Ошибка загрузки: {String(dictsQuery.error)}</div>
|
||||
return <div className="text-body text-pink">Ошибка загрузки: {String(dictsQuery.error)}</div>
|
||||
}
|
||||
|
||||
if (dicts.length === 0) {
|
||||
return <div className="text-sm text-mute">Справочники не найдены.</div>
|
||||
return <div className="text-body text-mute">Справочники не найдены.</div>
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -222,7 +222,7 @@ function GraphCanvas({
|
||||
return (
|
||||
<div ref={wrapperRef} className="relative w-full h-[calc(100vh-220px)] min-h-[500px] rounded-lg border border-line bg-surface overflow-hidden">
|
||||
{depsLoading && (
|
||||
<div className="absolute top-3 right-3 z-10 text-2xs font-mono uppercase tracking-wider text-mute bg-surface/80 backdrop-blur px-2 py-1 rounded">
|
||||
<div className="text-mono absolute top-3 right-3 z-10 uppercase tracking-wider text-mute bg-surface/80 backdrop-blur px-2 py-1 rounded">
|
||||
Загрузка связей…
|
||||
</div>
|
||||
)}
|
||||
@@ -318,7 +318,7 @@ function GraphCanvas({
|
||||
})}
|
||||
</svg>
|
||||
{/* Legend */}
|
||||
<div className="absolute bottom-3 left-3 flex flex-col gap-1 bg-surface/90 backdrop-blur px-3 py-2 rounded-md border border-line text-2xs">
|
||||
<div className="absolute bottom-3 left-3 flex flex-col gap-1 bg-surface/90 backdrop-blur px-3 py-2 rounded-md border border-line text-cell">
|
||||
<div className="font-display uppercase tracking-wider text-mute mb-0.5">Bundle</div>
|
||||
{Object.entries(BUNDLE_COLORS)
|
||||
.filter(([k]) => k !== 'default')
|
||||
|
||||
@@ -140,7 +140,7 @@ export function Sidebar() {
|
||||
{/* User block — sticky bottom */}
|
||||
{auth.isAuthenticated && auth.user?.profile && (
|
||||
<div className="border-t border-line px-3 py-3 flex items-center gap-2">
|
||||
<div className="size-8 rounded-full bg-accent text-on-accent inline-flex items-center justify-center text-xs font-semibold">
|
||||
<div className="size-8 rounded-full bg-accent text-on-accent inline-flex items-center justify-center text-cell font-semibold">
|
||||
{String(
|
||||
auth.user.profile.preferred_username ||
|
||||
auth.user.profile.name ||
|
||||
@@ -150,7 +150,7 @@ export function Sidebar() {
|
||||
.toUpperCase()}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-xs font-medium text-ink truncate">
|
||||
<div className="text-cell font-medium text-ink truncate">
|
||||
{String(
|
||||
auth.user.profile.preferred_username ||
|
||||
auth.user.profile.name ||
|
||||
@@ -159,7 +159,7 @@ export function Sidebar() {
|
||||
)}
|
||||
</div>
|
||||
{auth.user.profile.email && (
|
||||
<div className="text-2xs text-mute truncate">{String(auth.user.profile.email)}</div>
|
||||
<div className="text-cell text-mute truncate">{String(auth.user.profile.email)}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,9 +179,8 @@ function SidebarLink({ to, label, icon: Icon, badge }: NavItem) {
|
||||
<Link
|
||||
to={to}
|
||||
className={cn(
|
||||
// 13px per handoff spec для workhorse body text. text-sm = 14px (Tailwind),
|
||||
// text-[13px] = exact handoff value.
|
||||
'group flex items-center gap-2.5 px-3 py-1.5 rounded-md text-[13px] transition-colors',
|
||||
// text-body = 13px workhorse per handoff (см. styles.css @utility).
|
||||
'group flex items-center gap-2.5 px-3 py-1.5 rounded-md text-body transition-colors',
|
||||
active
|
||||
? 'bg-accent-bg text-accent font-medium'
|
||||
: 'text-ink-2 hover:text-ink hover:bg-surface-2',
|
||||
@@ -192,7 +191,7 @@ function SidebarLink({ to, label, icon: Icon, badge }: NavItem) {
|
||||
{badge !== undefined && (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-sm text-[11px] font-mono',
|
||||
'inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-sm text-mono',
|
||||
active
|
||||
? 'bg-accent text-on-accent'
|
||||
: 'bg-surface-2 text-ink-2 group-hover:bg-line',
|
||||
|
||||
@@ -60,12 +60,12 @@ export function TopBar() {
|
||||
return (
|
||||
<header className="h-14 shrink-0 border-b border-line bg-surface flex items-center px-4 sm:px-6 gap-3">
|
||||
{/* Breadcrumb — left */}
|
||||
<nav className="flex items-center gap-1.5 text-sm min-w-0">
|
||||
<nav className="flex items-center gap-1.5 text-body min-w-0">
|
||||
{breadcrumb.map((crumb, i) => {
|
||||
const isLast = i === breadcrumb.length - 1
|
||||
return (
|
||||
<span key={i} className="flex items-center gap-1.5 min-w-0">
|
||||
{i > 0 && <span className="text-mute text-xs">/</span>}
|
||||
{i > 0 && <span className="text-mute text-cell">/</span>}
|
||||
{crumb.to && !isLast ? (
|
||||
<Link
|
||||
to={crumb.to}
|
||||
@@ -101,7 +101,7 @@ export function TopBar() {
|
||||
href="/docs/"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="hidden sm:inline text-xs text-ink-2 hover:text-ink transition-colors"
|
||||
className="hidden sm:inline text-cell text-ink-2 hover:text-ink transition-colors"
|
||||
>
|
||||
{t('nav.docs')}
|
||||
</a>
|
||||
|
||||
@@ -119,7 +119,7 @@ export const CascadeConfirmDialog = ({
|
||||
{plan && (
|
||||
<>
|
||||
{/* Header summary */}
|
||||
<p className="text-sm text-ink">
|
||||
<p className="text-body text-ink">
|
||||
{hasBlockers
|
||||
? t('cascade.summary.blocked', {
|
||||
blockers: plan.blockers.length,
|
||||
@@ -137,7 +137,7 @@ export const CascadeConfirmDialog = ({
|
||||
variant="error"
|
||||
title={t('cascade.blockers.title', { count: plan.blockers.length })}
|
||||
>
|
||||
<ul className="space-y-1.5 mt-2 text-2xs">
|
||||
<ul className="space-y-1.5 mt-2 text-cell">
|
||||
{plan.blockers.slice(0, 5).map((b) => (
|
||||
<li key={b.recordId} className="flex items-center gap-2">
|
||||
<XCircleIcon weight="bold" size={12} className="text-aurora shrink-0" />
|
||||
@@ -153,7 +153,7 @@ export const CascadeConfirmDialog = ({
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
<p className="mt-2 text-2xs text-ink-2">
|
||||
<p className="mt-2 text-cell text-ink-2">
|
||||
{t('cascade.blockers.resolveFirst')}
|
||||
</p>
|
||||
</Alert>
|
||||
@@ -164,7 +164,7 @@ export const CascadeConfirmDialog = ({
|
||||
<section className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<WarningIcon weight="bold" size={14} className="text-horizon" />
|
||||
<h4 className="text-sm font-sans">
|
||||
<h4 className="text-body font-sans">
|
||||
{t('cascade.cascade.title', { count: plan.cascade.length })}
|
||||
</h4>
|
||||
</div>
|
||||
@@ -176,7 +176,7 @@ export const CascadeConfirmDialog = ({
|
||||
return (
|
||||
<li
|
||||
key={key}
|
||||
className="border border-line rounded-sm px-3 py-2 text-2xs"
|
||||
className="border border-line rounded-sm px-3 py-2 text-cell"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-mono text-accent">
|
||||
@@ -190,7 +190,7 @@ export const CascadeConfirmDialog = ({
|
||||
</Badge>
|
||||
<Badge variant="neutral">{entries.length}</Badge>
|
||||
</div>
|
||||
<ul className="text-2xs text-ink-2 ml-1 space-y-0.5">
|
||||
<ul className="text-cell text-ink-2 ml-1 space-y-0.5">
|
||||
{sample.map((e) => (
|
||||
<li key={e.recordId} className="font-mono">
|
||||
· {e.businessKey}
|
||||
@@ -215,7 +215,7 @@ export const CascadeConfirmDialog = ({
|
||||
variant="warning"
|
||||
title={t('cascade.warnings.title', { count: plan.warnings.length })}
|
||||
>
|
||||
<p className="text-2xs">{t('cascade.warnings.note')}</p>
|
||||
<p className="text-cell">{t('cascade.warnings.note')}</p>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@ export const DictionaryDependentsPanel = ({ dictionaryName }: Props) => {
|
||||
return (
|
||||
<Panel>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-sans text-ink">
|
||||
<h3 className="text-body font-sans text-ink">
|
||||
{t('lineage.usedBy.title')}
|
||||
</h3>
|
||||
<span className="text-2xs text-mute">
|
||||
<span className="text-cell text-mute">
|
||||
{t('lineage.usedBy.count', { count: data.length })}
|
||||
</span>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@ export const DictionaryDependentsPanel = ({ dictionaryName }: Props) => {
|
||||
{data.map((d) => (
|
||||
<li
|
||||
key={`${d.sourceDict}.${d.sourceField}`}
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 rounded-sm border border-line bg-white text-2xs"
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 rounded-sm border border-line bg-white text-cell"
|
||||
>
|
||||
<Link
|
||||
to="/dictionaries/$name"
|
||||
|
||||
@@ -85,7 +85,7 @@ const SectionLabel = ({
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div
|
||||
className={`text-2xs uppercase tracking-[0.10em] text-mute mb-2 ${
|
||||
className={`text-cap text-mute mb-2 ${
|
||||
side === 'left' ? 'text-right' : 'text-left'
|
||||
}`}
|
||||
>
|
||||
@@ -141,7 +141,7 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
|
||||
<div className="md:order-1 order-2 space-y-2">
|
||||
<SectionLabel side="left" count={outgoingDicts.length} />
|
||||
{outgoingDicts.length === 0 ? (
|
||||
<div className="text-2xs text-mute/70 text-right italic">
|
||||
<div className="text-cell text-mute/70 text-right italic">
|
||||
{t('hub.noOutgoing', { defaultValue: 'нет исходящих ссылок' })}
|
||||
</div>
|
||||
) : (
|
||||
@@ -166,14 +166,14 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
|
||||
{t('dict.list.approval', { defaultValue: 'approval' })}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="ml-auto font-mono text-[11px] text-mute">
|
||||
<span className="text-mono ml-auto text-mute">
|
||||
v{detail.schemaVersion}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-sans text-[22px] font-semibold text-accent leading-tight">
|
||||
<h2 className="text-title-xl font-sans text-accent leading-tight">
|
||||
{detail.displayName ?? detail.name}
|
||||
</h2>
|
||||
<div className="font-mono text-[11px] text-mute mt-0.5">
|
||||
<div className="text-mono text-mute mt-0.5">
|
||||
{detail.name}
|
||||
</div>
|
||||
{detail.description && (
|
||||
@@ -197,7 +197,7 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
|
||||
<div className="md:order-3 order-3 space-y-2">
|
||||
<SectionLabel side="right" count={incomingDicts.length} />
|
||||
{incomingDicts.length === 0 ? (
|
||||
<div className="text-2xs text-mute/70 italic">
|
||||
<div className="text-cell text-mute/70 italic">
|
||||
{t('hub.noIncoming', { defaultValue: 'никто не ссылается' })}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -68,19 +68,19 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
return (
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-sans text-ink">
|
||||
<h3 className="text-body font-sans text-ink">
|
||||
{t('lineage.refs.title')}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
{stale !== null && stale >= 2 && (
|
||||
<span
|
||||
className="text-2xs text-mute px-2 py-0.5 rounded-sm border border-line"
|
||||
className="text-cell text-mute px-2 py-0.5 rounded-sm border border-line"
|
||||
title={t('lineage.refs.staleHint')}
|
||||
>
|
||||
{t('lineage.refs.stale', { minutes: stale })}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-2xs text-mute">
|
||||
<span className="text-cell text-mute">
|
||||
{t('lineage.refs.total', { count: data.total })}
|
||||
</span>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
{data.perSource.map((s) => (
|
||||
<li
|
||||
key={`${s.sourceDict}.${s.sourceField}`}
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-sm border border-line bg-line/30 text-2xs"
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-sm border border-line bg-line/30 text-cell"
|
||||
>
|
||||
<Link
|
||||
to="/dictionaries/$name"
|
||||
@@ -115,7 +115,7 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
{data.items.map((r) => (
|
||||
<li
|
||||
key={r.id}
|
||||
className="flex items-center justify-between gap-3 px-3 py-2 text-2xs"
|
||||
className="flex items-center justify-between gap-3 px-3 py-2 text-cell"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -152,7 +152,7 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between text-2xs">
|
||||
<div className="flex items-center justify-between text-cell">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
|
||||
@@ -40,7 +40,7 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
</Alert>
|
||||
)}
|
||||
{data && data.length === 0 && (
|
||||
<p className="text-sm text-mute">{t('history.empty')}</p>
|
||||
<p className="text-body text-mute">{t('history.empty')}</p>
|
||||
)}
|
||||
{data && data.length > 0 && (
|
||||
<ol className="relative border-l-2 border-line pl-5 space-y-4">
|
||||
@@ -74,7 +74,7 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
}`}
|
||||
/>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-sm font-sans text-accent">v{displayVersion}</span>
|
||||
<span className="text-body font-sans text-accent">v{displayVersion}</span>
|
||||
{isLatest && status === 'active' && (
|
||||
<Badge variant="primary">{t('history.current')}</Badge>
|
||||
)}
|
||||
@@ -84,7 +84,7 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
<Badge variant="success">{t('history.active')}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-2xs text-ink-2 space-y-0.5">
|
||||
<div className="text-cell text-ink-2 space-y-0.5">
|
||||
<div>
|
||||
<span className="font-mono">{t('history.validFrom')}:</span>{' '}
|
||||
{new Date(rec.validFrom).toLocaleString()}
|
||||
@@ -102,10 +102,10 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
)}
|
||||
</div>
|
||||
<details className="mt-2">
|
||||
<summary className="text-2xs text-accent cursor-pointer hover:underline">
|
||||
<summary className="text-cell text-accent cursor-pointer hover:underline">
|
||||
{t('history.viewData')}
|
||||
</summary>
|
||||
<pre className="mt-1 bg-line/30 rounded p-2 text-2xs font-mono overflow-x-auto">
|
||||
<pre className="text-mono mt-1 bg-line/30 rounded p-2 overflow-x-auto">
|
||||
{JSON.stringify(rec.data, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
@@ -308,10 +308,10 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
className="mt-0.5 size-4 accent-ultramarain"
|
||||
/>
|
||||
<span className="flex-1">
|
||||
<span className="block text-sm font-sans text-ink">
|
||||
<span className="block text-body font-sans text-ink">
|
||||
{t('schema.redisProjection.label')}
|
||||
</span>
|
||||
<span className="block text-2xs text-mute mt-0.5">
|
||||
<span className="block text-cell text-mute mt-0.5">
|
||||
{t('schema.redisProjection.hint')}
|
||||
</span>
|
||||
</span>
|
||||
@@ -328,10 +328,10 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
className="mt-0.5 size-4 accent-orbit"
|
||||
/>
|
||||
<span className="flex-1">
|
||||
<span className="block text-sm font-sans text-ink">
|
||||
<span className="block text-body font-sans text-ink">
|
||||
{t('schema.approvalRequired.label')}
|
||||
</span>
|
||||
<span className="block text-2xs text-mute mt-0.5">
|
||||
<span className="block text-cell text-mute mt-0.5">
|
||||
{t('schema.approvalRequired.hint')}
|
||||
</span>
|
||||
</span>
|
||||
@@ -351,7 +351,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
onChange={(e) => setApprovalMinRole(e.target.value)}
|
||||
placeholder="ordinis:internal"
|
||||
/>
|
||||
<p className="text-2xs text-mute mt-0.5">
|
||||
<p className="text-cell text-mute mt-0.5">
|
||||
{t('schema.approvalMinRole.hint')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -364,7 +364,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
</div>
|
||||
|
||||
<div className={activeTab === 'preview' ? 'block' : 'hidden'}>
|
||||
<pre className="bg-line/30 rounded p-3 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<pre className="text-mono bg-line/30 rounded p-3 overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(schemaJson, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -384,8 +384,8 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
{isEdit && breaking && (
|
||||
<Alert variant="error" title={t('schema.diff.blockedTitle')}>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm">{t('schema.diff.blockedBody')}</p>
|
||||
<ul className="list-disc pl-5 text-sm space-y-0.5">
|
||||
<p className="text-body">{t('schema.diff.blockedBody')}</p>
|
||||
<ul className="list-disc pl-5 text-body space-y-0.5">
|
||||
{changes
|
||||
.filter((c) => c.severity === 'breaking')
|
||||
.map((c, i) => (
|
||||
@@ -400,7 +400,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
|
||||
{isEdit && !breaking && changes.length > 0 && (
|
||||
<Alert variant="info" title={t('schema.diff.compatibleTitle')}>
|
||||
<ul className="list-disc pl-5 text-sm space-y-0.5">
|
||||
<ul className="list-disc pl-5 text-body space-y-0.5">
|
||||
{changes.map((c, i) => (
|
||||
<li key={i}>
|
||||
<span className="font-mono">{c.field}</span> — {c.reason}
|
||||
|
||||
@@ -47,7 +47,7 @@ export const EventsPreviewTab = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-ink-2">
|
||||
<p className="text-body text-ink-2">
|
||||
{t('schema.events.intro')}
|
||||
</p>
|
||||
|
||||
@@ -57,18 +57,18 @@ export const EventsPreviewTab = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Badge variant="info">{current.eventType}</Badge>
|
||||
<span className="text-2xs text-ink-2">
|
||||
<span className="text-cell text-ink-2">
|
||||
{t('schema.events.topic')}:
|
||||
</span>
|
||||
<code className="text-2xs font-mono text-accent">
|
||||
<code className="text-mono text-accent">
|
||||
{current.topic}
|
||||
</code>
|
||||
</div>
|
||||
<p className="text-2xs text-mute">{current.description}</p>
|
||||
<pre className="bg-line/30 rounded p-3 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<p className="text-cell text-mute">{current.description}</p>
|
||||
<pre className="text-mono bg-line/30 rounded p-3 overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(current.envelope, null, 2)}
|
||||
</pre>
|
||||
<p className="text-2xs text-mute">
|
||||
<p className="text-cell text-mute">
|
||||
{t('schema.events.disclaimer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -118,14 +118,14 @@ export const PropertyEditor = ({
|
||||
/>
|
||||
|
||||
{prop.kind === 'opaque' && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-md p-3 text-2xs space-y-2">
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-md p-3 text-cell space-y-2">
|
||||
<div className="font-medium text-ink">
|
||||
{t('schema.opaque.title')}
|
||||
</div>
|
||||
<div className="text-ink-2">
|
||||
{t('schema.opaque.hint')}
|
||||
</div>
|
||||
<pre className="font-mono text-2xs bg-white border border-line rounded p-2 overflow-auto max-h-40">
|
||||
<pre className="text-mono bg-white border border-line rounded p-2 overflow-auto max-h-40">
|
||||
{JSON.stringify(prop.rawSchema ?? {}, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -285,7 +285,7 @@ const ReferenceTargetPicker = ({ value, onChange }: ReferenceTargetPickerProps)
|
||||
/>
|
||||
</div>
|
||||
{!isValid && (
|
||||
<div className="text-2xs text-mars">{t('schema.reference.required')}</div>
|
||||
<div className="text-cell text-mars">{t('schema.reference.required')}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ export function TimeTravelPicker({
|
||||
}, [marks, valueDate])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 px-4 py-3 rounded-md border border-warn bg-warn-bg text-sm">
|
||||
<div className="flex flex-col gap-3 px-4 py-3 rounded-md border border-warn bg-warn-bg text-body">
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockCounterClockwiseIcon weight="bold" size={16} className="text-warn" />
|
||||
<span className="text-cap font-display text-ink-2">
|
||||
@@ -58,7 +58,7 @@ export function TimeTravelPicker({
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-auto text-2xs text-mute hover:text-ink hover:underline inline-flex items-center gap-1"
|
||||
className="ml-auto text-cell text-mute hover:text-ink hover:underline inline-flex items-center gap-1"
|
||||
onClick={onClose}
|
||||
>
|
||||
<XIcon weight="bold" size={12} />
|
||||
@@ -306,7 +306,7 @@ function TimelineTrack({
|
||||
|
||||
{tooltipText && hover !== null && (
|
||||
<div
|
||||
className="absolute -top-2 -translate-y-full px-2 py-0.5 rounded bg-carbon text-white text-2xs font-mono pointer-events-none whitespace-nowrap"
|
||||
className="text-mono absolute -top-2 -translate-y-full px-2 py-0.5 rounded bg-carbon text-white pointer-events-none whitespace-nowrap"
|
||||
style={{ left: timeToX(hover), transform: 'translate(-50%, -100%)' }}
|
||||
>
|
||||
{tooltipText}
|
||||
@@ -314,7 +314,7 @@ function TimelineTrack({
|
||||
)}
|
||||
|
||||
{marks.length > 0 && (
|
||||
<div className="text-2xs text-mute font-display mt-1">
|
||||
<div className="text-cell text-mute font-display mt-1">
|
||||
{marks.length === 1
|
||||
? '1 точка изменений'
|
||||
: marks.length < 5
|
||||
@@ -386,7 +386,7 @@ function StatusFooter({
|
||||
|
||||
if (!valueDate) {
|
||||
return (
|
||||
<div className="text-2xs text-mute font-display italic">
|
||||
<div className="text-cell text-mute font-display italic">
|
||||
{t('timeTravel.hint')}
|
||||
</div>
|
||||
)
|
||||
@@ -395,14 +395,14 @@ function StatusFooter({
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2 pt-2 border-t border-warn/30">
|
||||
<ClockCounterClockwiseIcon weight="regular" size={14} className="text-warn shrink-0" />
|
||||
<span className="text-xs font-mono text-ink">
|
||||
<span className="text-mono text-ink">
|
||||
{valueDate.toLocaleString(lang === 'en' ? 'en-US' : 'ru-RU', {
|
||||
dateStyle: 'long',
|
||||
timeStyle: 'short',
|
||||
})}
|
||||
</span>
|
||||
{relativeLabel && (
|
||||
<span className="text-2xs text-mute font-display lowercase">
|
||||
<span className="text-cell text-mute font-display lowercase">
|
||||
({relativeLabel})
|
||||
</span>
|
||||
)}
|
||||
@@ -418,10 +418,10 @@ function StatusFooter({
|
||||
valueDate.getMinutes(),
|
||||
)
|
||||
}
|
||||
className="w-10 px-1.5 py-0.5 rounded-sm border border-line bg-white text-2xs font-mono text-center"
|
||||
className="text-mono w-10 px-1.5 py-0.5 rounded-sm border border-line bg-white text-center"
|
||||
aria-label={t('timeTravel.hours')}
|
||||
/>
|
||||
<span className="text-mute font-mono text-2xs">:</span>
|
||||
<span className="text-mono text-mute">:</span>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
@@ -433,13 +433,13 @@ function StatusFooter({
|
||||
Math.max(0, Math.min(59, parseInt(e.target.value, 10) || 0)),
|
||||
)
|
||||
}
|
||||
className="w-10 px-1.5 py-0.5 rounded-sm border border-line bg-white text-2xs font-mono text-center"
|
||||
className="text-mono w-10 px-1.5 py-0.5 rounded-sm border border-line bg-white text-center"
|
||||
aria-label={t('timeTravel.minutes')}
|
||||
/>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-auto text-2xs text-accent hover:underline"
|
||||
className="ml-auto text-cell text-accent hover:underline"
|
||||
onClick={onClear}
|
||||
>
|
||||
{t('timeTravel.clear')}
|
||||
|
||||
@@ -44,7 +44,7 @@ export function UpdateBanner() {
|
||||
aria-live="polite"
|
||||
className="bg-accent text-on-accent"
|
||||
>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-2 flex items-center gap-3 flex-wrap text-sm">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-2 flex items-center gap-3 flex-wrap text-body">
|
||||
<ArrowClockwise size={18} weight="bold" className="shrink-0" aria-hidden />
|
||||
<span className="flex-1 min-w-0">{message}</span>
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
|
||||
@@ -40,7 +40,7 @@ export function VersionBadge() {
|
||||
|
||||
return (
|
||||
<span
|
||||
className="inline-flex items-center gap-1.5 text-xs text-mute font-mono select-none"
|
||||
className="inline-flex items-center gap-1.5 text-mono text-mute select-none"
|
||||
title={tooltip}
|
||||
>
|
||||
{updateAvailable && (
|
||||
|
||||
Reference in New Issue
Block a user