TagAccessDialog Component
The TagAccessDialog component provides a comprehensive interface for creating and editing tags with access control permissions.
Overview
This component offers a modal dialog for tag management, combining tag metadata (name, description) with sophisticated access control configuration. It supports both creating new tags and editing existing ones with full validation and state management.
Location
src/components/TagAccessDialog.tsx
Features
Tag Management
- Create/Edit Mode: Single component handles both creation and editing
- Name Validation: Required field with trimming and validation
- Optional Description: Rich text description for tag purpose
- Automatic ID Generation: Creates URL-friendly IDs from tag names
Access Control Integration
- Full AccessSelector: Complete access permission configuration
- Permission Types: Support for all access control types (private, public, domain, etc.)
- User Domain Context: Organization-aware permission settings
- Real-time Updates: Immediate feedback on permission changes
Dialog Management
- Responsive Layout: Adapts to screen size with proper overflow handling
- State Persistence: Maintains form state during editing
- Form Reset: Cleans up state when dialog closes
- Validation Feedback: Visual indicators for form validity
Props Interface
interface TagAccessDialogProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
tag?: Tag; // Existing tag for editing (optional)
onSave: (tag: Tag) => void; // Callback when tag is saved
userDomain?: string; // User's organization domain
availableTags?: Tag[]; // Context for validation (optional)
}
Tag Data Structure
interface Tag {
id: string;
name: string;
description?: string;
accessControl: AccessControl;
createdBy: string;
createdAt: number;
updatedAt: number;
}
Usage Examples
Creating New Tags
import { TagAccessDialog } from '@/components/TagAccessDialog';
function TagManager() {
const [dialogOpen, setDialogOpen] = useState(false);
const handleSaveTag = (tag) => {
// Set createdBy field
const completeTag = {
...tag,
createdBy: currentUser.email
};
// Save to database
await saveTag(completeTag);
// Update local state
setTags(prev => [...prev, completeTag]);
};
return (
<>
<button onClick={() => setDialogOpen(true)}>
Create Tag
</button>
<TagAccessDialog
isOpen={dialogOpen}
onOpenChange={setDialogOpen}
onSave={handleSaveTag}
userDomain="example.com"
/>
</>
);
}
Editing Existing Tags
function TagEditButton({ tag }) {
const [editOpen, setEditOpen] = useState(false);
const handleUpdateTag = (updatedTag) => {
// Update in database and local state
updateTag(updatedTag);
};
return (
<>
<button onClick={() => setEditOpen(true)}>
Edit Tag
</button>
<TagAccessDialog
isOpen={editOpen}
onOpenChange={setEditOpen}
tag={tag}
onSave={handleUpdateTag}
userDomain={userDomain}
availableTags={allTags}
/>
</>
);
}
State Management
Form State
const [name, setName] = useState(tag?.name || '');
const [description, setDescription] = useState(tag?.description || '');
const [accessControl, setAccessControl] = useState<AccessControl>(
tag?.accessControl || { type: 'private' }
);
State Reset Effect
useEffect(() => {
if (isOpen && tag) {
// Editing existing tag
setName(tag.name);
setDescription(tag.description || '');
setAccessControl(tag.accessControl);
} else if (isOpen && !tag) {
// Creating new tag
setName('');
setDescription('');
setAccessControl({ type: 'private' });
}
}, [isOpen, tag]);
Form Components
Tag Name Input
<div className="space-y-2">
<Label htmlFor="tag-name">Tag Name</Label>
<input
id="tag-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="e.g., Finance, HR, Engineering"
className="w-full px-3 py-2 border rounded-md bg-background"
/>
</div>
Description Field
<div className="space-y-2">
<Label htmlFor="tag-description">Description (optional)</Label>
<textarea
id="tag-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Describe what this tag is used for..."
rows={3}
className="w-full px-3 py-2 border rounded-md bg-background resize-none"
/>
</div>
Access Control Section
<div className="space-y-4">
<Label>Access Permissions</Label>
<p className="text-sm text-muted-foreground">
Choose who can see and use resources with this tag
</p>
<AccessSelector
value={accessControl.type}
emails={accessControl.emails}
domains={accessControl.domains}
groups={accessControl.groups}
userDomain={userDomain}
onChange={setAccessControl}
/>
</div>
Save Functionality
Data Processing
const handleSave = () => {
if (!name.trim()) return;
const now = Date.now();
const tagData: Tag = {
id: tag?.id || name.toLowerCase().replace(/\s+/g, '-'),
name: name.trim(),
description: description.trim() || undefined,
accessControl,
createdBy: tag?.createdBy || '', // Should be set by parent
createdAt: tag?.createdAt || now,
updatedAt: now
};
onSave(tagData);
onOpenChange(false);
};
Validation
const isValid = name.trim().length > 0;
Dialog Layout
Responsive Structure
<DialogContent className="w-[calc(100%-32px)] sm:max-w-lg md:max-w-xl max-h-[95vh] flex flex-col overflow-hidden">
<DialogHeader className="px-1 shrink-0">
{/* Title and description */}
</DialogHeader>
<div className="flex-1 overflow-y-auto overflow-x-hidden pr-3 py-2 -mr-3 min-h-0 space-y-6">
{/* Form content */}
</div>
<DialogFooter className="mt-4 shrink-0 border-t pt-4">
{/* Action buttons */}
</DialogFooter>
</DialogContent>
Header Component
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<TagIcon className="h-5 w-5" />
{tag ? 'Edit Tag' : 'Create Tag'}
</DialogTitle>
<DialogDescription>
{tag
? 'Update tag settings and access permissions'
: 'Create a new tag to organize assistants and tools'
}
</DialogDescription>
</DialogHeader>
Footer Actions
<DialogFooter className="flex justify-end gap-2 flex-wrap sm:flex-nowrap">
<Button
variant="outline"
onClick={() => onOpenChange(false)}
className="w-full sm:w-auto"
>
Cancel
</Button>
<Button
onClick={handleSave}
disabled={!isValid}
className="w-full sm:w-auto"
>
{tag ? 'Save Changes' : 'Create Tag'}
</Button>
</DialogFooter>
ID Generation
Automatic ID Creation
// For new tags, generate ID from name
id: tag?.id || name.toLowerCase().replace(/\s+/g, '-')
Examples
- “Engineering Team” → “engineering-team”
- “Finance & Operations” → “finance-&-operations”
- “Product Management” → “product-management”
Accessibility
Form Accessibility
- Associated Labels: All inputs have proper label associations
- Placeholder Text: Helpful hints for form completion
- Keyboard Navigation: Tab order follows logical form flow
- Focus Management: Dialog traps focus appropriately
ARIA Support
- Dialog Role: Proper modal dialog semantics
- Descriptive Text: Clear descriptions for complex sections
- Button States: Disabled states clearly indicated
Responsive Design
Layout Adaptations
- Mobile Width: Full width minus margins on mobile
- Desktop Constraints: Maximum width restrictions for readability
- Scrollable Content: Form content scrolls when needed
- Flexible Footer: Buttons stack on mobile, inline on desktop
Overflow Handling
// Scrollable form area with proper overflow handling
className="flex-1 overflow-y-auto overflow-x-hidden pr-3 py-2 -mr-3 min-h-0"
Dependencies
@/components/ui/dialog- Modal dialog components@/components/ui/button- Action buttons@/components/ui/label- Form labels@/components/ui/separator- Visual section separation@/components/AccessSelector- Access control configuration@/components/TagSelector- Tag selection (imported but not used in current implementation)@/lib/firebase- AccessControl and Tag type definitionslucide-react- Tag icon
Error Handling
Validation States
- Empty Name: Save button disabled when name is empty
- Trim Whitespace: Automatic whitespace removal
- Optional Fields: Description can be empty
Form Reset
- Dialog Close: Form state persists until explicitly reset
- Successful Save: Dialog closes automatically after save
- Cancel Action: Discards changes and closes dialog
Related Components
AccessSelector- Provides access control configurationTagSelector- Used for tag selection in other contexts- Tag management interfaces and administrative tools
- Resource tagging systems
Best Practices
- Set Created By: Parent component should set the
createdByfield - Validate Permissions: Ensure user has permission to create/edit tags
- Handle Conflicts: Check for duplicate tag names when appropriate
- Provide Context: Include available tags for validation when possible
- Responsive Design: Test dialog on various screen sizes
- Accessibility: Support keyboard navigation and screen readers
- State Management: Clean up form state appropriately