// SiteFooter — the canonical public-facing footer for marketing / // docs / marketplace pages. NOT used inside the authenticated // dashboard (that surface has its own compact app-chrome footer). // // ┌──────────────────────────────────────────────────────────────┐ // │ • hairline top highlight (primary tint) │ // ├──────────────────────────────────────────────────────────────┤ // │ │ // │ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ // │ │ brand │ │ Product │ │ Docs │ │ Company │ │ // │ │ tagline │ │ Demo │ │ Getting… │ │ About │ │ // │ │ social row │ │ Pricing │ │ Plugins │ │ Blog │ │ // │ │ │ │ Status │ │ Reference│ │ Careers │ │ // │ └────────────┘ └──────────┘ └──────────┘ └──────────┘ │ // │ │ // ├──────────────────────────────────────────────────────────────┤ // │ © Year · brand status badge · lang · theme │ // └──────────────────────────────────────────────────────────────┘ // // Layout: data-driven via `brand`, `tagline`, `social`, `columns`, // `bottomLeft`, `bottomRight`. All ReactNodes — apps inject their own // router Link via the `LinkComponent` prop (consistent with DocsLayout // / DocShell / AppShell pattern). import type { ComponentType, ReactNode } from 'react' import { cn } from '../cn' import type { ShellLinkProps } from './docShell' export interface SiteFooterLink { readonly label: ReactNode readonly href: string /** External links bypass the internal Link (router) so they navigate * via plain . Defaults to detecting `http:`/`https:` prefix in * the href. */ readonly external?: boolean /** Small label shown next to the link text (e.g. "new", "beta"). */ readonly badge?: ReactNode } export interface SiteFooterColumn { /** Section heading — rendered as small uppercase tracking. */ readonly title: ReactNode readonly links: ReadonlyArray } export interface SiteFooterSocial { /** Accessible label, e.g. "GitHub". */ readonly label: string readonly href: string readonly icon: ReactNode } interface SiteFooterProps { /** Brand mark — usually a small wordmark + Badge. */ readonly brand: ReactNode /** Short paragraph under the brand. ~2 lines of prose. */ readonly tagline?: ReactNode /** Social / external profile links. Rendered as a row of 32×32 * icon buttons under the tagline. */ readonly social?: ReadonlyArray /** The column groups. 3 columns is the visual sweet spot; 4 fits * but starts feeling busy. */ readonly columns: ReadonlyArray /** Bottom strip slots. Left typically holds the copyright; right * holds status / language / theme toggle. When `bottomLeft` is * omitted, a default `© {year} {copyrightHolder}` line renders. */ readonly bottomLeft?: ReactNode readonly bottomRight?: ReactNode /** Copyright holder shown in the default bottom-left line (only when * `bottomLeft` is not supplied). Default `'Voltro. Built on the * framework.'`. Pass your own so the shipped footer isn't Voltro- * branded. */ readonly copyrightHolder?: ReactNode /** `aria-label` for the social-links list. Default `'Social links'`. */ readonly socialLabel?: string /** Optional client-router Link. Same contract as the other kit * compositions. Defaults to plain . */ readonly LinkComponent?: ComponentType readonly className?: string } const PlainLink = ({ to, className, children }: ShellLinkProps): ReactNode => ( {children} ) const isSchemed = (href: string): boolean => /^[a-z][a-z0-9+.-]*:\/\//i.test(href) // FooterLink wraps the user's link with the right element: external // → plain , internal → LinkComponent. const FooterLink = ({ link, Link, }: { readonly link: SiteFooterLink readonly Link: ComponentType }): ReactNode => { const external = link.external ?? isSchemed(link.href) const baseClass = 'inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors' if (external) { return ( {link.label} {link.badge ? {link.badge} : null} ) } return ( {link.label} {link.badge ? {link.badge} : null} ) } const FooterBadge = ({ children }: { children: ReactNode }): ReactNode => ( {children} ) export const SiteFooter = ({ brand, tagline, social, columns, bottomLeft, bottomRight, copyrightHolder = 'Voltro. Built on the framework.', socialLabel = 'Social links', LinkComponent = PlainLink, className, }: SiteFooterProps): ReactNode => { const Link = LinkComponent const year = new Date().getFullYear() // Default copyright when neither slot fills the bottom-left. const defaultLeft = ( © {year} {copyrightHolder} ) return ( ) } // ---- StatusBadge — handy bottom-right slot piece ---- // Solid green dot + "All systems operational" style label. Drop into // `bottomRight` when the app has a real status URL to link to. interface StatusBadgeProps { readonly href: string readonly label?: ReactNode /** `'ok' | 'degraded' | 'down'` — colours the dot accordingly. */ readonly tone?: 'ok' | 'degraded' | 'down' } const STATUS_DOT: Record, string> = { ok: 'bg-success shadow-[0_0_8px_2px_oklch(0.7_0.15_162_/_0.5)]', degraded: 'bg-warning shadow-[0_0_8px_2px_oklch(0.78_0.18_70_/_0.5)]', down: 'bg-destructive shadow-[0_0_8px_2px_oklch(0.7_0.19_22_/_0.5)]', } export const StatusBadge = ({ href, label = 'All systems operational', tone = 'ok', }: StatusBadgeProps): ReactNode => ( )