컴포넌트
문서 이전 다음
문서 아래에 두는 이전·다음 카드예요. 있는 쪽만 그려요.
- #arc
- #component
- #pager
- #navigation
- #docs
- #prev
- #next
1280px
설치
이 명령 한 줄이면 필요한 파일과 패키지가 함께 설치돼요.
터미널터미널터미널bash
npx shadcn@latest add https://dsui.vercel.app/r/doc-pager.json파일
components/arc/doc-pager/doc-pager.module.csscomponents/arc/doc-pager/doc-pager.tsx
함께 오는 항목
doc-pager.module.cssdoc-pager.module.cssdoc-pager.module.csscss
.pager { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
/* A lone "next" keeps the right-hand column, so which way it leads stays obvious. */
.link[data-dir="next"]:only-child { grid-column: 2; }
.link { display: flex; min-width: 0; flex-direction: column; gap: var(--space-1); border: 1px solid var(--border); border-radius: var(--radius-panel); padding: var(--space-5); background: var(--surface); text-decoration: none; -webkit-tap-highlight-color: transparent; transition: border-color var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard); }
.link[data-dir="next"] { align-items: flex-end; text-align: right; }
.hint { display: inline-flex; align-items: center; gap: var(--space-1); color: var(--text-secondary); font-size: var(--text-xs); font-weight: var(--label-weight); }
.hint svg { flex: none; }
.title { color: var(--foreground); font-size: var(--text-base); font-weight: 600; line-height: 1.4; text-wrap: balance; }
@media (hover: hover) and (pointer: fine) {
.link:hover { border-color: var(--border-strong); box-shadow: var(--shadow-raised); transform: translateY(-2px); }
.link:hover .hint { color: var(--accent); }
}
.link:active { box-shadow: var(--shadow-resting); transform: translateY(0); }
/* Side by side they would each be too narrow to read on a phone. */
@media (max-width: 599px) {
.pager { grid-template-columns: minmax(0, 1fr); }
.link[data-dir="next"]:only-child { grid-column: 1; }
}
@media (prefers-reduced-motion: reduce) { .link { transition: none; } .link:hover, .link:active { transform: none; } }
doc-pager.tsxdoc-pager.tsxdoc-pager.tsxtsx
"use client";
import { ArcLink } from "../lib/arc-provider";
import { playUiSound } from "../sound/play";
import { UiIcon } from "../theme/ui-icon";
import styles from "./doc-pager.module.css";
export interface DocPagerLink {
href: string;
title: string;
}
export interface DocPagerProps {
prev?: DocPagerLink;
next?: DocPagerLink;
/** The accessible name of the pair, e.g. "이전 다음 문서". */
label?: string;
/** The words above each title. */
labels?: { prev: string; next: string };
className?: string;
}
/**
* Previous and next at the foot of a document. Only the sides that exist are drawn, and the next card keeps its place
* on the right even when there is no previous one, so the direction stays readable. With both missing it renders
* nothing rather than an empty rule.
*/
export function DocPager({ prev, next, label = "이전 다음 문서", labels = { prev: "이전", next: "다음" }, className }: DocPagerProps) {
if (!prev && !next) return null;
return (
<nav aria-label={label} data-print="hide" className={[styles.pager, className].filter(Boolean).join(" ")}>
{prev ? (
<ArcLink href={prev.href} rel="prev" data-dir="prev" className={styles.link} onClick={() => playUiSound("tick")}>
<span className={styles.hint}>
<UiIcon iconRole="arrow-left" size={14} strokeWidth={1.75} aria-hidden="true" />
{labels.prev}
</span>
<span className={styles.title}>{prev.title}</span>
</ArcLink>
) : null}
{next ? (
<ArcLink href={next.href} rel="next" data-dir="next" className={styles.link} onClick={() => playUiSound("tick")}>
<span className={styles.hint}>
{labels.next}
<UiIcon iconRole="arrow-right" size={14} strokeWidth={1.75} aria-hidden="true" />
</span>
<span className={styles.title}>{next.title}</span>
</ArcLink>
) : null}
</nav>
);
}
DocPager.displayName = "DocPager";
export default DocPager;