feat: enhance QuickAddTask component with new UI elements

- Replaced input fields with `FormField`, `PrioritySelector`, and `DateTimeInput` for improved user experience and consistency.
- Integrated `LoadingSpinner` to indicate submission state, enhancing feedback during task creation.
- Streamlined state management for form fields, ensuring better data handling.
This commit is contained in:
Julien Froidefond
2025-09-30 10:11:44 +02:00
parent d1d65cdca1
commit 270a2bd4d0
5 changed files with 202 additions and 34 deletions

View File

@@ -0,0 +1,38 @@
'use client';
import { formatDateForDateTimeInput, parseDateTimeInput } from '@/lib/date-utils';
interface DateTimeInputProps {
value?: Date;
onChange: (date: Date | undefined) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
onFocus?: () => void;
}
export function DateTimeInput({
value,
onChange,
placeholder,
disabled = false,
className = '',
onFocus
}: DateTimeInputProps) {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value ? parseDateTimeInput(e.target.value) : undefined;
onChange(newValue);
};
return (
<input
type="datetime-local"
value={value ? formatDateForDateTimeInput(value) : ''}
onChange={handleChange}
onFocus={onFocus}
disabled={disabled}
placeholder={placeholder}
className={`bg-transparent border-none outline-none text-[var(--muted-foreground)] font-mono text-xs flex-shrink min-w-0 ${className}`}
/>
);
}

View File

@@ -0,0 +1,67 @@
'use client';
import { forwardRef } from 'react';
interface FormFieldProps {
type?: 'text' | 'textarea';
value: string;
onChange: (value: string) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
onKeyDown?: (e: React.KeyboardEvent) => void;
onFocus?: () => void;
rows?: number;
autoFocus?: boolean;
}
export const FormField = forwardRef<HTMLInputElement | HTMLTextAreaElement, FormFieldProps>(
({
type = 'text',
value,
onChange,
placeholder,
disabled = false,
className = '',
onKeyDown,
onFocus,
rows = 2,
autoFocus = false
}, ref) => {
const baseClasses = "bg-transparent border-none outline-none font-mono placeholder-[var(--muted-foreground)]";
if (type === 'textarea') {
return (
<textarea
ref={ref as React.Ref<HTMLTextAreaElement>}
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={onKeyDown}
onFocus={onFocus}
placeholder={placeholder}
rows={rows}
disabled={disabled}
autoFocus={autoFocus}
className={`${baseClasses} text-xs text-[var(--muted-foreground)] resize-none ${className}`}
/>
);
}
return (
<input
ref={ref as React.Ref<HTMLInputElement>}
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={onKeyDown}
onFocus={onFocus}
placeholder={placeholder}
disabled={disabled}
autoFocus={autoFocus}
className={`${baseClasses} text-[var(--foreground)] text-sm font-medium leading-tight ${className}`}
/>
);
}
);
FormField.displayName = 'FormField';

View File

@@ -0,0 +1,32 @@
'use client';
interface LoadingSpinnerProps {
size?: 'sm' | 'md' | 'lg';
className?: string;
text?: string;
}
const sizeClasses = {
sm: 'w-3 h-3',
md: 'w-4 h-4',
lg: 'w-6 h-6'
};
const textSizeClasses = {
sm: 'text-xs',
md: 'text-sm',
lg: 'text-base'
};
export function LoadingSpinner({
size = 'sm',
className = '',
text
}: LoadingSpinnerProps) {
return (
<div className={`flex items-center gap-1 text-[var(--primary)] font-mono ${textSizeClasses[size]} ${className}`}>
<div className={`${sizeClasses[size]} border border-[var(--primary)] border-t-transparent rounded-full animate-spin`}></div>
{text && <span>{text}</span>}
</div>
);
}

View File

@@ -0,0 +1,42 @@
'use client';
import { TaskPriority } from '@/lib/types';
import { getAllPriorities } from '@/lib/status-config';
interface PrioritySelectorProps {
value: TaskPriority;
onChange: (priority: TaskPriority) => void;
disabled?: boolean;
className?: string;
title?: string;
}
export function PrioritySelector({
value,
onChange,
disabled = false,
className = '',
title
}: PrioritySelectorProps) {
const priorities = getAllPriorities();
const currentPriority = priorities.find(p => p.key === value);
return (
<select
value={value}
onChange={(e) => {
const selectedValue = e.target.value as TaskPriority;
if (selectedValue) onChange(selectedValue);
}}
disabled={disabled}
className={`bg-transparent border-none outline-none text-lg text-[var(--muted-foreground)] cursor-pointer text-center ${className}`}
title={title || currentPriority?.label}
>
{priorities.map(priorityConfig => (
<option key={priorityConfig.key} value={priorityConfig.key}>
{priorityConfig.icon}
</option>
))}
</select>
);
}