From 1ac1c82a898b78aa9afcb9103e3e28a982555d27 Mon Sep 17 00:00:00 2001 From: taegeon2 Date: Thu, 19 Feb 2026 01:58:31 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[FEAT/#13]=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index f8c6c2e..9ff119e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,8 @@ app-example # generated native folders /ios /android + +# claude +.claudeignore +CLAUDE.md +CONVENTION.md \ No newline at end of file From a275a46d2907815ce45e1d1f5b625b967524d1f0 Mon Sep 17 00:00:00 2001 From: taegeon2 Date: Thu, 19 Feb 2026 04:11:52 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[FEAT/#13]=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(tabs)/index.tsx | 359 +++++++++++++----------- src/shared/ui/buttons/buttons.md | 111 ++++++++ src/shared/ui/buttons/large_button.tsx | 58 ++++ src/shared/ui/buttons/medium_button.tsx | 21 ++ src/shared/ui/buttons/small_button.tsx | 22 ++ 5 files changed, 402 insertions(+), 169 deletions(-) create mode 100644 src/shared/ui/buttons/buttons.md create mode 100644 src/shared/ui/buttons/large_button.tsx create mode 100644 src/shared/ui/buttons/medium_button.tsx create mode 100644 src/shared/ui/buttons/small_button.tsx diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx index 01b6a24..923895b 100644 --- a/src/app/(tabs)/index.tsx +++ b/src/app/(tabs)/index.tsx @@ -1,182 +1,203 @@ +import { shadows } from "@/shared/styles/shadows"; +import { LargeButton } from "@/shared/ui/buttons/large_button"; +import { MediumButton } from "@/shared/ui/buttons/medium_button"; +import { SmallButton } from "@/shared/ui/buttons/small_button"; import { ScrollView, Text, View } from "react-native"; -import { shadows } from "../../shared/styles/shadows"; export default function HomeScreen() { - return ( - - {/* 헤더 영역 */} - - - Welcome - - - 디자인 토큰이 적용된 메인 화면 - - + return ( + + {/* 헤더 영역 */} + + + Welcome + + + 디자인 토큰이 적용된 메인 화면 + + - {/* Primary 버튼 with Shadow */} - - - Primary 버튼 (Primary Shadow) - - + {/* Primary 버튼 with Shadow */} + + + Primary 버튼 (Primary Shadow) + + - {/* 카드 with Neutral Shadow */} - - - 카드 제목 - - - Neutral 배경 + Neutral Shadow 적용 - - + {/* 카드 with Neutral Shadow */} + + + 카드 제목 + + + Neutral 배경 + Neutral Shadow 적용 + + - {/* Primary Tint 카드 with Shadow */} - - - Primary Tint 카드 - - - 연한 블루 배경 + Shadow - - + {/* Primary Tint 카드 with Shadow */} + + + Primary Tint 카드 + + + 연한 블루 배경 + Shadow + + - {/* 그리드 아이템들 with Shadow */} - - - 그리드 아이템 (Gutter 적용) - - - - - 아이템 1 - - - - - 아이템 2 - - - - - 아이템 3 - - - - + {/* 그리드 아이템들 with Shadow */} + + + 그리드 아이템 (Gutter 적용) + + + + + 아이템 1 + + + + + 아이템 2 + + + + + 아이템 3 + + + + - {/* 리스트 아이템들 with Shadow */} - - - 리스트 아이템 (Gutter 적용) - - - - - 리스트 아이템 1 - - - - - 리스트 아이템 2 - - - - - 리스트 아이템 3 - - - - + {/* 리스트 아이템들 with Shadow */} + + + 리스트 아이템 (Gutter 적용) + + + + + 리스트 아이템 1 + + + + + 리스트 아이템 2 + + + + + 리스트 아이템 3 + + + + - {/* 비활성화 버튼 */} - - - 비활성화 버튼 (opacity 30%) - - + {/* 비활성화 버튼 */} + + + 비활성화 버튼 (opacity 30%) + + - {/* Danger 상태 */} - - - Danger 상태 - - + {/* Danger 상태 */} + + + Danger 상태 + + - {/* 폰트 굵기별 예시 */} - - - 폰트 굵기 (Pretendard) - - - - Regular (400) - 기본 굵기 - - - Medium (500) - 중간 굵기 - - - SemiBold (600) - 세미볼드 - - - Bold (700) - 볼드 - - - + {/* 폰트 굵기별 예시 */} + + + 폰트 굵기 (Pretendard) + + + + Regular (400) - 기본 굵기 + + + Medium (500) - 중간 굵기 + + + SemiBold (600) - 세미볼드 + + + Bold (700) - 볼드 + + + - {/* 폰트 굵기별 크기 비교 */} - - - 폰트 크기별 예시 - - - - text-xs (12px) - Regular - - - text-sm (14px) - Regular - - - text-base (16px) - Regular - - - text-lg (18px) - Medium - - - text-xl (20px) - SemiBold - - - text-2xl (24px) - Bold - - - text-[32px] - Bold - - - - - ); + {/* 폰트 굵기별 크기 비교 */} + + + 폰트 크기별 예시 + + + + text-xs (12px) - Regular + + + text-sm (14px) - Regular + + + text-base (16px) - Regular + + + text-lg (18px) - Medium + + + text-xl (20px) - SemiBold + + + text-2xl (24px) - Bold + + + text-[32px] - Bold + + + + + {/* LargeButton 예시 */} + + + + + {/* SmallButton 예시 */} + + 제휴 리뷰 작성하기 + + + {/* MediumButton 예시 */} + + {}}>인증완료 + + + ); } diff --git a/src/shared/ui/buttons/buttons.md b/src/shared/ui/buttons/buttons.md new file mode 100644 index 0000000..23fe27c --- /dev/null +++ b/src/shared/ui/buttons/buttons.md @@ -0,0 +1,111 @@ +# Button Components + +--- + +## SmallButton + +회색 배경의 소형 버튼입니다. + +| 속성 | 값 | +| ------ | ----------------------------------------------- | +| 너비 | 11.4rem | +| 높이 | 4.1rem | +| 배경 | `bg-neutral` | +| 텍스트 | `text-content-secondary` / 0.8125rem / SemiBold | + +**Props** — `PressableProps` 확장 (`onPress`, `disabled`, `style` 등 사용 가능) + +### 사용 예시 + +```tsx +import { SmallButton } from "@/shared/ui/buttons/small_button"; + +export default function MyScreen() { + return ( + console.log("pressed")}> + 제휴 리뷰 작성하기 + + ); +} +``` + +비활성화: + +```tsx +제휴 리뷰 작성하기 +``` + +--- + +## MediumButton + +파란색 배경의 중형 액션 버튼입니다. + +| 속성 | 값 | +| ------ | --------------------------------------- | +| 너비 | 21.5625rem | +| 패딩 | 1.12rem (상하) / 6.25rem (좌우) | +| 배경 | `bg-primary` | +| 텍스트 | `text-content-inverse` / 1.25rem / Bold | + +**Props** — `PressableProps` 확장 (`onPress`, `disabled`, `style` 등 사용 가능) + +### 사용 예시 + +```tsx +import { MediumButton } from "@/shared/ui/buttons/medium_button"; + +export default function MyScreen() { + return ( + console.log("pressed")}>인증완료 + ); +} +``` + +비활성화: + +```tsx +인증완료 +``` + +--- + +## LargeButton + +제목 + 설명을 포함하는 토글형 대형 버튼입니다. 클릭 시 선택/해제 상태가 전환됩니다. + +| 상태 | 테두리 | 배경 | +| ---- | ------------------------- | ----------------- | +| 기본 | `color-content-secondary` | `bg-neutral` | +| 선택 | `color-primary` | `bg-primary-tint` | + +**Props** + +| prop | 타입 | 설명 | +| ------------- | ------------ | ------------------ | +| `title` | `string` | 버튼 제목 | +| `description` | `string` | 버튼 설명 | +| `onPress` | `() => void` | 클릭 핸들러 (선택) | + +### 사용 예시 + +```tsx +import { LargeButton } from "@/shared/ui/buttons/large_button"; + +export default function MyScreen() { + return ( + console.log("선택됨")} + /> + ); +} +``` + +여러 개 나열: + +```tsx + + +``` diff --git a/src/shared/ui/buttons/large_button.tsx b/src/shared/ui/buttons/large_button.tsx new file mode 100644 index 0000000..1d82472 --- /dev/null +++ b/src/shared/ui/buttons/large_button.tsx @@ -0,0 +1,58 @@ +import { useState } from "react"; +import { Pressable, Text } from "react-native"; + +interface Props { + title: string; + description: string; + onPress?: () => void; +} + +const LargeButton = ({ title, description, onPress }: Props) => { + const [isSelected, setIsSelected] = useState(false); + + const handlePress = () => { + setIsSelected(!isSelected); + onPress?.(); + }; + + return ( + + + {title} + + + {description} + + + ); +}; + +export { LargeButton }; diff --git a/src/shared/ui/buttons/medium_button.tsx b/src/shared/ui/buttons/medium_button.tsx new file mode 100644 index 0000000..c63a85d --- /dev/null +++ b/src/shared/ui/buttons/medium_button.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from "react"; +import { Pressable, type PressableProps, Text } from "react-native"; + +interface Props extends PressableProps { + children: ReactNode; +} + +const MediumButton = ({ children, ...props }: Props) => { + return ( + + + {children} + + + ); +}; + +export { MediumButton }; diff --git a/src/shared/ui/buttons/small_button.tsx b/src/shared/ui/buttons/small_button.tsx new file mode 100644 index 0000000..4bbcd25 --- /dev/null +++ b/src/shared/ui/buttons/small_button.tsx @@ -0,0 +1,22 @@ +import type { ReactNode } from "react"; +import { Pressable, type PressableProps, Text } from "react-native"; + +interface Props extends PressableProps { + //PressableProps: 버튼 컴포넌트의 속성을 정의 + children: ReactNode; // ReactNode: 모든 타입의 자식 요소를 허용 +} + +const SmallButton = ({ children, ...props }: Props) => { + return ( + + + {children} + + + ); +}; + +export { SmallButton }; From e15c67ed8b35f7219ecd9205a3d449fffb147d84 Mon Sep 17 00:00:00 2001 From: taegeon2 Date: Thu, 19 Feb 2026 04:21:19 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[CHORE/#13]=20yarn=20=EB=AA=85=EB=A0=B9?= =?UTF-8?q?=EC=96=B4=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=ED=92=88=EC=A7=88=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(tabs)/index.tsx | 366 ++++++++++++------------- src/shared/ui/buttons/large_button.tsx | 74 ++--- src/shared/ui/buttons/small_button.tsx | 24 +- src/shared/utils/formatDate.ts | 4 +- 4 files changed, 234 insertions(+), 234 deletions(-) diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx index 923895b..15c1796 100644 --- a/src/app/(tabs)/index.tsx +++ b/src/app/(tabs)/index.tsx @@ -5,199 +5,199 @@ import { SmallButton } from "@/shared/ui/buttons/small_button"; import { ScrollView, Text, View } from "react-native"; export default function HomeScreen() { - return ( - - {/* 헤더 영역 */} - - - Welcome - - - 디자인 토큰이 적용된 메인 화면 - - + return ( + + {/* 헤더 영역 */} + + + Welcome + + + 디자인 토큰이 적용된 메인 화면 + + - {/* Primary 버튼 with Shadow */} - - - Primary 버튼 (Primary Shadow) - - + {/* Primary 버튼 with Shadow */} + + + Primary 버튼 (Primary Shadow) + + - {/* 카드 with Neutral Shadow */} - - - 카드 제목 - - - Neutral 배경 + Neutral Shadow 적용 - - + {/* 카드 with Neutral Shadow */} + + + 카드 제목 + + + Neutral 배경 + Neutral Shadow 적용 + + - {/* Primary Tint 카드 with Shadow */} - - - Primary Tint 카드 - - - 연한 블루 배경 + Shadow - - + {/* Primary Tint 카드 with Shadow */} + + + Primary Tint 카드 + + + 연한 블루 배경 + Shadow + + - {/* 그리드 아이템들 with Shadow */} - - - 그리드 아이템 (Gutter 적용) - - - - - 아이템 1 - - - - - 아이템 2 - - - - - 아이템 3 - - - - + {/* 그리드 아이템들 with Shadow */} + + + 그리드 아이템 (Gutter 적용) + + + + + 아이템 1 + + + + + 아이템 2 + + + + + 아이템 3 + + + + - {/* 리스트 아이템들 with Shadow */} - - - 리스트 아이템 (Gutter 적용) - - - - - 리스트 아이템 1 - - - - - 리스트 아이템 2 - - - - - 리스트 아이템 3 - - - - + {/* 리스트 아이템들 with Shadow */} + + + 리스트 아이템 (Gutter 적용) + + + + + 리스트 아이템 1 + + + + + 리스트 아이템 2 + + + + + 리스트 아이템 3 + + + + - {/* 비활성화 버튼 */} - - - 비활성화 버튼 (opacity 30%) - - + {/* 비활성화 버튼 */} + + + 비활성화 버튼 (opacity 30%) + + - {/* Danger 상태 */} - - - Danger 상태 - - + {/* Danger 상태 */} + + + Danger 상태 + + - {/* 폰트 굵기별 예시 */} - - - 폰트 굵기 (Pretendard) - - - - Regular (400) - 기본 굵기 - - - Medium (500) - 중간 굵기 - - - SemiBold (600) - 세미볼드 - - - Bold (700) - 볼드 - - - + {/* 폰트 굵기별 예시 */} + + + 폰트 굵기 (Pretendard) + + + + Regular (400) - 기본 굵기 + + + Medium (500) - 중간 굵기 + + + SemiBold (600) - 세미볼드 + + + Bold (700) - 볼드 + + + - {/* 폰트 굵기별 크기 비교 */} - - - 폰트 크기별 예시 - - - - text-xs (12px) - Regular - - - text-sm (14px) - Regular - - - text-base (16px) - Regular - - - text-lg (18px) - Medium - - - text-xl (20px) - SemiBold - - - text-2xl (24px) - Bold - - - text-[32px] - Bold - - - + {/* 폰트 굵기별 크기 비교 */} + + + 폰트 크기별 예시 + + + + text-xs (12px) - Regular + + + text-sm (14px) - Regular + + + text-base (16px) - Regular + + + text-lg (18px) - Medium + + + text-xl (20px) - SemiBold + + + text-2xl (24px) - Bold + + + text-[32px] - Bold + + + - {/* LargeButton 예시 */} - - - + {/* LargeButton 예시 */} + + + - {/* SmallButton 예시 */} - - 제휴 리뷰 작성하기 - + {/* SmallButton 예시 */} + + 제휴 리뷰 작성하기 + - {/* MediumButton 예시 */} - - {}}>인증완료 - - - ); + {/* MediumButton 예시 */} + + {}}>인증완료 + + + ); } diff --git a/src/shared/ui/buttons/large_button.tsx b/src/shared/ui/buttons/large_button.tsx index 1d82472..6a0a44a 100644 --- a/src/shared/ui/buttons/large_button.tsx +++ b/src/shared/ui/buttons/large_button.tsx @@ -2,57 +2,57 @@ import { useState } from "react"; import { Pressable, Text } from "react-native"; interface Props { - title: string; - description: string; - onPress?: () => void; + title: string; + description: string; + onPress?: () => void; } const LargeButton = ({ title, description, onPress }: Props) => { - const [isSelected, setIsSelected] = useState(false); + const [isSelected, setIsSelected] = useState(false); - const handlePress = () => { - setIsSelected(!isSelected); - onPress?.(); - }; + const handlePress = () => { + setIsSelected(!isSelected); + onPress?.(); + }; - return ( - - + - {title} - - + {title} + + - {description} - - - ); + > + {description} + + + ); }; export { LargeButton }; diff --git a/src/shared/ui/buttons/small_button.tsx b/src/shared/ui/buttons/small_button.tsx index 4bbcd25..4a4d369 100644 --- a/src/shared/ui/buttons/small_button.tsx +++ b/src/shared/ui/buttons/small_button.tsx @@ -2,21 +2,21 @@ import type { ReactNode } from "react"; import { Pressable, type PressableProps, Text } from "react-native"; interface Props extends PressableProps { - //PressableProps: 버튼 컴포넌트의 속성을 정의 - children: ReactNode; // ReactNode: 모든 타입의 자식 요소를 허용 + //PressableProps: 버튼 컴포넌트의 속성을 정의 + children: ReactNode; // ReactNode: 모든 타입의 자식 요소를 허용 } const SmallButton = ({ children, ...props }: Props) => { - return ( - - - {children} - - - ); + return ( + + + {children} + + + ); }; export { SmallButton }; diff --git a/src/shared/utils/formatDate.ts b/src/shared/utils/formatDate.ts index 3c7077f..1cbcbb0 100644 --- a/src/shared/utils/formatDate.ts +++ b/src/shared/utils/formatDate.ts @@ -11,7 +11,7 @@ export function formatDate( options?: { locale?: string; separator?: "." | "-" | "/"; - } + }, ): string { const date = toDate(input); if (!date) return ""; @@ -32,7 +32,7 @@ export function formatDateTime( input: DateInput, options?: { separator?: "." | "-" | "/"; - } + }, ): string { const date = toDate(input); if (!date) return ""; From 641182ec682e3a6d64196a9b829cf2a6f7674e76 Mon Sep 17 00:00:00 2001 From: taegeon2 Date: Sun, 22 Feb 2026 01:13:04 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[CHORE/#13]=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/buttons/{small_button.tsx => ActionButton.tsx} | 0 .../ui/buttons/{large_button.tsx => BenefitSelectButton.tsx} | 0 src/shared/ui/buttons/{medium_button.tsx => SubmitButton.tsx} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/shared/ui/buttons/{small_button.tsx => ActionButton.tsx} (100%) rename src/shared/ui/buttons/{large_button.tsx => BenefitSelectButton.tsx} (100%) rename src/shared/ui/buttons/{medium_button.tsx => SubmitButton.tsx} (100%) diff --git a/src/shared/ui/buttons/small_button.tsx b/src/shared/ui/buttons/ActionButton.tsx similarity index 100% rename from src/shared/ui/buttons/small_button.tsx rename to src/shared/ui/buttons/ActionButton.tsx diff --git a/src/shared/ui/buttons/large_button.tsx b/src/shared/ui/buttons/BenefitSelectButton.tsx similarity index 100% rename from src/shared/ui/buttons/large_button.tsx rename to src/shared/ui/buttons/BenefitSelectButton.tsx diff --git a/src/shared/ui/buttons/medium_button.tsx b/src/shared/ui/buttons/SubmitButton.tsx similarity index 100% rename from src/shared/ui/buttons/medium_button.tsx rename to src/shared/ui/buttons/SubmitButton.tsx From a254cf831cc57acaf38d503f0c1e35c15a5a3dfe Mon Sep 17 00:00:00 2001 From: taegeon2 <154239852+taegeon2@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:03:00 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[ADD/#13]=20=EC=A0=9C=ED=9C=B4=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=B2=84=ED=8A=BC=20=EB=9D=BC=EB=94=94=EC=98=A4?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=ED=99=94=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(tabs)/index.tsx | 28 ++++-- src/shared/ui/buttons/BenefitSelectButton.tsx | 38 +++---- .../ui/buttons/BenefitSelectionGroup.tsx | 42 ++++++++ src/shared/ui/buttons/buttons.md | 99 +++++++++++++++---- 4 files changed, 156 insertions(+), 51 deletions(-) create mode 100644 src/shared/ui/buttons/BenefitSelectionGroup.tsx diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx index 15c1796..f1fbb3b 100644 --- a/src/app/(tabs)/index.tsx +++ b/src/app/(tabs)/index.tsx @@ -1,7 +1,7 @@ import { shadows } from "@/shared/styles/shadows"; -import { LargeButton } from "@/shared/ui/buttons/large_button"; -import { MediumButton } from "@/shared/ui/buttons/medium_button"; -import { SmallButton } from "@/shared/ui/buttons/small_button"; +import { BenefitSelectionGroup } from "@/shared/ui/buttons/BenefitSelectionGroup"; +import { MediumButton } from "@/shared/ui/buttons/SubmitButton"; +import { SmallButton } from "@/shared/ui/buttons/ActionButton"; import { ScrollView, Text, View } from "react-native"; export default function HomeScreen() { @@ -181,11 +181,23 @@ export default function HomeScreen() { - {/* LargeButton 예시 */} - - + diff --git a/src/shared/ui/buttons/BenefitSelectButton.tsx b/src/shared/ui/buttons/BenefitSelectButton.tsx index 6a0a44a..9c0b56f 100644 --- a/src/shared/ui/buttons/BenefitSelectButton.tsx +++ b/src/shared/ui/buttons/BenefitSelectButton.tsx @@ -1,40 +1,34 @@ -import { useState } from "react"; import { Pressable, Text } from "react-native"; interface Props { title: string; description: string; - onPress?: () => void; + isSelected: boolean; + onPress: () => void; } -const LargeButton = ({ title, description, onPress }: Props) => { - const [isSelected, setIsSelected] = useState(false); - - const handlePress = () => { - setIsSelected(!isSelected); - onPress?.(); - }; - +const BenefitSelectButton = ({ + title, + description, + isSelected, + onPress, +}: Props) => { return ( {title} @@ -42,11 +36,7 @@ const LargeButton = ({ title, description, onPress }: Props) => { {description} @@ -55,4 +45,4 @@ const LargeButton = ({ title, description, onPress }: Props) => { ); }; -export { LargeButton }; +export { BenefitSelectButton }; diff --git a/src/shared/ui/buttons/BenefitSelectionGroup.tsx b/src/shared/ui/buttons/BenefitSelectionGroup.tsx new file mode 100644 index 0000000..cdd3cbd --- /dev/null +++ b/src/shared/ui/buttons/BenefitSelectionGroup.tsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { View } from "react-native"; +import { BenefitSelectButton } from "./BenefitSelectButton"; + +interface BenefitOption { + title: string; + description: string; +} + +interface Props { + options: BenefitOption[]; + onSelect?: (index: number) => void; +} + +const BenefitSelectionGroup = ({ options, onSelect }: Props) => { + const [selectedIndex, setSelectedIndex] = useState(null); + + const handlePress = (index: number) => { + const nextIndex = selectedIndex === index ? null : index; + setSelectedIndex(nextIndex); + if (nextIndex !== null) { + onSelect?.(nextIndex); + } + }; + + return ( + + {options.map((option, index) => ( + handlePress(index)} + /> + ))} + + ); +}; + +export { BenefitSelectionGroup }; +export type { BenefitOption }; diff --git a/src/shared/ui/buttons/buttons.md b/src/shared/ui/buttons/buttons.md index 23fe27c..1679c4c 100644 --- a/src/shared/ui/buttons/buttons.md +++ b/src/shared/ui/buttons/buttons.md @@ -18,7 +18,7 @@ ### 사용 예시 ```tsx -import { SmallButton } from "@/shared/ui/buttons/small_button"; +import { SmallButton } from "@/shared/ui/buttons/ActionButton"; export default function MyScreen() { return ( @@ -53,7 +53,7 @@ export default function MyScreen() { ### 사용 예시 ```tsx -import { MediumButton } from "@/shared/ui/buttons/medium_button"; +import { MediumButton } from "@/shared/ui/buttons/SubmitButton"; export default function MyScreen() { return ( @@ -70,42 +70,103 @@ export default function MyScreen() { --- -## LargeButton +## BenefitSelectButton -제목 + 설명을 포함하는 토글형 대형 버튼입니다. 클릭 시 선택/해제 상태가 전환됩니다. +제목 + 설명을 포함하는 혜택 선택 버튼입니다. **개별 버튼 컴포넌트**로, 선택 상태를 외부에서 관리받습니다. -| 상태 | 테두리 | 배경 | -| ---- | ------------------------- | ----------------- | -| 기본 | `color-content-secondary` | `bg-neutral` | -| 선택 | `color-primary` | `bg-primary-tint` | +| 상태 | 테두리 | 배경 | +| ---- | -------------------------- | ----------------- | +| 기본 | `border-content-secondary` | `bg-neutral` | +| 선택 | `border-primary` | `bg-primary-tint` | **Props** -| prop | 타입 | 설명 | -| ------------- | ------------ | ------------------ | -| `title` | `string` | 버튼 제목 | -| `description` | `string` | 버튼 설명 | -| `onPress` | `() => void` | 클릭 핸들러 (선택) | +| prop | 타입 | 필수 | 설명 | +| ------------- | ------------ | ---- | ----------- | +| `title` | `string` | ✓ | 버튼 제목 | +| `description` | `string` | ✓ | 버튼 설명 | +| `isSelected` | `boolean` | ✓ | 선택 여부 | +| `onPress` | `() => void` | ✓ | 클릭 핸들러 | ### 사용 예시 ```tsx -import { LargeButton } from "@/shared/ui/buttons/large_button"; +import { BenefitSelectButton } from "@/shared/ui/buttons/BenefitSelectButton"; +import { useState } from "react"; export default function MyScreen() { + const [isSelected, setIsSelected] = useState(false); + return ( - console.log("선택됨")} + isSelected={isSelected} + onPress={() => setIsSelected(!isSelected)} /> ); } ``` -여러 개 나열: +> **주의**: `BenefitSelectButton`은 개별 버튼 컴포넌트이므로 상태 관리가 필요합니다. +> 여러 버튼을 함께 사용할 때는 **`BenefitSelectionGroup`** 사용을 권장합니다. + +--- + +## BenefitSelectionGroup + +여러 개의 혜택 옵션 중 하나를 선택하는 **라디오 버튼 그룹 컴포넌트**입니다. +옵션 배열만 전달하면 자동으로 `BenefitSelectButton`들을 렌더링하고, 라디오 버튼처럼 동작하여 **한 번에 하나의 버튼만 선택**됩니다. + +**Props** + +| prop | 타입 | 필수 | 설명 | +| ---------- | ------------------------- | ---- | --------------------------------- | +| `options` | `BenefitOption[]` | ✓ | 혜택 옵션 배열 | +| `onSelect` | `(index: number) => void` | | 옵션 선택 시 콜백 (선택된 인덱스) | + +**BenefitOption 타입** + +```tsx +interface BenefitOption { + title: string; // 버튼 제목 + description: string; // 버튼 설명 +} +``` + +### 사용 예시 ```tsx - - +import { BenefitSelectionGroup } from "@/shared/ui/buttons/BenefitSelectionGroup"; + +export default function BenefitScreen() { + const benefits = [ + { + title: "컴퓨터학부 학생회", + description: "4인이상 식사시, 캔 음료 제공", + }, + { + title: "IT대학 학생회", + description: "4인이상 식사시, 캔 음료 제공", + }, + { + title: "IT대학 학생회", + description: "15,000원 이상 주문시 파인애플 샤베트 제공", + }, + ]; + + return ( + console.log(`선택된 혜택: ${index}`)} + /> + ); +} ``` + +### 동작 설명 + +- 옵션을 클릭하면 해당 버튼이 선택 상태로 변경됩니다. +- 이미 선택된 버튼을 다시 클릭하면 선택이 해제됩니다. +- 다른 버튼을 클릭하면 이전 선택이 자동으로 해제되고 새 버튼이 선택됩니다. +- `onSelect` 콜백으로 선택된 버튼의 인덱스를 받을 수 있습니다..