Claude Code · claude-opus-5-5 · React (TSX)
목록 데이터는 파일 안 상수입니다. 실제 게시판과 연결할 때 POSTS 배열을 게시판 데이터로 바꾸면 됩니다.
결과 코드펼치기
import { useMemo, useState, type FormEvent } from "react";
type State = "모집 중" | "진행 중" | "마감";
type Post = { id: number; title: string; state: State; date: string; views: number };
const CATEGORIES: State[] = ["모집 중", "진행 중", "마감"];
const POSTS: Post[] = [
{ id: 204, title: "나만의 사진집 만들기 (8주)", state: "모집 중", date: "2026.09.01", views: 318 },
{ id: 203, title: "라이트룸 보정 마스터 (4주)", state: "모집 중", date: "2026.09.01", views: 264 },
{ id: 103, title: "처음 만나는 카메라: 노출과 구도 (4주)", state: "모집 중", date: "2026.09.01", views: 402 },
{ id: 102, title: "스마트폰으로 찍는 일상 사진 (원데이)", state: "모집 중", date: "2026.08.28", views: 287 },
{ id: 202, title: "풍경 사진 장노출 실습 (5주)", state: "진행 중", date: "2026.07.15", views: 231 },
{ id: 101, title: "필름 카메라 입문과 현상 체험 (3주)", state: "진행 중", date: "2026.07.15", views: 199 },
{ id: 201, title: "인물 사진 라이팅 (6주)", state: "마감", date: "2026.05.02", views: 356 },
];
const FONT = '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif';
function StateBadge({ state }: { state: State }) {
const off = state === "마감";
return (
<span className={`inline-flex h-[26px] shrink-0 items-center whitespace-nowrap rounded-full px-2.5 text-xs font-bold leading-none ${off ? "bg-[#f4f6f9] text-[#8a93a3]" : "bg-[#e9effa] text-[#3D64B1]"}`}>
{state}
</span>
);
}
export default function CourseBoard() {
const [category, setCategory] = useState<State | "">("");
const [draft, setDraft] = useState("");
const [keyword, setKeyword] = useState("");
const posts = useMemo(
() =>
POSTS.filter((p) => (category ? p.state === category : true)).filter((p) =>
keyword ? p.title.toLowerCase().includes(keyword.toLowerCase()) : true,
),
[category, keyword],
);
const onSearch = (e: FormEvent) => {
e.preventDefault();
setKeyword(draft.trim());
};
return (
<section className="w-full bg-white" style={{ fontFamily: FONT }}>
<div className="mx-auto w-full max-w-[1200px] px-4 py-12 sm:px-6 lg:py-16">
<nav aria-label="분류" className="mb-6 flex flex-wrap gap-1.5">
{(["", ...CATEGORIES] as const).map((c) => (
<button
key={c || "all"}
type="button"
onClick={() => setCategory(c)}
aria-pressed={category === c}
className={`rounded-full border-[1.5px] px-[18px] py-2 text-[15px] font-semibold transition-colors ${
category === c ? "border-[#1B263B] bg-[#1B263B] text-white" : "border-[#e4e7ed] text-[#4a5466] hover:border-[#172031]"
}`}
>
{c || "전체"}
</button>
))}
</nav>
<div className="mb-5 flex flex-wrap items-center justify-between gap-4">
<p className="text-[15px] text-[#8a93a3]">
전체 <strong className="text-[#1B263B]">{posts.length}</strong>건
</p>
<form onSubmit={onSearch} role="search" className="flex w-full gap-1.5 rounded-full bg-[#f4f6f9] p-[5px] sm:w-auto">
<label htmlFor="course-keyword" className="sr-only">
검색어
</label>
<input
id="course-keyword"
type="search"
value={draft}
onChange={(e) => setDraft(e.target.value)}
placeholder="제목 검색"
className="h-[42px] min-w-0 flex-1 rounded-full bg-transparent px-4 text-[15px] outline-none focus:bg-white sm:w-60 sm:flex-none"
/>
<button type="submit" className="h-[42px] rounded-full bg-[#1B263B] px-[22px] font-bold text-white">
검색
</button>
</form>
</div>
{posts.length === 0 ? (
<p className="rounded-2xl bg-[#f4f6f9] py-20 text-center text-[#8a93a3]">등록된 글이 없습니다.</p>
) : (
<table className="w-full border-collapse tabular-nums">
<caption className="sr-only">클래스 목록</caption>
<thead>
<tr className="text-sm font-bold text-white">
<th scope="col" className="hidden w-20 rounded-l-[10px] bg-[#1B263B] p-3.5 sm:table-cell">번호</th>
<th scope="col" className="rounded-l-[10px] bg-[#1B263B] p-3.5 sm:rounded-none">제목</th>
<th scope="col" className="w-[92px] rounded-r-[10px] bg-[#1B263B] p-3.5 sm:w-[130px] sm:rounded-none">작성일</th>
<th scope="col" className="hidden w-[90px] rounded-r-[10px] bg-[#1B263B] p-3.5 sm:table-cell">조회</th>
</tr>
</thead>
<tbody>
{posts.map((p, i) => (
<tr key={p.id} className="border-b border-[#e4e7ed] text-center text-[15px] text-[#8a93a3]">
<td className="hidden px-3.5 py-5 sm:table-cell">{posts.length - i}</td>
<td className="px-2 py-4 text-left sm:px-3.5 sm:py-5">
<a href={`/classes/${p.id}`} className="group flex flex-col items-start gap-1.5 sm:flex-row sm:items-center sm:gap-2.5">
<StateBadge state={p.state} />
<span className="text-base font-semibold text-[#172031] group-hover:text-[#3D64B1]" style={{ wordBreak: "keep-all" }}>
{p.title}
</span>
</a>
</td>
<td className="px-2 py-4 text-[13px] sm:px-3.5 sm:py-5 sm:text-[15px]">{p.date}</td>
<td className="hidden px-3.5 py-5 sm:table-cell">{p.views}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</section>
);
}
적용 사이트 0
이 블록이 실제로 쓰인 사이트입니다.
아직 등록된 사이트가 없습니다. 이 블록을 쓴 사이트가 있다면 알려 주세요.
재현 후기
v1 · 0개아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.