From 561d2e93b1c9afe117e9fd7a29522b22d485a69f Mon Sep 17 00:00:00 2001 From: nonshaman Date: Tue, 7 Jul 2026 04:16:29 +0900 Subject: [PATCH 1/8] =?UTF-8?q?Feat:=20=ED=99=88=ED=99=94=EB=A9=B4=20nav?= =?UTF-8?q?=EB=B0=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/_layout.tsx | 19 +-- components/ui/icon-symbol.tsx | 3 + src/components/common/BottomTabBar.tsx | 206 +++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 src/components/common/BottomTabBar.tsx diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 85f9947..fcb9233 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,36 +1,25 @@ import { Tabs } from "expo-router"; import React from "react"; -import { HapticTab } from "@/components/haptic-tab"; -import { IconSymbol } from "@/components/ui/icon-symbol"; +import { BottomTabBar } from "@/src/components/common/BottomTabBar"; import { Colors } from "@/constants/theme"; import { useColorScheme } from "@/hooks/use-color-scheme"; -// 임시 - 공통 컴포넌트 구현 후 교체 예정 -// 네비게이션 탭으로 이동하여 보여지는 페이지들을 라우팅 export default function TabLayout() { const colorScheme = useColorScheme(); return ( } screenOptions={{ tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint, headerShown: false, - tabBarButton: HapticTab, }} > - ( - - ), - }} - /> + ); -} +} \ No newline at end of file diff --git a/components/ui/icon-symbol.tsx b/components/ui/icon-symbol.tsx index a0455e1..b7a4a8b 100644 --- a/components/ui/icon-symbol.tsx +++ b/components/ui/icon-symbol.tsx @@ -18,6 +18,9 @@ type IconSymbolName = keyof typeof MAPPING; */ const MAPPING = { "house.fill": "home", + "calendar": "calendar-today", + "chart.bar.fill": "bar-chart", + "gearshape.fill": "settings", } as IconMapping; /** diff --git a/src/components/common/BottomTabBar.tsx b/src/components/common/BottomTabBar.tsx new file mode 100644 index 0000000..acd30fe --- /dev/null +++ b/src/components/common/BottomTabBar.tsx @@ -0,0 +1,206 @@ + +import { useState, useEffect } from "react"; +import { View, Pressable, Text } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"; +import { IconSymbol } from "@/components/ui/icon-symbol"; +import { cn } from "@/src/lib/cn"; +import Animated, { + useSharedValue, + useAnimatedStyle, + withSpring, + withTiming, +} from "react-native-reanimated"; +import { router } from "expo-router"; + + +const TAB_ICONS = { + index: "house.fill", + calendar: "calendar", + statistics: "chart.bar.fill", + settings: "gearshape.fill", +} as const; + +export function BottomTabBar({ state, navigation }: BottomTabBarProps) { + const insets = useSafeAreaInsets(); + const [isOpen, setIsOpen] = useState(false); + + // 애니메이션 값 설정 (0: 닫힘/플러스, 1: 열림/엑스) + const animationProgress = useSharedValue(0); + + // 열릴 때 300ms ease-out, 닫힐 때 지정된 스프링값 + useEffect(() => { + if (isOpen) { + animationProgress.value = withTiming(1, { duration: 300 }); + } else { + animationProgress.value = withSpring(0, { + mass: 1, + stiffness: 100, + damping: 15, + }); + } + }, [isOpen]); + + // 피그마 디졸브 배경색 계산 (Reanimated 스타일) + const animatedBoxStyle = useAnimatedStyle(() => { + const backgroundColor = animationProgress.value > 0.5 ? "#F5F9F6" : "#FCFDFD"; + return { + backgroundColor, + }; + }); + + return ( + + {/* ----------------- 왼쪽 박스 영역 ----------------- */} + {!isOpen ? ( + + {state.routes.map((route, index) => { + const isFocused = state.index === index; + const iconName = + TAB_ICONS[route.name as keyof typeof TAB_ICONS] ?? "house.fill"; + const onPress = () => { + const event = navigation.emit({ + type: "tabPress", + target: route.key, + canPreventDefault: true, + }); + if (!isFocused && !event.defaultPrevented) { + navigation.navigate(route.name); + } + }; + return ( + + + + ); + })} + + ) : ( + // 플러스 버튼 열린 상태 (일정등록 | 일기작성) + + {/* 일정등록 버튼 */} + { + console.log("일정등록 클릭"); // 터미널 확인용 로그 + setIsOpen(false); + router.push("/schedule"); + }} + className="h-[24px] w-[55px] flex-row items-center justify-center gap-[8px]" + > + + 일정등록 + + + + + {/* 가운데 세로 구분선 */} + + + {/* 일기작성 버튼 */} + { + console.log("일기작성 클릭"); // 터미널 확인용 로그 + setIsOpen(false); + router.push("/diary/write"); + }} + className="h-[24px] w-[55px] flex-row items-center justify-center gap-[8px]" + > + + 일기작성 + + + + + )} + + {/* ----------------- 오른쪽 박스: 플러스 / 엑스 버튼 ----------------- */} + + setIsOpen(!isOpen)} + className="h-full w-full items-center justify-center rounded-full bg-transparent" + style={{ outlineStyle: "none" } as any} + > + + + + + ); +} + +export default BottomTabBar; From fc15490205ad37f990b3ccd34b4a63c85aa58506 Mon Sep 17 00:00:00 2001 From: nonshaman Date: Tue, 7 Jul 2026 06:57:05 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Feat:=20=EC=B2=B4=ED=81=AC=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=20&=20=EC=9D=BC=EC=A0=95=EB=B0=95=EC=8A=A4=20?= =?UTF-8?q?=EA=B3=B5=EC=9A=A9=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=ED=99=88=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/index.tsx | 139 +++++++++++++++++++- src/components/common/AddScheduleButton.tsx | 2 + src/components/common/BottomTabBar.tsx | 19 ++- src/components/common/ScheduleCheckItem.tsx | 70 ++++++++++ 4 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 src/components/common/ScheduleCheckItem.tsx diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 2a69ac4..b786148 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,11 +1,138 @@ -import { Text } from "react-native"; +import { useMemo, useState } from "react"; +import { Pressable, ScrollView, Text, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { router } from "expo-router"; +import { AddScheduleButton } from "@/src/components/common/AddScheduleButton"; +import { ScheduleCheckItem } from "@/src/components/common/ScheduleCheckItem"; -import { ScreenContainer } from "@/src/components/common"; +type Schedule = { + id: string; + categoryLabel: string; + categoryColor: string; + description: string; + checked: boolean; +}; + +const MOCK_SCHEDULES: Schedule[] = [ + { id: "1", categoryLabel: "CATEGORY", categoryColor: "#5B8FD9", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, + { id: "2", categoryLabel: "CATEGORY", categoryColor: "#D9A05B", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, + { id: "3", categoryLabel: "CATEGORY", categoryColor: "#5FAE72", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, + { id: "4", categoryLabel: "CATEGORY", categoryColor: "#9B7ED9", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, + { id: "5", categoryLabel: "CATEGORY", categoryColor: "#D95B8F", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, +]; + +function useFormattedDate() { + return useMemo(() => { + const days = ["일", "월", "화", "수", "목", "금", "토"]; + const now = new Date(); + return `${now.getMonth() + 1}월 ${now.getDate()}일 ${days[now.getDay()]}요일`; + }, []); +} export default function HomeScreen() { + const insets = useSafeAreaInsets(); + const dateLabel = useFormattedDate(); + const [schedules, setSchedules] = useState(MOCK_SCHEDULES); + const hasSchedules = schedules.length > 0; + + const toggleSchedule = (id: string) => { + setSchedules((prev) => + prev.map((s) => (s.id === id ? { ...s, checked: !s.checked } : s)) + ); + }; + return ( - - - + + + {/* 로고 영역 */} + + + DA·LOG + + + + {/* 날짜 */} + + {dateLabel} + + + {/* 일정 카드 */} + + + {hasSchedules + ? "오늘 계획한 일정은 모두 마치셨나요?" + : "오늘 계획된 일정이 없어요."} + + + {hasSchedules && + schedules.map((s) => ( + toggleSchedule(s.id)} + /> + ))} + + + router.push("/schedule")} /> + + + + {/* 오늘의 질문 */} + + + 오늘의 질문 + + + + 질문 받기 + + + + + ); -} +} \ No newline at end of file diff --git a/src/components/common/AddScheduleButton.tsx b/src/components/common/AddScheduleButton.tsx index 75a1cc1..8123301 100644 --- a/src/components/common/AddScheduleButton.tsx +++ b/src/components/common/AddScheduleButton.tsx @@ -26,3 +26,5 @@ export function AddScheduleButton({ ); } + +export default AddScheduleButton; diff --git a/src/components/common/BottomTabBar.tsx b/src/components/common/BottomTabBar.tsx index acd30fe..72e77fc 100644 --- a/src/components/common/BottomTabBar.tsx +++ b/src/components/common/BottomTabBar.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { View, Pressable, Text } from "react-native"; +import { View, Pressable, Text, useWindowDimensions } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"; import { IconSymbol } from "@/components/ui/icon-symbol"; @@ -25,6 +25,9 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { const insets = useSafeAreaInsets(); const [isOpen, setIsOpen] = useState(false); + // 현재 폰의 가로 사이즈를 자동으로 가져옴 + const { width } = useWindowDimensions(); + // 애니메이션 값 설정 (0: 닫힘/플러스, 1: 열림/엑스) const animationProgress = useSharedValue(0); @@ -50,13 +53,17 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { }); return ( - 0 ? insets.bottom + 10 : 30 + }} > {/* ----------------- 왼쪽 박스 영역 ----------------- */} {!isOpen ? ( void; +}; + +export function ScheduleCheckItem({ + categoryLabel, + categoryColor, + description, + checked, + onToggle, +}: ScheduleCheckItemProps) { + return ( + + {/* 왼쪽: 카테고리 dot + 텍스트 */} + + + + + {categoryLabel.toUpperCase()} + + + {description} + + + + + {/* 오른쪽: 체크박스 (공용 컴포넌트) */} + + {checked && } + + + ); +} + +export default ScheduleCheckItem; \ No newline at end of file From 8f1a7c386b50cc36e8949db06f3596e391ea7bcb Mon Sep 17 00:00:00 2001 From: Hyochan Date: Wed, 8 Jul 2026 17:52:02 +0900 Subject: [PATCH 3/8] =?UTF-8?q?Chore:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/icons/diary.svg | 4 ++++ assets/icons/schedule.svg | 11 +++++++++++ assets/icons/tab-calendar.svg | 6 ++++++ assets/icons/tab-cancle.svg | 4 ++++ assets/icons/tab-home.svg | 3 +++ assets/icons/tab-plus.svg | 4 ++++ assets/icons/tab-setting.svg | 10 ++++++++++ assets/icons/tab-statistic.svg | 7 +++++++ 8 files changed, 49 insertions(+) create mode 100644 assets/icons/diary.svg create mode 100644 assets/icons/schedule.svg create mode 100644 assets/icons/tab-calendar.svg create mode 100644 assets/icons/tab-cancle.svg create mode 100644 assets/icons/tab-home.svg create mode 100644 assets/icons/tab-plus.svg create mode 100644 assets/icons/tab-setting.svg create mode 100644 assets/icons/tab-statistic.svg diff --git a/assets/icons/diary.svg b/assets/icons/diary.svg new file mode 100644 index 0000000..c2710dc --- /dev/null +++ b/assets/icons/diary.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/schedule.svg b/assets/icons/schedule.svg new file mode 100644 index 0000000..0374d6d --- /dev/null +++ b/assets/icons/schedule.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/tab-calendar.svg b/assets/icons/tab-calendar.svg new file mode 100644 index 0000000..0995103 --- /dev/null +++ b/assets/icons/tab-calendar.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/tab-cancle.svg b/assets/icons/tab-cancle.svg new file mode 100644 index 0000000..b396f4d --- /dev/null +++ b/assets/icons/tab-cancle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/tab-home.svg b/assets/icons/tab-home.svg new file mode 100644 index 0000000..af74e12 --- /dev/null +++ b/assets/icons/tab-home.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/tab-plus.svg b/assets/icons/tab-plus.svg new file mode 100644 index 0000000..0450019 --- /dev/null +++ b/assets/icons/tab-plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/tab-setting.svg b/assets/icons/tab-setting.svg new file mode 100644 index 0000000..5389c35 --- /dev/null +++ b/assets/icons/tab-setting.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/tab-statistic.svg b/assets/icons/tab-statistic.svg new file mode 100644 index 0000000..4db25da --- /dev/null +++ b/assets/icons/tab-statistic.svg @@ -0,0 +1,7 @@ + + + + + + + From 01350611afbf7147222f1dce4f2b1082cf1f2ba3 Mon Sep 17 00:00:00 2001 From: Hyochan Date: Wed, 8 Jul 2026 17:52:15 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Refactor:=20=ED=95=98=EB=8B=A8=20=ED=83=AD?= =?UTF-8?q?=EB=B0=94=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/BottomTabBar.tsx | 78 +++++++++++++------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/src/components/common/BottomTabBar.tsx b/src/components/common/BottomTabBar.tsx index 72e77fc..030cf67 100644 --- a/src/components/common/BottomTabBar.tsx +++ b/src/components/common/BottomTabBar.tsx @@ -1,24 +1,29 @@ - -import { useState, useEffect } from "react"; -import { View, Pressable, Text, useWindowDimensions } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"; -import { IconSymbol } from "@/components/ui/icon-symbol"; +import DiaryIcon from "@/assets/icons/diary.svg"; +import ScheduleIcon from "@/assets/icons/schedule.svg"; +import TabCalendarIcon from "@/assets/icons/tab-calendar.svg"; +import TabCancleIcon from "@/assets/icons/tab-cancle.svg"; +import TabHomeIcon from "@/assets/icons/tab-home.svg"; +import TabPlusIcon from "@/assets/icons/tab-plus.svg"; +import TabSettingIcon from "@/assets/icons/tab-setting.svg"; +import TabStatisticIcon from "@/assets/icons/tab-statistic.svg"; import { cn } from "@/src/lib/cn"; +import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"; +import { router } from "expo-router"; +import { useEffect, useState } from "react"; +import { Pressable, Text, useWindowDimensions, View } from "react-native"; import Animated, { - useSharedValue, useAnimatedStyle, + useSharedValue, withSpring, withTiming, } from "react-native-reanimated"; -import { router } from "expo-router"; - +import { useSafeAreaInsets } from "react-native-safe-area-context"; const TAB_ICONS = { - index: "house.fill", - calendar: "calendar", - statistics: "chart.bar.fill", - settings: "gearshape.fill", + index: TabHomeIcon, + calendar: TabCalendarIcon, + statistics: TabStatisticIcon, + settings: TabSettingIcon, } as const; export function BottomTabBar({ state, navigation }: BottomTabBarProps) { @@ -46,7 +51,8 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { // 피그마 디졸브 배경색 계산 (Reanimated 스타일) const animatedBoxStyle = useAnimatedStyle(() => { - const backgroundColor = animationProgress.value > 0.5 ? "#F5F9F6" : "#FCFDFD"; + const backgroundColor = + animationProgress.value > 0.5 ? "#F5F9F6" : "#FCFDFD"; return { backgroundColor, }; @@ -55,15 +61,15 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { return ( 0 ? insets.bottom + 10 : 30 + style={{ + width: width - 32, + bottom: insets.bottom > 0 ? insets.bottom + 10 : 30, }} > {/* ----------------- 왼쪽 박스 영역 ----------------- */} {!isOpen ? ( {state.routes.map((route, index) => { const isFocused = state.index === index; - const iconName = - TAB_ICONS[route.name as keyof typeof TAB_ICONS] ?? "house.fill"; + const TabIcon = + TAB_ICONS[route.name as keyof typeof TAB_ICONS] ?? TabHomeIcon; const onPress = () => { const event = navigation.emit({ type: "tabPress", @@ -96,10 +102,10 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { )} style={{ outlineStyle: "none" } as any} > - ); @@ -108,7 +114,7 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { ) : ( // 플러스 버튼 열린 상태 (일정등록 | 일기작성) 일정등록 - + {/* 가운데 세로 구분선 */} @@ -171,11 +173,7 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { > 일기작성 - + )} @@ -199,11 +197,11 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { className="h-full w-full items-center justify-center rounded-full bg-transparent" style={{ outlineStyle: "none" } as any} > - + {isOpen ? ( + + ) : ( + + )} From 72263c08fc5152bc94ce68710c364c6abc22378f Mon Sep 17 00:00:00 2001 From: Yewon Date: Wed, 8 Jul 2026 20:15:42 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Chore:=20=EC=B2=B4=ED=81=AC=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=EA=B4=80=EB=A0=A8=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/icons/box.svg | 3 +++ assets/icons/checkbox.svg | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 assets/icons/box.svg create mode 100644 assets/icons/checkbox.svg diff --git a/assets/icons/box.svg b/assets/icons/box.svg new file mode 100644 index 0000000..e828def --- /dev/null +++ b/assets/icons/box.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/checkbox.svg b/assets/icons/checkbox.svg new file mode 100644 index 0000000..3aa1b31 --- /dev/null +++ b/assets/icons/checkbox.svg @@ -0,0 +1,4 @@ + + + + From 0106994e300602177dc1867f5cd4022b718eff36 Mon Sep 17 00:00:00 2001 From: nonshaman Date: Thu, 9 Jul 2026 21:43:10 +0900 Subject: [PATCH 6/8] =?UTF-8?q?Refactor:=20=ED=9A=A8=EC=B0=AC=EB=8B=98=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81(Na?= =?UTF-8?q?tiveWind=20=EB=B0=8F=20=EB=94=94=EC=9E=90=EC=9D=B8=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/BottomTabBar.tsx | 70 +++++++------------------- 1 file changed, 19 insertions(+), 51 deletions(-) diff --git a/src/components/common/BottomTabBar.tsx b/src/components/common/BottomTabBar.tsx index 030cf67..9e78f1e 100644 --- a/src/components/common/BottomTabBar.tsx +++ b/src/components/common/BottomTabBar.tsx @@ -26,6 +26,14 @@ const TAB_ICONS = { settings: TabSettingIcon, } as const; +const TAB_BAR_SHADOW_STYLE = { + shadowColor: "#4D826C", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.14, + shadowRadius: 16, + elevation: 6, +} as const; + export function BottomTabBar({ state, navigation }: BottomTabBarProps) { const insets = useSafeAreaInsets(); const [isOpen, setIsOpen] = useState(false); @@ -69,14 +77,8 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { {/* ----------------- 왼쪽 박스 영역 ----------------- */} {!isOpen ? ( {state.routes.map((route, index) => { const isFocused = state.index === index; @@ -97,10 +99,9 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { key={route.key} onPress={onPress} className={cn( - "h-[40px] w-[52px] flex-shrink-0 items-center justify-center rounded-full p-[10px]", - isFocused ? "bg-[#4D826C]" : "bg-[#FCFDFD]" + "h-[40px] w-[52px] flex-shrink-0 items-center justify-center rounded-full p-[10px] web:outline-none", + isFocused ? "bg-green-600" : "bg-gray-0" )} - style={{ outlineStyle: "none" } as any} > {/* 일정등록 버튼 */} - + 일정등록 {/* 가운데 세로 구분선 */} - + {/* 일기작성 버튼 */} - + 일기작성 @@ -183,19 +158,12 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { className="h-[60px] w-[60px] items-center justify-center rounded-full p-[10px]" style={[ animatedBoxStyle, - { - shadowColor: "#4D826C", - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.14, - shadowRadius: 16, - elevation: 6, - }, + TAB_BAR_SHADOW_STYLE, ]} > setIsOpen(!isOpen)} - className="h-full w-full items-center justify-center rounded-full bg-transparent" - style={{ outlineStyle: "none" } as any} + className="h-full w-full items-center justify-center rounded-full bg-transparent web:outline-none" > {isOpen ? ( From 863d46bc6cd13f06d8bbf5bf92a35c367b4fd0de Mon Sep 17 00:00:00 2001 From: nonshaman Date: Thu, 9 Jul 2026 22:16:09 +0900 Subject: [PATCH 7/8] =?UTF-8?q?Chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/extensions.json | 5 +-- app/(tabs)/_layout.tsx | 2 +- app/(tabs)/index.tsx | 47 ++++++++++++++++++--- components/ui/icon-symbol.tsx | 2 +- src/components/common/AddButton.tsx | 2 +- src/components/common/AddScheduleButton.tsx | 2 +- src/components/common/BottomTabBar.tsx | 9 ++-- src/components/common/CategoryCircle.tsx | 4 +- src/components/common/DiaryCard.tsx | 27 ++++-------- src/components/common/ScheduleCheckItem.tsx | 6 ++- src/components/common/TextField.tsx | 17 +++----- src/types/diaries/diaryCard.types.ts | 11 +---- 12 files changed, 71 insertions(+), 63 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 473344b..d203c19 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,3 @@ { - "recommendations": [ - "expo.vscode-expo-tools", - "bradlc.vscode-tailwindcss" - ] + "recommendations": ["expo.vscode-expo-tools", "bradlc.vscode-tailwindcss"] } diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index fcb9233..8cfbd5a 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -22,4 +22,4 @@ export default function TabLayout() { ); -} \ No newline at end of file +} diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index b786148..3224fbb 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -14,11 +14,41 @@ type Schedule = { }; const MOCK_SCHEDULES: Schedule[] = [ - { id: "1", categoryLabel: "CATEGORY", categoryColor: "#5B8FD9", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, - { id: "2", categoryLabel: "CATEGORY", categoryColor: "#D9A05B", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, - { id: "3", categoryLabel: "CATEGORY", categoryColor: "#5FAE72", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, - { id: "4", categoryLabel: "CATEGORY", categoryColor: "#9B7ED9", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, - { id: "5", categoryLabel: "CATEGORY", categoryColor: "#D95B8F", description: "Lorem ipsum dolor sit amet consectetur.", checked: false }, + { + id: "1", + categoryLabel: "CATEGORY", + categoryColor: "#5B8FD9", + description: "Lorem ipsum dolor sit amet consectetur.", + checked: false, + }, + { + id: "2", + categoryLabel: "CATEGORY", + categoryColor: "#D9A05B", + description: "Lorem ipsum dolor sit amet consectetur.", + checked: false, + }, + { + id: "3", + categoryLabel: "CATEGORY", + categoryColor: "#5FAE72", + description: "Lorem ipsum dolor sit amet consectetur.", + checked: false, + }, + { + id: "4", + categoryLabel: "CATEGORY", + categoryColor: "#9B7ED9", + description: "Lorem ipsum dolor sit amet consectetur.", + checked: false, + }, + { + id: "5", + categoryLabel: "CATEGORY", + categoryColor: "#D95B8F", + description: "Lorem ipsum dolor sit amet consectetur.", + checked: false, + }, ]; function useFormattedDate() { @@ -127,7 +157,10 @@ export default function HomeScreen() { className="w-full items-center justify-center rounded-2xl py-4" style={{ backgroundColor: "#4D826C", outlineStyle: "none" } as any} > - + 질문 받기 @@ -135,4 +168,4 @@ export default function HomeScreen() { ); -} \ No newline at end of file +} diff --git a/components/ui/icon-symbol.tsx b/components/ui/icon-symbol.tsx index b7a4a8b..70eea76 100644 --- a/components/ui/icon-symbol.tsx +++ b/components/ui/icon-symbol.tsx @@ -18,7 +18,7 @@ type IconSymbolName = keyof typeof MAPPING; */ const MAPPING = { "house.fill": "home", - "calendar": "calendar-today", + calendar: "calendar-today", "chart.bar.fill": "bar-chart", "gearshape.fill": "settings", } as IconMapping; diff --git a/src/components/common/AddButton.tsx b/src/components/common/AddButton.tsx index bfdcec6..f5da114 100644 --- a/src/components/common/AddButton.tsx +++ b/src/components/common/AddButton.tsx @@ -18,7 +18,7 @@ export function AddButton({ label, className, ...rest }: AddButtonProps) { {...rest} > - {label} + {label} ); } diff --git a/src/components/common/AddScheduleButton.tsx b/src/components/common/AddScheduleButton.tsx index 8123301..e70daad 100644 --- a/src/components/common/AddScheduleButton.tsx +++ b/src/components/common/AddScheduleButton.tsx @@ -21,7 +21,7 @@ export function AddScheduleButton({ )} {...rest} > - {label} + {label} ); diff --git a/src/components/common/BottomTabBar.tsx b/src/components/common/BottomTabBar.tsx index 9e78f1e..940f1f7 100644 --- a/src/components/common/BottomTabBar.tsx +++ b/src/components/common/BottomTabBar.tsx @@ -127,7 +127,7 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { }} className="h-[24px] w-[55px] flex-row items-center justify-center gap-[8px]" > - + 일정등록 @@ -145,7 +145,7 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { }} className="h-[24px] w-[55px] flex-row items-center justify-center gap-[8px]" > - + 일기작성 @@ -156,10 +156,7 @@ export function BottomTabBar({ state, navigation }: BottomTabBarProps) { {/* ----------------- 오른쪽 박스: 플러스 / 엑스 버튼 ----------------- */} setIsOpen(!isOpen)} diff --git a/src/components/common/CategoryCircle.tsx b/src/components/common/CategoryCircle.tsx index d07430a..b13d770 100644 --- a/src/components/common/CategoryCircle.tsx +++ b/src/components/common/CategoryCircle.tsx @@ -58,7 +58,7 @@ export function CategoryCircle({ "h-9 w-9 items-center justify-center rounded-full", isDisabled ? CATEGORY_COLOR_CLASS_NAMES[color].soft - : CATEGORY_COLOR_CLASS_NAMES[color].solid, + : CATEGORY_COLOR_CLASS_NAMES[color].solid )} > {isSelected || isDisabled ? ( @@ -70,4 +70,4 @@ export function CategoryCircle({ ) : null} ); -} \ No newline at end of file +} diff --git a/src/components/common/DiaryCard.tsx b/src/components/common/DiaryCard.tsx index 16521af..b8c298d 100644 --- a/src/components/common/DiaryCard.tsx +++ b/src/components/common/DiaryCard.tsx @@ -1,10 +1,5 @@ import { useState } from "react"; -import { - Image, - type LayoutChangeEvent, - Text, - View, -} from "react-native"; +import { Image, type LayoutChangeEvent, Text, View } from "react-native"; import { cn } from "@/src/lib/cn"; import type { @@ -36,10 +31,7 @@ const CONTENT_LINE_COUNTS: Record< "three-images": 5, }; -const IMAGE_CONTAINER_CLASS_NAMES: Record< - DiaryCardImageVariant, - string -> = { +const IMAGE_CONTAINER_CLASS_NAMES: Record = { "one-image": "w-full items-center", "two-images": "w-full flex-row gap-2", "three-images": "w-full flex-row gap-2", @@ -58,13 +50,13 @@ export function DiaryCard(props: DiaryCardProps) { style={CARD_SHADOW_STYLE} className={cn( "h-12 w-full justify-center rounded-xl bg-gray-0 px-4", - props.containerClassName, + props.containerClassName )} > {props.summary} @@ -72,8 +64,7 @@ export function DiaryCard(props: DiaryCardProps) { ); } - const imageVariant = - props.variant === "no-image" ? null : props.variant; + const imageVariant = props.variant === "no-image" ? null : props.variant; const images = props.variant === "no-image" ? [] : props.images; @@ -88,7 +79,7 @@ export function DiaryCard(props: DiaryCardProps) { style={CARD_SHADOW_STYLE} className={cn( "w-full gap-2 rounded-xl bg-gray-0 px-4 py-3", - props.containerClassName, + props.containerClassName )} > {imageVariant ? ( @@ -111,7 +102,7 @@ export function DiaryCard(props: DiaryCardProps) { {props.title} @@ -119,11 +110,11 @@ export function DiaryCard(props: DiaryCardProps) { {props.content} ); -} \ No newline at end of file +} diff --git a/src/components/common/ScheduleCheckItem.tsx b/src/components/common/ScheduleCheckItem.tsx index d3799ce..f53eb9d 100644 --- a/src/components/common/ScheduleCheckItem.tsx +++ b/src/components/common/ScheduleCheckItem.tsx @@ -57,7 +57,9 @@ export function ScheduleCheckItem({ hitSlop={8} className={ "h-[20px] w-[20px] items-center justify-center rounded-[4px] border-[1.5px] " + - (checked ? "border-[#4D826C] bg-[#4D826C]" : "border-[#D9E3DC] bg-transparent") + (checked + ? "border-[#4D826C] bg-[#4D826C]" + : "border-[#D9E3DC] bg-transparent") } style={{ outlineStyle: "none" } as any} > @@ -67,4 +69,4 @@ export function ScheduleCheckItem({ ); } -export default ScheduleCheckItem; \ No newline at end of file +export default ScheduleCheckItem; diff --git a/src/components/common/TextField.tsx b/src/components/common/TextField.tsx index 00d6a46..a228b8d 100644 --- a/src/components/common/TextField.tsx +++ b/src/components/common/TextField.tsx @@ -1,10 +1,5 @@ import { useState } from "react"; -import { - Text, - TextInput, - type TextInputProps, - View, -} from "react-native"; +import { Text, TextInput, type TextInputProps, View } from "react-native"; import { cn } from "@/src/lib/cn"; @@ -42,7 +37,7 @@ export function TextField({ isTextarea ? "h-[108px] items-start p-3" : "h-12 items-center px-3", isFocused ? "border-gray-400" : "border-gray-200", disabled && "opacity-50", - containerClassName, + containerClassName )} > { setIsFocused(true); @@ -67,10 +62,10 @@ export function TextField({ /> {rightText ? ( - + {rightText} ) : null} ); -} \ No newline at end of file +} diff --git a/src/types/diaries/diaryCard.types.ts b/src/types/diaries/diaryCard.types.ts index fe7312f..010579c 100644 --- a/src/types/diaries/diaryCard.types.ts +++ b/src/types/diaries/diaryCard.types.ts @@ -40,11 +40,7 @@ export type DiaryCardThreeImagesProps = DiaryCardBaseProps & { variant: "three-images"; title: string; content: string; - images: [ - ImageSourcePropType, - ImageSourcePropType, - ImageSourcePropType, - ]; + images: [ImageSourcePropType, ImageSourcePropType, ImageSourcePropType]; }; export type DiaryCardProps = @@ -54,7 +50,4 @@ export type DiaryCardProps = | DiaryCardTwoImagesProps | DiaryCardThreeImagesProps; -export type DiaryCardImageVariant = - | "one-image" - | "two-images" - | "three-images"; \ No newline at end of file +export type DiaryCardImageVariant = "one-image" | "two-images" | "three-images"; From 172c5994a67be329bd970cd8c1e7fb17cd816d4d Mon Sep 17 00:00:00 2001 From: nonshaman Date: Thu, 9 Jul 2026 23:17:04 +0900 Subject: [PATCH 8/8] =?UTF-8?q?Refactor:=20=EC=98=88=EC=9B=90=EB=8B=98=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81(Sc?= =?UTF-8?q?heduleCheckItem,=20Checkbox)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/icon-symbol.tsx | 7 +-- src/components/common/AddScheduleButton.tsx | 4 +- src/components/common/Checkbox.tsx | 32 +++++++++++ src/components/common/ScheduleCheckItem.tsx | 61 ++++++--------------- 4 files changed, 52 insertions(+), 52 deletions(-) create mode 100644 src/components/common/Checkbox.tsx diff --git a/components/ui/icon-symbol.tsx b/components/ui/icon-symbol.tsx index 70eea76..db2f02c 100644 --- a/components/ui/icon-symbol.tsx +++ b/components/ui/icon-symbol.tsx @@ -16,12 +16,7 @@ type IconSymbolName = keyof typeof MAPPING; * - see Material Icons in the [Icons Directory](https://icons.expo.fyi). * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app. */ -const MAPPING = { - "house.fill": "home", - calendar: "calendar-today", - "chart.bar.fill": "bar-chart", - "gearshape.fill": "settings", -} as IconMapping; +const MAPPING = {} as IconMapping; /** * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web. diff --git a/src/components/common/AddScheduleButton.tsx b/src/components/common/AddScheduleButton.tsx index e70daad..d0de945 100644 --- a/src/components/common/AddScheduleButton.tsx +++ b/src/components/common/AddScheduleButton.tsx @@ -25,6 +25,4 @@ export function AddScheduleButton({ ); -} - -export default AddScheduleButton; +} \ No newline at end of file diff --git a/src/components/common/Checkbox.tsx b/src/components/common/Checkbox.tsx new file mode 100644 index 0000000..31d172f --- /dev/null +++ b/src/components/common/Checkbox.tsx @@ -0,0 +1,32 @@ +import { Pressable, type PressableProps } from "react-native"; + +import BoxIcon from "@/assets/icons/box.svg"; +import CheckboxIcon from "@/assets/icons/checkbox.svg"; +import { cn } from "@/src/lib/cn"; + +export type CheckboxProps = Omit & { + checked: boolean; + onToggle: () => void; + className?: string; +}; + +export function Checkbox({ + checked, + onToggle, + className, + ...rest +}: CheckboxProps) { + const Icon = checked ? CheckboxIcon : BoxIcon; + + return ( + + + + ); +} \ No newline at end of file diff --git a/src/components/common/ScheduleCheckItem.tsx b/src/components/common/ScheduleCheckItem.tsx index f53eb9d..8c146b4 100644 --- a/src/components/common/ScheduleCheckItem.tsx +++ b/src/components/common/ScheduleCheckItem.tsx @@ -1,6 +1,6 @@ -// src/components/schedule/ScheduleCheckItem.tsx -import { Pressable, Text, View } from "react-native"; -import { IconSymbol } from "@/components/ui/icon-symbol"; + +import { Text, View } from "react-native"; +import { Checkbox } from "@/src/components/common/Checkbox"; export type ScheduleCheckItemProps = { categoryLabel: string; @@ -18,53 +18,28 @@ export function ScheduleCheckItem({ onToggle, }: ScheduleCheckItemProps) { return ( - + {/* 왼쪽: 카테고리 dot + 텍스트 */} - - - - + + + + {categoryLabel.toUpperCase()} - - {description} - + + {description} + {/* 오른쪽: 체크박스 (공용 컴포넌트) */} - - {checked && } - + ); }