From 341598f71bebb4bb37e8f86debb5bc2af24ad032 Mon Sep 17 00:00:00 2001 From: KimEunHye Date: Wed, 18 Feb 2026 17:17:52 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[ADD/#12]=20#b4b4b4=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=ED=86=A0=ED=81=B0=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/styles/global.styles.css | 4 ++++ src/shared/styles/tokens.ts | 35 +++++++++++++++++++++++++++++ tailwind.config.js | 1 + 3 files changed, 40 insertions(+) create mode 100644 src/shared/styles/tokens.ts diff --git a/src/shared/styles/global.styles.css b/src/shared/styles/global.styles.css index 6577dc7..f470bc0 100644 --- a/src/shared/styles/global.styles.css +++ b/src/shared/styles/global.styles.css @@ -14,6 +14,7 @@ /* 회색 계열 */ --gray-100: #f4f4f5; --gray-300: #dbdde1; + --gray-400: #b4b4b4; --gray-500: #8e9398; /* 흰색 */ @@ -46,6 +47,7 @@ /* Text Colors - Primitive 참조 */ --color-content-primary: var(--black); --color-content-secondary: var(--gray-500); + --color-content-tertiary: var(--gray-400); --color-content-inverse: var(--blue-50); /* Layout Spacing */ @@ -69,6 +71,7 @@ /* 회색 계열 */ --gray-100: #f4f4f5; --gray-300: #dbdde1; + --gray-400: #b4b4b4; --gray-500: #8e9398; /* 흰색 */ @@ -101,6 +104,7 @@ /* Text Colors - Primitive 참조 */ --color-content-primary: var(--black); --color-content-secondary: var(--gray-500); + --color-content-tertiary: var(--gray-400); --color-content-inverse: var(--blue-50); /* Layout Spacing */ diff --git a/src/shared/styles/tokens.ts b/src/shared/styles/tokens.ts new file mode 100644 index 0000000..2661aa6 --- /dev/null +++ b/src/shared/styles/tokens.ts @@ -0,0 +1,35 @@ +/** + * RN style 객체에서 사용하기 위한 토큰 상수. + * + * NOTE: + * - React Native style은 `var(--token)`을 직접 해석하지 못하므로, + * `global.styles.css`의 토큰 값을 여기에도 "동일한 값"으로 유지합니다. + * - 값 변경 시 `global.styles.css`와 함께 수정하세요. + */ + +export const colorTokens = { + /** global.styles.css: --color-primary (blue-500) */ + primary: "#0068FE", + /** global.styles.css: --color-primary-tint (blue-100) */ + primaryTint: "#E5F6FE", + + /** global.styles.css: --color-neutral (gray-100) */ + neutral: "#F4F4F5", + /** global.styles.css: --color-neutral-variant (gray-300) */ + neutralVariant: "#DBDDE1", + + /** global.styles.css: --color-canvas (white) */ + canvas: "#FEFFFE", + + /** global.styles.css: --color-danger (red-500) */ + danger: "#FF6562", + + /** global.styles.css: --color-content-primary (black) */ + contentPrimary: "#040404", + /** global.styles.css: --color-content-secondary (gray-500) */ + contentSecondary: "#8E9398", + /** global.styles.css: --color-content-tertiary (gray-400) */ + contentTertiary: "#B4B4B4", + /** global.styles.css: --color-content-inverse (blue-50) */ + contentInverse: "#F4F6FE", +} as const; diff --git a/tailwind.config.js b/tailwind.config.js index e643e37..63e86ff 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -22,6 +22,7 @@ module.exports = { // 글자 색상 "content-primary": "var(--color-content-primary)", "content-secondary": "var(--color-content-secondary)", + "content-tertiary": "var(--color-content-tertiary)", "content-inverse": "var(--color-content-inverse)", }, opacity: { From d216909ed5cb6e47ed3ca2187eb6b9b17ef62e44 Mon Sep 17 00:00:00 2001 From: KimEunHye Date: Wed, 18 Feb 2026 17:22:25 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[FEAT/#12]=20BottomTabBar=20=EC=9C=84?= =?UTF-8?q?=EC=A0=AF=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(tabs)/_layout.tsx | 36 ++++-- src/entities/user/model/types.ts | 1 + src/widgets/bottom-tab-bar/model/tabConfig.ts | 109 ++++++++++++++++++ .../bottom-tab-bar/ui/BottomTabBar.tsx | 48 ++++++++ .../bottom-tab-bar/ui/BottomTabItem.tsx | 38 ++++++ 5 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 src/entities/user/model/types.ts create mode 100644 src/widgets/bottom-tab-bar/model/tabConfig.ts create mode 100644 src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx create mode 100644 src/widgets/bottom-tab-bar/ui/BottomTabItem.tsx diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx index ef71d9f..32b4162 100644 --- a/src/app/(tabs)/_layout.tsx +++ b/src/app/(tabs)/_layout.tsx @@ -1,15 +1,35 @@ +import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"; import { Tabs } from "expo-router"; +import type { UserType } from "@/entities/user/model/types"; +import { BottomTabBar } from "@/widgets/bottom-tab-bar/ui/BottomTabBar"; + +// TODO: 추후 useAuthStore((s) => s.userType) 로 교체 +const TEMP_USER_TYPE: UserType = "company"; + +function TabBarAdapter({ state, navigation }: BottomTabBarProps) { + const activeRouteName = state.routes[state.index]?.name ?? "index"; + + return ( + navigation.navigate(routeName)} + /> + ); +} export default function TabLayout() { return ( - - null, - }} - /> + } + screenOptions={{ headerShown: false }} + > + + + + + + ); } diff --git a/src/entities/user/model/types.ts b/src/entities/user/model/types.ts new file mode 100644 index 0000000..8364375 --- /dev/null +++ b/src/entities/user/model/types.ts @@ -0,0 +1 @@ +export type UserType = "customer" | "manager" | "company"; diff --git a/src/widgets/bottom-tab-bar/model/tabConfig.ts b/src/widgets/bottom-tab-bar/model/tabConfig.ts new file mode 100644 index 0000000..d5dad59 --- /dev/null +++ b/src/widgets/bottom-tab-bar/model/tabConfig.ts @@ -0,0 +1,109 @@ +import type { Ionicons } from "@expo/vector-icons"; +import type { ComponentProps } from "react"; +import type { UserType } from "@/entities/user/model/types"; + +type IoniconName = ComponentProps["name"]; + +/** + * TODO: SVG 머지 후 activeIconName/inactiveIconName 필드를 + * Icon: ComponentType<{ size: number; color: string }> 으로 교체 + */ +export interface TabItem { + route: string; + label: string; + activeIconName: IoniconName; + inactiveIconName: IoniconName; +} + +export const TAB_CONFIG: Record = { + customer: [ + { + route: "index", + label: "홈", + activeIconName: "home", + inactiveIconName: "home-outline", + }, + { + route: "nearby", + label: "내 주변", + activeIconName: "location", + inactiveIconName: "location-outline", + }, + { + route: "coupons", + label: "제휴권의함", + activeIconName: "pricetag", + inactiveIconName: "pricetag-outline", + }, + { + route: "account", + label: "계정관리", + activeIconName: "person", + inactiveIconName: "person-outline", + }, + ], + manager: [ + { + route: "index", + label: "홈", + activeIconName: "home", + inactiveIconName: "home-outline", + }, + { + route: "nearby", + label: "주변 매장", + activeIconName: "location", + inactiveIconName: "location-outline", + }, + { + route: "dashboard", + label: "대시보드", + activeIconName: "bar-chart", + inactiveIconName: "bar-chart-outline", + }, + { + route: "chat", + label: "채팅", + activeIconName: "chatbubble", + inactiveIconName: "chatbubble-outline", + }, + { + route: "account", + label: "계정관리", + activeIconName: "person", + inactiveIconName: "person-outline", + }, + ], + company: [ + { + route: "index", + label: "홈", + activeIconName: "home", + inactiveIconName: "home-outline", + }, + { + route: "nearby", + label: "주변 업체", + activeIconName: "location", + inactiveIconName: "location-outline", + }, + { + route: "dashboard", + label: "대시보드", + activeIconName: "bar-chart", + inactiveIconName: "bar-chart-outline", + }, + { + route: "chat", + label: "채팅", + activeIconName: "chatbubble", + inactiveIconName: "chatbubble-outline", + }, + { + route: "account", + label: "계정관리", + activeIconName: "person", + inactiveIconName: "person-outline", + }, + ], +}; diff --git a/src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx b/src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx new file mode 100644 index 0000000..ad9ccac --- /dev/null +++ b/src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx @@ -0,0 +1,48 @@ +import { StyleSheet, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import type { UserType } from "@/entities/user/model/types"; +import { shadows } from "@/shared/styles/shadows"; +import { TAB_CONFIG } from "../model/tabConfig"; +import { BottomTabItem } from "./BottomTabItem"; + +interface BottomTabBarProps { + userType: UserType; + activeRouteName: string; + onTabPress: (routeName: string) => void; +} + +export function BottomTabBar({ + userType, + activeRouteName, + onTabPress, +}: BottomTabBarProps) { + const insets = useSafeAreaInsets(); + const tabs = TAB_CONFIG[userType]; + + return ( + + {tabs.map((tab) => ( + onTabPress(tab.route)} + /> + ))} + + ); +} diff --git a/src/widgets/bottom-tab-bar/ui/BottomTabItem.tsx b/src/widgets/bottom-tab-bar/ui/BottomTabItem.tsx new file mode 100644 index 0000000..450f1c6 --- /dev/null +++ b/src/widgets/bottom-tab-bar/ui/BottomTabItem.tsx @@ -0,0 +1,38 @@ +import { Ionicons } from "@expo/vector-icons"; +import type { ComponentProps } from "react"; +import { Pressable, Text } from "react-native"; +import { colorTokens } from "@/shared/styles/tokens"; + +type IoniconName = ComponentProps["name"]; + +interface BottomTabItemProps { + label: string; + activeIconName: IoniconName; + inactiveIconName: IoniconName; + isActive: boolean; + onPress: () => void; +} + +export function BottomTabItem({ + label, + activeIconName, + inactiveIconName, + isActive, + onPress, +}: BottomTabItemProps) { + const color = isActive ? colorTokens.primary : colorTokens.contentTertiary; + const iconName = isActive ? activeIconName : inactiveIconName; + + return ( + + + + {label} + + + ); +} From 5a70681eb3e40a8e3f3e067d0f41ec3511f35980 Mon Sep 17 00:00:00 2001 From: KimEunHye Date: Wed, 18 Feb 2026 17:23:58 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[ADD/#12]=20=ED=83=AD=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(tabs)/account.tsx | 10 ++++++++++ src/app/(tabs)/chat.tsx | 10 ++++++++++ src/app/(tabs)/coupons.tsx | 10 ++++++++++ src/app/(tabs)/dashboard.tsx | 10 ++++++++++ src/app/(tabs)/nearby.tsx | 10 ++++++++++ 5 files changed, 50 insertions(+) create mode 100644 src/app/(tabs)/account.tsx create mode 100644 src/app/(tabs)/chat.tsx create mode 100644 src/app/(tabs)/coupons.tsx create mode 100644 src/app/(tabs)/dashboard.tsx create mode 100644 src/app/(tabs)/nearby.tsx diff --git a/src/app/(tabs)/account.tsx b/src/app/(tabs)/account.tsx new file mode 100644 index 0000000..16639e1 --- /dev/null +++ b/src/app/(tabs)/account.tsx @@ -0,0 +1,10 @@ +import { Text, View } from "react-native"; + +// TODO: @/pages/account-page/ui/AccountPage 로 교체 +export default function AccountScreen() { + return ( + + 계정관리 + + ); +} diff --git a/src/app/(tabs)/chat.tsx b/src/app/(tabs)/chat.tsx new file mode 100644 index 0000000..a5ce16f --- /dev/null +++ b/src/app/(tabs)/chat.tsx @@ -0,0 +1,10 @@ +import { Text, View } from "react-native"; + +// TODO: @/pages/chat-page/ui/ChatPage 로 교체 +export default function ChatScreen() { + return ( + + 채팅 + + ); +} diff --git a/src/app/(tabs)/coupons.tsx b/src/app/(tabs)/coupons.tsx new file mode 100644 index 0000000..313f895 --- /dev/null +++ b/src/app/(tabs)/coupons.tsx @@ -0,0 +1,10 @@ +import { Text, View } from "react-native"; + +// TODO: @/pages/coupons-page/ui/CouponsPage 로 교체 +export default function CouponsScreen() { + return ( + + 제휴권의함 + + ); +} diff --git a/src/app/(tabs)/dashboard.tsx b/src/app/(tabs)/dashboard.tsx new file mode 100644 index 0000000..11e638e --- /dev/null +++ b/src/app/(tabs)/dashboard.tsx @@ -0,0 +1,10 @@ +import { Text, View } from "react-native"; + +// TODO: @/pages/dashboard-page/ui/DashboardPage 로 교체 +export default function DashboardScreen() { + return ( + + 대시보드 + + ); +} diff --git a/src/app/(tabs)/nearby.tsx b/src/app/(tabs)/nearby.tsx new file mode 100644 index 0000000..e29a319 --- /dev/null +++ b/src/app/(tabs)/nearby.tsx @@ -0,0 +1,10 @@ +import { Text, View } from "react-native"; + +// TODO: @/pages/nearby-page/ui/NearbyPage 로 교체 +export default function NearbyScreen() { + return ( + + 내 주변 + + ); +} From b91579f9b9e7c75751be29aedc73e4d999090efc Mon Sep 17 00:00:00 2001 From: KimEunHye Date: Wed, 18 Feb 2026 17:26:37 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[CHORE/#12]=20gitignore=EC=97=90=20claude?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index f8c6c2e..e97e825 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ app-example # generated native folders /ios /android + +# claude +CLAUDE.md +.claude/ \ No newline at end of file From 6156bfd2bf3dd94a0db93d624d0330bd50ddf227 Mon Sep 17 00:00:00 2001 From: KimEunHye Date: Thu, 19 Feb 2026 13:53:10 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[MOD/#12]=20gemini=20code=20review=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(tabs)/_layout.tsx | 5 +---- src/shared/styles/tokens.ts | 2 ++ src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx index 32b4162..622da57 100644 --- a/src/app/(tabs)/_layout.tsx +++ b/src/app/(tabs)/_layout.tsx @@ -20,10 +20,7 @@ function TabBarAdapter({ state, navigation }: BottomTabBarProps) { export default function TabLayout() { return ( - } - screenOptions={{ headerShown: false }} - > + diff --git a/src/shared/styles/tokens.ts b/src/shared/styles/tokens.ts index 2661aa6..a4c5a3f 100644 --- a/src/shared/styles/tokens.ts +++ b/src/shared/styles/tokens.ts @@ -28,6 +28,8 @@ export const colorTokens = { contentPrimary: "#040404", /** global.styles.css: --color-content-secondary (gray-500) */ contentSecondary: "#8E9398", + /** contentSecondary at opacity 0.3 — RN은 hex + opacity 조합 불가하므로 별도 정의 */ + contentSecondaryAlpha30: "rgba(142, 147, 152, 0.3)", /** global.styles.css: --color-content-tertiary (gray-400) */ contentTertiary: "#B4B4B4", /** global.styles.css: --color-content-inverse (blue-50) */ diff --git a/src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx b/src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx index ad9ccac..803f4ff 100644 --- a/src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx +++ b/src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx @@ -2,6 +2,7 @@ import { StyleSheet, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import type { UserType } from "@/entities/user/model/types"; import { shadows } from "@/shared/styles/shadows"; +import { colorTokens } from "@/shared/styles/tokens"; import { TAB_CONFIG } from "../model/tabConfig"; import { BottomTabItem } from "./BottomTabItem"; @@ -26,7 +27,7 @@ export function BottomTabBar({ shadows.primary, { borderTopWidth: StyleSheet.hairlineWidth, - borderTopColor: "rgba(142, 147, 152, 0.3)", + borderTopColor: colorTokens.contentSecondaryAlpha30, paddingTop: 5, paddingHorizontal: 10, paddingBottom: insets.bottom + 7,