Danh sách thông báo
Dùng khi người mở app sau vài ngày cần quét nhanh chuyện đã xảy ra và phân biệt cái đã đọc với chưa đọc.
Xem trước trực tiếp
Nguồn
<script>document.body.className="dc-bleed"</script>
<div class="dc-nt">
<header class="dc-nt__top">
<h1 class="dc-nt__title">알림</h1>
<button class="dc-nt__read" type="button">모두 읽음</button>
<button class="dc-nt__ico" type="button"><span aria-hidden="true">⚙</span><span class="dc-sr">알림 설정</span></button>
</header>
<div class="dc-nt__filters" role="group" aria-label="알림 종류 고르기">
<button class="dc-nt__chip dc-nt__chip--on" type="button" aria-pressed="true">전체 <span class="dc-nt__chip-num">2</span></button>
<button class="dc-nt__chip" type="button" aria-pressed="false">급여</button>
<button class="dc-nt__chip" type="button" aria-pressed="false">계약</button>
<button class="dc-nt__chip" type="button" aria-pressed="false">근무</button>
<button class="dc-nt__chip" type="button" aria-pressed="false">공지</button>
</div>
<main class="dc-nt__body">
<h2 class="dc-nt__day">오늘</h2>
<ul class="dc-nt__list">
<li>
<a class="dc-nt__item dc-nt__item--new" href="#">
<span class="dc-nt__ava dc-nt__ava--ok" aria-hidden="true">₩</span>
<span class="dc-nt__txt">
<span class="dc-nt__head"><span class="dc-nt__name">8월 급여명세서가 도착했어요</span><span class="dc-nt__time">14:20</span></span>
<span class="dc-nt__msg">실지급 2,099,400원 · 9월 10일 입금 예정입니다. 공제 내역을 확인해 주세요.</span>
</span>
<span class="dc-nt__dot" aria-hidden="true"></span>
<span class="dc-sr">읽지 않음</span>
</a>
</li>
<li>
<a class="dc-nt__item dc-nt__item--new" href="#">
<span class="dc-nt__ava dc-nt__ava--brand" aria-hidden="true">✎</span>
<span class="dc-nt__txt">
<span class="dc-nt__head"><span class="dc-nt__name">근로계약서 서명 요청</span><span class="dc-nt__time">11:05</span></span>
<span class="dc-nt__msg">미소식품 · 주방보조 · 시급 10,320원. 9월 14일까지 서명하면 됩니다.</span>
<span class="dc-nt__act">지금 서명하기</span>
</span>
<span class="dc-nt__dot" aria-hidden="true"></span>
<span class="dc-sr">읽지 않음</span>
</a>
</li>
<li>
<a class="dc-nt__item" href="#">
<span class="dc-nt__ava dc-nt__ava--warn" aria-hidden="true">!</span>
<span class="dc-nt__txt">
<span class="dc-nt__head"><span class="dc-nt__name">목요일 연장근로 2시간이 빠졌어요</span><span class="dc-nt__time">09:42</span></span>
<span class="dc-nt__msg">출퇴근 기록과 급여가 다릅니다. 상담사에게 보낼 수 있습니다.</span>
</span>
</a>
</li>
</ul>
<h2 class="dc-nt__day">어제</h2>
<ul class="dc-nt__list">
<li>
<a class="dc-nt__item" href="#">
<span class="dc-nt__ava dc-nt__ava--info" aria-hidden="true">☏</span>
<span class="dc-nt__txt">
<span class="dc-nt__head"><span class="dc-nt__name">김현수 팀장이 메시지를 보냈어요</span><span class="dc-nt__time">어제 18:31</span></span>
<span class="dc-nt__msg">“다음 주 월요일 오전 9시 출근으로 바뀌었어요.”</span>
</span>
</a>
</li>
<li>
<a class="dc-nt__item" href="#">
<span class="dc-nt__ava" aria-hidden="true">🛈</span>
<span class="dc-nt__txt">
<span class="dc-nt__head"><span class="dc-nt__name">추석 연휴 상담센터 운영 안내</span><span class="dc-nt__time">어제 10:00</span></span>
<span class="dc-nt__msg">9월 24일부터 26일까지는 네팔어 상담이 오후에만 열립니다.</span>
</span>
</a>
</li>
</ul>
<p class="dc-nt__end">30일이 지난 알림은 자동으로 지워집니다</p>
</main>
<nav class="dc-nt__tabs" aria-label="주요 메뉴">
<a class="dc-nt__tab" href="#">
<span class="dc-nt__tab-ico" aria-hidden="true">⌂</span>
<span class="dc-nt__tab-lbl">홈</span>
</a>
<a class="dc-nt__tab" href="#">
<span class="dc-nt__tab-ico" aria-hidden="true">▤</span>
<span class="dc-nt__tab-lbl">명세서</span>
</a>
<a class="dc-nt__tab" href="#" aria-current="page">
<span class="dc-nt__tab-ico" aria-hidden="true">◔<span class="dc-nt__badge">2</span></span>
<span class="dc-nt__tab-lbl">알림</span>
<span class="dc-sr">읽지 않은 알림 2건</span>
</a>
<a class="dc-nt__tab" href="#">
<span class="dc-nt__tab-ico" aria-hidden="true">◉</span>
<span class="dc-nt__tab-lbl">내 정보</span>
</a>
</nav>
</div>
.dc-nt { display:flex; flex-direction:column; height:100vh; overflow:hidden; background:var(--dc-bg); color:var(--dc-text); font-family:var(--dc-font); }
.dc-nt__top { flex:0 0 auto; display:flex; align-items:center; gap:var(--dc-1); padding:calc(var(--dc-safe-top) + var(--dc-2)) var(--dc-2) var(--dc-2) var(--dc-4); background:var(--dc-surface); }
.dc-nt__title { flex:1 1 auto; font-size:22px; font-weight:800; letter-spacing:-.03em; color:var(--dc-text); }
.dc-nt__read { display:inline-flex; align-items:center; min-height:44px; padding:0 var(--dc-3); border:0; border-radius:var(--dc-r-sm); background:transparent; color:var(--dc-brand-text); font-size:14px; font-weight:700; }
.dc-nt__read:active { background:var(--dc-sunken); }
.dc-nt__ico { display:grid; place-items:center; width:44px; height:44px; border:0; border-radius:var(--dc-r-full); background:transparent; color:var(--dc-text-sub); font-size:19px; line-height:1; }
.dc-nt__ico:active { background:var(--dc-sunken); }
.dc-nt__filters { flex:0 0 auto; display:flex; gap:var(--dc-2); padding:var(--dc-1) var(--dc-4) var(--dc-3); overflow-x:auto; background:var(--dc-surface); border-bottom:1px solid var(--dc-line); scrollbar-width:none; }
.dc-nt__filters::-webkit-scrollbar { display:none; }
.dc-nt__chip { flex:0 0 auto; display:inline-flex; align-items:center; gap:var(--dc-1); min-height:36px; padding:0 var(--dc-3); border:1px solid var(--dc-line-field); border-radius:var(--dc-r-full); background:var(--dc-surface); color:var(--dc-text-sub); font-family:inherit; font-size:13px; font-weight:700; white-space:nowrap; }
.dc-nt__chip--on { border-color:var(--dc-brand); background:var(--dc-brand-soft); color:var(--dc-brand-text); }
.dc-nt__chip-num { font-family:var(--dc-mono); font-size:12px; }
.dc-nt__body { flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-bottom:var(--dc-5); }
.dc-nt__day { position:sticky; top:0; z-index:1; padding:var(--dc-3) var(--dc-4) var(--dc-2); background:var(--dc-bg); font-size:12px; font-weight:800; letter-spacing:.02em; color:var(--dc-text-mute); }
.dc-nt__list { display:flex; flex-direction:column; }
.dc-nt__item { position:relative; display:flex; align-items:flex-start; gap:var(--dc-3); padding:var(--dc-3) var(--dc-5) var(--dc-3) var(--dc-4); border-bottom:1px solid var(--dc-line); background:var(--dc-bg); text-decoration:none; }
.dc-nt__item:active { background:var(--dc-sunken); }
.dc-nt__item--new { background:var(--dc-brand-soft); }
.dc-nt__item--new .dc-nt__time { color:var(--dc-text-sub); }
.dc-nt__ava { flex:0 0 auto; display:grid; place-items:center; width:40px; height:40px; border-radius:var(--dc-r-full); background:var(--dc-sunken); color:var(--dc-text-sub); font-size:17px; font-weight:800; }
.dc-nt__ava--ok { background:var(--dc-ok-soft); color:var(--dc-ok); }
.dc-nt__ava--brand { background:var(--dc-surface); border:1px solid var(--dc-brand); color:var(--dc-brand-text); }
.dc-nt__ava--warn { background:var(--dc-warn-soft); color:var(--dc-warn); }
.dc-nt__ava--info { background:var(--dc-info-soft); color:var(--dc-info); }
.dc-nt__txt { flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px; }
.dc-nt__head { display:flex; align-items:baseline; gap:var(--dc-2); }
.dc-nt__name { flex:1 1 auto; min-width:0; font-size:14px; font-weight:700; line-height:1.4; color:var(--dc-text); }
.dc-nt__time { flex:0 0 auto; font-size:11px; color:var(--dc-text-mute); white-space:nowrap; }
.dc-nt__msg { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; font-size:13px; line-height:1.5; color:var(--dc-text-sub); }
.dc-nt__act { align-self:flex-start; margin-top:var(--dc-2); padding:var(--dc-1) var(--dc-3); border-radius:var(--dc-r-full); background:var(--dc-brand); color:var(--dc-text-on); font-size:12px; font-weight:800; }
.dc-nt__dot { position:absolute; top:calc(50% - 4px); right:var(--dc-3); width:8px; height:8px; border-radius:var(--dc-r-full); background:var(--dc-brand); }
.dc-nt__end { padding:var(--dc-5) var(--dc-4); text-align:center; font-size:12px; color:var(--dc-text-mute); }
.dc-nt__body--empty { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:var(--dc-3); padding:var(--dc-7) var(--dc-6); text-align:center; }
.dc-nt__empty-art { display:grid; place-items:center; width:72px; height:72px; border-radius:var(--dc-r-full); background:var(--dc-sunken); color:var(--dc-text-mute); font-size:30px; }
.dc-nt__empty-title { font-size:18px; font-weight:800; color:var(--dc-text); }
.dc-nt__empty-body { max-width:30ch; font-size:14px; line-height:1.6; color:var(--dc-text-sub); }
.dc-nt__empty-btn { min-height:48px; padding:0 var(--dc-5); border:1px solid var(--dc-line-field); border-radius:var(--dc-r-sm); background:var(--dc-surface); color:var(--dc-text); font-family:inherit; font-size:15px; font-weight:700; }
.dc-nt__empty-btn:active { background:var(--dc-sunken); }
.dc-nt__tabs { flex:0 0 auto; display:flex; padding-bottom:var(--dc-safe-bottom); background:var(--dc-surface); border-top:1px solid var(--dc-line); }
.dc-nt__tab { flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px; min-height:56px; padding:var(--dc-2) var(--dc-1); color:var(--dc-text-mute); text-decoration:none; }
.dc-nt__tab:active { background:var(--dc-sunken); }
.dc-nt__tab[aria-current="page"] { color:var(--dc-brand-text); }
.dc-nt__tab-ico { position:relative; font-size:20px; line-height:1; }
.dc-nt__tab-lbl { font-size:11px; font-weight:700; letter-spacing:-.01em; }
.dc-nt__badge { position:absolute; top:-5px; left:13px; min-width:18px; height:18px; padding:0 4px; border-radius:var(--dc-r-full); background:var(--dc-danger); color:var(--dc-text-on); font-size:11px; font-weight:800; line-height:18px; text-align:center; }
Cách dùng
- Đánh dấu chưa đọc bằng hai cách: nền nhạt và dấu chấm; chỉ một dấu chấm thì quá nhỏ
- Nhóm theo ngày: Hôm nay, Hôm qua — chỉ có giờ thì không biết hôm nào
- Cắt nội dung ở hai dòng, chi tiết để khi bấm vào
- Chỉ gắn nút hành động cho dòng thật sự cần làm ngay
Điểm cần lưu ý
- Đừng chỉ dùng màu để báo chưa đọc — cho dc-sr đọc thêm chữ "chưa đọc"
- Số trên badge của thanh tab phải khớp với số dòng chưa đọc
- Thanh trên dùng --dc-safe-top, thanh tab dưới dùng --dc-safe-bottom