{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "footer-slim",
  "title": "Footer Slim",
  "description": "A one-row footer for apps and docs: wordmark, links, a light, dark and system switch and the copyright line.",
  "registryDependencies": [
    "utils",
    "@manniche/chart-kit"
  ],
  "files": [
    {
      "path": "registry/manniche/footer-slim/footer-slim.tsx",
      "content": "// The one-row footer for apps and docs: wordmark, inline links, a light / dark / system switch and the © line.\n// Signature: it is 56 px tall on a wide box and every control still has a 44 px target. On a narrow box it wraps into\n// two centred rows (brand and links, then © and theme) rather than squeezing. It does not touch the DOM theme itself:\n// the app owns the theme and passes `theme` and `onThemeChange`. Screen readers get a <footer> landmark, a <nav> list\n// for the links, and the theme switch as a labelled radio group (arrow keys, Home and End). The ©-year is computed\n// after mount unless `now` is passed. Under reduced motion the switch's pill jumps instead of sliding.\nimport { useSyncExternalStore, type ComponentPropsWithoutRef, type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\nimport { Pills } from '@/registry/manniche/chart-kit/chart-kit'\n\nexport type FooterSlimLink = {\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 FooterSlimTheme = 'light' | 'dark' | 'system'\n\nexport type FooterSlimLabels = {\n  navigation?: string\n  theme?: string\n  light?: string\n  dark?: string\n  system?: string\n  newTab?: string\n}\n\nexport type FooterSlimProps = Omit<ComponentPropsWithoutRef<'footer'>, 'children'> & {\n  /** The wordmark or logo, rendered as given. */\n  brand: ReactNode\n  /** Inline links: docs, status, privacy. Keep to about five. */\n  links?: FooterSlimLink[]\n  /** The name after the © sign. */\n  owner: string\n  /** The current theme choice. The switch only shows when `onThemeChange` is also given. */\n  theme?: FooterSlimTheme\n  /** Called with the picked theme. Apply it to your app yourself; this footer never touches the DOM. */\n  onThemeChange?: (theme: FooterSlimTheme) => void\n  /** Pins the © year and keeps the first render pure. When absent the year appears after mount. */\n  now?: Date\n  /** UI strings, for translation. */\n  labels?: FooterSlimLabels\n}\n\nconst DEFAULT_LABELS: Required<FooterSlimLabels> = {\n  navigation: 'Footer',\n  theme: 'Theme',\n  light: 'Light',\n  dark: 'Dark',\n  system: 'System',\n  newTab: '(opens in a new tab)',\n}\n\nconst noopSubscribe = () => () => {}\n// null on the server and in the first client render, the real year once mounted.\nfunction useYear(now?: Date) {\n  const clientYear = useSyncExternalStore(noopSubscribe, () => new Date().getFullYear(), () => null)\n  return now ? now.getFullYear() : clientYear\n}\n\n/** A one-row footer for apps and docs, with a theme switch. */\nexport function FooterSlim({ brand, links = [], owner, theme, onThemeChange, now, labels, className, ...rest }: FooterSlimProps) {\n  const l = { ...DEFAULT_LABELS, ...labels }\n  const year = useYear(now)\n  const options = [\n    { id: 'light', label: l.light },\n    { id: 'dark', label: l.dark },\n    { id: 'system', label: l.system },\n  ]\n\n  return (\n    <footer {...rest} className={cn('@container w-full border-t border-border bg-background text-foreground', className)}>\n      <div className=\"mx-auto flex w-full max-w-6xl flex-wrap items-center justify-center gap-x-6 gap-y-1 px-4 py-1 @min-[56rem]:min-h-14 @min-[56rem]:flex-nowrap @min-[56rem]:justify-between sm:px-6\">\n        <div className=\"flex min-w-0 flex-wrap items-center justify-center gap-x-6 @min-[56rem]:justify-start\">\n          <div className=\"inline-flex min-h-11 items-center text-[15px] font-semibold tracking-[-0.02em]\" style={{ fontFamily: 'var(--font-display, inherit)' }}>\n            {brand}\n          </div>\n          {links.length > 0 && (\n            <nav aria-label={l.navigation}>\n              <ul className=\"flex flex-wrap justify-center gap-x-1 @min-[56rem]:justify-start\">\n                {links.map((link) => (\n                  <li key={link.href + link.label}>\n                    <a\n                      href={link.href}\n                      {...(link.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n                      className=\"relative inline-flex min-h-11 items-center rounded-md px-2 text-[13px] text-muted-foreground transition-[color] duration-200 ease-out-quint hover:text-foreground focus-visible:text-foreground focus-visible:outline-offset-0\"\n                    >\n                      {link.label}\n                      {link.external && <span className=\"sr-only\"> {l.newTab}</span>}\n                    </a>\n                  </li>\n                ))}\n              </ul>\n            </nav>\n          )}\n        </div>\n        <div className=\"flex flex-wrap items-center justify-center gap-x-6 gap-y-1\">\n          <p className=\"min-h-11 content-center font-mono text-xs text-muted-foreground tabular-nums\">\n            &copy; {year ?? ''}\n            {year ? ' ' : ''}\n            {owner}\n          </p>\n          {theme && onThemeChange && (\n            <Pills\n              label={l.theme}\n              options={options}\n              value={theme}\n              onChange={(id) => onThemeChange(id as FooterSlimTheme)}\n            />\n          )}\n        </div>\n      </div>\n    </footer>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}