feat(ui): add draft target shortcut sequences

This commit is contained in:
ChangeHow
2026-08-06 14:18:14 +08:00
parent ab3ccf395d
commit 1b9da5f9fa
2 changed files with 35 additions and 2 deletions
@@ -101,6 +101,9 @@ and the send path reading the same grammar.
- `state/useDraftTarget.ts` — the draft can target a directory that does not - `state/useDraftTarget.ts` — the draft can target a directory that does not
exist yet (a worktree being created). It must survive not appearing in the exist yet (a worktree being created). It must survive not appearing in the
branch list, or the selector snaps back to the project root mid-creation. branch list, or the selector snaps back to the project root mid-creation.
- `ui/DraftTargetSelectors.tsx` owns the controlled project/worktree picker
state and registers its application shortcuts locally. The selectors only
consume their shared prefix while the draft target UI is mounted.
## Mobile ## Mobile
@@ -25,6 +25,7 @@ import {
import { useI18n } from '@/lib/i18n'; import { useI18n } from '@/lib/i18n';
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, ProjectIconImage } from '@/lib/projectMeta'; import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, ProjectIconImage } from '@/lib/projectMeta';
import { createWorktreeDraft } from '@/lib/worktreeSessionCreator'; import { createWorktreeDraft } from '@/lib/worktreeSessionCreator';
import { useKeybind } from '@/hooks/useKeybind';
import type { Theme } from '@/types/theme'; import type { Theme } from '@/types/theme';
import { normalizePath } from '../attachments/filePaths'; import { normalizePath } from '../attachments/filePaths';
import { getProjectDisplayLabel, type DraftTargetProject } from '../state/useDraftTarget'; import { getProjectDisplayLabel, type DraftTargetProject } from '../state/useDraftTarget';
@@ -103,14 +104,40 @@ export function DraftTargetSelectors(props: DraftTargetProps) {
onDirectoryChange, onDirectoryChange,
theme, theme,
} = props; } = props;
const [openPicker, setOpenPicker] = React.useState<'project' | 'worktree' | null>(null);
const projectTriggerRef = React.useRef<HTMLButtonElement>(null);
const worktreeTriggerRef = React.useRef<HTMLButtonElement>(null);
useKeybind('open_draft_project_picker', () => {
projectTriggerRef.current?.focus();
setOpenPicker('project');
});
useKeybind('open_draft_worktree_picker', () => {
if (!showBranchSelector) return false;
worktreeTriggerRef.current?.focus();
setOpenPicker('worktree');
});
const handleProjectChange = (projectId: string) => {
onProjectChange(projectId);
setOpenPicker(null);
};
const handleDirectoryChange = (directory: string) => {
onDirectoryChange(directory);
setOpenPicker(null);
};
return ( return (
<div className="mb-1.5 flex min-w-0 items-center gap-1.5 px-0.5"> <div className="mb-1.5 flex min-w-0 items-center gap-1.5 px-0.5">
<Select <Select
value={selectedProject.id} value={selectedProject.id}
onValueChange={onProjectChange} open={openPicker === 'project'}
onOpenChange={(open) => setOpenPicker(open ? 'project' : null)}
onValueChange={handleProjectChange}
> >
<SelectTrigger <SelectTrigger
ref={projectTriggerRef}
size="sm" size="sm"
className="h-7 min-w-0 w-fit max-w-[42vw] sm:max-w-[18rem] border-transparent bg-transparent px-1.5 hover:bg-transparent data-[popup-open]:bg-transparent" className="h-7 min-w-0 w-fit max-w-[42vw] sm:max-w-[18rem] border-transparent bg-transparent px-1.5 hover:bg-transparent data-[popup-open]:bg-transparent"
> >
@@ -130,9 +157,12 @@ export function DraftTargetSelectors(props: DraftTargetProps) {
{showBranchSelector ? ( {showBranchSelector ? (
<Select <Select
value={selectedDirectory ?? branchItems[0]?.value ?? normalizePath(selectedProject.path) ?? ''} value={selectedDirectory ?? branchItems[0]?.value ?? normalizePath(selectedProject.path) ?? ''}
onValueChange={onDirectoryChange} open={openPicker === 'worktree'}
onOpenChange={(open) => setOpenPicker(open ? 'worktree' : null)}
onValueChange={handleDirectoryChange}
> >
<SelectTrigger <SelectTrigger
ref={worktreeTriggerRef}
size="sm" size="sm"
className="h-7 min-w-0 w-fit max-w-[48vw] sm:max-w-[20rem] border-transparent bg-transparent px-1.5 hover:bg-transparent data-[popup-open]:bg-transparent" className="h-7 min-w-0 w-fit max-w-[48vw] sm:max-w-[20rem] border-transparent bg-transparent px-1.5 hover:bg-transparent data-[popup-open]:bg-transparent"
> >