Skip to content

Comments

Feat(design-system): input 공통 컴폰넌트, 스토리북 구현#22

Merged
jjangminii merged 9 commits intodevelopfrom
feat/#20/input-component
Aug 24, 2025
Merged

Feat(design-system): input 공통 컴폰넌트, 스토리북 구현#22
jjangminii merged 9 commits intodevelopfrom
feat/#20/input-component

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Aug 22, 2025

📌 Related Issues

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

✅ 체크 리스트

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

📄 Tasks

  • input 공통 컴폰넌트 구현
  • input 스토리북 작성
  • @storybook/test의존성 추가
  • .storybook/main.ts 수정

⭐ PR Point (To Reviewer)

@storybook/test의존성 추가

상호작용 테스트 필요하다고 생각해 스토리의 play 함수로 클릭/타이핑/어설션을 실행하고 Interactions 패널에서 단계별로 확인하기 위해 테스트 유틸을 도입했습니다.
다른 라이브러리도 있었지만 @storybook/test가 play 함수에서 필요한 테스트 유틸을 한 곳에서 제공하고, Interactions 패널과 연동되어 디버깅이 쉬워지기에 선택하였습니다.

.storybook/main.ts 수정

ESM 환경 오류 해결: Storybook v9에서 .storybook/main.ts가 ESM으로 로드되는데, 기존 require 사용으로 ReferenceError: require is not defined가 발생했습니다. require 직접 호출하는 방식에서 createRequire(import.meta.url)로 대체하여 ESM 컨텍스트에서 절대경로 해석(getAbsolutePath) 가능하도록 수정하였습니다.

사용방법

Input의 책임은 오직 “값을 입력받고 현재 시각적 상태를 보여주는 것”에 한정했습니다.
유효성 판단과 에러 메시지 결정은 폼 레벨의 로직이 맡고, Input은 그 결과로서 에러가 있다/없다만 전달받아 스타일을 전환합니다.
이렇게 역할을 분리하면 컴포넌트가 특정 도메인에 결합되지 않아 재사용성을 높일 수 있다고 판단했습니다-!

 const inputRef = useRef<HTMLInputElement>(null);

  const onChange = () => {
    if (inputRef.current) {
      console.log(inputRef.current.value);
    }
  };

<Input
        ref={inputRef}
        onChange={onChange}
        helperText="에러 텍스트"
        isError={유효성 로직}
        placeholder="Placeholder text"
      />

📷 Screenshot

Summary by CodeRabbit

  • 신기능

    • 디자인 시스템에 재사용 가능한 Input 컴포넌트 추가(표준 텍스트 입력, 에러 상태, helper 텍스트, 접근성 속성 포함).
    • Input이 디자인 시스템의 공개 API에 포함되어 전역 재사용 가능.
  • 문서

    • Input용 Storybook 문서 및 데모 스토리 추가(기본, 에러, helper 텍스트, 비활성, 제어형, 상호작용 테스트).

@jjangminii jjangminii self-assigned this Aug 22, 2025
@jjangminii jjangminii linked an issue Aug 22, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Aug 22, 2025

Walkthrough

디자인 시스템에 새로운 Input 컴포넌트를 추가하고 이를 컴포넌트 배럴에 re-export했습니다. Input은 isError, helperText, ref 전달 및 표준 HTMLInput 속성 확장을 지원하며, Storybook 스토리(상태·제어·interaction 테스트)와 관련 devDependency 및 ESM require 보완이 포함됩니다.

Changes

Cohort / File(s) Summary of Changes
Input 컴포넌트
packages/design-system/src/components/input/Input.tsx
새로운 Input 컴포넌트 추가: forwarded ref, isError prop으로 aria-invalid/border 상태 제어, helperText 조건 렌더링, 표준 HTMLInput 속성 확장.
Input 스토리북
packages/design-system/src/components/input/Input.stories.tsx
Input용 Storybook 메타 및 다수의 스토리(기본, 에러, helperText, disabled, 제어형, interaction play 테스트) 추가.
컴포넌트 배럴(export) 갱신
packages/design-system/src/components/index.ts
export { default as Input } from './input/Input'; 추가로 public API 확장.
Storybook ESM 보완
packages/design-system/.storybook/main.ts
ESM 환경에서 require 사용을 위해 createRequire 도입 및 경로 해결 로직에 적용.
패키지 설정(개발 의존성)
packages/design-system/package.json
devDependencies에 @storybook/test: 8.6.14 추가.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Consumer as 앱/사용자
    participant InputComp as DesignSystem.Input
    participant DOM as HTMLInputElement

    Note over InputComp,DOM: 렌더 흐름 (에러/헬퍼텍스트 포함)

    Consumer->>InputComp: <Input {...props} isError? helperText? ref=... />
    InputComp->>DOM: Render <input> with props, forward ref
    InputComp->>InputComp: set aria-invalid=true when isError
    alt isError && helperText
        InputComp-->>Consumer: Render helper/error text below input
    else
        InputComp-->>Consumer: No helper text rendered
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

