{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "empty",
  "title": "Empty",
  "description": "What a list or a search shows when it has nothing: an icon, a plain reason and the next step, centred, in a dashed slot, on a card or in one row.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/empty.tsx",
      "content": "import type * as React from \"react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype EmptyVariant = \"plain\" | \"dashed\" | \"card\" | \"compact\"\r\ntype EmptyEffect = \"materialize\"\r\ntype EmptyHeadingLevel = 2 | 3 | 4 | 5 | 6\r\n\r\nconst INSTANT = { duration: 0 } as const\r\nconst ENTER_SCALE = 0.96\r\nconst ENTER_BLUR = \"blur(8px)\"\r\n\r\nconst ROOT_CLASS: Record<EmptyVariant, string> = {\r\n  plain: \"flex flex-col items-center gap-3 px-4 py-12 text-center\",\r\n  dashed: \"flex flex-col items-center gap-3 rounded-lg border border-dashed px-4 py-12 text-center\",\r\n  card: \"grid gap-4 rounded-lg border bg-card p-6 text-left text-card-foreground\",\r\n  compact: \"flex flex-wrap items-center gap-x-3 gap-y-2 px-3 py-2 text-left\",\r\n}\r\n\r\nconst ICON_CLASS: Record<EmptyVariant, string> = {\r\n  plain: \"text-muted-foreground [&>svg]:size-6\",\r\n  dashed: \"flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground [&>svg]:size-5\",\r\n  card: \"flex size-10 items-center justify-center rounded-md bg-muted text-foreground [&>svg]:size-5\",\r\n  compact: \"shrink-0 text-muted-foreground [&>svg]:size-4\",\r\n}\r\n\r\nconst TEXT_CLASS: Record<EmptyVariant, string> = {\r\n  plain: \"grid max-w-sm gap-1\",\r\n  dashed: \"grid max-w-sm gap-1\",\r\n  card: \"grid gap-1\",\r\n  // The basis makes the actions wrap under the text once a row gets narrow, instead of squeezing the words.\r\n  compact: \"grid min-w-0 flex-1 basis-40 gap-1\",\r\n}\r\n\r\nconst ACTIONS_CLASS: Record<EmptyVariant, string> = {\r\n  plain: \"mt-1 flex flex-wrap justify-center gap-2\",\r\n  dashed: \"mt-1 flex flex-wrap justify-center gap-2\",\r\n  card: \"flex flex-wrap gap-2\",\r\n  compact: \"flex shrink-0 flex-wrap gap-2\",\r\n}\r\n\r\ntype EmptyProps = Omit<React.ComponentProps<\"div\">, \"title\" | \"children\"> & {\r\n  /** `plain` centred with nothing around it, `dashed` a slot waiting to be filled, `card` left-aligned on a surface, `compact` a single row. */\r\n  variant?: EmptyVariant\r\n  /** `materialize` arrives out of focus, so results that vanish feel replaced instead of broken. */\r\n  effect?: EmptyEffect\r\n  icon?: React.ReactNode\r\n  title: React.ReactNode\r\n  description?: React.ReactNode\r\n  /** Usually one or two Buttons. Lead with the step that gets people out of the empty state. */\r\n  actions?: React.ReactNode\r\n  /** The heading level of the title, to fit the page outline. */\r\n  headingLevel?: EmptyHeadingLevel\r\n  /** Reads the title out when this appears, such as after a search. Leave off for an empty state present on page load. */\r\n  announce?: boolean\r\n}\r\n\r\ntype EmptyBodyProps = Pick<EmptyProps, \"icon\" | \"title\" | \"description\" | \"actions\"> & {\r\n  variant: EmptyVariant\r\n  headingLevel: EmptyHeadingLevel\r\n}\r\n\r\nfunction EmptyBody({ variant, headingLevel, icon, title, description, actions }: EmptyBodyProps) {\r\n  const Heading = `h${headingLevel}` as const\r\n  const compact = variant === \"compact\"\r\n  return (\r\n    <>\r\n      {icon ? (\r\n        <span aria-hidden=\"true\" data-slot=\"empty-icon\" className={ICON_CLASS[variant]}>\r\n          {icon}\r\n        </span>\r\n      ) : null}\r\n      <div data-slot=\"empty-text\" className={TEXT_CLASS[variant]}>\r\n        <Heading className={cn(\"font-medium\", compact ? \"text-sm leading-5\" : \"text-[15px] leading-6\")}>{title}</Heading>\r\n        {description ? (\r\n          <p className={cn(\"text-muted-foreground\", compact ? \"text-[13px] leading-5\" : \"text-sm leading-5\")}>{description}</p>\r\n        ) : null}\r\n      </div>\r\n      {actions ? (\r\n        <div data-slot=\"empty-actions\" className={ACTIONS_CLASS[variant]}>\r\n          {actions}\r\n        </div>\r\n      ) : null}\r\n    </>\r\n  )\r\n}\r\n\r\n/** motion.div owns the drag and animation handlers, so the rest props have to be narrowed for it. */\r\ntype MotionRootProps = React.ComponentProps<typeof motion.div>\r\n\r\nfunction EmptyPresence(props: MotionRootProps) {\r\n  const reduceMotion = useReducedMotion() ?? false\r\n  const hidden = reduceMotion ? { opacity: 0 } : { opacity: 0, scale: ENTER_SCALE, filter: ENTER_BLUR }\r\n  const shown = reduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1, filter: \"blur(0px)\" }\r\n  return <motion.div initial={hidden} animate={shown} transition={reduceMotion ? INSTANT : SPRING_DEFAULT} {...props} />\r\n}\r\n\r\nfunction Empty({ variant = \"plain\", effect, icon, title, description, actions, headingLevel = 3, announce = false, className, ...props }: EmptyProps) {\r\n  const body = <EmptyBody {...{ variant, headingLevel, icon, title, description, actions }} />\r\n  const root = {\r\n    \"data-slot\": \"empty\",\r\n    \"data-variant\": variant,\r\n    \"data-effect\": effect,\r\n    role: announce ? \"status\" : undefined,\r\n    className: cn(\"w-full min-w-0\", ROOT_CLASS[variant], className),\r\n  }\r\n  if (effect === \"materialize\") {\r\n    return (\r\n      <EmptyPresence {...root} {...(props as MotionRootProps)}>\r\n        {body}\r\n      </EmptyPresence>\r\n    )\r\n  }\r\n  return (\r\n    <div {...root} {...props}>\r\n      {body}\r\n    </div>\r\n  )\r\n}\r\n\r\nexport { Empty, type EmptyProps, type EmptyVariant, type EmptyEffect, type EmptyHeadingLevel }\r\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}