From 9c923182ae42cce1460e267986b331d76b46d679 Mon Sep 17 00:00:00 2001 From: Neeraj-gagat Date: Sun, 4 Jan 2026 06:37:10 +0530 Subject: [PATCH 1/6] add shortcts keys in taskdialog --- .../HomeComponents/Tasks/TaskDialog.tsx | 299 +++++++++++++++++- .../components/HomeComponents/Tasks/Tasks.tsx | 2 +- frontend/src/components/ui/date-picker.tsx | 74 ++--- .../src/components/ui/date-time-picker.tsx | 16 +- 4 files changed, 332 insertions(+), 59 deletions(-) diff --git a/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx index c3be7de3..c577620a 100644 --- a/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx +++ b/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx @@ -35,6 +35,24 @@ import { } from 'lucide-react'; import CopyToClipboard from 'react-copy-to-clipboard'; import { formattedDate, handleCopy } from './tasks-utils'; +import { useEffect, useRef, useState } from 'react'; + +const FIELDS = [ + 'description', + 'due', + 'start', + 'end', + 'wait', + 'depends', + 'priority', + 'project', + 'tags', + 'entry', + 'recur', + 'annotations', +] as const; + +type FieldKey = (typeof FIELDS)[number]; export const TaskDialog = ({ index, @@ -68,6 +86,154 @@ export const TaskDialog = ({ isOverdue, isUnsynced, }: EditTaskDialogProps) => { + const editButtonRef = useRef>( + {} as any + ); + const inputRefs = useRef< + Record + >({} as any); + const [focusedFieldIndex, setFocusedFieldIndex] = useState(0); + + const isEditingAny = + editState.isEditing || + editState.isEditingDueDate || + editState.isEditingStartDate || + editState.isEditingEndDate || + editState.isEditingWaitDate || + editState.isEditingEntryDate || + editState.isEditingPriority || + editState.isEditingProject || + editState.isEditingTags || + editState.isEditingDepends || + editState.isEditingRecur || + editState.isEditingAnnotations; + + const focusedField = FIELDS[focusedFieldIndex]; + + useEffect(() => { + const el = editButtonRef.current[focusedField]; + if (!el) return; + + el.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + }, [focusedField]); + + const focusMap: Record void> = { + description: () => inputRefs.current.description?.focus(), + + due: () => { + const el = inputRefs.current.due; + el?.focus(); + el?.click(); + }, + + start: () => { + const el = inputRefs.current.start; + el?.focus(); + el?.click(); + }, + + end: () => { + const el = inputRefs.current.end; + el?.focus(); + el?.click(); + }, + + wait: () => { + const el = inputRefs.current.wait; + el?.focus(); + el?.click(); + }, + + entry: () => { + const el = inputRefs.current.entry; + el?.focus(); + el?.click(); + }, + + tags: () => inputRefs.current.tags?.focus(), + + annotations: () => inputRefs.current.annotations?.focus(), + }; + + useEffect(() => { + focusMap[focusedField]?.(); + }, [ + focusedField, + editState.isEditing, + editState.isEditingDueDate, + editState.isEditingStartDate, + editState.isEditingEndDate, + editState.isEditingWaitDate, + editState.isEditingEntryDate, + editState.isEditingTags, + editState.isEditingAnnotations, + ]); + + const handleDialogKeyDown = (e: React.KeyboardEvent) => { + const target = e.target as HTMLElement; + + const isTyping = + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.isContentEditable; + // ⛔ allow normal typing + if (isTyping) return; + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + if (isEditingAny) return; + setFocusedFieldIndex((i) => Math.min(i + 1, FIELDS.length - 1)); + break; + + case 'ArrowUp': + e.preventDefault(); + if (isEditingAny) return; + setFocusedFieldIndex((i) => Math.max(i - 1, 0)); + break; + + case 'Enter': + if (isEditingAny) return; + e.preventDefault(); + triggerEditForField(FIELDS[focusedFieldIndex]); + break; + + case 'Escape': + e.preventDefault(); + stopEditing(); + break; + } + }; + + const stopEditing = () => { + onUpdateState({ + isEditing: false, + isEditingDueDate: false, + isEditingStartDate: false, + isEditingEndDate: false, + isEditingWaitDate: false, + isEditingEntryDate: false, + isEditingPriority: false, + isEditingProject: false, + isEditingTags: false, + isEditingDepends: false, + isEditingRecur: false, + isEditingAnnotations: false, + }); + }; + + const triggerEditForField = (field: FieldKey) => { + editButtonRef.current[field]?.click(); + }; + + const saveAndExit = (fn: () => void) => { + fn(); + stopEditing(); + }; + const handleDialogOpenChange = (open: boolean) => { if (open) { onSelectTask(task, index); // Notify parent that this task is selected @@ -176,7 +342,18 @@ export const TaskDialog = ({ - + { + if (isEditingAny) { + e.preventDefault(); + stopEditing(); + return; + } + }} + tabIndex={0} + onKeyDown={handleDialogKeyDown} + className="sm:max-w-[625px] max-h-[90vh] flex flex-col" + > @@ -204,13 +381,27 @@ export const TaskDialog = ({ )} - + Description: {editState.isEditing ? ( <>
{ + if (e.key === 'Enter') { + e.preventDefault(); + saveAndExit(() => { + onSaveDescription( + task, + editState.editedDescription + ); + }); + } + }} + ref={(el) => (inputRefs.current.description = el)} id={`description-${task.id}`} name={`description-${task.id}`} type="text" @@ -250,6 +441,7 @@ export const TaskDialog = ({ <> {task.description} + + e.preventDefault()} > - - {date ? format(date, 'PPP') : {placeholder}} - - - e.preventDefault()} - > - - - - ); -} + + + + ); + } +); diff --git a/frontend/src/components/ui/date-time-picker.tsx b/frontend/src/components/ui/date-time-picker.tsx index 45ffd6d1..068dd51c 100644 --- a/frontend/src/components/ui/date-time-picker.tsx +++ b/frontend/src/components/ui/date-time-picker.tsx @@ -19,12 +19,13 @@ interface DateTimePickerProps { className?: string; } -export function DateTimePicker({ - date, - onDateTimeChange, - placeholder = 'Pick a date', - className, -}: DateTimePickerProps) { +export const DateTimePicker = React.forwardRef< + HTMLButtonElement, + DateTimePickerProps +>(function DatePicker( + { date, onDateTimeChange, placeholder = 'Pick a date', className }, + ref +) { const [isOpen, setIsOpen] = React.useState(false); const [internalDate, setInternalDate] = React.useState( date @@ -133,6 +134,7 @@ export function DateTimePicker({