-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 바텀시트 모달 추가 및 UI 수정 #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "55-feat-\uBC14\uD140\uC2DC\uD2B8-\uBAA8\uB2EC-\uCD94\uAC00-\uBC0F-ui-\uC218\uC815"
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces reusable bottom sheet and bottom modal components to improve UI consistency and adds confirmation dialogs for destructive actions (logout and account withdrawal). The refactoring extracts bottom sheet logic into a dedicated component with configurable behavior.
Changes:
- Added new
BottomSheetandBottomModalreusable components - Enhanced
useBottomSheethook with configuration options and callback support - Replaced direct modal usage with new components across Profile, MyPage, and Timer pages
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/hooks/useBottomSheet.ts | Enhanced with options object pattern, default position, and position change callback |
| src/components/common/BottomSheet.tsx | New resizable bottom sheet component with render prop support |
| src/components/common/BottomModal.tsx | New bottom modal component for confirmation dialogs |
| src/pages/Timer/index.tsx | Refactored to use new BottomSheet component, removing inline implementation |
| src/pages/User/Profile/index.tsx | Added withdrawal confirmation using BottomModal |
| src/pages/User/MyPage/index.tsx | Added logout confirmation using BottomModal |
| src/pages/Council/CouncilNotice/index.tsx | Added bottom padding for better content spacing |
| src/pages/Auth/SignUp/components/StepLayout.tsx | Updated margin calculation to use safe area insets |
| src/pages/Chat/index.tsx | Updated empty state text to be more conversational |
| src/components/layout/Header/routeTitles.ts | Added chat route title mapping |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (newPosition !== position) { | ||
| setPosition(newPosition); | ||
| onPositionChange?.(newPosition); |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The onPositionChange callback should be wrapped in useCallback or the callback ref pattern to prevent unnecessary re-renders when the callback reference changes. Currently, if the parent component passes a new function reference on each render, it could cause the hook to behave unexpectedly.
| <div className="mt-2 text-xs leading-3.5 text-indigo-300">{councilNoticeDetail.updatedAt}</div> | ||
| </div> | ||
| <div className="bg-indigo-0 flex-1 px-5 py-4 text-[13px] leading-4.5 whitespace-pre-line text-indigo-700"> | ||
| <div className="bg-indigo-0 flex-1 px-5 py-4 pb-20 text-[13px] leading-4.5 whitespace-pre-line text-indigo-700"> |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CSS classes contain conflicting padding declarations. The class 'py-4' sets both top and bottom padding, but 'pb-20' overrides the bottom padding. This makes the 'py-4' bottom padding redundant. Consider using 'pt-4 pb-20' instead for clarity and to avoid confusion about which padding value is actually applied.
| <div className="bg-indigo-0 flex-1 px-5 py-4 pb-20 text-[13px] leading-4.5 whitespace-pre-line text-indigo-700"> | |
| <div className="bg-indigo-0 flex-1 px-5 pt-4 pb-20 text-[13px] leading-4.5 whitespace-pre-line text-indigo-700"> |
| return ( | ||
| <div | ||
| ref={sheetRef} | ||
| className={clsx( | ||
| 'fixed inset-x-0 z-20 flex flex-col rounded-t-3xl bg-white transition-transform duration-300 ease-out', | ||
| resizable && isDragging && 'transition-none', | ||
| className | ||
| )} | ||
| style={{ | ||
| bottom: `${bottomOffset}px`, | ||
| height: | ||
| effectivePosition === 'full' | ||
| ? `calc(100% - ${fullTopOffset}px - ${bottomOffset}px)` | ||
| : `calc(100% - ${halfTopOffset}px - ${bottomOffset}px)`, | ||
| transform: getTransform(), | ||
| }} | ||
| > |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BottomSheet component is missing accessibility attributes. It should include role, aria-modal, and aria-label/aria-labelledby attributes to properly identify it as a sheet/dialog for screen readers. Consider adding role="region" or role="dialog" and appropriate ARIA attributes.
| > | ||
| 탈퇴하기 | ||
| </button> | ||
| <button onClick={closeModal} className="text-h3 w-full rounded-lg py-3.5 text-center text-indigo-400"> |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cancel button lacks semantic importance and could benefit from clearer styling. Currently the cancel button appears as plain text with only color differentiation, which may have insufficient contrast and could be easily missed. Consider adding a border or background to make it more visually distinct as an interactive element.
| <button onClick={closeModal} className="text-h3 w-full rounded-lg py-3.5 text-center text-indigo-400"> | |
| <button | |
| onClick={closeModal} | |
| className="text-h3 w-full rounded-lg py-3.5 text-center text-indigo-400 bg-indigo-50 border border-indigo-200" | |
| > |
| <div> | ||
| <button | ||
| onClick={() => withdraw()} | ||
| className="w-full rounded-lg bg-indigo-700 py-3.5 text-center text-lg leading-7 font-bold text-white" | ||
| className="bg-primary text-h3 w-full rounded-lg py-3.5 text-center text-white" | ||
| > | ||
| 탈퇴하기 | ||
| </button> | ||
| <button onClick={closeModal} className="text-h3 w-full rounded-lg py-3.5 text-center text-indigo-400"> | ||
| 취소 | ||
| </button> | ||
| </div> |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The button ordering places the destructive primary action (탈퇴하기/withdraw) above the cancel action. This is contrary to common UX patterns where destructive actions should typically be placed in a less prominent position to prevent accidental clicks. Consider placing the cancel button above or making the destructive action less prominent visually.
| const { position, isDragging, currentTranslate, sheetRef, handlers } = useBottomSheet({ | ||
| defaultPosition, | ||
| onPositionChange, | ||
| }); |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When resizable is false, the hook still runs and manages state unnecessarily. The handlers are created but never used, which wastes resources. Consider adding a condition to skip hook initialization when resizable is false, or create a simpler version of the component for non-resizable cases.
| export type { SheetPosition }; | ||
|
|
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exported SheetPosition type on line 3 is also re-exported on line 24 in the component file. This creates redundant exports. Consider removing one of these exports or adding a comment explaining why both are needed.
| export type { SheetPosition }; |
| }} | ||
| > | ||
| {resizable && ( | ||
| <div className="flex h-5 shrink-0 cursor-grab items-center justify-center active:cursor-grabbing" {...handlers}> |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The draggable handle element is missing keyboard accessibility. Users who cannot use touch gestures should be able to control the sheet expansion/collapse using keyboard controls. Consider making the handle focusable and adding keyboard event handlers for arrow keys or Enter/Space keys.
| <div className="flex h-5 shrink-0 cursor-grab items-center justify-center active:cursor-grabbing" {...handlers}> | |
| <div | |
| className="flex h-5 shrink-0 cursor-grab items-center justify-center active:cursor-grabbing" | |
| {...handlers} | |
| role="button" | |
| tabIndex={0} | |
| aria-label="Resize bottom sheet" | |
| onKeyDown={(event) => { | |
| if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') { | |
| event.preventDefault(); | |
| const anyHandlers = handlers as any; | |
| if (typeof anyHandlers.onMouseDown === 'function') { | |
| anyHandlers.onMouseDown(event); | |
| } else if (typeof anyHandlers.onPointerDown === 'function') { | |
| anyHandlers.onPointerDown(event); | |
| } | |
| } | |
| }} | |
| > |
| > | ||
| 로그아웃 | ||
| </button> | ||
| <button onClick={closeModal} className="text-h3 w-full rounded-lg py-3.5 text-center text-indigo-400"> |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cancel button lacks semantic importance and could benefit from clearer styling. Currently the cancel button appears as plain text with only color differentiation, which may have insufficient contrast and could be easily missed. Consider adding a border or background to make it more visually distinct as an interactive element.
| <button onClick={closeModal} className="text-h3 w-full rounded-lg py-3.5 text-center text-indigo-400"> | |
| <button | |
| type="button" | |
| onClick={closeModal} | |
| className="text-h3 w-full rounded-lg border border-indigo-200 bg-white py-3.5 text-center text-indigo-500" | |
| > |
No description provided.