Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ app-example
/android

# claude
.claudeignore
CLAUDE.md
CONVENTION.md
.claude/
.claudeignore
CONVENTION.md
33 changes: 25 additions & 8 deletions src/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
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 (
<BottomTabBar
userType={TEMP_USER_TYPE}
activeRouteName={activeRouteName}
onTabPress={(routeName) => navigation.navigate(routeName)}
/>
);
}

export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: () => null,
}}
/>
<Tabs tabBar={TabBarAdapter} screenOptions={{ headerShown: false }}>
<Tabs.Screen name="index" />
<Tabs.Screen name="nearby" />
<Tabs.Screen name="coupons" />
<Tabs.Screen name="dashboard" />
<Tabs.Screen name="chat" />
<Tabs.Screen name="account" />
</Tabs>
);
}
10 changes: 10 additions & 0 deletions src/app/(tabs)/account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Text, View } from "react-native";

// TODO: @/pages/account-page/ui/AccountPage ๋กœ ๊ต์ฒด
export default function AccountScreen() {
return (
<View className="flex-1 items-center justify-center bg-canvas">
<Text className="text-content-primary font-medium">๊ณ„์ •๊ด€๋ฆฌ</Text>
</View>
);
}
10 changes: 10 additions & 0 deletions src/app/(tabs)/chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Text, View } from "react-native";

// TODO: @/pages/chat-page/ui/ChatPage ๋กœ ๊ต์ฒด
export default function ChatScreen() {
return (
<View className="flex-1 items-center justify-center bg-canvas">
<Text className="text-content-primary font-medium">์ฑ„ํŒ…</Text>
</View>
);
}
10 changes: 10 additions & 0 deletions src/app/(tabs)/coupons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Text, View } from "react-native";

// TODO: @/pages/coupons-page/ui/CouponsPage ๋กœ ๊ต์ฒด
export default function CouponsScreen() {
return (
<View className="flex-1 items-center justify-center bg-canvas">
<Text className="text-content-primary font-medium">์ œํœด๊ถŒ์˜ํ•จ</Text>
</View>
);
}
10 changes: 10 additions & 0 deletions src/app/(tabs)/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Text, View } from "react-native";

// TODO: @/pages/dashboard-page/ui/DashboardPage ๋กœ ๊ต์ฒด
export default function DashboardScreen() {
return (
<View className="flex-1 items-center justify-center bg-canvas">
<Text className="text-content-primary font-medium">๋Œ€์‹œ๋ณด๋“œ</Text>
</View>
);
}
10 changes: 10 additions & 0 deletions src/app/(tabs)/nearby.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Text, View } from "react-native";

// TODO: @/pages/nearby-page/ui/NearbyPage ๋กœ ๊ต์ฒด
export default function NearbyScreen() {
return (
<View className="flex-1 items-center justify-center bg-canvas">
<Text className="text-content-primary font-medium">๋‚ด ์ฃผ๋ณ€</Text>
</View>
);
}
1 change: 1 addition & 0 deletions src/entities/user/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type UserType = "customer" | "manager" | "company";
4 changes: 4 additions & 0 deletions src/shared/styles/global.styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
/* ํšŒ์ƒ‰ ๊ณ„์—ด */
--gray-100: #f4f4f5;
--gray-300: #dbdde1;
--gray-400: #b4b4b4;
--gray-500: #8e9398;

/* ํฐ์ƒ‰ */
Expand Down Expand Up @@ -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 */
Expand All @@ -69,6 +71,7 @@
/* ํšŒ์ƒ‰ ๊ณ„์—ด */
--gray-100: #f4f4f5;
--gray-300: #dbdde1;
--gray-400: #b4b4b4;
--gray-500: #8e9398;

/* ํฐ์ƒ‰ */
Expand Down Expand Up @@ -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 */
Expand Down
4 changes: 4 additions & 0 deletions src/shared/styles/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ 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) */
contentInverse: "#F4F6FE",
} as const;
109 changes: 109 additions & 0 deletions src/widgets/bottom-tab-bar/model/tabConfig.ts
Original file line number Diff line number Diff line change
@@ -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<typeof Ionicons>["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<UserType, TabItem[]> = {
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",
},
],
};
49 changes: 49 additions & 0 deletions src/widgets/bottom-tab-bar/ui/BottomTabBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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";

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 (
<View
className="flex-row bg-canvas"
style={[
shadows.primary,
{
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colorTokens.contentSecondaryAlpha30,
paddingTop: 5,
paddingHorizontal: 10,
paddingBottom: insets.bottom + 7,
},
]}
>
{tabs.map((tab) => (
<BottomTabItem
key={tab.route}
label={tab.label}
activeIconName={tab.activeIconName}
inactiveIconName={tab.inactiveIconName}
isActive={activeRouteName === tab.route}
onPress={() => onTabPress(tab.route)}
/>
))}
</View>
);
}
38 changes: 38 additions & 0 deletions src/widgets/bottom-tab-bar/ui/BottomTabItem.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Ionicons>["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 (
<Pressable
onPress={onPress}
className="flex-1 items-center justify-center"
style={{ height: 48, paddingVertical: 7 }}
>
<Ionicons name={iconName} size={22} color={color} />
<Text className="text-xs mt-1 font-semibold" style={{ color }}>
{label}
</Text>
</Pressable>
);
}
1 change: 1 addition & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down