feat(web): add quick chat entry points on mobile (#1751)
* feat(web): add quick chat entry points on mobile Quick chat was unreachable on touch devices: every trigger was desktop-only (keyboard shortcut, command palette, app sidebar) and the full-screen dialog had no touch dismissal. - add quick chat button to the mobile kanban/tasks header - add quick chat button to the tablet header (640-767px has no sidebar) - add chat button to the mobile session task-switcher sheet - add a mobile-only close control to the quick chat tab bar - cover the touch entry points with a mobile Playwright spec Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(web): address review feedback on mobile quick chat spec - reuse the shared assertNoDocumentHorizontalOverflow layout helper in both quick chat mobile specs instead of local duplicates - assert the task switcher sheet is dismissed after opening quick chat Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState, memo } from "react";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
import { IconMessageCircle, IconPlus } from "@tabler/icons-react";
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@kandev/ui/sheet";
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import { TaskSwitcher } from "../task-switcher";
|
||||
@@ -22,6 +22,7 @@ import { SidebarLinkDialogs } from "../task-session-sidebar-dialogs";
|
||||
import { useSidebarLinkActions } from "../task-session-sidebar-link-actions";
|
||||
import { useSidebarTaskLinking } from "../task-session-sidebar-task-linking";
|
||||
import { useSheetData, useSheetActions } from "./session-task-switcher-sheet-hooks";
|
||||
import { useQuickChatLauncher } from "@/hooks/use-quick-chat-launcher";
|
||||
|
||||
type SessionTaskSwitcherSheetProps = {
|
||||
open: boolean;
|
||||
@@ -131,6 +132,58 @@ export function MobileTaskList({
|
||||
);
|
||||
}
|
||||
|
||||
function TaskSwitcherSheetHeader({
|
||||
workspaceId,
|
||||
workspaces,
|
||||
onWorkspaceChange,
|
||||
onQuickChat,
|
||||
onNewTask,
|
||||
}: {
|
||||
workspaceId: string | null;
|
||||
workspaces: Array<{ id: string; name: string }>;
|
||||
onWorkspaceChange: (workspaceId: string) => void;
|
||||
onQuickChat: () => void;
|
||||
onNewTask: () => void;
|
||||
}) {
|
||||
return (
|
||||
<SheetHeader className="p-4 pb-2 border-b border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<SheetTitle className="text-base">Tasks</SheetTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
{workspaceId && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 gap-1 cursor-pointer"
|
||||
onClick={onQuickChat}
|
||||
data-testid="mobile-sheet-quick-chat"
|
||||
>
|
||||
<IconMessageCircle className="h-4 w-4" />
|
||||
Chat
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 gap-1 cursor-pointer"
|
||||
onClick={onNewTask}
|
||||
>
|
||||
<IconPlus className="h-4 w-4" />
|
||||
New
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<WorkspaceSwitcher
|
||||
workspaces={workspaces}
|
||||
activeWorkspaceId={workspaceId}
|
||||
onSelect={onWorkspaceChange}
|
||||
/>
|
||||
</div>
|
||||
</SheetHeader>
|
||||
);
|
||||
}
|
||||
|
||||
export const SessionTaskSwitcherSheet = memo(function SessionTaskSwitcherSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
@@ -141,6 +194,11 @@ export const SessionTaskSwitcherSheet = memo(function SessionTaskSwitcherSheet({
|
||||
const data = useSheetData(workspaceId);
|
||||
const actions = useSheetActions(workspaceId, onOpenChange);
|
||||
const linking = useMobileTaskLinking(workspaceId);
|
||||
const openQuickChat = useQuickChatLauncher(workspaceId);
|
||||
const handleQuickChat = useCallback(() => {
|
||||
onOpenChange(false);
|
||||
openQuickChat();
|
||||
}, [onOpenChange, openQuickChat]);
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
@@ -149,27 +207,13 @@ export const SessionTaskSwitcherSheet = memo(function SessionTaskSwitcherSheet({
|
||||
side="left"
|
||||
className="w-[85vw] max-w-sm p-0 flex flex-col"
|
||||
>
|
||||
<SheetHeader className="p-4 pb-2 border-b border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<SheetTitle className="text-base">Tasks</SheetTitle>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 gap-1 cursor-pointer"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
<IconPlus className="h-4 w-4" />
|
||||
New
|
||||
</Button>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<WorkspaceSwitcher
|
||||
workspaces={data.workspaces.map((w) => ({ id: w.id, name: w.name }))}
|
||||
activeWorkspaceId={workspaceId}
|
||||
onSelect={actions.handleWorkspaceChange}
|
||||
/>
|
||||
</div>
|
||||
</SheetHeader>
|
||||
<TaskSwitcherSheetHeader
|
||||
workspaceId={workspaceId}
|
||||
workspaces={data.workspaces.map((w) => ({ id: w.id, name: w.name }))}
|
||||
onWorkspaceChange={actions.handleWorkspaceChange}
|
||||
onQuickChat={handleQuickChat}
|
||||
onNewTask={() => setDialogOpen(true)}
|
||||
/>
|
||||
|
||||
<SidebarFilterBar />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user