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
diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx
index 01b6a24..f1fbb3b 100644
--- a/src/app/(tabs)/index.tsx
+++ b/src/app/(tabs)/index.tsx
@@ -1,5 +1,8 @@
+import { shadows } from "@/shared/styles/shadows";
+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";
-import { shadows } from "../../shared/styles/shadows";
export default function HomeScreen() {
return (
@@ -177,6 +180,36 @@ export default function HomeScreen() {
+
+ {/* BenefitSelectionGroup 예시 */}
+
+
+
+
+ {/* SmallButton 예시 */}
+
+ 제휴 리뷰 작성하기
+
+
+ {/* MediumButton 예시 */}
+
+ {}}>인증완료
+
);
}
diff --git a/src/shared/ui/buttons/ActionButton.tsx b/src/shared/ui/buttons/ActionButton.tsx
new file mode 100644
index 0000000..4a4d369
--- /dev/null
+++ b/src/shared/ui/buttons/ActionButton.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 };
diff --git a/src/shared/ui/buttons/BenefitSelectButton.tsx b/src/shared/ui/buttons/BenefitSelectButton.tsx
new file mode 100644
index 0000000..9c0b56f
--- /dev/null
+++ b/src/shared/ui/buttons/BenefitSelectButton.tsx
@@ -0,0 +1,48 @@
+import { Pressable, Text } from "react-native";
+
+interface Props {
+ title: string;
+ description: string;
+ isSelected: boolean;
+ onPress: () => void;
+}
+
+const BenefitSelectButton = ({
+ title,
+ description,
+ isSelected,
+ onPress,
+}: Props) => {
+ return (
+
+
+ {title}
+
+
+ {description}
+
+
+ );
+};
+
+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/SubmitButton.tsx b/src/shared/ui/buttons/SubmitButton.tsx
new file mode 100644
index 0000000..c63a85d
--- /dev/null
+++ b/src/shared/ui/buttons/SubmitButton.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/buttons.md b/src/shared/ui/buttons/buttons.md
new file mode 100644
index 0000000..1679c4c
--- /dev/null
+++ b/src/shared/ui/buttons/buttons.md
@@ -0,0 +1,172 @@
+# 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/ActionButton";
+
+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/SubmitButton";
+
+export default function MyScreen() {
+ return (
+ console.log("pressed")}>인증완료
+ );
+}
+```
+
+비활성화:
+
+```tsx
+인증완료
+```
+
+---
+
+## BenefitSelectButton
+
+제목 + 설명을 포함하는 혜택 선택 버튼입니다. **개별 버튼 컴포넌트**로, 선택 상태를 외부에서 관리받습니다.
+
+| 상태 | 테두리 | 배경 |
+| ---- | -------------------------- | ----------------- |
+| 기본 | `border-content-secondary` | `bg-neutral` |
+| 선택 | `border-primary` | `bg-primary-tint` |
+
+**Props**
+
+| prop | 타입 | 필수 | 설명 |
+| ------------- | ------------ | ---- | ----------- |
+| `title` | `string` | ✓ | 버튼 제목 |
+| `description` | `string` | ✓ | 버튼 설명 |
+| `isSelected` | `boolean` | ✓ | 선택 여부 |
+| `onPress` | `() => void` | ✓ | 클릭 핸들러 |
+
+### 사용 예시
+
+```tsx
+import { BenefitSelectButton } from "@/shared/ui/buttons/BenefitSelectButton";
+import { useState } from "react";
+
+export default function MyScreen() {
+ const [isSelected, setIsSelected] = useState(false);
+
+ return (
+ 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` 콜백으로 선택된 버튼의 인덱스를 받을 수 있습니다..
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 "";