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>
This commit is contained in:
98
apps/tasks/components/features/InlineEditField.tsx
Normal file
98
apps/tasks/components/features/InlineEditField.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user