JS Design System v0.6

시스템 상태

에이전트·서버·연동 서비스의 상태를 한눈에 보여줍니다. 정상은 조용하게, 이상은 눈에 띄게.

Composition

이 패턴이 조합하는 컴포넌트입니다. 세부 옵션은 각 컴포넌트 페이지를 참고하세요.

1. 서비스 상태 보드

수집 서버가동 99.98%
정책 배포rev 2041
업데이트 서버지연 3분
SIEM 연동09:12 끊김
DB 점검02:00~04:00
라이선스842 / 1,000

2. 에이전트 상태 요약

연결 상태 비율 + 진행 중 작업.

에이전트 연결

정상
온라인1,247 / 1,284

오프라인 37대 · 10분 이상 미체크인 12대

진행 중 작업

정책 rev 2041 배포66%

842 / 1,284대 완료 · 예상 3분

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 { StatusList, StatusItem, Indicator, Count, WithCount, Steps, Card, CardHead, CardTitle, CardDesc, CardFoot, CardGrid, Tag, Notice, Toast, ToastStack, Banner, ToastProvider, useToast, Spinner, Skeleton, SkeletonRow, Progress } 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 .indicator, .count, .with-count, .steps, .card, .card-grid, .tag, .notice, .toast, .toast-stack, .banner, .spinner, .spinner-wrap, .skeleton, .skeleton-row, .progress, .progress-row, .status-list, .status-item, .stack

