A web app to help you design things, local, offline, on device. In your browser.
TypeScript
53.1%
JSON
45.6%
CSS
0.9%
Markdown
0.3%
JavaScript
0.1%
'use client';
import React, { useState } from 'react';
import { Search, Star, Clock } from 'lucide-react';
import { useEditorStore } from '@/lib/store/editor-store';
import type { ExportPreset, GradientConfig } from '@/types';
// Template categories and styles
const categories = [
{ id: 'all', label: 'All' },
{ id: 'app-store', label: 'App Store' },
{ id: 'social', label: 'Social' },
{ id: 'marketing', label: 'Marketing' },
] as const;
const styles = [
{ id: 'all', label: 'All Styles' },
{ id: 'minimal', label: 'Minimal' },
{ id: 'bold', label: 'Bold' },
{ id: 'gradient', label: 'Gradient' },
{ id: 'glass', label: 'Glass' },
{ id: 'dark', label: 'Dark' },
] as const;
// Sample templates
const templates = [
{
id: 'minimal-iphone',
name: 'Minimal iPhone',
category: 'app-store',
style: 'minimal',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
backgroundColor: '#f5f7fa',
},
{
id: 'gradient-sunset',
name: 'Sunset Gradient',
category: 'app-store',
style: 'gradient',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
backgroundColor: {
type: 'linear',
angle: 135,
stops: [
{ offset: 0, color: '#667eea' },
{ offset: 1, color: '#764ba2' },
],
} as GradientConfig,
},
{
id: 'gradient-ocean',
name: 'Ocean Gradient',
category: 'app-store',
style: 'gradient',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
backgroundColor: {
type: 'linear',
angle: 135,
stops: [
{ offset: 0, color: '#4facfe' },
{ offset: 1, color: '#00f2fe' },
],
} as GradientConfig,
},
{
id: 'dark-pro',
name: 'Dark Pro',
category: 'app-store',
style: 'dark',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #0f0f0f 0%, #1a1a2e 100%)',
backgroundColor: '#0f0f0f',
},
{
id: 'glass-modern',
name: 'Glass Modern',
category: 'app-store',
style: 'glass',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backgroundColor: '#1a1a2e',
},
{
id: 'bold-colors',
name: 'Bold Colors',
category: 'app-store',
style: 'bold',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #ff6b6b 0%, #feca57 100%)',
backgroundColor: {
type: 'linear',
angle: 135,
stops: [
{ offset: 0, color: '#ff6b6b' },
{ offset: 1, color: '#feca57' },
],
} as GradientConfig,
},
{
id: 'instagram-story',
name: 'Instagram Story',
category: 'social',
style: 'gradient',
preset: 'instagram-story' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
backgroundColor: {
type: 'linear',
angle: 135,
stops: [
{ offset: 0, color: '#f093fb' },
{ offset: 1, color: '#f5576c' },
],
} as GradientConfig,
},
{
id: 'twitter-minimal',
name: 'Twitter Post',
category: 'social',
style: 'minimal',
preset: 'twitter-post' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #1da1f2 0%, #0d8bd9 100%)',
backgroundColor: '#1da1f2',
},
{
id: 'product-hunt',
name: 'Product Hunt',
category: 'marketing',
style: 'minimal',
preset: 'product-hunt' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #da552f 0%, #e06744 100%)',
backgroundColor: '#da552f',
},
{
id: 'aurora-gradient',
name: 'Aurora',
category: 'app-store',
style: 'gradient',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #a29bfe 0%, #6c5ce7 50%, #fd79a8 100%)',
backgroundColor: {
type: 'linear',
angle: 135,
stops: [
{ offset: 0, color: '#a29bfe' },
{ offset: 0.5, color: '#6c5ce7' },
{ offset: 1, color: '#fd79a8' },
],
} as GradientConfig,
},
{
id: 'forest-gradient',
name: 'Forest',
category: 'app-store',
style: 'gradient',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #134e5e 0%, #71b280 100%)',
backgroundColor: {
type: 'linear',
angle: 135,
stops: [
{ offset: 0, color: '#134e5e' },
{ offset: 1, color: '#71b280' },
],
} as GradientConfig,
},
{
id: 'candy-gradient',
name: 'Candy',
category: 'app-store',
style: 'gradient',
preset: 'iphone-6.5' as ExportPreset,
thumbnail: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)',
backgroundColor: {
type: 'linear',
angle: 135,
stops: [
{ offset: 0, color: '#ff9a9e' },
{ offset: 1, color: '#fecfef' },
],
} as GradientConfig,
},
];
export function TemplatePanel() {
const [search, setSearch] = useState('');
const [activeCategory, setActiveCategory] = useState('all');
const [activeStyle, setActiveStyle] = useState('all');
const { setPreset, setBackgroundColor, resetProject } = useEditorStore();
// Filter templates
const filteredTemplates = templates.filter((t) => {
const matchesSearch = t.name.toLowerCase().includes(search.toLowerCase());
const matchesCategory = activeCategory === 'all' || t.category === activeCategory;
const matchesStyle = activeStyle === 'all' || t.style === activeStyle;
return matchesSearch && matchesCategory && matchesStyle;
});
const handleApplyTemplate = (template: (typeof templates)[0]) => {
// Reset and apply template
resetProject();
setPreset(template.preset);
setBackgroundColor(template.backgroundColor);
};
return (
<div className="p-4">
<h2 className="text-lg font-semibold text-white mb-4">Templates</h2>
{/* Search */}
<div className="relative mb-4">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-500" />
<input
type="text"
placeholder="Search templates..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="
w-full pl-10 pr-4 py-2 rounded-lg
bg-neutral-800 border border-neutral-700
text-white placeholder-neutral-500
focus:outline-none focus:border-indigo-500
"
/>
</div>
{/* Category filter */}
<div className="flex gap-1 mb-3 overflow-x-auto pb-1">
{categories.map((cat) => (
<button
key={cat.id}
onClick={() => setActiveCategory(cat.id)}
className={`
px-3 py-1.5 rounded-lg text-sm whitespace-nowrap transition-colors
${
activeCategory === cat.id
? 'bg-indigo-500/20 text-indigo-400'
: 'text-neutral-400 hover:text-white hover:bg-neutral-800'
}
`}
>
{cat.label}
</button>
))}
</div>
{/* Style filter */}
<div className="flex gap-1 mb-4 overflow-x-auto pb-1">
{styles.map((style) => (
<button
key={style.id}
onClick={() => setActiveStyle(style.id)}
className={`
px-2 py-1 rounded text-xs whitespace-nowrap transition-colors
${
activeStyle === style.id
? 'bg-neutral-700 text-white'
: 'text-neutral-500 hover:text-white hover:bg-neutral-800'
}
`}
>
{style.label}
</button>
))}
</div>
{/* Template grid */}
<div className="grid grid-cols-2 gap-3">
{filteredTemplates.map((template) => (
<button
key={template.id}
onClick={() => handleApplyTemplate(template)}
className="
group relative aspect-[3/4] rounded-xl overflow-hidden
border border-neutral-700/50 hover:border-indigo-500/50
transition-all hover:scale-[1.02]
"
>
{/* Preview */}
<div
className="absolute inset-0"
style={{ background: template.thumbnail }}
/>
{/* Device preview overlay */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-8 h-14 bg-black/30 rounded-lg border border-white/20" />
</div>
{/* Name overlay */}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent p-2">
<div className="text-xs font-medium text-white">{template.name}</div>
<div className="text-[10px] text-neutral-400 capitalize">{template.style}</div>
</div>
{/* Hover overlay */}
<div className="absolute inset-0 bg-indigo-500/0 group-hover:bg-indigo-500/10 transition-colors" />
</button>
))}
</div>
{filteredTemplates.length === 0 && (
<div className="text-center py-8 text-neutral-500">
<p className="text-sm">No templates found</p>
</div>
)}
{/* Quick actions */}
<div className="mt-6 space-y-2">
<button className="w-full flex items-center gap-2 p-3 rounded-lg bg-neutral-800/50 hover:bg-neutral-800 text-neutral-300 hover:text-white transition-colors">
<Star className="w-4 h-4" />
<span className="text-sm">Favorites</span>
</button>
<button className="w-full flex items-center gap-2 p-3 rounded-lg bg-neutral-800/50 hover:bg-neutral-800 text-neutral-300 hover:text-white transition-colors">
<Clock className="w-4 h-4" />
<span className="text-sm">Recent</span>
</button>
</div>
</div>
);
}
About
A web app to help you design things, local, offline, on device. In your browser.
0 stars
0 forks