---
description: Figma URL과 출력 경로를 받아 디자인 토큰·컴포넌트 매핑 규칙에 따라 page.tsx + page.module.scss를 생성한다.
---

## 작업
- 목표: Figma URL 디자인을 규칙에 따라 코드로 변환
- 출력: 지정 경로에 `page.tsx`, `page.module.scss`
- 입력:
  - Figma URL: `$ARGUMENTS` 첫 번째 인자
  - 출력 경로: `$ARGUMENTS` 두 번째 인자
  - 출력 경로 예시: `apps/docs/app/external/my-page`
- 생성 제한:
  - 생성 파일: `page.tsx`, `page.module.scss`만
  - 모든 컴포넌트·로직: `page.tsx` 한 파일 안에 작성

## Figma 조회
- `node id`가 주어지면 MCP `nodeId`에는 URL 전체가 아니라 해당 값만 넣는다.
- 기본 순서: `figma/get_metadata` → `figma/get_design_context`
- `get_design_context` timeout 시 보강:
  - `figma/get_screenshot`
  - `figma/get_variable_defs`
  - 필요 시 같은 `node id`로 `TalkToFigma/get_node_info`, `scan_text_nodes`, `scan_nodes_by_types`
- 금지: URL 전체를 `nodeId`에 넣어 schema mismatch를 만들지 않는다.

## 최종 비교
- 코드 초안 작성 후 원본 Figma screenshot 기준으로 최소 1회 재비교·보정한다.
- screenshot과 결과물이 다르면 상태성 차이를 먼저 점검한다.
  - `hover`
  - `focus`
  - `active / pressed`
  - `selected / current`
  - `checked`
  - `expanded / opened`
  - `disabled`
  - 입력 완료 / 값 선택 완료 상태
- 상태성 차이로 설명되면 기본 상태, 클래스 구조, 속성, 스타일을 정리해 자연스럽게 재현한다.
- 상태성 차이로 설명되지 않으면 생성 누락으로 보고 아래를 보정한다.
  - 레이아웃
  - 간격
  - 정렬
  - 색상
  - 테두리
  - 그림자
  - 타이포
  - 아이콘
  - 배경
  - 텍스트
- 상태 여부가 불명확하면 screenshot에 더 가까운 쪽을 우선하고, 억지 hover 연출보다 구조/스타일 누락 여부를 먼저 의심한다.

## 변환 규칙

### SCSS 설정
```scss
// sales-frontend-design-system 설치 후 상단에 선언(함수, 믹스인 제공)
@use 'sales-frontend-design-system/design-system-context' as *;
```

### 디자인 토큰

#### 색상: `colors(토큰명)`
- `Surface/primary`: `colors(surface-primary)`
- `Surface/secondary`: `colors(surface-secondary)`
- `Surface/neutral`: `colors(surface-neutral)`
- `Surface/neutral_1`: `colors(surface-neutral_1)`
- `Surface/neutral_2`: `colors(surface-neutral_2)`
- `Surface/neutral_3`: `colors(surface-neutral_3)`
- `Surface/neutral_4`: `colors(surface-neutral_4)`
- `Text/primary`: `colors(text-primary)`
- `Text/secondary`: `colors(text-secondary)`
- `Text/body`: `colors(text-body)`
- `Text/body_0`: `colors(text-body_0)`
- `Text/body_1`: `colors(text-body_1)`
- `Text/body_2`: `colors(text-body_2)`
- `Text/body_3`: `colors(text-body_3)`
- `Text/neutral_1`: `colors(text-neutral_1)`
- `Text/neutral_2`: `colors(text-neutral_2)`
- `Border/primary_1`: `colors(border-primary_1)`
- `Border/primary_2`: `colors(border-primary_2)`
- `Border/neutral_1`: `colors(border-neutral_1)`
- `Border/neutral_2`: `colors(border-neutral_2)`
- `Border/neutral_3`: `colors(border-neutral_3)`
- `Border/neutral_4`: `colors(border-neutral_4)`
- `Border/neutral_5`: `colors(border-neutral_5)`
- `Border/neutral_6`: `colors(border-neutral_6)`
- `Background/white`: `colors(background-100)`
- `Background/neutral`: `colors(background-200)`
- `Background/dimmed`: `colors(background-dimmed)`

