Files
task-team/apps/tasks/components/features/InlineEditField.tsx
Admin 8cf14dcf59 Feature pack: Media, Gamification, Templates, Time Tracking, Kanban, AI Briefing, Webhooks, Icon UI
API features (separate files in /api/src/features/):
- media-input: upload text/audio/photo/video, transcription
- gamification: points, streaks, badges, leaderboard
- templates: predefined task sets (sprint, study, moving)
- time-tracking: start/stop timer, task/user reports
- kanban: board view, drag-and-drop move
- ai-briefing: daily AI summary with tasks/goals/reviews
- webhooks-outgoing: notify external systems on events

UI components (separate files in /components/features/):
- IconButton: icon-only buttons with tooltip
- CompactHeader, PageActionBar, InlineEditField
- TaskDetailActions, GoalActionButtons, CollabActionButtons
- DeleteIconButton, CollabBackButton

All features modular — registry.js enables/disables each one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 01:13:09 +00:00

99 lines
3.2 KiB
TypeScript

'use client';
import { useState, useRef, useEffect } from 'react';
interface Props {
value: string;
onSave: (newValue: string) => void;
className?: string;
as?: 'input' | 'textarea';
placeholder?: string;
multiline?: boolean;
}
export default function InlineEditField({
value,
onSave,
className = '',
as = 'input',
placeholder = '',
multiline = false,
}: Props) {
const [editing, setEditing] = useState(false);
const [editValue, setEditValue] = useState(value);
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
useEffect(() => {
setEditValue(value);
}, [value]);
useEffect(() => {
if (editing && inputRef.current) {
inputRef.current.focus();
// Select all text
if ('setSelectionRange' in inputRef.current) {
inputRef.current.setSelectionRange(0, inputRef.current.value.length);
}
}
}, [editing]);
function handleBlur() {
setEditing(false);
const trimmed = editValue.trim();
if (trimmed !== value && trimmed !== '') {
onSave(trimmed);
} else {
setEditValue(value);
}
}
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === 'Enter' && !multiline) {
e.preventDefault();
(e.target as HTMLElement).blur();
}
if (e.key === 'Escape') {
setEditValue(value);
setEditing(false);
}
}
if (editing) {
if (as === 'textarea' || multiline) {
return (
<textarea
ref={inputRef as React.RefObject<HTMLTextAreaElement>}
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
placeholder={placeholder}
rows={3}
className={`w-full px-2 py-1 border border-blue-400 dark:border-blue-600 rounded-lg bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none resize-none text-sm ${className}`}
/>
);
}
return (
<input
ref={inputRef as React.RefObject<HTMLInputElement>}
type="text"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className={`w-full px-2 py-1 border border-blue-400 dark:border-blue-600 rounded-lg bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none text-sm ${className}`}
/>
);
}
return (
<div
onClick={() => setEditing(true)}
className={`cursor-pointer rounded-lg px-2 py-1 -mx-2 -my-1 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors ${className}`}
title="Click to edit"
>
{value || <span className="text-muted italic">{placeholder || 'Click to edit'}</span>}
</div>
);
}