@ngsk2784-lab/ui
v0.1.3
Published
React component + pure-CSS-token UI kit with 3 swappable skins (Dark Depth / Korean Service Light / Neo Pixel).
Readme
@ngsk2784-lab/ui
React 컴포넌트 라이브러리 + 순수 CSS 토큰. 3가지 스킨(Dark Depth / Korean Service Light / Neo Pixel)으로 빠르게 UI를 구축하세요.
주요 특징
- 토큰 기반 디자인: Semantic 토큰 30종으로 스킨 전환도 간편
- 3 스킨 지원
- Dark Depth: 도구·대시보드 (앰버 톤, 예리한 모서리)
- Korean Service Light: 대중 서비스 (따뜻한 톤, 부드러운 그림자)
- Neo Pixel: 픽셀 게임 (라임/마젠타, 하드엣지)
- 접근성 내장: WCAG 대비, 포커스 링, 44px 히트영역, 컬러 + 아이콘 병행
- 모션 기본값: 눌림/호버/진입 애니메이션 자동 적용
- Radix Primitives 기반: 복잡한 접근성·키보드 네비는 Radix가 담당, 스킨은 우리가
설치
npm install @ngsk2784-lab/ui
# 또는
pnpm add @ngsk2784-lab/ui피어 의존성 (반드시 설치):
npm install react react-domReact 18, 19 모두 지원.
빠른 시작 (10분)
1단계: CSS import
반드시 이 순서대로 import하세요:
// 1. 기본 토큰 (필수)
import '@ngsk2784-lab/ui/base.css';
// 2. 스킨 선택 (3개 중 1개)
import '@ngsk2784-lab/ui/skins/dark-depth.css';
// import '@ngsk2784-lab/ui/skins/korean-service-light.css';
// import '@ngsk2784-lab/ui/skins/neo-pixel.css';
// 3. 컴포넌트 스타일 (필수)
import '@ngsk2784-lab/ui/index.css';또는 한 줄로 번들 import (기본값 = Dark Depth):
import '@ngsk2784-lab/ui/styles.css';2단계: UIProvider 감싸기
앱의 최상위에서:
import { UIProvider, Button } from '@ngsk2784-lab/ui';
export default function App() {
return (
<UIProvider skin="dark-depth">
<YourApp />
</UIProvider>
);
}3단계: 컴포넌트 사용
import { Button, Input, Field } from '@ngsk2784-lab/ui';
export function LoginForm() {
const [email, setEmail] = useState('');
return (
<form>
<Field label="이메일" required>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="[email protected]"
/>
</Field>
<Button variant="primary" size="md">
로그인
</Button>
</form>
);
}스킨 전환
앱 렌더링 후 스킨을 바꾸려면 data-skin 속성만 변경:
const [skin, setSkin] = useState('dark-depth');
<UIProvider skin={skin}>
{/* 모든 자식이 자동으로 리스킨됨 */}
</UIProvider>
// 또는 document 직접 변경 (Provider 없을 때)
document.documentElement.dataset.skin = 'korean-service-light';컴포넌트 (18종)
| 카테고리 | 컴포넌트 | 용도 | |---------|---------|------| | 폼 | Button | 액션, 3종 스타일 (primary/ghost/danger) | | | Input | 단일행 텍스트 입력 | | | Textarea | 다중행 텍스트 | | | Select | 드롭다운 선택 | | | Checkbox | 다중 선택 (체크박스) | | | RadioGroup | 단일 선택 (라디오) | | | Switch | 토글 스위치 | | | Field | 라벨·헬퍼·에러 일관 배치 | | | FormRow | 필드 가로 배치 (반응형) | | 오버레이 & 피드백 | Modal | 다이얼로그, focus trap + Esc 닫기 | | | toast() | 스낵바 (success/error/info) | | | Badge | 상태 태그 (neutral/ok/warn/bad/info) | | | Spinner | 로딩 표시 (원형 회전) | | | Skeleton | 콘텐츠 로딩 placeholder (레이아웃 유지) | | 레이아웃 & 표시 | Card | 슬롯 기반 카드 (호버 리프트) | | | Table | 데이터 테이블 + 페이지네이션 | | | Tabs | 탭 네비게이션 | | | Segmented | 선택 알약 (라디오형) |
사용 예시
1. 폼 & 검증
import { useForm } from 'react-hook-form';
import { Field, Input, Button, toast } from '@ngsk2784-lab/ui';
export function SignupForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
try {
await api.signup(data);
toast({
type: 'success',
title: '가입 완료',
description: '로그인 페이지로 이동합니다.',
});
} catch (err) {
toast({
type: 'error',
title: '가입 실패',
description: err.message,
});
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Field label="이름" required error={errors.name?.message}>
<Input {...register('name', { required: '필수 입력' })} />
</Field>
<Button variant="primary" type="submit">
가입하기
</Button>
</form>
);
}2. 모달 다이얼로그
import { Modal, Button } from '@ngsk2784-lab/ui';
import { useState } from 'react';
export function DeleteModal() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="danger" onClick={() => setOpen(true)}>
삭제
</Button>
<Modal
open={open}
onOpenChange={setOpen}
title="정말 삭제하시겠어요?"
size="sm"
>
<p>이 작업은 되돌릴 수 없습니다.</p>
<Modal.Footer>
<Button variant="ghost" onClick={() => setOpen(false)}>
취소
</Button>
<Button variant="danger" onClick={() => {
// 삭제 로직
setOpen(false);
}}>
삭제
</Button>
</Modal.Footer>
</Modal>
</>
);
}3. 토스트 알림
import { toast, useToast, ToastViewport } from '@ngsk2784-lab/ui';
export function App() {
return (
<>
{/* 앱 최상위에 한 번만 */}
<ToastViewport />
<button onClick={() => {
toast({
type: 'success',
title: '저장됨',
description: '변경사항이 저장되었습니다.',
});
}}>
저장
</button>
</>
);
}
// 또는 hook 사용
export function Component() {
const { toast } = useToast();
return (
<button onClick={() => toast({ type: 'info', title: '알림' })}>
알림 보기
</button>
);
}디자인 파운데이션
모든 컴포넌트는 아래를 자동으로 준수합니다:
- 8pt 그리드 & 스페이싱: 토큰화된 간격 (
--space-1~--space-7) - 색 대비: WCAG AA 이상 (4.5:1 본문, 3:1 UI)
- 포커스 시각화: 2px 아웃라인 + glow
- 히트영역: 최소 44×44px (버튼, 필드, 체크박스 모두)
- 모션 준수:
prefers-reduced-motion자동 처리 - 색+기호 병행: 색만으로 상태 표시 금지 (성공=✓+색, 에러=⚠+색)
데모 & 개발
로컬에서 카탈로그 보기
pnpm demo브라우저에서 http://localhost:5173 열기. 모든 컴포넌트 × 3스킨 × 상태(default/hover/active/disabled/error) 볼 수 있습니다.
개발·테스트
pnpm typecheck # TypeScript 체크
pnpm lint # ESLint
pnpm test # Vitest
pnpm build # ESM 번들 생성TypeScript
모든 컴포넌트는 완전히 타입화되어 있습니다:
import { Button, ButtonProps } from '@ngsk2784-lab/ui';
const props: ButtonProps = {
variant: 'primary',
size: 'md',
loading: false,
};라이선스
MIT — 자유롭게 사용하세요.
지원
문제가 생기면:
- 문서: GitHub Discussions
- 버그 리포트: Issues
