315 lines
65beb53 samthecodingguy Jan 29, 2026
'use client';
import React, { useState } from 'react';
import { Type, AlignLeft, AlignCenter, AlignRight, Bold, Italic } from 'lucide-react';
import { HexColorPicker } from 'react-colorful';
import { useEditorStore } from '@/lib/store/editor-store';
import type { TextLayerData } from '@/types';
// Font families organized by category
const fontCategories = {
  'Sans-Serif (Modern)': [
    { name: 'Inter', value: 'Inter, sans-serif' },
    { name: 'DM Sans', value: '"DM Sans", sans-serif' },
    { name: 'Space Grotesk', value: '"Space Grotesk", sans-serif' },
    { name: 'Manrope', value: 'Manrope, sans-serif' },
    { name: 'Work Sans', value: '"Work Sans", sans-serif' },
    { name: 'Plus Jakarta Sans', value: '"Plus Jakarta Sans", sans-serif' },
    { name: 'Outfit', value: 'Outfit, sans-serif' },
  ],
  'Sans-Serif (Classic)': [
    { name: 'SF Pro', value: '-apple-system, BlinkMacSystemFont, sans-serif' },
    { name: 'Roboto', value: 'Roboto, sans-serif' },
    { name: 'Open Sans', value: '"Open Sans", sans-serif' },
    { name: 'Lato', value: 'Lato, sans-serif' },
    { name: 'Poppins', value: 'Poppins, sans-serif' },
    { name: 'Nunito', value: 'Nunito, sans-serif' },
  ],
  'Serif (Editorial)': [
    { name: 'Playfair Display', value: '"Playfair Display", serif' },
    { name: 'Libre Baskerville', value: '"Libre Baskerville", serif' },
    { name: 'Lora', value: 'Lora, serif' },
    { name: 'Merriweather', value: 'Merriweather, serif' },
    { name: 'Cormorant', value: 'Cormorant, serif' },
    { name: 'Georgia', value: 'Georgia, serif' },
  ],
  'Display (Headlines)': [
    { name: 'Montserrat', value: 'Montserrat, sans-serif' },
    { name: 'Bebas Neue', value: '"Bebas Neue", sans-serif' },
    { name: 'Oswald', value: 'Oswald, sans-serif' },
    { name: 'Archivo Black', value: '"Archivo Black", sans-serif' },
    { name: 'Righteous', value: 'Righteous, sans-serif' },
  ],
  'Monospace': [
    { name: 'JetBrains Mono', value: '"JetBrains Mono", monospace' },
    { name: 'Fira Code', value: '"Fira Code", monospace' },
    { name: 'Source Code Pro', value: '"Source Code Pro", monospace' },
    { name: 'Courier', value: '"Courier New", monospace' },
  ],
};
// Flatten for backward compatibility
const fontFamilies = Object.values(fontCategories).flat();
const textPresets = [
  {
    name: 'Headline',
    fontSize: 72,
    fontWeight: 700,
    fontFamily: 'Inter, sans-serif',
    letterSpacing: -2,
  },
  {
    name: 'Subheadline',
    fontSize: 36,
    fontWeight: 500,
    fontFamily: 'Inter, sans-serif',
    letterSpacing: -0.5,
  },
  {
    name: 'Body',
    fontSize: 24,
    fontWeight: 400,
    fontFamily: 'Inter, sans-serif',
    letterSpacing: 0,
  },
  {
    name: 'Caption',
    fontSize: 16,
    fontWeight: 400,
    fontFamily: 'Inter, sans-serif',
    letterSpacing: 0.5,
  },
  {
    name: 'Bold Statement',
    fontSize: 48,
    fontWeight: 800,
    fontFamily: 'Montserrat, sans-serif',
    letterSpacing: -1,
  },
];
export function TextPanel() {
  const [showColorPicker, setShowColorPicker] = useState(false);
  const [textColor, setTextColor] = useState('#ffffff');
  const [fontSize, setFontSize] = useState(48);
  const [fontWeight, setFontWeight] = useState(600);
  const [fontFamily, setFontFamily] = useState('Inter, sans-serif');
  const [textAlign, setTextAlign] = useState<'left' | 'center' | 'right'>('center');
  const { addLayer, canvas } = useEditorStore();
  const handleAddText = (preset?: (typeof textPresets)[0]) => {
    const data: TextLayerData = {
      type: 'text',
      text: 'Your text here',
      fontFamily: preset?.fontFamily || fontFamily,
      fontSize: preset?.fontSize || fontSize,
      fontWeight: preset?.fontWeight || fontWeight,
      color: textColor,
      align: textAlign,
      lineHeight: 1.2,
      letterSpacing: preset?.letterSpacing || 0,
    };
    const width = canvas.width * 0.8;
    const height = (preset?.fontSize || fontSize) * 1.5;
    addLayer({
      type: 'text',
      name: preset?.name || 'Text',
      visible: true,
      locked: false,
      opacity: 1,
      position: {
        x: (canvas.width - width) / 2,
        y: canvas.height / 2 - height / 2,
      },
      size: { width, height },
      rotation: 0,
      data,
    });
  };
  return (
    <div className="p-4">
      <h2 className="text-lg font-semibold text-white mb-4">Text</h2>
      {/* Quick presets */}
      <div className="mb-6">
        <label className="text-sm text-neutral-400 block mb-2">Quick Add</label>
        <div className="space-y-2">
          {textPresets.map((preset) => (
            <button
              key={preset.name}
              onClick={() => handleAddText(preset)}
              className="
                w-full p-3 rounded-lg text-left
                bg-neutral-800/50 hover:bg-neutral-800
                border border-neutral-700/50 hover:border-neutral-600
                transition-all group
              "
            >
              <div
                className="text-white group-hover:text-indigo-400 transition-colors truncate"
                style={{
                  fontFamily: preset.fontFamily,
                  fontSize: `${Math.min(preset.fontSize / 4, 20)}px`,
                  fontWeight: preset.fontWeight,
                }}
              >
                {preset.name}
              </div>
              <div className="text-xs text-neutral-500 mt-1">
                {preset.fontSize}px / {preset.fontWeight}
              </div>
            </button>
          ))}
        </div>
      </div>
      {/* Custom text options */}
      <div className="space-y-4">
        <div>
          <label className="text-sm text-neutral-400 block mb-2">Font Family</label>
          <select
            value={fontFamily}
            onChange={(e) => setFontFamily(e.target.value)}
            className="
              w-full px-3 py-2 rounded-lg
              bg-neutral-800 border border-neutral-700
              text-white focus:outline-none focus:border-indigo-500
            "
          >
            {Object.entries(fontCategories).map(([category, fonts]) => (
              <optgroup key={category} label={category}>
                {fonts.map((font) => (
                  <option key={font.value} value={font.value}>
                    {font.name}
                  </option>
                ))}
              </optgroup>
            ))}
          </select>
        </div>
        <div className="grid grid-cols-2 gap-3">
          <div>
            <label className="text-sm text-neutral-400 block mb-2">Size</label>
            <input
              type="number"
              value={fontSize}
              onChange={(e) => setFontSize(Number(e.target.value))}
              min={8}
              max={200}
              className="
                w-full px-3 py-2 rounded-lg
                bg-neutral-800 border border-neutral-700
                text-white focus:outline-none focus:border-indigo-500
              "
            />
          </div>
          <div>
            <label className="text-sm text-neutral-400 block mb-2">Weight</label>
            <select
              value={fontWeight}
              onChange={(e) => setFontWeight(Number(e.target.value))}
              className="
                w-full px-3 py-2 rounded-lg
                bg-neutral-800 border border-neutral-700
                text-white focus:outline-none focus:border-indigo-500
              "
            >
              <option value={300}>Light</option>
              <option value={400}>Regular</option>
              <option value={500}>Medium</option>
              <option value={600}>Semibold</option>
              <option value={700}>Bold</option>
              <option value={800}>Extra Bold</option>
              <option value={900}>Black</option>
            </select>
          </div>
        </div>
        {/* Text alignment */}
        <div>
          <label className="text-sm text-neutral-400 block mb-2">Alignment</label>
          <div className="flex gap-2">
            {[
              { value: 'left', icon: AlignLeft },
              { value: 'center', icon: AlignCenter },
              { value: 'right', icon: AlignRight },
            ].map(({ value, icon: Icon }) => (
              <button
                key={value}
                onClick={() => setTextAlign(value as 'left' | 'center' | 'right')}
                className={`
                  flex-1 p-2 rounded-lg flex items-center justify-center
                  transition-colors
                  ${
                    textAlign === value
                      ? 'bg-indigo-500/20 text-indigo-400 border border-indigo-500/50'
                      : 'bg-neutral-800 text-neutral-400 border border-neutral-700 hover:text-white'
                  }
                `}
              >
                <Icon className="w-4 h-4" />
              </button>
            ))}
          </div>
        </div>
        {/* Color */}
        <div>
          <label className="text-sm text-neutral-400 block mb-2">Color</label>
          <button
            onClick={() => setShowColorPicker(!showColorPicker)}
            className="
              w-full px-3 py-2 rounded-lg
              bg-neutral-800 border border-neutral-700
              flex items-center gap-3 hover:border-neutral-600 transition-colors
            "
          >
            <div
              className="w-6 h-6 rounded border border-neutral-600"
              style={{ backgroundColor: textColor }}
            />
            <span className="text-white font-mono text-sm">{textColor.toUpperCase()}</span>
          </button>
          {showColorPicker && (
            <div className="mt-2 p-3 bg-neutral-800 rounded-lg border border-neutral-700">
              <HexColorPicker color={textColor} onChange={setTextColor} />
              <input
                type="text"
                value={textColor}
                onChange={(e) => setTextColor(e.target.value)}
                className="
                  w-full mt-3 px-3 py-2 rounded
                  bg-neutral-900 border border-neutral-700
                  text-white text-sm font-mono
                  focus:outline-none focus:border-indigo-500
                "
              />
            </div>
          )}
        </div>
        {/* Add custom text button */}
        <button
          onClick={() => handleAddText()}
          className="
            w-full py-3 rounded-lg
            bg-indigo-500 hover:bg-indigo-600
            text-white font-medium
            transition-colors flex items-center justify-center gap-2
          "
        >
          <Type className="w-4 h-4" />
          Add Text
        </button>
      </div>
    </div>
  );
}