{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "empty-states",
  "title": "Empty States",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-svelte/blocks/empty-states/EmptyStates.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Component } from 'svelte'\n\n  export type EmptyStateVariant =\n    | 'no-data'\n    | 'no-search-results'\n    | 'first-use'\n    | 'error-recovery'\n    | 'no-access'\n    | 'sync-complete'\n\n  export 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    class?: 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</script>\n\n<script lang=\"ts\">\n  import { RotateCcw } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { Button, buttonVariants, type ButtonVariants } from '$lib/components/ui/button'\n\n  let {\n    variant = 'all',\n    size = 'default',\n    layout = 'grid',\n    class: 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 = $props()\n\n  const show = (v: EmptyStateVariant) => variant === 'all' || variant === v\n  const copy = (fallback: string, override?: string) => (override === undefined ? fallback : override)\n  const forVariant = (v: EmptyStateVariant, fallback: string, override?: string) =>\n    variant === v ? copy(fallback, override) : fallback\n\n  const titleNoData = $derived(forVariant('no-data', 'Nothing here yet', title))\n  const descNoData = $derived(\n    forVariant(\n      'no-data',\n      'Your workspace is empty. Create your first project or import existing data to get started.',\n      description,\n    ),\n  )\n  const imageNoData = $derived(forVariant('no-data', '/illustrations/empty-no-data.jpg', image))\n  const primaryNoData = $derived(forVariant('no-data', 'Create', primaryLabel))\n  const secondaryNoData = $derived(forVariant('no-data', 'Import data', secondaryLabel))\n\n  const titleSearch = $derived(forVariant('no-search-results', 'No matches', title))\n  const descSearch = $derived(\n    forVariant(\n      'no-search-results',\n      'Nothing matched your current filters. Try different keywords or broaden the search.',\n      description,\n    ),\n  )\n  const imageSearch = $derived(forVariant('no-search-results', '/illustrations/empty-no-search.jpg', image))\n  const primarySearch = $derived(forVariant('no-search-results', 'Clear filters', primaryLabel))\n  const secondarySearch = $derived(forVariant('no-search-results', 'View all', secondaryLabel))\n\n  const titleFirst = $derived(forVariant('first-use', 'Welcome to Acme', title))\n  const descFirst = $derived(\n    forVariant(\n      'first-use',\n      'This is your new dashboard. Set up your workspace and invite the team to start collaborating.',\n      description,\n    ),\n  )\n  const imageFirst = $derived(forVariant('first-use', '/illustrations/empty-first-use.jpg', image))\n  const primaryFirst = $derived(forVariant('first-use', 'Get started', primaryLabel))\n  const secondaryFirst = $derived(forVariant('first-use', 'Take a tour', secondaryLabel))\n\n  const titleError = $derived(forVariant('error-recovery', 'Something broke', title))\n  const descError = $derived(\n    forVariant(\n      'error-recovery',\n      \"We couldn't load your data. Your work is safe — check the connection and try again.\",\n      description,\n    ),\n  )\n  const imageError = $derived(forVariant('error-recovery', '/illustrations/empty-error-recovery.jpg', image))\n  const primaryError = $derived(forVariant('error-recovery', 'Retry', primaryLabel))\n  const secondaryError = $derived(forVariant('error-recovery', 'Contact support', secondaryLabel))\n\n  const titleAccess = $derived(forVariant('no-access', 'Ask your admin', title))\n  const descAccess = $derived(\n    forVariant(\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  )\n  const imageAccess = $derived(forVariant('no-access', '/illustrations/empty-no-access.jpg', image))\n  const primaryAccess = $derived(forVariant('no-access', 'Request access', primaryLabel))\n  const secondaryAccess = $derived(forVariant('no-access', 'Switch account', secondaryLabel))\n\n  const titleSync = $derived(forVariant('sync-complete', 'Everything synced', title))\n  const descSync = $derived(\n    forVariant(\n      'sync-complete',\n      'All changes were uploaded a few seconds ago. We will keep syncing in the background.',\n      description,\n    ),\n  )\n  const imageSync = $derived(forVariant('sync-complete', '/illustrations/empty-sync-complete.jpg', image))\n  const secondarySync = $derived(forVariant('sync-complete', 'View activity', secondaryLabel))\n\n  const cardClass = $derived(\n    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  )\n  const imgClass = $derived(cn('w-full object-cover', size === 'sm' ? 'aspect-[16/9]' : 'aspect-[16/10]'))\n  const panelClass = 'border-border bg-muted/40 border-b font-mono'\n  const bodyClass = $derived(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 = $derived(cn('flex flex-wrap items-center gap-2', size === 'sm' ? 'mt-4' : 'mt-5'))\n  const actionSize = $derived(size === 'sm' ? 'xs' : 'sm')\n</script>\n\n{#snippet actionButton(\n  href: string | undefined,\n  label: string,\n  variantName: ButtonVariants['variant'] = 'default',\n  disabled = false,\n  onclick?: () => void,\n  Icon?: Component,\n)}\n  {#if href}\n    <a {href} class={buttonVariants({ variant: variantName, size: actionSize })} {onclick} aria-disabled={disabled}>\n      {#if Icon}\n        <Icon />\n      {/if}\n      {label}\n    </a>\n  {:else}\n    <Button variant={variantName} size={actionSize} {disabled} {onclick}>\n      {#if Icon}\n        <Icon />\n      {/if}\n      {label}\n    </Button>\n  {/if}\n{/snippet}\n\n<div\n  data-slot=\"empty-states\"\n  data-layout={layout}\n  class={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  {#if show('no-data')}\n    <article class={cardClass} data-slot=\"empty-states-card\" data-variant=\"no-data\" role=\"status\">\n      {#if imageNoData}\n        <img src={imageNoData} alt={imageAlt} class={imgClass} />\n      {:else if imageNoData === undefined}\n        <div class={panelClass} aria-hidden=\"true\">\n          <div class=\"border-border text-muted-foreground truncate border-b px-3 py-2 text-xs\">GET /v1/projects</div>\n          <div class=\"space-y-1 p-3 text-xs leading-relaxed\">\n            <p><span class=\"text-muted-foreground\">HTTP/1.1</span> 200 OK</p>\n            <p class=\"text-muted-foreground\">{'{ \"data\": [], \"total\": 0 }'}</p>\n          </div>\n        </div>\n      {/if}\n      <div class={bodyClass}>\n        <h3 class={titleClass}>{titleNoData}</h3>\n        <p class={descClass}>{descNoData}</p>\n        {#if showPrimary || showSecondary}\n          <div class={actionsClass}>\n            {#if showPrimary}\n              {@render actionButton(primaryHref, primaryNoData, 'default', primaryDisabled, onPrimary)}\n            {/if}\n            {#if showSecondary}\n              {@render actionButton(secondaryHref, secondaryNoData, 'ghost', secondaryDisabled, onSecondary)}\n            {/if}\n          </div>\n        {/if}\n      </div>\n    </article>\n  {/if}\n\n  {#if show('no-search-results')}\n    <article class={cardClass} data-slot=\"empty-states-card\" data-variant=\"no-search-results\" role=\"status\">\n      {#if imageSearch}\n        <img src={imageSearch} alt={imageAlt} class={imgClass} />\n      {:else if imageSearch === undefined}\n        <div class={panelClass} aria-hidden=\"true\">\n          <div class=\"border-border text-muted-foreground truncate border-b px-3 py-2 text-xs\">\n            GET /search?q=northwind\n          </div>\n          <div class=\"space-y-1 p-3 text-xs leading-relaxed\">\n            <p><span class=\"text-muted-foreground\">HTTP/1.1</span> 200 OK</p>\n            <p class=\"text-muted-foreground\">{'{ \"hits\": 0 }'}</p>\n          </div>\n        </div>\n      {/if}\n      <div class={bodyClass}>\n        <h3 class={titleClass}>{titleSearch}</h3>\n        <p class={descClass}>{descSearch}</p>\n        {#if showPrimary || showSecondary}\n          <div class={actionsClass}>\n            {#if showPrimary}\n              {@render actionButton(primaryHref, primarySearch, 'default', primaryDisabled, onPrimary)}\n            {/if}\n            {#if showSecondary}\n              {@render actionButton(secondaryHref, secondarySearch, 'ghost', secondaryDisabled, onSecondary)}\n            {/if}\n          </div>\n        {/if}\n      </div>\n    </article>\n  {/if}\n\n  {#if show('first-use')}\n    <article class={cardClass} data-slot=\"empty-states-card\" data-variant=\"first-use\" role=\"status\">\n      {#if imageFirst}\n        <img src={imageFirst} alt={imageAlt} class={imgClass} />\n      {:else if imageFirst === undefined}\n        <div class={panelClass} aria-hidden=\"true\">\n          <div class=\"border-border text-muted-foreground truncate border-b px-3 py-2 text-xs\">GET /v1/workspace</div>\n          <div class=\"space-y-1 p-3 text-xs leading-relaxed\">\n            <p><span class=\"text-muted-foreground\">HTTP/1.1</span> 200 OK</p>\n            <p class=\"text-muted-foreground\">{'{ \"onboarded\": false }'}</p>\n          </div>\n        </div>\n      {/if}\n      <div class={bodyClass}>\n        <h3 class={titleClass}>{titleFirst}</h3>\n        <p class={descClass}>{descFirst}</p>\n        {#if showPrimary || showSecondary}\n          <div class={actionsClass}>\n            {#if showPrimary}\n              {@render actionButton(primaryHref, primaryFirst, 'default', primaryDisabled, onPrimary)}\n            {/if}\n            {#if showSecondary}\n              {@render actionButton(secondaryHref, secondaryFirst, 'ghost', secondaryDisabled, onSecondary)}\n            {/if}\n          </div>\n        {/if}\n      </div>\n    </article>\n  {/if}\n\n  {#if show('error-recovery')}\n    <article class={cardClass} data-slot=\"empty-states-card\" data-variant=\"error-recovery\" role=\"alert\">\n      {#if imageError}\n        <img src={imageError} alt={imageAlt} class={imgClass} />\n      {:else if imageError === undefined}\n        <div class={panelClass} aria-hidden=\"true\">\n          <div class=\"border-border text-muted-foreground truncate border-b px-3 py-2 text-xs\">GET /api/projects</div>\n          <div class=\"space-y-1 p-3 text-xs leading-relaxed\">\n            <p>\n              <span class=\"text-muted-foreground\">HTTP/1.1</span>\n              <span class=\"text-destructive\">500 Internal Server Error</span>\n            </p>\n            <p class=\"text-muted-foreground\">error: upstream_timeout</p>\n          </div>\n        </div>\n      {/if}\n      <div class={bodyClass}>\n        <h3 class={titleClass}>{titleError}</h3>\n        <p class={descClass}>{descError}</p>\n        {#if showPrimary || showSecondary}\n          <div class={actionsClass}>\n            {#if showPrimary}\n              {@render actionButton(primaryHref, primaryError, 'default', primaryDisabled, onPrimary, RotateCcw)}\n            {/if}\n            {#if showSecondary}\n              {@render actionButton(secondaryHref, secondaryError, 'ghost', secondaryDisabled, onSecondary)}\n            {/if}\n          </div>\n        {/if}\n      </div>\n    </article>\n  {/if}\n\n  {#if show('no-access')}\n    <article class={cardClass} data-slot=\"empty-states-card\" data-variant=\"no-access\" role=\"status\">\n      {#if imageAccess}\n        <img src={imageAccess} alt={imageAlt} class={imgClass} />\n      {:else if imageAccess === undefined}\n        <div class={panelClass} aria-hidden=\"true\">\n          <div class=\"border-border text-muted-foreground truncate border-b px-3 py-2 text-xs\">GET /settings/billing</div>\n          <div class=\"space-y-1 p-3 text-xs leading-relaxed\">\n            <p>\n              <span class=\"text-muted-foreground\">HTTP/1.1</span>\n              <span class=\"text-warning\">403 Forbidden</span>\n            </p>\n            <p class=\"text-muted-foreground\">scope billing:read — missing</p>\n          </div>\n        </div>\n      {/if}\n      <div class={bodyClass}>\n        <h3 class={titleClass}>{titleAccess}</h3>\n        <p class={descClass}>{descAccess}</p>\n        {#if showPrimary || showSecondary}\n          <div class={actionsClass}>\n            {#if showPrimary}\n              {@render actionButton(primaryHref, primaryAccess, 'default', primaryDisabled, onPrimary)}\n            {/if}\n            {#if showSecondary}\n              {@render actionButton(secondaryHref, secondaryAccess, 'ghost', secondaryDisabled, onSecondary)}\n            {/if}\n          </div>\n        {/if}\n      </div>\n    </article>\n  {/if}\n\n  {#if show('sync-complete')}\n    <article class={cardClass} data-slot=\"empty-states-card\" data-variant=\"sync-complete\" role=\"status\">\n      {#if imageSync}\n        <img src={imageSync} alt={imageAlt} class={imgClass} />\n      {:else if imageSync === undefined}\n        <div class={panelClass} aria-hidden=\"true\">\n          <div class=\"border-border text-muted-foreground truncate border-b px-3 py-2 text-xs\">POST /sync</div>\n          <div class=\"space-y-1 p-3 text-xs leading-relaxed\">\n            <p><span class=\"text-muted-foreground\">HTTP/1.1</span> 200 OK</p>\n            <p class=\"text-muted-foreground\">{'{ \"synced\": 128, \"lag_ms\": 40 }'}</p>\n          </div>\n        </div>\n      {/if}\n      <div class={bodyClass}>\n        <h3 class={titleClass}>{titleSync}</h3>\n        <p class={descClass}>{descSync}</p>\n        {#if showPrimary || showSecondary}\n          <div class={actionsClass}>\n            {#if showSecondary}\n              {@render actionButton(secondaryHref, secondarySync, 'ghost', secondaryDisabled, onSecondary)}\n            {/if}\n          </div>\n        {/if}\n      </div>\n    </article>\n  {/if}\n</div>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/empty-states/EmptyStates.svelte"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/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"
  ]
}