Merge branch 'fix/catalog-toolbar-compact' into 'main'
fix(admin-ui): compact catalog toolbar (single row) See merge request 2-6/2-6-4/terravault/ordinis!19
This commit is contained in:
@@ -225,6 +225,10 @@ i18n
|
||||
'dict.list.section.PUBLIC': 'Публичные',
|
||||
'dict.list.section.INTERNAL': 'Внутренние',
|
||||
'dict.list.section.RESTRICTED': 'Ограниченные',
|
||||
'dict.list.section.PUBLIC.short': 'PUB',
|
||||
'dict.list.section.INTERNAL.short': 'INT',
|
||||
'dict.list.section.RESTRICTED.short': 'RES',
|
||||
'dict.list.deps.short': 'СО СВЯЗЯМИ',
|
||||
// Catalog v2 (URL-driven filters + sort + density)
|
||||
'dict.list.heading': 'Каталог справочников',
|
||||
'dict.list.filter.scope': 'Фильтр по области',
|
||||
@@ -695,6 +699,10 @@ i18n
|
||||
'dict.list.section.PUBLIC': 'Public',
|
||||
'dict.list.section.INTERNAL': 'Internal',
|
||||
'dict.list.section.RESTRICTED': 'Restricted',
|
||||
'dict.list.section.PUBLIC.short': 'PUB',
|
||||
'dict.list.section.INTERNAL.short': 'INT',
|
||||
'dict.list.section.RESTRICTED.short': 'RES',
|
||||
'dict.list.deps.short': 'WITH LINKS',
|
||||
// Catalog v2
|
||||
'dict.list.heading': 'Dictionary catalog',
|
||||
'dict.list.filter.scope': 'Scope filter',
|
||||
|
||||
@@ -213,9 +213,11 @@ function DictionariesPage() {
|
||||
actions={createButton}
|
||||
/>
|
||||
|
||||
{/* Toolbar: search + count + scope chips + "deps only" */}
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="flex-1 min-w-[300px] max-w-md">
|
||||
{/* Compact toolbar: всё в одной строке (wraps на narrow viewport).
|
||||
Tradeoff: scope/bundle chips меньше padding'а + краткие labels.
|
||||
UPPERCASE labels оставлены для consistency с brand voice (Tektur-like). */}
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<div className="flex-1 min-w-[240px] max-w-sm">
|
||||
<SearchInput
|
||||
value={search.q ?? ''}
|
||||
onChange={(e) => setSearch({ q: e.target.value })}
|
||||
@@ -224,14 +226,15 @@ function DictionariesPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="text-2xs uppercase tracking-label text-carbon/70 whitespace-nowrap">
|
||||
{t('dict.list.found', { shown: filtered.length, total: data.length })}
|
||||
<span className="text-2xs uppercase tracking-label text-carbon/60 whitespace-nowrap font-mono">
|
||||
{filtered.length}/{data.length}
|
||||
</span>
|
||||
|
||||
{/* Scope chips: компактные, dot + count, full label по hover'у через title */}
|
||||
<div
|
||||
role="group"
|
||||
aria-label={t('dict.list.filter.scope')}
|
||||
className="flex items-center gap-1.5 flex-wrap"
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{SCOPE_ORDER.map((scope) => {
|
||||
const selected = scopeFilter.has(scope)
|
||||
@@ -242,76 +245,86 @@ function DictionariesPage() {
|
||||
type="button"
|
||||
onClick={() => toggleScope(scope)}
|
||||
aria-pressed={selected}
|
||||
className={`px-3 py-1.5 rounded-full text-2xs uppercase tracking-label flex items-center gap-1.5 border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 ${
|
||||
title={t(`dict.list.section.${scope}`)}
|
||||
className={`px-2 py-1 rounded-full text-2xs uppercase tracking-label flex items-center gap-1 border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 font-mono ${
|
||||
selected
|
||||
? 'border-ultramarain bg-orbit/40 text-ultramarain'
|
||||
: 'border-regolith hover:border-ultramarain/60 text-carbon'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block size-2 rounded-full ${SCOPE_DOT[scope]}`}
|
||||
className={`inline-block size-1.5 rounded-full ${SCOPE_DOT[scope]}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{t(`dict.list.section.${scope}`)} · {count}
|
||||
{t(`dict.list.section.${scope}.short`)}
|
||||
<span className="text-carbon/60 ml-0.5">{count}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Vertical divider */}
|
||||
<span className="h-5 w-px bg-regolith" aria-hidden="true" />
|
||||
|
||||
{/* Bundle filter — chips inline */}
|
||||
<div
|
||||
role="group"
|
||||
aria-label="Bundle"
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBundle(undefined)}
|
||||
aria-pressed={!bundleFilter}
|
||||
className={`px-2 py-1 rounded-sm text-2xs uppercase tracking-label border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 ${
|
||||
!bundleFilter
|
||||
? 'border-ultramarain bg-orbit/40 text-ultramarain'
|
||||
: 'border-regolith hover:border-ultramarain/60 text-carbon'
|
||||
}`}
|
||||
>
|
||||
{t('dict.list.bundle.all')}
|
||||
</button>
|
||||
{Array.from(bundleCounts.entries())
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([bundle, count]) => {
|
||||
const selected = bundleFilter === bundle
|
||||
return (
|
||||
<button
|
||||
key={bundle}
|
||||
type="button"
|
||||
onClick={() => setBundle(selected ? undefined : bundle)}
|
||||
aria-pressed={selected}
|
||||
className={`px-2 py-1 rounded-sm text-2xs uppercase tracking-label flex items-center gap-1 border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 font-mono ${
|
||||
selected
|
||||
? 'border-ultramarain bg-orbit/40 text-ultramarain'
|
||||
: 'border-regolith hover:border-ultramarain/60 text-carbon'
|
||||
}`}
|
||||
>
|
||||
{bundle}
|
||||
<span className="text-carbon/60">{count}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Vertical divider + deps toggle */}
|
||||
<span className="h-5 w-px bg-regolith" aria-hidden="true" />
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearch({ deps: withDepsOnly ? undefined : '1' })}
|
||||
aria-pressed={withDepsOnly}
|
||||
className={`px-3 py-1.5 rounded-sm text-2xs uppercase tracking-label border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 ${
|
||||
title={t('dict.list.deps.only')}
|
||||
className={`px-2 py-1 rounded-sm text-2xs uppercase tracking-label border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 ${
|
||||
withDepsOnly
|
||||
? 'border-ultramarain bg-orbit/40 text-ultramarain'
|
||||
: 'border-regolith hover:border-ultramarain/60 text-carbon'
|
||||
}`}
|
||||
>
|
||||
{t('dict.list.deps.only')}
|
||||
{t('dict.list.deps.short')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Bundle filter row */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-2xs uppercase tracking-label text-carbon/70 mr-1 font-mono">
|
||||
BUNDLE
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBundle(undefined)}
|
||||
aria-pressed={!bundleFilter}
|
||||
className={`px-3 py-1 rounded-sm text-2xs uppercase tracking-label border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 ${
|
||||
!bundleFilter
|
||||
? 'border-ultramarain bg-orbit/40 text-ultramarain'
|
||||
: 'border-regolith hover:border-ultramarain/60 text-carbon'
|
||||
}`}
|
||||
>
|
||||
{t('dict.list.bundle.all')}
|
||||
</button>
|
||||
{Array.from(bundleCounts.entries())
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([bundle, count]) => {
|
||||
const selected = bundleFilter === bundle
|
||||
return (
|
||||
<button
|
||||
key={bundle}
|
||||
type="button"
|
||||
onClick={() => setBundle(selected ? undefined : bundle)}
|
||||
aria-pressed={selected}
|
||||
className={`px-3 py-1 rounded-sm text-2xs uppercase tracking-label border transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 font-mono ${
|
||||
selected
|
||||
? 'border-ultramarain bg-orbit/40 text-ultramarain'
|
||||
: 'border-regolith hover:border-ultramarain/60 text-carbon'
|
||||
}`}
|
||||
>
|
||||
{bundle}
|
||||
<span className="ml-1.5 text-carbon/60">{count}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Empty state */}
|
||||
{filtered.length === 0 ? (
|
||||
<div className="border border-regolith rounded-lg p-12 text-center bg-orbit/10">
|
||||
|
||||
Reference in New Issue
Block a user