/* JS Design System v0.6 · .indicator, .count, .with-count, .steps, .card, .card-grid, .tag, .notice, .toast, .toast-stack, .banner, .spinner, .spinner-wrap, .skeleton, .skeleton-row, .progress, .progress-row, .status-list, .status-item, .stack · 토큰(var(--*))은 assets/style.css :root 참고 */
.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)}
.tabs .tab .count{font-size:12px;font-weight:700;padding:1px 6px;border-radius:var(--radius-full);background:var(--bg-panel);color:var(--text-secondary);min-width:0;height:auto}
.tabs .tab.on .count{background:var(--accent-subtle);color:var(--accent-text)}
.sidenav a .count{margin-left:auto}
.sidenav.collapsed a span,.sidenav.collapsed .group,.sidenav.collapsed a .count{display:none}
.card{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);padding:var(--card-pad);display:flex;flex-direction:column;gap:12px;min-width:260px;position:relative}
.card .card-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.card .title{font-size:16px;line-height:24px;font-weight:600;margin:0}
.card .desc{font-size:14px;color:var(--text-secondary);margin:0;line-height:1.6}
.card .card-foot{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:auto;padding-top:4px}
.card.clickable{appearance:none;border:0;font:inherit;color:inherit;text-align:left;text-decoration:none;cursor:pointer;transition:box-shadow var(--motion-fast)}
.card.bordered{box-shadow:none;border:1px solid var(--border-default)}
.card.clickable:hover{box-shadow:var(--shadow-2)}
.card.on{box-shadow:inset 0 0 0 2px var(--accent),var(--shadow-1);background:var(--accent-subtle)}
.card.clickable.on:hover{box-shadow:inset 0 0 0 2px var(--accent),var(--shadow-2)}
.card.bordered.on{box-shadow:inset 0 0 0 2px var(--accent)}
.card.compact{padding:16px;gap:8px}
.card.compact .title{font-size:14px;line-height:20px}
.card.flush{padding:0;gap:0;overflow:hidden}
.card.flush .card-head{padding:16px var(--card-pad)}
.card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--card-gap);width:100%}
.notice{display:flex;gap:12px;align-items:flex-start;border-radius:var(--radius-lg);padding:14px 18px;font-size:14px;border:1px solid var(--border-default);width:100%;background:var(--bg-surface);line-height:1.55}
.notice b{display:block;margin-bottom:2px}
.notice .ico{flex:none;width:20px;height:20px;display:grid;place-items:center;color:var(--info-text);margin-top:1px}
.notice .ico svg{width:20px;height:20px}
.notice .close{margin-left:auto;flex:none;border:0;background:transparent;color:var(--text-tertiary);font:inherit;line-height:1;cursor:pointer;padding:2px;display:grid;place-items:center}
.notice .close svg{width:14px;height:14px}
.notice .close:hover{color:var(--text-primary)}
.notice .actions{display:flex;gap:8px;margin-top:8px}
.notice.info{background:var(--info-subtle);border-color:color-mix(in srgb,var(--info) 30%,transparent)}
.notice.success{background:var(--success-subtle);border-color:color-mix(in srgb,var(--success) 30%,transparent)}
.notice.success .ico{color:var(--success-text)}
.notice.warning{background:var(--warning-subtle);border-color:color-mix(in srgb,var(--warning) 30%,transparent)}
.notice.warning .ico{color:var(--warning-text)}
.notice.danger{background:var(--danger-subtle);border-color:color-mix(in srgb,var(--danger) 30%,transparent)}
.notice.danger .ico{color:var(--danger-text)}
.toast{display:inline-flex;align-items:center;gap:12px;width:360px;max-width:100%;padding:12px 16px;border-radius:var(--radius-lg);background:var(--bg-inverse);color:var(--text-inverse);font-size:14px;box-shadow:var(--shadow-3)}
.toast .ico{flex:none;width:20px;height:20px;display:grid;place-items:center;color:var(--success)}
.toast .ico svg{width:20px;height:20px}
.toast.danger .ico{color:var(--danger)}
.toast .action{margin-left:auto;background:none;border:0;color:var(--accent-inverse);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}
.toast .action:hover{text-decoration:underline}
.toast-stack{display:flex;flex-direction:column;gap:8px;align-items:flex-end}
.toast-stack.fixed{position:fixed;right:24px;bottom:24px;z-index:110}
.banner{display:flex;align-items:center;gap:12px;width:100%;padding:10px 20px;background:var(--accent);color:var(--accent-on);font-size:14px;font-weight:500}
.banner .btn{margin-left:auto}
.banner.warning{background:var(--warning);color:var(--warning-on)}
.banner.danger{background:var(--danger);color:var(--danger-on)}
.spinner{display:inline-block;width:24px;height:24px;border:3px solid var(--border-default);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite}
.spinner.sm{width:16px;height:16px;border-width:2px}
.spinner.lg{width:40px;height:40px;border-width:4px}
.spinner-wrap{display:inline-flex;flex-direction:column;align-items:center;gap:12px;font-size:12px;color:var(--text-tertiary)}
.skeleton{display:block;height:14px;border-radius:6px;background:linear-gradient(90deg,var(--bg-panel) 25%,var(--bg-subtle) 50%,var(--bg-panel) 75%);background-size:200% 100%;animation:shimmer 1.4s infinite}
.skeleton.title{height:20px;width:40%}
.skeleton.text{width:100%}
.skeleton.text.short{width:70%}
.skeleton.circle{width:40px;height:40px;border-radius:50%;flex:none}
.skeleton.rect{height:120px;border-radius:var(--radius-md)}
.skeleton-row{display:flex;align-items:center;gap:12px;width:100%}
.skeleton-row .lines{flex:1;display:flex;flex-direction:column;gap:8px}
.progress{width:100%;height:8px;border-radius:var(--radius-full);background:var(--fill-strong);overflow:hidden}
.progress i{display:block;height:100%;background:var(--accent-graphic);border-radius:var(--radius-full);transition:width var(--motion-base)}
.progress.sm{height:4px}
.progress.success i{background:var(--success)}
.progress.danger i{background:var(--danger)}
.progress-row{display:flex;flex-direction:column;gap:6px;width:100%;font-size:12px;color:var(--text-secondary)}
.progress-row .row{display:flex;justify-content:space-between}
.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}
.count{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--radius-full);background:var(--danger);color:var(--danger-on);font-size:12px;font-weight:700;line-height:1;}
.count.neutral{background:var(--bg-panel);color:var(--text-secondary)}
.count.accent{background:var(--accent);color:var(--accent-on)}
.count.dot{min-width:8px;width:8px;height:8px;padding:0}
.with-count{position:relative;display:inline-flex}
.with-count .count{position:absolute;top:-6px;right:-6px}
.steps{display:flex;align-items:center;gap:8px;list-style:none;padding:0;margin:0;font-size:12px;max-width:none}
.steps li{display:flex;align-items:center;gap:8px;color:var(--text-tertiary);margin:0;white-space:nowrap}
.steps li i{width:24px;height:24px;border-radius:50%;display:inline-grid;place-items:center;font-style:normal;font-size:12px;font-weight:700;background:var(--fill-strong);color:var(--text-secondary);flex:none}
.steps li i svg{width:12px;height:12px;stroke-width:3}
.steps li.done i{background:var(--success);color:var(--success-on)}
.steps li.on{color:var(--text-primary);font-weight:600}
.steps li.on i{background:var(--accent);color:var(--accent-on)}
.steps li+li::before{content:"";width:32px;height:1px;background:var(--border-strong)}
.steps.vertical{flex-direction:column;align-items:flex-start}
.steps.vertical li+li::before{display:none}
.tile .tag{position:absolute;top:12px;right:12px}
.stack{display:flex;flex-direction:column;gap:16px;width:100%}
.status-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;width:100%}
.status-item{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);font-size:14px}
.status-item .indicator{font-weight:600}
.status-item .meta{margin-left:auto;font-size:12px;color:var(--text-tertiary);}
.onboard .card{width:100%}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes shimmer{to{background-position:-200% 0}}
@keyframes pulse{70%{box-shadow:0 0 0 8px transparent}}