Skip to content

Comments

Feat(design-system): textarea 공통 컴포넌트 구현#23

Merged
jjangminii merged 12 commits intodevelopfrom
feat/#19/textField-component
Aug 25, 2025
Merged

Feat(design-system): textarea 공통 컴포넌트 구현#23
jjangminii merged 12 commits intodevelopfrom
feat/#19/textField-component

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Aug 24, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

✅ 체크 리스트

  • PR 제목의 형식을 잘 작성했나요? e.g. Feat(client): PR 템플릿 작성
  • 빌드가 성공했나요? (pnpm build)

📄 Tasks

  • textarea 공통 컴포넌트 구현
  • 스크롤바 커스텀 추가

⭐ PR Point (To Reviewer)

스크롤바 커스텀 적용 방식 결정 필요

현재 ds-scrollbar 클래스를 붙인 요소에만 적용됩니다. 피그마에있는 너비, 컬러로 공통적인 디자인 적용해뒀습니다.
스크롤이 생기는 컴포넌트에 매번 적용하기에는 번거로울거같아 다른 방법있을지 궁금합니다.

maxLength 매직 넘버 분리

Textarea.tsx 내부에 MAX_TEXTAREA_LENGTH = 500 상수로 선언해 사용했습니다. 추후 상수만 관리하는 파일을 만들어 따로 분리하여 사용하는 것에 대해 어떻게 생각하시는지 얘기해보면 좋을거같아요-!!

파일 제목 컨벤션

저희가 현재 컴포넌트에는 PascalCase 사용하기로했는데 shadecn으로 생성한 파일들은 carmelCase로 자동생성됩니다. 현재는 그냥 자동생성된 형식으로 파일 이름 지정해뒀는데 PascalCase로변경하는게 좋을까요?

📷 Screenshot

Summary by CodeRabbit

  • New Features

    • Textarea 컴포넌트가 디자인 시스템에 공개 추가되어 바로 사용 가능(별도 기본 maxLength 값 없음).
  • Style

    • ds-scrollbar 유틸리티 추가로 스크롤바 크기·색상·트랙·썸 스타일 일관화. 호버/활성 상태와 둥근 모양 적용.
  • Documentation

    • Storybook 스토리 추가: 기본, maxLength 예시, 스크롤 오버플로우 확인, 길이 제한 동작 검증용 상호작용 테스트.

@jjangminii jjangminii linked an issue Aug 24, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Aug 24, 2025

Walkthrough

디자인 시스템에 신규 Textarea 컴포넌트를 추가하고 이를 공개 재익스포트했으며, Textarea용 Storybook 스토리(인터랙션 플레이 포함)와 공용 스크롤바 유틸리티(.ds-scrollbar)를 도입했습니다.

Changes

Cohort / File(s) Summary
컴포넌트 구현
packages/design-system/src/components/textarea/Textarea.tsx
신규 Textarea 컴포넌트 및 TextareaProps 추가. maxLength: number을 prop으로 받고 textarea에 data-slot="textarea", maxLength 바인딩, 기본 인라인 style에 scrollbarGutter: 'stable' 병합, 클래스에 ds-scrollbar 포함
공개 재익스포트
packages/design-system/src/components/index.ts
export { Textarea } from './textarea/Textarea' 추가
스토리북 및 플레이 테스트
packages/design-system/src/components/textarea/Textarea.stories.tsx
Textarea용 Storybook 메타/argTypes/기본 args와 4개 스토리 추가(기본, maxLength=500, 오버플로우 스크롤 검사, maxLength 제한 검사). Play 함수로 스크롤/타이핑 검증 포함
공용 스크롤바 스타일
packages/tailwind-config/shared-styles.css
@layer utilities.ds-scrollbar 유틸 추가: --ds-scroll-size, --ds-scroll-thumb, --ds-scroll-track 변수 및 scrollbar-color, scrollbar-gutter 선언과 WebKit 스크롤바 규칙 추가

Sequence Diagram(s)

