UNPKG

@brutalcomponent/react

Version:
911 lines (908 loc) 32.6 kB
import { Tabs, TabsList, TabsTrigger, TabsContent, Card, Toggle, Tooltip } from '../../chunk-JDHAADYK.mjs'; import { Input, Textarea, Select } from '../../chunk-YLIM27QY.mjs'; import { Button } from '../../chunk-E2BWEC67.mjs'; import { Icon } from '../../chunk-QB4EPFXT.mjs'; import '../../chunk-KMHX64YN.mjs'; import '../../chunk-7QJK2SK5.mjs'; import React4, { useState, useRef, useCallback, useEffect } from 'react'; import { clsx } from 'clsx'; import { FaBold, FaItalic, FaHeading, FaListUl, FaLink, FaChevronDown, FaListOl, FaQuoteLeft, FaCode, FaImage, FaSave, FaPen, FaEye, FaCog, FaClock, FaUnderline, FaStrikethrough, FaAlignLeft, FaAlignCenter, FaAlignRight, FaTable, FaPalette, FaHighlighter, FaIndent, FaOutdent, FaUndo, FaRedo, FaPlus, FaTimes, FaCompress, FaExpand } from 'react-icons/fa'; /** * @brutalcomponent/react * (c) David Heffler (https://dvh.sh) * Licensed under MIT */ var MarkdownToolbar = ({ onAction, brutal = true, showMobile = true, className }) => { const [showMore, setShowMore] = useState(false); const primaryActions = [ { icon: FaBold, label: "Bold", markdown: "**text**" }, { icon: FaItalic, label: "Italic", markdown: "*text*" }, { icon: FaHeading, label: "Heading", markdown: "## " }, { icon: FaListUl, label: "List", markdown: "- " }, { icon: FaLink, label: "Link", markdown: "[text](url)" } ]; const secondaryActions = [ { icon: FaListOl, label: "Numbered", markdown: "1. " }, { icon: FaQuoteLeft, label: "Quote", markdown: "> " }, { icon: FaCode, label: "Code", markdown: "`code`" }, { icon: FaImage, label: "Image", markdown: "![alt](url)" } ]; return /* @__PURE__ */ React4.createElement( "div", { className: clsx( "border-b-2 border-brutal-black bg-brutal-gray-50", className ) }, /* @__PURE__ */ React4.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React4.createElement("div", { className: "flex items-center" }, primaryActions.map((action, index) => /* @__PURE__ */ React4.createElement( "button", { key: index, type: "button", onClick: () => onAction(action.markdown), className: clsx( "p-3 hover:bg-brutal-gray-200 transition-colors", "border-r border-brutal-gray-300" ), title: action.label }, /* @__PURE__ */ React4.createElement(Icon, { icon: action.icon, size: "sm" }) ))), showMobile && /* @__PURE__ */ React4.createElement( "button", { type: "button", onClick: () => setShowMore(!showMore), className: clsx( "p-3 hover:bg-brutal-gray-200 transition-colors", "flex items-center gap-1 lg:hidden" ) }, /* @__PURE__ */ React4.createElement("span", { className: "text-xs font-bold uppercase" }, "More"), /* @__PURE__ */ React4.createElement( Icon, { icon: FaChevronDown, size: "xs", className: clsx("transition-transform", showMore && "rotate-180") } ) ), /* @__PURE__ */ React4.createElement("div", { className: "hidden lg:flex items-center border-l-2 border-brutal-black" }, secondaryActions.map((action, index) => /* @__PURE__ */ React4.createElement( "button", { key: index, type: "button", onClick: () => onAction(action.markdown), className: clsx( "p-3 hover:bg-brutal-gray-200 transition-colors", "border-r border-brutal-gray-300" ), title: action.label }, /* @__PURE__ */ React4.createElement(Icon, { icon: action.icon, size: "sm" }) )))), showMore && showMobile && /* @__PURE__ */ React4.createElement("div", { className: "flex items-center border-t border-brutal-gray-300 lg:hidden" }, secondaryActions.map((action, index) => /* @__PURE__ */ React4.createElement( "button", { key: index, type: "button", onClick: () => onAction(action.markdown), className: clsx( "p-3 hover:bg-brutal-gray-200 transition-colors", "border-r border-brutal-gray-300" ), title: action.label }, /* @__PURE__ */ React4.createElement(Icon, { icon: action.icon, size: "sm" }) ))) ); }; // src/modules/editor/MarkdownEditor/MarkdownEditor.tsx var MarkdownEditor = ({ value, onChange, placeholder = "Write your content in Markdown...", minHeight = "400px", showToolbar = true, brutal = true, className, textareaClassName }) => { const textareaRef = useRef(null); const insertMarkdown = useCallback( (markdown) => { const textarea = textareaRef.current; if (!textarea) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; const newContent = value.substring(0, start) + markdown + value.substring(end); onChange(newContent); setTimeout(() => { textarea.focus(); textarea.setSelectionRange( start + markdown.length, start + markdown.length ); }, 0); }, [value, onChange] ); return /* @__PURE__ */ React4.createElement( "div", { className: clsx( brutal && "border-4 border-brutal-black", !brutal && "border-2 border-brutal-gray-300", "bg-brutal-white", className ) }, showToolbar && /* @__PURE__ */ React4.createElement(MarkdownToolbar, { onAction: insertMarkdown, brutal }), /* @__PURE__ */ React4.createElement("div", { className: "relative", style: { minHeight } }, /* @__PURE__ */ React4.createElement( "textarea", { ref: textareaRef, value, onChange: (e) => onChange(e.target.value), placeholder, className: clsx( "w-full h-full p-4 resize-none", "font-mono text-sm bg-transparent", "focus:outline-none", textareaClassName ), style: { minHeight } } ), /* @__PURE__ */ React4.createElement("div", { className: "absolute bottom-2 right-2 text-xs text-brutal-gray-500" }, value.length, " characters")) ); }; var MarkdownPreview = ({ content, className, parser }) => { const [html, setHtml] = useState(""); const [error, setError] = useState(null); useEffect(() => { const parseContent = async () => { if (!content) { setHtml(""); return; } try { if (parser) { const parsed = await parser(content); setHtml(parsed); } else { setHtml(`<pre>${content}</pre>`); console.warn( "No markdown parser provided. Install marked: npm install marked" ); } setError(null); } catch (err) { console.error("Error parsing markdown:", err); setError("Error rendering markdown"); } }; parseContent(); }, [content, parser]); if (!content) { return /* @__PURE__ */ React4.createElement("div", { className: "text-brutal-gray-400 text-sm italic p-4" }, "Preview will appear here..."); } if (error) { return /* @__PURE__ */ React4.createElement("div", { className: "text-brutal-coral text-sm p-4" }, error); } return /* @__PURE__ */ React4.createElement( "div", { className: clsx( "prose prose-brutal max-w-none", // Brutal prose styles "prose-headings:font-black prose-headings:uppercase prose-headings:tracking-wider", "prose-h1:text-4xl prose-h1:text-brutal-black prose-h1:transform prose-h1:-skew-x-2", "prose-h2:text-3xl prose-h2:text-brutal-black", "prose-h3:text-2xl prose-h3:text-brutal-black", // Paragraphs "prose-p:text-brutal-gray-700 prose-p:font-mono prose-p:leading-relaxed", // Links "prose-a:text-brutal-pink prose-a:no-underline prose-a:font-bold", "prose-a:border-b-2 prose-a:border-brutal-pink", "prose-a:hover:text-brutal-peach prose-a:hover:border-brutal-peach", // Lists "prose-ul:list-none prose-ul:space-y-2", 'prose-li:before:content-["\u2192"] prose-li:before:text-brutal-pink', "prose-li:before:font-black prose-li:before:mr-2", // Code "prose-code:bg-brutal-gray-200 prose-code:text-brutal-black", "prose-code:px-2 prose-code:py-1", "prose-pre:bg-brutal-black prose-pre:text-brutal-white", "prose-pre:border-4 prose-pre:border-brutal-black", className ), dangerouslySetInnerHTML: { __html: html } } ); }; var BlogEditor = ({ initialData = {}, onSave, onCancel, categories = [], authors = [], loading = false, brutal = true, className, markdownParser }) => { const [formData, setFormData] = useState({ title: initialData.title || "", slug: initialData.slug || "", content: initialData.content || "", excerpt: initialData.excerpt || "", status: initialData.status || "draft", tags: initialData.tags || "", category: initialData.category || "", featuredImage: initialData.featuredImage || "", publishedAt: initialData.publishedAt || "", isFeatured: initialData.isFeatured || false, author: initialData.author || "" }); const [autoSave, setAutoSave] = useState(true); const [lastSaved, setLastSaved] = useState(null); const generateSlug = (title) => { return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").trim(); }; const handleTitleChange = (e) => { const title = e.target.value; setFormData((prev) => ({ ...prev, title, slug: initialData.slug ? prev.slug : generateSlug(title) })); }; const handleSave = async () => { if (onSave) { await onSave(formData); setLastSaved(/* @__PURE__ */ new Date()); } }; const getWordCount = () => { return formData.content.split(/\s+/).filter(Boolean).length; }; return /* @__PURE__ */ React4.createElement("div", { className: clsx("min-h-screen bg-brutal-white", className) }, /* @__PURE__ */ React4.createElement(Tabs, { defaultValue: "editor", brutal }, /* @__PURE__ */ React4.createElement( "div", { className: clsx( "sticky top-0 z-40", "border-b-4 border-brutal-black bg-brutal-white", "px-4 py-3" ) }, /* @__PURE__ */ React4.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React4.createElement("div", { className: "flex-1 min-w-0 mr-4" }, /* @__PURE__ */ React4.createElement("h1", { className: "text-sm lg:text-base font-bold uppercase truncate" }, initialData.title ? "Edit Post" : "New Post", formData.title && /* @__PURE__ */ React4.createElement("span", { className: "hidden sm:inline text-brutal-gray-600" }, " ", "\u2014 ", formData.title)), /* @__PURE__ */ React4.createElement("div", { className: "flex items-center gap-2 mt-1 text-xs" }, /* @__PURE__ */ React4.createElement( "span", { className: clsx( "flex items-center gap-1", formData.status === "published" ? "text-brutal-mint" : "text-brutal-yellow" ) }, /* @__PURE__ */ React4.createElement("span", { className: "w-2 h-2 rounded-full bg-current" }), formData.status ), lastSaved && /* @__PURE__ */ React4.createElement("span", { className: "text-brutal-gray-500" }, "Saved ", lastSaved.toLocaleTimeString()))), /* @__PURE__ */ React4.createElement("div", { className: "flex items-center gap-2" }, onCancel && /* @__PURE__ */ React4.createElement( Button, { variant: "secondary", size: "sm", onClick: onCancel, disabled: loading, brutal, className: "hidden sm:flex" }, "Cancel" ), /* @__PURE__ */ React4.createElement( Button, { variant: "primary", size: "sm", onClick: handleSave, disabled: loading, loading, brutal, leftIcon: () => /* @__PURE__ */ React4.createElement(FaSave, null) }, /* @__PURE__ */ React4.createElement("span", { className: "hidden sm:inline" }, "Save") ))) ), /* @__PURE__ */ React4.createElement(TabsList, { brutal, stretch: true }, /* @__PURE__ */ React4.createElement(TabsTrigger, { value: "editor", icon: FaPen, stretch: true }, "Write"), /* @__PURE__ */ React4.createElement(TabsTrigger, { value: "preview", icon: FaEye, stretch: true }, "Preview"), /* @__PURE__ */ React4.createElement(TabsTrigger, { value: "settings", icon: FaCog, stretch: true }, "Settings")), /* @__PURE__ */ React4.createElement(TabsContent, { value: "editor", className: "p-4 lg:p-6" }, /* @__PURE__ */ React4.createElement("div", { className: "max-w-4xl mx-auto space-y-6" }, /* @__PURE__ */ React4.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React4.createElement( Input, { label: "Title", value: formData.title, onChange: handleTitleChange, placeholder: "Post title...", brutal, required: true } ), /* @__PURE__ */ React4.createElement( Input, { label: "Slug", value: formData.slug, onChange: (e) => setFormData((prev) => ({ ...prev, slug: e.target.value })), placeholder: "post-slug", brutal, required: true } )), /* @__PURE__ */ React4.createElement( Textarea, { label: "Excerpt", value: formData.excerpt, onChange: (e) => setFormData((prev) => ({ ...prev, excerpt: e.target.value })), placeholder: "Brief description of your post...", rows: 3, brutal, hint: `${formData.excerpt.length}/300 characters` } ), /* @__PURE__ */ React4.createElement("div", null, /* @__PURE__ */ React4.createElement("label", { className: "block text-xs font-bold uppercase mb-2" }, "Content"), /* @__PURE__ */ React4.createElement( MarkdownEditor, { value: formData.content, onChange: (content) => setFormData((prev) => ({ ...prev, content })), brutal, minHeight: "500px" } )))), /* @__PURE__ */ React4.createElement(TabsContent, { value: "preview", className: "p-4 lg:p-6" }, /* @__PURE__ */ React4.createElement("div", { className: "max-w-4xl mx-auto" }, /* @__PURE__ */ React4.createElement(Card, { brutal, variant: "raised" }, /* @__PURE__ */ React4.createElement("article", null, /* @__PURE__ */ React4.createElement("header", { className: "mb-8" }, /* @__PURE__ */ React4.createElement("h1", { className: "text-4xl font-black uppercase tracking-wider mb-4" }, formData.title || "Untitled Post"), /* @__PURE__ */ React4.createElement("p", { className: "text-lg text-brutal-gray-600 font-mono" }, formData.excerpt || "No excerpt provided"), /* @__PURE__ */ React4.createElement("div", { className: "flex items-center gap-4 mt-4 text-xs text-brutal-gray-500" }, formData.author && /* @__PURE__ */ React4.createElement("span", null, "By", " ", authors.find((a) => a.value === formData.author)?.label), formData.category && /* @__PURE__ */ React4.createElement("span", null, categories.find((c) => c.value === formData.category)?.label), /* @__PURE__ */ React4.createElement("span", null, getWordCount(), " words"))), /* @__PURE__ */ React4.createElement( MarkdownPreview, { content: formData.content, parser: markdownParser } ))))), /* @__PURE__ */ React4.createElement(TabsContent, { value: "settings", className: "p-4 lg:p-6" }, /* @__PURE__ */ React4.createElement("div", { className: "max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-6" }, /* @__PURE__ */ React4.createElement(Card, { brutal }, /* @__PURE__ */ React4.createElement("h3", { className: "text-sm font-bold uppercase mb-4" }, "Post Settings"), /* @__PURE__ */ React4.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React4.createElement( Select, { label: "Category", options: [ { value: "", label: "Select Category" }, ...categories ], value: formData.category, onChange: (e) => setFormData((prev) => ({ ...prev, category: e.target.value })), brutal } ), /* @__PURE__ */ React4.createElement( Select, { label: "Author", options: [{ value: "", label: "Select Author" }, ...authors], value: formData.author, onChange: (e) => setFormData((prev) => ({ ...prev, author: e.target.value })), brutal } ), /* @__PURE__ */ React4.createElement( Input, { label: "Tags", value: formData.tags, onChange: (e) => setFormData((prev) => ({ ...prev, tags: e.target.value })), placeholder: "tag1, tag2, tag3", brutal, hint: "Separate tags with commas" } ), /* @__PURE__ */ React4.createElement( Select, { label: "Status", options: [ { value: "draft", label: "Draft" }, { value: "published", label: "Published" } ], value: formData.status, onChange: (e) => setFormData((prev) => ({ ...prev, status: e.target.value })), brutal } ), /* @__PURE__ */ React4.createElement( Toggle, { label: "Featured Post", checked: formData.isFeatured, onChange: (checked) => setFormData((prev) => ({ ...prev, isFeatured: checked })), brutal } ))), /* @__PURE__ */ React4.createElement(Card, { brutal }, /* @__PURE__ */ React4.createElement("h3", { className: "text-sm font-bold uppercase mb-4" }, "Editor Settings"), /* @__PURE__ */ React4.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React4.createElement( Toggle, { label: "Auto-save", checked: autoSave, onChange: setAutoSave, brutal } ), /* @__PURE__ */ React4.createElement("div", null, /* @__PURE__ */ React4.createElement("label", { className: "block text-xs font-bold uppercase mb-2" }, "Featured Image"), formData.featuredImage ? /* @__PURE__ */ React4.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React4.createElement("div", { className: "border-4 border-brutal-black p-2" }, /* @__PURE__ */ React4.createElement( "img", { src: formData.featuredImage, alt: "Featured", className: "w-full h-48 object-cover" } )), /* @__PURE__ */ React4.createElement( Button, { variant: "danger", size: "sm", className: "w-full", onClick: () => setFormData((prev) => ({ ...prev, featuredImage: "" })), brutal }, "Remove Image" )) : /* @__PURE__ */ React4.createElement( Button, { variant: "secondary", size: "sm", className: "w-full", leftIcon: () => /* @__PURE__ */ React4.createElement(FaImage, null), brutal }, "Upload Image" )), /* @__PURE__ */ React4.createElement( Input, { label: "Publish Date", type: "datetime-local", value: formData.publishedAt, onChange: (e) => setFormData((prev) => ({ ...prev, publishedAt: e.target.value })), brutal, leftIcon: FaClock } ))), /* @__PURE__ */ React4.createElement(Card, { brutal, className: "md:col-span-2" }, /* @__PURE__ */ React4.createElement("h3", { className: "text-sm font-bold uppercase mb-4" }, "Statistics"), /* @__PURE__ */ React4.createElement("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 text-sm" }, /* @__PURE__ */ React4.createElement("div", null, /* @__PURE__ */ React4.createElement("span", { className: "text-xs uppercase text-brutal-gray-600" }, "Words"), /* @__PURE__ */ React4.createElement("p", { className: "font-bold text-lg" }, getWordCount())), /* @__PURE__ */ React4.createElement("div", null, /* @__PURE__ */ React4.createElement("span", { className: "text-xs uppercase text-brutal-gray-600" }, "Characters"), /* @__PURE__ */ React4.createElement("p", { className: "font-bold text-lg" }, formData.content.length)), /* @__PURE__ */ React4.createElement("div", null, /* @__PURE__ */ React4.createElement("span", { className: "text-xs uppercase text-brutal-gray-600" }, "Paragraphs"), /* @__PURE__ */ React4.createElement("p", { className: "font-bold text-lg" }, formData.content.split("\n\n").filter(Boolean).length)), /* @__PURE__ */ React4.createElement("div", null, /* @__PURE__ */ React4.createElement("span", { className: "text-xs uppercase text-brutal-gray-600" }, "Reading Time"), /* @__PURE__ */ React4.createElement("p", { className: "font-bold text-lg" }, "~", Math.ceil(getWordCount() / 200), " min")))))))); }; var EditorToolbar = ({ actions, onAction, onFormatText, fullscreen = false, onFullscreenToggle, brutal = true, variant = "default", showGroups = ["text", "paragraph", "insert", "tools"], className, customActions }) => { const [showColorPicker, setShowColorPicker] = useState(false); const [showLinkDialog, setShowLinkDialog] = useState(false); const [linkUrl, setLinkUrl] = useState(""); const colorPickerRef = useRef(null); const defaultActions = [ // Text formatting group { id: "bold", icon: FaBold, label: "Bold", shortcut: "Ctrl+B", action: () => onFormatText?.("bold"), group: "text" }, { id: "italic", icon: FaItalic, label: "Italic", shortcut: "Ctrl+I", action: () => onFormatText?.("italic"), group: "text" }, { id: "underline", icon: FaUnderline, label: "Underline", shortcut: "Ctrl+U", action: () => onFormatText?.("underline"), group: "text" }, { id: "strike", icon: FaStrikethrough, label: "Strikethrough", action: () => onFormatText?.("strikethrough"), group: "text" }, // Paragraph formatting group { id: "heading", icon: FaHeading, label: "Heading", action: () => onFormatText?.("heading"), group: "paragraph" }, { id: "unordered-list", icon: FaListUl, label: "Bullet List", action: () => onFormatText?.("unorderedList"), group: "paragraph" }, { id: "ordered-list", icon: FaListOl, label: "Numbered List", action: () => onFormatText?.("orderedList"), group: "paragraph" }, { id: "quote", icon: FaQuoteLeft, label: "Quote", action: () => onFormatText?.("quote"), group: "paragraph" }, { id: "code", icon: FaCode, label: "Code Block", action: () => onFormatText?.("code"), group: "paragraph" }, // Alignment group { id: "align-left", icon: FaAlignLeft, label: "Align Left", action: () => onFormatText?.("alignLeft"), group: "align" }, { id: "align-center", icon: FaAlignCenter, label: "Align Center", action: () => onFormatText?.("alignCenter"), group: "align" }, { id: "align-right", icon: FaAlignRight, label: "Align Right", action: () => onFormatText?.("alignRight"), group: "align" }, // Insert group { id: "link", icon: FaLink, label: "Insert Link", shortcut: "Ctrl+K", action: () => setShowLinkDialog(true), group: "insert" }, { id: "image", icon: FaImage, label: "Insert Image", action: () => onFormatText?.("image"), group: "insert" }, { id: "table", icon: FaTable, label: "Insert Table", action: () => onFormatText?.("table"), group: "insert" }, // Tools group { id: "color", icon: FaPalette, label: "Text Color", action: () => setShowColorPicker(!showColorPicker), group: "tools" }, { id: "highlight", icon: FaHighlighter, label: "Highlight", action: () => onFormatText?.("highlight"), group: "tools" }, { id: "indent", icon: FaIndent, label: "Increase Indent", action: () => onFormatText?.("indent"), group: "tools" }, { id: "outdent", icon: FaOutdent, label: "Decrease Indent", action: () => onFormatText?.("outdent"), group: "tools" }, // History group { id: "undo", icon: FaUndo, label: "Undo", shortcut: "Ctrl+Z", action: () => onFormatText?.("undo"), group: "history" }, { id: "redo", icon: FaRedo, label: "Redo", shortcut: "Ctrl+Y", action: () => onFormatText?.("redo"), group: "history" } ]; const toolbarActions = actions || defaultActions; const groupedActions = toolbarActions.reduce( (acc, action) => { const group = action.group || "default"; if (!acc[group]) acc[group] = []; acc[group].push(action); return acc; }, {} ); const handleAction = (action) => { if (action.disabled) return; action.action(); if (onAction) onAction(action.id); }; const handleLinkInsert = () => { if (linkUrl && onFormatText) { onFormatText("link", linkUrl); setLinkUrl(""); setShowLinkDialog(false); } }; const colors = [ "#000000", "#FFFFFF", "#FFD6E8", "#FFE5D6", "#FFF3D6", "#D6FFE5", "#D6EDFF", "#E8D6FF", "#FFD6D6", "#737373", "#404040", "#171717" ]; const variantClasses = { default: clsx( "flex flex-wrap items-center gap-1 p-2", brutal && "border-b-4 border-brutal-black bg-brutal-gray-50", !brutal && "border-b-2 border-brutal-gray-200 bg-brutal-gray-50" ), minimal: "flex items-center gap-1 p-1", floating: clsx( "inline-flex items-center gap-1 p-2", "bg-brutal-white rounded-lg", brutal && "border-2 border-brutal-black shadow-brutal", !brutal && "border border-brutal-gray-300 shadow-lg" ) }; return /* @__PURE__ */ React4.createElement("div", { className: clsx(variantClasses[variant], className) }, showGroups.map((groupName) => { const actions2 = groupedActions[groupName]; if (!actions2 || actions2.length === 0) return null; return /* @__PURE__ */ React4.createElement("div", { key: groupName, className: "flex items-center" }, actions2.map((action) => /* @__PURE__ */ React4.createElement( Tooltip, { key: action.id, content: /* @__PURE__ */ React4.createElement("div", null, action.label, action.shortcut && /* @__PURE__ */ React4.createElement("span", { className: "ml-2 text-xs opacity-75" }, action.shortcut)) }, /* @__PURE__ */ React4.createElement( "button", { type: "button", onClick: () => handleAction(action), disabled: action.disabled, className: clsx( "p-2 transition-all duration-200", "hover:bg-brutal-gray-200", action.active && "bg-brutal-black text-brutal-white", action.disabled && "opacity-50 cursor-not-allowed" ), "aria-label": action.label }, /* @__PURE__ */ React4.createElement(Icon, { icon: action.icon, size: "sm" }) ) )), showGroups.indexOf(groupName) < showGroups.length - 1 && /* @__PURE__ */ React4.createElement("div", { className: "mx-1 w-px h-6 bg-brutal-gray-300" })); }), showColorPicker && /* @__PURE__ */ React4.createElement( "div", { ref: colorPickerRef, className: clsx( "absolute top-full left-0 mt-1 z-50", "bg-brutal-white p-2", brutal && "border-4 border-brutal-black shadow-brutal", !brutal && "border-2 border-brutal-gray-300 shadow-lg" ) }, /* @__PURE__ */ React4.createElement("div", { className: "grid grid-cols-6 gap-1" }, colors.map((color) => /* @__PURE__ */ React4.createElement( "button", { key: color, type: "button", onClick: () => { onFormatText?.("color", color); setShowColorPicker(false); }, className: clsx( "w-8 h-8 border-2 border-brutal-black", "hover:scale-110 transition-transform" ), style: { backgroundColor: color }, "aria-label": `Color ${color}` } ))) ), showLinkDialog && /* @__PURE__ */ React4.createElement( "div", { className: clsx( "absolute top-full left-0 mt-1 z-50", "bg-brutal-white p-4", brutal && "border-4 border-brutal-black shadow-brutal", !brutal && "border-2 border-brutal-gray-300 shadow-lg" ) }, /* @__PURE__ */ React4.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React4.createElement( "input", { type: "url", value: linkUrl, onChange: (e) => setLinkUrl(e.target.value), placeholder: "Enter URL...", className: clsx( "px-3 py-2 text-sm", brutal && "border-2 border-brutal-black", !brutal && "border border-brutal-gray-300", "focus:outline-none" ), onKeyDown: (e) => { if (e.key === "Enter") handleLinkInsert(); if (e.key === "Escape") setShowLinkDialog(false); }, autoFocus: true } ), /* @__PURE__ */ React4.createElement(Button, { size: "sm", onClick: handleLinkInsert, brutal }, /* @__PURE__ */ React4.createElement(FaPlus, null), "Insert"), /* @__PURE__ */ React4.createElement( Button, { size: "sm", variant: "ghost", onClick: () => setShowLinkDialog(false), brutal }, /* @__PURE__ */ React4.createElement(FaTimes, null) )) ), customActions && /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement("div", { className: "mx-1 w-px h-6 bg-brutal-gray-300" }), customActions), onFullscreenToggle && /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement("div", { className: "mx-1 w-px h-6 bg-brutal-gray-300" }), /* @__PURE__ */ React4.createElement( Tooltip, { content: fullscreen ? "Exit Fullscreen" : "Enter Fullscreen" }, /* @__PURE__ */ React4.createElement( "button", { type: "button", onClick: onFullscreenToggle, className: clsx( "p-2 transition-all duration-200", "hover:bg-brutal-gray-200", fullscreen && "bg-brutal-black text-brutal-white" ), "aria-label": fullscreen ? "Exit Fullscreen" : "Enter Fullscreen" }, /* @__PURE__ */ React4.createElement(Icon, { icon: fullscreen ? FaCompress : FaExpand, size: "sm" }) ) ))); }; /** * @file src/modules/editor/MarkdownToolbar/MarkdownToolbar.tsx * @author David (https://dvh.sh) * @license MIT * * @created Fri Sep 12 2025 * @updated Fri Sep 12 2025 * * @description * Toolbar for markdown editor with common formatting actions * @client This component requires client-side JavaScript */ /** * @file src/modules/editor/MarkdownEditor/MarkdownEditor.tsx * @author David (https://dvh.sh) * @license MIT * * @created Fri Sep 12 2025 * @updated Fri Sep 12 2025 * * @description * Lightweight markdown editor component without preview * @client This component requires client-side JavaScript */ /** * @file src/modules/editor/MarkdownPreview/MarkdownPreview.tsx * @author David (https://dvh.sh) * @license MIT * * @created Fri Sep 12 2025 * @updated Fri Sep 12 2025 * * @description * Markdown preview component (requires marked or similar parser) * @client This component requires client-side JavaScript */ /** * @file src/modules/editor/BlogEditor/BlogEditor.tsx * @author David (https://dvh.sh) * @license MIT * * @created Fri Sep 12 2025 * @updated Fri Sep 12 2025 * * @description * Complete blog editor with tabs for write/preview/settings * @client This component requires client-side JavaScript */ /** * @file src/modules/editor/EditorToolbar/EditorToolbar.tsx * @author David (https://dvh.sh) * @license MIT * * @created Fri Sep 12 2025 * @updated Fri Sep 12 2025 * * @description * Advanced editor toolbar with formatting options and custom actions * @client This component requires client-side JavaScript */ /** * @file src/modules/editor/index.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 12 2025 * @updated Fri Sep 12 2025 * * @description * */ export { BlogEditor, EditorToolbar, MarkdownEditor, MarkdownPreview, MarkdownToolbar }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map