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:
@@ -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} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user