컴포넌트
링크 카드
카드 전체가 하나의 링크예요. 호버하면 살짝 들려요.
- #arc
- #component
- #card
- #link
- #navigation
- #next-step
1280px
설치
이 명령 한 줄이면 필요한 파일과 패키지가 함께 설치돼요.
터미널터미널터미널bash
npx shadcn@latest add https://dsui.vercel.app/r/link-card.json파일
components/arc/link-card/link-card.module.csscomponents/arc/link-card/link-card.tsx
함께 오는 항목
link-card.module.csslink-card.module.csslink-card.module.csscss
/* A card that sits on the page, so hover lifts it a little; it is not a floating layer and keeps a real border. */
.card { display: flex; align-items: flex-start; gap: var(--space-4); box-sizing: border-box; border: 1px solid var(--border); border-radius: var(--radius-panel); padding: var(--space-6); background: var(--surface); color: var(--foreground); text-decoration: none; -webkit-tap-highlight-color: transparent; transition: border-color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard); }
.body { display: flex; min-width: 0; flex: 1 1 auto; flex-direction: column; gap: var(--space-1); }
.eyebrow { color: var(--text-secondary); font-size: var(--text-xs); font-weight: var(--label-weight); }
.title { color: var(--foreground); font-size: var(--text-lg); font-weight: 600; line-height: 1.45; text-wrap: balance; }
.description { color: var(--text-secondary); font-size: var(--text-sm); line-height: 1.6; word-break: keep-all; }
.icon { display: inline-flex; width: var(--control-height-md); height: var(--control-height-md); flex: none; align-items: center; justify-content: center; border-radius: var(--radius-control); background: var(--highlight-subtle); color: var(--foreground); }
/* The arrow sits on the first line of the title rather than centred, so it stays put as the description grows. */
.arrow { flex: none; margin-top: 3px; color: var(--text-secondary); transition: color var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard); }
.card[aria-current="page"] { border-color: transparent; background: var(--accent-subtle); }
@media (hover: hover) and (pointer: fine) {
.card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-raised); transform: translateY(-2px); }
.card:hover .arrow { color: var(--accent); transform: translateX(2px); }
.card[aria-current="page"]:hover { border-color: var(--accent); }
}
.card:active { box-shadow: var(--shadow-resting); transform: translateY(0) scale(.995); }
@media (prefers-reduced-motion: reduce) {
.card, .arrow { transition: none; }
.card:hover, .card:active { transform: none; }
.card:hover .arrow { transform: none; }
}
link-card.tsxlink-card.tsxlink-card.tsxtsx
"use client";
import type { ReactNode } from "react";
import { ArcLink } from "../lib/arc-provider";
import { playUiSound } from "../sound/play";
import { UiIcon } from "../theme/ui-icon";
import styles from "./link-card.module.css";
export interface LinkCardProps {
href: string;
/** The card's heading, and the link's accessible name. */
title: ReactNode;
/** One or two lines under the title. */
description?: ReactNode;
/** A short word above the title, such as "다음 단계". */
eyebrow?: ReactNode;
/** A mark at the start of the card, e.g. an icon in a tinted square. */
icon?: ReactNode;
/** The arrow at the end. On by default; turn it off for a card that is not a "go there" card. */
arrow?: boolean;
/** Marks the card as the page the reader is on. */
current?: boolean;
className?: string;
/** Anything after the description, such as a row of chips. Keep it free of links and buttons. */
children?: ReactNode;
}
/**
* A card whose whole face is one link. Nothing inside it may be a link or a button of its own: two targets inside one
* target leave the reader guessing which one they hit, and a screen reader reads them as separate stops. For a card
* that carries its own actions, use Card and put a link in it instead.
*/
export function LinkCard({
href,
title,
description,
eyebrow,
icon,
arrow = true,
current = false,
className,
children,
}: LinkCardProps) {
return (
<ArcLink
href={href}
className={[styles.card, className].filter(Boolean).join(" ")}
aria-current={current ? "page" : undefined}
onClick={() => playUiSound("tick")}
>
{icon ? <span className={styles.icon} aria-hidden="true">{icon}</span> : null}
<span className={styles.body}>
{eyebrow ? <span className={styles.eyebrow}>{eyebrow}</span> : null}
<span className={styles.title}>{title}</span>
{description ? <span className={styles.description}>{description}</span> : null}
{children}
</span>
{arrow ? <UiIcon iconRole="arrow-right" size={20} strokeWidth={1.75} className={styles.arrow} aria-hidden="true" /> : null}
</ArcLink>
);
}
LinkCard.displayName = "LinkCard";
export default LinkCard;