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

주어진 Figma URL의 디자인을 아래 규칙에 따라 코드로 변환하고, 지정한 경로에 `*.stories.tsx`와 `*.module.scss`를 생성한다.

## 참조 조건

- Figma 조회 순서, `node id` 처리 방식, 디자인 토큰 매핑 규칙을 확인해야 할 때 이 문서를 읽는다.
- screenshot 비교와 상태성 UI 판단 기준을 확인해야 할 때 이 문서를 읽는다.

## 수행 계약

- Figma 조회는 이 문서의 `node id` 처리와 조회 순서를 실제로 따른다.
- 디자인 토큰, 아이콘, 컴포넌트 매핑은 추정이 아니라 이 문서의 기준을 우선 적용한다.
- 최종 결과물은 screenshot 비교와 상태성 UI 판단을 거쳐 마지막 보정 1회까지 수행한 뒤 확정한다.

## 입력

- **Figma URL**: $ARGUMENTS 의 첫 번째 인자
- **출력 경로**: $ARGUMENTS 의 두 번째 인자 (예: `apps/docs/app/external/my-page`)

> **⚠️ 파일 생성 규칙**: `*.stories.tsx` 와 `*.module.scss` 외 어떤 파일도 생성하지 않는다.
> 모든 컴포넌트·로직은 story 파일 한 개 안에 작성한다.

## Figma 조회 절차

1. 작업 입력값이나 `Figma 조회 힌트` 섹션에 `node id` 가 주어지면, Figma MCP 호출의 `nodeId` 인자에는 URL 전체가 아니라 그 값만 넣는다.
2. 조회 순서는 기본적으로 `figma/get_metadata` → `figma/get_design_context` 순으로 진행한다.
3. `get_design_context` 가 timeout 되면 `figma/get_screenshot`, `figma/get_variable_defs` 로 보강하고, 필요 시 `TalkToFigma/get_node_info`, `scan_text_nodes`, `scan_nodes_by_types` 로 같은 node id를 직접 조회한다.
4. URL 전체를 `nodeId` 자리에 넣어 schema mismatch를 만들지 않는다.

## 최종 비교 및 마지막 보정 규칙

1. 코드 초안을 만든 뒤, 최종 결과물을 원본 Figma screenshot 기준으로 한 번 더 비교 검토한다.
2. screenshot 과 결과물이 다르면 먼저 아래 상태성 차이 가능성을 우선 점검한다.
   - hover
   - focus
   - active / pressed
   - selected / current
   - checked
   - expanded / opened
   - disabled
   - 입력 완료 / 값 선택 완료 상태
3. 위 상태성 차이로 설명되는 경우, 그 상태를 반영한 결과가 자연스럽게 재현되도록 기본 상태, 클래스 구조, 속성, 스타일을 정리한다.
4. 상태성 차이로 설명되지 않으면 실제 생성 누락으로 보고 레이아웃, 간격, 정렬, 색상, 테두리, 그림자, 타이포, 아이콘, 배경, 텍스트를 보정한다.
5. 마지막 보정은 최소 1회 수행한 뒤 응답을 종료한다.
6. 상태 여부가 불명확할 때는 screenshot 에 더 가깝게 보이는 쪽을 우선하되, 억지 hover 연출보다 구조/스타일 누락 여부를 먼저 의심한다.

## 변환 규칙

### SCSS 설정

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

### 디자인 토큰 매핑

#### 색상 — `colors(토큰명)`

| Figma 변수           | SCSS 함수                   |
| -------------------- | --------------------------- |
| `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(토큰명)`

| Figma 변수          | SCSS 함수            |
| ------------------- | -------------------- |
| `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(토큰명)`

| Figma 변수                     | SCSS 함수                       |
| ------------------------------ | ------------------------------- |
| `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(토큰명)`

| Figma 변수      | SCSS 함수              |
| --------------- | ---------------------- |
| `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(토큰명)`

| Figma 변수      | SCSS 함수        |
| --------------- | ---------------- |
| `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(토큰명)`

| Figma 변수                | SCSS 믹스인                      |
| ------------------------- | -------------------------------- |
| `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';
```

Figma 컴포넌트명과 코드 컴포넌트명이 다를 수 있으므로 아래 표와 패턴 힌트 규칙을 기준으로 매핑한다.
표에 없는 컴포넌트는 HTML + module.scss로 직접 구현한다.

#### 매핑 표 (정확한 이름 매칭 우선)

Figma에서 `field` 또는 `field base` 컴포넌트를 발견하면 **variant 값을 먼저 확인**해 아래 표로 매핑한다.

| Figma 컴포넌트명                            | 코드 컴포넌트                              |
| ------------------------------------------- | ------------------------------------------ |
| `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`는 `Radio.Item`) |
| `checkbox`                                  | `Checkbox`                                 |
| `checkbox button`                           | `CheckboxButton`                           |
| `chip` (개별 항목)                          | `Chip.Item`                                |
| `Chips` (래퍼)                              | `Chip.Root` (내부 `chip`은 `Chip.Item`)    |
| `Segment`, `segment group`, `Segment Group` | `Segment.Root` + `Segment.Item`            |
| `bottom bar`                                | `BottomBar.Desktop` / `BottomBar.Mobile`   |
| `Modal`, `modal`                            | `Modal` (Compound)                         |

#### 패턴 힌트 규칙 (부분 문자열 매칭, 대소문자 무시)

Figma 컴포넌트명에 다음 패턴이 포함되면 아래 컴포넌트로 매핑한다:

| Figma명 패턴 (포함 여부)                                      | 매핑 컴포넌트                            |
| ------------------------------------------------------------- | ---------------------------------------- |
| `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)                       |

**매핑 우선순위**: ① 매핑 표 (정확한 이름 매칭) → ② 패턴 힌트 (부분 문자열 매칭) → ③ HTML + module.scss 직접 구현

> 아래는 매핑된 컴포넌트의 **사용 예시**다. 각 props의 허용값과 구조를 확인하고 Figma 디자인에 맞게 적용한다.

#### 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** 값에 따라 props를 매핑한다:

| Figma state | props      |
| ----------- | ---------- |
| `default`   | (없음)     |
| `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 (Compound)

Figma에서 `radio` 개별 항목은 `Radio.Item`, 이를 감싸는 래퍼(`RadioGroup` 등)는 `Radio.Root`로 매핑한다.

```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>
```

- `align`: 피그마에서 radio 항목들의 배치 방향에 따라 선택 (가로 나열 → `"horizontal"`, 세로 나열 → `"vertical"`)

#### Chip (Compound)

Figma에서 `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 (Compound)

```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 컴포넌트에 추가 스타일 적용**: 피그마에서 DS 컴포넌트에 `min-width`, `max-width`, `width` 등 props로 지원되지 않는 스타일이 있을 경우:
  1. 디자인 시스템이 제공하는 **아토믹 클래스**로 해결 가능하면 해당 클래스를 `className`에 직접 전달한다. 사용 가능한 클래스는 `packages/design-system/src/styles/base/_atomic-classes.scss`를 참조한다.
  2. 아토믹 클래스로 해결할 수 없으면 `*.module.scss`에 클래스를 생성하고 `cx()`를 통해 `className`에 전달한다.
  ```tsx
  // 예: 피그마에서 Segment 아이템에 min-width: 100px이 지정된 경우
  <Segment.Item value="no" className={cx('segment-item')}>
    아니오
  </Segment.Item>
  ```
  ```scss
  // example.module.scss
  .segment-item {
    min-width: 6.25rem;
  }
  ```
