feat(editor): match redesign prototype layout — TopBar title + tab counts + InfoPanel actions
Per /Users/zimin/Downloads/redesign/screens/03-editor-records.png редизайн
prototype editor layout отличается от current MVP:
1. TopBar absorbs editor title + version (was в PageHeader)
2. PageHeader удалён — title и breadcrumb теперь только в TopBar
3. Tab labels получили counts (Записи 127, Связи 6, Поля 12)
4. Action buttons распределены per role:
- Time-travel + AOI → InfoPanel bottom (close к data context)
- Schema edit + +Запись → tab toolbar right (close к tab actions)
Changes:
src/components/layout/TopBar.tsx
- Breadcrumb получил subtitle slot — mono '<name> · v<version>'
- useBreadcrumb fetches dict detail для editor route '/dictionaries/$name'
- Render: '← Справочники / Космические аппараты satellites · v1.0.0'
src/api/queries.ts
- useDictionaryDetail принимает name: string | undefined + enabled flag
- Guard для editor breadcrumb когда route не matches dict
src/routes/dictionaries.$name.tsx
- Removed <PageHeader> — title/breadcrumb теперь в TopBar
- EditorInfoPanel получает actions prop:
onTimeTravel + timeTravelActive + onAoi + aoiActive
- EditorTabBar получает counts (per-tab badge) + actions slot
(right-aligned Schema + +Запись buttons)
- extractOutgoingFkCount helper для relations count
src/components/editor/EditorInfoPanel.tsx
- actions prop с Time-travel + AOI buttons
- Rendered в panel выше Relations section, между metadata и FK list
- aria-pressed когда active (time-travel или AOI с filter)
src/components/editor/EditorTabBar (inline in route)
- counts prop: Partial<Record<EditorTab, number>>
- actions prop: ReactNode для right-aligned cluster
- inline count badge — text-mono text-mute next to label
Tests: 116 pass. TS strict clean.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, useLocation, useNavigate } from '@tanstack/react-router'
|
||||
import { useDictionaryDetail } from '@/api/queries'
|
||||
import { LanguageSwitch } from '@/ui'
|
||||
import { AuthBadge } from '@/auth/AuthBadge'
|
||||
import { ThemeSwitch } from '@/components/layout/ThemeSwitch'
|
||||
@@ -72,7 +73,9 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) {
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Breadcrumb — left */}
|
||||
{/* Breadcrumb — left. Editor route ('/dictionaries/$name') получает
|
||||
* augmented breadcrumb с displayName + version (per redesign prototype).
|
||||
* Остальные routes: regular path-based breadcrumb. */}
|
||||
<nav className="flex items-center gap-1.5 text-body min-w-0">
|
||||
{breadcrumb.map((crumb, i) => {
|
||||
const isLast = i === breadcrumb.length - 1
|
||||
@@ -91,6 +94,10 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) {
|
||||
{crumb.label}
|
||||
</span>
|
||||
)}
|
||||
{/* Editor route: inline mono "<name> · v<version>" after title */}
|
||||
{isLast && crumb.subtitle && (
|
||||
<span className="text-mono text-mute shrink-0 ml-2">{crumb.subtitle}</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
@@ -134,14 +141,22 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) {
|
||||
)
|
||||
}
|
||||
|
||||
type Crumb = { label: string; to?: string; subtitle?: string }
|
||||
|
||||
/**
|
||||
* Breadcrumb из current route — простой mapping для типичных страниц.
|
||||
* Breadcrumb из current route. Для editor route ('/dictionaries/$name')
|
||||
* добавляет displayName + version (mono subtitle) per redesign prototype.
|
||||
*/
|
||||
function useBreadcrumb(): { label: string; to?: string }[] {
|
||||
function useBreadcrumb(): Crumb[] {
|
||||
const { t } = useTranslation()
|
||||
const location = useLocation()
|
||||
const path = location.pathname
|
||||
|
||||
// Editor route: fetch dict detail для display name + schema version.
|
||||
const editorMatch = path.match(/^\/dictionaries\/([^/]+)/)
|
||||
const editorName = editorMatch ? decodeURIComponent(editorMatch[1]) : undefined
|
||||
const editorDict = useDictionaryDetail(editorName)
|
||||
|
||||
if (path === '/' || path === '') {
|
||||
return [{ label: t('nav.home') }]
|
||||
}
|
||||
@@ -161,12 +176,20 @@ function useBreadcrumb(): { label: string; to?: string }[] {
|
||||
}
|
||||
|
||||
const rootLabel = ROOT_LABELS[first] ?? first
|
||||
const crumbs: { label: string; to?: string }[] = [
|
||||
const crumbs: Crumb[] = [
|
||||
{ label: rootLabel, to: segments.length === 1 ? undefined : `/${first}` },
|
||||
]
|
||||
|
||||
if (segments.length >= 2) {
|
||||
crumbs.push({ label: decodeURIComponent(segments[1]) })
|
||||
// Editor route enrichment: displayName + version
|
||||
if (editorName && editorDict.data) {
|
||||
crumbs.push({
|
||||
label: editorDict.data.displayName ?? editorDict.data.name,
|
||||
subtitle: `${editorDict.data.name} · v${editorDict.data.schemaVersion}`,
|
||||
})
|
||||
} else {
|
||||
crumbs.push({ label: decodeURIComponent(segments[1]) })
|
||||
}
|
||||
}
|
||||
|
||||
return crumbs
|
||||
|
||||
Reference in New Issue
Block a user