feat(admin-ui): codemod @nstart/ui → @/ui (Stage 2.2)

This commit is contained in:
Александр Зимин
2026-05-10 22:17:06 +00:00
parent 0710efb8ce
commit b7d2fbd7ce
34 changed files with 398 additions and 92 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import { useAuth } from 'react-oidc-context'
import { useTranslation } from 'react-i18next'
import { Button, IconButton } from '@nstart/ui'
import { Button, IconButton } from '@/ui'
import { SignInIcon, SignOutIcon, UserIcon } from '@phosphor-icons/react'
/**
@@ -20,7 +20,7 @@ import {
TextInput,
type SelectOption,
type TabItem,
} from '@nstart/ui'
} from '@/ui'
import type { CreateRecordRequest, FlattenedRecord, JsonSchema } from '@/api/client'
import { useReferencedRecords } from '@/api/queries'
import {
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, Button, FormActions, Modal } from '@nstart/ui'
import { Alert, Button, FormActions, Modal } from '@/ui'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, Badge, Button, LoadingBlock, Modal, TextInput } from '@nstart/ui'
import { Alert, Badge, Button, LoadingBlock, Modal, TextInput } from '@/ui'
import { WarningIcon, XCircleIcon } from '@phosphor-icons/react'
import { useCascadePreview } from '@/api/queries'
import { useCascadeCloseRecord } from '@/api/mutations'
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'
import { Link } from '@tanstack/react-router'
import { Badge, Panel } from '@nstart/ui'
import { Badge, Panel } from '@/ui'
import { ArrowRightIcon } from '@phosphor-icons/react'
import { useDictionaryDependents } from '@/api/queries'
import type { OnCloseAction } from '@/api/client'
@@ -16,7 +16,7 @@
import { useMemo } from 'react'
import { Link } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import { Badge } from '@nstart/ui'
import { Badge } from '@/ui'
import { useDictionaryDependents, useDictionaries } from '@/api/queries'
import type { DictionaryDetail, DictionaryDefinition, JsonSchema } from '@/api/client'
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from '@tanstack/react-router'
import { Alert, Badge, Button, LoadingBlock } from '@nstart/ui'
import { Alert, Badge, Button, LoadingBlock } from '@/ui'
import { CaretLeftIcon, CaretRightIcon, ArrowSquareOutIcon } from '@phosphor-icons/react'
import { useRecordDependents } from '@/api/queries'
import type { OnCloseAction } from '@/api/client'
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
import { Alert, Badge, Drawer, LoadingBlock } from '@nstart/ui'
import { Alert, Badge, Drawer, LoadingBlock } from '@/ui'
import { useRecordHistory } from '@/api/queries'
import { RecordDependentsPanel } from '@/components/lineage/RecordDependentsPanel'
@@ -14,7 +14,7 @@ import {
TextInput,
type SelectOption,
type TabItem,
} from '@nstart/ui'
} from '@/ui'
import { useCreateDictionary, useUpdateDictionary } from '@/api/mutations'
import { dictionaryDetailQuery, useDictionaries } from '@/api/queries'
import type { CreateDictionaryRequest, DataScope, DictionaryDetail } from '@/api/client'
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Badge, Tabs, type TabItem } from '@nstart/ui'
import { Badge, Tabs, type TabItem } from '@/ui'
import { generateEventPreviews } from './eventPreview'
import type { DataScope, JsonSchema } from '@/api/client'
@@ -9,7 +9,7 @@ import {
TextInput,
TextArea,
type SelectOption,
} from '@nstart/ui'
} from '@/ui'
import { TrashIcon, ArrowUpIcon, ArrowDownIcon } from '@phosphor-icons/react'
import type { PropertyDef, PropertyKind } from './types'
import { PROPERTY_KINDS } from './types'
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
import { Button, EmptyState } from '@nstart/ui'
import { Button, EmptyState } from '@/ui'
import { PlusIcon } from '@phosphor-icons/react'
import { PropertyEditor } from './PropertyEditor'
import { newPropertyDef, type PropertyDef } from './types'
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
import { Button } from '@nstart/ui'
import { Button } from '@/ui'
import { ArrowClockwise, BookOpen, X } from '@phosphor-icons/react'
import { useState } from 'react'
import { useAppVersion } from './useAppVersion'
+1 -1
View File
@@ -2,7 +2,7 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { NStartUiProvider, DatePickerProvider, DATE_PICKER_LOCALE_RU } from '@nstart/ui'
import { NStartUiProvider, DatePickerProvider, DATE_PICKER_LOCALE_RU } from '@/ui'
import { AuthProvider } from 'react-oidc-context'
import { routeTree } from './routeTree.gen'
import { oidcAuthConfig } from '@/auth/oidcConfig'
+1 -1
View File
@@ -1,7 +1,7 @@
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'
import { LanguageSwitch } from '@/ui'
import { AuthBadge } from '@/auth/AuthBadge'
import { useCanMutate } from '@/auth/useCanMutate'
import { ThemeSwitch } from '@/components/layout/ThemeSwitch'
+1 -1
View File
@@ -19,7 +19,7 @@ import {
TableHeaderCell,
TableRow,
TextInput,
} from '@nstart/ui'
} from '@/ui'
import {
ArrowsClockwiseIcon,
CaretDownIcon,
@@ -21,7 +21,7 @@ import {
TableHeaderCell,
TableRow,
TextInput,
} from '@nstart/ui'
} from '@/ui'
import { PlusIcon, PencilSimpleIcon, XCircleIcon, GearIcon, ClockCounterClockwiseIcon, MapTrifoldIcon, TrashIcon, CheckCircleIcon, WarningIcon, XIcon, DownloadSimpleIcon, CaretLeftIcon, CaretRightIcon } from '@phosphor-icons/react'
import {
useDictionaryDetail,
@@ -16,7 +16,7 @@
import { useDeferredValue, useMemo, useState } from 'react'
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import { Alert, Badge, Button, EmptyState, LoadingBlock, PageHeader, SearchInput } from '@nstart/ui'
import { Alert, Badge, Button, EmptyState, LoadingBlock, PageHeader, SearchInput } from '@/ui'
import { PlusIcon } from '@phosphor-icons/react'
import { useQueries } from '@tanstack/react-query'
import { dictionaryDependentsQuery, useDictionaries, useDictionaryDependents } from '@/api/queries'
+1 -1
View File
@@ -15,7 +15,7 @@ import {
TableHead,
TableHeaderCell,
TableRow,
} from '@nstart/ui'
} from '@/ui'
import { useMyDrafts } from '@/api/queries'
import { useWithdrawDraft } from '@/api/mutations'
import type { DraftOperation, DraftStatus } from '@/api/client'
+1 -1
View File
@@ -14,7 +14,7 @@ import {
TableHead,
TableHeaderCell,
TableRow,
} from '@nstart/ui'
} from '@/ui'
import { ArrowsClockwiseIcon } from '@phosphor-icons/react'
import { useDlq, useOutboxStats } from '@/api/queries'
+1 -1
View File
@@ -20,7 +20,7 @@ import {
TableRow,
TextArea,
TextInput,
} from '@nstart/ui'
} from '@/ui'
import { CheckCircleIcon, XCircleIcon } from '@phosphor-icons/react'
import { useDraft, useLiveRecord, useReviewQueue } from '@/api/queries'
import { useApproveDraft, useRejectDraft } from '@/api/mutations'
+1 -1
View File
@@ -9,7 +9,7 @@ import {
PageHeader,
Panel,
SearchInput,
} from '@nstart/ui'
} from '@/ui'
import { useSearch } from '@/api/queries'
type SearchSearch = { q?: string }
+1 -1
View File
@@ -16,7 +16,7 @@ import {
TableHead,
TableHeaderCell,
TableRow,
} from '@nstart/ui'
} from '@/ui'
import {
ArrowClockwiseIcon,
ArrowsClockwiseIcon,
@@ -21,7 +21,7 @@ import {
TextArea,
TextInput,
type SelectOption,
} from '@nstart/ui'
} from '@/ui'
import {
CopyIcon,
PlusIcon,
+10 -1
View File
@@ -15,6 +15,8 @@ const alertVariants = cva(
success: 'border-l-green bg-green-bg text-ink',
warning: 'border-l-warn bg-warn-bg text-ink',
danger: 'border-l-pink bg-pink-bg text-ink',
// Alias для nstart-compat: error === danger визуально.
error: 'border-l-pink bg-pink-bg text-ink',
neutral: 'border-l-line bg-surface-2 text-ink',
},
},
@@ -25,15 +27,22 @@ const alertVariants = cva(
export type AlertProps = React.HTMLAttributes<HTMLDivElement> &
VariantProps<typeof alertVariants> & {
title?: React.ReactNode
/** Right-aligned slot для action button(s) inside alert. */
action?: React.ReactNode
}
export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
function Alert({ className, variant, title, children, ...props }, ref) {
function Alert({ className, variant, title, action, children, ...props }, ref) {
return (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props}>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
{title && <div className="font-medium mb-0.5">{title}</div>}
<div className="text-ink-2 text-[13px] leading-snug">{children}</div>
</div>
{action && <div className="shrink-0 flex items-center gap-2">{action}</div>}
</div>
</div>
)
},
)
@@ -12,6 +12,13 @@ const badgeVariants = cva(
success: 'bg-green-bg text-green',
warning: 'bg-warn-bg text-warn',
danger: 'bg-pink-bg text-pink',
// Aliases для nstart-compat: error == danger, neutral == default,
// info == accent.
error: 'bg-pink-bg text-pink',
neutral: 'bg-surface-2 text-ink-2 border border-line',
info: 'bg-accent-bg text-accent',
// Alias для nstart-compat — старый Badge variant='primary' эквивалентен accent.
primary: 'bg-accent text-on-accent',
outline: 'border border-line text-ink-2',
},
},
+25 -2
View File
@@ -61,21 +61,44 @@ export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
asChild?: boolean
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
/** Loading state — disables button + replaces leftIcon с inline spinner. */
loading?: boolean
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
function Button(
{ className, variant, size, asChild, leftIcon, rightIcon, children, ...props },
{
className,
variant,
size,
asChild,
leftIcon,
rightIcon,
loading,
disabled,
children,
...props
},
ref,
) {
const Comp = asChild ? Slot : 'button'
const renderedLeftIcon = loading ? (
<svg className="animate-spin size-3.5" viewBox="0 0 24 24" aria-hidden>
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" strokeOpacity="0.25" strokeWidth="4" />
<path d="M4 12a8 8 0 0 1 8-8" fill="none" stroke="currentColor" strokeWidth="4" strokeLinecap="round" />
</svg>
) : (
leftIcon
)
return (
<Comp
ref={ref}
className={cn(buttonVariants({ variant, size }), className)}
disabled={disabled || loading}
aria-busy={loading || undefined}
{...props}
>
{leftIcon}
{renderedLeftIcon}
{children}
{rightIcon}
</Comp>
@@ -0,0 +1,68 @@
import * as React from 'react'
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
} from './sheet'
import { cn } from '@/lib/utils'
/**
* Drawer adapter с @nstart/ui-style API над Radix Sheet (slide-over).
* Используется существующим кодом для side panels (record drawer, history,
* review preview).
*
* Props mapping:
* isOpen Sheet.open
* onClose Sheet.onOpenChange
* title SheetTitle
* side SheetContent.side (right default)
* widthClassName custom className на SheetContent (override defaults)
* size SheetContent.size shorthand (sm/md/lg/xl)
*/
export type DrawerProps = {
isOpen: boolean
onClose: () => void
title?: React.ReactNode
description?: React.ReactNode
side?: 'left' | 'right' | 'top' | 'bottom'
size?: 'sm' | 'md' | 'lg' | 'xl'
widthClassName?: string
children?: React.ReactNode
}
export function Drawer({
isOpen,
onClose,
title,
description,
side = 'right',
size,
widthClassName,
children,
}: DrawerProps) {
return (
<Sheet
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose()
}}
>
<SheetContent
side={side}
size={size}
className={cn('overflow-y-auto', widthClassName)}
>
{(title || description) && (
<SheetHeader>
{title && <SheetTitle>{title}</SheetTitle>}
{description && <SheetDescription>{description}</SheetDescription>}
</SheetHeader>
)}
{children}
</SheetContent>
</Sheet>
)
}
+49 -5
View File
@@ -4,27 +4,71 @@ import { cn } from '@/lib/utils'
/**
* Text input single-line. Использует наши tokens, focus-ring через accent.
*
* Wrap'итесь в {@code <Label>} выше для accessibility. Для search input'а
* с иконкой см. {@link SearchInput}.
* Поддерживает 2 режима:
* - "bare" (default) просто <input>, для inline использования в кастомных
* layout'ах (см. SearchInput, FormGrid с FieldLabel separately).
* - "field" когда передан label / hint / error, рендерится FormField
* обёртка с label сверху + hint/error снизу. Match'ит @nstart/ui TextInput API.
*/
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
/** Если задан — рендерится field-обёртка с label выше. */
label?: React.ReactNode
/** Optional helper text under input. */
hint?: React.ReactNode
/** Error message — overrides hint визуально, accent в pink. */
error?: React.ReactNode
/** Wrapper className когда rendering field-mode. */
containerClassName?: string
}
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
function Input({ className, type = 'text', ...props }, ref) {
return (
function Input(
{ className, type = 'text', label, hint, error, required, containerClassName, id, ...props },
ref,
) {
const autoId = React.useId()
const inputId = id ?? autoId
const inputEl = (
<input
id={inputId}
ref={ref}
type={type}
required={required}
aria-required={required || undefined}
aria-invalid={Boolean(error) || undefined}
className={cn(
'flex h-9 w-full rounded-md border border-line bg-surface px-3 py-1 text-sm text-ink',
'placeholder:text-mute',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-bg',
'disabled:cursor-not-allowed disabled:opacity-50',
'file:border-0 file:bg-transparent file:text-sm file:font-medium',
error && 'border-pink focus-visible:ring-pink',
className,
)}
{...props}
/>
)
if (!label && !hint && !error) return inputEl
return (
<div className={cn('flex flex-col gap-1', containerClassName)}>
{label && (
<label
htmlFor={inputId}
className="text-2xs font-medium font-display uppercase tracking-[0.10em] text-mute leading-none"
>
{label}
{required && <span className="text-pink ml-0.5">*</span>}
</label>
)}
{inputEl}
{error ? (
<span className="text-xs text-pink">{error}</span>
) : hint ? (
<span className="text-xs text-mute">{hint}</span>
) : null}
</div>
)
},
)
@@ -4,23 +4,37 @@ import { cn } from '@/lib/utils'
/**
* LoadingBlock central spinner с подписью. Заменяет {@code @nstart/ui}
* LoadingBlock. Для inline placeholder используйте Skeleton.
*
* Size variants:
* - sm: small inline spinner (py-4)
* - md: default centered (py-12)
* - lg: prominent (py-20)
*/
export type LoadingBlockProps = React.HTMLAttributes<HTMLDivElement> & {
label?: React.ReactNode
size?: 'sm' | 'md' | 'lg'
}
export function LoadingBlock({ className, label, ...props }: LoadingBlockProps) {
const SIZES = {
sm: { wrapper: 'py-4', icon: 'size-3' },
md: { wrapper: 'py-12', icon: 'size-5' },
lg: { wrapper: 'py-20', icon: 'size-8' },
} as const
export function LoadingBlock({ className, label, size = 'md', ...props }: LoadingBlockProps) {
const s = SIZES[size]
return (
<div
role="status"
aria-busy="true"
className={cn(
'flex flex-col items-center justify-center gap-2 py-12 text-mute',
'flex flex-col items-center justify-center gap-2 text-mute',
s.wrapper,
className,
)}
{...props}
>
<Loader2 className="size-5 animate-spin text-accent" />
<Loader2 className={cn('animate-spin text-accent', s.icon)} />
{label && <span className="text-sm">{label}</span>}
</div>
)
@@ -0,0 +1,78 @@
import * as React from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from './dialog'
/**
* Modal adapter с @nstart/ui-style API над Radix Dialog. Используется
* существующим кодом (см. dictionaries.$name.tsx, webhooks, reviews etc.)
* упрощает codemod без рефактора каждого callsite.
*
* Props mapping:
* isOpen Dialog.open
* onClose Dialog.onOpenChange (false case)
* title DialogTitle children
* description DialogDescription
* maxWidth DialogContent size (mapped: max-w-mdsm, max-w-lgmd, max-w-2xllg, max-w-4xlxl)
* children DialogContent body
*
* Новый код может использовать Dialog напрямую он остаётся exported.
*/
const SIZE_MAP: Record<string, 'sm' | 'md' | 'lg' | 'xl' | 'full'> = {
'max-w-sm': 'sm',
'max-w-md': 'sm',
'max-w-lg': 'md',
'max-w-xl': 'md',
'max-w-2xl': 'lg',
'max-w-3xl': 'lg',
'max-w-4xl': 'xl',
'max-w-5xl': 'xl',
'max-w-6xl': 'full',
}
export type ModalProps = {
isOpen: boolean
onClose: () => void
title?: React.ReactNode
description?: React.ReactNode
maxWidth?: string
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
children?: React.ReactNode
hideClose?: boolean
}
export function Modal({
isOpen,
onClose,
title,
description,
maxWidth,
size,
hideClose,
children,
}: ModalProps) {
const resolved = size ?? (maxWidth ? SIZE_MAP[maxWidth] : undefined) ?? 'md'
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose()
}}
>
<DialogContent size={resolved} hideClose={hideClose}>
{(title || description) && (
<DialogHeader>
{title && <DialogTitle>{title}</DialogTitle>}
{description && <DialogDescription>{description}</DialogDescription>}
</DialogHeader>
)}
{children}
</DialogContent>
</Dialog>
)
}
@@ -3,11 +3,15 @@ import { cn } from '@/lib/utils'
/**
* PageHeader title + optional description + actions slot. Use в верху route'а.
*
* breadcrumb prop рендерится тонкой строкой ВЫШЕ title (cap-style).
* Обычно JSX с <Link>ами через separator: "Справочники / Космические аппараты".
*/
export type PageHeaderProps = React.HTMLAttributes<HTMLDivElement> & {
title: React.ReactNode
description?: React.ReactNode
actions?: React.ReactNode
breadcrumb?: React.ReactNode
}
export function PageHeader({
@@ -15,11 +19,17 @@ export function PageHeader({
title,
description,
actions,
breadcrumb,
children,
...props
}: PageHeaderProps) {
return (
<div className={cn('flex flex-col gap-1 mb-4', className)} {...props}>
{breadcrumb && (
<div className="text-2xs font-display uppercase tracking-[0.10em] text-mute leading-tight mb-1">
{breadcrumb}
</div>
)}
<div className="flex items-start justify-between gap-3 flex-wrap">
<div className="min-w-0">
<h1 className="text-lg font-semibold text-ink leading-tight truncate">{title}</h1>
@@ -0,0 +1,53 @@
import * as React from 'react'
import {
Tabs as RadixTabs,
TabsList as RadixTabsList,
TabsTrigger as RadixTabsTrigger,
} from './tabs'
/**
* TabItem-based single-component API над Radix Tabs. Замена @nstart/ui Tabs
* с теми же props ({@code items, value, onValueChange}).
*
* Использование (legacy):
* <Tabs items={[{id: 'a', label: 'A'}, {id: 'b', label: 'B'}]}
* value={active} onValueChange={setActive} />
*
* Для content panels используйте TabsContent с radix Tabs root напрямую
* этот adapter рендерит только tab bar.
*
* Новый код может использовать `<Tabs.Root>...<TabsList><TabsTrigger value="a">A</...`
* directly из ui/components/tabs.tsx.
*/
export type TabItem = {
id: string
label: React.ReactNode
disabled?: boolean
/** Right-aligned content в tab (badge, indicator). */
trailing?: React.ReactNode
}
export type TabsSimpleProps = {
items: ReadonlyArray<TabItem>
value: string
onValueChange: (id: string) => void
className?: string
}
export function TabsSimple({ items, value, onValueChange, className }: TabsSimpleProps) {
return (
<RadixTabs value={value} onValueChange={onValueChange} className={className}>
<RadixTabsList>
{items.map((item) => (
<RadixTabsTrigger key={item.id} value={item.id} disabled={item.disabled}>
<span className="inline-flex items-center gap-1.5">
{item.label}
{item.trailing}
</span>
</RadixTabsTrigger>
))}
</RadixTabsList>
</RadixTabs>
)
}
+55 -55
View File
@@ -1,40 +1,59 @@
/**
* Barrel re-export для shadcn-style UI primitives.
* Barrel re-export для UI primitives.
*
* <p>Это единая точка входа для UI компонентов в admin-ui. Цель постепенно
* переключить все импорты с {@code @nstart/ui} на {@code @/ui}, и затем
* удалить @nstart/ui из package.json.
* <p>Стратегия миграции (Phase 2 stack migration):
* <ul>
* <li>{@code @nstart/ui} остаётся подключённым все его компоненты
* pass-through через этот barrel, чтобы существующий код работал без
* рефактора props (Checkbox.label/description, Modal.panelClassName,
* FormActions.align, и т.п.).</li>
* <li>shadcn-style primitives в {@code ./components/*} добавлены как
* параллельный набор. Новый код использует их через alias'ы где они
* чище nstart'а ({@code Button}, {@code Badge}, {@code Alert} с
* Tailwind v4 tokens; {@code Cap}, {@code ScopeBadge}, {@code ScopeDot},
* {@code ScopeStrip} handoff design system primitives).</li>
* <li>Phase 3: компоненты которые имеют bug'и или missing features в nstart
* (Combobox vs MultiSelect, TanStack Table vs Table) заменяются
* per-file rewrite.</li>
* <li>Phase 4: @nstart/ui удаляется из package.json когда все callsites
* переписаны.</li>
* </ul>
*
* <p>Phase 2 миграции (handoff plan): новые компоненты Radix primitives +
* Tailwind v4 tokens. Phase 3 каждый touched файл переносится с {@code
* @nstart/ui} на {@code @/ui} имена. Phase 4 pkg removed.
*
* <p>Все компоненты используют наши design tokens (--color-ink, --color-accent
* etc.) через @theme inline в styles.css.
* <p>Текущая семантика: import {X} from '@/ui' Х берётся приоритетно из
* наших shadcn primitives (overrides ниже), иначе passthrough из @nstart/ui.
* Этот файл single source of truth для UI imports.
*/
// Buttons + actions
// === Полная re-export из @nstart/ui — все компоненты, hooks, locale const ===
// Дальше будут selective overrides где наши shadcn primitives лучше.
export * from '@nstart/ui'
// === Overrides: наши shadcn primitives с Tailwind v4 tokens ===
// Эти именованные re-export'ы перекрывают одноимённые из @nstart/ui выше
// (TS resolves last declaration → наши берут верх). Mostly cosmetic — общий
// API design сохранён, добавлены теневые props (loading, error-variant и т.д.).
export { Button, buttonVariants, type ButtonProps } from './components/button'
export { IconButton, type IconButtonProps } from './components/icon-button'
// Inputs
export { Input, type InputProps } from './components/input'
export { Label } from './components/label'
export { SearchInput, type SearchInputProps } from './components/search-input'
// Form layout
export { FormActions, type FormActionsProps } from './components/form-actions'
// Status / feedback
export { Badge, type BadgeProps } from './components/badge'
export { Alert, type AlertProps } from './components/alert'
export { Skeleton } from './components/skeleton'
export { LoadingBlock, type LoadingBlockProps } from './components/loading-block'
export { EmptyState, type EmptyStateProps } from './components/empty-state'
// Surfaces
// === Handoff design system primitives — новые компоненты без nstart-аналога ===
export { Cap, type CapProps } from './components/cap'
export {
Card,
ScopeDot,
ScopeBadge,
ScopeStrip,
type DataScope as ScopeKind,
} from './components/scope'
// === shadcn primitives для НОВОГО кода (Phase 3) ===
// Эти именованные exports НЕ конфликтуют с nstart (имена не пересекаются).
// Используются в редизайне catalog/editor/drawer per handoff README.
export { IconButton as ShadcnIconButton, type IconButtonProps } from './components/icon-button'
export { Input as ShadcnInput, type InputProps as ShadcnInputProps } from './components/input'
export { Label as ShadcnLabel } from './components/label'
export { SearchInput as ShadcnSearchInput, type SearchInputProps as ShadcnSearchInputProps } from './components/search-input'
export {
Card as ShadcnCard,
CardHeader,
CardTitle,
CardDescription,
@@ -42,9 +61,7 @@ export {
CardFooter,
} from './components/card'
export { Separator } from './components/separator'
export { PageHeader, type PageHeaderProps } from './components/page-header'
// Overlays
export { Skeleton } from './components/skeleton'
export {
Dialog,
DialogTrigger,
@@ -82,9 +99,13 @@ export {
TooltipTrigger,
TooltipContent,
} from './components/tooltip'
// Navigation
export { Tabs, TabsList, TabsTrigger, TabsContent } from './components/tabs'
// Tabs из shadcn — composition API.
export {
Tabs as ShadcnTabsRoot,
TabsList as ShadcnTabsList,
TabsTrigger as ShadcnTabsTrigger,
TabsContent as ShadcnTabsContent,
} from './components/tabs'
export {
DropdownMenu,
DropdownMenuTrigger,
@@ -101,25 +122,4 @@ export {
DropdownMenuSubContent,
DropdownMenuRadioGroup,
} from './components/dropdown-menu'
// Form controls
export { Checkbox } from './components/checkbox'
export { Switch } from './components/switch'
// Domain-specific atoms (handoff design system)
export { Cap, type CapProps } from './components/cap'
export {
ScopeDot,
ScopeBadge,
ScopeStrip,
type DataScope,
} from './components/scope'
// === @nstart/ui compatibility aliases ===
// Чтобы упростить миграцию touched файлов: перевод import только-source,
// not всего code. Каждый alias документирован, как соотносится с новым API.
export { Card as Panel } from './components/card'
export { Dialog as Modal } from './components/dialog'
export { Sheet as Drawer } from './components/sheet'
export { Input as TextInput } from './components/input'
export { Switch as ShadcnSwitch } from './components/switch'