feat(admin-ui): scope accent on dictionary detail page
Перенесли scope-визуальные константы (SCOPE_ORDER, SCOPE_DOT, SCOPE_ACCENT) в `lib/scope-style.ts` для переиспользования. На detail странице (`dictionaries/$name`) добавили top accent strip 4px цветом scope + цветной чип в breadcrumb рядом с back-link. Теперь при переходе из списка в карточку scope сразу виден без чтения badge в таблице.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import type { DataScope } from '@/api/client'
|
||||
|
||||
/**
|
||||
* Visual ordering: PUBLIC → INTERNAL → RESTRICTED.
|
||||
* Reads as escalating sensitivity, matches semaphore mental model.
|
||||
*/
|
||||
export const SCOPE_ORDER: ReadonlyArray<DataScope> = [
|
||||
'PUBLIC',
|
||||
'INTERNAL',
|
||||
'RESTRICTED',
|
||||
]
|
||||
|
||||
/**
|
||||
* Solid background classes for ●-shaped scope indicators (size-2 dots,
|
||||
* section-header bullets).
|
||||
*/
|
||||
export const SCOPE_DOT: Record<DataScope, string> = {
|
||||
PUBLIC: 'bg-emerald-500',
|
||||
INTERNAL: 'bg-amber-500',
|
||||
RESTRICTED: 'bg-rose-500',
|
||||
}
|
||||
|
||||
/**
|
||||
* `before:` background classes used for the 4px left-accent stripe on
|
||||
* dictionary cards. Pair with `relative` + `before:absolute before:left-0
|
||||
* before:top-0 before:bottom-0 before:w-1 before:rounded-l-lg`.
|
||||
*/
|
||||
export const SCOPE_ACCENT: Record<DataScope, string> = {
|
||||
PUBLIC: 'before:bg-emerald-500',
|
||||
INTERNAL: 'before:bg-amber-500',
|
||||
RESTRICTED: 'before:bg-rose-500',
|
||||
}
|
||||
|
||||
/**
|
||||
* Solid border-top classes for page-level scope banner stripes (used on
|
||||
* dictionary detail page).
|
||||
*/
|
||||
export const SCOPE_BORDER_TOP: Record<DataScope, string> = {
|
||||
PUBLIC: 'border-t-emerald-500',
|
||||
INTERNAL: 'border-t-amber-500',
|
||||
RESTRICTED: 'border-t-rose-500',
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { SchemaDrivenForm } from '@/components/form/SchemaDrivenForm'
|
||||
import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog'
|
||||
import { RecordHistoryDrawer } from '@/components/record/RecordHistoryDrawer'
|
||||
import { nowIsoLocal } from '@/lib/dates'
|
||||
import { SCOPE_BORDER_TOP, SCOPE_DOT } from '@/lib/scope-style'
|
||||
|
||||
export const Route = createFileRoute('/dictionaries/$name')({
|
||||
component: DictionaryDetail,
|
||||
@@ -87,14 +88,33 @@ function DictionaryDetail() {
|
||||
const serverError = serverErrorMessage(activeMutation?.error)
|
||||
|
||||
const totalRecords = recordsResult.data?.length ?? 0
|
||||
const scope = detailQuery.data?.scope
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{scope && (
|
||||
<div
|
||||
className={`-mt-6 -mx-4 sm:-mx-6 lg:-mx-8 mb-2 h-1 border-t-4 ${SCOPE_BORDER_TOP[scope]}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<PageHeader
|
||||
breadcrumb={
|
||||
<Link to="/dictionaries" className="text-sm text-carbon/70 hover:text-ultramarain">
|
||||
← {t('nav.dictionaries')}
|
||||
</Link>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<Link to="/dictionaries" className="text-carbon/70 hover:text-ultramarain">
|
||||
← {t('nav.dictionaries')}
|
||||
</Link>
|
||||
{scope && (
|
||||
<span className="flex items-center gap-1.5 text-carbon/70">
|
||||
<span className="text-carbon/40">·</span>
|
||||
<span
|
||||
className={`inline-block size-2 rounded-full ${SCOPE_DOT[scope]}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="font-medium">{t(`dict.list.section.${scope}`)}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
title={detailQuery.data?.displayName ?? name}
|
||||
description={
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PlusIcon } from '@phosphor-icons/react'
|
||||
import { useDictionaries } from '@/api/queries'
|
||||
import type { DataScope, DictionaryDefinition } from '@/api/client'
|
||||
import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog'
|
||||
import { SCOPE_ACCENT, SCOPE_DOT, SCOPE_ORDER } from '@/lib/scope-style'
|
||||
|
||||
export const Route = createFileRoute('/dictionaries/')({
|
||||
component: DictionariesPage,
|
||||
@@ -27,20 +28,6 @@ const matchesQuery = (d: DictionaryDefinition, q: string): boolean => {
|
||||
return haystack.includes(q)
|
||||
}
|
||||
|
||||
const SCOPE_ORDER: ReadonlyArray<DataScope> = ['PUBLIC', 'INTERNAL', 'RESTRICTED']
|
||||
|
||||
const SCOPE_DOT: Record<DataScope, string> = {
|
||||
PUBLIC: 'bg-emerald-500',
|
||||
INTERNAL: 'bg-amber-500',
|
||||
RESTRICTED: 'bg-rose-500',
|
||||
}
|
||||
|
||||
const SCOPE_ACCENT: Record<DataScope, string> = {
|
||||
PUBLIC: 'before:bg-emerald-500',
|
||||
INTERNAL: 'before:bg-amber-500',
|
||||
RESTRICTED: 'before:bg-rose-500',
|
||||
}
|
||||
|
||||
function DictionariesPage() {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
Reference in New Issue
Block a user