Feat(design-system): input 공통 컴폰넌트, 스토리북 구현#22
Conversation
Walkthrough디자인 시스템에 새로운 Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
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 detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
✅ Storybook chromatic 배포 확인: |
There was a problem hiding this comment.
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.
📒 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';를 그대로 사용하셔도 문제가 없습니다.
| interface InputProps extends InputHTMLAttributes<HTMLInputElement> { | ||
| ref: Ref<HTMLInputElement>; | ||
| isError?: boolean; | ||
| helperText?: string; | ||
| } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
React 19부터 함수 컴포넌트에서도 ref를 일반 prop처럼 받을 수 있습니다
There was a problem hiding this comment.
🧩 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:
- 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]
- React documentation — Forwarding refs: https://reactjs.org/docs/forwarding-refs.html
- 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"을 사용하고 있네요.
이는 다음 중 하나일 가능성이 높습니다:
- React 19가 릴리스되었지만 검색 결과가 최신 정보를 반영하지 못함
- 프로젝트에서 React 19 beta/RC 버전을 사용 중
- 웹 검색 도구의 정보가 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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
💡 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' || trueLength 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.
There was a problem hiding this comment.
이건 따로 우리의 디자인 시스템 컬러예요
There was a problem hiding this comment.
@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`.
There was a problem hiding this comment.
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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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 전달 예제를 별도 컴포넌트로 분리해 훅 규칙을 준수한 점 좋습니다. 다른 스토리도 동일 패턴으로 일관성 유지하면 린트 경고를 줄일 수 있습니다.
| "@storybook/blocks": "^8.6.14", | ||
| "@storybook/react-vite": "9.1.3", | ||
| "@storybook/test": "8.6.14", | ||
| "@tailwindcss/vite": "^4.1.12", |
There was a problem hiding this comment.
💡 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.
| "@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.
There was a problem hiding this comment.
패키지별로 안정화된 릴리스 라인이 다르기 때문에 버전이 불일치할 수 밖에 없을가같아요
There was a problem hiding this comment.
🧩 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'; | |||
There was a problem hiding this comment.
🛠️ 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.
| 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.
constantly-dev
left a comment
There was a problem hiding this comment.
고생하셨어요!! input 신경 쓸 부분이 많은데 설계 너무 잘 해주신 것 같아요. 더군다나 storybook도 여러 case를 다 보여주셔서 리뷰하는 것도 너무 편했습니다 👍
간단한 코멘트 몇 개 달았으니 확인해주세요~~
| <div className={cn(inputBorderVariants({ isError }), className)}> | ||
| <input | ||
| ref={ref} | ||
| className="placeholder-font-gray-3 w-full focus:outline-none" |
There was a problem hiding this comment.
input focus할 때 outline이 뜨는 게 reset되지는 않은 것 같네요!
이 부분을 global로 초기화하는 게 맞을까요? 아니면 이렇게 필요한 곳에서 쓰는 게 맞을까요?
뭔가 input을 어차피 공통 컴포넌트를 대부분 사용할 거라 global 초기화 할 필요가 없는 것 같기도 한 생각도 드네요!
정민님 생각이 궁금합니다!
There was a problem hiding this comment.
전역에서 outline을 없애는 건 별로일 것같아요. 키보드 사용자에게 포커스 표시는 필수라 생각이 들어 글로벌 리셋은 접근성을 해칠 수 있을거같아요. 대신 디자인 시스템의 공통 컴포넌트안에서만 기본 outline을 끄고 커스텀 포커스를 제공하는정도는 괜찮지 않을까싶습니다
| 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'); | ||
| }, | ||
| }; |
There was a problem hiding this comment.
import { within, userEvent, expect } from '@storybook/test';
정확하게 이 의존의 역할이 무엇인가요?? 이렇게 코드를 작성 시에 어떤 역할을 하는지, 어떤 목적으로 적으신건지 궁금해요!
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
확인했습니다! 상호작용 테스트도 진행할 수 있다니 좋네요 👍
| interface InputProps extends InputHTMLAttributes<HTMLInputElement> { | ||
| ref: Ref<HTMLInputElement>; | ||
| isError?: boolean; | ||
| helperText?: string; | ||
| } |
| 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, | ||
| }, | ||
| } | ||
| ); |
이렇게 생각하신 부분에 동의합니다. 적절한 설계라고 생각해요 👍 |
📌 Related Issues
✅ 체크 리스트
📄 Tasks
⭐ 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은 그 결과로서 에러가 있다/없다만 전달받아 스타일을 전환합니다.
이렇게 역할을 분리하면 컴포넌트가 특정 도메인에 결합되지 않아 재사용성을 높일 수 있다고 판단했습니다-!
📷 Screenshot
Summary by CodeRabbit
신기능
문서