From 5007e0f4eb730454c9883942fa3c59c6416f0637 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 11 May 2026 15:17:37 +0300 Subject: [PATCH] feat(ui): drop @nstart/ui dependency (Stage 3.9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Все font cascade bugs из последних MR были вызваны @nstart/ui pollution (Tektur inheritance от Card parent'ов, Onest font в Button, font-primary class overrides). Решили обрезать раньше plan'а. What changed: New primitives (8 файлов в src/ui/components/): - panel.tsx — container с border/surface - field.tsx — FieldLabel/Hint/Error typography helpers - text-input.tsx — Input + label/hint/error composite - table.tsx — Table/TableHeader/Body/Row/HeaderCell/Cell/Empty - date-picker.tsx — native wrapper с label/hint/error - multi-select.tsx — Radix Popover + inline checkboxes - single-select.tsx — native .indeterminate hack - Radix Checkbox принимает checked='indeterminate' declaratively package.json: -@nstart/ui (was 0.1.3) Bundle impact: before: vendor-nstart-ui chunk = 1228 KB after: no nstart chunk, всё в vendor-misc (+~70 KB Radix wrappers) net savings: ~1.15 MB minified, ~440 KB gzip Tests: 116 pass (1 fixed: tab role from 'button' → 'tab', Radix semantically correct). TS strict: clean. Browser-verified (preview): 7 utilities computed correctly, nstart bundle absent. --- ordinis-admin-ui/package.json | 1 - ordinis-admin-ui/pnpm-lock.yaml | 60 ------- .../components/form/SchemaDrivenForm.test.tsx | 4 +- .../src/components/form/SchemaDrivenForm.tsx | 4 +- .../src/components/geo/AoiPickerDialog.tsx | 4 +- .../components/lineage/DictionaryHubView.tsx | 2 +- .../components/record/RecordHistoryDrawer.tsx | 4 +- .../timetravel/TimeTravelPicker.tsx | 2 +- ordinis-admin-ui/src/main.tsx | 12 +- ordinis-admin-ui/src/routes/audit.tsx | 2 +- .../src/routes/dictionaries.$name.tsx | 27 ++- ordinis-admin-ui/src/routes/webhooks.$id.tsx | 4 +- .../src/routes/webhooks.index.tsx | 2 +- ordinis-admin-ui/src/styles.css | 29 +-- .../src/ui/components/checkbox.tsx | 59 ++++++- .../src/ui/components/date-picker.tsx | 95 ++++++++++ ordinis-admin-ui/src/ui/components/field.tsx | 49 ++++++ .../src/ui/components/form-actions.tsx | 16 +- .../src/ui/components/language-switch.tsx | 70 ++++++++ ordinis-admin-ui/src/ui/components/modal.tsx | 10 +- .../src/ui/components/multi-select.tsx | 165 ++++++++++++++++++ ordinis-admin-ui/src/ui/components/panel.tsx | 28 +++ .../src/ui/components/single-select.tsx | 93 ++++++++++ ordinis-admin-ui/src/ui/components/table.tsx | 156 +++++++++++++++++ .../src/ui/components/tabs-simple.tsx | 6 +- .../src/ui/components/text-input.tsx | 52 ++++++ ordinis-admin-ui/src/ui/index.ts | 151 ++++++++++------ 27 files changed, 916 insertions(+), 191 deletions(-) create mode 100644 ordinis-admin-ui/src/ui/components/date-picker.tsx create mode 100644 ordinis-admin-ui/src/ui/components/field.tsx create mode 100644 ordinis-admin-ui/src/ui/components/language-switch.tsx create mode 100644 ordinis-admin-ui/src/ui/components/multi-select.tsx create mode 100644 ordinis-admin-ui/src/ui/components/panel.tsx create mode 100644 ordinis-admin-ui/src/ui/components/single-select.tsx create mode 100644 ordinis-admin-ui/src/ui/components/table.tsx create mode 100644 ordinis-admin-ui/src/ui/components/text-input.tsx diff --git a/ordinis-admin-ui/package.json b/ordinis-admin-ui/package.json index 48bd945..98f9de4 100644 --- a/ordinis-admin-ui/package.json +++ b/ordinis-admin-ui/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.1", - "@nstart/ui": "^0.1.3", "@phosphor-icons/react": "^2.1.10", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", diff --git a/ordinis-admin-ui/pnpm-lock.yaml b/ordinis-admin-ui/pnpm-lock.yaml index 206794d..7683a0f 100644 --- a/ordinis-admin-ui/pnpm-lock.yaml +++ b/ordinis-admin-ui/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@hookform/resolvers': specifier: ^3.9.1 version: 3.10.0(react-hook-form@7.75.0(react@19.2.5)) - '@nstart/ui': - specifier: ^0.1.3 - version: 0.1.3(i18next@26.0.8(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react-i18next@17.0.6(i18next@26.0.8(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3))(react@19.2.5) '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -698,15 +695,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nstart/ui@0.1.3': - resolution: {integrity: sha512-dLhM2Uzvok1UB+86c+WIYz/1uYr/wN3Ch1Ab3ioZbzxBEt0bZQs+QYkj3GXAEzffCjV4zTHen31yJUHzJmpyTQ==} - engines: {node: ^24.0.0 || ^22.0.0} - peerDependencies: - i18next: ^26.0.3 - react: ^19.0.0 - react-dom: ^19.0.0 - react-i18next: ^17.0.2 - '@octokit/auth-token@6.0.0': resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} engines: {node: '>= 20'} @@ -2055,9 +2043,6 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} - echarts@6.0.0: - resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==} - electron-to-chromium@1.5.349: resolution: {integrity: sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==} @@ -2321,9 +2306,6 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - hls.js@1.6.16: - resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==} - hook-std@4.0.0: resolution: {integrity: sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==} engines: {node: '>=20'} @@ -2647,15 +2629,6 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - lottie-react@2.4.1: - resolution: {integrity: sha512-LQrH7jlkigIIv++wIyrOYFLHSKQpEY4zehPicL9bQsrt1rnoKRYCYgpCUe5maqylNtacy58/sQDZTkwMcTRxZw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - lottie-web@5.13.0: - resolution: {integrity: sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==} - loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -3742,9 +3715,6 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zrender@6.0.0: - resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} - snapshots: '@adobe/css-tools@4.4.4': {} @@ -4127,17 +4097,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nstart/ui@0.1.3(i18next@26.0.8(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react-i18next@17.0.6(i18next@26.0.8(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3))(react@19.2.5)': - dependencies: - '@phosphor-icons/react': 2.1.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - echarts: 6.0.0 - hls.js: 1.6.16 - i18next: 26.0.8(typescript@5.9.3) - lottie-react: 2.4.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-i18next: 17.0.6(i18next@26.0.8(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3) - '@octokit/auth-token@6.0.0': {} '@octokit/core@7.0.6': @@ -5481,11 +5440,6 @@ snapshots: dependencies: readable-stream: 2.3.8 - echarts@6.0.0: - dependencies: - tslib: 2.3.0 - zrender: 6.0.0 - electron-to-chromium@1.5.349: {} emoji-regex@8.0.0: {} @@ -5808,8 +5762,6 @@ snapshots: highlight.js@10.7.3: {} - hls.js@1.6.16: {} - hook-std@4.0.0: {} hosted-git-info@7.0.2: @@ -6076,14 +6028,6 @@ snapshots: lodash@4.18.1: {} - lottie-react@2.4.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5): - dependencies: - lottie-web: 5.13.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - lottie-web@5.13.0: {} - loupe@3.2.1: {} lowercase-keys@3.0.0: {} @@ -6976,7 +6920,3 @@ snapshots: yoctocolors@2.1.2: {} zod@3.25.76: {} - - zrender@6.0.0: - dependencies: - tslib: 2.3.0 diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx index 676cc60..7d42d5a 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx @@ -66,8 +66,8 @@ describe('SchemaDrivenForm', () => { onCancel={() => {}} />, ) - // @nstart/ui Tabs использует role="button" не "tab". - await user.click(screen.getByRole('button', { name: /Дополнительно/i })) + // Radix Tabs (Stage 3.9) использует role="tab" — semantically correct. + await user.click(screen.getByRole('tab', { name: /Дополнительно/i })) // description (optional) теперь виден. i18n field.description=Описание. expect(screen.getByText('Описание')).toBeInTheDocument() }) diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx index 78f7da4..d114e3d 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx @@ -775,7 +775,7 @@ const ReferenceSelectField = ({ return records .map((r) => buildOption(r, refField, defaultLocale)) .filter((o): o is SelectOption => o !== null) - .sort((a, b) => a.label.localeCompare(b.label, 'ru')) + .sort((a, b) => String(a.label ?? '').localeCompare(String(b.label ?? ''), 'ru')) }, [records, refField, defaultLocale]) const hint = schema.description ?? `FK на ${ref}` @@ -832,7 +832,7 @@ const ReferenceSelectField = ({ 'w-full appearance-none border rounded-sm pl-3 pr-10 py-2 text-body font-display', 'bg-white text-black transition-colors', 'focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent', - errorMsg ? 'border-mars' : 'border-line hover:border-carbon/40', + errorMsg ? 'border-mars' : 'border-line hover:border-ink/40', isLoading ? 'cursor-wait text-mute' : 'cursor-pointer', ].join(' ')} > diff --git a/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx b/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx index bb3e57d..80f67cb 100644 --- a/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx +++ b/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx @@ -166,7 +166,7 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) => 'px-3 py-1.5 text-cell font-display rounded-sm border transition-colors', active ? 'border-accent bg-accent text-white' - : 'border-line bg-white text-ink hover:border-carbon/40', + : 'border-line bg-white text-ink hover:border-ink/40', ].join(' ')} > {label} @@ -192,7 +192,7 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) => type="button" onClick={handleClear} disabled={!shape} - className="px-3 py-1.5 text-cell font-display rounded-sm border border-line bg-white text-ink hover:border-carbon/40 disabled:opacity-40 disabled:cursor-not-allowed" + className="px-3 py-1.5 text-cell font-display rounded-sm border border-line bg-white text-ink hover:border-ink/40 disabled:opacity-40 disabled:cursor-not-allowed" > {t('aoi.dialog.clear')} diff --git a/ordinis-admin-ui/src/components/lineage/DictionaryHubView.tsx b/ordinis-admin-ui/src/components/lineage/DictionaryHubView.tsx index ce20cf4..3478965 100644 --- a/ordinis-admin-ui/src/components/lineage/DictionaryHubView.tsx +++ b/ordinis-admin-ui/src/components/lineage/DictionaryHubView.tsx @@ -61,7 +61,7 @@ const NeighborCard = ({