> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 장바구니 아이템 블록

> Cart items 블록: 라인 아이템 목록, Product 행, 그리고 중첩된 하위 블록의 컨테이너예요.

> **Cart items** 블록은 장바구니의 라인 아이템 목록으로, 구매자가 추가한 각 상품을 이미지, 제목, 옵션(variant), 가격, 수량 스테퍼, 제거 컨트롤과 함께 렌더링해요. 필수 블록이며, 장바구니의 하위 블록(**Product** 행, [**Subscription upgrade**](/ko/aftersell/cart/subscription-upgrade-block), [**Custom code**](/ko/aftersell/cart/custom-code-blocks))을 담는 컨테이너로서 라인별 하위 블록이 연결되는 구조를 제공해요.

<Frame>
  <img src="https://mintcdn.com/aftersell/1Y3gBpUfxv16VGSW/images/aftersell/cart-items-block-line-product-title-variant.png?fit=max&auto=format&n=1Y3gBpUfxv16VGSW&q=85&s=3c9f088b55cfd8450dcb8670dfe0728a" alt="상품 이미지, 제목, 옵션, 가격, 수량 스테퍼, 제거 컨트롤과 함께 라인 아이템을 보여주는 Cart items 블록" width="1420" height="486" data-path="images/aftersell/cart-items-block-line-product-title-variant.png" />
</Frame>

<div id="the-product-row">
  ## Product 행
</div>

Cart items 안에는 **Product** 하위 블록이 있어요: 실제 라인 아이템 행이에요. 잠겨 있고 자동으로 추가되므로, 모든 Cart items 블록에는 제거할 수 없는 Product 행이 항상 정확히 하나 있으며, 다른 하위 블록의 위치를 그 주변으로 조정해요. 이 설정은 각 라인의 가격 표시 방식을 제어해요:

| 설정                                         | 제어 대상                                                                                                                                                                                   | 기본값                            |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| **Strike-through price**                   | 취소선으로 표시할 가격: **Compare-at 또는 할인 전 가격 중 더 높은 것**, **Compare-at 가격**, **할인 전 가격**, 또는 **취소선 없음**.                                                                                        | Compare-at 또는 할인 전 가격 중 더 높은 것 |
| **Strike-through price for subscriptions** | 구독 라인에 대한 동일한 선택인데, 두 가지 차이가 있어요: 추가 옵션인 **Subscription compare-at price**가 있고, **Compare-at price**가 **Product compare-at, then subscription compare-at**으로 이름이 바뀌어요.                  | Compare-at 또는 할인 전 가격 중 더 높은 것 |
| **Savings label**                          | 절약 금액을 **Amount**(금액), **Percentage**(비율)로 표시할지, 아니면 **Hidden**(숨김)으로 할지 결정해요.                                                                                                          | Amount                         |
| **Bundle price**                           | 번들 라인에 표시되는 가격의 계산 방식이에요. **Automatic**은 번들 내 모든 아이템의 총액(다른 아이템이 무료일 때는 메인 아이템의 가격)을 표시해요. **Main item price only**는 메인(앵커) 아이템의 가격만 표시해요. 이는 표시 라벨일 뿐이에요 — Shopify의 장바구니 총액이 항상 기준이에요. | Automatic                      |
| **Savings text**                           | 절약 라벨이에요. `{{value}}` 토큰을 지원해요.                                                                                                                                                         | `Save {{value}}`               |

행 자체는 상품 이미지(가능한 경우 상품 페이지로 연결), 제목, 옵션, 가격 및 취소선 처리된 compare-at 가격, 수량 스테퍼, 제거 버튼을 렌더링해요. 번들 라인은 구성 요소의 펼침 목록을 표시해요.

<div id="text-styling">
  ### 텍스트 스타일
</div>

Product 행의 Design 설정에는 **Text** 섹션이 있어요. 각 라인 아이템의 개별 텍스트 요소의 타이포그래피를 제어하는 데 사용하세요. 선택기에서 텍스트 요소를 선택해 설정을 조정하세요:

| 설정                 | 제어 대상                                                                                                  |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| **Text color**     | 선택한 텍스트 요소의 색상이에요.                                                                                     |
| **Font**           | **Theme font**(테마의 폰트를 상속) 또는 **Custom font**(테마가 이미 로드하는 폰트의 이름을 입력). **Product title**에서만 사용할 수 있어요. |
| **Size**           | 픽셀 단위의 글꼴 크기예요.                                                                                        |
| **Weight**         | 글꼴 굵기: Light, Regular, Medium, Semibold, Bold.                                                         |
| **Line height**    | 글꼴 크기에 대한 배수로 지정하는 줄 높이예요(예: `1.4`).                                                                   |
| **Letter spacing** | 픽셀 단위의 자간이에요. 음수 값은 텍스트를 좁혀요.                                                                          |

