JS Design System v0.6

Data Table

로그·이벤트·정책 목록처럼 대량의 행 데이터를 다룹니다. 정렬·선택·행 액션을 갖춘 콘솔의 핵심 컴포넌트입니다. 단순 표는 Table 을 씁니다.

1. Default (Sortable)

헤더 클릭 정렬. 정렬 중인 열은 .sorted(.desc). 숫자 열은 .num.

시간심각도이벤트대상횟수상태
09:41:12Critical랜섬웨어 행위 탐지PC-2041 · 김민준3차단
09:40:58Medium미승인 USB 연결PC-1187 · 이서연1검토 필요
09:38:03Low정책 동기화 완료Server-0312정상
09:36:47High알려진 악성코드 차단PC-0932 · 박지훈2차단

2. Selectable + Action Bar

체크박스 열(.check) + 선택 시 상단 액션 바. 선택 행은 .on.

2개 선택
정책적용 그룹대상수정사용
외부 USB 차단전체1,2842026-09-06
스크립트 실행 제한개발팀 제외1,1022026-09-02
원격 접속 허용 목록인프라팀482026-08-21

3. Compact

로그 뷰어처럼 행이 많을 때 행 높이 44. 시간·IP 는 모노스페이스(.log-row).

시간레벨소스메시지
2026-09-07 09:41:12.031CRIT10.0.4.21process.create blocked: rundll32.exe → cryptor.dll
2026-09-07 09:41:11.870WARN10.0.4.21usb.attach vendor=0x0781 product=0x5583
2026-09-07 09:41:09.114INFO10.0.0.3policy.sync ok (rev 2041)
2026-09-07 09:41:08.552INFO10.0.0.3agent.checkin 1284/1284

4. Empty

결과 없음. 조건 초기화 등 다음 행동을 제시.

시간심각도이벤트대상
조건에 맞는 이벤트가 없습니다. 필터 초기화

Props

HTML 에서는 클래스와 속성으로, React(@jiran/ds-react)에서는 같은 이름의 prop(variant · size …)으로 변형·상태를 지정합니다.

Prop / ClassType · ValuesDefaultDescription
.tbl-wrapcontainer—보더 + radius/lg + 가로 스크롤. 안에 .action-bar 와 table.tbl
.tbltable—기본 행 52. .compact 44 · .zebra 줄무늬
th.sortable / .sorted(.desc)header—정렬 가능 / 정렬 중. aria-sort
tr.on / .disabledrow—선택 / 비활성
td.num / .check / .actions / .emptycell—숫자 우측 정렬 / 체크박스 열 / 행 액션 / 빈 상태
.action-bardiv—선택 건수 + 일괄 액션

Usage

React: @jiran/ds-react 패키지를 설치하고 style.css 를 한 번 import 하면 위 React 탭 코드를 그대로 쓸 수 있습니다(React Package). HTML: assets/style.css 를 연결하고(일부만 필요하면 아래 컴포넌트 CSS 복사), 동작이 필요하면 assets/ds.js 를 함께 넣습니다 — 이 페이지의 프리뷰가 그 스크립트로 동작합니다. 색은 전부 토큰(var(--accent) 등)을 참조하므로 --accent-* 만 제품 컬러로 바꾸면 됩니다.

React @jiran/ds-react

npm i @jiran/ds-react

// main.tsx — 앱 진입점에서 한 번
import "@jiran/ds-react/style.css";

// 컴포넌트
import { DataTable, ActionBar } from "@jiran/ds-react";

HTML

<!-- 1) 토큰 + 전체 컴포넌트 스타일 한 번에 연결 -->
<link rel="stylesheet" href="assets/style.css">
<!-- 동작(드롭다운·탭·달력·표 정렬/선택·토스트 등) — 순수 JS 한 파일. React 제품은 불필요 -->
<script src="assets/ds.js" defer></script>

<!-- 2) 또는 아래 "Component CSS" 만 프로젝트 CSS 에 붙이고,
        :root 토큰은 Foundations › Design Token 에서 복사 -->

Component CSS .tbl-wrap, .tbl, .action-bar, .tag, .checkbox, .indicator

