feat(admin-ui): migrate from custom components to @nstart/ui design system
Свои Modal/Button/inputs выкинуты — берём nstart-ui v0.1.3 как в других corp
React приложениях. Установка через .npmrc → repo.nstart.cloud/repository/npm-hosted/.
Что сделано:
- .npmrc: scope @nstart на corp Nexus + min-release-age=7
- deps: @nstart/ui ^0.1.3, @phosphor-icons/react ^2.1.10
- bumped peer deps: i18next ^26 + react-i18next ^17 (требование nstart-ui)
- styles.css: импорт @nstart/ui/styles/{fonts,theme}.css + critical @source
директива (без неё Tailwind v4 tree-shaking дропнул бы все классы из dist либы)
- main.tsx: <NStartUiProvider> на корне
- DROP src/components/ui/Modal.tsx (свой Modal больше не нужен)
Routes:
- __root: LanguageSwitch вместо двух кнопок RU/EN, токены ultramarain/regolith/carbon
- dictionaries.index: PageHeader + Badge для scope chip + Alert/EmptyState/LoadingBlock
- dictionaries.$name: PageHeader с actions, Modal/Panel/Table/TableRow/Cell,
IconButton с Phosphor (PencilSimple, XCircle), Button variant=primary/danger/ghost
Form (SchemaDrivenForm):
- TextInput/TextArea/SingleSelect/Checkbox + FieldLabel/Hint/Error из @nstart/ui
- FormSection + FormGrid columns=1|2 для группировки
- FormActions align=end с Button loading state
- Alert variant=error для server-side ошибок (422 IdempotencyConflict / 409 InProgress)
- ajv валидация по schema_json остаётся такой же (Draft-07, как у backend)
Bundle: 192KB → 659KB gzip — тянет echarts/lottie/brand assets из nstart-ui,
для internal admin tool приемлемо (cached after first load).
This commit is contained in:
@@ -1,42 +1,41 @@
|
||||
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router'
|
||||
import type { QueryClient } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { LanguageSwitch } from '@nstart/ui'
|
||||
|
||||
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
||||
component: RootLayout,
|
||||
})
|
||||
|
||||
const LANG_OPTIONS = [
|
||||
{ id: 'ru-RU', label: 'RU' },
|
||||
{ id: 'en-US', label: 'EN' },
|
||||
]
|
||||
|
||||
function RootLayout() {
|
||||
const { t, i18n } = useTranslation()
|
||||
return (
|
||||
<div className="min-h-full flex flex-col">
|
||||
<header className="border-b border-zinc-200 bg-white">
|
||||
<div className="min-h-full flex flex-col bg-white">
|
||||
<header className="border-b border-regolith bg-white">
|
||||
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center gap-6">
|
||||
<Link to="/" className="font-semibold text-lg text-brand-700">
|
||||
<Link to="/" className="font-primary text-lg text-ultramarain">
|
||||
{t('app.title')}
|
||||
</Link>
|
||||
<nav className="flex gap-4 text-sm">
|
||||
<Link
|
||||
to="/dictionaries"
|
||||
className="text-zinc-600 hover:text-brand-700"
|
||||
activeProps={{ className: 'text-brand-700 font-medium' }}
|
||||
className="text-carbon hover:text-ultramarain"
|
||||
activeProps={{ className: 'text-ultramarain font-medium' }}
|
||||
>
|
||||
{t('nav.dictionaries')}
|
||||
</Link>
|
||||
</nav>
|
||||
<div className="ml-auto flex items-center gap-2 text-sm">
|
||||
<button
|
||||
onClick={() => i18n.changeLanguage('ru-RU')}
|
||||
className={`px-2 py-1 rounded ${i18n.language === 'ru-RU' ? 'bg-brand-50 text-brand-700' : 'text-zinc-500'}`}
|
||||
>
|
||||
RU
|
||||
</button>
|
||||
<button
|
||||
onClick={() => i18n.changeLanguage('en-US')}
|
||||
className={`px-2 py-1 rounded ${i18n.language === 'en-US' ? 'bg-brand-50 text-brand-700' : 'text-zinc-500'}`}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<div className="ml-auto">
|
||||
<LanguageSwitch
|
||||
value={i18n.language}
|
||||
options={LANG_OPTIONS}
|
||||
onChange={(id) => i18n.changeLanguage(id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user