스타일을 지정할 수 있는 텍스트 요소는 카테고리별로 그룹화되어 있어요:

**Product**

* **Product title** — 각 라인의 상품 이름이에요. 커스텀 폰트 패밀리도 지원해요.
* **Variant** — 옵션 라벨이에요(예: *Size: Medium*).
* **Subscription plan** — 구독 라인에 표시되는 읽기 전용 플랜 라벨이에요.

**Pricing**

* **Price** — 라인의 현재 가격이에요.
* **Compare-at price** — 취소선 처리된 원래 가격이에요.
* **Savings** — 절약 라벨이에요(예: *Save \$5.00*). Size와 line height만 지정할 수 있어요 — 굵기와 색상은 위의 Rich Text Editor에서 설정해요.

**Bundle**

* **Bundle toggle** — 번들의 구성 요소 목록을 확장하는 펼침 헤더예요.
* **Bundle item title** — 번들 내부 각 구성 요소의 제목이에요.
* **Bundle item variant** — 각 번들 구성 요소의 옵션 라벨이에요.

필드를 비워두면 요소의 기본값이 유지돼요.

<Tip>
  장바구니 미리보기에서 텍스트 요소를 직접 클릭하면 해당 요소가 강조 표시되고 패널에서 컨트롤이 자동으로 열려요.
</Tip>

<div id="discount-tags-design">
  ### 할인 태그 디자인
</div>

Product 행의 Design 설정에는 **Discount tags** 섹션이 있어요. 각 라인 아이템에 표시되는 할인 태그 알약(pill)의 스타일을 지정하는 데 사용하세요:

| 설정                   | 제어 대상                   | 기본값       |
| -------------------- | ----------------------- | --------- |
| **Background color** | 할인 태그 알약의 배경색이에요.       | `#F1F1F1` |
| **Text color**       | 할인 태그 알약 내부의 텍스트 색상이에요. | `#585858` |
| **Border radius**    | 할인 태그 알약의 모서리 둥글기예요.    | `6px`     |

이 설정은 Cart items 블록의 라인 아이템 할인 태그에만 적용돼요. [Summary 블록](/ko/aftersell/cart/summary-block)의 할인 코드 태그는 별도로 스타일이 지정돼요.

<div id="sub-blocks-and-how-they-position">
  ## 하위 블록과 배치 방식
</div>

Cart items는 하위 블록을 담는 유일한 블록이에요. **하위 블록은 라인마다 한 번씩, 모든 상품 행 안에 렌더링되며**, 고정된 Product 행을 기준으로 배치돼요:

* Product 행 **앞에** 배치된 하위 블록은 각 라인에서 상품 콘텐츠 **위에** 표시돼요.
* Product 행 **뒤에** 배치된 하위 블록은 각 라인에서 상품 콘텐츠 **아래에** 표시돼요.

따라서 Product 행 뒤에 배치된 [Subscription upgrade](/ko/aftersell/cart/subscription-upgrade-block)는 전체 목록 하단에 한 번이 아니라, 대상이 되는 각 라인 아래에 표시돼요.

<div id="behavior">
  ## 동작 방식
</div>

* 장바구니에 아이템이 없으면 드로어가 빈 상태로 전환되고 이 블록은 표시되지 않아요.
* **장바구니 변경은 한 번에 하나씩 실행돼요.** 수량 업데이트나 제거가 진행되는 동안에는 장바구니 일관성을 유지하기 위해 행의 컨트롤이 비활성화되고, 변경이 완료되면 다시 활성화돼요.
* 라인의 수량을 1 미만으로 낮추면 해당 라인이 제거돼요. 스토어가 거부하는 수량(예: 재고 초과)은 마지막 유효 값으로 다시 동기화돼요.
* **번들은 하나의 단위로 변경돼요.** 번들의 앵커 라인에서 수량을 조정하면 전체 번들이 한 번의 작업으로 배율 조정돼요 — 자식 아이템이 앵커당 3개로 포함되어 있다면, 앵커를 1에서 2로 올리면 그 자식은 6이 돼요. 앵커를 제거하면 번들의 모든 구성원이 한꺼번에 제거돼요.
* **일부 번들은 수량을 변경할 수 없어요.** 번들의 자식 중 하나라도 소수 비율(예: 앵커당 1.5개)로 포함되어 있으면 해당 번들의 수량 스테퍼가 잠겨요: +/− 버튼과 수량 필드가 모두 비활성화되고, 입력한 수량도 허용되지 않아요. 번들 제거는 여전히 가능해요.
* **구독 라인은 플랜을 표시해요.** 라인에 판매 플랜이 있고 [Subscription upgrade](/ko/aftersell/cart/subscription-upgrade-block) 하위 블록이 꺼져 있거나 추가되지 않은 경우, Product 행이 옵션 아래에 읽기 전용 플랜 라벨을 표시해요 — 예: *Delivers every month (save 30%)*. 해당 하위 블록이 활성화되면 자체 선택기에서 플랜을 표시하므로, 중복되지 않도록 읽기 전용 라벨이 숨겨져요.

