Merge branch 'fix/v1.3.1-fonts-handoff' into 'main'

fix(fonts): align all text sizes to handoff spec via Tailwind theme

See merge request 2-6/2-6-4/terravault/ordinis!45
This commit is contained in:
Александр Зимин
2026-05-11 11:13:25 +00:00
19 changed files with 56 additions and 31 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ export function AuthBadge() {
if (auth.isLoading) { if (auth.isLoading) {
return ( return (
<span className="text-2xs uppercase tracking-[0.10em] text-mute"> <span className="text-cap text-mute">
{t('loading')} {t('loading')}
</span> </span>
) )
@@ -327,7 +327,7 @@ const FieldBody = ({
<div> <div>
<FieldLabel required={required}> <FieldLabel required={required}>
{label}{' '} {label}{' '}
<span className="text-2xs uppercase tracking-[0.10em] text-mute ml-1">i18n</span> <span className="text-cap text-mute ml-1">i18n</span>
</FieldLabel> </FieldLabel>
<div className="space-y-2 pl-3 border-l-2 border-line mt-1"> <div className="space-y-2 pl-3 border-l-2 border-line mt-1">
{supportedLocales.map((loc) => ( {supportedLocales.map((loc) => (
@@ -126,7 +126,7 @@ export function Sidebar() {
{visibleSections.map((section, idx) => ( {visibleSections.map((section, idx) => (
<div key={idx} className="flex flex-col gap-0.5"> <div key={idx} className="flex flex-col gap-0.5">
{section.label && ( {section.label && (
<div className="px-3 py-1 font-display uppercase tracking-[0.10em] text-mute text-[10.5px] leading-none"> <div className="text-cap px-3 py-1 text-mute">
{section.label} {section.label}
</div> </div>
)} )}
@@ -224,7 +224,7 @@ export const CascadeConfirmDialog = ({
<div> <div>
<label <label
htmlFor="cascade-reason" htmlFor="cascade-reason"
className="text-2xs text-ink-2 uppercase tracking-[0.10em]" className="text-cap text-ink-2"
> >
{t('cascade.reason.label')} {t('cascade.reason.label')}
</label> </label>
@@ -242,7 +242,7 @@ export const CascadeConfirmDialog = ({
<div> <div>
<label <label
htmlFor="cascade-confirm" htmlFor="cascade-confirm"
className="text-2xs text-ink-2 uppercase tracking-[0.10em]" className="text-cap text-ink-2"
> >
{t('cascade.confirmGate.label', { word: 'CONFIRM' })} {t('cascade.confirmGate.label', { word: 'CONFIRM' })}
</label> </label>
@@ -181,7 +181,7 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
{detail.description} {detail.description}
</p> </p>
)} )}
<div className="mt-4 pt-3 border-t border-line flex items-center justify-between text-2xs uppercase tracking-[0.10em] text-mute"> <div className="text-cap mt-4 pt-3 border-t border-line flex items-center justify-between text-mute">
<span>{detail.bundle}</span> <span>{detail.bundle}</span>
<span>{detail.supportedLocales.join(', ')}</span> <span>{detail.supportedLocales.join(', ')}</span>
{typeof detail.recordCount === 'number' && ( {typeof detail.recordCount === 'number' && (
@@ -341,7 +341,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
<div className="md:col-span-2"> <div className="md:col-span-2">
<label <label
htmlFor="approval-min-role" htmlFor="approval-min-role"
className="text-2xs uppercase tracking-[0.10em] text-ink-2" className="text-cap text-ink-2"
> >
{t('schema.approvalMinRole.label')} {t('schema.approvalMinRole.label')}
</label> </label>
@@ -51,7 +51,7 @@ export const PropertyEditor = ({
return ( return (
<div className="border border-line rounded-lg p-3 space-y-3 bg-white"> <div className="border border-line rounded-lg p-3 space-y-3 bg-white">
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<span className="text-2xs uppercase tracking-[0.10em] text-mute"> <span className="text-cap text-mute">
{t('schema.property')} #{index + 1} {t('schema.property')} #{index + 1}
</span> </span>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@@ -53,7 +53,7 @@ export function TimeTravelPicker({
<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-sm">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ClockCounterClockwiseIcon weight="bold" size={16} className="text-warn" /> <ClockCounterClockwiseIcon weight="bold" size={16} className="text-warn" />
<span className="text-2xs font-display uppercase tracking-[0.10em] text-ink-2"> <span className="text-cap font-display text-ink-2">
{t('timeTravel.label')} {t('timeTravel.label')}
</span> </span>
<button <button
@@ -340,7 +340,7 @@ function QuickPresets({ onPick }: { onPick: (deltaMs: number) => void }) {
] ]
return ( return (
<div className="flex flex-wrap items-center gap-1.5"> <div className="flex flex-wrap items-center gap-1.5">
<span className="text-2xs uppercase tracking-[0.10em] text-mute font-display mr-1"> <span className="text-cap text-mute font-display mr-1">
{t('timeTravel.quickPresets')} {t('timeTravel.quickPresets')}
</span> </span>
{presets.map((p) => ( {presets.map((p) => (
@@ -348,7 +348,7 @@ function QuickPresets({ onPick }: { onPick: (deltaMs: number) => void }) {
key={p.key} key={p.key}
type="button" type="button"
onClick={() => onPick(p.deltaMs)} onClick={() => onPick(p.deltaMs)}
className="px-2.5 py-1 rounded-full border border-line hover:border-accent hover:bg-accent/4 text-2xs font-mono uppercase tracking-[0.10em] text-ink transition-colors" className="text-cap px-2.5 py-1 rounded-full border border-line hover:border-accent hover:bg-accent/4 font-mono text-ink transition-colors"
> >
{p.label} {p.label}
</button> </button>
+3 -3
View File
@@ -299,7 +299,7 @@ function ActiveFilterChips({ filters, onRemove }: ActiveFilterChipsProps) {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<span className="text-2xs uppercase tracking-[0.10em] text-ink-2"> <span className="text-cap text-ink-2">
{t('audit.filter.active')} {t('audit.filter.active')}
</span> </span>
{filters.map((f) => ( {filters.map((f) => (
@@ -506,7 +506,7 @@ function AuditRow({ row }: { row: AuditEntry }) {
<TableCell colSpan={8}> <TableCell colSpan={8}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 bg-line/30 rounded p-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3 bg-line/30 rounded p-3">
<div> <div>
<div className="text-2xs uppercase tracking-[0.10em] text-ink-2 mb-1"> <div className="text-cap text-ink-2 mb-1">
{t('audit.diff.before')} {t('audit.diff.before')}
</div> </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-2xs font-mono whitespace-pre-wrap break-all bg-white border border-line rounded p-2 max-h-60 overflow-auto">
@@ -516,7 +516,7 @@ function AuditRow({ row }: { row: AuditEntry }) {
</pre> </pre>
</div> </div>
<div> <div>
<div className="text-2xs uppercase tracking-[0.10em] text-ink-2 mb-1"> <div className="text-cap text-ink-2 mb-1">
{t('audit.diff.after')} {t('audit.diff.after')}
</div> </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-2xs font-mono whitespace-pre-wrap break-all bg-white border border-line rounded p-2 max-h-60 overflow-auto">
@@ -1390,7 +1390,7 @@ function FieldsTabContent({ detail }: { detail: { schemaJson: import('@/api/clie
return ( return (
<div className="rounded-lg border border-line overflow-hidden"> <div className="rounded-lg border border-line overflow-hidden">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead className="bg-surface-2 text-2xs font-display uppercase tracking-[0.10em] text-mute"> <thead className="text-cap bg-surface-2 font-display text-mute">
<tr> <tr>
<th className="text-left px-3 py-2">name</th> <th className="text-left px-3 py-2">name</th>
<th className="text-left px-3 py-2">type</th> <th className="text-left px-3 py-2">type</th>
@@ -253,7 +253,7 @@ function DictionariesPage() {
/> />
</div> </div>
<span className="text-2xs uppercase tracking-[0.10em] text-mute whitespace-nowrap font-mono"> <span className="text-cap text-mute whitespace-nowrap font-mono">
{filtered.length}/{data.length} {filtered.length}/{data.length}
</span> </span>
@@ -372,7 +372,7 @@ function DictionariesPage() {
<span className="text-sm uppercase tracking-[0.10em] text-accent font-mono font-medium"> <span className="text-sm uppercase tracking-[0.10em] text-accent font-mono font-medium">
{bundle} {bundle}
</span> </span>
<span className="text-2xs uppercase tracking-[0.10em] text-mute"> <span className="text-cap text-mute">
· {items.length} {t('dict.list.records.short')} · {items.length} {t('dict.list.records.short')}
</span> </span>
</h2> </h2>
@@ -482,7 +482,7 @@ const DictCard = ({ d, t }: { d: DictionaryDefinition; t: TFunc }) => {
)} )}
{hasFkRow && ( {hasFkRow && (
<div className="flex flex-wrap items-center gap-1.5 mt-2.5"> <div className="flex flex-wrap items-center gap-1.5 mt-2.5">
<span className="text-2xs font-display uppercase tracking-[0.10em] text-mute mr-1"> <span className="text-cap font-display text-mute mr-1">
{t('dict.list.fk.usedBy', { count: refBy.length })} {t('dict.list.fk.usedBy', { count: refBy.length })}
</span> </span>
{refBy.slice(0, MAX_REFBY_CHIPS).map((dep) => ( {refBy.slice(0, MAX_REFBY_CHIPS).map((dep) => (
@@ -509,7 +509,7 @@ const DictCard = ({ d, t }: { d: DictionaryDefinition; t: TFunc }) => {
v{d.schemaVersion} v{d.schemaVersion}
</span> </span>
{typeof d.recordCount === 'number' && ( {typeof d.recordCount === 'number' && (
<span className="text-2xs font-display uppercase tracking-[0.10em] text-mute/80 whitespace-nowrap"> <span className="text-cap font-display text-mute/80 whitespace-nowrap">
{t('dict.list.recordCount', { count: d.recordCount })} {t('dict.list.recordCount', { count: d.recordCount })}
</span> </span>
)} )}
+1 -1
View File
@@ -165,7 +165,7 @@ function StatCard({ label, value, tone }: StatCardProps) {
: 'text-accent' : 'text-accent'
return ( return (
<div className="bg-white border border-line rounded-lg p-4"> <div className="bg-white border border-line rounded-lg p-4">
<div className="text-2xs uppercase tracking-[0.10em] text-ink-2 mb-1"> <div className="text-cap text-ink-2 mb-1">
{label} {label}
</div> </div>
<div className={`text-2xl font-sans tabular-nums ${colour}`}>{value}</div> <div className={`text-2xl font-sans tabular-nums ${colour}`}>{value}</div>
+3 -3
View File
@@ -423,7 +423,7 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div> <div>
<p className="text-2xs uppercase tracking-[0.10em] text-mute mb-1"> <p className="text-cap text-mute mb-1">
{t('reviews.drawer.live')} {t('reviews.drawer.live')}
</p> </p>
{liveQ.isLoading && <LoadingBlock size="sm" label={t('loading')} />} {liveQ.isLoading && <LoadingBlock size="sm" label={t('loading')} />}
@@ -439,7 +439,7 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
)} )}
</div> </div>
<div> <div>
<p className="text-2xs uppercase tracking-[0.10em] text-mute mb-1"> <p className="text-cap text-mute mb-1">
{t('reviews.drawer.proposed')} {t('reviews.drawer.proposed')}
</p> </p>
{draft.operation === 'CLOSE' ? ( {draft.operation === 'CLOSE' ? (
@@ -463,7 +463,7 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
<div className="border-t border-line pt-3 space-y-2"> <div className="border-t border-line pt-3 space-y-2">
<label <label
htmlFor="review-comment" htmlFor="review-comment"
className="text-2xs uppercase tracking-[0.10em] text-ink-2" className="text-cap text-ink-2"
> >
{t('reviews.drawer.comment')} {t('reviews.drawer.comment')}
</label> </label>
+4 -4
View File
@@ -169,7 +169,7 @@ function WebhookDetailPage() {
{data.scopeFilter.map((s) => ( {data.scopeFilter.map((s) => (
<span <span
key={s} key={s}
className="inline-flex items-center gap-1 text-2xs uppercase tracking-[0.10em]" className="text-cap inline-flex items-center gap-1"
> >
<span className={`inline-block size-2 rounded-full ${SCOPE_DOT[s]}`} aria-hidden /> <span className={`inline-block size-2 rounded-full ${SCOPE_DOT[s]}`} aria-hidden />
{s} {s}
@@ -394,7 +394,7 @@ function TestResultAlert({
<div className="space-y-1 text-sm"> <div className="space-y-1 text-sm">
{result.httpStatus != null && ( {result.httpStatus != null && (
<div> <div>
<span className="text-2xs uppercase tracking-[0.10em] text-mute mr-2"> <span className="text-cap text-mute mr-2">
HTTP HTTP
</span> </span>
<span className="font-mono">{result.httpStatus}</span> <span className="font-mono">{result.httpStatus}</span>
@@ -402,7 +402,7 @@ function TestResultAlert({
)} )}
{result.latencyMs != null && ( {result.latencyMs != null && (
<div> <div>
<span className="text-2xs uppercase tracking-[0.10em] text-mute mr-2"> <span className="text-cap text-mute mr-2">
{t('webhooks.test.latency')} {t('webhooks.test.latency')}
</span> </span>
<span className="font-mono">{result.latencyMs} ms</span> <span className="font-mono">{result.latencyMs} ms</span>
@@ -421,7 +421,7 @@ function TestResultAlert({
function InfoRow({ label, children }: { label: string; children: React.ReactNode }) { function InfoRow({ label, children }: { label: string; children: React.ReactNode }) {
return ( return (
<div> <div>
<div className="text-2xs uppercase tracking-[0.10em] text-mute mb-1"> <div className="text-cap text-mute mb-1">
{label} {label}
</div> </div>
<div>{children}</div> <div>{children}</div>
@@ -182,7 +182,7 @@ function ScopeChips({ scopes }: { scopes?: DataScope[] | null }) {
{scopes.map((s) => ( {scopes.map((s) => (
<span <span
key={s} key={s}
className="inline-flex items-center gap-1 text-2xs uppercase tracking-[0.10em]" className="text-cap inline-flex items-center gap-1"
title={s} title={s}
> >
<span className={`inline-block size-2 rounded-full ${SCOPE_DOT[s]}`} aria-hidden /> <span className={`inline-block size-2 rounded-full ${SCOPE_DOT[s]}`} aria-hidden />
+25
View File
@@ -146,11 +146,36 @@
--font-mono: 'JetBrains Mono', 'SF Mono', monospace; --font-mono: 'JetBrains Mono', 'SF Mono', monospace;
--font-display: 'Tektur', 'Inter', sans-serif; --font-display: 'Tektur', 'Inter', sans-serif;
/* Font sizes per design_handoff_ordinis_mdm/README.md spec.
* Override Tailwind defaults так чтобы существующие utility classes
* автоматически соответствовали handoff:
* text-sm : 13px (workhorse body, buttons, tabs)
* text-xs : 12px (default, без изменений secondary meta)
* text-2xs : 11px (custom IDs, dates, table mono meta)
* Tektur caps 10.5px используй .cap или text-cap utility (отдельно).
* Table cells 12.5px / page titles 17px / modal titles 22px explicit
* через text-[Npx] arbitrary value по месту. */
--text-sm: 13px;
--text-sm--line-height: 1.4;
--text-2xs: 11px;
--text-2xs--line-height: 1.35;
--radius-sm: 4px; --radius-sm: 4px;
--radius-md: 6px; --radius-md: 6px;
--radius-lg: 8px; --radius-lg: 8px;
} }
/* Tektur caps utility handoff 10.5px uppercase tracking 0.10em.
* Codemod target: replace `text-2xs uppercase tracking-[0.10em]` `text-cap`. */
@utility text-cap {
font-family: var(--font-display);
font-size: 10.5px;
line-height: 1;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.10em;
}
html, body, #root { html, body, #root {
height: 100%; height: 100%;
} }
+1 -1
View File
@@ -56,7 +56,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
{label && ( {label && (
<label <label
htmlFor={inputId} htmlFor={inputId}
className="text-2xs font-medium font-display uppercase tracking-[0.10em] text-mute leading-none" className="text-cap font-medium font-display text-mute leading-none"
> >
{label} {label}
{required && <span className="text-pink ml-0.5">*</span>} {required && <span className="text-pink ml-0.5">*</span>}
@@ -26,7 +26,7 @@ export function PageHeader({
return ( return (
<div className={cn('flex flex-col gap-1 mb-4', className)} {...props}> <div className={cn('flex flex-col gap-1 mb-4', className)} {...props}>
{breadcrumb && ( {breadcrumb && (
<div className="text-2xs font-display uppercase tracking-[0.10em] text-mute leading-tight mb-1"> <div className="text-cap font-display text-mute leading-tight mb-1">
{breadcrumb} {breadcrumb}
</div> </div>
)} )}
@@ -55,7 +55,7 @@ export const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
{label && ( {label && (
<label <label
htmlFor={inputId} htmlFor={inputId}
className="text-2xs font-medium font-display uppercase tracking-[0.10em] text-mute leading-none" className="text-cap font-medium font-display text-mute leading-none"
> >
{label} {label}
{required && <span className="text-pink ml-0.5">*</span>} {required && <span className="text-pink ml-0.5">*</span>}