From c65b44db8210c4cec4eb87d5d17f6f50d4e14a74 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Tue, 7 Apr 2026 11:40:27 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor(components):=20Button=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=99=95=EC=9E=A5=EC=84=B1=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ButtonHTMLAttributes 확장으로 type, aria-*, data-* 등 자동 지원 - 하드코딩 색상을 테마 시스템(theme.colors)으로 교체 - transient props($animated, $width) 적용으로 DOM 전달 경고 제거 --- frontend/docs/features/components/Button.md | 42 ++++++++++++ .../src/components/common/Button/Button.tsx | 65 ++++++++----------- 2 files changed, 69 insertions(+), 38 deletions(-) create mode 100644 frontend/docs/features/components/Button.md diff --git a/frontend/docs/features/components/Button.md b/frontend/docs/features/components/Button.md new file mode 100644 index 000000000..ed395c980 --- /dev/null +++ b/frontend/docs/features/components/Button.md @@ -0,0 +1,42 @@ +# Button 컴포넌트 + +공용 버튼 컴포넌트. `React.ButtonHTMLAttributes`를 extend하여 HTML 버튼의 모든 속성을 지원한다. + +## Props + +| Prop | Type | Default | 설명 | +| ---------- | ---------------------------- | -------- | ------------------------------------------------------------------------ | +| `width` | `string` | `'auto'` | 버튼 너비 (예: `'100%'`, `'150px'`) | +| `animated` | `boolean` | `false` | hover 시 pulse 애니메이션, active 시 scale 축소 | +| 그 외 | `React.ButtonHTMLAttributes` | — | `type`, `disabled`, `onClick`, `aria-*`, `data-*` 등 모든 HTML 버튼 속성 | + +## 사용 예시 + +```tsx +// 기본 + + +// 너비 지정 + submit + + +// 애니메이션 + 비활성화 + +``` + +## 스타일 + +테마 시스템(`theme.colors`, `theme.typography`)을 참조한다. + +- 배경: `gray[900]` (#3A3A3A) +- 텍스트: `base.white` +- 높이: 42px, border-radius: 10px +- 폰트: `typography.paragraph.p2` (16px, weight 600) +- disabled: `gray[500]` 배경, `gray[600]` 텍스트 + +## 관련 코드 + +- `src/components/common/Button/Button.tsx` — 컴포넌트 구현 +- `src/styles/theme/colors.ts` — 색상 토큰 +- `src/styles/theme/typography.ts` — 타이포그래피 토큰 diff --git a/frontend/src/components/common/Button/Button.tsx b/frontend/src/components/common/Button/Button.tsx index 61fe7eba5..faa90a0a7 100644 --- a/frontend/src/components/common/Button/Button.tsx +++ b/frontend/src/components/common/Button/Button.tsx @@ -1,71 +1,60 @@ import styled, { css, keyframes } from 'styled-components'; -export interface ButtonProps { +export interface ButtonProps extends React.ButtonHTMLAttributes { width?: string; - children: React.ReactNode; - type?: string; - onClick?: () => void; animated?: boolean; - disabled?: boolean; - className?: string; } const pulse = keyframes` - 0% { transform: scale(1); background-color: #3a3a3a; } - 50% { transform: scale(1.05); background-color: #505050; } - 100% { transform: scale(1); background-color: #3a3a3a; } + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } `; -const StyledButton = styled.button` - background-color: #3a3a3a; - color: #ffffff; +const StyledButton = styled.button<{ $animated: boolean; $width?: string }>` + display: inline-flex; + align-items: center; + justify-content: center; + background-color: ${({ theme }) => theme.colors.gray[900]}; + color: ${({ theme }) => theme.colors.base.white}; height: 42px; - border-radius: 10px; + padding: 0 16px; border: none; - font-weight: 600; - font-size: 16px; + border-radius: 10px; + font-size: ${({ theme }) => theme.typography.paragraph.p2.size}; + font-weight: ${({ theme }) => theme.typography.paragraph.p2.weight}; cursor: pointer; transition: background-color 0.2s; - width: ${({ width }) => width || 'auto'}; + width: ${({ $width }) => $width ?? 'auto'}; - &:hover { - background-color: #333333; - ${({ animated }) => - animated && + &:hover:not(:disabled) { + background-color: ${({ theme }) => theme.colors.gray[800]}; + ${({ $animated }) => + $animated && css` animation: ${pulse} 0.4s ease-in-out; `} } - &:active { - transform: ${({ animated }) => (animated ? 'scale(0.95)' : 'none')}; + &:active:not(:disabled) { + transform: ${({ $animated }) => ($animated ? 'scale(0.95)' : 'none')}; } &:disabled { - background-color: #cccccc; /* 비활성화된 느낌의 회색 */ - color: #666666; - cursor: not-allowed; /* 클릭할 수 없음을 나타내는 커서 */ + background-color: ${({ theme }) => theme.colors.gray[500]}; + color: ${({ theme }) => theme.colors.gray[600]}; + cursor: not-allowed; opacity: 0.7; } `; const Button = ({ width, - children, - onClick, - type, animated = false, - disabled = false, - className, + children, + ...rest }: ButtonProps) => ( - + {children} ); From 6bebe4b74a6230d48623b39c17985e68de32dd4d Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Tue, 7 Apr 2026 15:45:29 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(components):=20Button=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20type=EC=9D=84=20'button'=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 폼 내부에서 의도치 않은 submit 방지 --- frontend/src/components/common/Button/Button.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/common/Button/Button.tsx b/frontend/src/components/common/Button/Button.tsx index faa90a0a7..e969ec10a 100644 --- a/frontend/src/components/common/Button/Button.tsx +++ b/frontend/src/components/common/Button/Button.tsx @@ -1,6 +1,7 @@ +import type { ButtonHTMLAttributes } from 'react'; import styled, { css, keyframes } from 'styled-components'; -export interface ButtonProps extends React.ButtonHTMLAttributes { +export interface ButtonProps extends ButtonHTMLAttributes { width?: string; animated?: boolean; } @@ -51,10 +52,11 @@ const StyledButton = styled.button<{ $animated: boolean; $width?: string }>` const Button = ({ width, animated = false, + type = 'button', children, ...rest }: ButtonProps) => ( - + {children} );