RealBlocks / client /src /components /FrameworkSelector /FrameworkSelector.tsx
Sebebeb's picture
Update FrameworkSelector.tsx
03eca1e
Raw
History Blame Contribute Delete
4.59 kB
import { useState } from 'react';
import { FrameworkType } from '../../types';
import { FRAMEWORKS } from '../../types/project';
import {
Globe, Monitor, Smartphone, Server, ChevronRight,
Check, Code2, Zap, Layers, Layout
} from 'lucide-react';
interface FrameworkSelectorProps {
onSelect: (name: string, framework: FrameworkType, description: string) => void;
onCancel: () => void;
}
export default function FrameworkSelector({ onSelect, onCancel }: FrameworkSelectorProps) {
const [selected, setSelected] = useState<FrameworkType | null>(null);
const [projectName, setProjectName] = useState('');
const [description, setDescription] = useState('');
const getIcon = (fw: FrameworkType) => {
switch (fw) {
case 'web': return Globe;
case 'electron': return Monitor;
case 'maui': return Smartphone;
case 'nodejs': return Server;
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (selected && projectName.trim()) {
onSelect(projectName.trim(), selected, description.trim());
}
};
return (
<form onSubmit={handleSubmit} className="space-y-6">
{/* Project Name */}
<div>
<div className="flex items-center justify-between mb-1">
<label className="block text-sm text-surface-300">Project Name</label>
<span className={`text-xs ${projectName.length > 45 ? 'text-red-400' : 'text-surface-500'}`}>
{projectName.length}/50
</span>
</div>
<input
type="text"
value={projectName}
onChange={(e) => setProjectName(e.target.value.slice(0, 50))}
className="input"
placeholder="My Awesome App"
required
autoFocus
maxLength={50}
/>
</div>
{/* Framework Selection */}
<div>
<label className="block text-sm text-surface-300 mb-3">Choose Framework</label>
<div className="grid grid-cols-2 gap-3">
{(Object.entries(FRAMEWORKS) as [FrameworkType, typeof FRAMEWORKS[FrameworkType]][]).map(([key, fw]) => {
const Icon = getIcon(key);
const isSelected = selected === key;
return (
<button
type="button"
key={key}
onClick={() => setSelected(key)}
className={`relative p-4 rounded-xl border text-left transition-all duration-200 ${
isSelected
? 'border-primary-500 bg-primary-500/10 ring-1 ring-primary-500'
: 'border-surface-700 bg-surface-800/50 hover:border-surface-500'
}`}
>
{isSelected && (
<div className="absolute top-2 right-2 w-5 h-5 bg-primary-500 rounded-full flex items-center justify-center">
<Check className="w-3 h-3 text-white" />
</div>
)}
<div className={`w-10 h-10 rounded-lg bg-gradient-to-br ${fw.gradient} flex items-center justify-center mb-2`}>
<Icon className="w-5 h-5 text-white" />
</div>
<h4 className="font-semibold text-white text-sm">{fw.name}</h4>
<p className="text-xs text-surface-400 mt-1 line-clamp-2">{fw.description}</p>
<div className="flex flex-wrap gap-1 mt-2">
{fw.languages.map((lang) => (
<span key={lang} className="text-[10px] px-1.5 py-0.5 rounded bg-surface-700 text-surface-300">
{lang}
</span>
))}
</div>
</button>
);
})}
</div>
</div>
{/* Description */}
<div>
<label className="block text-sm text-surface-300 mb-1">
Description <span className="text-surface-500">(optional)</span>
</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="input resize-none h-20"
placeholder="What does your project do?"
/>
</div>
{/* Action Buttons */}
<div className="flex gap-3 justify-end pt-2">
<button type="button" onClick={onCancel} className="btn-secondary">
Cancel
</button>
<button
type="submit"
disabled={!selected || !projectName.trim()}
className="btn-primary"
>
<Zap className="w-4 h-4" />
Create Project
</button>
</div>
</form>
);
}