민혜린 민혜린 08-14
refactor: FoxInput·FoxTextArea·FoxSelect의 라벨을 FoxFormLabel로 교체
@8d37131c4147dc444bdc8772e01e4dd7cb1bed7f
@fox/core/components/fox-input/fox-input.tsx
--- @fox/core/components/fox-input/fox-input.tsx
+++ @fox/core/components/fox-input/fox-input.tsx
@@ -10,6 +10,7 @@
   type ReactNode,
 } from "react";
 import { cx } from "../../utils";
+import { FoxFormLabel } from "../fox-form-label";
 
 export type FoxInputSize = "lg" | "md" | "sm";
 export type FoxInputState = "default" | "focused" | "completed" | "error" | "disabled" | "view";
@@ -137,9 +138,7 @@
       data-state={state}
     >
       {label && (
-        <label className="fox-input__label" htmlFor={fieldId}>
-          {label}
-        </label>
+        <FoxFormLabel htmlFor={fieldId}>{label}</FoxFormLabel>
       )}
 
       <div className="fox-input__box">
@fox/core/components/fox-select/fox-select.tsx
--- @fox/core/components/fox-select/fox-select.tsx
+++ @fox/core/components/fox-select/fox-select.tsx
@@ -10,6 +10,7 @@
   type Ref,
 } from "react";
 import { cx } from "../../utils";
+import { FoxFormLabel, type FoxFormLabelRequirement } from "../fox-form-label";
 import { FoxSelectOption } from "../fox-select-option";
 import { FoxSelectOptionItem } from "../fox-select-option-item";
 
@@ -60,6 +61,8 @@
   readOnly?: boolean;
   /** 필드 위 라벨. 넘기지 않으면 라벨을 렌더링하지 않는다. */
   label?: ReactNode;
+  /** 라벨 뒤 필수·선택 표시. `label`이 없으면 의미 없다. */
+  requirement?: FoxFormLabelRequirement;
   /** select 아래 힌트 메시지. 넘기지 않으면 힌트 영역을 렌더링하지 않는다. */
   hint?: ReactNode;
   /** 힌트 메시지 앞에 붙는 아이콘. `hint`가 없으면 의미 없다. */
@@ -102,6 +105,7 @@
   disabled = false,
   readOnly = false,
   label,
+  requirement,
   hint,
   hintIcon,
   id,
@@ -232,9 +236,9 @@
       onMouseLeave={onHoverChange ? () => onHoverChange(false) : undefined}
     >
       {label ? (
-        <label id={labelId} htmlFor={triggerId} className="fox-select__label">
+        <FoxFormLabel id={labelId} htmlFor={triggerId} requirement={requirement}>
           {label}
-        </label>
+        </FoxFormLabel>
       ) : null}
 
       <div className="fox-select__control">
@fox/core/components/fox-text-area/fox-text-area.tsx
--- @fox/core/components/fox-text-area/fox-text-area.tsx
+++ @fox/core/components/fox-text-area/fox-text-area.tsx
@@ -9,6 +9,7 @@
   type ReactNode,
 } from "react";
 import { cx } from "../../utils";
+import { FoxFormLabel } from "../fox-form-label";
 
 export type FoxTextAreaState = "default" | "focused" | "completed" | "error" | "disabled" | "view";
 
@@ -95,9 +96,7 @@
   return (
     <div className={cx("fox-text-area", className)} data-state={state}>
       {label && (
-        <label className="fox-text-area__label" htmlFor={fieldId}>
-          {label}
-        </label>
+        <FoxFormLabel htmlFor={fieldId}>{label}</FoxFormLabel>
       )}
 
       <div className="fox-text-area__box">
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -984,6 +984,16 @@
           </div>
         ),
       },