🛠️ Feature, 정민

Suggested reviewers

  • jllee000
  • constantly-dev
  • karnelll

Poem

깡충 깡충, 토끼가 말해요 🐇
새 Input이 왔네, 테두리 반짝여요
에러면 빨갛게, helper는 속삭여요
ref는 살며시 손을 잡고, 값은 쑥쑥 자라요
배럴 타고 나가면 폼이 반짝 빛나요.

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.


📜 Recent 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 b638ab0 and 4dfd480.

📒 Files selected for processing (1)
  • packages/design-system/src/components/input/Input.stories.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/design-system/src/components/input/Input.stories.tsx
✨ 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/#20/input-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 22, 2025
@github-actions github-actions bot requested a review from jllee000 August 22, 2025 18:35
@github-actions
Copy link

github-actions bot commented Aug 22, 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: 2

🧹 Nitpick comments (3)
packages/design-system/src/components/index.ts (1)

3-3: 타입도 함께 재-익스포트 제안

소비자가 InputProps를 외부에서 바로 참조할 수 있도록 타입도 배럴에서 내보내면 DX가 좋아집니다. 아래처럼 추가를 고려해 주세요(전제: Input.tsx에서 InputProps를 export 해야 함).

 export { default as Input } from './input/Input';
+export type { InputProps } from './input/Input';
packages/design-system/src/components/input/Input.tsx (2)

35-41: a11y 강화 — 에러 보조텍스트를 aria-describedby로 연결

에러 텍스트가 노출될 때, 해당 텍스트와 입력을 프로그램적으로 연결하면 스크린 리더 경험이 좋아집니다. useId로 고유 ID를 생성하고 aria-describedby를 설정하세요. 에러 텍스트에는 role="alert"/aria-live="polite"를 부여합니다.

필요 변경:

-import { InputHTMLAttributes, Ref } from 'react';
+import { InputHTMLAttributes, Ref, useId } from 'react';
-}: InputProps) => {
+}: InputProps) => {
+  const describedById = useId();
         <input
           ref={ref}
           className="placeholder-font-gray-3 w-full focus:outline-none"
           aria-invalid={isError}
+          aria-describedby={isError && helperText ? describedById : undefined}
           {...props}
         />
-          <p className="text-error body4-r">{helperText}</p>
+          <p id={describedById} role="alert" aria-live="polite" className="text-error body4-r">
+            {helperText}
+          </p>

Also applies to: 44-48, 2-2, 33-33


5-9: 소비자 확장성: wrapper와 input class를 분리 지원

현재 className은 wrapper에 적용됩니다. 실제 입력 필드(input)의 스타일을 세밀하게 제어하려면 inputClassName를 추가해 분리하는 편이 좋습니다. 기존 사용자는 영향 없습니다.

예시 변경:

 interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
   isError?: boolean;
   helperText?: string;
+  inputClassName?: string;
 }
-const Input = ({
-  ref,
-  isError,
-  className,
-  helperText,
-  ...props
-}: InputProps) => {
+const Input = ({
+  ref,
+  isError,
+  className,
+  helperText,
+  inputClassName,
+  ...props
+}: InputProps) => {
-          className="placeholder-font-gray-3 w-full focus:outline-none"
+          className={cn('placeholder-font-gray-3 w-full focus:outline-none', inputClassName)}

Also applies to: 26-33, 38-38

📜 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 2363eb4 and f3f94c1.

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

3-3: LGTM — 배럴에서 Input 공개 잘 추가됨

디자인 시스템 소비자 관점에서 import { Input } from '.../components' 경로가 자연스러워집니다. 별다른 사이드 이펙트 없어 보입니다.

packages/design-system/src/components/input/Input.tsx (1)

3-3: cn 재-익스포트가 확인되어 import 경로 유지 가능합니다
packages/design-system/src/lib/index.ts에서

export * from './utils';

구문을 통해 cn이 재-익스포트되고 있어,
packages/design-system/src/components/input/Input.tsx

import { cn } from '../../lib';

를 그대로 사용하셔도 문제가 없습니다.

Comment on lines +5 to +9
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
ref: Ref<HTMLInputElement>;
isError?: boolean;
helperText?: string;
}
Copy link

@coderabbitai coderabbitai bot Aug 22, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

ref 전달 방식이 React 규약을 위반 — forwardRef로 전환 필요

현재 ref를 일반 props로 받는 형태입니다. 함수형 컴포넌트에서 ref는 props로 전달되지 않으며, forwardRef로 감싸야 합니다. 지금 구조로는 소비자가 <Input ref={...} />를 사용해도 내부 input으로 ref가 전달되지 않아 포커스, selection, 값 접근 등이 깨집니다.

아래 최소 수정으로 forwardRef 패턴을 적용해 주세요.

  • (1) Props에서 ref 제거
  • (2) 컴포넌트를 forwardRef<HTMLInputElement, InputProps>로 전환
  • (3) 닫는 구문 };);로 교체

변경 예:

-interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
-  ref: Ref<HTMLInputElement>;
-  isError?: boolean;
-  helperText?: string;
-}
+interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
+  isError?: boolean;
+  helperText?: string;
+}
-const Input = ({
-  ref,
-  isError,
-  className,
-  helperText,
-  ...props
-}: InputProps) => {
+const Input = forwardRef<HTMLInputElement, InputProps>(
+  ({ isError, className, helperText, ...props }, ref) => {
-};
+);

또한 import를 다음과 같이 조정하세요:

-import { InputHTMLAttributes, Ref } from 'react';
+import { InputHTMLAttributes, forwardRef } from 'react';

옵션: DevTools 가독성을 위해 Input.displayName = 'Input'을 추가하면 좋습니다.

Also applies to: 26-33, 51-51, 2-2

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

React 19부터 함수 컴포넌트에서도 ref를 일반 prop처럼 받을 수 있습니다

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

@jjangminii 맞습니다! React 19에서는 정말로 함수 컴포넌트에서 ref를 일반 prop으로 받을 수 있게 되었습니다. 제가 이전 댓글에서 forwardRef 사용을 제안한 것이 React 18 기준의 outdated한 조언이었네요.

React 19의 주요 변경사항 중 하나가 바로 이 부분입니다:

  • 함수 컴포넌트에서 ref를 props로 직접 받을 수 있음
  • forwardRef 없이도 ref 전달이 자연스럽게 작동
  • 더 간단하고 직관적인 컴포넌트 작성 가능

현재 프로젝트가 React 19를 사용하고 있다면, 지금의 구현 방식이 완전히 올바릅니다. 불필요한 혼란을 드려 죄송합니다!


🏁 Script executed:

#!/bin/bash
# Check React version in the project
fd package.json --exec cat {} | jq '.dependencies.react // .devDependencies.react // empty'

Length of output: 135


🌐 Web query:

React 19 function components ref prop without forwardRef

💡 Result:

Short answer: you can’t — a ref passed by a parent is not delivered to a plain function component’s props. To receive a parent ref you must either wrap the function component with React.forwardRef or accept a custom callback/prop (commonly named innerRef) and have the parent pass that instead.

Why: React treats the ref attribute specially — it is not part of the normal props object for function components. forwardRef lets React hand the ref to the component as a second argument; otherwise refs only work on DOM elements or class components. Examples:

  • Using forwardRef (recommended)
    const MyComponent = React.forwardRef((props, ref) => {
    return <input ref={ref} {...props} />;
    });

  • Using a custom prop (no forwardRef)
    // child
    function MyComponent({ innerRef, ...props }) {
    return <input ref={innerRef} {...props} />;
    }
    // parent
    <MyComponent innerRef={node => { /* use node */ }} />

References:

  • React docs — Forwarding refs [1]
  • React docs — Refs and the DOM [2]

[1] https://reactjs.org/docs/forwarding-refs.html
[2] https://reactjs.org/docs/refs-and-the-dom.html


@jjangminii 정말 감사합니다! 프로젝트가 React ^19.1.1을 사용하고 있는 것을 확인했고, React 19의 새로운 기능에 대해 제가 최신 정보를 놓쳤네요.

React 19에서 함수 컴포넌트가 ref를 일반 prop으로 받을 수 있다는 변경사항에 대해 알려주셔서 고맙습니다. 현재의 구현 방식:

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  ref: Ref<HTMLInputElement>;
  isError?: boolean;
  helperText?: string;
}

const Input = ({ ref, isError, className, helperText, ...props }: InputProps) => {
  // ...
};

이 올바른 React 19 방식이군요. 제가 이전에 forwardRef 사용을 제안한 것은 부적절했습니다. React 19의 이런 개선사항으로 컴포넌트 작성이 더욱 직관적이 되었네요!

업데이트된 정보를 알려주셔서 정말 감사합니다. 🙏


🌐 Web query:

React 19 release notes ref props function components changelog

💡 Result:

I don’t have live web browsing access, so I can’t fetch or cite the React 19 release notes right now. I can either:

  • Summarize what I know about React (up to my cutoff in Mar 2025) — e.g., ref handling, forwardRef, refs for function components, and typical changelog items — or
  • If you paste links or the release notes text, I’ll extract and summarize the ref/props/function-component changes with citations to the provided text.

Which would you prefer?


🌐 Web query:

"React 19" official release notes ref function components forwardRef deprecated

💡 Result:

I searched for official React 19 release notes and any deprecation of function components accepting refs or of React.forwardRef.

Findings:

  • There is no official React 19 release notes or announcement from the React team as of now; React 18 is the latest stable major release from the React core team (React 18 release notes and blog posts are on reactjs.org) [1][2].
  • There is no official React documentation or React team statement deprecating function components receiving refs or deprecating React.forwardRef. The current recommended pattern remains to use forwardRef to pass refs to function components when needed; refs on function components are not supported except via forwardRef [2][3].
  • Historical context: class components support refs directly; function components do not accept a ref prop unless wrapped with React.forwardRef. This is unchanged in current official docs [2][3].

