feat: add swipe-back from chat list to workspace list on mobile #1

Open
swallow wants to merge 0 commits from fix/workspace-swipe-back into main
Owner

Summary

Fix the mobile swipe-back behavior so users can swipe right from the chat list
in a workspace to navigate back to the workspace list (matching the behavior on
the old task detail page).

Changes

In ui/src/components/spaces/SpaceDetail.tsx:

  1. Pass onSelectSpace to SpaceChat: So that SpaceChat can trigger
    workspace list return via onSelectSpace(undefined).

  2. Add useEdgeSwipe to SpaceChat: The existing useEdgeSwipe hook (already
    used at the app level for opening the navigation drawer) is reused for the
    chat list pane.

  3. Enable only on the chat list: The edge swipe is enabled when:

    • onSelectSpace is provided
    • No task is selected (spaceTaskId == null) — i.e., the chat list view
    • No create dialog is open (!showCreate)

Design Notes

  • The useEdgeSwipe hook detects a right-edge swipe (starts within 20px from the
    left edge, moves right by at least 60px or 22% of container width, with minimal
    vertical movement).
  • The existing SwipeableTabs component on the conversation detail pane already
    handles swipe-back from the detail to the chat list (onSwipeBackFromFirst).
  • This change fills the gap: the chat list -> workspace list swipe.

Patch

diff --git a/ui/src/components/spaces/SpaceDetail.tsx b/ui/src/components/spaces/SpaceDetail.tsx
index 62d9fef..921c526 100644
--- a/ui/src/components/spaces/SpaceDetail.tsx
+++ b/ui/src/components/spaces/SpaceDetail.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState, type TouchEvent } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useQuery, useQueryClient } from '@tanstack/react-query';
 import { useSpaces, useArchiveSpace } from '../../hooks/useSpaces';
@@ -30,6 +30,7 @@ import { SpaceSettings } from './SpaceSettings';
 import { SpaceCalendar } from './SpaceCalendar';
 import { SchedulesPage } from '../../pages/SpacesPage';
 import { SpaceApps } from './SpaceApps';
+import { useEdgeSwipe } from '../../hooks/useEdgeSwipe';
 import { useAuthState } from '../../App';
 import { SkeletonChatPane } from '../shared/Skeleton';
 import { EmptyState } from '../shared/EmptyState';
