/** * ComponentLibrary * Provides a draggable library of available CMS components */ import React, { useState, useMemo, useCallback } from "react"; import { ComponentType, ComponentCategory, CMSComponent, ComponentMetadata, } from "../types/components.js"; // ======================================== // Types & Interfaces // ======================================== export interface ComponentLibraryProps { onComponentSelect?: (component: CMSComponent) => void; onComponentDrag?: (component: CMSComponent) => void; selectedCategory?: ComponentCategory; searchTerm?: string; className?: string; style?: React.CSSProperties; } export interface ComponentTemplateProps { type: ComponentType; _category: ComponentCategory; metadata: ComponentMetadata; onSelect: (component: CMSComponent) => void; onDrag: (component: CMSComponent) => void; isSelected?: boolean; } export interface CategoryFilterProps { categories: ComponentCategory[]; selectedCategory?: ComponentCategory; onCategorySelect: (category?: ComponentCategory) => void; } // ======================================== // Component Templates Data // ======================================== const COMPONENT_TEMPLATES: Array<{ type: ComponentType; category: ComponentCategory; metadata: ComponentMetadata; defaultConfig: Partial; }> = [ // Content Components { type: "heading", category: "content", metadata: { title: "Heading", description: "Display headings from H1 to H6", tags: ["text", "title", "header"], icon: "📝", previewUrl: "", }, defaultConfig: { configuration: { text: "Your Heading Here", level: "h1", alignment: "left", }, style: { fontSize: "2rem", fontWeight: "bold", color: "#1f2937", marginBottom: "1rem", }, }, }, { type: "paragraph", category: "content", metadata: { title: "Paragraph", description: "Add text content with rich formatting", tags: ["text", "content", "body"], icon: "📄", previewUrl: "", }, defaultConfig: { configuration: { text: "Your paragraph text goes here. You can format it with bold, italic, and other styles.", alignment: "left", }, style: { fontSize: "1rem", lineHeight: "1.6", color: "#374151", marginBottom: "1rem", }, }, }, { type: "rich_text", category: "content", metadata: { title: "Rich Text", description: "Advanced text editor with formatting options", tags: ["text", "editor", "formatting"], icon: "✍️", previewUrl: "", }, defaultConfig: { configuration: { content: "

Start writing your rich content...