Sources:

  1. React releases and blog (official site) — React 18 release notes and docs: https://reactjs.org/blog/2022/03/29/react-v18.html [React team official blog]
  2. React documentation — Forwarding refs: https://reactjs.org/docs/forwarding-refs.html
  3. React documentation — Refs and the DOM: https://reactjs.org/docs/refs-and-the-dom.html

Conclusion: There are no official React 19 release notes and no official deprecation of forwardRef or of using forwardRef to allow function components to receive refs. If you have a specific link or claim you want verified, share it and I can inspect it.


@jjangminii 흥미롭게도 웹 검색 결과에서는 React 19가 아직 공식 릴리스되지 않았다고 나오는데, 프로젝트의 package.json에서는 확실히 "^19.1.1"을 사용하고 있네요.

이는 다음 중 하나일 가능성이 높습니다:

  1. React 19가 릴리스되었지만 검색 결과가 최신 정보를 반영하지 못함
  2. 프로젝트에서 React 19 beta/RC 버전을 사용 중
  3. 웹 검색 도구의 정보가 outdated

실제로 프로젝트에서 React 19.1.1을 사용하고 계시고, 현재 구현이 정상 작동한다면 React 19에서 정말로 함수 컴포넌트가 ref를 일반 prop으로 받을 수 있는 것 같습니다.

혹시 확실한 정보가 필요하시다면 React 공식 문서를 직접 확인해보시는 것도 좋을 것 같습니다. 어쨌든 현재 구현 방식이 프로젝트에서 올바르게 작동하고 있다면 그대로 진행하시면 될 것 같네요!

정정해주셔서 감사합니다. 🙏


✏️ Learnings added
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.943Z
Learning: React 19부터 함수 컴포넌트에서 ref를 일반 prop으로 받을 수 있어 forwardRef가 더 이상 필요하지 않다. InputProps interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있다.

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.943Z
Learning: React 19에서는 함수 컴포넌트가 ref를 일반 prop으로 받을 수 있다. interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있어 forwardRef가 불필요하다. React 19.1.1+ 프로젝트에서는 이 방식을 사용해야 한다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

<div className={cn(inputBorderVariants({ isError }), className)}>
<input
ref={ref}
className="placeholder-font-gray-3 w-full focus:outline-none"
Copy link

@coderabbitai coderabbitai bot Aug 22, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Tailwind placeholder 클래스 검증

placeholder-font-gray-3 네이밍이 Tailwind 기본 유틸 형태(placeholder:text-gray-300 등)와 상이합니다. 사내 프리셋/플러그인에서 제공되는 유틸인지 확인이 필요합니다. 미정의라면 Purge 과정에서 제거되거나 의도치 않은 스타일 누락이 발생할 수 있습니다.

다음으로 프로젝트 내 정의 여부를 점검해 주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 해당 유틸이 정의/사용되는지 검색
rg -n "placeholder-font-gray-3" -g '!**/node_modules/**' || true

# tailwind config에서 프리셋/플러그인 정의 여부 단서 검색
fd -a 'tailwind.config.*' .
rg -n -C2 -g 'tailwind.config.*' 'placeholder|gray-3|plugin|variants' || true

Length of output: 605


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Listing files in packages/tailwind-config:"
find packages/tailwind-config -maxdepth 2 -type f

echo "Searching for placeholder-font-gray-3 in packages/tailwind-config:"
rg -n "placeholder-font-gray-3" -g 'packages/tailwind-config/**' || echo "No matches for placeholder-font-gray-3"

echo "Searching for plugin utility functions (addUtilities, matchUtilities, addComponents):"
rg -nE "addUtilities|matchUtilities|addComponents" -g 'packages/tailwind-config/**' || echo "No plugin utility functions found"

echo "Searching for gray-3 definitions in packages/tailwind-config:"
rg -n "gray-3" -g 'packages/tailwind-config/**' || echo "No matches for gray-3"

Length of output: 1543


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for .placeholder-font-gray-3 in shared-styles.css"
rg -n "\.placeholder-font-gray-3" -g 'packages/tailwind-config/shared-styles.css' || echo "No .placeholder-font-gray-3 class found"

echo "Printing lines 180-220 from shared-styles.css"
sed -n '180,220p' packages/tailwind-config/shared-styles.css || echo "Failed to print lines"

Length of output: 1633


placeholder-font-gray-3 클래스 정의 누락 확인 및 수정 필요

현재 코드는 아래와 같이 Tailwind 내장 유틸과 일치하지 않는 커스텀 클래스(placeholder-font-gray-3)를 사용 중이며, 실제로 정의된 곳이 없어 Purge 과정에서 제거되거나 스타일이 적용되지 않을 위험이 있습니다.

• 파일: packages/design-system/src/components/input/Input.tsx
줄 38:

className="placeholder-font-gray-3 w-full focus:outline-none"

