// ======================================== // SpecDialog Component // ======================================== // Dialog for editing spec frontmatter (title, readMode, priority, keywords) import * as React from 'react'; import { useIntl } from 'react-intl'; import { cn } from '@/lib/utils'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/Dialog'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Label } from '@/components/ui/Label'; import { Badge } from '@/components/ui/Badge'; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from '@/components/ui/Select'; import { Tag, X } from 'lucide-react'; import type { Spec, SpecReadMode, SpecPriority } from './SpecCard'; // ========== Types ========== /** * Spec form data for editing */ export interface SpecFormData { title: string; readMode: SpecReadMode; priority: SpecPriority; keywords: string[]; } /** * SpecDialog component props */ export interface SpecDialogProps { /** Whether dialog is open */ open: boolean; /** Called when dialog open state changes */ onOpenChange: (open: boolean) => void; /** Spec being edited */ spec: Spec | null; /** Called when save is clicked */ onSave: (specId: string, data: SpecFormData) => Promise | void; /** Optional loading state */ isLoading?: boolean; } // ========== Constants ========== const READ_MODE_OPTIONS: { value: SpecReadMode; labelKey: string }[] = [ { value: 'required', labelKey: 'specs.readMode.required' }, { value: 'optional', labelKey: 'specs.readMode.optional' }, ]; const PRIORITY_OPTIONS: { value: SpecPriority; labelKey: string }[] = [ { value: 'critical', labelKey: 'specs.priority.critical' }, { value: 'high', labelKey: 'specs.priority.high' }, { value: 'medium', labelKey: 'specs.priority.medium' }, { value: 'low', labelKey: 'specs.priority.low' }, ]; // ========== Component ========== /** * SpecDialog component for editing spec frontmatter */ export function SpecDialog({ open, onOpenChange, spec, onSave, isLoading = false, }: SpecDialogProps) { const { formatMessage } = useIntl(); const [formData, setFormData] = React.useState({ title: '', readMode: 'optional', priority: 'medium', keywords: [], }); const [keywordInput, setKeywordInput] = React.useState(''); const [errors, setErrors] = React.useState>>({}); // Reset form when spec changes React.useEffect(() => { if (spec) { setFormData({ title: spec.title, readMode: spec.readMode, priority: spec.priority, keywords: [...spec.keywords], }); setErrors({}); setKeywordInput(''); } }, [spec]); // Validate form const validateForm = (): boolean => { const newErrors: Partial> = {}; if (!formData.title.trim()) { newErrors.title = formatMessage({ id: 'specs.validation.titleRequired' }); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Handle save const handleSave = async () => { if (!spec || !validateForm()) return; await onSave(spec.id, formData); }; // Handle keyword input const handleKeywordKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); addKeyword(); } }; // Add keyword const addKeyword = () => { const keyword = keywordInput.trim().toLowerCase(); if (keyword && !formData.keywords.includes(keyword)) { setFormData((prev) => ({ ...prev, keywords: [...prev.keywords, keyword], })); } setKeywordInput(''); }; // Remove keyword const removeKeyword = (keyword: string) => { setFormData((prev) => ({ ...prev, keywords: prev.keywords.filter((k) => k !== keyword), })); }; if (!spec) return null; return ( {formatMessage({ id: 'specs.dialog.editTitle' }, { title: spec.title })} {formatMessage({ id: 'specs.dialog.editDescription' })}
{/* Title field */}
setFormData((prev) => ({ ...prev, title: e.target.value }))} placeholder={formatMessage({ id: 'specs.form.titlePlaceholder' })} error={!!errors.title} disabled={isLoading} /> {errors.title && (

{errors.title}

)}
{/* Read mode and Priority */}
{/* Keywords */}
setKeywordInput(e.target.value)} onKeyDown={handleKeywordKeyDown} onBlur={addKeyword} placeholder={formatMessage({ id: 'specs.form.keywordsPlaceholder' })} disabled={isLoading} />

{formatMessage({ id: 'specs.form.keywordsHint' })}

{/* Keywords display */} {formData.keywords.length > 0 && (
{formData.keywords.map((keyword) => ( {keyword} ))}
)}
{/* File info */}

{formatMessage({ id: 'specs.form.fileInfo' }, { file: spec.file })}

); } export default SpecDialog;