From bebd55874c3ee6af46e497d732d1332bcb011a2d Mon Sep 17 00:00:00 2001 From: gahusb Date: Mon, 27 Apr 2026 13:39:09 +0900 Subject: [PATCH] =?UTF-8?q?fix(todo):=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94=20=E2=80=94=20=ED=84=B0=EC=B9=98=20?= =?UTF-8?q?=ED=83=80=EA=B2=9F=2044px,=20=EB=9D=BC=EB=B2=A8=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC,=20=ED=99=95=EC=9D=B8=20=EC=8B=9C=ED=8A=B8,=20?= =?UTF-8?q?=ED=83=AD=20=EC=9D=B8=EB=94=94=EC=BC=80=EC=9D=B4=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 카드 액션 버튼 36px→44px + 아이콘+텍스트 라벨 (모바일) - 날짜 필터/입력 터치 타겟 36px min-height로 확대 - 빈 상태 메시지 모바일 적절하게 변경 ("드래그하여 이동"→"아직 항목이 없습니다") - 완료 비우기 MobileSheet 확인 다이얼로그 (모바일) - 완료 탭 내 "비우기" 버튼 추가 - SwipeableView 활성 탭 하단 인디케이터 + 44px 높이 - 폼 라벨 14px, 입력 16px (iOS 줌 방지) - 모바일 컬럼/패널 배경·보더 제거로 공간 절약 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/MobileSheet.css | 2 +- src/components/SwipeableView.css | 21 ++++ src/pages/todo/Todo.css | 163 +++++++++++++++++++++++++++++++ src/pages/todo/Todo.jsx | 59 ++++++++--- 4 files changed, 231 insertions(+), 14 deletions(-) diff --git a/src/components/MobileSheet.css b/src/components/MobileSheet.css index 6939b58..33a60f5 100644 --- a/src/components/MobileSheet.css +++ b/src/components/MobileSheet.css @@ -108,7 +108,7 @@ flex: 1; overflow-y: auto; padding: 16px 20px; - padding-bottom: calc(16px + var(--safe-area-bottom)); + padding-bottom: calc(20px + var(--safe-area-bottom)); overscroll-behavior: contain; } diff --git a/src/components/SwipeableView.css b/src/components/SwipeableView.css index ebd7bb6..36b6275 100644 --- a/src/components/SwipeableView.css +++ b/src/components/SwipeableView.css @@ -46,6 +46,18 @@ .swipeable-view__tab.is-active { background: var(--surface-raised); color: var(--neon-cyan); + position: relative; +} + +.swipeable-view__tab.is-active::after { + content: ''; + position: absolute; + bottom: 2px; + left: 20%; + right: 20%; + height: 2px; + background: var(--neon-cyan); + border-radius: 1px; } /* Sliding track */ @@ -67,6 +79,15 @@ overflow-y: auto; } +/* Mobile touch targets */ +@media (max-width: 768px) { + .swipeable-view__tab { + min-height: 44px; + font-size: 14px; + padding: 10px 16px; + } +} + /* Reduced motion */ @media (prefers-reduced-motion: reduce) { .swipeable-view__track { diff --git a/src/pages/todo/Todo.css b/src/pages/todo/Todo.css index f8eaf75..ecc4af9 100644 --- a/src/pages/todo/Todo.css +++ b/src/pages/todo/Todo.css @@ -233,6 +233,7 @@ transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; padding: 0; line-height: 1; + -webkit-tap-highlight-color: transparent; } .todo-card__btn:hover { @@ -288,6 +289,24 @@ opacity: 0.6; } +.todo-done-panel__clear-btn { + font-size: 11px; + padding: 4px 10px; + border-radius: 6px; + border: 1px solid rgba(239, 68, 68, 0.3); + background: rgba(239, 68, 68, 0.08); + color: #ef4444; + cursor: pointer; + font-family: inherit; + -webkit-tap-highlight-color: transparent; + transition: background 0.15s ease; + min-height: 32px; +} + +.todo-done-panel__clear-btn:active { + background: rgba(239, 68, 68, 0.2); +} + /* ── 날짜 필터 ────────────────────────────────────────────────────────── */ .todo-done-panel__filter { @@ -311,6 +330,7 @@ white-space: nowrap; font-family: inherit; line-height: 1.6; + -webkit-tap-highlight-color: transparent; } .todo-date-btn:hover { @@ -387,23 +407,166 @@ display: block; } + .todo-toolbar { + display: none; + } + .todo-col { min-height: 120px; + border: none; + background: transparent; + backdrop-filter: none; + -webkit-backdrop-filter: none; + } + + .todo-col__head { + padding: 10px 4px; + border-bottom: none; + } + + .todo-col__body { + padding: 4px 0; + } + + /* 카드 버튼 44px 터치 타겟 */ + .todo-card__btn { + width: 44px; + height: 44px; + font-size: 14px; + border-radius: 10px; + } + + .todo-card__actions { + gap: 6px; + } + + .todo-card__title { + font-size: 15px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + /* 날짜 필터 버튼 44px 터치 타겟 */ + .todo-date-btn { + font-size: 12px; + padding: 8px 14px; + min-height: 36px; + } + + .todo-date-input { + font-size: 13px; + padding: 8px 12px; + height: 36px; + min-height: 36px; + } + + .todo-done-panel { + border: none; + background: transparent; + backdrop-filter: none; + -webkit-backdrop-filter: none; } .todo-done-panel__head { flex-direction: column; align-items: flex-start; gap: 10px; + padding: 10px 4px; + border-bottom: none; } .todo-done-panel__filter { justify-content: flex-start; + gap: 8px; } .todo-done-panel__body { grid-template-columns: 1fr; + padding: 4px 0; } + + /* 폼 라벨 가독성 */ + .todo-form__field { + font-size: 14px; + gap: 8px; + } + + .todo-form__field input, + .todo-form__field textarea { + font-size: 16px; + padding: 12px 14px; + } + + .todo-form__actions .button { + width: 100%; + min-height: 48px; + font-size: 15px; + } + + /* 빈 상태 메시지 */ + .todo-col__empty { + font-size: 13px; + padding: 32px 16px; + } + + /* 라벨 버튼 (모바일) */ + .todo-card__btn--labeled { + width: auto; + height: 38px; + padding: 0 12px; + gap: 4px; + font-size: 12px; + } + + .todo-card__btn-label { + font-size: 11px; + font-weight: 500; + } + + .todo-card__actions { + flex-wrap: wrap; + } +} + +/* ── 확인 시트 (모바일) ─────────────────────────────────────────────── */ + +.todo-confirm-sheet { + display: flex; + flex-direction: column; + gap: 20px; + padding: 8px 0; +} + +.todo-confirm-sheet__msg { + margin: 0; + font-size: 15px; + color: var(--text); + line-height: 1.6; + text-align: center; +} + +.todo-confirm-sheet__actions { + display: flex; + gap: 10px; +} + +.todo-confirm-sheet__actions .button { + flex: 1; + min-height: 48px; + font-size: 15px; + justify-content: center; +} + +.button.danger { + background: rgba(239, 68, 68, 0.15); + border: 1px solid rgba(239, 68, 68, 0.4); + color: #ef4444; +} + +.button.danger:hover { + background: rgba(239, 68, 68, 0.25); } @media (max-width: 480px) { diff --git a/src/pages/todo/Todo.jsx b/src/pages/todo/Todo.jsx index c1d66f2..d877073 100644 --- a/src/pages/todo/Todo.jsx +++ b/src/pages/todo/Todo.jsx @@ -35,6 +35,7 @@ const Todo = () => { const [dragging, setDragging] = useState(null); const [dragOver, setDragOver] = useState(null); const [doneDate, setDoneDate] = useState(''); // '' = 전체 + const [confirmClear, setConfirmClear] = useState(false); const dragItem = useRef(null); const load = useCallback(async () => { @@ -93,6 +94,7 @@ const Todo = () => { }; const handleClear = async () => { + setConfirmClear(false); try { await clearTodos(); setTodos((prev) => prev.filter((t) => t.status !== 'done')); @@ -172,20 +174,22 @@ const Todo = () => { ))} @@ -208,7 +212,9 @@ const Todo = () => {
{items.length === 0 && ( -

드래그하여 이동

+

+ {isMobile ? '아직 항목이 없습니다' : '드래그하여 이동'} +

)} {items.map((todo) => renderCard(todo, col.id))}
@@ -265,7 +271,11 @@ const Todo = () => { @@ -285,15 +295,20 @@ const Todo = () => { { key: 'todo', label: '할 일', content: renderColumn({ id: 'todo', label: '할 일' }) }, { key: 'in_progress', label: '진행 중', content: renderColumn({ id: 'in_progress', label: '진행 중' }) }, { key: 'done', label: '완료', content: ( -
onDragOver(e, 'done')} - onDrop={(e) => onDrop(e, 'done')} - > +
완료 {doneTodos.length} + {doneTodos.length > 0 && ( + + )}
@@ -306,7 +321,7 @@ const Todo = () => {
{doneTodos.length === 0 ? ( -

{doneDate ? '해당 날짜에 완료된 항목이 없습니다' : '드래그하여 이동'}

+

{doneDate ? '해당 날짜에 완료된 항목이 없습니다' : '완료된 항목이 없습니다'}

) : ( doneTodos.map((todo) => renderCard(todo, 'done')) )} @@ -369,7 +384,7 @@ const Todo = () => {
{doneTodos.length === 0 ? (

- {doneDate ? '해당 날짜에 완료된 항목이 없습니다' : '드래그하여 이동'} + {doneDate ? '해당 날짜에 완료된 항목이 없습니다' : (isMobile ? '완료된 항목이 없습니다' : '드래그하여 이동')}

) : ( doneTodos.map((todo) => renderCard(todo, 'done')) @@ -388,6 +403,24 @@ const Todo = () => { {addForm} + {/* 모바일: 완료 비우기 확인 시트 */} + setConfirmClear(false)} + title="완료 항목 비우기" + snap="half" + > +
+

+ 완료된 항목 {todos.filter(t => t.status === 'done').length}건을 모두 삭제합니다. +

+
+ + +
+
+
+ setAddSheetOpen(true)} label="할일 추가" />