• Tailwind 설정 확인:
apps/extension/tailwind.config.ts 에서 해당 클래스 관련 plugins 또는 extend 정의 누락
• 커스텀 스타일 시트 확인:
packages/tailwind-config/shared-styles.css 에서도 .placeholder-font-gray-3 클래스 정의가 없음

해결 방안:

  • Tailwind 기본 유틸 사용
    - className="placeholder-font-gray-3 w-full focus:outline-none"
    + className="placeholder:text-gray-300 w-full focus:outline-none"
  • 커스텀 유틸로 유지해야 한다면, tailwind.config.ts 에 아래 예시처럼 유틸 정의를 추가
    // tailwind.config.ts 예시
    import type { Config } from 'tailwindcss'
    
    const plugin = require('tailwindcss/plugin')
    
    const config: Config = {
      // …
      plugins: [
        plugin(function({ addUtilities, theme }) {
          const grays = {
            '.placeholder-font-gray-3::placeholder': {
              color: theme('colors.gray.300'),
            },
          }
          addUtilities(grays, ['responsive'])
        }),
      ],
    }
    export default config

위 사항을 반영해 클래스 명을 교체하거나, 커스텀 유틸을 정의해 주세요.

🤖 Prompt for AI Agents
packages/design-system/src/components/input/Input.tsx around line 38: the
component uses a non-standard class "placeholder-font-gray-3" which isn't
defined in tailwind config or shared CSS and will be stripped by Purge; replace
it with a built-in Tailwind placeholder utility such as "placeholder-gray-300"
(so className="placeholder-gray-300 w-full focus:outline-none") OR if you must
keep a custom utility, add a plugin/utility definition to your Tailwind config
(or shared-styles.css) that registers .placeholder-font-gray-3 mapping to the
desired placeholder color and ensure the config is included by the consuming
apps.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이건 따로 우리의 디자인 시스템 컬러예요

Copy link

Choose a reason for hiding this comment

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

@jjangminii 알겠습니다! 설명해주셔서 감사합니다. 커스텀 디자인 시스템 컬러라는 점을 확인했습니다. 앞으로는 이런 커스텀 토큰들을 더 잘 인식하겠습니다.


✏️ Learnings added
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/src/components/input/Input.tsx:38-38
Timestamp: 2025-08-23T09:31:57.908Z
Learning: In the Pinback-Team/pinback-client project, `placeholder-font-gray-3` is a custom design token used as part of their design system, not a typo or undefined class. The project uses custom color tokens with their own naming conventions that differ from standard Tailwind CSS utilities.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#89
File: apps/client/src/pages/onBoarding/components/OnboardingNavButton.tsx:26-34
Timestamp: 2025-07-16T13:11:52.976Z
Learning: In the Pinback-Team/pinback-client project, `bg-gray200` is a custom design token used consistently throughout the design system, not a typo. The project uses custom color tokens like `gray0`, `gray100`, `gray200`, `gray300`, etc. without hyphens, which is different from standard Tailwind CSS naming conventions. This should not be changed to `bg-gray-200` as it would break the design system.

Learnt from: jjangminii
PR: Pinback-Team/pinback-client#32
File: apps/extension/src/components/textfieldPopup/TextfieldPopup.tsx:12-12
Timestamp: 2025-07-08T12:34:59.692Z
Learning: In the Pinback-Team/pinback-client project, `text-gray900` is a custom design token, not a typo. It's intentionally defined without a hyphen as part of their design system, so it shouldn't be suggested to change it to `text-gray-900`.

@jjangminii jjangminii changed the title Feat(design-system): input 공통 컴폰너트 구현 Feat(design-system): input 공통 컴폰넌트, 스토리북 구현 Aug 23, 2025
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: 4

🧹 Nitpick comments (4)
packages/design-system/.storybook/main.ts (2)

2-3: Node 빌트인 모듈 네임스페이스 사용 권장(미세 개선)

가능하면 'path' 대신 'node:path', 'module' 대신 'node:module' 네임스페이스를 사용하면 번들러/폴리필 이슈를 줄일 수 있습니다.

다음과 같이 변경을 제안합니다:

-import { dirname, join } from 'path';
-import { createRequire } from 'node:module';
+import { dirname, join } from 'node:path';
+import { createRequire } from 'node:module';

16-22: Play 함수 활용 시 Interactions 패널 추가 고려(선택)

스토리에서 play를 사용하므로, 디버깅/패널 가시성을 위해 @storybook/addon-interactions 추가를 고려해볼 수 있습니다. 필수는 아니지만 UX가 개선됩니다.

적용 시:

   addons: [
     getAbsolutePath('@chromatic-com/storybook'),
     getAbsolutePath('@storybook/addon-docs'),
     getAbsolutePath('@storybook/addon-onboarding'),
     getAbsolutePath('@storybook/addon-a11y'),
     getAbsolutePath('@storybook/addon-vitest'),
+    getAbsolutePath('@storybook/addon-interactions'),
   ],

패키지에 @storybook/addon-interactions@9.1.3도 devDependency로 추가해 주세요.

