Spaces:
Running
Running
"use client"; | |
import React, { useState, useEffect } from 'react'; | |
import { | |
Dialog, | |
DialogContent, | |
DialogDescription, | |
DialogFooter, | |
DialogHeader, | |
DialogTitle, | |
} from '@/components/ui/dialog'; | |
import { | |
// Select, | |
// SelectContent, | |
// SelectItem, | |
// SelectTrigger, | |
// SelectValue, | |
} from '@/components/ui/select'; | |
import { Button } from '@/components/ui/button'; | |
import { Input } from '@/components/ui/input'; | |
import { Label } from '@/components/ui/label'; | |
import { Textarea } from '@/components/ui/textarea'; | |
import { Badge } from '@/components/ui/badge'; | |
import { | |
// Folder, | |
FolderPlus, | |
Palette, | |
Tag, | |
// Settings | |
} from 'lucide-react'; | |
export interface CreateFolderDialogProps { | |
open: boolean; | |
onOpenChange: (open: boolean) => void; | |
onFolderCreate: (folderData: { | |
name: string; | |
parentFolderId?: string; | |
metadata?: { | |
description?: string; | |
color?: string; | |
icon?: string; | |
tags?: string[]; | |
}; | |
}) => void; | |
parentFolderId?: string | null; | |
} | |
const FOLDER_COLORS = [ | |
{ name: 'Blue', value: '#3B82F6', class: 'bg-blue-500' }, | |
{ name: 'Green', value: '#10B981', class: 'bg-green-500' }, | |
{ name: 'Yellow', value: '#F59E0B', class: 'bg-yellow-500' }, | |
{ name: 'Red', value: '#EF4444', class: 'bg-red-500' }, | |
{ name: 'Purple', value: '#8B5CF6', class: 'bg-purple-500' }, | |
{ name: 'Pink', value: '#EC4899', class: 'bg-pink-500' }, | |
{ name: 'Indigo', value: '#6366F1', class: 'bg-indigo-500' }, | |
{ name: 'Gray', value: '#6B7280', class: 'bg-gray-500' }, | |
]; | |
const FOLDER_ICONS = [ | |
'๐', '๐', '๐๏ธ', '๐', '๐', '๐', '๐', '๐ฆ', | |
'๐จ', '๐ผ๏ธ', '๐ต', '๐ฌ', '๐ท', '๐ฎ', 'โ๏ธ', '๐ง', | |
'๐ผ', '๐ ', '๐', 'โค๏ธ', '๐ฅ', 'โก', '๐', '๐ก' | |
]; | |
const COMMON_FOLDER_NAMES = [ | |
'components', 'pages', 'styles', 'assets', 'images', 'scripts', | |
'utils', 'helpers', 'hooks', 'services', 'api', 'data', | |
'tests', 'docs', 'config', 'public', 'src', 'lib' | |
]; | |
export function CreateFolderDialog({ | |
open, | |
onOpenChange, | |
onFolderCreate, | |
parentFolderId | |
}: CreateFolderDialogProps) { | |
const [folderName, setFolderName] = useState(''); | |
const [description, setDescription] = useState(''); | |
const [selectedColor, setSelectedColor] = useState(FOLDER_COLORS[0].value); | |
const [selectedIcon, setSelectedIcon] = useState('๐'); | |
const [tags, setTags] = useState<string[]>([]); | |
const [tagInput, setTagInput] = useState(''); | |
const [isCreating, setIsCreating] = useState(false); | |
// Reset form when dialog opens | |
useEffect(() => { | |
if (open) { | |
setFolderName(''); | |
setDescription(''); | |
setSelectedColor(FOLDER_COLORS[0].value); | |
setSelectedIcon('๐'); | |
setTags([]); | |
setTagInput(''); | |
} | |
}, [open]); | |
const handleCreate = async () => { | |
if (!folderName.trim()) { | |
return; | |
} | |
setIsCreating(true); | |
try { | |
onFolderCreate({ | |
name: folderName.trim(), | |
parentFolderId: parentFolderId || undefined, | |
metadata: { | |
description: description.trim() || undefined, | |
color: selectedColor, | |
icon: selectedIcon, | |
tags: tags.length > 0 ? tags : undefined | |
} | |
}); | |
onOpenChange(false); | |
} catch (error) { | |
console.error('Error creating folder:', error); | |
} finally { | |
setIsCreating(false); | |
} | |
}; | |
const handleAddTag = () => { | |
const tag = tagInput.trim().toLowerCase(); | |
if (tag && !tags.includes(tag)) { | |
setTags([...tags, tag]); | |
setTagInput(''); | |
} | |
}; | |
const handleRemoveTag = (tagToRemove: string) => { | |
setTags(tags.filter(tag => tag !== tagToRemove)); | |
}; | |
const handleTagInputKeyDown = (e: React.KeyboardEvent) => { | |
if (e.key === 'Enter') { | |
e.preventDefault(); | |
handleAddTag(); | |
} | |
}; | |
const handleQuickName = (name: string) => { | |
setFolderName(name); | |
}; | |
return ( | |
<Dialog open={open} onOpenChange={onOpenChange}> | |
<DialogContent className="max-w-md"> | |
<DialogHeader> | |
<DialogTitle className="flex items-center gap-2"> | |
<FolderPlus className="h-5 w-5" /> | |
Create New Folder | |
</DialogTitle> | |
<DialogDescription> | |
Create a new folder to organize your files | |
{parentFolderId && " in the selected folder"}. | |
</DialogDescription> | |
</DialogHeader> | |
<div className="space-y-4"> | |
{/* Folder Name */} | |
<div className="space-y-2"> | |
<Label htmlFor="folderName">Folder Name</Label> | |
<Input | |
id="folderName" | |
placeholder="Enter folder name..." | |
value={folderName} | |
onChange={(e) => setFolderName(e.target.value)} | |
/> | |
{/* Quick Name Suggestions */} | |
<div className="flex flex-wrap gap-1"> | |
{COMMON_FOLDER_NAMES.slice(0, 8).map(name => ( | |
<Badge | |
key={name} | |
variant="outline" | |
className="cursor-pointer text-xs hover:bg-neutral-700" | |
onClick={() => handleQuickName(name)} | |
> | |
{name} | |
</Badge> | |
))} | |
</div> | |
</div> | |
{/* Description */} | |
<div className="space-y-2"> | |
<Label htmlFor="description">Description (Optional)</Label> | |
<Textarea | |
id="description" | |
placeholder="Describe what this folder contains..." | |
value={description} | |
onChange={(e) => setDescription(e.target.value)} | |
className="min-h-[60px] resize-none" | |
/> | |
</div> | |
{/* Appearance */} | |
<div className="space-y-3"> | |
<Label className="flex items-center gap-2"> | |
<Palette className="h-4 w-4" /> | |
Appearance | |
</Label> | |
{/* Color Selection */} | |
<div className="space-y-2"> | |
<div className="text-sm text-neutral-400">Color</div> | |
<div className="flex flex-wrap gap-2"> | |
{FOLDER_COLORS.map(color => ( | |
<button | |
key={color.value} | |
className={` | |
w-6 h-6 rounded-full border-2 transition-all | |
${selectedColor === color.value | |
? 'border-white scale-110' | |
: 'border-neutral-600 hover:border-neutral-400' | |
} | |
${color.class} | |
`} | |
onClick={() => setSelectedColor(color.value)} | |
title={color.name} | |
/> | |
))} | |
</div> | |
</div> | |
{/* Icon Selection */} | |
<div className="space-y-2"> | |
<div className="text-sm text-neutral-400">Icon</div> | |
<div className="grid grid-cols-8 gap-2"> | |
{FOLDER_ICONS.map(icon => ( | |
<button | |
key={icon} | |
className={` | |
w-8 h-8 rounded border text-lg transition-all | |
${selectedIcon === icon | |
? 'border-blue-500 bg-blue-500/20' | |
: 'border-neutral-600 hover:border-neutral-400 hover:bg-neutral-800' | |
} | |
`} | |
onClick={() => setSelectedIcon(icon)} | |
> | |
{icon} | |
</button> | |
))} | |
</div> | |
</div> | |
</div> | |
{/* Tags */} | |
<div className="space-y-2"> | |
<Label className="flex items-center gap-2"> | |
<Tag className="h-4 w-4" /> | |
Tags (Optional) | |
</Label> | |
<div className="flex gap-2"> | |
<Input | |
placeholder="Add tag..." | |
value={tagInput} | |
onChange={(e) => setTagInput(e.target.value)} | |
onKeyDown={handleTagInputKeyDown} | |
className="flex-1" | |
/> | |
<Button | |
variant="outline" | |
size="sm" | |
onClick={handleAddTag} | |
disabled={!tagInput.trim()} | |
> | |
Add | |
</Button> | |
</div> | |
{/* Display Tags */} | |
{tags.length > 0 && ( | |
<div className="flex flex-wrap gap-1"> | |
{tags.map(tag => ( | |
<Badge | |
key={tag} | |
variant="secondary" | |
className="text-xs cursor-pointer hover:bg-red-500/20" | |
onClick={() => handleRemoveTag(tag)} | |
> | |
{tag} ร | |
</Badge> | |
))} | |
</div> | |
)} | |
</div> | |
{/* Preview */} | |
<div className="p-3 bg-neutral-800 rounded-lg"> | |
<div className="text-sm text-neutral-400 mb-2">Preview</div> | |
<div className="flex items-center gap-2"> | |
<span style={{ color: selectedColor }}>{selectedIcon}</span> | |
<span className="text-white font-medium">{folderName || 'Folder Name'}</span> | |
{tags.length > 0 && ( | |
<Badge variant="outline" className="text-xs"> | |
{tags.length} tag{tags.length !== 1 ? 's' : ''} | |
</Badge> | |
)} | |
</div> | |
{description && ( | |
<div className="text-xs text-neutral-400 mt-1"> | |
{description} | |
</div> | |
)} | |
</div> | |
</div> | |
<DialogFooter> | |
<Button variant="outline" onClick={() => onOpenChange(false)}> | |
Cancel | |
</Button> | |
<Button | |
onClick={handleCreate} | |
disabled={!folderName.trim() || isCreating} | |
> | |
{isCreating ? 'Creating...' : 'Create Folder'} | |
</Button> | |
</DialogFooter> | |
</DialogContent> | |
</Dialog> | |
); | |
} | |