#### 패딩: `padding(토큰명)`
- `Padding/zero`: `padding(zero)`
- `Padding/xxxxSmall`: `padding(xxxxsmall)`
- `Padding/xxxSmall`: `padding(xxxsmall)`
- `Padding/xxSmall`: `padding(xxsmall)`
- `Padding/xSmall`: `padding(xsmall)`
- `Padding/small`: `padding(small)`
- `Padding/medium`: `padding(medium)`
- `Padding/large`: `padding(large)`
- `Padding/xLarge`: `padding(xlarge)`
- `Padding/xxLarge`: `padding(xxlarge)`
- `Padding/xxxLarge`: `padding(xxxlarge)`
- `Padding/xxxxLarge`: `padding(xxxxlarge)`

#### 마진: `margin(토큰명)`
- `Margin/zero`: `margin(zero)`
- `Margin/inputfield-vertical`: `margin(inputfield-vertical)`
- `Margin/inputfield-horizontal`: `margin(inputfield-horizontal)`
- `Margin/button-large`: `margin(button-large)`
- `Margin/button-medium`: `margin(button-medium)`
- `Margin/button-small`: `margin(button-small)`
- `Margin/card`: `margin(card)`
- `Margin/divider-xlarge`: `margin(divider-xlarge)`
- `Margin/divider-large`: `margin(divider-large)`
- `Margin/divider-medium`: `margin(divider-medium)`
- `Margin/divider-small`: `margin(divider-small)`
- `Margin/divider-xsmall`: `margin(divider-xsmall)`
- `Margin/title-xlarge`: `margin(title-xlarge)`
- `Margin/title-large`: `margin(title-large)`
- `Margin/title-medium`: `margin(title-medium)`
- `Margin/title-small`: `margin(title-small)`
- `Margin/title-xsmall`: `margin(title-xsmall)`
- `Margin/textarea-large`: `margin(textarea-large)`
- `Margin/textarea-medium`: `margin(textarea-medium)`
- `Margin/textarea-small`: `margin(textarea-small)`
- `Margin/textarea-xsmall`: `margin(textarea-xsmall)`
- `Margin/textarea-xxsmall`: `margin(textarea-xxsmall)`
- `Margin/card/around_large`: `margin(card-around-large)`
- `Margin/card/around_small`: `margin(card-around-small)`
- `Margin/modal`: `margin(modal)`

#### 보더 라디우스: `radius-basic(토큰명)`
- `Corner/small`: `radius-basic(small)`
- `Corner/medium`: `radius-basic(medium)`
- `Corner/large`: `radius-basic(large)`
- `Corner/xlarge`: `radius-basic(xlarge)`
- `Corner/full`: `radius-basic(full)`

#### 그림자: `shadow(토큰명)`
- `Shadow/small`: `shadow(small)`
- `Shadow/medium`: `shadow(medium)`
- `Shadow/large`: `shadow(large)`

```scss
.card {
  box-shadow: shadow(medium);
}
```

#### 호버: `@include hover { ... }`
- 터치 디바이스 sticky hover 방지 믹스인
- 데스크톱(마우스): `:hover`
- 터치 디바이스: `:active`

```scss
.card {
  @include hover {
    background-color: colors(surface-neutral_1);
  }
}
```

#### 반응형: `@include media(디바이스) { ... }`
- `tablet`: `min-width: 768px`, 태블릿 이상
- `pc`: `min-width: 1281px`, PC 이상
- `pc-wide`: `min-width: 1441px`, PC Wide 이상

```scss
.container {
  padding: padding(small);

  @include media(tablet) {
    padding: padding(medium);
  }

  @include media(pc) {
    padding: padding(large);
  }
}
```