packages/design-system/src/components/input/Input.stories.tsx (2)

2-2: 불필요한 default React 임포트 제거(React 17+ JSX 트랜스폼 가정)

파일에서 React 식별자를 직접 사용하지 않으므로 default 임포트를 제거해도 됩니다. 훅만 임포트하세요.

-import React, { useRef, useState } from 'react';
+import { useRef, useState } from 'react';

101-106: 테스트 어서션 개선: DOM 매처 사용

입력 값 검증은 toHaveValue 매처가 가독성이 더 좋고 타입 단언이 필요 없습니다.

-    const textbox = await canvas.findByRole('textbox');
+    const textbox = await canvas.findByRole('textbox');
     await userEvent.click(textbox);
     await userEvent.type(textbox, 'hello');
-    await expect((textbox as HTMLInputElement).value).toBe('hello');
+    await expect(textbox).toHaveValue('hello');
📜 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 f3f94c1 and b638ab0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/design-system/.storybook/main.ts (1 hunks)
  • packages/design-system/package.json (1 hunks)
  • packages/design-system/src/components/input/Input.stories.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
packages/design-system/src/components/input/Input.stories.tsx

[error] 57-57: Do not shadow the global "Error" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🪛 GitHub Check: lint
packages/design-system/src/components/input/Input.stories.tsx

[warning] 73-73:
React Hook "useRef" is called in function "render" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"


[warning] 72-72:
React Hook "useState" is called in function "render" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

🔇 Additional comments (2)
packages/design-system/.storybook/main.ts (1)

3-6: ESM 환경에서의 require 브리지 추가, Yarn PnP 대응에 적절함

createRequire(import.meta.url)require.resolve를 사용할 수 있게 한 점 좋습니다. 모노레포/PNP 환경에서 애드온 경로 해석 안정성이 좋아집니다.

packages/design-system/src/components/input/Input.stories.tsx (1)

37-44: WithRef 패턴 좋습니다

ref 전달 예제를 별도 컴포넌트로 분리해 훅 규칙을 준수한 점 좋습니다. 다른 스토리도 동일 패턴으로 일관성 유지하면 린트 경고를 줄일 수 있습니다.

Comment on lines 32 to 35
"@storybook/blocks": "^8.6.14",
"@storybook/react-vite": "9.1.3",
"@storybook/test": "8.6.14",
"@tailwindcss/vite": "^4.1.12",
Copy link

@coderabbitai coderabbitai bot Aug 23, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Storybook 버전 혼합(8.x ↔ 9.x) — 통합 필요

@storybook/blocks@storybook/test가 8.x 라인인데, 나머지 Storybook 패키지는 9.1.3입니다. 메이저 혼합은 peer 의존성/런타임 충돌을 유발할 수 있습니다. 9.1.3으로 정렬하는 것을 권장합니다.

다음 패치를 제안합니다:

-    "@storybook/blocks": "^8.6.14",
+    "@storybook/blocks": "9.1.3",
-    "@storybook/test": "8.6.14",
+    "@storybook/test": "9.1.3",

추가로, 패키지 정합성 확인 스크립트입니다:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="packages/design-system/package.json"

echo "Storybook 패키지 버전:"
jq -r '.devDependencies | to_entries[] | select(.key|test("^@?storybook")) | "\(.key) \(.value)"' "$FILE" | sort

echo
echo "메이저 버전 불일치(기대: 9):"
jq -r '.devDependencies | to_entries[] | select(.key|test("^@?storybook")) | "\(.key) \(.value)"' "$FILE" \
| awk '{ v=$2; gsub(/[\^~<>=>=]/,"",v); split(v,a,"."); if(a[1]!="9") printf(" - %s %s\n",$1,$2) }'

Length of output: 810


Storybook 주요 버전 불일치 (8.x ↔ 9.x) – 패키지 정합성 맞추기 필요

packages/design-system/package.json의 devDependencies에서 다음 두 패키지가 8.x 라인에 머물러 있고, 나머지 Storybook 패키지는 9.1.3으로 지정되어 있습니다. 메이저 버전 혼합은 peer 의존성 충돌이나 런타임 에러를 초래할 수 있으므로, 모든 Storybook 패키지를 동일한 메이저 버전(9.1.3)으로 통일해야 합니다.

• 파일: packages/design-system/package.json
• 대상: devDependencies 내
@storybook/blocks (^8.6.14)
@storybook/test (8.6.14)

다음과 같이 버전을 맞춰주세요:

-    "@storybook/blocks": "^8.6.14",
+    "@storybook/blocks": "9.1.3",
-    "@storybook/test": "8.6.14",
+    "@storybook/test": "9.1.3",
📝 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
"@storybook/blocks": "^8.6.14",
"@storybook/react-vite": "9.1.3",
"@storybook/test": "8.6.14",
"@tailwindcss/vite": "^4.1.12",
"@storybook/blocks": "9.1.3",
"@storybook/react-vite": "9.1.3",
"@storybook/test": "9.1.3",
"@tailwindcss/vite": "^4.1.12",
🤖 Prompt for AI Agents
In packages/design-system/package.json around lines 32 to 35, two Storybook deps
are on the 8.x line while the rest use 9.1.3; update "@storybook/blocks" and
"@storybook/test" to the same major version (use "9.1.3") in devDependencies to
ensure consistency, then run your package manager install (pnpm/yarn/npm) and
verify Storybook starts/tests run without peer dependency warnings.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