sequenceDiagram
  participant U as 사용자
  participant T as Textarea 컴포넌트
  participant CSS as .ds-scrollbar 스타일

  U->>T: 입력(타이핑)
  T->>T: maxLength 검사 및 적용
  alt 입력 길이 초과
    T-->>U: 추가 입력 차단(브라우저 기본 동작)
  end

  U->>T: 내용이 영역 초과
  T->>CSS: overflow 발생 → 스크롤바 렌더링
  CSS-->>U: 스크롤 표시 (ds-scrollbar 스타일)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Progress Bar 컴포넌트 구현 및 스타일 분기 처리 [#25]
Text Field 공통 컴포넌트 제작 [#19] Textarea가 추가되었으나 "Text Field 공통 컴포넌트" 요구사항(범위/API)이 명시되어 있지 않아 충족 여부 불명확.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
신규 Textarea 컴포넌트 추가 (packages/design-system/src/components/textarea/Textarea.tsx) 링크된 이슈들(#25, #19)에 Textarea 추가 요구가 명시되어 있지 않음. 목적과 직접 연결되지 않음.
Textarea 스토리 및 플레이 테스트 추가 (packages/design-system/src/components/textarea/Textarea.stories.tsx) 스토리/테스트는 Textarea 관련 변경의 문서화로, 링크된 이슈 목표와 직접 연관되지 않음.
공용 스크롤바 유틸리티 추가 (packages/tailwind-config/shared-styles.css) 스크롤바 유틸은 Textarea 스타일을 보완하나, 링크된 이슈의 목표(Progress/공통 Text Field)와 명확한 연결이 없음.

Possibly related PRs

Suggested reviewers

  • jllee000
  • constantly-dev

Poem

새 깃털로 글을 긁는 토끼가 말하네,
한 칸 한 칸 글자들이 모여 집을 짓네.
손끝 멈추면 500의 문턱, 소곤소곤,
스크롤은 살짝 고정된 마루를 비추네.
오늘도 컴포넌트 숲에 달빛을 심는다. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#19/textField-component

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Aug 24, 2025
@github-actions github-actions bot requested a review from jllee000 August 24, 2025 09:01
@github-actions
Copy link

github-actions bot commented Aug 24, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (15)
packages/tailwind-config/scrollbar-styles.css (3)

2-5: Firefox 얇은 스크롤바와 토큰화 지원 추가 제안

  • Firefox 전용 얇은 스크롤바(scrollbar-width: thin)를 함께 지정하면 WebKit과 두께가 더 일치합니다.
  • 색상을 바로 --gray100에 의존하기보다 전용 토큰(--scrollbar-thumb, --scrollbar-track)으로 감싸두면 다크모드/테마별 조정이 수월합니다.

적용 예시:

 :where(.ds-scrollbar) {
-  /* Firefox: thumb 색, track 투명 */
-  scrollbar-color: var(--gray100, #e5e7eb) transparent;
+  /* Firefox: thumb 색, track 투명 (전용 토큰 → gray100 fallback) */
+  scrollbar-color: var(--scrollbar-thumb, var(--gray100, #e5e7eb))
+                   var(--scrollbar-track, transparent);
+  /* Firefox 얇은 스크롤바 */
+  scrollbar-width: thin;
 }

15-17: WebKit hover/active 상태 및 모서리 반경 부여로 사용성 개선

Thumb에 hover/active 시 색 변화를 주고 약간의 radius를 주면 조작성이 올라갑니다.

적용 예시:

 :where(.ds-scrollbar)::-webkit-scrollbar-thumb {
-  background: var(--gray100, #e5e7eb); /* 토큰 없으면 fallback */
+  background: var(--scrollbar-thumb, var(--gray100, #e5e7eb)); /* 토큰 → fallback */
+  border-radius: 0.4rem;
 }
+
+:where(.ds-scrollbar)::-webkit-scrollbar-thumb:hover {
+  /* color-mix가 미지원이어도 기본색으로 표시되어 안전 */
+  background: color-mix(in oklab,
+    var(--scrollbar-thumb, var(--gray100, #e5e7eb)) 85%, black);
+}
+:where(.ds-scrollbar)::-webkit-scrollbar-thumb:active {
+  background: color-mix(in oklab,
+    var(--scrollbar-thumb, var(--gray100, #e5e7eb)) 70%, black);
+}

1-17: Windows 고대비 모드 호환성 보완

고대비(Forced Colors) 환경에서는 시스템 색을 우선하도록 두는 것이 안전합니다.

적용 예시(파일 하단에 추가):

+:@media (forced-colors: active) {
+  :where(.ds-scrollbar) {
+    /* OS가 지정한 대비 색을 사용하도록 위임 */
+    scrollbar-color: auto;
+  }
+  :where(.ds-scrollbar)::-webkit-scrollbar {
+    width: auto;
+    height: auto;
+  }
+}
packages/design-system/src/components/index.ts (1)

4-4: Textarea 컴포넌트 파일명 및 export 방식 일관화 권장

현재 packages/design-system/src/components/textarea/textarea.tsx는 소문자 파일명·named export를 사용하고 있어, 다른 컴포넌트(Button/Input)의 default export·PascalCase 파일명과 불일치합니다. 일관된 DX를 위해 아래와 같이 옵션 A(파일명 PascalCase + default export)로 통일을 권장드립니다.

– 변경 요약
• 파일명: textarea.tsxTextarea.tsx
• 컴포넌트: export { Textarea };export default Textarea;
packages/design-system/src/components/index.ts: named → default as export
• 스토리 파일 등 import 경로: named import → default import

– 예시 diff

  1. 컴포넌트 파일 packages/design-system/src/components/textarea/textarea.tsx…/Textarea.tsx
-export { Textarea };
+export default Textarea;
  1. 디렉토리 내 파일명 변경
git mv packages/design-system/src/components/textarea/textarea.tsx \
       packages/design-system/src/components/textarea/Textarea.tsx
  1. packages/design-system/src/components/index.ts
-export { Textarea } from './textarea/textarea';
+export { default as Textarea } from './textarea/Textarea';
  1. 스토리 파일(testarea.stories.tsx) 등 기존 사용처
-import { Textarea } from './textarea';
+import Textarea from './textarea/Textarea';

위 수정 후에는 lint·테스트를 통해 경로·import가 모두 올바르게 변경되었는지 확인 부탁드립니다.

packages/tailwind-config/package.json (1)

8-10: 서브패스 export 추가는 적절합니다. CSS tree-shaking 방지용 sideEffects 설정을 함께 고려

일부 번들러 환경에서 CSS가 사이드이펙트로 간주되지 않아 제거될 위험을 줄이기 위해 sideEffects*.css를 명시해두면 안전합니다(패키지가 private여도 워크스페이스 빌드에서 유효).

예시:

 {
   "name": "@pinback/tailwind-config",
   "version": "0.0.0",
   "type": "module",
   "private": true,
+  "sideEffects": [
+    "*.css"
+  ],
   "exports": {
     ".": "./shared-styles.css",
     "./shared-styles.css": "./shared-styles.css",
     "./scrollbar-styles.css": "./scrollbar-styles.css"
   },
packages/design-system/styles.css (1)

4-4: 공유 CSS 중복 import 확인 및 정리 권장

앱별로 아래 파일에서 @pinback/tailwind-config/shared-styles.css를 직접 import 하고 있습니다. 디자인 시스템(packages/design-system/styles.css)에서만 한 번 import 되도록 중복을 제거해주세요.

  • apps/landing/src/App.css:1
  • apps/extension/src/App.css:1
  • apps/client/src/App.css:1

위 import 구문을 삭제하거나, 디자인 시스템의 styles.css를 통해서만 불러오도록 수정해 주세요.

packages/design-system/src/components/textarea/textarea.tsx (4)

5-5: MAX_TEXTAREA_LENGTH 상수의 공용화 제안

컴포넌트 내부 하드코딩 대신 디자인시스템 차원의 상수/토큰 파일로 분리하면 재사용과 변경 추적이 용이합니다. 예: packages/design-system/src/foundations/constants.ts 또는 tokens.ts.

원하시면 상수 파일 신설과 참조 변경 패치를 제안드릴게요.


9-13: forwardRef 적용 및 style 병합 방식 보완

  • 외부에서 focus 제어 등을 위해 ref 전달이 필요한 경우가 많아 forwardRef가 표준적입니다.
  • 현재 style={{ scrollbarGutter: 'stable' }} 뒤에 {...props}를 배치해 사용자가 넘긴 style이 컴포넌트의 기본 스타일을 덮어쓰는 구조입니다. 의도라면 괜찮지만, 보통은 병합이 안전합니다.

적용 예시:

-type TextareaProps = React.ComponentProps<'textarea'>;
+export type TextareaProps = React.ComponentProps<'textarea'>;
 
-function Textarea({
-  className,
-  maxLength = MAX_TEXTAREA_LENGTH,
-  ...props
-}: TextareaProps) {
+const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
+  function Textarea(
+    { className, maxLength = MAX_TEXTAREA_LENGTH, style, ...props },
+    ref
+  ) {

그리고 렌더 부분(style 병합 및 ref 전달은 아래 코멘트 diff 참고).


15-27: style 병합, 고정 크기 완화, 비활성화 상태 스타일 추가 제안

  • scrollbarGutter 기본값을 유지하면서 사용자 style과 병합합니다.
  • w-[24.8rem] 같은 고정 폭은 재사용성을 떨어뜨립니다. DS 기본은 w-full을 추천하고, 너비 제어는 컨테이너에서 맡기면 조합성이 좋아집니다.
  • 높이는 최소 높이로 두고 컨텐츠에 따라 커지게 하거나, rows 속성으로 제어할 수 있게 하는 편이 유연합니다.
  • disabled 상태 시각 피드백을 추가합니다.

적용 예시:

   return (
     <textarea
       data-slot="textarea"
       maxLength={maxLength}
       className={cn(
-        'h-[12rem] w-[24.8rem]',
-        'resize-none overflow-y-auto',
-        'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
+        'min-h-[12rem] w-full',
+        'resize-none overflow-y-auto',
+        'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
+        'disabled:cursor-not-allowed disabled:opacity-50',
         'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
         'ds-scrollbar',
         className
       )}
-      style={{ scrollbarGutter: 'stable' }}
-      {...props}
+      ref={ref}
+      style={{ scrollbarGutter: 'stable', ...style }}
+      {...props}
     />
   );

16-24: ds-scrollbar 적용 범위를 data-slot과도 매핑하는 대안

이미 컴포넌트에 ds-scrollbar를 포함해두신 점 아주 좋습니다. 추가로 전역 CSS에서 [data-slot="textarea"]를 함께 대상으로 잡아두면, 추후 실수로 ds-scrollbar를 제거해도 안전합니다.

관련 CSS(패키지: tailwind-config/scrollbar-styles.css)에 다음을 추가하는 방안:

-:where(.ds-scrollbar) { ... }
+:where(.ds-scrollbar, [data-slot="textarea"]) { ... }

이렇게 하면 Textarea는 data-slot만으로도 일관된 스크롤바가 적용됩니다.

packages/design-system/src/components/textarea/testarea.stories.tsx (5)

1-1: 파일명/주석 불일치 정리 권장

상단 주석은 Textarea.stories.tsx인데 실제 파일명은 testarea.stories.tsx로 보입니다. 팀 컨벤션과 스토리 자동 탐색/정렬 시 혼선을 줄이기 위해 파일명을 Textarea.stories.tsx로 통일하는 것을 권장합니다.


32-35: 중복 기본값 제거: meta.args의 maxLength(500)

컴포넌트 내부 기본값이 500이라면 스토리 meta.args에서 동일 값을 다시 지정할 필요는 없습니다. 단일 소스화로 유지보수 비용을 낮추세요.

   args: {
     placeholder: '나중에 내가 꺼내줄 수 있게 살짝 적어줘!',
-    maxLength: 500,
   },

64-78: maxLength 동작 테스트 보강 제안

현재도 충분히 동작 검증이 되지만, 아래 두 가지를 추가하면 회귀를 더 잘 잡아낼 수 있습니다.

  • DOM 속성(ta.maxLength)이 args와 일치하는지 확인
  • 제한 길이에 도달한 뒤 추가 타이핑이 무시되는지 확인
 export const PreventOverflowByMaxLength: Story = {
   name: '입력 길이 제한 동작',
   args: { maxLength: 50, placeholder: '최대 50자' },
   render: (args) => <Textarea {...args} />,
   play: async ({ canvasElement, args }) => {
     const limit = Number(args.maxLength ?? 500);
     const canvas = within(canvasElement);
     const ta = (await canvas.findByRole('textbox')) as HTMLTextAreaElement;

+    // 속성 자체가 올바르게 전달되었는지 확인
+    expect(ta.maxLength).toBe(limit);
+
     await userEvent.click(ta);
     await userEvent.type(ta, 'a'.repeat(limit + 10)); // 제한 초과 입력 시도

     await expect(ta.value.length).toBe(limit);
+
+    // 추가 입력이 무시되는지 재확인
+    const prev = ta.value.length;
+    await userEvent.type(ta, 'b');
+    expect(ta.value.length).toBe(prev);
   },
 };

1-79: 상수 관리 및 스크롤바 적용 편의성에 대한 제안

  • maxLength 기본값(500)의 단일 소스화: 컴포넌트 내부의 MAX_TEXTAREA_LENGTH(500)를 기준으로, 스토리/문서에서는 상수를 직접 적기보다 import하여 재사용하거나(디자인 시스템에서 공개가 가능하다면) 주석으로만 언급하는 방식을 권장합니다. 추후 값 변경 시 누락 위험을 줄입니다.
  • ds-scrollbar 적용 편의성: 매 소비자마다 클래스를 직접 붙여야 하는 비용이 있으니, Textarea에 기본 적용하고 필요 시 비활성화할 수 있는 prop(예: useScrollbarStyle = true)을 제공하는 패턴도 고려해볼 만합니다. 또는 디자인 시스템 공용 글로벌 스타일을 앱 루트에서 import하는 컨벤션(learned: shared-styles.css 활용 계획)과 함께 “스크롤 가능한 컴포넌트는 기본적으로 ds-scrollbar를 포함한다”는 규칙을 문서화하는 것도 방법입니다.

51-62: ScrollableOverflow 스토리에 커스텀 스크롤바 클래스 추가 및 await 제거 검토

  • 스토리 args 수정 (packages/design-system/src/components/textarea/testarea.stories.tsx:51-62)
    커스텀 스크롤바 스타일은 ds-scrollbar 클래스가 붙은 요소에만 적용되므로, 데모용 스토리에서 해당 클래스를 명시적으로 부여합니다.
  • 불필요한 await 제거
    expect는 동기 호출이므로 await 없이 바로 사용해도 무방합니다.
  • CSS 로드 위치 확인
    • packages/design-system/styles.css 4행에서 @pinback/tailwind-config/scrollbar-styles.css가 import됨을 확인했습니다.
    • packages/design-system/.storybook/preview.ts에서 ../styles.css가 전역으로 import되어 있어 스토리북 환경에도 스크롤바 스타일이 적용됩니다.

제안하는 변경사항:

 export const ScrollableOverflow: Story = {
   name: '넘치면 스크롤',
   args: {
-    defaultValue: 'o '.repeat(400),
+    defaultValue: 'o '.repeat(400),
+    className: 'ds-scrollbar',
   },
   render: (args) => <Textarea {...args} />,
   play: async ({ canvasElement }) => {
     const canvas = within(canvasElement);
     const ta = (await canvas.findByRole('textbox')) as HTMLTextAreaElement;
-    await expect(ta.scrollHeight).toBeGreaterThan(ta.clientHeight);
+    expect(ta.scrollHeight).toBeGreaterThan(ta.clientHeight);
   },
 };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3cbf52e and 504689c.

📒 Files selected for processing (6)
  • packages/design-system/src/components/index.ts (1 hunks)
  • packages/design-system/src/components/textarea/testarea.stories.tsx (1 hunks)
  • packages/design-system/src/components/textarea/textarea.tsx (1 hunks)
  • packages/design-system/styles.css (1 hunks)
  • packages/tailwind-config/package.json (1 hunks)
  • packages/tailwind-config/scrollbar-styles.css (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#5
File: apps/extension/src/index.css:1-1
Timestamp: 2025-08-19T17:18:57.657Z
Learning: constantly-dev는 디자인 시스템 설정 PR 머지 후 `import 'pinback/tailwind-config/shared-styles.css';`를 `app.css`나 `index.css`에서 사용하여 공유 스타일을 관리할 계획입니다.
📚 Learning: 2025-08-19T17:18:57.657Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#5
File: apps/extension/src/index.css:1-1
Timestamp: 2025-08-19T17:18:57.657Z
Learning: constantly-dev는 디자인 시스템 설정 PR 머지 후 `import 'pinback/tailwind-config/shared-styles.css';`를 `app.css`나 `index.css`에서 사용하여 공유 스타일을 관리할 계획입니다.

Applied to files:

  • packages/design-system/styles.css
📚 Learning: 2025-07-04T10:12:01.690Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#10
File: apps/landing/src/index.css:1-1
Timestamp: 2025-07-04T10:12:01.690Z
Learning: In TailwindCSS v4, the import "tailwindcss"; syntax is the new recommended approach that replaces the three separate tailwind directives (tailwind base;, tailwind components;, tailwind utilities;). This single import pulls in all of Tailwind's preflight, components, and utilities in one step and is used with the tailwindcss/vite plugin.

Applied to files:

  • packages/design-system/styles.css
📚 Learning: 2025-07-04T10:12:01.690Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#10
File: apps/landing/src/index.css:1-1
Timestamp: 2025-07-04T10:12:01.690Z
Learning: In TailwindCSS v4, the import "tailwindcss"; syntax is the new recommended approach that replaces the three separate tailwind directives (tailwind base;, tailwind components;, tailwind utilities;). This is used with the tailwindcss/vite plugin.

Applied to files:

  • packages/design-system/styles.css
🧬 Code graph analysis (2)
packages/design-system/src/components/textarea/testarea.stories.tsx (2)
packages/design-system/src/components/textarea/textarea.tsx (1)
  • Textarea (32-32)
packages/design-system/src/components/index.ts (1)
  • Textarea (4-4)
packages/design-system/src/components/textarea/textarea.tsx (3)
packages/design-system/src/components/index.ts (1)
  • Textarea (4-4)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
packages/design-system/src/components/Textarea.tsx (1)
  • Textarea (8-28)
🔇 Additional comments (1)
packages/design-system/src/components/textarea/testarea.stories.tsx (1)

2-2: 공식 Storybook 문서에 따른 타입 import 유지 요청
현재 @storybook/react-vite(v9.1.3) 기반 Vite 빌더를 사용 중이므로, MetaStoryObj 타입은 문서에서 안내하듯이 @storybook/react-vite에서 가져와야 합니다. 별도 @storybook/react 패키지 설치나 import 경로 변경은 불필요하며, 현 상태를 유지해 주시기 바랍니다. (storybook.js.org)

Likely an incorrect or invalid review comment.

Comment on lines 45 to 49
export const WithMaxLength100: Story = {
name: 'MaxLength=500',
args: { maxLength: 500, placeholder: '최대 500자' },
render: (args) => <Textarea {...args} />,
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스토리 명/내용 불일치 수정 (WithMaxLength100)

식별자명은 100을 가리키는데 표시 이름(name)과 args는 500으로 지정되어 있습니다. 혼란을 피하기 위해 실제로 100 제한 시나리오를 보여주도록 정정 바랍니다.

 export const WithMaxLength100: Story = {
-  name: 'MaxLength=500',
-  args: { maxLength: 500, placeholder: '최대 500자' },
+  name: 'MaxLength=100',
+  args: { maxLength: 100, placeholder: '최대 100자' },
   render: (args) => <Textarea {...args} />,
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const WithMaxLength100: Story = {
name: 'MaxLength=500',
args: { maxLength: 500, placeholder: '최대 500자' },
render: (args) => <Textarea {...args} />,
};
export const WithMaxLength100: Story = {
name: 'MaxLength=100',
args: { maxLength: 100, placeholder: '최대 100자' },
render: (args) => <Textarea {...args} />,
};
🤖 Prompt for AI Agents
In packages/design-system/src/components/textarea/testarea.stories.tsx around
lines 45 to 49, the story identifier WithMaxLength100 is inconsistent with the
name and args which use 500; change the story to reflect a 100-character
scenario by updating the name to 'MaxLength=100', set args.maxLength to 100, and
update the placeholder to '최대 100자' so identifier, display name, and args all
match.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
packages/design-system/src/components/textarea/testarea.stories.tsx (5)

5-29: Meta 선언에 satisfies 사용으로 타입 안정성 강화

const meta = {...} satisfies Meta<typeof Textarea> 패턴은 불필요한 속성 유입을 막고, 제네릭 narowing을 개선합니다. 스토리 args 추론도 더 안정적입니다.

-const meta: Meta<typeof Textarea> = {
+const meta = {
   title: 'Components/Textarea',
   component: Textarea,
   tags: ['autodocs'],
   parameters: {
     layout: 'centered',
     docs: {
       description: {
-        component:
-          '고정 크기 **h-[12rem] / w-[24.8rem]** · 기본 **500자** 제한 · 내용 초과 시 내부 스크롤이 나타나는 텍스트영역입니다. ' +
-          '`maxLength`로 글자수 제한을 변경할 수 있습니다.',
+        component:
+          '고정 크기 **h-[12rem] / w-[24.8rem]** · 기본 **500자** 제한 · 내용 초과 시 내부 스크롤이 나타나는 텍스트영역입니다. ' +
+          '`maxLength`로 글자수 제한을 변경할 수 있습니다.',
       },
     },
   },
   argTypes: {
     className: { table: { disable: true } },
     maxLength: { control: { type: 'number', min: 1 } },
     placeholder: { control: 'text' },
     defaultValue: { control: 'text' },
   },
   args: {
     placeholder: '나중에 내가 꺼내줄 수 있게 살짝 적어줘!',
     maxLength: 500,
   },
-};
+} satisfies Meta<typeof Textarea>;

14-15: 기본 글자수(500) 하드코딩 제거 — 컴포넌트 상수와 싱크 유지

Textarea 컴포넌트 내부에 MAX_TEXTAREA_LENGTH = 500이 선언되어 있다면(또는 앞으로 중앙화 예정이라면), 스토리의 설명/args에도 같은 상수를 사용해 드리프트를 방지하는 것이 좋습니다.

  • 옵션 A: textarea.tsx에서 export const MAX_TEXTAREA_LENGTH = 500로 노출
  • 옵션 B: packages/design-system/src/constants/textarea.ts 등으로 분리하여 양쪽에서 import

적용 예시(diff는 상수 export가 가능하다는 가정):

@@
-import type { Meta, StoryObj } from '@storybook/react-vite';
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { MAX_TEXTAREA_LENGTH } from './textarea';
@@
-        component:
-          '고정 크기 **h-[12rem] / w-[24.8rem]** · 기본 **500자** 제한 · 내용 초과 시 내부 스크롤이 나타나는 텍스트영역입니다. ' +
-          '`maxLength`로 글자수 제한을 변경할 수 있습니다.',
+        component:
+          `고정 크기 **h-[12rem] / w-[24.8rem]** · 기본 **${MAX_TEXTAREA_LENGTH}자** 제한 · 내용 초과 시 내부 스크롤이 나타나는 텍스트영역입니다. ` +
+          '`maxLength`로 글자수 제한을 변경할 수 있습니다.',
@@
-    maxLength: 500,
+    maxLength: MAX_TEXTAREA_LENGTH,

PR 설명의 “상수 중앙 관리” 논의와도 맞물립니다. 디자인 토큰/컴포넌트 상수의 소스오브트루스를 단일화해 주세요.

Also applies to: 25-28


69-69: expect는 Promise가 아닙니다 — await 제거 권장

간결성과 테스트 실행 성능 측면에서 await 제거를 추천합니다.

-    await expect(ta.value.length).toBe(limit);
+    expect(ta.value.length).toBe(limit);

1-1: 파일명 오타 의심: testarea.stories.tsxtextarea.stories.tsx

컴포넌트명 및 디렉터리명과의 일관성을 위해 파일명을 textarea.stories.tsx로 변경하는 것이 검색/탐색성 측면에서 유리합니다. 스토리북은 파일명에 의존하지 않지만, 레포 관리 관점에서는 혼동을 줄일 수 있습니다.


44-55: ScrollableOverflow 스토리: ds-scrollbar 클래스 지정 불필요 및 await 제거 제안

Textarea 컴포넌트 내부에서 이미 ds-scrollbar 클래스를 적용하고 있으므로 스토리의 args에 중복 지정할 필요가 없습니다. 또한 expect는 Promise가 아니므로 await를 제거하여 코드를 더 간결하게 유지할 수 있습니다.

  • 파일: packages/design-system/src/components/textarea/testarea.stories.tsx
    • args에서 className: 'ds-scrollbar' 제거
    • play 함수에서
  • await expect(ta.scrollHeight).toBeGreaterThan(ta.clientHeight);
  • expect(ta.scrollHeight).toBeGreaterThan(ta.clientHeight);
    
    
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 504689c and e7f1d4b.

📒 Files selected for processing (1)
  • packages/design-system/src/components/textarea/testarea.stories.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#5
File: apps/extension/src/index.css:1-1
Timestamp: 2025-08-19T17:18:57.657Z
Learning: constantly-dev는 디자인 시스템 설정 PR 머지 후 `import 'pinback/tailwind-config/shared-styles.css';`를 `app.css`나 `index.css`에서 사용하여 공유 스타일을 관리할 계획입니다.
🧬 Code graph analysis (1)
packages/design-system/src/components/textarea/testarea.stories.tsx (2)
packages/design-system/src/components/textarea/textarea.tsx (1)
  • Textarea (32-32)
packages/design-system/src/components/index.ts (1)
  • Textarea (4-4)
🔇 Additional comments (2)
packages/design-system/src/components/textarea/testarea.stories.tsx (2)

38-42: WithMaxLength500 스토리: 명칭과 args 일치 — OK

이전 커밋에서 지적되었던 명칭/args 불일치가 해소되었습니다. 현재 식별자·표시 이름·args가 모두 500으로 일관됩니다.


1-1: 스토리북 타입 import 경로 유지: @storybook/react-vite가 올바릅니다.

검토 결과, packages/design-system/package.json 내에는 @storybook/react-vite@9.1.3만이 의존성으로 등록되어 있고, @storybook/react는 포함되어 있지 않습니다.
또한 Vite 환경에서 Storybook을 사용하는 경우 공식 가이드에 따라 @storybook/react-vite에서 타입을 가져오는 것이 권장됩니다.
따라서 기존 import 구문을 그대로 유지해 주세요.

Likely an incorrect or invalid review comment.

Copy link
Member

@constantly-dev constantly-dev left a comment

Choose a reason for hiding this comment

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

수고하셨습니다 몇 개 코멘트 남겼으니 확인부탁드려요~

Comment on lines 46 to 48
args: {
defaultValue: 'o '.repeat(400),
},
Copy link
Member

Choose a reason for hiding this comment

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

o 반복보다 글이나 시라도 넣는 것은 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

코드래빗 시 넣는것도 재밌을것 같아요


const MAX_TEXTAREA_LENGTH = 500;

type TextareaProps = React.ComponentProps<'textarea'>;
Copy link
Member

Choose a reason for hiding this comment

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

shadcn을 사용하셔서 그렇기는 하지만 저희의 컨벤션 상 objectinterface를 사용하기로 했기 때문에 이 부분 interface로 반영하고, textarea 기본 속성 extends하면 될 것 같아요!

shadcn이 단순 라이브러리가 아니라 코드 베이스니까 자유롭게 커스텀이 가능하다는 장점을 가지고 있으니, 컨벤션을 지켜주세요!

Suggested change
type TextareaProps = React.ComponentProps<'textarea'>;
interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {

data-slot="textarea"
maxLength={maxLength}
className={cn(
'h-[12rem] w-[24.8rem]',
Copy link
Member

Choose a reason for hiding this comment

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

하나 질문이 있습니다!
현재 GUI로는 메모 부분에 textarea를 사용하기는 하는데, 이후 확장성을 고려하면 width값이 매번 같지 않아 고정이 안될 것 같은데 어떻게 생각하시나요? 그런 관점에서는 width를 고정하는 것이 아닌, w-full을 주고 외부 containerwidth값을 조정해서 크기를 맞추는 것은 어떨까요??

높이는 조금 더 고민해봐야 할 것 같지만, 일단 width 먼저 생각해봐야 할 것 같아요.
정민님은 이에 대해서 어떻게 생각하시나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

width는 확장성을 고려해서 w-full을 주고 외부 container의 width값을 조정해서 크기를 맞추는 것이 좋을것 같다고 생각합니다 반응형이나 다른 사이즈가 추가 된다면 w-full주는 방식이 좋아보여요.

높이는 아직 고정해서 사용하는게 좋릏 것 같다고 생각합니다-!!

Copy link
Member

Choose a reason for hiding this comment

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

shared-styles.css가 있는 입장에서 스크롤 바에만 해당되는 scrollbar-styles.css 패키지를 분리하신 이유가 궁금합니다!

특히 안에 스크롤 폭/높이가 고정 값으로 담겨있는데, 이렇게 되면 다른 스타일이 있는 스크롤바에서는 재사용이 불가능할 것 같아서 패키지의 의미가 크게 없다고 생각해요.

스타일을 분리할 목적이라면 기본 스크롤 없애는 스타일만 유틸리티로 shared-styles.css에 만들고 사용하는 곳에서 className에 만든 유틸리티 넣어서 기본 스타일 제거하고, 추가 스타일은 사용하는 곳에서 정의하는 것은 어떤가요??

물론 기본 스타일 높이/폭 등도 적용한 유틸리티를 만들어서 재사용하고 싶으시다면 디자이너 분들께 스크롤바를 사용하는 곳에서 스타일을 통일하는 방향으로 디자인을 해주시면 감사하겠다고 전달해야 할 것 같아요.

정민님 생각이 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

스타일 초기화하기위해 scrollbar-styles.css 패키지 만들었는데 생각해보니 shared-styles.css 안에 같이 정의하는게 좋을것같다고 판단되네요 이 부분은 수정하겠습니다-!

디자이너 분들께 전달해서 스크롤바를 사용하는 곳에서 스타일을 통일하는 방향으로 디자인 통일하기로했습니다-!

Copy link
Member

Choose a reason for hiding this comment

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

image image

해당 구현 보면 오른쪽 피그마처럼 구현이 되어야 할 것 같아요. 기본 스크롤 형태 스타일을 지우고 바만 남겨야 할 것 같습니다! 스타일도 더 정확하게!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵-!!

import * as React from 'react';
import { cn } from '@/lib/utils';

const MAX_TEXTAREA_LENGTH = 500;
Copy link
Member

Choose a reason for hiding this comment

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

MAX_TEXTAREA_LENGTH는 모든 textarea에서 고정인가요? 해당 값을 props로 받는 것에 대해서 어떻게 생각하시나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

기능 명세서에 500자 제한으로 되어있어 textarea에서 선언했던건데 사용처에서 props로 받는 방식이 안헷갈리고 더 좋을것같다는 생각이드네요 수정하겠습니다-!

@constantly-dev
Copy link
Member

현재 ds-scrollbar 클래스를 붙인 요소에만 적용됩니다. 피그마에있는 너비, 컬러로 공통적인 디자인 적용해뒀습니다. 스크롤이 생기는 컴포넌트에 매번 적용하기에는 번거로울거같아 다른 방법있을지 궁금합니다.

스크롤은 기본적으로 요소가 height값을 넘어설 때 자동으로 생기게 하면 될 것 같아요.

@constantly-dev
Copy link
Member

파일 제목 컨벤션

저희가 현재 컴포넌트에는 PascalCase 사용하기로했는데 shadecn으로 생성한 파일들은 carmelCase로 자동생성됩니다. 현재는 그냥 자동생성된 형식으로 파일 이름 지정해뒀는데 PascalCase로변경하는게 좋을까요?

일단 제 생각은 컨벤션대로 PacalCase를 사용하는 것이 맞다고 생각합니다!
그래서 궁금해서 shadcn에서 config options나 cli 제공하는 것이 있나 봤더니, issue에 올라온 것이 있긴 한데 아직 반영은 안된 거 같더라고요. 최근 이슈가 아니라 아예 제공할 생각이 없는 것 같기도...!

shadcn-ui/ui#962
shadcn-ui/ui#774

따라서 두 가지 방법이 있을 것 같은데,

  1. 수동으로 파일명 바꾼다.
  2. 파일명 변환해주는 함수 만들어서 적용한다.

그냥 바꾸는 건 어렵지 않으니 1번으로 해도 될 것 같은데, 다만 add하고 파일명 수정할 때 OS에 따라 아마 윈도우..? 파일 이름 변경하고 대/소문자 관련 문제가 있던 경우가 있어서, 한번 바꾸고 체크해봐야겠네요 👍

@constantly-dev
Copy link
Member

스크롤바 커스텀 적용 방식 결정 필요

현재 ds-scrollbar 클래스를 붙인 요소에만 적용됩니다. 피그마에있는 너비, 컬러로 공통적인 디자인 적용해뒀습니다. 스크롤이 생기는 컴포넌트에 매번 적용하기에는 번거로울거같아 다른 방법있을지 궁금합니다.

maxLength 매직 넘버 분리

Textarea.tsx 내부에 MAX_TEXTAREA_LENGTH = 500 상수로 선언해 사용했습니다. 추후 상수만 관리하는 파일을 만들어 따로 분리하여 사용하는 것에 대해 어떻게 생각하시는지 얘기해보면 좋을거같아요-!!

이 부분은 리뷰로 달았으니 참고해주세요 👍

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
packages/design-system/src/components/textarea/Textarea.tsx (3)

23-30: 고정 폭(w-[24.8rem]) 대신 w-full(+max-w) 기본값 권장

컴포넌트 기본 폭을 고정하면 레이아웃 재사용성이 떨어집니다. 기본을 w-full로 두고, Figma 스펙은 max-w로 제한하면 사용성이 좋아집니다. twMerge 덕에 외부 className이 우선이지만, 기본값 자체를 더 범용적으로 설정하는 편이 안전합니다.

-        'h-[12rem] w-[24.8rem]',
+        'h-[12rem] w-full max-w-[24.8rem]',

26-29: scrollbar-gutter + pr 동시 적용 시 우측 여백 과도 가능성 → 조건부 padding으로 정리

현재 'scrollbarGutter: stable'로 스크롤바 거터가 항상 예약되는데, 동시에 'pr-[1.4rem]'을 고정 적용하면 환경에 따라(오버레이 스크롤바, 플랫폼별 차이) 우측 여백이 과도해질 수 있습니다. stableScrollbarGutter가 true일 때는 pr 제거, false일 때에만 pr을 주는 형태를 권장합니다.

-        'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
+        'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem]',
         'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
-        'ds-scrollbar',
+        'ds-scrollbar',
+        stableScrollbarGutter ? undefined : 'pr-[1.4rem]',
         className

5-5: MAX_TEXTAREA_LENGTH 상수 중앙화 제안

여러 컴포넌트에서 재사용될 여지가 있으므로 상수를 별도 모듈로 이동해 단일 출처로 관리하는 것을 권장합니다. 현 파일에서는 import 후 재-exports 하여 기존 퍼블릭 API도 유지하세요.

예시:

  • 새 파일 생성: packages/design-system/src/constants/form.ts
// packages/design-system/src/constants/form.ts
export const MAX_TEXTAREA_LENGTH = 500 as const;
  • 본 컴포넌트 수정:
-import { cn } from '@/lib/utils';
-
-export const MAX_TEXTAREA_LENGTH = 500;
+import { cn } from '@/lib/utils';
+import { MAX_TEXTAREA_LENGTH } from '@/constants/form';
  • 파일 하단에 재-exports 추가(퍼블릭 API 유지 목적):
export { MAX_TEXTAREA_LENGTH };

원하시면 관련 파일(Index barrel 포함)까지 일괄 리팩터링 패치 드리겠습니다.

packages/design-system/src/components/textarea/Textarea.stories.tsx (5)

1-1: Storybook 타입 import를 '@storybook/react'로 교체 권장

프레임워크/빌더에 덜 결합된 공식 권장 import는 '@storybook/react'입니다. v8 기준으로도 해당 경로 사용을 권장합니다.

-import type { Meta, StoryObj } from '@storybook/react-vite';
+import type { Meta, StoryObj } from '@storybook/react';

19-28: stableScrollbarGutter를 컨트롤러에 노출해 시나리오 검증 확대

컴포넌트가 제공하는 핵심 옵션(stableScrollbarGutter)을 스토리 argTypes/args에 노출하면 QA/디자인 리뷰가 용이합니다.

   argTypes: {
     className: { table: { disable: true } },
     maxLength: { control: { type: 'number', min: 1 } },
     placeholder: { control: 'text' },
     defaultValue: { control: 'text' },
+    stableScrollbarGutter: { control: 'boolean' },
   },
   args: {
     placeholder: '나중에 내가 꺼내줄 수 있게 살짝 적어줘!',
     maxLength: 500,
+    stableScrollbarGutter: true,
   },

38-42: WithMaxLength500 스토리는 Basic과 중복됨

기본 args가 이미 500이므로 중복 케이스입니다. 유지하려면 1000 등 차별화된 값으로 바꾸거나 삭제를 제안합니다.

 export const WithMaxLength500: Story = {
-  name: 'MaxLength=500',
-  args: { maxLength: 500, placeholder: '최대 500자' },
+  name: 'MaxLength=1000',
+  args: { maxLength: 1000, placeholder: '최대 1000자' },
   render: (args) => <Textarea {...args} />,
 };

44-55: 오버플로 스토리의 maxLength 상호작용 제거로 테스트 안정성 강화

브라우저마다 defaultValue와 maxlength 상호작용이 다를 수 있어(일반적으로 defaultValue는 잘려나가지 않지만) 명시적으로 큰 maxLength를 지정해 테스트 안정성을 높이는 것을 권장합니다.

 export const ScrollableOverflow: Story = {
   name: '넘치면 스크롤',
   args: {
-    defaultValue: 'o '.repeat(400),
+    defaultValue: 'o '.repeat(400),
+    maxLength: 1000,
   },
   render: (args) => <Textarea {...args} />,

57-71: maxLength 플레이라이트 테스트 적절 — 추가로 이모지(서로게이트) 케이스 문서화 고려

현재 테스트는 영문자 기준이며, 브라우저의 maxLength는 UTF-16 코드 유닛 기준이라 이모지 카운트가 사용자 기대와 다를 수 있습니다. 문서에 주석을 추가하거나 별도 스토리/노트로 알려주면 혼선을 줄일 수 있습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e7f1d4b and 42d0faf.

📒 Files selected for processing (3)
  • packages/design-system/src/components/index.ts (1 hunks)
  • packages/design-system/src/components/textarea/Textarea.stories.tsx (1 hunks)
  • packages/design-system/src/components/textarea/Textarea.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/design-system/src/components/index.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#5
File: apps/extension/src/index.css:1-1
Timestamp: 2025-08-19T17:18:57.678Z
Learning: constantly-dev는 디자인 시스템 설정 PR 머지 후 `import 'pinback/tailwind-config/shared-styles.css';`를 `app.css`나 `index.css`에서 사용하여 공유 스타일을 관리할 계획입니다.
🧬 Code graph analysis (2)
packages/design-system/src/components/textarea/Textarea.tsx (2)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
packages/design-system/src/components/Textarea.tsx (1)
  • Textarea (8-28)
packages/design-system/src/components/textarea/Textarea.stories.tsx (1)
packages/design-system/src/components/textarea/Textarea.tsx (1)
  • Textarea (12-38)
🔇 Additional comments (5)
packages/design-system/src/components/textarea/Textarea.tsx (4)

31-34: 인라인 style 병합 우선순위 적절합니다

사용자 style이 마지막에 병합되어 최종 우선권을 갖는 설계가 올바릅니다.


21-29: 클래스 구성(cn + twMerge) 순서 적절

기본 클래스 → 유틸리티 → 사용자 className 순으로 병합되어 외부 오버라이드가 자연스럽습니다.


12-18: forwardRef 적용이 누락되어 폼 라이브러리/포커스 제어에 제약이 있습니다

디자인 시스템의 입력 컴포넌트는 ref 전달을 기본으로(react-hook-form, focus 관리, 스크롤-into-view 등) 지원하는 게 일반적입니다. forwardRef로 전환을 권장합니다.

아래처럼 변경 제안드립니다:

-export function Textarea({
-  className,
-  maxLength = MAX_TEXTAREA_LENGTH,
-  stableScrollbarGutter = true,
-  style,
-  ...props
-}: TextareaProps) {
-  return (
-    <textarea
-      data-slot="textarea"
+export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
+  ({ className, maxLength = MAX_TEXTAREA_LENGTH, stableScrollbarGutter = true, style, ...props }, ref) => {
+    return (
+      <textarea
+        data-slot="textarea"
+        ref={ref}
         maxLength={maxLength}
         className={cn(
           'h-[12rem] w-[24.8rem]',
           'resize-none overflow-y-auto',
           'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
           'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
           'ds-scrollbar',
           className
         )}
         style={{
           ...(stableScrollbarGutter ? { scrollbarGutter: 'stable' } : {}),
           ...style, // 사용자가 넘긴 style이 최종 우선
         }}
         {...props}
       />
-  );
-}
+    );
+  }
+);
+
+Textarea.displayName = 'Textarea';

Also applies to: 20-21, 37-38

⛔ Skipped due to learnings
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/src/components/input/Input.tsx:5-9
Timestamp: 2025-08-23T18:37:59.985Z
Learning: React 19에서는 함수 컴포넌트가 ref를 일반 prop으로 받을 수 있다. interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있어 forwardRef가 불필요하다. React 19.1.1+ 프로젝트에서는 이 방식을 사용해야 한다.
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/src/components/input/Input.tsx:5-9
Timestamp: 2025-08-23T18:37:59.985Z
Learning: React 19부터 함수 컴포넌트에서 ref를 일반 prop으로 받을 수 있어 forwardRef가 더 이상 필요하지 않다. InputProps interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있다.

1-4: 폴더/파일 네이밍 컨벤션 검토 필요

  • packages/design-system/src/components/textarea/Textarea.tsx 내부에 export function Textarea 정의가 단일로 존재하므로 중복 정의 이슈는 없습니다.
  • packages/design-system/src/components/index.ts에서 export { Textarea } from './textarea/Textarea'; 로 정상 노출되고 있습니다.
  • packages/design-system/styles.css 에서도 shared-styles.css, scrollbar-styles.css 를 임포트 중이어서 전역 스타일 로딩에는 문제가 없습니다.
  • 다만, 현재 컴포넌트 디렉토리(textarea)는 소문자, 컴포넌트 파일(Textarea.tsx)은 PascalCase로 분리되어 있습니다.
    • 팀 내 컨벤션이 “컴포넌트 폴더도 PascalCase”라면, 다른 컴포넌트 디렉토리(예: input, switch, …)와 동일하게 일괄 리네임이 필요합니다.
    • 리네임 시 Windows 등 OS별 대소문자 민감도 환경에서 의도치 않은 파일 누락/충돌이 발생하지 않도록 사전 테스트를 권장합니다.

위 내용을 바탕으로 팀 컨벤션을 재확인하시고, 필요 시 디렉토리명을 조정해주세요.

packages/design-system/src/components/textarea/Textarea.stories.tsx (1)

5-18: 문서(description)와 기본 args가 컴포넌트 기본값과 일치합니다

고정 크기 및 기본 500자 제한 설명이 실제 기본값과 맞습니다. 좋습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (8)
packages/tailwind-config/shared-styles.css (2)

242-251: Firefox 대응 보강: scrollbar-width 추가

scrollbar-color는 Firefox에서만 적용되고, 크기 제어도 Firefox에서는 scrollbar-width(auto/thin/none)만 지원합니다. 현재 크기 변수(--ds-scroll-size)는 Chromium/Safari에서만 의미가 있으므로 Firefox에서는 폭이 기본값으로 보일 수 있습니다.

아래 한 줄을 추가해 시각적 일관성을 높이세요.

   :where(.ds-scrollbar) {
     --ds-scroll-size: 0.4rem;
     --ds-scroll-thumb: var(--color-gray100);
     --ds-scroll-track: transparent;

     scrollbar-color: var(--ds-scroll-thumb) var(--ds-scroll-track);
+    scrollbar-width: thin; /* Firefox */
-    scrollbar-gutter: stable;
   }

참고: scrollbar-width는 구체 px 지정이 불가합니다. 필요 시 Firefox 전용 대안 레이아웃(내부 패딩 조정 등)도 검토해보세요.


264-270: 디자인 미세 조정 제안: Thumb 시각적 품질

현재 thumb는 단색+큰 라운딩입니다. 트랙과의 대비가 약할 수 있으니, 필요 시 다음을 고려할 수 있습니다.

  • hover 상태에만 약한 명도 변화 도입(모션은 유지하지 않음)
  • 고대비 모드 대응: @media (prefers-contrast: more)에서 --ds-scroll-thumb 상향

원칙적으로 괜찮지만, 테마/다크모드가 들어오면 CSS 변수만 교체해도 되도록 변수 전략만 유지하면 충분합니다.

packages/design-system/src/components/textarea/Textarea.stories.tsx (6)

14-16: 문서 설명의 사이즈 정보가 컴포넌트와 불일치

설명에는 고정 너비 w-[24.8rem]으로 표기돼 있으나, 실제 Textareaw-full(부모 컨테이너 너비)에 맞춰집니다. 문서-구현 간 혼선을 줄이기 위해 정정이 필요합니다.

아래처럼 서술을 맞추는 것을 제안합니다.

-          '고정 크기 **h-[12rem] / w-[24.8rem]** · 기본 **500자** 제한 · 내용 초과 시 내부 스크롤이 나타나는 텍스트영역입니다. ' +
+          '고정 높이 **h-[12rem]**, 너비는 **w-full(컨테이너 기준)** · 기본 **500자** 제한 · 내용 초과 시 내부 스크롤이 나타나는 텍스트영역입니다. ' +

25-28: 중복 기본값은 제거 가능

컴포넌트가 이미 maxLength 기본값(500)을 제공한다면, 스토리의 args에서 동일 값을 중복 지정할 필요는 없습니다. 제거하면 오히려 “컴포넌트 기본값”을 검증하는 의도도 드러납니다.

   args: {
     placeholder: '나중에 내가 꺼내줄 수 있게 살짝 적어줘!',
-    maxLength: 500,
   },

44-94: 오버플로우 검증의 안정성 향상 포인트

scrollHeight > clientHeight 검증은 타이밍/폰트 로딩에 영향을 받을 수 있어 드물게 플래키할 수 있습니다. 테스트 신뢰도를 높이려면 페인트 이후로 한 프레임을 양보하거나(raf), requestIdleCallback 대체 대기 등을 추가하는 방법이 있습니다.

예시:

await new Promise<void>((r) => requestAnimationFrame(() => r()));
const ta = (await canvas.findByRole('textbox')) as HTMLTextAreaElement;
await expect(ta.scrollHeight).toBeGreaterThan(ta.clientHeight);

또한, stableScrollbarGutter 토글 스토리를 별도로 두고 “레이아웃 시프트가 없는지”를 시각/상호작용 테스트로 보강하면 회귀를 잡는 데 도움이 됩니다.


96-110: 입력 길이 제한 테스트: 속성 자체도 단정 추가 권장

현재는 결과 값의 길이로만 검증합니다. 추가로 DOM 속성(maxlength)이 기대대로 설정됐는지도 단정하면 회귀 방지가 강화됩니다.

   const ta = (await canvas.findByRole('textbox')) as HTMLTextAreaElement;

+  // 속성 자체가 올바르게 반영되는지 먼저 확인
+  await expect(ta).toHaveAttribute('maxlength', String(limit));
+
   await userEvent.click(ta);
   await userEvent.type(ta, 'a'.repeat(limit + 10));

   await expect(ta.value.length).toBe(limit);

34-42: 중복 렌더 패턴 간소화(선택)

여러 스토리에서 동일한 render: (args) => <Textarea {...args} />가 반복됩니다. 기본 렌더는 생략 가능하며, 스토리는 args만 제공해도 됩니다. 불필요한 보일러플레이트를 줄일 수 있습니다.

보정 예:

  • Basic, WithMaxLength500, PreventOverflowByMaxLength에서 render 제거
  • 커스텀 렌더가 필요한 스토리만 유지

38-41: 이름/표기 일관성

WithMaxLength500 스토리는 name: 'MaxLength=500'으로 표기되어 있습니다. 한글/영문 혼용 중인 다른 스토리들과 네이밍 규칙을 맞추면 탐색성이 좋아집니다. 예: '최대 길이 500' 또는 'Max length: 500'.

원하시면 전체 Story 네이밍 규칙(포맷) 가이드 초안도 드리겠습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 42d0faf and 9388016.

📒 Files selected for processing (3)
  • packages/design-system/src/components/textarea/Textarea.stories.tsx (1 hunks)
  • packages/design-system/src/components/textarea/Textarea.tsx (1 hunks)
  • packages/tailwind-config/shared-styles.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/design-system/src/components/textarea/Textarea.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/design-system/src/components/textarea/Textarea.stories.tsx (1)
packages/design-system/src/components/textarea/Textarea.tsx (1)
  • Textarea (11-37)
🔇 Additional comments (1)
packages/design-system/src/components/textarea/Textarea.stories.tsx (1)

1-1: Storybook CSF: Vite 환경에서는 @storybook/react-vite에서 타입을 임포트해야 합니다
공식 문서에 따르면 CSF 파일에서 import type { Meta, StoryObj } from '@storybook/your-framework'을 사용하며, Vite 환경에서는 @storybook/react-vite를 지정해야 합니다(storybook.js.org). 따라서 현재 상태 그대로 유지해 주시면 됩니다.

Likely an incorrect or invalid review comment.

Comment on lines +250 to +251
scrollbar-gutter: stable;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Textarea의 stableScrollbarGutter 속성이 비활성화되지 않는 버그

.ds-scrollbarscrollbar-gutter: stable;가 기본 적용되어 있어, 컴포넌트에서 stableScrollbarGutter={false}로 내려도 실제로는 비활성화되지 않습니다. 현재 구현(Textarea.tsx)은 true일 때만 인라인 스타일을 추가하므로 false일 때 CSS 규칙이 그대로 남습니다.

  • 옵션을 신뢰할 수 있게 하려면 인라인 스타일에서 항상 값을 명시해 우선순위로 덮어쓰는 방식이 안전합니다.

컴포넌트 쪽 수정 제안(Textarea.tsx 내 style 속성만 교체):

-      style={{
-        ...(stableScrollbarGutter ? { scrollbarGutter: 'stable' } : {}),
-        ...style,
-      }}
+      style={{
+        scrollbarGutter: stableScrollbarGutter ? 'stable' : 'auto',
+        ...style,
+      }}

또는 CSS에서 기본값을 제거하고(아래 라인 삭제) 컴포넌트에서만 제어하는 것도 방법입니다.

-    scrollbar-gutter: stable;
🤖 Prompt for AI Agents
In packages/tailwind-config/shared-styles.css around lines 250-251 the rule
"scrollbar-gutter: stable;" applied to .ds-scrollbar forces the gutter on all
textareas and prevents stableScrollbarGutter={false} from taking effect; either
remove this default CSS line so the component controls the gutter, or
(preferred) update Textarea.tsx to always set an inline scrollbar-gutter style
that overrides the stylesheet — e.g. in the style prop always include
scrollbarGutter: stableScrollbarGutter ? 'stable' : 'unset' (or 'auto') so the
prop reliably enables or disables the gutter via inline priority.

Comment on lines +253 to +258
:where(.ds-scrollbar)::-webkit-scrollbar,
:where(.ds-scrollbar)::-webkit-scrollbar:hover,
:where(.ds-scrollbar)::-webkit-scrollbar:active {
width: var(--ds-scroll-size);
height: var(--ds-scroll-size);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

불필요/무효한 선택자 정리: ::-webkit-scrollbar:hover, :active

::-webkit-scrollbar 자체에는 :hover/:active 상태가 적용되지 않습니다. 현재 셀렉터 병합은 의미가 없고 규칙 파싱만 늘립니다. 기본 ::-webkit-scrollbar만 남기세요.

아래처럼 간소화:

-  :where(.ds-scrollbar)::-webkit-scrollbar,
-  :where(.ds-scrollbar)::-webkit-scrollbar:hover,
-  :where(.ds-scrollbar)::-webkit-scrollbar:active {
+  :where(.ds-scrollbar)::-webkit-scrollbar {
     width: var(--ds-scroll-size);
     height: var(--ds-scroll-size);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
:where(.ds-scrollbar)::-webkit-scrollbar,
:where(.ds-scrollbar)::-webkit-scrollbar:hover,
:where(.ds-scrollbar)::-webkit-scrollbar:active {
width: var(--ds-scroll-size);
height: var(--ds-scroll-size);
}
:where(.ds-scrollbar)::-webkit-scrollbar {
width: var(--ds-scroll-size);
height: var(--ds-scroll-size);
}
🤖 Prompt for AI Agents
In packages/tailwind-config/shared-styles.css around lines 253 to 258, the
selector group includes invalid/unnecessary pseudo-classes
(::-webkit-scrollbar:hover and ::-webkit-scrollbar:active) which have no effect
on the pseudo-element; remove the :hover and :active variants and keep only the
base ::-webkit-scrollbar selector (and its width/height declarations) to
simplify and avoid meaningless selector duplication.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/design-system/src/components/textarea/Textarea.tsx (3)

5-8: maxLength를 필수로 강제할지 재확인 + 기본값(예: 500)과 상수 추출 제안

현 구현은 모든 사용처에서 maxLength를 매번 지정해야 합니다. DS의 사용성을 고려하면 기본값을 제공하고, 프로젝트 전역에서 재사용할 수 있도록 상수로 추출하는 편이 실용적입니다. PR 코멘트에서도 중앙 관리 필요성이 언급되어 있어 다음과 같이 개선을 제안합니다.

 import { cn } from '@/lib/utils';
 
+// TODO: 별도 constants 모듈로 이동을 검토하세요.
+const DEFAULT_TEXTAREA_MAX_LENGTH = 500;
+
 export interface TextareaProps
   extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength'> {
-  maxLength: number;
+  maxLength?: number;
 }

아울러 JSX에서도 기본값을 사용하도록 다음과 같이 변경합니다(아래 diff는 본문 JSX 일부):

-      maxLength={maxLength}
+      maxLength={maxLength ?? DEFAULT_TEXTAREA_MAX_LENGTH}

필수로 유지하고 싶은 명확한 가이드(예: 서비스 정책상 모든 입력에 상한 필요)가 있다면 그대로 두되, 최소한 디자인 시스템 레벨에서 합의된 상수로 관리하는 것을 권장합니다.


22-22: 리사이즈 정책 고정(resize-none) — 옵션화 제안

현재 사용자는 텍스트 입력이 길어도 크기를 조절할 수 없습니다. 디자인 요구에 따라 기본은 비리사이즈로 두되, resizable 같은 불리언 prop을 제공해 컨텍스트에 따라 활성화할 수 있도록 열어두는 것을 제안합니다.

예시:

-export interface TextareaProps
+export interface TextareaProps
   extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength'> {
   maxLength?: number;
+  resizable?: boolean;
 }
 
-  ({ className, maxLength, style, ...props }, ref) => {
+  ({ className, maxLength, style, resizable = false, ...props }, ref) => {
 ...
-        'resize-none overflow-y-auto',
+        (resizable ? 'resize-y' : 'resize-none'),
+        'overflow-y-auto',

25-25: ds-scrollbar를 컴포넌트 내부에서 상시 적용할지 여부

지금은 내부적으로 항상 ds-scrollbar를 붙입니다. 디자인 시스템 전역 정책이라면 괜찮지만, 특정 페이지/컨테이너에서만 커스텀 스크롤바가 필요하다면 과도한 적용이 될 수 있습니다.

옵션:

  • 기본 적용 유지 + withScrollbar(기본값 true) prop로 제어폭 제공.
  • 반대로 기본 미적용 + 소비자가 opt-in으로 ds-scrollbar를 넘기도록 문서화.
  • Tailwind 플러그인/전역 스타일 레이어에서 특정 슬롯(data-slot="textarea") 기준으로 일괄 적용.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9388016 and b102c1e.

📒 Files selected for processing (1)
  • packages/design-system/src/components/textarea/Textarea.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/design-system/src/components/textarea/Textarea.tsx (1)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
🔇 Additional comments (2)
packages/design-system/src/components/textarea/Textarea.tsx (2)

28-28: 스타일 병합 순서(LTR) 적절합니다

{ scrollbarGutter: 'stable', ...style }로 사용자 style이 우선 적용됩니다. 의도에 부합하며, 예외 케이스 대응도 용이합니다.


10-15: ref 포워딩 누락 — 폼 라이브러리, 포커스 관리, 스크롤-투-에러 등과의 호환을 위해 forwardRef 권장

Textarea는 포커스 제어, 커서 위치, 유효성 검증 스크롤 등에서 ref 접근이 빈번합니다. DS 기본 프리미티브는 React.forwardRef 패턴을 따르는 것이 일반적입니다.

-export function Textarea({
-  className,
-  maxLength,
-  style,
-  ...props
-}: TextareaProps) {
-  return (
-    <textarea
+export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
+  ({ className, maxLength, style, ...props }, ref) => {
+    return (
+      <textarea
+        ref={ref}
         data-slot="textarea"
-      maxLength={maxLength}
+        maxLength={maxLength ?? DEFAULT_TEXTAREA_MAX_LENGTH}
         className={cn(
           'h-[12rem] w-full',
           'resize-none overflow-y-auto',
           'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
-          'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
+          'focus:border-input outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
           'ds-scrollbar',
           className
         )}
         style={{ scrollbarGutter: 'stable', ...style }}
         {...props}
       />
-  );
-}
+    );
+  }
+);
+
+Textarea.displayName = 'Textarea';

참고: 위 diff엔 접근성 포커스 개선도 함께 반영했습니다. 포커스 스타일 변경은 아래 별도 코멘트에서도 다룹니다.

Also applies to: 17-31

⛔ Skipped due to learnings
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/src/components/input/Input.tsx:5-9
Timestamp: 2025-08-23T18:37:59.985Z
Learning: React 19에서는 함수 컴포넌트가 ref를 일반 prop으로 받을 수 있다. interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있어 forwardRef가 불필요하다. React 19.1.1+ 프로젝트에서는 이 방식을 사용해야 한다.
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/src/components/input/Input.tsx:5-9
Timestamp: 2025-08-23T18:37:59.985Z
Learning: React 19부터 함수 컴포넌트에서 ref를 일반 prop으로 받을 수 있어 forwardRef가 더 이상 필요하지 않다. InputProps interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있다.

Comment on lines +21 to +22
'h-[12rem] w-full',
'resize-none overflow-y-auto',
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

rows/height 충돌: 고정 height가 소비자 props(예: rows, style.height)를 무력화합니다

h-[12rem]가 명시되어 있어 rows나 인라인 style={{ height }}를 전달해도 효과가 없습니다. 디자인 시스템 컴포넌트는 합리적인 기본값을 주되, 소비자 제어를 막지 않는 쪽이 좋습니다. 최소 높이만 보장하고 나머지는 소비자/브라우저 기본 동작에 맡기는 형태로 변경을 권장합니다.

아래처럼 최소 높이로 완화하면 rows/style.height와도 잘 공존합니다.

-        'h-[12rem] w-full',
+        'min-h-[12rem] h-auto w-full',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'h-[12rem] w-full',
'resize-none overflow-y-auto',
'min-h-[12rem] h-auto w-full',
'resize-none overflow-y-auto',
🤖 Prompt for AI Agents
In packages/design-system/src/components/textarea/Textarea.tsx around lines
21-22, the textarea uses a fixed height class 'h-[12rem]' which prevents
consumer props like rows or inline style={{ height }} from taking effect;
replace the fixed height with a minimum height (e.g., 'min-h-[12rem]') so the
component provides a sensible default while allowing consumers or the browser to
override height via rows/style; ensure the class list retains 'w-full' and any
overflow/resize classes and that the min-h class appears where the fixed height
was.

Comment on lines +21 to +27
'h-[12rem] w-full',
'resize-none overflow-y-auto',
'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
'ds-scrollbar',
className
)}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성: 포커스 인디케이터가 충분히 눈에 띄지 않을 수 있습니다

현재 클래스는 outline/ring을 제거하고 border 색만 바꾸는데, 이는 키보드 사용자에게 포커스가 잘 보이지 않을 수 있습니다. 최소 focus-visible 상태에서 두께 있는 ring과 대비를 주길 권장합니다.

권장 수정(토큰 네이밍은 디자인 토큰에 맞춰 조정하세요):

-        'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
+        'focus:border-input outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',

추가로 배경색 토큰이 있다면 focus-visible:ring-offset-2 focus-visible:ring-offset-[bg-token]도 고려해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'h-[12rem] w-full',
'resize-none overflow-y-auto',
'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
'ds-scrollbar',
className
)}
// packages/design-system/src/components/textarea/Textarea.tsx
className={clsx(
'h-[12rem] w-full',
'resize-none overflow-y-auto',
'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]',
'focus:border-input outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
'ds-scrollbar',
className
)}
🤖 Prompt for AI Agents
In packages/design-system/src/components/textarea/Textarea.tsx around lines 21
to 27, the current classes remove outline/ring making keyboard focus hard to
see; update the focus-visible styles to add a thicker, high-contrast ring and
optional offset instead of fully suppressing focus indicators (e.g., add classes
like focus-visible:ring-2 focus-visible:ring-[token-foreground]
focus-visible:ring-offset-2 focus-visible:ring-offset-[token-background] and
keep focus:outline-none only for non-visible focus while ensuring focus-visible
shows the ring); use your design token names for the ring color and offset
background.

Copy link
Collaborator

@jllee000 jllee000 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

  1. 컨벤션은 shadcn이더라도 저희 서비스에 맞춰 수정하는 게 좋을 것 같습니당
  2. 상수화 의견은 적극 찬성하고 상수 관리 파일 도입 필요하다고 생각합니당! 그렇다면 이 파일은 디자인 시스템 내의 상수만 따로 모아서 파일 만들어야 할 지 도메인별로 할 지 같이 고민해보면 좋을 거 같아요!
  3. 스크롤바 커스텀 너무 고생하셨습니다!

Comment on lines +53 to +63
가슴속에 하나둘 새겨지는 별을
이제 다 못 헤는 것은
쉬이 아침이 오는 까닭이요,
내일 밤이 남은 까닭이요,
아직 나의 청춘이 다하지 않은 까닭입니다.

별 하나에 추억과
별 하나에 사랑과
별 하나에 쓸쓸함과
별 하나에 동경과
별 하나에 시와
Copy link
Collaborator

Choose a reason for hiding this comment

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

엌,, 삭막한 테스트용 글귀에 윤동주씨 시라니,,, 감성 좋아여어 짱

Comment on lines +18 to +20
data-slot="textarea"
maxLength={maxLength}
className={cn(
Copy link
Collaborator

Choose a reason for hiding this comment

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

maxLength 상수화에 대한 의견은 좋은 것 같습니다!

근데 just 질문이 있는데, 혹시 이 maxLength를 별도로 받는 이유가 있을까요?? 이 length로 글자수 제한이나 입력창 높이가 달라지는 걸까요? figma상에서는 옛날 앱잼 UI때처럼 0/500 이런 텍스트 처리 멘트가 없어져서요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

원래 textarea에서 고정으로 지정할까싶었지만 추후 확장성을 고려해서 사용처에서 내려주는게 좋다고 판단했습니다-!
이 length로 글자수 제한은 달라지지만 ui상에서 달라지는건 없습니다

Comment on lines +28 to +29
style={{ scrollbarGutter: 'stable', ...style }}
{...props}
Copy link
Collaborator

Choose a reason for hiding this comment

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

오왕 scrollbarGutter는 스크롤바 커스텀해주는 속성인건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아뇽-!! 스크롤바가 생길 때를 대비해 콘텐츠 옆에 공간을 미리 확보해서 스크롤바 등장/사라짐으로 인한 레이아웃 흔들림을 막아줍니다-!

@jjangminii jjangminii merged commit 49c3959 into develop Aug 25, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] Text Field 공통 컴포넌트 제작

3 participants