feat(ui): drop @nstart/ui dependency (Stage 3.9)
Все 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 <input type=date> wrapper с label/hint/error
- multi-select.tsx — Radix Popover + inline checkboxes
- single-select.tsx — native <select> с label/hint/error chrome
- language-switch.tsx — segmented control в Tektur uppercase
Compatibility shims (existing components extended):
- checkbox.tsx — + label/description/error/onChange (nstart-compat
synthetic event), wrap в <label> когда есть chrome
- modal.tsx — + panelClassName / bodyClassName props
- form-actions.tsx — + align prop (start/end/between)
- tabs-simple.tsx — TabItem.count alias for .trailing (nstart-compat)
- table TableHead — alias TableHeaderCell с align prop
Barrel rewrite (src/ui/index.ts):
- Удалён 'export * from @nstart/ui'
- Explicit exports для всех 45+ нужных символов
- SelectOption / MultiSelectOption / LanguageOption теперь {id, label}
с optional 'value' alias — nstart-compat без массового codemod call-sites
styles.css cleanup:
- Removed @import @nstart/ui/styles/{fonts,theme}.css
- Removed @source nstart/dist directive (Tailwind v4 scan)
- Removed --text-sm: 13px override (был костыль для @nstart/ui dist)
- Legacy color tokens (--color-carbon/ultramarain/orbit/regolith/asteroid)
sсодержали fallback mappings — оставлены, но callsites больше не используют
Codemod: 9 файлов конвертированы legacy colors → handoff tokens
carbon → ink, ultramarain → accent, orbit → warn, regolith → line, asteroid → mute
main.tsx:
- NStartUiProvider → TooltipProvider (Radix)
- DatePickerProvider убран (наш DatePicker native, no provider needed)
routes/dictionaries.$name.tsx:
- Removed useRef-based <input>.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.
This commit is contained in:
@@ -309,7 +309,7 @@ function ActiveFilterChips({ filters, onRemove }: ActiveFilterChipsProps) {
|
||||
onClick={() => onRemove(f.key)}
|
||||
className={[
|
||||
'inline-flex items-center gap-1.5 px-2 py-1 rounded-sm border text-cell',
|
||||
'bg-white border-line hover:border-carbon/40 hover:bg-line/30',
|
||||
'bg-white border-line hover:border-ink/40 hover:bg-line/30',
|
||||
'transition-colors group',
|
||||
].join(' ')}
|
||||
title={t('audit.filter.removeChip')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import axios from 'axios'
|
||||
@@ -322,18 +322,12 @@ function DictionaryDetail() {
|
||||
const someVisibleSelected =
|
||||
!allVisibleSelected && visibleKeys.some((k) => selection.has(k))
|
||||
|
||||
// Native checkbox indeterminate state — sync через useEffect, не callback ref.
|
||||
// Callback ref срабатывает только на mount/unmount → state становится stale
|
||||
// когда user toggle'ает selection per-row. useEffect re-runs на изменение
|
||||
// someVisibleSelected. См. design handoff review #17 + Checkbox в @nstart/ui:
|
||||
// ref forwarded к нативному <input>, но `indeterminate` это JS DOM-only prop
|
||||
// (не HTML attribute), нужен manual sync.
|
||||
const selectAllRef = useRef<HTMLInputElement>(null)
|
||||
useEffect(() => {
|
||||
if (selectAllRef.current) {
|
||||
selectAllRef.current.indeterminate = someVisibleSelected
|
||||
}
|
||||
}, [someVisibleSelected])
|
||||
// Stage 3.9: Radix Checkbox принимает {@code checked="indeterminate"} как
|
||||
// tri-state значение declaratively. Старый useEffect+ref hack (для нативного
|
||||
// input.indeterminate JS-only prop) больше не нужен.
|
||||
const selectAllChecked: boolean | 'indeterminate' = someVisibleSelected
|
||||
? 'indeterminate'
|
||||
: allVisibleSelected
|
||||
|
||||
const toggleSelect = (key: string) => {
|
||||
setSelection((prev) => {
|
||||
@@ -723,7 +717,7 @@ function DictionaryDetail() {
|
||||
'text-cap inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border transition-colors',
|
||||
active
|
||||
? 'border-accent bg-accent/8 text-accent'
|
||||
: 'border-line bg-white text-ink-2 hover:border-carbon/40',
|
||||
: 'border-line bg-white text-ink-2 hover:border-ink/40',
|
||||
].join(' ')}
|
||||
>
|
||||
<span
|
||||
@@ -773,8 +767,7 @@ function DictionaryDetail() {
|
||||
<TableHeaderCell>
|
||||
<Checkbox
|
||||
aria-label={t('dict.bulk.selectAll')}
|
||||
checked={allVisibleSelected}
|
||||
ref={selectAllRef}
|
||||
checked={selectAllChecked}
|
||||
onChange={toggleSelectAll}
|
||||
/>
|
||||
</TableHeaderCell>
|
||||
@@ -834,7 +827,7 @@ function DictionaryDetail() {
|
||||
'inline-block px-1.5 py-0.5 rounded-sm font-mono text-cell',
|
||||
c.isFk
|
||||
? 'bg-accent/8 text-accent'
|
||||
: 'bg-carbon/8 text-ink',
|
||||
: 'bg-ink/8 text-ink',
|
||||
].join(' ')}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -235,7 +235,7 @@ function WebhookDetailPage() {
|
||||
'text-cap inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border transition-colors',
|
||||
active
|
||||
? 'border-accent bg-accent/8 text-accent'
|
||||
: 'border-line bg-white text-ink-2 hover:border-carbon/40',
|
||||
: 'border-line bg-white text-ink-2 hover:border-ink/40',
|
||||
].join(' ')}
|
||||
>
|
||||
{s}
|
||||
@@ -452,7 +452,7 @@ function RotateRevealModal({
|
||||
<Alert variant="warning" title={t('webhooks.secret.warningTitle')}>
|
||||
{t('webhooks.secret.rotatedWarning')}
|
||||
</Alert>
|
||||
<div className="bg-carbon/5 border border-line rounded-md p-3">
|
||||
<div className="bg-ink/5 border border-line rounded-md p-3">
|
||||
<code className="text-mono break-all">{secret}</code>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
|
||||
@@ -367,7 +367,7 @@ function SecretRevealModal({
|
||||
<Alert variant="warning" title={t('webhooks.secret.warningTitle')}>
|
||||
{t('webhooks.secret.warning')}
|
||||
</Alert>
|
||||
<div className="bg-carbon/5 border border-line rounded-md p-3">
|
||||
<div className="bg-ink/5 border border-line rounded-md p-3">
|
||||
<code className="text-mono break-all">{revealed.secret}</code>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
|
||||
Reference in New Issue
Block a user