// FoxTextArea — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F) // Figma textarea (135:1655 · 섹션 391:3107) // // 마크업 계약 (React 밖 소비자용): //
// //
// //
// //
// // 상태는 FoxInput과 같은 두 갈래다. // - 기본(자동): 포커스는 `:focus-within`, 비활성·읽기전용은 네이티브 속성, 오류는 // `aria-invalid="true"`. 시안의 completed는 default와 글자색만 다른데 그건 // 플레이스홀더와 입력값의 네이티브 차이라 규칙이 필요 없다. // - 고정: 루트에 `data-state`를 주면 그 모양으로 못박힌다. // // 시안에 크기 축이 없다 — 상자 하나뿐이고 높이는 줄 수가 정한다(시안은 5줄). // // 토큰이 아닌 값(FoxInput과 동일한 근거로 승인된 항목): // - line-height(본문·헬퍼 1.5, 라벨·카운터 1) / letter-spacing: 시안 텍스트 스타일의 // 값이나 Figma가 변수로 export하지 않는다. // - box-sizing / resize / flex 관련: 구조·상호작용 규칙. 시안에 리사이즈 핸들이 없어 // 막아 둔다 — 높이가 필요하면 `rows`로 정한다. @use "abstracts" as fox; .fox-text-area { 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; } &__box { box-sizing: border-box; display: flex; gap: fox.gap(3); inline-size: 100%; padding-inline: fox.form(padding-md); padding-block: fox.padding(5); border: fox.border(1) solid fox.color(input-border); border-radius: fox.form(radius-lg); background: fox.color(input-surface); } &__field { flex: 1 0 0; min-inline-size: 0; margin: 0; padding: 0; border: none; background: none; resize: none; 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; &::placeholder { color: fox.color(font-neutral-subtler); } // 포커스는 상자 테두리가 표시하므로 기본 외곽선을 지운다. &:focus { outline: none; } &:disabled { color: fox.color(font-neutral-disabled-strong); cursor: default; } } // 헬퍼는 왼쪽에서 남는 폭을 먹고 카운터는 오른쪽에 붙는다. &__footer { display: flex; gap: fox.gap(5); align-items: center; justify-content: flex-end; inline-size: 100%; } &__message { display: flex; flex: 1 0 0; gap: fox.number(3); align-items: flex-start; min-inline-size: 0; 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); } } &__counter { flex-shrink: 0; margin: 0; color: fox.color(font-neutral-default); font-family: fox.font-family(body); font-size: fox.font-size(label-sm); font-weight: fox.font-weight(regular); line-height: 1; letter-spacing: -0.025em; text-align: end; } // 현재 글자 수만 굵고 색이 다르다. &__counter-current { color: fox.color(font-theme-primary); font-weight: fox.font-weight(bold); } } // ── 상태 · 자동 ───────────────────────────────────────────────────────────── // 뒤에 오는 규칙이 이긴다: 포커스 < 오류 < 읽기전용 < 비활성. .fox-text-area:not([data-state]) { .fox-text-area__box:focus-within { border-width: fox.border(2); border-color: fox.color(input-border-active); } .fox-text-area__box:has(.fox-text-area__field[aria-invalid="true"]) { border-width: fox.border(2); border-color: fox.color(input-border-error); } // 비활성도 `:read-only`에 걸리므로 떼어낸다. .fox-text-area__box:has(.fox-text-area__field:read-only:not(:disabled)) { border-width: fox.border(1); border-color: fox.color(input-border-disabled); background: fox.color(input-surface-disabled); } .fox-text-area__box:has(.fox-text-area__field:disabled) { border-width: fox.border(1); border-color: fox.color(input-border-disabled); background: fox.color(input-surface-disabled); } &:focus-within .fox-text-area__message { color: fox.color(font-system-information); } &:has(.fox-text-area__field[aria-invalid="true"]) .fox-text-area__message { color: fox.color(font-system-danger); } // 시안의 오류 변형에서만 카운터 현재값이 빨갛다. &:has(.fox-text-area__field[aria-invalid="true"]) .fox-text-area__counter-current { color: fox.color(font-system-danger); } } // ── 상태 · 고정 ───────────────────────────────────────────────────────────── // 시안의 focused·completed·error·view는 값이 들어찬 모습이라 플레이스홀더도 진하게 둔다. .fox-text-area[data-state="default"] .fox-text-area__box, .fox-text-area[data-state="completed"] .fox-text-area__box { border-width: fox.border(1); border-color: fox.color(input-border); background: fox.color(input-surface); } .fox-text-area[data-state="focused"] .fox-text-area__box { border-width: fox.border(2); border-color: fox.color(input-border-active); background: fox.color(input-surface); } .fox-text-area[data-state="error"] .fox-text-area__box { border-width: fox.border(2); border-color: fox.color(input-border-error); background: fox.color(input-surface); } .fox-text-area[data-state="view"] .fox-text-area__box, .fox-text-area[data-state="disabled"] .fox-text-area__box { border-width: fox.border(1); border-color: fox.color(input-border-disabled); background: fox.color(input-surface-disabled); } .fox-text-area[data-state="completed"] .fox-text-area__field::placeholder, .fox-text-area[data-state="focused"] .fox-text-area__field::placeholder, .fox-text-area[data-state="error"] .fox-text-area__field::placeholder, .fox-text-area[data-state="view"] .fox-text-area__field::placeholder { color: fox.color(font-neutral-default); } .fox-text-area[data-state="disabled"] .fox-text-area__field, .fox-text-area[data-state="disabled"] .fox-text-area__field::placeholder { color: fox.color(font-neutral-disabled-strong); } .fox-text-area[data-state="focused"] .fox-text-area__message { color: fox.color(font-system-information); } .fox-text-area[data-state="completed"] .fox-text-area__message { color: fox.color(font-system-success); } .fox-text-area[data-state="error"] .fox-text-area__message { color: fox.color(font-system-danger); } .fox-text-area[data-state="error"] .fox-text-area__counter-current { color: fox.color(font-system-danger); }