패키지별로 안정화된 릴리스 라인이 다르기 때문에 버전이 불일치할 수 밖에 없을가같아요

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

@jjangminii, 해당 설명을 이해했습니다. Storybook 패키지들의 실제 안정화 릴리스 라인을 확인해보겠습니다.


🌐 Web query:

Storybook @storybook/test and @storybook/blocks latest stable versions and release lines 2025

💡 Result:

I searched for the latest stable versions and release lines for Storybook packages @storybook/test and @storybook/blocks as of 2025.

Findings:

  • @storybook/test: latest stable release line is 8.x (current stable: 8.1.0) — source: Storybook release notes / npm registry [1][2].
  • @storybook/blocks: latest stable release line is 8.x (current stable: 8.0.0) — source: Storybook release notes / npm registry [1][3].

References:
[1] Storybook Releases — official release notes/changelog (releases page) — https://github.com/storybookjs/storybook/releases
[2] @storybook/test on npm — https://www.npmjs.com/package/@storybook/test
[3] @storybook/blocks on npm — https://www.npmjs.com/package/@storybook/blocks

If you want I can fetch exact patch versions (npm dist-tags) right now and paste exact version strings.


실제 npm 레지스트리에서 현재 사용 가능한 버전들을 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
# Check latest versions of the mentioned Storybook packages
echo "=== @storybook/test versions ==="
npm view @storybook/test versions --json | jq -r '.[-10:][]' 2>/dev/null || npm view @storybook/test version

echo -e "\n=== @storybook/blocks versions ==="
npm view @storybook/blocks versions --json | jq -r '.[-10:][]' 2>/dev/null || npm view @storybook/blocks version

echo -e "\n=== @storybook/react-vite versions ==="
npm view @storybook/react-vite versions --json | jq -r '.[-10:][]' 2>/dev/null || npm view @storybook/react-vite version

echo -e "\n=== Latest dist-tags ==="
echo "storybook/test:"
npm view @storybook/test dist-tags

echo "storybook/blocks:"
npm view @storybook/blocks dist-tags

echo "storybook/react-vite:"
npm view @storybook/react-vite dist-tags

Length of output: 1804


@jjangminii, 확인해보니 정말로 맞습니다!

npm 레지스트리 확인 결과:

  • @storybook/test: latest 태그가 8.6.14 (현재 사용 중인 버전과 정확히 일치)
  • @storybook/blocks: latest 태그가 8.6.14 (package.json의 ^8.6.14와 일치)
  • @storybook/react-vite: latest 태그가 9.1.3

@storybook/test@storybook/blocks는 아직 8.x 라인이 안정 버전이고, 9.x는 alpha 상태인 것을 확인했습니다. 따라서 현재 설정이 각 패키지의 최신 안정 버전을 사용하는 올바른 선택이었네요.

제가 처음에 모든 패키지를 9.1.3으로 맞추라고 제안한 것이 잘못된 권장사항이었습니다. 패키지별로 서로 다른 릴리스 사이클을 갖는다는 설명을 해주셔서 감사합니다!


✏️ Learnings added
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/package.json:32-35
Timestamp: 2025-08-23T18:42:42.761Z
Learning: Storybook packages have different release cycles and stable versions. As of 2025, storybook/test and storybook/blocks have their latest stable versions in the 8.x line (8.6.14), while other packages like storybook/react-vite are stable in 9.x line (9.1.3). Mixed major versions across Storybook packages can be intentional and correct when using the latest stable version of each package.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@@ -0,0 +1,107 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 임포트 경로 정정: '@storybook/react' 권장

Storybook 타입은 일반적으로 @storybook/react에서 가져옵니다. 빌더 독립성을 위해 다음과 같이 수정하는 것을 권장합니다.

-import type { Meta, StoryObj } from '@storybook/react-vite';
+import type { Meta, StoryObj } from '@storybook/react';
📝 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
import type { Meta, StoryObj } from '@storybook/react-vite';
import type { Meta, StoryObj } from '@storybook/react';
🤖 Prompt for AI Agents
In packages/design-system/src/components/input/Input.stories.tsx around line 1,
the Storybook type import uses '@storybook/react-vite' but should import from
'@storybook/react' for standard typing and builder independence; update the
import path to '@storybook/react' for Meta and StoryObj types and ensure any
tooling/configs still resolve correctly.

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.

고생하셨어요!! input 신경 쓸 부분이 많은데 설계 너무 잘 해주신 것 같아요. 더군다나 storybook도 여러 case를 다 보여주셔서 리뷰하는 것도 너무 편했습니다 👍