#### 타이포그래피: `@include apply-typography(토큰명)`
- `Typography/title1`: `apply-typography(title1)`
- `Typography/title2`: `apply-typography(title2)`
- `Typography/title3`: `apply-typography(title3)`
- `Typography/title4`: `apply-typography(title4)`
- `Typography/title5`: `apply-typography(title5)`
- `Typography/subtitle1`: `apply-typography(subtitle1)`
- `Typography/subtitle2`: `apply-typography(subtitle2)`
- `Typography/subtitle3`: `apply-typography(subtitle3)`
- `Typography/body1`: `apply-typography(body1)`
- `Typography/body2`: `apply-typography(body2)`
- `Typography/body3`: `apply-typography(body3)`
- `Typography/body4`: `apply-typography(body4)`
- `Typography/caption`: `apply-typography(caption)`
- `Typography/link_large`: `apply-typography(link_large)`
- `Typography/link_medium`: `apply-typography(link_medium)`
- `Typography/link_small`: `apply-typography(link_small)`
- `Typography/input_large`: `apply-typography(input_large)`
- `Typography/input_medium`: `apply-typography(input_medium)`
- `Typography/input_small`: `apply-typography(input_small)`

### 아이콘
- 아이콘 노드(`data-name`: `sub-ui/*`, `main-ui/*`, `illust/*` 등)를 발견하면 `project-context.md`의 `아이콘 시스템` 섹션 규칙을 따른다.
- 피그마 노드 `data-name`에서 아이콘 경로를 추출해 변환한다.
- 시스템 아이콘: `sub-ui/*`, `main-ui/*` → `sales-frontend-assets`의 SVG React 컴포넌트 import
- 그래픽/일러스트: `graphics/*`, `images/*`, `illust/*` → 아이콘 CSS 클래스 사용

```tsx
// 시스템 아이콘 — Button의 icon prop 예시
import { IconSubUiArrowRightWhite } from 'sales-frontend-assets';

<Button
  variant="primary"
  size="medium"
  appearance="filled"
  icon={<IconSubUiArrowRightWhite width={20} height={20} />}
  iconDir="right"
>
  다음
</Button>;

// 그래픽/일러스트 — CSS 클래스 예시
<span className={cx('dsp-icons', 'dsp-icons-graphics-avatar-man-60')} />;
```

### 컴포넌트 매핑
- 디자인시스템 컴포넌트는 `sales-frontend-design-system`에서 import한다.

```tsx
// ✅ 올바른 import 예시
import {
  Button,
  Icon,
  FormField,
  DatePickerSingleRenew,
  Radio,
  Checkbox,
  CheckboxButton,
  Segment,
  Select,
  BottomBar,
  Modal
} from 'sales-frontend-design-system';
```

- 매핑 우선순위: ① 매핑 표(정확한 이름 매칭) → ② 패턴 힌트(부분 문자열 매칭, 대소문자 무시) → ③ HTML + module.scss 직접 구현
- `field`, `field base`는 `variant`를 먼저 확인한다.

#### 정확한 이름 매칭
- `Button`: `Button`
- `label`: `FormField.Label`
- `field` / `field base` + `variant=text`: `FormField.TextField`
- `Supporting Text`: `FormField.HelperText`
- `field` / `field base` + `variant=date`: `DatePickerSingleRenew`
- `field` / `field base` + `variant=select`: `Select` + `Select.Option`
- `radio`(개별 항목): `Radio.Item`
- `RadioGroup` / `radio group`(래퍼): `Radio.Root` + 내부 `Radio.Item`
- `checkbox`: `Checkbox`
- `checkbox button`: `CheckboxButton`
- `chip`(개별 항목): `Chip.Item`
- `Chips`(래퍼): `Chip.Root` + 내부 `Chip.Item`
- `Segment`, `segment group`, `Segment Group`: `Segment.Root` + `Segment.Item`
- `bottom bar`: `BottomBar.Desktop` / `BottomBar.Mobile`
- `Modal`, `modal`: `Modal`(Compound)

#### 패턴 힌트
- `radio group`, `RadioGroup`: `Radio.Root`(래퍼)
- `radio`: `Radio.Item`(개별 항목)
- `input`, `text field`, `textfield`, `text input`: `FormField.TextField`
- `calendar`, `date`, `datepicker`, `date field`, `date picker`: `DatePickerSingleRenew`
- `select`, `dropdown`, `drop down`: `Select` + `Select.Option`
- `chips`, `chip group`: `Chip.Root`(래퍼)
- `chip`: `Chip.Item`(개별 항목)
- `segment`, `tab group`, `button group`, `toggle group`: `Segment.Root` + `Segment.Item`
- `bottom bar`, `action bar`, `footer bar`: `BottomBar.Desktop` / `BottomBar.Mobile`
- `modal`, `dialog`, `popup`, `alert`, `confirm`: `Modal`(Compound)

