이 문서는 프로젝트 내 공용 컴포넌트의 사용법, 위치, 담당자를 명시합니다. 디자인 구현 사항은 상단 Design 링크의 피그마(Figma)를 참고하세요.
components/atomic/BookCover.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
imageUrl |
string |
X | - | 책 표지 이미지 URL |
size |
'XS' | 'S' | 'M' | 'XL' |
O | - | 표지 크기 |
type |
'Image' | 'Upload' |
O | - | 'Image' 일 경우 반드시 imageUrl 함께 제공 |
className |
string |
X | - | 추가 디자인 |
<BookCover size="M" type="Image" imageUrl="https://via.placeholder.com/150" /><BookCover size="M" type="Upload" />components/atomic/Theme.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
imageUrl |
string |
O | - | 이미지 URL |
select |
boolean |
O | - | 선택 유무 |
onClick |
void |
O | - | 클릭 시 핸들러 함수 |
<Theme imageUrl="" select={select} onClick={() => setSelect(!select)} />components/atomic/Image.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
imageUrl |
string |
X | - | 이미지 URL |
type |
'Upload' | 'Skeleton'| 'Image'| 'Delete' |
O | - | 'Image', 'Delete'일 경우 반드시imageUrl` 함께 제공 |
<Image imageUrl="" type="Delete" /><Image type="Upload" />components/layout/Divider.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
width |
number | string |
O | - | 구분선 길이 |
<Divider width={"full"} /><Divider width={36} />components/layout/Dim.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
width |
number | string |
O | - | 너비 |
height |
number | string |
O | - | 높이 |
top |
number | string |
X | 0 | relative 기준 top 위치 |
left |
number | string |
X | 0 | relative 기준 left 위치 |
사용하기 위해선 반드시 부모 요소에 relative를 주어야 함
<Dim width={"full"} height={"full"} /><Dim width={360} height={200} top={20} left={1} />components/layout/MaskGradient.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
width |
number | string |
O | - | 너비 |
height |
number | string |
O | - | 높이 |
top |
number | string |
X | 0 | relative 기준 top 위치 |
left |
number | string |
X | 0 | relative 기준 left 위치 |
className |
string |
X | - | 기타 요소 (만약 부모 요소에 padding이 들어간다면 -m-4 이런 식으로 padding값 만큼 음수 margin으로 빼줌) |
사용하기 위해선 반드시 부모 요소에 relative를 주어야 함
<MaskGradient width={"full"} height={"full"} /><MaskGradient width={360} height={200} top={20} left={1} />src/components/input/SearchField.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
value |
string |
O | - | 검색어 값 |
onChange |
(v: string) => void |
O | - | 입력값 변경 핸들러 |
onSearchClick |
() => void |
X | - | 검색 버튼 클릭 시 실행 |
onEnter |
() => void |
X | - | Enter 키 입력 시 실행 |
onFocus |
() => void |
X | - | input focus 시 실행 |
onBlur |
() => void |
X | - | input blur 시 실행 |
placeholder |
string |
X | "검색어를 입력하세요" |
placeholder 텍스트 |
isInputMode |
boolean |
X | - | 입력 모드 여부 |
| Name | Type | Required | Default | Note |
| :-------------- | :-------------------- | :------: | :------------- | :--------------- |
value |
string |
O | - | 검색어 값 |
onChange |
(v: string) => void |
O | - | 입력값 변경 핸들러 |
onSearchClick |
() => void |
X | - | 검색 버튼 클릭 시 실행 |
onEnter |
() => void |
X | - | Enter 키 입력 시 실행 |
onFocus |
() => void |
X | - | input focus 시 실행 |
onBlur |
() => void |
X | - | input blur 시 실행 |
placeholder |
string |
X | "검색어를 입력하세요" |
placeholder 텍스트 |
<SearchField
value={query}
onChange={setQuery}
onSearchClick={handleSearch}
onEnter={handleSearch}
/>src/components/input/TextField.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
title |
string |
O | - | 입력 필드 제목 |
value |
string |
O | - | 입력 값 |
onChange |
(v: string) => void |
O | - | 값 변경 핸들러 |
placeholder |
string |
X | "" |
placeholder 텍스트 |
inputMode |
React.HTMLAttributes<HTMLInputElement>["inputMode"] |
X | - | 입력 모드 설정 |
disabled |
boolean |
X | false |
비활성화 여부 |
<TextField
title="이름"
value={name}
onChange={setName}
placeholder="이름을 입력해주세요."
/>src/components/input/TextField.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
title |
string |
O | - | 입력 필드 제목 |
value |
string |
O | - | 입력 값 |
onChange |
(v: string) => void |
O | - | 값 변경 핸들러 |
placeholder |
string |
X | "" |
placeholder 텍스트 |
disabled |
boolean |
X | false |
비활성화 여부 |
<TitleTextField
title="책 제목"
value={title}
onChange={setTitle}
placeholder="책 제목을 입력하세요"
/>src/components/input/TextField.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
title |
string |
O | - | 입력 필드 제목 |
value |
Record<string, string> |
O | - | 3개 input의 값 객체 |
onChange |
(v: Record<string, string>) => void |
O | - | 값 변경 핸들러 |
fields |
[{ key, placeholder, maxLen, inputMode? }, ...] |
O | - | 3칸 입력 필드 정의 |
digitsOnly |
boolean |
X | true |
숫자만 입력 허용 |
disabled |
boolean |
X | false |
비활성화 여부 |
const [birth, setBirth] = useState({
year: "",
month: "",
day: "",
});
<TripleTextField
title="생년월일"
value={birth}
onChange={setBirth}
fields={[
{ key: "year", placeholder: "YYYY", maxLen: 4, inputMode: "numeric" },
{ key: "month", placeholder: "MM", maxLen: 2, inputMode: "numeric" },
{ key: "day", placeholder: "DD", maxLen: 2, inputMode: "numeric" },
]}
/>;src/components/input/textinput/TextArea.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
title |
string |
X | - | textarea 제목 |
value |
string |
O | - | 입력 값 |
onChange |
(v: string) => void |
O | - | 값 변경 핸들러 |
placeholder |
string |
X | "" |
placeholder 텍스트 |
maxLength |
number |
X | - | 최대 글자 수 제한 |
disabled |
boolean |
X | false |
비활성화 여부 |
<TextArea
title="소개"
value={text}
onChange={setText}
placeholder="내용을 입력해주세요."
maxLength={200}
/>
- Design:
- Author: 임서연
src/components/section/dropDown/DropDown.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
initialYear |
number |
X | 2026 | 초기 년도 |
initialMonth |
number |
O | - | 초기 월 |
startYear |
number |
O | - | 나타낼 시작 연도 |
endYear |
number |
O | - | 나타낼 마지막 연도 |
onApply |
(value: { year: number; month: number; yearMonth: string }) => void |
O | - | 적용 버튼에 대한 함수 |
<DropDown
initialYear={2026}
initialMonth={11}
startYear={2025}
endYear={2026}
onApply={(value) => {
setAppliedValue(value);
console.log("적용된 값:", value);
}}
/>src/components/section/checkvox/Checkbox.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
text |
string |
O | - | 체크박스 내용 |
<Checkbox text="이거어디까지길어지는지한번ㅁ만실행해보고싶은데가능할까여?"/>
- Design:
- Author: 임서연
src/components/action/Button/Icon.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
size |
"xs" or "s" or "m |
O | - | 아이콘 사이즈 |
children |
ReactNode |
O | - | svg 파일 |
className |
string |
X | - | 추가적인 클래스명 |
<Icon size="xs">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
{" "}
<path
d="M16 8.75C16 6.8272 15.2356 4.98267 13.876 3.62305C12.5164 2.2637 10.6726 1.50002 8.75 1.5C6.8272 1.5 4.98267 2.26342 3.62305 3.62305C2.26342 4.98267 1.5 6.8272 1.5 8.75C1.50002 10.6726 2.2637 12.5164 3.62305 13.876C4.98267 15.2356 6.8272 16 8.75 16C10.6728 16 12.5164 15.2356 13.876 13.876C15.2356 12.5164 16 10.6728 16 8.75ZM17.5 8.75C17.5 10.8192 16.7645 12.8109 15.4424 14.3818L19.2803 18.2197C19.573 18.5126 19.5731 18.9874 19.2803 19.2803C18.9874 19.5731 18.5126 19.573 18.2197 19.2803L14.3818 15.4424C12.8109 16.7645 10.8192 17.5 8.75 17.5C6.42941 17.5 4.20342 16.5784 2.5625 14.9375C0.921589 13.2966 2.04574e-05 11.0706 0 8.75C0 6.42938 0.921571 4.20343 2.5625 2.5625C4.20343 0.921571 6.42938 0 8.75 0C11.0706 2.04577e-05 13.2966 0.921589 14.9375 2.5625C16.5784 4.20342 17.5 6.42941 17.5 8.75Z"
fill="#ECECEC"
/>{" "}
</svg>
</Icon>src/components/action/Button/Text.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
text |
string |
O | - | 텍스트 내용 |
size |
"12" | "14" | "18" |
O | - | 텍스트 사이즈 |
active |
boolean |
O | "false" |
텍스트 활성화/비활성화 여부 |
onClick |
() => void |
X | - | 텍스트 Click에 대한 기능 |
<TextButton text="비활성" size="18" active={false} />
<TextButton text="활성" size="12" active={true} />src/components/action/Button/Solid.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
text |
string |
O | - | 텍스트 내용 |
variant |
"primary" | "secondary" | "disabled" | "alert" |
X | default |
버튼 종류 |
size |
"s" | "m" |
X | default |
버튼 크기 |
onClick |
void |
X | - | 클릭 함수 |
className |
string |
X | - | 추가적인 클래스 명 |
//default
<Solid text="버튼 텍스트"> 기본 </Solid>
//dark
<Solid text="버튼 텍스트" variant="primary">다른</Solid>
//danger
<Solid text="버튼 텍스트" variant="alert">제거</Solid>src/components/action/Button/FAB.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
icon |
ReactNode |
O | - | 내부에 들어갈 icon svg |
onClick |
() => void |
X | - | 눌럿을 때의 기능 함수 |
//svg 파일이라서 이렇게 <img 태그에 묶어서 진행했습니다.>
<FAB icon={<img src={PlusIcon} />} />src/components/action/Button/ContainerText.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
text |
string |
O | - | 텍스트 내용 |
active |
boolean |
X | false |
활성화/비활성화 여부 |
//비활성화
<ContainerText text="Text" />
//활성화
<ContainerText text="Text" active />src/components/action/chip/Chip.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
text |
string |
O | - | 텍스트 내용 |
variant |
"none" | "icon" |
O | - | 활성화/비활성화 유무 |
active |
boolean |
O | false |
활성화/비활성화 유무 |
icon |
ReactNode |
X | - | 아이콘 |
onClick |
()=> void |
X | - | 눌럿을 때의 기능 |
const [isActive, setIsActive] = useState(false);
//icon 있는 칩
<Chip
text="Text"
variant="icon"
active={isActive}
icon={<img src={PlusIcon} alt="Plus Icon"/>}
onClick={() => setIsActive(!isActive)}
/>
//text만 있는 칩
<Chip
text="Text"
variant="none"
active={true}
/>src/components/Chip/Emotion.tsx
| Name | Type | Required | Default | Note |
| :-------------- | :----------------------------------------------------------- | :-----------: | :---------- | :-------------------------- | ------------- | ---------------- | --- | --- | ---------- |
| size | string => "s" or "m" | O | - | chip의 사이즈 |
| emoji | string | O | - | emoji 문자 |
| text | string | X | - | emoji 옆 텍스트 |
| variant | "yellow" \| "pink" \| "green" \| "blue" \| "red" \| "none" | X | "default" | 탭 스타일 variant |
| active | boolean | O | "true" | chip의 비활성화/활성화 여부 |
| Name | Type | Required | Default | Note |
| :-------------- | :--------------------------------- | :------: | :---------- | :-------------- |
| size | string => "s" or "m" | O | - | chip의 사이즈 |
| emojiKey | "Fun" | "EMPATHIZING" | "USEFUL" | "SAD" | "COMPLICATED" | "UNCOMFORTABLE" | O | - | emoji 종류 |
| active | boolean | O | "false" | chip의 비활성화/활성화 여부 |
//size = s
<Emotion size="s" emoji="(^_^)" variant="yellow" active />
//size = s
<Emotion size="s" emojiKey={key} active={false}/>
//size = m
<Emotion size="m" emoji="(^_^)" text="재밌어요" variant="yellow" active />
//active 비활성화"
<Emotion size="m" emoji="(• o •)" text="유익해요" variant="yellow" active={false} />
- Design:
- Author: 박수지
src/components/navigation/tabs/TabBar.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
options |
readonly TabOption<T>[] |
O | - | 탭 옵션 목록 |
value |
T |
O | - | 현재 선택된 탭 값 |
onChange |
(v: T) => void |
O | - | 탭 변경 핸들러 |
buttonWidthPx |
number |
X | - | 탭 버튼 고정 너비 |
variant |
"default" | "underlineGradient" |
X | "default" |
탭 스타일 variant |
className |
string |
X | "" |
추가 클래스명 |
const tabOptions = [
{ value: "all", label: "전체" },
{ value: "popular", label: "인기" },
] as const;
<TabBar
options={tabOptions}
value={tab}
onChange={setTab}
variant="underlineGradient"
/>;src/components/navigation/tabs/Text.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
options |
readonly SegmentedOption<T>[] |
O | - | 세그먼트 옵션 목록 |
value |
T |
O | - | 현재 선택된 값 |
onChange |
(v: T) => void |
O | - | 값 변경 핸들러 |
className |
string |
X | "" |
추가 클래스명 |
ariaLabel |
string |
X | "segmented control" |
접근성용 라벨 |
variant |
"fluid" | "fixed" |
X | - | 레이아웃 variant |
buttonWidthPx |
number |
X | - | 버튼 고정 너비 |
const options = [
{ value: "book", label: "도서" },
{ value: "record", label: "기록" },
] as const;
<Text
options={options}
value={selected}
onChange={setSelected}
ariaLabel="콘텐츠 타입 선택"
/>;src/components/navigation/tabs/TextTab.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
label |
string |
O | - | 버튼에 표시될 텍스트 |
active |
boolean |
O | - | 활성화 여부 |
onClick |
() => void |
O | - | 클릭 핸들러 |
widthPx |
number |
X | - | 버튼 고정 너비 |
disabled |
boolean |
X | false |
비활성화 여부 |
<TextTab
label="전체"
active={activeTab === "all"}
onClick={() => setActiveTab("all")}
/>src/components/navigation/topnavigation/TopNavigation.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
left |
React.ReactNode |
X | - | 좌측 영역 요소 |
onClickLeft |
void |
X | - | 좌측 영역 요소 클릭 시 |
center |
React.ReactNode |
X | - | 중앙 영역 요소 |
right |
React.ReactNode |
X | - | 우측 영역 요소 |
onClickRight |
void |
X | - | 우측 영역 요소 클릭 시 |
className |
string |
X | "" |
추가 클래스명 |
<TopNavigation
left={<BackButton onClick={handleBack} />}
center={<span>상세 페이지</span>}
right={<button type="button">편집</button>}
/>src/components/navigation/topnavigation/TopGnb.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
onSearchClick |
() => void |
X | - | 검색 버튼 클릭 핸들러 |
onMenuClick |
() => void |
X | - | 메뉴 버튼 클릭 핸들러 |
onLogoClick |
() => void |
X | - | 로고 클릭 핸들러 |
logoSrc |
string |
X | - | 커스텀 로고 이미지 경로 |
logoAlt |
string |
X | "nook" |
로고 대체 텍스트 |
showSearch |
boolean |
X | true |
검색 아이콘 표시 여부 |
showMenu |
boolean |
X | true |
메뉴 아이콘 표시 여부 |
className |
string |
X | "w-full h-10 flex items-center justify-between" |
추가 클래스명 |
<TopGnb
onLogoClick={() => navigate("/")}
onSearchClick={() => navigate("/search")}
onMenuClick={openMenu}
/>src/components/navigation/ProgressIndicator.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
step |
number |
O | - | 현재 단계 |
total |
number |
O | - | 전체 단계 수 |
heightClassName |
string |
X | "h-1" |
진행바 높이 클래스 |
wrapperClassName |
string |
X | "w-full px-1" |
바깥 래퍼 클래스 |
<ProgressIndicator
step={2}
total={5}
heightClassName="h-1"
wrapperClassName="w-full px-1"
/>src/components/navigation/Footer.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| - | - | - | - | props 없음 |
<Footer />src/components/navigation/Pagination/PageIndicator/Resource/Dot.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| current | boolean | O | false |
현재 indx 유무 |
//false
<Dot/>
<Dot current={true} />src/components/navigation/Pagination/PageIndicator.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| cur | number | O | - | 현재 indx |
| total | number | O | - | 전체 indx 개수 |
<PageIndicator cur={0} total={4} />
- Design:
- Author: 임서연
components/content/list/Rank
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| rank | number | O | - | 등수 |
| title | string | O | - | 책 제목 |
const MOCK_RANKS = [
{ rank: 0, title: "The Pragmatic Programmer" },
{ rank: 1, title: "Clean Code: A Handbook of Agile Software Craftsmanship" },
{ rank: 2, title: "Refactoring: Improving the Design of Existing Code" },
{
rank: 3,
title:
"아주아주아주 긴 제목 테스트용 — 한 줄 말줄임이 제대로 적용되는지 확인하는 텍스트입니다",
},
];
<div className="space-y-3">
{MOCK_RANKS.map((item) => (
<Rank key={item.rank} rank={item.rank} title={item.title} />
))}
</div>;components/content/list/History
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| variant | "history" | "time" |
O | - | 독서 기록 / 시간 컴포넌트 기록 |
| time | string | O | - | 타이머 시간 |
| title | string | X | - | 시간 기록일 때의 제목 |
| hasIcon | boolean | X | true |
아이콘 유무 |
| onClick | void | X | - | 클릭 함수 |
//history (독서기록)
<HistoryInfoCard
variant="history"
time="2025.09.11"
/>
//time
<HistoryInfoCard
variant="time"
title="집중 시간"
time="01:24:12"
/>
//독서 상태 변경
<HistoryInfoCard
variant="time"
title="독서 중"
time="독서 상태를 변경했어요."
hasIcon=false
/>components/content/list/resource/Date
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| topText | string | O | - | 월.일 |
| bottomText | string | O | - | 해당 년도 4자리 |
<ResourceDate topText="01.12" bottomText="2026" />src/components/content/card/bookGoal/BookGoal.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
percent |
"ZERO" | "PCT_1_9" |"PCT_10_19"|"PCT_20_29" | |
O | - | 도서 읽은 퍼센트 |
"PCT_30_39" | "PCT_40_49" | "PCT_50_59" | "PCT_60_69" |
||||
"PCT_70_79" | "PCT_80_89" | "PCT_90_99" | "PCT_100" |
||||
message |
string |
O | - | 텍스트 |
//zero => mesage가 독서 목표를 설정하세요 라고 정해져있음
<BookGoal percent="ZERO" />
//zero 가 아닌 경우 message를 받아와야함
<BookGoal
percent="PCT_1_9"
message="100권까지 99권 남았어요."
/>src/components/content/card/book/Normal.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
imageUrl |
string |
O | - | 이미지 Url |
title |
string |
O | - | 제목 |
author |
string |
O | - | 저자 |
imageAlt |
string |
X | "card thumbnail" |
이미지 이름 |
onClick |
() => void |
X | - | 클릭할 때의 기능 |
<Normal
imageUrl={sampleBook}
title="아주 보통의 하루"
author="작가 이름"
/>
<Normal
imageUrl="https://covers.openlibrary.org/b/isbn/9780156012195-M.jpg"
title="The Little Prince"
author="Antoine de Saint-Exupéry"
/>
<Normal
imageUrl="https://covers.openlibrary.org/b/isbn/9780062316097-M.jpg"
title="아주 긴 제목이 들어갔을 때 두 줄까지 말줄임 처리가 잘 되는지 확인하기 위한 테스트용 제목입니다"
author="아주 긴 저자명 테스트용"
onClick={() => { console.log("normal clicked");}}
/>src/components/content/card/book/List.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
imageUrl |
string |
O | - | 이미지 url |
title |
string |
O | - | 제목 |
author |
string |
O | - | 저자 |
type |
"NONE" | "SEARCH" | "LIBRARY" | "REPORT" |
O | - | 제목 리스트에서 사용하는 종류 |
typeLabel |
string |
X | null |
기록에서 도서 선택 페이지에서 독서 전 / 독서 중 / 완독 |
imageAlt |
string |
X | - | 이미지 이름 |
onClick |
() => void |
X | - | 클릭했을 때의 기능 |
<BookList
imageUrl={sampleBook}
title="검색 결과 도서"
author="저자 이름"
type="SEARCH"
/>
<BookList
imageUrl="https://covers.openlibrary.org/b/isbn/9780156012195-M.jpg"
title="서재 도서"
author="Antoine de Saint-Exupéry"
type="LIBRARY"
typeLabel="01:00:30"
/>
<BookList
imageUrl="https://covers.openlibrary.org/b/isbn/9780062316097-M.jpg"
title="아이콘 없이 라벨만 있는 경우"
author="Yuval Noah Harari"
type="REPORT"
typeLabel="완독"
/>
<BookList
imageUrl="https://covers.openlibrary.org/b/isbn/9780439554930-M.jpg"
title="아이콘도 라벨도 없는 경우"
author="J.K. Rowling"
type="NONE"
/>
<BookList
imageUrl={sampleBook}
title="아주 긴 제목이 들어갔을 때 한 줄 말줄임 처리가 잘 되는지 확인하기 위한 테스트용 제목입니다"
author="아주 긴 저자명 테스트"
type="SEARCH"
typeLabel="읽는 중"
onClick={() => {console.log("book list clicked");}}
/>src/components/content/card/book/list/Focus.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
imageUrl |
string |
O | - | 이미지 url |
imageAlt |
string |
X | - | 이미지 이름 |
timeText |
string |
O | - | 시간 |
title |
string |
O | - | 제목 |
author |
string |
O | - | 저자 |
onClick |
() => void |
X | - | 클릭했을 때의 기능 |
<Focus
imageUrl="https://covers.openlibrary.org/b/isbn/9780156012195-M.jpg"
timeText="01:24:12"
title="The Little Prince"
author="Antoine de Saint-Exupéry"
/>
<Focus
imageUrl="https://covers.openlibrary.org/b/isbn/9780062316097-M.jpg"
timeText="12:59:59"
title="아주 긴 제목이 들어갔을 때 한 줄 말줄임 처리가 잘 되는지 확인하기 위한 테스트용 제목입니다"
author="아주 긴 저자명 테스트"
onClick={() => { console.log("media info card clicked");}}
/>src/components/content/Card/Book/List/Report.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
imageUrl |
string |
O | - | 이미지 url |
title |
string |
O | - | 제목 |
author |
string |
O | - | 저자 |
recent |
string |
O | - | 최근 리뷰 |
reviewNumber |
number |
O | - | 리뷰 수 |
imageAlt |
string |
X | "report cover" |
이미지 이름 |
onClick |
() => void |
X | - | 클릭했을 때의 기능 |
<Report
imageUrl="https://covers.openlibrary.org/b/isbn/9780156012195-M.jpg"
title="아주 긴 제목이 들어갔을 때 한 줄 말줄임 처리가 잘 되는지 확인하는 테스트용 제목입니다"
author="아주 긴 저자명 테스트"
recent="이 문장도 최근 리포트가 길어졌을 때 두 줄까지 자연스럽게 말줄임 처리되는지 확인하기 위한 테스트용 문장입니다."
reviewNumber={3}
/>
<Report
imageUrl="https://covers.openlibrary.org/b/isbn/9780062316097-M.jpg"
title="나는 오늘 어디까지라도 달릴 수 있어"
author="윤지한"
recent="최근 리포트 문장입니다. 두 줄 영역 안에서 얼마나 자연스럽게 정리되는지 확인합니다."
reviewNumber={99}
onClick={() => {console.log("report clicked");}}/>src/components/content/Card/Report/List.tsx
| Name | Type | Required | Default | Note |
| :-------------- | :----------------------------------------------------------- | :-----------: | :---------- | :----------------------------- | ------------- | ---------------- | --- | --- | ---------- |
| date | string | O | - | chip의 사이즈 |
| emoji | string | O | - | emoji 문자 |
| variant | "yellow" \| "pink" \| "green" \| "blue" \| "red" \| "none" | X | yellow | emoji 색상 |
| review | string | O | - | emoji 옆 텍스트 |
| image | string[] | X | [] | chip의 비활성화/활성화 여부 |
| onClick | () => void | X | - | 해당 컴포넌트 눌럿을 때의 함수 |
| Name | Type | Required | Default | Note |
| :-------------- | :------------------- | :------: | :---------- | :-------------- |
| date | string | O | - | chip의 사이즈 |
| emojiKey | "Fun" | "EMPATHIZING" | "USEFUL" | "SAD" | "COMPLICATED" | "UNCOMFORTABLE" | O | - | emoji 문자 |
| review | string | O | - | emoji 옆 텍스트 |
| image | string[] | X | [] | chip의 비활성화/활성화 여부 |
| onClick | () => void | X | - | 해당 컴포넌트 눌럿을 때의 함수 |
<ReportList
date="25.09.13"
emojiKey="FUN"
review="생각이 많아서 초반에는 잘 안 읽혔는데, 중간부터 조금씩 흐름을 탔다. 마지막 문장이 특히 오래 남았다."
images={[sampleBook]}
/>
<ReportList
date="25.09.14"
emojiKey="FUN"
review="오늘 기록은 이미지 여러 장이 들어갔을 때의 레이아웃을 확인하기 위한 테스트입니다. 줄 수가 늘어나면 카드 높이도 자연스럽게 커져야 합니다."
images={[
sampleBook,
"https://covers.openlibrary.org/b/isbn/9780156012195-M.jpg",
"https://covers.openlibrary.org/b/isbn/9780062316097-M.jpg",
"https://covers.openlibrary.org/b/isbn/9780439554930-M.jpg",
"https://covers.openlibrary.org/b/isbn/9780140449136-M.jpg",
]}
/>src/components/content/informationText/SectionHeader.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
size |
"13" | "14" | "16" | "20" | |
O | - | 컴포넌트 사이즈 |
top |
ReactNode |
O | - | 윗 칸 |
bottom |
ReactNode |
X | - | 아래 칸 |
showCaret |
boolean |
X | false |
토글 표시 여부 |
onToggle |
(open: boolean) => void |
X | - | 토글 내렸을 때의 함수 |
onClick |
() => void |
X | - | 해당 컴포넌트 눌럿을 때의 함수 |
//size 13 (토글 없음, 원하면 onClick 추가 가능)
<SectionHeader
size="13"
top={<span>Text</span>}
bottom={<span>Text</span>}
/>
//size 14 (토글 있음, 밑 설명단 없음)
<SectionHeader
size="14"
showCaret={true}
top={<CustomTitle />}
/>
//size 16 (토글 있음, 밑 설명단 있음)
<SectionHeader
size="16"
showCaret={true}
top={<span>제목</span>}
bottom={<CustomDescription />}
/>src/components/content/informationText/InformationSection.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
flow |
"vertical" | "horizontal" |
O | - | 수직/수평 선택 |
top |
string |
X | - | 위 또는 왼쪽 텍스트 |
bottom |
string |
O | - | 아래 또는 오른쪽 테스트 |
showCaret |
boolean |
X | false |
토글 표시 여부 |
onToggle |
(open: boolean) => void |
X | - | 토글 내렸을 때의 함수 |
onClick |
() => void |
X | - | 해당 컴포넌트 눌럿을 때의 함수 |
//vertical
<InformationSection
flow="vertical"
top="Text"
bottom="세로형 설명 텍스트입니다."
onClick={() => { console.log("vertical click");}}
onToggle={(open) => { console.log("vertical toggle:", open); }}
/>
//horizontal
<InformationSection
flow="horizontal"
top="Text"
bottom="가로형 설명 텍스트입니다."
/>components/content/calendar/resource/DayOfTheWeek.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| text | string |
O | - | 요일 |
<DayOfTheWeek text="T" />components/content/calendar/resource/Day.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| text | string |
O | - | 요일 |
| disable | boolean |
X | true |
활성화 유무 (비활성화 = true) |
<Day text="1" />
<Day text="M" disable={false} />components/content/calendar/resource/Indicator.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| percent | "none" | "0" | "25" | "50" | "75" | "100" |
O | - | 퍼센트 |
<Indicator percent="0" />components/content/calendar/resource/IndicatorSet.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| day | string |
O | - | 퍼센트 |
| disble | boolean |
X | true |
활성화 유무 |
| percent | "none" | "0" | "25" | "50" | "75" | "100" |
O | none |
퍼센트 |
<IndicatorSet day="TT"/>
<IndicatorSet day="TT" disable={false} percent="0" />components/content/Calendar/Resource/BookSet.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| day | string |
O | - | 퍼센트 |
| visible | boolean |
X | false |
시각화 유무 |
| disble | boolean |
X | false |
활성화 유무 |
| count | "single" | "multiple" |
X | single |
도서 수 여러 개 유무 |
| imageUrl | string |
X | - | 도서 이미지 |
| bookNum | number |
X | 0 |
도서 개수 |
<BookSet day="TT" visible={false} />
<BookSet
day="TT"
visible
disable
imageUrl="https://picsum.photos/44/64?random=1"
/>
<BookSet
day="TT"
visible
disable={false}
count="single"
imageUrl="https://picsum.photos/44/64?random=3"
/>
<BookSet
day="TT"
visible
disable={false}
count="multiple"
bookNum={2}
imageUrl="https://picsum.photos/44/64?random=5"
/>components/content/EmptyState/EmptyState.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| text | string |
O | - | 텍스트 |
| buttonText | string |
X | - | 버튼 이름 |
| onButtonClick | () => void |
X | - | 버튼 클릭 함수 |
<EmptyState text="표시할 항목이 없습니다." />
<EmptyState
text="아직 등록된 일정이 없습니다."
buttonText="추가하기"
onButtonClick={() => {console.log("추가하기 클릭");}}
/>components/content/Profile/Profile.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| active | boolean |
X | true |
활성화 유무 |
| imageUrl | string |
O | - | 이미지 url |
| name | string |
O | - | 이름 |
| time | string |
O | - | 시간 |
<Profile
imageUrl="https://picsum.photos/56/56?random=1"
name="수연"
time="09:00"
/>
<Profile
active={false}
imageUrl="https://picsum.photos/56/56?random=3"
name="수연"
time="09:00"
/>components/feedback/snackbar.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| icon | string | X | - | 아이콘 url |
| text | string | O | - | snack bar 문구 |
| buttonText | string | O | - | 버튼 문구 |
| onButtonClick | () => void | O | - | 버튼 클릭 함수 |
| isOpen | boolean | O | - | snackbar 노출 여부 |
| onClose | () => void | X | - | snackbar 종료 함수 |
사용하기 위해선 반드시 부모 요소에 relative를 주어야 함
해당 컴포넌트는 z-100으로 설정되어 있음
import book_shelf from "../assets/icons/book_shelf-gray-30.svg";
const [snackbar, setSnackbar] = useState({
open: false,
message: "",
});
<Snackbar
icon={book_shelf}
isOpen={snackbar.open}
onClose={() => setSnackbar({ ...snackbar, open: false })}
text={snackbar.message}
buttonText="서재로 이동"
onButtonClick={onClickSnackbar}
/>;components/feedback/Toast.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
| icon | string | X | - | 아이콘 url |
| text | string | O | - | toast 문구 |
| isOpen | boolean | O | - | toast 노출 여부 |
| onClose | () => void | X | - | toast 종료 함수 |
const [isToastOpen, setIsToastOpen] = useState(false);
<Toast
isOpen={isToastOpen}
onClose={() => setIsToastOpen(false)}
text="저장되었습니다."
/>;
- Design:
- Author:
src/components/presentation/modal/bottombanner/Origin.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
line1 |
string |
O | - | 첫 번째 문장 |
line2 |
string |
O | - | 두 번째 문장 |
iconSrc |
string |
X | defaultArrowRight |
우측 아이콘 이미지 경로 |
iconAlt |
string |
X | "arrow right" |
아이콘 대체 텍스트 |
useGradientOverlay |
boolean |
X | false |
그라데이션 오버레이 사용 여부 |
onClick |
() => void |
X | - | 클릭 핸들러 |
maxWidthPx |
number |
X | 343 |
최대 너비 |
className |
string |
X | "" |
추가 클래스명 |
ariaLabel |
string |
X | "banner action card" |
접근성 라벨 |
<BannerActionCard
line1="독서 기록을 추가해보세요"
line2="지금 바로 시작할 수 있어요"
onClick={handleClick}
/>src/components/presentation/modal/bottombanner/ReadingRecord.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
count |
number | string |
O | - | 독서 기록 개수 |
subtitle |
string |
O | - | 하단 설명 문구 |
onClick |
() => void |
X | - | 클릭 핸들러 |
maxWidthPx |
number |
X | 343 |
최대 너비 |
iconSrc |
string |
X | plusIcon |
우측 아이콘 이미지 경로 |
iconAlt |
string |
X | "plus" |
아이콘 대체 텍스트 |
className |
string |
X | "" |
추가 클래스명 |
ariaLabel |
string |
X | "reading record banner" |
접근성 라벨 |
<ReadingRecordBannerCard
count={12}
subtitle="이번 달 읽은 책을 기록해보세요"
onClick={handleClick}
/>src/components/presentation/modal/bottombanner/Small.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
label |
string |
O | - | 배너 문구 |
iconSrc |
string |
X | defaultArrowRight |
우측 아이콘 이미지 경로 |
iconAlt |
string |
X | "arrow right" |
아이콘 대체 텍스트 |
onClick |
() => void |
X | - | 클릭 핸들러 |
maxWidthPx |
number |
X | 343 |
최대 너비 |
className |
string |
X | "" |
추가 클래스명 |
ariaLabel |
string |
X | "single line banner card" |
접근성 라벨 |
<SingleLineBannerCard label="추천 도서를 확인해보세요" onClick={handleClick} />src/components/presentation/modal/bottomsheet/Origin.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
open |
boolean |
O | - | 바텀시트 열림 여부 |
onClose |
() => void |
O | - | 닫기 핸들러 |
title |
string |
X | - | 헤더 제목 |
footer |
BottomSheetFooterConfig |
X | - | 하단 버튼 설정 |
children |
React.ReactNode |
O | - | 바디 콘텐츠 |
closeOnOverlayClick |
boolean |
X | true |
오버레이 클릭 닫기 여부 |
className |
string |
X | "" |
추가 클래스명 |
<BottomSheet
open={open}
onClose={() => setOpen(false)}
title="필터 선택"
footer={{
layout: "double",
sizeMode: "equal",
leftVariant: "secondary",
leftLabel: "취소",
rightLabel: "적용",
onLeftClick: () => setOpen(false),
onRightClick: handleApply,
}}
>
<div>바텀시트 내용</div>
</BottomSheet>src/components/presentation/modal/popup/Origin.tsx
| Name | Type | Required | Default | Note |
|---|---|---|---|---|
open |
boolean |
O | - | 팝업 열림 여부 |
title |
string |
O | - | 팝업 제목 |
description |
string |
O | - | 팝업 설명 |
leftLabel |
string |
O | - | 왼쪽 버튼 텍스트 |
rightLabel |
string |
O | - | 오른쪽 버튼 텍스트 |
onLeftClick |
() => void |
X | - | 왼쪽 버튼 클릭 핸들러 |
onRightClick |
() => void |
X | - | 오른쪽 버튼 클릭 핸들러 |
onClose |
() => void |
X | - | 외부 닫기 핸들러 |
closeOnOverlayClick |
boolean |
X | true |
오버레이 클릭 닫기 여부 |
className |
string |
X | "" |
추가 클래스명 |
ariaLabel |
string |
X | "popup confirm modal" |
접근성 라벨 |
<PopupConfirmModal
open={open}
title="정말 삭제하시겠어요?"
description="삭제한 내용은 되돌릴 수 없어요."
leftLabel="취소"
rightLabel="삭제"
onLeftClick={() => setOpen(false)}
onRightClick={handleDelete}
/>