간단한 코멘트 몇 개 달았으니 확인해주세요~~

<div className={cn(inputBorderVariants({ isError }), className)}>
<input
ref={ref}
className="placeholder-font-gray-3 w-full focus:outline-none"
Copy link
Member

Choose a reason for hiding this comment

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

input focus할 때 outline이 뜨는 게 reset되지는 않은 것 같네요!
이 부분을 global로 초기화하는 게 맞을까요? 아니면 이렇게 필요한 곳에서 쓰는 게 맞을까요?
뭔가 input을 어차피 공통 컴포넌트를 대부분 사용할 거라 global 초기화 할 필요가 없는 것 같기도 한 생각도 드네요!

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

Copy link
Collaborator Author

@jjangminii jjangminii Aug 24, 2025

Choose a reason for hiding this comment

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

전역에서 outline을 없애는 건 별로일 것같아요. 키보드 사용자에게 포커스 표시는 필수라 생각이 들어 글로벌 리셋은 접근성을 해칠 수 있을거같아요. 대신 디자인 시스템의 공통 컴포넌트안에서만 기본 outline을 끄고 커스텀 포커스를 제공하는정도는 괜찮지 않을까싶습니다

Copy link
Member

Choose a reason for hiding this comment

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

좋습니다 이대로 진행하시죠! 👍

Comment on lines +100 to +110
export const WithInteraction: Story = {
args: { placeholder: '클릭 후 타이핑 테스트' },
render: (args) => <WithRef {...args} />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const textbox = await canvas.findByRole('textbox');
await userEvent.click(textbox);
await userEvent.type(textbox, 'hello');
await expect((textbox as HTMLInputElement).value).toBe('hello');
},
};
Copy link
Member

Choose a reason for hiding this comment

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

import { within, userEvent, expect } from '@storybook/test';
정확하게 이 의존의 역할이 무엇인가요?? 이렇게 코드를 작성 시에 어떤 역할을 하는지, 어떤 목적으로 적으신건지 궁금해요!

Copy link
Collaborator Author

@jjangminii jjangminii Aug 24, 2025

Choose a reason for hiding this comment

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

within은 Testing Library의 쿼리를 특정 영역으로 스코프합니다. within(canvasElement)로 쓰면 현재 스토리의 캔버스 안에서만 findByRole 같은 검색이 이뤄져 교차 스토리 간 간섭을 방지해줘요
userEvent는 사용자의 실제 입력에 가까운 상호작용 시뮬레이터입니다. 클릭,타이핑,탭 이동 등 제스처를 비동기적으로 재현하고, Interactions 패널에 클릭 → 타이핑 같은 스텝이 순서대로 표시되게 도와줍니다
expect는 검증 함수입니다. play 안에서 상태/DOM을 검증하면, 실패 시 스토리 상호작용이 빨갛게 표시되고 테스트 러너에서도 fail 처리됩니다.
@storybook/test는 스토리 내부 상호작용 테스트를 패널로그로 만드는 도구고 각자 다른 라이브러리에서 가져와 사용할 수 있지만 @storybook/test에서 가져오면 버전과 타입을 일관되게 사용할 수 있어 선택했습니다-!! 추가적으로 디자이너분들과 소통할때 상호작용테스트를 해볼 수 있으면 좋을거같아 추가했습니다
https://storybook.js.org/docs/writing-tests/interaction-testing?

Copy link
Member

Choose a reason for hiding this comment

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

확인했습니다! 상호작용 테스트도 진행할 수 있다니 좋네요 👍

Comment on lines +5 to +9
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
ref: Ref<HTMLInputElement>;
isError?: boolean;
helperText?: string;
}
Copy link
Member

Choose a reason for hiding this comment

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

👍

Comment on lines +11 to +24
const inputBorderVariants = cva(
'w-full h-[4.4rem] rounded-[4px] border box-border body4-r px-[0.8rem] py-[1.2rem] transition-colors ',
{
variants: {
isError: {
true: 'border-error focus-within:border-error',
false: 'border-gray200 focus-within:border-main400',
},
},
defaultVariants: {
isError: false,
},
}
);
Copy link
Member

Choose a reason for hiding this comment

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

분기 깔끔하네요~~ cva 활용 굳굳 👍

@constantly-dev
Copy link
Member

Input의 책임은 오직 “값을 입력받고 현재 시각적 상태를 보여주는 것”에 한정했습니다. 유효성 판단과 에러 메시지 결정은 폼 레벨의 로직이 맡고, Input은 그 결과로서 에러가 있다/없다만 전달받아 스타일을 전환합니다. 이렇게 역할을 분리하면 컴포넌트가 특정 도메인에 결합되지 않아 재사용성을 높일 수 있다고 판단했습니다-!

이렇게 생각하신 부분에 동의합니다. 적절한 설계라고 생각해요 👍

@jjangminii jjangminii merged commit 3cbf52e into develop Aug 24, 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] input 공통 컴포넌트 구현

2 participants