<div id="placement-and-limits">
  ## 배치 및 제한
</div>

* **영역:** 본문.
* **최대 개수:** 장바구니 상태당 1개.
* **상태:** 채워진 장바구니에서만.
* **잠겨 있으며 기본으로 추가돼요.** Cart items는 제거하거나 숨길 수 없고, 위치만 변경할 수 있어요.

<div id="custom-template">
  ## 커스텀 템플릿
</div>

Code 탭에서 [커스텀 템플릿](/ko/aftersell/cart/custom-templates)을 지원하며, 이 블록의 기본 마크업을 여러분의 JSX로 대체해요. 다음은 템플릿이 받는 props예요.

**Cart items** 컨테이너에는 커스텀 템플릿이 없어요. 그 안의 **Product** 행에는 있으며, 장바구니에서 가장 풍부한 영역이에요: 템플릿이 라인마다 한 번씩 렌더링돼요.

<div id="line-content">
  ### 라인 콘텐츠
</div>

| Prop               | 타입                          | 용도                                                                                                                                                       |
| ------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`            | `string`                    | 일반 텍스트 형태의 상품 제목이에요.                                                                                                                                     |
| `variantTitle`     | `string \| null`            | 옵션 라벨이에요. 단일 옵션 상품과 네이티브 번들에서는 `null`이에요.                                                                                                                |
| `url`              | `string \| null`            | 상품 페이지 URL이에요. 라인이 외부로 연결되지 않아야 할 때는 `null`이에요.                                                                                                          |
| `imageUrl`         | `string \| null`            | 라인 이미지예요. 상품에 이미지가 없으면 `null`이에요.                                                                                                                        |
| `quantity`         | `number`                    | 라인의 현재 수량이에요.                                                                                                                                            |
| `price`            | `string`                    | **이미 포맷된** 라인 가격이에요.                                                                                                                                     |
| `compareAtPrice`   | `string \| null`            | 취소선 처리된 "이전" 가격으로, 이미 포맷되어 있어요. 취소선 처리할 것이 없으면 `null`이에요.                                                                                                |
| `savingsHtml`      | `string \| null`            | 정제된 HTML 형태의 절약 라벨이에요. 숨겨져 있거나 절약이 없으면 `null`이에요.                                                                                                        |
| `discountTags`     | `string[]`                  | 이 라인에 적용된 할인의 제목이에요(예: `['Spring Sale']`). 없으면 `[]`이에요.                                                                                                  |
| `sellingPlanLabel` | `string \| null`            | 읽기 전용 구독 플랜 이름이에요. 일회성 라인이거나 [Subscription upgrade](/ko/aftersell/cart/subscription-upgrade-block#custom-template) 하위 블록이 대신 플랜 UI를 렌더링하고 있으면 `null`이에요. |
| `bundle`           | `object \| null`            | 앵커 라인의 [번들](/ko/aftersell/cart/sdk-cart-object#bundles) 뷰 모델이에요. 그 외에는 `null`이에요.                                                                        |
| `productId`        | `number`                    | Shopify 상품 ID예요.                                                                                                                                         |
| `variantId`        | `number`                    | Shopify 옵션 ID예요.                                                                                                                                         |
| `line`             | `AftersellCartLine`         | 위 props가 다루지 않는 모든 것을 위한 전체 [장바구니 라인](/ko/aftersell/cart/sdk-cart-object#cart-lines)이에요.                                                                 |
| `formatMoney`      | `(cents: number) => string` | 최소 단위 금액을 포맷해요. `line`에서 읽은 가격에 사용하세요.                                                                                                                   |

<Warning>
  **`price`와 `compareAtPrice`는 포맷된 문자열이고, `line`의 모든 값은 센트 단위예요.** `price`로 산술 연산을 하지 마세요. `line.finalLinePrice` 등에서 계산한 다음 결과를 `formatMoney`에 통과시키세요.
</Warning>

<div id="quantity-and-removal">
  ### 수량과 제거
</div>

| Prop                | 타입                                               | 용도                                                                                                         |
| ------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `increment`         | `() => void`                                     | 라인에 1을 추가해요.                                                                                               |
| `decrement`         | `() => void`                                     | 라인에서 1을 제거해요.                                                                                              |
| `remove`            | `() => void`                                     | 라인을 완전히 제거해요.                                                                                              |
| `quantityInput`     | `string`                                         | 제어되는(controlled) 수량 `<input>`의 현재 값이에요. 입력 중간 상태가 유지되도록 문자열이에요.                                            |
| `onQuantityInput`   | `(event: Event) => void`                         | 해당 필드의 `onInput` 핸들러예요.                                                                                    |
| `commitQuantity`    | `() => void`                                     | 입력한 수량을 적용해요. `onBlur`에 연결하세요.                                                                             |
| `onQuantityKeyDown` | `(event: KeyboardEvent) => void`                 | Enter로 적용되도록 하는 `onKeyDown` 핸들러예요.                                                                         |
| `busy`              | `boolean`                                        | 장바구니 변경이 진행 중인 동안 `true`예요. 이 값으로 컨트롤을 비활성화하세요.                                                            |
| `pending`           | `'increment' \| 'decrement' \| 'remove' \| null` | 현재 진행 중인 작업으로, 특정 위치에 스피너를 표시할 때 사용해요.                                                                     |
| `stepperLocked`     | `boolean`                                        | 라인이 소수 비율의 자식을 포함한 번들 앵커라서 수량을 변경할 수 없을 때 `true`예요. 스테퍼를 숨기거나 비활성화하세요 — 이 값이 설정된 동안에는 기본 핸들러가 이미 변경을 거부해요. |

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomTemplate(props) {
  return (
    <div className="cart-external-cart-items__row" style={{ display: 'flex', gap: '12px', opacity: props.busy ? 0.6 : 1 }}>
      {props.imageUrl && <img src={props.imageUrl} alt="" width={64} height={64} />}

      <div style={{ flex: 1 }}>
        {props.url ? <a href={props.url}>{props.title}</a> : <span>{props.title}</span>}
        {props.variantTitle && <div style={{ opacity: 0.6 }}>{props.variantTitle}</div>}
        {props.sellingPlanLabel && <div style={{ opacity: 0.6 }}>{props.sellingPlanLabel}</div>}

        {props.discountTags.map((tag) => (
          <span key={tag} style={{ fontSize: '11px', border: '1px solid', borderRadius: '4px', padding: '1px 5px' }}>
            {tag}
          </span>
        ))}

        {!props.stepperLocked && (
          <div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginTop: '6px' }}>
            <button type="button" onClick={props.decrement} disabled={props.busy}>&minus;</button>
            <input
              value={props.quantityInput}
              onInput={props.onQuantityInput}
              onBlur={props.commitQuantity}
              onKeyDown={props.onQuantityKeyDown}
              size={2}
            />
            <button type="button" onClick={props.increment} disabled={props.busy}>+</button>
            <button type="button" onClick={props.remove} disabled={props.busy}>
              {props.pending === 'remove' ? 'Removing…' : 'Remove'}
            </button>
          </div>
        )}
      </div>

      <div style={{ textAlign: 'right' }}>
        <div>{props.price}</div>
        {props.compareAtPrice && <s style={{ opacity: 0.5 }}>{props.compareAtPrice}</s>}
        {props.savingsHtml && <div dangerouslySetInnerHTML={{ __html: props.savingsHtml }} />}
      </div>
    </div>
  );
}
```

<div id="rendering-a-bundle">
  ### 번들 렌더링
</div>

번들의 앵커 라인에서 `bundle.children`이 번들 내용을 담고 있어요. 자식은 절대 자체 행으로 나타나지 않으므로, 렌더링하지 않으면 구매자가 번들에 무엇이 들어 있는지 볼 수 없어요:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomTemplate(props) {
  return (
    <div>
      <div>{props.title} {props.price}</div>

      {props.bundle && (
        <ul style={{ margin: '4px 0 0 12px', fontSize: '12px', opacity: 0.7 }}>
          {props.bundle.children.map((child, i) => (
            <li key={child.key ?? i}>{child.quantity} × {child.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
}
```

Shopify 네이티브 번들 구성 요소의 경우 자식의 `key`가 `null`이므로, 위와 같이 인덱스를 대신 사용하세요.

<div id="design">
  ## 디자인
</div>

설정 패널의 **Design** 섹션에서 이 블록의 스타일을 지정하세요. 이는 전역 디자인 위에 적용되는 블록별 재정의이며, 비어 있으면 전역 디자인으로 대체돼요.

디자인 설정이 무엇인가요? 자세한 내용은 여기에서 알아보세요: [디자인 설정](/ko/aftersell/cart/design-settings).