+      {
+        label: "requirement — 라벨을 FoxFormLabel이 그린다",
+        node: (
+          <div className={styles.demoFields}>
+            <FoxSelect size="md" label="표시 없음" options={SELECT_ITEMS} />
+            <FoxSelect size="md" label="필수" options={SELECT_ITEMS} requirement="required" />
+            <FoxSelect size="md" label="선택" options={SELECT_ITEMS} requirement="optional" />
+          </div>
+        ),
+      },
     ],
   },
   {
@fox/styles/_fox-input.scss
--- @fox/styles/_fox-input.scss
+++ @fox/styles/_fox-input.scss
@@ -3,7 +3,7 @@
 //
 // 마크업 계약 (React 밖 소비자용):
 //   <div class="fox-input fox-input--md">
-//     <label class="fox-input__label" for="…">레이블</label>
+//     <label class="fox-form-label" for="…">레이블</label>
 //     <div class="fox-input__box">
 //       <input class="fox-input__field" id="…" placeholder="…">
 //       <button class="fox-input__clear" type="button">…svg…</button>   <!-- 선택 -->
@@ -35,10 +35,6 @@
 
 .fox-input {
   @include field.root;
-
-  &__label {
-    @include field.label;
-  }
 
   // 크기(높이·좌우 여백·모서리)는 아래 크기 수식어가 정한다.
   &__box {
@fox/styles/_fox-select.scss
--- @fox/styles/_fox-select.scss
+++ @fox/styles/_fox-select.scss
@@ -5,7 +5,7 @@
 //
 // 마크업 계약 (React 밖 소비자용):
 //   <div class="fox-select fox-select--md">
-//     <label class="fox-select__label" for="sel">라벨</label>        <!-- 선택 -->
+//     <label class="fox-form-label" for="sel">라벨</label>           <!-- 선택 -->
 //     <div class="fox-select__control">                             <!-- 리스트박스 위치 기준 -->
 //       <button class="fox-select__trigger fox-select__trigger--completed" id="sel"
 //               type="button" role="combobox" aria-haspopup="listbox"
@@ -36,8 +36,11 @@
 //
 // 폭은 정하지 않는다 — 부모가 정한다. 높이만 `size`가 정한다.
 //
-// 라벨·옵션·힌트는 `size`와 무관하게 공통 스타일이다. 크기 모디파이어는 트리거와 트리거
-// 아이콘만 바꾼다 — 드롭다운 목록은 어느 크기에서 열려도 같은 서체로 읽혀야 한다.
+// 라벨은 `fox-form-label`이 갖는다 — 시안에서 입력 계열과 같은 공용 조각이라 값이 갈라질
+// 이유가 없고, 필수·선택 표시도 그쪽에 붙는다. 이 파일에는 라벨 규칙이 없다.
+//
+// 옵션·힌트는 `size`와 무관하게 공통 스타일이다. 크기 모디파이어는 트리거와 트리거 아이콘만
+// 바꾼다 — 드롭다운 목록은 어느 크기에서 열려도 같은 서체로 읽혀야 한다.
 //
 // 토큰이 아닌 값(FoxButton과 동일한 근거로 승인된 항목):
 //   - line-height / letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로 export하지 않는다.
@@ -49,13 +52,6 @@
   display: flex;
   flex-direction: column;
   gap: fox.gap(4);
-
-  &__label {
-    color: fox.color(font-neutral-default);
-    font-family: fox.font-family(body);
-    font-size: fox.font-size(label-md);
-    font-weight: fox.font-weight(medium);
-  }
 
   &__control {
     position: relative;
@fox/styles/_fox-text-area.scss
--- @fox/styles/_fox-text-area.scss
+++ @fox/styles/_fox-text-area.scss
@@ -3,7 +3,7 @@
 //
 // 마크업 계약 (React 밖 소비자용):
 //   <div class="fox-text-area">
-//     <label class="fox-text-area__label" for="…">레이블</label>
+//     <label class="fox-form-label" for="…">레이블</label>
 //     <div class="fox-text-area__box">
 //       <textarea class="fox-text-area__field" id="…" rows="5" placeholder="…"></textarea>
 //     </div>
@@ -37,10 +37,6 @@
 
 .fox-text-area {
   @include field.root;
-
-  &__label {
-    @include field.label;
-  }
 
   // 여러 줄이라 세로 가운데 정렬을 끄고, 크기 축이 없어 md 여백을 직접 준다.
   &__box {
Add a comment
List