Barazo default frontend
barazo.forum
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}