IMPLEMENTATION CATALOG
실제 화면에서 동작하는 예시와 구현 코드를 함께 제공합니다. 프로젝트 구현 검토와 화면 구성 협의에 필요한 상태, 크기, 사용 방식을 빠르게 확인할 수 있습니다.
EasyObj와 EasyList는 템플릿 화면에서 반복되는 상태 조작을 단순화하는 데이터 헬퍼입니다. 객체/배열을 직접 다루는 문법을 유지하면서 React 렌더링과 상태 추적을 맞춥니다.
EasyObj객체 상태를 프록시로 래핑해 중첩 속성 변경을 추적
EasyList배열 상태에 push/pop/forAll 같은 조작 메서드 제공
forAll목록 전체를 순회하며 각 항목을 직접 수정
toJS프록시 상태를 순수 JavaScript 구조로 변환
프로필처럼 중첩된 객체 상태를 setter 없이 직접 갱신합니다.
버튼을 눌러 중첩 객체와 배열 값을 직접 갱신합니다.
{
"name": "김민준",
"role": "Product Owner",
"score": 82,
"tags": [
"온보딩",
"우선순위"
],
"address": {
"city": "서울",
"office": "강남 오피스"
}
}const dataObj = Lib.EasyObj({
name: '김민준',
score: 82,
tags: ['온보딩', '우선순위'],
address: {
city: '서울',
office: '강남 오피스'
}
});
// 상태 변경 시 자동으로 리렌더링
dataObj.score += 5;
dataObj.tags.push('리뷰 완료');
dataObj.address.city = '부산';업무 목록처럼 반복되는 배열 상태를 CRUD 메서드로 다룹니다.
배열 메서드와 forAll로 항목을 조작합니다.
[
{
"id": 1,
"text": "고객 문의 확인",
"status": "진행 중"
},
{
"id": 2,
"text": "상담 일정 안내",
"status": "대기"
}
]const taskList = Lib.EasyList([
{ id: 1, text: '고객 문의 확인', status: '진행 중' },
{ id: 2, text: '상담 일정 안내', status: '대기' }
]);
// 배열 메서드 사용
taskList.push({ id: 3, text: '추가 작업 3', status: '신규' });
taskList.pop();
// forAll 메서드로 모든 항목 수정
taskList.forAll(item => {
item.status = '완료';
});Icon 컴포넌트는 react-icons 세트를 하나의 API로 연결합니다. 장식 아이콘은 decorative=true 기본값으로 스크린리더에서 숨깁니다. 의미 있는 아이콘은 decorative=false로 설정하면 role="img"가 적용되며, 라벨은 ariaLabel을 우선 사용하고 없으면 icon 문자열을 대체 라벨로 사용합니다.
icon세트 prefix와 아이콘 이름. 예: md:MdHome
size?아이콘 크기. 기본값 1em
color?직접 색상 지정
ariaLabel?의미 있는 아이콘의 우선 접근성 라벨
decorative?기본 true는 스크린리더에서 숨김. false면 role=img와 라벨을 제공
className?Tailwind 색상/크기 클래스
기본 아이콘, 상태 색상, 접근성 라벨을 한 번에 확인합니다.
Material 아이콘과 크기 변형
// 기본 아이콘
<Lib.Icon icon="md:MdHome" size="24px" />
// 다양한 크기
<Lib.Icon icon="md:MdHome" size="16px" /> // 작은 크기
<Lib.Icon icon="md:MdHome" size="24px" /> // 기본 크기
<Lib.Icon icon="md:MdHome" size="32px" /> // 큰 크기
<Lib.Icon icon="md:MdHome" size="40px" /> // 더 큰 크기상태 색상과 함께 쓰는 Bootstrap 아이콘
// 색상이 있는 아이콘
<Lib.Icon icon="bs:BsCheckCircle" className="text-emerald-700" size="20px" />
<Lib.Icon icon="bs:BsExclamationCircle" className="text-amber-800" size="20px" />
<Lib.Icon icon="bs:BsXCircle" className="text-rose-700" size="20px" />decorative=와 ariaLabel을 함께 사용합니다.접근성 라벨이 필요한 의미 있는 아이콘
<Lib.Icon
icon="fi:FiGithub"
size="22px"
ariaLabel="GitHub 저장소"
decorative={false}
/>Input은 텍스트, 숫자, 검색, 비밀번호처럼 폼에서 가장 많이 반복되는 입력 요소입니다. EasyObj 데이터 연결과 외부 상태 제어를 모두 지원하고, 마스크·필터·오류 상태를 한 컴포넌트 안에서 처리합니다.
dataObj/dataKey?EasyObj 상태와 필드 키를 연결하는 입력
value/defaultValue?외부 상태 제어 또는 초기값 기반 입력
type?text, email, password, number 등 HTML input 타입
filter?허용 문자 범위를 정해 입력 전 단계에서 차단
mask?전화번호·사업자번호처럼 고정 포맷으로 변환
maxDigits/maxDecimals?number 입력의 정수부·소수부 자릿수 제한
prefix/suffix?검색 아이콘, 단위, 통화 등 보조 표시
error?오류 상태와 에러 메시지 표시
마스크 패턴: # 숫자, A 대문자, a 소문자, ? 영문, * 모든 문자
텍스트와 이메일처럼 가장 자주 쓰는 폼 입력을 업무 화면 밀도로 확인합니다.
일반 텍스트 필드와 placeholder 상태
가장 기본적인 텍스트 입력
<Lib.Input
dataObj={inputDataObj}
dataKey="projectName"
placeholder="예: 고객 포털 리뉴얼"
/>type=email을 그대로 전달
이메일 입력
<Lib.Input
dataObj={inputDataObj}
dataKey="email"
type="email"
placeholder="owner@example.com"
/>전화번호와 사업자번호처럼 형식이 정해진 값을 입력 중에 바로 정리합니다.
숫자 입력을 전화번호 포맷으로 표시
전화번호 마스킹
<Lib.Input
dataObj={inputDataObj}
dataKey="phone"
mask="###-####-####"
placeholder="010-1234-5678"
/>고정 포맷 식별자 입력
사업자번호 마스킹
<Lib.Input
dataObj={inputDataObj}
dataKey="businessNo"
mask="###-##-#####"
placeholder="사업자번호 123-45-67890"
/>숫자 자릿수 제한과 문자셋 필터를 같은 카드 안에서 비교합니다.
정수 10자리, 소수 2자리까지 허용
숫자 입력 (자리수 제한)
<Lib.Input
dataObj={inputDataObj}
dataKey="amount"
type="number"
maxDigits={10}
maxDecimals={2}
placeholder="1200000.00"
/>영문/숫자만 허용
영문/숫자 필터
<Lib.Input
dataObj={inputDataObj}
dataKey="code"
filter="A-Za-z0-9"
placeholder="TEAM2026"
/>한글 조합 입력을 유지하면서 필터링
한글 필터
<Lib.Input
dataObj={inputDataObj}
dataKey="koreanName"
filter="가-힣"
placeholder="홍길동"
/>prefix, suffix, error, password toggle처럼 실제 폼에서 자주 필요한 상태입니다.
이메일 형식이 올바르지 않습니다
검증 실패 메시지를 인라인으로 표시
에러 상태
<Lib.Input
dataObj={inputDataObj}
dataKey="recoveryEmail"
error="이메일 형식이 올바르지 않습니다"
placeholder="wrong-email"
/>우측 정렬과 suffix 단위 표시
금액 입력 (우측 정렬, 접미사 표시)
<Lib.Input
dataObj={inputDataObj}
dataKey="price"
type="number"
maxDigits={10}
className="text-right"
placeholder="금액 입력"
suffix="원"
/>prefix 아이콘으로 검색 목적을 빠르게 인지
아이콘이 있는 검색 입력
<Lib.Input
dataObj={inputDataObj}
dataKey="searchKeyword"
prefix={<Lib.Icon icon="ri:RiSearchLine" className="h-5 w-5 text-slate-400" />}
placeholder="이름, 이메일, 회사명 검색"
/>togglePassword로 표시/숨김 전환
비밀번호 토글 기능
<Lib.Input
dataObj={inputDataObj}
dataKey="password"
type="password"
placeholder="비밀번호 입력"
togglePassword
/>Textarea는 긴 메모, 설명, 고객 응답처럼 줄바꿈을 보존해야 하는 텍스트 입력에 사용합니다. 데이터 연결, 외부 상태 제어, 한글 조합 입력, 오류 상태, 읽기 전용 상태를 같은 API로 처리합니다.
rows?기본 노출 줄 수. 기본값 4
dataObj/dataKey?EasyObj 필드와 textarea 값을 연결
value/defaultValue?외부 상태 제어 또는 초기값 기반 입력
error?aria-invalid와 오류 테두리 상태
onChange/onValueChange?변경 이벤트와 값 전용 콜백
placeholder?비어 있을 때 보여주는 안내 문구
disabled?입력 불가 상태
readOnly?값은 노출하지만 편집을 막는 상태
EasyObj 필드와 긴 메모 값을 연결해 입력 즉시 상태를 확인합니다.
EasyObj 필드에 바로 연결된 긴 텍스트 입력
고객 상담에서 필요한 화면과 우선 제공할 기능을 확인했습니다.
dataObj + dataKey로 긴 메모 상태를 직접 연결
const textDataObj = Lib.EasyObj({
memo: '고객 상담에서 필요한 화면과 우선 제공할 기능을 확인했습니다.',
});
<Lib.Textarea
id="textarea-bound-memo"
dataObj={textDataObj}
dataKey="memo"
rows={4}
placeholder="메모를 입력하세요"
/>value/onValueChange를 사용해 외부 React 상태와 동기화합니다.
React state를 단일 소스로 유지
value + onValueChange로 외부 상태와 동기화
const [textValue, setTextValue] = useState('고객 요청에 따라 신청서 항목과 관리자 확인 절차를 정리했습니다.');
<Lib.Textarea
id="textarea-controlled-note"
value={textValue}
onValueChange={setTextValue}
rows={3}
/>최소 글자 수처럼 사용자가 바로 복구할 수 있는 검증 메시지를 보여줍니다.
10자 미만이면 바로 오류 상태로 노출
error prop과 aria-invalid를 사용한 즉시 검증
<Lib.Textarea
id="textarea-error-reason"
dataObj={textDataObj}
dataKey="memo"
rows={4}
error={textDataObj.memo.length < 10}
placeholder="10자 이상 입력"
/>
<div className="mt-1 text-xs text-red-600">{textDataObj.memo.length < 10 ? '10자 이상 입력해주세요' : '정상'}</div>편집 불가 상태와 disabled 상태를 같은 표면에서 비교합니다.
readOnly와 disabled 상태를 나란히 비교
<Lib.Textarea
id="textarea-readonly"
value="검토 완료되어 더 이상 수정할 수 없습니다."
readOnly
/>
<Lib.Textarea
id="textarea-disabled"
placeholder="관리자 권한이 필요합니다"
disabled
/>Select는 짧은 옵션 목록에서 하나의 값을 고르는 입력 요소입니다. EasyList의 selected 플래그, EasyObj 데이터 연결, 외부 상태 제어를 모두 지원하고 상태 메시지와 aria-live 안내를 함께 제공합니다.
dataList옵션 배열 또는 EasyList. selected 플래그 동기화
valueKey/textKey?옵션 값과 라벨로 사용할 키
dataObj/dataKey?EasyObj 선택 값과 연결
value/onValueChange?외부 상태의 선택 값과 동기화
status?default, success, info, warning, error, loading, empty
statusMessage?상태별 가시 안내 문구
assistiveText?스크린리더용 보조 안내
disabled?선택 불가 상태
EasyList selected 플래그와 외부 상태 값을 실제 직무 선택 흐름으로 비교합니다.
dataList 내부 selected 플래그를 단일 선택으로 유지
dataList의 selected 플래그와 동기화됩니다.
EasyList 모드 — dataList 내부의 selected 플래그만으로 선택 상태를 관리
const jobOptionList = Lib.EasyList([
{ id: '', label: '담당 역할 선택', placeholder: true, selected: true },
{ id: 'designer', label: '프로덕트 디자이너' },
{ id: 'developer', label: '프론트엔드 개발자' },
{ id: 'pm', label: '프로덕트 매니저' },
]);
<Lib.Select
id="select-easylist"
dataList={jobOptionList}
valueKey="id"
textKey="label"
status="success"
statusMessage="dataList의 selected 플래그와 동기화됩니다."
/>value prop을 외부 상태와 동기화
value prop: review
외부 상태 제어 — value/onValueChange로 선택값을 동기화하고 dataList.selected도 자동 갱신
const [roleValue, setRoleValue] = useState('review');
<Lib.Select
id="select-controlled"
dataList={jobOptionList}
valueKey="id"
textKey="label"
value={roleValue}
onValueChange={setRoleValue}
status="info"
statusMessage={`value prop: ${roleValue}`}
/>loading, error, empty처럼 사용자가 판단해야 하는 상태를 한 표면에서 확인합니다.
불러오는 중…옵션을 불러오는 중입니다.
로딩/비활성화 상태 — status="loading" + assistiveText로 라이브 영역 안내
<Lib.Select
id="select-loading"
dataList={loadingOptionList}
valueKey="id"
textKey="label"
status="loading"
assistiveText="옵션을 불러오는 중입니다."
disabled
/>필수 입력 항목입니다.
에러 상태 — status="error"와 안내 메시지
<Lib.Select
id="select-error"
dataList={jobOptionList}
valueKey="id"
textKey="label"
status="error"
statusMessage="필수 입력 항목입니다."
/>표시할 항목이 없습니다.선택 가능한 항목이 비어 있습니다.
빈 상태 — status="empty" 프리셋으로 항목 부재 안내 및 aria-live=assertive 적용
<Lib.Select
id="select-empty"
dataList={Lib.EasyList([])}
status="empty"
assistiveText="선택 가능한 항목이 비어 있습니다."
/>Checkbox는 약관 동의, 알림 수신, 체크리스트처럼 여러 항목을 독립적으로 켜고 끄는 입력입니다. EasyObj 데이터 연결과 외부 상태 제어를 모두 지원하며, 색상 프리셋으로 상태 의미를 함께 전달할 수 있습니다.
label?입력 옆에 노출되는 라벨 텍스트
name?폼 제출·그룹 식별용 이름. 없으면 dataKey 또는 label 사용
dataObj/dataKey?EasyObj 필드와 체크 상태를 양방향으로 연결
checked/onValueChange?외부 상태로 체크 여부를 제어
color?primary, success, warning, danger, neutral 프리셋
disabled?읽기 전용·권한 부족 상태의 비활성화 표시
className?문서/폼 레이아웃에 맞춘 추가 클래스
연결된 값은 boolean, Y, 1 계열을 체크 상태로 해석합니다.
단일 체크와 EasyObj 데이터 연결을 설정·권한 화면에 가까운 밀도로 확인합니다.
EasyObj boolean 필드에 각 항목을 독립 저장
EasyObj 데이터 연결 — 알림 설정처럼 여러 boolean 필드를 독립적으로 저장
const notificationDataObj = Lib.EasyObj({
securityNotice: true,
productNotice: false,
marketingNotice: false,
});
<Lib.Checkbox
name="notice"
label="보안 알림 받기"
dataObj={notificationDataObj}
dataKey="securityNotice"
color="danger"
/>외부 상태 제어 — checked/onValueChange로 확인 상태와 동기화
const [isApproved, setIsApproved] = useState(false);
<Lib.Checkbox
label="상담 내용 확인 완료"
checked={isApproved}
onValueChange={setIsApproved}
color="success"
/>권한이 없는 항목은 라벨까지 흐리게 처리됩니다.
비활성화 상태 — 권한 부족 또는 읽기 전용 항목 표시
<Lib.Checkbox
label="관리자 전용 설정"
disabled
/>필수 약관, 알림 옵션, 상태 색상처럼 실제 폼에서 반복되는 선택 패턴입니다.
약관 동의 — 필수/선택 항목을 같은 폼 그룹에서 관리
<Lib.Checkbox
name="terms"
label="[필수] 서비스 이용약관 동의"
dataObj={consentDataObj}
dataKey="termsAgreed"
color="success"
/>상태 색상 — success/warning/danger 프리셋으로 의미를 구분
<Lib.Checkbox
label="요청 내용 확인"
dataObj={checklistDataObj}
dataKey="requestChecked"
color="success"
/>
<Lib.Checkbox
label="전화 상담 필요"
dataObj={checklistDataObj}
dataKey="callbackNeeded"
color="warning"
/>Radiobox는 여러 선택지 중 하나만 고르는 기본 라디오 입력입니다. 같은 name을 공유하는 선택지는 하나의 그룹으로 동작하고, 선택된 value가 EasyObj 필드 또는 변경 콜백으로 전달됩니다.
label?라디오 입력 옆에 표시되는 선택지 텍스트
name?동일 그룹을 묶는 이름. 같은 name 안에서 하나만 선택
value선택 시 dataObj 또는 onValueChange로 전달할 값
dataObj/dataKey?EasyObj 단일 필드에 선택 value 저장
checked/defaultChecked?외부에서 제어하는 상태 또는 초기 선택값
onValueChange?선택된 value를 직접 받는 변경 핸들러
color?primary, success, warning, danger, neutral 프리셋
disabled?선택 불가 상태의 입력과 라벨 비활성화
버튼형 선택이 필요하면 같은 계약의 RadioButton을 사용합니다.
라벨형 단일 선택을 역할·요금제 같은 실제 폼 패턴으로 확인합니다.
선택된 value가 selectedPlan 필드에 저장됩니다.
EasyObj 데이터 연결 — 같은 name 그룹의 선택 value를 단일 필드에 저장
const planDataObj = Lib.EasyObj({
selectedPlan: 'growth',
});
<Lib.Radiobox
name="plan"
label="Growth"
value="growth"
dataObj={planDataObj}
dataKey="selectedPlan"
color="primary"
/>priority = normal
외부 상태 제어 — checked/onValueChange로 단일 선택값을 동기화
const [priorityValue, setPriorityValue] = useState('normal');
<Lib.Radiobox
name="priority"
label="긴급"
value="urgent"
checked={priorityValue === 'urgent'}
onValueChange={setPriorityValue}
color="danger"
/>정책상 바꿀 수 없는 설정은 disabled로 잠급니다.
비활성화 상태 — 정책상 바꿀 수 없는 단일 선택 항목 표시
<Lib.Radiobox
name="lockedRegion"
label="국내 리전"
value="kr"
disabled
defaultChecked
/>외부 상태 제어, 비활성화, 상태 색상까지 라디오 그룹 동작을 비교합니다.
상태 색상 — neutral/warning/success 프리셋으로 선택 의미 구분
<Lib.Radiobox
name="publishStatus"
label="검토 요청"
value="review"
dataObj={statusDataObj}
dataKey="publishStatus"
color="warning"
/>Switch는 설정, 권한, 알림처럼 즉시 켜고 끄는 boolean 입력입니다. role="switch"와 aria-checked를 포함하며 EasyObj 데이터 연결과 외부 상태 제어를 모두 지원합니다.
dataObj/dataKey?EasyObj boolean 또는 Y/1 계열 값을 스위치 상태와 연결
checked/defaultChecked?외부에서 제어하는 상태 또는 초기 ON/OFF 값
onChange/onValueChange?토글 후 boolean 값과 연결 정보 전달
label?스위치 오른쪽에 표시되는 짧은 라벨
disabled?수정 불가 상태와 접근성 비활성 상태 표시
id/name?라벨 연결, 폼 전송, 테스트 셀렉터에 사용할 식별자
className?설정 행·카드 내부 배치에 맞춘 간격 보정
연결된 값은 true, Y, 1 계열을 ON으로 해석해 서버·폼 데이터와 함께 쓰기 쉽습니다.
설정 화면에서 ON/OFF 값을 즉시 바꾸는 업무형 토글 패턴입니다.
월요일 오전 9시에 팀 요약을 발송합니다.
설정 객체와 dataKey를 직접 연결한 스위치
const switchDataObj = Lib.EasyObj({ weeklyDigest: true });
<Lib.Switch
dataObj={switchDataObj}
dataKey="weeklyDigest"
label={switchDataObj.weeklyDigest ? 'ON' : 'OFF'}
/>입력 중인 초안을 30초마다 저장합니다.
외부 React state로 상태를 제어하는 스위치
const [isAutoSaveOn, setIsAutoSaveOn] = useState(false);
<Lib.Switch
checked={isAutoSaveOn}
onValueChange={(nextValue) => setIsAutoSaveOn(nextValue)}
label={isAutoSaveOn ? '사용' : '중지'}
/>비활성·초기값·id 연결처럼 실제 운영 화면에서 필요한 상태를 확인합니다.
disabled와 defaultChecked를 함께 사용한 잠긴 설정 상태
<Lib.Switch disabled defaultChecked label="비활성화" />
<Lib.Switch disabled label="비활성화 (OFF)" />명시적인 id로 외부 라벨과 스위치를 연결합니다.
id를 지정해 외부 label과 스위치 입력을 명확하게 연결
<Lib.Switch
dataObj={switchDataObj}
dataKey="notifications"
id="notify-switch"
label="푸시 알림"
/>NumberInput은 직접 입력과 증감 버튼을 함께 제공하는 숫자 입력입니다. 입력을 벗어나는 시점에 값을 정규화하고, min/max 범위를 벗어나면 허용 범위로 보정합니다.
dataObj/dataKey?EasyObj 숫자 필드와 입력 값을 양방향으로 연결
value/defaultValue?외부에서 제어하는 값 또는 독립형 초기 숫자
min/max?입력·증감 버튼으로 확정되는 허용 범위
step?버튼·키보드 증감 단위. 기본값은 1
onChange/onValueChange?확정된 숫자 또는 빈 값과 연결 정보 전달
disabled/readOnly?입력 및 증감 버튼을 잠그는 상태
placeholder/id?빈 값 안내 문구와 접근성 식별자
className?카드/폼 행 안에서 폭과 간격을 보정
키보드 ArrowUp/ArrowDown과 PageUp/PageDown도 step 단위로 동작해 데이터 입력 화면에서 빠르게 값을 조정할 수 있습니다.
수량처럼 최소값과 step 1이 필요한 가장 기본적인 숫자 입력입니다.
좌석 수처럼 최소·최대 범위가 있는 기본 데이터 연결 숫자 입력
const numberDataObj = Lib.EasyObj({ seatCount: 3 });
<Lib.NumberInput
id="number-seat-count"
dataObj={numberDataObj}
dataKey="seatCount"
min={1}
max={20}
step={1}
/>예산·비율처럼 min/max와 소수 step을 함께 쓰는 케이스입니다.
0.5% 단위로 0~50% 범위 안에서 조정합니다.
소수 step을 쓰는 비율 입력과 min/max 보정
<Lib.NumberInput
id="number-discount-rate"
dataObj={priceDataObj}
dataKey="discountRate"
min={0}
max={50}
step={0.5}
/>간단한 초기값 입력이나 독립 위젯으로 사용할 때의 형태입니다.
간단한 독립 입력으로 5일 단위 초기값을 제공합니다.
외부 state 연결 없이 defaultValue로 시작하는 독립 숫자 입력
<Lib.NumberInput id="number-report-cycle" defaultValue={10} min={5} step={5} />DateInput과 TimeInput은 텍스트 입력과 선택 도구를 함께 제공해 날짜·시간 값을 폼 문자열로 확정합니다. EasyObj 데이터 연결, 외부 상태 제어, 직접 입력 후 blur 또는 Enter로 확정하는 흐름을 지원합니다.
dataObj/dataKey?EasyObj의 날짜 또는 시간 문자열 필드와 연결
value/defaultValue?외부에서 제어하는 값 또는 초기 날짜·시간 값
min/max?DateInput의 선택 가능 날짜 범위
step?TimeInput 옵션 간격. 분 단위로 옵션 목록 생성
onChange/onValueChange?확정된 문자열 값과 연결 정보 전달
disabled/readOnly?직접 입력과 선택 버튼을 함께 잠금
placeholder/id?입력 안내와 라벨 연결 식별자
className?예약 폼, 필터 카드 등에서 폭을 보정
DateInput은 YYYY-MM-DD, TimeInput은 HH:mm 문자열을 확정 값으로 사용합니다.
일정 시작일, 계약 기간처럼 날짜 문자열을 입력하고 달력으로 선택합니다.
프로젝트 시작일을 EasyObj 필드와 연결
const dateDataObj = Lib.EasyObj({ startDate: '2026-07-15' });
<Lib.DateInput
id="date-start"
dataObj={dateDataObj}
dataKey="startDate"
/>2026년 계약 기간 안에서만 선택하도록 제한합니다.
min/max와 defaultValue로 선택 가능 기간 제한
<Lib.DateInput
id="date-contract-end"
defaultValue="2026-12-31"
min="2026-01-01"
max="2026-12-31"
/>예약 시간, 발송 시간처럼 고정 간격 옵션과 직접 입력을 함께 지원합니다.
알림 발송 시간을 EasyObj 필드와 연결
const timeDataObj = Lib.EasyObj({ sendTime: '09:30' });
<Lib.TimeInput
id="time-send"
dataObj={timeDataObj}
dataKey="sendTime"
step={30}
/>15분 단위 옵션으로 예약 시간을 선택합니다.
defaultValue와 15분 단위 옵션 목록
<Lib.TimeInput id="time-meeting" defaultValue="14:00" step={15} />검색 가능한 단일·다중 선택 입력입니다. dataList의 selected 값을 기반으로 선택 상태를 관리하며 초성 검색을 지원합니다.
dataList: 선택 항목 배열(EasyList 가능)valueKey / textKey (선택): 값/라벨 키 (기본: 'value'/'text')value?: 제어 값 (단일 또는 배열)defaultValue?: 초기 선택 값multi?: 다중 선택 모드multiSummary?: 다중 선택 요약 배지 표시summaryText?: 요약 배지 텍스트 템플릿filterable?: 입력 검색 기능placeholder?: 선택 전 표시 문구noResultsText?: 검색 결과 없음 문구showSelectAll?: 전체 선택/해제 버튼 표시selectAllText?/clearAllText?: 전체 선택/해제 텍스트onChange?/onValueChange?: 값 변경 콜백className?: 추가 Tailwind 클래스id?: input id 지정disabled?: 비활성화 여부선택 도시: incheon
const cityList = Lib.EasyList([
{ value: 'seoul', text: '서울' },
{ value: 'busan', text: '부산' },
{ value: 'incheon', text: '인천' },
{ value: 'daegu', text: '대구' },
]);
const profileDataObj = Lib.EasyObj({ address: { city: 'incheon' } });
<Lib.Combobox
dataList={cityList}
dataObj={profileDataObj.address}
dataKey="city"
placeholder="도시 선택"
status="success"
statusMessage={`선택 도시: ${profileDataObj.address.city}`}
/>value prop: seoul
const [controlledCity, setControlledCity] = useState('seoul');
<Lib.Combobox
dataList={cityList}
value={controlledCity}
onValueChange={setControlledCity}
placeholder="도시 선택 (외부 상태 제어)"
status="info"
statusMessage={`value prop: ${controlledCity}`}
/>다중 선택 (EasyList selected와 연결된 값 동시 반영)
<Lib.Combobox
dataList={cityList}
dataObj={profileDataObj.address}
dataKey="favorites"
multi
multiSummary
showSelectAll
summaryText="{count}개 도시 선택"
placeholder="좋아하는 도시 선택"
status="warning"
statusMessage="다중 선택 (EasyList selected와 연결된 값 동시 반영)"
/>불러오는 중…도시 목록을 불러오는 중입니다.
<Lib.Combobox
dataList={Lib.EasyList([{ value: '', text: '불러오는 중', placeholder: true }])}
status="loading"
assistiveText="도시 목록을 불러오는 중입니다."
disabled
/>표시할 항목이 없습니다.선택 가능한 도시가 없습니다.
<Lib.Combobox
dataList={Lib.EasyList([])}
status="empty"
assistiveText="선택 가능한 도시가 없습니다."
/>EasyList(dataList)를 기반으로 동작합니다. 선택 상태가 dataList.selected에 반영되므로 별도의 useState가 필요 없습니다. 키보드(↑/↓/Enter/ESC) 조작과 바깥 영역 클릭 시 닫기를 지원합니다.
dataList: 항목 배열 또는 EasyList (label, value, selected, disabled)open?: 외부에서 열림 상태 제어defaultOpen?: 초기 열림 상태onOpenChange?: 열림 상태 변경 시 콜백onSelect?: 항목 선택 시 콜백 (선택 항목 반환)trigger?: 버튼 대신 사용할 노드 또는 render-proplabelKey?/valueKey?: 라벨/값 키 (기본 'label'/'value')placeholder?: 선택 없음 표시 텍스트variant?: 'outlined' | 'filled' | 'text'size?: 'sm' | 'md' | 'lg'rounded?: 모서리 class (기본 'rounded-lg')elevation?: shadow class (기본 'shadow-md')buttonClassName?/iconClassName?: 버튼/아이콘 추가 클래스menuClassName?/itemClassName?/activeClassName?: 메뉴/항목/활성 항목 클래스selectedItemClassName?: 선택 항목 텍스트 클래스showCheck?: 선택 체크 표시 여부 (기본 true)multiSelect?: 다중 선택 모드 (선택해도 자동으로 닫히지 않음)side?/align?: 메뉴 위치/정렬closeOnSelect?: 선택 후 자동 닫힘 여부 (기본 true)disabled?: 드롭다운 비활성화className?: 루트 추가 클래스const [lastAction, setLastAction] = useState('없음');
<Dropdown
dataList={actionList}
trigger={<span>행 액션</span>}
onSelect={(actionItemObj) => {
const label = actionItemObj?.label;
setLastAction(label || '없음');
}}
/>selected 플래그로만 관리되고, 드롭다운은 바깥을 클릭하거나 트리거를 다시 눌러야 닫힌다.<Dropdown dataList={multiRoleList} multiSelect placeholder="역할 선택 (다중 선택)" /><Dropdown dataList={fruitOptionList} variant="filled" size="lg" elevation="shadow-lg" />const [sortLabel, setSortLabel] = useState('최신순');
<Dropdown
dataList={sortOptionList}
variant="text"
placeholder="정렬 기준 선택"
trigger={({ selectedLabel }) => (
<span>{selectedLabel ?? '정렬 기준'}</span>
)}
onSelect={(sortItemObj) => {
const label = sortItemObj?.label;
setSortLabel(label || '');
}}
/><Dropdown dataList={placementMenuList} side="top" align="end" /><Dropdown dataList={presetChoiceList} />전역 setLoading을 통해 전체 화면 로딩 오버레이를 표시합니다.
const loadingTimerRef = useRef(null);
const { setLoading } = useGlobalUi();
/**
* @description 전역 로딩 예제 타이머를 정리
* 처리 규칙: 예제 컴포넌트가 사라지면 pending timeout을 취소한다.
*/
useEffect(() => () => clearTimeout(loadingTimerRef.current), []);
/**
* @description 전역 로딩 버튼 클릭을 처리
* 처리 규칙: 로딩 표시 후 2초 뒤 자동 해제한다.
*/
const handleLoadingClick = () => {
setLoading(true);
clearTimeout(loadingTimerRef.current);
loadingTimerRef.current = setTimeout(() => setLoading(false), 2000);
};
<Lib.Button onClick={handleLoadingClick}>
전체 화면 로딩 (2초)
</Lib.Button>전역 스토어(useGlobalUi)의 showAlert로 간단히 알림을 표시합니다.
정보/성공/경고/오류 유형을 지원하며 제목과 메시지를 지정할 수 있습니다.
title?: 알림 제목 (기본: '알림')text: 표시 메시지type?: 'info' | 'success' | 'warning' | 'error'onClick?: 확인 버튼 클릭 콜백// useSharedStore 사용
const { showAlert } = useGlobalUi();
// 기본 알림
showAlert('기본 알림 메시지입니다.');// 정보/성공/경고/오류 알림
showAlert('정보 알림 메시지입니다.', { title: '정보', type: 'info' });
showAlert('성공 알림 메시지입니다.', { title: '성공', type: 'success' });
showAlert('경고 알림 메시지입니다.', { title: '경고', type: 'warning' });
showAlert('오류 알림 메시지입니다.', { title: '오류', type: 'error' });// 알림 닫힘 시 실행될 콜백
showAlert('작업이 완료되었습니다.', {
title: '알림',
onClick: function() {
alert('알림을 닫았습니다.');
}
});// useRef 로 입력창 참조 생성
const inputRef = useRef(null);
// 알림을 닫으면 입력창으로 포커스 이동
<div className="flex gap-4 items-center">
<Lib.Button
onClick={() => {
showAlert('알림을 닫히면 입력창으로 커서가 이동합니다.', {
title: '알림',
onFocus: () => inputRef.current?.focus(),
});
}}
>
알림 띄우기
</Lib.Button>
<Lib.Input ref={inputRef} placeholder="커서가 여기로 이동합니다" />
</div>전역 스토어(useGlobalUi)의 showConfirm로 확인 대화상자를 띄웁니다.
Promise를 반환하며, 사용자의 선택(확인: true, 취소: false)을 then으로 받을 수 있습니다.
title?: 대화상자 제목text: 표시 메시지type?: 'info' | 'warning' | 'danger'onConfirm?, onCancel?: 콜백confirmText?, cancelText?: 버튼 텍스트// useSharedStore 사용
const { showConfirm, showAlert } = useGlobalUi();
// 기본 확인
showConfirm('정말 진행하시겠습니까?').then((result) => {
if (result) showAlert('확인했습니다.');
});// 경고 확인
showConfirm('해당 작업은 되돌릴 수 없습니다.\n계속하시겠습니까?', {
title: '주의',
type: 'warning',
confirmText: '계속',
cancelText: '중단',
});
// 위험 확인
showConfirm('모든 데이터를 삭제합니다.\n정말 삭제하시겠습니까?', {
title: '위험 확인',
type: 'danger',
confirmText: '삭제',
cancelText: '취소',
});// 확인/취소 시 실행될 콜백
showConfirm('삭제를 진행하시겠습니까?', {
title: '위험 확인',
type: 'danger',
confirmText: '삭제',
cancelText: '취소',
onConfirm: () => showAlert('삭제가 완료되었습니다.'),
onCancel: () => showAlert('삭제가 취소되었습니다.'),
});// useRef 로 입력창 참조 생성
const inputRef = useRef(null);
// 모달 닫힘 후 입력창으로 포커스 이동
<div className="flex gap-4 items-center">
<Lib.Button
onClick={() => {
showConfirm('확인 모달이 닫히면 입력창으로 커서가 이동합니다.', {
title: '포커스 이동',
onFocus: () => inputRef.current?.focus(),
});
}}
>
포커스 이동 표시
</Lib.Button>
<Lib.Input ref={inputRef} placeholder="커서가 여기로 이동합니다" />
</div>전역 스토어(useGlobalUi)의 showToast로 간단한 알림 배너를 표시합니다.
정보/성공/경고/오류 유형, 6가지 위치, 지속시간 제어를 지원합니다.
message: 표시 내용type?: 'info' | 'success' | 'warning' | 'error'position?: top/bottom - left/center/rightduration?: 자동 닫힘 시간(ms), Infinity로 무한// useSharedStore 사용
const { showToast } = useGlobalUi();
// 기본 토스트
showToast('기본 토스트 메시지입니다.');showToast('정보 토스트 메시지입니다.', { type: 'info' });
showToast('성공 토스트 메시지입니다.', { type: 'success' });
showToast('경고 토스트 메시지입니다.', { type: 'warning' });
showToast('오류 토스트 메시지입니다.', { type: 'error' });showToast('상단 왼쪽에 표시합니다.', { position: 'top-left' });
showToast('상단 중앙에 표시합니다.', { position: 'top-center' });
showToast('상단 오른쪽에 표시합니다.', { position: 'top-right' });
showToast('하단 왼쪽에 표시합니다.', { position: 'bottom-left' });
showToast('하단 중앙에 표시합니다.', { position: 'bottom-center' });
showToast('하단 오른쪽에 표시합니다.', { position: 'bottom-right' });showToast('2초에 사라집니다.', { duration: 2000 });
showToast('5초에 사라집니다.', { duration: 5000 });
showToast('자동으로 사라지지 않습니다.', { duration: Infinity });hover, focus 또는 클릭에 반응하는 간단한 툴팁입니다.
content: 툴팁 내용placement?: 위치 'top' | 'bottom' | 'left' | 'right'trigger?: 'hover' | 'click' | 'focus'delay?: 표시 지연(ms)disabled?: 비활성화 여부textDirection?: 텍스트 방향 'lr' | 'tb'className?: 추가 Tailwind 클래스children?: 트리거 요소<Lib.Tooltip content="기본 툴팁">
<Lib.Button>Hover</Lib.Button>
</Lib.Tooltip><Lib.Tooltip content="오른쪽" placement="right"><Lib.Button>right</Lib.Button></Lib.Tooltip><Lib.Tooltip content="클릭" trigger="click"><Lib.Button>Click</Lib.Button></Lib.Tooltip>Badge는 상태, 권한, 단계, 카테고리를 짧은 라벨로 표현하는 컴포넌트입니다. 채도가 낮은 slate·indigo 색상 위에서 과하게 튀지 않도록 색상과 밀도를 정돈합니다.
children배지 내부에 표시할 텍스트 또는 아이콘
variant?neutral, primary, success, warning, danger, outline
size?sm 또는 md 크기 선택
pill?완전히 둥근 pill 형태로 표시
className?추가 Tailwind 클래스
운영 화면에서 자주 쓰는 상태값을 색상별로 빠르게 구분합니다.
<Lib.Badge pill>검토 대기</Lib.Badge>
<Lib.Badge variant="primary" pill>신규 요청</Lib.Badge>
<Lib.Badge variant="success" pill>운영 정상</Lib.Badge>
<Lib.Badge variant="warning" pill>확인 필요</Lib.Badge>
<Lib.Badge variant="danger" pill>장애 발생</Lib.Badge>강조도를 낮춘 보조 라벨과 둥근 상태 칩을 함께 보여줍니다.
<Lib.Badge variant="outline">읽기 전용</Lib.Badge>
<Lib.Badge variant="outline" pill>보조 필터</Lib.Badge>
<Lib.Badge variant="primary" pill>활성 조건</Lib.Badge>테이블 안의 작은 라벨과 카드 상단의 중간 라벨을 분리해 사용합니다.
<Lib.Badge size="sm" variant="success" pill>완료</Lib.Badge>
<Lib.Badge size="md" variant="primary" pill>진행 중</Lib.Badge>상태 의미가 중요한 곳에는 아이콘을 붙여 스캔 속도를 높입니다.
모든 smoke가 통과한 상태
리뷰 또는 QA 대기
즉시 원인 확인 필요
<Lib.Badge variant="success" pill><Lib.Icon icon="md:MdCheck" /> 배포 완료</Lib.Badge>
<Lib.Badge variant="warning" pill><Lib.Icon icon="md:MdSchedule" /> 검토중</Lib.Badge>
<Lib.Badge variant="danger" pill><Lib.Icon icon="md:MdClose" /> 차단됨</Lib.Badge>Stat은 대시보드와 관리 화면에서 핵심 KPI를 한눈에 보여주는 요약 카드입니다. 값, 증감, 아이콘, 도움말을 조합해 숫자형 지표와 상태형 지표를 같은 규칙으로 표시합니다.
label지표 이름 또는 기준
value강조 표시할 핵심 값
delta?증감률 또는 상태 변화
deltaType?up, down, neutral 변화 방향
icon?우측 보조 아이콘 노드
helpText?하단 보조 설명
className?루트 추가 클래스
가장 중요한 지표를 카드 한 장으로 명확하게 보여줍니다.
<Lib.Stat
label="이번 주 활성 사용자"
value="12,340"
delta="+3.2%"
deltaType="up"
helpText="지난 7일 기준, 전주 대비"
icon={<Lib.Icon icon="ri:RiUserHeartLine" className="h-5 w-5 text-indigo-600" />}
/>상승/하락/중립 지표를 같은 grid 안에서 비교합니다.
<div className="grid gap-3 md:grid-cols-3">
<Lib.Stat label="완료된 요청" value="1,024" delta="+84" deltaType="up" />
<Lib.Stat label="대기 시간" value="132ms" delta="-18ms" deltaType="down" />
<Lib.Stat label="리뷰 대기" value="6건" delta="동일" deltaType="neutral" />
</div>값이 숫자가 아니어도 상태와 도움말을 함께 전달할 수 있습니다.
<Lib.Stat
label="서비스 상태"
value="정상"
delta="99.99%"
deltaType="neutral"
helpText="최근 30일 가용성"
icon={<Lib.Icon icon="md:MdCloudDone" className="h-5 w-5 text-emerald-600" />}
/>Skeleton은 실제 데이터가 도착하기 전에도 화면의 구조와 밀도를 유지하는 로딩 플레이스홀더입니다. 목록, 프로필, 카드처럼 반복되는 패턴을 먼저 보여주면 레이아웃 흔들림이 줄어듭니다.
variant?rect, text, circle 형태 선택
lines?text variant의 라인 수
circleSize?circle variant의 크기(px)
className?높이, 너비, 여백 등 추가 클래스
문단 또는 리스트가 로딩 중일 때 콘텐츠 밀도를 미리 보여줍니다.
<div className="space-y-3">
<Lib.Skeleton variant="text" lines={3} />
<Lib.Skeleton className="h-3 w-2/3 rounded-full" />
</div>프로필, 댓글, 활동 로그처럼 반복되는 행 구조에 사용합니다.
<div className="flex items-center gap-3">
<Lib.Skeleton variant="circle" circleSize={48} />
<div className="flex-1 space-y-2">
<Lib.Skeleton className="h-3 w-36 rounded-full" />
<Lib.Skeleton className="h-3 w-2/3 rounded-full" />
</div>
<Lib.Skeleton className="h-6 w-16 rounded-full" />
</div>대시보드 카드가 로딩 중일 때 최종 레이아웃을 안정적으로 유지합니다.
<Lib.Card className="bg-white">
<div className="flex items-center gap-3">
<Lib.Skeleton variant="circle" circleSize={40} />
<div className="flex-1 space-y-2">
<Lib.Skeleton className="h-3 w-32 rounded-full" />
<Lib.Skeleton className="h-3 w-24 rounded-full" />
</div>
</div>
<div className="mt-5 grid gap-3 sm:grid-cols-3">
<Lib.Skeleton className="h-16 w-full rounded-xl" />
<Lib.Skeleton className="h-16 w-full rounded-xl" />
<Lib.Skeleton className="h-16 w-full rounded-xl" />
</div>
</Lib.Card>Empty는 데이터가 없거나 필터 결과가 비었을 때 상황과 다음 행동을 안내합니다. 단순한 빈 화면이 아니라, 사용자가 복구하거나 새 항목을 만들 수 있는 방향을 함께 제시합니다.
icon?상단 아이콘 이름
title?비어 있는 상태 제목
description?후속 행동을 안내하는 설명
children?추가 안내 또는 보조 콘텐츠
action?버튼 등 주요 액션 요소
className?루트 추가 클래스
목록이나 조회 결과가 비어 있을 때 간결한 안내를 제공합니다.
새 프로젝트를 만들거나 필터 조건을 조정하면 이 영역에 결과가 표시됩니다.
<Lib.Empty
icon="ri:RiInboxArchiveLine"
title="아직 등록된 항목이 없습니다"
description="새 프로젝트를 만들거나 필터 조건을 조정하면 이 영역에 결과가 표시됩니다."
/>사용자가 다음 행동을 바로 선택할 수 있도록 CTA를 함께 배치합니다.
검색어를 줄이거나 상태 필터를 전체로 바꿔 다시 확인해 보세요.
<Lib.Empty
icon="md:MdSearchOff"
title="조건에 맞는 결과가 없습니다"
description="검색어를 줄이거나 상태 필터를 전체로 바꿔 다시 확인해 보세요."
action={<Lib.Button size="sm">새 항목 만들기</Lib.Button>}
>
<Lib.Badge variant="outline" pill>검색어: design</Lib.Badge>
</Lib.Empty>헤더, 본문, 푸터를 한 번에 묶는 기본 컨테이너 컴포넌트입니다. 대시보드 요약, 설정 패널, 동작 카드처럼 반복되는 정보 블록을 정돈된 흰색 카드로 표현합니다.
id와 title을 함께 전달하면 카드가 생성된 제목 id를 aria-labelledby로 참조합니다. 제목이 없으면 깨진 참조를 만들지 않도록 aria-labelledby도 생략됩니다.
children본문 콘텐츠
title?헤더 제목
subtitle?제목 아래 보조 텍스트
actions?헤더 우측 액션 요소
footer?하단 푸터 콘텐츠
className?추가 Tailwind 클래스
id?카드 식별자. title과 함께 사용하면 제목 기반 aria-labelledby를 연결
headerClassName?헤더 영역에 추가할 클래스
bodyClassName?본문 영역에 추가할 클래스
footerClassName?푸터 영역에 추가할 클래스
콘텐츠를 한 덩어리로 묶는 가장 기본적인 카드 구조입니다.
이번 스프린트 핵심 지표
<Lib.Card title="프로젝트 요약" subtitle="이번 스프린트 핵심 지표">
<div className="flex flex-wrap gap-2 text-sm">
<Lib.Badge variant="success" pill>운영 정상</Lib.Badge>
<Lib.Badge variant="primary" pill>12개 작업 완료</Lib.Badge>
<Lib.Badge variant="neutral" pill>리뷰 2건 대기</Lib.Badge>
</div>
</Lib.Card>헤더 액션과 푸터 메타 정보를 함께 배치한 운영 화면형 카드입니다.
릴리즈 전 필수 확인
<Lib.Card
title="배포 체크리스트"
subtitle="릴리즈 전 필수 확인"
actions={<Lib.Button size="sm" onClick={() => showAlert('체크리스트 액션')}>검토 시작</Lib.Button>}
footer={<span>마지막 업데이트: 방금 전</span>}
>
<div className="grid gap-3 sm:grid-cols-2">
<div className="rounded-lg bg-slate-50 px-3 py-2 ring-1 ring-slate-200/80">
<div className="text-xs font-semibold uppercase tracking-wide text-slate-500">완료</div>
<div className="mt-1 text-lg font-semibold text-slate-950">12건</div>
</div>
<div className="rounded-lg bg-indigo-50 px-3 py-2 ring-1 ring-indigo-100">
<div className="text-xs font-semibold uppercase tracking-wide text-indigo-600">진행 중</div>
<div className="mt-1 text-lg font-semibold text-indigo-700">3건</div>
</div>
</div>
</Lib.Card>헤더 없이 본문만 강조할 때 사용하는 간결한 정보 패널입니다.
간단한 안내, 공지, 상태 메모를 헤더 없이 표시할 때 사용합니다.
<Lib.Card className="bg-slate-950 text-white ring-slate-800" bodyClassName="p-5 text-slate-200">
<div className="text-xs font-semibold uppercase tracking-wide text-slate-400">System note</div>
<div className="mt-2 text-base font-semibold text-white">본문만으로도 강조되는 패널</div>
<p className="mt-1 text-sm text-slate-300">간단한 안내, 공지, 상태 메모를 헤더 없이 표시할 때 사용합니다.</p>
</Lib.Card>Badge, Icon, footer를 조합해 실제 대시보드 카드에 가까운 구조를 보여줍니다.
활성 사용자 그룹
<Lib.Card
title="고객 세그먼트"
subtitle="활성 사용자 그룹"
actions={<Lib.Badge variant="primary" pill>New</Lib.Badge>}
footer={<div className="flex items-center gap-2 text-xs text-slate-500"><Lib.Icon icon="md:MdSchedule" /> 업데이트: 방금 전</div>}
>
<div className="flex items-start gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-indigo-50 text-indigo-700 ring-1 ring-inset ring-indigo-100">
<Lib.Icon icon="ri:RiUserSmileLine" size="22px" />
</div>
<div>
<div className="font-semibold text-slate-900">프리미엄 전환 후보</div>
<div className="text-sm text-slate-500">최근 30일 활동량이 높은 계정 128개</div>
</div>
</div>
</Lib.Card>데이터 테이블과 카드 목록을 같은 사용 방식으로 렌더링합니다. 외부 상태 제어 또는 자체 페이지 이동, 주소 검색 조건, 브라우저 저장 값 유지를 지원해 실제 관리 화면의 목록 경험을 빠르게 구성할 수 있습니다.
data/dataListcolumnsrowKey?page?pageSize?defaultPage?onPageChange?pageParam?persistKey?variant?renderCard?gridClassName?empty?loading?status?className?headerClassName?rowClassName?cellClassName?rowsClassName?주소의 검색 조건과 세션 저장 값을 함께 사용하는 기본 데이터 테이블입니다.
<Lib.EasyTable
data={tableRowList}
columns={tableColumnList}
pageParam="page"
persistKey="table-basic"
defaultPage={1}
pageSize={10}
className="shadow-sm"
/>page/onPageChange를 외부 상태로 관리하는 제어형 페이지네이션 예시입니다.
const [pageNo, setPageNo] = useState(2);
<Lib.EasyTable
data={tableRowList}
columns={tableColumnList}
page={pageNo}
pageSize={5}
maxPageButtons={7}
onPageChange={setPageNo}
/>같은 데이터 소스를 카드 그리드로 보여주는 사용자 목록 패턴입니다.
<Lib.EasyTable
variant="card"
data={tableRowList}
pageSize={8}
renderCard={(row) => (
<div className="rounded-xl bg-white p-4 shadow-sm ring-1 ring-slate-200/80 transition-shadow hover:-translate-y-0.5 hover:shadow-md">...</div>
)}
/>행과 셀을 분리된 카드처럼 표현한 정돈된 업무 화면 스타일입니다.
<Lib.EasyTable
data={tableRowList}
columns={tableStyleColList}
headerClassName="bg-transparent gap-2"
rowClassName="gap-2 !bg-transparent !border-0 hover:!bg-transparent"
rowsClassName="mt-2 space-y-2"
cellClassName="rounded-lg bg-white p-3 shadow-sm ring-1 ring-slate-200/80"
pageSize={6}
/>데이터가 없을 때 사용자에게 명확한 안내를 제공하는 상태 예시입니다.
<Lib.EasyTable data={[]} columns={tableColumnList} empty="표시할 데이터가 없습니다." />독립 컴포넌트로 제어형 페이지 이동을 제공하며, Table 내장 페이징과 같은 상호작용 계약을 공유합니다. 목록 하단의 방향 버튼, edge 이동, 번호 윈도우를 일관된 컨트롤로 제공합니다.
page현재 페이지 번호 (1부터)
pageCount전체 페이지 수
onChange페이지 변경 시 새 페이지 전달
maxButtons?표시할 최대 번호 버튼
showEdges?처음/끝 버튼과 생략 표시 여부
className?래퍼 추가 클래스
목록 하단에 바로 넣기 좋은 기본 page/onChange 패턴입니다.
const [pageNo, setPageNo] = useState(2);
<Lib.Pagination
page={pageNo}
pageCount={12}
onChange={setPageNo}
className="rounded-full bg-slate-50 px-2 py-1 ring-1 ring-slate-200/80"
/>페이지 수가 많을 때 번호 버튼을 제한하고 edge 이동을 유지하는 패턴입니다.
const [pageNo, setPageNo] = useState(5);
<Lib.Pagination
page={pageNo}
pageCount={50}
maxButtons={5}
onChange={setPageNo}
className="rounded-full bg-white px-2 py-1 shadow-sm"
/>Tab 컴포넌트는 Tab.Item을 사용해 관련 콘텐츠 패널을 묶습니다. 기본은 분할 버튼 형태이며, 밀도 높은 화면에서는 variant="underline"으로 전환해 같은 사용 방식 안에서 두 가지 탭 스타일을 유지합니다.
dataObj?/dataKey?현재 탭 인덱스와 데이터 필드를 연결
tabIndex?초기 또는 제어 탭 인덱스
onChange?탭 변경 시 호출
variant?segmented 또는 underline
className?래퍼 추가 클래스
childrenTab.Item 목록
EasyObj 데이터 연결로 현재 탭을 관리하는 기본 분할형 탭입니다.
const tabDataObj = Lib.EasyObj({
selectedTab: 0
});
<Lib.Tab dataObj={tabDataObj} dataKey="selectedTab">...</Lib.Tab>tabIndex/onChange를 외부 상태에 연결해 화면 상태를 직접 제어합니다.
tabIndex와 onChange를 직접 연결한 제어 탭 예시입니다.
const [activeTab, setActiveTab] = useState(0);
<Lib.Tab tabIndex={activeTab} onChange={setActiveTab}>...</Lib.Tab>className으로 주변 배경과 밀도를 조정한 관리 화면형 탭입니다.
<Lib.Tab
className="rounded-xl bg-white p-4 shadow-sm ring-1 ring-slate-200/80"
dataObj={tabDataObj}
dataKey="customTab"
>...</Lib.Tab>variant="underline"을 prop으로 유지해 밀도 높은 화면의 상단 탭에 사용합니다.
<Lib.Tab variant="underline" dataObj={tabDataObj} dataKey="underlineTab">...</Lib.Tab>Tab.Item title에 JSX를 전달해 아이콘과 텍스트를 함께 표시합니다.
<Lib.Tab dataObj={tabDataObj} dataKey="iconTab">...</Lib.Tab>화면 측면에서 슬라이드 인 되는 패널입니다. 외부 Collapse 탭과 리사이즈를 지원하며 Tailwind px 클래스 기반 크기 설정이 가능합니다.
isOpen: 열림 상태onClose?: 닫힘 콜백side?: 위치 'right' | 'left' | 'top' | 'bottom'size?: 패널 크기 Tailwind 클래스 문자열(min-[1468px]:w-[360px], min-[1468px]:h-[220px] 등)closeOnBackdrop?: 배경 클릭 시 닫힘closeOnEsc?: ESC 키로 닫힘resizable?: 드래그로 크기 조절collapseButton?: 접기 버튼 표시className?: 추가 Tailwind 클래스children?: 패널 내용<Lib.Drawer isOpen={open} onClose={close} side="right" resizable collapseButton>패널 내용</Lib.Drawer><Lib.Drawer isOpen={open} onClose={close} side="right" size="min-[1468px]:w-[360px]" collapseButton>width 360px</Lib.Drawer><Lib.Drawer isOpen={open} onClose={close} side="left" size="min-[1468px]:w-[420px]" collapseButton>width 420px</Lib.Drawer><Lib.Drawer isOpen={open} onClose={close} side="top" size="min-[1468px]:h-[220px]" collapseButton>height 220px</Lib.Drawer><Lib.Drawer isOpen={open} onClose={close} side="bottom" size="min-[1468px]:h-[260px]" collapseButton>height 260px</Lib.Drawer><Lib.Drawer isOpen={open} onClose={close} side="right" collapseButton><Lib.Card title="카드 샘플">드로어 안 카드</Lib.Card></Lib.Drawer>Modal 컴포넌트는 Header, Body, Footer 영역을 가진 팝업 대화상자입니다.
5가지 크기(sm, md, lg, xl, full)를 지원하며, full은 화면 가장자리 16px 여백을 둔 너비·높이 전체 영역을 사용합니다.
ESC 키, 배경 클릭, 포커스 이동 제한, 기본 접근성 이름, 선택적 드래그 이동을 지원합니다.
isOpen: 열림 상태onClose?: 닫힘 콜백size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'draggable?: 헤더 드래그 이동closeOnBackdrop?: 배경 클릭 시 닫힘closeOnEsc?: ESC 키로 닫힘top?/left?: 초기 위치 지정className?: 추가 Tailwind 클래스children: 모달 내부 콘텐츠고객 상태 변경 확인
중요 변경 전 요약과 액션을 한 화면에서 확인합니다.
const [isOpen, setIsOpen] = useState(false);
<Lib.Button icon="ri:RiCheckboxCircleLine" onClick={() => setIsOpen(true)}>
기본 모달 열기
</Lib.Button>
<Lib.Modal isOpen={isOpen} onClose={() => setIsOpen(false)} ariaLabel="고객 상태 변경 확인 모달">
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-indigo-600">CUSTOMER STATUS</p>
<h2 className="mt-1 text-xl font-semibold text-slate-950">상담 완료로 변경할까요?</h2>
<p className="mt-1 text-sm text-slate-500">변경 전에 고객 정보와 처리 내용을 확인하세요.</p>
</Lib.Modal.Header>
<Lib.Modal.Body>
<div className="rounded-xl border border-slate-200 bg-slate-50/80 p-4">...</div>
</Lib.Modal.Body>
<Lib.Modal.Footer>
<div className="flex justify-end gap-2">...</div>
</Lib.Modal.Footer>
</Lib.Modal>사이즈 프리셋
작업 밀도에 따라 sm부터 full까지 선택합니다.
const [isOpen, setIsOpen] = useState(false);
const modalSizeList = ['sm', 'md', 'lg', 'xl', 'full'];
const [currentSize, setCurrentSize] = useState('md');
<div className="flex flex-wrap gap-2">
{modalSizeList.map(size => (
<Lib.Button key={size} aria-pressed={size === currentSize} variant={size === currentSize ? 'primary' : 'outline'} onClick={() => {
setCurrentSize(size);
setIsOpen(true);
}}>
{size.toUpperCase()} 크기
</Lib.Button>
))}
</div>
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
size={currentSize}
ariaLabel={`${currentSize.toUpperCase()} 크기 모달`}
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-indigo-600">Preset size</p>
<h2 className="mt-1 text-xl font-semibold text-slate-950">{currentSize.toUpperCase()} 크기 모달</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>
<div className="flex flex-wrap gap-2" role="group" aria-label="모달 크기 변경">
{modalSizeList.map(size => (
<Lib.Button
key={size}
size="sm"
aria-pressed={size === currentSize}
variant={size === currentSize ? 'primary' : 'outline'}
onClick={() => setCurrentSize(size)}
>
{size.toUpperCase()}
</Lib.Button>
))}
</div>
</Lib.Modal.Body>
</Lib.Modal>const [isOpen, setIsOpen] = useState(false);
<Lib.Button icon="ri:RiUserSettingsLine" onClick={() => setIsOpen(true)}>
폼 모달 열기
</Lib.Button>
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
size="lg"
ariaLabel="사용자 정보 편집 모달"
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-indigo-600">Profile</p>
<h2 className="mt-1 text-xl font-semibold text-slate-950">사용자 정보</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>
<form className="grid gap-4 sm:grid-cols-2">
<div>
<label className="block text-sm font-medium text-slate-700">이름</label>
<Lib.Input className="mt-1" placeholder="이름을 입력하세요" />
</div>
<div>
<label className="block text-sm font-medium text-slate-700">이메일</label>
<Lib.Input className="mt-1" type="email" placeholder="이메일을 입력하세요" />
</div>
</form>
</Lib.Modal.Body>
<Lib.Modal.Footer>
<div className="flex justify-end gap-2">
<Lib.Button variant="outline" onClick={() => setIsOpen(false)}>취소</Lib.Button>
<Lib.Button onClick={() => setIsOpen(false)}>저장</Lib.Button>
</div>
</Lib.Modal.Footer>
</Lib.Modal><Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
draggable
ariaLabel="드래그 가능한 모달"
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-indigo-600">Draggable</p>
<h2 className="mt-1 text-xl font-semibold text-slate-950">드래그 가능한 모달</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>...</Lib.Modal.Body>
</Lib.Modal>const [isOpen, setIsOpen] = useState(false);
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
top="20px"
left="calc(100% - 20px - 512px)"
draggable
ariaLabel="위치 지정 모달"
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-indigo-600">Positioned</p>
<h2 className="mt-1 text-xl font-semibold text-slate-950">위치 지정 모달</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>
<p>top, left prop으로 초기 위치를 지정할 수 있습니다.</p>
</Lib.Modal.Body>
</Lib.Modal>EasyEditor는 Tiptap 기반 리치 텍스트 에디터로, EasyObj 데이터 연결과 미리 구성된 확장 기능을 통해 쉽게 사용할 수 있습니다. 기본 직렬화는 JSON이며 serialization="html" | "text"로 모드를 바꿀 수 있습니다. 글자 크기, 색상, 정렬, 링크, 이미지/파일 첨부, Editor/HTML 모드 전환 등 핵심 기능을 제공합니다.
dataObj + dataKey: EasyObj 객체와 연결합니다. JSON을 기본 직렬화 형식으로 사용합니다.value + onChange: 외부 상태로 값을 제어합니다.serialization?: 'json' | 'html' | 'text' (기본: 'json')extensions?: Tiptap Extension 배열 (메모이즈되어 불필요한 재생성 방지)imageUploadUrl?, fileUploadUrl?: 업로드 엔드포인트 (기본 제공 Alert 안내)onUploadImage?, onUploadFile?: 커스텀 업로드 함수 주입 가능toolbar?: 툴바 표시 여부 (기본: true)status?: 'idle' | 'loading' | 'error' | 'success', 상태에 따른 스타일readOnly?: 읽기 전용 모드 (HTML 모드 전환 시 비활성화)툴바에서 폰트, 색상, 정렬, HTML 모드를 시험해보세요.
EasyObj 데이터 연결 기반 기본 사용
<Lib.EasyEditor
dataObj={editorDataObj}
dataKey="announcement"
serialization="html"
placeholder="팀 공지를 작성하세요"
label="공지 작성"
helperText="툴바에서 폰트, 색상, 정렬, HTML 모드를 시험해보세요."
/>status='success'로 상태 프리셋을 표시합니다.
초기 콘텐츠가 있는 데이터 연결 예시
<Lib.EasyEditor
dataObj={editorDataObj}
dataKey="onboardingGuide"
serialization="html"
placeholder="온보딩 가이드를 작성하세요"
label="가이드 편집"
status="success"
helperText="status='success'로 상태 프리셋을 표시합니다."
/><h3>HTML 메모</h3><p>외부 상태 제어에서는 <strong>serialization="html"</strong>을 사용합니다.</p>
EasyObj 데이터 연결 + HTML 직렬화
const editorDataObj = Lib.EasyObj({ htmlMemo: '<p>초기 HTML</p>' });
<Lib.EasyEditor
dataObj={editorDataObj}
dataKey="htmlMemo"
serialization="html"
placeholder="HTML 문자열을 직접 관리"
label="HTML 편집기"
/>툴바 숨김 + 수정 불가
오류 스타일 및 안내
처리 중 상태
성공 스타일
readOnly/invalid/status 매트릭스 예시
<div className="grid md:grid-cols-2 gap-4">
<Lib.EasyEditor value={'<p>읽기 전용 내용</p>'} serialization="html" readOnly toolbar={false} />
<Lib.EasyEditor value={'<p>유효성 오류 예시</p>'} serialization="html" invalid />
<Lib.EasyEditor value={'<p>로딩 상태</p>'} serialization="html" status="loading" />
<Lib.EasyEditor value={'<p>성공 상태</p>'} serialization="html" status="success" />
</div>EasyList 또는 배열 데이터를 그대로 받아 카드 스타일로 차트를 렌더링합니다.
시리즈는 {seriesId, seriesNm, dataKey, type, color} 구조를 권장합니다.
| Prop | 설명 |
|---|---|
| dataList | EasyList/배열 차트 데이터 |
| seriesList | EasyList/배열 시리즈 ({seriesId, seriesNm, dataKey, type, color}) |
| xKey | X축에 사용할 필드 키 (기본 label) |
| type | 기본 시리즈 타입 line|bar|area|pie|donut (기본 line) |
| loading / status | 로딩/에러/빈 상태 표시 플래그 |
| empty | 빈 상태 메시지 혹은 노드 |
| actions | 카드 우측 액션 영역 |
| hideLegend | 범례 숨김 여부 |
| legendFontSize | 범례 글자 크기(px, 기본 12) |
| showPieLabels | 파이/도넛 외부 라벨 표시 여부(도넛 기본 false, 좁은 파이 자동 숨김) |
| yAxisWidth | Y축 라벨 영역 너비(px, 최소 44, 기본 52) |
Example 1
가입자와 활성 이용자 비교
const sampleData = [
{ label: "1월", signups: 120, active: 90, churn: 12 },
{ label: "2월", signups: 150, active: 110, churn: 15 },
{ label: "3월", signups: 180, active: 130, churn: 18 },
{ label: "4월", signups: 220, active: 170, churn: 16 },
{ label: "5월", signups: 240, active: 190, churn: 20 },
];
<EasyChart
title="월별 가입 추이"
subtitle="가입자와 활성 이용자 비교"
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", color: "#4f46e5" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", color: "#10b981" },
]}
xKey="label"
type="line"
hideLegend={false}
height={260}
actions={<Button size="sm">내보내기</Button>}
/>Example 2
가입자/활성 이용자 막대 비교
const sampleData = [
{ label: "1월", signups: 120, active: 90, churn: 12 },
{ label: "2월", signups: 150, active: 110, churn: 15 },
{ label: "3월", signups: 180, active: 130, churn: 18 },
{ label: "4월", signups: 220, active: 170, churn: 16 },
{ label: "5월", signups: 240, active: 190, churn: 20 },
];
<EasyChart
title="월별 유입 비교"
subtitle="가입자/활성 이용자 막대 비교"
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "bar", color: "#4f46e5" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "bar", color: "#10b981" },
]}
xKey="label"
type="bar"
hideLegend
height={260}
/>Example 3
스택 막대와 이탈 라인 동시 확인
const sampleData = [
{ label: "1월", signups: 120, active: 90, churn: 12 },
{ label: "2월", signups: 150, active: 110, churn: 15 },
{ label: "3월", signups: 180, active: 130, churn: 18 },
{ label: "4월", signups: 220, active: 170, churn: 16 },
{ label: "5월", signups: 240, active: 190, churn: 20 },
];
<EasyChart
title="획득/이탈 믹스"
subtitle="스택 막대와 이탈 라인 동시 확인"
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "bar", color: "#4f46e5", stackId: "v" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "bar", color: "#10b981", stackId: "v" },
{ seriesId: "churn", seriesNm: "이탈", dataKey: "churn", type: "line", color: "#f43f5e" },
]}
xKey="label"
type="bar"
hideLegend={false}
xLabelFormatter={(label) => `${label} /22`}
yLabelFormatter={(val) => `${val}명`}
height={260}
/>Example 4
월별 데이터의 구성 분포
const sampleData = [
{ label: "1월", signups: 120, active: 90, churn: 12 },
{ label: "2월", signups: 150, active: 110, churn: 15 },
{ label: "3월", signups: 180, active: 130, churn: 18 },
{ label: "4월", signups: 220, active: 170, churn: 16 },
{ label: "5월", signups: 240, active: 190, churn: 20 },
];
<EasyChart
title="가입 구성 비율"
subtitle="월별 데이터의 구성 분포"
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "pie", color: "#4f46e5" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "pie", color: "#10b981" },
{ seriesId: "churn", seriesNm: "이탈", dataKey: "churn", type: "pie", color: "#f43f5e" },
]}
xKey="label"
type="pie"
hideLegend={false}
height={260}
/>Example 5
주요 지표의 상대 비중
const sampleData = [
{ label: "1월", signups: 120, active: 90, churn: 12 },
{ label: "2월", signups: 150, active: 110, churn: 15 },
{ label: "3월", signups: 180, active: 130, churn: 18 },
{ label: "4월", signups: 220, active: 170, churn: 16 },
{ label: "5월", signups: 240, active: 190, churn: 20 },
];
<EasyChart
title="활동 비중 도넛"
subtitle="주요 지표의 상대 비중"
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "donut", color: "#4f46e5" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "donut", color: "#10b981" },
{ seriesId: "churn", seriesNm: "이탈", dataKey: "churn", type: "donut", color: "#f43f5e" },
]}
xKey="label"
type="donut"
hideLegend
height={260}
/>Example 6
<EasyChart title="차트 로딩" dataList={[]} seriesList={[]} xKey="label" loading hideLegend />Example 7
<EasyChart title="빈 차트" dataList={[]} seriesList={[]} xKey="label" status="empty" empty="데이터 없음" />Example 8
const sampleData = [
{ label: "1월", signups: 120, active: 90, churn: 12 },
{ label: "2월", signups: 150, active: 110, churn: 15 },
{ label: "3월", signups: 180, active: 130, churn: 18 },
{ label: "4월", signups: 220, active: 170, churn: 16 },
{ label: "5월", signups: 240, active: 190, churn: 20 },
];
<EasyChart title="차트 오류" dataList={sampleDataList} seriesList={[]} xKey="label" status="error" errorText="API 에러가 발생했습니다." />로컬 파일 또는 외부 URL의 PDF를 미리봅니다. public/pdf-sample.pdf 예제가 포함되어 있습니다.
src(string|File|Blob|ArrayBuffer), workerSrc?, withToolbar?Example 1
public 폴더의 pdf-sample.pdf 미리보기
public/pdf-sample.pdf 파일이 제공되면 아래 뷰어가 렌더링됩니다.
PDF preview
Public path
<Lib.PdfViewer src="/pdf-sample.pdf" />Example 2
withToolbar=false 예시
툴바 비활성화(페이지/검색/줌 UI 숨김)
PDF preview
Public path
<Lib.PdfViewer src="/pdf-sample.pdf" withToolbar={false} />Example 3
로컬 파일 선택 후 뷰어로 표시
const [localFile, setLocalFile] = useState(null);
<input
type="file"
accept="application/pdf"
onChange={(event) => setLocalFile(event.target.files?.[0] ?? null)}
/>
{localFile && <Lib.PdfViewer src={localFile} />}Example 4
원격 URL로 PDF 표시(서버 CORS 허용 필요)
const [remoteUrl, setRemoteUrl] = useState('');
<input
value={remoteUrl}
onChange={(event) => setRemoteUrl(event.target.value)}
/>
{remoteUrl && <Lib.PdfViewer src={remoteUrl} />}Example 5
404/네트워크 오류시 오류 안내 렌더링
오류 상태(404) 시 Empty 안내로 대체
PDF preview
Public path
<Lib.PdfViewer src="/not-exists.pdf" />