@@ -189,6 +190,7 @@ export function SpaceDetail({ spaceId, spaceTaskId, initialTab, onInitialTabAppl
             spaceId={spaceId}
             isPersonalSpace={space.kind === 'personal'}
             spaceTaskId={spaceTaskId}
+            onSelectSpace={onSelectSpace}
             onSelectSpaceTask={onSelectSpaceTask}
             onCreateTask={onCreateTask}
             filter={chatFilter}
@@ -426,6 +428,7 @@ function SpaceChat({
   spaceId,
   isPersonalSpace,
   spaceTaskId,
+  onSelectSpace,
   onSelectSpaceTask,
   filter,
   onFilterChange,
@@ -433,6 +436,7 @@ function SpaceChat({
   spaceId: string;
   isPersonalSpace: boolean;
   spaceTaskId?: number;
+  onSelectSpace?: (id: string | undefined) => void;
   onSelectSpaceTask: (id: number) => void;
   onCreateTask: SpaceDetailProps['onCreateTask'];
   filter: SpaceChatFilter;
@@ -501,10 +505,19 @@ function SpaceChat({
     onSelectSpaceTask(created.task.id);
   }, [qc, onSelectSpaceTask]);
 
+  // 右スワイプでチャット一覧(ワークスペース一覧)に戻る(モバイル操作感)。
+  // チャット一覧画面のみで有効(会話はSwipeableTabsが処理)。
+  const edgeSwipe = useEdgeSwipe({
+    enabled: !!onSelectSpace && spaceTaskId == null && !showCreate,
+    onOpen: () => { onSelectSpace?.(undefined); },
+  });
+
   return (
     <div className="flex h-full min-h-0">
-      {/* 左: チャット一覧。会話を開いている狭幅では隠す(会話が一覧を置き換える)。 */}
+      {/* 左: チャット一覧。右スワイプでワークスペース一覧へ戻る。
+          会話を開いている狭幅では隠す(会話が一覧を置き換える)。 */}
       <div
+        {...edgeSwipe}
         className={`w-full md:w-[280px] md:shrink-0 flex flex-col min-h-0 overflow-hidden border-r border-hairline ${
           spaceTaskId != null || showCreate ? 'hidden md:flex' : 'flex'
         }`}
## Summary Fix the mobile swipe-back behavior so users can swipe right from the chat list in a workspace to navigate back to the workspace list (matching the behavior on the old task detail page). ## Changes In `ui/src/components/spaces/SpaceDetail.tsx`: 1. **Pass `onSelectSpace` to `SpaceChat`**: So that `SpaceChat` can trigger workspace list return via `onSelectSpace(undefined)`. 2. **Add `useEdgeSwipe` to `SpaceChat`**: The existing `useEdgeSwipe` hook (already used at the app level for opening the navigation drawer) is reused for the chat list pane. 3. **Enable only on the chat list**: The edge swipe is enabled when: - `onSelectSpace` is provided - No task is selected (`spaceTaskId == null`) — i.e., the chat list view - No create dialog is open (`!showCreate`) ## Design Notes - The `useEdgeSwipe` hook detects a right-edge swipe (starts within 20px from the left edge, moves right by at least 60px or 22% of container width, with minimal vertical movement). - The existing `SwipeableTabs` component on the conversation detail pane already handles swipe-back from the detail to the chat list (`onSwipeBackFromFirst`). - This change fills the gap: the chat list -> workspace list swipe. ## Patch ```diff diff --git a/ui/src/components/spaces/SpaceDetail.tsx b/ui/src/components/spaces/SpaceDetail.tsx index 62d9fef..921c526 100644 --- a/ui/src/components/spaces/SpaceDetail.tsx +++ b/ui/src/components/spaces/SpaceDetail.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState, type TouchEvent } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useSpaces, useArchiveSpace } from '../../hooks/useSpaces'; @@ -30,6 +30,7 @@ import { SpaceSettings } from './SpaceSettings'; import { SpaceCalendar } from './SpaceCalendar'; import { SchedulesPage } from '../../pages/SpacesPage'; import { SpaceApps } from './SpaceApps'; +import { useEdgeSwipe } from '../../hooks/useEdgeSwipe'; import { useAuthState } from '../../App'; import { SkeletonChatPane } from '../shared/Skeleton'; import { EmptyState } from '../shared/EmptyState'; @@ -189,6 +190,7 @@ export function SpaceDetail({ spaceId, spaceTaskId, initialTab, onInitialTabAppl spaceId={spaceId} isPersonalSpace={space.kind === 'personal'} spaceTaskId={spaceTaskId} + onSelectSpace={onSelectSpace} onSelectSpaceTask={onSelectSpaceTask} onCreateTask={onCreateTask} filter={chatFilter} @@ -426,6 +428,7 @@ function SpaceChat({ spaceId, isPersonalSpace, spaceTaskId, + onSelectSpace, onSelectSpaceTask, filter, onFilterChange, @@ -433,6 +436,7 @@ function SpaceChat({ spaceId: string; isPersonalSpace: boolean; spaceTaskId?: number; + onSelectSpace?: (id: string | undefined) => void; onSelectSpaceTask: (id: number) => void; onCreateTask: SpaceDetailProps['onCreateTask']; filter: SpaceChatFilter; @@ -501,10 +505,19 @@ function SpaceChat({ onSelectSpaceTask(created.task.id); }, [qc, onSelectSpaceTask]); + // 右スワイプでチャット一覧(ワークスペース一覧)に戻る(モバイル操作感)。 + // チャット一覧画面のみで有効(会話はSwipeableTabsが処理)。 + const edgeSwipe = useEdgeSwipe({ + enabled: !!onSelectSpace && spaceTaskId == null && !showCreate, + onOpen: () => { onSelectSpace?.(undefined); }, + }); + return ( <div className="flex h-full min-h-0"> - {/* 左: チャット一覧。会話を開いている狭幅では隠す(会話が一覧を置き換える)。 */} + {/* 左: チャット一覧。右スワイプでワークスペース一覧へ戻る。 + 会話を開いている狭幅では隠す(会話が一覧を置き換える)。 */} <div + {...edgeSwipe} className={`w-full md:w-[280px] md:shrink-0 flex flex-col min-h-0 overflow-hidden border-r border-hairline ${ spaceTaskId != null || showCreate ? 'hidden md:flex' : 'flex' }`} ```
All checks were successful
CI / build-and-test (push) Successful in 9m8s
CI / build-and-test (pull_request) Successful in 9m42s
This branch is already included in the target branch. There is nothing to merge.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/workspace-swipe-back:fix/workspace-swipe-back
git checkout fix/workspace-swipe-back
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: swallow/maestro#1
No description provided.