02Danh sách thông cáo báo chí
Bốn thông cáo xếp theo ngày; bấm chip phân loại phía trên (Tất cả·Sản phẩm·Hợp tác·Giải thưởng) là chỉ phân loại đó còn lại, phần khác gập lại thật.
Dùng choKho thông cáo của press room, section danh sách newsroom
clickrl__filtersaria-pressed

<main class="stage">
<section class="rl is-demo">
<div class="rl__filters" role="group" aria-label="Phân loại">
<button class="rl__chip" type="button" data-cat="all" aria-pressed="true">Tất cả</button>
<button class="rl__chip" type="button" data-cat="product" aria-pressed="false">Sản phẩm</button>
<button class="rl__chip" type="button" data-cat="partner" aria-pressed="false">Hợp tác</button>
<button class="rl__chip" type="button" data-cat="award" aria-pressed="false">Giải thưởng</button>
</div>
<ul class="rl__list">
<li class="rl__row" data-cat="product"><span class="rl__date">08.20</span><span class="rl__title">Ra mắt tính năng workspace mới</span><span class="rl__tag">Sản phẩm</span></li>
<li class="rl__row" data-cat="partner"><span class="rl__date">07.02</span><span class="rl__title">Ký hợp tác phân phối với Công ty B</span><span class="rl__tag">Hợp tác</span></li>
<li class="rl__row" data-cat="award"><span class="rl__date">06.15</span><span class="rl__title">Đoạt giải Startup of the Year</span><span class="rl__tag">Giải thưởng</span></li>
<li class="rl__row" data-cat="product"><span class="rl__date">05.30</span><span class="rl__title">Công bố bản cập nhật 3.0</span><span class="rl__tag">Sản phẩm</span></li>
</ul>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-base; // 칩·행 전환
$easing: $ease-spring;
$color: $subject-ink; // 강조 악센트 색
@include stage($stage-yellow);
.rl {
box-sizing: border-box;
width: min(400px, 94vw);
background: $subject-cream;
border-radius: $r-card;
padding: $sp-2 $sp-4;
}
.rl__filters { display: flex; gap: $sp-1; flex-wrap: wrap; margin-bottom: $sp-2; }
.rl__chip {
padding: $sp-1 $sp-3;
border-radius: $r-pill;
border: 1px solid rgba(23, 20, 26, .18);
background: transparent;
color: rgba(23, 20, 26, .6);
font-weight: 600; font-size: 10px; line-height: 1.6; font-family: inherit;
cursor: pointer;
transition: background $duration $easing, color $duration $easing, border-color $duration $easing;
}
.rl__chip[aria-pressed="true"] {
background: $color;
border-color: $color;
color: $subject-cream;
}
.rl__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: $sp-1; }
.rl__row {
position: relative;
box-sizing: border-box;
display: flex; align-items: center; gap: $sp-2;
padding: $sp-2 $sp-2 $sp-2 $sp-3;
border-radius: $r-sm;
background: rgba(23, 20, 26, .04);
overflow: hidden;
max-height: 40px;
opacity: 1;
transition: max-height $duration $easing, opacity $duration $easing, padding $duration $easing, margin $duration $easing;
}
.rl__row::before {
content: "";
position: absolute; left: 0; top: 0; bottom: 0;
width: 3px;
background: $subject-blue;
}
.rl__row.is-hidden { max-height: 0; opacity: 0; padding-top: 0; padding-bottom: 0; margin: 0; }
.rl__date { flex: 0 0 auto; font-weight: 700; font-size: 10px; line-height: 1; font-family: inherit; color: rgba($subject-ink, .72); }
.rl__title {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600; font-size: clamp(11px, 3vw, 12px); line-height: 1.35; font-family: inherit;
color: $subject-ink;
}
.rl__tag {
flex: 0 0 auto;
padding: $sp-1 $sp-2;
border-radius: $r-xs;
background: rgba(47, 109, 246, .12);
color: $subject-blue-deep;
font-weight: 700; font-size: 9px; line-height: 1.6; font-family: inherit;
}
// ── 데모 루프: 행이 순서대로 살짝 밝아졌다 돌아온다(항상 재생, 클릭과 무관) ──────────────
.rl.is-demo .rl__row::before { animation: rl-scan 2s $easing infinite; }
.rl.is-demo .rl__row:nth-child(1)::before { animation-delay: 0ms; }
.rl.is-demo .rl__row:nth-child(2)::before { animation-delay: 160ms; }
.rl.is-demo .rl__row:nth-child(3)::before { animation-delay: 320ms; }
.rl.is-demo .rl__row:nth-child(4)::before { animation-delay: 480ms; }
@keyframes rl-scan {
0%, 80%, 100% { transform: scaleY(1); opacity: 1; }
30% { transform: scaleY(1.6); opacity: .5; }
}
// ── 반응형(BD9) ──────────────────────────────────────────
@media (max-width: 340px), (max-height: 220px) {
.rl { padding: $sp-1 $sp-3; }
.rl__filters { margin-bottom: $sp-1; }
.rl__date { display: none; }
.rl__row { padding: $sp-1 $sp-2 $sp-1 $sp-3; max-height: 26px; }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.rl__row { transition: none; }
.rl.is-demo .rl__row::before { animation: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.rl {
box-sizing: border-box;
width: min(400px, 94vw);
background: #fff7e6;
border-radius: 16px;
padding: 8px 16px;
}
.rl__filters {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.rl__chip {
padding: 4px 12px;
border-radius: 999px;
border: 1px solid rgba(23, 20, 26, 0.18);
background: transparent;
color: rgba(23, 20, 26, 0.6);
font-weight: 600;
font-size: 10px;
line-height: 1.6;
font-family: inherit;
cursor: pointer;
transition: background 300ms cubic-bezier(0.2, 0.8, 0.2, 1), color 300ms cubic-bezier(0.2, 0.8, 0.2, 1), border-color 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.rl__chip[aria-pressed=true] {
background: #17141a;
border-color: #17141a;
color: #fff7e6;
}
.rl__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.rl__row {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 8px;
padding: 8px 8px 8px 12px;
border-radius: 8px;
background: rgba(23, 20, 26, 0.04);
overflow: hidden;
max-height: 40px;
opacity: 1;
transition: max-height 300ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 300ms cubic-bezier(0.2, 0.8, 0.2, 1), padding 300ms cubic-bezier(0.2, 0.8, 0.2, 1), margin 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.rl__row::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: #2f6df6;
}
.rl__row.is-hidden {
max-height: 0;
opacity: 0;
padding-top: 0;
padding-bottom: 0;
margin: 0;
}
.rl__date {
flex: 0 0 auto;
font-weight: 700;
font-size: 10px;
line-height: 1;
font-family: inherit;
color: rgba(23, 20, 26, 0.72);
}
.rl__title {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600;
font-size: clamp(11px, 3vw, 12px);
line-height: 1.35;
font-family: inherit;
color: #17141a;
}
.rl__tag {
flex: 0 0 auto;
padding: 4px 8px;
border-radius: 4px;
background: rgba(47, 109, 246, 0.12);
color: #0a4fe8;
font-weight: 700;
font-size: 9px;
line-height: 1.6;
font-family: inherit;
}
.rl.is-demo .rl__row::before {
animation: rl-scan 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.rl.is-demo .rl__row:nth-child(1)::before {
animation-delay: 0ms;
}
.rl.is-demo .rl__row:nth-child(2)::before {
animation-delay: 160ms;
}
.rl.is-demo .rl__row:nth-child(3)::before {
animation-delay: 320ms;
}
.rl.is-demo .rl__row:nth-child(4)::before {
animation-delay: 480ms;
}
@keyframes rl-scan {
0%, 80%, 100% {
transform: scaleY(1);
opacity: 1;
}
30% {
transform: scaleY(1.6);
opacity: 0.5;
}
}
@media (max-width: 340px), (max-height: 220px) {
.rl {
padding: 4px 12px;
}
.rl__filters {
margin-bottom: 4px;
}
.rl__date {
display: none;
}
.rl__row {
padding: 4px 8px 4px 12px;
max-height: 26px;
}
}
@media (prefers-reduced-motion: reduce) {
.rl__row {
transition: none;
}
.rl.is-demo .rl__row::before {
animation: none !important;
}
}// 실제 동작: Phân loại 칩을 클릭하면 그 분류가 아닌 행이 실제로 접히고 칩 상태가 배타적으로 바뀐다.
var root = document.querySelector('.rl');
var chips = root.querySelectorAll('.rl__chip');
var rows = root.querySelectorAll('.rl__row');
chips.forEach(function (chip) {
chip.addEventListener('click', function () {
root.classList.remove('is-demo');
chips.forEach(function (c) { c.setAttribute('aria-pressed', 'false'); });
chip.setAttribute('aria-pressed', 'true');
var cat = chip.getAttribute('data-cat');
rows.forEach(function (row) {
var show = cat === 'all' || row.getAttribute('data-cat') === cat;
row.classList.toggle('is-hidden', !show);
});
});
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./PressReleaseList.module.scss";
export type PressReleaseListProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
type Cat = "all" | "product" | "partner" | "award";
const CHIPS: { cat: Cat; label: string }[] = [
{ cat: "all", label: "전체" },
{ cat: "product", label: "제품" },
{ cat: "partner", label: "파트너십" },
{ cat: "award", label: "수상" },
];
const RELEASES: { date: string; title: string; cat: Exclude<Cat, "all">; tag: string }[] = [
{ date: "08.20", title: "새 워크스페이스 기능 출시", cat: "product", tag: "제품" },
{ date: "07.02", title: "B사와 유통 파트너십 체결", cat: "partner", tag: "파트너십" },
{ date: "06.15", title: "올해의 스타트업상 수상", cat: "award", tag: "수상" },
{ date: "05.30", title: "3.0 버전 업데이트 공개", cat: "product", tag: "제품" },
];
export default function PressReleaseList({ duration = 300, easing = "cubic-bezier(.2,.8,.2,1)", color = "#17141a" }: PressReleaseListProps) {
const [active, setActive] = useState<Cat>("all");
const [interacted, setInteracted] = useState(false);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={interacted ? styles.rl : `${styles.rl} ${styles.demo}`} style={vars}>
<div className={styles.filters} role="group" aria-label="분류">
{CHIPS.map((c) => (
<button
key={c.cat}
type="button"
className={styles.chip}
aria-pressed={active === c.cat}
onClick={() => { setInteracted(true); setActive(c.cat); }}
>
{c.label}
</button>
))}
</div>
<ul className={styles.list}>
{RELEASES.map((r) => {
const show = active === "all" || active === r.cat;
return (
<li key={r.title} className={show ? styles.row : `${styles.row} ${styles.hidden}`}>
<span className={styles.date}>{r.date}</span>
<span className={styles.title}>{r.title}</span>
<span className={styles.tag}>{r.tag}</span>
</li>
);
})}
</ul>
</section>
);
}02 Danh sách thông cáo báo chí — Diện tích đo được 0.271% · khung hình chuyển động 16/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 mẫu press page design — dán vào landing
Bài viết chứa demo này
9 mẫu press page design — dán vào landingPress page design là bộ code gom sẵn từng khối của trang báo chí: press release, media kit, liên hệ người phụ trách. Từ Xem tất cả demo trong bài này

