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 "";