# TDS 디자인 시스템
Mordy와 Morit이 함께 쓰는 디자인 규칙이다. 화면을 만드는 데 필요한 값(토큰)과 부품(컴포넌트)을 이름으로 정해 두었다. 서비스가 달라도 이름과 모양은 같고 색 값만 바뀐다.
## 서비스
| 서비스 | 설명 | 주색(라이트) | 값을 읽은 곳 |
| --- | --- | --- | --- |
| Mordy | 사진으로 찍은 학습 자료를 문제로 바꿔 복습하게 해 주는 앱 | `#1F7BFF` | Mordy/apps/mobile/src/ui/tokens.ts (React Native 토큰(tokens.ts)) |
| Morit | 메모, 파일, AI, 플러그인을 한곳에 모으는 개인 작업 앱 | `#00856A` | Moring_Morit/lib/core/morit_theme.dart (Flutter Material 3 ColorScheme) |
## 빠른 시작
1. 서비스를 정한다(mordy 또는 morit).
2. 토큰을 불러온다: `` 그리고 ``. 값 전체는 https://developers.moring.co/tds/tokens.json.
3. 컴포넌트 목록(https://developers.moring.co/tds/components.md)에서 필요한 부품을 찾아 "사용 방법"의 코드를 쓴다. 코드와 속성 표는 앱(React Native, `@/ui/...`) 기준이다.
4. 사용 규칙(https://developers.moring.co/tds/guidelines.md)을 확인한다.
## 문서 목록
- [개요](https://developers.moring.co/tds.md): TDS가 무엇이고 어떻게 시작하는지
- [색상](https://developers.moring.co/tds/colors.md): 색상 토큰 전체와 서비스별 HEX 값, 글자 색 대비
- [글자](https://developers.moring.co/tds/typography.md): Pretendard, 글자 스타일 11종, 글자 크기 설정
- [간격](https://developers.moring.co/tds/spacing.md): 간격 9단계와 누르는 영역
- [모서리](https://developers.moring.co/tds/radius.md): 모서리 7단계
- [그림자](https://developers.moring.co/tds/elevation.md): 그림자 3단계와 다크 모드
- [모션](https://developers.moring.co/tds/motion.md): 시간, 곡선, 눌림, 진동
- [아이콘](https://developers.moring.co/tds/icons.md): Lucide 규칙과 아이콘 목록
- [컴포넌트](https://developers.moring.co/tds/components.md): 컴포넌트 문서 목록
- [사용 규칙](https://developers.moring.co/tds/guidelines.md): 번호가 붙은 사용 규칙
- [화면 전용 컴포넌트](https://developers.moring.co/tds/inventory.md): 공통 키트 밖 컴포넌트 전체 목록
- [AI용 파일](https://developers.moring.co/tds/for-ai.md): 기계가 읽는 파일 안내
---
# 색상
TDS의 색은 이름(토큰)으로 정해져 있다. 서비스(Mordy, Morit)가 바뀌어도 이름은 같고 HEX 값만 바뀐다. 코드에는 HEX 값이 아니라 토큰 이름을 쓴다.
웹에서는 CSS 변수 `--tds-{이름}`(예: `--tds-primary`)으로 쓴다. 값 전체: https://developers.moring.co/tds/tokens.json, https://developers.moring.co/tds/tokens.css
- Mordy 값 출처: `apps/mobile/src/ui/tokens.ts` (Mordy 저장소 60df633)
- Morit 값 출처: `lib/core/morit_theme.dart` (Moring_Morit 저장소 e50d1ee)
- "웹 확장" 토큰은 앱 코드에는 없고, 웹에서 글자 대비를 맞추려고 TDS가 더한 토큰이다.
## 배경과 면
화면, 패널, 입력 칸처럼 넓은 면에 쓰는 색이다.
| 토큰 | CSS 변수 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 용도 |
| --- | --- | --- | --- | --- | --- | --- |
| `bg` | `--tds-bg` | `#FCFCFD` | `#0B0F17` | `#F7F8FA` | `#1C2327` | 화면 배경. 화면 맨 뒤 배경이다. Screen 컴포넌트의 기본 배경이다. |
| `canvas` | `--tds-canvas` | `#F4F6FA` | `#0B0F17` | `#F7F8FA` | `#1C2327` | 회색 바탕. 흰 패널(Panel) 아래에 까는 옅은 회색 바탕이다. 시험 계획과 리포트 화면이 쓴다. |
| `surface` | `--tds-surface` | `#FFFFFF` | `#141A25` | `#FBFDFC` | `#242C30` | 흰 면. 패널, 시트, 대화상자, 탭 바, 선택 전 보기 칸의 배경이다. |
| `well` | `--tds-well` | `#F6F8FB` | `#1A2130` | `#F2F4F6` | `#2C353A` | 홈. 패널 안에서 행을 감싸는 옅은 홈이다. WellRow 배경과 시트 닫기 버튼 배경이다. |
| `card` | `--tds-card` | `#F3F7FE` | `#18202E` | `#F3FBF8` | `#283837` | 카드. 주색을 옅게 깐 카드 배경이다. Card의 기본 색이고 메뉴 목록 배경이다. |
| `cardStrong` | `--tds-card-strong` | `#EAF2FD` | `#1D2839` | `#EAF8F4` | `#2C433D` | 진한 카드. card보다 한 단계 진한 틴트다. 온보딩에서 선택된 타일과 다크 모드 토스트의 배경이다. |
| `selected` | `--tds-selected` | `#F4F8FF` | `#15233C` | `#F3FBF8` | `#2A3D3A` | 선택됨. 선택된 항목의 배경이다. ChoiceCard, WellRow, FilterPill이 선택되면 이 색을 깐다. |
| `field` | `--tds-field` | `#F4F7FB` | `#1A2130` | `#F2F4F6` | `#2C353A` | 입력 칸. Field의 기본 배경이다. 포커스를 받으면 surface로 바뀐다. |
| `muted` | `--tds-muted` | `#F2F5F9` | `#1A2130` | `#F0F3F2` | `#2C353A` | 보조 면. 누르지 않는 보조 면이다. 표 머리글, 작은 태그, 이전·다음 둥근 버튼, 모임 커버 자리의 배경이다. |
| `pressed` | `--tds-pressed` | `#F5F8FC` | `#1B2230` | `#F0F3F2` | `#343F44` | 누름. 행을 누르는 동안의 배경이다. SettingsRow와 메뉴 행이 쓴다. |
| `photo` | `--tds-photo` | `#F1F3F7` | `#10151F` | `#F2F4F6` | `#191F23` | 사진 자리. 사진을 불러오기 전이나 사진이 없을 때 깔리는 색이다. Avatar, SourceCrop, ImageBlock이 쓴다. |
| `skeleton` | `--tds-skeleton` | `#EAF0F8` | `#1C2433` | `#E8EAED` | `#343F44` | 로딩 막대. Skeleton의 막대 색이다. |
| `track` | `--tds-track` | `#E6ECF5` | `#253043` | `#E8EAED` | `#3D494F` | 빈 막대. 진행 막대, 슬라이더 레일, 차트 막대의 비어 있는 부분이다. |
## 선
구분선과 테두리에 쓰는 색이다.
| 토큰 | CSS 변수 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 용도 |
| --- | --- | --- | --- | --- | --- | --- |
| `line` | `--tds-line` | `#E8EDF5` | `#232C3C` | `#EEF1F0` | `#3A464A` | 기본 선. 구분선과 기본 테두리다. 두께는 1px(또는 헤어라인)이다. |
| `lineStrong` | `--tds-line-strong` | `#D9E1EC` | `#2F394C` | `#D8E1DE` | `#5E6D6D` | 진한 선. 선택 전 체크 원, 라디오 테두리, 시트 손잡이처럼 눈에 띄어야 하는 선이다. |
| `cardBorder` | `--tds-card-border` | `#EDF1F6` | `#212A3A` | `#E2E9E7` | `#3A464A` | 카드 테두리. ChoiceCard 테두리이고, 다크 모드에서는 Panel 테두리다. |
| `selectedLine` (앱 미사용) | `--tds-selected-line` | `#DCE8FB` | `#284B82` | `#D5EBE6` | `#42625C` | 선택 테두리. 선택된 항목의 옅은 테두리로 정의되어 있다. 앱 코드에서는 아직 읽지 않는다. |
## 글자
글자와 아이콘에 쓰는 색이다.
| 토큰 | CSS 변수 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 용도 |
| --- | --- | --- | --- | --- | --- | --- |
| `text` | `--tds-text` | `#141B2D` | `#ECF0F7` | `#17211F` | `#DCE3E4` | 본문. 제목과 본문 글자다. 흰 면 위에서 가장 진한 글자다. |
| `textSecondary` | `--tds-text-secondary` | `#737B8F` | `#9AA4B8` | `#65717D` | `#ACBABD` | 보조 글자. 날짜, 설명, 개수처럼 본문 아래에 놓는 보조 글자다. |
| `textTertiary` | `--tds-text-tertiary` | `#9AA3B5` | `#667085` | `#8B95A1` | `#82948F` | 약한 글자. 입력 칸 안내 문구와 쓸 수 없는 상태의 글자다. 읽어야 하는 내용에는 쓰지 않는다. |
| `tabInactive` | `--tds-tab-inactive` | `#6F7486` | `#7C8699` | `#65717D` | `#ACBABD` | 선택 안 된 탭. 하단 탭 바에서 선택되지 않은 아이콘과 이름이다. |
| `slate` | `--tds-slate` | `#4B5A74` | `#AAB5C8` | `#4E5A57` | `#A5BEC4` | 슬레이트. 선택되지 않은 달력 날짜 숫자, 노트 카드의 종류 이름, 선택되지 않은 알림 선택지의 글자다. |
| `navy` | `--tds-navy` | `#0B1F56` | `#DCE7FF` | `#173D34` | `#BAE7D6` | 네이비. 워드마크 글자와 PillTabs의 선택된 칸 색이다. 다크 모드의 PillTabs는 navy 대신 primary를 쓴다. |
| `textSecondaryInk` (웹 확장) | `--tds-text-secondary-ink` | `#6A7184` | `#9AA4B8` | `#65717D` | `#ACBABD` | 보조 글자(웹). 웹에서 작은 보조 글자를 쓸 때 쓴다. textSecondary를 흰 면에서 4.5:1이 되도록 진하게 만든 값이다. |
## 주색
서비스마다 값이 가장 크게 달라지는 색이다. 버튼, 링크, 선택 표시에 쓴다.
| 토큰 | CSS 변수 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 용도 |
| --- | --- | --- | --- | --- | --- | --- |
| `primary` | `--tds-primary` | `#1F7BFF` | `#3D8BFF` | `#00856A` | `#79C7AF` | 주색. 주 행동, 링크, 선택 표시, 진행 막대의 색이다. 앱 코드에서 가장 많이 쓰이는 색이다. |
| `primaryStrong` | `--tds-primary-strong` | `#0C70FC` | `#2F80FF` | `#00856A` | `#326C59` | 진한 주색. 색이 채워진 둥근 표식에 쓴다. 오늘 날짜 원과 중앙 버튼 그림자가 이 색이다. |
| `primarySoft` | `--tds-primary-soft` | `#E8F1FD` | `#172A4A` | `#EAF8F4` | `#2F4D43` | 옅은 주색. 선택 타일, D-Day 칩, 부드러운 버튼, 안내(Notice) 배경이다. |
| `primaryLine` | `--tds-primary-line` | `#9CC3FF` | `#35609E` | `#88D6BA` | `#286B55` | 주색 테두리. SecondaryButton의 테두리와 켜진 FilterPill의 테두리다. |
| `gradientFrom` | `--tds-gradient-from` | `#3A93FF` | `#3A93FF` | `#00856A` | `#326C59` | 그라데이션 시작. 주 버튼, 중앙 버튼, 진행 막대, 아바타 바탕의 그라데이션 시작 색(왼쪽 또는 위)이다. |
| `gradientTo` | `--tds-gradient-to` | `#0A70FF` | `#0A70FF` | `#00856A` | `#326C59` | 그라데이션 끝. 그라데이션의 끝 색(오른쪽 또는 아래)이다. |
| `onPrimary` (웹 확장) | `--tds-on-primary` | `#FFFFFF` | `#FFFFFF` | `#FFFFFF` | `#132F25` | primary 위 글자(웹). primary로 채운 면 위에 놓는 글자와 아이콘 색이다. 앱 코드에서는 white를 쓴다. |
| `onPrimaryStrong` (웹 확장) | `--tds-on-primary-strong` | `#FFFFFF` | `#FFFFFF` | `#FFFFFF` | `#EDF4EF` | primaryStrong·그라데이션 위 글자(웹). primaryStrong 또는 gradient로 채운 면 위에 놓는 글자와 아이콘 색이다. 앱 코드에서는 white를 쓴다. |
| `primaryInk` (웹 확장) | `--tds-primary-ink` | `#1A69D9` | `#4590FF` | `#007E65` | `#79C7AF` | 주색 글자(웹). 웹에서 작은 글자를 주색으로 쓸 때 쓴다. primary를 흰 면에서 4.5:1이 되도록 진하게 만든 값이다. |
## 탭과 구분 컨트롤
탭, 칩, 구분 컨트롤의 바탕에 쓰는 색이다.
| 토큰 | CSS 변수 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 용도 |
| --- | --- | --- | --- | --- | --- | --- |
| `segment` | `--tds-segment` | `#4B5A74` | `#3A4A66` | `#4E5A57` | `#30434C` | 구분 컨트롤 선택. 촬영 검토 화면에서 선택된 필터 칩의 배경이다. |
| `segmentTrack` | `--tds-segment-track` | `#F1F4F8` | `#1A2130` | `#F2F4F6` | `#2C353A` | 구분 컨트롤 바탕. 중립 대화상자 버튼, 시간 선택 띠, AiMark 칩의 배경이다. |
| `tabTrack` | `--tds-tab-track` | `#E9EDF3` | `#1A2130` | `#E8EAED` | `#2C353A` | 탭 바탕. PillTabs의 바탕 띠다. |
## 상태
성공, 주의, 오류를 알리는 색이다. 색만으로 알리지 않고 아이콘과 글자를 함께 쓴다.
| 토큰 | CSS 변수 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 용도 |
| --- | --- | --- | --- | --- | --- | --- |
| `success` | `--tds-success` | `#0E9057` | `#2FBF7E` | `#00856A` | `#79C7AF` | 성공. 정답, 완료, 성공 토스트의 아이콘 색이다. |
| `successSoft` | `--tds-success-soft` | `#ECF8F2` | `#11281E` | `#EAF8F4` | `#2F4D43` | 성공 면. 정답 보기와 FeedbackCard(correct)의 배경이다. |
| `successText` | `--tds-success-text` | `#1F724D` | `#7EDDB0` | `#173D34` | `#BAE7D6` | 성공 글자. successSoft 위에 놓는 글자 색이다. |
| `mint` | `--tds-mint` | `#2DC48D` | `#33C891` | `#00A884` | `#79C7AF` | 민트. 학습 목표선, 학습 완료 표시, 차트의 세 번째 선 색, 올라간 수치의 배경이다. |
| `mintText` | `--tds-mint-text` | `#17A673` | `#4FD6A2` | `#00856A` | `#79C7AF` | 민트 글자. 이번 주 달성 목표 수치와 올라간 수치의 글자와 아이콘이다. |
| `amber` | `--tds-amber` | `#F8A434` | `#F8A434` | `#E3A008` | `#D5BD8E` | 주의. 복습 추천 버튼(AmberButton)과 경고 표시 색이다. |
| `amberSoft` | `--tds-amber-soft` | `#FCF6E9` | `#2A2215` | `#FFF8D6` | `#4B4231` | 주의 면. Notice(warn)와 복습 추천 카드의 배경이다. |
| `amberText` | `--tds-amber-text` | `#8A5A12` | `#F5C576` | `#6D5429` | `#E9D5AC` | 주의 글자. amberSoft 위에 놓는 글자 색이다. |
| `orange` | `--tds-orange` | `#F59A3C` | `#F59A3C` | `#E3A008` | `#D5BD8E` | 주황. 연속 학습일을 나타내는 불꽃 아이콘의 색이다. |
| `danger` | `--tds-danger` | `#E5213A` | `#FF5A6E` | `#C43C42` | `#EBA6A4` | 오류. 오류 글자, 오답 표시, 삭제 버튼, D-Day 임박 점의 색이다. |
| `dangerSoft` | `--tds-danger-soft` | `#FFEFF2` | `#321820` | `#FFF1F1` | `#513638` | 오류 면. Notice(error), 오답 보기, 삭제 행 아이콘 타일의 배경이다. |
| `dangerLine` | `--tds-danger-line` | `#F6C3CB` | `#6A2A36` | `#ECC4C6` | `#645153` | 오류 테두리. 오류 면 위의 옅은 테두리다. 문제 번호 격자와 단원 카드에 쓴다. |
| `onDanger` (웹 확장) | `--tds-on-danger` | `#FFFFFF` | `#FFFFFF` | `#FFFFFF` | `#441C1C` | 오류 위 글자(웹). danger 위에 놓는 글자 색이다. 앱 코드에서는 white를 쓴다. |
## 덮개와 고정색
화면을 덮거나, 어떤 서비스에서도 바뀌지 않는 색이다.
| 토큰 | CSS 변수 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 용도 |
| --- | --- | --- | --- | --- | --- | --- |
| `scrim` | `--tds-scrim` | `rgba(12, 18, 34, 0.45)` | `rgba(0, 0, 0, 0.62)` | `rgba(12, 18, 34, 0.45)` | `rgba(0, 0, 0, 0.62)` | 어둠막. 대화상자와 시트 뒤를 어둡게 덮는 반투명 색이다. |
| `veil` | `--tds-veil` | `rgba(255, 255, 255, 0.55)` | `rgba(11, 15, 23, 0.55)` | `rgba(255, 255, 255, 0.55)` | `rgba(11, 15, 23, 0.55)` | 베일. 촬영 검토 화면에서 사진 위를 흐리게 덮는 반투명 흰색이다. |
| `white` | `--tds-white` | `#FFFFFF` | `#FFFFFF` | `#FFFFFF` | `#FFFFFF` | 흰색. 색이 채워진 면 위의 글자와 아이콘이다. 모든 모드와 서비스에서 #FFFFFF다. |
| `black` (앱 미사용) | `--tds-black` | `#000000` | `#000000` | `#000000` | `#000000` | 검정. 정의되어 있지만 앱 코드에서는 아직 쓰지 않는다. |
## 과목 팔레트
과목을 구분하는 8가지 색이다. 데이터를 구분하는 색이라 모든 서비스에서 값이 같다.
| 이름 | 글리프 라이트 | 글리프 다크 | 옅은 면 라이트 | 옅은 면 다크 | 채움 시작 | 채움 끝 | 막대 | 기본 과목 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| blue | #3B82F6 | #5B9BFF | #E9F3FD | rgba(91, 155, 255, 0.16) | #7DB4FF | #4C8DFB | #5B9BFF | 수학 |
| green | #18A865 | #35C483 | #E6F7EE | rgba(53, 196, 131, 0.16) | #6FD7A0 | #3DBE7C | #7CD0A4 | 영어 |
| orange | #F08A24 | #FFA24A | #FFF3E6 | rgba(255, 162, 74, 0.16) | #FFC07A | #F59A3C | #FCC470 | 사회 |
| red | #F25C5C | #FF7676 | #FFECEF | rgba(255, 118, 118, 0.16) | #FF8F8F | #F25C5C | #FC8C64 | 한국사 |
| purple | #7B61FF | #9C86FF | #F0EBFF | rgba(156, 134, 255, 0.16) | #B3A2FF | #8B72FF | #A897FF | — |
| teal | #14A89A | #2EC7B5 | #E0F8F0 | rgba(46, 199, 181, 0.16) | #6ADBC6 | #1FB58A | #6ACFB8 | 과학 |
| yellow | #E0A100 | #F2BE2E | #FFF7DD | rgba(242, 190, 46, 0.16) | #FFD96B | #F1B81C | #F6D36B | — |
| gray | #6B7A99 | #98A5BD | #F1F4F8 | rgba(152, 165, 189, 0.16) | #AAB4C7 | #8391AA | #AAB4C7 | 국어, 기타 |
## Morit 값의 출처
Morit은 Flutter Material 3 색 체계를 쓴다. 코드 값은 코드(또는 실행해서 읽은 colorScheme 역할)에서 그대로 가져온 값, 계산은 코드 값 둘을 섞어 만든 값, 공통은 Mordy와 같은 값이다. Morit에서는 `success`와 `primary`가 같은 초록이므로 성공은 항상 체크 아이콘과 글자를 함께 쓴다.
| 토큰 | 라이트 값 | 라이트 출처 | 다크 값 | 다크 출처 |
| --- | --- | --- | --- | --- |
| `bg` | `#F7F8FA` | 코드 값: colorScheme.surface | `#1C2327` | 코드 값: colorScheme.surface |
| `canvas` | `#F7F8FA` | 코드 값: colorScheme.surface | `#1C2327` | 코드 값: colorScheme.surface |
| `surface` | `#FBFDFC` | 코드 값: colorScheme.surfaceContainerLow (cardTheme.color) | `#242C30` | 코드 값: colorScheme.surfaceContainerLow (cardTheme.color) |
| `well` | `#F2F4F6` | 코드 값: colorScheme.surfaceContainer | `#2C353A` | 코드 값: colorScheme.surfaceContainer |
| `card` | `#F3FBF8` | 계산: primaryContainer 50% + surfaceContainerLow 50% | `#283837` | 계산: primaryContainer 35% + surfaceContainerLow 65% |
| `cardStrong` | `#EAF8F4` | 코드 값: colorScheme.primaryContainer | `#2C433D` | 계산: primaryContainer 70% + surfaceContainerLow 30% |
| `selected` | `#F3FBF8` | 계산: primaryContainer 50% + surfaceContainerLow 50% | `#2A3D3A` | 계산: primaryContainer 50% + surfaceContainerLow 50% |
| `field` | `#F2F4F6` | 코드 값: inputDecorationTheme.fillColor | `#2C353A` | 코드 값: inputDecorationTheme.fillColor |
| `muted` | `#F0F3F2` | 코드 값: colorScheme.surfaceContainerHigh | `#2C353A` | 코드 값: colorScheme.surfaceContainer |
| `pressed` | `#F0F3F2` | 코드 값: colorScheme.surfaceContainerHigh | `#343F44` | 코드 값: colorScheme.surfaceContainerHigh |
| `photo` | `#F2F4F6` | 코드 값: colorScheme.surfaceContainer | `#191F23` | 코드 값: colorScheme.surfaceContainerLowest |
| `skeleton` | `#E8EAED` | 코드 값: colorScheme.surfaceContainerHighest | `#343F44` | 코드 값: colorScheme.surfaceContainerHigh |
| `track` | `#E8EAED` | 코드 값: colorScheme.surfaceContainerHighest | `#3D494F` | 코드 값: colorScheme.surfaceContainerHighest |
| `line` | `#EEF1F0` | 코드 값: dividerTheme.color | `#3A464A` | 코드 값: dividerTheme.color |
| `lineStrong` | `#D8E1DE` | 코드 값: outlinedButtonTheme 테두리색 | `#5E6D6D` | 계산: outline 50% + outlineVariant 50% |
| `cardBorder` | `#E2E9E7` | 코드 값: _lightCardBorder | `#3A464A` | 코드 값: colorScheme.outlineVariant |
| `selectedLine` | `#D5EBE6` | 계산: primary 15% + surfaceContainerLow 85% | `#42625C` | 계산: primary 35% + surfaceContainerLow 65% |
| `text` | `#17211F` | 코드 값: colorScheme.onSurface | `#DCE3E4` | 코드 값: colorScheme.onSurface |
| `textSecondary` | `#65717D` | 코드 값: colorScheme.onSurfaceVariant | `#ACBABD` | 코드 값: colorScheme.onSurfaceVariant |
| `textTertiary` | `#8B95A1` | 코드 값: colorScheme.outline | `#82948F` | 코드 값: colorScheme.outline |
| `tabInactive` | `#65717D` | 코드 값: colorScheme.onSurfaceVariant | `#ACBABD` | 코드 값: colorScheme.onSurfaceVariant |
| `slate` | `#4E5A57` | 코드 값: listTileTheme.iconColor | `#A5BEC4` | 코드 값: colorScheme.secondary |
| `navy` | `#173D34` | 코드 값: colorScheme.onPrimaryContainer | `#BAE7D6` | 코드 값: colorScheme.onPrimaryContainer |
| `textSecondaryInk` | `#65717D` | 웹 확장: textSecondary를 surface·bg·canvas·well·card 위에서 4.5:1이 되도록 조정한 값 | `#ACBABD` | 웹 확장: textSecondary를 surface·bg·canvas·well·card 위에서 4.5:1이 되도록 조정한 값 |
| `primary` | `#00856A` | 코드 값: colorScheme.primary | `#79C7AF` | 코드 값: colorScheme.primary |
| `primaryStrong` | `#00856A` | 코드 값: filledButtonTheme 배경색 | `#326C59` | 코드 값: filledButtonTheme 배경색 |
| `primarySoft` | `#EAF8F4` | 코드 값: colorScheme.primaryContainer | `#2F4D43` | 코드 값: colorScheme.primaryContainer |
| `primaryLine` | `#88D6BA` | 코드 값: colorScheme.inversePrimary | `#286B55` | 코드 값: colorScheme.inversePrimary |
| `gradientFrom` | `#00856A` | 코드 값: filledButtonTheme 배경색 (Morit 버튼은 단색이라 시작과 끝이 같다) | `#326C59` | 코드 값: filledButtonTheme 배경색 (Morit 버튼은 단색이라 시작과 끝이 같다) |
| `gradientTo` | `#00856A` | 코드 값: filledButtonTheme 배경색 (Morit 버튼은 단색이라 시작과 끝이 같다) | `#326C59` | 코드 값: filledButtonTheme 배경색 (Morit 버튼은 단색이라 시작과 끝이 같다) |
| `onPrimary` | `#FFFFFF` | 코드 값: colorScheme.onPrimary | `#132F25` | 코드 값: colorScheme.onPrimary |
| `onPrimaryStrong` | `#FFFFFF` | 코드 값: filledButtonTheme 글자색 | `#EDF4EF` | 코드 값: filledButtonTheme 글자색 |
| `primaryInk` | `#007E65` | 웹 확장: primary를 surface·primarySoft·canvas·card·selected 위에서 4.5:1이 되도록 조정한 값 | `#79C7AF` | 웹 확장: primary를 surface·primarySoft·canvas·card·selected 위에서 4.5:1이 되도록 조정한 값 |
| `segment` | `#4E5A57` | 코드 값: listTileTheme.iconColor | `#30434C` | 코드 값: colorScheme.secondaryContainer |
| `segmentTrack` | `#F2F4F6` | 코드 값: colorScheme.surfaceContainer | `#2C353A` | 코드 값: colorScheme.surfaceContainer |
| `tabTrack` | `#E8EAED` | 코드 값: colorScheme.surfaceContainerHighest | `#2C353A` | 코드 값: colorScheme.surfaceContainer |
| `success` | `#00856A` | 코드 값: colorScheme.primary | `#79C7AF` | 코드 값: colorScheme.primary |
| `successSoft` | `#EAF8F4` | 코드 값: colorScheme.primaryContainer | `#2F4D43` | 코드 값: colorScheme.primaryContainer |
| `successText` | `#173D34` | 코드 값: colorScheme.onPrimaryContainer | `#BAE7D6` | 코드 값: colorScheme.onPrimaryContainer |
| `mint` | `#00A884` | 코드 값: seed | `#79C7AF` | 코드 값: colorScheme.primary |
| `mintText` | `#00856A` | 코드 값: colorScheme.primary | `#79C7AF` | 코드 값: colorScheme.primary |
| `amber` | `#E3A008` | 코드 값: colorScheme.tertiary | `#D5BD8E` | 코드 값: colorScheme.tertiary |
| `amberSoft` | `#FFF8D6` | 코드 값: colorScheme.tertiaryContainer | `#4B4231` | 코드 값: colorScheme.tertiaryContainer |
| `amberText` | `#6D5429` | 코드 값: colorScheme.onTertiaryContainer | `#E9D5AC` | 코드 값: colorScheme.onTertiaryContainer |
| `orange` | `#E3A008` | 코드 값: colorScheme.tertiary | `#D5BD8E` | 코드 값: colorScheme.tertiary |
| `danger` | `#C43C42` | 코드 값: colorScheme.error | `#EBA6A4` | 코드 값: colorScheme.error |
| `dangerSoft` | `#FFF1F1` | 코드 값: colorScheme.errorContainer | `#513638` | 코드 값: colorScheme.errorContainer |
| `dangerLine` | `#ECC4C6` | 계산: error 30% + surfaceContainerLowest 70% | `#645153` | 계산: error 35% + surface 65% |
| `onDanger` | `#FFFFFF` | 코드 값: colorScheme.onError | `#441C1C` | 코드 값: colorScheme.onError |
| `scrim` | `rgba(12, 18, 34, 0.45)` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 | `rgba(0, 0, 0, 0.62)` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 |
| `veil` | `rgba(255, 255, 255, 0.55)` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 | `rgba(11, 15, 23, 0.55)` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 |
| `white` | `#FFFFFF` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 | `#FFFFFF` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 |
| `black` | `#000000` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 | `#000000` | 공통: Morit 코드에 정한 값이 없어 Mordy와 같은 값을 쓴다 |
---
# 글자
글꼴은 Pretendard 하나다(굵기 400~800). 글자 스타일은 11종이고 서비스가 바뀌어도 같다.
## 글자 스타일 11종
| 이름 | 크기 | 줄 높이 | 굵기 | 자간 | 용도 | CSS 변수 | 앱에서 쓴 횟수 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `display` (가장 큰 제목) | 26 | 34 | 800 | -0.4 | 온보딩의 큰 제목, 완료 화면 제목 | `--tds-text-display-size` 등 | 11 |
| `title` (화면 제목) | 21 | 29 | 700 | -0.3 | "내 과목", "복습 스케줄" 같은 `TitleHeader` | `--tds-text-title-size` 등 | 38 |
| `headline` (카드 제목) | 17 | 24 | 700 | -0.2 | `Panel` 제목, 대화상자 제목, 빈 화면 제목 | `--tds-text-headline-size` 등 | 88 |
| `bodyStrong` (강조 본문) | 15 | 22 | 600 | -0.1 | 목록 행의 제목, 설정 행 이름 | `--tds-text-body-strong-size` 등 | 155 |
| `body` (본문) | 15 | 22 | 500 | -0.1 | 문제 보기, 해설, 대화상자 본문 | `--tds-text-body-size` 등 | 62 |
| `small` (작은 본문) | 13 | 19 | 500 | -0.1 | 행 아래 보조 설명, 안내(`Notice`) 글자 | `--tds-text-small-size` 등 | 248 |
| `smallStrong` (작은 강조) | 13 | 19 | 600 | -0.1 | 칩, 작은 버튼, 입력 칸 라벨 | `--tds-text-small-strong-size` 등 | 201 |
| `caption` (아주 작은 글자) | 11.5 | 16 | 500 | 0 | 차트 축 숫자, 탭 이름, 행의 보조 줄 | `--tds-text-caption-size` 등 | 248 |
| `button` (버튼 글자) | 15 | 20 | 700 | -0.1 | `PrimaryButton`, `SecondaryButton` | `--tds-text-button-size` 등 | 15 |
| `metric` (큰 숫자) | 24 | 30 | 800 | -0.4 | 정의만 있고 앱 코드에서는 아직 쓰지 않는다. | `--tds-text-metric-size` 등 | 0 |
| `question` (문제 본문) | 18 | 27 | 700 | -0.2 | 문제 풀이 화면의 질문 글 | `--tds-text-question-size` 등 | 4 |
## 글자 크기 설정
앱 설정에서 글자 크기를 4단계로 고른다. 크기와 줄 높이에 배율을 곱하고 소수 첫째 자리에서 반올림한다. 시스템 글자 크기는 최대 1.6배까지 적용한다.
| 글자 스타일 | 작게 (0.92배) | 기본 (1배) | 크게 (1.12배) | 아주 크게 (1.24배) |
| --- | --- | --- | --- | --- |
| `display` | 23.9 / 31 | 26 / 34 | 29.1 / 38 | 32.2 / 42 |
| `title` | 19.3 / 27 | 21 / 29 | 23.5 / 32 | 26 / 36 |
| `headline` | 15.6 / 22 | 17 / 24 | 19 / 27 | 21.1 / 30 |
| `bodyStrong` | 13.8 / 20 | 15 / 22 | 16.8 / 25 | 18.6 / 27 |
| `body` | 13.8 / 20 | 15 / 22 | 16.8 / 25 | 18.6 / 27 |
| `small` | 12 / 17 | 13 / 19 | 14.6 / 21 | 16.1 / 24 |
| `smallStrong` | 12 / 17 | 13 / 19 | 14.6 / 21 | 16.1 / 24 |
| `caption` | 10.6 / 15 | 11.5 / 16 | 12.9 / 18 | 14.3 / 20 |
| `button` | 13.8 / 18 | 15 / 20 | 16.8 / 22 | 18.6 / 25 |
| `metric` | 22.1 / 28 | 24 / 30 | 26.9 / 34 | 29.8 / 37 |
| `question` | 16.6 / 25 | 18 / 27 | 20.2 / 30 | 22.3 / 33 |
## 한국어 줄바꿈
앱은 `keepWords()`로 글자 사이에 U+2060을 넣어 단어가 쪼개지지 않게 한다. 웹은 `word-break: keep-all`과 `overflow-wrap: break-word`를 쓴다.
## 시험지 서체
`PaperType`으로 감싸면 안쪽의 `AppText`가 Noto Serif KR(Regular, Bold)로 바뀐다. 시험지 화면에서만 쓴다.
---
# 간격
간격은 9단계 값만 쓴다. 서비스가 바뀌어도 같다.
| 토큰 | 값(px) | CSS 변수 | 용도 | 앱에서 쓴 횟수 |
| --- | --- | --- | --- | --- |
| `xxs` | 2 | `--tds-space-xxs` | 아주 좁게. 정의만 있고 앱 코드에서는 아직 쓰지 않는다. | 0 |
| `xs` | 4 | `--tds-space-xs` | 붙여서. 서로 붙어 있어야 하는 요소 사이. 예: 개념 본문에서 핵심 포인트 목록 위쪽. | 9 |
| `sm` | 8 | `--tds-space-sm` | 가깝게. 관련된 작은 요소 사이. 버튼 두 개 사이, 칩 사이, 대화상자 버튼 사이. | 62 |
| `md` | 12 | `--tds-space-md` | 보통. 카드 안의 요소 사이. `Gap`의 기본값, 시트 안 항목 사이, 섹션 제목 아래. | 125 |
| `lg` | 16 | `--tds-space-lg` | 넉넉하게. 카드 안쪽 여백(`Card`), 시트와 하단 고정 영역의 아래 여백. | 143 |
| `gutter` | 20 | `--tds-space-gutter` | 화면 좌우. 화면 가장자리와 내용 사이. `Screen`이 좌우에 20을 둔다. | 119 |
| `xl` | 24 | `--tds-space-xl` | 구역 사이. 섹션과 섹션 사이. `SectionTitle`이 위에 24를 둔다. | 83 |
| `xxl` | 32 | `--tds-space-xxl` | 큰 구역 사이. 화면 맨 아래 여백(`Screen` 스크롤 영역), 비어 있는 화면의 위아래 여백. | 20 |
| `xxxl` | 40 | `--tds-space-xxxl` | 가장 크게. 앱에서 한 곳(검색 화면)에서만 쓴다. | 1 |
누르는 영역은 `hitSlop`으로 사방 10씩 넓힌다. 이웃한 눌림 요소 사이는 8 이상 띄운다.
규칙: S1~S6은 사용 규칙 문서를 본다.
---
# 모서리
모서리 둥글기는 7단계이고 서비스가 바뀌어도 같다.
| 토큰 | 값(px) | CSS 변수 | 용도 | 앱에서 쓴 횟수 |
| --- | --- | --- | --- | --- |
| `sm` | 8 | `--tds-radius-sm` | 작게. 작은 태그와 썸네일의 모서리. | 6 |
| `md` | 12 | `--tds-radius-md` | 입력 칸. `Field`의 모서리, 시간 선택 띠, 대화상자 버튼(md + 2 = 14). | 55 |
| `tile` | 14 | `--tds-radius-tile` | 타일. 정의만 있다. 앱 코드는 같은 값 14를 숫자로 직접 쓴다(`OptionRow`, `RowIcon`). | 0 |
| `lg` | 16 | `--tds-radius-lg` | 카드. `FeedbackCard`, `ChoiceCard`, 메뉴 목록 같은 안쪽 카드. | 44 |
| `card` | 20 | `--tds-radius-card` | 큰 카드. `Card`의 모서리. | 18 |
| `sheet` | 24 | `--tds-radius-sheet` | 시트. `Sheet`와 `dialog`의 모서리. 시트는 위쪽 두 모서리만 둥글다. | 10 |
| `pill` | 999 | `--tds-radius-pill` | 알약. 버튼, 칩, 탭 같은 알약 모양. 가로가 어떤 길이든 양 끝이 완전히 둥글다. | 33 |
## 코드가 직접 쓰는 값
| 값 | 쓰는 곳 | 설명 |
| --- | --- | --- |
| 22 | `Panel`, `SettingsGroup` | 리포트와 설정의 흰 패널 |
| 18 | `WellRow`, 토스트, `SwipeRow` 뒷면 | 앱에서 가장 많이 쓰는 직접 값 |
| 14 | `OptionRow`, `RowIcon` | `radius.tile`과 같은 값 |
| 10 | `SmallButton` | 작은 사각형 버튼 |
| 높이의 절반 | `PillTabs`(26, 21), `SubjectIcon`(크기 × 0.28) | 크기에 비례하는 모서리 |
---
# 그림자
그림자는 3단계이고 서비스가 바뀌어도 같다. 다크 모드에서는 투명도가 2배, 색은 검정이 된다. Android는 그림자 대신 고도(elevation)를 쓰고 다크에서는 0이다.
| 단계 | 아래로 이동 | 번짐 | 투명도 | Android 고도 | 용도 | 라이트 CSS | 다크 CSS | 앱에서 쓴 횟수 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| shadow(1) | 4px | 14px | 0.06 | 2 | 카드. `PrimaryButton`, `Panel`, `SettingsGroup`, `RangeSlider` 손잡이. | `0 4px 14px rgba(27, 42, 78, 0.06)` | `0 4px 14px rgba(0, 0, 0, 0.12)` | 9 |
| shadow(2) | 6px | 18px | 0.1 | 4 | 떠 있는 요소. 탭 바 가운데 버튼, `CircleButton`, 토스트. | `0 6px 18px rgba(27, 42, 78, 0.1)` | `0 6px 18px rgba(0, 0, 0, 0.2)` | 14 |
| shadow(3) | 8px | 22px | 0.16 | 8 | 겹쳐 뜨는 면. `dialog`의 중앙 대화상자, Calendar의 드래그 중인 일정. | `0 8px 22px rgba(27, 42, 78, 0.16)` | `0 8px 22px rgba(0, 0, 0, 0.32)` | 2 |
그림자 색: 라이트 `#1B2A4E`, 다크 `#000000`.
---
# 모션
움직임은 상태가 바뀌었다는 것을 알리는 데만 쓴다. 앱은 기기의 "동작 줄이기" 설정을 아직 읽지 않는다. 웹에서는 `prefers-reduced-motion`을 지킨다.
## 곡선
| 이름 | 앱 코드 | CSS | 쓰는 때 |
| --- | --- | --- | --- |
| out | `Easing.out(Easing.cubic)` | `cubic-bezier(0.33, 1, 0.68, 1)` | 나타날 때. 처음에 빠르고 끝에서 부드럽게 멈춘다. |
| in | `Easing.in(Easing.quad)` | `cubic-bezier(0.11, 0, 0.5, 0)` | 사라질 때. 천천히 시작해 빠르게 사라진다. |
## 시간
| 움직임 | 시간 | 곡선 | 쓰는 곳 |
| --- | --- | --- | --- |
| 시트 열림 | 240ms | out | `Sheet`. 어둠막은 투명해졌다 나타나고 시트는 아래에서 올라온다. |
| 시트 닫힘 | 180ms | in | `Sheet` |
| 메뉴 시트 열림 | 220ms | out | `dialog.menu` |
| 중앙 대화상자 열림 | 180ms | out | `dialog.alert`, `dialog.confirm`, `dialog.choose`. 0.96배에서 1배로 커지며 나타난다. |
| 대화상자·메뉴 닫힘 | 130ms | in | `dialog` 전체 |
| 끌어내리다 놓음 | 160ms | 기본 | `Sheet`와 메뉴 시트. 닫힐 만큼 끌지 않았을 때 제자리로 돌아온다. |
| 토스트 나타남 | 스프링(damping 18) | 스프링 | `toast`. 아래에서 위로 떠오른다. |
| 토스트 사라짐 | 160ms | 기본 | `toast` |
| 토스트 표시 시간 | 2400ms (되돌리기 버튼이 있으면 6000ms) | — | `toast` |
| 로딩 막대 깜빡임 | 700ms씩 반복 | 기본 | `Skeleton`. 투명도 0.5와 1 사이를 오간다. |
| 링 차트 채움 | 900ms (150ms 지연) | out | `AnimatedRing` |
| 숫자 올라감 | 900ms (150ms 지연) | out | `useCountUp` |
| 문제 진행 칸 채움 | 380ms | out | `SegmentedProgress` |
| 밀어서 삭제: 화면 밖으로 | 200ms | 기본 | `SwipeRow`. 110 이상 밀고 놓았을 때. |
| 밀어서 삭제: 제자리로 | 스프링(damping 18) | 스프링 | `SwipeRow` |
| 이미지 확대 되돌리기 | 180~220ms | 기본 | `ZoomableImage`. 더블탭 확대 220ms, 되돌리기 180~200ms. |
| 화면 모드 바뀜: 덮기 | 90ms | 기본 | `ThemeFade`. 새 배경색으로 화면을 덮는다. |
| 화면 모드 바뀜: 걷기 | 300ms (60ms 지연) | 기본 | `ThemeFade`. 새 모드가 그려진 뒤 덮개를 걷는다. |
## 눌림 표현
| 컴포넌트 | 눌렀을 때 |
| --- | --- |
| PrimaryButton | 0.985배 |
| CircleButton | 0.96배 |
| 탭 바 가운데 버튼 | 0.95배 |
| SecondaryButton, SmallButton, WellRow, FilterPill | 투명도 0.8 |
| AmberButton, Card | 투명도 0.85 |
| OptionRow, ChoiceCard | 투명도 0.9 |
| TextButton, IconButton | 투명도 0.6 |
| SettingsRow, 메뉴 행 | 배경이 `pressed` 색으로 바뀜 |
## 진동
| 함수 | 느낌 | 쓰는 때 |
| --- | --- | --- |
| `haptic.select` | 가벼운 선택 진동 | 탭, 칩, 슬라이더 같은 선택이 바뀔 때 |
| `haptic.tap` | 가벼운 눌림 | PrimaryButton, 대화상자 버튼을 누를 때 |
| `haptic.firm` | 중간 세기 눌림 | 길게 눌러 열기·끌기를 시작할 때 |
| `haptic.success` | 성공 알림 진동 | 정답을 맞혔을 때, 길게 눌러 확정했을 때 |
| `haptic.warning` | 경고 알림 진동 | 문제를 틀렸을 때 |
| `haptic.error` | 오류 알림 진동 | 확인 문제를 틀렸을 때, 신고 전송이 실패했을 때 |
---
# 아이콘
아이콘은 Lucide 아웃라인 스타일 하나만 쓴다. 기본은 크기 18~22, 선 두께 2.2다. 색은 글자와 같은 토큰을 쓴다.
## Mordy 아이콘 (156개)
`ALargeSmall`, `ArrowDown`, `ArrowDownUp`, `ArrowLeft`, `ArrowLeftRight`, `ArrowRight`, `ArrowUp`, `Bell`, `Blend`, `Bookmark`, `BookOpen`, `BookOpenCheck`, `Brain`, `BriefcaseBusiness`, `Calculator`, `CalendarCheck`, `CalendarDays`, `CalendarRange`, `Camera`, `ChartColumn`, `ChartNoAxesColumn`, `ChartPie`, `Check`, `CheckCheck`, `ChevronDown`, `ChevronLeft`, `ChevronRight`, `ChevronUp`, `Circle`, `CircleAlert`, `CircleCheck`, `CircleDot`, `CircleHelp`, `CircleQuestionMark`, `CircleX`, `ClipboardCheck`, `ClipboardPenLine`, `Clock`, `Columns2`, `Crop`, `Ellipsis`, `EllipsisVertical`, `Eraser`, `ExternalLink`, `Eye`, `EyeOff`, `FileStack`, `FileText`, `FileUp`, `Flag`, `Flame`, `FolderInput`, `Gauge`, `Globe`, `GripVertical`, `Hand`, `HardDrive`, `House`, `Images`, `Info`, `KeyRound`, `Landmark`, `Layers`, `Layers2`, `LayoutGrid`, `Leaf`, `Lightbulb`, `List`, `ListChecks`, `LocateFixed`, `Lock`, `LogOut`, `Medal`, `Minus`, `Moon`, `NotebookPen`, `Palette`, `Pause`, `Pencil`, `Play`, `Plus`, `RefreshCw`, `Rocket`, `RotateCcw`, `RotateCw`, `ScanLine`, `Search`, `Settings`, `Settings2`, `ShieldCheck`, `Sigma`, `SlidersHorizontal`, `Sparkles`, `Sprout`, `Star`, `Sun`, `SunMoon`, `Target`, `Timer`, `Trash2`, `TrendingDown`, `TrendingUp`, `TriangleAlert`, `Trophy`, `Undo2`, `UserRound`, `WandSparkles`, `WifiOff`, `X`, `Zap`, `ZapOff`, `ZoomIn`, `Download`, `Share2`, `FileDown`, `Printer`, `Headphones`, `Square`, `Hourglass`, `BookOpenText`, `Volume2`, `PenLine`, `Stop`, `ImagePlus`, `GraduationCap`, `School`, `Award`, `Languages`, `UsersRound`, `Users`, `MessagesSquare`, `Heart`, `MessageCircle`, `Paperclip`, `Link2`, `Vote`, `AtSign`, `Hash`, `Pin`, `Crown`, `UserRoundPlus`, `UserRoundCheck`, `Megaphone`, `ShieldAlert`, `Ban`, `Video`, `BadgeCheck`, `Repeat2`, `Film`, `MessageSquareText`, `BellRing`, `CirclePlus`, `SendHorizontal`, `Gift`, `UserRoundMinus`, `Maximize2`
## Calendar 앱 전용 (8개)
`SkipForward`, `Inbox`, `CalendarPlus`, `CalendarClock`, `AlarmClock`, `NotebookText`, `Tag`, `ImageMinus`
---
# 컴포넌트
공통 UI 키트의 컴포넌트 문서 목록이다. 각 문서는 같은 순서(미리보기, 언제 쓰는가, 종류, 크기, 상태, 사용 방법, 치수와 토큰, 접근성, 잘한 예와 잘못한 예)로 쓰여 있다.
## 버튼
누르면 동작을 시작하는 컴포넌트
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- [SecondaryButton](https://developers.moring.co/tds/components/secondary-button.md): 테두리만 있거나 옅은 주색으로 채운 알약 모양 버튼이다. PrimaryButton 옆의 두 번째 행동에 쓴다.
- [AmberButton](https://developers.moring.co/tds/components/amber-button.md): 주의 색(amber)으로 채운 알약 모양 버튼이다. "복습 추천" 카드의 행동에만 쓴다.
- [SmallButton](https://developers.moring.co/tds/components/small-button.md): 목록 행 안에 놓는 작은 사각형 버튼이다. "풀기", "승인", "거절" 같은 한 단어 행동에 쓴다.
- [TextButton](https://developers.moring.co/tds/components/text-button.md): 글자만 있는 가장 가벼운 버튼이다. "나중에", "건너뛰기" 같은 선택 행동에 쓴다.
- [CircleButton](https://developers.moring.co/tds/components/circle-button.md): 화살표가 들어 있는 둥근 주색 버튼이다. 온보딩에서 다음 단계로 넘어갈 때 쓴다.
- [IconButton](https://developers.moring.co/tds/components/icon-button.md): 아이콘 하나만 있는 투명한 둥근 버튼이다. 헤더의 "뒤로", "검색"에 쓴다.
- [Pressable](https://developers.moring.co/tds/components/pressable.md): React Native의 Pressable에 Android 눌림 물결(ripple)을 더한 기본 눌림 영역이다. 앱의 모든 버튼이 이것을 쓴다.
## 입력과 선택
값을 적거나 고르는 컴포넌트
- [Field](https://developers.moring.co/tds/components/field.md): 글자를 입력하는 칸이다. 이름(라벨), 안내 문구, 오류 문구, 비밀번호 보기와 지우기 버튼을 포함한다.
- [PillTabs](https://developers.moring.co/tds/components/pill-tabs.md): 알약 모양의 탭 모음이다. 하나를 고르면 그 칸이 짙은 색(네이비, 다크 모드는 주색)으로 채워진다.
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
- [OptionRow](https://developers.moring.co/tds/components/option-row.md): 퀴즈 보기 한 줄이다. 고르기 전, 고른 뒤, 채점 뒤(정답, 오답)의 모습이 정해져 있다.
- [ChoiceCard](https://developers.moring.co/tds/components/choice-card.md): 카드 모양의 선택지다. 고르면 테두리가 굵어지고 체크 표시가 붙는다. 온보딩의 과목·알림 선택에 쓴다.
- [ChoiceNumber](https://developers.moring.co/tds/components/choice-number.md): 숫자가 들어간 얇은 동그라미다. 시험지 보기 번호(① ② ③ ④ ⑤)를 그린다.
- [FilterPill](https://developers.moring.co/tds/components/filter-pill.md): "과목 전체 ▾"처럼 이름과 지금 값을 보여 주고, 누르면 선택 목록을 여는 알약 버튼이다.
- [SettingsSwitch](https://developers.moring.co/tds/components/settings-switch.md): 켜고 끄는 스위치다. 설정 행의 오른쪽에 놓는다.
- [RangeSlider](https://developers.moring.co/tds/components/range-slider.md): 손잡이 두 개로 정수 범위(시작~끝)를 고르는 슬라이더다. PDF 페이지 범위를 고를 때 쓴다.
- [Calendar](https://developers.moring.co/tds/components/date-calendar.md): 한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
- [TimeSheet](https://developers.moring.co/tds/components/time-sheet.md): 아래에서 올라오는 시트에서 시각을 고른다. 오전/오후, 시, 분(5분 단위) 세 줄 바퀴를 돌려서 고른다.
- [RadioMark](https://developers.moring.co/tds/components/radio-mark.md): 선택 목록 행의 끝에 놓는 동그란 라디오 표시다. 시트의 선택 목록에서 쓴다.
## 탐색
화면 안에서 위치를 바꾸거나 보여 주는 컴포넌트
- [TabBar](https://developers.moring.co/tds/components/tab-bar.md): Mordy 앱 맨 아래의 탭 줄이다. 홈 · 과목 · (+) · 커뮤니티 · 더보기. 가운데 +는 탭이 아니라 자료 추가 행동이다.
- [PageDots](https://developers.moring.co/tds/components/page-dots.md): 지금이 몇 번째 단계인지 알려 주는 작은 점 줄이다. 온보딩 단계 표시에 쓴다.
- [WeekStrip](https://developers.moring.co/tds/components/week-strip.md): 월요일부터 일요일까지 7일을 한 줄로 보여 주는 주간 표시다. 공부한 날에는 점이 찍힌다.
- [Timeline](https://developers.moring.co/tds/components/timeline.md): 세로 선으로 단계를 이어 보여 주는 타임라인이다. 시험 준비 단계(개념 → 문제 → 모의고사)에 쓴다.
- [BackHeader](https://developers.moring.co/tds/components/back-header.md): 화면 위쪽의 뒤로 가기 줄이다. 왼쪽에 뒤로 화살표, 가운데에 제목(선택), 오른쪽에 행동 자리가 있다.
- [TitleHeader](https://developers.moring.co/tds/components/title-header.md): 탭의 첫 화면 위쪽에 놓는 큰 제목 줄이다. 왼쪽에 제목, 오른쪽에 행동 자리가 있다.
## 면과 목록
내용을 담는 면, 헤더, 목록 행
- [Screen](https://developers.moring.co/tds/components/screen.md): 모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
- [Card](https://developers.moring.co/tds/components/card.md): 옅은 주색 틴트(`card`)를 깐 둥근 상자다. 안내, 요약, 눌러서 이동하는 카드에 쓴다.
- [Panel](https://developers.moring.co/tds/components/panel.md): 회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
- [SectionTitle](https://developers.moring.co/tds/components/section-title.md): 화면 안의 한 묶음(섹션)을 시작하는 제목 줄이다. 위에 24, 아래에 12의 간격을 포함한다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [SettingsGroup](https://developers.moring.co/tds/components/settings-list.md): 설정 화면의 목록이다. 작은 구역 이름, 행이 든 흰 패널, 아래 안내 글로 이루어진다.
- [SwipeRow](https://developers.moring.co/tds/components/swipe-row.md): 행을 왼쪽으로 밀면 뒤에서 행동 하나(삭제 등)가 나타나고, 끝까지 밀면 그 행동이 실행되는 행이다.
- [Gap · HScroll](https://developers.moring.co/tds/components/layout-helpers.md): `Gap`은 세로 빈 간격이고, `HScroll`은 가로로 밀어 보는 줄이다. 레이아웃을 돕는 작은 컴포넌트다.
- [ThemeProvider · ThemeFade](https://developers.moring.co/tds/components/theme.md): 화면 모드(시스템·라이트·다크)와 글자 크기 설정을 앱 전체에 적용하는 도구다. 모드가 바뀌는 순간 새 배경색 덮개로 화면을 가렸다가 걷어서 깜빡임을 줄인다.
## 알림과 안내
상태, 결과, 확인을 알리는 컴포넌트
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
- [FeedbackCard](https://developers.moring.co/tds/components/feedback-card.md): 문제를 채점한 뒤 결과("정답!", "오답")와 해설을 보여 주는 카드다.
- [EmptyState](https://developers.moring.co/tds/components/empty-state.md): 보여 줄 내용이 없을 때의 안내다. 옅은 아이콘 타일, 제목, 한 줄 설명, 다음 행동 버튼으로 이루어진다.
- [Skeleton](https://developers.moring.co/tds/components/skeleton.md): 내용이 불러와지기 전에 그 자리를 채우는 깜빡이는 막대다.
- [Sheet](https://developers.moring.co/tds/components/sheet.md): 화면 아래에서 올라오는 시트다. 위쪽 두 모서리가 둥글고, 손잡이와 제목 줄, 닫기 버튼이 있다.
- [OptionSheet](https://developers.moring.co/tds/components/option-sheet.md): 시트 안에서 목록 중 하나를 고르는 컴포넌트다. 행마다 라디오 표시가 있고, 고르면 바로 닫힌다.
- [dialog](https://developers.moring.co/tds/components/dialog.md): 화면 가운데 뜨는 확인 대화상자와 아래에서 올라오는 메뉴를 함수 호출 하나로 여는 도구다. 결과(눌린 버튼)를 약속(Promise)으로 돌려준다.
- [toast](https://developers.moring.co/tds/components/toast.md): 화면 아래에 잠깐 떴다 사라지는 짧은 확인 메시지다. 함수 한 번 호출로 띄운다. 되돌리기 같은 행동 하나를 붙일 수 있다.
- [AiMark](https://developers.moring.co/tds/components/ai-mark.md): 생성형 AI가 만든 결과임을 알리는 표시다. AI가 만든 모든 결과에 붙인다.
## 데이터 표시
글자, 아이콘, 이미지, 진행률 같은 값을 보여 주는 컴포넌트
- [AppText](https://developers.moring.co/tds/components/app-text.md): 앱의 모든 글자를 그리는 컴포넌트다. 글자 스타일 11종 중 하나를 `variant`로 고르면 크기, 줄 높이, 굵기, 자간이 정해진다.
- [PaperType](https://developers.moring.co/tds/components/paper-type.md): 안쪽의 모든 글자를 시험지 서체(Noto Serif KR)로 바꾸는 감싸개다. 모의고사 시험지 화면에 쓴다.
- [SubjectIcon](https://developers.moring.co/tds/components/subject-icon.md): 과목을 나타내는 둥근 아이콘 타일이다. 과목마다 기호(Σ, Aa, 책, 건물, 잎, 지구, …)와 색이 정해져 있다.
- [Avatar](https://developers.moring.co/tds/components/avatar.md): 사용자의 사진, 또는 닉네임 첫 글자를 브랜드 그라데이션 원 위에 보여 준다. `NameLine`은 닉네임, 공식 배지, @아이디를 한 줄로 놓는다.
- [BrandMark](https://developers.moring.co/tds/components/brand-mark.md): Mordy의 심벌(웃는 얼굴이 복습 화살표로 이어지는 모양)을 브랜드 타일 위에 그린 이미지다.
- [ProgressRing](https://developers.moring.co/tds/components/progress-ring.md): 진행률을 둥근 고리로 보여 준다. `ProgressRing`은 작은 고정 고리, `AnimatedRing`은 나타날 때 0%에서 값까지 채워지는 큰 고리다.
- [ProgressBar](https://developers.moring.co/tds/components/progress-bar.md): 진행률을 가로 막대로 보여 준다. 채운 부분은 기본으로 서비스 주색 그라데이션이다.
- [SegmentedProgress](https://developers.moring.co/tds/components/segmented-progress.md): 문제 하나에 칸 하나씩, 푼 문제만큼 칸이 채워지는 진행 막대다. 퀴즈 맨 위에 쓴다.
## 차트
숫자를 그림으로 보여 주는 컴포넌트
- [DonutChart](https://developers.moring.co/tds/components/donut-chart.md): 전체를 여러 부분으로 나눠 보여 주는 도넛 차트다. 부분 사이에 3의 틈이 있다. 시험 계획의 익힘/학습 중/시작 전 비율에 쓴다.
- [Sparkline](https://developers.moring.co/tds/components/sparkline.md): 정답률 같은 작은 추이를 보여 주는 선 그림이다. 값이 없는 날은 선이 끊기고, 마지막 점이 크게 표시된다.
- [HourBars](https://developers.moring.co/tds/components/hour-bars.md): 값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
- [AreaLine](https://developers.moring.co/tds/components/area-line.md): 누적 값을 선과 옅은 면으로 보여 주는 차트다. 모든 점에 점이 찍히고 마지막 점에 말풍선이 뜬다. 리포트의 누적 문제 수, 누적 학습 시간에 쓴다.
- [CalendarHeat](https://developers.moring.co/tds/components/calendar-heat.md): 한 달 달력에서 날마다 공부한 시간을 색의 진하기(5단계)로 보여 주는 차트다.
- [ScoreBars](https://developers.moring.co/tds/components/score-bars.md): 과목별 예상 점수(0~100)를 막대로 보여 준다. 점수는 막대 위에, 과목 이름은 아래에 쓴다.
## 학습 콘텐츠
문제, 개념, 수식, 그림을 그리는 컴포넌트
- [MathText](https://developers.moring.co/tds/components/math-text.md): 수식이 섞인 글을 그린다. 서버가 미리 그려 둔 수식(SVG)을 글자 사이에 끼워 넣고, 없으면 읽을 수 있는 글자로 대신 보여 준다.
- [Blocks](https://developers.moring.co/tds/components/blocks.md): 문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
- [ConceptBody](https://developers.moring.co/tds/components/concept-body.md): 개념의 요약 문장과 핵심 포인트 목록을 그린다. 개념 카드, 관련 개념, 틀린 개념, 자료 상세, 레슨 목록이 모두 같은 모양을 쓴다.
- [ChartBlock](https://developers.moring.co/tds/components/chart-block.md): 문제와 해설 안의 그래프(선, 막대, 점)를 그린다. 함수 그래프는 교과서처럼 원점을 지나는 화살표 축으로, 자료 그래프는 격자와 눈금과 범례로 그린다.
- [DiagramBlock](https://developers.moring.co/tds/components/diagram-block.md): 선, 도형, 글자, 회로 부품(저항, 전지, 전구, 스위치), 스프링 같은 기본 모양으로 만든 도식을 그린다. 컨테이너 폭에 맞게 줄어든다.
- [ImageBlock](https://developers.moring.co/tds/components/image-block.md): 문제에 쓰인 원본 자료의 한 영역을 오려 보여 준다. 영역 위에 라벨과 강조 고리를 얹을 수 있고, 눌러서 크게 볼 수 있다.
- [SourceCrop](https://developers.moring.co/tds/components/source-crop.md): 문제가 나온 원본 자료의 근거 부분을 보여 준다. 근거가 있는 줄을 가운데로 한 가로 띠, 또는 전체 페이지에 근거 영역 표시를 얹은 모양이다.
- [ZoomableImage](https://developers.moring.co/tds/components/zoomable-image.md): 페이지 이미지를 두 손가락으로 1배에서 4배까지 확대하고, 확대한 동안에만 끌어서 움직이며, 두 번 눌러 확대·축소하는 이미지다.
## Calendar 앱
Mordy Calendar 앱이 쓰는 컴포넌트
- [CheckCircle](https://developers.moring.co/tds/components/check-circle.md): 할 일을 완료로 바꾸는 동그란 체크 표시다. 비어 있다가 누르면 색이 채워지며 체크가 나타난다.
- [DayStrip](https://developers.moring.co/tds/components/day-strip.md): 한 주 7일을 한 줄로 보여 주고 날짜를 골라 이동하는 주간 줄이다. 좌우로 밀면 앞뒤 주로 이동한다. 일정이 있는 날에는 점이 찍힌다.
- [TabBar (Calendar)](https://developers.moring.co/tds/components/calendar-tab-bar.md): Mordy Calendar 앱의 탭 줄이다. 오늘 · 캘린더 · (+) · 할 일 · 더보기. Mordy 앱의 `TabBar`와 같은 모양이고 가운데 +는 빠른 추가를 연다.
- [ScreenHeader](https://developers.moring.co/tds/components/calendar-header.md): Calendar 앱의 전체 화면 위쪽 줄이다. 왼쪽에 뒤로, 가운데에 제목, 오른쪽에 행동. 모두 48의 누르는 영역이고 화면 가장자리에서 8 들어와 있다.
- [MonthView](https://developers.moring.co/tds/components/month-view.md): 한 달을 화면 가득 보여 주는 달력이다. 날마다 칩(시험 먼저, 시간 있는 일정, 나머지)을 줄에 들어가는 만큼 보이고 넘치면 "+n"으로 줄인다.
- [TimeGrid](https://developers.moring.co/tds/components/time-grid.md): 주·일 보기의 24시간 시간표다. 일정 블록을 길게 눌러 끌면 시간(15분 단위)과 날짜가 바뀌고, 빈 칸을 누르면 그 시각에 추가한다.
- [ItemRow](https://developers.moring.co/tds/components/item-row.md): 오늘과 할 일 목록의 한 줄이다. 할 일은 앞에 체크 원, 일정은 앞에 시간과 색 막대가 있다.
- [PlanRow](https://developers.moring.co/tds/components/plan-row.md): Mordy가 만든 학습 계획 한 줄이다. 체크 원, 제목, "Mordy" 표지, 푼 문제 진행률(막대)이 있다. 직접 옮긴 계획에는 자물쇠가 붙는다.
- [DdayChip](https://developers.moring.co/tds/components/dday-chip.md): 시험까지 남은 날을 "D-24"처럼 보여 주는 칩이다. 7일 이내면 빨간색, 그 외는 주색이다.
- [MordyTag](https://developers.moring.co/tds/components/mordy-tag.md): "Mordy"라고 적힌 작은 표지다. Mordy가 만든 계획 항목임을 알린다. 이 계획은 실제 학습 기록으로 계산한 규칙 기반이라 AI라고 부르지 않는다.
- [SubjectMark · CategoryDot · GroupAvatar](https://developers.moring.co/tds/components/calendar-marks.md): 일정 줄과 칩에 붙는 작은 표식 세 가지다. 과목 아이콘(`SubjectMark`), 분류 색 점(`CategoryDot`), 그룹 캘린더의 동그란 그림(`GroupAvatar`).
- [ColorRow](https://developers.moring.co/tds/components/color-row.md): 과목 팔레트 8색을 동그란 점으로 늘어놓고 하나를 고르는 컴포넌트다. 카테고리와 캘린더의 색을 정할 때 쓴다.
- [PlannerPreview](https://developers.moring.co/tds/components/planner-preview.md): 환영·설정 화면에서 플래너가 어떤 모양인지 보여 주는 작은 그림이다. 하루를 과목별 또는 시간별로 나눈 예시다.
- [QuickAddHost](https://developers.moring.co/tds/components/quick-add.md): 할 일, 일정, 시험을 한 줄로 빠르게 추가하는 시트다. 한 줄에 "내일 4시 영어학원"처럼 쓰면 날짜와 시간을 알아듣고, 못 알아들은 것은 알약 선택으로 채운다.
- [DateSheetHost · PlanSheetHost · MissedSheet · CategorySheetHost](https://developers.moring.co/tds/components/calendar-sheets.md): Calendar 앱의 시트 네 가지다. 날짜 고르기, Mordy 계획 항목 메뉴, 어제 못한 계획 정리, 카테고리 고르기. 앞의 둘과 마지막은 앱 루트에 한 번 두고 함수 호출로 연다.
- [ExamForm](https://developers.moring.co/tds/components/exam-form.md): 시험을 만드는 양식이다. 시험 날짜(여러 날 가능), 과목 칩, 과목별 시험 범위를 고른다. 빠른 추가 시트와 전체 편집 화면이 같은 양식을 쓴다.
---
# 사용 규칙
모든 규칙에 번호가 있다. 인용할 때는 번호(예: C4)를 쓴다.
## 색 조합
색은 이름(토큰)으로만 쓰고, 이름이 가진 의미를 지킨다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| C1 | 화면 바탕은 `bg`, 대시보드형 화면은 `canvas`를 깐다. 그 위의 면은 `surface`다. 면 위에 면을 겹칠 때는 `well`이나 `card`를 쓴다. | 한 화면의 면 색은 3단계(바탕 → 면 → 홈) 이하로 한다. |
| C2 | 글자는 본문 `text`, 보조 `textSecondary`, 입력 안내 문구 `textTertiary`로 나눈다. 읽어야 하는 내용에 `textTertiary`를 쓰지 않는다. | `textTertiary`는 흰 면에서 대비가 약하다(색상 페이지의 대비 표). |
| C3 | `primary`는 행동과 선택 표시에만 쓴다. 한 화면에서 `primary`로 채운 큰 면은 1개(주 버튼)까지다. | 파란색(초록색)이 여러 곳에 있으면 무엇이 주 행동인지 알 수 없다. |
| C4 | 색이 채워진 면 위의 글자는 짝을 지킨다: `gradient`·`primaryStrong` 위는 `onPrimaryStrong`, `primary` 위는 `onPrimary`, `danger` 위는 `onDanger`, `successSoft` 위는 `successText`, `amberSoft` 위는 `amberText`, `dangerSoft` 위는 `danger`. | 짝이 맞아야 라이트·다크와 두 서비스 모두에서 글자가 읽힌다. |
| C5 | 상태 색의 뜻은 고정이다. `success`는 정답과 완료, `danger`는 오류·오답·삭제, `amber`는 주의와 추천, `primary`는 선택과 행동이다. 선택한 보기를 `success` 색으로 칠하지 않는다. | `OptionRow`는 선택 전 `primary`, 채점 뒤 정답 `success`, 오답 `danger`로 뜻을 나눈다. |
| C6 | 과목 색은 과목을 구분하는 작은 표식(아이콘 타일, 점, 막대)에만 쓴다. 과목 이름을 글자로 항상 함께 보여 준다. | 색만으로 과목을 알아보기 어려운 사용자가 있다. |
| C7 | 색만으로 상태를 알리지 않는다. 정답은 `success` 색 + 체크 아이콘 + "정답!" 글자를 함께 쓴다. | 색을 구분하기 어려운 사용자와 Morit(성공과 주색이 같은 초록)을 위해서다. |
| C8 | 코드에 HEX 값을 직접 쓰지 않는다. 반투명이 필요하면 `withAlpha(토큰, 투명도)`를 쓴다. | 서비스와 라이트·다크가 바뀌면 직접 쓴 HEX는 바뀌지 않는다. |
## 간격
간격 9단계 값만 쓰고, 묶음 안은 좁게 묶음 사이는 넓게 띄운다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| S1 | 간격은 `xxs`(2) ~ `xxxl`(40) 9단계 값만 쓴다. 표에 없는 숫자(10, 14, 18 등)를 간격으로 쓰지 않는다. | 값이 정해져 있어야 화면끼리 리듬이 맞는다. |
| S2 | 화면의 좌우 여백은 `gutter`(20)다. 한 화면의 모든 요소가 같은 왼쪽 선에서 시작한다. | `Screen`이 20을 자동으로 넣는다. |
| S3 | 묶음 안의 간격은 묶음 사이의 간격보다 작다. 예: 묶음 안 8(`sm`) 또는 12(`md`), 묶음 사이 24(`xl`) 이상. | 간격 차이가 눈으로 묶음을 나눈다. |
| S4 | 섹션 제목은 위 간격이 아래 간격보다 크다. `SectionTitle`은 위 24, 아래 12다. | 제목이 아래 내용에 속한다는 것이 보인다. |
| S5 | 이웃한 눌림 요소 사이는 8 이상 띄운다. | `hitSlop`(사방 10)으로 넓힌 눌림 영역이 겹치지 않게 한다. |
| S6 | 카드 안쪽 여백은 `Card` 16, `Panel` 18이다. 안쪽 여백을 카드마다 다르게 하지 않는다. | 같은 종류의 면은 같은 여백을 가진다. |
## 정렬
왼쪽 정렬이 기본이다. 가운데 정렬은 정해진 경우에만 쓴다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| A1 | 글자는 왼쪽 정렬이 기본이다. 가운데 정렬은 빈 화면(`EmptyState`)의 제목과 설명, 버튼·탭 안의 글자, 숫자 하나만 놓인 칸에만 쓴다. | 여러 줄 글자를 가운데 정렬하면 읽는 시작점이 줄마다 달라진다. |
| A2 | 목록 행은 앞(아이콘 타일) · 가운데(제목과 보조 줄, 남는 폭 전부) · 뒤(값, 화살표) 순서다. 뒤쪽 요소는 오른쪽 끝에 맞춘다. | `WellRow`, `SettingsRow`가 이 구조다. |
| A3 | 표와 차트의 숫자는 오른쪽 끝에 맞추고 단위를 붙여 쓴다. | 자릿수가 맞아 크기를 비교하기 쉽다. |
| A4 | 화면 아래에 고정하는 주 버튼은 폭을 가득 채우고(좌우 `gutter`), `Screen`의 `footer`에 넣는다. | 엄지가 닿는 위치에 하나뿐인 주 행동이 있다. |
| A5 | 아이콘과 글자는 세로 가운데로 맞춘다. 사이 간격은 버튼 안 6, 행 안 8~12다. | `PrimaryButton`은 6, `WellRow`는 12를 쓴다. |
| A6 | 웹에서 긴 글의 한 줄은 약 46rem(736px) 이하로 한다. 한글은 65~75자 안이다. | 줄이 너무 길면 다음 줄의 시작을 찾기 어렵다. |
## 글자
글자 스타일은 11종 중에서만 고른다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| T1 | 한 화면에서 글자 스타일은 3~4종만 쓴다. | 제목, 본문, 보조만으로 위계가 충분하다. |
| T2 | 화면 제목은 `title`, 카드 제목은 `headline`, 본문은 `body`, 보조 줄은 `small` 또는 `caption`이다. | 글자 스타일 페이지의 "용도" 열과 같다. |
| T3 | 문제 본문, 보기, 해설은 줄을 자르지 않는다(`numberOfLines`를 쓰지 않는다). 목록 행의 제목만 1~2줄로 자른다. | 잘린 문제는 풀 수 없다. |
| T4 | 버튼 글자는 한 줄이다. 길면 80%까지 줄이고, 그래도 안 들어가면 글자를 짧게 고친다. | `PrimaryButton`은 `minimumFontScale` 0.8을 쓴다. |
| T5 | 글자 스타일 밖의 크기를 쓰려면 줄 높이도 함께 지정한다. | 줄 높이가 모자라면 글자의 아래쪽이 잘린다. |
| T6 | 한국어 글은 단어 중간에서 줄을 바꾸지 않는다. 앱은 `keepWords()`, 웹은 `word-break: keep-all`을 쓴다. | Android는 "무엇인가/요?"처럼 단어를 쪼갠다. |
## 행동과 버튼
화면마다 가장 중요한 행동이 하나이고, 나머지는 무게를 낮춘다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| B1 | 한 화면(또는 한 시트)에 `PrimaryButton`은 1개다. | 주 행동이 둘이면 사용자가 고르지 못한다. |
| B2 | 행동의 무게는 `PrimaryButton` > `SecondaryButton` > `TextButton` 순서다. 두 번째 행동은 `SecondaryButton`, 선택 행동("나중에")은 `TextButton`이다. | 무게 차이가 중요도 차이를 보여 준다. |
| B3 | 되돌릴 수 없는 행동(삭제, 탈퇴)은 `dialog.confirm`을 `tone: "danger"`로 호출해 한 번 더 묻는다. 대화상자에 무엇이 사라지는지 적는다. | 실수로 누른 삭제를 막는다. |
| B4 | 결과를 기다리는 동안은 `busy`, 누를 수 없는 이유가 있으면 `disabled`와 함께 이유를 글자로 알린다. | 이유 없는 비활성 버튼은 고장으로 보인다. |
| B5 | 아이콘만 있는 버튼에는 `label`을 반드시 쓴다. | 화면 읽기 프로그램이 이름을 읽는다. |
## 모양
모서리와 그림자는 면의 종류를 나타낸다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| R1 | 시트와 대화상자는 `sheet`(24), 카드는 `card`(20) 또는 `lg`(16), 입력 칸은 `md`(12), 짧은 눌림 요소만 `pill`이다. | 큰 면을 알약으로 만들지 않는다. |
| R2 | 그림자는 기본 없음이다. 카드와 버튼은 `shadow(1)`, 떠 있는 요소는 `shadow(2)`, 대화상자는 `shadow(3)`만 쓴다. | 면 구분은 먼저 배경색 차이와 1px 선으로 한다. |
| R3 | 카드 안에 카드를 넣지 않는다. 카드(패널) 안의 묶음은 `well`(홈)이나 구분선으로 나눈다. | `Panel` 안에서 `WellRow`가 하는 일이다. |
| R4 | 목록의 행마다 그림자를 주지 않는다. | 행이 많으면 그림자가 화면을 어지럽힌다. |
| R5 | 기본 테두리는 1px `line`이다. 선택된 항목은 `primary` 테두리 1.2~1.8px을 쓴다. | `ChoiceCard`는 선택 전 1.2, 선택 후 1.8이다. |
## 접근성
누구나 읽고 누를 수 있어야 한다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| X1 | 작은 글자는 바탕과 4.5:1 이상, 큰 글자(굵은 18.66px 이상)는 3:1 이상이다. | 색상 페이지의 "글자 색 대비" 표에서 서비스와 모드별로 확인한다. 웹에서는 `textSecondaryInk`, `primaryInk`를 쓴다. |
| X2 | 눌리는 영역은 48 × 48 이상을 목표로 한다. 보이는 크기가 작으면 `hitSlop`이나 주변 여백으로 채운다. | 손끝이 눌리는 영역보다 크다. |
| X3 | 눌리는 요소에는 역할(`accessibilityRole`), 이름(`accessibilityLabel` 또는 안의 글자), 상태(`accessibilityState`)를 지정한다. | 화면 읽기 프로그램이 "버튼, 저장, 사용할 수 없음"처럼 읽는다. |
| X4 | 글자 크기 1.24배(앱 설정)와 시스템 설정 최대 1.6배에서도 글자가 잘리지 않게 한다. 높이는 고정하지 않고 `minHeight`를 쓴다. | `Field`, `WellRow`는 `minHeight`다. |
| X5 | 상태는 색 + 아이콘 + 글자를 함께 쓴다. | C7과 같다. |
| X6 | 웹에서는 모든 조작을 키보드로 할 수 있게 하고, 포커스 링(2.5px 실선, 3px 띄움, `primaryInk` 색)을 지운 채로 두지 않는다. `prefers-reduced-motion`을 지킨다. | 앱에는 포커스 표시가 없으므로 웹에서 새로 그린다. |
| X7 | 사진과 그림에는 대체 글(`alt`, `accessibilityLabel`)을 쓴다. 장식 아이콘은 읽지 않게 숨긴다. | `ImageBlock`은 `alt`를 접근성 이름으로 쓴다. |
## 서비스 색 전환
서비스가 달라도 같은 코드를 쓰고 색 값만 바꾼다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| V1 | 색은 토큰 이름으로만 쓴다. 코드에서 `if (service === "morit")`처럼 서비스를 보고 색을 고르지 않는다. | 서비스가 늘어도 코드를 고치지 않게 한다. |
| V2 | 색 말고는 서비스별로 바꾸지 않는다. 간격, 글자, 모서리, 그림자, 모션은 모든 서비스에서 같다. | 화면이 같은 시스템에서 나왔다는 것이 보여야 한다. |
| V3 | 새 색이 필요하면 모든 서비스의 라이트·다크 값을 함께 추가한다. | 한 서비스에만 값이 있으면 다른 서비스 화면이 깨진다. |
| V4 | Morit에서는 `success`와 `primary`가 같은 초록이다. 성공은 항상 체크 아이콘과 글자를 함께 쓴다. | 색으로 성공과 선택을 구분할 수 없다. |
| V5 | 화면을 만든 뒤 서비스 2개 × 모드 2개(라이트·다크), 모두 4가지 조합에서 확인한다. | 한 조합에서만 읽히는 색을 막는다. |
## 문구
앱의 문장은 짧고 구체적으로 쓴다.
| 번호 | 규칙 | 이유와 값 |
| --- | --- | --- |
| W1 | 문장은 해요체로 쓰고, 한 문장에 한 가지 내용만 담는다. | 앱의 문구("거절했어요", "다시 가입할 수 있어요")가 이 말투다. |
| W2 | 버튼 글자는 행동의 이름이다. "저장", "다음으로", "지금 풀기"처럼 쓴다. "확인"은 알림을 닫을 때만 쓴다. | 버튼만 읽어도 무슨 일이 일어나는지 알아야 한다. |
| W3 | 오류 문구는 무엇이 문제인지와 다음에 할 일을 함께 쓴다. | 문제만 알리면 사용자가 멈춘다. |
| W4 | 숫자에는 단위를 붙여 쓴다. "12문제", "약 3분", "D-24". | 숫자만 있으면 무엇의 수인지 알 수 없다. |
---
# 화면 전용 컴포넌트
공통 UI 키트가 아니라 한 기능이나 한 화면에서만 쓰는 컴포넌트의 전체 목록이다. 코드에서 자동으로 모았다.
## Mordy 기능: announcements (2)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `AnnouncementPopup` | `apps/mobile/src/features/announcements/AnnouncementUI.tsx:14` | 실행 팝업: one notice per launch, the highest priority. 확인 and 다시 보지 않기 both hold until Mordy publishes a new revision. | 0 |
| `AnnouncementBanner` | `apps/mobile/src/features/announcements/AnnouncementUI.tsx:54` | A thin strip above 홈 and 커뮤니티; ✕ hides that notice for good. | 1 |
## Mordy 기능: auth (4)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `AuthScaffold` | `apps/mobile/src/features/auth/AuthScaffold.tsx:11` | — | 6 |
| `PolicyConsent` | `apps/mobile/src/features/auth/PolicyConsent.tsx:8` | 생성형 AI 사전 고지(「인공지능 기본법」 제31조제1항) and "계속하면 … 동의하게 돼요" under the button that creates the account, with the two documents. | 1 |
| `ProfileCard` | `apps/mobile/src/features/auth/ProfileCard.tsx:11` | Who is signed in: top of 설정 (opens 계정) and of the 계정 page itself. | 1 |
| `AuthProvider` | `apps/mobile/src/features/auth/provider.tsx:46` | — | 1 |
## Mordy 기능: bookmarks (1)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `BookmarkButton` | `apps/mobile/src/features/bookmarks/BookmarkButton.tsx:14` | The one bookmark button (문제 · 개념 · 레슨 · 자료): filled when saved, a toast that points to 더보기 > 북마크 when added. | 5 |
## Mordy 기능: capture (1)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `PdfRange` | `apps/mobile/src/features/capture/PdfRange.tsx:35` | PDF 페이지 범위: 전체 or a chosen range, with the first/last chosen pages previewed. | 3 |
## Mordy 기능: community (40)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `CollectionCover` | `apps/mobile/src/features/community/Collections.tsx:44` | A collection as a textbook cover: coloured board, spine, emoji and title. | 4 |
| `CollectionGrid` | `apps/mobile/src/features/community/Collections.tsx:65` | Two columns of covers (profile 컬렉션 tab). | 2 |
| `CollectionEditor` | `apps/mobile/src/features/community/Collections.tsx:80` | 새 컬렉션 / 편집: title, a line about it, emoji and colour, who can see it. | 4 |
| `CollectSheet` | `apps/mobile/src/features/community/Collections.tsx:145` | "컬렉션에 담기" from a post or a material: tap a collection to add. | 4 |
| `AddItemSheet` | `apps/mobile/src/features/community/Collections.tsx:189` | + 항목 추가 (owner): from my materials, bookmarks, wrong answers, saved posts, or a link. | 3 |
| `CollectionItemRow` | `apps/mobile/src/features/community/Collections.tsx:265` | One row inside a collection, the same for every kind. | 3 |
| `CollectHost` | `apps/mobile/src/features/community/Collections.tsx:387` | — | 0 |
| `FeedTabs` | `apps/mobile/src/features/community/FeedTabs.tsx:12` | The one tab bar of the community: text with a short blue underline. Equal widths for a few fixed tabs (프로필, 알림, 검색, 그룹 화면). | 3 |
| `ScrollTabs` | `apps/mobile/src/features/community/FeedTabs.tsx:23` | Scrolling tabs (커뮤니티: 추천 · 그룹 · followed topics) with a fixed trailing slot (+). A few tabs share the whole width; more than fit keep their label width and scroll. Tapping the current tab again calls onReselect. | 5 |
| `TextToggle` | `apps/mobile/src/features/community/FeedTabs.tsx:61` | Inline text choices inside a list header (전체 · 공지, 이번 주 · 이번 달 · 전체). | 3 |
| `FollowTopic` | `apps/mobile/src/features/community/FollowTopic.tsx:12` | — | 2 |
| `GroupIcon` | `apps/mobile/src/features/community/GroupBits.tsx:14` | The group picture, or its topic emoji on a soft tile. | 2 |
| `GroupCover` | `apps/mobile/src/features/community/GroupBits.tsx:29` | — | 2 |
| `GroupCard` | `apps/mobile/src/features/community/GroupBits.tsx:40` | The group card (X communities): cover on top, name below. The same card everywhere a group is listed. | 1 |
| `NewGroupCard` | `apps/mobile/src/features/community/GroupBits.tsx:66` | — | 0 |
| `GroupCardRow` | `apps/mobile/src/features/community/GroupBits.tsx:78` | A sideways row of group cards, optionally ending with 새 그룹 만들기. | 2 |
| `GroupRow` | `apps/mobile/src/features/community/GroupBits.tsx:88` | A row in lists and search: icon, name, topic · members, and a lock for 승인제. | 2 |
| `GroupTile` | `apps/mobile/src/features/community/GroupBits.tsx:109` | A compact card for "내 그룹" (horizontal), with this week's goal when there is one. | 1 |
| `GoalBar` | `apps/mobile/src/features/community/GroupBits.tsx:132` | — | 1 |
| `GroupForm` | `apps/mobile/src/features/community/GroupForm.tsx:32` | 그룹 만들기 / 설정: name, the one required topic, category, how people join, about and rules, pictures. | 4 |
| `WeekPanel` | `apps/mobile/src/features/community/GroupHome.tsx:35` | 이번 주 그룹 현황: who is here, then three figures against last week. | 2 |
| `GoalsPanel` | `apps/mobile/src/features/community/GroupHome.tsx:105` | 그룹 목표: up to three, each with its period, progress and my share. | 4 |
| `PopularPanel` | `apps/mobile/src/features/community/GroupHome.tsx:160` | 인기 게시물: the group's most engaging recent posts, and the way to all of them. | 2 |
| `GoalEditor` | `apps/mobile/src/features/community/GroupHome.tsx:189` | — | 6 |
| `GroupsPane` | `apps/mobile/src/features/community/GroupsPane.tsx:11` | 그룹 tab: my groups as cards (or groups to join when I have none), a 새 그룹 card at the end, then the newest posts from my groups. | 0 |
| `PostExtra` | `apps/mobile/src/features/community/LearningCards.tsx:22` | The card under an 'activity' or 'material' post. | 1 |
| `GroupGoalNudge` | `apps/mobile/src/features/community/LearningCards.tsx:155` | 홈: the group goal card ("그룹 목표까지 문제 30개") that starts a session. | 4 |
| `PostCard` | `apps/mobile/src/features/community/PostCard.tsx:36` | One post, X-style. Every list (feeds, profile tabs, threads, search) uses it. - context: a line above the post, aligned to the content column (고정됨 · 공지 · 그룹) - lineAbove / lineBelow: the thread connector in the avatar column - focus: the post a thread page is about (larger text, full time) - showReplyTo: "@누구에게 보내는 답글" when the parent is not drawn right above it | 7 |
| `Shell` | `apps/mobile/src/features/community/PostCard.tsx:150` | The same frame for placeholders (deleted / unavailable posts) inside a thread. | 4 |
| `PostList` | `apps/mobile/src/features/community/PostList.tsx:8` | A paged list of posts under an arbitrary header (topic, profile, saved). | 9 |
| `PostMedia` | `apps/mobile/src/features/community/PostMedia.tsx:22` | Photos (1–4 in a grid), one video, or files, inside a post. | 2 |
| `LinkCard` | `apps/mobile/src/features/community/PostMedia.tsx:162` | — | 1 |
| `ProfileForm` | `apps/mobile/src/features/community/ProfileForm.tsx:28` | 닉네임 · @핸들 · 아바타. The handle is checked while typing; the server has the last word. | 5 |
| `RichBody` | `apps/mobile/src/features/community/RichBody.tsx:10` | — | 2 |
| `RulesSheet` | `apps/mobile/src/features/community/RulesSheet.tsx:20` | The community guide, accepted once before the first post or reply. | 3 |
| `ShareSheet` | `apps/mobile/src/features/community/ShareSheet.tsx:18` | 공유하기: a session result or a material, to my profile (everyone) or one of my groups. | 6 |
| `SortSheet` | `apps/mobile/src/features/community/SortSheet.tsx:16` | One way to sort a timeline everywhere (주제 탭을 한 번 더, 그룹 게시물). | 5 |
| `SortControl` | `apps/mobile/src/features/community/SortSheet.tsx:45` | "최신순 ⌄" at the right of a list header; opens the same sheet. | 3 |
| `TopicPicker` | `apps/mobile/src/features/community/TopicPicker.tsx:12` | Search topics, or start a new one with what was typed. | 6 |
| `TopicTimeline` | `apps/mobile/src/features/community/TopicTimeline.tsx:9` | A topic's posts, used by the community tab (followed topics) and the topic page. | 3 |
## Mordy 기능: documents (1)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `ProcessingCard` | `apps/mobile/src/features/documents/ProcessingCard.tsx:40` | "문제를 준비하고 있어요" with a 4-step progress rail (home, document detail). | 6 |
## Mordy 기능: errors (1)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `ErrorScreen` | `apps/mobile/src/features/errors/ErrorScreen.tsx:14` | A screen that throws while rendering: a calm "다시 시도" instead of a blank app, and the error goes to the server log (report_client_error, 30 a day per account at most). | 2 |
## Mordy 기능: exams (3)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `CalendarArt` | `apps/mobile/src/features/exams/CalendarArt.tsx:8` | The soft 3D calendar of the exam references (시험 정보 art, 최종 확인 card), drawn with views so it follows the theme and stays sharp at any size. | 1 |
| `ScopeSheet` | `apps/mobile/src/features/exams/ScopeSheet.tsx:22` | "수학 시험 범위" (reference 1, step 3) as a sheet: the picker and 적용. | 6 |
| `ScopePicker` | `apps/mobile/src/features/exams/ScopeSheet.tsx:43` | The range itself: 단원/개념 or 자료 on top, then the whole subject or a hand-picked range (both tabs edit the same set of concepts). Used by the exam wizard and 모의고사. Starts from `value` (remount with a key to reset); reports every change. | 4 |
## Mordy 기능: home (1)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `FirstRunHero` | `apps/mobile/src/features/home/FirstRunHero.tsx:19` | Home before the first document: what Mordy does in three steps, and one clear action. | 2 |
## Mordy 기능: lessons (4)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `CheckQuestion` | `apps/mobile/src/features/lessons/CheckQuestion.tsx:20` | A lesson checkpoint: one question graded on the spot (bare, like the quiz), with the one-line explanation. The answer goes through the usual outbox. | 3 |
| `LessonLink` | `apps/mobile/src/features/lessons/LessonLink.tsx:8` | "3분 레슨으로 다시 보기": under a concept wherever it is shown on its own. | 3 |
| `LessonList` | `apps/mobile/src/features/lessons/LessonList.tsx:26` | 과목 → 레슨: every concept as a 3-minute lesson, by unit. | 3 |
| `StoryReader` | `apps/mobile/src/features/lessons/StoryReader.tsx:39` | 스토리 레슨: the concept as a picture book — a cover with its picture, one idea per page (swipe or tap the arrows), the checkpoints between pages, and the finish at the end. 듣기 reads the pages aloud and turns them. | 6 |
## Mordy 기능: mock (9)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `AnswerSheet` | `apps/mobile/src/features/mock/AnswerSheet.tsx:14` | 답안지 (OMR): one row per question — its number, then ①–⑤ to mark (or a field for 단답형). Marks here are the answers. Tapping the number opens the question (in the app); on paper there is nothing to open. | 6 |
| `MockRow` | `apps/mobile/src/features/mock/MockCard.tsx:22` | One mock in a list: title, date and size, its state on the right. Opens the preview. | 1 |
| `MockCard` | `apps/mobile/src/features/mock/MockCard.tsx:39` | 시험 플래너 > 학습 추천: this exam's mocks and 모의고사 만들기 (the same sheet as 과목 > 문제 > 모의고사, starting from the exam's range). | 1 |
| `MockGenerating` | `apps/mobile/src/features/mock/MockGenerating.tsx:29` | — | 1 |
| `WhileWaiting` | `apps/mobile/src/features/mock/MockGenerating.tsx:88` | 기다리는 동안: two lessons from the paper's range — what was missed last first, then what has not been studied yet — so the minutes the paper takes are not dead time. | 1 |
| `MockSheet` | `apps/mobile/src/features/mock/MockSheet.tsx:43` | 모의고사 만들기 in two steps: 1) the range (the exam wizard's picker; from an exam, its subjects on top with the exam range already picked), 2) questions, level, 단답형 share and time, preset like the subject's real paper. Made → its preview, where the student starts it. | 4 |
| `PaperPdfSheet` | `apps/mobile/src/features/mock/PaperPdfSheet.tsx:56` | 시험지 PDF: asks the worker to print the paper (문제지, A3 like the real papers) and its answer key (정답표, A4), waits while it prints, shows the first page, then saves it to 다운로드/Mordy or shares it. | 3 |
| `PaperQuestion` | `apps/mobile/src/features/mock/PaperQuestion.tsx:45` | A question on the mock exam paper, set like the printed papers: the number hanging in bold, the stem and its material, [n점] after the stem when the points are not the usual, and ①–⑤ in one row, in 3 + 2, or one per line as they fit. The picked number fills in like a marked answer sheet. Nothing is graded here (정답은 제출 후). | 5 |
| `References` | `apps/mobile/src/features/mock/References.tsx:13` | 참고된 자료: every page of the student's material, 기출 item and web page the worker read to write the paper, with the questions that used it. A tap opens it. | 1 |
## Mordy 기능: notes (2)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `AnswerCompare` | `apps/mobile/src/features/notes/AnswerCompare.tsx:8` | "내 답 ✕ … / 정답 ✓ …" rows shared by the session result and 오답 노트. | 2 |
| `NoteCard` | `apps/mobile/src/features/notes/NoteCard.tsx:16` | One missed question: my answer next to the right one; the explanation unfolds. | 8 |
## Mordy 기능: selfstudy (2)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `HoldButton` | `apps/mobile/src/features/selfstudy/HoldButton.tsx:14` | One button for a running 자습: a tap does `label` (잠깐 멈추기 / 계속하기); holding it fills a gauge for 3 s, turns it into 그만하기, and calls onHold. | 3 |
| `RunningBanner` | `apps/mobile/src/features/selfstudy/RunningBanner.tsx:16` | On home while a 자습 runs (or is paused): where it stands — "집중 2/4 · 12분 남음", "자습 멈춤 · 공부 32분" — and a tap back to the timer. | 0 |
## Mordy 기능: session (8)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `OXPad` | `apps/mobile/src/features/session/answers.tsx:27` | OX: two big tiles. `choices` come shuffled from the session; O always sits left. | 4 |
| `BlankSlot` | `apps/mobile/src/features/session/answers.tsx:56` | The inline "____" of a 빈칸 question: empty, the picked word, or the result. | 2 |
| `ShortAnswerField` | `apps/mobile/src/features/session/answers.tsx:70` | 단답형: one large field; the keyboard's "완료" submits. | 5 |
| `ChoiceGrid` | `apps/mobile/src/features/session/answers.tsx:110` | Choices that are graphs, diagrams or pictures (reference 7): two cards a row, the number in the corner, the picked card outlined with a filled check. | 5 |
| `QuestionGrid` | `apps/mobile/src/features/session/QuestionGrid.tsx:18` | 문제 목록: every loaded question as a number; tap to jump there. | 6 |
| `QuestionTrack` | `apps/mobile/src/features/session/QuestionTrack.tsx:8` | Progress through the current worksheet: one segment per question, filled once answered (바로 채점: green or red), the current one marked. Long worksheets fall back to a single bar. | 2 |
| `ReasonChips` | `apps/mobile/src/features/session/ReasonChips.tsx:13` | 오답 원인: one tap on why it went wrong. It shapes the report (실수형 · 개념형) and can be changed or cleared by tapping again. | 2 |
| `ReportSheet` | `apps/mobile/src/features/session/ReportSheet.tsx:26` | "이 문제 이상해요": the question is withdrawn at once and a replacement for the same concept is made from the same source. | 4 |
## Mordy 기능: settings (2)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `ThemeSheet` | `apps/mobile/src/features/settings/AppearanceSheets.tsx:41` | — | 2 |
| `FontSizeSheet` | `apps/mobile/src/features/settings/AppearanceSheets.tsx:86` | — | 2 |
## Mordy 기능: source (1)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `DocThumb` | `apps/mobile/src/features/source/DocThumb.tsx:8` | First page of a document (page scans only; other formats show a file glyph). | 3 |
## Mordy 기능: subjects (9)
| 이름 | 코드 위치 | 설명 | 속성 수 |
| --- | --- | --- | --- |
| `DeckLink` | `apps/mobile/src/features/subjects/DeckLink.tsx:7` | Entry to 개념 카드 (subject overview, unit page, exam planner): a well row like every list row. | 4 |
| `DocCard` | `apps/mobile/src/features/subjects/DocCard.tsx:19` | A document in a subject or unit: first page, title, date and pages, status. | 2 |
| `EmptySubjectHero` | `apps/mobile/src/features/subjects/EmptySubject.tsx:14` | A subject with no material yet: one card in the subject's colour, both ways to add. | 3 |
| `EmptyUnitCard` | `apps/mobile/src/features/subjects/EmptySubject.tsx:41` | A unit whose material is still missing or being processed. | 1 |
| `SubjectEditor` | `apps/mobile/src/features/subjects/SubjectEditor.tsx:24` | Create or edit a subject (name, 학교/학원/개인, icon). Colour follows the icon preset. | 4 |
| `Tag` | `apps/mobile/src/features/subjects/TopicCard.tsx:21` | — | 2 |
| `TopicCard` | `apps/mobile/src/features/subjects/TopicCard.tsx:31` | 문제 탭 row: topic, unit, type and difficulty, the concept bookmark. Tap = solve this topic. | 4 |
| `UnitEditSheet` | `apps/mobile/src/features/subjects/UnitSheets.tsx:34` | 단원 편집: a new name, or folding this unit into another one (its concepts, materials and exam ranges move along). Returns the unit that remains. | 6 |
| `MoveConceptSheet` | `apps/mobile/src/features/subjects/UnitSheets.tsx:90` | 개념 옮기기: puts one concept in another unit of its subject, or in a new one. | 5 |
---
# AI용 파일
이 문서의 모든 내용은 아래 파일로도 읽을 수 있다.
| 파일 | 내용 |
| --- | --- |
| https://developers.moring.co/tds/llms.txt | 문서 목록과 요약(llms.txt 형식) |
| https://developers.moring.co/tds/llms-full.txt | 전체 문서를 하나로 합친 마크다운 |
| https://developers.moring.co/tds/tokens.json | 모든 토큰 값(서비스·모드별), 용도, 출처 |
| https://developers.moring.co/tds/tokens.css | CSS 변수 파일 |
| https://developers.moring.co/tds/components/{id}.md | 컴포넌트별 마크다운 (예: primary-button.md) |
| https://developers.moring.co/tds/{colors,typography,spacing,radius,elevation,motion,icons,guidelines,inventory}.md | 페이지별 마크다운 |
## 읽는 순서 추천
1. `llms.txt`로 전체 구조를 본다.
2. `colors.md`와 `guidelines.md`를 읽는다.
3. 만들려는 부품의 `components/{id}.md`를 읽는다.
---
# PrimaryButton
서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- 분류: 버튼
- 가져오기: `import { PrimaryButton } from '@/ui/Button'`
- 코드 위치: `apps/mobile/src/ui/Button.tsx:23` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/primary-button
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/primary-button 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 사용자가 다음에 해야 할 가장 중요한 행동 하나에 쓴다. 예: "저장", "다음으로", "동의하고 시작하기".
- 한 화면에 하나만 둔다. 화면 아래에 고정할 때는 `Screen`의 `footer`에 넣는다.
- 저장이나 전송처럼 결과를 기다리는 동안에는 `busy`를 켠다. 글자가 스피너로 바뀌고 다시 눌리지 않는다.
- 다음 화면으로 넘어가는 행동에는 화살표를 둔다(기본값). 저장, 확인, 재시도처럼 화면을 넘기지 않는 행동에는 `arrow={false}`를 쓴다.
### 쓰지 않는다
- 한 화면에 두 개 이상 놓지 않는다. 두 번째 행동에는 `SecondaryButton`이나 `TextButton`을 쓴다.
- 삭제처럼 되돌릴 수 없는 행동에 쓰지 않는다. `dialog.confirm`을 `tone: "danger"`로 호출해 한 번 더 확인한다.
- 목록 행 안에 쓰지 않는다. 행 안의 작은 행동에는 `SmallButton`을 쓴다.
- 사용할 수 없는 이유를 숨긴 채 비활성으로 두지 않는다. 이유를 근처 글자로 알린다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 화살표 있음 (기본) | `arrow`가 `true`다. 글자 뒤에 화살표 아이콘이 붙는다. 다음 화면으로 넘어가는 행동에 쓴다. |
| 화살표 없음 | `arrow={false}`. 저장, 확인, 재시도처럼 화면을 넘기지 않는 행동에 쓴다. 앱에서 가장 많이 쓰는 모양이다. |
| 앞쪽 아이콘 | `icon`에 17~18px 아이콘을 넣는다. 글자 앞에 놓인다. 아이콘 색은 직접 white로 지정한다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 52 | `height`를 생략하면 52다. |
| 시트·하단 영역 | 높이 54, 56 | 앱의 시트와 하단 고정 영역에서 `height={54}` 또는 `height={56}`을 쓴다. 이 외의 값은 쓰지 않는다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 그라데이션 배경, 그림자 `shadow(1)`. |
| 누름 | 0.985배로 살짝 줄어든다. 그림자와 색은 그대로다. 누르는 순간 가벼운 진동(`haptic.tap`)이 난다. |
| 비활성 | 투명도 0.45. 눌러도 아무 일도 일어나지 않고 진동도 없다. |
| 처리 중 | 글자 대신 스피너를 보여 준다. 크기는 그대로이고 눌러도 반응하지 않는다. |
## 사용 방법
### 기본
```tsx
import { PrimaryButton } from '@/ui/Button';
```
### 저장 버튼: 처리 중과 비활성
```tsx
void save()}
/>
```
### 화면 아래에 고정
```tsx
import { Screen } from '@/ui/layout';
}>
{/* 화면 내용 */}
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 버튼에 쓰는 글자. 한 줄로 보여 주고, 길면 글자를 80%까지 줄인다. |
| `onPress` | `() => void` | — | 선택 | 누르면 실행할 함수. 없거나 `disabled`, `busy`이면 눌러도 실행하지 않는다. |
| `disabled` | `boolean` | — | 선택 | `true`이면 투명도 0.45로 바꾸고 누를 수 없게 한다. |
| `busy` | `boolean` | — | 선택 | `true`이면 글자 대신 스피너를 보여 주고 누를 수 없게 한다. 접근성 상태에도 busy가 전달된다. |
| `arrow` | `boolean` | `true` | 선택 | `false`이면 글자 뒤의 화살표를 숨긴다. |
| `icon` | `ReactNode` | — | 선택 | 글자 앞에 놓을 아이콘. 흰색(`color.white`)으로 그린다. |
| `style` | `StyleProp` | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. 색, 높이, 모서리는 바꾸지 않는다. |
| `height` | `number` | `52` | 선택 | 버튼 높이(px). 앱은 52, 54, 56만 쓴다. |
| `accessibilityHint` | `string` | — | 선택 | 화면 읽기 프로그램이 버튼 이름 뒤에 읽어 주는 설명. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 높이 | 52 (기본) |
| 모서리 | pill(999) |
| 좌우 안쪽 여백 | 20 |
| 글자 | button (15/20, Bold), 색 white(웹에서는 onPrimaryStrong) |
| 배경 | gradient: gradientFrom → gradientTo, 왼쪽에서 오른쪽(90°) |
| 그림자 | shadow(1) |
| 아이콘 | ArrowRight 18px, 선 두께 2.4 |
| 글자와 아이콘 사이 | 6 |
## 접근성
- 역할은 `button`이다. 접근성 상태에 `disabled`와 `busy`가 들어간다.
- 버튼 높이가 52 이상이라 눌러야 하는 영역의 최소 크기(48×48)를 넘는다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
- Android에서는 흰색 물결(ripple)이 눌림 표시를 대신한다. 이때 누름 크기 변화(0.985배)는 유지된다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 버튼 글자 (15px 굵게) | `onPrimaryStrong` | `gradient` (양 끝 중 낮은 값) | 3.08:1 미달 | 3.08:1 미달 | 4.60:1 통과 | 5.48:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 가장 중요한 행동 하나만 PrimaryButton으로 하고, 나머지는 글자 버튼으로 낮춘다.
- 잘한 예: 저장 중에는 같은 자리에서 스피너로 바꾼다. 버튼 크기가 그대로라서 화면이 흔들리지 않는다.
- 잘못한 예: 한 화면에 PrimaryButton이 둘이면 무엇이 중요한지 알 수 없다.
- 잘못한 예: 삭제 같은 되돌릴 수 없는 행동에 쓰지 않는다. 위험한 행동은 확인 대화상자를 거친다.
## 함께 보는 컴포넌트
- [SecondaryButton](https://developers.moring.co/tds/components/secondary-button.md): 테두리만 있거나 옅은 주색으로 채운 알약 모양 버튼이다. PrimaryButton 옆의 두 번째 행동에 쓴다.
- [TextButton](https://developers.moring.co/tds/components/text-button.md): 글자만 있는 가장 가벼운 버튼이다. "나중에", "건너뛰기" 같은 선택 행동에 쓴다.
- [SmallButton](https://developers.moring.co/tds/components/small-button.md): 목록 행 안에 놓는 작은 사각형 버튼이다. "풀기", "승인", "거절" 같은 한 단어 행동에 쓴다.
- [Screen](https://developers.moring.co/tds/components/screen.md): 모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
- [dialog](https://developers.moring.co/tds/components/dialog.md): 화면 가운데 뜨는 확인 대화상자와 아래에서 올라오는 메뉴를 함수 호출 하나로 여는 도구다. 결과(눌린 버튼)를 약속(Promise)으로 돌려준다.
---
# SecondaryButton
테두리만 있거나 옅은 주색으로 채운 알약 모양 버튼이다. PrimaryButton 옆의 두 번째 행동에 쓴다.
- 분류: 버튼
- 가져오기: `import { SecondaryButton } from '@/ui/Button'`
- 코드 위치: `apps/mobile/src/ui/Button.tsx:50` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/secondary-button
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/secondary-button 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 같은 화면에 PrimaryButton이 이미 있을 때, 그다음으로 중요한 행동에 쓴다. 예: "이전", "시험지 PDF 받기".
- 화면 어디에도 PrimaryButton이 필요 없을 만큼 가벼운 행동에 쓴다. 예: "다시 확인하기".
- `tone="outline"`은 흰 면 위에, `tone="soft"`는 눈에 덜 띄게 하고 싶을 때 쓴다.
### 쓰지 않는다
- 화면의 가장 중요한 행동에 쓰지 않는다. 그 자리는 PrimaryButton이다.
- 한 줄에 세 개 이상 나란히 놓지 않는다. 둘까지만 나란히 놓는다.
- 목록 행 안에 쓰지 않는다. `SmallButton`을 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| outline (기본) | 흰 면(`surface`), 1.2px 테두리(`primaryLine`), 글자 `primary`. |
| soft | 옅은 주색 면(`primarySoft`), 테두리 없음, 글자 `primary`. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 48 | `height`를 생략하면 48이다. |
| 큰 버튼 | 높이 50, 52, 54 | 하단 영역에서 PrimaryButton과 높이를 맞출 때 쓴다. |
| 작은 버튼 | 높이 36 | 앱에서는 운영 도구 화면 한 곳에서만 쓴다. 새 화면에서는 쓰지 않는다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | outline은 흰 면과 테두리, soft는 옅은 주색 면. |
| 누름 | 투명도 0.8. |
| 비활성 | 투명도 0.45. 눌러도 반응하지 않는다. |
## 사용 방법
### 기본
```tsx
import { SecondaryButton } from '@/ui/Button';
```
### 옅은 면과 아이콘
```tsx
}
onPress={practiceWeak}
/>
```
### 이전·다음을 나란히
```tsx
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 버튼에 쓰는 글자. 한 줄, 길면 80%까지 줄인다. |
| `onPress` | `() => void` | — | 선택 | 누르면 실행할 함수. |
| `disabled` | `boolean` | — | 선택 | `true`이면 투명도 0.45로 바꾸고 누를 수 없게 한다. |
| `busy` | `boolean` | — | 선택 | 이 컴포넌트는 받지만 쓰지 않는다. 처리 중 표시가 필요하면 PrimaryButton을 쓴다. |
| `arrow` | `boolean` | — | 선택 | 이 컴포넌트는 받지만 쓰지 않는다. 화살표를 그리지 않는다. |
| `icon` | `ReactNode` | — | 선택 | 글자 앞에 놓을 아이콘. 색은 직접 `color.primary`로 지정한다. |
| `style` | `StyleProp` | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. 색, 높이, 모서리는 바꾸지 않는다. |
| `height` | `number` | `48` | 선택 | 버튼 높이(px). 기본 48. |
| `accessibilityHint` | `string` | — | 선택 | 화면 읽기 프로그램이 읽어 주는 설명. |
| `tone` | `'outline' \| 'soft'` | `'outline'` | 선택 | `"outline"`(기본)은 테두리, `"soft"`는 옅은 주색 면. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 높이 | 48 (기본) |
| 모서리 | pill(999) |
| 좌우 안쪽 여백 | 20 |
| 글자 | button (15/20, Bold), 색 primary |
| outline 면과 테두리 | surface 면, 1.2px primaryLine |
| soft 면 | primarySoft |
## 접근성
- 역할은 `button`이다. 접근성 상태에 `disabled`가 들어간다.
- 높이 48 이상이라 눌러야 하는 영역의 최소 크기를 넘는다. 높이 36 버튼은 최소 크기에 못 미치므로 새 화면에서는 쓰지 않는다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| outline 글자 (15px 굵게) | `primary` | `surface` | 3.93:1 미달 | 5.26:1 통과 | 4.50:1 통과 | 7.17:1 통과 | 4.5:1 |
| soft 글자 (15px 굵게) | `primary` | `primarySoft` | 3.45:1 미달 | 4.32:1 미달 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 나란히 놓을 때 PrimaryButton이 더 넓고, SecondaryButton은 보조 행동이라는 것이 보인다.
- 잘못한 예: 두 버튼의 무게가 같으면 어느 쪽이 중요한지 알 수 없다. 한쪽은 PrimaryButton으로 한다.
## 함께 보는 컴포넌트
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- [TextButton](https://developers.moring.co/tds/components/text-button.md): 글자만 있는 가장 가벼운 버튼이다. "나중에", "건너뛰기" 같은 선택 행동에 쓴다.
- [SmallButton](https://developers.moring.co/tds/components/small-button.md): 목록 행 안에 놓는 작은 사각형 버튼이다. "풀기", "승인", "거절" 같은 한 단어 행동에 쓴다.
---
# AmberButton
주의 색(amber)으로 채운 알약 모양 버튼이다. "복습 추천" 카드의 행동에만 쓴다.
- 분류: 버튼
- 가져오기: `import { AmberButton } from '@/ui/Button'`
- 코드 위치: `apps/mobile/src/ui/Button.tsx:66` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/amber-button
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/amber-button 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- AmberSoft 면 위에 놓는 "복습 추천" 같은 추천 카드의 행동 하나에 쓴다.
### 쓰지 않는다
- 일반 행동에 쓰지 않는다. 일반 행동은 PrimaryButton이다.
- 경고나 오류를 알리는 데 쓰지 않는다. 오류는 `danger`, 안내는 `Notice`를 쓴다.
- 흰 글자 대비가 낮으므로(아래 표) 새 화면에서 글자가 작아지는 곳에는 쓰지 않는다.
## 종류
종류가 없다. 모양이 하나다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 46 | `height`를 생략하면 46이다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | amber 면, 흰 글자. |
| 누름 | 투명도 0.85. |
| 비활성 | 비활성 표현이 없다. 처리 중에는 글자를 "준비 중…"으로 바꾸고 `onPress`를 막는다. |
## 사용 방법
### 기본
```tsx
import { AmberButton } from '@/ui/Button';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 버튼에 쓰는 글자. |
| `onPress` | `() => void` | — | 선택 | 누르면 실행할 함수. |
| `disabled` | `boolean` | — | 선택 | 이 컴포넌트는 받지만 쓰지 않는다. |
| `busy` | `boolean` | — | 선택 | 이 컴포넌트는 받지만 쓰지 않는다. |
| `arrow` | `boolean` | — | 선택 | 이 컴포넌트는 받지만 쓰지 않는다. |
| `icon` | `ReactNode` | — | 선택 | 이 컴포넌트는 받지만 쓰지 않는다. |
| `style` | `StyleProp` | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. 색, 높이, 모서리는 바꾸지 않는다. |
| `height` | `number` | `46` | 선택 | 버튼 높이(px). 기본 46. |
| `accessibilityHint` | `string` | — | 선택 | 이 컴포넌트는 받지만 쓰지 않는다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 높이 | 46 |
| 모서리 | pill(999) |
| 글자 | button (15/20, Bold), 색 white |
| 배경 | amber |
## 접근성
- 역할은 `button`이다.
- 흰 글자와 amber 면의 대비가 낮다. 아래 표에서 서비스별 값을 확인한다. 새로 만드는 웹 화면에서는 글자를 `text` 색으로 바꿔 대비를 맞춘다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 버튼 글자 (15px 굵게) | `white` | `amber` | 2.02:1 미달 | 2.02:1 미달 | 2.25:1 미달 | 1.82:1 미달 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 추천 카드 안에서 행동이 하나일 때만 쓴다.
- 잘못한 예: 일반 저장 행동에 쓰지 않는다. 색이 "주의"를 뜻하기 때문이다.
## 함께 보는 컴포넌트
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
---
# SmallButton
목록 행 안에 놓는 작은 사각형 버튼이다. "풀기", "승인", "거절" 같은 한 단어 행동에 쓴다.
- 분류: 버튼
- 가져오기: `import { SmallButton } from '@/ui/Button'`
- 코드 위치: `apps/mobile/src/ui/Button.tsx:79` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/small-button
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/small-button 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 목록 행 오른쪽에 놓는 짧은 행동에 쓴다. 글자는 두세 글자로 짧게 한다.
- 두 행동을 나란히 놓을 때는 중요한 쪽을 `primary`, 다른 쪽을 `soft`로 한다.
### 쓰지 않는다
- 화면의 주 행동에 쓰지 않는다. PrimaryButton을 쓴다.
- 긴 문장을 넣지 않는다. 폭이 글자에 맞춰 늘어나 행이 무너진다.
## 종류
| 이름 | 설명 |
| --- | --- |
| primary (기본) | `primary` 면, 흰 글자. |
| soft | `primarySoft` 면, `primary` 글자. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 32, 최소 폭 48 | 크기를 바꾸는 속성이 없다. 누르는 영역은 `hitSlop` 10으로 사방 10씩 넓어진다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
| 누름 | 투명도 0.8. |
| 비활성 | 비활성 표현이 없다. 누를 수 없는 행동은 렌더링하지 않는다. |
## 사용 방법
### 승인·거절 한 쌍
```tsx
import { SmallButton } from '@/ui/Button';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 버튼에 쓰는 글자. |
| `onPress` | `() => void` | — | 선택 | 누르면 실행할 함수. |
| `tone` | `'primary' \| 'soft'` | `'primary'` | 선택 | `"primary"`(기본)는 주색 면, `"soft"`는 옅은 주색 면. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 높이 | 32 (최소 폭 48) |
| 모서리 | 10 |
| 좌우 안쪽 여백 | 12 |
| 글자 | smallStrong (13/19, SemiBold) |
| 누르는 영역 | hitSlop 사방 10 |
## 접근성
- 역할은 `button`이다.
- 보이는 높이는 32지만 `hitSlop` 덕분에 누르는 영역은 52×68 이상이 된다. 이웃한 버튼과 간격이 8 미만이면 영역이 겹치므로 8 이상 띄운다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| primary 글자 (13px) | `onPrimary` | `primary` | 3.93:1 미달 | 3.31:1 미달 | 4.60:1 통과 | 7.25:1 통과 | 4.5:1 |
| soft 글자 (13px) | `primary` | `primarySoft` | 3.45:1 미달 | 4.32:1 미달 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 행 오른쪽에 짧은 글자로 놓는다.
- 잘못한 예: 긴 문장을 넣으면 버튼이 길어져 행이 무너진다. 짧은 단어로 쓴다.
## 함께 보는 컴포넌트
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
---
# TextButton
글자만 있는 가장 가벼운 버튼이다. "나중에", "건너뛰기" 같은 선택 행동에 쓴다.
- 분류: 버튼
- 가져오기: `import { TextButton } from '@/ui/Button'`
- 코드 위치: `apps/mobile/src/ui/Button.tsx:93` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/text-button
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/text-button 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- PrimaryButton 아래에 놓는 "나중에", "취소", "건너뛰기"에 쓴다.
- 패널 안의 "학습 카드 공유" 같은 보조 링크에 쓴다.
### 쓰지 않는다
- 화면의 유일한 행동에 쓰지 않는다. 버튼으로 보이지 않아 눌러야 한다는 것을 알기 어렵다.
- 문장 안에 끼워 넣지 않는다. 문장 속 링크는 별도 컴포넌트가 없다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 주색 (기본) | 글자가 `primary`다. |
| 보조색 | `tone`에 `color.textSecondary`를 넘긴다. "나중에", "건너뛰기"처럼 하지 않아도 되는 행동에 쓴다. 앱에서는 이 모양이 더 많다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 글자 13, 위아래 여백 6 | 크기를 바꾸는 속성이 없다. 누르는 영역은 `hitSlop` 10으로 넓어진다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
| 누름 | 투명도 0.6. |
| 비활성 | 비활성 표현이 없다. |
## 사용 방법
### 기본
```tsx
import { TextButton } from '@/ui/Button';
```
### 보조색
```tsx
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 버튼에 쓰는 글자. |
| `onPress` | `() => void` | — | 선택 | 누르면 실행할 함수. |
| `tone` | `string` | `color.primary` | 선택 | 글자 색. 기본은 `color.primary`. 보조 행동에는 `color.textSecondary`를 넘긴다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 글자 | smallStrong (13/19, SemiBold) |
| 위아래 여백 | 6 |
| 누르는 영역 | hitSlop 사방 10 |
## 접근성
- 역할은 `button`이다.
- `textSecondary` 색 글자는 흰 면에서 대비가 4.5:1에 못 미친다. 웹에서는 `textSecondaryInk`를 쓴다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 주색 글자 (13px) | `primary` | `surface` | 3.93:1 미달 | 5.26:1 통과 | 4.50:1 통과 | 7.17:1 통과 | 4.5:1 |
| 보조색 글자 (13px) | `textSecondary` | `surface` | 4.23:1 미달 | 6.95:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 주 행동 아래에 낮은 무게로 놓는다.
- 잘못한 예: 화면에 이 버튼만 있으면 버튼처럼 보이지 않아 사용자가 지나친다.
## 함께 보는 컴포넌트
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- [SecondaryButton](https://developers.moring.co/tds/components/secondary-button.md): 테두리만 있거나 옅은 주색으로 채운 알약 모양 버튼이다. PrimaryButton 옆의 두 번째 행동에 쓴다.
---
# CircleButton
화살표가 들어 있는 둥근 주색 버튼이다. 온보딩에서 다음 단계로 넘어갈 때 쓴다.
- 분류: 버튼
- 가져오기: `import { CircleButton } from '@/ui/Button'`
- 코드 위치: `apps/mobile/src/ui/Button.tsx:102` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/circle-button
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/circle-button 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 온보딩처럼 한 단계씩 넘어가는 화면의 "다음"에 쓴다.
### 쓰지 않는다
- 글자가 필요한 행동에 쓰지 않는다. 글자가 없어서 접근성 이름(`label`)에만 의존한다.
- 온보딩 밖에서 쓰지 않는다. 다른 화면에서는 PrimaryButton이 다음 행동을 맡는다.
## 종류
종류가 없다. 화살표 아이콘 하나만 들어간다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 지름 56 | `size`로 바꿀 수 있다. 앱은 56만 쓴다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 대각선(135°) 그라데이션, 그림자 `shadow(2)`. |
| 누름 | 0.96배로 줄어든다. |
| 비활성 | 비활성 표현이 없다. |
## 사용 방법
### 기본
```tsx
import { CircleButton } from '@/ui/Button';
router.push('/(onboarding)/fields')} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `onPress` | `() => void` | — | 선택 | 누르면 실행할 함수. |
| `size` | `number` | `56` | 선택 | 지름(px). 기본 56. |
| `label` | `string` | `'다음'` | 선택 | 화면 읽기 프로그램이 읽는 이름. 기본 "다음". |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 지름 | 56 |
| 배경 | gradient, 대각선(왼쪽 위 → 오른쪽 아래) |
| 그림자 | shadow(2) |
| 아이콘 | ArrowRight 22px, 선 두께 2.4, white |
## 접근성
- 역할은 `button`이고 이름은 `label`("다음")이다. 글자가 없으므로 이름을 반드시 둔다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 아이콘 (22px) | `onPrimaryStrong` | `gradient` (양 끝 중 낮은 값) | 3.08:1 통과 | 3.08:1 통과 | 4.60:1 통과 | 5.48:1 통과 | 3:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 이름(`label`)을 화면의 행동에 맞춰 "다음 단계"처럼 구체적으로 쓴다.
- 잘못한 예: 결과가 중요한 행동(저장, 결제)에 쓰지 않는다. 글자가 없어 무엇이 일어나는지 알 수 없다.
## 함께 보는 컴포넌트
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- [PageDots](https://developers.moring.co/tds/components/page-dots.md): 지금이 몇 번째 단계인지 알려 주는 작은 점 줄이다. 온보딩 단계 표시에 쓴다.
---
# IconButton
아이콘 하나만 있는 투명한 둥근 버튼이다. 헤더의 "뒤로", "검색"에 쓴다.
- 분류: 버튼
- 가져오기: `import { IconButton } from '@/ui/Button'`
- 코드 위치: `apps/mobile/src/ui/Button.tsx:112` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/icon-button
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/icon-button 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 헤더의 뒤로 가기, 검색, 북마크처럼 뜻이 널리 알려진 아이콘 행동에 쓴다.
- `label`에 행동 이름을 반드시 쓴다.
### 쓰지 않는다
- 처음 보는 아이콘에 쓰지 않는다. 글자가 있는 버튼을 쓴다.
- 배경색이 필요한 버튼에 쓰지 않는다. 배경이 없다.
## 종류
종류가 없다. 안에 들어가는 아이콘은 사용하는 쪽이 넣는다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 40 × 40 | 크기를 바꾸는 속성이 없다. `hitSlop` 10으로 누르는 영역은 60×60이 된다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 투명 배경. 아이콘만 보인다. |
| 누름 | 투명도 0.6. |
| 비활성 | 비활성 표현이 없다. |
## 사용 방법
### 헤더의 검색 버튼
```tsx
import { IconButton } from '@/ui/Button';
import { Search } from '@/ui/icons';
router.push('/search')}>
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `onPress` | `() => void` | — | 선택 | 누르면 실행할 함수. |
| `children` | `ReactNode` | — | 필수 | 버튼 안에 그릴 아이콘. |
| `label` | `string` | — | 필수 | 화면 읽기 프로그램이 읽는 이름. 필수. |
| `style` | `StyleProp` | — | 선택 | 위치를 맞출 때만 쓴다. 예: 헤더 가장자리에 맞추려고 `marginLeft: -10`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 크기 | 40 × 40, 모서리 20(원) |
| 아이콘 | 22px, 선 두께 2.2, 색 text |
| 누르는 영역 | hitSlop 사방 10 |
## 접근성
- 역할은 `button`이고 이름은 `label`이다. 이름이 없으면 화면 읽기 프로그램이 "버튼"이라고만 읽는다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
## 잘한 예와 잘못한 예
- 잘한 예: 이름(`label`)과 아이콘 뜻이 같다.
- 잘못한 예: `label` 없이 쓰지 않는다. 시각 장애가 있는 사용자는 이 버튼의 뜻을 알 수 없다.
## 함께 보는 컴포넌트
- [BackHeader](https://developers.moring.co/tds/components/back-header.md): 화면 위쪽의 뒤로 가기 줄이다. 왼쪽에 뒤로 화살표, 가운데에 제목(선택), 오른쪽에 행동 자리가 있다.
- [TitleHeader](https://developers.moring.co/tds/components/title-header.md): 탭의 첫 화면 위쪽에 놓는 큰 제목 줄이다. 왼쪽에 제목, 오른쪽에 행동 자리가 있다.
---
# Pressable
React Native의 Pressable에 Android 눌림 물결(ripple)을 더한 기본 눌림 영역이다. 앱의 모든 버튼이 이것을 쓴다.
- 분류: 버튼
- 가져오기: `import { Pressable } from '@/ui/Pressable'`
- 코드 위치: `apps/mobile/src/ui/Pressable.tsx:41` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/pressable
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/pressable 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 직접 눌리는 새 컴포넌트를 만들 때 React Native의 `Pressable` 대신 이것을 가져와 쓴다.
- 물결을 끄고 싶은 곳(배경 가림막)에는 `ripple={false}`를 쓴다.
### 쓰지 않는다
- React Native의 `Pressable`을 직접 가져오지 않는다. Android에서 눌림 표시가 빠진다.
- 눌림 표시를 `opacity`로만 처리하지 않는다. Android에서는 이 컴포넌트가 물결로 대신하고 `opacity`와 배경색 변화를 지운다.
## 종류
| 이름 | 설명 |
| --- | --- |
| auto (기본) | 눌리는 영역 안에서만 퍼지는 물결. 아이콘만 있는 컨트롤(배경 없음, `hitSlop` 있음)은 원형으로 퍼진다. 배경이 어두운 색이면 흰 물결이다. |
| light | 흰 물결을 면 앞쪽에 그린다. 그라데이션이나 사진 위의 컨트롤에 쓴다. |
| borderless | 반지름 22의 원형 물결. 머티리얼 아이콘 버튼 모양이다. |
| false | 물결을 그리지 않는다. 화면을 덮는 배경, 감싸기용 영역에 쓴다. |
## 크기
크기는 없다. 자식과 `style`이 정한다.
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| Android | 눌리는 동안 물결이 퍼진다. 색은 흰 물결 `rgba(255,255,255,0.26)`(채운 면 위), 밝은 모드에서 `rgba(17,24,39,0.09)`, 어두운 모드에서 `rgba(255,255,255,0.14)`. |
| iOS와 웹 | 물결이 없다. `style`에 넘긴 누름 상태 스타일(`pressed`)이 그대로 쓰인다. |
## 사용 방법
### 새 눌림 영역
```tsx
import { Pressable } from '@/ui/Pressable';
[styles.row, pressed && { backgroundColor: color.pressed }]}
>
{/* 내용 */}
```
### 물결 끄기
```tsx
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `ripple` | `Ripple` | `'auto'` | 선택 | 물결의 종류. `"auto"`(기본), `"light"`, `"borderless"`, `false`. |
| `android_ripple` | `(React Native)` | — | 선택 | Android 물결 설정을 직접 넘긴다. 넘기면 `ripple` 설정보다 우선한다. |
| `style` | `(React Native)` | — | 선택 | React Native의 `Pressable`과 같다. 객체, 배열, 또는 `({ pressed }) => 스타일` 함수. |
나머지 속성은 `PressableProps`를 그대로 받는다.
위에 적지 않은 나머지 속성(`onPress`, `hitSlop`, `disabled`, `accessibilityRole` 등)은 React Native의 `PressableProps`를 그대로 받는다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 물결(채운 면 위) | rgba(255,255,255,0.26) |
| 물결(밝은 모드) | rgba(17,24,39,0.09) |
| 물결(어두운 모드) | rgba(255,255,255,0.14) |
| 원형 물결 반지름 | 22 |
| "어두운 배경" 판정 | 배경색 밝기(0.2126R+0.7152G+0.0722B)가 0.55 미만 |
## 접근성
- `onPress`가 있고 `disabled`가 아닐 때만 물결이 켜진다.
- 눌리는 영역에는 `accessibilityRole`과 이름(`accessibilityLabel` 또는 안의 글자)을 둔다.
## 잘한 예와 잘못한 예
- 잘한 예: 눌림 표시는 배경색 변화로 쓰고, Android 물결은 이 컴포넌트에 맡긴다.
- 잘못한 예: `opacity`를 줄여 누름을 나타내면 Android에서는 지워진다. 눌림 표시가 두 번 나타나지 않게 `Pressable`을 쓴다.
## 함께 보는 컴포넌트
- [IconButton](https://developers.moring.co/tds/components/icon-button.md): 아이콘 하나만 있는 투명한 둥근 버튼이다. 헤더의 "뒤로", "검색"에 쓴다.
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
---
# Field
글자를 입력하는 칸이다. 이름(라벨), 안내 문구, 오류 문구, 비밀번호 보기와 지우기 버튼을 포함한다.
- 분류: 입력과 선택
- 가져오기: `import { Field } from '@/ui/Field'`
- 코드 위치: `apps/mobile/src/ui/Field.tsx:11` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/field
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/field 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이메일, 이름, 메모처럼 짧은 글자를 한 줄로 받을 때 쓴다.
- 입력 칸마다 `label`을 반드시 쓴다. 안내 문구(`placeholder`)로 이름을 대신하지 않는다.
- 값이 틀렸을 때 `error`에 이유를 쓴다. 칸의 테두리가 `danger` 색이 되고 문구가 아래에 나온다.
### 쓰지 않는다
- 여러 줄 글을 받을 때 쓰지 않는다. `multiline`을 넘길 수는 있지만 높이는 최소 52로 한 줄 기준이다.
- 여러 보기 중 고르는 데 쓰지 않는다. `ChoiceCard`나 `OptionSheet`를 쓴다.
- `error`와 `helper`를 동시에 보여 주지 않는다. `error`가 있으면 `helper`는 가려진다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 기본 | `field` 배경의 채워진 칸. 포커스를 받으면 `surface` 배경과 `primary` 테두리로 바뀐다. 이름 글자는 `smallStrong`. |
| outline | `outline`이 `true`다. 흰 면에 `line` 테두리, 더 큰 칸(최소 56)과 큰 글자(17, SemiBold). 이름 글자는 `bodyStrong`. 로그인처럼 칸이 화면의 주인공일 때 쓴다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 최소 높이 52 | 글자가 커지면 칸도 커진다(`minHeight`). |
| outline | 최소 높이 56 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 비어 있음 | 안내 문구(`placeholder`)가 `textTertiary` 색으로 보인다. |
| 포커스 | 테두리가 `primary`(1.2px), 배경이 `surface`. |
| 입력됨 | 글자가 `text` 색. `clearable`이면 지우기 버튼이 나온다. |
| 오류 | 테두리가 `danger`, 아래에 `danger` 색 오류 문구(`caption`). |
| 비활성 | 입력할 수 없다. 앱의 `Field`에는 비활성 전용 스타일이 없고 React Native의 `editable={false}`를 따른다. |
## 사용 방법
### 기본
```tsx
import { Field } from '@/ui/Field';
```
### 오류와 지우기
```tsx
```
### 비밀번호
```tsx
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 칸 위에 쓰는 이름. 화면 읽기 프로그램의 이름도 이것이다. |
| `error` | `string \| null` | — | 선택 | 오류 문구. 있으면 테두리가 `danger`가 되고 문구가 아래에 나온다. `null`이면 오류가 없다. |
| `helper` | `string` | — | 선택 | 칸 아래의 안내 문구(`textSecondary`). `error`가 있으면 보이지 않는다. |
| `secure` | `boolean` | — | 선택 | `true`이면 입력을 점으로 가리고 오른쪽에 보기·숨기기 버튼을 둔다. |
| `clearable` | `boolean` | — | 선택 | `true`이고 값이 있으면 오른쪽에 지우기(×) 버튼을 둔다. |
| `outline` | `boolean` | — | 선택 | `true`이면 outline 모양(흰 면, 큰 칸)이다. |
| `prefix` | `string` | — | 선택 | 입력 앞에 붙는 고정 글자. `textTertiary` 색. 예: "@". |
| `status` | `ReactNode` | — | 선택 | 입력 오른쪽에 놓을 요소(사용 가능 표시 등). |
| `style` | `(React Native)` | — | 선택 | 입력 글자(`TextInput`)에 적용할 스타일. |
나머지 속성은 `TextInputProps`를 그대로 받는다.
위에 적지 않은 속성(`value`, `onChangeText`, `placeholder`, `keyboardType`, `autoCapitalize` 등)은 React Native `TextInput`의 속성을 그대로 받는다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 칸 | 최소 높이 52, 모서리 `radius.md`(12), 좌우 안쪽 여백 16, 안쪽 요소 사이 8 |
| 배경과 테두리 | `field` 배경, 1.2px `field` 테두리. 포커스 시 `surface` 배경, `primary` 테두리. 오류 시 `danger` 테두리 |
| 입력 글자 | Pretendard Medium 15, 색 `text`. 안내 문구 `textTertiary` |
| 이름과 칸 사이 | 6 (outline은 10) |
| outline | 최소 높이 56, 모서리 16, 배경 `surface`, 테두리 `line`, 좌우 18, 입력 글자 17 SemiBold |
| 보기·지우기 버튼 | 아이콘 20px, 색 `textTertiary`, `hitSlop` 10 |
## 접근성
- 이름(`accessibilityLabel`)이 `label`로, 힌트가 `error` 또는 `helper`로 지정된다.
- 비밀번호 보기 버튼의 이름은 "비밀번호 보기" 또는 "비밀번호 숨기기", 지우기 버튼은 "{label} 지우기"다.
- 오류는 글자(`error`)로도 알린다. 테두리 색만으로 알리지 않는다.
- `placeholder`(`textTertiary`)는 대비가 낮으므로 이름 역할을 맡기지 않는다.
- 앱은 터치 화면이라 마우스 올림(hover)이 없다. 웹에서는 포커스 링(2.5px 실선, 바깥 3px)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 입력 글자 | `text` | `field` | 15.96:1 통과 | 14.08:1 통과 | 14.95:1 통과 | 9.62:1 통과 | 4.5:1 |
| 오류 문구 (11.5px) | `danger` | `surface` | 4.55:1 통과 | 5.76:1 통과 | 5.04:1 통과 | 7.11:1 통과 | 4.5:1 |
| 안내 문구 `placeholder` | `textTertiary` | `field` | 2.36:1 미달 | 3.23:1 미달 | 2.75:1 미달 | 3.92:1 미달 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 이름(`label`)과 이유가 있는 오류 문구를 함께 쓴다.
- 잘못한 예: 안내 문구만 있고 이름이 없다. 입력을 시작하면 안내가 사라져 무엇을 적는 칸인지 알 수 없다.
## 함께 보는 컴포넌트
- [OptionRow](https://developers.moring.co/tds/components/option-row.md): 퀴즈 보기 한 줄이다. 고르기 전, 고른 뒤, 채점 뒤(정답, 오답)의 모습이 정해져 있다.
- [ChoiceCard](https://developers.moring.co/tds/components/choice-card.md): 카드 모양의 선택지다. 고르면 테두리가 굵어지고 체크 표시가 붙는다. 온보딩의 과목·알림 선택에 쓴다.
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
---
# PillTabs
알약 모양의 탭 모음이다. 하나를 고르면 그 칸이 짙은 색(네이비, 다크 모드는 주색)으로 채워진다.
- 분류: 입력과 선택
- 가져오기: `import { PillTabs } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:13` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/pill-tabs
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/pill-tabs 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 한 화면 안에서 같은 종류의 내용을 2~4개로 나눠 보여 줄 때 쓴다. 예: 리포트의 "하루 · 주 · 월".
- 카드나 시트 안의 작은 선택에는 `size="sm"`을 쓴다.
- 탭이 5개 이상이면 `scroll`을 켜서 옆으로 밀어 보게 한다.
### 쓰지 않는다
- 페이지를 이동하는 데 쓰지 않는다. 화면 간 이동은 `TabBar`다.
- 설정 값을 켜고 끄는 데 쓰지 않는다. `SettingsSwitch`를 쓴다.
- 탭 이름을 6글자 넘게 쓰지 않는다. 칸이 좁아진다.
## 종류
| 이름 | 설명 |
| --- | --- |
| md (기본) | 페이지의 탭. 칸 높이 42, 글자 `bodyStrong`. |
| sm | 카드, 시트, 설정 안의 탭. 칸 높이 36, 글자 `smallStrong`. |
| scroll | 탭이 많을 때. 칸 폭이 글자에 맞춰지고(좌우 16) 바탕 띠를 옆으로 민다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| md | 칸 42, 띠 안쪽 여백 4, 모서리 26 | |
| sm | 칸 36, 띠 안쪽 여백 3, 모서리 21 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택됨 | 칸이 `navy`(다크 모드는 `primary`)로 채워지고 글자는 흰색이다. 접근성 상태 `selected`. |
| 선택 안 됨 | 바탕은 `tabTrack`, 글자는 `textSecondary`. |
| 누름 | 선택이 바뀌는 순간 가벼운 선택 진동(`haptic.select`). 이미 선택된 칸을 눌러도 아무 일도 없다. |
## 사용 방법
### 기본
```tsx
import { PillTabs } from '@/ui/controls';
const [range, setRange] = useState<'day' | 'week' | 'month'>('day');
```
### 작은 크기와 옆으로 밀기
```tsx
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `options` | `{ value: T; label: string }[]` | — | 필수 | 탭 목록. 각 항목은 `{ value, label }`. `value`는 문자열이다. |
| `value` | `T` | — | 필수 | 지금 선택된 `value`. |
| `onChange` | `(v: T) => void` | — | 필수 | 다른 칸을 눌렀을 때 새 `value`와 함께 호출된다. |
| `size` | `'md' \| 'sm'` | `'md'` | 선택 | `"md"`(기본) 또는 `"sm"`. |
| `scroll` | `boolean` | — | 선택 | `true`이면 칸 폭이 글자에 맞춰지고 띠를 옆으로 밀 수 있다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 띠(바탕) | `tabTrack` 배경, md는 안쪽 여백 4·모서리 26, sm은 3·21 |
| 칸 | md 높이 42·모서리 21, sm 높이 36·모서리 18, 좌우 6 (scroll은 16) |
| 선택된 칸 | `navy` 배경(다크는 `primary`), 글자 white |
| 글자 | md `bodyStrong`(15/22 SemiBold), sm `smallStrong`(13/19 SemiBold). 선택 안 된 글자 `textSecondary` |
## 접근성
- 띠의 역할은 `tablist`, 각 칸은 `tab`이고 상태 `selected`가 전달된다.
- 칸 높이가 42 또는 36이라 눌리는 영역이 48 미만이다. 위아래 여백을 6 이상 두어 눌림을 돕는다.
- 선택은 색(채움)과 글자 색 변화로 나타낸다. 다크 모드에서도 글자(흰색)와 채움이 함께 바뀐다.
- 앱은 터치 화면이라 마우스 올림(hover)이 없다. 웹에서는 포커스 링(2.5px 실선, 바깥 3px)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 선택된 칸 글자 (15px 굵게) | `white` | `navy` | 15.61:1 통과 | 1.24:1 미달 | 11.97:1 통과 | 1.35:1 미달 | 4.5:1 |
| 선택 안 된 글자 (15px 굵게) | `textSecondary` | `tabTrack` | 3.60:1 미달 | 6.42:1 통과 | 4.13:1 미달 | 6.26:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 탭 이름을 짧게 쓰고 2~4개로 둔다.
- 잘못한 예: 이름이 길고 개수가 많으면 칸이 좁아져 글자가 잘린다. 이럴 때는 `scroll`을 켠다.
## 함께 보는 컴포넌트
- [TabBar](https://developers.moring.co/tds/components/tab-bar.md): Mordy 앱 맨 아래의 탭 줄이다. 홈 · 과목 · (+) · 커뮤니티 · 더보기. 가운데 +는 탭이 아니라 자료 추가 행동이다.
- [SettingsSwitch](https://developers.moring.co/tds/components/settings-switch.md): 켜고 끄는 스위치다. 설정 행의 오른쪽에 놓는다.
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
---
# Chip
짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
- 분류: 입력과 선택
- 가져오기: `import { Chip } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:34` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/chip
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/chip 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 항목의 분류나 상태를 한두 단어로 알릴 때 쓴다. 예: "복습 추천", "D-24".
- 글자만 바꿔 색이 다른 표지가 필요하면 `tone`(글자색)과 `soft`(바탕색)를 함께 지정한다.
### 쓰지 않는다
- 누르는 버튼으로 쓰지 않는다. 눌리지 않는 요소다. 선택 칩이 필요하면 `FilterPill`을 쓴다.
- 문장을 넣지 않는다. 한 줄 안에서 두세 단어까지만 쓴다.
- `tone`과 `soft`를 짝이 맞지 않게 바꾸지 않는다(바탕이 연하면 글자는 진하게).
## 종류
| 이름 | 설명 |
| --- | --- |
| 기본 | `primarySoft` 바탕, `primary` 글자. |
| 색 바꾸기 | `tone`(글자색), `soft`(바탕색)로 지정한다. 예: 오류 `danger`/`dangerSoft`, 성공 `successText`/`successSoft`. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 28, 좌우 12 | 크기를 바꾸는 속성이 없다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
| 비활성·눌림 | 눌리지 않는 요소라서 누름, 비활성 상태가 없다. |
## 사용 방법
### 기본
```tsx
import { Chip } from '@/ui/controls';
```
### 색 바꾸기
```tsx
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 표지에 쓰는 글자. |
| `tone` | `string` | `color.primary` | 선택 | 글자색. 기본 `color.primary`. |
| `soft` | `string` | `color.primarySoft` | 선택 | 바탕색. 기본 `color.primarySoft`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 크기 | 높이 28, 좌우 12, 모서리 pill |
| 글자 | `smallStrong` (13/19, SemiBold) |
| 위치 | `alignSelf: flex-start`. 부모의 폭을 채우지 않고 글자 폭만큼만 차지한다. |
## 접근성
- 눌리지 않는 표지라서 역할을 지정하지 않는다. 글자 자체가 읽힌다.
- 의미를 색으로만 전달하지 않는다. 글자("D-3")가 뜻을 말한다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 기본 글자 (13px 굵게) | `primary` | `primarySoft` | 3.45:1 미달 | 4.32:1 미달 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
| 오류 글자 (13px 굵게) | `danger` | `dangerSoft` | 4.09:1 미달 | 5.39:1 통과 | 4.69:1 통과 | 5.43:1 통과 | 4.5:1 |
| 성공 글자 (13px 굵게) | `successText` | `successSoft` | 5.39:1 통과 | 9.55:1 통과 | 10.96:1 통과 | 6.84:1 통과 | 4.5:1 |
| 주의 글자 (13px 굵게) | `amberText` | `amberSoft` | 5.48:1 통과 | 9.81:1 통과 | 6.66:1 통과 | 6.86:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 짧은 글자와 짝이 맞는 색을 쓴다.
- 잘못한 예: 바탕이 연한데 글자도 연하게 지정하면 읽히지 않는다.
## 함께 보는 컴포넌트
- [FilterPill](https://developers.moring.co/tds/components/filter-pill.md): "과목 전체 ▾"처럼 이름과 지금 값을 보여 주고, 누르면 선택 목록을 여는 알약 버튼이다.
- [AiMark](https://developers.moring.co/tds/components/ai-mark.md): 생성형 AI가 만든 결과임을 알리는 표시다. AI가 만든 모든 결과에 붙인다.
- [DdayChip](https://developers.moring.co/tds/components/dday-chip.md): 시험까지 남은 날을 "D-24"처럼 보여 주는 칩이다. 7일 이내면 빨간색, 그 외는 주색이다.
---
# OptionRow
퀴즈 보기 한 줄이다. 고르기 전, 고른 뒤, 채점 뒤(정답, 오답)의 모습이 정해져 있다.
- 분류: 입력과 선택
- 가져오기: `import { OptionRow } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:46` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/option-row
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/option-row 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 객관식 문제의 보기를 보여 줄 때 쓴다. 한 문제의 보기를 모두 `OptionRow`로 통일한다.
- 시험지처럼 번호(① ② …)를 붙이려면 `index`에 1부터 번호를 넘긴다.
- 채점 뒤에는 `state`를 `correct`(내가 고른 정답), `wrong`(내가 고른 오답), `answer`(내가 안 골랐지만 정답)로 바꾼다.
### 쓰지 않는다
- 여러 개를 동시에 고르는 선택에 쓰지 않는다. 이 컴포넌트의 접근성 역할은 하나만 고르는 `radio`다. 여러 개 선택은 `ChoiceCard`를 쓴다.
- 설정 목록에 쓰지 않는다. `SettingsRow`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 기본 | 앞에 빈 동그라미 아이콘이 붙는다. |
| 번호(`index`) | 선택 전(`idle`)과 오답(`wrong`)일 때 동그라미 안에 번호가 들어간다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 최소 높이 48 | 글자가 길어지면 줄이 늘어나고 칸도 커진다. 좌우 14, 위아래 10. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| idle (기본) | `surface` 배경, 1px `line` 테두리. |
| selected | `primarySoft` 배경, 1.5px `primary` 테두리, 체크된 파란 원. |
| correct | `successSoft` 배경, 1.5px `success` 테두리, 체크된 초록 원. |
| wrong | `dangerSoft` 배경, 1.5px `danger` 테두리, `danger` 색 원. |
| answer | 내가 고르지 않은 정답. `correct`와 같은 모습. |
| 비활성 | `disabled`이면 눌러도 반응하지 않는다. 채점 뒤에 쓴다. |
## 사용 방법
### 기본
```tsx
import { OptionRow } from '@/ui/controls';
{choices.map((c, i) => (
setPicked(c.id)} />
))}
```
### 채점 뒤
```tsx
const state = c.id === answer ? (picked === c.id ? 'correct' : 'answer') : picked === c.id ? 'wrong' : 'idle';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `ReactNode` | — | 필수 | 보기의 내용. 글자(문자열)면 `body`로 그리고, 수식이나 그림을 넣으려면 요소를 넘긴다. |
| `state` | `OptionState` | — | 필수 | `"idle"`, `"selected"`, `"correct"`, `"wrong"`, `"answer"` 중 하나. |
| `onPress` | `() => void` | — | 선택 | 눌렀을 때 호출된다. |
| `disabled` | `boolean` | — | 선택 | `true`이면 눌러도 반응하지 않는다. |
| `index` | `number` | — | 선택 | 번호(1부터). 있으면 `idle`과 `wrong`일 때 동그라미 안에 번호를 그린다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 최소 높이 48, 모서리 14, 좌우 14, 위아래 10, 아이콘과 글자 사이 12 |
| 테두리 | `idle` 1px, 그 밖에는 1.5px |
| 아이콘 | 20px. 선택·정답은 채운 원에 `surface` 색 체크, 번호 원은 22px에 선 1.4 |
| 글자 | `body` (15/22 Medium) |
| 누름 | 투명도 0.9 |
## 접근성
- 역할은 `radio`이고 상태 `selected`가 전달된다.
- 정답과 오답은 색(초록, 빨강)과 아이콘(체크 원, 빈 원), 번호 색으로 함께 나타낸다. 채점 결과 글자는 `FeedbackCard`가 따로 알린다.
- 앱은 터치 화면이라 마우스 올림(hover)이 없다. 웹에서는 포커스 링(2.5px 실선, 바깥 3px)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 보기 글자 | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
| 선택된 보기 글자 | `text` | `primarySoft` | 15.05:1 통과 | 12.52:1 통과 | 15.09:1 통과 | 7.13:1 통과 | 4.5:1 |
| 정답 보기 글자 | `text` | `successSoft` | 15.74:1 통과 | 13.64:1 통과 | 15.09:1 통과 | 7.13:1 통과 | 4.5:1 |
| 오답 보기 글자 | `text` | `dangerSoft` | 15.42:1 통과 | 14.27:1 통과 | 14.99:1 통과 | 8.34:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 선택한 보기는 주색으로, 채점 뒤의 정답은 초록으로 나눠 칠한다.
- 잘못한 예: 채점 전에 선택한 보기를 정답처럼 초록으로 칠하면 맞았다고 오해한다.
## 함께 보는 컴포넌트
- [ChoiceCard](https://developers.moring.co/tds/components/choice-card.md): 카드 모양의 선택지다. 고르면 테두리가 굵어지고 체크 표시가 붙는다. 온보딩의 과목·알림 선택에 쓴다.
- [ChoiceNumber](https://developers.moring.co/tds/components/choice-number.md): 숫자가 들어간 얇은 동그라미다. 시험지 보기 번호(① ② ③ ④ ⑤)를 그린다.
- [FeedbackCard](https://developers.moring.co/tds/components/feedback-card.md): 문제를 채점한 뒤 결과("정답!", "오답")와 해설을 보여 주는 카드다.
---
# ChoiceCard
카드 모양의 선택지다. 고르면 테두리가 굵어지고 체크 표시가 붙는다. 온보딩의 과목·알림 선택에 쓴다.
- 분류: 입력과 선택
- 가져오기: `import { ChoiceCard } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:66` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/choice-card
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/choice-card 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 여러 개를 고르는 선택(과목 고르기)에는 기본값 `role="checkbox"`를 쓴다.
- 하나만 고르는 선택(알림 받기/나중에)에는 `role="radio"`를 쓰고, 체크는 `check="end"`로 줄 오른쪽에 둔다.
- 선택지마다 아이콘과 두 줄 정도의 설명을 넣을 수 있다.
### 쓰지 않는다
- 퀴즈 보기에 쓰지 않는다. `OptionRow`를 쓴다.
- 선택지가 7개를 넘으면 쓰지 않는다. `OptionSheet`로 목록을 띄운다.
- 눌러도 상태가 안 바뀌는 안내 카드에 쓰지 않는다. `Card`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| corner (기본) | 체크 배지가 카드 오른쪽 위 모서리(7, 7)에 붙는다. 타일 모양에 쓴다. |
| end | 체크 배지(22)가 줄 오른쪽 끝에 놓인다. 한 줄짜리 선택지에 쓴다. |
| role | `checkbox`(여러 개) 또는 `radio`(하나). 접근성 역할만 바뀌고 모양은 같다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 안쪽 여백 14, 모서리 16 | 크기를 바꾸는 속성이 없다. 폭과 높이는 안의 내용과 `style`이 정한다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택 전 | `surface` 배경, 1.2px `cardBorder` 테두리. |
| 선택됨 | `selected` 배경, 1.8px `primary` 테두리, 체크 배지. |
| 누름 | 투명도 0.9. |
## 사용 방법
### 여러 개 선택
```tsx
import { ChoiceCard } from '@/ui/controls';
toggle(id)} style={{ width: '48%' }}>
수학
```
### 하나만 선택
```tsx
setChoice('yes')}>
네, 알림을 받을게요
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `selected` | `boolean` | — | 필수 | 선택되었는지. |
| `onPress` | `() => void` | — | 필수 | 눌렀을 때 호출된다. |
| `children` | `ReactNode` | — | 필수 | 카드 안에 그릴 내용. |
| `style` | `StyleProp` | — | 선택 | 폭, 높이, 바깥 여백을 정하는 스타일. |
| `check` | `'corner' \| 'end'` | `'corner'` | 선택 | 체크 배지의 위치. `"corner"`(기본) 또는 `"end"`. |
| `role` | `'checkbox' \| 'radio'` | `'checkbox'` | 선택 | 접근성 역할. `"checkbox"`(기본) 또는 `"radio"`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 모서리 `radius.lg`(16), 안쪽 여백 14 |
| 선택 전 | `surface` 배경, 1.2px `cardBorder` |
| 선택됨 | `selected` 배경, 1.8px `primary` |
| 체크 배지 | corner 20px(오른쪽 위 7·7), end 22px, `primary` 원, 흰 체크 12px 선 3.2 |
## 접근성
- 역할은 `checkbox` 또는 `radio`, 상태는 `checked`(checkbox) 또는 `selected`(radio)다.
- 선택은 테두리 굵기, 배경색, 체크 배지로 함께 나타내므로 색만으로 구분하지 않는다.
- 앱은 터치 화면이라 마우스 올림(hover)이 없다. 웹에서는 포커스 링(2.5px 실선, 바깥 3px)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 체크 배지 아이콘 | `onPrimary` | `primary` | 3.93:1 통과 | 3.31:1 통과 | 4.60:1 통과 | 7.25:1 통과 | 3:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 하나만 고르는 선택에는 `radio`로 지정한다.
- 잘못한 예: 선택했는지 테두리만 바뀌고 체크 배지가 없으면 색을 구분하기 어렵다. `ChoiceCard`를 직접 흉내 내지 않는다.
## 함께 보는 컴포넌트
- [OptionRow](https://developers.moring.co/tds/components/option-row.md): 퀴즈 보기 한 줄이다. 고르기 전, 고른 뒤, 채점 뒤(정답, 오답)의 모습이 정해져 있다.
- [RadioMark](https://developers.moring.co/tds/components/radio-mark.md): 선택 목록 행의 끝에 놓는 동그란 라디오 표시다. 시트의 선택 목록에서 쓴다.
- [OptionSheet](https://developers.moring.co/tds/components/option-sheet.md): 시트 안에서 목록 중 하나를 고르는 컴포넌트다. 행마다 라디오 표시가 있고, 고르면 바로 닫힌다.
---
# ChoiceNumber
숫자가 들어간 얇은 동그라미다. 시험지 보기 번호(① ② ③ ④ ⑤)를 그린다.
- 분류: 입력과 선택
- 가져오기: `import { ChoiceNumber } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:88` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/choice-number
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/choice-number 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 시험지 모양의 보기 앞에 번호를 붙일 때 쓴다. `OptionRow`가 `index`를 받으면 자동으로 쓴다.
- 번호 색은 `tone`으로 바꾼다. 오답이면 `danger`.
### 쓰지 않는다
- 글자가 들어가는 표지에 쓰지 않는다. 숫자 한 자리를 가정한 크기다. 글자는 `Chip`을 쓴다.
- 두 자리 숫자에 쓰지 않는다. 동그라미 안에서 숫자가 넘친다.
## 종류
종류가 없다. `tone`으로 색만 바꾼다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 지름 22 | `size`로 바꾼다. 숫자 크기는 지름 × 0.56, 선 두께는 1.4다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | `textSecondary` 선과 숫자. |
| 오답 | `danger` 색. |
## 사용 방법
### 기본
```tsx
import { ChoiceNumber } from '@/ui/controls';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `n` | `number` | — | 필수 | 그릴 숫자(한 자리). |
| `tone` | `string` | `color.textSecondary` | 선택 | 선과 숫자의 색. 기본 `color.textSecondary`. |
| `size` | `number` | `22` | 선택 | 지름(px). 기본 22. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 지름 22, 선 1.4px |
| 숫자 | caption 계열 Bold, 크기 = 지름 × 0.56 |
## 접근성
- 눌리지 않는 표지다. 보기 전체를 `OptionRow`가 하나의 라디오로 묶는다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 번호 (12px 굵게) | `textSecondary` | `surface` | 4.23:1 미달 | 6.95:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 한 자리 번호에 쓴다.
- 잘못한 예: 두 자리 숫자는 동그라미를 넘친다.
## 함께 보는 컴포넌트
- [OptionRow](https://developers.moring.co/tds/components/option-row.md): 퀴즈 보기 한 줄이다. 고르기 전, 고른 뒤, 채점 뒤(정답, 오답)의 모습이 정해져 있다.
---
# FilterPill
"과목 전체 ▾"처럼 이름과 지금 값을 보여 주고, 누르면 선택 목록을 여는 알약 버튼이다.
- 분류: 입력과 선택
- 가져오기: `import { FilterPill } from '@/ui/FilterSheet'`
- 코드 위치: `apps/mobile/src/ui/FilterSheet.tsx:12` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/filter-pill
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/filter-pill 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 목록 위의 필터 조건(과목, 정렬, 기간)을 보여 주고 바꿀 때 쓴다.
- 누르면 `OptionSheet`를 열어 값을 고르게 한다.
- "전체"가 아닌 값이 골라졌으면 `active`를 켜서 주색으로 표시한다.
### 쓰지 않는다
- 값을 바로 바꾸는 토글에 쓰지 않는다. 누르면 목록이 열린다는 약속이다. 켜고 끄기는 `SettingsSwitch`.
- 필터 조건이 하나뿐일 때는 쓰지 않는다. 목록 제목 옆의 `TextButton`이 간단하다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 기본 (`active=false`) | `surface` 배경, `line` 테두리. |
| 켜짐 (`active=true`) | `selected` 배경, `primaryLine` 테두리, 글자와 화살표 `primary`. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 38, 좌우 14 | `shrink`(기본 `true`)면 줄이 모자랄 때 값 글자를 줄여서 맞춘다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
| 켜짐 | 기본 모습이다. |
| 비활성 | 투명도 0.4. 눌러도 반응하지 않는다. |
| 누름 | 투명도 0.8. 누르는 순간 선택 진동. |
## 사용 방법
### 필터와 선택 목록
```tsx
import { FilterPill, OptionSheet } from '@/ui/FilterSheet';
setOpen(true)} />
setOpen(false)}
/>
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 필터 이름(작은 글자). |
| `value` | `string` | — | 필수 | 지금 고른 값. "전체"처럼 기본 값도 글자로 쓴다. |
| `active` | `boolean` | — | 필수 | "전체"가 아닌 값이 골라졌는지. `true`면 주색으로 표시한다. |
| `onPress` | `() => void` | — | 필수 | 눌렀을 때 호출된다. 보통 `OptionSheet`를 연다. |
| `disabled` | `boolean` | — | 선택 | `true`이면 투명도 0.4로 바꾸고 누를 수 없게 한다. |
| `shrink` | `boolean` | `true` | 선택 | `true`(기본)면 공간이 모자랄 때 줄어든다. `false`면 줄어들지 않는다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 높이 38, 좌우 14, pill, 1px 테두리, 요소 사이 6 |
| 글자 | 이름 caption SemiBold(11.5), 값 `smallStrong`(13) |
| 화살표 | ChevronDown 15px, 선 2.3 |
## 접근성
- 역할은 `button`, 이름은 "{label} {value}"(예: "과목 수학")이다.
- 켜짐은 색과 값 글자로 함께 나타낸다.
- 앱은 터치 화면이라 마우스 올림(hover)이 없다. 웹에서는 포커스 링(2.5px 실선, 바깥 3px)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 기본 값 글자 (13px 굵게) | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
| 켜진 글자 (13px 굵게) | `primary` | `selected` | 3.69:1 미달 | 4.73:1 통과 | 4.37:1 미달 | 5.80:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 고른 값을 같이 보여 준다. 눌러 보지 않아도 어떤 필터인지 안다.
- 잘못한 예: 값이 없이 이름만 있으면 지금 어떤 조건인지 알 수 없다.
## 함께 보는 컴포넌트
- [OptionSheet](https://developers.moring.co/tds/components/option-sheet.md): 시트 안에서 목록 중 하나를 고르는 컴포넌트다. 행마다 라디오 표시가 있고, 고르면 바로 닫힌다.
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
- [PillTabs](https://developers.moring.co/tds/components/pill-tabs.md): 알약 모양의 탭 모음이다. 하나를 고르면 그 칸이 짙은 색(네이비, 다크 모드는 주색)으로 채워진다.
---
# SettingsSwitch
켜고 끄는 스위치다. 설정 행의 오른쪽에 놓는다.
- 분류: 입력과 선택
- 가져오기: `import { SettingsSwitch } from '@/ui/SettingsList'`
- 코드 위치: `apps/mobile/src/ui/SettingsList.tsx:98` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/settings-switch
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/settings-switch 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 설정을 켜고 끌 때 쓴다. 누르는 즉시 값이 바뀌고 따로 저장 버튼이 없을 때 쓴다.
- `SettingsRow`의 `right`에 넣어 쓴다.
- `label`에 무엇을 켜고 끄는지 쓴다.
### 쓰지 않는다
- 저장 버튼이 필요한 양식에는 쓰지 않는다. `ChoiceCard`나 체크 항목을 쓴다.
- 둘 중 하나를 고르는 데 쓰지 않는다("라이트/다크"). `PillTabs`나 `OptionSheet`를 쓴다.
## 종류
종류가 없다.
## 크기
크기를 정하는 속성이 없다. 운영체제의 기본 스위치 크기를 따른다. 이 문서의 미리보기는 51 × 31이다.
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 꺼짐 | 트랙이 `lineStrong`, 손잡이는 흰색. |
| 켜짐 | 트랙이 `primary`. |
| 비활성 | `disabled`이면 누를 수 없다. |
## 사용 방법
### 설정 행 안에서
```tsx
import { SettingsRow, SettingsSwitch } from '@/ui/SettingsList';
} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `value` | `boolean` | — | 필수 | 켜져 있는지. |
| `onChange` | `(v: boolean) => void` | — | 필수 | 눌렀을 때 새 값(`true`/`false`)과 함께 호출된다. |
| `label` | `string` | — | 필수 | 화면 읽기 프로그램이 읽는 이름. |
| `disabled` | `boolean` | — | 선택 | `true`이면 누를 수 없다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 색 | 꺼짐 트랙 `lineStrong`, 켜짐 트랙 `primary`, 손잡이 `white` |
| 구현 | React Native `Switch`를 쓰므로 모양은 운영체제가 정한다 |
## 접근성
- 이름은 `label`이다. 상태(켜짐/꺼짐)는 운영체제가 전달한다.
- 켜짐은 트랙 색과 손잡이 위치로 함께 나타낸다.
- 눌리는 영역은 운영체제 스위치 크기를 따른다. 스위치만 누르게 하지 말고 행 전체에도 이름을 둔다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 켜진 트랙과 손잡이 (비글자) | `white` | `primary` | 3.93:1 통과 | 3.31:1 통과 | 4.60:1 통과 | 1.98:1 미달 | 3:1 |
## 잘한 예와 잘못한 예
- 잘한 예: `label`이 행 제목과 같다.
- 잘못한 예: `label` 없이 쓰지 않는다. 읽기 프로그램이 "스위치"라고만 읽는다.
## 함께 보는 컴포넌트
- [SettingsGroup](https://developers.moring.co/tds/components/settings-list.md): 설정 화면의 목록이다. 작은 구역 이름, 행이 든 흰 패널, 아래 안내 글로 이루어진다.
- [PillTabs](https://developers.moring.co/tds/components/pill-tabs.md): 알약 모양의 탭 모음이다. 하나를 고르면 그 칸이 짙은 색(네이비, 다크 모드는 주색)으로 채워진다.
---
# RangeSlider
손잡이 두 개로 정수 범위(시작~끝)를 고르는 슬라이더다. PDF 페이지 범위를 고를 때 쓴다.
- 분류: 입력과 선택
- 가져오기: `import { RangeSlider } from '@/ui/RangeSlider'`
- 코드 위치: `apps/mobile/src/ui/RangeSlider.tsx:13` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/range-slider
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/range-slider 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 1부터 `max`까지의 정수 범위를 고를 때 쓴다. 예: PDF에서 읽을 페이지 범위.
- 범위 길이를 제한하려면 `maxSpan`을 넘긴다.
### 쓰지 않는다
- 값 하나를 고를 때 쓰지 않는다. 손잡이가 둘이다.
- 소수나 큰 숫자(수백 단계 이상)에 쓰지 않는다. 손으로 정확히 맞추기 어렵다. 입력 칸을 쓴다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 전체 높이 36, 손잡이 26, 레일 6 | 폭은 부모를 채운다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 레일은 `track`, 고른 구간은 `primary`, 손잡이는 `surface` 원에 6px `primary` 테두리와 `shadow(1)`. |
| 끌기 | 손잡이를 끌면 한 칸씩 움직이고 칸이 바뀔 때마다 선택 진동이 난다. |
## 사용 방법
### 기본
```tsx
import { RangeSlider } from '@/ui/RangeSlider';
`${v}쪽`} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `max` | `number` | — | 필수 | 고를 수 있는 가장 큰 값. 가장 작은 값은 항상 1. |
| `value` | `[number, number]` | — | 필수 | `[시작, 끝]`. |
| `onChange` | `(v: [number, number]) => void` | — | 필수 | 손잡이가 움직일 때 새 `[시작, 끝]`과 함께 호출된다. |
| `maxSpan` | `number` | — | 선택 | 범위의 최대 길이(칸 수). 넘기면 이보다 넓게 벌릴 수 없다. |
| `label` | `(v: number) => string` | — | 필수 | 값을 읽어 주는 함수. 접근성 이름("시작 3쪽")에 쓰인다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 레일 | 높이 6, 모서리 3, `track` |
| 채움 | `primary` |
| 손잡이 | 26 × 26, 원, `surface` + 6px `primary` 테두리, `shadow(1)`, 누르는 영역 사방 10~14 추가 |
## 접근성
- 손잡이는 `adjustable` 역할이다. 화면 읽기 프로그램에서 위아래로 쓸어 한 칸씩 바꿀 수 있다(`increment`, `decrement`).
- 이름은 "시작 {label}", "끝 {label}"이다.
- 웹에서는 `` 두 개로 만들어 방향키로 움직일 수 있다.
## 잘한 예와 잘못한 예
- 잘한 예: `label`로 단위(쪽, 일)를 붙여 값을 읽어 준다.
- 잘못한 예: 손잡이만 있고 값 글자가 없으면 지금 몇 쪽인지 알 수 없다. 값을 옆에 글자로 함께 보여 준다.
## 함께 보는 컴포넌트
- [Calendar](https://developers.moring.co/tds/components/date-calendar.md): 한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
- [TimeSheet](https://developers.moring.co/tds/components/time-sheet.md): 아래에서 올라오는 시트에서 시각을 고른다. 오전/오후, 시, 분(5분 단위) 세 줄 바퀴를 돌려서 고른다.
---
# Calendar
한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
- 분류: 입력과 선택
- 가져오기: `import { Calendar } from '@/ui/Calendar'`
- 코드 위치: `apps/mobile/src/ui/Calendar.tsx:16` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/date-calendar
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/date-calendar 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 시험일처럼 날짜를 여러 개 고를 때 쓴다. 이어진 날짜는 띠로 이어 보인다.
- 지난 날짜를 막으려면 `minDate`를 넘긴다.
### 쓰지 않는다
- 날짜 하나를 고르는 데 쓰지 않는다(Calendar 앱에서는 `pickDate` 시트를 쓴다).
- 날짜 사이의 범위를 끌어서 고르는 데 쓰지 않는다. 날짜를 하나씩 누른다.
- 주간 일정 표시에 쓰지 않는다. `WeekStrip`이나 Calendar 앱의 `MonthView`를 쓴다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 날짜 칸 높이 44, 날짜 원 36 | 폭은 부모를 채우고 7칸으로 나눈다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택 안 됨 | 글자 `text`. |
| 선택됨 | `primarySoft` 원, `primary` 굵은 글자. 이어진 날짜는 `primarySoft` 띠로 연결. |
| 첫 선택일 | 가장 이른 선택 날짜는 `primary` 채운 원에 흰 글자. |
| 오늘 | `primary` 굵은 글자. |
| 비활성 | `minDate`보다 이르거나 이번 달이 아닌 칸. `textTertiary`, 누를 수 없다. |
## 사용 방법
### 기본
```tsx
import { Calendar } from '@/ui/Calendar';
const [days, setDays] = useState([]);
setDays((cur) => (cur.includes(d) ? cur.filter((x) => x !== d) : [...cur, d]))}
minDate={today}
/>
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `selected` | `string[]` | — | 필수 | 선택된 날짜 목록. `"YYYY-MM-DD"` 문자열. |
| `onToggle` | `(date: string) => void` | — | 필수 | 날짜를 눌렀을 때 그 날짜와 함께 호출된다. 선택과 해제를 부모가 정한다. |
| `minDate` | `string` | — | 선택 | 이 날짜보다 이른 날짜와 그 이전 달 이동을 막는다. |
| `initialMonth` | `string` | — | 선택 | 처음 보여 줄 달의 아무 날짜. 없으면 가장 이른 선택 날짜, 그것도 없으면 오늘의 달. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 머리 | 이전 달·다음 달 버튼 36 × 36, 가운데 "YYYY년 M월"(`headline`) |
| 요일 줄 | 높이 34, `small`, `textTertiary`. 일요일부터 시작 |
| 날짜 칸 | 높이 44, 날짜 원 36, 글자 15/20 Medium(선택·오늘은 Bold) |
| 띠 | 위아래 4씩 안쪽, `primarySoft` |
| 줄 수 | 5줄 또는 6줄(달에 따라) |
## 접근성
- 각 날짜는 `button`이고 선택 상태가 전달된다. 비활성 날짜는 `disabled`.
- 이전·다음 달 버튼의 이름은 "이전 달", "다음 달"이다.
- 선택은 채움 색과 굵은 글자로 나타낸다.
- 앱은 터치 화면이라 마우스 올림(hover)이 없다. 웹에서는 포커스 링(2.5px 실선, 바깥 3px)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 선택된 날짜 글자 | `primary` | `primarySoft` | 3.45:1 미달 | 4.32:1 미달 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
| 첫 선택일 글자 | `onPrimary` | `primary` | 3.93:1 미달 | 3.31:1 미달 | 4.60:1 통과 | 7.25:1 통과 | 4.5:1 |
| 비활성 날짜 | `textTertiary` | `surface` | 2.53:1 미달 | 3.50:1 미달 | 2.97:1 미달 | 4.45:1 미달 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: `minDate`로 지난 날짜를 막는다.
- 잘못한 예: 선택한 날짜를 알 수 없게 아무 표시도 없으면 안 된다.
## 함께 보는 컴포넌트
- [TimeSheet](https://developers.moring.co/tds/components/time-sheet.md): 아래에서 올라오는 시트에서 시각을 고른다. 오전/오후, 시, 분(5분 단위) 세 줄 바퀴를 돌려서 고른다.
- [WeekStrip](https://developers.moring.co/tds/components/week-strip.md): 월요일부터 일요일까지 7일을 한 줄로 보여 주는 주간 표시다. 공부한 날에는 점이 찍힌다.
- [RangeSlider](https://developers.moring.co/tds/components/range-slider.md): 손잡이 두 개로 정수 범위(시작~끝)를 고르는 슬라이더다. PDF 페이지 범위를 고를 때 쓴다.
---
# TimeSheet
아래에서 올라오는 시트에서 시각을 고른다. 오전/오후, 시, 분(5분 단위) 세 줄 바퀴를 돌려서 고른다.
- 분류: 입력과 선택
- 가져오기: `import { TimeSheet } from '@/ui/TimeSheet'`
- 코드 위치: `apps/mobile/src/ui/TimeSheet.tsx:58` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/time-sheet
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/time-sheet 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- "HH:MM" 형식의 시각 하나를 고를 때 쓴다. 분은 5분 단위다.
- 시트 제목(`title`)에 무엇의 시각인지 쓴다. 예: "알림 시각".
### 쓰지 않는다
- 1분 단위로 정확히 맞춰야 할 때 쓰지 않는다. 5분 단위로만 고른다.
- 시각 범위(시작~끝)를 한 번에 고르는 데 쓰지 않는다. 시트를 두 번 연다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 바퀴 높이 220 (한 줄 44 × 5줄) | 바퀴 폭은 오전/오후 72, 시 80, 분 80이다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 열림 | `Sheet`가 240ms 동안 올라온다. 가운데 줄에 `segmentTrack` 띠가 깔린다. |
| 바퀴를 돌리는 중 | 가운데 줄의 글자는 `headline`·`text`, 위아래는 `textTertiary`에 투명도가 줄어든다. 칸이 바뀔 때마다 선택 진동. |
| 저장 | 아래 "저장" 버튼(`PrimaryButton`, 화살표 없음)을 누르면 `onSave("HH:MM")`을 부르고 닫힌다. |
## 사용 방법
### 기본
```tsx
import { TimeSheet } from '@/ui/TimeSheet';
setOpen(false)} onSave={setTime} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `visible` | `boolean` | — | 필수 | 시트를 열었는지. |
| `value` | `string` | — | 필수 | 지금 값. `"HH:MM"`(24시간). 분은 가장 가까운 5분으로 맞춘다. |
| `title` | `string` | — | 필수 | 시트 제목. |
| `onClose` | `() => void` | — | 필수 | 닫을 때 호출된다. |
| `onSave` | `(value: string) => void` | — | 필수 | "저장"을 눌렀을 때 새 `"HH:MM"`과 함께 호출된다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 한 줄 | 높이 44, 5줄 표시, 가운데가 선택 |
| 가운데 띠 | `segmentTrack`, 모서리 `radius.md`(12) |
| 글자 | 가운데 `headline`(`text`), 나머지 `body`(`textTertiary`), 위아래로 갈수록 투명도 0.7 → 0.35 |
| 단위 | 오전/오후 2칸, 시 12칸(12, 1~11), 분 12칸(00~55) |
## 접근성
- 각 바퀴는 `adjustable` 역할이고 "{이름} {값}"을 읽어 준다. 위아래로 쓸면 한 칸씩 바뀐다.
- 웹 미리보기의 바퀴는 키보드로 포커스를 받아 방향키로 스크롤할 수 있다.
## 잘한 예와 잘못한 예
- 잘한 예: 시트 제목에 무엇의 시각인지 쓴다.
- 잘못한 예: 제목 없이 숫자 바퀴만 있으면 무엇을 고르는지 알 수 없다.
## 함께 보는 컴포넌트
- [Sheet](https://developers.moring.co/tds/components/sheet.md): 화면 아래에서 올라오는 시트다. 위쪽 두 모서리가 둥글고, 손잡이와 제목 줄, 닫기 버튼이 있다.
- [Calendar](https://developers.moring.co/tds/components/date-calendar.md): 한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
- [RangeSlider](https://developers.moring.co/tds/components/range-slider.md): 손잡이 두 개로 정수 범위(시작~끝)를 고르는 슬라이더다. PDF 페이지 범위를 고를 때 쓴다.
---
# RadioMark
선택 목록 행의 끝에 놓는 동그란 라디오 표시다. 시트의 선택 목록에서 쓴다.
- 분류: 입력과 선택
- 가져오기: `import { RadioMark } from '@/ui/surface'`
- 코드 위치: `apps/mobile/src/ui/surface.tsx:103` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/radio-mark
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/radio-mark 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- `WellRow`의 `right`에 넣어 "이 행이 선택됨"을 나타낼 때 쓴다. `OptionSheet`가 이렇게 쓴다.
### 쓰지 않는다
- 혼자 눌리는 컨트롤로 쓰지 않는다. 눌림은 행이 맡는다.
- 여러 개 선택(체크)에 쓰지 않는다. 동그란 모양은 "하나만"이라는 약속이다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 지름 22 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 꺼짐 | 2px `lineStrong` 테두리의 빈 원. |
| 켜짐 | 테두리가 `primary`, 가운데 지름 10의 `primary` 점. |
## 사용 방법
### 행 끝에 놓기
```tsx
import { RadioMark, WellRow } from '@/ui/surface';
setSort('new')} right={} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `on` | `boolean` | — | 필수 | 선택되었는지. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 지름 22, 테두리 2px, 안쪽 점 10 |
| 색 | 꺼짐 `lineStrong`, 켜짐 `primary` |
## 접근성
- 혼자서는 읽히는 역할이 없다. 부모 행(`WellRow`)의 `selected` 상태가 선택을 알린다.
## 잘한 예와 잘못한 예
- 잘한 예: 행 전체가 눌리고, 끝의 표시가 선택을 보여 준다.
- 잘못한 예: 체크 선택에 동그라미를 쓰지 않는다.
## 함께 보는 컴포넌트
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [OptionSheet](https://developers.moring.co/tds/components/option-sheet.md): 시트 안에서 목록 중 하나를 고르는 컴포넌트다. 행마다 라디오 표시가 있고, 고르면 바로 닫힌다.
- [ChoiceCard](https://developers.moring.co/tds/components/choice-card.md): 카드 모양의 선택지다. 고르면 테두리가 굵어지고 체크 표시가 붙는다. 온보딩의 과목·알림 선택에 쓴다.
---
# TabBar
Mordy 앱 맨 아래의 탭 줄이다. 홈 · 과목 · (+) · 커뮤니티 · 더보기. 가운데 +는 탭이 아니라 자료 추가 행동이다.
- 분류: 탐색
- 가져오기: `import { TabBar } from '@/ui/TabBar'`
- 코드 위치: `apps/mobile/src/ui/TabBar.tsx:20` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/tab-bar
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/tab-bar 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 앱의 가장 큰 화면 이동(홈, 과목, 커뮤니티, 더보기)에 쓴다. 화면 맨 아래에 한 번만 둔다.
- 가운데 둥근 버튼은 이동이 아니라 "학습 자료 추가"(카메라 열기) 행동이다.
### 쓰지 않는다
- 화면 안의 내용 전환에 쓰지 않는다. `PillTabs`를 쓴다.
- 탭을 5개 넘게 두지 않는다. 이 컴포넌트는 이름 있는 탭 4개와 가운데 버튼 하나를 가정한다.
- 퀴즈, 촬영처럼 한 가지에 집중해야 하는 화면에서는 숨긴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| Mordy | 홈 · 과목 · (+) · 커뮤니티 · 더보기. `@/ui/TabBar`. |
| Calendar 앱 | 오늘 · 캘린더 · (+) · 할 일 · 더보기. 같은 모양이고 가운데 +는 빠른 추가다. `calendar-tab-bar` 문서를 본다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 탭 높이 50, 가운데 버튼 52 × 52 | 위쪽 여백 6, 아래는 안전 영역 또는 6 중 큰 값. 줄 전체 높이는 약 62 + 안전 영역. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택된 탭 | 아이콘과 이름이 `primary`, 아이콘 선 2.3, 이름 Bold. |
| 선택 안 된 탭 | 아이콘과 이름이 `tabInactive`, 아이콘 선 1.9, 이름 Medium. |
| 가운데 버튼 누름 | 0.95배로 줄어든다. |
## 사용 방법
### 탭 네비게이터에 연결
```tsx
import { TabBar } from '@/ui/TabBar';
}>
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `state` | `(React Native)` | — | 선택 | Expo Router 탭 네비게이터의 상태(현재 탭 위치). |
| `navigation` | `(React Native)` | — | 선택 | 탭 이동을 요청하는 객체. |
나머지 속성은 `BottomTabBarProps`를 그대로 받는다.
`BottomTabBarProps`를 받는다. 직접 호출하지 않고 탭 네비게이터의 `tabBar`에 연결한다. 탭 이름과 아이콘은 컴포넌트 안에 정해져 있다(`index` 홈, `subjects` 과목, `community` 커뮤니티, `more` 더보기).
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 배경 | `surface`, 위쪽 헤어라인 `line` |
| 탭 | 높이 50, 아이콘 22, 이름 10.5px, 아이콘과 이름 사이 2 |
| 가운데 버튼 | 52 × 52 원, `gradient` 대각선, Plus 26 선 2.6, 그림자 `shadow(2)`에 `primaryStrong` 색 |
| 선택 | `primary`. 선택 안 됨 `tabInactive` |
## 접근성
- 줄의 각 탭은 `tab` 역할이고 `selected` 상태가 전달된다. 이름은 "홈", "과목" 등이다.
- 가운데 버튼의 이름은 "학습 자료 추가"이고 `button` 역할이다(탭이 아니다).
- 아이콘과 글자 이름을 항상 함께 보인다.
- 선택은 색, 아이콘 선 두께, 이름 굵기로 함께 나타낸다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 선택 안 된 탭 이름 (10.5px) | `tabInactive` | `surface` | 4.64:1 통과 | 4.75:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
| 선택된 탭 이름 (10.5px 굵게) | `primary` | `surface` | 3.93:1 미달 | 5.26:1 통과 | 4.50:1 통과 | 7.17:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 아이콘과 이름을 함께 보여 준다.
- 잘못한 예: 이름 없이 아이콘만 두면 처음 쓰는 사용자가 무엇인지 알 수 없다.
## 함께 보는 컴포넌트
- [TabBar (Calendar)](https://developers.moring.co/tds/components/calendar-tab-bar.md): Mordy Calendar 앱의 탭 줄이다. 오늘 · 캘린더 · (+) · 할 일 · 더보기. Mordy 앱의 `TabBar`와 같은 모양이고 가운데 +는 빠른 추가를 연다.
- [PillTabs](https://developers.moring.co/tds/components/pill-tabs.md): 알약 모양의 탭 모음이다. 하나를 고르면 그 칸이 짙은 색(네이비, 다크 모드는 주색)으로 채워진다.
- [Screen](https://developers.moring.co/tds/components/screen.md): 모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
---
# PageDots
지금이 몇 번째 단계인지 알려 주는 작은 점 줄이다. 온보딩 단계 표시에 쓴다.
- 분류: 탐색
- 가져오기: `import { PageDots } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:96` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/page-dots
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/page-dots 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 3~6단계로 나뉜 온보딩이나 안내에서 지금 위치를 알릴 때 쓴다.
### 쓰지 않는다
- 눌러서 이동하는 컨트롤로 쓰지 않는다. 눌리지 않는다.
- 7단계가 넘는 흐름에 쓰지 않는다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 점 6, 현재 점 8, 간격 6 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 지금 단계의 점은 8px `primary`, 나머지는 6px `lineStrong`. |
## 사용 방법
### 기본
```tsx
import { PageDots } from '@/ui/controls';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `count` | `number` | — | 필수 | 전체 단계 수. |
| `index` | `number` | — | 필수 | 지금 단계(0부터). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 점 | 6px 원, 현재 점 8px |
| 색 | 현재 `primary`, 나머지 `lineStrong` |
| 간격 | 6 |
## 접근성
- 점 줄 전체에 "{전체}단계 중 {현재}단계"라는 이름이 붙는다. 점 하나하나는 읽지 않는다.
## 잘한 예와 잘못한 예
- 잘한 예: 단계 수가 적고 이동 버튼이 따로 있다.
- 잘못한 예: 점이 너무 많으면 어디인지 알 수 없다.
## 함께 보는 컴포넌트
- [CircleButton](https://developers.moring.co/tds/components/circle-button.md): 화살표가 들어 있는 둥근 주색 버튼이다. 온보딩에서 다음 단계로 넘어갈 때 쓴다.
- [WeekStrip](https://developers.moring.co/tds/components/week-strip.md): 월요일부터 일요일까지 7일을 한 줄로 보여 주는 주간 표시다. 공부한 날에는 점이 찍힌다.
---
# WeekStrip
월요일부터 일요일까지 7일을 한 줄로 보여 주는 주간 표시다. 공부한 날에는 점이 찍힌다.
- 분류: 탐색
- 가져오기: `import { WeekStrip } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:108` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/week-strip
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/week-strip 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 홈에서 이번 주 공부 기록을 가볍게 보여 줄 때 쓴다.
### 쓰지 않는다
- 날짜를 골라야 할 때 쓰지 않는다. 눌리지 않는다. 날짜를 고르게 하려면 Calendar 앱의 `DayStrip`이나 `Calendar`를 쓴다.
- 일정이 있는 날을 표시하는 데 쓰지 않는다. `studied`(공부함)만 나타낸다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 요일 칸 폭 38, 날짜 원 34 | 7칸이 좌우로 고르게 벌어진다(`space-between`). |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 오늘 | 요일 글자가 `primary` Bold, 날짜 원이 `primaryStrong`으로 채워지고 흰 글자. |
| 공부한 날 | 날짜 아래에 4px `primary` 점. 오늘은 점 대신 채운 원으로 나타낸다. |
| 그 외 | 요일 `textSecondary`, 날짜 `text`. |
## 사용 방법
### 기본
```tsx
import { WeekStrip } from '@/ui/controls';
({ date: d.date, isToday: d.date === today, studied: d.seconds > 0 }))} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `days` | `{ date: string; isToday: boolean; studied: boolean }[]` | — | 필수 | 7일의 목록(월~일). 각 항목은 `{ date: "YYYY-MM-DD", isToday, studied }`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 요일 칸 | 폭 38, 요일 글자 `small`, 요일과 날짜 사이 6 |
| 날짜 원 | 34 × 34, 글자 `bodyStrong` |
| 점 | 4px 원, 날짜 원 아래(-2 겹침) |
## 접근성
- 각 칸에 "월요일 5일, 공부함, 오늘"처럼 날짜와 상태를 모두 담은 이름이 붙는다.
- 공부함은 점으로, 오늘은 채운 원으로 나타낸다. 색만으로 구분하지 않는다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 오늘 날짜 | `onPrimaryStrong` | `primaryStrong` | 4.43:1 미달 | 3.71:1 미달 | 4.60:1 통과 | 5.48:1 통과 | 4.5:1 |
| 요일 글자 (13px) | `textSecondary` | `bg` | 4.13:1 미달 | 7.65:1 통과 | 4.69:1 통과 | 7.96:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 7일을 한 줄에 보여 주고 공부한 날은 점으로 알린다.
- 잘못한 예: 눌러서 날짜를 고르는 용도로 쓰지 않는다. 눌러도 아무 일도 없다.
## 함께 보는 컴포넌트
- [DayStrip](https://developers.moring.co/tds/components/day-strip.md): 한 주 7일을 한 줄로 보여 주고 날짜를 골라 이동하는 주간 줄이다. 좌우로 밀면 앞뒤 주로 이동한다. 일정이 있는 날에는 점이 찍힌다.
- [Calendar](https://developers.moring.co/tds/components/date-calendar.md): 한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
- [CalendarHeat](https://developers.moring.co/tds/components/calendar-heat.md): 한 달 달력에서 날마다 공부한 시간을 색의 진하기(5단계)로 보여 주는 차트다.
---
# Timeline
세로 선으로 단계를 이어 보여 주는 타임라인이다. 시험 준비 단계(개념 → 문제 → 모의고사)에 쓴다.
- 분류: 탐색
- 가져오기: `import { Timeline } from '@/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/controls.tsx:126` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/timeline
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/timeline 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 순서가 있는 2~5개 단계와 지금 위치를 보여 줄 때 쓴다.
- 단계마다 점 색을 다르게 줄 수 있고, 선은 앞뒤 점의 색이 섞이며 이어진다.
### 쓰지 않는다
- 순서가 없는 목록에 쓰지 않는다. 일반 목록은 `WellRow`.
- 단계마다 긴 문단을 넣지 않는다. 라벨, 제목, 한 줄 설명만 쓴다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 점 15, 점 둘레 24, 선 폭 2 | 단계 사이 간격 18. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 지금 단계 | 점 둘레에 점 색 18% 투명도의 원이 깔리고 라벨이 점 색이다. |
| 지난 단계 | `done`이면 투명도 0.5. |
| 다음 단계 | 기본 모습. 라벨 `slate`. |
## 사용 방법
### 기본
```tsx
import { Timeline } from '@/ui/controls';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `items` | `{ key: string; label: string; title: string; detail: string; dot: string; active?: boolean; done?: boolean }[]` | — | 필수 | 단계 목록. 각 항목: `key`, `label`(작은 이름), `title`(제목), `detail`(설명), `dot`(점 색), `active`(지금 단계), `done`(지난 단계). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 점 | 15px 원 + 가운데 5px 흰 점, 둘레 24px(지금 단계는 18% 색) |
| 선 | 폭 2, 앞 점 색 55% → 뒤 점 색 55% 그라데이션 |
| 글자 | 라벨 `small` SemiBold, 제목 `headline` 16/23, 설명 `small` `textSecondary` |
## 접근성
- 각 단계에 "{라벨}, {제목}, {설명}, 지금 단계"라는 이름이 붙는다.
- 지금 단계는 색과 "지금 단계" 글자로 함께 나타낸다.
## 잘한 예와 잘못한 예
- 잘한 예: 지금 단계의 라벨에 글자("지금 단계")를 쓴다.
- 잘못한 예: 모든 단계를 같은 색으로 하고 라벨도 없으면 지금 어디인지 알 수 없다.
## 함께 보는 컴포넌트
- [WeekStrip](https://developers.moring.co/tds/components/week-strip.md): 월요일부터 일요일까지 7일을 한 줄로 보여 주는 주간 표시다. 공부한 날에는 점이 찍힌다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
---
# BackHeader
화면 위쪽의 뒤로 가기 줄이다. 왼쪽에 뒤로 화살표, 가운데에 제목(선택), 오른쪽에 행동 자리가 있다.
- 분류: 탐색
- 가져오기: `import { BackHeader } from '@/ui/layout'`
- 코드 위치: `apps/mobile/src/ui/layout.tsx:67` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/back-header
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/back-header 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 상세 화면, 설정 하위 화면처럼 앞 화면으로 돌아갈 수 있는 화면 맨 위에 쓴다.
- 오른쪽 행동은 `right`에 `IconButton`이나 `TextButton`으로 넣는다.
### 쓰지 않는다
- 탭의 첫 화면에 쓰지 않는다. 첫 화면은 `TitleHeader`다.
- 가운데 제목을 한 줄이 넘게 쓰지 않는다. 한 줄로 줄이고 넘치면 …으로 줄인다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 제목 있음 | 가운데에 `headline` 제목. |
| 제목 없음 | 가운데가 비어 있다. 이미지나 사진 위에 놓을 때 쓴다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 52 | 뒤로 버튼은 40 × 40이고 왼쪽 -10만큼 당겨 아이콘이 화면 왼쪽 선(20)에 맞는다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
| 뒤로 누름 | 앞 화면이 있으면 돌아가고, 없으면 홈(`/`)으로 간다. 투명도 0.6. |
## 사용 방법
### 기본
```tsx
import { BackHeader } from '@/ui/layout';
} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `title` | `string` | — | 선택 | 가운데 제목. 없으면 가운데가 비어 있다. |
| `right` | `ReactNode` | — | 선택 | 오른쪽에 놓을 요소(행동 버튼). |
| `onBack` | `() => void` | — | 선택 | 뒤로 누를 때 실행할 함수. 없으면 앞 화면으로 돌아가고, 앞 화면이 없으면 홈으로 간다. |
| `tone` | `string` | `color.text` | 선택 | 뒤로 화살표의 색. 기본 `color.text`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 높이 | 52 |
| 뒤로 아이콘 | ChevronLeft 26px, 선 2.2, `IconButton` 40 × 40 |
| 제목 | `headline`(17/24 Bold), 가운데 정렬, 한 줄 |
| 오른쪽 자리 | 최소 폭 40, 오른쪽 정렬 |
## 접근성
- 뒤로 버튼의 이름은 "뒤로"이고 `button` 역할이다.
- 제목은 `header` 역할이다.
## 잘한 예와 잘못한 예
- 잘한 예: 제목은 짧게, 오른쪽 행동은 하나만 둔다.
- 잘못한 예: 제목이 길면 잘린다. 한 줄에 들어갈 길이로 쓴다.
## 함께 보는 컴포넌트
- [TitleHeader](https://developers.moring.co/tds/components/title-header.md): 탭의 첫 화면 위쪽에 놓는 큰 제목 줄이다. 왼쪽에 제목, 오른쪽에 행동 자리가 있다.
- [IconButton](https://developers.moring.co/tds/components/icon-button.md): 아이콘 하나만 있는 투명한 둥근 버튼이다. 헤더의 "뒤로", "검색"에 쓴다.
- [Screen](https://developers.moring.co/tds/components/screen.md): 모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
---
# TitleHeader
탭의 첫 화면 위쪽에 놓는 큰 제목 줄이다. 왼쪽에 제목, 오른쪽에 행동 자리가 있다.
- 분류: 탐색
- 가져오기: `import { TitleHeader } from '@/ui/layout'`
- 코드 위치: `apps/mobile/src/ui/layout.tsx:57` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/title-header
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/title-header 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- "내 과목", "복습 스케줄"처럼 탭의 첫 화면 맨 위에 쓴다.
- 오른쪽에는 아이콘 버튼 한두 개를 넣는다.
### 쓰지 않는다
- 뒤로 갈 수 있는 화면에 쓰지 않는다. `BackHeader`를 쓴다.
- 제목을 두 줄로 쓰지 않는다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 최소 높이 56 | 위쪽 여백 16, 아래쪽 12. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { TitleHeader } from '@/ui/layout';
} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `title` | `string` | — | 필수 | 제목. |
| `right` | `ReactNode` | — | 선택 | 오른쪽에 놓을 요소들(간격 4로 가로로 놓인다). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 높이 | 최소 56, 위 16, 아래 12 |
| 제목 | `title`(21/29 Bold), 역할 `header` |
| 오른쪽 | 가로 배치, 요소 사이 4 |
## 접근성
- 제목은 `header` 역할이다.
## 잘한 예와 잘못한 예
- 잘한 예: 탭의 첫 화면에서 제목과 행동 하나.
- 잘못한 예: 상세 화면에서는 뒤로 갈 방법이 없어진다. `BackHeader`를 쓴다.
## 함께 보는 컴포넌트
- [BackHeader](https://developers.moring.co/tds/components/back-header.md): 화면 위쪽의 뒤로 가기 줄이다. 왼쪽에 뒤로 화살표, 가운데에 제목(선택), 오른쪽에 행동 자리가 있다.
- [Screen](https://developers.moring.co/tds/components/screen.md): 모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
- [SectionTitle](https://developers.moring.co/tds/components/section-title.md): 화면 안의 한 묶음(섹션)을 시작하는 제목 줄이다. 위에 24, 아래에 12의 간격을 포함한다.
---
# Screen
모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
- 분류: 면과 목록
- 가져오기: `import { Screen } from '@/ui/layout'`
- 코드 위치: `apps/mobile/src/ui/layout.tsx:23` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/screen
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/screen 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 모든 화면의 가장 바깥에 하나 쓴다.
- 아래에 고정할 주 행동은 `footer`에 넣는다. 키보드가 올라와도 가려지지 않는다.
- 목록을 끌어내려 새로고침하려면 `onRefresh`를 넘긴다.
### 쓰지 않는다
- `Screen`을 중첩하지 않는다.
- 안쪽에 또 `ScrollView`를 두지 않는다(스크롤이 겹친다). 자체 스크롤이 필요하면 `scroll={false}`.
- 화면 안에서 직접 좌우 여백을 또 넣지 않는다. `padded`가 20을 넣는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 스크롤 (기본) | `scroll`이 `true`다. 내용이 길면 스크롤된다. |
| 고정 | `scroll={false}`. 내용이 화면에 딱 맞는 화면(퀴즈, 촬영). |
| 회색 바탕 | `background={color.canvas}`. 대시보드형 화면(시험 계획, 리포트). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 전체 | 화면 전체 | 위쪽 안전 영역은 기본으로 적용(`edges=["top"]`). 아래는 `footer`가 처리한다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 새로고침 중 | `onRefresh`가 있고 아래로 끌었을 때만 스피너가 보인다. 백그라운드 갱신에서는 보이지 않는다. |
| 키보드 열림 | 아래쪽이 키보드 높이만큼 밀려 올라간다. |
## 사용 방법
### 기본
```tsx
import { Screen, TitleHeader } from '@/ui/layout';
{/* 내용 */}
```
### 아래 고정 버튼과 새로고침
```tsx
refetch()}
footer={}
>
{/* 내용 */}
```
### 회색 바탕
```tsx
{/* … */}
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 화면 내용. |
| `scroll` | `boolean` | `true` | 선택 | `true`(기본)면 세로로 스크롤된다. |
| `edges` | `Edge[]` | `['top']` | 선택 | 안전 영역을 적용할 가장자리. 기본 `["top"]`. |
| `padded` | `boolean` | `true` | 선택 | `true`(기본)면 좌우에 `space.gutter`(20)를 둔다. |
| `footer` | `ReactNode` | — | 선택 | 아래에 고정할 요소. 위 12, 아래 16, 좌우 20의 여백과 화면 배경색이 깔린다. |
| `background` | `string` | `color.bg` | 선택 | 바탕색. 기본 `color.bg`. |
| `onRefresh` | `() => Promise \| void` | — | 선택 | 아래로 끌어 새로고침할 때 호출. 끝나면 스피너가 사라진다. |
| `contentStyle` | `StyleProp` | — | 선택 | 스크롤 내용(또는 고정 내용)에 적용할 스타일. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 좌우 여백 | `space.gutter`(20) |
| 스크롤 아래 여백 | `space.xxl`(32) |
| 아래 고정 영역 | 위 `md`(12), 아래 `lg`(16), 좌우 `gutter`(20), 배경 `bg` |
| 바탕 | `bg` (회색 바탕이 필요하면 `canvas`) |
| 키보드 | `KeyboardAvoidingView` padding 방식 |
## 접근성
- 스크롤 막대는 숨기지만 스크롤은 된다.
- 입력 중 바깥을 눌러도 키보드가 먼저 닫히지 않고 눌림이 전달된다(`keyboardShouldPersistTaps="handled"`).
## 잘한 예와 잘못한 예
- 잘한 예: 주 행동을 `footer`에 두면 스크롤해도 항상 보인다.
- 잘못한 예: 내용 맨 아래에 주 버튼을 두면 긴 화면에서는 스크롤해야 보인다.
## 함께 보는 컴포넌트
- [TitleHeader](https://developers.moring.co/tds/components/title-header.md): 탭의 첫 화면 위쪽에 놓는 큰 제목 줄이다. 왼쪽에 제목, 오른쪽에 행동 자리가 있다.
- [BackHeader](https://developers.moring.co/tds/components/back-header.md): 화면 위쪽의 뒤로 가기 줄이다. 왼쪽에 뒤로 화살표, 가운데에 제목(선택), 오른쪽에 행동 자리가 있다.
- [Panel](https://developers.moring.co/tds/components/panel.md): 회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
---
# Card
옅은 주색 틴트(`card`)를 깐 둥근 상자다. 안내, 요약, 눌러서 이동하는 카드에 쓴다.
- 분류: 면과 목록
- 가져오기: `import { Card } from '@/ui/layout'`
- 코드 위치: `apps/mobile/src/ui/layout.tsx:80` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/card
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/card 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 홈의 복습 카드, 과목 행처럼 흰 배경 위에 옅은 색으로 묶어 보여 줄 때 쓴다.
- `onPress`를 넘기면 누를 수 있는 카드가 된다.
- `tone`으로 바탕색을 바꿀 수 있다(`amberSoft` 등).
### 쓰지 않는다
- 카드 안에 카드를 넣지 않는다.
- 흰 패널이 필요한 리포트·설정에는 쓰지 않는다. `Panel`을 쓴다.
- 그림자가 필요하면 쓰지 않는다. `Card`에는 그림자가 없다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 기본 | `card` 바탕. |
| tone | `tone`으로 바탕색을 지정한다. 예: 복습 추천 카드는 `color.amberSoft`. |
| 누르는 카드 | `onPress`가 있으면 버튼으로 동작한다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 안쪽 여백 16, 모서리 20 | 크기는 내용이 정한다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
| 누름 | `onPress`가 있을 때 투명도 0.85. |
## 사용 방법
### 기본
```tsx
import { Card } from '@/ui/layout';
router.push('/review')} accessibilityLabel="오늘의 복습">
오늘의 복습
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 카드 안의 내용. |
| `style` | `StyleProp` | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. |
| `tone` | `string` | `color.card` | 선택 | 바탕색. 기본 `color.card`. |
| `onPress` | `() => void` | — | 선택 | 있으면 누를 수 있는 카드가 된다. |
| `accessibilityLabel` | `string` | — | 선택 | 누르는 카드의 이름. 안의 글자와 다르게 읽히게 할 때만 쓴다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모서리 | `radius.card`(20) |
| 안쪽 여백 | `space.lg`(16) |
| 바탕 | `card` |
| 누름 | 투명도 0.85 |
## 접근성
- `onPress`가 있으면 `button` 역할이다. 이름은 `accessibilityLabel` 또는 안의 글자.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 카드 위 본문 | `text` | `card` | 15.96:1 통과 | 14.29:1 통과 | 15.67:1 통과 | 9.43:1 통과 | 4.5:1 |
| 카드 위 보조 글자 (13px) | `textSecondary` | `card` | 3.94:1 미달 | 6.51:1 통과 | 4.74:1 통과 | 6.14:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 카드 안은 제목과 보조 한 줄로 단순하게.
- 잘못한 예: 카드 안에 카드를 넣으면 경계가 겹쳐 복잡해 보인다.
## 함께 보는 컴포넌트
- [Panel](https://developers.moring.co/tds/components/panel.md): 회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
---
# Panel
회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
- 분류: 면과 목록
- 가져오기: `import { Panel, PanelHead, PanelLink } from '@/ui/surface'`
- 코드 위치: `apps/mobile/src/ui/surface.tsx:21` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/panel
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/panel 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 리포트, 시험 계획 같은 대시보드 화면에서 내용을 묶는 흰 면으로 쓴다.
- 제목 줄 오른쪽에는 `PanelLink`("전체보기 ›")나 토글을 둔다.
- 제목 옆 설명(ⓘ)이 필요하면 `info`에 `[제목, 설명]`을 넘긴다. 누르면 대화상자가 뜬다.
### 쓰지 않는다
- 흰 배경 화면(`bg`) 위에서 구분 없이 쓰지 않는다. 패널은 `canvas` 위에서 보인다.
- 패널 안에 패널을 넣지 않는다. 안의 묶음은 `WellRow`로 나눈다.
- 제목 줄 오른쪽에 요소를 둘 넘게 두지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| Panel | 흰 패널. 제목(`title`)을 주면 제목 줄이 생긴다. |
| PanelHead | 제목 줄만 따로 쓴다. 패널 밖에서 같은 모양의 제목이 필요할 때. |
| PanelLink | "전체보기 ›" 모양의 작은 링크. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 안쪽 여백 18, 모서리 22 | 제목 줄 아래 간격 14. 좁은 화면(글자 배율 반영 폭 370 미만)에서는 `link`가 패널 맨 아래로 옮겨 간다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | `surface` 바탕, 그림자 `shadow(1)`. 다크 모드에서는 그림자 대신 1px `cardBorder` 테두리. |
| 누르는 패널 | `onPress`가 있으면 버튼이 되고 눌렀을 때 투명도 0.88. |
## 사용 방법
### 기본
```tsx
import { Panel, PanelLink, WellRow } from '@/ui/surface';
}>
```
### 속성(props)
#### Panel
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 선택 | 패널 안의 내용. |
| `style` | `StyleProp` | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. |
| `title` | `string` | — | 선택 | 제목. 있으면 제목 줄이 생긴다. |
| `info` | `[string, string]` | — | 선택 | 제목 옆 ⓘ 설명. `[제목, 설명]`. 누르면 대화상자가 뜬다. |
| `right` | `ReactNode` | — | 선택 | 제목 줄 오른쪽 요소. `link`보다 우선한다. |
| `link` | `ReactNode` | — | 선택 | 제목 줄 오른쪽 링크. 좁은 화면에서는 패널 아래로 옮겨 간다. |
| `onPress` | `() => void` | — | 선택 | 있으면 패널 전체가 버튼이 된다. |
| `accessibilityLabel` | `string` | — | 선택 | 누르는 패널의 이름. |
#### PanelHead
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `title` | `string` | — | 필수 | 제목(`headline` 18, 한 줄). |
| `info` | `[string, string]` | — | 선택 | 제목 옆 ⓘ 설명 `[제목, 설명]`. |
| `right` | `ReactNode` | — | 선택 | 오른쪽 요소. |
| `below` | `boolean` | — | 선택 | `true`이면 오른쪽 요소를 제목 아래로 내린다(좁은 화면). |
| `style` | `StyleProp` | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. |
#### PanelLink
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 링크 글자. |
| `onPress` | `() => void` | — | 필수 | 눌렀을 때 실행할 함수. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 패널 | `surface` 바탕, 모서리 22, 안쪽 여백 18, `shadow(1)`(다크는 1px `cardBorder`) |
| 제목 줄 | 제목 `headline` 18 Bold, 아래 간격 14, ⓘ 17px(`textTertiary`) |
| PanelLink | `small` `textSecondary`, ChevronRight 15 선 2.3 |
| 좁은 화면 | 글자 배율을 반영한 화면 폭이 370 미만이면 링크를 패널 아래(위 구분선)로 옮긴다 |
## 접근성
- 제목은 `header` 역할이다. ⓘ 버튼의 이름은 "{제목} 설명"이다.
- `onPress`가 있으면 패널 전체가 `button` 역할이다.
- PanelLink는 `button`이고 눌리는 영역이 사방 8 넓다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 패널 위 본문 | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
| PanelLink 글자 (13px) | `textSecondary` | `surface` | 4.23:1 미달 | 6.95:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: `canvas` 위에 패널을 놓고, 안의 묶음은 홈(`WellRow`)으로 나눈다.
- 잘못한 예: 패널 안에 패널을 넣지 않는다.
## 함께 보는 컴포넌트
- [Card](https://developers.moring.co/tds/components/card.md): 옅은 주색 틴트(`card`)를 깐 둥근 상자다. 안내, 요약, 눌러서 이동하는 카드에 쓴다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [SectionTitle](https://developers.moring.co/tds/components/section-title.md): 화면 안의 한 묶음(섹션)을 시작하는 제목 줄이다. 위에 24, 아래에 12의 간격을 포함한다.
- [Screen](https://developers.moring.co/tds/components/screen.md): 모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
---
# SectionTitle
화면 안의 한 묶음(섹션)을 시작하는 제목 줄이다. 위에 24, 아래에 12의 간격을 포함한다.
- 분류: 면과 목록
- 가져오기: `import { SectionTitle } from '@/ui/layout'`
- 코드 위치: `apps/mobile/src/ui/layout.tsx:98` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/section-title
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/section-title 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- "내 과목", "최근 자료"처럼 목록 묶음 위에 쓴다.
- 오른쪽에는 "전체보기" 같은 `TextButton`을 둘 수 있다.
### 쓰지 않는다
- 패널 안의 제목에는 쓰지 않는다. `PanelHead`를 쓴다.
- 화면의 맨 위 큰 제목에는 쓰지 않는다. `TitleHeader`다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 위 24, 아래 12 | `style`로 바꿀 수 있지만 바꾸지 않는다(규칙 S4). |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { SectionTitle } from '@/ui/layout';
} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `title` | `string` | — | 필수 | 제목(`headline`). |
| `right` | `ReactNode` | — | 선택 | 오른쪽 요소. |
| `style` | `StyleProp` | — | 선택 | 간격을 바꿀 때만 쓴다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 간격 | 위 `xl`(24), 아래 `md`(12) |
| 글자 | `headline`(17/24 Bold) |
| 배치 | 제목은 왼쪽, 오른쪽 요소는 끝(`space-between`) |
## 접근성
- 제목 글자는 읽힌다. 역할은 따로 지정하지 않는다.
## 잘한 예와 잘못한 예
- 잘한 예: 위 간격이 아래보다 커서 제목이 아래 목록에 속해 보인다.
- 잘못한 예: 간격을 모두 같게 하면 제목이 위 목록에 속해 보인다.
## 함께 보는 컴포넌트
- [Panel](https://developers.moring.co/tds/components/panel.md): 회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
- [TitleHeader](https://developers.moring.co/tds/components/title-header.md): 탭의 첫 화면 위쪽에 놓는 큰 제목 줄이다. 왼쪽에 제목, 오른쪽에 행동 자리가 있다.
- [TextButton](https://developers.moring.co/tds/components/text-button.md): 글자만 있는 가장 가벼운 버튼이다. "나중에", "건너뛰기" 같은 선택 행동에 쓴다.
---
# WellRow
패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- 분류: 면과 목록
- 가져오기: `import { WellRow, RowIcon } from '@/ui/surface'`
- 코드 위치: `apps/mobile/src/ui/surface.tsx:73` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/well-row
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/well-row 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 과목, 자료, 시험처럼 눌러서 이동하거나 고르는 목록 행에 쓴다.
- 선택 목록(시트)에서는 `selected`와 `RadioMark`를 함께 쓴다.
- 앞에 `RowIcon`을 두면 상태 색을 가진 아이콘 타일이 된다.
### 쓰지 않는다
- 설정 행에는 쓰지 않는다. `SettingsRow`를 쓴다.
- 행마다 그림자를 주지 않는다.
- 제목을 두 줄 넘게 쓰지 않는다. `titleLines`로 줄 수를 정하고 넘치면 …으로 줄인다.
## 종류
| 이름 | 설명 |
| --- | --- |
| WellRow | 목록 행. `onPress`가 있으면 버튼이고 뒤에 ChevronRight가 자동으로 붙는다. |
| RowIcon | 행 앞의 42 × 42 아이콘 타일. `tone`으로 색을 준다: 기본(흰), `primary`, `success`, `amber`, `danger`. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 행 | 최소 높이 56 | 좌우 12, 위아래 10, 모서리 18, 요소 사이 12. |
| RowIcon | 42 × 42, 모서리 14 | 아이콘은 20px. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | `well` 바탕, 같은 색 1.2px 테두리. |
| 선택됨 | `selected` 바탕, `primary` 1.2px 테두리. |
| 누름 | 투명도 0.8. |
## 사용 방법
### 기본
```tsx
import { RowIcon, WellRow } from '@/ui/surface';
}
title="수학"
meta="자료 18개 · 복습 12개"
onPress={() => router.push('/subject/math')}
/>
```
### 속성(props)
#### WellRow
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `icon` | `ReactNode` | — | 선택 | 앞에 놓을 요소. 보통 `RowIcon`. |
| `title` | `ReactNode` | — | 필수 | 제목. 글자면 `bodyStrong`으로 그린다. |
| `meta` | `ReactNode` | — | 선택 | 제목 아래 보조 줄. 글자면 `caption` `textSecondary`. |
| `right` | `ReactNode` | — | 선택 | 뒤에 놓을 요소. 생략하고 `onPress`가 있으면 ChevronRight를 둔다. |
| `onPress` | `() => void` | — | 선택 | 있으면 행이 버튼이 된다. |
| `selected` | `boolean` | — | 선택 | 선택되었는지. |
| `accessibilityLabel` | `string` | — | 선택 | 행의 이름. 안의 글자와 다르게 읽히게 할 때만 쓴다. |
| `style` | `StyleProp` | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. |
| `titleLines` | `number` | `1` | 선택 | 제목의 최대 줄 수. 기본 1. |
#### RowIcon
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 타일 안의 아이콘. |
| `tone` | `'danger' \| 'primary' \| 'success' \| 'amber'` | — | 선택 | 타일 색. `"danger"`, `"primary"`, `"success"`, `"amber"`. 없으면 흰색(`surface`). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 행 | 최소 높이 56, 모서리 18, 좌우 12, 위아래 10, 사이 12, 바탕 `well` |
| 글자 | 제목 `bodyStrong`, 보조 `caption`(`textSecondary`) |
| 선택 | `selected` 바탕 + `primary` 테두리 |
| RowIcon | 42 × 42, 모서리 14, 바탕은 tone의 `…Soft`, 아이콘 20 선 2.1 |
## 접근성
- `onPress`가 있으면 `button` 역할이고 `selected` 상태가 전달된다.
- 선택은 색과 테두리, (시트에서는) 라디오 표시로 나타낸다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 행 제목 | `text` | `well` | 16.12:1 통과 | 14.08:1 통과 | 14.95:1 통과 | 9.62:1 통과 | 4.5:1 |
| 보조 줄 (11.5px) | `textSecondary` | `well` | 3.98:1 미달 | 6.42:1 통과 | 4.52:1 통과 | 6.26:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 앞 아이콘, 제목, 보조 줄, 뒤 화살표 순서로 놓는다.
- 잘못한 예: 행마다 그림자를 주지 않는다. 홈(`well`) 색이 구분을 맡는다.
## 함께 보는 컴포넌트
- [Panel](https://developers.moring.co/tds/components/panel.md): 회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
- [SettingsGroup](https://developers.moring.co/tds/components/settings-list.md): 설정 화면의 목록이다. 작은 구역 이름, 행이 든 흰 패널, 아래 안내 글로 이루어진다.
- [RadioMark](https://developers.moring.co/tds/components/radio-mark.md): 선택 목록 행의 끝에 놓는 동그란 라디오 표시다. 시트의 선택 목록에서 쓴다.
- [SwipeRow](https://developers.moring.co/tds/components/swipe-row.md): 행을 왼쪽으로 밀면 뒤에서 행동 하나(삭제 등)가 나타나고, 끝까지 밀면 그 행동이 실행되는 행이다.
---
# SettingsGroup
설정 화면의 목록이다. 작은 구역 이름, 행이 든 흰 패널, 아래 안내 글로 이루어진다.
- 분류: 면과 목록
- 가져오기: `import { SettingsGroup, SettingsCard, SettingsIcon, SettingsRow } from '@/ui/SettingsList'`
- 코드 위치: `apps/mobile/src/ui/SettingsList.tsx:22` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/settings-list
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/settings-list 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 설정과 그 하위 화면의 모든 목록에 쓴다.
- 행 하나는 아이콘 · 제목 · 현재 값 · 화살표다. 누르면 하위 화면이나 시트로 간다.
- 숫자 입력기 같은 자유로운 내용은 `SettingsCard`에 넣는다.
### 쓰지 않는다
- 설정이 아닌 목록에는 쓰지 않는다. `WellRow`를 쓴다.
- 삭제처럼 위험한 행이 아니면 아이콘에 색을 주지 않는다. 아이콘 타일은 모두 중립색이다. 빨강은 위험한 행(`danger`)만 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| SettingsGroup | 행 목록. 행 사이에 얇은 구분선이 자동으로 들어간다. |
| SettingsCard | 자유로운 내용을 담는 같은 모양의 패널(안쪽 여백 18). |
| SettingsRow | 행. `onPress`가 있으면 버튼이고 ChevronRight가 붙는다. `right`에 스위치를 넣을 수 있다. |
| SettingsIcon | 행 앞의 34 × 34 중립 아이콘 타일. `danger`면 빨간 타일. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 행 | 최소 높이 58 | 좌우 18, 위아래 12, 요소 사이 12. 구분선은 아이콘이 있으면 왼쪽에서 64, 없으면 18 들여 시작한다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | `surface` 바탕, 모서리 22, `shadow(1)`(다크는 1px `cardBorder`). |
| 누름 | 행 배경이 `pressed` 색으로 바뀐다. |
| 값이 긴 행 | 제목은 폭을 지키고 값이 먼저 …으로 줄어든다(값은 행 폭의 58%까지). |
## 사용 방법
### 기본
```tsx
import { SettingsGroup, SettingsIcon, SettingsRow, SettingsSwitch } from '@/ui/SettingsList';
} title="복습 알림" chevron={false}
right={} />
} title="언어" value="한국어" onPress={openLanguage} />
} title="로그아웃" tone={color.danger} onPress={logout} />
```
### 속성(props)
#### SettingsGroup
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 행들. 행 사이에 구분선이 자동으로 들어간다. |
| `title` | `string` | — | 선택 | 패널 위의 작은 구역 이름(`smallStrong`, `textSecondary`). |
| `footer` | `string` | — | 선택 | 패널 아래 안내 글(`caption`). |
#### SettingsCard
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 자유로운 내용. |
| `title` | `string` | — | 선택 | 패널 위의 작은 구역 이름. |
| `footer` | `string` | — | 선택 | 패널 아래 안내 글. |
#### SettingsIcon
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `Icon` | `Icon` | — | 필수 | 그릴 아이콘 컴포넌트. |
| `danger` | `boolean` | — | 선택 | `true`이면 `dangerSoft` 타일에 `danger` 아이콘. |
#### SettingsRow
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `icon` | `ReactNode` | — | 선택 | 앞 요소. 보통 `SettingsIcon`. |
| `title` | `string` | — | 필수 | 제목(`bodyStrong`). |
| `subtitle` | `string` | — | 선택 | 제목 아래 보조 글(`small`). |
| `value` | `string` | — | 선택 | 현재 값. 길면 …으로 줄어든다. |
| `onPress` | `() => void` | — | 선택 | 있으면 행이 버튼이 된다. |
| `right` | `ReactNode` | — | 선택 | 뒤 요소. 스위치 등. |
| `tone` | `string` | `color.text` | 선택 | 제목 글자색. 위험한 행은 `color.danger`. |
| `chevron` | `boolean` | `true` | 선택 | `false`이면 화살표를 숨긴다. 기본 `true`(`onPress`가 있을 때). |
| `accessibilityHint` | `string` | — | 선택 | 화면 읽기 프로그램이 읽는 설명. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 패널 | `surface`, 모서리 22, `shadow(1)`(다크는 1px `cardBorder`) |
| 행 | 최소 58, 좌우 18, 위아래 12, 사이 12 |
| 구분선 | 헤어라인 `line`, 시작 위치 18(아이콘 있으면 64) |
| 아이콘 타일 | 34 × 34, 모서리 11, `well` 바탕, 아이콘 18 선 2.1 `textSecondary` |
| 구역 이름 | `smallStrong`, `textSecondary`, 아래 8, 왼쪽 4 |
## 접근성
- `onPress`가 있는 행은 `button`이고 이름은 "제목, 보조 글, 값"을 이어 읽는다.
- 화살표(ChevronRight)가 눌리는 행임을 보여 준다.
- 위험한 행은 빨간 글자와 아이콘으로, 글자(로그아웃 등)로도 알린다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 행 제목 | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
| 현재 값 (13px) | `textSecondary` | `surface` | 4.23:1 미달 | 6.95:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
| 위험한 행 제목 | `danger` | `surface` | 4.55:1 통과 | 5.76:1 통과 | 5.04:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 아이콘은 모두 중립색이고, 위험한 행만 빨강이다.
- 잘못한 예: 행마다 다른 색 아이콘을 쓰면 위험한 행이 눈에 띄지 않는다.
## 함께 보는 컴포넌트
- [SettingsSwitch](https://developers.moring.co/tds/components/settings-switch.md): 켜고 끄는 스위치다. 설정 행의 오른쪽에 놓는다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [Panel](https://developers.moring.co/tds/components/panel.md): 회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
---
# SwipeRow
행을 왼쪽으로 밀면 뒤에서 행동 하나(삭제 등)가 나타나고, 끝까지 밀면 그 행동이 실행되는 행이다.
- 분류: 면과 목록
- 가져오기: `import { SwipeRow } from '@/ui/SwipeRow'`
- 코드 위치: `apps/mobile/src/ui/SwipeRow.tsx:12` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/swipe-row
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/swipe-row 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 목록 행에서 자주 쓰는 행동 하나(삭제, 보관)를 빠르게 하게 할 때 쓴다.
- 밀어서 하는 행동은 반드시 다른 방법(메뉴, 상세 화면의 버튼)으로도 할 수 있게 한다.
### 쓰지 않는다
- 밀기가 유일한 방법인 행동에 쓰지 않는다. 손을 쓰기 어려운 사용자가 할 수 없다.
- 되돌릴 수 없는 삭제를 확인 없이 실행하지 않는다. `onAction`에서 `dialog.confirm`이나 되돌리기 토스트를 쓴다.
- 가로로 스크롤되는 목록 안에서 쓰지 않는다. 제스처가 겹친다.
## 종류
종류가 없다. 행동의 색은 `tint`(기본 `danger`)로 바꾼다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 밀기 기준 110 | 왼쪽으로 110 넘게 밀고 놓으면 실행한다. 모서리는 18. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 쉬는 중 | 행동 면이 보이지 않는다(가장자리에 색이 비치지 않는다). |
| 미는 중 | 뒤에서 `tint` 색 면과 아이콘·글자가 나타난다. 기준(110)을 넘는 순간 선택 진동이 난다. |
| 놓음 | 기준을 넘었으면 200ms 동안 화면 밖으로 사라지며 `onAction`을 부른다. 못 넘었으면 스프링(damping 18)으로 제자리로 돌아온다. |
## 사용 방법
### 기본
```tsx
import { SwipeRow } from '@/ui/SwipeRow';
} onAction={() => void removeWithUndo(item)}>
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 밀리는 행. |
| `label` | `string` | — | 필수 | 뒤에 나타나는 행동 이름. |
| `icon` | `ReactNode` | — | 필수 | 뒤에 나타나는 행동 아이콘. |
| `tint` | `string` | `color.danger` | 선택 | 뒤 면의 색. 기본 `color.danger`. |
| `onAction` | `() => void` | — | 필수 | 끝까지 밀고 놓았을 때 실행할 함수. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 기준 | 왼쪽으로 110 이상 밀면 실행 |
| 뒤 면 | `tint` 면, 모서리 18, 오른쪽 안쪽 22, 아이콘과 글자(`smallStrong`, white) 세로로 |
| 제스처 | 가로 18 이상 움직여야 시작, 세로 12 넘게 움직이면 취소(목록 스크롤을 방해하지 않는다) |
## 접근성
- 밀기 제스처에는 화면 읽기 프로그램용 대체 동작이 없다. 같은 행동을 상세 화면이나 메뉴에서도 제공해야 한다.
- 뒤 면의 행동은 색과 글자로 나타난다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 뒤 면 글자 (13px 굵게) | `white` | `danger` | 4.55:1 통과 | 3.02:1 미달 | 5.15:1 통과 | 1.99:1 미달 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 삭제 뒤 되돌리기 토스트를 함께 둔다.
- 잘못한 예: 밀기만으로 되는 행동은 사용자가 찾지 못할 수 있다. 같은 행동을 보이는 버튼으로도 둔다.
## 함께 보는 컴포넌트
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [dialog](https://developers.moring.co/tds/components/dialog.md): 화면 가운데 뜨는 확인 대화상자와 아래에서 올라오는 메뉴를 함수 호출 하나로 여는 도구다. 결과(눌린 버튼)를 약속(Promise)으로 돌려준다.
- [toast](https://developers.moring.co/tds/components/toast.md): 화면 아래에 잠깐 떴다 사라지는 짧은 확인 메시지다. 함수 한 번 호출로 띄운다. 되돌리기 같은 행동 하나를 붙일 수 있다.
---
# Gap · HScroll
`Gap`은 세로 빈 간격이고, `HScroll`은 가로로 밀어 보는 줄이다. 레이아웃을 돕는 작은 컴포넌트다.
- 분류: 면과 목록
- 가져오기: `import { Gap, HScroll } from '@/ui/layout, @/ui/controls'`
- 코드 위치: `apps/mobile/src/ui/layout.tsx:107` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/layout-helpers
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/layout-helpers 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- `Gap`: 요소 사이에 간격 토큰 높이의 빈 공간을 넣을 때 쓴다. 기본 12(`md`).
- `HScroll`: 칩이나 카드가 많아 한 줄에 다 안 들어갈 때 옆으로 밀게 한다. 요소 사이 간격은 8(`sm`)이다.
### 쓰지 않는다
- `Gap`에 간격 토큰에 없는 값을 쓰지 않는다.
- `HScroll` 안에 핵심 행동을 숨기지 않는다. 가로로 밀어야 보인다는 것을 모르는 사용자가 있다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| Gap | `size` 기본 12 | 값은 간격 토큰(`xs`4 ~ `xxxl`40) 중에서 고른다. |
| HScroll | 요소 사이 8 | 스크롤 막대는 숨긴다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | Gap은 눈에 보이지 않는다. HScroll은 가로로 밀린다. |
## 사용 방법
### Gap
```tsx
import { Gap } from '@/ui/layout';
{/* … */}{/* … */}
```
### HScroll
```tsx
import { HScroll } from '@/ui/controls';
{subjects.map((s) => )}
```
### 속성(props)
#### Gap
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `size` | `number` | `space.md` | 선택 | 높이(px). 기본 `space.md`(12). |
#### HScroll
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 가로로 놓을 요소들. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| Gap | 높이만 있는 빈 `View` |
| HScroll | 가로 `ScrollView`, 요소 사이 `space.sm`(8), 스크롤 막대 숨김 |
## 접근성
- `HScroll`은 화면 읽기 프로그램에서 가로 스크롤 영역으로 읽힌다. 줄에 들어 있는 요소는 모두 같은 방법으로 읽을 수 있어야 한다.
## 잘한 예와 잘못한 예
- 잘한 예: 칩 같은 보조 선택지를 가로 줄에 둔다.
- 잘못한 예: 주 버튼을 가로 스크롤 줄에 두면 안 보일 수 있다.
## 함께 보는 컴포넌트
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
- [Panel](https://developers.moring.co/tds/components/panel.md): 회색 바탕(`canvas`) 위에 놓는 흰 패널이다. 제목 줄(제목, 설명 아이콘, 오른쪽 링크)과 내용을 담는다. 리포트와 시험 계획 화면의 기본 면이다.
- [SectionTitle](https://developers.moring.co/tds/components/section-title.md): 화면 안의 한 묶음(섹션)을 시작하는 제목 줄이다. 위에 24, 아래에 12의 간격을 포함한다.
---
# Notice
화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
- 분류: 알림과 안내
- 가져오기: `import { Notice } from '@/ui/feedback'`
- 코드 위치: `apps/mobile/src/ui/feedback.tsx:29` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/notice
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/notice 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 작업 가까이에 상태를 알릴 때 쓴다. 예: "네트워크가 불안정해요", "저장하지 못했어요".
- 다시 시도 같은 복구 행동이 있으면 `action`에 `SmallButton`이나 `TextButton`을 넣는다.
- 처리 중이라는 안내는 `busy`로 스피너를 보여 준다.
### 쓰지 않는다
- 화면 전체를 대신하는 오류에는 쓰지 않는다. 데이터가 전혀 없을 때는 `EmptyState`다.
- 잠깐 뜨고 사라지는 확인("저장했어요")에는 쓰지 않는다. `toast`를 쓴다.
- 긴 문장이나 여러 문단을 넣지 않는다. 한두 줄이다.
## 종류
| 이름 | 설명 |
| --- | --- |
| info (기본) | `primarySoft` 바탕, `primary` 글자·아이콘(원 안의 i). |
| warn | `amberSoft` 바탕, `amberText` 글자와 아이콘(느낌표). |
| error | `dangerSoft` 바탕, `danger` 글자와 아이콘(느낌표). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 좌우 14, 위아래 12, 모서리 12 | 높이는 글자 줄 수가 정한다. 아이콘은 18px. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| info | 기본 모습이다. |
| warn | 기본 모습이다. |
| error | 기본 모습이다. |
| 처리 중 | 아이콘 자리에 스피너가 돈다. |
## 사용 방법
### 기본
```tsx
import { Notice } from '@/ui/feedback';
```
### 복구 행동과 함께
```tsx
} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `tone` | `'info' \| 'warn' \| 'error'` | `'info'` | 선택 | `"info"`(기본), `"warn"`, `"error"`. |
| `text` | `string` | — | 필수 | 안내 문구. |
| `action` | `ReactNode` | — | 선택 | 오른쪽에 놓을 행동(버튼). |
| `busy` | `boolean` | — | 선택 | `true`이면 아이콘 대신 스피너를 보여 준다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 모서리 `radius.md`(12), 좌우 14, 위아래 12, 요소 사이 8 |
| 글자 | `small`(13/19 Medium), 톤 색 |
| 아이콘 | 18px, 선 2 |
## 접근성
- 역할은 `alert`라서 나타나면 화면 읽기 프로그램이 바로 읽어 준다.
- 톤은 색과 아이콘 모양(i, !)과 문구로 나타낸다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| info 글자 (13px) | `primary` | `primarySoft` | 3.45:1 미달 | 4.32:1 미달 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
| warn 글자 (13px) | `amberText` | `amberSoft` | 5.48:1 통과 | 9.81:1 통과 | 6.66:1 통과 | 6.86:1 통과 | 4.5:1 |
| error 글자 (13px) | `danger` | `dangerSoft` | 4.09:1 미달 | 5.39:1 통과 | 4.69:1 통과 | 5.43:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 무엇이 문제인지와 다음 행동(다시)을 함께 둔다.
- 잘못한 예: 문제만 알리고 할 일이 없으면 사용자가 멈춘다.
## 함께 보는 컴포넌트
- [toast](https://developers.moring.co/tds/components/toast.md): 화면 아래에 잠깐 떴다 사라지는 짧은 확인 메시지다. 함수 한 번 호출로 띄운다. 되돌리기 같은 행동 하나를 붙일 수 있다.
- [EmptyState](https://developers.moring.co/tds/components/empty-state.md): 보여 줄 내용이 없을 때의 안내다. 옅은 아이콘 타일, 제목, 한 줄 설명, 다음 행동 버튼으로 이루어진다.
- [FeedbackCard](https://developers.moring.co/tds/components/feedback-card.md): 문제를 채점한 뒤 결과("정답!", "오답")와 해설을 보여 주는 카드다.
---
# FeedbackCard
문제를 채점한 뒤 결과("정답!", "오답")와 해설을 보여 주는 카드다.
- 분류: 알림과 안내
- 가져오기: `import { FeedbackCard } from '@/ui/feedback'`
- 코드 위치: `apps/mobile/src/ui/feedback.tsx:14` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/feedback-card
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/feedback-card 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 문제를 풀고 채점이 끝난 순간의 결과 카드에 쓴다. 제목은 결과, 안에는 해설을 넣는다.
- 정답은 `correct`, 오답은 `wrong`, 그 밖의 안내(건너뜀, 모르겠어요)는 `neutral`.
### 쓰지 않는다
- 채점 전에는 쓰지 않는다. 정답을 미리 알려 주게 된다.
- 일반 안내에 쓰지 않는다. `Notice`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| correct | `successSoft` 바탕, `success` 체크 원, 제목 `success`. |
| wrong | `dangerSoft` 바탕, `danger` × 원, 제목 `danger`. |
| neutral | `card` 바탕, `textSecondary` 느낌표 원. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 안쪽 여백 16, 모서리 16 | 내용(해설)은 아이콘 너비만큼(30) 들여 쓴다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| correct | 기본 모습이다. |
| wrong | 기본 모습이다. |
| neutral | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { FeedbackCard } from '@/ui/feedback';
{explanation}
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `tone` | `'correct' \| 'wrong' \| 'neutral'` | — | 필수 | `"correct"`, `"wrong"`, `"neutral"`. |
| `title` | `string` | — | 필수 | 결과 제목. |
| `children` | `ReactNode` | — | 선택 | 제목 아래 내용(해설). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 모서리 16, 안쪽 여백 16 |
| 제목 | `headline`, 톤 색. 아이콘 22px(채운 원 + `surface` 색 기호) |
| 내용 | 제목 아래 8, 왼쪽 30 |
## 접근성
- `accessibilityLiveRegion="polite"`라서 나타날 때 읽어 준다.
- 결과는 색, 아이콘(체크, ×), 제목 글자로 함께 알린다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 정답 제목 (17px 굵게) | `success` | `successSoft` | 3.74:1 미달 | 6.59:1 통과 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
| 오답 제목 (17px 굵게) | `danger` | `dangerSoft` | 4.09:1 미달 | 5.39:1 통과 | 4.69:1 통과 | 5.43:1 통과 | 4.5:1 |
| 중립 제목 (17px 굵게) | `textSecondary` | `card` | 3.94:1 미달 | 6.51:1 통과 | 4.74:1 통과 | 6.14:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 결과 글자와 아이콘, 색을 함께 쓴다.
- 잘못한 예: 제목 없이 색만 칠하면 의미가 전달되지 않는다.
## 함께 보는 컴포넌트
- [OptionRow](https://developers.moring.co/tds/components/option-row.md): 퀴즈 보기 한 줄이다. 고르기 전, 고른 뒤, 채점 뒤(정답, 오답)의 모습이 정해져 있다.
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
---
# EmptyState
보여 줄 내용이 없을 때의 안내다. 옅은 아이콘 타일, 제목, 한 줄 설명, 다음 행동 버튼으로 이루어진다.
- 분류: 알림과 안내
- 가져오기: `import { EmptyState } from '@/ui/feedback'`
- 코드 위치: `apps/mobile/src/ui/feedback.tsx:45` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/empty-state
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/empty-state 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 목록이나 화면에 데이터가 전혀 없을 때 쓴다. 예: 자료가 하나도 없는 과목.
- 다음에 할 일을 `action`에 버튼으로 넣는다(예: "자료 추가").
### 쓰지 않는다
- 데이터가 일부만 없을 때 쓰지 않는다. 그 부분만 안내한다.
- 오류를 알리는 데 쓰지 않는다. 복구 행동이 있는 `Notice`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 아이콘 타일 | `Icon`을 주면 옅은 원(96)과 둥근 타일(64) 안에 아이콘(28)을 그린다. 색은 `tint`. |
| 직접 그림 | `icon`에 원하는 요소를 넣는다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 위아래 32, 좌우 16 | 요소 사이 6, 타일 아래 8. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { EmptyState } from '@/ui/feedback';
import { Inbox } from '@/ui/icons';
}
title="자료가 없어요"
body="사진을 찍어 첫 자료를 추가해 보세요."
action={}
/>
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `title` | `string` | — | 필수 | 제목(`headline`). |
| `body` | `string` | — | 선택 | 한 줄 설명. 단어가 중간에서 끊기지 않게 처리된다. |
| `action` | `ReactNode` | — | 선택 | 아래에 놓을 행동 버튼. 폭을 가득 채운다. |
| `icon` | `ReactNode` | — | 선택 | 아이콘 타일 대신 직접 그릴 요소. |
| `Icon` | `IconType` | — | 선택 | 아이콘 컴포넌트. 있으면 타일을 그린다. |
| `tint` | `string` | `color.primary` | 선택 | 타일 색. 기본 `color.primary`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 타일 | 바깥 원 96(`tint` 6%), 둥근 타일 64·모서리 22(`tint` 13%), 아이콘 28 선 2 |
| 글자 | 제목 `headline`, 설명 `small` `textSecondary`, 모두 가운데 |
| 행동 | 위 12(`md`), 폭 가득 |
## 접근성
- 제목과 설명이 읽힌다. 아이콘 타일은 장식이다.
- 행동 버튼에는 이름이 있다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 설명 (13px) | `textSecondary` | `bg` | 4.13:1 미달 | 7.65:1 통과 | 4.69:1 통과 | 7.96:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 다음에 할 일을 버튼으로 준다.
- 잘못한 예: "없음"만 쓰면 사용자가 무엇을 해야 하는지 모른다.
## 함께 보는 컴포넌트
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
- [Skeleton](https://developers.moring.co/tds/components/skeleton.md): 내용이 불러와지기 전에 그 자리를 채우는 깜빡이는 막대다.
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
---
# Skeleton
내용이 불러와지기 전에 그 자리를 채우는 깜빡이는 막대다.
- 분류: 알림과 안내
- 가져오기: `import { Skeleton } from '@/ui/feedback'`
- 코드 위치: `apps/mobile/src/ui/feedback.tsx:64` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/skeleton
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/skeleton 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 목록이나 카드의 내용이 로딩 중일 때, 나중에 올 내용과 같은 크기로 놓는다.
- 여러 개를 쌓아 문단이나 행의 모양을 흉내 낸다.
### 쓰지 않는다
- 2초 넘게 걸리는 작업에 막대만 두지 않는다. 진행 안내(`Notice` `busy`)를 더한다.
- 실제 내용과 크기가 다르게 놓지 않는다. 내용이 나타날 때 화면이 튄다.
## 종류
종류가 없다. 높이, 폭, 모서리로 모양을 정한다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 16, 폭 100%, 모서리 8 | `height`, `width`, `radiusValue`로 바꾼다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 깜빡임 | 투명도가 0.5와 1 사이를 700ms씩 오간다. 끝없이 반복한다. |
## 사용 방법
### 기본
```tsx
import { Skeleton } from '@/ui/feedback';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `height` | `number` | `16` | 선택 | 높이(px). 기본 16. |
| `width` | `number \| `${number}%`` | `'100%'` | 선택 | 폭(px 또는 "60%" 같은 문자열). 기본 `"100%"`. |
| `radiusValue` | `number` | `8` | 선택 | 모서리(px). 기본 8. |
| `style` | `object` | — | 선택 | 위치를 맞추는 추가 스타일. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 색 | `skeleton` |
| 움직임 | 투명도 0.5 ↔ 1, 700ms씩(`Animated.loop`) |
## 접근성
- 웹 미리보기는 `prefers-reduced-motion`이면 깜빡이지 않는다. 앱은 아직 그 설정을 읽지 않는다.
- 막대에는 이름이 없다. 로딩 중이라는 사실은 부모에 접근성 이름("불러오는 중")을 달아 알린다.
## 잘한 예와 잘못한 예
- 잘한 예: 나중에 올 내용과 같은 크기로 놓는다.
- 잘못한 예: 크기가 다르면 내용이 나타날 때 화면이 튄다.
## 함께 보는 컴포넌트
- [EmptyState](https://developers.moring.co/tds/components/empty-state.md): 보여 줄 내용이 없을 때의 안내다. 옅은 아이콘 타일, 제목, 한 줄 설명, 다음 행동 버튼으로 이루어진다.
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
---
# Sheet
화면 아래에서 올라오는 시트다. 위쪽 두 모서리가 둥글고, 손잡이와 제목 줄, 닫기 버튼이 있다.
- 분류: 알림과 안내
- 가져오기: `import { Sheet } from '@/ui/feedback'`
- 코드 위치: `apps/mobile/src/ui/feedback.tsx:80` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/sheet
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/sheet 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 짧은 선택, 한 가지 입력, 작은 양식을 현재 화면 위에서 처리할 때 쓴다.
- 제목(`title`)을 주면 제목 줄과 ✕ 닫기가 생긴다. 모든 시트의 제목 줄은 같은 모양이다.
- 아래로 끌거나 바깥을 누르거나 뒤로 가기를 눌러 닫는다.
### 쓰지 않는다
- 긴 양식이나 여러 단계에는 쓰지 않는다. 전체 화면으로 만든다.
- 꼭 답해야 하는 확인에는 쓰지 않는다. `dialog`를 쓴다.
- 시트 위에 시트를 겹치지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 제목 있음 | 제목 `headline` 20과 ✕ 버튼(36 원, `well` 바탕). |
| 제목 없음 | 손잡이만 있다. 내용이 스스로 제목을 가질 때. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 폭 전체, 높이는 내용 | 좌우 20(`gutter`), 아래는 안전 영역과 16 중 큰 값. 모서리 `radius.sheet`(24). |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 열림 | 어둠막이 나타나고(`scrim`) 시트가 240ms 동안 올라온다. |
| 닫힘 | 180ms 동안 내려간다. |
| 끄는 중 | 손잡이 줄을 아래로 끌면 따라 내려간다. 100 넘게 끌거나 빠르게(속도 900 초과) 끌고 놓으면 닫힌다. 아니면 160ms 동안 제자리로 돌아온다. |
| 위로 끄는 중 | `onPullUp`을 넘겼을 때만 가능하다. 위로 끌고 놓으면 `onPullUp`이 호출되고, 짧게 끌면 제자리로 돌아온다. |
| 키보드 | 키보드가 올라오면 시트가 그 위로 올라간다. |
## 사용 방법
### 기본
```tsx
import { Sheet } from '@/ui/feedback';
setOpen(false)} title="시험 선택">
{/* 내용 */}
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `visible` | `boolean` | — | 필수 | 열려 있는지. |
| `onClose` | `() => void` | — | 필수 | 닫을 때 호출된다(끌기, 바깥 누르기, 뒤로 가기, ✕). |
| `children` | `ReactNode` | — | 필수 | 시트 안의 내용. |
| `title` | `string` | — | 선택 | 제목. 있으면 제목 줄과 ✕가 생긴다. |
| `onPullUp` | `() => void` | — | 선택 | 손잡이 줄을 위로 70 넘게(또는 빠르게) 끌고 놓으면 호출된다. 더 큰 화면(전체 화면 편집기)을 여는 데 쓴다. 넘기면 시트가 위로도 최대 160까지 따라 올라간다(위 방향은 40%만 따라온다). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 시트 | `surface`, 위쪽 모서리 24, 좌우 20, 아래 max(안전 영역, 16) |
| 손잡이 | 40 × 4, 모서리 2, `lineStrong`, 아래 12 |
| 제목 줄 | 제목 `headline` 20, ✕ 36 원(`well`), 아래 16 |
| 어둠막 | `scrim` |
| 움직임 | 열림 240ms(out), 닫힘 180ms(in), 되돌림 160ms |
## 접근성
- 시트는 모달이라 뒤 내용은 읽히지 않는다(`accessibilityViewIsModal`).
- 바깥 눌림 영역과 ✕의 이름은 "닫기"다.
- 웹에서는 시트가 열리면 초점이 시트 안으로 들어가고 Esc로 닫혀야 한다.
## 잘한 예와 잘못한 예
- 잘한 예: 제목을 주면 무엇을 고르는 시트인지 알 수 있다.
- 잘못한 예: 긴 양식을 시트에 넣지 않는다. 전체 화면으로 만든다.
## 함께 보는 컴포넌트
- [OptionSheet](https://developers.moring.co/tds/components/option-sheet.md): 시트 안에서 목록 중 하나를 고르는 컴포넌트다. 행마다 라디오 표시가 있고, 고르면 바로 닫힌다.
- [dialog](https://developers.moring.co/tds/components/dialog.md): 화면 가운데 뜨는 확인 대화상자와 아래에서 올라오는 메뉴를 함수 호출 하나로 여는 도구다. 결과(눌린 버튼)를 약속(Promise)으로 돌려준다.
- [TimeSheet](https://developers.moring.co/tds/components/time-sheet.md): 아래에서 올라오는 시트에서 시각을 고른다. 오전/오후, 시, 분(5분 단위) 세 줄 바퀴를 돌려서 고른다.
---
# OptionSheet
시트 안에서 목록 중 하나를 고르는 컴포넌트다. 행마다 라디오 표시가 있고, 고르면 바로 닫힌다.
- 분류: 알림과 안내
- 가져오기: `import { OptionSheet } from '@/ui/FilterSheet'`
- 코드 위치: `apps/mobile/src/ui/FilterSheet.tsx:30` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/option-sheet
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/option-sheet 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- `FilterPill`을 눌렀을 때 값을 고르는 목록으로 쓴다.
- 선택지가 7개 이상일 때 `ChoiceCard` 대신 쓴다.
- 행 오른쪽에 개수(`count`)나 색 점(`dot`)을 보여 줄 수 있다.
### 쓰지 않는다
- 여러 개를 동시에 고르는 목록에 쓰지 않는다. 라디오라서 하나만 고른다.
- 선택지가 2~3개면 쓰지 않는다. `PillTabs`나 `ChoiceCard`가 가볍다.
## 종류
종류가 없다. 선택지에 `count`, `dot`, `hint`를 붙일 수 있다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 목록 최대 높이 420 | 넘치면 목록 안에서 스크롤된다. 행 사이 간격 8. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택됨 | `WellRow`가 `selected`가 되고 라디오가 켜진다. |
| 고르면 | 값을 `onPick`으로 알리고 시트를 닫는다(`햅틱 select`). |
## 사용 방법
### 기본
```tsx
import { OptionSheet } from '@/ui/FilterSheet';
setOpen(false)}
/>
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `visible` | `boolean` | — | 필수 | 열려 있는지. |
| `title` | `string` | — | 필수 | 시트 제목. |
| `options` | `SheetOption[]` | — | 필수 | 선택지 목록. 각 항목: `value`(`null` 가능), `label`, `count`(개수), `dot`(색 점), `hint`(보조 글). |
| `value` | `string \| null` | — | 필수 | 지금 고른 `value`. |
| `onPick` | `(v: string \| null) => void` | — | 필수 | 고른 `value`와 함께 호출된다. |
| `onClose` | `() => void` | — | 필수 | 닫을 때 호출된다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 행 | `WellRow` + 오른쪽에 개수(`smallStrong` `textTertiary`)와 `RadioMark` |
| 목록 | 최대 높이 420, 행 사이 8, 아래 8 |
## 접근성
- 각 행의 이름은 "{label}, {count}개"이고 선택 상태가 전달된다.
- `Sheet`의 접근성을 그대로 따른다.
## 잘한 예와 잘못한 예
- 잘한 예: 고른 값이 라디오와 테두리로 분명하다.
- 잘못한 예: 한 번에 여러 개를 고르는 데 쓰지 않는다.
## 함께 보는 컴포넌트
- [FilterPill](https://developers.moring.co/tds/components/filter-pill.md): "과목 전체 ▾"처럼 이름과 지금 값을 보여 주고, 누르면 선택 목록을 여는 알약 버튼이다.
- [Sheet](https://developers.moring.co/tds/components/sheet.md): 화면 아래에서 올라오는 시트다. 위쪽 두 모서리가 둥글고, 손잡이와 제목 줄, 닫기 버튼이 있다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [RadioMark](https://developers.moring.co/tds/components/radio-mark.md): 선택 목록 행의 끝에 놓는 동그란 라디오 표시다. 시트의 선택 목록에서 쓴다.
---
# dialog
화면 가운데 뜨는 확인 대화상자와 아래에서 올라오는 메뉴를 함수 호출 하나로 여는 도구다. 결과(눌린 버튼)를 약속(Promise)으로 돌려준다.
- 분류: 알림과 안내
- 가져오기: `import { DialogHost, DialogCard } from '@/ui/dialog'`
- 코드 위치: `apps/mobile/src/ui/dialog.tsx:73` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/dialog
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/dialog 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 알려야 하고 꼭 확인받아야 하는 일에 `dialog.alert`를 쓴다.
- "삭제할까요?" 같은 확인에는 `dialog.confirm`을 쓴다. 되돌릴 수 없는 일은 `tone: "danger"`로 한다.
- 선택지 여러 개를 세로로 늘어놓으려면 `dialog.choose`, 행동 목록(이름 바꾸기, 삭제)은 `dialog.menu`를 쓴다.
### 쓰지 않는다
- 사용자를 막지 않아도 되는 알림에는 쓰지 않는다. `toast`나 `Notice`를 쓴다.
- 대화상자 위에 대화상자를 연속으로 띄우지 않는다. 연속으로 부르면 하나씩 차례로 뜬다.
- 긴 입력이나 양식을 넣지 않는다. `Sheet`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| dialog.alert | 제목, 본문, 버튼 하나("확인"). 닫으면 끝. |
| dialog.confirm | 취소와 확인 버튼 두 개가 가로로 놓인다. 확인을 누르면 `true`. |
| dialog.choose | 선택지가 세로로 쌓이고 맨 아래에 취소가 붙는다. 고른 번호를 돌려준다. |
| dialog.menu | 아래에서 올라오는 메뉴 시트. 행마다 아이콘, 이름, 보조 글. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 대화상자 | 폭 최대 420, 좌우 28 여백 | 안쪽 여백 22(위 24). 버튼 높이 48. |
| 메뉴 | 행 최소 높이 56 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 열림 | 대화상자는 180ms 동안 0.96배에서 1배로 커지며 나타난다. 메뉴는 220ms 동안 올라온다. |
| 닫힘 | 130ms. 바깥을 누르거나 뒤로 가기를 누르면 닫히고 취소(`false`/`null`)로 처리된다. |
| 여러 개 호출 | 순서대로 하나씩 뜬다(대기열). |
## 사용 방법
### 앱 맨 위에 한 번 놓기
```tsx
import { DialogHost } from '@/ui/dialog';
// 루트 레이아웃에서 한 번만 렌더링한다.
```
### 확인받기
```tsx
import { dialog } from '@/ui/dialog';
const ok = await dialog.confirm({
title: '자료를 삭제할까요?',
body: '문제와 풀이 기록이 모두 사라져요. 되돌릴 수 없어요.',
confirmLabel: '삭제',
tone: 'danger',
icon: Trash2,
});
if (ok) await remove();
```
### 알림과 메뉴
```tsx
await dialog.alert('저장했어요', '오답 노트에서 다시 볼 수 있어요.');
const i = await dialog.menu({
title: '자료 관리',
options: [
{ label: '이름 바꾸기', icon: Pencil },
{ label: '삭제', icon: Trash2, tone: 'danger' },
],
});
if (i === 1) await remove();
```
### 속성(props)
| 함수 | 형태 | 설명 |
| --- | --- | --- |
| `dialog.alert` | `(title: string, body?: string, label = '확인'): Promise` | 알림. 닫힐 때 끝나는 약속을 돌려준다. |
| `dialog.confirm` | `({ title, body, confirmLabel = '확인', cancelLabel = '취소', tone = 'primary', icon }: { title: string; body?: string; confirmLabel?: string; cancelLabel?: string; tone?: 'primary' \| 'danger'; icon?: Icon; }): Promise` | 확인. 확인을 눌렀으면 `true`, 취소하거나 바깥을 눌렀으면 `false`. |
| `dialog.choose` | `({ title, body, options, cancelLabel = '취소', icon, tone }: { title: string; body?: string; options: Option[]; cancelLabel?: string \| null; icon?: Icon; tone?: Tone; }): Promise` | 선택지 목록. 고른 번호(0부터), 취소면 `null`. |
| `dialog.menu` | `({ title, body, options }: { title: string; body?: string; options: Option[] }): Promise` | 메뉴 시트. 고른 번호, 닫으면 `null`. |
#### DialogHost
속성이 없다.
#### DialogCard
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 카드 안의 내용. 대화상자와 같은 면(`surface`, 모서리 24, `shadow(3)`)을 직접 그릴 때 쓴다. |
`DialogHost`는 속성이 없다. 앱 루트에 한 번 두면 `dialog.*` 호출을 받아 그린다. 선택지 항목(`Option`)은 `{ label, tone?: "primary" | "danger" | "neutral", icon?, detail? }`이다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 대화상자 | `surface`, 모서리 `radius.sheet`(24), 안쪽 22(위 24), `shadow(3)`, 좌우 28 여백, 최대 폭 420 |
| 아이콘 타일 | 44 × 44, 모서리 14, 톤 색 12% 투명도, 아이콘 22 선 2.2 |
| 제목과 본문 | 제목 `headline`, 본문 `body` `textSecondary`(제목 아래 6) |
| 버튼 | 높이 48, 모서리 14, 둘이면 가로로 같은 폭, 그 밖에는 세로로 쌓기(사이 8). neutral은 `segmentTrack` 바탕, primary는 `primary`, danger는 `danger` |
| 메뉴 | `card` 바탕 목록, 행 최소 56, 아이콘 타일 34(색 10%) |
| 어둠막 | `scrim` |
## 접근성
- 대화상자의 역할은 `alert`이고 모달이다(`accessibilityViewIsModal`).
- 버튼에는 이름(글자)이 있고 글자가 길면 80%까지 줄어든다.
- 위험한 확인(`danger`)은 색, 아이콘, 제목·본문 글자로 함께 알린다.
- Android 뒤로 가기는 닫기와 같다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| primary 버튼 글자 (15px 굵게) | `onPrimary` | `primary` | 3.93:1 미달 | 3.31:1 미달 | 4.60:1 통과 | 7.25:1 통과 | 4.5:1 |
| danger 버튼 글자 (15px 굵게) | `onDanger` | `danger` | 4.55:1 통과 | 3.02:1 미달 | 5.15:1 통과 | 7.36:1 통과 | 4.5:1 |
| neutral 버튼 글자 (15px 굵게) | `text` | `segmentTrack` | 15.54:1 통과 | 14.08:1 통과 | 14.95:1 통과 | 9.62:1 통과 | 4.5:1 |
| 본문 (15px) | `textSecondary` | `surface` | 4.23:1 미달 | 6.95:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 되돌릴 수 없는 일은 무엇이 사라지는지 쓰고 `danger`로 한다.
- 잘못한 예: 사용자를 막지 않아도 되는 알림("저장했어요")은 `toast`로 충분하다.
## 함께 보는 컴포넌트
- [Sheet](https://developers.moring.co/tds/components/sheet.md): 화면 아래에서 올라오는 시트다. 위쪽 두 모서리가 둥글고, 손잡이와 제목 줄, 닫기 버튼이 있다.
- [toast](https://developers.moring.co/tds/components/toast.md): 화면 아래에 잠깐 떴다 사라지는 짧은 확인 메시지다. 함수 한 번 호출로 띄운다. 되돌리기 같은 행동 하나를 붙일 수 있다.
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
- [PrimaryButton](https://developers.moring.co/tds/components/primary-button.md): 서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
---
# toast
화면 아래에 잠깐 떴다 사라지는 짧은 확인 메시지다. 함수 한 번 호출로 띄운다. 되돌리기 같은 행동 하나를 붙일 수 있다.
- 분류: 알림과 안내
- 가져오기: `import { ToastHost } from '@/ui/toast'`
- 코드 위치: `apps/mobile/src/ui/toast.tsx:28` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/toast
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/toast 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 저장, 삭제, 복사처럼 일이 끝났다는 것을 짧게 알릴 때 쓴다.
- 삭제 뒤에는 되돌리기 행동을 붙여 6초 동안 보여 준다.
### 쓰지 않는다
- 꼭 읽어야 하는 오류에는 쓰지 않는다. 사라져 버린다. `Notice`나 `dialog`를 쓴다.
- 한 번에 둘 이상 띄우지 않는다. 새 토스트가 이전 것을 대신한다.
- 긴 문장을 넣지 않는다. 한 줄이다.
## 종류
| 이름 | 설명 |
| --- | --- |
| toast.done | 완료. 체크 원(`success`)과 글자. |
| toast.info | 안내. 정보 아이콘과 글자. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 좌우 16, 최소 높이 50 | 탭 바 위 76 + 안전 영역 위에 뜬다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 나타남 | 아래에서 위로 스프링(damping 18)으로 떠오른다. |
| 표시 | 행동이 없으면 2400ms, 행동(되돌리기)이 있으면 6000ms. |
| 사라짐 | 160ms 동안 내려가며 사라진다. |
## 사용 방법
### 앱 맨 위에 한 번 놓기
```tsx
import { ToastHost } from '@/ui/toast';
```
### 띄우기
```tsx
import { toast } from '@/ui/toast';
toast.done('저장했어요');
toast.done('자료를 삭제했어요', { label: '되돌리기', onPress: restore });
toast.info('오프라인이에요');
```
### 속성(props)
| 함수 | 형태 | 설명 |
| --- | --- | --- |
| `toast.done` | `(text: string, action?: Toast['action'])` | 완료 토스트. 두 번째 인자로 행동 `{ label, onPress }`를 줄 수 있다. |
| `toast.info` | `(text: string, action?: Toast['action'])` | 안내 토스트. 같은 형식이다. |
속성이 없다.
`ToastHost`는 속성이 없다. 앱 루트와 시트(`Sheet`) 안에 한 번씩 둔다. 시트는 앱 루트를 가리기 때문에 시트 안에서 띄운 토스트는 시트가 자기 `ToastHost`로 그린다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 모서리 18, 좌우 16, 위아래 13, 최소 높이 50, `shadow(2)` |
| 바탕 | 라이트 `text`(진한 색), 다크 `cardStrong`. 색이 아니라 한 줄 막대다 |
| 글자 | `smallStrong`, white. 행동 글자는 앱이 `#8DB8FF`를 직접 지정한다(웹에서는 `primaryLine`을 쓴다) |
| 아이콘 | 18px, 완료는 `success` 채운 원 |
| 위치 | 아래 = max(안전 영역, 12) + 76 |
## 접근성
- `accessibilityLiveRegion="polite"`, 역할 `alert`라서 나타나면 읽어 준다.
- 사라지는 시간이 짧으므로(2.4초) 중요한 정보를 토스트에만 두지 않는다.
- 행동 글자는 `hitSlop` 10으로 눌리는 영역을 넓힌다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 토스트 글자 (13px 굵게) | `white` | `text` | 17.15:1 통과 | 1.14:1 미달 | 16.48:1 통과 | 1.30:1 미달 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 짧은 확인 한 줄과 되돌리기.
- 잘못한 예: 꼭 읽어야 하는 오류는 사라지는 토스트로 알리지 않는다.
## 함께 보는 컴포넌트
- [Notice](https://developers.moring.co/tds/components/notice.md): 화면 안에 끼워 넣는 한 줄 안내 상자다. 안내(파랑), 주의(주황), 오류(빨강) 세 가지 톤이 있다.
- [dialog](https://developers.moring.co/tds/components/dialog.md): 화면 가운데 뜨는 확인 대화상자와 아래에서 올라오는 메뉴를 함수 호출 하나로 여는 도구다. 결과(눌린 버튼)를 약속(Promise)으로 돌려준다.
---
# AiMark
생성형 AI가 만든 결과임을 알리는 표시다. AI가 만든 모든 결과에 붙인다.
- 분류: 알림과 안내
- 가져오기: `import { AiMark } from '@/ui/AiMark'`
- 코드 위치: `apps/mobile/src/ui/AiMark.tsx:8` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/ai-mark
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/ai-mark 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- AI가 만든 문제, 해설, 그림에는 반드시 붙인다(「인공지능 기본법」 제31조 제2항·제3항).
- 문제 칩 옆에는 `chip`, 제목 아래 한 줄은 `note`, AI가 그린 그림 위에는 `overlay`.
### 쓰지 않는다
- AI가 만들지 않은 내용에 붙이지 않는다.
- 글자를 바꿔서 의미를 흐리지 않는다. `text`로 바꿀 수는 있지만 "AI 생성"이라는 뜻이 남아야 한다.
- 표시를 눈에 안 띄게 숨기지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| chip (기본) | `segmentTrack` 바탕의 알약(높이 28). 반짝이 아이콘 12 + "AI 생성". |
| note | 바탕 없는 한 줄. 아이콘 13 + `caption` 글자. |
| overlay | 그림의 왼쪽 아래(8, 8)에 검은 반투명(55%) 알약. 흰 아이콘 11 + 흰 굵은 글자. 기본 글자 "AI 생성 그림". |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| chip | 높이 28, 좌우 10 | |
| overlay | 좌우 8, 위아래 3 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| chip | 기본 모습이다. |
| note | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { AiMark } from '@/ui/AiMark';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `variant` | `'chip' \| 'note' \| 'overlay'` | `'chip'` | 선택 | `"chip"`(기본), `"note"`, `"overlay"`. |
| `text` | `string` | — | 선택 | 표시 글자. 기본은 chip·note "AI 생성", overlay "AI 생성 그림". |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| chip | 높이 28, 좌우 10, pill, `segmentTrack` 바탕, 아이콘 12 선 2.3, 글자 `smallStrong` `textSecondary` |
| note | 아이콘 13, `caption` `textSecondary`, 사이 5 |
| overlay | 바탕 `rgba(0,0,0,0.55)`, 위치 왼쪽 8·아래 8(절대 위치), 흰 글자 |
## 접근성
- 접근성 이름은 표시 글자 그대로다("AI 생성").
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| chip 글자 (13px 굵게) | `textSecondary` | `segmentTrack` | 3.83:1 미달 | 6.42:1 통과 | 4.52:1 통과 | 6.26:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: AI가 만든 결과 바로 옆에 붙인다.
- 잘못한 예: 사람이 만든 내용에 붙이지 않는다.
## 함께 보는 컴포넌트
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
- [FeedbackCard](https://developers.moring.co/tds/components/feedback-card.md): 문제를 채점한 뒤 결과("정답!", "오답")와 해설을 보여 주는 카드다.
---
# AppText
앱의 모든 글자를 그리는 컴포넌트다. 글자 스타일 11종 중 하나를 `variant`로 고르면 크기, 줄 높이, 굵기, 자간이 정해진다.
- 분류: 데이터 표시
- 가져오기: `import { AppText } from '@/ui/AppText'`
- 코드 위치: `apps/mobile/src/ui/AppText.tsx:15` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/app-text
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/app-text 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 화면의 모든 글자에 React Native의 `Text` 대신 쓴다.
- 글자 크기와 굵기는 `variant`로 고르고, 색은 `color`에 토큰을 넘긴다.
- 굵기만 바꾸려면 `weight`를 쓴다.
### 쓰지 않는다
- `Text`를 직접 쓰지 않는다. 앱 설정의 글자 크기 배율과 시험지 서체가 적용되지 않는다.
- `style`로 `fontSize`만 바꾸지 않는다. 줄 높이를 함께 주어야 한다(없으면 크기의 1.34배로 자동 보정되지만 의도한 값이 아니다).
- 글자 색을 HEX로 직접 쓰지 않는다. `color.text` 같은 토큰을 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| variant | `display`, `title`, `headline`, `bodyStrong`, `body`(기본), `small`, `smallStrong`, `caption`, `button`, `metric`, `question`. 값은 글자 페이지의 표에 있다. |
| weight | `regular`~`extrabold`. 앱의 글꼴 이름이 굵기별로 나뉘어 있어서 굵기는 글꼴을 바꾸는 방식으로 적용된다. |
| align | `left`, `center`, `right` 같은 글자 정렬. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 글자 크기 설정 | 0.92 / 1 / 1.12 / 1.24배 | 앱 설정의 글자 크기 배율이 크기와 줄 높이에 곱해진다. 시스템 글자 크기는 최대 1.6배까지 반영된다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 시험지 서체 | `PaperType` 안에서는 모든 글자가 Noto Serif KR로 바뀐다. |
## 사용 방법
### 기본
```tsx
import { AppText } from '@/ui/AppText';
import { color } from '@/ui/tokens';
오늘의 복습자료 18개 · 복습 12개
```
### 굵기와 정렬
```tsx
완료했어요
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `variant` | `TextVariant` | `'body'` | 선택 | 글자 스타일 이름. 기본 `"body"`. |
| `color` | `string` | — | 선택 | 글자 색. 기본 `color.text`. |
| `weight` | `keyof typeof font` | — | 선택 | 굵기(`"regular"` ~ `"extrabold"`). 지정하면 `variant`의 굵기를 덮는다. |
| `align` | `TextStyle['textAlign']` | — | 선택 | 글자 정렬. |
| `style` | `(React Native)` | — | 선택 | 추가 스타일. `fontSize`를 바꾸면 줄 높이가 없을 때 크기의 1.34배로 자동으로 정해진다. |
나머지 속성은 `TextProps`를 그대로 받는다.
위에 적지 않은 나머지 속성(`numberOfLines`, `adjustsFontSizeToFit`, `accessibilityRole` 등)은 React Native `Text`를 그대로 받는다. 시스템 글자 크기 배율은 `maxFontSizeMultiplier=1.6`으로 제한된다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 글꼴 | Pretendard (굵기별 파일). 시험지 서체 안에서는 Noto Serif KR |
| 기본 색 | `text` |
| 글자 크기 배율 | 앱 설정 배율 × (크기, 줄 높이)를 소수 첫째 자리(줄 높이는 정수)로 반올림 |
| 여백 | `includeFontPadding: false`, 세로 가운데 정렬 |
## 접근성
- 글자 크기 배율에서 글자가 커져도 잘리지 않게 부모의 높이를 고정하지 않는다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 본문 (15px) | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
| 보조 글자 (13px) | `textSecondary` | `surface` | 4.23:1 미달 | 6.95:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: `variant`와 색 토큰으로 지정한다.
- 잘못한 예: 크기만 바꾸고 줄 높이를 정하지 않으면 글자 아래가 잘린다.
## 함께 보는 컴포넌트
- [PaperType](https://developers.moring.co/tds/components/paper-type.md): 안쪽의 모든 글자를 시험지 서체(Noto Serif KR)로 바꾸는 감싸개다. 모의고사 시험지 화면에 쓴다.
- [MathText](https://developers.moring.co/tds/components/math-text.md): 수식이 섞인 글을 그린다. 서버가 미리 그려 둔 수식(SVG)을 글자 사이에 끼워 넣고, 없으면 읽을 수 있는 글자로 대신 보여 준다.
---
# PaperType
안쪽의 모든 글자를 시험지 서체(Noto Serif KR)로 바꾸는 감싸개다. 모의고사 시험지 화면에 쓴다.
- 분류: 데이터 표시
- 가져오기: `import { PaperType } from '@/ui/typeface'`
- 코드 위치: `apps/mobile/src/ui/typeface.tsx:8` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/paper-type
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/paper-type 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 모의고사 문제지처럼 인쇄된 시험지처럼 보여야 하는 화면 전체를 감싼다.
- 안의 `AppText`, `MathText`, `Blocks`가 모두 명조체로 바뀌고 박스와 표는 시험지의 줄 모양을 따른다.
### 쓰지 않는다
- 일반 학습 화면에 쓰지 않는다. 일반 화면의 글꼴은 Pretendard 하나다.
- 화면 일부에만 쓰지 않는다. 글꼴이 섞이면 어색하다.
## 종류
종류가 없다.
## 크기
크기가 없다. 자식을 그대로 감싼다.
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 굵기 대응 | Pretendard의 Bold, SemiBold, ExtraBold는 Noto Serif KR Bold로, 나머지는 Regular로 바뀐다. |
## 사용 방법
### 기본
```tsx
import { PaperType } from '@/ui/typeface';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 시험지 서체로 그릴 내용. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 글꼴 | Noto Serif KR Regular, Bold |
| 구현 | React Context로 안쪽 `AppText`에 알린다 |
## 접근성
- 글꼴만 바꾼다. 글자 크기와 접근성 이름은 그대로다.
## 잘한 예와 잘못한 예
- 잘한 예: 시험지 화면 전체를 한 번에 감싼다.
- 잘못한 예: 일반 화면 안에 일부만 명조체로 두지 않는다.
## 함께 보는 컴포넌트
- [AppText](https://developers.moring.co/tds/components/app-text.md): 앱의 모든 글자를 그리는 컴포넌트다. 글자 스타일 11종 중 하나를 `variant`로 고르면 크기, 줄 높이, 굵기, 자간이 정해진다.
- [MathText](https://developers.moring.co/tds/components/math-text.md): 수식이 섞인 글을 그린다. 서버가 미리 그려 둔 수식(SVG)을 글자 사이에 끼워 넣고, 없으면 읽을 수 있는 글자로 대신 보여 준다.
- [Blocks](https://developers.moring.co/tds/components/blocks.md): 문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
---
# SubjectIcon
과목을 나타내는 둥근 아이콘 타일이다. 과목마다 기호(Σ, Aa, 책, 건물, 잎, 지구, …)와 색이 정해져 있다.
- 분류: 데이터 표시
- 가져오기: `import { SubjectIcon, SubjectGlyph } from '@/ui/SubjectIcon'`
- 코드 위치: `apps/mobile/src/ui/SubjectIcon.tsx:28` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/subject-icon
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/subject-icon 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 과목 목록, 과목 선택, 복습 행의 앞에 과목을 나타낼 때 쓴다.
- 메인 보드처럼 눈에 띄어야 하면 `filled`, 온보딩이나 복습 행처럼 가벼워야 하면 `soft`.
- 사용자가 과목에 사진을 지정했으면 `image`에 넘긴다.
### 쓰지 않는다
- 과목이 아닌 분류에 쓰지 않는다.
- 과목 이름 없이 색만으로 구분하지 않는다. 이름을 옆에 글자로 쓴다.
- 타일 모양을 바꾸지 않는다. 모서리는 크기에 비례해 정해진다.
## 종류
| 이름 | 설명 |
| --- | --- |
| filled (기본) | 채운 그라데이션 타일(`fill` 시작 → 끝, 왼쪽 위 → 오른쪽 아래), 흰 기호. |
| soft | 옅은 면(`soft`) 위에 색 기호(`glyph`). |
| image | `image`가 있으면 사진을 같은 둥근 타일로 보여 준다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 48 | `size`로 바꾼다. 모서리는 크기의 28%, 기호는 크기의 48%. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| filled | 기본 모습이다. |
| soft | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { SubjectIcon } from '@/ui/SubjectIcon';
```
### 속성(props)
#### SubjectIcon
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `icon` | `SubjectKey \| string` | — | 필수 | 기호 종류. `"math"`, `"english"`, `"korean"`, `"history"`, `"science"`, `"social"`, `"other"` 중 하나(다른 문자열은 `other`의 점 셋). |
| `colorKey` | `ColorKey \| string` | — | 필수 | 과목 팔레트의 색 이름. `"blue"`, `"green"`, `"orange"`, `"red"`, `"purple"`, `"teal"`, `"yellow"`, `"gray"`. |
| `size` | `number` | `48` | 선택 | 한 변의 길이(px). 기본 48. |
| `variant` | `'filled' \| 'soft'` | `'filled'` | 선택 | `"filled"`(기본) 또는 `"soft"`. |
| `image` | `string \| null` | — | 선택 | 과목 사진(작은 데이터 주소). 있으면 사진을 타일에 보여 준다. |
#### SubjectGlyph
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `icon` | `SubjectKey \| string` | — | 필수 | 기호 종류. |
| `size` | `number` | — | 필수 | 기호 크기(px). |
| `tint` | `string` | — | 필수 | 기호 색. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 타일 | 크기 × 크기, 모서리 = 크기 × 0.28 |
| 기호 | 크기 × 0.48, 선 2. 영어는 아이콘이 아니라 글자 "Aa"(Bold, 크기 × 0.82) |
| 색 | 과목 팔레트: `filled`는 `fill` 그라데이션 + 흰 기호, `soft`는 `soft` 면 + `glyph` 기호 |
| 기본 과목 색 | 수학 blue, 영어 green, 국어 gray, 한국사 red, 과학 teal, 사회 orange, 기타 gray |
## 접근성
- 타일은 장식이다. 과목 이름을 옆에 글자로 함께 보여 준다.
- 과목 색은 구분을 돕는 보조 표시일 뿐이다.
## 잘한 예와 잘못한 예
- 잘한 예: 과목 이름을 옆에 글자로 쓴다.
- 잘못한 예: 이름 없이 타일만 쓰면 색을 구분하지 못하는 사용자가 알 수 없다.
## 함께 보는 컴포넌트
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
---
# Avatar
사용자의 사진, 또는 닉네임 첫 글자를 브랜드 그라데이션 원 위에 보여 준다. `NameLine`은 닉네임, 공식 배지, @아이디를 한 줄로 놓는다.
- 분류: 데이터 표시
- 가져오기: `import { Avatar, NameLine } from '@/ui/community/Avatar'`
- 코드 위치: `apps/mobile/src/ui/community/Avatar.tsx:10` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/avatar
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/avatar 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 커뮤니티의 글쓴이, 프로필 화면에 쓴다.
- 공식 계정은 `official`로 오른쪽 아래에 파란 체크 배지를 붙인다.
- 이름 줄은 `NameLine`으로 쓴다. 긴 @아이디가 먼저 …으로 줄어든다.
### 쓰지 않는다
- 사진이 없는데 빈 회색 원을 쓰지 않는다. 첫 글자 원이 자동으로 나온다.
- 공식 배지를 일반 사용자에게 붙이지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 사진 | `uri` 또는 `path`가 있으면 사진. |
| 첫 글자 | 사진이 없으면 `gradient` 원에 닉네임 첫 글자(영문은 대문자). 글자 크기는 지름의 0.42. |
| official | 배지 크기는 max(14, 지름 × 0.36). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 지름 40 | `size`로 바꾼다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { Avatar, NameLine } from '@/ui/community/Avatar';
```
### 속성(props)
#### Avatar
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `path` | `string \| null` | — | 선택 | 저장소에 있는 사진 경로. |
| `uri` | `string \| null` | — | 선택 | 사진 주소. `path`보다 우선한다. |
| `name` | `string` | — | 필수 | 닉네임. 첫 글자 원과 접근성에 쓰인다. |
| `size` | `number` | `40` | 선택 | 지름(px). 기본 40. |
| `official` | `boolean` | — | 선택 | `true`이면 공식 배지를 붙인다. |
#### NameLine
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `nickname` | `string` | — | 필수 | 닉네임(굵게, 한 줄). |
| `handle` | `string` | — | 선택 | @아이디. 없으면 생략. |
| `official` | `boolean` | — | 선택 | `true`이면 닉네임 옆에 공식 배지. |
| `size` | `'body' \| 'small'` | `'body'` | 선택 | `"body"`(기본) 또는 `"small"`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| Avatar | 지름 40, 원, 사진은 `photo` 바탕. 첫 글자 원은 `gradient`(대각선), 글자 white Bold |
| 배지 | `BadgeCheck`, 채움 `primary`, 아이콘 white, 위치 오른쪽 아래 -2, 흰 원 배경 |
| NameLine | 닉네임 `bodyStrong`(최대 66%), 배지 15, @아이디 `small` `textTertiary`, 사이 4 |
## 접근성
- 사진은 `accessibilityIgnoresInvertColors`(색 반전에서 제외).
- 이름은 닉네임 글자로 읽힌다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 첫 글자 (17px 굵게) | `onPrimaryStrong` | `gradient` (양 끝 중 낮은 값) | 3.08:1 미달 | 3.08:1 미달 | 4.60:1 통과 | 5.48:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 사진이 없으면 첫 글자 원이 나온다.
- 잘못한 예: 닉네임이 길면 NameLine은 @아이디부터 줄인다. 닉네임을 두 줄로 늘이지 않는다.
## 함께 보는 컴포넌트
- [BrandMark](https://developers.moring.co/tds/components/brand-mark.md): Mordy의 심벌(웃는 얼굴이 복습 화살표로 이어지는 모양)을 브랜드 타일 위에 그린 이미지다.
- [WellRow](https://developers.moring.co/tds/components/well-row.md): 패널 안의 목록 한 줄이다. 옅은 홈(`well`)에 앞 아이콘 타일, 제목, 보조 줄, 뒤 요소가 놓인다.
---
# BrandMark
Mordy의 심벌(웃는 얼굴이 복습 화살표로 이어지는 모양)을 브랜드 타일 위에 그린 이미지다.
- 분류: 데이터 표시
- 가져오기: `import { BrandMark } from '@/ui/BrandMark'`
- 코드 위치: `apps/mobile/src/ui/BrandMark.tsx:5` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/brand-mark
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/brand-mark 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- Mordy 앱의 소개, 로그인, 정보 화면에서 앱을 나타낼 때 쓴다.
### 쓰지 않는다
- Morit 화면에 쓰지 않는다. 이 이미지는 Mordy의 로고이고 서비스 색을 따르지 않는다.
- 타일 바깥의 흰 심벌만 따로 쓰지 않는다. 흰 배경에서는 보이지 않는다. 항상 파란 타일 위에 둔다.
- 이미지 비율을 바꾸지 않는다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 40 × 40 | `size`로 바꾼다. 원본은 384 × 384다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { BrandMark } from '@/ui/BrandMark';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `size` | `number` | `40` | 선택 | 한 변의 길이(px). 기본 40. |
| `style` | `StyleProp` | — | 선택 | 이미지에 적용할 추가 스타일. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 원본 | apps/mobile/assets/brand/tile.png, 384 × 384, 투명 배경 PNG |
| 접근성 이름 | "Mordy 로고" |
| 색 | 고정(파란 그라데이션 타일). 서비스 색 토큰을 따르지 않는다. |
## 접근성
- 이미지 이름은 "Mordy 로고"다.
## 잘한 예와 잘못한 예
- 잘한 예: 원본 비율 그대로, 정사각형으로 쓴다.
- 잘못한 예: 가로로 늘이면 로고가 찌그러진다.
## 함께 보는 컴포넌트
- [Avatar](https://developers.moring.co/tds/components/avatar.md): 사용자의 사진, 또는 닉네임 첫 글자를 브랜드 그라데이션 원 위에 보여 준다. `NameLine`은 닉네임, 공식 배지, @아이디를 한 줄로 놓는다.
- [AppText](https://developers.moring.co/tds/components/app-text.md): 앱의 모든 글자를 그리는 컴포넌트다. 글자 스타일 11종 중 하나를 `variant`로 고르면 크기, 줄 높이, 굵기, 자간이 정해진다.
---
# ProgressRing
진행률을 둥근 고리로 보여 준다. `ProgressRing`은 작은 고정 고리, `AnimatedRing`은 나타날 때 0%에서 값까지 채워지는 큰 고리다.
- 분류: 데이터 표시
- 가져오기: `import { ProgressRing, AnimatedRing } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:12` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/progress-ring
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/progress-ring 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 과목 목록처럼 작은 곳에서 진도를 숫자와 함께 보일 때 `ProgressRing`을 쓴다.
- 문제 풀이 결과, 시험 목표처럼 화면의 주인공이 되는 값에는 `AnimatedRing`을 쓰고, 가운데에 숫자나 글자를 `children`으로 넣는다.
### 쓰지 않는다
- 값이 없는데 0%로 보이지 않는다. `ProgressRing`에 `null`을 넘기면 "기록 없음"과 "–"로 표시한다.
- 한 화면에 고리를 여러 개 큰 크기로 놓지 않는다. 주인공 하나만 크게 쓴다.
- 여러 부분으로 나뉜 값은 `DonutChart`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| ProgressRing | 고정 고리. 가운데 글자는 퍼센트(또는 `label`). |
| AnimatedRing | 900ms 동안 0에서 값까지 채워진다(150ms 뒤 시작). 가운데는 `children`. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| ProgressRing | 지름 52, 선 5 | 글자 크기는 지름의 0.24. |
| AnimatedRing | 지름 140, 선 12 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 값 있음 | `primary` 호가 `track` 고리 위에 그려진다. 끝은 둥글다. |
| 값 없음 | `null`이면 호가 없고 "–"를 보인다. |
## 사용 방법
### ProgressRing
```tsx
import { ProgressRing } from '@/ui/charts';
{/* 0~1, 없으면 null */}
```
### AnimatedRing
```tsx
import { AnimatedRing } from '@/ui/charts';
{score}%
```
### 속성(props)
#### ProgressRing
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `value` | `number \| null` | — | 필수 | 진행률(0~1). 없으면 `null`. |
| `size` | `number` | `52` | 선택 | 지름(px). 기본 52. |
| `stroke` | `number` | `5` | 선택 | 선 두께(px). 기본 5. |
| `tint` | `string` | `color.primary` | 선택 | 호의 색. 기본 `color.primary`. |
| `track` | `string` | `color.track` | 선택 | 바탕 고리 색. 기본 `color.track`. |
| `label` | `string` | — | 선택 | 가운데 글자. 없으면 퍼센트. |
| `labelSize` | `number` | — | 선택 | 가운데 글자 크기. |
| `labelColor` | `string` | `color.text` | 선택 | 가운데 글자 색. 기본 `color.text`. |
#### AnimatedRing
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `value` | `number` | — | 필수 | 진행률(0~1). |
| `size` | `number` | `140` | 선택 | 지름(px). 기본 140. |
| `stroke` | `number` | `12` | 선택 | 선 두께(px). 기본 12. |
| `tint` | `string` | `color.primary` | 선택 | 호의 색. |
| `track` | `string` | `color.track` | 선택 | 바탕 고리 색. |
| `delay` | `number` | `150` | 선택 | 채우기 시작 전 기다리는 시간(ms). 기본 150. |
| `children` | `ReactNode` | — | 선택 | 고리 가운데에 놓을 요소. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 호 | 시작은 12시 방향, 시계 방향, 끝이 둥글다(`round`) |
| 색 | 호 `primary`, 바탕 `track` |
| 움직임 | `AnimatedRing` 900ms, out 곡선, 150ms 지연 |
## 접근성
- `ProgressRing`의 이름은 퍼센트(또는 `label`)이고 값이 없으면 "기록 없음"이다.
- 가운데 숫자를 함께 보인다. 호의 길이만으로 값을 전달하지 않는다.
## 잘한 예와 잘못한 예
- 잘한 예: 가운데에 숫자를 함께 쓴다.
- 잘못한 예: 숫자 없이 고리만 있으면 정확한 값을 알 수 없다.
## 함께 보는 컴포넌트
- [DonutChart](https://developers.moring.co/tds/components/donut-chart.md): 전체를 여러 부분으로 나눠 보여 주는 도넛 차트다. 부분 사이에 3의 틈이 있다. 시험 계획의 익힘/학습 중/시작 전 비율에 쓴다.
- [ProgressBar](https://developers.moring.co/tds/components/progress-bar.md): 진행률을 가로 막대로 보여 준다. 채운 부분은 기본으로 서비스 주색 그라데이션이다.
- [SegmentedProgress](https://developers.moring.co/tds/components/segmented-progress.md): 문제 하나에 칸 하나씩, 푼 문제만큼 칸이 채워지는 진행 막대다. 퀴즈 맨 위에 쓴다.
---
# ProgressBar
진행률을 가로 막대로 보여 준다. 채운 부분은 기본으로 서비스 주색 그라데이션이다.
- 분류: 데이터 표시
- 가져오기: `import { ProgressBar } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:110` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/progress-bar
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/progress-bar 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 자료 처리, 단원 진도처럼 한 줄에서 진행 정도를 보일 때 쓴다.
- 막대 옆이나 위에 퍼센트 글자를 함께 쓴다.
### 쓰지 않는다
- 문제를 한 칸씩 푸는 흐름에는 쓰지 않는다. `SegmentedProgress`를 쓴다.
- 값이 없는 상태에 빈 막대만 두지 않는다. 글자("준비 중")를 함께 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 그라데이션 (기본) | `tint`를 생략하면 `gradient`(왼쪽 → 오른쪽). |
| 단색 | `tint`에 색 문자열을 주면 그 색으로 채운다. |
| 2색 | `tint`에 `[시작, 끝]`을 주면 그 그라데이션으로 채운다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 8 | `height`로 바꾼다. 모서리는 높이와 같아서 양 끝이 둥글다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 0% | 채움이 없고 `track` 바탕만 보인다. |
| 진행 중 | 기본 모습이다. |
| 100% | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { ProgressBar } from '@/ui/charts';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `value` | `number` | — | 필수 | 진행률(0~1). |
| `height` | `number` | `8` | 선택 | 높이(px). 기본 8. |
| `tint` | `string \| readonly [string, string]` | — | 선택 | 채움 색. 생략하면 `gradient`, 문자열이면 단색, `[시작, 끝]`이면 그 그라데이션. |
| `track` | `string` | `color.track` | 선택 | 바탕 색. 기본 `color.track`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 높이와 모서리 | 높이 8, 모서리는 높이와 같다 |
| 색 | 바탕 `track`, 채움 `gradient` |
## 접근성
- 역할은 `progressbar`, 값 0~100이 전달된다.
- 퍼센트 글자를 함께 쓴다.
## 잘한 예와 잘못한 예
- 잘한 예: 퍼센트 글자를 옆에 쓴다.
- 잘못한 예: 막대만 있으면 정확한 값을 모른다.
## 함께 보는 컴포넌트
- [SegmentedProgress](https://developers.moring.co/tds/components/segmented-progress.md): 문제 하나에 칸 하나씩, 푼 문제만큼 칸이 채워지는 진행 막대다. 퀴즈 맨 위에 쓴다.
- [ProgressRing](https://developers.moring.co/tds/components/progress-ring.md): 진행률을 둥근 고리로 보여 준다. `ProgressRing`은 작은 고정 고리, `AnimatedRing`은 나타날 때 0%에서 값까지 채워지는 큰 고리다.
---
# SegmentedProgress
문제 하나에 칸 하나씩, 푼 문제만큼 칸이 채워지는 진행 막대다. 퀴즈 맨 위에 쓴다.
- 분류: 데이터 표시
- 가져오기: `import { SegmentedProgress } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:127` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/segmented-progress
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/segmented-progress 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 문제 풀이 화면의 위쪽에서 "몇 문제 중 몇 문제"를 보여 줄 때 쓴다.
### 쓰지 않는다
- 칸이 30개를 넘으면 쓰지 않는다. 칸이 너무 얇아진다. `ProgressBar`를 쓴다.
- 진행 정도가 연속적인 값(퍼센트)일 때 쓰지 않는다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 7, 칸 사이 6 | 칸 폭은 전체 폭을 `total`로 나눈다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 채워짐 | 칸이 `gradient`로 380ms 동안 왼쪽에서 오른쪽으로 채워진다. |
| 비어 있음 | `track` 색. |
## 사용 방법
### 기본
```tsx
import { SegmentedProgress } from '@/ui/charts';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `total` | `number` | — | 필수 | 전체 칸 수(문제 수). 0이면 칸 1개. |
| `done` | `number` | — | 필수 | 채울 칸 수. |
| `height` | `number` | `7` | 선택 | 높이(px). 기본 7. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 칸 | 높이 7, 모서리는 높이와 같음, 사이 6, `track` 바탕, `gradient` 채움 |
| 움직임 | 채움 380ms, out 곡선 |
## 접근성
- 역할은 `progressbar`, 값은 0~`total`이다.
- "3/8문제" 같은 글자를 함께 쓴다.
## 잘한 예와 잘못한 예
- 잘한 예: 문제 수가 적고 글자로도 알린다.
- 잘못한 예: 칸이 너무 많으면 칸이 얇아져 보이지 않는다.
## 함께 보는 컴포넌트
- [ProgressBar](https://developers.moring.co/tds/components/progress-bar.md): 진행률을 가로 막대로 보여 준다. 채운 부분은 기본으로 서비스 주색 그라데이션이다.
- [OptionRow](https://developers.moring.co/tds/components/option-row.md): 퀴즈 보기 한 줄이다. 고르기 전, 고른 뒤, 채점 뒤(정답, 오답)의 모습이 정해져 있다.
---
# DonutChart
전체를 여러 부분으로 나눠 보여 주는 도넛 차트다. 부분 사이에 3의 틈이 있다. 시험 계획의 익힘/학습 중/시작 전 비율에 쓴다.
- 분류: 차트
- 가져오기: `import { DonutChart } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:64` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/donut-chart
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/donut-chart 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 전체가 2~4개 부분으로 나뉘는 값(익힘, 학습 중, 시작 전)을 보여 줄 때 쓴다.
- 이름과 비율을 옆에 범례로 함께 쓴다. 가운데에는 총합이나 대표 숫자를 `children`으로 넣는다.
### 쓰지 않는다
- 부분이 5개를 넘으면 쓰지 않는다.
- 값 하나의 진행률에는 쓰지 않는다. `ProgressRing`을 쓴다.
- 범례 없이 색만으로 부분을 구분하지 않는다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 지름 132, 선 16 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 값이 없을 때 | 모든 `value`의 합이 0이면 `track` 고리만 보인다. |
| 부분이 하나 | 틈 없이 이어진다. |
## 사용 방법
### 기본
```tsx
import { DonutChart } from '@/ui/charts';
{total}
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `parts` | `{ value: number; tint: string }[]` | — | 필수 | 부분 목록. 각 항목 `{ value, tint }`. 0 이하는 그리지 않는다. |
| `size` | `number` | `132` | 선택 | 지름(px). 기본 132. |
| `stroke` | `number` | `16` | 선택 | 선 두께(px). 기본 16. |
| `track` | `string` | `color.track` | 선택 | 바탕 고리 색. 기본 `color.track`. |
| `children` | `ReactNode` | — | 선택 | 가운데에 놓을 요소. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 시작 | 12시 방향, 시계 방향, 순서대로 이어 그린다 |
| 틈 | 부분이 둘 이상이면 각 부분 끝에 3의 틈 |
| 색 | 부분마다 `tint`. 의미 색(`mint`=익힘, `primary`=학습 중, `lineStrong`=시작 전)을 쓴다 |
## 접근성
- 앱 코드의 차트는 이름이 없다. 부모에 "익힘 12, 학습 중 7, 시작 전 5" 같은 이름을 붙이고 범례를 글자로 함께 쓴다. 이 문서의 미리보기는 그렇게 만들었다.
## 잘한 예와 잘못한 예
- 잘한 예: 범례에 이름과 개수를 쓴다.
- 잘못한 예: 부분이 너무 많으면 구분할 수 없다.
## 함께 보는 컴포넌트
- [ProgressRing](https://developers.moring.co/tds/components/progress-ring.md): 진행률을 둥근 고리로 보여 준다. `ProgressRing`은 작은 고정 고리, `AnimatedRing`은 나타날 때 0%에서 값까지 채워지는 큰 고리다.
- [HourBars](https://developers.moring.co/tds/components/hour-bars.md): 값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
---
# Sparkline
정답률 같은 작은 추이를 보여 주는 선 그림이다. 값이 없는 날은 선이 끊기고, 마지막 점이 크게 표시된다.
- 분류: 차트
- 가져오기: `import { Sparkline } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:151` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/sparkline
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/sparkline 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 카드 안에서 최근 며칠의 변화를 가볍게 보여 줄 때 쓴다.
- 값은 0~1이다. 값이 없는 날은 `null`을 넣는다.
### 쓰지 않는다
- 정확한 값을 읽어야 하는 곳에는 쓰지 않는다. 축이 없다. `HourBars`나 `AreaLine`을 쓴다.
- 값이 2개 미만이면 쓰지 않는다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 44, 폭은 `width`로 지정 | 위아래 안쪽 여백 5. 폭은 필수다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 값이 없는 날 | `null`이면 선이 끊기고 점도 없다. |
| 마지막 점 | 지름 8의 채운 점. 나머지는 지름 4.8의 속 빈 점. |
## 사용 방법
### 기본
```tsx
import { Sparkline } from '@/ui/charts';
(d.answered ? d.correct / d.answered : null))} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `values` | `(number \| null)[]` | — | 필수 | 0~1 값 목록. 값이 없는 날은 `null`. |
| `width` | `number` | — | 필수 | 폭(px). 필수. |
| `height` | `number` | `44` | 선택 | 높이(px). 기본 44. |
| `tint` | `string` | `color.primary` | 선택 | 선과 점의 색. 기본 `color.primary`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 선 | 두께 2.4, 둥근 끝과 꺾임 |
| 기준선 | 값 0.5 위치에 점선(`line`, 3 4) |
| 점 | 지름 4.8(속은 `surface`), 마지막은 지름 8 채움 |
## 접근성
- 앱 코드의 차트에는 이름이 없다. 부모에 "최근 7일 정답률" 같은 이름과 마지막 값을 글자로 붙인다.
## 잘한 예와 잘못한 예
- 잘한 예: 옆에 최근 값을 숫자로 함께 쓴다.
- 잘못한 예: 축이 없으므로 정확한 값을 읽어야 하는 곳에는 쓰지 않는다.
## 함께 보는 컴포넌트
- [AreaLine](https://developers.moring.co/tds/components/area-line.md): 누적 값을 선과 옅은 면으로 보여 주는 차트다. 모든 점에 점이 찍히고 마지막 점에 말풍선이 뜬다. 리포트의 누적 문제 수, 누적 학습 시간에 쓴다.
- [HourBars](https://developers.moring.co/tds/components/hour-bars.md): 값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
---
# HourBars
값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
- 분류: 차트
- 가져오기: `import { HourBars } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:207` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/hour-bars
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/hour-bars 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 일별·주별 학습 시간 또는 푼 문제 수를 비교할 때 쓴다.
- `format="count"`는 문제 수, `"time"`(기본)은 초 단위 학습 시간이다.
- 목표선(`goal`)을 점선으로 그을 수 있다.
### 쓰지 않는다
- 시간 순서가 없는 항목 비교에는 쓰지 않는다.
- 막대가 14개를 넘으면 쓰지 않는다. `labelEvery`로 라벨을 줄이거나 `AreaLine`을 쓴다.
- 말풍선 없이 값을 숨기지 않는다. 막대를 눌러 값을 볼 수 있게 한다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 기본 | 둥근 막대(모서리 = 폭의 절반). 고른 막대는 `gradient`, 나머지는 `primary` 20~34% 투명. |
| block | 리포트 모양. 각진 막대(모서리 7), 네이비 말풍선, 고른 요일 라벨이 `primary`. |
| 축 형식 | `time`: 0h·1h·2h… 또는 분. `count`: 0·중간값·최댓값(정수). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 막대 높이 140, 막대 폭 22 | 위쪽 34는 말풍선 자리다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 막대 선택 | 막대를 누르면 그 막대가 진해지고 위에 말풍선("2시간 34분", "48문제")이 뜬다. 다시 누르면 해제. |
| 값 0 | 높이 4의 `track` 점선 막대. |
## 사용 방법
### 학습 시간
```tsx
import { HourBars } from '@/ui/charts';
({ label: d.weekday, value: d.seconds }))}
selected={sel}
onSelect={setSel}
bubble={sel != null ? durationShort(days[sel].seconds) : null}
/>
```
### 푼 문제 수와 목표선
```tsx
({ label: d.weekday, value: d.answered }))} selected={sel} onSelect={setSel} bubble={`${days[sel]?.answered}문제`} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `data` | `{ label: string; value: number; part?: number; part2?: number; a11y?: string }[]` | — | 필수 | 막대 목록. 각 항목: `label`, `value`(초 또는 개수), `part`/`part2`(위·아래에 겹쳐 그릴 부분 값), `a11y`(접근성 이름). |
| `height` | `number` | `140` | 선택 | 막대 영역 높이(px). 기본 140. |
| `selected` | `number \| null` | — | 선택 | 선택된 막대의 번호(없으면 `null`). |
| `onSelect` | `(i: number) => void` | — | 선택 | 막대를 눌렀을 때 번호와 함께 호출된다. |
| `bubble` | `string \| null` | — | 선택 | 선택된 막대 위에 띄울 글자. |
| `labelEvery` | `number` | `1` | 선택 | N번째 막대마다 라벨을 쓴다. 기본 1. |
| `barWidth` | `number` | `22` | 선택 | 막대 폭(px). 기본 22. |
| `format` | `'time' \| 'count'` | `'time'` | 선택 | `"time"`(기본) 또는 `"count"`. |
| `goal` | `number \| null` | — | 선택 | 목표 값. 점선으로 긋는다. |
| `bubbleColor` | `string` | `color.primary` | 선택 | 말풍선 색. 기본 `color.primary`. |
| `block` | `boolean` | — | 선택 | `true`이면 리포트 모양. |
| `partColor` | `string` | — | 선택 | `part` 부분의 색. |
| `part2Color` | `string` | — | 선택 | `part2` 부분의 색. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 축 | 시간: 1시간 이상이면 `0h 1h 2h…`(최대 3단계), 미만이면 15분 단위. 문제 수: 0·중간·최대의 정수 |
| 막대 | 폭 22, 최소 높이 폭 × 0.6, 선택 안 된 막대 `primary` 20%→34%, 선택 `gradient` |
| 격자선 | `line` 투명도 0.7, 목표선 `mint` 점선 1.6(5 4) |
| 말풍선 | 높이 26, 모서리 13(`block`은 9), `primary`(`block`은 네이비), 글자 `caption` Bold white |
## 접근성
- 각 막대는 `button`이고 이름은 `a11y` 또는 라벨이다. 웹 미리보기는 "{요일}, {값}"으로 읽는다.
- 값은 말풍선 글자와 축 숫자로 확인할 수 있다. 색만으로 전달하지 않는다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 말풍선 글자 (11.5px 굵게) | `onPrimary` | `primary` | 3.93:1 미달 | 3.31:1 미달 | 4.60:1 통과 | 7.25:1 통과 | 4.5:1 |
| 축 숫자 (11.5px) | `textTertiary` | `surface` | 2.53:1 미달 | 3.50:1 미달 | 2.97:1 미달 | 4.45:1 미달 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 요일 라벨과 값 말풍선을 함께 둔다.
- 잘못한 예: 막대가 너무 많으면 라벨이 겹친다.
## 함께 보는 컴포넌트
- [AreaLine](https://developers.moring.co/tds/components/area-line.md): 누적 값을 선과 옅은 면으로 보여 주는 차트다. 모든 점에 점이 찍히고 마지막 점에 말풍선이 뜬다. 리포트의 누적 문제 수, 누적 학습 시간에 쓴다.
- [Sparkline](https://developers.moring.co/tds/components/sparkline.md): 정답률 같은 작은 추이를 보여 주는 선 그림이다. 값이 없는 날은 선이 끊기고, 마지막 점이 크게 표시된다.
- [CalendarHeat](https://developers.moring.co/tds/components/calendar-heat.md): 한 달 달력에서 날마다 공부한 시간을 색의 진하기(5단계)로 보여 주는 차트다.
---
# AreaLine
누적 값을 선과 옅은 면으로 보여 주는 차트다. 모든 점에 점이 찍히고 마지막 점에 말풍선이 뜬다. 리포트의 누적 문제 수, 누적 학습 시간에 쓴다.
- 분류: 차트
- 가져오기: `import { AreaLine } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:291` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/area-line
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/area-line 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 월별 누적 문제 수나 학습 시간처럼 계속 늘어나는 값을 보여 줄 때 쓴다.
### 쓰지 않는다
- 증가·감소가 오가는 값에는 쓰지 않는다. 누적(계속 늘기만 하는 값)용이다.
- 점이 2개 미만이면 선이 없다. 쓰지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| count (기본) | 축이 0·중간·최대의 정수. 말풍선은 숫자. |
| time | 축이 시간(h) 또는 분. 말풍선은 "n시간 n분". |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 그래프 높이 150 | 위쪽 36은 말풍선 자리. 축 라벨 폭 38. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 마지막 점 | 지름 9의 채운 점과 위에 말풍선. 나머지 점은 지름 6의 속 빈 점. |
## 사용 방법
### 기본
```tsx
import { AreaLine } from '@/ui/charts';
m.total)} labels={months.map((m) => `${m.month}월`)} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `values` | `number[]` | — | 필수 | 점의 값 목록(누적). |
| `labels` | `string[]` | — | 필수 | 점마다의 라벨(같은 길이). |
| `height` | `number` | `150` | 선택 | 그래프 높이(px). 기본 150. |
| `format` | `'count' \| 'time'` | `'count'` | 선택 | `"count"`(기본) 또는 `"time"`. |
| `bubbleColor` | `string` | `color.primary` | 선택 | 말풍선 색. 기본 `color.primary`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 선 | 두께 2.4, 면은 `primary` 30% → 2% 그라데이션 |
| 점 | 지름 6(속 `surface`, 테두리 1.8), 마지막은 지름 9 채움 |
| 라벨 | 최대 6개 정도로 줄여 겹치지 않게 놓는다(마지막은 항상 표시) |
## 접근성
- 앱 코드의 차트에는 이름이 없다. 부모에 이름과 마지막 값을 글자로 붙인다. 웹 미리보기는 그림 전체에 "누적 추이. 마지막 값 …" 이름을 붙였다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 말풍선 글자 (11.5px 굵게) | `onPrimary` | `primary` | 3.93:1 미달 | 3.31:1 미달 | 4.60:1 통과 | 7.25:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 마지막 값이 말풍선으로 보인다.
- 잘못한 예: 오르내리는 값에는 쓰지 않는다. 누적용이다.
## 함께 보는 컴포넌트
- [HourBars](https://developers.moring.co/tds/components/hour-bars.md): 값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
- [Sparkline](https://developers.moring.co/tds/components/sparkline.md): 정답률 같은 작은 추이를 보여 주는 선 그림이다. 값이 없는 날은 선이 끊기고, 마지막 점이 크게 표시된다.
---
# CalendarHeat
한 달 달력에서 날마다 공부한 시간을 색의 진하기(5단계)로 보여 주는 차트다.
- 분류: 차트
- 가져오기: `import { CalendarHeat } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:372` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/calendar-heat
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/calendar-heat 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- "이번 달" 학습 기록처럼 날마다 공부한 정도를 한눈에 볼 때 쓴다.
- 칸을 누르면 그 날의 자세한 기록을 보여 준다(`onSelect`).
- 5단계 범례를 반드시 함께 쓴다.
### 쓰지 않는다
- 한 달을 넘는 기간에는 쓰지 않는다. 시작은 일요일, 달 하나 기준이다.
- 색만으로 값을 알리지 않는다. 눌렀을 때 시간을 글자로 보여 준다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 칸 최대 40, 칸 사이 8 | 폭이 좁으면 칸이 줄어든다. 7열. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 5단계 | 학습 없음(`track`), 1~30분(`primary` 22%), 30분~1시간(45%), 1~2시간(72%), 2시간 이상(100%). |
| 미래 | 점선 테두리(`line`)의 빈 칸. 누를 수 없다. |
| 선택 | 2px `text` 테두리. |
## 사용 방법
### 기본
```tsx
import { CalendarHeat } from '@/ui/charts';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `days` | `{ date: string; seconds: number; answered: number }[]` | — | 필수 | 그 달의 날짜 목록. 각 항목 `{ date: "YYYY-MM-DD", seconds, answered }`. 첫 날의 요일로 앞 빈 칸을 정한다. |
| `today` | `string` | — | 필수 | 오늘 날짜. 이후 날짜는 미래 칸이 된다. |
| `selected` | `string \| null` | — | 필수 | 선택된 날짜(없으면 `null`). |
| `onSelect` | `(date: string) => void` | — | 필수 | 칸을 눌렀을 때 날짜와 함께 호출된다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 칸 | 최대 40 정사각형, 모서리 = 크기 × 0.26, 사이 8 |
| 단계 기준 | 0초, 1800초 미만, 3600초 미만, 7200초 미만, 7200초 이상 |
| 요일 줄 | 일월화수목금토, `small`, `textSecondary` |
## 접근성
- 각 칸은 `button`이고 이름은 "{일}일, {단계 글자}"("12일, 30분~1시간")다. 미래 칸은 "아직 오지 않은 날".
- 5단계 범례를 글자로 함께 쓴다.
## 잘한 예와 잘못한 예
- 잘한 예: 범례(5단계 이름)를 함께 쓴다.
- 잘못한 예: 범례 없이 색만 있으면 무엇을 뜻하는지 알 수 없다.
## 함께 보는 컴포넌트
- [HourBars](https://developers.moring.co/tds/components/hour-bars.md): 값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
- [WeekStrip](https://developers.moring.co/tds/components/week-strip.md): 월요일부터 일요일까지 7일을 한 줄로 보여 주는 주간 표시다. 공부한 날에는 점이 찍힌다.
- [Calendar](https://developers.moring.co/tds/components/date-calendar.md): 한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
---
# ScoreBars
과목별 예상 점수(0~100)를 막대로 보여 준다. 점수는 막대 위에, 과목 이름은 아래에 쓴다.
- 분류: 차트
- 가져오기: `import { ScoreBars } from '@/ui/charts'`
- 코드 위치: `apps/mobile/src/ui/charts.tsx:416` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/score-bars
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/score-bars 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 과목별 점수 비교(예상 점수)를 보여 줄 때 쓴다. 막대 색은 과목 색을 쓴다.
- 기록이 모자란 과목은 `value`를 `null`로 두면 "–"와 짧은 막대로 표시된다.
### 쓰지 않는다
- 0~100이 아닌 값에는 쓰지 않는다.
- 점수 숫자를 막대 위에서 빼지 않는다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 최대 막대 높이 110 | 막대 폭은 칸의 72%(최대 44). 위쪽 모서리 12, 아래쪽 4. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 값 있음 | 막대는 과목 색의 55% → 90% 그라데이션. |
| 값 없음 | `null`이면 높이 10, 12% 색, 점수 자리에 "–". |
## 사용 방법
### 기본
```tsx
import { ScoreBars } from '@/ui/charts';
({ label: s.name, value: s.score, tint: palette[s.color].glyph }))} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `data` | `{ label: string; value: number \| null; tint: string; a11y?: string }[]` | — | 필수 | 막대 목록. 각 항목: `label`(과목 이름), `value`(0~100 또는 `null`), `tint`(막대 색), `a11y`(접근성 이름). |
| `height` | `number` | `110` | 선택 | 가장 큰 막대의 높이(px). 기본 110. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 막대 | 폭 칸의 72%(최대 44), 높이 = 값/100 × 110(최소 10), 모서리 위 12·아래 4 |
| 점수 | `bodyStrong`, 막대 위 6 |
| 이름 | `small` `textSecondary`, 막대 아래 8 |
## 접근성
- 각 막대의 이름은 `a11y` 또는 "{과목} {점수}"("수학 82")다. 기록이 모자라면 "기록 부족".
## 잘한 예와 잘못한 예
- 잘한 예: 점수 숫자와 과목 이름을 함께 쓴다.
- 잘못한 예: 기록이 없는 과목을 0점처럼 보이게 하지 않는다. `null`로 "–"를 쓴다.
## 함께 보는 컴포넌트
- [HourBars](https://developers.moring.co/tds/components/hour-bars.md): 값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
- [SubjectIcon](https://developers.moring.co/tds/components/subject-icon.md): 과목을 나타내는 둥근 아이콘 타일이다. 과목마다 기호(Σ, Aa, 책, 건물, 잎, 지구, …)와 색이 정해져 있다.
---
# MathText
수식이 섞인 글을 그린다. 서버가 미리 그려 둔 수식(SVG)을 글자 사이에 끼워 넣고, 없으면 읽을 수 있는 글자로 대신 보여 준다.
- 분류: 학습 콘텐츠
- 가져오기: `import { MathText } from '@/ui/MathText'`
- 코드 위치: `apps/mobile/src/ui/MathText.tsx:21` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/math-text
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/math-text 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 문제, 보기, 해설, 개념처럼 수식이 들어갈 수 있는 모든 글에 `AppText` 대신 쓴다.
- `____`(밑줄 네 개)를 빈칸으로 바꾸려면 `blank`에 요소를 넘긴다.
- 강조(굵게), 밑줄, 표시(형광펜) 표식이 있는 글은 `segments`로 넘긴다.
### 쓰지 않는다
- 수식이 없는 평범한 라벨에는 쓰지 않는다. `AppText`를 쓴다.
- 수식을 이미지로 따로 만들어 넣지 않는다. 글자 크기 설정을 따르지 않게 된다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 미리 그린 수식 | `segments`가 있으면 수식은 SVG다. 크기는 글자 크기를 따르고, 글자의 x-높이(크기의 절반)에 맞춰 그려진다. |
| 글자만 | `segments`가 없으면 `$…$`의 달러 기호를 빼고 읽을 수 있는 글자로 보여 준다. |
| 표식 | `b` 굵게, `u` 시험지식 밑줄, `hl` 단어 뒤 옅은 형광 표시. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 글자 스타일 | `variant`(기본 `body`) | 수식은 시스템 글자 크기(최대 1.6배)와 앱 글자 크기 배율을 함께 따라 커진다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 빈칸 | `blank`가 있고 글에 `____`가 있으면 그 자리에 요소를 넣는다. |
| 긴 문장 | 단어 단위로 나뉘어 수식 둘레에서도 줄바꿈이 된다. 한국어 단어는 중간에서 끊기지 않는다. |
## 사용 방법
### 기본
```tsx
import { MathText } from '@/ui/MathText';
```
### 빈칸
```tsx
} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `text` | `string` | — | 필수 | 글. 수식은 `$…$`로 쓴다. |
| `segments` | `Segment[] \| null` | — | 선택 | 서버가 미리 그린 조각 목록(`{ t: "text", v }` 또는 `{ t: "math", svg, w, h, va, tex }`). 있으면 `text`보다 우선한다. |
| `variant` | `TextVariant` | `'body'` | 선택 | 글자 스타일. 기본 `"body"`. |
| `color` | `string` | `palette.text` | 선택 | 글자 색. 기본 `color.text`. |
| `style` | `TextStyle` | — | 선택 | 추가 글자 스타일. |
| `blank` | `ReactElement` | — | 선택 | `____` 자리에 넣을 요소. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 수식 크기 | 글자 크기 × (시스템 배율, 최대 1.6) × (앱 배율). 1ex = 크기 × 0.5 |
| 표식 | 굵게 = Bold, 밑줄 = 시험지식 밑줄, 표시 = 단어 뒤 옅은 배경 |
| 글자 폴백 | `$x^2$` → `x^2` (달러 기호 제거) |
## 접근성
- 수식은 그림이지만 원문(`tex`)이 읽을 수 있는 글자로 보존된다. 접근성 이름에는 글자 폴백을 쓴다.
- 글자 크기 설정과 시스템 설정에 따라 커지므로 부모의 높이를 고정하지 않는다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 본문 | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 수식이 있을 수 있는 모든 글에 쓴다.
- 잘못한 예: 수식을 이미지로 따로 넣지 않는다. 글자 크기 설정을 따르지 않는다.
## 함께 보는 컴포넌트
- [Blocks](https://developers.moring.co/tds/components/blocks.md): 문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
- [AppText](https://developers.moring.co/tds/components/app-text.md): 앱의 모든 글자를 그리는 컴포넌트다. 글자 스타일 11종 중 하나를 `variant`로 고르면 크기, 줄 높이, 굵기, 자간이 정해진다.
- [PaperType](https://developers.moring.co/tds/components/paper-type.md): 안쪽의 모든 글자를 시험지 서체(Noto Serif KR)로 바꾸는 감싸개다. 모의고사 시험지 화면에 쓴다.
---
# Blocks
문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
- 분류: 학습 콘텐츠
- 가져오기: `import { Blocks, RichText } from '@/ui/blocks/Blocks'`
- 코드 위치: `apps/mobile/src/ui/blocks/Blocks.tsx:24` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/blocks
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/blocks 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 문제의 본문(stem), 보기(choices), 해설(explanation)을 그릴 때 같은 `Blocks`를 쓴다.
- 첫 문단을 문제 글자 스타일(`question`)로 그리려면 `lead`를 준다.
- 보기 안의 그림이나 그래프처럼 작게 그릴 곳에는 `compact`를 켠다.
### 쓰지 않는다
- 블록 종류를 직접 나눠 그리지 않는다. 새 블록 종류는 서버의 블록 정의와 함께 추가한다.
- 블록 사이 간격을 바꾸지 않는다(12, `compact`는 6).
## 종류
| 이름 | 설명 |
| --- | --- |
| p | 문단. `RichText` → `MathText`. |
| box | <보기>나 조건 상자. 항목에 라벨(ㄱ, ㄴ, (가))이 붙는다. `passage`면 지문 문단으로 그린다. |
| math | 가운데 정렬 수식. |
| table | 표. 머리글은 `muted`, 강조 칸은 `primarySoft`. |
| chart · image · diagram | `ChartBlock`, `ImageBlock`, `DiagramBlock` 문서를 본다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 블록 사이 | 12 (`compact`는 6) | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 시험지 서체 안 | `PaperType` 안에서는 상자와 표가 시험지의 줄 모양(검은 1px 선, 모서리 없음)이 되고 "<보 기>" 제목이 상자 위 선에 걸린다. |
## 사용 방법
### 문제 본문
```tsx
import { Blocks } from '@/ui/blocks/Blocks';
```
### 보기 안에서 작게
```tsx
```
### 문단 하나
```tsx
import { RichText } from '@/ui/blocks/Blocks';
```
### 속성(props)
#### Blocks
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `blocks` | `Block[]` | — | 필수 | 블록 목록. 종류: `p`, `box`, `math`, `table`, `chart`, `image`, `diagram`. |
| `assets` | `Assets \| null` | — | 선택 | 그림 블록이 쓰는 페이지 이미지 목록. |
| `variant` | `TextVariant` | `'body'` | 선택 | 문단의 글자 스타일. 기본 `"body"`. |
| `lead` | `TextVariant` | — | 선택 | 첫 문단의 글자 스타일(문제 본문용). |
| `compact` | `boolean` | — | 선택 | `true`이면 간격과 그림 크기를 줄인다. |
| `blank` | `ReactElement` | — | 선택 | 첫 문단의 `____` 자리에 넣을 요소. |
#### RichText
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `text` | `Rich` | — | 필수 | 미리 그려진 글(`{ segs }`). |
| `variant` | `TextVariant` | `'body'` | 선택 | 글자 스타일. 기본 `"body"`. |
| `tint` | `string` | — | 선택 | 글자 색. |
| `blank` | `ReactElement` | — | 선택 | `____` 자리에 넣을 요소. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| box | 1px `lineStrong` 테두리, 모서리 14, 안쪽 14, `well` 바탕 |
| table | 1px `lineStrong`, 모서리 `radius.md`(12), 칸 안쪽 가로 4·세로 8, 머리글 `muted`, 강조 칸 `primarySoft` |
| 간격 | 블록 사이 12, `compact` 6 |
| 시험지 | 선 `text` 1px, 모서리 0, 표 칸 선도 `text` |
## 접근성
- 보기 상자와 지문은 "지문. ㄱ … ㄴ …"처럼 전체 내용을 이름으로 가진다. 표는 `summary` 역할이다.
- 수식, 그래프, 그림은 각 블록이 접근성 이름을 준다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 표 머리글 글자 | `text` | `muted` | 15.68:1 통과 | 14.08:1 통과 | 14.76:1 통과 | 9.62:1 통과 | 4.5:1 |
| 강조 칸 글자 | `text` | `primarySoft` | 15.05:1 통과 | 12.52:1 통과 | 15.09:1 통과 | 7.13:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 본문, 보기, 해설 모두 같은 `Blocks`로 그린다.
- 잘못한 예: 보기 안의 그림을 크게 그리면 보기 한 줄이 화면을 차지한다. `compact`를 켠다.
## 함께 보는 컴포넌트
- [MathText](https://developers.moring.co/tds/components/math-text.md): 수식이 섞인 글을 그린다. 서버가 미리 그려 둔 수식(SVG)을 글자 사이에 끼워 넣고, 없으면 읽을 수 있는 글자로 대신 보여 준다.
- [ChartBlock](https://developers.moring.co/tds/components/chart-block.md): 문제와 해설 안의 그래프(선, 막대, 점)를 그린다. 함수 그래프는 교과서처럼 원점을 지나는 화살표 축으로, 자료 그래프는 격자와 눈금과 범례로 그린다.
- [DiagramBlock](https://developers.moring.co/tds/components/diagram-block.md): 선, 도형, 글자, 회로 부품(저항, 전지, 전구, 스위치), 스프링 같은 기본 모양으로 만든 도식을 그린다. 컨테이너 폭에 맞게 줄어든다.
- [ImageBlock](https://developers.moring.co/tds/components/image-block.md): 문제에 쓰인 원본 자료의 한 영역을 오려 보여 준다. 영역 위에 라벨과 강조 고리를 얹을 수 있고, 눌러서 크게 볼 수 있다.
- [PaperType](https://developers.moring.co/tds/components/paper-type.md): 안쪽의 모든 글자를 시험지 서체(Noto Serif KR)로 바꾸는 감싸개다. 모의고사 시험지 화면에 쓴다.
---
# ConceptBody
개념의 요약 문장과 핵심 포인트 목록을 그린다. 개념 카드, 관련 개념, 틀린 개념, 자료 상세, 레슨 목록이 모두 같은 모양을 쓴다.
- 분류: 학습 콘텐츠
- 가져오기: `import { ConceptBody } from '@/ui/blocks/ConceptBody'`
- 코드 위치: `apps/mobile/src/ui/blocks/ConceptBody.tsx:11` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/concept-body
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/concept-body 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 개념을 보여 주는 모든 곳에서 요약과 핵심 포인트를 이것으로 그린다.
- 카드 안처럼 좁은 곳에서는 `compact`를, 요약만 보이려면 `summaryOnly`를 켠다.
### 쓰지 않는다
- 개념 내용을 `AppText`로 직접 이어 붙이지 않는다. 수식과 블록이 빠진다.
- 핵심 포인트를 번호 목록으로 바꾸지 않는다. 앞에 6px 점을 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 블록 내용 | `content`(서버가 만든 블록 내용)가 있으면 그것을 `Blocks`로 그린다. |
| 글자 내용 | 없으면 `summary` 글과 `keyPoints`를 `MathText`로 그린다. |
| summaryOnly | 요약 첫 블록만 보인다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 요약 `body`, 포인트 `small` | `compact`면 요약도 `small`, 간격 8(기본 12). |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { ConceptBody } from '@/ui/blocks/ConceptBody';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `summary` | `string` | — | 필수 | 요약 문장(글자). `content`가 없을 때 쓴다. |
| `keyPoints` | `string[]` | `[]` | 선택 | 핵심 포인트 글 목록. `content`가 없을 때 쓴다. |
| `render` | `Partial> \| null` | — | 선택 | 요약과 포인트를 미리 그린 조각(수식). |
| `content` | `ConceptContent \| null` | — | 선택 | 서버가 만든 블록 내용. 있으면 위의 글자 대신 이것을 쓴다. |
| `assets` | `Assets \| null` | — | 선택 | 그림 블록이 쓰는 페이지 이미지 목록. |
| `tint` | `string` | `color.primary` | 선택 | 핵심 포인트 점의 색. 기본 `color.primary`. 과목 색을 넘긴다. |
| `summaryOnly` | `boolean` | — | 선택 | `true`이면 요약 첫 블록만 그린다. |
| `compact` | `boolean` | — | 선택 | `true`이면 작은 글자와 좁은 간격으로 그린다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 핵심 포인트 | 앞에 6px 원(`tint`), 윗선에서 8 아래, 글자 `small` `textSecondary`, 포인트 사이 10 |
| 간격 | 요약과 포인트 사이 `md`(12), `compact` `sm`(8). 포인트 목록 위 `xs`(4) |
## 접근성
- 요약과 포인트는 모두 읽히는 글이다. 점은 장식이다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 핵심 포인트 (13px) | `textSecondary` | `surface` | 4.23:1 미달 | 6.95:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 개념은 모두 같은 `ConceptBody`로 그린다.
- 잘못한 예: 번호 목록으로 바꾸면 다른 화면의 개념과 모양이 달라진다.
## 함께 보는 컴포넌트
- [Blocks](https://developers.moring.co/tds/components/blocks.md): 문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
- [MathText](https://developers.moring.co/tds/components/math-text.md): 수식이 섞인 글을 그린다. 서버가 미리 그려 둔 수식(SVG)을 글자 사이에 끼워 넣고, 없으면 읽을 수 있는 글자로 대신 보여 준다.
---
# ChartBlock
문제와 해설 안의 그래프(선, 막대, 점)를 그린다. 함수 그래프는 교과서처럼 원점을 지나는 화살표 축으로, 자료 그래프는 격자와 눈금과 범례로 그린다.
- 분류: 학습 콘텐츠
- 가져오기: `import { ChartBlock } from '@/ui/blocks/ChartBlock'`
- 코드 위치: `apps/mobile/src/ui/blocks/ChartBlock.tsx:27` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/chart-block
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/chart-block 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 문제 안의 그래프를 `Blocks`가 자동으로 부른다. 직접 쓰는 일은 드물다.
- 그래프의 폭은 항상 부모 폭이다.
### 쓰지 않는다
- 앱의 학습 통계에는 쓰지 않는다. 통계는 `HourBars`, `AreaLine` 같은 차트를 쓴다.
- 그래프 모양을 위해 코드에서 점을 조작하지 않는다. 서버가 만든 점을 그대로 그린다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 함수 그래프 (cartesian) | 막대가 아니고, 범주축이 없고, 두 축의 범위가 0을 포함하며 단위가 없을 때. 원점을 지나는 화살표 축, 격자 없음, x · y · O 글자. |
| 자료 그래프 | 그 밖의 모든 경우. 격자, 눈금, 단위, 범주, 막대, 범례. |
| kind | `line`(선), `bar`(막대), `scatter`(점). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 = min(폭 × 0.64, 240) | `compact`는 폭 × 0.66. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 시리즈 색 | 1번째 `primary`, 2번째 `danger`, 3번째 `mint`, 4번째 `amber`. |
| 보조선·표시 | `lines`(가로·세로 보조선)와 `marks`(강조점과 라벨)를 그린다. |
## 사용 방법
### 기본
```tsx
import { ChartBlock } from '@/ui/blocks/ChartBlock';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `block` | `Chart` | — | 필수 | 그래프 블록(`{ t: "chart", kind, x, y, series, lines?, marks? }`). 축 `x`, `y`는 `{ label?, unit?, cats?, min, max }`. |
| `compact` | `boolean` | — | 선택 | `true`이면 보기 안에 들어가게 작게 그린다(글자 10, 높이 폭 × 0.66). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 색 | `primary`, `danger`, `mint`, `amber` 순서 |
| 글자 | 눈금과 라벨 11px(`compact` 10), 색 `textTertiary` |
| 안쪽 여백 | 함수 그래프 14/16/14/14, 자료 그래프 36/12/18/24 (왼/오/위/아래) |
| 눈금 | 약 4개, 1·2·2.5·5·10 배수로 고른다 |
## 접근성
- 그래프 전체에 "그래프, 가로 {라벨}, 세로 {라벨}"이라는 이름이 붙는다. 점의 값은 읽히지 않으므로 문제 글에 필요한 값을 써 둔다.
- 시리즈는 색과 범례의 이름으로 함께 구분한다.
## 잘한 예와 잘못한 예
- 잘한 예: 단위와 라벨이 있는 자료 그래프에는 범례를 함께 쓴다.
- 잘못한 예: 그래프의 값을 알아야 풀 수 있는 문제라면 값을 글로도 쓴다. 화면 읽기 프로그램은 점의 값을 읽지 못한다.
## 함께 보는 컴포넌트
- [Blocks](https://developers.moring.co/tds/components/blocks.md): 문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
- [DiagramBlock](https://developers.moring.co/tds/components/diagram-block.md): 선, 도형, 글자, 회로 부품(저항, 전지, 전구, 스위치), 스프링 같은 기본 모양으로 만든 도식을 그린다. 컨테이너 폭에 맞게 줄어든다.
- [HourBars](https://developers.moring.co/tds/components/hour-bars.md): 값 축이 있는 막대 차트다. 학습 시간(시간·분)이나 푼 문제 수를 날짜별로 보여 주고, 고른 막대 위에 말풍선이 뜬다.
---
# DiagramBlock
선, 도형, 글자, 회로 부품(저항, 전지, 전구, 스위치), 스프링 같은 기본 모양으로 만든 도식을 그린다. 컨테이너 폭에 맞게 줄어든다.
- 분류: 학습 콘텐츠
- 가져오기: `import { DiagramBlock } from '@/ui/blocks/DiagramBlock'`
- 코드 위치: `apps/mobile/src/ui/blocks/DiagramBlock.tsx:13` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/diagram-block
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/diagram-block 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 과학, 물리 문제의 간단한 도식을 `Blocks`가 자동으로 부른다.
### 쓰지 않는다
- 사진이나 복잡한 그림에는 쓰지 않는다. `ImageBlock`을 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 모양 | `line`, `arrow`, `rect`, `circle`, `ellipse`, `polyline`, `polygon`, `text`, `resistor`, `battery`, `bulb`, `switch`, `dot`, `ground`, `spring`. |
| tone | `default`(글자색), `accent`(주색), `muted`(약한 색)로 모양마다 색을 고른다. `dashed`면 점선. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 폭 = 컨테이너, 높이 = 폭 × (도식 높이/너비) | `compact`면 최대 높이가 낮다. 선 두께는 도식 단위 × 1.1. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { DiagramBlock } from '@/ui/blocks/DiagramBlock';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `block` | `Diagram` | — | 필수 | 도식 블록(`{ t: "diagram", w, h, shapes }`). `shapes`는 모양 목록. |
| `compact` | `boolean` | — | 선택 | `true`이면 보기 안에 들어가게 작게 그린다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 선 | 두께 = 도식 단위 × 1.1, 둥근 끝과 꺾임 |
| 색 | `tone`에 따라 `text`, `primary`, `textTertiary` |
| 접근성 이름 | "그림: {도식 안의 글자들}" |
## 접근성
- 그림 전체에 "그림: …" 이름이 붙고, 도식 안의 글자(전지, 전구)가 이름에 들어간다. 도식의 연결 관계는 문제 글에도 쓴다.
## 잘한 예와 잘못한 예
- 잘한 예: 도식 안의 글자(전지, 전구)를 `text` 모양으로 함께 넣는다.
- 잘못한 예: 사진이나 복잡한 그림을 도형으로 흉내 내지 않는다. `ImageBlock`을 쓴다.
## 함께 보는 컴포넌트
- [Blocks](https://developers.moring.co/tds/components/blocks.md): 문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
- [ChartBlock](https://developers.moring.co/tds/components/chart-block.md): 문제와 해설 안의 그래프(선, 막대, 점)를 그린다. 함수 그래프는 교과서처럼 원점을 지나는 화살표 축으로, 자료 그래프는 격자와 눈금과 범례로 그린다.
- [ImageBlock](https://developers.moring.co/tds/components/image-block.md): 문제에 쓰인 원본 자료의 한 영역을 오려 보여 준다. 영역 위에 라벨과 강조 고리를 얹을 수 있고, 눌러서 크게 볼 수 있다.
---
# ImageBlock
문제에 쓰인 원본 자료의 한 영역을 오려 보여 준다. 영역 위에 라벨과 강조 고리를 얹을 수 있고, 눌러서 크게 볼 수 있다.
- 분류: 학습 콘텐츠
- 가져오기: `import { ImageBlock } from '@/ui/blocks/ImageBlock'`
- 코드 위치: `apps/mobile/src/ui/blocks/ImageBlock.tsx:21` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/image-block
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/image-block 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 자료 사진 속 그림, 표, 그래프가 문제에 필요할 때 `Blocks`가 자동으로 부른다.
- 모의고사 문제를 위해 AI가 그린 그림도 같은 블록이다. 이 그림에는 `AiMark`(`overlay`)가 자동으로 붙는다.
### 쓰지 않는다
- 전체 페이지를 보여 줄 때는 쓰지 않는다. `SourceCrop`의 `full`이나 `ZoomableImage`를 쓴다.
- `alt` 없이 쓰지 않는다. 그림 설명이 접근성 이름이다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 영역 오려내기 | `part`와 `page`의 이미지에서 `region`(0~1 비율)만 보여 준다. |
| 라벨 | `labels`: 영역 위의 글자(A, B)와 선택적 지시선. |
| 고리 | `rings`: 강조할 영역 둘레의 고리. |
| AI 그림 | `blob`: AI가 그린 그림의 저장 경로. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 폭 = 컨테이너 | `compact`면 작게. 오른쪽 아래의 확대 버튼은 26 원(`rgba(12,18,34,0.45)`). |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 확대 | 눌러서 검은 바탕(`rgba(8,10,16,0.92)`)의 전체 화면 보기로 연다. 오른쪽 위에 닫기 버튼(40 원). |
| 자료 없음 | 이미지를 찾지 못하면 `well` 상자에 "(그림: {alt})"를 보인다. |
| 라벨 | 흰색 94% 상자(최소 26 × 24, 모서리 7)에 굵은 글자. |
## 사용 방법
### 기본
```tsx
import { ImageBlock } from '@/ui/blocks/ImageBlock';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `block` | `ImageB` | — | 필수 | 그림 블록(`{ t: "image", part, page, region, labels?, rings?, alt, blob? }`). |
| `assets` | `Assets \| null` | — | 선택 | 페이지 이미지 목록(버전 부분 id → 경로). |
| `compact` | `boolean` | — | 선택 | `true`이면 보기 안에 들어가게 작게 그린다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 확대 버튼 | 26 원, `rgba(12,18,34,0.45)`, 오른쪽 아래 8 |
| 라벨 | 흰색 94% 바탕, 모서리 7, 글자 굵게 |
| 확대 화면 | 배경 `rgba(8,10,16,0.92)`, 닫기 40 원 |
| 자리 색 | 이미지가 오기 전 `photo` |
## 접근성
- 이미지의 이름은 블록의 `alt`다. 그림 속 글자나 값은 `alt`에 풀어 쓴다.
- 확대 버튼에는 이름이 있다.
## 잘한 예와 잘못한 예
- 잘한 예: `alt`에 그림이 말해 주는 내용을 쓴다.
- 잘못한 예: `alt` 없이 그림만 두면 그림이 보이지 않는 사용자에게 아무 내용도 전달되지 않는다.
## 함께 보는 컴포넌트
- [SourceCrop](https://developers.moring.co/tds/components/source-crop.md): 문제가 나온 원본 자료의 근거 부분을 보여 준다. 근거가 있는 줄을 가운데로 한 가로 띠, 또는 전체 페이지에 근거 영역 표시를 얹은 모양이다.
- [ZoomableImage](https://developers.moring.co/tds/components/zoomable-image.md): 페이지 이미지를 두 손가락으로 1배에서 4배까지 확대하고, 확대한 동안에만 끌어서 움직이며, 두 번 눌러 확대·축소하는 이미지다.
- [AiMark](https://developers.moring.co/tds/components/ai-mark.md): 생성형 AI가 만든 결과임을 알리는 표시다. AI가 만든 모든 결과에 붙인다.
- [Blocks](https://developers.moring.co/tds/components/blocks.md): 문제 본문, 보기, 해설을 이루는 블록(문단, 보기 상자, 수식, 표, 그래프, 그림)을 위에서 아래로 그린다. `RichText`는 그중 문단 하나다.
---
# SourceCrop
문제가 나온 원본 자료의 근거 부분을 보여 준다. 근거가 있는 줄을 가운데로 한 가로 띠, 또는 전체 페이지에 근거 영역 표시를 얹은 모양이다.
- 분류: 학습 콘텐츠
- 가져오기: `import { SourceCrop } from '@/ui/SourceCrop'`
- 코드 위치: `apps/mobile/src/ui/SourceCrop.tsx:9` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/source-crop
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/source-crop 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 해설에서 "이 문제는 자료의 이 부분에서 나왔어요"를 보여 줄 때 쓴다.
- 근거 줄만 보이려면 기본(띠), 전체 페이지에서 위치를 보이려면 `full`.
### 쓰지 않는다
- 그림이 문제의 일부일 때는 쓰지 않는다. `ImageBlock`을 쓴다.
- 근거 영역(`region`)이 없는데 `full`로 쓰지 않는다. 강조가 없는 그냥 페이지가 된다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 띠 (기본) | 폭 전체, 높이 `height`(기본 110). 근거 영역이 세로 가운데에 오게 위치를 맞춘다. 옆으로는 자르지 않는다. |
| full | 페이지 전체를 폭에 맞춰 보여 주고 근거 영역에 2px `primary` 테두리와 옅은 파란 면을 얹는다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 띠 | 높이 110 (기본) | `height`로 바꾼다. 모서리 `radius.md`(12). |
| full | 높이 = 폭 ÷ 페이지 비율 | 비율을 알 때까지 400. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 불러오는 중 | `photo` 바탕이 먼저 보인다. |
## 사용 방법
### 기본
```tsx
import { SourceCrop } from '@/ui/SourceCrop';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `uri` | `string` | — | 필수 | 페이지 이미지 주소. |
| `region` | `Region \| null` | — | 선택 | 근거 영역(`{ x, y, width, height }`, 0~1 비율). 없으면 페이지 전체. |
| `height` | `number` | `110` | 선택 | 띠의 높이(px). 기본 110. |
| `full` | `boolean` | `false` | 선택 | `true`이면 전체 페이지에 근거 영역 표시를 얹어 보여 준다. 기본 `false`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모서리 | `radius.md`(12) |
| 근거 표시(full) | 2px `primary` 테두리 + `rgba(31,123,255,0.10)` 면, 영역보다 사방 3 크게. 이 색은 앱 코드가 직접 쓴 파란색이다(웹에서는 `primary` 10%를 쓴다) |
| 바탕 | 띠 `photo`, full `surface` |
## 접근성
- 이미지에는 이름이 없다. 부모에 "원본 자료의 근거 부분" 같은 이름을 붙인다.
## 잘한 예와 잘못한 예
- 잘한 예: 근거 줄이 띠의 가운데에 온다.
- 잘못한 예: 근거 영역 없이 전체 페이지를 띠로 자르면 엉뚱한 곳이 보인다.
## 함께 보는 컴포넌트
- [ImageBlock](https://developers.moring.co/tds/components/image-block.md): 문제에 쓰인 원본 자료의 한 영역을 오려 보여 준다. 영역 위에 라벨과 강조 고리를 얹을 수 있고, 눌러서 크게 볼 수 있다.
- [ZoomableImage](https://developers.moring.co/tds/components/zoomable-image.md): 페이지 이미지를 두 손가락으로 1배에서 4배까지 확대하고, 확대한 동안에만 끌어서 움직이며, 두 번 눌러 확대·축소하는 이미지다.
---
# ZoomableImage
페이지 이미지를 두 손가락으로 1배에서 4배까지 확대하고, 확대한 동안에만 끌어서 움직이며, 두 번 눌러 확대·축소하는 이미지다.
- 분류: 학습 콘텐츠
- 가져오기: `import { ZoomableImage } from '@/ui/ZoomableImage'`
- 코드 위치: `apps/mobile/src/ui/ZoomableImage.tsx:16` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/zoomable-image
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/zoomable-image 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 원본 자료 페이지나 게시물 이미지를 크게 볼 때 쓴다. 문서 원본 보기 화면과 커뮤니티 게시물 미디어가 쓴다.
- 확대한 동안 옆 페이지로 넘기는 제스처를 막으려면 `onZoomChange`로 알아내서 부모의 스와이프를 끈다.
- 이미지 위에 표시를 얹으려면 `overlay`를 쓴다.
### 쓰지 않는다
- 작은 썸네일에는 쓰지 않는다. 제스처가 목록 스크롤과 겹친다.
- 4배보다 더 키우려고 `ZoomHandle`을 건드리지 않는다.
## 종류
종류가 없다. 확대 비율은 1~4배, 두 번 누르면 2.5배다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | `width` × `height` | 필수. 이미지가 이 상자 안에서 보인다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 1배 | 끌기가 꺼져 있어 부모의 가로 스와이프(페이지 넘기기)가 동작한다. |
| 확대 | 손가락 위치를 중심으로 커지고, 확대한 동안에만 끌어서 움직인다. 가장자리는 이미지 밖이 보이지 않게 제한된다. |
| 두 번 누름 | 1배 → 2.5배(220ms), 확대 상태 → 1배(200ms). |
| 초기화 | `ref.reset()`을 부르면 180ms 동안 1배로 돌아온다. |
## 사용 방법
### 기본
```tsx
import { ZoomableImage, type ZoomHandle } from '@/ui/ZoomableImage';
const zoom = useRef(null);
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `uri` | `string` | — | 필수 | 이미지 주소. |
| `width` | `number` | — | 필수 | 상자 폭(px). 필수. |
| `height` | `number` | — | 필수 | 상자 높이(px). 필수. |
| `onZoomChange` | `(zoomed: boolean) => void` | — | 선택 | 확대 상태가 바뀔 때 `true`/`false`와 함께 호출된다. |
| `overlay` | `ReactNode` | — | 선택 | 이미지 위에 얹을 요소(표시, 라벨). |
| `label` | `string` | — | 선택 | 접근성 이름. |
| `onSize` | `(w: number, h: number) => void` | — | 선택 | 이미지 원본 크기를 알게 되었을 때 호출된다. |
| `headers` | `Record` | — | 선택 | 비공개 저장소 이미지를 불러올 때 쓰는 요청 헤더. |
`ref`로 `{ reset() }`을 받을 수 있다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 확대 | 최대 4배, 두 번 누르면 2.5배 |
| 시간 | 두 번 누름 220ms, 되돌림 200ms, 초기화·한계 보정 180ms |
## 접근성
- 제스처가 필요한 컴포넌트다. 확대 버튼이나 키보드 같은 대체 방법이 앱 코드에는 없다. 웹에서 쓸 때는 확대·축소 버튼을 함께 둔다(이 문서의 미리보기처럼).
- 이미지의 이름은 `label`이다.
## 잘한 예와 잘못한 예
- 잘한 예: 확대한 동안 부모의 스와이프를 끈다.
- 잘못한 예: 확대할 방법이 제스처뿐이면 손을 쓰기 어려운 사용자가 쓸 수 없다. 웹에서는 버튼을 더한다.
## 함께 보는 컴포넌트
- [ImageBlock](https://developers.moring.co/tds/components/image-block.md): 문제에 쓰인 원본 자료의 한 영역을 오려 보여 준다. 영역 위에 라벨과 강조 고리를 얹을 수 있고, 눌러서 크게 볼 수 있다.
- [SourceCrop](https://developers.moring.co/tds/components/source-crop.md): 문제가 나온 원본 자료의 근거 부분을 보여 준다. 근거가 있는 줄을 가운데로 한 가로 띠, 또는 전체 페이지에 근거 영역 표시를 얹은 모양이다.
---
# CheckCircle
할 일을 완료로 바꾸는 동그란 체크 표시다. 비어 있다가 누르면 색이 채워지며 체크가 나타난다.
- 분류: Calendar 앱
- 가져오기: `import { CheckCircle } from '@/cal/ui'`
- 코드 위치: `apps/calendar/src/cal/ui.tsx:93` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/check-circle
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/check-circle 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 할 일과 Mordy 계획 행의 앞에 쓴다. 과목이나 캘린더 색을 `tint`로 준다.
- 완료는 항상 같은 모양이어야 하므로 이 컴포넌트만 쓴다.
### 쓰지 않는다
- 설정을 켜고 끄는 데 쓰지 않는다. `SettingsSwitch`를 쓴다.
- 눌러도 바뀌지 않아야 할 때(권한 없음)는 `disabled`로 둔다. 색으로만 비활성을 알리지 않는다.
## 종류
종류가 없다. `tint`로 색을 바꾼다. 기본은 `primary`.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 24 × 24, 선 2 | `hitSlop` 10으로 누르는 영역이 44 × 44가 된다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 비어 있음 | 2px `lineStrong` 테두리의 빈 원. |
| 완료 | `tint`로 채워지고 흰 체크(14px, 선 3). |
| 비활성 | `disabled`이면 눌러도 반응하지 않는다. |
## 사용 방법
### 기본
```tsx
import { CheckCircle } from '@/cal/ui';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `done` | `boolean` | — | 필수 | 완료인지. |
| `onPress` | `() => void` | — | 선택 | 눌렀을 때 호출된다(가벼운 진동 `haptic.tap`). |
| `tint` | `string` | `color.primary` | 선택 | 완료일 때 채우는 색. 기본 `color.primary`. |
| `label` | `string` | — | 필수 | 접근성 이름. 필수. 예: "수학 숙제 완료". |
| `disabled` | `boolean` | — | 선택 | `true`이면 누를 수 없다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 24 × 24 원, 선 2, 비었을 때 `lineStrong`, 완료 시 `tint` 채움 |
| 체크 | Check 14px, 선 3, white |
| 누르는 영역 | `hitSlop` 10, 물결은 원형(`borderless`) |
## 접근성
- 역할은 `checkbox`이고 상태 `checked`, `disabled`가 전달된다. 이름은 `label`이다.
- 완료는 색과 체크 표시로 함께 나타낸다.
- 앱은 터치 화면이라 마우스 올림(hover)이 없다. 웹에서는 포커스 링(2.5px 실선, 바깥 3px)을 직접 그린다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 완료 체크 (비글자) | `white` | `primary` | 3.93:1 통과 | 3.31:1 통과 | 4.60:1 통과 | 1.98:1 미달 | 3:1 |
## 잘한 예와 잘못한 예
- 잘한 예: `label`에 무엇을 완료하는지 쓴다.
- 잘못한 예: `label` 없이 쓰지 않는다. "체크 상자"라고만 읽힌다.
## 함께 보는 컴포넌트
- [ItemRow](https://developers.moring.co/tds/components/item-row.md): 오늘과 할 일 목록의 한 줄이다. 할 일은 앞에 체크 원, 일정은 앞에 시간과 색 막대가 있다.
- [PlanRow](https://developers.moring.co/tds/components/plan-row.md): Mordy가 만든 학습 계획 한 줄이다. 체크 원, 제목, "Mordy" 표지, 푼 문제 진행률(막대)이 있다. 직접 옮긴 계획에는 자물쇠가 붙는다.
- [RadioMark](https://developers.moring.co/tds/components/radio-mark.md): 선택 목록 행의 끝에 놓는 동그란 라디오 표시다. 시트의 선택 목록에서 쓴다.
---
# DayStrip
한 주 7일을 한 줄로 보여 주고 날짜를 골라 이동하는 주간 줄이다. 좌우로 밀면 앞뒤 주로 이동한다. 일정이 있는 날에는 점이 찍힌다.
- 분류: Calendar 앱
- 가져오기: `import { DayStrip } from '@/cal/DayStrip'`
- 코드 위치: `apps/calendar/src/cal/DayStrip.tsx:13` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/day-strip
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/day-strip 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 오늘 화면 맨 위에서 날짜를 고를 때 쓴다.
- 일정이 있는 날은 `marks`에 `"item"`, 시험일은 `"exam"`으로 알린다.
### 쓰지 않는다
- 눌러서 고르지 않는 단순 표시에는 쓰지 않는다. `WeekStrip`을 쓴다.
- 한 달 전체를 한눈에 봐야 할 때 쓰지 않는다. `MonthView`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 시작 요일 | `startsOn`: 1(월요일, 기본) 또는 0(일요일). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 날짜 원 36, 점 5 | 7칸이 폭을 똑같이 나눈다. 위아래 안쪽 여백 4, 칸 모서리 16. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택 | `primaryStrong`으로 채워진 원과 흰 글자. 요일 글자는 `primary` Bold. |
| 오늘 (선택 아님) | 1.5px `primaryLine` 테두리 원, 숫자 `primary`. |
| 점 | 일정 있음: 선택되면 `primary`, 아니면 `textTertiary`. 시험일: `danger`. |
| 밀기 | 왼쪽으로 50 넘게 밀면 다음 주, 오른쪽이면 이전 주(선택 날짜가 7일 이동). |
## 사용 방법
### 기본
```tsx
import { DayStrip } from '@/cal/DayStrip';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `selected` | `string` | — | 필수 | 선택된 날짜(`"YYYY-MM-DD"`). |
| `today` | `string` | — | 필수 | 오늘 날짜. |
| `onSelect` | `(day: string) => void` | — | 필수 | 날짜를 눌렀거나 주를 밀었을 때 새 날짜와 함께 호출된다. |
| `marks` | `Record` | — | 선택 | 날짜별 점 종류. `{ "2026-10-09": "item" \| "exam" }`. |
| `startsOn` | `0 \| 1` | `1` | 선택 | 주의 첫 요일. 1(월, 기본) 또는 0(일). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 날짜 원 | 36 × 36, 선택은 `primaryStrong` 채움(투명도만 바뀌어 모서리가 유지된다) |
| 글자 | 요일 `small`, 날짜 `bodyStrong` |
| 점 | 5px 원, 날짜 아래 6 |
## 접근성
- 줄은 `tablist`, 각 날짜는 `tab`이고 이름은 "10월 9일 목요일, 오늘"이다. 선택 상태가 전달된다.
- 점의 의미(일정, 시험)는 색만으로 구분되므로 선택한 날짜의 목록에서 글자로도 알려 준다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 선택된 날짜 | `onPrimaryStrong` | `primaryStrong` | 4.43:1 미달 | 3.71:1 미달 | 4.60:1 통과 | 5.48:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 선택한 날의 일정 목록을 줄 아래에 글자로 함께 보여 준다.
- 잘못한 예: 점만으로 시험일을 알리지 않는다. 시험일은 목록에 "D-3 중간고사"로도 보여 준다.
## 함께 보는 컴포넌트
- [WeekStrip](https://developers.moring.co/tds/components/week-strip.md): 월요일부터 일요일까지 7일을 한 줄로 보여 주는 주간 표시다. 공부한 날에는 점이 찍힌다.
- [MonthView](https://developers.moring.co/tds/components/month-view.md): 한 달을 화면 가득 보여 주는 달력이다. 날마다 칩(시험 먼저, 시간 있는 일정, 나머지)을 줄에 들어가는 만큼 보이고 넘치면 "+n"으로 줄인다.
- [TabBar (Calendar)](https://developers.moring.co/tds/components/calendar-tab-bar.md): Mordy Calendar 앱의 탭 줄이다. 오늘 · 캘린더 · (+) · 할 일 · 더보기. Mordy 앱의 `TabBar`와 같은 모양이고 가운데 +는 빠른 추가를 연다.
---
# TabBar (Calendar)
Mordy Calendar 앱의 탭 줄이다. 오늘 · 캘린더 · (+) · 할 일 · 더보기. Mordy 앱의 `TabBar`와 같은 모양이고 가운데 +는 빠른 추가를 연다.
- 분류: Calendar 앱
- 가져오기: `import { TabBar } from '@/cal/TabBar'`
- 코드 위치: `apps/calendar/src/cal/TabBar.tsx:20` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/calendar-tab-bar
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/calendar-tab-bar 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 앱의 큰 화면 이동에 쓴다.
- 가운데 +는 이동이 아니라 "할 일이나 일정 추가"(빠른 추가 시트 열기)다.
### 쓰지 않는다
- Mordy 앱에는 쓰지 않는다. 그쪽은 `TabBar`(`@/ui/TabBar`)다.
## 종류
종류가 없다. Mordy의 `TabBar`와 탭 이름·아이콘·가운데 버튼의 행동만 다르다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 탭 높이 50, 가운데 버튼 52 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택됨 | `primary`, 아이콘 선 2.3, 이름 Bold. |
| 선택 안 됨 | `tabInactive`, 선 1.9, Medium. |
## 사용 방법
### 기본
```tsx
import { TabBar } from '@/cal/TabBar';
}>…
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `state` | `(React Native)` | — | 선택 | 탭 네비게이터의 상태. |
| `navigation` | `(React Native)` | — | 선택 | 탭 이동을 요청하는 객체. |
나머지 속성은 `BottomTabBarProps`를 그대로 받는다.
`BottomTabBarProps`를 받는다. 탭은 `index` 오늘, `calendar` 캘린더, `tasks` 할 일, `more` 더보기다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | Mordy `TabBar`와 같다(탭 아이콘 22, 이름 10.5, 가운데 52 원 `gradient`) |
| 가운데 버튼 이름 | "할 일이나 일정 추가" |
## 접근성
- `TabBar`와 같다. 가운데 버튼은 `button` 역할이다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 선택 안 된 탭 이름 | `tabInactive` | `surface` | 4.64:1 통과 | 4.75:1 통과 | 4.88:1 통과 | 7.11:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 아이콘과 이름을 함께 보인다.
- 잘못한 예: Mordy 앱 화면에 쓰지 않는다.
## 함께 보는 컴포넌트
- [TabBar](https://developers.moring.co/tds/components/tab-bar.md): Mordy 앱 맨 아래의 탭 줄이다. 홈 · 과목 · (+) · 커뮤니티 · 더보기. 가운데 +는 탭이 아니라 자료 추가 행동이다.
- [QuickAddHost](https://developers.moring.co/tds/components/quick-add.md): 할 일, 일정, 시험을 한 줄로 빠르게 추가하는 시트다. 한 줄에 "내일 4시 영어학원"처럼 쓰면 날짜와 시간을 알아듣고, 못 알아들은 것은 알약 선택으로 채운다.
---
# ScreenHeader
Calendar 앱의 전체 화면 위쪽 줄이다. 왼쪽에 뒤로, 가운데에 제목, 오른쪽에 행동. 모두 48의 누르는 영역이고 화면 가장자리에서 8 들어와 있다.
- 분류: Calendar 앱
- 가져오기: `import { ScreenHeader, HeaderAction } from '@/cal/ScreenHeader'`
- 코드 위치: `apps/calendar/src/cal/ScreenHeader.tsx:12` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/calendar-header
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/calendar-header 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 일정 편집, 설정 같은 전체 화면 맨 위에 쓴다.
- 오른쪽 행동은 `HeaderAction`에 아이콘을 넣어 `right`에 넘긴다.
### 쓰지 않는다
- Mordy 앱에는 쓰지 않는다. 그쪽은 `BackHeader`다.
- 오른쪽에 행동을 둘 넘게 두지 않는다. 제목이 화면 가운데에서 밀린다.
## 종류
| 이름 | 설명 |
| --- | --- |
| ScreenHeader | 줄 전체. 제목은 두 옆 영역 사이가 아니라 화면 가운데에 놓인다. |
| HeaderAction | 오른쪽 아이콘 행동 하나(48 × 48, 원형 물결). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 56, 좌우 8 | 양쪽 영역 폭 56, 누르는 영역 48. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 뒤로 누름 | 앞 화면이 있으면 돌아가고, 없으면 홈으로 간다. |
## 사용 방법
### 기본
```tsx
import { HeaderAction, ScreenHeader } from '@/cal/ScreenHeader';
} />
```
### 속성(props)
#### ScreenHeader
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `title` | `string` | — | 선택 | 가운데 제목(`headline`, 한 줄). |
| `right` | `ReactNode` | — | 선택 | 오른쪽에 놓을 요소(보통 `HeaderAction`). |
| `onBack` | `() => void` | — | 선택 | 뒤로 누를 때 실행할 함수. 없으면 앞 화면, 없으면 홈. |
#### HeaderAction
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `label` | `string` | — | 필수 | 접근성 이름. 필수. |
| `onPress` | `() => void` | — | 필수 | 눌렀을 때 실행할 함수. |
| `children` | `ReactNode` | — | 필수 | 그릴 아이콘. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 줄 | 높이 56, 좌우 8 |
| 양쪽 영역 | 폭 56, 높이 48. 뒤로 아이콘은 ChevronLeft 26 선 2.2 |
| 제목 | `headline`, 가운데, 역할 `header` |
| HeaderAction | 48 × 48 |
## 접근성
- 뒤로 버튼은 "뒤로", 제목은 `header` 역할이다. `HeaderAction`은 `label`이 이름이다.
- 눌리는 영역이 48 × 48이라 최소 크기를 지킨다.
## 잘한 예와 잘못한 예
- 잘한 예: 행동의 `label`을 쓴다.
- 잘못한 예: 오른쪽 행동이 많으면 제목이 가운데에서 밀린다.
## 함께 보는 컴포넌트
- [BackHeader](https://developers.moring.co/tds/components/back-header.md): 화면 위쪽의 뒤로 가기 줄이다. 왼쪽에 뒤로 화살표, 가운데에 제목(선택), 오른쪽에 행동 자리가 있다.
- [IconButton](https://developers.moring.co/tds/components/icon-button.md): 아이콘 하나만 있는 투명한 둥근 버튼이다. 헤더의 "뒤로", "검색"에 쓴다.
---
# MonthView
한 달을 화면 가득 보여 주는 달력이다. 날마다 칩(시험 먼저, 시간 있는 일정, 나머지)을 줄에 들어가는 만큼 보이고 넘치면 "+n"으로 줄인다.
- 분류: Calendar 앱
- 가져오기: `import { MonthView } from '@/cal/MonthView'`
- 코드 위치: `apps/calendar/src/cal/MonthView.tsx:18` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/month-view
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/month-view 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 캘린더 탭의 월 보기에 쓴다. 날짜를 누르면 선택, 길게 누르면 `onLongPress`(그날에 추가).
- 좌우로 밀어 앞뒤 달로 이동한다(`onSwipe`).
### 쓰지 않는다
- 한 주를 시간표처럼 볼 때는 쓰지 않는다. `TimeGrid`를 쓴다.
- 날짜를 고르기만 하는 입력에는 쓰지 않는다. `Calendar`를 쓴다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 그날의 사진 | 날짜에 개인 사진이 있으면 칸 뒤에 38% 투명도로 깔린다(글자가 읽히도록). |
| 계획 보기 | `showPlan`이 켜지면 Mordy 계획 칩도 보인다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 칩 높이 15, 글자 9.5 | 줄 높이에서 번호 줄(약 30)을 뺀 만큼 17씩 칩을 놓는다. 넘치면 마지막 자리는 "+n". |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 오늘 | 날짜 숫자가 `primaryStrong` 원. |
| 선택 | 칸 배경 `selected`. |
| 칩 | 과목 색 18% 바탕, 글자 `text`. 시험은 채운 칩(`solid`), 완료는 흐리게. |
## 사용 방법
### 기본
```tsx
import { MonthView } from '@/cal/MonthView';
openQuickAdd({ date: d })} onSwipe={(n) => setMonth(addMonths(month, n))} startsOn={prefs.weekStartsOn} showPlan={prefs.showPlan} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `month` | `string` | — | 필수 | 보여 줄 달(`"YYYY-MM"` 또는 그 달의 날짜). |
| `selected` | `string` | — | 필수 | 선택된 날짜. |
| `today` | `string` | — | 필수 | 오늘 날짜. |
| `data` | `Range \| undefined` | — | 필수 | 이 달의 항목(일정, 계획, 시험, 사진). 아직 불러오는 중이면 `undefined`. |
| `onSelect` | `(d: string) => void` | — | 필수 | 날짜를 눌렀을 때 호출된다. |
| `onLongPress` | `(d: string) => void` | — | 필수 | 날짜를 길게 눌렀을 때 호출된다. |
| `onSwipe` | `(n: number) => void` | — | 필수 | 좌우로 밀었을 때 `1` 또는 `-1`과 함께 호출된다. |
| `startsOn` | `0 \| 1` | — | 필수 | 주의 첫 요일. 0(일) 또는 1(월). |
| `showPlan` | `boolean` | — | 필수 | Mordy 계획 칩을 보일지. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 바탕 | `surface`, 모서리 22, 좌우 4, 위 10, 아래 6 |
| 주 줄 | 위쪽 1px `line`, 칸 모서리 10 |
| 날짜 숫자 | 24 원(오늘은 `primaryStrong` 채움) |
| 칩 | 모서리 4, 좌우 3, 높이 15, 글자 9.5/13 SemiBold |
## 접근성
- 각 날짜는 눌리는 요소다. 칩 글자가 매우 작으므로(9.5) 선택한 날의 상세 목록을 화면 아래에 글자로 따로 보여 준다.
- 시험은 채운 칩과 글자로 함께 알린다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 칩 글자 (9.5px 굵게) | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 선택한 날의 목록을 아래에 크게 보여 준다.
- 잘못한 예: 칩 글자(9.5px)만으로 내용을 전달하지 않는다. 넘치는 일정은 "+n"으로 줄고 상세 목록에서 본다.
## 함께 보는 컴포넌트
- [TimeGrid](https://developers.moring.co/tds/components/time-grid.md): 주·일 보기의 24시간 시간표다. 일정 블록을 길게 눌러 끌면 시간(15분 단위)과 날짜가 바뀌고, 빈 칸을 누르면 그 시각에 추가한다.
- [DayStrip](https://developers.moring.co/tds/components/day-strip.md): 한 주 7일을 한 줄로 보여 주고 날짜를 골라 이동하는 주간 줄이다. 좌우로 밀면 앞뒤 주로 이동한다. 일정이 있는 날에는 점이 찍힌다.
- [Calendar](https://developers.moring.co/tds/components/date-calendar.md): 한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
---
# TimeGrid
주·일 보기의 24시간 시간표다. 일정 블록을 길게 눌러 끌면 시간(15분 단위)과 날짜가 바뀌고, 빈 칸을 누르면 그 시각에 추가한다.
- 분류: Calendar 앱
- 가져오기: `import { TimeGrid } from '@/cal/TimeGrid'`
- 코드 위치: `apps/calendar/src/cal/TimeGrid.tsx:25` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/time-grid
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/time-grid 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 캘린더 탭의 주 보기와 일 보기에 쓴다. 날짜 목록(`days`)이 1개면 일 보기, 7개면 주 보기다.
- 시간이 없는 할 일은 위쪽 띠에 칩으로 놓인다. 끌어서 그 띠에 놓으면 시간 없음으로 바뀐다.
### 쓰지 않는다
- 한 달을 보는 데 쓰지 않는다. `MonthView`를 쓴다.
- 끌기만으로 옮길 수 있게 두지 않는다. 일정 편집 화면에서도 시간을 바꿀 수 있어야 한다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 일 보기 | `days`가 1개. |
| 주 보기 | `days`가 7개. 옆으로 날짜를 바꿀 수 있다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 시각 줄 높이 44, 왼쪽 시각 영역 50 | 끌기 단위 15분. 일정 길이를 모르면 할 일 30분, 일정 60분으로 그린다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 끌기 시작 | 길게 누르면 중간 세기 진동(`haptic.firm`)과 함께 블록이 떠오르고 `shadow(3)`가 붙는다. 원래 자리는 투명도 0.3. |
| 놓음 | 새 시각이 `onMove`로 전달된다. 위 띠에 놓으면 `time`이 `null`이다. |
| 빈 칸 누름 | `onAdd(날짜, 시각)`이 호출된다. |
## 사용 방법
### 기본
```tsx
import { TimeGrid } from '@/cal/TimeGrid';
openItem(e)}
onAdd={(day, time) => openQuickAdd({ date: day, time })}
onMove={(e, day, time) => move(e, day, time)} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `days` | `string[]` | — | 필수 | 보여 줄 날짜 목록(`"YYYY-MM-DD"`). 1개(일) 또는 7개(주). |
| `today` | `string` | — | 필수 | 오늘 날짜. |
| `data` | `Range \| undefined` | — | 필수 | 해당 기간의 항목. 불러오는 중이면 `undefined`. |
| `showPlan` | `boolean` | — | 필수 | Mordy 계획 블록을 보일지. |
| `onOpen` | `(e: { item?: CalItem; plan?: PlanItem }) => void` | — | 필수 | 블록이나 칩을 눌렀을 때 호출된다. |
| `onAdd` | `(day: string, time: string) => void` | — | 필수 | 빈 칸을 눌렀을 때 날짜와 시각(15분 단위)과 함께 호출된다. |
| `onMove` | `(e: { item?: CalItem; plan?: PlanItem }, day: string, time: string \| null) => void` | — | 필수 | 끌어서 놓았을 때 새 날짜와 시각(`null`이면 시간 없음)과 함께 호출된다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 시각 줄 | 높이 44, 위쪽 1px `line` |
| 블록 | 모서리 6, 왼쪽 3px 과목 색 테두리, 바탕 과목 색 15%, 글자 11.5/15 SemiBold |
| 끌기 | 15분 단위(`SNAP`), 좌우로는 날짜 이동 |
| 시각 영역 | 폭 50 |
## 접근성
- 끌기는 제스처다. 대체 방법으로 블록을 눌러 일정 편집 화면에서 시간을 바꿀 수 있다.
- 시각 글자는 `caption`, 블록 글자는 11.5px로 작다. 일정 상세는 눌러서 편집 화면에서 읽는다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 블록 글자 (11.5px 굵게) | `text` | `surface` | 17.15:1 통과 | 15.25:1 통과 | 16.13:1 통과 | 10.92:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 끌어서 옮기는 일은 일정 편집 화면에서도 할 수 있게 한다.
- 잘못한 예: 끌기만 시간을 바꾸는 유일한 방법으로 두지 않는다.
## 함께 보는 컴포넌트
- [MonthView](https://developers.moring.co/tds/components/month-view.md): 한 달을 화면 가득 보여 주는 달력이다. 날마다 칩(시험 먼저, 시간 있는 일정, 나머지)을 줄에 들어가는 만큼 보이고 넘치면 "+n"으로 줄인다.
- [DayStrip](https://developers.moring.co/tds/components/day-strip.md): 한 주 7일을 한 줄로 보여 주고 날짜를 골라 이동하는 주간 줄이다. 좌우로 밀면 앞뒤 주로 이동한다. 일정이 있는 날에는 점이 찍힌다.
- [ItemRow](https://developers.moring.co/tds/components/item-row.md): 오늘과 할 일 목록의 한 줄이다. 할 일은 앞에 체크 원, 일정은 앞에 시간과 색 막대가 있다.
---
# ItemRow
오늘과 할 일 목록의 한 줄이다. 할 일은 앞에 체크 원, 일정은 앞에 시간과 색 막대가 있다.
- 분류: Calendar 앱
- 가져오기: `import { ItemRow } from '@/cal/ui'`
- 코드 위치: `apps/calendar/src/cal/ui.tsx:133` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/item-row
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/item-row 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 할 일과 일정을 같은 목록에서 보여 줄 때 쓴다.
- 보조 줄에는 날짜, 시간, 과목, 그룹 캘린더 이름이 가운뎃점으로 이어진다.
### 쓰지 않는다
- Mordy 계획에는 쓰지 않는다. `PlanRow`를 쓴다.
- 제목을 세 줄 이상 쓰지 않는다. 두 줄에서 줄인다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 할 일 (task) | 앞에 `CheckCircle`. 완료되면 제목에 취소선과 `textTertiary`. |
| 일정 (event) | 앞에 시작 시간(없으면 "종일")과 끝 시간, 그 옆에 과목·캘린더 색 막대(3px). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 최소 높이 52, 위아래 11, 요소 사이 12 | 시간 영역 폭 44. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 완료 | 취소선, `textTertiary`. |
| 반복·그룹 | 오른쪽에 반복(Repeat2) 또는 그룹(UsersRound) 아이콘 15px. |
| 권한 없음 | 그룹 일정을 고칠 권한이 없으면 체크가 `disabled`다. |
## 사용 방법
### 기본
```tsx
import { ItemRow, useLookups } from '@/cal/ui';
const l = useLookups();
toggle(item)} onPress={() => open(item)} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `item` | `CalItem` | — | 필수 | 항목(할 일 또는 일정). |
| `l` | `ReturnType` | — | 필수 | 과목·캘린더 조회 표(`useLookups()`의 결과). 색과 이름을 찾는 데 쓴다. |
| `onToggle` | `() => void` | — | 선택 | 체크 원을 눌렀을 때 호출된다(할 일). |
| `onPress` | `() => void` | — | 선택 | 행을 눌렀을 때 호출된다. |
| `showDate` | `string` | — | 선택 | 보조 줄 맨 앞에 쓸 날짜 글자. |
| `right` | `ReactNode` | — | 선택 | 오른쪽에 놓을 요소. |
| `inSubject` | `boolean` | — | 선택 | `true`이면 보조 줄에서 과목 이름을 뺀다(과목 화면 안에서). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 행 | 최소 높이 52, 위아래 11, 요소 사이 12 |
| 제목 | `body`, 최대 2줄, 완료는 `textTertiary` + 취소선 |
| 보조 줄 | `small` `textSecondary`, 1줄, 가운뎃점으로 이은 날짜·시간·과목·캘린더 |
| 일정 막대 | 폭 3, 모서리 2, 과목 색(없으면 캘린더 색) |
## 접근성
- 행의 역할은 `button`, 이름은 제목이다. 체크 원은 따로 `checkbox`("{제목} 완료")다.
- 완료는 취소선, 색, 체크로 함께 나타낸다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 제목 | `text` | `bg` | 16.72:1 통과 | 16.78:1 통과 | 15.51:1 통과 | 12.24:1 통과 | 4.5:1 |
| 보조 줄 (13px) | `textSecondary` | `bg` | 4.13:1 미달 | 7.65:1 통과 | 4.69:1 통과 | 7.96:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 일정은 시간과 색 막대, 할 일은 체크로 구분한다.
- 잘못한 예: 긴 제목을 한 줄에 억지로 넣지 않는다. 두 줄까지 보이고 줄여진다.
## 함께 보는 컴포넌트
- [PlanRow](https://developers.moring.co/tds/components/plan-row.md): Mordy가 만든 학습 계획 한 줄이다. 체크 원, 제목, "Mordy" 표지, 푼 문제 진행률(막대)이 있다. 직접 옮긴 계획에는 자물쇠가 붙는다.
- [CheckCircle](https://developers.moring.co/tds/components/check-circle.md): 할 일을 완료로 바꾸는 동그란 체크 표시다. 비어 있다가 누르면 색이 채워지며 체크가 나타난다.
- [DdayChip](https://developers.moring.co/tds/components/dday-chip.md): 시험까지 남은 날을 "D-24"처럼 보여 주는 칩이다. 7일 이내면 빨간색, 그 외는 주색이다.
---
# PlanRow
Mordy가 만든 학습 계획 한 줄이다. 체크 원, 제목, "Mordy" 표지, 푼 문제 진행률(막대)이 있다. 직접 옮긴 계획에는 자물쇠가 붙는다.
- 분류: Calendar 앱
- 가져오기: `import { PlanRow } from '@/cal/ui'`
- 코드 위치: `apps/calendar/src/cal/ui.tsx:179` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/plan-row
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/plan-row 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- Mordy 시험 계획에서 온 항목을 보여 줄 때 쓴다. 풀면 계획이 자동으로 체크된다.
- "Mordy" 표지는 이 계획이 AI가 아니라 Mordy의 규칙 기반 계산이라는 뜻이다.
### 쓰지 않는다
- 사용자가 직접 만든 할 일에는 쓰지 않는다. `ItemRow`를 쓴다.
- "Mordy" 표지를 "AI"로 바꾸지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 진행 중 | 푼 문제가 있으면 제목 아래에 4px 진행 막대(과목 색). |
| 완료 | 체크, 취소선. Mordy에서 풀어서 완료했으면 "Mordy에서 풀어서 완료" 보조 글. |
| 건너뜀 | 취소선, "건너뜀". |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 최소 높이 52 | `ItemRow`와 같다. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 진행 중 | 기본 모습이다. |
| 완료 | 기본 모습이다. |
| 직접 옮김 | 오른쪽에 자물쇠(14px). 다시 계산해도 그 날짜를 유지한다. |
## 사용 방법
### 기본
```tsx
import { PlanRow, useLookups } from '@/cal/ui';
setStatus(plan)} onPress={() => openPlan(plan, today)} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `plan` | `PlanItem` | — | 필수 | 계획 항목. |
| `l` | `ReturnType` | — | 필수 | 과목·캘린더 조회 표(`useLookups()`의 결과). |
| `onToggle` | `() => void` | — | 필수 | 체크 원을 눌렀을 때 호출된다. |
| `onPress` | `() => void` | — | 필수 | 행을 눌렀을 때 호출된다(계획 시트가 열린다). |
| `showDate` | `string` | — | 선택 | 보조 줄 맨 앞에 쓸 날짜 글자. |
| `inSubject` | `boolean` | — | 선택 | `true`이면 제목 앞의 "과목 · "을 뺀다. |
| `tag` | `boolean` | `true` | 선택 | `false`이면 "Mordy" 표지를 숨긴다. 기본 `true`. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 표지 | `MordyTag` small(높이 17) |
| 진행 막대 | 높이 4, 모서리 2, `track` 바탕, 과목 색 채움, 위 6 |
| 보조 줄 | "n/m문제 · 약 n분 · Mordy에서 풀어서 완료 · 건너뜀" |
## 접근성
- 행은 `button`, 체크 원은 `checkbox`다. 자물쇠는 이름 "직접 옮긴 계획"을 가진다.
- 진행률은 글자("3/8문제")와 막대로 함께 알린다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 보조 줄 (13px) | `textSecondary` | `bg` | 4.13:1 미달 | 7.65:1 통과 | 4.69:1 통과 | 7.96:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 진행률을 글자와 막대로 함께 보인다.
- 잘못한 예: "Mordy" 표지를 "AI"로 바꾸지 않는다. 이 계획은 규칙 기반 계산이다.
## 함께 보는 컴포넌트
- [ItemRow](https://developers.moring.co/tds/components/item-row.md): 오늘과 할 일 목록의 한 줄이다. 할 일은 앞에 체크 원, 일정은 앞에 시간과 색 막대가 있다.
- [MordyTag](https://developers.moring.co/tds/components/mordy-tag.md): "Mordy"라고 적힌 작은 표지다. Mordy가 만든 계획 항목임을 알린다. 이 계획은 실제 학습 기록으로 계산한 규칙 기반이라 AI라고 부르지 않는다.
- [CheckCircle](https://developers.moring.co/tds/components/check-circle.md): 할 일을 완료로 바꾸는 동그란 체크 표시다. 비어 있다가 누르면 색이 채워지며 체크가 나타난다.
- [ProgressBar](https://developers.moring.co/tds/components/progress-bar.md): 진행률을 가로 막대로 보여 준다. 채운 부분은 기본으로 서비스 주색 그라데이션이다.
---
# DdayChip
시험까지 남은 날을 "D-24"처럼 보여 주는 칩이다. 7일 이내면 빨간색, 그 외는 주색이다.
- 분류: Calendar 앱
- 가져오기: `import { DdayChip } from '@/cal/ui'`
- 코드 위치: `apps/calendar/src/cal/ui.tsx:119` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/dday-chip
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/dday-chip 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 시험 제목 옆에 D-Day를 알릴 때 쓴다. 제목을 함께 주면 "중간고사 D-24"가 된다.
### 쓰지 않는다
- 시험이 아닌 마감에는 쓰지 않는다.
- 색만으로 임박함을 알리지 않는다. "D-3"이라는 글자가 뜻을 말한다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 여유 (8일 이상) | `primarySoft` 바탕, `primary` 글자. |
| 임박 (7일 이하) | `dangerSoft` 바탕, `danger` 글자. |
| 당일·지남 | 0이면 "D-Day", 음수이면 "D+3". |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 26, 좌우 10 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 여유 | 기본 모습이다. |
| 임박 | 기본 모습이다. |
| 당일 | 기본 모습이다. |
## 사용 방법
### 기본
```tsx
import { DdayChip } from '@/cal/ui';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `dday` | `number` | — | 필수 | 남은 날 수. 0이면 당일, 음수이면 지난 날. |
| `title` | `string` | — | 선택 | 앞에 붙일 시험 제목. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 높이 26, 좌우 10, pill, 글자 `smallStrong` |
| 색 기준 | 7일 이하이면 `danger`, 그 외 `primary` |
## 접근성
- 눌리지 않는 표지다. 글자가 날 수를 읽어 준다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 여유 글자 (13px 굵게) | `primary` | `primarySoft` | 3.45:1 미달 | 4.32:1 미달 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
| 임박 글자 (13px 굵게) | `danger` | `dangerSoft` | 4.09:1 미달 | 5.39:1 통과 | 4.69:1 통과 | 5.43:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: 임박하면 빨간 칩, 글자로도 "D-3".
- 잘못한 예: 시험이 아닌 일정에 D-Day 칩을 붙이지 않는다.
## 함께 보는 컴포넌트
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
- [ItemRow](https://developers.moring.co/tds/components/item-row.md): 오늘과 할 일 목록의 한 줄이다. 할 일은 앞에 체크 원, 일정은 앞에 시간과 색 막대가 있다.
---
# MordyTag
"Mordy"라고 적힌 작은 표지다. Mordy가 만든 계획 항목임을 알린다. 이 계획은 실제 학습 기록으로 계산한 규칙 기반이라 AI라고 부르지 않는다.
- 분류: Calendar 앱
- 가져오기: `import { MordyTag } from '@/cal/ui'`
- 코드 위치: `apps/calendar/src/cal/ui.tsx:110` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/mordy-tag
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/mordy-tag 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- Mordy 계획 항목의 제목 옆에 붙인다(`PlanRow`가 자동으로 쓴다).
### 쓰지 않는다
- 사용자가 직접 만든 항목에 붙이지 않는다.
- "AI"로 바꾸지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 기본 | 높이 19, 글자 11. |
| small | 높이 17, 글자 10, 좌우 5. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 높이 19 | `small`은 17. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 기본 | `primarySoft` 바탕, `primary` Bold 글자, 모서리 6. |
## 사용 방법
### 기본
```tsx
import { MordyTag } from '@/cal/ui';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `small` | `boolean` | — | 선택 | `true`이면 더 작게(높이 17, 글자 10). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 모양 | 높이 19, 모서리 6, 좌우 6, `primarySoft` |
| 글자 | `caption` 계열 Bold, 11, `primary` |
## 접근성
- 눌리지 않는 표지다. "Mordy"라고 읽힌다.
### 글자 색 대비
| 쓰임 | 글자색 | 바탕 | Mordy 라이트 | Mordy 다크 | Morit 라이트 | Morit 다크 | 기준 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 글자 (11px 굵게) | `primary` | `primarySoft` | 3.45:1 미달 | 4.32:1 미달 | 4.21:1 미달 | 4.68:1 통과 | 4.5:1 |
## 잘한 예와 잘못한 예
- 잘한 예: Mordy가 만든 계획에만 붙인다.
- 잘못한 예: 사용자가 직접 만든 할 일에 붙이지 않는다.
## 함께 보는 컴포넌트
- [PlanRow](https://developers.moring.co/tds/components/plan-row.md): Mordy가 만든 학습 계획 한 줄이다. 체크 원, 제목, "Mordy" 표지, 푼 문제 진행률(막대)이 있다. 직접 옮긴 계획에는 자물쇠가 붙는다.
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
---
# SubjectMark · CategoryDot · GroupAvatar
일정 줄과 칩에 붙는 작은 표식 세 가지다. 과목 아이콘(`SubjectMark`), 분류 색 점(`CategoryDot`), 그룹 캘린더의 동그란 그림(`GroupAvatar`).
- 분류: Calendar 앱
- 가져오기: `import { SubjectMark, CategoryDot, GroupAvatar } from '@/cal/ui'`
- 코드 위치: `apps/calendar/src/cal/ui.tsx:81` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/calendar-marks
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/calendar-marks 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- `SubjectMark`: 과목 칩과 행에 과목 아이콘(사진이 있으면 사진)을 작게 보인다.
- `CategoryDot`: 카테고리의 색을 점으로 보인다.
- `GroupAvatar`: 그룹 캘린더의 그림을, 없으면 주제 이모지나 이름 첫 글자를 보인다.
### 쓰지 않는다
- 표식만으로 과목이나 그룹을 구분하지 않는다. 이름을 글자로 함께 쓴다.
- 큰 크기로 쓰지 않는다. 행과 칩 안의 작은 표식이다.
## 종류
| 이름 | 설명 |
| --- | --- |
| SubjectMark | `SubjectIcon`을 작게 쓴다. 기본 18. |
| CategoryDot | 색 점. 기본 10. |
| GroupAvatar | 원. 기본 24. 사진, 이모지, 첫 글자 순서로 보인다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| SubjectMark | 18 | |
| CategoryDot | 10 | |
| GroupAvatar | 24 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 그림 없는 그룹 | `primarySoft` 원에 `primary` 이모지나 첫 글자. |
## 사용 방법
### 기본
```tsx
import { CategoryDot, GroupAvatar, SubjectMark } from '@/cal/ui';
```
### 속성(props)
#### SubjectMark
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `subject` | `Subject` | — | 필수 | 과목(아이콘, 색, 사진을 가진 객체). |
| `size` | `number` | `18` | 선택 | 크기(px). 기본 18. |
#### CategoryDot
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `category` | `Pick` | — | 필수 | 카테고리(`color`를 가진 객체). |
| `size` | `number` | `10` | 선택 | 지름(px). 기본 10. |
#### GroupAvatar
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `cal` | `Pick` | — | 필수 | 그룹 캘린더(`name`, `groupIconPath`, `groupEmoji`). |
| `size` | `number` | `24` | 선택 | 지름(px). 기본 24. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| GroupAvatar 글자 | 이모지는 지름 × 0.55, 첫 글자는 × 0.45, Bold, `primary` |
| CategoryDot 색 | 과목 팔레트의 `glyph` 색 |
## 접근성
- 표식은 장식이다. `GroupAvatar`의 사진에는 캘린더 이름이 접근성 이름으로 붙는다. 이름 글자를 함께 쓴다.
## 잘한 예와 잘못한 예
- 잘한 예: 이름을 옆에 글자로 쓴다.
- 잘못한 예: 점 색만으로 카테고리를 구분하지 않는다.
## 함께 보는 컴포넌트
- [SubjectIcon](https://developers.moring.co/tds/components/subject-icon.md): 과목을 나타내는 둥근 아이콘 타일이다. 과목마다 기호(Σ, Aa, 책, 건물, 잎, 지구, …)와 색이 정해져 있다.
- [Avatar](https://developers.moring.co/tds/components/avatar.md): 사용자의 사진, 또는 닉네임 첫 글자를 브랜드 그라데이션 원 위에 보여 준다. `NameLine`은 닉네임, 공식 배지, @아이디를 한 줄로 놓는다.
- [ColorRow](https://developers.moring.co/tds/components/color-row.md): 과목 팔레트 8색을 동그란 점으로 늘어놓고 하나를 고르는 컴포넌트다. 카테고리와 캘린더의 색을 정할 때 쓴다.
---
# ColorRow
과목 팔레트 8색을 동그란 점으로 늘어놓고 하나를 고르는 컴포넌트다. 카테고리와 캘린더의 색을 정할 때 쓴다.
- 분류: Calendar 앱
- 가져오기: `import { ColorRow } from '@/cal/sheets'`
- 코드 위치: `apps/calendar/src/cal/sheets.tsx:301` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/color-row
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/color-row 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 카테고리나 캘린더의 색을 고르게 할 때 쓴다.
### 쓰지 않는다
- 팔레트에 없는 색을 고르게 하지 않는다. 8색(`blue`, `green`, `orange`, `red`, `purple`, `teal`, `yellow`, `gray`)만 쓴다.
## 종류
종류가 없다.
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 점 둘레 고리 36, 점 22 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 선택됨 | 점 둘레에 그 색의 고리가 생긴다. |
## 사용 방법
### 기본
```tsx
import { ColorRow } from '@/cal/sheets';
setDraft({ ...draft, color: k })} />
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `value` | `string` | — | 필수 | 선택된 색 이름. |
| `onChange` | `(k: ColorKey) => void` | — | 필수 | 색을 눌렀을 때 새 색 이름과 함께 호출된다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 점 | 색은 과목 팔레트의 `glyph` |
| 고리 | 선택된 점 둘레에 2px 같은 색 |
## 접근성
- 역할은 `radiogroup`, 각 점은 `radio`이고 이름은 색 이름("blue")이다. 한국어 이름이 없어서 화면 읽기 프로그램은 영어로 읽는다.
- 선택은 고리로 나타낸다.
## 잘한 예와 잘못한 예
- 잘한 예: 선택된 색에 고리가 보인다.
- 잘못한 예: 점 색만으로 이름을 알 수 없다. 선택한 이름을 근처에 글자로 쓰면 더 좋다.
## 함께 보는 컴포넌트
- [SubjectMark · CategoryDot · GroupAvatar](https://developers.moring.co/tds/components/calendar-marks.md): 일정 줄과 칩에 붙는 작은 표식 세 가지다. 과목 아이콘(`SubjectMark`), 분류 색 점(`CategoryDot`), 그룹 캘린더의 동그란 그림(`GroupAvatar`).
- [SubjectIcon](https://developers.moring.co/tds/components/subject-icon.md): 과목을 나타내는 둥근 아이콘 타일이다. 과목마다 기호(Σ, Aa, 책, 건물, 잎, 지구, …)와 색이 정해져 있다.
---
# PlannerPreview
환영·설정 화면에서 플래너가 어떤 모양인지 보여 주는 작은 그림이다. 하루를 과목별 또는 시간별로 나눈 예시다.
- 분류: Calendar 앱
- 가져오기: `import { PlannerPreview } from '@/cal/Preview'`
- 코드 위치: `apps/calendar/src/cal/Preview.tsx:18` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/planner-preview
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/planner-preview 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 시작 화면에서 앱의 모양을 미리 보여 줄 때만 쓴다.
### 쓰지 않는다
- 실제 사용자 데이터 화면에 쓰지 않는다. 고정된 예시다.
- 예시 글(국어·수학 항목)을 실제 기능 설명으로 쓰지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| subject | 과목별 묶음(기본). |
| time | 시간별 줄(16:00, 19:30, 21:00). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 안쪽 여백 18 | `compact`면 14. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 예시 | 고정된 예시 항목을 보인다. |
## 사용 방법
### 기본
```tsx
import { PlannerPreview } from '@/cal/Preview';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `style` | `'subject' \| 'time'` | `'subject'` | 선택 | `"subject"`(기본) 또는 `"time"`. |
| `compact` | `boolean` | — | 선택 | `true`이면 안쪽 여백을 줄인다. |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 면 | `surface`, 모서리 22, `shadow(1)` |
## 접근성
- 장식용 그림이다. 안의 글자는 읽힌다.
## 잘한 예와 잘못한 예
- 잘한 예: 시작 화면의 예시로만 쓴다.
- 잘못한 예: 실제 일정 목록 대신 쓰지 않는다.
## 함께 보는 컴포넌트
- [PlanRow](https://developers.moring.co/tds/components/plan-row.md): Mordy가 만든 학습 계획 한 줄이다. 체크 원, 제목, "Mordy" 표지, 푼 문제 진행률(막대)이 있다. 직접 옮긴 계획에는 자물쇠가 붙는다.
- [ItemRow](https://developers.moring.co/tds/components/item-row.md): 오늘과 할 일 목록의 한 줄이다. 할 일은 앞에 체크 원, 일정은 앞에 시간과 색 막대가 있다.
---
# QuickAddHost
할 일, 일정, 시험을 한 줄로 빠르게 추가하는 시트다. 한 줄에 "내일 4시 영어학원"처럼 쓰면 날짜와 시간을 알아듣고, 못 알아들은 것은 알약 선택으로 채운다.
- 분류: Calendar 앱
- 가져오기: `import { QuickAddHost } from '@/cal/QuickAdd'`
- 코드 위치: `apps/calendar/src/cal/QuickAdd.tsx:35` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/quick-add
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/quick-add 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 앱 루트에 `QuickAddHost`를 한 번 두고, 어디서든 `openQuickAdd(seed)`로 연다.
- 날짜와 시각을 미리 채워 열려면 `seed`에 넘긴다(예: 달력에서 길게 누른 날).
### 쓰지 않는다
- 긴 양식에는 쓰지 않는다. 마지막 알약 "자세히"를 누르거나 시트를 위로 끌면 전체 편집 화면이 열리고 입력은 그대로 이어진다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 할 일 · 일정 · 시험 | 위쪽의 작은 `PillTabs`로 고른다. 글을 쓰면 알아듣고 자동으로 정한다(직접 고르면 고정). |
| 알약 | 날짜, 시간, 과목, 카테고리, 알림, 캘린더, 자세히. 한 줄에서 말하지 않은 것만 남는다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | `Sheet` | 입력 글자는 최대 80자. 아래 "추가" 버튼 높이 48. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 열림 | `openQuickAdd(seed)`로 연다. 입력 칸에 바로 초점이 간다. |
| 저장 중 | "추가" 버튼이 `busy`가 된다. |
| 추가 불가 | 제목이 없으면 "추가"가 `disabled`다. |
## 사용 방법
### 루트에 두기
```tsx
import { QuickAddHost } from '@/cal/QuickAdd';
```
### 열기
```tsx
import { openQuickAdd } from '@/cal/QuickAdd';
openQuickAdd(); // 빈 상태로
openQuickAdd({ kind: 'event', date: '2026-10-10', time: '16:00' });
```
### 속성(props)
| 함수 | 형태 | 설명 |
| --- | --- | --- |
| `openQuickAdd` | `—` | 빠른 추가 시트를 연다. `seed`: `{ kind?: "task" \| "event" \| "exam", date?, time?, calendarId?, subjectId? }`. |
속성이 없다.
`QuickAddHost`는 속성이 없다. 앱 루트에 한 번 두고 `openQuickAdd`로 연다. 한국어 날짜와 시간 해석은 `lib/cal/parse.ts`가 한다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 구성 | `Sheet` + 작은 `PillTabs` + 한 줄 입력 + 알약들 + `PrimaryButton`(추가, 높이 48) |
| 입력 | 최대 80자, 안내 문구는 종류별로 다르다(예: "예: 내일 4시 영어학원") |
## 접근성
- `Sheet`의 접근성을 따른다. 입력 칸과 알약에는 이름(글자)이 있다.
- 날짜와 시간을 알아들었는지는 알약의 글자가 바뀌어 알린다.
## 잘한 예와 잘못한 예
- 잘한 예: 한 줄로 쓰고 알아듣지 못한 것만 알약으로 채운다.
- 잘못한 예: 긴 양식을 이 시트에 늘리지 않는다. "자세히"로 전체 화면을 연다.
## 함께 보는 컴포넌트
- [Sheet](https://developers.moring.co/tds/components/sheet.md): 화면 아래에서 올라오는 시트다. 위쪽 두 모서리가 둥글고, 손잡이와 제목 줄, 닫기 버튼이 있다.
- [TabBar (Calendar)](https://developers.moring.co/tds/components/calendar-tab-bar.md): Mordy Calendar 앱의 탭 줄이다. 오늘 · 캘린더 · (+) · 할 일 · 더보기. Mordy 앱의 `TabBar`와 같은 모양이고 가운데 +는 빠른 추가를 연다.
- [DateSheetHost · PlanSheetHost · MissedSheet · CategorySheetHost](https://developers.moring.co/tds/components/calendar-sheets.md): Calendar 앱의 시트 네 가지다. 날짜 고르기, Mordy 계획 항목 메뉴, 어제 못한 계획 정리, 카테고리 고르기. 앞의 둘과 마지막은 앱 루트에 한 번 두고 함수 호출로 연다.
- [ExamForm](https://developers.moring.co/tds/components/exam-form.md): 시험을 만드는 양식이다. 시험 날짜(여러 날 가능), 과목 칩, 과목별 시험 범위를 고른다. 빠른 추가 시트와 전체 편집 화면이 같은 양식을 쓴다.
---
# DateSheetHost · PlanSheetHost · MissedSheet · CategorySheetHost
Calendar 앱의 시트 네 가지다. 날짜 고르기, Mordy 계획 항목 메뉴, 어제 못한 계획 정리, 카테고리 고르기. 앞의 둘과 마지막은 앱 루트에 한 번 두고 함수 호출로 연다.
- 분류: Calendar 앱
- 가져오기: `import { DateSheetHost, PlanSheetHost, MissedSheet, CategorySheetHost } from '@/cal/sheets'`
- 코드 위치: `apps/calendar/src/cal/sheets.tsx:41` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/calendar-sheets
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/calendar-sheets 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 날짜 고르기: `await pickDate({ title, value, today })` 또는 여러 날(`pickDates`)을 부른다. 날짜, 날짜 없음(`null`), 닫음(`undefined`)을 돌려준다.
- 계획 항목 메뉴: `openPlan(plan, today)`. "Mordy에서 풀기", "오늘로 옮기기", "다른 날로 옮기기" 등을 보인다.
- 어제 못한 계획: `MissedSheet`. 자동으로 옮기지 않고 항목마다 사용자가 정한다.
- 카테고리 고르기: `await pickCategory(value)`. 새 카테고리도 만들 수 있다.
### 쓰지 않는다
- 사용자 몰래 계획을 옮기지 않는다. 어제 못한 계획은 항상 사용자가 정한다.
- 시트 위에 시트를 겹치지 않는다. 앞 시트가 닫힌 뒤 다음 시트를 연다.
## 종류
| 이름 | 설명 |
| --- | --- |
| DateSheetHost | `Calendar`로 날짜를 고르고 "n일로 정하기" 버튼. 여러 날 시험이면 날짜를 모두 눌러 "n일 선택"(최대 14일). |
| PlanSheetHost | `Sheet`와 행동 목록. Mordy 앱이 없으면 설치 안내를 한다. |
| MissedSheet | 어제 못한 계획 목록과 항목별 선택. |
| CategorySheetHost | 카테고리 목록(없음, 내 카테고리, 시험으로 묶기)과 "새 카테고리"(이름 + `ColorRow` + 만들기). |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | `Sheet` | 카테고리 목록은 최대 높이 440. |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 키보드 입력 중 | 새 카테고리를 만들 때는 시트가 짧아지도록 양식만 보인다. |
## 사용 방법
### 앱 루트에 두기
```tsx
import { CategorySheetHost, DateSheetHost, PlanSheetHost } from '@/cal/sheets';
```
### 날짜 고르기
```tsx
import { pickDate } from '@/cal/sheets';
const date = await pickDate({ title: '마감일', value: item.date, today, allowNone: true });
if (date === undefined) return; // 닫음
save({ date });
```
### 어제 못한 계획
```tsx
setOpen(false)} />
```
### 속성(props)
| 함수 | 형태 | 설명 |
| --- | --- | --- |
| `pickDate` | `—` | 날짜 하나를 고른다. 날짜 문자열, 날짜 없음이면 `null`, 닫으면 `undefined`. |
| `pickDates` | `—` | 여러 날(시험일)을 고른다. 정렬된 날짜 목록, 닫으면 `undefined`. |
| `openPlan` | `—` | Mordy 계획 항목 메뉴를 연다. |
| `pickCategory` | `—` | 카테고리를 고른다. 카테고리 id, 없음이면 `null`, 닫으면 `undefined`. |
| `openInMordy` | `—` | Mordy 앱에서 풀이를 시작한다. 앱이 없으면 스토어를 안내한다. |
#### DateSheetHost
속성이 없다.
#### PlanSheetHost
속성이 없다.
#### MissedSheet
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `visible` | `boolean` | — | 필수 | 열려 있는지. |
| `items` | `PlanItem[]` | — | 필수 | 어제 못한 계획 항목 목록. |
| `today` | `string` | — | 필수 | 오늘 날짜. |
| `onClose` | `() => void` | — | 필수 | 닫을 때 호출된다. |
#### CategorySheetHost
속성이 없다.
`DateSheetHost`, `PlanSheetHost`, `CategorySheetHost`는 속성이 없다. 앱 루트에 한 번씩 두고 위 함수로 연다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 공통 | 모두 `Sheet`(제목 줄, ✕, 끌어서 닫기) |
| 날짜 버튼 | `PrimaryButton`(화살표 없음). 고르기 전에는 `disabled`와 "날짜를 골라 주세요" |
## 접근성
- `Sheet`의 접근성을 그대로 따른다.
- 카테고리 행은 `radio`이고 선택 상태가 전달된다.
## 잘한 예와 잘못한 예
- 잘한 예: 선택 전에는 버튼에 "날짜를 골라 주세요"처럼 이유를 글자로 쓴다.
- 잘못한 예: 어제 못한 계획을 사용자 몰래 오늘로 옮기지 않는다.
## 함께 보는 컴포넌트
- [Sheet](https://developers.moring.co/tds/components/sheet.md): 화면 아래에서 올라오는 시트다. 위쪽 두 모서리가 둥글고, 손잡이와 제목 줄, 닫기 버튼이 있다.
- [Calendar](https://developers.moring.co/tds/components/date-calendar.md): 한 달 달력에서 여러 날짜를 고르는 컴포넌트다. 시험 날짜를 고를 때 쓴다.
- [ColorRow](https://developers.moring.co/tds/components/color-row.md): 과목 팔레트 8색을 동그란 점으로 늘어놓고 하나를 고르는 컴포넌트다. 카테고리와 캘린더의 색을 정할 때 쓴다.
- [PlanRow](https://developers.moring.co/tds/components/plan-row.md): Mordy가 만든 학습 계획 한 줄이다. 체크 원, 제목, "Mordy" 표지, 푼 문제 진행률(막대)이 있다. 직접 옮긴 계획에는 자물쇠가 붙는다.
---
# ExamForm
시험을 만드는 양식이다. 시험 날짜(여러 날 가능), 과목 칩, 과목별 시험 범위를 고른다. 빠른 추가 시트와 전체 편집 화면이 같은 양식을 쓴다.
- 분류: Calendar 앱
- 가져오기: `import { ExamForm } from '@/cal/ExamForm'`
- 코드 위치: `apps/calendar/src/cal/ExamForm.tsx:59` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/exam-form
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/exam-form 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- 이 컴포넌트는 Mordy Calendar 앱(`apps/calendar/src/cal`)에만 있다.
- 시험을 추가하거나 고칠 때 쓴다. 시트 안에서는 기본 모양, 전체 화면에서는 `full`.
- 여러 날 시험이면 날짜마다 과목을 따로 고를 수 있다(`detailed`).
### 쓰지 않는다
- 시험이 아닌 일정에 쓰지 않는다.
- 과목을 하나도 고르지 않은 채 저장하지 않는다. `examPayload`가 `null`을 돌려주고 저장할 수 없다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 시트 모양 | 날짜 알약, 과목 칩, 마지막에 "자세히"(`trailing`). |
| `full` | 전체 화면. 새 날짜는 시트에서 고른 과목으로 시작하고 날짜별로 바꿀 수 있다. |
## 크기
| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | 내용에 맞는 높이 | |
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 과목 선택 | 과목 칩(아이콘 + 이름)이 체크 상자 역할이다. 선택하면 `primary` 계열로 바뀐다. |
| 범위 선택 | 과목을 누르면 그 과목의 시험 범위(단원, 개념)를 고르는 시트가 열린다. |
## 사용 방법
### 기본
```tsx
import { ExamForm } from '@/cal/ExamForm';
```
### 속성(props)
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `draft` | `ExamDraft` | — | 필수 | 양식의 값(날짜, 과목, 과목별 범위). |
| `onChange` | `(d: ExamDraft) => void` | — | 필수 | 값이 바뀔 때 새 값과 함께 호출된다. |
| `subjects` | `Subject[]` | — | 필수 | 고를 수 있는 과목 목록. |
| `today` | `string` | — | 필수 | 오늘 날짜. |
| `full` | `boolean` | — | 선택 | `true`이면 전체 화면 양식이다. |
| `trailing` | `ReactNode` | — | 선택 | 마지막 알약 줄 끝에 놓을 요소(시트의 "자세히"). |
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 날짜 알약 | 한 날이면 "10월 20일", 여러 날이면 "10월 20일 외 n일", 없으면 "시험 날짜" |
| 과목 칩 | `SubjectMark` 18 + `smallStrong` 이름, 역할 `checkbox` |
## 접근성
- 과목 칩은 `checkbox`이고 `checked` 상태가 전달된다.
## 잘한 예와 잘못한 예
- 잘한 예: 여러 날 시험이면 날짜마다 과목을 따로 고르게 한다.
- 잘못한 예: 과목 없이 저장하게 두지 않는다.
## 함께 보는 컴포넌트
- [QuickAddHost](https://developers.moring.co/tds/components/quick-add.md): 할 일, 일정, 시험을 한 줄로 빠르게 추가하는 시트다. 한 줄에 "내일 4시 영어학원"처럼 쓰면 날짜와 시간을 알아듣고, 못 알아들은 것은 알약 선택으로 채운다.
- [DateSheetHost · PlanSheetHost · MissedSheet · CategorySheetHost](https://developers.moring.co/tds/components/calendar-sheets.md): Calendar 앱의 시트 네 가지다. 날짜 고르기, Mordy 계획 항목 메뉴, 어제 못한 계획 정리, 카테고리 고르기. 앞의 둘과 마지막은 앱 루트에 한 번 두고 함수 호출로 연다.
- [Chip](https://developers.moring.co/tds/components/chip.md): 짧은 글자를 담는 작은 알약 표지다. 상태, 분류, D-Day를 알린다. 눌리지 않는다.
---
# ThemeProvider · ThemeFade
화면 모드(시스템·라이트·다크)와 글자 크기 설정을 앱 전체에 적용하는 도구다. 모드가 바뀌는 순간 새 배경색 덮개로 화면을 가렸다가 걷어서 깜빡임을 줄인다.
- 분류: 면과 목록
- 가져오기: `import { ThemeProvider, ThemeFade } from '@/ui/theme'`
- 코드 위치: `apps/mobile/src/ui/theme.tsx:117` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/theme
## 미리보기
실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/theme 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.
## 언제 쓰는가
### 쓴다
- `ThemeProvider`로 앱 맨 바깥을 감싼다. "시스템 설정" 모드일 때 기기의 다크 모드를 따라간다.
- `ThemeFade`를 앱 루트에 한 번 둔다. 모드가 바뀔 때만 보인다.
- 컴포넌트는 `useTheme()`을 호출해 모드가 바뀌면 다시 그려지게 한다. 안 쓰는 화면(탭 뒤에 있는 화면)은 다시 돌아올 때 한꺼번에 갱신된다.
### 쓰지 않는다
- 모드를 컴포넌트 안에서 직접 판단해 색을 고르지 않는다. 토큰(`color.*`)이 모드를 이미 따른다.
- `useAppearance`로 저장하는 값(모드, 글자 크기, 자동 다음, 채점 방식) 말고 새 설정을 여기에 섞지 않는다.
## 종류
| 이름 | 설명 |
| --- | --- |
| 화면 모드 | `"system"`(시스템 설정, 기본), `"light"`(라이트), `"dark"`(다크). |
| 글자 크기 | 0.92(작게), 1(기본), 1.12(크게), 1.24(아주 크게). |
## 크기
크기가 없다.
## 상태
| 상태 | 모습과 동작 |
| --- | --- |
| 모드 바뀜 | 1) 새 배경색 덮개가 90ms 동안 나타난다. 2) 한 프레임 뒤 토큰이 바뀌고 모든 화면이 다시 그려진다. 3) 60ms 기다렸다가 300ms 동안 덮개가 사라진다. |
| 처음 열림 | 저장된 설정을 읽어 애니메이션 없이 바로 적용한다(스플래시 화면이 사라지기 전). |
## 사용 방법
### 앱 루트
```tsx
import { ThemeFade, ThemeProvider } from '@/ui/theme';
```
### 설정 바꾸기
```tsx
import { useAppearance } from '@/ui/theme';
const { mode, setMode, fontScale, setFontScale } = useAppearance();
setMode('dark');
setFontScale(1.12);
```
### 컴포넌트에서
```tsx
import { useTheme } from '@/ui/theme';
const { isDark, fontScale } = useTheme();
```
### 속성(props)
#### ThemeProvider
| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | — | 필수 | 앱 전체. |
#### ThemeFade
속성이 없다.
`ThemeFade`는 속성이 없다. 설정은 `useAppearance()`(저장소), 읽기는 `useTheme()`로 한다.
## 치수와 토큰
| 항목 | 값 |
| --- | --- |
| 저장 | 기기의 로컬 저장소 `ui.appearance` |
| 덮개 | 나타남 90ms, 걷힘 300ms(60ms 지연) |
| 글자 크기 | `FONT_STEPS` 4단계. 글자 페이지의 표를 본다. |
## 접근성
- 기기의 글자 크기와 다크 모드 설정을 따른다.
- 앱 설정의 글자 크기 단계에는 "작게, 기본, 크게, 아주 크게"라는 이름이 있다.
## 잘한 예와 잘못한 예
- 잘한 예: 모드에 따른 색은 토큰으로만 쓴다.
- 잘못한 예: 컴포넌트 안에서 `isDark`로 HEX를 고르지 않는다.
## 함께 보는 컴포넌트
- [AppText](https://developers.moring.co/tds/components/app-text.md): 앱의 모든 글자를 그리는 컴포넌트다. 글자 스타일 11종 중 하나를 `variant`로 고르면 크기, 줄 높이, 굵기, 자간이 정해진다.
- [PillTabs](https://developers.moring.co/tds/components/pill-tabs.md): 알약 모양의 탭 모음이다. 하나를 고르면 그 칸이 짙은 색(네이비, 다크 모드는 주색)으로 채워진다.
- [SettingsGroup](https://developers.moring.co/tds/components/settings-list.md): 설정 화면의 목록이다. 작은 구역 이름, 행이 든 흰 패널, 아래 안내 글로 이루어진다.