프로덕션 서비스에서 검증된 테이블 헤더·자동 해제 메시지·그리드 자동완성·칩 입력 4종입니다.
모든 테이블의 thead th를 전역 규칙으로 볼드 처리해 헤더/본문을 일관되게 구분합니다.
| 플랜 | 가격 | 좌석 수 |
|---|---|---|
| 표준 플랜 | 9,900원 | 5 |
| 프로 플랜 | 29,900원 | 20 |
| 엔터프라이즈 플랜 | 99,000원 | 100 |
/* app/globals.css @layer base */
thead th {
@apply font-bold;
}
/* 컴포넌트에서는 font-bold를 개별로 붙일 필요가 없다 */
<TableHeader>
<TableRow>
<TableHead>플랜</TableHead>
<TableHead className="text-right">가격</TableHead>
</TableRow>
</TableHeader>액션 직후 표시한 성공/실패 메시지를 useAutoDismiss 훅으로 일정 시간 후 자동으로 지웁니다.
const [submittedAt, setSubmittedAt] = useState<number | null>(null)
const showMessage = useAutoDismiss(submittedAt !== null, submittedAt)
<Button onClick={() => setSubmittedAt(Date.now())}>저장</Button>
{showMessage && <p>저장되었습니다.</p>}접두어 검색 결과를 2~3열 그리드 카드로 보여주고, 전체 건수 대비 표시 건수를 안내합니다.
const matches = scoped.filter((item) => item.code.includes(query))
const visible = matches.slice(0, 6)
<p className="text-xs text-muted-foreground">
{`전체 ${matches.length}건 중 ${visible.length}건 표시${
matches.length > visible.length ? " — 더 입력해 좁혀보세요" : ""
}`}
</p>
<div className="grid grid-cols-2 gap-1.5 lg:grid-cols-3">{/* 카드 버튼 */}</div>입력값을 자동 포맷팅해 펜딩 칩으로 쌓고 한 번에 제출합니다. 붙여넣기 분할과 중복 사전검증을 포함합니다.
function addByDigits(digits: string) {
if (isDuplicate(digits, chips)) {
setNotice({ tone: "destructive", text: "이미 추가된 번호입니다." })
return
}
setChips((prev) => [...prev, formatPhoneTyping(digits)])
}
function handlePaste(e: ClipboardEvent<HTMLInputElement>) {
const tokens = splitTokens(e.clipboardData.getData("text"))
if (tokens.length <= 1) return
e.preventDefault()
addBatch(tokens)
}