feat: preview panel option (#49)
* feat: preview panel option * feat: improved card navigation
This commit is contained in:
@@ -23,6 +23,7 @@ export type KanbanBoardGridProps = {
|
||||
onDragEnd: (event: DragEndEvent) => void;
|
||||
onDragCancel: () => void;
|
||||
activeTask: Task | null;
|
||||
showMaximizeButton?: boolean;
|
||||
};
|
||||
|
||||
export function KanbanBoardGrid({
|
||||
@@ -36,6 +37,7 @@ export function KanbanBoardGrid({
|
||||
onDragEnd,
|
||||
onDragCancel,
|
||||
activeTask,
|
||||
showMaximizeButton,
|
||||
}: KanbanBoardGridProps) {
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
@@ -78,6 +80,7 @@ export function KanbanBoardGrid({
|
||||
onOpenTask={onOpenTask}
|
||||
onEditTask={onEditTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
showMaximizeButton={showMaximizeButton}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,9 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@kandev/ui/dropdown-menu';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@kandev/ui/select';
|
||||
import { IconAdjustmentsHorizontal, IconPlus, IconSettings } from '@tabler/icons-react';
|
||||
import { IconPlus, IconSettings } from '@tabler/icons-react';
|
||||
import { ConnectionStatus } from './connection-status';
|
||||
import { KanbanDisplayDropdown } from './kanban-display-dropdown';
|
||||
import type { Repository } from '@/lib/types/http';
|
||||
|
||||
type KanbanBoardHeaderProps = {
|
||||
@@ -29,9 +16,11 @@ type KanbanBoardHeaderProps = {
|
||||
repositoriesLoading: boolean;
|
||||
allRepositoriesSelected: boolean;
|
||||
selectedRepositoryId: string | null;
|
||||
enablePreviewOnClick?: boolean;
|
||||
onWorkspaceChange: (workspaceId: string | null) => void;
|
||||
onBoardChange: (boardId: string | null) => void;
|
||||
onRepositoryChange: (repositoryId: string | 'all') => void;
|
||||
onTogglePreviewOnClick?: (enabled: boolean) => void;
|
||||
onAddTask: () => void;
|
||||
};
|
||||
|
||||
@@ -44,13 +33,13 @@ export function KanbanBoardHeader({
|
||||
repositoriesLoading,
|
||||
allRepositoriesSelected,
|
||||
selectedRepositoryId,
|
||||
enablePreviewOnClick,
|
||||
onWorkspaceChange,
|
||||
onBoardChange,
|
||||
onRepositoryChange,
|
||||
onTogglePreviewOnClick,
|
||||
onAddTask,
|
||||
}: KanbanBoardHeaderProps) {
|
||||
const repositoryValue = allRepositoriesSelected ? 'all' : selectedRepositoryId ?? 'all';
|
||||
|
||||
return (
|
||||
<header className="flex items-center justify-between p-4 pb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -64,84 +53,21 @@ export function KanbanBoardHeader({
|
||||
<IconPlus className="h-4 w-4" />
|
||||
Add task
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="cursor-pointer">
|
||||
<IconAdjustmentsHorizontal className="h-4 w-4 mr-2" />
|
||||
Display
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[280px] p-3">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<DropdownMenuLabel className="px-0">Workspace</DropdownMenuLabel>
|
||||
<Select
|
||||
value={activeWorkspaceId ?? ''}
|
||||
onValueChange={(value) => onWorkspaceChange(value || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select workspace" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{workspaces.map((workspace) => (
|
||||
<SelectItem key={workspace.id} value={workspace.id}>
|
||||
{workspace.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="space-y-1.5">
|
||||
<DropdownMenuLabel className="px-0">Board</DropdownMenuLabel>
|
||||
<Select
|
||||
value={activeBoardId ?? ''}
|
||||
onValueChange={(value) => onBoardChange(value || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select board" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{boards.map((board) => (
|
||||
<SelectItem key={board.id} value={board.id}>
|
||||
{board.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="space-y-1.5">
|
||||
<DropdownMenuLabel className="px-0">Repository</DropdownMenuLabel>
|
||||
<Select
|
||||
value={repositoryValue}
|
||||
onValueChange={(value) => onRepositoryChange(value as string | 'all')}
|
||||
disabled={repositories.length === 0}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
repositoriesLoading
|
||||
? 'Loading repositories...'
|
||||
: repositories.length === 0
|
||||
? 'No repositories'
|
||||
: 'Select repository'
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All repositories</SelectItem>
|
||||
{repositories.map((repo) => (
|
||||
<SelectItem key={repo.id} value={repo.id}>
|
||||
{repo.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<KanbanDisplayDropdown
|
||||
workspaces={workspaces}
|
||||
boards={boards}
|
||||
activeWorkspaceId={activeWorkspaceId}
|
||||
activeBoardId={activeBoardId}
|
||||
repositories={repositories}
|
||||
repositoriesLoading={repositoriesLoading}
|
||||
allRepositoriesSelected={allRepositoriesSelected}
|
||||
selectedRepositoryId={selectedRepositoryId}
|
||||
enablePreviewOnClick={enablePreviewOnClick}
|
||||
onWorkspaceChange={onWorkspaceChange}
|
||||
onBoardChange={onBoardChange}
|
||||
onRepositoryChange={onRepositoryChange}
|
||||
onTogglePreviewOnClick={onTogglePreviewOnClick}
|
||||
/>
|
||||
<Link href="/settings" className="cursor-pointer">
|
||||
<Button variant="outline" className="cursor-pointer">
|
||||
<IconSettings className="h-4 w-4 mr-2" />
|
||||
|
||||
@@ -21,9 +21,11 @@ import { linkToSession } from '@/lib/links';
|
||||
interface KanbanBoardProps {
|
||||
onPreviewTask?: (task: Task) => void;
|
||||
onOpenTask?: (task: Task, sessionId: string) => void;
|
||||
enablePreviewOnClick?: boolean;
|
||||
onTogglePreviewOnClick?: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export function KanbanBoard({ onPreviewTask, onOpenTask }: KanbanBoardProps = {}) {
|
||||
export function KanbanBoard({ onPreviewTask, onOpenTask, enablePreviewOnClick, onTogglePreviewOnClick }: KanbanBoardProps = {}) {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
||||
const [isMovingTask, setIsMovingTask] = useState(false);
|
||||
@@ -333,6 +335,8 @@ export function KanbanBoard({ onPreviewTask, onOpenTask }: KanbanBoardProps = {}
|
||||
repositoriesLoading={repositoriesLoading}
|
||||
allRepositoriesSelected={allRepositoriesSelected}
|
||||
selectedRepositoryId={userSettings.repositoryIds[0] ?? null}
|
||||
enablePreviewOnClick={enablePreviewOnClick}
|
||||
onTogglePreviewOnClick={onTogglePreviewOnClick}
|
||||
onWorkspaceChange={(nextWorkspaceId) => {
|
||||
setActiveWorkspace(nextWorkspaceId);
|
||||
if (nextWorkspaceId) {
|
||||
@@ -426,6 +430,7 @@ export function KanbanBoard({ onPreviewTask, onOpenTask }: KanbanBoardProps = {}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={handleDragCancel}
|
||||
activeTask={activeTask}
|
||||
showMaximizeButton={enablePreviewOnClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ interface KanbanCardProps {
|
||||
onEdit?: (task: Task) => void;
|
||||
onDelete?: (task: Task) => void;
|
||||
onOpenFullPage?: (task: Task) => void;
|
||||
showMaximizeButton?: boolean;
|
||||
}
|
||||
|
||||
function KanbanCardBody({
|
||||
@@ -81,7 +82,7 @@ function KanbanCardLayout({ task, className }: KanbanCardProps & { className?: s
|
||||
);
|
||||
}
|
||||
|
||||
export function KanbanCard({ task, onClick, onEdit, onDelete, onOpenFullPage }: KanbanCardProps) {
|
||||
export function KanbanCard({ task, onClick, onEdit, onDelete, onOpenFullPage, showMaximizeButton = true }: KanbanCardProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
||||
id: task.id,
|
||||
});
|
||||
@@ -121,8 +122,8 @@ export function KanbanCard({ task, onClick, onEdit, onDelete, onOpenFullPage }:
|
||||
repoName={repoName}
|
||||
actions={
|
||||
<div className="flex items-center gap-2">
|
||||
{statusIcon}
|
||||
{onOpenFullPage && task.hasSession !== false && (
|
||||
{task.state === 'IN_PROGRESS' && statusIcon}
|
||||
{showMaximizeButton && onOpenFullPage && task.hasSession !== false && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-sm p-1 -m-1 transition-colors cursor-pointer"
|
||||
|
||||
@@ -18,9 +18,10 @@ interface KanbanColumnProps {
|
||||
onOpenTask: (task: Task) => void;
|
||||
onEditTask: (task: Task) => void;
|
||||
onDeleteTask: (task: Task) => void;
|
||||
showMaximizeButton?: boolean;
|
||||
}
|
||||
|
||||
export function KanbanColumn({ column, tasks, onPreviewTask, onOpenTask, onEditTask, onDeleteTask }: KanbanColumnProps) {
|
||||
export function KanbanColumn({ column, tasks, onPreviewTask, onOpenTask, onEditTask, onDeleteTask, showMaximizeButton }: KanbanColumnProps) {
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
id: column.id,
|
||||
});
|
||||
@@ -57,6 +58,7 @@ export function KanbanColumn({ column, tasks, onPreviewTask, onOpenTask, onEditT
|
||||
onOpenFullPage={onOpenTask}
|
||||
onEdit={onEditTask}
|
||||
onDelete={onDeleteTask}
|
||||
showMaximizeButton={showMaximizeButton}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Checkbox } from '@kandev/ui/checkbox';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@kandev/ui/dropdown-menu';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@kandev/ui/select';
|
||||
import { IconAdjustmentsHorizontal } from '@tabler/icons-react';
|
||||
import type { Repository } from '@/lib/types/http';
|
||||
|
||||
type KanbanDisplayDropdownProps = {
|
||||
workspaces: Array<{ id: string; name: string }>;
|
||||
boards: Array<{ id: string; workspaceId: string; name: string }>;
|
||||
activeWorkspaceId: string | null;
|
||||
activeBoardId: string | null;
|
||||
repositories: Repository[];
|
||||
repositoriesLoading: boolean;
|
||||
allRepositoriesSelected: boolean;
|
||||
selectedRepositoryId: string | null;
|
||||
enablePreviewOnClick?: boolean;
|
||||
onWorkspaceChange: (workspaceId: string | null) => void;
|
||||
onBoardChange: (boardId: string | null) => void;
|
||||
onRepositoryChange: (repositoryId: string | 'all') => void;
|
||||
onTogglePreviewOnClick?: (enabled: boolean) => void;
|
||||
};
|
||||
|
||||
export function KanbanDisplayDropdown({
|
||||
workspaces,
|
||||
boards,
|
||||
activeWorkspaceId,
|
||||
activeBoardId,
|
||||
repositories,
|
||||
repositoriesLoading,
|
||||
allRepositoriesSelected,
|
||||
selectedRepositoryId,
|
||||
enablePreviewOnClick,
|
||||
onWorkspaceChange,
|
||||
onBoardChange,
|
||||
onRepositoryChange,
|
||||
onTogglePreviewOnClick,
|
||||
}: KanbanDisplayDropdownProps) {
|
||||
const repositoryValue = allRepositoriesSelected ? 'all' : selectedRepositoryId ?? 'all';
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="cursor-pointer">
|
||||
<IconAdjustmentsHorizontal className="h-4 w-4 mr-2" />
|
||||
Display
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[280px] p-3">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<DropdownMenuLabel className="px-0">Workspace</DropdownMenuLabel>
|
||||
<Select
|
||||
value={activeWorkspaceId ?? ''}
|
||||
onValueChange={(value) => onWorkspaceChange(value || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select workspace" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{workspaces.map((workspace) => (
|
||||
<SelectItem key={workspace.id} value={workspace.id}>
|
||||
{workspace.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="space-y-1.5">
|
||||
<DropdownMenuLabel className="px-0">Board</DropdownMenuLabel>
|
||||
<Select
|
||||
value={activeBoardId ?? ''}
|
||||
onValueChange={(value) => onBoardChange(value || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select board" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{boards.map((board) => (
|
||||
<SelectItem key={board.id} value={board.id}>
|
||||
{board.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="space-y-1.5">
|
||||
<DropdownMenuLabel className="px-0">Repository</DropdownMenuLabel>
|
||||
<Select
|
||||
value={repositoryValue}
|
||||
onValueChange={(value) => onRepositoryChange(value as string | 'all')}
|
||||
disabled={repositories.length === 0}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
repositoriesLoading
|
||||
? 'Loading repositories...'
|
||||
: repositories.length === 0
|
||||
? 'No repositories'
|
||||
: 'Select repository'
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All repositories</SelectItem>
|
||||
{repositories.map((repo) => (
|
||||
<SelectItem key={repo.id} value={repo.id}>
|
||||
{repo.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="space-y-1.5">
|
||||
<DropdownMenuLabel className="px-0">Preview Panel</DropdownMenuLabel>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={enablePreviewOnClick ?? false}
|
||||
onCheckedChange={(checked) => {
|
||||
onTogglePreviewOnClick?.(!!checked);
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm">Open preview on click</span>
|
||||
</label>
|
||||
<p className="text-xs text-muted-foreground pl-6">
|
||||
When enabled, clicking a task opens the preview panel. When disabled, clicking navigates directly to the session.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -24,13 +24,16 @@ export function KanbanWithPreview({ initialTaskId, initialSessionId }: KanbanWit
|
||||
|
||||
// Get tasks from the kanban store
|
||||
const kanbanTasks = useAppStore((state) => state.kanban.tasks);
|
||||
const taskSessionsByTaskId = useAppStore((state) => state.taskSessionsByTask.itemsByTaskId);
|
||||
|
||||
const {
|
||||
selectedTaskId,
|
||||
isOpen,
|
||||
previewWidthPx,
|
||||
enablePreviewOnClick,
|
||||
open,
|
||||
close,
|
||||
setEnablePreviewOnClick,
|
||||
updatePreviewWidth,
|
||||
} = useKanbanPreview({
|
||||
initialTaskId,
|
||||
@@ -168,14 +171,56 @@ export function KanbanWithPreview({ initialTaskId, initialSessionId }: KanbanWit
|
||||
// Preview handler - task data is computed from selectedTaskId
|
||||
// Toggle behavior: clicking the same task closes the panel
|
||||
const handlePreviewTaskWithData = useCallback(
|
||||
(task: Task) => {
|
||||
if (isOpen && selectedTaskId === task.id) {
|
||||
close();
|
||||
async (task: Task) => {
|
||||
if (enablePreviewOnClick) {
|
||||
// Preview mode: open/toggle preview panel
|
||||
if (isOpen && selectedTaskId === task.id) {
|
||||
close();
|
||||
} else {
|
||||
open(task.id);
|
||||
}
|
||||
} else {
|
||||
open(task.id);
|
||||
// Navigate mode: go directly to session details
|
||||
// First check if we have session data in the store
|
||||
const sessionsFromStore = taskSessionsByTaskId[task.id];
|
||||
const sessionIdFromStore = sessionsFromStore?.[0]?.id;
|
||||
|
||||
if (sessionIdFromStore) {
|
||||
// We have a session ID in the store - use it directly
|
||||
handleNavigateToTask(task, sessionIdFromStore);
|
||||
return;
|
||||
}
|
||||
|
||||
// No session in store - fetch from backend
|
||||
const client = getWebSocketClient();
|
||||
if (!client) {
|
||||
// If no WebSocket client, fall back to opening preview
|
||||
open(task.id);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.request<{ sessions: Array<{ id: string }> }>(
|
||||
"task.session.list",
|
||||
{ task_id: task.id },
|
||||
10000
|
||||
);
|
||||
const sessionId = response.sessions[0]?.id;
|
||||
|
||||
if (sessionId) {
|
||||
handleNavigateToTask(task, sessionId);
|
||||
} else {
|
||||
// If no session exists, fall back to opening preview
|
||||
open(task.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load task sessions:", error);
|
||||
// Fall back to opening preview on error
|
||||
open(task.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
[isOpen, selectedTaskId, open, close]
|
||||
[enablePreviewOnClick, isOpen, selectedTaskId, taskSessionsByTaskId, open, close, handleNavigateToTask]
|
||||
);
|
||||
|
||||
// Handle Escape key to close preview
|
||||
@@ -241,6 +286,8 @@ export function KanbanWithPreview({ initialTaskId, initialSessionId }: KanbanWit
|
||||
<KanbanBoard
|
||||
onPreviewTask={handlePreviewTaskWithData}
|
||||
onOpenTask={handleNavigateToTask}
|
||||
enablePreviewOnClick={enablePreviewOnClick}
|
||||
onTogglePreviewOnClick={setEnablePreviewOnClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -296,6 +343,8 @@ export function KanbanWithPreview({ initialTaskId, initialSessionId }: KanbanWit
|
||||
<KanbanBoard
|
||||
onPreviewTask={handlePreviewTaskWithData}
|
||||
onOpenTask={handleNavigateToTask}
|
||||
enablePreviewOnClick={enablePreviewOnClick}
|
||||
onTogglePreviewOnClick={setEnablePreviewOnClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export function useKanbanPreview(options: UseKanbanPreviewOptions = {}) {
|
||||
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [previewWidthPx, setPreviewWidthPx] = useState<number>(PREVIEW_PANEL.DEFAULT_WIDTH_PX);
|
||||
const [enablePreviewOnClick, setEnablePreviewOnClick] = useState<boolean>(false);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const hasInitialized = useRef(false);
|
||||
|
||||
@@ -27,6 +28,7 @@ export function useKanbanPreview(options: UseKanbanPreviewOptions = {}) {
|
||||
isOpen: false,
|
||||
previewWidthPx: PREVIEW_PANEL.DEFAULT_WIDTH_PX,
|
||||
selectedTaskId: null,
|
||||
enablePreviewOnClick: false,
|
||||
});
|
||||
|
||||
// Prioritize initial task ID from SSR
|
||||
@@ -42,6 +44,9 @@ export function useKanbanPreview(options: UseKanbanPreviewOptions = {}) {
|
||||
if (taskIdToUse) {
|
||||
setSelectedTaskId(taskIdToUse);
|
||||
}
|
||||
if (savedState.enablePreviewOnClick !== undefined) {
|
||||
setEnablePreviewOnClick(savedState.enablePreviewOnClick);
|
||||
}
|
||||
}, [options.initialTaskId]);
|
||||
|
||||
// Persist state to localStorage
|
||||
@@ -59,6 +64,10 @@ export function useKanbanPreview(options: UseKanbanPreviewOptions = {}) {
|
||||
setKanbanPreviewState({ selectedTaskId });
|
||||
}, [selectedTaskId]);
|
||||
|
||||
useEffect(() => {
|
||||
setKanbanPreviewState({ enablePreviewOnClick });
|
||||
}, [enablePreviewOnClick]);
|
||||
|
||||
const open = useCallback((taskId: string) => {
|
||||
setSelectedTaskId(taskId);
|
||||
setIsOpen(true);
|
||||
@@ -82,10 +91,12 @@ export function useKanbanPreview(options: UseKanbanPreviewOptions = {}) {
|
||||
selectedTaskId,
|
||||
isOpen,
|
||||
previewWidthPx,
|
||||
enablePreviewOnClick,
|
||||
open,
|
||||
close,
|
||||
toggle,
|
||||
setSelectedTaskId,
|
||||
setEnablePreviewOnClick,
|
||||
updatePreviewWidth,
|
||||
containerRef,
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ const KANBAN_PREVIEW_KEYS = {
|
||||
OPEN: 'kandev.kanban.preview.open',
|
||||
WIDTH: 'kandev.kanban.preview.width',
|
||||
SELECTED_TASK: 'kandev.kanban.preview.selectedTask',
|
||||
ENABLE_PREVIEW_ON_CLICK: 'kandev.kanban.preview.enablePreviewOnClick',
|
||||
} as const;
|
||||
|
||||
// Kanban preview state type
|
||||
@@ -41,6 +42,7 @@ export interface KanbanPreviewState {
|
||||
isOpen: boolean;
|
||||
previewWidthPx: number;
|
||||
selectedTaskId: string | null;
|
||||
enablePreviewOnClick: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +55,7 @@ export function getKanbanPreviewState(defaults: KanbanPreviewState): KanbanPrevi
|
||||
isOpen: getLocalStorage(KANBAN_PREVIEW_KEYS.OPEN, defaults.isOpen),
|
||||
previewWidthPx: getLocalStorage(KANBAN_PREVIEW_KEYS.WIDTH, defaults.previewWidthPx),
|
||||
selectedTaskId: getLocalStorage(KANBAN_PREVIEW_KEYS.SELECTED_TASK, defaults.selectedTaskId),
|
||||
enablePreviewOnClick: getLocalStorage(KANBAN_PREVIEW_KEYS.ENABLE_PREVIEW_ON_CLICK, defaults.enablePreviewOnClick),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,4 +77,7 @@ export function setKanbanPreviewState(state: Partial<KanbanPreviewState>): void
|
||||
setLocalStorage(KANBAN_PREVIEW_KEYS.SELECTED_TASK, state.selectedTaskId);
|
||||
}
|
||||
}
|
||||
if (state.enablePreviewOnClick !== undefined) {
|
||||
setLocalStorage(KANBAN_PREVIEW_KEYS.ENABLE_PREVIEW_ON_CLICK, state.enablePreviewOnClick);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user