From 580e798e16346eb894eb899edac82d2efdf40adc Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sat, 18 Apr 2026 22:32:53 -0500 Subject: initial commit --- src/App.tsx | 130 +++ src/components/BrandDoc.tsx | 518 ++++++++++++ src/components/CopyButton.tsx | 31 + src/components/InputPanel.tsx | 133 +++ src/components/PackageSwitcher.tsx | 125 +++ src/components/PreviewPanel.tsx | 116 +++ src/components/SettingsPanel.tsx | 158 ++++ src/components/TokenInput.tsx | 85 ++ src/engine/aiGenerator.ts | 138 ++++ src/engine/generator.ts | 957 ++++++++++++++++++++++ src/hooks/usePackages.ts | 112 +++ src/hooks/useSettings.ts | 41 + src/hooks/useWorkspace.ts | 225 +++++ src/lib/clipboard.ts | 17 + src/lib/export.ts | 326 ++++++++ src/lib/sanitize.ts | 141 ++++ src/main.tsx | 10 + src/styles/globals.css | 1594 ++++++++++++++++++++++++++++++++++++ src/types.ts | 100 +++ 19 files changed, 4957 insertions(+) create mode 100644 src/App.tsx create mode 100644 src/components/BrandDoc.tsx create mode 100644 src/components/CopyButton.tsx create mode 100644 src/components/InputPanel.tsx create mode 100644 src/components/PackageSwitcher.tsx create mode 100644 src/components/PreviewPanel.tsx create mode 100644 src/components/SettingsPanel.tsx create mode 100644 src/components/TokenInput.tsx create mode 100644 src/engine/aiGenerator.ts create mode 100644 src/engine/generator.ts create mode 100644 src/hooks/usePackages.ts create mode 100644 src/hooks/useSettings.ts create mode 100644 src/hooks/useWorkspace.ts create mode 100644 src/lib/clipboard.ts create mode 100644 src/lib/export.ts create mode 100644 src/lib/sanitize.ts create mode 100644 src/main.tsx create mode 100644 src/styles/globals.css create mode 100644 src/types.ts (limited to 'src') diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..bd43726 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,130 @@ +import { useState, useEffect } from 'react'; +import { useWorkspace } from './hooks/useWorkspace'; +import { usePackages } from './hooks/usePackages'; +import { useSettings } from './hooks/useSettings'; +import type { PackageSlot } from './hooks/usePackages'; +import { InputPanel } from './components/InputPanel'; +import { PreviewPanel } from './components/PreviewPanel'; +import { PackageSwitcher } from './components/PackageSwitcher'; +import { SettingsPanel } from './components/SettingsPanel'; +import type { BrandOutputs } from './types'; + +interface WorkspaceShellProps { + storageKey: string; + slots: PackageSlot[]; + activeId: string; + onSwitch: (id: string) => void; + onCreate: () => void; + onRemove: (id: string) => void; + onRename: (id: string, name: string) => void; + onDuplicate: (id: string) => void; + onOpenSettings: () => void; + aiEnabled: boolean; +} + +function WorkspaceShell({ + storageKey, slots, activeId, + onSwitch, onCreate, onRemove, onRename, onDuplicate, + onOpenSettings, aiEnabled, +}: WorkspaceShellProps) { + const { + inputs, setInputs, outputs, isGenerating, generateMode, generateError, + locked, runGenerate, cancelGenerate, updateEdit, toggleLock, + undo, redo, canUndo, canRedo, + } = useWorkspace(storageKey); + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (!(e.metaKey || e.ctrlKey) || e.key !== 'z') return; + e.preventDefault(); + if (e.shiftKey) redo(); + else undo(); + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, [undo, redo]); + + const handleEdit = (key: K, value: BrandOutputs[K]) => + updateEdit(key, value); + + const generateLabel = isGenerating + ? 'Cancel' + : outputs + ? (aiEnabled ? `Regenerate` : 'Regenerate') + : (aiEnabled ? 'Generate with AI' : 'Generate'); + + return ( + <> +
+
+ Brandbench + +
+
+ {outputs && ( + <> + + + + )} + {aiEnabled && ( + AI + )} + +
+
+ +
+ + +
+ + ); +} + +export default function App() { + const { slots, activeId, activeSlot, switchTo, createNew, remove, rename, duplicate } = usePackages(); + const { settings, setSettings } = useSettings(); + const [settingsOpen, setSettingsOpen] = useState(false); + + return ( + <> + setSettingsOpen(true)} + aiEnabled={settings.enabled} + /> + {settingsOpen && ( + setSettingsOpen(false)} + /> + )} + + ); +} diff --git a/src/components/BrandDoc.tsx b/src/components/BrandDoc.tsx new file mode 100644 index 0000000..fffea97 --- /dev/null +++ b/src/components/BrandDoc.tsx @@ -0,0 +1,518 @@ +import { useRef, useState } from 'react'; +import type { BrandInputs, BrandOutputs, ColorSwatch, LockedSections, Typography } from '../types'; +import { CopyButton } from './CopyButton'; + +// ── Editable text ───────────────────────────────────────────────────────────── + +interface EditableProps { + value: string; + onChange: (v: string) => void; + multiline?: boolean; +} + +function Editable({ value, onChange, multiline = false }: EditableProps) { + const [editing, setEditing] = useState(false); + const [draft, setDraft] = useState(value); + const ref = useRef(null); + + const commit = () => { + setEditing(false); + if (draft !== value) onChange(draft); + }; + + if (!editing) { + return ( +
{ setDraft(value); setEditing(true); }} + title="Click to edit" + style={{ whiteSpace: multiline ? 'pre-wrap' : 'normal', padding: '2px 4px', margin: '-2px -4px' }} + > + {value} +
+ ); + } + + if (multiline) { + return ( +