", toolbar: ["bold", "italic", "link", "list"], }, }, }, { type: "list", category: "content", metadata: { title: "List", description: "Create ordered or unordered lists", tags: ["list", "bullet", "numbered"], icon: "📋", previewUrl: "", }, defaultConfig: { configuration: { items: ["Item 1", "Item 2", "Item 3"], listType: "unordered", style: "bullet", }, }, }, // Media Components { type: "image", category: "media", metadata: { title: "Image", description: "Display images with responsive sizing", tags: ["image", "photo", "media"], icon: "🖼️", previewUrl: "", }, defaultConfig: { configuration: { src: "/placeholder-image.jpg", alt: "Image description", width: "auto", height: "auto", }, style: { borderRadius: "8px", maxWidth: "100%", height: "auto", }, }, }, { type: "video", category: "media", metadata: { title: "Video", description: "Embed videos with custom controls", tags: ["video", "media", "embed"], icon: "🎥", previewUrl: "", }, defaultConfig: { configuration: { src: "", poster: "", controls: true, autoplay: false, loop: false, }, }, }, { type: "gallery", category: "media", metadata: { title: "Gallery", description: "Image gallery with lightbox", tags: ["gallery", "images", "slideshow"], icon: "🖼️", previewUrl: "", }, defaultConfig: { configuration: { images: [], layout: "grid", columns: 3, showThumbnails: true, }, }, }, // Layout Components { type: "container", category: "layout", metadata: { title: "Container", description: "Flexible container for grouping content", tags: ["container", "wrapper", "layout"], icon: "📦", previewUrl: "", }, defaultConfig: { layout: { display: "block", position: "static", maxWidth: "1200px", margin: { x: "auto" }, padding: { all: "1rem" }, }, }, }, { type: "grid", category: "layout", metadata: { title: "Grid", description: "CSS Grid layout for complex arrangements", tags: ["grid", "layout", "responsive"], icon: "⚏", previewUrl: "", }, defaultConfig: { layout: { display: "grid", position: "static", gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))", gridGap: "1rem", }, }, }, { type: "section", category: "layout", metadata: { title: "Section", description: "Page section with background and spacing", tags: ["section", "area", "background"], icon: "📐", previewUrl: "", }, defaultConfig: { layout: { display: "block", position: "static", padding: { y: "4rem", x: "2rem" }, }, style: { backgroundColor: "#ffffff", }, }, }, // Interactive Components { type: "button", category: "interactive", metadata: { title: "Button", description: "Clickable button with custom styling", tags: ["button", "action", "cta"], icon: "🔘", previewUrl: "", }, defaultConfig: { configuration: { text: "Click Me", variant: "primary", size: "medium", disabled: false, }, style: { backgroundColor: "#3b82f6", color: "#ffffff", padding: "0.75rem 1.5rem", borderRadius: "0.5rem", border: "none", cursor: "pointer", }, }, }, { type: "cta", category: "interactive", metadata: { title: "Call to Action", description: "Prominent call-to-action section", tags: ["cta", "action", "conversion"], icon: "📢", previewUrl: "", }, defaultConfig: { configuration: { title: "Ready to Get Started?", description: "Join thousands of satisfied customers", buttonText: "Get Started", buttonLink: "#", }, style: { backgroundColor: "#1e40af", color: "#ffffff", padding: "4rem 2rem", textAlign: "center", borderRadius: "1rem", }, }, }, { type: "form", category: "interactive", metadata: { title: "Form", description: "Custom form with validation", tags: ["form", "input", "contact"], icon: "📝", previewUrl: "", }, defaultConfig: { configuration: { fields: [ { type: "text", name: "name", label: "Name", required: true }, { type: "email", name: "email", label: "Email", required: true }, { type: "textarea", name: "message", label: "Message", required: false, }, ], submitText: "Submit", action: "", method: "POST", }, }, }, // Navigation Components { type: "navigation", category: "navigation", metadata: { title: "Navigation", description: "Site navigation menu", tags: ["nav", "menu", "links"], icon: "🧭", previewUrl: "", }, defaultConfig: { configuration: { items: [ { label: "Home", href: "/" }, { label: "About", href: "/about" }, { label: "Services", href: "/services" }, { label: "Contact", href: "/contact" }, ], orientation: "horizontal", style: "default", }, }, }, { type: "breadcrumb", category: "navigation", metadata: { title: "Breadcrumb", description: "Navigation breadcrumb trail", tags: ["breadcrumb", "navigation", "path"], icon: "🍞", previewUrl: "", }, defaultConfig: { configuration: { items: [ { label: "Home", href: "/" }, { label: "Category", href: "/category" }, { label: "Current Page", href: "#" }, ], separator: "/", }, }, }, // Data Components { type: "table", category: "data", metadata: { title: "Table", description: "Data table with sorting and filtering", tags: ["table", "data", "grid"], icon: "📊", previewUrl: "", }, defaultConfig: { configuration: { headers: ["Name", "Email", "Role"], rows: [ ["John Doe", "john@example.com", "Admin"], ["Jane Smith", "jane@example.com", "User"], ], sortable: true, filterable: false, }, }, }, { type: "chart", category: "data", metadata: { title: "Chart", description: "Interactive charts and graphs", tags: ["chart", "graph", "analytics"], icon: "📈", previewUrl: "", }, defaultConfig: { configuration: { type: "bar", data: { labels: ["Jan", "Feb", "Mar", "Apr"], datasets: [ { label: "Sales", data: [10, 20, 30, 40], }, ], }, }, }, }, ]; // ======================================== // Utility Functions // ======================================== const generateUniqueId = (): string => { return `comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }; const createComponentFromTemplate = ( template: (typeof COMPONENT_TEMPLATES)[0] ): CMSComponent => { return { id: generateUniqueId(), name: template.metadata.title, type: template.type, category: template.category, description: template.metadata.description, props: {}, defaultProps: {}, requiredProps: [], configuration: template.defaultConfig.configuration || {}, layout: template.defaultConfig.layout || { display: "block", position: "static", }, style: template.defaultConfig.style || {}, theme: { primary: "#3b82f6", secondary: "#6b7280", accent: "#10b981", background: "#ffffff", surface: "#f9fafb", text: "#1f2937", textSecondary: "#6b7280", border: "#e5e7eb", shadow: "0 1px 3px rgba(0, 0, 0, 0.1)", success: "#10b981", warning: "#f59e0b", error: "#ef4444", info: "#3b82f6", }, editable: true, locked: false, hidden: false, tags: template.metadata.tags, author: "System", metadata: template.metadata, children: [], parent: undefined, dependencies: [], validation: { rules: [], async: false, realTime: false, showErrors: true, showWarnings: true, }, preview: { responsive: true, devices: [], defaultDevice: "desktop", refreshMode: "auto", interactive: true, showRulers: false, showGrid: false, showBoundaries: false, }, createdAt: new Date(), updatedAt: new Date(), version: "1.0.0", }; }; // ======================================== // Category Filter Component // ======================================== const CategoryFilter: React.FC = ({ categories, selectedCategory, onCategorySelect, }) => { const categoryLabels: Record = { content: "Content", layout: "Layout", interactive: "Interactive", data: "Data", media: "Media", navigation: "Navigation", }; const categoryIcons: Record = { content: "📝", layout: "📐", interactive: "🔘", data: "📊", media: "🖼️", navigation: "🧭", }; return (
{categories.map((category) => ( ))}
); }; // ======================================== // Component Template Component // ======================================== const ComponentTemplate: React.FC = ({ type, _category, metadata, onSelect, onDrag, isSelected = false, }) => { const handleClick = useCallback(() => { const template = COMPONENT_TEMPLATES.find((t) => t.type === type); if (template) { const component = createComponentFromTemplate(template); onSelect(component); } }, [type, onSelect]); const handleDragStart = useCallback( (e: React.DragEvent) => { const template = COMPONENT_TEMPLATES.find((t) => t.type === type); if (template) { const component = createComponentFromTemplate(template); e.dataTransfer.setData("text/plain", JSON.stringify(component)); onDrag(component); } }, [type, onDrag] ); return (
{ if (!isSelected) { e.currentTarget.style.borderColor = "#9ca3af"; e.currentTarget.style.backgroundColor = "#f9fafb"; } }} onMouseLeave={(e) => { if (!isSelected) { e.currentTarget.style.borderColor = "#e5e7eb"; e.currentTarget.style.backgroundColor = "#ffffff"; } }} >
{metadata.icon}
{metadata.title}
{metadata.description}
{metadata.tags.slice(0, 2).map((tag) => ( {tag} ))}
); }; // ======================================== // Main ComponentLibrary Component // ======================================== export const ComponentLibrary: React.FC = ({ onComponentSelect, onComponentDrag, selectedCategory, searchTerm = "", className = "", style = {}, }) => { const [internalSelectedCategory, setInternalSelectedCategory] = useState< ComponentCategory | undefined >(selectedCategory); const [selectedComponent, setSelectedComponent] = useState(null); const categories = useMemo(() => { const uniqueCategories = Array.from( new Set(COMPONENT_TEMPLATES.map((template) => template.category)) ); return uniqueCategories.sort(); }, []); const filteredTemplates = useMemo(() => { return COMPONENT_TEMPLATES.filter((template) => { // Category filter if ( internalSelectedCategory && template.category !== internalSelectedCategory ) { return false; } // Search filter if (searchTerm) { const searchLower = searchTerm.toLowerCase(); return ( template.metadata.title.toLowerCase().includes(searchLower) || template.metadata.description.toLowerCase().includes(searchLower) || template.metadata.tags.some((tag) => tag.toLowerCase().includes(searchLower) ) ); } return true; }); }, [internalSelectedCategory, searchTerm]); const handleCategorySelect = useCallback((category?: ComponentCategory) => { setInternalSelectedCategory(category); }, []); const handleComponentSelect = useCallback( (component: CMSComponent) => { setSelectedComponent(component); if (onComponentSelect) { onComponentSelect(component); } }, [onComponentSelect] ); const handleComponentDrag = useCallback( (component: CMSComponent) => { if (onComponentDrag) { onComponentDrag(component); } }, [onComponentDrag] ); return (
{/* Header */}

Component Library

Drag components to content zones or click to select

{/* Category Filter */} {/* Search Results Count */}
{filteredTemplates.length} component {filteredTemplates.length !== 1 ? "s" : ""} available {internalSelectedCategory && ` in ${internalSelectedCategory}`} {searchTerm && ` matching "${searchTerm}"`}
{/* Component Grid */}
{filteredTemplates.map((template) => ( ))}
{/* Empty State */} {filteredTemplates.length === 0 && (
🔍
No components found
Try adjusting your filters or search terms
)}
); }; // ======================================== // Export Default // ======================================== export default ComponentLibrary;