From a8f619f654f225b633f80fcb04b7bddbef9b9497 Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 12:54:31 +0900 Subject: [PATCH 1/8] Style:#321 - remove bg-background class from body --- src/app/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ce94889..dbae595 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -62,7 +62,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo return ( - + {children} From 65f8f690fdafd27a09565417942bd7fa2dd64af0 Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 12:56:15 +0900 Subject: [PATCH 2/8] Style:#321 - widen goal detail container at lg breakpoint --- src/components/goals/goalDetail/GoalDetailClient.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/goals/goalDetail/GoalDetailClient.tsx b/src/components/goals/goalDetail/GoalDetailClient.tsx index dcc46b5..19ec27b 100644 --- a/src/components/goals/goalDetail/GoalDetailClient.tsx +++ b/src/components/goals/goalDetail/GoalDetailClient.tsx @@ -67,7 +67,7 @@ const GoalDetailClient = ({ goalId }: GoalDetailClientProps) => { } return ( -
+
{/* 목표 정보 헤더 */}
From 7e1de8656417d5742ad742fc5e94d3c67cd0ff5c Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 13:04:31 +0900 Subject: [PATCH 3/8] Style:#321 - add responsive size and text for sideNote button --- src/components/ui/Button.tsx | 2 +- src/components/ui/NoteSidebar/NoteDetailReadMode.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index cf78d87..f50443f 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -49,7 +49,7 @@ const buttonVariants = cva('flex cursor-pointer items-center justify-center', { schedule: 'h-48 w-120 sm:w-165.5', scheduleDashboard: 'h-40 w-120 md:w-160', tempNote: 'h-40 w-84', - sideNote: 'h-48 w-260', + sideNote: 'h-40 w-100 md:h-48 md:w-200 lg:w-260', error: 'h-44 w-200', eula: 'h-48 w-520', }, diff --git a/src/components/ui/NoteSidebar/NoteDetailReadMode.tsx b/src/components/ui/NoteSidebar/NoteDetailReadMode.tsx index c636820..c9aaed3 100644 --- a/src/components/ui/NoteSidebar/NoteDetailReadMode.tsx +++ b/src/components/ui/NoteSidebar/NoteDetailReadMode.tsx @@ -40,7 +40,8 @@ const NoteDetailReadMode = ({ note, onEdit, goalTitle, todoTitle }: NoteDetailRe className="bg-primary-01 text-white" icon={} > - 수정하기 + 수정 + 수정하기
From c27d3b5d4f17d648de648ed05922ff98f068fb8b Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 13:38:52 +0900 Subject: [PATCH 4/8] Style:321 - match GoalSelector dropdown to trigger width and widen layout --- src/components/notes/NotesClient.tsx | 4 ++-- src/components/todos/GoalSelector.tsx | 2 ++ src/components/ui/DropdownMenu.tsx | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/notes/NotesClient.tsx b/src/components/notes/NotesClient.tsx index ac3f730..11ddd62 100644 --- a/src/components/notes/NotesClient.tsx +++ b/src/components/notes/NotesClient.tsx @@ -63,7 +63,7 @@ const NotesClient = ({ initialGoalId }: NotesClientProps) => { }; return ( -
+

@@ -74,7 +74,7 @@ const NotesClient = ({ initialGoalId }: NotesClientProps) => {
-
+
diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx index 27fb010..189142a 100644 --- a/src/components/ui/DropdownMenu.tsx +++ b/src/components/ui/DropdownMenu.tsx @@ -25,7 +25,7 @@ const dropdownVariants = cva('rounded-b-[20px] border shadow-lg', { lg: 'max-w-md min-w-64', auto: 'w-auto max-w-xs', goalListFilter: 'max-w-148', - todo: 'max-w-520', + todo: 'max-w-1296', full: 'w-auto', }, animation: { @@ -56,6 +56,7 @@ export interface DropdownMenuProps extends VariantProps position?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'; matchTriggerWidth?: boolean; zIndex?: number; + offsetPx?: number; } const DropdownMenu = ({ @@ -70,6 +71,7 @@ const DropdownMenu = ({ position = 'bottom-start', matchTriggerWidth = false, zIndex = 9999, + offsetPx, }: DropdownMenuProps) => { // Floating UI 설정 const { refs, floatingStyles, context } = useFloating({ @@ -78,7 +80,7 @@ const DropdownMenu = ({ if (!open) onClose(); }, middleware: [ - offset(16), // 16px 간격 + offset(offsetPx), flip(), // 화면 경계에 닿으면 위치 자동 조정 shift(), // 화면 밖으로 나가지 않도록 이동 ...(matchTriggerWidth @@ -87,6 +89,7 @@ const DropdownMenu = ({ apply({ rects, elements }) { Object.assign(elements.floating.style, { width: `${rects.reference.width}px`, + boxSizing: 'border-box', }); }, }), From 6e94be47533fdd7ef72500a625f9243581637c26 Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 16:10:07 +0900 Subject: [PATCH 5/8] Feat:#321 - add NoteSidebarProvider for global sidebar state --- src/app/(main)/layout.tsx | 5 +++- src/app/providers/NoteSidebarProvider.tsx | 28 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/app/providers/NoteSidebarProvider.tsx diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 54f3c10..4b64b80 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -2,13 +2,16 @@ import { usePathname } from 'next/navigation'; +import { NoteSidebarProvider } from '@/app/providers/NoteSidebarProvider'; import { SidebarProvider } from '@/app/providers/SidebarProvider'; import Sidebar from '@/components/sidebar/Sidebar'; export default function MainLayout({ children }: { children: React.ReactNode }) { return ( - {children} + + {children} + ); } diff --git a/src/app/providers/NoteSidebarProvider.tsx b/src/app/providers/NoteSidebarProvider.tsx new file mode 100644 index 0000000..e7aff2f --- /dev/null +++ b/src/app/providers/NoteSidebarProvider.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { createContext, ReactNode, useContext, useState } from 'react'; + +interface NoteSidebarContextType { + isNoteSidebarOpen: boolean; + setIsNoteSidebarOpen: (value: boolean) => void; +} + +const NoteSidebarContext = createContext(undefined); + +export const useNoteSidebar = () => { + const context = useContext(NoteSidebarContext); + if (!context) { + throw new Error('useNoteSidebar must be used within a NoteSidebarProvider'); + } + return context; +}; + +export const NoteSidebarProvider = ({ children }: { children: ReactNode }) => { + const [isNoteSidebarOpen, setIsNoteSidebarOpen] = useState(false); + + return ( + + {children} + + ); +}; From 08b54d65d782b5adcb42eea548d98905f3fbed72 Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 16:10:29 +0900 Subject: [PATCH 6/8] Refactor:#321 - decouple sidebar control from NotesClient to NotesPage --- src/app/(main)/goal/notes/page.tsx | 58 +++++++++++++++++++++-- src/components/notes/NotesClient.tsx | 71 +++++++++------------------- 2 files changed, 75 insertions(+), 54 deletions(-) diff --git a/src/app/(main)/goal/notes/page.tsx b/src/app/(main)/goal/notes/page.tsx index b690c73..24b3866 100644 --- a/src/app/(main)/goal/notes/page.tsx +++ b/src/app/(main)/goal/notes/page.tsx @@ -1,4 +1,11 @@ +'use client'; + +import React, { useState } from 'react'; + +import { useNoteSidebar } from '@/app/providers/NoteSidebarProvider'; import NotesClient from '@/components/notes/NotesClient'; +import NoteSidebar from '@/components/ui/NoteSidebar/NoteSidebar'; +import { TodoWithNotes } from '@/interfaces/todo'; import { cn } from '@/lib/utils'; interface NotesPageProps { @@ -7,14 +14,55 @@ interface NotesPageProps { }>; } -const NotesPage = async ({ searchParams }: NotesPageProps) => { - const params = await searchParams; +const NotesPage = ({ searchParams }: NotesPageProps) => { + const params = React.use(searchParams); const goalId = params.goalId ? Number(params.goalId) : undefined; + const { setIsNoteSidebarOpen } = useNoteSidebar(); // 사이드바 열림 여부 + const [selectedTodo, setSelectedTodo] = useState(null); + + const handleTodoClick = (todo: TodoWithNotes) => { + setSelectedTodo(todo); + setIsNoteSidebarOpen(true); + }; + + const handleCloseSidebar = () => { + setIsNoteSidebarOpen(false); + setSelectedTodo(null); + }; + return ( -
- -
+ <> +
+ +
+ + {/* 데스크탑 사이드바 */} + {selectedTodo && ( +
+ +
+ )} + + {/* 모바일/태블릿 사이드바 */} + {selectedTodo && ( +
+ +
+ )} + ); }; diff --git a/src/components/notes/NotesClient.tsx b/src/components/notes/NotesClient.tsx index 11ddd62..bbf9f62 100644 --- a/src/components/notes/NotesClient.tsx +++ b/src/components/notes/NotesClient.tsx @@ -4,7 +4,6 @@ import { useMemo, useState } from 'react'; import TodoWithNoteList from '@/components/notes/TodoWithNoteList'; import GoalSelector from '@/components/todos/GoalSelector'; -import NoteSidebar from '@/components/ui/NoteSidebar/NoteSidebar'; import Pagination from '@/components/ui/Pagination'; import { useTodosWithNotes } from '@/hooks/useNotes'; import { TodoWithNotes } from '@/interfaces/todo'; @@ -13,19 +12,17 @@ const ITEMS_PER_PAGE = 6; // 페이지당 표시할 할 일 개수 interface NotesClientProps { initialGoalId?: number; + onTodoClick: (todo: TodoWithNotes) => void; } -const NotesClient = ({ initialGoalId }: NotesClientProps) => { +const NotesClient = ({ initialGoalId, onTodoClick }: NotesClientProps) => { const [selectedGoalId, setSelectedGoalId] = useState(initialGoalId || 0); - const [isSidebarOpen, setIsSidebarOpen] = useState(false); - const [selectedTodo, setSelectedTodo] = useState(null); const [currentPage, setCurrentPage] = useState(1); const { data: allTodosWithNotes = [] } = useTodosWithNotes( selectedGoalId === 0 ? undefined : selectedGoalId, ); - // 페이지네이션 계산 const paginatedData = useMemo(() => { const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const endIndex = startIndex + ITEMS_PER_PAGE; @@ -42,28 +39,17 @@ const NotesClient = ({ initialGoalId }: NotesClientProps) => { }; }, [allTodosWithNotes.length, currentPage]); - const handleTodoClick = (todo: TodoWithNotes) => { - setSelectedTodo(todo); - setIsSidebarOpen(true); - }; - - const handleCloseSidebar = () => { - setIsSidebarOpen(false); - setSelectedTodo(null); - }; - const handlePageChange = (page: number) => { setCurrentPage(page); }; - // 목표 변경 시 페이지 초기화 const handleGoalChange = (goalId: number) => { setSelectedGoalId(goalId); setCurrentPage(1); }; return ( -
+

@@ -72,38 +58,25 @@ const NotesClient = ({ initialGoalId }: NotesClientProps) => {

-
-
-
- -
- - - {/* 페이지네이션 */} - {paginationInfo.totalPages > 1 && ( - - )} -
-
- - {/* 할 일 클릭 시 열리는 사이드바 */} - {isSidebarOpen && selectedTodo && ( - - )} +
+
+ +
+ + + {paginationInfo.totalPages > 1 && ( + + )} +
); }; From 6b15d1d61990924450bea28c058a4c40554bc1b7 Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 16:10:57 +0900 Subject: [PATCH 7/8] Feat:#321 - implement responsive NoteSidebar layout for desktop and mobile --- src/app/(main)/layout.tsx | 12 ++- src/components/ui/NoteSidebar/NoteSidebar.tsx | 84 +++++++++++++------ 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 4b64b80..ffc2f49 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -2,7 +2,7 @@ import { usePathname } from 'next/navigation'; -import { NoteSidebarProvider } from '@/app/providers/NoteSidebarProvider'; +import { NoteSidebarProvider, useNoteSidebar } from '@/app/providers/NoteSidebarProvider'; import { SidebarProvider } from '@/app/providers/SidebarProvider'; import Sidebar from '@/components/sidebar/Sidebar'; @@ -32,10 +32,18 @@ function SidebarLayout({ children }: { children: React.ReactNode }) { } function MainContent({ children }: { children: React.ReactNode }) { + const { isNoteSidebarOpen } = useNoteSidebar(); + return (
-
{children}
+
+ {children} +
); diff --git a/src/components/ui/NoteSidebar/NoteSidebar.tsx b/src/components/ui/NoteSidebar/NoteSidebar.tsx index 4ac1e96..f0d0228 100644 --- a/src/components/ui/NoteSidebar/NoteSidebar.tsx +++ b/src/components/ui/NoteSidebar/NoteSidebar.tsx @@ -10,55 +10,85 @@ interface NoteSidebarProps { todo?: TodoWithNotes; goalTitle?: string; onClose: () => void; + isDesktop: boolean; } -const NoteSidebar = ({ isOpen, todo, goalTitle, onClose }: NoteSidebarProps) => { +// 2단계 레이어 구조: NoteListSidebar → NoteDetailSidebar +const NoteSidebar = ({ isOpen, todo, goalTitle, onClose, isDesktop }: NoteSidebarProps) => { const [currentView, setCurrentView] = useState<'note-list' | 'note-detail'>('note-list'); - const [setselectedNoteId, setsetselectedNoteId] = useState(null); + const [selectedNoteId, setSelectedNoteId] = useState(null); if (!isOpen) return null; const handleNoteClick = (noteId: number) => { setCurrentView('note-detail'); - setsetselectedNoteId(noteId); + setSelectedNoteId(noteId); }; const handleBack = () => { setCurrentView('note-list'); + setSelectedNoteId(null); }; const handleClose = () => { setCurrentView('note-list'); + setSelectedNoteId(null); onClose(); }; return ( <> - {/* 배경 오버레이 */} -
+ {/* ===== 첫 번째 레이어: NoteListSidebar ===== */} + {currentView === 'note-list' && ( + <> + {/* 데스크탑: fixed 우측 */} + {isDesktop && ( +
+ {todo && ( + + )} +
+ )} - {/* 사이드바 */} -
- {currentView === 'note-list' && todo && ( - - )} - {currentView === 'note-detail' && setselectedNoteId && todo && ( - - )} -
+ {/* 모바일/태블릿: fixed 우측 + 오버레이 */} + {!isDesktop && ( + <> +
+
+ {todo && ( + + )} +
+ + )} + + )} + + {/* ===== 두 번째 레이어: NoteDetailSidebar (모달) ===== */} + {currentView === 'note-detail' && selectedNoteId && ( + <> + {/* 배경 오버레이 - NoteListSidebar 위를 어둡게 */} +
+ + {/* NoteDetailSidebar 모달 */} +
+ {todo && ( + + )} +
+ + )} ); }; From 065b5f3b3c6f76df36d24e70ae3d39cbffd9e622 Mon Sep 17 00:00:00 2001 From: 00kang Date: Tue, 4 Nov 2025 18:19:25 +0900 Subject: [PATCH 8/8] Fix#321 - make isDesktop prop optional to resolve TS2741 build error --- src/components/ui/NoteSidebar/NoteSidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/NoteSidebar/NoteSidebar.tsx b/src/components/ui/NoteSidebar/NoteSidebar.tsx index f0d0228..5d6886e 100644 --- a/src/components/ui/NoteSidebar/NoteSidebar.tsx +++ b/src/components/ui/NoteSidebar/NoteSidebar.tsx @@ -10,7 +10,7 @@ interface NoteSidebarProps { todo?: TodoWithNotes; goalTitle?: string; onClose: () => void; - isDesktop: boolean; + isDesktop?: boolean; } // 2단계 레이어 구조: NoteListSidebar → NoteDetailSidebar