컴포넌트
푸터 메타 줄
푸터 맨 아래의 피드 링크 줄과 크레딧 줄이에요.
- #arc
- #component
- #footer
- #feed
- #rss
- #credits
- #meta
1280px
설치
이 명령 한 줄이면 필요한 파일과 패키지가 함께 설치돼요.
터미널터미널터미널bash
npx shadcn@latest add https://dsui.vercel.app/r/footer-meta.json파일
components/arc/footer-meta/footer-meta.module.csscomponents/arc/footer-meta/footer-meta.tsx
함께 오는 항목
footer-meta.module.cssfooter-meta.module.cssfooter-meta.module.csscss
.meta { display: flex; flex-direction: column; }
.feeds { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); padding-block: var(--space-4); border-block-end: 1px solid var(--border-subtle); }
.feedItem { display: inline-flex; align-items: center; gap: var(--space-2) var(--space-3); }
.divider { width: 1px; height: var(--space-4); background: var(--border); }
.feed { display: inline-flex; align-items: center; gap: var(--space-1); border-radius: var(--radius-pill); padding: var(--space-1) var(--space-2); color: var(--text-secondary); font-size: var(--text-sm); text-decoration: none; transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard); }
.feed svg { flex: none; }
@media (hover: hover) and (pointer: fine) { .feed:hover { background: var(--surface-muted); color: var(--foreground); } }
.feed:active { background: var(--surface-sunken); color: var(--foreground); }
.credits { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4); padding-block: var(--space-4); }
/* Smaller than the feed row, since nobody comes to a page looking for this. The colour still has to hold its contrast. */
.copyright { color: var(--text-secondary); font-size: var(--text-xs); }
.creditList { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.credit { color: var(--text-secondary); font-size: var(--text-xs); }
.creditName { color: var(--foreground); }
.creditLink { color: var(--foreground); text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .creditLink:hover { text-decoration: underline; text-underline-offset: 3px; } }
@media (prefers-reduced-motion: reduce) { .feed { transition: none; } }
footer-meta.tsxfooter-meta.tsxfooter-meta.tsxtsx
import type { HTMLAttributes, ReactNode } from "react";
import { ArcLink } from "../lib/arc-provider";
import styles from "./footer-meta.module.css";
export interface FooterFeedLink {
href: string;
label: string;
/** A small decorative mark before the label. */
icon?: ReactNode;
/** Leaves the link as a plain anchor: a file, a feed, another origin. */
plain?: boolean;
}
export interface FooterCredit {
/** What is being credited, e.g. "Powered by". */
label: string;
/** Who, e.g. "Arc". */
name: ReactNode;
href?: string;
}
export interface FooterMetaProps extends HTMLAttributes<HTMLDivElement> {
/** Machine-readable things: a feed, llms.txt, a sitemap. */
feeds?: FooterFeedLink[];
/** The copyright line, at the start of the credits row. */
copyright?: ReactNode;
/** What the site owes to others. */
credits?: FooterCredit[];
/** The accessible name of the feed row. */
feedLabel?: string;
}
/**
* The two quiet rows at the very bottom of a footer: what machines can read, and what the site owes to other people.
* They are separated because they are read by different audiences — one by a feed reader or a crawler, the other by a
* person — and keeping them in one row makes both harder to find.
*
* Everything here is 12–13px. Bigger and it competes with the content; the contrast still has to hold, so the size
* drops but the colour does not.
*/
export function FooterMeta({ className, feeds, copyright, credits, feedLabel = "피드와 파일", ...props }: FooterMetaProps) {
if (!feeds?.length && !copyright && !credits?.length) return null;
return (
<div {...props} className={[styles.meta, className].filter(Boolean).join(" ")}>
{feeds?.length ? (
<nav aria-label={feedLabel} className={styles.feeds}>
{feeds.map((feed, index) => (
<span key={feed.href} className={styles.feedItem}>
{index > 0 ? <span aria-hidden="true" className={styles.divider} /> : null}
{feed.plain ? (
<a href={feed.href} className={styles.feed}>{feed.icon}{feed.label}</a>
) : (
<ArcLink href={feed.href} className={styles.feed}>{feed.icon}{feed.label}</ArcLink>
)}
</span>
))}
</nav>
) : null}
{copyright || credits?.length ? (
<div className={styles.credits}>
{copyright ? <span className={styles.copyright}>{copyright}</span> : null}
{credits?.length ? (
<span className={styles.creditList}>
{credits.map(credit => (
<span key={credit.label} className={styles.credit}>
{credit.label}{" "}
{credit.href
? <a href={credit.href} className={styles.creditLink} rel="noreferrer">{credit.name}</a>
: <span className={styles.creditName}>{credit.name}</span>}
</span>
))}
</span>
) : null}
</div>
) : null}
</div>
);
}
FooterMeta.displayName = "FooterMeta";
export default FooterMeta;