Empty State

Ecsight

Nothing to show — explain why and offer a next step.

Motion: Icon, title, text, action stagger in.

ยังไม่มีโปรเจกต์

สร้างโปรเจกต์แรกเพื่อเริ่มติดตามงานของทีม

ติดตั้ง

npx shadcn@latest add @ecsight/empty-state

Source

components/ui/empty-state.tsx
"use client"

import * as React from "react"
import { motion } from "motion/react"
import { cn } from "cn"

import { fadeUp, listStagger } from "@/lib/motion"
import type { SolarIcon } from "@/components/ui/icon"

type EmptyStateProps = Omit<React.ComponentProps<typeof motion.div>, "title"> & {
  /** Use a `bold-duotone` Solar icon — empty states are the one place for duotone */
  icon?: SolarIcon
  title: React.ReactNode
  description?: React.ReactNode
  /** Primary next step, usually a <Button> */
  action?: React.ReactNode
}

/** Shown when a list, table or search has nothing to display. Always offer a next step. */
function EmptyState({ icon: IconCmp, title, description, action, className, ...props }: EmptyStateProps) {
  return (
    <motion.div
      data-slot="empty-state"
      variants={listStagger}
      initial="hidden"
      animate="visible"
      className={cn("flex flex-col items-center justify-center gap-3 px-6 py-12 text-center", className)}
      {...props}
    >
      {IconCmp && (
        <motion.div
          variants={fadeUp}
          className="mb-1 grid size-12 place-items-center rounded-xl bg-accent text-accent-foreground"
        >
          <IconCmp className="size-6" aria-hidden />
        </motion.div>
      )}
      <motion.h3 variants={fadeUp} className="text-base font-semibold text-foreground">
        {title}
      </motion.h3>
      {description && (
        <motion.p variants={fadeUp} className="max-w-sm text-sm text-muted-foreground">
          {description}
        </motion.p>
      )}
      {action && (
        <motion.div variants={fadeUp} className="mt-2">
          {action}
        </motion.div>
      )}
    </motion.div>
  )
}

export { EmptyState }