diff --git a/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx index d2e5770e..234f2efa 100644 --- a/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx +++ b/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx @@ -1,4 +1,4 @@ -import { EditTaskDialogProps } from '../../utils/types'; +import { EditTaskDialogProps, FieldKey } from '../../utils/types'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { DateTimePicker } from '@/components/ui/date-time-picker'; @@ -34,6 +34,10 @@ import { } from 'lucide-react'; import CopyToClipboard from 'react-copy-to-clipboard'; import { formattedDate, handleCopy } from './tasks-utils'; +import { useEffect, useRef, useState } from 'react'; +import { useTaskDialogKeyboard } from './UseTaskDialogKeyboard'; +import { FIELDS } from './constants'; +import { useTaskDialogFocusMap } from './UseTaskDialogFocusMap'; export const TaskDialog = ({ index, @@ -67,6 +71,94 @@ export const TaskDialog = ({ isOverdue, isUnsynced, }: EditTaskDialogProps) => { + const editButtonRef = useRef< + Partial> + >({}); + const inputRefs = useRef< + Partial> + >({}); + 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]; + + 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(); + }; + + useEffect(() => { + const element = editButtonRef.current[focusedField]; + if (!element) return; + + element.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + }, [focusedField]); + + useEffect(() => { + focusMap(focusedField); + }, [ + focusedField, + editState.isEditing, + editState.isEditingDueDate, + editState.isEditingStartDate, + editState.isEditingEndDate, + editState.isEditingWaitDate, + editState.isEditingEntryDate, + editState.isEditingTags, + editState.isEditingAnnotations, + ]); + + const focusMap = useTaskDialogFocusMap({ + fields: FIELDS, + inputRefs: inputRefs, + }); + + const handleDialogKeyDown = useTaskDialogKeyboard({ + fields: FIELDS, + focusedFieldIndex: focusedFieldIndex, + setFocusedFieldIndex: setFocusedFieldIndex, + isEditingAny: isEditingAny, + stopEditing: stopEditing, + triggerEditForField: triggerEditForField, + }); + + const saveAndExit = (onSave: () => void) => { + onSave(); + stopEditing(); + }; + const handleDialogOpenChange = (open: boolean) => { if (open) { onSelectTask(task, index); @@ -175,7 +267,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" + > @@ -203,13 +306,30 @@ export const TaskDialog = ({ )} - + Description: {editState.isEditing ? ( <>
{ + if (e.key === 'Enter') { + e.preventDefault(); + saveAndExit(() => { + onSaveDescription( + task, + editState.editedDescription + ); + }); + } + }} + ref={(element) => + (inputRefs.current.description = element) + } id={`description-${task.id}`} name={`description-${task.id}`} type="text" @@ -249,6 +369,9 @@ export const TaskDialog = ({ <> {task.description} @@ -853,7 +1043,9 @@ export const TaskDialog = ({ )} - + Project: {editState.isEditingProject ? ( @@ -917,6 +1109,9 @@ export const TaskDialog = ({ {isCreatingNewProject && (
{ + inputRefs.current.project = element; + }} id="project-name" placeholder="New project name" value={editState.editedProject} @@ -964,6 +1159,9 @@ export const TaskDialog = ({ <> {task.project} + + 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 a4d830b1..0ee83ab1 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 @@ -125,6 +126,8 @@ export function DateTimePicker({