{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "footer-status",
  "title": "Footer Status",
  "description": "A product footer with a service status chip in words and shape, the version, a few links and a way back to the top.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "@manniche/data-tile"
  ],
  "files": [
    {
      "path": "registry/manniche/footer-status/footer-status.tsx",
      "content": "// A footer for products: how the service is doing right now, which version you are on, a few links, and a way\n// back to the top. Signature: the status chip, which says it in words and a distinct shape (never colour alone)\n// and carries a mono \"Updated 14:20\" that you pass in.\n// Screen readers: a <footer> with a visually hidden h2; the chip is a polite status region (\"Operational. Updated\n// 14:20\"); links are a real list. The back-to-top button scrolls and then moves focus to `focusTarget`, so the\n// next Tab starts at the top of the page, not where the footer was.\n// Reduced motion: the scroll is instant, the press feedback is dropped.\nimport { ArrowUp, CircleAlert, CircleCheck, CircleX } from 'lucide-react'\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\nimport { DataTile } from '@/registry/manniche/data-tile/data-tile'\n\nexport type ServiceStatus = 'operational' | 'degraded' | 'outage'\n\nexport type FooterStatusLink = {\n  label: string\n  href: string\n  /** Opens in a new tab with `rel=\"noopener noreferrer\"` and a hidden \"(opens in a new tab)\". */\n  external?: boolean\n}\n\nexport type FooterStatusProps = Omit<ComponentPropsWithoutRef<'footer'>, 'children'> & {\n  /** How the service is doing. */\n  status: ServiceStatus\n  /** When the status was last checked, as text you format: \"14:20\". Shown in mono after \"Updated\". */\n  updated?: string\n  /** Where the full status page lives. Makes the chip a link. */\n  statusHref?: string\n  /** The running version, and the changelog it links to. */\n  version?: { label: string; href: string }\n  /** A short list of links. */\n  links?: FooterStatusLink[]\n  /** The id of the element at the top of the page that receives focus after \"Back to top\". */\n  focusTarget?: string\n  /** A quiet line at the end: © and the like. */\n  legal?: ReactNode\n  /** Interface strings, for translation. */\n  labels?: {\n    /** Hidden heading of the footer. */\n    heading?: string\n    operational?: string\n    degraded?: string\n    outage?: string\n    updated?: string\n    version?: string\n    backToTop?: string\n    /** Hidden text after an external link. */\n    newTab?: string\n    linksLabel?: string\n  }\n}\n\nconst L = {\n  heading: 'Product status and links',\n  operational: 'All systems operational',\n  degraded: 'Degraded performance',\n  outage: 'Service outage',\n  updated: 'Updated',\n  version: 'Version',\n  backToTop: 'Back to top',\n  newTab: '(opens in a new tab)',\n  linksLabel: 'Product links',\n}\n\nconst ICON = { operational: CircleCheck, degraded: CircleAlert, outage: CircleX }\n// The colour only supports the icon and the words. Honey is weak on a light card, so the text stays foreground.\nconst DOT = { operational: 'text-success', degraded: 'text-chart-2', outage: 'text-destructive' }\n\nconst focus = 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'\n\nexport function FooterStatus({ status, updated, statusHref, version, links = [], focusTarget, legal, labels, className, ...rest }: FooterStatusProps) {\n  const t = { ...L, ...labels }\n  const Icon = ICON[status]\n\n  const toTop = () => {\n    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches\n    window.scrollTo({ top: 0, behavior: reduce ? 'auto' : 'smooth' })\n    const target = focusTarget ? document.getElementById(focusTarget) : null\n    if (target) {\n      if (!target.hasAttribute('tabindex')) target.setAttribute('tabindex', '-1')\n      target.focus({ preventScroll: true })\n    }\n  }\n\n  const chip = (\n    <>\n      <Icon aria-hidden className={cn('size-4 flex-none', DOT[status])} />\n      <span className=\"text-sm font-medium\">{t[status]}</span>\n      {updated && (\n        <span className=\"font-mono text-[12px] text-muted-foreground tabular-nums\">\n          {t.updated} {updated}\n        </span>\n      )}\n    </>\n  )\n  const chipClass = 'inline-flex min-h-11 max-w-full flex-wrap items-center gap-x-2.5 gap-y-0.5 rounded-full bg-muted px-4 py-1.5'\n\n  return (\n    <footer className={cn('mx-auto w-full max-w-6xl px-4 sm:px-6', className)} {...rest}>\n      <h2 className=\"sr-only\">{t.heading}</h2>\n      <div className=\"@container\">\n        <DataTile density=\"compact\">\n          <div className=\"flex flex-wrap items-center gap-x-6 gap-y-4 pb-1\">\n            <div role=\"status\" className=\"min-w-0\">\n              {statusHref ? (\n                <a href={statusHref} className={cn(chipClass, 'transition-[opacity] duration-200 ease-out-quint hover:opacity-80', focus)}>\n                  {chip}\n                </a>\n              ) : (\n                <p className={chipClass}>{chip}</p>\n              )}\n            </div>\n\n            {version && (\n              <a\n                href={version.href}\n                className={cn('inline-flex min-h-11 items-center gap-2 rounded-full px-1 text-sm text-muted-foreground transition-[opacity] duration-200 ease-out-quint hover:text-foreground', focus)}\n              >\n                {t.version}\n                <span className=\"rounded-full border px-2.5 py-0.5 font-mono text-[12px] text-foreground tabular-nums\">{version.label}</span>\n              </a>\n            )}\n\n            {links.length > 0 && (\n              <nav aria-label={t.linksLabel} className=\"min-w-0 @min-[48rem]:ml-auto\">\n                <ul className=\"flex flex-wrap gap-x-5\">\n                  {links.map((l) => (\n                    <li key={l.href + l.label}>\n                      <a\n                        href={l.href}\n                        {...(l.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n                        className={cn('inline-flex min-h-11 items-center rounded-sm text-sm text-muted-foreground underline-offset-4 hover:text-foreground hover:underline', focus)}\n                      >\n                        {l.label}\n                        {l.external && <span className=\"sr-only\"> {t.newTab}</span>}\n                      </a>\n                    </li>\n                  ))}\n                </ul>\n              </nav>\n            )}\n\n            <button\n              type=\"button\"\n              onClick={toTop}\n              className={cn(\n                'inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border px-4 text-sm font-medium transition-[transform] duration-200 ease-out-quint hover:bg-muted active:scale-[0.97] motion-reduce:active:scale-100',\n                focus,\n                links.length === 0 && '@min-[48rem]:ml-auto',\n                links.length > 0 && 'ml-auto',\n              )}\n            >\n              <ArrowUp aria-hidden className=\"size-4\" />\n              {t.backToTop}\n            </button>\n          </div>\n        </DataTile>\n        {legal && <div className=\"flex flex-wrap items-center gap-x-5 gap-y-2 px-2 pt-5 pb-8 text-[13px] text-muted-foreground\">{legal}</div>}\n      </div>\n    </footer>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}