/* JS Design System v0.6 · .tbl-wrap, .tbl, .action-bar, .tag, .checkbox, .indicator · 토큰(var(--*))은 assets/style.css :root 참고 */
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.tag{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;padding:3px 10px;border-radius:var(--radius-full);line-height:1.6;background:var(--bg-panel);color:var(--text-secondary);white-space:nowrap}
.tag::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.tag.critical{background:var(--sev-critical-bg);color:var(--sev-critical-text)}
.tag.high{background:var(--sev-high-bg);color:var(--sev-high-text)}
.tag.medium{background:var(--sev-medium-bg);color:var(--sev-medium-text)}
.tag.low{background:var(--sev-low-bg);color:var(--sev-low-text)}
.tag.info{background:var(--sev-info-bg);color:var(--sev-info-text)}
.tag.ok{background:var(--success-subtle);color:var(--success-text)}
.tag.accent{background:var(--accent-subtle);color:var(--accent-text)}
.tag.sm{font-size:12px;padding:1px 8px}
.tag.lg{font-size:12px;padding:5px 12px}
.tag.plain::before{display:none}
.tag.outline{background:transparent;box-shadow:inset 0 0 0 1px currentColor}
.tag.solid.critical{background:var(--sev-critical);color:var(--sev-critical-on)}
.tag.solid.high{background:var(--sev-high);color:var(--sev-high-on)}
.tag.solid.medium{background:var(--sev-medium);color:var(--sev-medium-on)}
.tag.solid.low{background:var(--sev-low);color:var(--sev-low-on)}
.tag.solid.info{background:var(--sev-info);color:var(--sev-info-on)}
.tag.solid.ok{background:var(--success);color:var(--success-on)}
.tag.solid.accent{background:var(--accent);color:var(--accent-on)}
.indicator{display:inline-flex;align-items:center;gap:8px;font-size:14px}
.indicator::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--interaction-inactive);flex:none}
.indicator.ok::before{background:var(--success)}
.indicator.warn::before{background:var(--warning)}
.indicator.danger::before{background:var(--danger)}
.indicator.info::before{background:var(--info)}
.indicator.accent::before{background:var(--accent-graphic)}
.indicator.pulse::before{box-shadow:0 0 0 0 var(--danger);animation:pulse 1.6s infinite}
.tbl-wrap{overflow-x:auto;border:1px solid var(--border-default);border-radius:var(--radius-lg);background:var(--bg-surface);width:100%}
.tbl{width:100%;border-collapse:collapse;font-size:14px;margin:0}
.tbl th{text-align:left;font-weight:600;color:var(--text-tertiary);font-size:12px;height:44px;padding:0 16px;border-bottom:1px solid var(--border-default);white-space:nowrap;background:var(--bg-subtle)}
.tbl td{height:52px;padding:12px 16px;border-bottom:1px solid var(--border-default);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl tbody tr:hover td{background:var(--bg-subtle)}
.tbl tr.on td{background:var(--accent-subtle)}
.tbl tr.disabled td{color:var(--text-disabled)}
.tbl.compact td{height:44px;padding:8px 16px;font-size:14px}
.tbl.compact th{height:40px}
.tbl.zebra tbody tr:nth-child(even) td{background:var(--bg-subtle)}
.tbl th.sortable{cursor:pointer;user-select:none}
.tbl th.sortable::after{content:"";display:inline-block;width:14px;height:14px;margin-left:6px;vertical-align:-3px;opacity:.45;background:currentColor;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M3 9l4 -4l4 4m-4 -4v14%22 /%3E%3Cpath d=%22M21 15l-4 4l-4 -4m4 4v-14%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sortable:hover{color:var(--text-primary)}
.tbl th.sorted{color:var(--accent-text)}
.tbl th.sorted::after{opacity:1;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 11l-6 -6%22 /%3E%3Cpath d=%22M6 11l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl th.sorted.desc::after{-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M12 5l0 14%22 /%3E%3Cpath d=%22M18 13l-6 6%22 /%3E%3Cpath d=%22M6 13l6 6%22 /%3E%3C/svg%3E") center/contain no-repeat}
.tbl .num{text-align:right;}
.tbl td svg{vertical-align:middle}
.tbl .check{width:44px;padding-right:0}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions{text-align:right;white-space:nowrap}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.tbl.kv th{width:200px;height:auto;padding:12px 16px;font-size:14px;color:var(--text-secondary);font-weight:500;border-right:1px solid var(--border-default);white-space:normal;vertical-align:top}
.tbl.kv td{height:auto}
.tbl td.empty{display:table-cell;text-align:center;color:var(--text-tertiary);height:auto;padding:24px 16px;font-size:14px;border:none;border-radius:0;background:transparent}
.tbl .link{color:var(--accent-text);text-decoration:none;font-weight:500}
.tbl .link:hover{text-decoration:underline}
.action-bar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--accent-subtle);border-bottom:1px solid var(--border-default);font-size:14px}
.action-bar b{color:var(--accent-text)}
.action-bar .spacer{flex:1}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.tile .tag{position:absolute;top:12px;right:12px}
.status-item .indicator{font-weight:600}
@keyframes pulse{70%{box-shadow:0 0 0 8px transparent}}