02Press release list
보도자료 네 건이 날짜순으로 나열되고, 위쪽 분류 칩(전체·제품·파트너십·수상)을 클릭하면 그 분류만 실제로 남고 나머지는 접힙니다.
Best for
clickrl__filtersaria-pressed

<main class="stage">
<section class="rl is-demo">
<div class="rl__filters" role="group" aria-label="Category">
<button class="rl__chip" type="button" data-cat="all" aria-pressed="true">All</button>
<button class="rl__chip" type="button" data-cat="product" aria-pressed="false">Product</button>
<button class="rl__chip" type="button" data-cat="partner" aria-pressed="false">Partnership</button>
<button class="rl__chip" type="button" data-cat="award" aria-pressed="false">Awards</button>
</div>
<ul class="rl__list">
<li class="rl__row" data-cat="product"><span class="rl__date">08.20</span><span class="rl__title">New workspace features</span><span class="rl__tag">Product</span></li>
<li class="rl__row" data-cat="partner"><span class="rl__date">07.02</span><span class="rl__title">Distribution deal with Company B</span><span class="rl__tag">Partnership</span></li>
<li class="rl__row" data-cat="award"><span class="rl__date">06.15</span><span class="rl__title">Wins Startup of the Year</span><span class="rl__tag">Awards</span></li>
<li class="rl__row" data-cat="product"><span class="rl__date">05.30</span><span class="rl__title">Version 3.0 released</span><span class="rl__tag">Product</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;
}
}// 실제 동작: Category 칩을 클릭하면 그 분류가 아닌 행이 실제로 접히고 칩 상태가 배타적으로 바뀐다.
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 Press release list — Measured area 0.271% · moving frames 16/23Original by GODRICH · SCSS and React ship in the zip · 9 Media Page Templates — Copy-Paste
The post this demo lives in
9 Media Page Templates — Copy-PasteA media page template bundles the sections a press room needs — release feed, downloadable assets, spokesperson contact See all demos in this post


