{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "empty-state",
  "title": "Empty State",
  "description": "A full empty-state layout combining a featured icon, message and call to action.",
  "dependencies": [
    "@untitledui/icons@^0.0.22"
  ],
  "registryDependencies": [
    "@nx-ui/background-pattern",
    "@nx-ui/featured-icon",
    "@nx-ui/flare-styles",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/empty-state.tsx",
      "content": "\"use client\"\n\n// @registry-dep: flare-styles\n\nimport * as React from \"react\"\nimport { SearchLg } from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { BackgroundPattern } from \"@/components/ui/background-pattern\"\nimport type { PatternName, PatternSize } from \"@/components/ui/background-pattern\"\nimport { FeaturedIcon as FeaturedIconBase } from \"@/components/blocks/featured-icon\"\n\n/**\n * EmptyState — the signature Untitled UI empty state, where the flare lives in\n * the header: a radially-masked BackgroundPattern (rings / grid / dots) sitting\n * behind a FeaturedIcon, or one of three avatar decorations (concentric ring\n * orbits, a symmetric row, or scrolling marquee grid) for \"invite / community\"\n * states.\n *\n * Compound API — compose the parts you need:\n *   <EmptyState size=\"lg\">\n *     <EmptyState.Header>\n *       <EmptyState.FeaturedIcon icon={SearchLg} />\n *     </EmptyState.Header>\n *     <EmptyState.Content>\n *       <EmptyState.Title>No results</EmptyState.Title>\n *       <EmptyState.Description>…</EmptyState.Description>\n *     </EmptyState.Content>\n *     <EmptyState.Footer>…buttons…</EmptyState.Footer>\n *   </EmptyState>\n *\n * The avatar decorations rely on the `animate-marquee` utility from the\n * `flare-styles` registry item — install it alongside this block.\n */\n\ntype RootSize = \"sm\" | \"md\" | \"lg\"\n\nconst RootContext = React.createContext<{ size: RootSize }>({ size: \"lg\" })\n\nfunction Root({\n  size = \"lg\",\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: RootSize }) {\n  return (\n    <RootContext.Provider value={{ size }}>\n      <div\n        data-slot=\"empty-state\"\n        data-size={size}\n        // `relative isolate` is the stacking boundary the -z-10 header flare\n        // resolves against (see background-pattern's paint-behind contract).\n        // It must live HERE, not on the header: the flare deliberately bleeds\n        // behind the title/description/actions, so the isolating ancestor has\n        // to contain them, or the flare would paint on top of the text.\n        className={cn(\n          \"relative isolate mx-auto flex w-full max-w-lg flex-col items-center justify-center\",\n          className\n        )}\n        {...props}\n      />\n    </RootContext.Provider>\n  )\n}\n\nfunction FeaturedIcon({\n  color = \"gray\",\n  theme = \"modern\",\n  icon = SearchLg,\n  size,\n  ...props\n}: React.ComponentProps<typeof FeaturedIconBase>) {\n  const { size: rootSize } = React.useContext(RootContext)\n  const resolved = size ?? (rootSize === \"lg\" ? \"xl\" : \"lg\")\n  return (\n    <FeaturedIconBase\n      icon={icon}\n      color={color}\n      theme={theme}\n      size={resolved}\n      className=\"relative z-[1]\"\n      {...props}\n    />\n  )\n}\n\nfunction Header({\n  pattern = \"circle\",\n  patternSize = \"md\",\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  pattern?: \"none\" | PatternName\n  patternSize?: PatternSize\n}) {\n  const { size } = React.useContext(RootContext)\n  return (\n    <header\n      data-slot=\"empty-state-header\"\n      // Plain `relative` (positioning only): the -z-10 pattern / avatar\n      // decorations resolve against the Root's `isolate` so they paint behind\n      // the content sections too. The flare is bigger than the header on\n      // purpose — the consumer clips it with `overflow-hidden` on the card /\n      // section / canvas that owns the visual bounds.\n      className={cn(\n        \"relative mb-4 flex items-center justify-center\",\n        (size === \"md\" || size === \"lg\") && \"mb-5\",\n        className\n      )}\n      {...props}\n    >\n      {pattern !== \"none\" && (\n        <BackgroundPattern\n          pattern={pattern}\n          size={patternSize}\n          className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\"\n        />\n      )}\n      {children}\n    </header>\n  )\n}\n\nfunction Content({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { size } = React.useContext(RootContext)\n  return (\n    <div\n      data-slot=\"empty-state-content\"\n      className={cn(\n        \"z-[1] mb-6 flex w-full max-w-88 flex-col items-center justify-center gap-1\",\n        (size === \"md\" || size === \"lg\") && \"mb-8 gap-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction Footer({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-state-footer\"\n      className={cn(\"z-[1] flex flex-col gap-3 sm:flex-row\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction Title({ className, ...props }: React.ComponentProps<\"h2\">) {\n  const { size } = React.useContext(RootContext)\n  return (\n    <h2\n      data-slot=\"empty-state-title\"\n      className={cn(\n        \"text-center font-semibold text-text-primary\",\n        size === \"sm\" && \"text-md\",\n        size === \"md\" && \"text-lg\",\n        size === \"lg\" && \"text-xl\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction Description({ className, ...props }: React.ComponentProps<\"p\">) {\n  const { size } = React.useContext(RootContext)\n  return (\n    <p\n      data-slot=\"empty-state-description\"\n      className={cn(\n        \"text-center text-sm text-text-tertiary\",\n        size === \"lg\" && \"text-md\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Avatar decorations\n// ---------------------------------------------------------------------------\n\ntype AvatarItem = { src: string; alt?: string }\n\n/** Avatar slots positioned on concentric rings — {ring radius, CSS angle}. */\nconst avatarSlots = [\n  { ring: 80, angle: 330, size: \"size-8\" },\n  { ring: 80, angle: 56, size: \"size-8\" },\n  { ring: 144, angle: 82, size: \"size-7\" },\n  { ring: 144, angle: 299, size: \"size-7\" },\n  { ring: 144, angle: 241, size: \"size-7\" },\n  { ring: 176, angle: 64, size: \"size-6\" },\n  { ring: 176, angle: 270, size: \"size-6\" },\n  { ring: 144, angle: 112, size: \"size-7\" },\n  { ring: 80, angle: 249, size: \"size-8\" },\n] as const\n\nconst RING_RADII = [48, 80, 112, 144, 176]\n\nfunction AvatarRadius({\n  avatars = [],\n  className,\n  style,\n  ...props\n}: React.ComponentProps<\"div\"> & { avatars?: AvatarItem[] }) {\n  return (\n    <div\n      aria-hidden=\"true\"\n      data-slot=\"empty-state-avatar-radius\"\n      className={cn(\n        \"pointer-events-none absolute top-1/2 left-1/2 -z-10 size-120 -translate-x-1/2 -translate-y-1/2\",\n        className\n      )}\n      style={{\n        maskImage: \"radial-gradient(circle, black 10%, transparent 70%)\",\n        WebkitMaskImage: \"radial-gradient(circle, black 10%, transparent 70%)\",\n        ...style,\n      }}\n      {...props}\n    >\n      {RING_RADII.map((radius) => (\n        <div\n          key={radius}\n          className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-border-secondary\"\n          style={{ width: radius * 2, height: radius * 2 }}\n        />\n      ))}\n      {avatars.slice(0, avatarSlots.length).map((avatar, i) => {\n        const slot = avatarSlots[i]\n        const rad = (slot.angle * Math.PI) / 180\n        // Round the trig offsets: Math.sin/cos can differ in the last ULP\n        // between the SSR (Node) and browser V8 engines, which would otherwise\n        // serialise to different `transform` strings and trip a hydration\n        // mismatch. Two decimals is sub-pixel and byte-stable across engines.\n        const x = Math.round(Math.sin(rad) * slot.ring * 100) / 100\n        const y = Math.round(-Math.cos(rad) * slot.ring * 100) / 100\n        return (\n          <div\n            key={i}\n            className={cn(\n              \"absolute top-1/2 left-1/2 rounded-full bg-bg-primary p-px shadow-xs ring-[0.5px] ring-black/10\",\n              slot.size\n            )}\n            style={{\n              transform: `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))`,\n            }}\n          >\n            {/* eslint-disable-next-line @next/next/no-img-element */}\n            <img\n              src={avatar.src}\n              alt=\"\"\n              className=\"size-full rounded-full object-cover outline-[0.5px] -outline-offset-[0.5px] outline-black/16\"\n            />\n          </div>\n        )\n      })}\n    </div>\n  )\n}\n\nconst rowStyles: Record<RootSize, string[]> = {\n  sm: [\"size-8 rounded-md\", \"size-9 rounded-[7px]\", \"size-10 rounded-lg\", \"size-11 rounded-[9px]\"],\n  md: [\"size-10 rounded-lg\", \"size-11 rounded-[9px]\", \"size-12 rounded-[10px]\"],\n  lg: [\"size-10 rounded-lg\", \"size-11 rounded-[9px]\", \"size-12 rounded-[10px]\"],\n}\n\nfunction renderRowAvatar(avatar: AvatarItem, sizeClass: string, key: number) {\n  return (\n    <div\n      key={key}\n      className={cn(\n        \"relative shrink-0 overflow-hidden outline-[0.5px] -outline-offset-[0.5px] outline-black/16 before:absolute before:inset-0 before:rounded-[inherit] before:border before:border-white/32 before:mask-[linear-gradient(to_bottom,black_0%,transparent_25%,transparent_75%,black_100%)]\",\n        sizeClass\n      )}\n    >\n      {/* eslint-disable-next-line @next/next/no-img-element */}\n      <img src={avatar.src} alt={avatar.alt || \"\"} className=\"size-full object-cover\" />\n    </div>\n  )\n}\n\nfunction AvatarRow({\n  avatars = [],\n  children,\n  className,\n  style,\n  ...props\n}: React.ComponentProps<\"div\"> & { avatars?: AvatarItem[] }) {\n  const { size } = React.useContext(RootContext)\n  const sizes = rowStyles[size]\n  const count = sizes.length\n  const left = avatars.slice(0, count)\n  const right = avatars.slice(count, count * 2)\n\n  return (\n    <div\n      aria-hidden=\"true\"\n      data-slot=\"empty-state-avatar-row\"\n      className={cn(\n        \"-m-1 flex items-center justify-center p-1\",\n        size === \"sm\" ? \"gap-4\" : size === \"md\" ? \"gap-5\" : \"gap-6\",\n        className\n      )}\n      style={{\n        maskImage: \"radial-gradient(circle, black 5%, transparent 105%)\",\n        WebkitMaskImage: \"radial-gradient(circle, black 5%, transparent 105%)\",\n        ...style,\n      }}\n      {...props}\n    >\n      <div className={cn(\"flex items-center\", size === \"sm\" ? \"gap-3\" : \"gap-4\")}>\n        {left.map((a, i) => renderRowAvatar(a, sizes[i], i))}\n      </div>\n      {children}\n      <div className={cn(\"flex items-center\", size === \"sm\" ? \"gap-3\" : \"gap-4\")}>\n        {right.map((a, i) => renderRowAvatar(a, sizes[count - 1 - i], i + count))}\n      </div>\n    </div>\n  )\n}\n\nconst gridStyles: Record<RootSize, { avatar: string; inner: string; gap: string; rowGap: string }> = {\n  sm: { avatar: \"size-8 rounded-md\", inner: \"rounded-[5px]\", gap: \"gap-2 pl-2\", rowGap: \"gap-2\" },\n  md: { avatar: \"size-10 rounded-lg\", inner: \"rounded-[7px]\", gap: \"gap-3 pl-3\", rowGap: \"gap-3\" },\n  lg: { avatar: \"size-12 rounded-[10px]\", inner: \"rounded-[9px]\", gap: \"gap-3 pl-3\", rowGap: \"gap-3\" },\n}\n\nfunction AvatarGrid({\n  avatars = [],\n  className,\n  style,\n  ...props\n}: React.ComponentProps<\"div\"> & { avatars?: AvatarItem[] }) {\n  const { size } = React.useContext(RootContext)\n  const config = gridStyles[size]\n  const mid = Math.ceil(avatars.length / 2)\n  const row1Base = avatars.slice(0, mid)\n  const row2Base = avatars.slice(mid)\n  const row1 = [...row1Base, ...row1Base, ...row1Base]\n  const row2 = [...row2Base, ...row2Base, ...row2Base]\n\n  const renderGridAvatar = (avatar: AvatarItem, key: number) => (\n    <div\n      key={key}\n      className={cn(\n        \"shrink-0 bg-bg-primary p-px shadow-xs ring-[0.75px] ring-black/10\",\n        config.avatar\n      )}\n    >\n      <div\n        className={cn(\n          \"relative size-full overflow-hidden outline-[0.5px] -outline-offset-[0.5px] outline-black/16 before:absolute before:inset-0 before:rounded-[inherit] before:border before:border-white/32 before:mask-[linear-gradient(to_bottom,black_0%,transparent_25%,transparent_75%,black_100%)]\",\n          config.inner\n        )}\n      >\n        {/* eslint-disable-next-line @next/next/no-img-element */}\n        <img src={avatar.src} alt={avatar.alt || \"\"} className=\"size-full object-cover\" />\n      </div>\n    </div>\n  )\n\n  return (\n    <div\n      aria-hidden=\"true\"\n      data-slot=\"empty-state-avatar-grid\"\n      className={cn(\n        \"-m-1 flex max-w-lg flex-col items-center overflow-x-clip p-1\",\n        config.rowGap,\n        className\n      )}\n      style={{\n        maskImage: \"radial-gradient(circle, black 10%, transparent 100%)\",\n        WebkitMaskImage: \"radial-gradient(circle, black 10%, transparent 100%)\",\n        ...style,\n      }}\n      {...props}\n    >\n      <div className=\"flex\">\n        <div className={cn(\"flex w-auto max-w-none shrink-0 animate-marquee motion-reduce:animate-none\", config.gap)}>\n          {row1.map((a, i) => renderGridAvatar(a, i))}\n        </div>\n        <div className={cn(\"flex w-auto max-w-none shrink-0 animate-marquee motion-reduce:animate-none\", config.gap)}>\n          {row1.map((a, i) => renderGridAvatar(a, i))}\n        </div>\n      </div>\n      <div className=\"flex\">\n        <div className={cn(\"flex w-auto max-w-none shrink-0 animate-marquee direction-reverse motion-reduce:animate-none\", config.gap)}>\n          {row2.map((a, i) => renderGridAvatar(a, i + mid))}\n        </div>\n        <div className={cn(\"flex w-auto max-w-none shrink-0 animate-marquee direction-reverse motion-reduce:animate-none\", config.gap)}>\n          {row2.map((a, i) => renderGridAvatar(a, i + mid))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nconst EmptyState = Object.assign(Root, {\n  Header,\n  FeaturedIcon,\n  Content,\n  Footer,\n  Title,\n  Description,\n  AvatarRadius,\n  AvatarRow,\n  AvatarGrid,\n})\n\nexport { EmptyState }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}