← back to Norma

components/InboxThemeBanner.tsx

149 lines

'use client';

// Top-of-page inbox-theme banner. Full-width strip at the very top of the
// AppShell with prev/next cycle buttons + dropdown so you can scrub through
// the full 32-theme set. Writes <body data-inbox-theme="..."> + persists to
// localStorage. CSS skins in globals.css scope down to .gmail-crm-tab so
// only the Gmail CRM tab visual changes.

import { useEffect, useState, useCallback } from 'react';
import { ChevronLeft, ChevronRight, Palette, Shuffle } from 'lucide-react';
import { INBOX_THEMES, INBOX_THEME_LS_KEY, DEFAULT_INBOX_THEME } from '@/lib/inbox-themes';

export default function InboxThemeBanner() {
  const [theme, setTheme] = useState<string>(DEFAULT_INBOX_THEME);
  const [mounted, setMounted] = useState(false);

  // Hydrate from localStorage post-mount (avoids SSR mismatch)
  useEffect(() => {
    setMounted(true);
    try {
      const s = localStorage.getItem(INBOX_THEME_LS_KEY);
      if (s && INBOX_THEMES.some(t => t.id === s)) setTheme(s);
    } catch {}
  }, []);

  // Apply to <body data-inbox-theme="..."> + persist
  useEffect(() => {
    if (!mounted) return;
    if (typeof document !== 'undefined') document.body.dataset.inboxTheme = theme;
    try { localStorage.setItem(INBOX_THEME_LS_KEY, theme); } catch {}
  }, [theme, mounted]);

  const currentIdx = Math.max(0, INBOX_THEMES.findIndex(t => t.id === theme));
  const current    = INBOX_THEMES[currentIdx];

  const step = useCallback((delta: number) => {
    const n = INBOX_THEMES.length;
    const next = ((currentIdx + delta) % n + n) % n; // wrap both directions
    setTheme(INBOX_THEMES[next].id);
  }, [currentIdx]);

  const randomize = useCallback(() => {
    const others = INBOX_THEMES.filter(t => t.id !== theme);
    setTheme(others[Math.floor(Math.random() * others.length)].id);
  }, [theme]);

  return (
    <div
      role="toolbar"
      aria-label="Gmail CRM theme switcher"
      style={{
        position: 'sticky',
        top: 0,
        zIndex: 110,
        display: 'flex',
        alignItems: 'center',
        gap: 12,
        padding: '8px 16px',
        background: 'var(--color-surface)',
        borderBottom: '1px solid var(--color-border)',
        fontSize: 12,
        color: 'var(--color-text-secondary)',
        flexWrap: 'wrap',
      }}
    >
      <div style={{
        display: 'flex', alignItems: 'center', gap: 6,
        fontWeight: 700, color: 'var(--color-text)', fontSize: 11.5,
        letterSpacing: '0.06em', textTransform: 'uppercase',
        paddingRight: 10, borderRight: '1px solid var(--color-border)',
      }}>
        <Palette size={14} />
        Gmail CRM theme
      </div>

      {/* Prev / counter / Next cycle */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
        <button
          onClick={() => step(-1)}
          className="btn btn-ghost btn-sm"
          aria-label="Previous theme"
          title="Previous theme (←)"
          style={{ padding: '4px 8px' }}
        >
          <ChevronLeft size={14} />
        </button>
        <span style={{
          minWidth: 56, textAlign: 'center',
          fontFamily: 'ui-monospace, Menlo, monospace',
          fontSize: 11.5, color: 'var(--color-text-muted)',
        }}>
          {currentIdx + 1} / {INBOX_THEMES.length}
        </span>
        <button
          onClick={() => step(1)}
          className="btn btn-ghost btn-sm"
          aria-label="Next theme"
          title="Next theme (→)"
          style={{ padding: '4px 8px' }}
        >
          <ChevronRight size={14} />
        </button>
        <button
          onClick={randomize}
          className="btn btn-ghost btn-sm"
          aria-label="Random theme"
          title="Random theme"
          style={{ padding: '4px 8px' }}
        >
          <Shuffle size={13} />
        </button>
      </div>

      {/* Dropdown for direct jump */}
      <select
        value={theme}
        onChange={e => setTheme(e.target.value)}
        className="input"
        aria-label="Pick inbox theme"
        title={current?.description || 'Pick a theme'}
        style={{
          fontSize: 12, padding: '4px 8px',
          minWidth: 220, maxWidth: 280,
        }}
      >
        {INBOX_THEMES.map(t => (
          <option key={t.id} value={t.id}>🎨 {t.label}</option>
        ))}
      </select>

      {/* Current theme name + description (caption to the right) */}
      <span style={{ flex: 1, minWidth: 0, fontStyle: 'italic', opacity: 0.85, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
        {current ? `${current.label.replace(/^\d+ · /, '')} — ${current.description}` : ''}
      </span>

      <a
        href="/mockups/inbox/index.html"
        target="_blank"
        rel="noopener noreferrer"
        className="btn btn-ghost btn-sm"
        title="Open the full 32-mockup gallery in a new tab"
        style={{ fontSize: 11, padding: '4px 10px' }}
      >
        Gallery ↗
      </a>
    </div>
  );
}