Skip to content

[✨ Feat/#43] TextArea 공통 컴포넌트 구현#44

Merged
Lseojeong merged 8 commits into
devfrom
feat/#43
Jun 22, 2026
Merged

[✨ Feat/#43] TextArea 공통 컴포넌트 구현#44
Lseojeong merged 8 commits into
devfrom
feat/#43

Conversation

@Lseojeong

@Lseojeong Lseojeong commented Jun 22, 2026

Copy link
Copy Markdown
Member

#️⃣연관된 이슈

체크 사항

  • UI 동작 및 레이아웃 확인
  • 불필요한 console 제거
  • 기능 정상 동작
  • 팀 컨벤션에 맞게 구현했는지
  • 관련 문서 또는 주석을 갱신했는지

📝작업 내용

TextArea 공통 컴포넌트 구현

  • TextArea.Root, TextArea.Label, TextArea.Field,
    TextArea.ErrorMessage로 구성된 Compound Component를 추가했습니다.
  • focus, invalid, disabled, required 상태에 따른 공통 스타일을 적용했습니다.
  • placeholder, value, defaultValue, onChange, onBlur, ref
    네이티브 textarea props를 지원합니다.
  • React Hook Form의 field 객체를 실제 textarea에 전달할 수 있도록
    props 타입을 구성했습니다.

글자 수 기준 및 입력 제한

  • recommendedLength는 초과 입력을 허용하는 권장 길이로 처리했습니다.
  • maxLength는 설정한 길이까지만 입력할 수 있는 최대 길이로 처리했습니다.
  • 두 길이 기준을 동시에 전달할 수 없도록 상호 배타적인 타입으로 구성했습니다.
  • controlled와 uncontrolled 입력 모두 현재 글자 수를 표시하도록 구현했습니다.
  • 권장 길이의 90% 이상에서는 현재 글자 수를 노란색으로 표시하고,
    권장 길이를 초과하면 빨간색으로 표시합니다.

접근성 연결

  • Root의 id를 Label의 htmlFor와 textarea의 id에 연결했습니다.
  • 오류 상태에서 aria-invalid, aria-describedby,
    aria-errormessage를 적용했습니다.
  • 글자 수 안내와 오류 메시지가 함께 존재할 때 두 설명 영역을 모두
    textarea에 연결했습니다.
  • 필수 입력 상태를 네이티브 requiredaria-required로 전달했습니다.

FormControl 공통 기반 분리

  • Input과 TextArea가 Context, Label, ErrorMessage, Field 스타일을
    공유할 수 있도록 form-control 기반을 추가했습니다.
  • 기존 Input의 외부 API를 유지하면서 공통 FormControl 기반으로
    내부 구현을 변경했습니다.
  • Field와 Button을 배치하는 기존 Input.Control을 역할이 명확한
    Input.FieldGroup으로 변경했습니다.
  • FormControl, FormField, TextArea 타입에 역할과 사용 조건을 설명하는
    TSDoc을 추가했습니다.

TextArea 스크롤 영역 개선

  • TextArea 전용 스크롤바 스타일을 추가했습니다.
  • Chromium, Safari, Firefox의 스크롤바 스타일을 지원합니다.
  • disabled 상태와 hover 상태의 스크롤바 색상을 구분했습니다.
  • 세로 크기 조절 기능을 유지하면서 resize 영역의 네모 배경을 제거하고
    대각선 형태의 resize 아이콘을 적용했습니다.
  • 글자 수 카운터에 불투명한 하단 영역을 제공해 긴 본문과 겹쳐 보이지
    않도록 개선했습니다.

스크린샷 (선택)

image

추가한 라이브러리 (선택)

  • 없음

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • TextArea 컴포넌트(라벨/필드/에러 메시지)와 최대/권장 길이 카운트, 필수 표시, 비활성, 오류 상태 표시를 지원합니다.
  • 개선사항

    • Input 컴포넌트의 조합 API가 Control 중심에서 FieldGroup 중심으로 정리되었습니다(버튼 포함 레이아웃 포함).
    • 폼 상태 기반 라벨/에러 메시지 렌더링이 더 일관되게 동작합니다.
  • 문서/스토리

    • TextArea 스토리북 예제가 추가되어 다양한 상태를 확인할 수 있습니다.
  • 스타일

    • 폼 스크롤바 스타일이 전역으로 확장 적용되었습니다.

Input과 TextArea가 라벨, 오류 상태, 필드 스타일을 공유할 수 있도록
FormControl 기반을 분리했습니다. Input.Control은 역할이 드러나는
Input.FieldGroup으로 정리했습니다.
긴 텍스트 입력에서 라벨, 오류, 비활성 상태와 글자 수 표시를
일관되게 사용할 수 있는 Compound TextArea를 추가했습니다.
권장 길이와 최대 길이를 타입으로 구분하고 최대 입력을 보장합니다.
TextArea의 스크롤바와 크기 조절 아이콘을 디자인 토큰에 맞게
정리했습니다. 하단 카운터에 배경 영역을 제공해 긴 입력 내용과
겹치지 않도록 개선했습니다.
FormField의 React Hook Form 상태와 TextArea의 길이 기준 타입을
명확히 이해할 수 있도록 역할과 사용 조건을 문서화했습니다.
@Lseojeong Lseojeong self-assigned this Jun 22, 2026
@Lseojeong Lseojeong added the ✨Feature 새로운 기능 구현 label Jun 22, 2026
@Lseojeong Lseojeong linked an issue Jun 22, 2026 that may be closed by this pull request
10 tasks
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rewrite Ready Ready Preview, Comment Jun 22, 2026 2:56pm

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 367eda1e-15e3-45ea-9875-368622b2e9c4

📥 Commits

Reviewing files that changed from the base of the PR and between 657ef25 and 9b34407.

📒 Files selected for processing (1)
  • src/shared/ui/textarea/TextArea.types.ts
✅ Files skipped from review due to trivial changes (1)
  • src/shared/ui/textarea/TextArea.types.ts

Walkthrough

Input 컴포넌트의 Context/Label/ErrorMessage 로직을 FormControl 공통 레이어로 추출했습니다. FormControlContext, FormControlProvider, 공용 스타일을 구축하고 Input을 마이그레이션하며, 동일한 공통 레이어를 기반으로 TextArea 컴파운드 컴포넌트(Field, Label, ErrorMessage)를 신규 구현했습니다.

Changes

FormControl 공통화 및 TextArea 신규 구현

Layer / File(s) Summary
FormControl 공통 타입·Context·Provider·스타일 기반 구축
src/shared/ui/form-control/FormControl.types.ts, src/shared/ui/form-control/FormControlContext.ts, src/shared/ui/form-control/FormControlProvider.tsx, src/shared/ui/form-control/formControlStyles.ts
FormControlContextValue, FormControlProviderProps 타입을 정의하고, FormControlContextuseFormControlContext 훅(미제공 시 에러 throw)을 구현합니다. FormControlProvider는 내부 errorMessageCount 상태로 hasErrorMessage를 관리합니다. Input/TextArea 공용 Tailwind 클래스 상수(fieldBaseClassName, fieldInteractionClassName)를 정의합니다.
FormControlLabel·FormControlErrorMessage 공통 컴포넌트
src/shared/ui/form-control/FormControlLabel.tsx, src/shared/ui/form-control/FormControlErrorMessage.tsx
FormControlLabel은 컨텍스트에서 fieldId/required를 읽어 htmlFor 연결 및 장식 * 렌더링을 처리합니다. FormControlErrorMessageinvaliderrorMessageId를 읽어 role="alert" <p>를 조건부 렌더링하고 useEffect에서 registerErrorMessage()를 호출합니다.
Input 컴포넌트의 FormControl 공통 레이어 마이그레이션
src/shared/ui/input/Input.tsx, src/shared/ui/input/Input.types.ts, src/shared/ui/input/InputField.tsx, src/shared/ui/input/InputFieldGroup.tsx, src/shared/ui/input/InputLabel.tsx, src/shared/ui/input/InputErrorMessage.tsx, src/shared/ui/input/Input.stories.tsx
InputContextFormControlProvider로 교체합니다. Input.ControlInput.FieldGroup으로 이름 변경하고, InputFielduseFormControlContext와 공용 스타일 상수를 사용합니다. InputLabel/InputErrorMessage는 공통 컴포넌트 래퍼로 단순화되며, aria-describedby 대신 aria-errormessage를 조건부로 설정합니다.
TextArea 타입 정의 및 Root 컴포넌트 구현
src/shared/ui/textarea/TextArea.types.ts, src/shared/ui/textarea/TextArea.tsx, src/shared/ui/textarea/index.ts
TextAreaProps, TextAreaFieldProps(maxLength/recommendedLength 배타적 유니온), TextAreaLabelProps, TextAreaErrorMessageProps를 정의합니다. TextAreaRootuseId 기반 fieldId/errorMessageId를 생성하고 FormControlProvider를 통해 컨텍스트를 제공합니다.
TextAreaField·TextAreaLabel·TextAreaErrorMessage 하위 컴포넌트
src/shared/ui/textarea/TextAreaField.tsx, src/shared/ui/textarea/TextAreaLabel.tsx, src/shared/ui/textarea/TextAreaErrorMessage.tsx
TextAreaFielduseFormControlContext로 aria 속성 및 검증 상태를 반영하고, controlled/uncontrolled 글자 수 추적, maxLength 초과 입력 차단, showCount에 따른 카운터 표시(권장 초과/최대 근접/도달 분기)를 구현합니다. TextAreaLabel/TextAreaErrorMessage는 공통 컴포넌트의 얇은 래퍼입니다.
TextArea 스크롤바 스타일, 전역 CSS 등록, 문서화
src/shared/styles/base/scroll.css, src/shared/styles/globals.css, src/shared/ui/form-field/FormField.types.ts
.textarea-scrollbar 클래스에 대한 Firefox/WebKit 크로스 브라우저 스크롤바 스타일(기본/hover/disabled 상태, SVG 리사이저 배경)을 추가하고 globals.css에 import합니다. FormFieldRenderPropsinvalid, errorMessage 의미를 설명하는 JSDoc을 추가합니다.
TextArea Storybook 스토리 구성
src/shared/ui/textarea/TextArea.stories.tsx
TextAreaPlayground, ReactHookFormExample 컴포넌트와 Default, WithoutCount, RecommendedLengthExceeded, MaxLengthReached, WithError, Disabled, ReactHookForm 스토리 7종을 추가합니다.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(99, 132, 199, 0.5)
    Note over TextArea,FormControlProvider: Root 초기화
    TextArea->>FormControlProvider: disabled, invalid, required, fieldId, errorMessageId 제공
  end
  rect rgba(75, 192, 120, 0.5)
    Note over FormControlProvider,TextAreaField: 필드 렌더링
    TextAreaField->>FormControlProvider: useFormControlContext()로 상태 읽기
    TextAreaField->>TextAreaField: aria-errormessage, aria-invalid, id 설정
    TextAreaField->>TextAreaField: onChange에서 maxLength 초과 시 값 잘라 재설정
    TextAreaField->>TextAreaField: showCount 시 currentLength/limitValue 계산 및 표시
  end
  rect rgba(255, 160, 86, 0.5)
    Note over FormControlProvider,TextAreaErrorMessage: 레이블/에러 렌더링
    TextAreaLabel->>FormControlProvider: FormControlLabel 통해 fieldId/required 읽기
    TextAreaErrorMessage->>FormControlProvider: FormControlErrorMessage 통해 invalid/errorMessageId 읽기
    TextAreaErrorMessage->>FormControlProvider: registerErrorMessage() 호출 시 errorMessageCount 증가
    TextAreaErrorMessage->>TextAreaErrorMessage: invalid && children 존재 시 role="alert" p 렌더링
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • Rewrite-Team/Rewrite-FE#42: Input 컴파운드 컴포넌트의 초기 Context/ErrorMessage/Label/Field 구조를 구성한 PR로, 이번 PR에서 해당 구조를 FormControl 공통 레이어로 리팩터링하는 직접적인 기반입니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 TextArea 공통 컴포넌트 구현이라는 핵심 변경사항을 명확하게 반영하고 있으며, 제공된 커밋 목표와도 일치합니다.
Linked Issues check ✅ Passed PR은 Issue #43의 모든 구현 목록을 충족합니다. TextArea 공통 컴포넌트 구현, 다양한 상태 스타일(기본/focus/error/disabled), 속성 지원(disabled, placeholder, maxLength), 글자 수 표시 기능, 에러 메시지 영역, Label 연결, React Hook Form 연동, 그리고 FormControl 기반의 재사용 가능한 구조 모두가 구현되었습니다.
Out of Scope Changes check ✅ Passed PR의 모든 변경사항은 TextArea 구현 및 이를 지원하기 위한 FormControl 리팩토링(Input과의 코드 공유)과 직접 관련이 있습니다. 추가적인 범위 외 변경사항은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#43

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

🧪 테스트 결과

항목 결과
✅ Jest 테스트 success

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

🚦 CI 검증 결과

항목 결과
🧠 TypeScript 타입 체크 success
🧹 ESLint 검사 success
🎨 Prettier 검사 success
🏗️ Build 검증 success

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/shared/ui/form-control/FormControlErrorMessage.tsx`:
- Around line 43-45: The FormControlErrorMessage component currently returns
null when invalid is false or children is empty, but this causes a mismatch with
ARIA attributes in parent components (InputField and TextAreaField) that
reference the errorMessageId. When the error message node is not rendered due to
empty children, the aria-errormessage and aria-describedby attributes pointing
to the non-existent errorMessageId become invalid. Either prevent empty children
from being passed to this component entirely, or ensure that the parent field
components (InputField and TextAreaField) do not set aria-errormessage and
aria-describedby attributes when there is no error message to display.

In `@src/shared/ui/input/Input.tsx`:
- Around line 115-118: The removal of the `Control` static member from the
Object.assign statement for the Input component will break the public API for
existing code using `<Input.Control>`. To maintain backward compatibility, add
`Control: InputFieldGroup` as an alias alongside the new `FieldGroup:
InputFieldGroup` property in the Object.assign block. This allows users to
gradually migrate from the old `Input.Control` syntax to the new
`Input.FieldGroup` while maintaining existing functionality during the
transition period.

In `@src/shared/ui/textarea/TextArea.types.ts`:
- Around line 12-23: The TextAreaFieldBaseProps interface currently allows the
children prop to be passed through from ComponentPropsWithRef<'textarea'>, which
can lead to misuse of the component (e.g.,
<TextArea.Field>...</TextArea.Field>). Add 'children' to the Omit list alongside
the other restricted props like 'aria-describedby', 'aria-errormessage',
'aria-invalid', 'disabled', 'id', 'maxLength', and 'required' to ensure the
component only operates with value/defaultValue-based control.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0e6a6c2c-e210-4c4a-a541-b7247283ca97

📥 Commits

Reviewing files that changed from the base of the PR and between 98afbdf and 33646f5.

📒 Files selected for processing (22)
  • src/shared/styles/base/scroll.css
  • src/shared/styles/globals.css
  • src/shared/ui/form-control/FormControl.types.ts
  • src/shared/ui/form-control/FormControlContext.ts
  • src/shared/ui/form-control/FormControlErrorMessage.tsx
  • src/shared/ui/form-control/FormControlLabel.tsx
  • src/shared/ui/form-control/formControlStyles.ts
  • src/shared/ui/input/Input.stories.tsx
  • src/shared/ui/input/Input.tsx
  • src/shared/ui/input/Input.types.ts
  • src/shared/ui/input/InputContext.ts
  • src/shared/ui/input/InputErrorMessage.tsx
  • src/shared/ui/input/InputField.tsx
  • src/shared/ui/input/InputFieldGroup.tsx
  • src/shared/ui/input/InputLabel.tsx
  • src/shared/ui/textarea/TextArea.stories.tsx
  • src/shared/ui/textarea/TextArea.tsx
  • src/shared/ui/textarea/TextArea.types.ts
  • src/shared/ui/textarea/TextAreaErrorMessage.tsx
  • src/shared/ui/textarea/TextAreaField.tsx
  • src/shared/ui/textarea/TextAreaLabel.tsx
  • src/shared/ui/textarea/index.ts
💤 Files with no reviewable changes (1)
  • src/shared/ui/input/InputContext.ts

Comment thread src/shared/ui/form-control/FormControlErrorMessage.tsx Outdated
Comment thread src/shared/ui/input/Input.tsx
Comment thread src/shared/ui/textarea/TextArea.types.ts
실제 ErrorMessage 노드가 렌더링된 경우에만 필드가 오류 id를
참조하도록 등록 상태를 추가했습니다. Context와 상태를 관리하는
Provider도 분리해 각 파일의 책임을 명확히 했습니다.
보조 설명은 aria-describedby로, 오류 메시지는 aria-errormessage로
연결하도록 역할을 분리했습니다. Input에는 불필요한 설명 참조를
제거하고 TextArea는 글자 수 안내만 설명으로 유지했습니다.
TextArea.Field가 네이티브 textarea의 children을 상속하지 않도록
제외해 value와 defaultValue 기반 입력 계약을 보장했습니다.
@Lseojeong Lseojeong merged commit 5838ca7 into dev Jun 22, 2026
5 checks passed
@Lseojeong Lseojeong deleted the feat/#43 branch June 22, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feature 새로운 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[✨Feature] TextArea 공통 컴포넌트 구현

1 participant