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%
TemplatePanel.tsx 325 lines (10 KB)
'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