[REFACTOR] 공용 버튼 컴포넌트 재사용성 향상 - #218
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughButton 컴포넌트의 API를 ButtonProps에서 ButtonType로 변경하여 크기(Btn_L/M/S)와 변형(primary/gray/transparent) 옵션을 추가했습니다. text prop을 children으로 바꾸고 forwardRef를 지원하도록 리팩토링했으며, tailwind-merge 의존성을 추가했습니다. 해당 변경에 맞춰 Button을 사용하는 10개 파일을 업데이트했습니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/common/Button.tsx`:
- Line 28: The Button component's default size is incorrectly set to "Btn_M";
update the default prop for size in the Button component (where you currently
have size = "Btn_M") to "Btn_L" so that consumers that omit the size prop render
the required Btn_L by default; ensure you change the default in the function
signature or defaultProps/FC default assignment for Button to "Btn_L".
- Around line 14-24: Rename the exported constant maps "sizes" and "variants" to
UPPER_SNAKE_CASE (SIZES and VARIANTS) in Button.tsx and update any local
references/usages to the new names; keep the internal keys (Btn_L, Btn_M, Btn_S,
primary, gray, transparent) the same unless style naming also needs change, and
ensure any other modules importing these symbols (e.g., SIZES or VARIANTS usage
inside the Button component or elsewhere) are updated to the new identifiers to
avoid unresolved reference errors.
In `@src/components/meal-plan/BottomSheet.tsx`:
- Around line 218-226: The Button in the BottomSheet component is missing its
click handler so handleChange is never used and isModalOpen cannot be toggled;
add an onClick prop to the Button (the same Button rendering the {isOpen ? "바꾸기"
: "수정완료"} label) and wire it to call handleChange (ensuring handleChange
implements the logic to toggle isModalOpen or open the modal); this will remove
the unused-handleChange TS6133 error and restore the CTA behavior.
In `@src/components/meal-plan/ReviewModal.tsx`:
- Around line 116-122: The "등록" Button in ReviewModal no longer triggers
submission because its onClick was removed; restore the submit action by adding
an onClick that calls handleSubmitReview (or change to type="submit" and ensure
handleSubmitReview is wired to the form's onSubmit). Update the Button element
in ReviewModal to include onClick={handleSubmitReview} (or switch to submit
semantics and wire handleSubmitReview on the form) so clicking the button sends
the review save request.
In `@src/components/profile/ProfileDataForm.tsx`:
- Around line 258-268: The CTA button in ProfileDataForm currently uses
type="button" and has no onClick, so the form's onSubmit={handleSubmit} is never
triggered; update the Button in the ProfileDataForm component (the one
referencing profileMutation.isPending) to submit the form—either change its type
to "submit" or add an onClick that calls handleSubmit (and ensure the enclosing
form has onSubmit={handleSubmit}); keep the button state logic using
profileMutation.isPending unchanged.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
package.jsonsrc/components/common/Button.tsxsrc/components/meal-plan/BottomSheet.tsxsrc/components/meal-plan/ReviewModal.tsxsrc/components/meal-plan/TodayMeal.tsxsrc/components/mypage/GetDateRangeModal.tsxsrc/components/onboarding/ThirdStep.tsxsrc/components/profile/ProfileDataForm.tsxsrc/pages/auth/terms-agreement-page.tsxsrc/pages/family/allergies-search-page.tsxsrc/pages/family/family-create-page.tsxsrc/pages/family/family-invite-page.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/meal-plan/BottomSheet.tsx`:
- Around line 217-227: The button currently always calls handleChange regardless
of isOpen, causing an error toast when the menu list is hidden; update the
Button onClick behavior so it respects isOpen: when isOpen is true call
handleChange, otherwise toggle open state (call setIsOpen(true) or disable the
button). Locate the Button component rendering the span and change its onClick
from onClick={handleChange} to conditional behavior based on isOpen (referencing
isOpen, handleChange, and setIsOpen) and ensure the UI/aria reflects the
disabled state if you choose to disable instead of toggling.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
src/components/meal-plan/BottomSheet.tsxsrc/components/meal-plan/ReviewModal.tsx
| interface ButtonType { | ||
| children: ReactElement | string; | ||
| size: "Btn_L" | "Btn_M" | "Btn_S"; | ||
| variant: "primary" | "gray" | "transparent"; | ||
| disabled?: boolean; | ||
| bgColor?: "primary" | "white"; | ||
| onClick?: () => void; | ||
| type?: "button" | "submit" | "reset"; | ||
| className: string; | ||
| } |
There was a problem hiding this comment.
이부분을 매튜님이 예시 보여주셨던 것 처럼
interface ButtonProp extends React.HTMLAttributes<HTMLButtonElement>
이런식으로 선언해보는 건 어떨까 싶습니다. disabled, onClick, type 등이 props로 존재하는 걸로 압니다! 한번 적용해보면 좋을 것 같아요!
참고할만한 블로그 첨부합니다
https://velog.io/@dongkyun/TS-HTMLElement의-type-상속받기
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/common/Button.tsx`:
- Around line 4-8: The ButtonType interface declares size and variant as
required even though the Button component provides defaults ("Btn_M" and
"primary"); update the ButtonType definition to make size and variant optional
(add ?), leaving the component's defaulting logic in the Button component intact
so callers need not always pass those props; reference ButtonType and the Button
component to locate the change.
- Line 23: Update the Button component's props default so its size default is
"Btn_L" instead of "Btn_M": in the Button component where props are destructured
(the line with { children, size = "Btn_M", variant = "primary", className,
...props }), change the size default to "Btn_L" to align the component
implementation with the PR requirement and existing usage.
In `@src/pages/meal-plan/meal-plan-create-page.tsx`:
- Around line 107-117: The onClick currently uses an unnecessary wrapper
function; replace the arrow wrapper on the Button's onClick prop with the
handler reference directly (use onClick={handleCreate}) to avoid creating a new
function on each render—update the Button component where onClick={() =>
handleCreate()} is used and ensure the handleCreate function is defined in the
component scope (preserving any required binding or useCallback if needed).
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
src/components/common/Button.tsxsrc/components/profile/ProfileDataForm.tsxsrc/pages/meal-plan/meal-plan-create-page.tsx
| interface ButtonType extends ButtonHTMLAttributes<HTMLButtonElement> { | ||
| children: ReactNode; | ||
| size: "Btn_L" | "Btn_M" | "Btn_S"; | ||
| variant: "primary" | "gray" | "transparent"; | ||
| } |
There was a problem hiding this comment.
size와 variant가 필수 속성으로 선언되어 있지만 기본값이 존재합니다.
인터페이스에서 size와 variant가 필수(? 없음)로 선언되어 있으나, 컴포넌트 내부에서 기본값("Btn_M", "primary")을 제공하고 있습니다. 이로 인해 TypeScript에서 props 전달 시 항상 명시해야 하는 것으로 인식됩니다.
🛠️ 제안 수정안
interface ButtonType extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
- size: "Btn_L" | "Btn_M" | "Btn_S";
- variant: "primary" | "gray" | "transparent";
+ size?: "Btn_L" | "Btn_M" | "Btn_S";
+ variant?: "primary" | "gray" | "transparent";
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/common/Button.tsx` around lines 4 - 8, The ButtonType
interface declares size and variant as required even though the Button component
provides defaults ("Btn_M" and "primary"); update the ButtonType definition to
make size and variant optional (add ?), leaving the component's defaulting logic
in the Button component intact so callers need not always pass those props;
reference ButtonType and the Button component to locate the change.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/common/Button.tsx`:
- Around line 23-40: The Button component currently omits an explicit button
type so it defaults to "submit" inside forms; update the component's
props/defaults to set type = "button" by default (e.g., in the props
destructuring where children, size, variant, className, ...props are defined)
and ensure the rendered <button> uses that type while still allowing callers to
override via props; also update the ButtonProps/type annotation (if present) to
include HTMLButtonElement types that allow "button" | "submit" | "reset".
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
src/components/common/Button.tsxsrc/pages/meal-plan/meal-plan-create-page.tsx
| { children, size = "Btn_L", variant = "primary", className, ...props }, | ||
| ref, | ||
| ) => { | ||
| const sizeClass = SIZES[size]; | ||
| const variantClass = VARIANTS[variant]; | ||
| const disabledClass = props.disabled ? "opacity-50 cursor-not-allowed" : ""; | ||
| return ( | ||
| <button | ||
| ref={ref} | ||
| className={twMerge( | ||
| "rounded-xl flex justify-center items-center transition-colors", | ||
| sizeClass, | ||
| variantClass, | ||
| disabledClass, | ||
| className, | ||
| )} | ||
| {...props} | ||
| > |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Button 컴포넌트 전체 내용 확인 (라인 수 먼저 확인)
wc -l src/components/common/Button.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 106
🏁 Script executed:
# Button.tsx 파일 내용 확인
cat -n src/components/common/Button.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 1694
🏁 Script executed:
# Button 사용처 검색 - type 속성이 없는 경우
rg -nP --type=tsx '<Button\b(?![^>]*\btype=)' -g '!**/*.test.tsx' || echo "No matches found or pattern issue"Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 149
🏁 Script executed:
# form 내 Button 사용 패턴 확인
rg -nP --type=tsx '<form' -A 10 -B 2 -g '!**/*.test.tsx' | head -50Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 98
🏁 Script executed:
# Button 컴포넌트의 타입 정의 확인
rg -nP --type=tsx 'type.*Button.*=' -A 5 src/components/common/Button.tsx || rg -nP 'interface|type' src/components/common/Button.tsx | head -20Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 269
🏁 Script executed:
# Button 사용처 검색 (파일 패턴으로 필터링)
rg '<Button\b' --glob='*.tsx' --glob='*.ts' -g '!**/*.test.tsx' -g '!**/*.test.ts' | head -30Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 801
🏁 Script executed:
# form 내 Button 사용 패턴 확인
rg '<form' --glob='*.tsx' --glob='*.ts' -A 15 -g '!**/*.test.tsx' | head -60Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 2585
🏁 Script executed:
# Button 컴포넌트에서 type 속성 명시 여부 재확인
cat src/components/common/Button.tsx | grep -n 'type\|<button'Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 168
🏁 Script executed:
# Button 사용 시 type 속성이 명시된 경우와 미명시된 경우 비교
rg '<Button' --glob='*.tsx' -A 2 | head -50Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 2571
🏁 Script executed:
# ProfileDataForm.tsx 전체 내용 확인
cat -n src/components/profile/ProfileDataForm.tsx | head -100Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 3584
🏁 Script executed:
# Button이 form 내부에 있고 type 속성이 명시되었는지 확인
rg '<form|<Button|type=' src/components/profile/ProfileDataForm.tsx -nRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 406
🏁 Script executed:
# 모든 Button 사용처에서 type 속성 여부 확인
rg '<Button' --glob='*.tsx' -A 5 | grep -E '(<Button|type=)' | head -60Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1560
🏁 Script executed:
# ProfileDataForm 전체 확인 - Button 주변 코드
sed -n '100,270p' src/components/profile/ProfileDataForm.tsx | cat -nRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 6687
🏁 Script executed:
# family-invite-page.tsx에서 Button 사용 확인
cat -n src/pages/family/family-invite-page.tsx | grep -A 5 -B 5 '<Button'Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 427
🏁 Script executed:
# family-invite-page.tsx 전체 구조 확인
wc -l src/pages/family/family-invite-page.tsx && head -80 src/pages/family/family-invite-page.tsx | tail -30Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1144
🏁 Script executed:
# family-invite-page.tsx 전체 구조 파악 및 Button 주변 context 확인
cat -n src/pages/family/family-invite-page.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 4927
Button 컴포넌트에 기본 type="button" 기본값 추가 필요
현재 Button 컴포넌트는 <button> 요소에서 type 속성을 명시하지 않으므로, 폼 내부에서 사용될 경우 HTML 기본값인 type="submit"으로 동작하여 의도치 않은 폼 제출이 발생할 수 있습니다. 컴포넌트 props에서 type 기본값을 "button"으로 설정하는 것이 필요합니다.
제안 수정안
const Button = forwardRef<HTMLButtonElement, ButtonType>(
(
- { children, size = "Btn_L", variant = "primary", className, ...props },
+ {
+ children,
+ size = "Btn_L",
+ variant = "primary",
+ className,
+ type = "button",
+ ...props
+ },
ref,
) => {
@@
<button
ref={ref}
+ type={type}
className={twMerge(
"rounded-xl flex justify-center items-center transition-colors",
sizeClass,
variantClass,
disabledClass,
className,
)}
{...props}
>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/common/Button.tsx` around lines 23 - 40, The Button component
currently omits an explicit button type so it defaults to "submit" inside forms;
update the component's props/defaults to set type = "button" by default (e.g.,
in the props destructuring where children, size, variant, className, ...props
are defined) and ensure the rendered <button> uses that type while still
allowing callers to override via props; also update the ButtonProps/type
annotation (if present) to include HTMLButtonElement types that allow "button" |
"submit" | "reset".
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/meal-plan/meal-plan-page.tsx`:
- Around line 174-182: The Button rendering in the meal plan page uses plain
string children while other pages use a styled span; update the Button (the JSX
with onClick using isDone ? () => setIsOpen(true) : () => setIsDone(true)) so
both children branches are wrapped in <span className="text-xl font-semibold
leading-[22px]"> to match meal-plan-create-page.tsx and meal-plan-edit-page.tsx
and ensure consistent typography for the Button label.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: b394074e-e9e8-4490-a465-4a8834812532
📒 Files selected for processing (3)
src/pages/meal-plan/meal-plan-create-page.tsxsrc/pages/meal-plan/meal-plan-edit-page.tsxsrc/pages/meal-plan/meal-plan-page.tsx
| <Button | ||
| onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)} | ||
| size="Btn_L" | ||
| variant="primary" | ||
| type="button" | ||
| className="w-[343px]" | ||
| > | ||
| {isDone ? "리뷰 작성" : "식사 완료"} | ||
| </Button> |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
children 스타일링이 다른 Button 사용처와 일관되지 않습니다.
PR 내 다른 Button 사용처(meal-plan-create-page.tsx, meal-plan-edit-page.tsx)에서는 children을 <span className="text-xl font-semibold leading-[22px]">으로 감싸고 있지만, 여기서는 plain string을 직접 전달하고 있습니다. 일관된 스타일링을 위해 span으로 감싸는 것을 권장합니다.
♻️ 일관된 스타일링을 위한 수정 제안
<Button
onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)}
size="Btn_L"
variant="primary"
type="button"
className="w-[343px]"
>
- {isDone ? "리뷰 작성" : "식사 완료"}
+ <span className="text-xl font-semibold leading-[22px]">
+ {isDone ? "리뷰 작성" : "식사 완료"}
+ </span>
</Button>📝 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.
| <Button | |
| onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)} | |
| size="Btn_L" | |
| variant="primary" | |
| type="button" | |
| className="w-[343px]" | |
| > | |
| {isDone ? "리뷰 작성" : "식사 완료"} | |
| </Button> | |
| <Button | |
| onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)} | |
| size="Btn_L" | |
| variant="primary" | |
| type="button" | |
| className="w-[343px]" | |
| > | |
| <span className="text-xl font-semibold leading-[22px]"> | |
| {isDone ? "리뷰 작성" : "식사 완료"} | |
| </span> | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 174 - 182, The Button
rendering in the meal plan page uses plain string children while other pages use
a styled span; update the Button (the JSX with onClick using isDone ? () =>
setIsOpen(true) : () => setIsDone(true)) so both children branches are wrapped
in <span className="text-xl font-semibold leading-[22px]"> to match
meal-plan-create-page.tsx and meal-plan-edit-page.tsx and ensure consistent
typography for the Button label.
PR 제목
[REFACTOR] 공용 버튼 컴포넌트 재사용성 향상
#️⃣연관된 이슈
📝작업 내용
재사용성에 집중해 기존 버튼 컴포넌트의 코드를 수정했습니다
컴포넌트 사용 예시
💬리뷰 요구사항(선택)
Summary by CodeRabbit
릴리스 노트