Loading...
Loading...
02-reusable-page/footer/footer-saas.tsx
/**
* SaaS Footer — SaaS 서비스형 푸터
*
* shadcn CSS 변수 기반 (light/dark 자동 전환).
* 브랜드+소셜 / 링크 그룹 N개 / 하단 저작권+크레딧.
*
* @source monet-registry-main (260114)
* @extracted 2026-02-18
* @version 1.0.0
*
* @dependencies lucide-react, next/link
*
* @example
* ```tsx
* import { FooterSaaS } from './footer-saas';
* import { Github, Twitter } from 'lucide-react';
*
* <FooterSaaS
* brand={{ name: 'Monet', description: 'Premium React UI components' }}
* socialLinks={[
* { platform: 'twitter', href: 'https://twitter.com/example', icon: <Twitter className="size-5" /> },
* { platform: 'github', href: 'https://github.com/example', icon: <Github className="size-5" /> },
* ]}
* linkGroups={[
* { title: 'Product', links: [{ label: 'Components', href: '/components' }] },
* { title: 'Resources', links: [{ label: 'Docs', href: '/docs' }] },
* ]}
* bottomBar={{ creditText: 'Built with Next.js & Tailwind CSS' }}
* />
* ```
*/
import Link from 'next/link';
import type {
FooterBrandInfo,
FooterLinkGroup,
FooterBottomBar,
SocialLink,
} from './_shared/types';
import type { SaaSFooterLabels } from './_shared/i18n';
import { DEFAULT_SAAS_LABELS } from './_shared/i18n';
// ============================================
// Props
// ============================================
export interface FooterSaaSProps {
/** 브랜드 정보 */
brand: FooterBrandInfo;
/** 소셜 미디어 링크 */
socialLinks?: SocialLink[];
/** 링크 그룹 목록 */
linkGroups: FooterLinkGroup[];
/** 하단 바 (저작권, 크레딧, 링크) */
bottomBar?: FooterBottomBar;
/** i18n 라벨 */
labels?: SaaSFooterLabels;
/** 추가 className */
className?: string;
}
// ============================================
// 컴포넌트
// ============================================
export function FooterSaaS({
brand,
socialLinks,
linkGroups,
bottomBar,
labels = DEFAULT_SAAS_LABELS,
className = '',
}: FooterSaaSProps) {
const currentYear = new Date().getFullYear();
const homeHref = brand.homeHref ?? '/';
return (
<footer
className={`border-t border-border bg-background py-12 ${className}`}
>
<div className="container mx-auto px-4">
<div className="mb-8 grid grid-cols-2 gap-8 md:grid-cols-4">
{/* 브랜드 + 소셜 */}
<div className="col-span-2 md:col-span-1">
<Link href={homeHref} className="mb-4 flex items-center gap-2">
{brand.icon && brand.icon}
<span className="font-semibold text-foreground">
{brand.name}
</span>
</Link>
{brand.description && (
<p className="mb-4 text-sm text-muted-foreground">
{brand.description}
</p>
)}
{/* 소셜 링크 */}
{socialLinks && socialLinks.length > 0 && (
<div className="flex items-center gap-3">
{socialLinks.map((social) => (
<a
key={social.platform}
href={social.href}
className="text-muted-foreground transition-colors hover:text-foreground"
target="_blank"
rel="noopener noreferrer"
aria-label={social.platform}
>
{social.icon}
</a>
))}
</div>
)}
</div>
{/* 링크 그룹 */}
{linkGroups.map((group) => (
<div key={group.title}>
<h4 className="mb-3 font-semibold text-foreground">
{group.title}
</h4>
<ul className="space-y-2">
{group.links.map((link) => (
<li key={link.href}>
<Link
href={link.href}
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
{...(link.external
? { target: '_blank', rel: 'noopener noreferrer' }
: {})}
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
{/* 하단 바 */}
<div className="flex flex-col items-center justify-between gap-4 border-t border-border pt-6 sm:flex-row">
<p className="text-xs text-muted-foreground">
{bottomBar?.copyrightText ??
`© ${currentYear} ${brand.name}. ${labels.allRightsReserved}`}
</p>
{bottomBar?.creditText && (
<p className="text-xs text-muted-foreground">
{bottomBar.creditText}
</p>
)}
</div>
</div>
</footer>
);
}