diff --git a/src/Drawd.jsx b/src/Drawd.jsx index b15d3c8..5047e85 100644 --- a/src/Drawd.jsx +++ b/src/Drawd.jsx @@ -19,6 +19,7 @@ import { useFileActions } from "./hooks/useFileActions"; import { useCollabSync } from "./hooks/useCollabSync"; import { useInteractionCallbacks } from "./hooks/useInteractionCallbacks"; import { useDerivedCanvasState } from "./hooks/useDerivedCanvasState"; +import { useTemplateInserter } from "./hooks/useTemplateInserter"; import { TopBar } from "./components/TopBar"; import { Sidebar } from "./components/Sidebar"; import { StickyNoteSidebar } from "./components/StickyNoteSidebar"; @@ -158,6 +159,18 @@ export default function Drawd({ initialRoomCode }) { const [renameModal, setRenameModal] = useState(null); const [showShortcuts, setShowShortcuts] = useState(false); const [formSummaryScreen, setFormSummaryScreen] = useState(null); + const [showTemplateBrowser, setShowTemplateBrowser] = useState(false); + + // ── Template inserter ───────────────────────────────────────────────── + const { insertTemplate } = useTemplateInserter({ + screens, mergeAll, replaceAll, pan, zoom, canvasRef, + }); + + const onTemplates = useCallback(() => setShowTemplateBrowser(true), []); + const onInsertTemplate = useCallback((data) => { + insertTemplate(data); + setShowTemplateBrowser(false); + }, [insertTemplate]); // ── Instruction generation ───────────────────────────────────────────── const { instructions, showInstructions, setShowInstructions, onGenerate, buildInstructionResult } = @@ -315,6 +328,7 @@ export default function Drawd({ initialRoomCode }) { selectedScreenGroup, setSelectedScreenGroup, deleteScreenGroup, undo, redo, saveNow, isFileSystemSupported, onSaveAs, onExport, onOpen, setActiveTool, + onTemplates, isReadOnly, }); @@ -379,6 +393,7 @@ export default function Drawd({ initialRoomCode }) { ) : null} onToggleParticipants={() => setShowParticipants((v) => !v)} showParticipants={showParticipants} + onTemplates={onTemplates} />
@@ -488,6 +503,7 @@ export default function Drawd({ initialRoomCode }) { setGroupContextMenu={setGroupContextMenu} handleImageUpload={handleImageUpload} addScreenAtCenter={addScreenAtCenter} + onTemplates={onTemplates} /> {selectedScreenData && ( @@ -573,6 +589,9 @@ export default function Drawd({ initialRoomCode }) { setFigmaError={setFigmaError} formSummaryScreen={formSummaryScreen} setFormSummaryScreen={setFormSummaryScreen} + showTemplateBrowser={showTemplateBrowser} + setShowTemplateBrowser={setShowTemplateBrowser} + onInsertTemplate={onInsertTemplate} />
); diff --git a/src/components/CanvasArea.jsx b/src/components/CanvasArea.jsx index 3689349..6c65c17 100644 --- a/src/components/CanvasArea.jsx +++ b/src/components/CanvasArea.jsx @@ -52,6 +52,8 @@ export function CanvasArea({ groupContextMenu, setGroupContextMenu, // ToolBar setActiveTool, handleImageUpload, addScreenAtCenter, + // Templates + onTemplates, }) { return (
- {screens.length === 0 && } + {screens.length === 0 && } {/* Zoom indicator */}
); diff --git a/src/components/EmptyState.jsx b/src/components/EmptyState.jsx index e17205b..b6cde2a 100644 --- a/src/components/EmptyState.jsx +++ b/src/components/EmptyState.jsx @@ -1,6 +1,6 @@ import { COLORS, FONTS } from "../styles/theme"; -export function EmptyState() { +export function EmptyState({ onTemplates }) { return (
wireframes, screenshots, or mockups
+ {onTemplates && ( + + )}
); } diff --git a/src/components/ModalsLayer.jsx b/src/components/ModalsLayer.jsx index 8fba927..86ca49a 100644 --- a/src/components/ModalsLayer.jsx +++ b/src/components/ModalsLayer.jsx @@ -11,6 +11,7 @@ import { ShortcutsPanel } from "./ShortcutsPanel"; import { ShareModal } from "./ShareModal"; import { HostLeftModal } from "./HostLeftModal"; import { FormSummaryPanel } from "./FormSummaryPanel"; +import { TemplateBrowserModal } from "./TemplateBrowserModal"; export function ModalsLayer({ // Hotspot modal @@ -43,6 +44,8 @@ export function ModalsLayer({ figmaProcessing, figmaError, setFigmaError, // Form summary formSummaryScreen, setFormSummaryScreen, + // Template browser + showTemplateBrowser, setShowTemplateBrowser, onInsertTemplate, }) { return ( <> @@ -212,6 +215,13 @@ export function ModalsLayer({ )} + {showTemplateBrowser && ( + setShowTemplateBrowser(false)} + /> + )} + {formSummaryScreen && ( + + + + + + ); +} + +function TemplateCard({ template, onSelect, loading }) { + return ( + + ); +} + +export function TemplateBrowserModal({ onInsert, onClose }) { + const [loading, setLoading] = useState(false); + + const handleSelect = async (template) => { + setLoading(true); + try { + const data = await template.getData(); + setLoading(false); + onInsert(data); + } catch { + setLoading(false); + } + }; + + return ( +
+
e.stopPropagation()} + style={{ + ...styles.modalCard, + width: 540, + maxHeight: "80vh", + display: "flex", + flexDirection: "column", + }} + > +
+
+ +
+

Templates

+

+ Start with a pre-built flow and customize it +

+
+
+ +
+ +
+ {TEMPLATES.map((template) => ( + + ))} +
+ +
+ +
+
+
+ ); +} diff --git a/src/components/ToolBar.jsx b/src/components/ToolBar.jsx index 0b22044..c861c1c 100644 --- a/src/components/ToolBar.jsx +++ b/src/components/ToolBar.jsx @@ -1,4 +1,5 @@ import { COLORS, FONTS } from "../styles/theme"; +import { TemplateIcon as TemplateIconBase } from "./TemplateBrowserModal"; const SelectIcon = () => ( @@ -89,7 +90,9 @@ function ActionButton({ icon: Icon, label, shortcutKey, onClick }) { ); } -export function ToolBar({ activeTool, onToolChange, onUpload, onAddBlank, onAddStickyNote, isReadOnly }) { +const TemplateIcon = () => ; + +export function ToolBar({ activeTool, onToolChange, onUpload, onAddBlank, onAddStickyNote, isReadOnly, onTemplates }) { return (
+
+ )}
diff --git a/src/components/TopBar.jsx b/src/components/TopBar.jsx index 7d81846..49fbdea 100644 --- a/src/components/TopBar.jsx +++ b/src/components/TopBar.jsx @@ -94,7 +94,7 @@ function ShareIcon() { ); } -export function TopBar({ screenCount, connectionCount, onExport, onImport, onGenerate, canUndo, canRedo, onUndo, onRedo, connectedFileName, saveStatus, isFileSystemSupported, onNew, onOpen, onSaveAs, onDocuments, documentCount = 0, onDataModels, dataModelCount = 0, collabState, onShare, collabBadge, collabPresence, onToggleParticipants, showParticipants }) { +export function TopBar({ screenCount, connectionCount, onExport, onImport, onGenerate, canUndo, canRedo, onUndo, onRedo, connectedFileName, saveStatus, isFileSystemSupported, onNew, onOpen, onSaveAs, onDocuments, documentCount = 0, onDataModels, dataModelCount = 0, collabState, onShare, collabBadge, collabPresence, onToggleParticipants, showParticipants, onTemplates }) { const [fileMenuOpen, setFileMenuOpen] = useState(false); const fileMenuRef = useRef(null); @@ -360,6 +360,15 @@ export function TopBar({ screenCount, connectionCount, onExport, onImport, onGen New + + {isFileSystemSupported && (