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:
@@ -0,0 +1,70 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* LanguageSwitch — segmented control для смены i18next language.
|
||||
* Замена @nstart/ui LanguageSwitch (тот же value/options/onChange API).
|
||||
*
|
||||
* <p>Compact: 3-letter codes (RU/EN/ZH...) в Tektur uppercase. Tab-like
|
||||
* underline indicator под active. На narrow screens сворачивается в icon
|
||||
* только (TODO mobile responsive).
|
||||
*/
|
||||
|
||||
/** nstart-compat: {id, label}. `value` accepted as alias. */
|
||||
export type LanguageOption = {
|
||||
id: string
|
||||
label: string
|
||||
/** Alias of `id`. */
|
||||
value?: string
|
||||
/** Short 2-3 char code (RU/EN/ZH). Default — id.toUpperCase(). */
|
||||
short?: string
|
||||
}
|
||||
|
||||
export type LanguageSwitchProps = {
|
||||
value: string
|
||||
options: LanguageOption[]
|
||||
onChange: (id: string) => void
|
||||
className?: string
|
||||
'aria-label'?: string
|
||||
}
|
||||
|
||||
export function LanguageSwitch({
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
className,
|
||||
'aria-label': ariaLabel,
|
||||
}: LanguageSwitchProps) {
|
||||
return (
|
||||
<div
|
||||
role="radiogroup"
|
||||
aria-label={ariaLabel ?? 'Language'}
|
||||
className={cn(
|
||||
'inline-flex items-center rounded-md border border-line bg-surface p-0.5 gap-0.5',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{options.map((opt) => {
|
||||
const v = opt.id ?? opt.value!
|
||||
const active = v === value
|
||||
return (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={active}
|
||||
onClick={() => onChange(v)}
|
||||
title={opt.label}
|
||||
className={cn(
|
||||
'px-2 py-1 rounded-sm text-cap leading-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||
active
|
||||
? 'bg-accent-bg text-accent'
|
||||
: 'text-mute hover:text-ink hover:bg-surface-2',
|
||||
)}
|
||||
>
|
||||
{opt.short ?? v.toUpperCase()}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user