nnuer
강희태@gmlxo9917

공지사항 게시판 목록 (고정 공지 + 검색)

블로그 블록

네이비 표 머리, 연골드 배경의 고정 공지 행, 제목 검색이 있는 공지사항 목록 표입니다. 모바일에서는 번호·조회 열을 숨깁니다.

#board#table#notice#search#pinned2026. 10. 16. 업데이트

블록 신고

운영자가 확인한 뒤 필요하면 블록을 숨깁니다.

사유
Claude Code · claude-opus-5-5 · React (TSX)

자료실 목록도 같은 형태라 데이터만 바꿔 쓸 수 있습니다.

결과 코드펼치기
import { useMemo, useState, type FormEvent } from "react";

type Post = { id: number; title: string; date: string; views: number; pin?: boolean };

const POSTS: Post[] = [
  { id: 12, title: "가을 학기 클래스 일정 및 수강료", date: "2026.09.01", views: 341, pin: true },
  { id: 11, title: "11월 야외 출사 일정 안내", date: "2026.08.24", views: 158 },
  { id: 10, title: "수강생 사진전 출품작 접수", date: "2026.08.10", views: 203 },
  { id: 9, title: "스튜디오 대관 운영 시간 변경", date: "2026.07.18", views: 127 },
  { id: 8, title: "장비 대여 규정 안내", date: "2026.06.30", views: 189 },
  { id: 7, title: "여름 휴강 기간 안내", date: "2026.06.12", views: 142 },
];

const FONT = '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif';

export default function NoticeBoard() {
  const [draft, setDraft] = useState("");
  const [keyword, setKeyword] = useState("");

  const posts = useMemo(() => POSTS.filter((p) => (keyword ? p.title.toLowerCase().includes(keyword.toLowerCase()) : true)), [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">
        <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="notice-keyword" className="sr-only">
              검색어
            </label>
            <input
              id="notice-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) => {
                const number = posts.filter((q) => !q.pin).length - posts.slice(0, i).filter((q) => !q.pin).length;
                return (
                  <tr key={p.id} className={`border-b border-[#e4e7ed] text-center text-[15px] text-[#8a93a3] ${p.pin ? "bg-[#fff7dc]" : ""}`}>
                    <td className="hidden px-3.5 py-5 sm:table-cell">{p.pin ? "" : number}</td>
                    <td className="px-2 py-4 text-left sm:px-3.5 sm:py-5">
                      <a href={`/notice/${p.id}`} className="group flex flex-col items-start gap-1.5 sm:flex-row sm:items-center sm:gap-2.5">
                        {p.pin && <span className="inline-flex h-[26px] items-center rounded-full bg-[#F5C842] px-2.5 text-xs font-bold text-[#1B263B]">공지</span>}
                        <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개

아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.