feat(catalog): scope-colored row stripes + filter pills + chips

Per redesign prototype screenshot: каждая row имеет 3px цветной strip слева
по scope + filter chips заполняются scope-color когда active.

- DictRow: добавлен absolute-positioned `<span>` шириной 3px на левом краю
  первой td (relative wrapper), background = SCOPE_DOT (PUBLIC=accent,
  INTERNAL=warn, RESTRICTED=pink). pl-1 на контенте чтобы не наезжал.
- Row bg tint: для INTERNAL/RESTRICTED rows — subtle SCOPE_BG_TINT/30,
  PUBLIC = plain white (default).
- SCOPE col chip: использует SCOPE_BG_TINT + SCOPE_TEXT вместо generic
  Badge info — теперь PUBLIC chip orange, INTERNAL amber, RESTRICTED pink.
- Toolbar scope filter pills: active state = SCOPE_BG_TINT + SCOPE_TEXT +
  border-transparent (filled in scope color). Inactive = outline.
This commit is contained in:
Zimin A.N.
2026-05-11 19:35:43 +03:00
parent 180a5920fe
commit bc737820d6
@@ -23,7 +23,7 @@ import { dictionaryDependentsQuery, useDictionaries, useDictionaryDependents } f
import type { DataScope, DictionaryDefinition, SchemaDependent } from '@/api/client' import type { DataScope, DictionaryDefinition, SchemaDependent } from '@/api/client'
import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog' import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog'
import { useCanMutate } from '@/auth/useCanMutate' import { useCanMutate } from '@/auth/useCanMutate'
import { SCOPE_DOT, SCOPE_ORDER } from '@/lib/scope-style' import { SCOPE_BG_TINT, SCOPE_DOT, SCOPE_ORDER, SCOPE_TEXT } from '@/lib/scope-style'
// ===== Search params ===== // ===== Search params =====
@@ -248,7 +248,7 @@ function DictionariesPage() {
title={t(`dict.list.section.${scope}`)} title={t(`dict.list.section.${scope}`)}
className={`text-cap tracking-[0.14em] uppercase font-semibold px-3 py-1.5 rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-accent/40 ${ className={`text-cap tracking-[0.14em] uppercase font-semibold px-3 py-1.5 rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-accent/40 ${
selected selected
? 'border-ink bg-surface text-ink ring-1 ring-ink' ? `${SCOPE_BG_TINT[scope]} ${SCOPE_TEXT[scope]} border-transparent`
: 'border-line bg-surface text-ink-2 hover:border-ink-2 hover:bg-surface-2' : 'border-line bg-surface text-ink-2 hover:border-ink-2 hover:bg-surface-2'
}`} }`}
> >
@@ -473,18 +473,28 @@ function DictRow({ d, t }: { d: DictionaryDefinition; t: TFunc }) {
} }
} }
// Per redesign prototype: row левый край имеет 4px stripe в scope color +
// INTERNAL/RESTRICTED rows получают subtle bg tint для visual scope grouping.
// PUBLIC = plain white (default — самый common scope не нужно подкрашивать).
const rowTint = d.scope === 'PUBLIC' ? '' : `${SCOPE_BG_TINT[d.scope]}/30`
return ( return (
<tr <tr
role="link" role="link"
tabIndex={0} tabIndex={0}
onClick={handleClick} onClick={handleClick}
onKeyDown={handleKey} onKeyDown={handleKey}
className="border-b border-line-2 last:border-b-0 cursor-pointer hover:bg-surface-2/40 focus-visible:outline-none focus-visible:bg-accent-bg/30 transition-colors" className={`relative border-b border-line-2 last:border-b-0 cursor-pointer hover:bg-surface-2/40 focus-visible:outline-none focus-visible:bg-accent-bg/30 transition-colors ${rowTint}`}
> >
{/* name + subtitle (description short) */} {/* name + subtitle (description short) + scope-color left stripe */}
<td className="px-3 py-2 align-top min-w-0"> <td className="relative px-3 py-2 align-top min-w-0">
<div className="flex items-center gap-2"> {/* 3px scope stripe — absolutely positioned на левом краю row.
<span className={`inline-block size-1.5 rounded-full shrink-0 ${SCOPE_DOT[d.scope]}`} aria-hidden /> * td имеет relative чтобы before работало в table context. */}
<span
aria-hidden
className={`absolute left-0 top-0 bottom-0 w-[3px] ${SCOPE_DOT[d.scope]}`}
/>
<div className="flex items-center gap-2 pl-1">
<span className="text-body font-medium text-ink truncate"> <span className="text-body font-medium text-ink truncate">
{d.displayName ?? d.name} {d.displayName ?? d.name}
</span> </span>
@@ -493,7 +503,7 @@ function DictRow({ d, t }: { d: DictionaryDefinition; t: TFunc }) {
)} )}
</div> </div>
{d.description && ( {d.description && (
<p className="text-cell text-mute mt-0.5 truncate max-w-md"> <p className="text-cell text-mute mt-0.5 truncate max-w-md pl-1">
{d.description} {d.description}
</p> </p>
)} )}
@@ -506,9 +516,13 @@ function DictRow({ d, t }: { d: DictionaryDefinition; t: TFunc }) {
<td className="px-3 py-2 hidden lg:table-cell"> <td className="px-3 py-2 hidden lg:table-cell">
<span className="text-cap text-mute">{d.bundle}</span> <span className="text-cap text-mute">{d.bundle}</span>
</td> </td>
{/* scope badge */} {/* scope badge — colored по scope (PUBLIC accent / INTERNAL warn / RESTRICTED pink) */}
<td className="px-3 py-2 hidden sm:table-cell"> <td className="px-3 py-2 hidden sm:table-cell">
<Badge variant="info">{d.scope}</Badge> <span
className={`inline-flex items-center px-2 py-0.5 rounded-sm text-cap tracking-wider font-semibold uppercase ${SCOPE_BG_TINT[d.scope]} ${SCOPE_TEXT[d.scope]}`}
>
{d.scope}
</span>
</td> </td>
{/* records count */} {/* records count */}
<td className="px-3 py-2 text-right tabular-nums text-mono text-ink-2 hidden md:table-cell"> <td className="px-3 py-2 text-right tabular-nums text-mono text-ink-2 hidden md:table-cell">