File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
// FoxPhoneNumber — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F)
// Figma form-phonenumber (섹션 391:3156)
//
// 두 모양이 있다.
// - unit(136:2926): 상자 셋을 하이픈으로 잇는다. 앞자리는 선택, 뒤 둘은 입력이며
// 시안에서도 select·input 컴포넌트의 인스턴스라 그대로 조합한다. 그래서 이 파일은
// 줄 배치와 하이픈만 갖고, 상자 모양·상태는 FoxSelect·FoxInput이 가진 것을 쓴다.
// - combine(190:1772): 상자 하나 안에 세 칸을 두고 각 칸을 가운데 정렬한다. 이건
// 시안이 별도 상자라 여기서 직접 그린다.
//
// 마크업 계약 (React 밖 소비자용):
// <div class="fox-phone-number" role="group" aria-labelledby="…">
// <span class="fox-phone-number__label" id="…">레이블</span>
// <div class="fox-phone-number__row"> <!-- unit -->
// <div class="fox-phone-number__part">…select…</div>
// <span class="fox-phone-number__separator">-</span>
// <div class="fox-phone-number__part">…input…</div>
// <span class="fox-phone-number__separator">-</span>
// <div class="fox-phone-number__part">…input…</div>
// </div>
// <div class="fox-phone-number__box"> <!-- combine -->
// <input class="fox-phone-number__segment">
// <span class="fox-phone-number__separator">-</span>
// …
// </div>
// <p class="fox-phone-number__message">…</p>
// </div>
//
// 상태는 FoxInput과 같은 두 갈래다(자동 / `data-state` 고정). combine 상자에만 규칙이
// 필요하다 — unit은 조각이 알아서 따라온다.
//
// 시안에 크기 축이 없다. 상자는 md 하나뿐이다.
//
// 토큰이 아닌 값(FoxInput과 동일한 근거로 승인된 항목):
// - line-height / letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로
// export하지 않는다.
// - box-sizing / flex / text-align: 구조 규칙.
@use "abstracts" as fox;
.fox-phone-number {
display: flex;
flex-direction: column;
gap: fox.gap(3);
inline-size: 100%;
&__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);
line-height: 1;
letter-spacing: -0.025em;
}
// ── unit ──────────────────────────────────────────────────────────────────
&__row {
display: flex;
align-items: center;
gap: fox.gap(2);
inline-size: 100%;
}
// 세 조각이 남는 폭을 똑같이 나눈다.
&__part {
flex: 1 0 0;
min-inline-size: 0;
}
&__separator {
flex-shrink: 0;
color: fox.color(font-neutral-default);
font-family: fox.font-family(body);
font-size: fox.font-size(body-md);
font-weight: fox.font-weight(regular);
line-height: 1.5;
letter-spacing: -0.025em;
}
// ── combine ───────────────────────────────────────────────────────────────
&__box {
box-sizing: border-box;
display: flex;
align-items: center;
gap: fox.gap(2);
inline-size: 100%;
block-size: fox.form(height-md);
padding-inline: fox.form(padding-md);
border: fox.border(1) solid fox.color(input-border);
border-radius: fox.form(radius-md);
background: fox.color(input-surface);
}
&__segment {
flex: 1 0 0;
min-inline-size: 0;
margin: 0;
padding: 0;
border: none;
background: none;
color: fox.color(font-neutral-default);
font-family: fox.font-family(body);
font-size: fox.font-size(label-md);
font-weight: fox.font-weight(regular);
line-height: 1;
letter-spacing: -0.025em;
text-align: center;
&::placeholder {
color: fox.color(font-neutral-subtler);
}
// 포커스는 상자 테두리가 표시하므로 기본 외곽선을 지운다.
&:focus {
outline: none;
}
&:disabled {
color: fox.color(font-neutral-disabled-strong);
cursor: default;
}
}
&__message {
display: flex;
gap: fox.number(3);
align-items: flex-start;
margin: 0;
color: fox.color(font-neutral-subtle);
font-family: fox.font-family(body);
font-size: fox.font-size(body-sm);
font-weight: fox.font-weight(regular);
line-height: 1.5;
letter-spacing: -0.025em;
}
&__message-icon {
display: inline-flex;
flex-shrink: 0;
align-items: flex-start;
padding-block-start: fox.padding(1);
> * {
inline-size: fox.icon(3);
block-size: fox.icon(3);
}
}
}
// ── 상태 · 자동 (combine 상자) ──────────────────────────────────────────────
// 뒤에 오는 규칙이 이긴다: 포커스 < 오류 < 읽기전용 < 비활성.
.fox-phone-number:not([data-state]) {
.fox-phone-number__box:focus-within {
border-width: fox.border(2);
border-color: fox.color(input-border-active);
}
.fox-phone-number__box:has(.fox-phone-number__segment[aria-invalid="true"]) {
border-width: fox.border(2);
border-color: fox.color(input-border-error);
}
// 비활성도 `:read-only`에 걸리므로 떼어낸다.
.fox-phone-number__box:has(.fox-phone-number__segment:read-only:not(:disabled)) {
border-width: fox.border(1);
border-color: fox.color(input-border-disabled);
background: fox.color(input-surface-disabled);
}
.fox-phone-number__box:has(.fox-phone-number__segment:disabled) {
border-width: fox.border(1);
border-color: fox.color(input-border-disabled);
background: fox.color(input-surface-disabled);
}
&:focus-within .fox-phone-number__message {
color: fox.color(font-system-information);
}
&:has([aria-invalid="true"]) .fox-phone-number__message {
color: fox.color(font-system-danger);
}
}
// ── 상태 · 고정 ─────────────────────────────────────────────────────────────
.fox-phone-number[data-state="default"] .fox-phone-number__box,
.fox-phone-number[data-state="completed"] .fox-phone-number__box {
border-width: fox.border(1);
border-color: fox.color(input-border);
background: fox.color(input-surface);
}
.fox-phone-number[data-state="focused"] .fox-phone-number__box {
border-width: fox.border(2);
border-color: fox.color(input-border-active);
background: fox.color(input-surface);
}
.fox-phone-number[data-state="error"] .fox-phone-number__box {
border-width: fox.border(2);
border-color: fox.color(input-border-error);
background: fox.color(input-surface);
}
.fox-phone-number[data-state="view"] .fox-phone-number__box,
.fox-phone-number[data-state="disabled"] .fox-phone-number__box {
border-width: fox.border(1);
border-color: fox.color(input-border-disabled);
background: fox.color(input-surface-disabled);
}
.fox-phone-number[data-state="completed"] .fox-phone-number__segment::placeholder,
.fox-phone-number[data-state="focused"] .fox-phone-number__segment::placeholder,
.fox-phone-number[data-state="error"] .fox-phone-number__segment::placeholder,
.fox-phone-number[data-state="view"] .fox-phone-number__segment::placeholder {
color: fox.color(font-neutral-default);
}
.fox-phone-number[data-state="disabled"] .fox-phone-number__segment,
.fox-phone-number[data-state="disabled"] .fox-phone-number__segment::placeholder {
color: fox.color(font-neutral-disabled-strong);
}
.fox-phone-number[data-state="focused"] .fox-phone-number__message {
color: fox.color(font-system-information);
}
.fox-phone-number[data-state="completed"] .fox-phone-number__message {
color: fox.color(font-system-success);
}
.fox-phone-number[data-state="error"] .fox-phone-number__message {
color: fox.color(font-system-danger);
}