组件
空状态 Empty State
在没有数据或结果时解释现状并引导下一步。
基础示例
Loading…
pnpm dlx wui@latest add @wui/empty-stateimport * as React from "react"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const emptyStateVariants = cva(
"flex w-full flex-col items-center justify-center text-center",
{
variants: {
size: {
sm: "min-h-40 px-5 py-8",
default: "min-h-64 px-6 py-12",
lg: "min-h-80 px-8 py-16",
},
},
defaultVariants: { size: "default" },
}
)
const emptyStateClassicIllustrations = [
"nothing-here-no-image",
"under-construction-maintenance",
"no-files-found",
"empty-folder",
"add-files",
"search-folder",
"add-media",
"xls-file",
"zip-file",
"file-not-found",
"search",
"file-broken-or-not-found",
"general-files",
"search-general",
"content-unavailable",
"add-photo",
"add-photos",
"search-photos-files",
"task-done",
"no-content-general",
"no-reports",
"add-dashboard",
"no-files",
"upload-file",
"upload-general",
"messages-add-message",
"no-messages",
"cards",
"add-card",
"card-expired",
"card-rejected",
"no-credits",
"no-tasks",
"add-task",
"no-appointments",
"no-location",
"no-address",
"empty-inbox",
"add-to-inbox",
"email-sent",
"empty-inbox-2",
"check-your-inbox",
"no-emails",
"add-to-group",
"add-user",
"search-user",
"add-user-2",
"users",
"something-went-wrong",
"404-page-not-found",
"nothing-here",
"nothing-here-2",
"no-products",
"no-friends",
"trash-empty",
"empty-box",
"add-product",
"no-notifications",
"launch-app",
"add-alarm",
"no-alarms",
"youre-awesome",
"no-vacation",
"empty-cart",
"connection-lost",
] as const
const emptyStateColorIllustrations = [
"add-email",
"allow-notifications",
"app-locked",
"camera-access",
"email-sent",
"expired-alt",
"expired",
"microphone-access",
"no-plan-chosen",
"payment",
"phone-numbers",
"search",
"send-email",
"settings",
"resource-1",
"resource-2",
"resource-3",
"resource-4",
"resource-5",
"resource-6",
] as const
const emptyStateIllustrations = {
gradient: emptyStateClassicIllustrations,
flat: emptyStateClassicIllustrations,
color: emptyStateColorIllustrations,
} as const
export type EmptyStateClassicIllustration =
(typeof emptyStateClassicIllustrations)[number]
export type EmptyStateColorIllustration =
(typeof emptyStateColorIllustrations)[number]
export type EmptyStateIllustrationVariant = keyof typeof emptyStateIllustrations
type EmptyStateIllustrationSelection =
| {
variant?: "gradient" | "flat"
name: EmptyStateClassicIllustration
}
| {
variant: "color"
name: EmptyStateColorIllustration
}
export type EmptyStateIllustrationProps = Omit<
React.ComponentProps<"img">,
"src"
> &
EmptyStateIllustrationSelection & {
/** Public directory that contains the bundled illustration variants. */
assetBasePath?: string
}
export interface EmptyStateProps extends React.ComponentProps<"div"> {
/** Vertical density preset. @default "default" */
size?: "sm" | "default" | "lg"
}
/** A focused zero-data state with optional illustration and actions. */
function EmptyState({
className,
size = "default",
...props
}: EmptyStateProps) {
return (
<div
data-slot="empty-state"
className={cn(emptyStateVariants({ size }), className)}
{...props}
/>
)
}
function EmptyStateIcon({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-state-icon"
className={cn(
"bg-muted/50 text-muted-foreground shadow-xs mb-5 flex size-11 items-center justify-center rounded-full border [&_svg]:size-5",
className
)}
{...props}
/>
)
}
function EmptyStateMedia({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-state-media"
className={cn("mb-6 flex items-center justify-center", className)}
{...props}
/>
)
}
function EmptyStateIllustration({
name,
variant = "gradient",
assetBasePath = "/wui/empty-state",
alt = "",
className,
...props
}: EmptyStateIllustrationProps) {
return (
<img
data-slot="empty-state-illustration"
src={`${assetBasePath}/${variant}/${name}.svg`}
alt={alt}
className={cn("h-auto w-36 max-w-full object-contain", className)}
loading="lazy"
decoding="async"
{...props}
/>
)
}
function EmptyStateTitle({ className, ...props }: React.ComponentProps<"h3">) {
return (
<h3
data-slot="empty-state-title"
className={cn("text-base font-semibold tracking-tight", className)}
{...props}
/>
)
}
function EmptyStateDescription({
className,
...props
}: React.ComponentProps<"p">) {
return (
<p
data-slot="empty-state-description"
className={cn(
"text-muted-foreground mt-1.5 max-w-sm text-sm leading-6",
className
)}
{...props}
/>
)
}
function EmptyStateActions({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-state-actions"
className={cn(
"mt-5 flex flex-wrap items-center justify-center gap-2",
className
)}
{...props}
/>
)
}
function EmptyStateHint({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-state-hint"
className={cn(
"text-muted-foreground mt-6 border-t pt-4 text-xs",
className
)}
{...props}
/>
)
}
export {
EmptyState,
EmptyStateActions,
EmptyStateDescription,
EmptyStateHint,
EmptyStateIcon,
EmptyStateIllustration,
EmptyStateMedia,
EmptyStateTitle,
emptyStateClassicIllustrations,
emptyStateColorIllustrations,
emptyStateIllustrations,
emptyStateVariants,
}
内置插画
Loading…
组件内置 gradient、flat 和 color 三套插画。渐变与扁平系列各包含 65 个通用空状态场景,彩色系列包含 20 个权限、通知与业务状态场景。使用 EmptyStateMedia 管理插画与正文之间的间距,通过 EmptyStateIllustration 的 name 和 variant 选择素材。
<EmptyStateMedia>
<EmptyStateIllustration variant="flat" name="search-folder" />
</EmptyStateMedia>emptyStateClassicIllustrations、emptyStateColorIllustrations 和 emptyStateIllustrations 提供完整的类型安全名称清单,可用于业务侧选择器或二次封装。
组件作用
EmptyState 用于首次使用、筛选无结果和内容被清空后的页面区域。标题应说明发生了什么,描述解释原因或价值,主操作只保留最合理的下一步;加载中应使用 Skeleton,错误反馈应使用 Message。
组件属性
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "default" | "lg" | default | Vertical density preset. |
事件
容器本身不产生业务事件。操作由 EmptyStateActions 中组合的 Button 或链接承担,原生容器事件仍会透传。
扩展使用
使用 size 控制纵向留白;简单语义图标使用 EmptyStateIcon,复杂插画使用 EmptyStateMedia 与 EmptyStateIllustration。EmptyStateHint 用于放置快捷键、拖放提示等低优先级信息。