refactor(ui): заменить Badge и Alert на ReUI semantic

Перевести AppBadge/AppAlert и потребители на @/components/reui; EmptyState — IconStack в духе empty-state-12.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-17 13:48:42 +07:00
co-authored by Cursor
parent c3ad676e75
commit 57ee231417
7 changed files with 63 additions and 31 deletions
+7 -1
View File
@@ -1,5 +1,10 @@
import type { ComponentProps } from 'react' import type { ComponentProps } from 'react'
import { Alert, AlertAction, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert' import {
Alert,
AlertAction,
AlertDescription,
AlertTitle,
} from '@/components/reui/alert'
import { cn } from '@cfdm/ui/lib/utils' import { cn } from '@cfdm/ui/lib/utils'
export type AppAlertProps = ComponentProps<typeof Alert> export type AppAlertProps = ComponentProps<typeof Alert>
@@ -7,6 +12,7 @@ export type AppAlertTitleProps = ComponentProps<typeof AlertTitle>
export type AppAlertDescriptionProps = ComponentProps<typeof AlertDescription> export type AppAlertDescriptionProps = ComponentProps<typeof AlertDescription>
export type AppAlertActionProps = ComponentProps<typeof AlertAction> export type AppAlertActionProps = ComponentProps<typeof AlertAction>
/** Thin alias over ReUI Alert — prefer importing from `@/components/reui/alert` in new code. */
export function AppAlert({ className, ...props }: AppAlertProps) { export function AppAlert({ className, ...props }: AppAlertProps) {
return <Alert className={cn(className)} {...props} /> return <Alert className={cn(className)} {...props} />
} }
+2 -1
View File
@@ -1,9 +1,10 @@
import type { ComponentProps } from 'react' import type { ComponentProps } from 'react'
import { Badge } from '@cfdm/ui/components/badge' import { Badge } from '@/components/reui/badge'
import { cn } from '@cfdm/ui/lib/utils' import { cn } from '@cfdm/ui/lib/utils'
export type AppBadgeProps = ComponentProps<typeof Badge> export type AppBadgeProps = ComponentProps<typeof Badge>
/** Thin alias over ReUI Badge — prefer importing Badge/StatusBadge directly in new code. */
export function AppBadge({ className, ...props }: AppBadgeProps) { export function AppBadge({ className, ...props }: AppBadgeProps) {
return <Badge className={cn(className)} {...props} /> return <Badge className={cn(className)} {...props} />
} }
+4 -4
View File
@@ -1,4 +1,4 @@
import { AppBadge } from '@/components/app-badge' import { Badge } from '@/components/reui/badge'
import { import {
AppItem, AppItem,
AppItemContent, AppItemContent,
@@ -19,9 +19,9 @@ interface DomainIpBadgesProps {
function IpBadge({ ip }: { ip: string }) { function IpBadge({ ip }: { ip: string }) {
return ( return (
<AppBadge variant="secondary" className="font-mono"> <Badge variant="secondary" className="font-mono">
{ip} {ip}
</AppBadge> </Badge>
) )
} }
@@ -43,7 +43,7 @@ export function DomainIpBadges({ ips }: DomainIpBadgesProps) {
<Tooltip> <Tooltip>
<TooltipTrigger <TooltipTrigger
render={ render={
<AppBadge variant="outline" className="cursor-default font-mono tabular-nums" /> <Badge variant="outline" className="cursor-default font-mono tabular-nums" />
} }
> >
+{rest.length} +{rest.length}
+28 -7
View File
@@ -1,4 +1,5 @@
import { InboxIcon, type LucideIcon } from 'lucide-react' import { InboxIcon, type LucideIcon } from 'lucide-react'
import { IconStack } from '@/components/reui/icon-stack'
import { import {
Empty, Empty,
EmptyContent, EmptyContent,
@@ -7,6 +8,7 @@ import {
EmptyMedia, EmptyMedia,
EmptyTitle, EmptyTitle,
} from '@cfdm/ui/components/empty' } from '@cfdm/ui/components/empty'
import { cn } from '@cfdm/ui/lib/utils'
interface EmptyStateProps { interface EmptyStateProps {
icon?: LucideIcon icon?: LucideIcon
@@ -14,6 +16,8 @@ interface EmptyStateProps {
description?: string description?: string
action?: React.ReactNode action?: React.ReactNode
className?: string className?: string
/** Use IconStack media (empty-state-12). Default true. */
stackedIcon?: boolean
} }
export function EmptyState({ export function EmptyState({
@@ -22,17 +26,34 @@ export function EmptyState({
description, description,
action, action,
className, className,
stackedIcon = true,
}: EmptyStateProps) { }: EmptyStateProps) {
return ( return (
<Empty className={className ?? 'border-0'}> <Empty className={cn('max-w-md flex-none border-0 bg-transparent p-0', className)}>
<EmptyHeader> <EmptyHeader className="gap-5 text-center">
<EmptyMedia variant="icon"> <EmptyMedia className="mb-0">
<Icon /> {stackedIcon ? (
<IconStack aria-hidden="true" className="h-14 w-12">
<Icon strokeWidth={1.9} aria-hidden="true" className="size-5" />
</IconStack>
) : (
<span className="bg-muted text-muted-foreground flex size-10 items-center justify-center rounded-lg [&_svg]:size-5">
<Icon aria-hidden="true" />
</span>
)}
</EmptyMedia> </EmptyMedia>
<EmptyTitle>{title}</EmptyTitle> <div className="flex flex-col items-center gap-2">
{description && <EmptyDescription>{description}</EmptyDescription>} <EmptyTitle className="text-base font-semibold tracking-tight">
{title}
</EmptyTitle>
{description ? (
<EmptyDescription className="max-w-sm text-sm/relaxed">
{description}
</EmptyDescription>
) : null}
</div>
</EmptyHeader> </EmptyHeader>
{action && <EmptyContent>{action}</EmptyContent>} {action ? <EmptyContent>{action}</EmptyContent> : null}
</Empty> </Empty>
) )
} }
+12 -13
View File
@@ -1,5 +1,9 @@
import { CircleAlertIcon } from 'lucide-react' import { CircleAlertIcon } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert' import {
Alert,
AlertDescription,
AlertTitle,
} from '@/components/reui/alert'
import { Button } from '@cfdm/ui/components/button' import { Button } from '@cfdm/ui/components/button'
import { Skeleton } from '@cfdm/ui/components/skeleton' import { Skeleton } from '@cfdm/ui/components/skeleton'
@@ -39,19 +43,14 @@ export function QueryState({
<Alert variant="destructive"> <Alert variant="destructive">
<CircleAlertIcon /> <CircleAlertIcon />
<AlertTitle>Не удалось загрузить данные</AlertTitle> <AlertTitle>Не удалось загрузить данные</AlertTitle>
<AlertDescription> <AlertDescription className="flex flex-col gap-2">
{error?.message ?? 'Произошла ошибка при загрузке.'} <span>{error?.message ?? 'Произошла ошибка при загрузке.'}</span>
{onRetry ? (
<Button variant="outline" size="sm" onClick={onRetry}>
Повторить
</Button>
) : null}
</AlertDescription> </AlertDescription>
{onRetry && (
<Button
variant="outline"
size="sm"
className="mt-2"
onClick={onRetry}
>
Повторить
</Button>
)}
</Alert> </Alert>
) )
} }
@@ -9,7 +9,7 @@ import {
type RowSelectionState, type RowSelectionState,
type SortingState, type SortingState,
} from '@tanstack/react-table' } from '@tanstack/react-table'
import { FilterIcon, FunnelXIcon } from 'lucide-react' import { CircleAlertIcon, FilterIcon, FunnelXIcon } from 'lucide-react'
import { CountedLineTabs } from '@/components/counted-line-tabs' import { CountedLineTabs } from '@/components/counted-line-tabs'
import { Badge } from '@/components/reui/badge' import { Badge } from '@/components/reui/badge'
@@ -33,7 +33,11 @@ import {
import { Button } from '@cfdm/ui/components/button' import { Button } from '@cfdm/ui/components/button'
import { Separator } from '@cfdm/ui/components/separator' import { Separator } from '@cfdm/ui/components/separator'
import { Skeleton } from '@cfdm/ui/components/skeleton' import { Skeleton } from '@cfdm/ui/components/skeleton'
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert' import {
Alert,
AlertDescription,
AlertTitle,
} from '@/components/reui/alert'
import { EmptyState } from '@/components/empty-state' import { EmptyState } from '@/components/empty-state'
import { applyFiltersToData } from './filter-utils' import { applyFiltersToData } from './filter-utils'
@@ -222,6 +226,7 @@ export function ResourcePage<T extends object>({
if (isError) { if (isError) {
return ( return (
<Alert variant="destructive"> <Alert variant="destructive">
<CircleAlertIcon />
<AlertTitle>Ошибка загрузки</AlertTitle> <AlertTitle>Ошибка загрузки</AlertTitle>
<AlertDescription className="flex flex-col gap-2"> <AlertDescription className="flex flex-col gap-2">
<span>{error?.message ?? 'Не удалось загрузить данные'}</span> <span>{error?.message ?? 'Не удалось загрузить данные'}</span>
+3 -3
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { XIcon } from 'lucide-react' import { XIcon } from 'lucide-react'
import { AppBadge } from '@/components/app-badge' import { Badge } from '@/components/reui/badge'
import { import {
InputGroup, InputGroup,
InputGroupButton, InputGroupButton,
@@ -78,7 +78,7 @@ export function TaggedInput({
)} )}
> >
{value.map((tag) => ( {value.map((tag) => (
<AppBadge key={tag} variant="secondary" className="gap-1 pr-1"> <Badge key={tag} variant="secondary" className="gap-1 pr-1">
{tag} {tag}
<InputGroupButton <InputGroupButton
type="button" type="button"
@@ -90,7 +90,7 @@ export function TaggedInput({
> >
<XIcon /> <XIcon />
</InputGroupButton> </InputGroupButton>
</AppBadge> </Badge>
))} ))}
<InputGroupInput <InputGroupInput
id={id} id={id}