{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "empty-states",
  "title": "Empty States",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/empty-states/EmptyStates.tsx",
      "content": "'use client'\n\nimport type { ComponentProps, ReactNode } from 'react'\nimport { RotateCcw } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/components/ui/button'\n\nexport type EmptyStateVariant =\n  | 'no-data'\n  | 'no-search-results'\n  | 'first-use'\n  | 'error-recovery'\n  | 'no-access'\n  | 'sync-complete'\n\nexport interface EmptyStatesProps {\n  /** `'all'` renders the full gallery; a single variant renders just that card. */\n  variant?: EmptyStateVariant | 'all'\n  size?: 'sm' | 'default'\n  layout?: 'grid' | 'list'\n  className?: string\n  title?: string\n  description?: string\n  /** Replaces the response panel on a single variant. Pass `''` to hide art. */\n  image?: string\n  imageAlt?: string\n  primaryLabel?: string\n  secondaryLabel?: string\n  primaryHref?: string\n  secondaryHref?: string\n  showPrimary?: boolean\n  showSecondary?: boolean\n  primaryDisabled?: boolean\n  secondaryDisabled?: boolean\n  onPrimary?: () => void\n  onSecondary?: () => void\n}\n\nconst copy = (fallback: string, override?: string) => (override === undefined ? fallback : override)\n\nfunction Action({\n  href,\n  onClick,\n  children,\n  ...buttonProps\n}: {\n  href?: string\n  onClick?: () => void\n  children: ReactNode\n} & Omit<ComponentProps<typeof Button>, 'onClick'>) {\n  if (href) {\n    return (\n      <Button asChild {...buttonProps}>\n        <a href={href} onClick={onClick}>\n          {children}\n        </a>\n      </Button>\n    )\n  }\n  return (\n    <Button {...buttonProps} onClick={onClick}>\n      {children}\n    </Button>\n  )\n}\n\nfunction ResponsePanel({\n  request,\n  status,\n  detail,\n  tone,\n}: {\n  request: string\n  status: string\n  detail: string\n  tone?: 'destructive' | 'warning'\n}) {\n  return (\n    <div className=\"border-border bg-muted/40 border-b font-mono\" aria-hidden=\"true\">\n      <div className=\"border-border text-muted-foreground truncate border-b px-3 py-2 text-xs\">{request}</div>\n      <div className=\"space-y-1 p-3 text-xs leading-relaxed\">\n        <p>\n          <span className=\"text-muted-foreground\">HTTP/1.1</span>{' '}\n          {tone === 'destructive' ? (\n            <span className=\"text-destructive\">{status}</span>\n          ) : tone === 'warning' ? (\n            <span className=\"text-warning\">{status}</span>\n          ) : (\n            <>{status}</>\n          )}\n        </p>\n        <p className=\"text-muted-foreground\">{detail}</p>\n      </div>\n    </div>\n  )\n}\n\nexport function EmptyStates({\n  variant = 'all',\n  size = 'default',\n  layout = 'grid',\n  className,\n  title,\n  description,\n  image,\n  imageAlt = '',\n  primaryLabel,\n  secondaryLabel,\n  primaryHref,\n  secondaryHref,\n  showPrimary = true,\n  showSecondary = true,\n  primaryDisabled = false,\n  secondaryDisabled = false,\n  onPrimary,\n  onSecondary,\n}: EmptyStatesProps) {\n  const show = (v: EmptyStateVariant) => variant === 'all' || variant === v\n  const pick = (v: EmptyStateVariant, fallback: string, override?: string) =>\n    variant === v ? copy(fallback, override) : fallback\n  const defaults: Record<EmptyStateVariant, string> = {\n    'no-data': '/illustrations/empty-no-data.jpg',\n    'no-search-results': '/illustrations/empty-no-search.jpg',\n    'first-use': '/illustrations/empty-first-use.jpg',\n    'error-recovery': '/illustrations/empty-error-recovery.jpg',\n    'no-access': '/illustrations/empty-no-access.jpg',\n    'sync-complete': '/illustrations/empty-sync-complete.jpg',\n  }\n  const art = (v: EmptyStateVariant) => (variant === v ? copy(defaults[v], image) : defaults[v])\n\n  const cardClass = cn(\n    'border-border bg-card overflow-hidden rounded-xl border text-left',\n    size === 'sm' && 'rounded-lg',\n    layout === 'list' && 'w-full max-w-md',\n  )\n  const imgClass = cn('w-full object-cover', size === 'sm' ? 'aspect-[16/9]' : 'aspect-[16/10]')\n  const bodyClass = size === 'sm' ? 'p-5' : 'p-6'\n  const titleClass = 'text-sm font-medium'\n  const descClass = 'text-muted-foreground mt-1 max-w-xs text-sm'\n  const actionsClass = cn('flex flex-wrap items-center gap-2', size === 'sm' ? 'mt-4' : 'mt-5')\n  const actionSize = size === 'sm' ? ('xs' as const) : ('sm' as const)\n\n  return (\n    <div\n      data-slot=\"empty-states\"\n      data-layout={layout}\n      className={cn(\n        layout === 'list' ? 'flex flex-col items-center gap-4' : 'grid gap-4 sm:grid-cols-2 xl:grid-cols-3',\n        className,\n      )}\n    >\n      {show('no-data') && (\n        <article className={cardClass} data-slot=\"empty-states-card\" data-variant=\"no-data\" role=\"status\">\n          {art('no-data') ? (\n            <img src={art('no-data')} alt={imageAlt} className={imgClass} />\n          ) : art('no-data') === undefined ? (\n            <ResponsePanel request=\"GET /v1/projects\" status=\"200 OK\" detail='{ \"data\": [], \"total\": 0 }' />\n          ) : null}\n          <div className={bodyClass}>\n            <h3 className={titleClass}>{pick('no-data', 'Nothing here yet', title)}</h3>\n            <p className={descClass}>\n              {pick(\n                'no-data',\n                'Your workspace is empty. Create your first project or import existing data to get started.',\n                description,\n              )}\n            </p>\n            {showPrimary || showSecondary ? (\n              <div className={actionsClass}>\n                {showPrimary ? (\n                  <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onPrimary}>\n                    {pick('no-data', 'Create', primaryLabel)}\n                  </Action>\n                ) : null}\n                {showSecondary ? (\n                  <Action\n                    href={secondaryHref}\n                    size={actionSize}\n                    variant=\"ghost\"\n                    disabled={secondaryDisabled}\n                    onClick={onSecondary}\n                  >\n                    {pick('no-data', 'Import data', secondaryLabel)}\n                  </Action>\n                ) : null}\n              </div>\n            ) : null}\n          </div>\n        </article>\n      )}\n\n      {show('no-search-results') && (\n        <article className={cardClass} data-slot=\"empty-states-card\" data-variant=\"no-search-results\" role=\"status\">\n          {art('no-search-results') ? (\n            <img src={art('no-search-results')} alt={imageAlt} className={imgClass} />\n          ) : art('no-search-results') === undefined ? (\n            <ResponsePanel request=\"GET /search?q=northwind\" status=\"200 OK\" detail='{ \"hits\": 0 }' />\n          ) : null}\n          <div className={bodyClass}>\n            <h3 className={titleClass}>{pick('no-search-results', 'No matches', title)}</h3>\n            <p className={descClass}>\n              {pick(\n                'no-search-results',\n                'Nothing matched your current filters. Try different keywords or broaden the search.',\n                description,\n              )}\n            </p>\n            {showPrimary || showSecondary ? (\n              <div className={actionsClass}>\n                {showPrimary ? (\n                  <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onPrimary}>\n                    {pick('no-search-results', 'Clear filters', primaryLabel)}\n                  </Action>\n                ) : null}\n                {showSecondary ? (\n                  <Action\n                    href={secondaryHref}\n                    size={actionSize}\n                    variant=\"ghost\"\n                    disabled={secondaryDisabled}\n                    onClick={onSecondary}\n                  >\n                    {pick('no-search-results', 'View all', secondaryLabel)}\n                  </Action>\n                ) : null}\n              </div>\n            ) : null}\n          </div>\n        </article>\n      )}\n\n      {show('first-use') && (\n        <article className={cardClass} data-slot=\"empty-states-card\" data-variant=\"first-use\" role=\"status\">\n          {art('first-use') ? (\n            <img src={art('first-use')} alt={imageAlt} className={imgClass} />\n          ) : art('first-use') === undefined ? (\n            <ResponsePanel request=\"GET /v1/workspace\" status=\"200 OK\" detail='{ \"onboarded\": false }' />\n          ) : null}\n          <div className={bodyClass}>\n            <h3 className={titleClass}>{pick('first-use', 'Welcome to Acme', title)}</h3>\n            <p className={descClass}>\n              {pick(\n                'first-use',\n                'This is your new dashboard. Set up your workspace and invite the team to start collaborating.',\n                description,\n              )}\n            </p>\n            {showPrimary || showSecondary ? (\n              <div className={actionsClass}>\n                {showPrimary ? (\n                  <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onPrimary}>\n                    {pick('first-use', 'Get started', primaryLabel)}\n                  </Action>\n                ) : null}\n                {showSecondary ? (\n                  <Action\n                    href={secondaryHref}\n                    size={actionSize}\n                    variant=\"ghost\"\n                    disabled={secondaryDisabled}\n                    onClick={onSecondary}\n                  >\n                    {pick('first-use', 'Take a tour', secondaryLabel)}\n                  </Action>\n                ) : null}\n              </div>\n            ) : null}\n          </div>\n        </article>\n      )}\n\n      {show('error-recovery') && (\n        <article className={cardClass} data-slot=\"empty-states-card\" data-variant=\"error-recovery\" role=\"alert\">\n          {art('error-recovery') ? (\n            <img src={art('error-recovery')} alt={imageAlt} className={imgClass} />\n          ) : art('error-recovery') === undefined ? (\n            <ResponsePanel\n              request=\"GET /api/projects\"\n              status=\"500 Internal Server Error\"\n              detail=\"error: upstream_timeout\"\n              tone=\"destructive\"\n            />\n          ) : null}\n          <div className={bodyClass}>\n            <h3 className={titleClass}>{pick('error-recovery', 'Something broke', title)}</h3>\n            <p className={descClass}>\n              {pick(\n                'error-recovery',\n                \"We couldn't load your data. Your work is safe — check the connection and try again.\",\n                description,\n              )}\n            </p>\n            {showPrimary || showSecondary ? (\n              <div className={actionsClass}>\n                {showPrimary ? (\n                  <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onPrimary}>\n                    <RotateCcw />\n                    {pick('error-recovery', 'Retry', primaryLabel)}\n                  </Action>\n                ) : null}\n                {showSecondary ? (\n                  <Action\n                    href={secondaryHref}\n                    size={actionSize}\n                    variant=\"ghost\"\n                    disabled={secondaryDisabled}\n                    onClick={onSecondary}\n                  >\n                    {pick('error-recovery', 'Contact support', secondaryLabel)}\n                  </Action>\n                ) : null}\n              </div>\n            ) : null}\n          </div>\n        </article>\n      )}\n\n      {show('no-access') && (\n        <article className={cardClass} data-slot=\"empty-states-card\" data-variant=\"no-access\" role=\"status\">\n          {art('no-access') ? (\n            <img src={art('no-access')} alt={imageAlt} className={imgClass} />\n          ) : art('no-access') === undefined ? (\n            <ResponsePanel\n              request=\"GET /settings/billing\"\n              status=\"403 Forbidden\"\n              detail=\"scope billing:read — missing\"\n              tone=\"warning\"\n            />\n          ) : null}\n          <div className={bodyClass}>\n            <h3 className={titleClass}>{pick('no-access', 'Ask your admin', title)}</h3>\n            <p className={descClass}>\n              {pick(\n                'no-access',\n                \"You don't have access to this workspace yet. Request permission and we'll notify you once it's granted.\",\n                description,\n              )}\n            </p>\n            {showPrimary || showSecondary ? (\n              <div className={actionsClass}>\n                {showPrimary ? (\n                  <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onPrimary}>\n                    {pick('no-access', 'Request access', primaryLabel)}\n                  </Action>\n                ) : null}\n                {showSecondary ? (\n                  <Action\n                    href={secondaryHref}\n                    size={actionSize}\n                    variant=\"ghost\"\n                    disabled={secondaryDisabled}\n                    onClick={onSecondary}\n                  >\n                    {pick('no-access', 'Switch account', secondaryLabel)}\n                  </Action>\n                ) : null}\n              </div>\n            ) : null}\n          </div>\n        </article>\n      )}\n\n      {show('sync-complete') && (\n        <article className={cardClass} data-slot=\"empty-states-card\" data-variant=\"sync-complete\" role=\"status\">\n          {art('sync-complete') ? (\n            <img src={art('sync-complete')} alt={imageAlt} className={imgClass} />\n          ) : art('sync-complete') === undefined ? (\n            <ResponsePanel request=\"POST /sync\" status=\"200 OK\" detail='{ \"synced\": 128, \"lag_ms\": 40 }' />\n          ) : null}\n          <div className={bodyClass}>\n            <h3 className={titleClass}>{pick('sync-complete', 'Everything synced', title)}</h3>\n            <p className={descClass}>\n              {pick(\n                'sync-complete',\n                'All changes were uploaded a few seconds ago. We will keep syncing in the background.',\n                description,\n              )}\n            </p>\n            {showPrimary || showSecondary ? (\n              <div className={actionsClass}>\n                {showSecondary ? (\n                  <Action\n                    href={secondaryHref}\n                    size={actionSize}\n                    variant=\"ghost\"\n                    disabled={secondaryDisabled}\n                    onClick={onSecondary}\n                  >\n                    {pick('sync-complete', 'View activity', secondaryLabel)}\n                  </Action>\n                ) : null}\n              </div>\n            ) : null}\n          </div>\n        </article>\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/EmptyStates.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/button.json"
  ],
  "description": "Gallery of six zero-data cards — no data, no search results, first-use, error recovery, no access, and sync complete — each with a matching isometric illustration, title, copy, and action pair. `variant` picks a single card, `size` compacts it, `layout` switches grid vs list. Override title, description, image, labels, hrefs, and callbacks. Pass `image=\"\"` to hide the art.",
  "categories": [
    "layout",
    "feedback",
    "display"
  ]
}