EasyObj와 EasyList는 React에서 복잡한 상태 관리를 단순화하는 데이터 클래스입니다.
객체나 배열의 중첩된 속성까지 자동으로 상태를 관리하며, 직관적인 방식으로 데이터를 조작할 수 있습니다.
useState와 달리 상태 변경 시 setter 함수를 호출할 필요가 없으며, 불변성을 자동으로 관리합니다.
EasyObj: 객체 형태 상태를 프록시로 래핑EasyList: 배열 상태에 CRUD 메서드 제공forAll: 모든 항목 순회하며 수정toJS: 순수 JS 구조로 변환{
"name": "홍길동",
"age": 20,
"hobbies": [
"독서",
"운동"
],
"address": {
"city": "서울",
"street": "강남대로"
}
}const dataObj = Lib.EasyObj({
name: '홍길동',
age: 20,
hobbies: ['독서', '운동'],
address: {
city: '서울',
street: '강남대로'
}
});
// 상태 변경 시 자동으로 리렌더링
dataObj.age += 1;
dataObj.hobbies.push('여행');
dataObj.address.city = '부산';[
{
"id": 1,
"text": "할 일 1"
},
{
"id": 2,
"text": "할 일 2"
}
]const taskList = Lib.EasyList([
{ id: 1, text: '할 일 1' },
{ id: 2, text: '할 일 2' }
]);
// 배열 메서드 사용
taskList.push({ id: 3, text: '할 일 3' });
taskList.pop();
// forAll 메서드로 모든 항목 수정
taskList.forAll(item => {
item.text += ' (완료)';
});Icon 컴포넌트는 다양한 아이콘 라이브러리를 통합하여 제공합니다.
각 아이콘 세트의 prefix를 사용하여 원하는 아이콘을 선택할 수 있습니다.
icon: 세트prefix:아이콘이름size?: 아이콘 크기 (기본: 1em)color?: 아이콘 색상ariaLabel?: 접근성 라벨decorative?: 장식용 여부(true면 숨김)className?: 추가 Tailwind 클래스// 기본 아이콘
<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="48px" /> // 더 큰 크기// 색상이 있는 아이콘
<Lib.Icon icon="bs:BsCheckCircle" className="text-green-500" size="24px" />
// 다양한 크기
<Lib.Icon icon="bs:BsCheckCircle" className="text-green-500" size="16px" />
<Lib.Icon icon="bs:BsCheckCircle" className="text-green-500" size="24px" />
<Lib.Icon icon="bs:BsCheckCircle" className="text-green-500" size="32px" />
<Lib.Icon icon="bs:BsCheckCircle" className="text-green-500" size="48px" />// 소셜 미디어 아이콘
<Lib.Icon icon="fi:FiGithub" size="24px" />
// 다양한 크기
<Lib.Icon icon="fi:FiGithub" size="16px" />
<Lib.Icon icon="fi:FiGithub" size="24px" />
<Lib.Icon icon="fi:FiGithub" size="32px" />
<Lib.Icon icon="fi:FiGithub" size="48px" />Input 컴포넌트는 dataObj와 dataKey를 통해 양방향 바인딩을 지원합니다.
주요 기능:
dataObj?/dataKey?: 바운드 상태 객체와 키value?/defaultValue?: 제어/초기 값type?: HTML input 타입 (기본: 'text')placeholder?: 안내 문구filter?: 허용 문자 필터mask?: 입력 마스크 패턴 문자열 또는 함수maxDigits?/maxDecimals?: 숫자 자릿수 제한prefix?/suffix?: 입력 앞/뒤 표시 문자열togglePassword?: 비밀번호 토글 버튼error?: 에러 메시지onChange?/onValueChange?: 값 변경 콜백className?: 추가 Tailwind 클래스<Lib.Input
dataObj={inputDataObj}
dataKey="basicInput"
placeholder="텍스트 입력하세요"
/><Lib.Input
dataObj={inputDataObj}
dataKey="email"
type="email"
placeholder="이메일을 입력하세요"
/><Lib.Input
dataObj={inputDataObj}
dataKey="phone"
mask="###-####-####"
placeholder="전화번호: 010-1234-5678"
/><Lib.Input
dataObj={inputDataObj}
dataKey="businessNo"
mask="###-##-#####"
placeholder="사업자번호 123-45-67890"
/><Lib.Input
dataObj={inputDataObj}
dataKey="amount"
type="number"
maxDigits={10}
maxDecimals={2}
placeholder="숫자 입력 (최대 10자리, 소수점2자리)"
/><Lib.Input
dataObj={inputDataObj}
dataKey="code"
filter="A-Za-z0-9"
placeholder="영문/숫자 입력"
/><Lib.Input
dataObj={inputDataObj}
dataKey="koreanName"
filter="가-힣"
placeholder="한글 입력"
/><Lib.Input
dataObj={inputDataObj}
dataKey="email"
error="이메일 형식이 올바르지 않습니다"
placeholder="에러 상태 표시"
/><Lib.Input
dataObj={inputDataObj}
dataKey="price"
type="number"
maxDigits={10}
className="text-right"
placeholder="금액 입력"
suffix="원"
/><Lib.Input
dataObj={inputDataObj}
dataKey="searchKeyword"
prefix={<Lib.Icon icon="ri:RiSearchLine" className="w-5 h-5 text-gray-400" />}
placeholder="검색어를 입력하세요"
/><Lib.Input
dataObj={inputDataObj}
dataKey="password"
type="password"
placeholder="비밀번호 입력"
togglePassword
/>바운드와 컨트롤드 모드를 지원하며 줄바꿈을 보존하고 aria-invalid를 사용합니다.
rows?: 기본 줄 수 (기본: 4)dataObj?/dataKey?: 바운드 상태 객체와 키value?/defaultValue?: 제어/초기 값error?: 에러 메시지onChange?/onValueChange?: 값 변경 콜백placeholder?: 안내 문구className?: 추가 Tailwind 클래스disabled?/readOnly?: 상태 제어const textDataObj = Lib.EasyObj({ memo: '' });
<Lib.Textarea
dataObj={textDataObj}
dataKey="memo"
rows={4}
placeholder="메모를 입력하세요"
/>const [textValue, setTextValue] = useState('');
<Lib.Textarea
value={textValue}
onValueChange={setTextValue}
rows={3}
/><Lib.Textarea
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><Lib.Textarea placeholder="읽기 전용" value="내용 편집 불가" readOnly />
<Lib.Textarea placeholder="비활성화" disabled />Select 컴포넌트는 dataList의 selected 속성을 통해 선택 상태를 관리합니다.
옵션을 선택하면 선택된 항목의 selected가 true로, 나머지는 false로 자동 변경됩니다.
또는 dataObj/dataKey를 주면 EasyObj 바인딩으로 동작하며, 선택 값이 자동으로 반영됩니다.
dataList: 옵션 배열(EasyList 가능)valueKey/textKey?: 값/라벨 키 (기본: 'value'/'text')dataObj/dataKey?: EasyObj 바인딩 (value prop보다 우선하지 않음)value?/onValueChange?: 선택 값을 외부 상태와 동기화할 때 사용onChange?: change 이벤트 콜백 (value 포함)disabled?: 비활성화 여부error?: 에러 상태 표시className?: 추가 Tailwind 클래스dataList의 selected 플래그와 동기화됩니다.
const jobOptionList = Lib.EasyList([
{ id: '', label: '직무를 선택하세요', placeholder: true, selected: true },
{ id: 'designer', label: '디자이너' },
{ id: 'developer', label: '개발자' },
{ id: 'pm', label: '프로덕트 매니저' },
]);
<Lib.Select
dataList={jobOptionList}
valueKey="id"
textKey="label"
status="success"
statusMessage="dataList의 selected 플래그와 동기화됩니다."
/>value prop: developer
const [roleValue, setRoleValue] = useState('developer');
<Lib.Select
dataList={jobOptionList}
valueKey="id"
textKey="label"
value={roleValue}
onValueChange={setRoleValue}
status="info"
statusMessage={`value prop: ${roleValue}`}
/>사용할 수 없는 상태입니다.옵션을 불러오는 중입니다.
<Lib.Select
dataList={loadingOptionList}
valueKey="id"
textKey="label"
status="loading"
assistiveText="옵션을 불러오는 중입니다."
disabled
/>필수 입력 항목입니다.
<Lib.Select
dataList={jobOptionList}
valueKey="id"
textKey="label"
status="error"
statusMessage="필수 입력 항목입니다."
/>표시할 항목이 없습니다.선택 가능한 항목이 비어 있습니다.
<Lib.Select
dataList={Lib.EasyList([])}
status="empty"
assistiveText="선택 가능한 항목이 비어 있습니다."
/>Checkbox 컴포넌트는 dataObj와 dataKey를 통해 양방향 바인딩을 지원합니다.
name prop이 없을 경우 dataKey 또는 label을 name으로 사용합니다.
label?: 체크박스 옆에 표시할 텍스트name?: 그룹/폼 이름checked?: 선택 상태 제어dataObj?/dataKey?: 바운드 상태 객체와 키onChange?: 선택 변경 콜백color?: 체크박스 색상 또는 CSS 컬러disabled?: 비활성화 여부className?: 추가 Tailwind 클래스<Lib.Checkbox
label="기본 체크박스"
dataObj={checkboxDataObj}
dataKey="basicCheckbox"
/><Lib.Checkbox
label="비활성화 체크박스"
disabled
/><Lib.Checkbox
label="기본 색상 (Primary)"
dataObj={checkboxDataObj}
dataKey="primary"
color="primary"
/>
<Lib.Checkbox
label="커스텀 빨간색"
dataObj={checkboxDataObj}
dataKey="red"
color="#FF0000"
/>const [isChecked, setIsChecked] = useState(false);
<Lib.Checkbox
label="제어 컴포넌트"
checked={isChecked}
onChange={(event) => setIsChecked(event.target.checked)}
/><Lib.Checkbox
name="terms"
label="[필수] 서비스 이용약관 동의"
dataObj={termsDataObj}
dataKey="termsAgreed"
/>Radiobox 컴포넌트는 dataObj와 dataKey를 통해 양방향 바인딩을 지원합니다.
같은 name을 가진 라디오박스 그룹에서 선택된 value가 dataObj에 저장됩니다.
name prop이 없을 경우 dataKey 또는 label을 name으로 사용합니다.
label?: 라벨 텍스트name?: 그룹/폼 이름value: 선택 값dataObj?/dataKey?: 바운드 상태 객체와 키checked?/defaultChecked?: 선택 상태 제어onChange?: 선택 변경 콜백color?: 표시 색상disabled?: 비활성화 여부className?: 추가 Tailwind 클래스<Lib.Radiobox
name="job"
label="개발자"
value="developer"
dataObj={radioDataObj}
dataKey="selectedJob"
/><Lib.Radiobox
name="disabled"
label="비활성화 1"
value="disabled1"
disabled
/><Lib.Radiobox
name="payment"
label="신용카드"
value="card"
dataObj={radioDataObj}
dataKey="paymentMethod"
color="#FF6B6B"
/>const [optionValue, setOptionValue] = useState('');
<Lib.Radiobox
name="controlled"
label="옵션 1"
value="option1"
checked={optionValue === 'option1'}
onChange={(event) => setOptionValue(event.target.value)}
/>접근성을 준수하기 위해 role="switch"와 aria-checked를 사용합니다. dataObj와 dataKey 또는 제어 모드를 지원합니다.
name?: 그룹/폼 이름checked?: 선택 상태 제어defaultChecked?: 초기 선택 상태dataObj?/dataKey?: 바운드 상태 객체와 키onChange?/onValueChange?: 값 변경 콜백disabled?: 비활성화 여부label?: 스위치 라벨className?: 추가 Tailwind 클래스id?: input id 지정const switchDataObj = Lib.EasyObj({ enabled: false });
<Lib.Switch
dataObj={switchDataObj}
dataKey="enabled"
label={`바운드: ${switchDataObj.enabled ? 'ON' : 'OFF'}`}
/>const [isLocalOn, setIsLocalOn] = useState(false);
<Lib.Switch
checked={isLocalOn}
onValueChange={(nextValue) => setIsLocalOn(nextValue)}
label={`컨트롤드: ${isLocalOn ? 'ON' : 'OFF'}`}
/><Lib.Switch disabled defaultChecked label="비활성화" />
<Lib.Switch disabled label="비활성화 (OFF)" /><Lib.Switch
dataObj={switchDataObj}
dataKey="notifications"
id="notify-switch"
label="알림 허용"
/>스텝 버튼이 있는 숫자 입력입니다. EasyObj 바운드와 컨트롤드 모드를 지원합니다.
value?/defaultValue?: 제어/초기 값min?/max?: 허용 범위step?: 증감 단위 (기본: 1)dataObj?/dataKey?: 바운드 상태 객체와 키placeholder?: 안내 문구onChange?/onValueChange?: 값 변경 콜백className?: 추가 Tailwind 클래스disabled?: 비활성화 여부readOnly?: 읽기 전용id?: input id 지정const numberDataObj = Lib.EasyObj({ qty: 1 });
<Lib.NumberInput dataObj={numberDataObj} dataKey="qty" min={0} step={1} /><Lib.NumberInput dataObj={priceDataObj} dataKey="price" min={0} max={100} step={0.5} /><Lib.NumberInput defaultValue={10} step={5} />브라우저 기본 Date와 Time 입력을 래핑한 컴포넌트입니다. EasyObj 바운드와 컨트롤드 모드를 지원합니다.
value?: ISO 문자열 제어 값dataObj?/dataKey?: 바운드 상태 객체와 키defaultValue?: 초기 값min?/max?: 선택 가능 범위onChange?/onValueChange?: 값 변경 콜백className?: 추가 Tailwind 클래스disabled?: 입력 비활성화readOnly?: 읽기 전용placeholder?: 안내 문구id?: input id 지정const dateDataObj = Lib.EasyObj({ date: '' });
<Lib.DateInput dataObj={dateDataObj} dataKey="date" /><Lib.DateInput defaultValue="2025-01-01" min="2025-01-01" max="2025-12-31" />const timeDataObj = Lib.EasyObj({ time: '' });
<Lib.TimeInput dataObj={timeDataObj} dataKey="time" /><Lib.TimeInput defaultValue="09:30" step={60} />검색 가능한 단일·다중 선택 입력입니다. 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
/>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>상태 표시용 레이블입니다. 색상, 크기, 모양을 지원합니다.
children: 표시할 내용variant?: 색상 스타일 (기본: 'neutral')size?: 크기 'sm' | 'md' (기본: 'sm')pill?: 둥근 모양 여부 (기본: false)className?: 추가 Tailwind 클래스<Lib.Badge>Neutral</Lib.Badge>
<Lib.Badge variant="primary">Primary</Lib.Badge>
<Lib.Badge variant="success">Success</Lib.Badge>
<Lib.Badge variant="warning">Warning</Lib.Badge>
<Lib.Badge variant="danger">Danger</Lib.Badge><Lib.Badge variant="outline">Outline</Lib.Badge>
<Lib.Badge pill>Rounded</Lib.Badge><Lib.Badge size="sm">Small</Lib.Badge>
<Lib.Badge size="md">Medium</Lib.Badge><Lib.Badge variant="success"><Lib.Icon icon="md:MdCheck" /> 완료</Lib.Badge>
<Lib.Badge variant="warning"><Lib.Icon icon="md:MdSchedule" /> 진행중</Lib.Badge>
<Lib.Badge variant="danger"><Lib.Icon icon="md:MdClose" /> 실패</Lib.Badge>간단한 KPI/지표를 보여주는 카드. 아이콘은 aria-hidden, 값/증감에는 라벨 제공.
label: 지표 이름value: 표시할 값delta?: 증감 수치 또는 텍스트deltaType?: 'up' | 'down' | 'neutral' (기본 neutral)icon?: 우측 아이콘 노드helpText?: 하단 보조 설명className?: 루트 추가 클래스<Lib.Stat label="주간 활성 사용자" value="12,340" delta="+3.2%" deltaType="up" /><Lib.Stat label="월간 이탈률" value="1,024" delta="-1.1%" deltaType="down" /><Lib.Stat label="서버 상태" value="정상" deltaType="neutral" />로딩 중 콘텐츠 구조를 힌트로 보여주는 플레이스홀더입니다.
variant?: 'rect' | 'text' | 'circle'lines?: 텍스트 라인 수 (variant='text')circleSize?: 원형 크기(px)className?: 추가 Tailwind 클래스<Lib.Skeleton variant="text" lines={3} /><Lib.Skeleton variant="circle" circleSize={48} />
<Lib.Skeleton variant="text" lines={2} /><Lib.Card className="bg-white">
<div className="flex items-center gap-3">
<Lib.Skeleton variant="circle" circleSize={40} />
<div className="flex-1">
<Lib.Skeleton variant="text" lines={2} />
</div>
</div>
<div className="mt-4">
<Lib.Skeleton className="h-24 w-full" />
</div>
</Lib.Card>데이터가 없을 때 안내와 액션을 제공합니다.
icon?: 상단 아이콘 (react-icons)title?: 제목 텍스트description?: 부가 설명children?: 추가 내용action?: 버튼 등 액션 요소className?: 추가 Tailwind 클래스<Lib.Empty />다른 키워드로 다시 시도해 보세요
<Lib.Empty title="검색 결과 없음" description="다른 키워드로 다시 시도해 보세요" action={<Lib.Button>새로 만들기</Lib.Button>} />헤더, 본문, 푸터로 구성된 컴포넌트입니다.
children: 본문 콘텐츠title?: 헤더 제목subtitle?: 제목 아래 보조 텍스트actions?: 헤더 우측 액션 요소footer?: 하단 푸터 콘텐츠className?: 추가 Tailwind 클래스보조 설명
<Lib.Card title="간단 카드" subtitle="보조 설명">
카드 본문을 간결하게 구성합니다.
</Lib.Card>버튼과 함께
<Lib.Card
title="액션 카드"
subtitle="버튼과 함께"
actions={<Lib.Button onClick={() => showAlert('버튼 액션')}>Action</Lib.Button>}
footer="푸터 텍스트"
>
<div className="space-y-2">
<div>리스트 항목 1</div>
<div>리스트 항목 2</div>
</div>
</Lib.Card><Lib.Card className="bg-slate-50" bodyClassName="p-6">
헤더/푸터 패딩이 있는 카드입니다.
</Lib.Card><Lib.Card
title="조합 예시"
actions={<Lib.Badge variant="primary">New</Lib.Badge>}
footer={<div className="flex items-center gap-2 text-xs"><Lib.Icon icon="md:MdSchedule" /> 업데이트: 방금 전</div>}
>
<div className="flex items-start gap-3">
<div className="h-12 w-12 rounded bg-zinc-100 flex items-center justify-center text-zinc-700 ring-1 ring-zinc-200/60">IMG</div>
<div>
<div className="font-medium">이미지/아이콘과 텍스트</div>
<div className="text-sm text-gray-600">레이아웃과 구성 예시</div>
</div>
</div>
</Lib.Card>데이터 테이블과 카드 리스트를 렌더링합니다. 제어형 및 비제어 페이징을 지원하며 URL과 스토리지에 상태를 보존합니다.
data: 행 데이터 배열/EasyListcolumns: 열 정의 배열rowKey?: 행 키 결정 함수/키className?: 래퍼 추가 클래스headerClassName?/rowClassName?/cellClassName?/rowsClassName?: 세부 스타일empty?: 빈 상태 메시지loading?: 로딩 표시onRowClick?: 행 클릭 콜백page?/pageSize?/defaultPage?: 현재/페이지당/초기 페이지onPageChange?: 페이지 변경 콜백pageParam?/persist?/persistKey?: URL/스토리지 상태 유지maxPageButtons?: 페이지 버튼 수variant?: 'table' | 'card'renderCard?: 카드 모드 렌더 함수gridClassName?: 카드 모드 grid 클래스<Lib.EasyTable
data={tableRowList}
columns={tableColumnList}
pageParam="page"
persistKey="table-basic"
defaultPage={1}
pageSize={10}
/>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="border rounded p-4 bg-white hover:shadow">...</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="bg-white ring-1 ring-gray-200 rounded-2xl shadow-sm p-3"
pageSize={6}
/><Lib.EasyTable data={[]} columns={tableColumnList} empty="표시할 데이터가 없습니다." />독립 컴포넌트로 제어형 페이지 이동을 제공하며, Table 내장 페이징으로도 사용할 수 있습니다.
page: 현재 페이지 번호 (1부터)pageCount: 전체 페이지 수onChange: 페이지 변경 시 호출 (새 페이지 전달)maxButtons?: 표시할 최대 번호 버튼 (기본 7)showEdges?: 처음/끝 버튼 및 ... 표시 여부 (기본 true)className?: 래퍼 추가 클래스const [pageNo, setPageNo] = useState(2);
<Lib.Pagination page={pageNo} pageCount={12} onChange={setPageNo} />const [pageNo, setPageNo] = useState(5);
<Lib.Pagination page={pageNo} pageCount={50} maxButtons={5} onChange={setPageNo} />Tab 컴포넌트는 Tab.Item을 사용하여 탭 패널을 구성합니다.
기본 상태는 EasyObj 바인딩을 우선 사용하고, 제어 props 동작 설명용 예제에서만 useState를 함께 보여줍니다.
className prop을 통해 커스텀 스타일링을 지원합니다.
dataObj?/dataKey?: 현재 탭 인덱스 바인딩tabIndex?: 초기 탭 인덱스onChange?: 탭 변경 시 호출className?: 래퍼 추가 클래스children: Tab.Item 목록const tabDataObj = Lib.EasyObj({
selectedTab: 0
});
<Lib.Tab dataObj={tabDataObj} dataKey="selectedTab">...</Lib.Tab>tabIndex와 onChange를 직접 연결한 제어 탭 예시입니다.
const [activeTab, setActiveTab] = useState(0);
<Lib.Tab tabIndex={activeTab} onChange={setActiveTab}>...</Lib.Tab><Lib.Tab
className="bg-gray-100 rounded-lg p-4"
dataObj={tabDataObj}
dataKey="customTab"
>...</Lib.Tab><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)를 지원하며, 드래그 기능을 선택적으로 활성화할 수 있습니다.
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 onClick={() => setIsOpen(true)}>
기본 모달 열기
</Lib.Button>
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<h2 className="text-xl font-semibold">기본 모달</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>
<p>기본적인 모달 예시입니다.</p>
</Lib.Modal.Body>
<Lib.Modal.Footer>
<div className="flex justify-end">
<Lib.Button onClick={() => setIsOpen(false)}>
닫기
</Lib.Button>
</div>
</Lib.Modal.Footer>
</Lib.Modal>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}
onClick={() => {
setCurrentSize(size);
setIsOpen(true);
}}
>
{size.toUpperCase()} 크기
</Lib.Button>
))}
</div>
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
size={currentSize}
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<h2 className="text-xl font-semibold">{currentSize.toUpperCase()} 크기 모달</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>
<p>다양한 크기의 모달을 지원합니다.</p>
</Lib.Modal.Body>
</Lib.Modal>const [isOpen, setIsOpen] = useState(false);
<Lib.Button onClick={() => setIsOpen(true)}>
폼 모달 열기
</Lib.Button>
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<h2 className="text-xl font-semibold">사용자 정보</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>
<form className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">이름</label>
<Lib.Input className="mt-1" placeholder="이름을 입력하세요" />
</div>
<div>
<label className="block text-sm font-medium text-gray-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 onClick={() => setIsOpen(false)}>
저장
</Lib.Button>
<Lib.Button
variant="outline"
onClick={() => setIsOpen(false)}
>
취소
</Lib.Button>
</div>
</Lib.Modal.Footer>
</Lib.Modal>const [isOpen, setIsOpen] = useState(false);
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
draggable={true}
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<h2 className="text-xl font-semibold">드래그 가능한 모달</h2>
</Lib.Modal.Header>
<Lib.Modal.Body>
<p>이 모달은 헤더 영역을 드래그하여 이동할 수 있습니다.</p>
</Lib.Modal.Body>
<Lib.Modal.Footer>
<div className="flex justify-end">
<Lib.Button onClick={() => setIsOpen(false)}>
닫기
</Lib.Button>
</div>
</Lib.Modal.Footer>
</Lib.Modal>const [isOpen, setIsOpen] = useState(false);
<Lib.Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
top="20px"
left="calc(100% - 20px - 512px)"
draggable
>
<Lib.Modal.Header onClose={() => setIsOpen(false)}>
<h2 className="text-xl font-semibold">위치 지정 모달</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 | 범례 숨김 여부 |
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
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", color: "#18181b" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", color: "#0f766e" },
]}
xKey="label"
type="line"
hideLegend={false}
height={260}
actions={<Button size="sm">내보내기</Button>}
/>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
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "bar", color: "#18181b" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "bar", color: "#0f766e" },
]}
xKey="label"
type="bar"
hideLegend
height={260}
/>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
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "bar", color: "#18181b", stackId: "v" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "bar", color: "#0f766e", stackId: "v" },
{ seriesId: "churn", seriesNm: "이탈", dataKey: "churn", type: "line", color: "#dc2626" },
]}
xKey="label"
type="bar"
hideLegend={false}
xLabelFormatter={(label) => `${label} /22`}
yLabelFormatter={(val) => `${val}명`}
height={260}
/>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
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "pie", color: "#18181b" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "pie", color: "#0f766e" },
{ seriesId: "churn", seriesNm: "이탈", dataKey: "churn", type: "pie", color: "#dc2626" },
]}
xKey="label"
type="pie"
hideLegend={false}
height={260}
/>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
dataList={sampleDataList}
seriesList={[
{ seriesId: "signups", seriesNm: "가입자", dataKey: "signups", type: "donut", color: "#18181b" },
{ seriesId: "active", seriesNm: "활성이용자", dataKey: "active", type: "donut", color: "#0f766e" },
{ seriesId: "churn", seriesNm: "이탈", dataKey: "churn", type: "donut", color: "#dc2626" },
]}
xKey="label"
type="donut"
hideLegend
height={260}
/><EasyChart dataList={[]} seriesList={[]} xKey="label" loading hideLegend /><EasyChart dataList={[]} seriesList={[]} xKey="label" status="empty" empty="데이터 없음" />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 dataList={sampleDataList} seriesList={[]} xKey="label" status="error" errorText="API 에러가 발생했습니다." />로컬 파일 또는 외부 URL의 PDF를 미리봅니다. public/pdf-sample.pdf 예제가 포함되어 있습니다.
src(string|File|Blob|ArrayBuffer), workerSrc?, withToolbar?public/pdf-sample.pdf 파일이 제공되면 아래 뷰어가 렌더링됩니다.
public 폴더의 pdf-sample.pdf 미리보기
<Lib.PdfViewer src="/pdf-sample.pdf" />툴바 비활성화(페이지/검색/줌 UI 숨김)
withToolbar=false 예시
<Lib.PdfViewer src="/pdf-sample.pdf" withToolbar={false} />로컬 파일 선택 후 뷰어로 표시
const [localFile, setLocalFile] = useState(null);
<input
type="file"
accept="application/pdf"
onChange={(event) => setLocalFile(event.target.files?.[0] ?? null)}
/>
{localFile && <Lib.PdfViewer src={localFile} />}원격 URL로 PDF 표시(서버 CORS 허용 필요)
const [remoteUrl, setRemoteUrl] = useState('');
<input
value={remoteUrl}
onChange={(event) => setRemoteUrl(event.target.value)}
/>
{remoteUrl && <Lib.PdfViewer src={remoteUrl} />}오류 상태(404) 시 Empty 안내로 대체
404/네트워크 오류시 오류 안내 렌더링
<Lib.PdfViewer src="/not-exists.pdf" />