### 컴포넌트 사용 예시

#### Button
```tsx
<Button
  variant="primary" // 'primary' | 'secondary' | 'neutral' | 'negative'
  size="medium" // 'large' | 'medium' | 'small' | 'xsmall'
  appearance="filled" // 'filled' | 'outline'
  width="auto" // 'full' | 'fit-content' | 'auto'
  disabled={false}
  icon={<SomeIcon />} // optional
  iconDir="right" // 'left' | 'right'
  tagName="button" // 'button' | 'a'
>
  텍스트
</Button>
```

#### FormField.Label
```tsx
<FormField.Label className={cx('field-label')}>대면일자</FormField.Label>
```

#### FormField.TextField
- Figma `field` / `field base`의 `state` 값 매핑:
  - `default`: props 없음
  - `readonly`: `readOnly`
  - `disabled`: `disabled`

```tsx
// default
<FormField.TextField placeholder="입력하세요" size="medium" value={value} onChange={handleChange} />

// readonly
<FormField.TextField size="medium" value="김한화" readOnly />

// disabled
<FormField.TextField size="medium" value="김한화" disabled />
```

#### FormField.HelperText
```tsx
<FormField.HelperText variant="error" size="small">
  내용을 입력해주세요
</FormField.HelperText>

<FormField.HelperText variant="caption" size="small">
  도움말 텍스트
</FormField.HelperText>
```

#### DatePickerSingleRenew
```tsx
import dayjs from 'dayjs';

<DatePickerSingleRenew
  defaultValue={dayjs()}
  inputFormat="YYYY.MM.DD"
  onValueChange={(date) => { ... }} // dayjs.Dayjs 반환
  inputProps={{
    fieldControlContextProps: { size: 'medium' },
  }}
/>
```

#### Radio
- 개별 `radio`: `Radio.Item`
- 래퍼 `RadioGroup`, `radio group`: `Radio.Root`
- `align`: 가로 나열이면 `"horizontal"`, 세로 나열이면 `"vertical"`

```tsx
// RadioGroup 래퍼가 있는 경우 → Radio.Root로 감싸기
<Radio.Root
  name="search-type"
  size="medium"       // 'large' | 'medium' | 'small' | 'xsmall'
  align="horizontal"  // 'horizontal'(가로) | 'vertical'(세로)
  defaultValue="ssn"
  onValueChange={(value) => { ... }}
>
  <Radio.Item value="ssn">주민등록번호로 검색</Radio.Item>
  <Radio.Item value="policy">증권번호로 검색</Radio.Item>
</Radio.Root>
```

#### Chip
- 개별 `chip`: `Chip.Item`
- 래퍼 `Chips`: `Chip.Root`
- 다중 선택 가능

```tsx
<Chip.Root
  appearance="filled"   // 'filled' | 'outline'
  size="medium"          // 'medium' | 'small'
  variant="rectangle"    // 'rectangle' | 'round'
  defaultValue={selectedValues}
  onValueChange={(values) => { ... }}
>
  <Chip.Item value="option1">옵션 1</Chip.Item>
  <Chip.Item value="option2">옵션 2</Chip.Item>
  <Chip.Item value="option3">옵션 3</Chip.Item>
</Chip.Root>
```

#### Checkbox
```tsx
<Checkbox
  id="agree-1"
  size="medium" // 'large' | 'medium' | 'small' | 'xsmall'
  variant="sub" // 'main' | 'sub'
  checked={isChecked}
  onChange={(e) => setIsChecked(e.target.checked)}
>
  동의함
</Checkbox>
```

#### CheckboxButton
```tsx
<CheckboxButton
  id="option-1"
  appearance="outline" // 'filled' | 'outline'
  size="medium"
  variant="sub"
  checked={isChecked}
  onChange={(e) => setIsChecked(e.target.checked)}
>
  옵션 1
</CheckboxButton>
```

