# 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';

<PrimaryButton label="다음으로" onPress={next} />
```

### 저장 버튼: 처리 중과 비활성

```tsx
<PrimaryButton
  label="저장"
  arrow={false}
  busy={saving}
  disabled={!changed}
  onPress={() => void save()}
/>
```

### 화면 아래에 고정

```tsx
import { Screen } from '@/ui/layout';

<Screen footer={<PrimaryButton label="완료" arrow={false} height={56} onPress={done} />}>
  {/* 화면 내용 */}
</Screen>
```

### 속성(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<ViewStyle>` | — | 선택 | 바깥 여백이나 배치만 바꾸는 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)으로 돌려준다.
