Files
stack/apps/web/src/components/knowledge/EntryMetadata.tsx
2026-01-29 17:05:48 -06:00

144 lines
4.7 KiB
TypeScript

"use client";
import React from "react";
import type { KnowledgeTag } from "@mosaic/shared";
import { EntryStatus, Visibility } from "@mosaic/shared";
interface EntryMetadataProps {
title: string;
status: EntryStatus;
visibility: Visibility;
selectedTags: string[];
availableTags: KnowledgeTag[];
onTitleChange: (title: string) => void;
onStatusChange: (status: EntryStatus) => void;
onVisibilityChange: (visibility: Visibility) => void;
onTagsChange: (tags: string[]) => void;
}
/**
* EntryMetadata - Title, tags, status, and visibility controls
*/
export function EntryMetadata({
title,
status,
visibility,
selectedTags,
availableTags,
onTitleChange,
onStatusChange,
onVisibilityChange,
onTagsChange,
}: EntryMetadataProps) {
const handleTagToggle = (tagId: string): void => {
if (selectedTags.includes(tagId)) {
onTagsChange(selectedTags.filter((id) => id !== tagId));
} else {
onTagsChange([...selectedTags, tagId]);
}
};
return (
<div className="entry-metadata space-y-4">
{/* Title */}
<div>
<label
htmlFor="entry-title"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
Title
</label>
<input
id="entry-title"
type="text"
value={title}
onChange={(e) => onTitleChange(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Entry title..."
required
/>
</div>
{/* Status and Visibility Row */}
<div className="grid grid-cols-2 gap-4">
{/* Status */}
<div>
<label
htmlFor="entry-status"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
Status
</label>
<select
id="entry-status"
value={status}
onChange={(e) => onStatusChange(e.target.value as EntryStatus)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value={EntryStatus.DRAFT}>Draft</option>
<option value={EntryStatus.PUBLISHED}>Published</option>
<option value={EntryStatus.ARCHIVED}>Archived</option>
</select>
</div>
{/* Visibility */}
<div>
<label
htmlFor="entry-visibility"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
Visibility
</label>
<select
id="entry-visibility"
value={visibility}
onChange={(e) => onVisibilityChange(e.target.value as Visibility)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value={Visibility.PRIVATE}>Private</option>
<option value={Visibility.WORKSPACE}>Workspace</option>
<option value={Visibility.PUBLIC}>Public</option>
</select>
</div>
</div>
{/* Tags */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Tags
</label>
<div className="flex flex-wrap gap-2">
{availableTags.length > 0 ? (
availableTags.map((tag) => {
const isSelected = selectedTags.includes(tag.id);
return (
<button
key={tag.id}
type="button"
onClick={() => handleTagToggle(tag.id)}
className={`px-3 py-1 rounded-full text-sm font-medium transition-colors ${
isSelected
? "bg-blue-600 text-white"
: "bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600"
}`}
style={
isSelected && tag.color
? { backgroundColor: tag.color }
: undefined
}
>
{tag.name}
</button>
);
})
) : (
<p className="text-sm text-gray-500 dark:text-gray-400">
No tags available. Create tags first.
</p>
)}
</div>
</div>
</div>
);
}