#### Segment
```tsx
import { Segment } from 'sales-frontend-design-system';

<Segment.Root
  appearance="filled" // 'filled' | 'outline'
  size="medium"       // 'large' | 'medium' | 'small' | 'xsmall'
  align="center"      // 'center' | 'left'
  defaultValue="a"
  onValueChange={(value) => { ... }}
>
  <Segment.Item value="a" className={cx('segment-item')}>옵션 A</Segment.Item>
  <Segment.Item value="b" className={cx('segment-item')} disabled>옵션 B</Segment.Item>
</Segment.Root>
```

#### Select
```tsx
import { Select } from 'sales-frontend-design-system';

const [value, setValue] = useState('');

<Select
  value={value}
  onChange={setValue}
  placeholder="선택하세요"
  size="large" // 'large' | 'medium' | 'small' | 'xsmall'
>
  <Select.Option value="option1">옵션 1</Select.Option>
  <Select.Option value="option2">옵션 2</Select.Option>
  <Select.Option value="option3">옵션 3</Select.Option>
</Select>;
```

#### BottomBar
```tsx
// Desktop: 좌/우 영역에 ReactNode 자유 배치
<BottomBar.Desktop
  sticky={false} // true면 sticky, false(기본)면 fixed
  leftContent={<Button variant="neutral" size="medium" appearance="outline">이전</Button>}
  rightContent={<Button variant="primary" size="medium" appearance="filled">다음</Button>}
/>

// Mobile: buttons 배열로 전달
<BottomBar.Mobile
  sticky={false}
  isVertical={false}
  buttons={[
    <Button variant="neutral" size="large" appearance="outline">이전</Button>,
    <Button variant="primary" size="large" appearance="filled">다음</Button>,
  ]}
/>
```

#### Modal
```tsx
import { Modal, Button, useModalState } from 'sales-frontend-design-system';

const { isOpen, openModal, closeModal } = useModalState();

// Compound 패턴 (구조를 자유롭게 조합)
<Modal.Root
  isOpen={isOpen}
  onClose={closeModal}
  modalSize="medium" // 'small' | 'medium' | 'large' | 'xlarge' | 'full-screen'
  closeOnBackdropClick={true}
  closeOnEscapeKey={true}
>
  <Modal.Overlay />
  <Modal.Content>
    <Modal.Header headerTitle="제목" showCloseButton={true} />
    <Modal.Body>본문 내용</Modal.Body>
    <Modal.Footer>
      <Button variant="neutral" size="large" appearance="filled" width="full" onClick={closeModal}>
        취소
      </Button>
      <Button variant="primary" size="large" appearance="filled" width="full">
        확인
      </Button>
    </Modal.Footer>
  </Modal.Content>
</Modal.Root>;
```

## 코드 작성 규칙
- 레이아웃 구조:
  - 피그마 노드의 `flex-direction`, 부모-자식 배치를 코드에 그대로 반영
  - Fill Container(`flex-[1_0_0]`) → `flex: 1` 또는 `width: 100%`
  - 노드별 사이즈 속성(고정값/Fill/Hug)은 개별적으로 반영하고 임의로 통합하지 않음
  - DS 컴포넌트 기본 레이아웃과 피그마 구조가 다르면 피그마 구조 우선
  - Figma Group 노드는 무시하고 자식 노드만 직접 배치
- DS 컴포넌트에 추가 스타일이 필요할 때:
  1. 아토믹 클래스로 해결 가능하면 `className`에 직접 전달
  2. 사용 가능한 클래스는 `packages/design-system/src/styles/base/_atomic-classes.scss` 참조
  3. 아토믹 클래스로 해결할 수 없으면 `page.module.scss`에 클래스를 만들고 `cx()`로 `className`에 전달

```tsx
// 예: 피그마에서 Segment 아이템에 min-width: 100px이 지정된 경우
<Segment.Item value="no" className={cx('segment-item')}>
  아니오
</Segment.Item>
```

```scss
// page.module.scss
.segment-item {
  min-width: 6.25rem;
}
```
