-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR] 공용 버튼 컴포넌트 재사용성 향상 #218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
f525dff
d638896
755274c
18dd1df
c6e7b7d
6abdf55
f8b6c97
97511d6
b8a80ef
6da04c6
beab41d
6cc7e83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,47 @@ | ||
| import Pencil from "../../assets/icons/pencil-primary-500.svg"; | ||
| import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react"; | ||
| import { twMerge } from "tailwind-merge"; | ||
|
|
||
| interface ButtonProps { | ||
| onClick?: () => void; | ||
| text: string; | ||
| type: "button" | "submit" | "reset"; | ||
| disabled?: boolean; | ||
| bgColor?: "primary" | "white"; | ||
| interface ButtonType extends ButtonHTMLAttributes<HTMLButtonElement> { | ||
| children: ReactNode; | ||
| size?: "Btn_L" | "Btn_M" | "Btn_S"; | ||
| variant?: "primary" | "gray" | "transparent"; | ||
| } | ||
|
|
||
| export default function Button({ | ||
| onClick, | ||
| text, | ||
| type, | ||
| disabled, | ||
| bgColor = "primary", | ||
| }: ButtonProps) { | ||
| return ( | ||
| <button | ||
| className={`w-[343px] px-[10px] py-[16px] flex items-center justify-center gap-[10px] rounded-xl font-semibold text-[20px] leading-[22px] text-center cursor-pointer | ||
| ${bgColor == "primary" ? "bg-primary-700 text-white" : "bg-white text-primary-500 border border-primary-500"}`} | ||
| onClick={onClick} | ||
| type={type} | ||
| disabled={disabled} | ||
| > | ||
| {bgColor === "white" && <img src={Pencil} />} | ||
| {text} | ||
| </button> | ||
| ); | ||
| } | ||
| const SIZES = { | ||
| Btn_L: "px-[10px] py-[16px]", | ||
| Btn_S: "px-[24px] py-[6px]", | ||
| Btn_M: "px-[10px] py-[16px]", // padding을 className에 직접 지정 가능 | ||
| }; | ||
| const VARIANTS = { | ||
| primary: "bg-primary-700 text-white", | ||
| gray: "bg-gray-100", | ||
| transparent: "bg-transparent border-1 border-primary-700", | ||
| }; | ||
|
|
||
| const Button = forwardRef<HTMLButtonElement, ButtonType>( | ||
| ( | ||
| { 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} | ||
| > | ||
|
Comment on lines
+23
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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 컴포넌트에 기본 현재 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 |
||
| {children} | ||
| </button> | ||
| ); | ||
| }, | ||
| ); | ||
| Button.displayName = "Button"; | ||
| export default Button; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -171,20 +171,15 @@ function TodayMealTab({ data }: { data: TodayMeal }) { | |||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| {!data.isReviewed && ( | ||||||||||||||||||||||||||||||||||||||||||
| <div className="pt-6"> | ||||||||||||||||||||||||||||||||||||||||||
| {!isDone ? ( | ||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||
| text="식사 완료" | ||||||||||||||||||||||||||||||||||||||||||
| onClick={() => setIsDone(true)} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||
| text="리뷰 작성" | ||||||||||||||||||||||||||||||||||||||||||
| bgColor="white" | ||||||||||||||||||||||||||||||||||||||||||
| onClick={() => setIsOpen(true)} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||
| onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)} | ||||||||||||||||||||||||||||||||||||||||||
| size="Btn_L" | ||||||||||||||||||||||||||||||||||||||||||
| variant="primary" | ||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||
| className="w-[343px]" | ||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||
| {isDone ? "리뷰 작성" : "식사 완료"} | ||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+174
to
+182
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial children 스타일링이 다른 Button 사용처와 일관되지 않습니다. PR 내 다른 Button 사용처(meal-plan-create-page.tsx, meal-plan-edit-page.tsx)에서는 children을 ♻️ 일관된 스타일링을 위한 수정 제안 <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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||
| <div className="pt-11 pb-29"> | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -409,8 +404,10 @@ function ReviewModal({ recipeId, onClick, type }: ReviewModalProps) { | |||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full p-[10px] flex justify-center"> | ||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||
| size="Btn_L" | ||||||||||||||||||||||||||||||||||||||||||
| variant="primary" | ||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||
| text="등록" | ||||||||||||||||||||||||||||||||||||||||||
| className="w-[343px]" | ||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||
| const reviewData: createReview = { | ||||||||||||||||||||||||||||||||||||||||||
| recipeId, | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -421,7 +418,9 @@ function ReviewModal({ recipeId, onClick, type }: ReviewModalProps) { | |||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| handleSubmitReview(reviewData); | ||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||
| <span className="text-xl font-semibold leading-[22px]">등록</span> | ||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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