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 { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog'
|
||||||
import { RecordHistoryDrawer } from '@/components/record/RecordHistoryDrawer'
|
import { RecordHistoryDrawer } from '@/components/record/RecordHistoryDrawer'
|
||||||
import { nowIsoLocal } from '@/lib/dates'
|
import { nowIsoLocal } from '@/lib/dates'
|
||||||
|
import { SCOPE_BORDER_TOP, SCOPE_DOT } from '@/lib/scope-style'
|
||||||
|
|
||||||
export const Route = createFileRoute('/dictionaries/$name')({
|
export const Route = createFileRoute('/dictionaries/$name')({
|
||||||
component: DictionaryDetail,
|
component: DictionaryDetail,
|
||||||
@@ -87,14 +88,33 @@ function DictionaryDetail() {
|
|||||||
const serverError = serverErrorMessage(activeMutation?.error)
|
const serverError = serverErrorMessage(activeMutation?.error)
|
||||||
|
|
||||||
const totalRecords = recordsResult.data?.length ?? 0
|
const totalRecords = recordsResult.data?.length ?? 0
|
||||||
|
const scope = detailQuery.data?.scope
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<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
|
<PageHeader
|
||||||
breadcrumb={
|
breadcrumb={
|
||||||
<Link to="/dictionaries" className="text-sm text-carbon/70 hover:text-ultramarain">
|
<div className="flex items-center gap-3 text-sm">
|
||||||
← {t('nav.dictionaries')}
|
<Link to="/dictionaries" className="text-carbon/70 hover:text-ultramarain">
|
||||||
</Link>
|
← {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}
|
title={detailQuery.data?.displayName ?? name}
|
||||||
description={
|
description={
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { PlusIcon } from '@phosphor-icons/react'
|
|||||||
import { useDictionaries } from '@/api/queries'
|
import { useDictionaries } from '@/api/queries'
|
||||||
import type { DataScope, DictionaryDefinition } from '@/api/client'
|
import type { DataScope, DictionaryDefinition } from '@/api/client'
|
||||||
import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog'
|
import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog'
|
||||||
|
import { SCOPE_ACCENT, SCOPE_DOT, SCOPE_ORDER } from '@/lib/scope-style'
|
||||||
|
|
||||||
export const Route = createFileRoute('/dictionaries/')({
|
export const Route = createFileRoute('/dictionaries/')({
|
||||||
component: DictionariesPage,
|
component: DictionariesPage,
|
||||||
@@ -27,20 +28,6 @@ const matchesQuery = (d: DictionaryDefinition, q: string): boolean => {
|
|||||||
return haystack.includes(q)
|
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() {
|
function DictionariesPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|||||||
Reference in New Issue
Block a user