Barazo default frontend barazo.forum
at main 110 lines 3.7 kB view raw
1/** 2 * TopicMetaFields - Title, category, and tags inputs for topic form. 3 * @see specs/prd-web.md Section 4 (Editor Components) 4 */ 5 6import { cn } from '@/lib/utils' 7import { FormLabel } from '@/components/ui/form-label' 8import type { CategoryTreeNode } from '@/lib/api/types' 9import { flattenCategoryTree } from '@/lib/flatten-category-tree' 10 11interface TopicMetaFieldsProps { 12 title: string 13 category: string 14 tagInput: string 15 categories: CategoryTreeNode[] 16 errors: { title?: string; category?: string } 17 onTitleChange: (title: string) => void 18 onCategoryChange: (category: string) => void 19 onTagInputChange: (tags: string) => void 20} 21 22export function TopicMetaFields({ 23 title, 24 category, 25 tagInput, 26 categories, 27 errors, 28 onTitleChange, 29 onCategoryChange, 30 onTagInputChange, 31}: TopicMetaFieldsProps) { 32 return ( 33 <> 34 <div className="space-y-1"> 35 <FormLabel htmlFor="topic-title" required> 36 Title 37 </FormLabel> 38 <input 39 id="topic-title" 40 type="text" 41 value={title} 42 onChange={(e) => onTitleChange(e.target.value)} 43 placeholder="Enter a descriptive title" 44 required 45 aria-invalid={errors.title ? 'true' : undefined} 46 aria-describedby={errors.title ? 'topic-title-error' : undefined} 47 className={cn( 48 'block w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground', 49 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 50 errors.title && 'border-destructive' 51 )} 52 /> 53 {errors.title && ( 54 <p id="topic-title-error" className="text-sm text-destructive" role="alert"> 55 {errors.title} 56 </p> 57 )} 58 </div> 59 60 <div className="space-y-1"> 61 <FormLabel htmlFor="topic-category" required> 62 Category 63 </FormLabel> 64 <select 65 id="topic-category" 66 value={category} 67 onChange={(e) => onCategoryChange(e.target.value)} 68 required 69 aria-invalid={errors.category ? 'true' : undefined} 70 aria-describedby={errors.category ? 'topic-category-error' : undefined} 71 className={cn( 72 'block w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground', 73 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 74 errors.category && 'border-destructive' 75 )} 76 > 77 <option value="">Select a category</option> 78 {flattenCategoryTree(categories).map(({ category: cat, depth }) => ( 79 <option key={cat.slug} value={cat.slug}> 80 {'\u00A0'.repeat(depth * 3)} 81 {cat.name} 82 </option> 83 ))} 84 </select> 85 {errors.category && ( 86 <p id="topic-category-error" className="text-sm text-destructive" role="alert"> 87 {errors.category} 88 </p> 89 )} 90 </div> 91 92 <div className="space-y-1"> 93 <FormLabel htmlFor="topic-tags" optional> 94 Tags 95 </FormLabel> 96 <input 97 id="topic-tags" 98 type="text" 99 value={tagInput} 100 onChange={(e) => onTagInputChange(e.target.value)} 101 placeholder="Comma-separated tags (e.g., discussion, help)" 102 className={cn( 103 'block w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground', 104 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring' 105 )} 106 /> 107 </div> 108 </> 109 ) 110}