Skip to content
Merged
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: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,16 @@ src/app/index.tsx # 시작
src/app/terms.tsx # 약관 동의
src/app/profile.tsx # 프로필 작성 및 수정
src/app/settings.tsx # 설정
src/app/(tabs)/ # 하단 탭 (홈, 일정, 학생, 리포트, 수입)
src/features/onboarding/ # 화면 UI 및 데모 공유 상태
src/features/main/ # 탭 화면 UI
app.json # Expo 앱 설정
```

### 현재 구현 범위

- 시작 → 약관 동의 → 프로필 → 설정 화면
- 시작 → 약관 동의 → 프로필 → 하단 탭 화면, 홈에서 설정으로 이동
- 하단 탭 화면은 이동만 가능한 빈 화면이며 내용은 아직 구현 전입니다.
- 필수 약관 선택, 이름 필수 입력, 과목 복수 선택, 프로필 수정
- Expo Image로 번들에 포함된 SVG를 표시하며 웹 전용 절대 경로를 사용하지 않습니다.
- Safe Area 및 iOS 키보드 회피 처리
Expand Down
42 changes: 42 additions & 0 deletions src/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Link, Redirect, Tabs } from 'expo-router';
import { StyleSheet, Text, View } from 'react-native';
import { MainTabBar } from '@/features/main/MainTabBar';
import { useOnboarding } from '@/features/onboarding/OnboardingProvider';

export default function TabsLayout() {
const { profile } = useOnboarding();
if (!profile.name) return <Redirect href="/" />;
return (
<View style={s.page}>
<View style={s.phone}>
<Tabs
tabBar={(props) => <MainTabBar {...props} />}
screenOptions={{ headerTitleAlign: 'center' }}
>
<Tabs.Screen
name="home"
options={{
title: '홈',
headerRight: () => (
<Link href="/settings" style={s.settings}>
<Text style={s.settingsText}>설정</Text>
</Link>
),
}}
/>
<Tabs.Screen name="schedule" options={{ title: '일정' }} />
<Tabs.Screen name="students" options={{ title: '학생' }} />
<Tabs.Screen name="reports" options={{ title: '리포트' }} />
<Tabs.Screen name="income" options={{ title: '수입' }} />
</Tabs>
</View>
</View>
);
}

const s = StyleSheet.create({
page: { flex: 1, alignItems: 'center', backgroundColor: '#f3f4f7' },
phone: { flex: 1, width: '100%', maxWidth: 390 },
settings: { paddingHorizontal: 16, paddingVertical: 8 },
settingsText: { color: '#343845', fontSize: 14 },
});
4 changes: 4 additions & 0 deletions src/app/(tabs)/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import PlaceholderScreen from '@/features/main/PlaceholderScreen';
export default function Screen() {
return <PlaceholderScreen title="홈" />;
}
4 changes: 4 additions & 0 deletions src/app/(tabs)/income.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import PlaceholderScreen from '@/features/main/PlaceholderScreen';
export default function Screen() {
return <PlaceholderScreen title="수입" />;
}
4 changes: 4 additions & 0 deletions src/app/(tabs)/reports.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import PlaceholderScreen from '@/features/main/PlaceholderScreen';
export default function Screen() {
return <PlaceholderScreen title="리포트" />;
}
4 changes: 4 additions & 0 deletions src/app/(tabs)/schedule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import PlaceholderScreen from '@/features/main/PlaceholderScreen';
export default function Screen() {
return <PlaceholderScreen title="일정" />;
}
4 changes: 4 additions & 0 deletions src/app/(tabs)/students.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import PlaceholderScreen from '@/features/main/PlaceholderScreen';
export default function Screen() {
return <PlaceholderScreen title="학생" />;
}
68 changes: 68 additions & 0 deletions src/features/main/MainTabBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { BottomTabBarProps } from 'expo-router/js-tabs';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { primitive, semantic } from '@/styles/colors';
import { gap, padding } from '@/styles/measurement';
import { fontSize, fontWeight, letterSpacing, lineHeight } from '@/styles/typography';

// 아이콘 프레임(여백 포함) 크기
const ICON_SIZE = 32;

export function MainTabBar({
state,
descriptors,
emitter,
navigateToTab,
insets,
}: BottomTabBarProps) {
return (
<View role="tablist" style={[s.bar, { paddingBottom: padding[16] + insets.bottom }]}>
{state.routes.map((route, index) => {
const focused = index === state.index;
const { options } = descriptors[route.key];
const label = options.title ?? route.name;
const onPress = () => {
const event = emitter.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) navigateToTab(route.key);
};
return (
<Pressable
key={route.key}
role="tab"
aria-selected={focused}
accessibilityState={{ selected: focused }}
onPress={onPress}
style={s.item}
>
<View style={s.icon} />
<Text style={[s.label, focused ? s.labelActive : s.labelInactive]}>{label}</Text>
</Pressable>
);
})}
</View>
);
}

const s = StyleSheet.create({
bar: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingTop: padding[16],
paddingHorizontal: padding[24],
backgroundColor: primitive.white[0],
},
item: { alignItems: 'center', gap: gap[4] },
icon: { width: ICON_SIZE, height: ICON_SIZE },
label: {
fontSize: fontSize[12],
lineHeight: lineHeight[12],
fontWeight: fontWeight.semiBold,
letterSpacing: letterSpacing.none,
textAlign: 'center',
},
labelActive: { color: semantic.action.default },
labelInactive: { color: semantic.icon.secondary },
});
18 changes: 18 additions & 0 deletions src/features/main/PlaceholderScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { StyleSheet, Text, View } from 'react-native';

export default function PlaceholderScreen({ title }: { title: string }) {
return (
<View style={s.page}>
<Text accessibilityRole="header" style={s.title}>
{title}
</Text>
<Text style={s.body}>화면 준비 중입니다.</Text>
</View>
);
}

const s = StyleSheet.create({
page: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#fff' },
title: { color: '#343845', fontSize: 18, fontWeight: '700', marginBottom: 8 },
body: { color: '#7c8391', fontSize: 14 },
});
9 changes: 7 additions & 2 deletions src/features/onboarding/OnboardingApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,13 @@ export default function OnboardingApp({ screen }: { screen: Screen }) {
<View style={s.footer}>
{primary(editing ? '저장' : '완료', !validName, () => {
setProfile({ name: name.trim(), subjects: selected });
if (editing && router.canGoBack()) router.back();
else router.replace('/settings');
if (editing) {
if (router.canGoBack()) router.back();
else router.replace('/settings');
return;
}
if (router.canDismiss()) router.dismissAll();
router.replace('/home');
})}
</View>
)}
Expand Down
Loading