07수준 칩으로 거르는 시스템 로그
정보·경고·오류 칩이 aria-pressed 로 눌린 상태를 갖고 눌린 수준의 줄만 남으며 나머지 줄은 hidden 로 화면에서 빠지고, 칩 색이 바뀌는 순간은 steps(1, end) 로 잘립니다.
쓰임관리자 화면의 서비스 로그 패널, 배포 콘솔의 이벤트 목록
aria-pressedhiddensteps(1, end)

<main class="stage">
<section class="lg is-demo" id="lg" aria-label="시스템 로그">
<header class="lg__h">
<span class="lg__cursor" aria-hidden="true"></span>
<h2 class="lg__t">시스템 로그</h2>
<span class="lg__live" aria-live="polite" id="lg-live">칩으로 수준을 골라 보세요</span>
</header>
<div class="lg__chips" role="group" aria-label="수준 필터">
<button class="lg__chip" type="button" data-k="info" aria-pressed="true">
<span class="lg__ci" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M144 176a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8m88-48A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88m-92-32a12 12 0 1 0-12-12a12 12 0 0 0 12 12"/></g></svg></span>
정보 3
</button>
<button class="lg__chip" type="button" data-k="warn" aria-pressed="true">
<span class="lg__ci lg__ci--w" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M215.46 216H40.54c-12.62 0-20.54-13.21-14.41-23.91l87.46-151.87c6.3-11 22.52-11 28.82 0l87.46 151.87c6.13 10.7-1.79 23.91-14.41 23.91" opacity=".2"/><path d="M236.8 188.09L149.35 36.22a24.76 24.76 0 0 0-42.7 0L19.2 188.09a23.51 23.51 0 0 0 0 23.72A24.35 24.35 0 0 0 40.55 224h174.9a24.35 24.35 0 0 0 21.33-12.19a23.51 23.51 0 0 0 .02-23.72m-13.87 15.71a8.5 8.5 0 0 1-7.48 4.2H40.55a8.5 8.5 0 0 1-7.48-4.2a7.59 7.59 0 0 1 0-7.72l87.45-151.87a8.75 8.75 0 0 1 15 0l87.45 151.87a7.59 7.59 0 0 1-.04 7.72M120 144v-40a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0m20 36a12 12 0 1 1-12-12a12 12 0 0 1 12 12"/></g></svg></span>
경고 2
</button>
<button class="lg__chip" type="button" data-k="error" aria-pressed="true">
<span class="lg__ci lg__ci--e" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M165.66 101.66L139.31 128l26.35 26.34a8 8 0 0 1-11.32 11.32L128 139.31l-26.34 26.35a8 8 0 0 1-11.32-11.32L116.69 128l-26.35-26.34a8 8 0 0 1 11.32-11.32L128 116.69l26.34-26.35a8 8 0 0 1 11.32 11.32M232 128A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88"/></g></svg></span>
오류 1
</button>
</div>
<ul class="lg__list" id="lg-list">
<li class="lg__row" data-k="info" data-row="1">
<div class="lg__fold"><p class="lg__line"><i class="lg__lv lg__lv--i" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M144 176a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8m88-48A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88m-92-32a12 12 0 1 0-12-12a12 12 0 0 0 12 12"/></g></svg></i>배포 1.4.2 완료 <time class="lg__tm">09:12</time></p></div>
</li>
<li class="lg__row" data-k="warn" data-row="2">
<div class="lg__fold"><p class="lg__line"><i class="lg__lv lg__lv--w" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M215.46 216H40.54c-12.62 0-20.54-13.21-14.41-23.91l87.46-151.87c6.3-11 22.52-11 28.82 0l87.46 151.87c6.13 10.7-1.79 23.91-14.41 23.91" opacity=".2"/><path d="M236.8 188.09L149.35 36.22a24.76 24.76 0 0 0-42.7 0L19.2 188.09a23.51 23.51 0 0 0 0 23.72A24.35 24.35 0 0 0 40.55 224h174.9a24.35 24.35 0 0 0 21.33-12.19a23.51 23.51 0 0 0 .02-23.72m-13.87 15.71a8.5 8.5 0 0 1-7.48 4.2H40.55a8.5 8.5 0 0 1-7.48-4.2a7.59 7.59 0 0 1 0-7.72l87.45-151.87a8.75 8.75 0 0 1 15 0l87.45 151.87a7.59 7.59 0 0 1-.04 7.72M120 144v-40a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0m20 36a12 12 0 1 1-12-12a12 12 0 0 1 12 12"/></g></svg></i>캐시 지연 840ms <time class="lg__tm">09:15</time></p></div>
</li>
<li class="lg__row" data-k="info" data-row="3">
<div class="lg__fold"><p class="lg__line"><i class="lg__lv lg__lv--i" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M144 176a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8m88-48A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88m-92-32a12 12 0 1 0-12-12a12 12 0 0 0 12 12"/></g></svg></i>예약 작업 등록 <time class="lg__tm">09:18</time></p></div>
</li>
<li class="lg__row" data-k="error" data-row="4">
<div class="lg__fold"><p class="lg__line"><i class="lg__lv lg__lv--e" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M165.66 101.66L139.31 128l26.35 26.34a8 8 0 0 1-11.32 11.32L128 139.31l-26.34 26.35a8 8 0 0 1-11.32-11.32L116.69 128l-26.35-26.34a8 8 0 0 1 11.32-11.32L128 116.69l26.34-26.35a8 8 0 0 1 11.32 11.32M232 128A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88"/></g></svg></i>결제 웹훅 실패 (재시도 1/3) <time class="lg__tm">09:21</time></p></div>
</li>
<li class="lg__row" data-k="warn" data-row="5">
<div class="lg__fold"><p class="lg__line"><i class="lg__lv lg__lv--w" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M215.46 216H40.54c-12.62 0-20.54-13.21-14.41-23.91l87.46-151.87c6.3-11 22.52-11 28.82 0l87.46 151.87c6.13 10.7-1.79 23.91-14.41 23.91" opacity=".2"/><path d="M236.8 188.09L149.35 36.22a24.76 24.76 0 0 0-42.7 0L19.2 188.09a23.51 23.51 0 0 0 0 23.72A24.35 24.35 0 0 0 40.55 224h174.9a24.35 24.35 0 0 0 21.33-12.19a23.51 23.51 0 0 0 .02-23.72m-13.87 15.71a8.5 8.5 0 0 1-7.48 4.2H40.55a8.5 8.5 0 0 1-7.48-4.2a7.59 7.59 0 0 1 0-7.72l87.45-151.87a8.75 8.75 0 0 1 15 0l87.45 151.87a7.59 7.59 0 0 1-.04 7.72M120 144v-40a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0m20 36a12 12 0 1 1-12-12a12 12 0 0 1 12 12"/></g></svg></i>디스크 여유 12% <time class="lg__tm">09:24</time></p></div>
</li>
<li class="lg__row" data-k="info" data-row="6">
<div class="lg__fold"><p class="lg__line"><i class="lg__lv lg__lv--i" aria-hidden="true"><svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><g><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M144 176a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8m88-48A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88m-92-32a12 12 0 1 0-12-12a12 12 0 0 0 12 12"/></g></svg></i>상태 점검 통과 <time class="lg__tm">09:27</time></p></div>
</li>
</ul>
</section>
</main>@use "sass:color";
@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-quick;
$easing: $ease-spring;
$color: $subject-blue;
@include stage($stage-ink);
$line: color.mix($subject-cream, $stage-ink, 16%);
$soft: color.mix($subject-cream, $stage-ink, 8%);
.lg {
box-sizing: border-box;
width: min(304px, 92vw);
padding: $sp-3;
border: 1px solid $line;
border-radius: $r-card;
background: $stage-ink;
color: $subject-cream;
font-family: "Cascadia Code", Consolas, -apple-system, "Segoe UI", sans-serif;
font-size: 12px;
line-height: 1.3;
}
.lg__h {
display: flex;
align-items: center;
gap: $sp-1;
margin: 0 0 $sp-2;
}
// 터미널 커서 — 로그가 살아 있음을 알리는 깜빡임
.lg__cursor {
width: 7px;
height: 13px;
border-radius: $r-xs;
background: $subject-mint;
}
.lg__t { margin: 0; font-size: 12px; font-weight: 700; }
.lg__live {
margin-left: auto;
color: rgba(255, 255, 255, .72);
font-size: 10px;
white-space: nowrap;
}
.lg__chips {
display: flex;
gap: $sp-1;
margin: 0 0 $sp-2;
}
.lg__chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0 $sp-2;
border: 1px solid $line;
border-radius: $r-pill;
background: $soft;
color: $subject-cream;
font-family: inherit;
font-size: 11px;
font-weight: 700;
line-height: 1.7;
cursor: pointer;
}
.lg__chip:focus-visible { outline: 2px solid $color; outline-offset: 2px; }
.lg__ci { display: inline-flex; width: 11px; height: 11px; color: $subject-sky; }
.lg__ci--w { color: $stage-yellow; }
.lg__ci--e { color: $error-soft; }
.lg__chip.is-off {
border-color: rgba(255, 255, 255, .14);
background: transparent;
color: rgba(255, 255, 255, .38);
}
.lg__list {
display: grid;
gap: $sp-1;
margin: 0;
padding: 0;
list-style: none;
}
.lg__row[hidden] { display: none; }
.lg__fold { display: grid; grid-template-rows: 1fr; }
.lg__line {
display: flex;
align-items: center;
gap: $sp-2;
margin: 0;
min-height: 0;
overflow: hidden;
padding: $sp-1 $sp-2;
border-radius: $r-xs;
background: rgba(255, 255, 255, .06);
color: rgba(255, 255, 255, .82);
font-size: 11px;
white-space: nowrap;
}
.lg__lv { display: inline-flex; flex: 0 0 auto; width: 12px; height: 12px; }
.lg__lv--i { color: $subject-sky; }
.lg__lv--w { color: $stage-yellow; }
.lg__lv--e { color: $error-soft; }
.lg__tm { margin-left: auto; color: rgba(255, 255, 255, .72); font-size: 10px; }
// ── 격자 미리보기 전용 2초 루프 ─────────────────────
// 칩 끄기와 줄 숨김은 grid 접힘으로 — 색이 바뀌는 순간은 steps 컷
@keyframes lg-warn-off {
0%, 24% { border-color: $line; background: $soft; color: $subject-cream; }
25%, 86% { border-color: rgba(255, 255, 255, .14); background: transparent; color: rgba(255, 255, 255, .38); }
87%, 100% { border-color: $line; background: $soft; color: $subject-cream; }
}
@keyframes lg-err-off {
0%, 54% { border-color: $line; background: $soft; color: $subject-cream; }
55%, 86% { border-color: rgba(255, 255, 255, .14); background: transparent; color: rgba(255, 255, 255, .38); }
87%, 100% { border-color: $line; background: $soft; color: $subject-cream; }
}
@keyframes lg-warn-fold {
0%, 24% { grid-template-rows: 1fr; }
31%, 86% { grid-template-rows: 0fr; }
87%, 100% { grid-template-rows: 1fr; }
}
@keyframes lg-err-fold {
0%, 54% { grid-template-rows: 1fr; }
61%, 86% { grid-template-rows: 0fr; }
87%, 100% { grid-template-rows: 1fr; }
}
@keyframes lg-chip-press-w {
0%, 24% { transform: scale(1); }
27% { transform: scale(.9); }
30%, 86% { transform: scale(1); }
87%, 100% { transform: scale(1); }
}
@keyframes lg-chip-press-e {
0%, 54% { transform: scale(1); }
57% { transform: scale(.9); }
60%, 100% { transform: scale(1); }
}
@keyframes lg-blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
.lg.is-demo {
.lg__chip[data-k="warn"] { animation-name: lg-warn-off, lg-chip-press-w; }
.lg__chip[data-k="error"] { animation-name: lg-err-off, lg-chip-press-e; }
.lg__chip {
animation-duration: $dur-loop, $dur-loop;
animation-timing-function: steps(1, end), $easing;
animation-iteration-count: infinite, infinite;
}
.lg__row[data-k="warn"] .lg__fold { animation-name: lg-warn-fold; }
.lg__row[data-k="error"] .lg__fold { animation-name: lg-err-fold; }
.lg__row[data-k="warn"] .lg__fold,
.lg__row[data-k="error"] .lg__fold {
animation-duration: $dur-loop;
animation-timing-function: $easing;
animation-iteration-count: infinite;
}
.lg__cursor {
animation-name: lg-blink;
animation-duration: 1s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
}
@media (max-width: 340px) {
.lg { padding: $sp-2; font-size: 11px; }
.lg__h { margin-bottom: $sp-1; }
.lg__chips { margin-bottom: $sp-1; }
.lg__chip { font-size: 10px; }
.lg__line { padding: 0 $sp-2; font-size: 10px; }
.lg__row[data-row="6"] { display: none; } // 좁은 화면에서는 마지막 줄부터 비운다
.lg__live { font-size: 9px; }
}
@media (prefers-reduced-motion: reduce) {
.lg.is-demo .lg__chip,
.lg.is-demo .lg__row .lg__fold,
.lg.is-demo .lg__cursor { animation: none !important; transform: none !important; opacity: 1 !important; }
// 움직임만 끄고 판정 결과는 남긴다 — 경고 칩은 꺼진 상태
.lg.is-demo .lg__chip[data-k="warn"] {
border-color: rgba(255, 255, 255, .14) !important;
background: transparent !important;
color: rgba(255, 255, 255, .38) !important;
}
.lg.is-demo .lg__row[data-k="warn"] .lg__fold { grid-template-rows: 0fr !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.lg {
box-sizing: border-box;
width: min(304px, 92vw);
padding: 12px;
border: 1px solid rgb(23.5764705882%, 22.0862745098%, 22.9960784314%);
border-radius: 16px;
background: #17141a;
color: #fff7e6;
font-family: "Cascadia Code", Consolas, -apple-system, "Segoe UI", sans-serif;
font-size: 12px;
line-height: 1.3;
}
.lg__h {
display: flex;
align-items: center;
gap: 4px;
margin: 0 0 8px;
}
.lg__cursor {
width: 7px;
height: 13px;
border-radius: 4px;
background: #4cd4a6;
}
.lg__t {
margin: 0;
font-size: 12px;
font-weight: 700;
}
.lg__live {
margin-left: auto;
color: rgba(255, 255, 255, 0.72);
font-size: 10px;
white-space: nowrap;
}
.lg__chips {
display: flex;
gap: 4px;
margin: 0 0 8px;
}
.lg__chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0 8px;
border: 1px solid rgb(23.5764705882%, 22.0862745098%, 22.9960784314%);
border-radius: 999px;
background: rgb(16.2980392157%, 14.9647058824%, 16.5960784314%);
color: #fff7e6;
font-family: inherit;
font-size: 11px;
font-weight: 700;
line-height: 1.7;
cursor: pointer;
}
.lg__chip:focus-visible {
outline: 2px solid #2f6df6;
outline-offset: 2px;
}
.lg__ci {
display: inline-flex;
width: 11px;
height: 11px;
color: #6ec8ff;
}
.lg__ci--w {
color: #ffd23f;
}
.lg__ci--e {
color: #ffe0d8;
}
.lg__chip.is-off {
border-color: rgba(255, 255, 255, 0.14);
background: transparent;
color: rgba(255, 255, 255, 0.38);
}
.lg__list {
display: grid;
gap: 4px;
margin: 0;
padding: 0;
list-style: none;
}
.lg__row[hidden] {
display: none;
}
.lg__fold {
display: grid;
grid-template-rows: 1fr;
}
.lg__line {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
min-height: 0;
overflow: hidden;
padding: 4px 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.82);
font-size: 11px;
white-space: nowrap;
}
.lg__lv {
display: inline-flex;
flex: 0 0 auto;
width: 12px;
height: 12px;
}
.lg__lv--i {
color: #6ec8ff;
}
.lg__lv--w {
color: #ffd23f;
}
.lg__lv--e {
color: #ffe0d8;
}
.lg__tm {
margin-left: auto;
color: rgba(255, 255, 255, 0.72);
font-size: 10px;
}
@keyframes lg-warn-off {
0%, 24% {
border-color: rgb(23.5764705882%, 22.0862745098%, 22.9960784314%);
background: rgb(16.2980392157%, 14.9647058824%, 16.5960784314%);
color: #fff7e6;
}
25%, 86% {
border-color: rgba(255, 255, 255, 0.14);
background: transparent;
color: rgba(255, 255, 255, 0.38);
}
87%, 100% {
border-color: rgb(23.5764705882%, 22.0862745098%, 22.9960784314%);
background: rgb(16.2980392157%, 14.9647058824%, 16.5960784314%);
color: #fff7e6;
}
}
@keyframes lg-err-off {
0%, 54% {
border-color: rgb(23.5764705882%, 22.0862745098%, 22.9960784314%);
background: rgb(16.2980392157%, 14.9647058824%, 16.5960784314%);
color: #fff7e6;
}
55%, 86% {
border-color: rgba(255, 255, 255, 0.14);
background: transparent;
color: rgba(255, 255, 255, 0.38);
}
87%, 100% {
border-color: rgb(23.5764705882%, 22.0862745098%, 22.9960784314%);
background: rgb(16.2980392157%, 14.9647058824%, 16.5960784314%);
color: #fff7e6;
}
}
@keyframes lg-warn-fold {
0%, 24% {
grid-template-rows: 1fr;
}
31%, 86% {
grid-template-rows: 0fr;
}
87%, 100% {
grid-template-rows: 1fr;
}
}
@keyframes lg-err-fold {
0%, 54% {
grid-template-rows: 1fr;
}
61%, 86% {
grid-template-rows: 0fr;
}
87%, 100% {
grid-template-rows: 1fr;
}
}
@keyframes lg-chip-press-w {
0%, 24% {
transform: scale(1);
}
27% {
transform: scale(0.9);
}
30%, 86% {
transform: scale(1);
}
87%, 100% {
transform: scale(1);
}
}
@keyframes lg-chip-press-e {
0%, 54% {
transform: scale(1);
}
57% {
transform: scale(0.9);
}
60%, 100% {
transform: scale(1);
}
}
@keyframes lg-blink {
0%, 49% {
opacity: 1;
}
50%, 100% {
opacity: 0;
}
}
.lg.is-demo .lg__chip[data-k=warn] {
animation-name: lg-warn-off, lg-chip-press-w;
}
.lg.is-demo .lg__chip[data-k=error] {
animation-name: lg-err-off, lg-chip-press-e;
}
.lg.is-demo .lg__chip {
animation-duration: 2s, 2s;
animation-timing-function: steps(1, end), cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite, infinite;
}
.lg.is-demo .lg__row[data-k=warn] .lg__fold {
animation-name: lg-warn-fold;
}
.lg.is-demo .lg__row[data-k=error] .lg__fold {
animation-name: lg-err-fold;
}
.lg.is-demo .lg__row[data-k=warn] .lg__fold,
.lg.is-demo .lg__row[data-k=error] .lg__fold {
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
}
.lg.is-demo .lg__cursor {
animation-name: lg-blink;
animation-duration: 1s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
@media (max-width: 340px) {
.lg {
padding: 8px;
font-size: 11px;
}
.lg__h {
margin-bottom: 4px;
}
.lg__chips {
margin-bottom: 4px;
}
.lg__chip {
font-size: 10px;
}
.lg__line {
padding: 0 8px;
font-size: 10px;
}
.lg__row[data-row="6"] {
display: none;
}
.lg__live {
font-size: 9px;
}
}
@media (prefers-reduced-motion: reduce) {
.lg.is-demo .lg__chip,
.lg.is-demo .lg__row .lg__fold,
.lg.is-demo .lg__cursor {
animation: none !important;
transform: none !important;
opacity: 1 !important;
}
.lg.is-demo .lg__chip[data-k=warn] {
border-color: rgba(255, 255, 255, 0.14) !important;
background: transparent !important;
color: rgba(255, 255, 255, 0.38) !important;
}
.lg.is-demo .lg__row[data-k=warn] .lg__fold {
grid-template-rows: 0fr !important;
}
}// 실제 동작: 칩을 눌러 수준을 끄고 켠다 — 눌린 수준의 줄만 남고 나머지 줄은 hidden 로 화면에서 빠진다.
// 전부 꺼지면 아무 걸러짐이 없으므로 줄 전부를 다시 보여준다.
var lg = document.getElementById('lg');
var lgChips = [].slice.call(document.querySelectorAll('.lg__chip'));
var lgRows = [].slice.call(document.querySelectorAll('.lg__row'));
var lgLive = document.getElementById('lg-live');
function lgFilter() {
var on = lgChips.filter(function (c) { return c.getAttribute('aria-pressed') === 'true'; })
.map(function (c) { return c.getAttribute('data-k'); });
var showAll = on.length === 0 || on.length === lgChips.length;
lgRows.forEach(function (r) {
r.hidden = !(showAll || on.indexOf(r.getAttribute('data-k')) >= 0);
});
lgLive.textContent = showAll ? '모든 줄을 보여 줍니다' : on.length + '개 수준만 보여 줍니다';
}
lgChips.forEach(function (c) {
c.addEventListener('click', function () {
lg.classList.remove('is-demo');
var now = c.getAttribute('aria-pressed') === 'true';
c.setAttribute('aria-pressed', now ? 'false' : 'true');
c.classList.toggle('is-off', now);
lgFilter();
});
});
lgFilter();import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./LogLevelFilter.module.scss";
export type LogLevelFilterProps = {
groupLabel?: string;
chipsLabel?: string;
heading?: string;
liveHint?: string;
showAllMsg?: string;
onlyMsg?: (n: number) => string;
chips?: { key: string; label: string }[];
rows?: { key: string; level: string; msg: string; tm: string }[];
demo?: boolean;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
function InfoIcon() {
return (
<svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor">
<g>
<path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2" />
<path d="M144 176a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8m88-48A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88m-92-32a12 12 0 1 0-12-12a12 12 0 0 0 12 12" />
</g>
</svg>
);
}
function WarnIcon() {
return (
<svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor">
<g>
<path d="M215.46 216H40.54c-12.62 0-20.54-13.21-14.41-23.91l87.46-151.87c6.3-11 22.52-11 28.82 0l87.46 151.87c6.13 10.7-1.79 23.91-14.41 23.91" opacity=".2" />
<path d="M236.8 188.09L149.35 36.22a24.76 24.76 0 0 0-42.7 0L19.2 188.09a23.51 23.51 0 0 0 0 23.72A24.35 24.35 0 0 0 40.55 224h174.9a24.35 24.35 0 0 0 21.33-12.19a23.51 23.51 0 0 0 .02-23.72m-13.87 15.71a8.5 8.5 0 0 1-7.48 4.2H40.55a8.5 8.5 0 0 1-7.48-4.2a7.59 7.59 0 0 1 0-7.72l87.45-151.87a8.75 8.75 0 0 1 15 0l87.45 151.87a7.59 7.59 0 0 1-.04 7.72M120 144v-40a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0m20 36a12 12 0 1 1-12-12a12 12 0 0 1 12 12" />
</g>
</svg>
);
}
function ErrIcon() {
return (
<svg viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor">
<g>
<path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2" />
<path d="M165.66 101.66L139.31 128l26.35 26.34a8 8 0 0 1-11.32 11.32L128 139.31l-26.34 26.35a8 8 0 0 1-11.32-11.32L116.69 128l-26.35-26.34a8 8 0 0 1 11.32-11.32L128 116.69l26.34-26.35a8 8 0 0 1 11.32 11.32M232 128A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88" />
</g>
</svg>
);
}
const LEVEL_ICONS: Record<string, typeof InfoIcon> = { info: InfoIcon, warn: WarnIcon, error: ErrIcon };
const DEFAULT_CHIPS = [
{ key: "info", label: "Info 3" },
{ key: "warn", label: "Warn 2" },
{ key: "error", label: "Error 1" },
];
const DEFAULT_ROWS = [
{ key: "l1", level: "info", msg: "Deploy 1.4.2 done", tm: "09:12" },
{ key: "l2", level: "warn", msg: "Cache latency 840ms", tm: "09:15" },
{ key: "l3", level: "info", msg: "Scheduled job registered", tm: "09:18" },
{ key: "l4", level: "error", msg: "Payment webhook failed (retry 1/3)", tm: "09:21" },
{ key: "l5", level: "warn", msg: "Disk free 12%", tm: "09:24" },
{ key: "l6", level: "info", msg: "Health check passed", tm: "09:27" },
];
export default function LogLevelFilter({
groupLabel = "System log",
chipsLabel = "Level filter",
heading = "System log",
liveHint = "Pick levels with the chips",
showAllMsg = "Showing every line",
onlyMsg = (n) => `${n} levels only`,
chips = DEFAULT_CHIPS,
rows = DEFAULT_ROWS,
demo = true,
duration = 220,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#2f6df6",
}: LogLevelFilterProps) {
const [live, setLive] = useState(demo);
const [on, setOn] = useState<Record<string, boolean>>({ info: true, warn: true, error: true });
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
function toggle(k: string) {
setLive(false);
setOn((o) => ({ ...o, [k]: !o[k] }));
}
const onList = chips.filter((c) => on[c.key]).map((c) => c.key);
const showAll = onList.length === 0 || onList.length === chips.length;
const cls = [styles.lg, live ? styles.isDemo : ""].filter(Boolean).join(" ");
return (
<section className={cls} style={vars} aria-label={groupLabel}>
<header className={styles.h}>
<span className={styles.cursor} aria-hidden="true"></span>
<h2 className={styles.t}>{heading}</h2>
<span className={styles.live} aria-live="polite">{showAll ? showAllMsg : onlyMsg(onList.length)}</span>
</header>
<div className={styles.chips} role="group" aria-label={chipsLabel}>
{chips.map((c) => {
const Icon = LEVEL_ICONS[c.key] || InfoIcon;
return (
<button
key={c.key}
className={[styles.chip, styles[`c_${c.key}`], on[c.key] ? "" : styles.isOff].filter(Boolean).join(" ")}
type="button"
aria-pressed={on[c.key]}
onClick={() => toggle(c.key)}
>
<span className={[styles.ci, c.key === "warn" ? styles.ciW : c.key === "error" ? styles.ciE : ""].filter(Boolean).join(" ")} aria-hidden="true"><Icon /></span>
{c.label}
</button>
);
})}
</div>
<ul className={styles.list}>
{rows.map((r) => {
const Icon = LEVEL_ICONS[r.level] || InfoIcon;
return (
<li key={r.key} className={[styles.row, styles[`lv_${r.level}`]].filter(Boolean).join(" ")} hidden={!(showAll || onList.indexOf(r.level) >= 0)}>
<div className={styles.fold}>
<p className={styles.line}>
<i className={[styles.lv, r.level === "warn" ? styles.lvW : r.level === "error" ? styles.lvE : styles.lvI].filter(Boolean).join(" ")} aria-hidden="true"><Icon /></i>
{r.msg}
<time className={styles.tm}>{r.tm}</time>
</p>
</div>
</li>
);
})}
</ul>
</section>
);
}07 수준 칩으로 거르는 시스템 로그 — 측정 누적면적 29.574% · 움직인 프레임 9/23GODRICH 원작 · zip 에 SCSS·React 포함 · 알림 목록 ui 9가지 — 사이드바에 박히는 활동 피드와 이력 위젯
이 데모가 속한 글
알림 목록 ui 9가지 — 사이드바에 박히는 활동 피드와 이력 위젯알림 목록 ui는 떴다 사라지는 토스트가 아니라 화면 한구석에 자리를 잡고 계속 쌓이는 목록을 말합니다. 사이드바에 박히는 이 아홉 개 위젯은 읽음 표시, 고치기 전 값, 로그 수준, 오늘의 건수까지 전부 실제 상태 이 글의 데모 전부 보기


