@mounaji_npm/forum 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +445 -0
- package/dist/mounajiforum.es.js +1090 -0
- package/dist/mounajiforum.es.js.map +1 -0
- package/dist/mounajiforum.umd.cjs +2 -0
- package/dist/mounajiforum.umd.cjs.map +1 -0
- package/package.json +47 -0
- package/src/CreatePostPage.jsx +237 -0
- package/src/ForumPage.jsx +193 -0
- package/src/PostPage.jsx +122 -0
- package/src/components/CategoryNav.jsx +83 -0
- package/src/components/PostCard.jsx +186 -0
- package/src/components/PostDetail.jsx +89 -0
- package/src/components/ReplyThread.jsx +244 -0
- package/src/components/shared.jsx +171 -0
- package/src/demo.js +125 -0
- package/src/index.js +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mounajiforum.umd.cjs","sources":["../src/components/CategoryNav.jsx","../src/components/shared.jsx","../src/components/PostCard.jsx","../src/demo.js","../src/ForumPage.jsx","../src/components/PostDetail.jsx","../src/components/ReplyThread.jsx","../src/PostPage.jsx","../src/CreatePostPage.jsx"],"sourcesContent":["/**\n * CategoryNav — @mounaji_npm/forum\n *\n * Sidebar category list with counts and active state.\n *\n * Props:\n * categories — { id, label, icon?, count?, pinned? }[]\n * active — string (category id)\n * onChange(id) — callback\n * isDark — boolean\n * title — string (default: 'Categories')\n * style — CSSProperties\n */\n\nexport function CategoryNav({ categories = [], active = 'all', onChange, isDark = true, title = 'Categories', style }) {\n const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';\n const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';\n\n return (\n <div style={{\n borderRadius: 'var(--mn-radius-lg, 0.75rem)',\n backgroundColor: card,\n border: `1px solid ${border}`,\n overflow: 'hidden',\n ...style,\n }}>\n {title && (\n <div style={{ padding: '12px 16px', borderBottom: `1px solid ${border}` }}>\n <p style={{ margin: 0, fontSize: '0.75rem', fontWeight: 600, color: textSec, textTransform: 'uppercase', letterSpacing: '0.07em' }}>\n {title}\n </p>\n </div>\n )}\n <div style={{ padding: '8px 0' }}>\n {categories.map(cat => {\n const isActive = active === cat.id;\n return (\n <button\n key={cat.id}\n onClick={() => onChange?.(cat.id)}\n style={{\n width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between',\n padding: '7px 16px', border: 'none', cursor: 'pointer', fontFamily: 'inherit',\n backgroundColor: isActive\n ? (isDark ? 'rgba(59,130,246,0.1)' : 'rgba(59,130,246,0.07)')\n : 'transparent',\n borderLeft: `3px solid ${isActive ? 'var(--mn-color-primary, #3B82F6)' : 'transparent'}`,\n transition: 'all 100ms',\n gap: 8,\n }}\n onMouseEnter={e => { if (!isActive) e.currentTarget.style.backgroundColor = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)'; }}\n onMouseLeave={e => { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>\n {cat.icon && <span style={{ fontSize: 14, flexShrink: 0 }}>{cat.icon}</span>}\n <span style={{\n fontSize: '0.875rem',\n fontWeight: isActive ? 600 : 400,\n color: isActive ? 'var(--mn-color-primary, #3B82F6)' : textPri,\n whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',\n }}>\n {cat.label}\n </span>\n </div>\n {cat.count != null && (\n <span style={{\n fontSize: '0.7rem', fontWeight: 500, padding: '1px 7px', borderRadius: 9999,\n backgroundColor: isActive ? 'rgba(59,130,246,0.15)' : (isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)'),\n color: isActive ? 'var(--mn-color-primary, #3B82F6)' : textSec,\n flexShrink: 0,\n }}>\n {cat.count}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","/**\n * shared.jsx — @mounaji_npm/forum\n * Small reusable primitives: VoteButton, AuthorMeta, TagChip, CategoryBadge\n */\n\nimport { useState } from 'react';\n\n// ─── VoteButton ────────────────────────────────────────────────────────────────\n\n/**\n * Props:\n * count — number\n * userVote — 1 | 0 | -1\n * onChange(newVote: 1 | 0 | -1)\n * vertical — boolean (default: false — horizontal layout)\n * isDark — boolean\n */\nexport function VoteButton({ count = 0, userVote = 0, onChange, vertical = false, isDark = true, size = 'md' }) {\n const [optimistic, setOptimistic] = useState(null);\n\n const vote = optimistic ?? userVote;\n const total = optimistic !== null ? count + (optimistic - userVote) : count;\n const upCol = vote === 1 ? '#10B981' : (isDark ? '#64748B' : '#94A3B8');\n const downCol = vote === -1 ? '#EF4444' : (isDark ? '#64748B' : '#94A3B8');\n const numCol = vote === 1 ? '#10B981' : vote === -1 ? '#EF4444' : (isDark ? '#F0F4FF' : '#1A1A2E');\n const btnSz = size === 'sm' ? 22 : 28;\n const iconSz = size === 'sm' ? 11 : 13;\n\n function cast(v) {\n const next = vote === v ? 0 : v;\n setOptimistic(next);\n onChange?.(next);\n }\n\n const wrapStyle = vertical\n ? { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }\n : { display: 'flex', alignItems: 'center', gap: 4 };\n\n return (\n <div style={wrapStyle}>\n <VBtn size={btnSz} icon={size === 'sm' ? '▲' : '▲'} color={upCol} iconSz={iconSz} onClick={() => cast(1)} title=\"Upvote\" />\n <span style={{ fontSize: size === 'sm' ? '0.75rem' : '0.875rem', fontWeight: 700, color: numCol, minWidth: 20, textAlign: 'center' }}>\n {total}\n </span>\n {vertical && <VBtn size={btnSz} icon=\"▼\" color={downCol} iconSz={iconSz} onClick={() => cast(-1)} title=\"Downvote\" />}\n </div>\n );\n}\n\nfunction VBtn({ size, icon, color, iconSz, onClick, title }) {\n return (\n <button\n onClick={onClick}\n title={title}\n style={{\n width: size, height: size, borderRadius: 4, border: 'none', cursor: 'pointer',\n backgroundColor: 'transparent', color, fontSize: iconSz,\n display: 'flex', alignItems: 'center', justifyContent: 'center',\n transition: 'background 100ms, color 100ms', padding: 0, fontFamily: 'inherit',\n }}\n onMouseEnter={e => { e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.07)'; }}\n onMouseLeave={e => { e.currentTarget.style.backgroundColor = 'transparent'; }}\n >\n {icon}\n </button>\n );\n}\n\n// ─── AuthorMeta ────────────────────────────────────────────────────────────────\n\n/**\n * Props:\n * author — { name, avatar? }\n * date — string | Date\n * prefix — string (default: '')\n * isDark — boolean\n * size — 'sm' | 'md'\n */\nexport function AuthorMeta({ author, date, prefix = '', isDark = true, size = 'md' }) {\n const textSec = isDark ? '#94A3B8' : '#64748B';\n const textPri = isDark ? '#F0F4FF' : '#1A1A2E';\n const avatarSz = size === 'sm' ? 18 : 22;\n const fontSize = size === 'sm' ? '0.75rem' : '0.8125rem';\n\n return (\n <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <Avatar name={author?.name ?? '?'} size={avatarSz} src={author?.avatar} />\n <span style={{ fontSize, color: textPri, fontWeight: 500 }}>{author?.name ?? 'unknown'}</span>\n {(prefix || date) && (\n <span style={{ fontSize, color: textSec }}>\n {prefix && <>{prefix} </>}{date ? formatRelative(date) : ''}\n </span>\n )}\n </div>\n );\n}\n\n// ─── TagChip ─────────────────────────────────────────────────────────────────\n\nexport function TagChip({ label, onClick, isDark = true }) {\n const bg = isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';\n const border = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';\n const color = isDark ? '#94A3B8' : '#64748B';\n return (\n <span\n onClick={onClick}\n style={{\n fontSize: '0.7rem', padding: '2px 8px', borderRadius: 9999,\n backgroundColor: bg, color, border: `1px solid ${border}`,\n cursor: onClick ? 'pointer' : 'default', fontFamily: 'inherit',\n transition: 'background 100ms',\n }}\n >\n #{label}\n </span>\n );\n}\n\n// ─── CategoryBadge ────────────────────────────────────────────────────────────\n\nconst CAT_COLORS = {\n announcements: { bg: 'rgba(239,68,68,0.12)', text: '#F87171', border: 'rgba(239,68,68,0.25)' },\n general: { bg: 'rgba(59,130,246,0.12)', text: '#60A5FA', border: 'rgba(59,130,246,0.25)' },\n questions: { bg: 'rgba(245,158,11,0.12)', text: '#FCD34D', border: 'rgba(245,158,11,0.25)' },\n ideas: { bg: 'rgba(139,92,246,0.12)', text: '#A78BFA', border: 'rgba(139,92,246,0.25)' },\n bugs: { bg: 'rgba(239,68,68,0.12)', text: '#FCA5A5', border: 'rgba(239,68,68,0.2)' },\n default: { bg: 'rgba(255,255,255,0.06)', text: '#94A3B8', border: 'rgba(255,255,255,0.1)' },\n};\n\nexport function CategoryBadge({ category }) {\n const c = CAT_COLORS[category?.id] ?? CAT_COLORS.default;\n return (\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: '0.7rem', fontWeight: 500, padding: '2px 8px', borderRadius: 9999, backgroundColor: c.bg, color: c.text, border: `1px solid ${c.border}` }}>\n {category?.icon && <span>{category.icon}</span>}\n {category?.label}\n </span>\n );\n}\n\n// ─── Avatar ───────────────────────────────────────────────────────────────────\n\nexport function Avatar({ name, size = 24, src }) {\n const initials = (name ?? '?').slice(0, 1).toUpperCase();\n const hue = [...(name ?? '')].reduce((h, c) => (h * 31 + c.charCodeAt(0)) % 360, 0);\n const bg = `hsl(${hue}, 55%, 35%)`;\n\n if (src) {\n return <img src={src} alt={name} style={{ width: size, height: size, borderRadius: '50%', objectFit: 'cover' }} />;\n }\n return (\n <div style={{ width: size, height: size, borderRadius: '50%', backgroundColor: bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: size * 0.45, fontWeight: 700, color: '#fff', flexShrink: 0, userSelect: 'none' }}>\n {initials}\n </div>\n );\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport function formatRelative(date) {\n const d = typeof date === 'string' ? new Date(date) : date;\n const diff = (Date.now() - d.getTime()) / 1000;\n if (diff < 60) return 'just now';\n if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;\n if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;\n if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`;\n return d.toLocaleDateString();\n}\n\nexport function plural(n, word) {\n return `${n} ${word}${n === 1 ? '' : 's'}`;\n}\n","/**\n * PostCard — @mounaji_npm/forum\n *\n * Compact card shown in the forum listing. Displays vote count, category badge,\n * title, preview text, author meta, reply/view counts, and status badges.\n *\n * Props (single post):\n * post — Post object (see data shape in demo.js)\n * onClick — () => void\n * onVote — (postId, vote: 1|0|-1) => void\n * isDark — boolean\n * compact — boolean (smaller variant, no body preview)\n * style — CSSProperties\n *\n * PostList:\n * posts, onPostClick, onVote, isLoading, isDark\n */\n\nimport { VoteButton, AuthorMeta, TagChip, CategoryBadge } from './shared.jsx';\n\n// ─── PostCard ────────────────────────────────────────────────────────────────\n\nexport function PostCard({ post, onClick, onVote, isDark = true, compact = false, style }) {\n const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';\n const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';\n\n return (\n <div\n onClick={onClick}\n style={{\n display: 'flex', gap: 14, padding: compact ? '12px 16px' : '16px 18px',\n borderRadius: 'var(--mn-radius-lg, 0.75rem)',\n backgroundColor: card, border: `1px solid ${border}`,\n cursor: onClick ? 'pointer' : 'default',\n transition: 'border-color 120ms, background 120ms',\n ...style,\n }}\n onMouseEnter={e => { if (onClick) { e.currentTarget.style.borderColor = isDark ? 'rgba(255,255,255,0.14)' : 'rgba(0,0,0,0.16)'; } }}\n onMouseLeave={e => { e.currentTarget.style.borderColor = border; }}\n >\n {/* Vote column */}\n <div\n onClick={e => e.stopPropagation()}\n style={{ flexShrink: 0, paddingTop: 2 }}\n >\n <VoteButton\n count={post.votes}\n userVote={post.userVote ?? 0}\n onChange={v => onVote?.(post.id, v)}\n vertical\n isDark={isDark}\n size=\"sm\"\n />\n </div>\n\n {/* Content */}\n <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>\n {/* Badges row */}\n <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>\n {post.isPinned && (\n <span style={{ fontSize: '0.7rem', fontWeight: 600, padding: '2px 7px', borderRadius: 9999, backgroundColor: 'rgba(245,158,11,0.12)', color: '#FCD34D', border: '1px solid rgba(245,158,11,0.25)' }}>\n 📌 Pinned\n </span>\n )}\n {post.isSolved && (\n <span style={{ fontSize: '0.7rem', fontWeight: 600, padding: '2px 7px', borderRadius: 9999, backgroundColor: 'rgba(16,185,129,0.12)', color: '#34D399', border: '1px solid rgba(16,185,129,0.25)' }}>\n ✓ Solved\n </span>\n )}\n <CategoryBadge category={post.category} />\n </div>\n\n {/* Title */}\n <h3 style={{ margin: 0, fontSize: compact ? '0.9375rem' : '1rem', fontWeight: 600, color: textPri, lineHeight: 1.4 }}>\n {post.title}\n </h3>\n\n {/* Body preview */}\n {!compact && post.body && (\n <p style={{ margin: 0, fontSize: '0.8125rem', color: textSec, lineHeight: 1.55, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>\n {post.body}\n </p>\n )}\n\n {/* Tags */}\n {post.tags?.length > 0 && (\n <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>\n {post.tags.map(t => <TagChip key={t} label={t} isDark={isDark} />)}\n </div>\n )}\n\n {/* Footer meta */}\n <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginTop: 2 }}>\n <AuthorMeta author={post.author} date={post.createdAt} isDark={isDark} size=\"sm\" />\n <MetaCount icon=\"💬\" value={post.replyCount} label=\"replies\" isDark={isDark} />\n <MetaCount icon=\"👁\" value={post.viewCount} label=\"views\" isDark={isDark} />\n </div>\n </div>\n </div>\n );\n}\n\nfunction MetaCount({ icon, value, label, isDark }) {\n const color = isDark ? '#64748B' : '#94A3B8';\n return (\n <span style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: '0.75rem', color }}>\n <span style={{ fontSize: 12 }}>{icon}</span>\n {value} {label}\n </span>\n );\n}\n\n// ─── PostList ─────────────────────────────────────────────────────────────────\n\nexport function PostList({ posts = [], onPostClick, onVote, isLoading = false, isDark = true }) {\n if (isLoading) {\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>\n {Array.from({ length: 4 }).map((_, i) => (\n <SkeletonCard key={i} isDark={isDark} />\n ))}\n </div>\n );\n }\n\n if (posts.length === 0) {\n return <EmptyPostList isDark={isDark} />;\n }\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>\n {posts.map(post => (\n <PostCard\n key={post.id}\n post={post}\n onClick={() => onPostClick?.(post.id)}\n onVote={onVote}\n isDark={isDark}\n />\n ))}\n </div>\n );\n}\n\nexport function EmptyPostList({ isDark = true, message = 'No posts yet', cta, onCta }) {\n const textPri = isDark ? '#F0F4FF' : '#1A1A2E';\n const textSec = isDark ? '#94A3B8' : '#64748B';\n return (\n <div style={{ textAlign: 'center', padding: '64px 24px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>\n <p style={{ fontSize: 40, margin: 0 }}>💬</p>\n <p style={{ margin: 0, fontSize: '1rem', fontWeight: 600, color: textPri }}>{message}</p>\n <p style={{ margin: 0, fontSize: '0.875rem', color: textSec }}>Be the first to start a discussion.</p>\n {cta && (\n <button\n onClick={onCta}\n style={{ marginTop: 8, padding: '8px 20px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: 'none', cursor: 'pointer', backgroundColor: 'var(--mn-color-primary, #3B82F6)', color: '#fff', fontSize: '0.875rem', fontWeight: 600, fontFamily: 'inherit' }}\n >\n {cta}\n </button>\n )}\n </div>\n );\n}\n\nfunction SkeletonCard({ isDark }) {\n const bg = isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const S = (w, h = 12) => ({ width: w, height: h, borderRadius: 6, backgroundColor: bg, animation: 'mn-pulse 1.4s ease infinite' });\n return (\n <div style={{ display: 'flex', gap: 14, padding: '16px 18px', borderRadius: 'var(--mn-radius-lg, 0.75rem)', backgroundColor: isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)', border: `1px solid ${border}` }}>\n <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, paddingTop: 2, flexShrink: 0 }}>\n <div style={S(22, 10)} />\n <div style={S(22, 10)} />\n </div>\n <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 9 }}>\n <div style={S('60%', 8)} />\n <div style={S('85%', 14)} />\n <div style={S('50%', 8)} />\n <div style={S('40%', 8)} />\n </div>\n <style>{`@keyframes mn-pulse { 0%,100%{opacity:.5} 50%{opacity:1} }`}</style>\n </div>\n );\n}\n","/**\n * Demo data for @mounaji_npm/forum\n * Import to populate forum components during prototyping.\n */\n\nexport const DEMO_CATEGORIES = [\n { id: 'all', label: 'All Posts', icon: '◉', count: 124 },\n { id: 'announcements', label: 'Announcements', icon: '📣', count: 8, pinned: true },\n { id: 'general', label: 'General', icon: '💬', count: 32 },\n { id: 'questions', label: 'Questions', icon: '❓', count: 45 },\n { id: 'ideas', label: 'Ideas', icon: '💡', count: 27 },\n { id: 'bugs', label: 'Bug Reports', icon: '🐛', count: 12 },\n];\n\nexport const DEMO_POSTS = [\n {\n id: '1',\n title: 'Introducing the new dark mode token system',\n body: 'We have completely overhauled the design token system to support seamless dark/light mode switching. The new system uses CSS custom properties under a `data-mn-theme` attribute, which means zero-JavaScript theme switching in most cases.',\n author: { id: 'u1', name: 'alex_m', avatar: null },\n category: { id: 'announcements', label: 'Announcements', icon: '📣' },\n tags: ['design-system', 'tokens', 'dark-mode'],\n votes: 87,\n userVote: 0,\n replyCount: 14,\n viewCount: 420,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 3).toISOString(),\n isPinned: true,\n isSolved: false,\n },\n {\n id: '2',\n title: 'How do I add custom categories to my forum?',\n body: 'I am setting up a community forum and want to add my own categories beyond the defaults. Is there a config option for this, or do I need to pass a custom categories array?',\n author: { id: 'u2', name: 'sara_dev', avatar: null },\n category: { id: 'questions', label: 'Questions', icon: '❓' },\n tags: ['configuration', 'categories'],\n votes: 12,\n userVote: 0,\n replyCount: 5,\n viewCount: 98,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 6).toISOString(),\n isSolved: true,\n },\n {\n id: '3',\n title: 'Feature request: markdown support in replies',\n body: 'It would be great to have rich text / markdown support in the reply composer. Being able to add code blocks, links, and bold/italic text would make the forum much more useful for technical discussions.',\n author: { id: 'u3', name: 'techwriter_k', avatar: null },\n category: { id: 'ideas', label: 'Ideas', icon: '💡' },\n tags: ['markdown', 'editor', 'replies'],\n votes: 34,\n userVote: 1,\n replyCount: 9,\n viewCount: 203,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 12).toISOString(),\n },\n {\n id: '4',\n title: 'Vote count not updating in real-time',\n body: 'After clicking the upvote button, the vote count updates locally but does not sync back from the server on page refresh. Seems like the onVote callback is firing but the optimistic update is not being persisted.',\n author: { id: 'u4', name: 'james_d', avatar: null },\n category: { id: 'bugs', label: 'Bug Reports', icon: '🐛' },\n tags: ['voting', 'bug', 'real-time'],\n votes: 7,\n userVote: 0,\n replyCount: 3,\n viewCount: 61,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 18).toISOString(),\n },\n {\n id: '5',\n title: 'Best practices for organizing forum categories',\n body: 'We are launching a developer community and are unsure how to structure our categories. Should we go broad (3-5 categories) or granular (10+ sub-categories)? Looking for advice from people who have run communities before.',\n author: { id: 'u5', name: 'community_lead', avatar: null },\n category: { id: 'general', label: 'General', icon: '💬' },\n tags: ['community', 'organization', 'best-practices'],\n votes: 22,\n userVote: 0,\n replyCount: 17,\n viewCount: 312,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(),\n },\n {\n id: '6',\n title: 'Building a searchable FAQ from pinned posts',\n body: 'One pattern I have found useful is using pinned posts as a structured FAQ. You can tag them with `faq` and surface them in a dedicated section. Here is how I set it up with the forum module.',\n author: { id: 'u1', name: 'alex_m', avatar: null },\n category: { id: 'general', label: 'General', icon: '💬' },\n tags: ['faq', 'pinned', 'pattern'],\n votes: 41,\n userVote: 0,\n replyCount: 6,\n viewCount: 188,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 36).toISOString(),\n },\n];\n\nexport const DEMO_REPLIES = [\n {\n id: 'r1',\n body: 'You can pass a `categories` prop directly to `<ForumPage>`. The shape is `{ id, label, icon, count }`. If you want a global default, set it in your `mn-config.js` under the `forum` key.',\n author: { id: 'u1', name: 'alex_m', avatar: null },\n votes: 15,\n userVote: 1,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 5).toISOString(),\n isAccepted: true,\n },\n {\n id: 'r2',\n body: 'Also worth noting — you can use the `CategoryNav` component standalone if you want to place it somewhere other than the default sidebar.',\n author: { id: 'u3', name: 'techwriter_k', avatar: null },\n votes: 8,\n userVote: 0,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 4).toISOString(),\n },\n {\n id: 'r3',\n body: 'Great question — I had the same issue when I first set up the forum. The docs example in the README shows the full config including custom categories.',\n author: { id: 'u5', name: 'community_lead', avatar: null },\n votes: 3,\n userVote: 0,\n createdAt: new Date(Date.now() - 1000 * 60 * 60 * 3).toISOString(),\n },\n];\n","/**\n * ForumPage — @mounaji_npm/forum\n *\n * Full forum listing page: category sidebar + search/sort header + post list.\n * Self-contained — no router assumptions, all navigation via callbacks.\n *\n * Props:\n * categories — Category[] sidebar list (default: DEMO_CATEGORIES)\n * posts — Post[] (default: DEMO_POSTS)\n * activeCategory — string category id (default: 'all')\n * searchQuery — string\n * sortBy — 'newest'|'top'|'unanswered'\n * onCategoryChange — (id) => void\n * onSearch — (q) => void\n * onSortChange — (sort) => void\n * onPostClick — (postId) => void\n * onNewPost — () => void\n * onVote — (postId, vote) => void\n * isLoading — boolean\n * isDark — boolean (default: true)\n * header — React node\n * title — string (default: 'Forum')\n * subtitle — string\n * style — CSSProperties\n */\n\nimport { useState, useMemo } from 'react';\nimport { CategoryNav } from './components/CategoryNav.jsx';\nimport { PostList } from './components/PostCard.jsx';\nimport { DEMO_POSTS, DEMO_CATEGORIES } from './demo.js';\n\nconst SORTS = [\n { id: 'newest', label: 'Newest' },\n { id: 'top', label: 'Top Voted' },\n { id: 'unanswered', label: 'Unanswered' },\n];\n\nexport function ForumPage({\n categories = DEMO_CATEGORIES,\n posts = DEMO_POSTS,\n activeCategory = 'all',\n searchQuery = '',\n sortBy = 'newest',\n onCategoryChange,\n onSearch,\n onSortChange,\n onPostClick,\n onNewPost,\n onVote,\n isLoading = false,\n isDark = true,\n header,\n title = 'Forum',\n subtitle = 'Discussions, questions, and ideas from the community.',\n style,\n}) {\n const [localSearch, setLocalSearch] = useState(searchQuery);\n const [localSort, setLocalSort] = useState(sortBy);\n const [localCategory, setLocalCategory] = useState(activeCategory);\n\n const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';\n const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const inputBg = isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.04)';\n\n function handleSearch(q) {\n setLocalSearch(q);\n onSearch?.(q);\n }\n\n function handleSort(s) {\n setLocalSort(s);\n onSortChange?.(s);\n }\n\n function handleCategory(id) {\n setLocalCategory(id);\n onCategoryChange?.(id);\n }\n\n // Client-side filter + sort (overridden by server data when posts prop changes)\n const displayed = useMemo(() => {\n let list = posts;\n if (localCategory !== 'all') {\n list = list.filter(p => p.category?.id === localCategory);\n }\n if (localSearch.trim()) {\n const q = localSearch.toLowerCase();\n list = list.filter(p => p.title.toLowerCase().includes(q) || p.body?.toLowerCase().includes(q));\n }\n if (localSort === 'top') list = [...list].sort((a, b) => b.votes - a.votes);\n if (localSort === 'newest') list = [...list].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));\n if (localSort === 'unanswered') list = list.filter(p => !p.isSolved && (p.replyCount ?? 0) === 0);\n // Pinned always first\n return [...list].sort((a, b) => (b.isPinned ? 1 : 0) - (a.isPinned ? 1 : 0));\n }, [posts, localCategory, localSearch, localSort]);\n\n return (\n <div style={{\n padding: 'var(--mn-spacing-xl, 32px)',\n display: 'flex', flexDirection: 'column', gap: 24,\n fontFamily: 'var(--mn-font-family, system-ui, sans-serif)',\n ...style,\n }}>\n {/* Header */}\n {header ?? (\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', flexWrap: 'wrap', gap: 12 }}>\n <div>\n <h1 style={{ margin: 0, fontSize: 'var(--mn-font-size-2xl, 1.5rem)', fontWeight: 700, color: textPri }}>{title}</h1>\n {subtitle && <p style={{ margin: '4px 0 0', fontSize: '0.875rem', color: textSec }}>{subtitle}</p>}\n </div>\n <button\n onClick={onNewPost}\n style={{ padding: '8px 18px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: 'none', cursor: 'pointer', backgroundColor: 'var(--mn-color-primary, #3B82F6)', color: '#fff', fontSize: '0.875rem', fontWeight: 600, fontFamily: 'inherit', display: 'flex', alignItems: 'center', gap: 6 }}\n >\n + New Post\n </button>\n </div>\n )}\n\n {/* Body: sidebar + main */}\n <div style={{ display: 'flex', gap: 20, alignItems: 'flex-start' }}>\n {/* Sidebar */}\n <div style={{ width: 200, flexShrink: 0, position: 'sticky', top: 80 }}>\n <CategoryNav\n categories={categories}\n active={localCategory}\n onChange={handleCategory}\n isDark={isDark}\n />\n </div>\n\n {/* Main column */}\n <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 14 }}>\n {/* Search + sort bar */}\n <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>\n {/* Search */}\n <div style={{ position: 'relative', flex: '1 1 200px' }}>\n <span style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: textSec, pointerEvents: 'none', fontSize: 14 }}>🔍</span>\n <input\n value={localSearch}\n onChange={e => handleSearch(e.target.value)}\n placeholder=\"Search posts…\"\n style={{ width: '100%', boxSizing: 'border-box', padding: '8px 10px 8px 32px', borderRadius: 'var(--mn-radius-md, 0.5rem)', backgroundColor: inputBg, border: `1px solid ${border}`, color: textPri, fontSize: '0.875rem', fontFamily: 'inherit', outline: 'none' }}\n onFocus={e => { e.target.style.borderColor = 'var(--mn-color-primary, #3B82F6)'; }}\n onBlur={e => { e.target.style.borderColor = border; }}\n />\n </div>\n\n {/* Sort tabs */}\n <div style={{ display: 'flex', gap: 2, backgroundColor: isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)', padding: 3, borderRadius: 'var(--mn-radius-md, 0.5rem)' }}>\n {SORTS.map(s => {\n const isActive = localSort === s.id;\n return (\n <button\n key={s.id}\n onClick={() => handleSort(s.id)}\n style={{\n padding: '4px 12px', borderRadius: 6, border: 'none', cursor: 'pointer',\n backgroundColor: isActive ? (isDark ? 'rgba(255,255,255,0.09)' : '#fff') : 'transparent',\n color: isActive ? textPri : textSec,\n fontSize: '0.8125rem', fontWeight: isActive ? 600 : 400, fontFamily: 'inherit',\n transition: 'all 100ms',\n }}\n >\n {s.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Post count */}\n {!isLoading && (\n <p style={{ margin: 0, fontSize: '0.8125rem', color: textSec }}>\n {displayed.length} {displayed.length === 1 ? 'post' : 'posts'}\n {localSearch ? ` matching \"${localSearch}\"` : ''}\n </p>\n )}\n\n {/* Post list */}\n <PostList\n posts={displayed}\n onPostClick={onPostClick}\n onVote={onVote}\n isLoading={isLoading}\n isDark={isDark}\n />\n </div>\n </div>\n </div>\n );\n}\n","/**\n * PostDetail — @mounaji_npm/forum\n *\n * Full post content view: title, body, metadata, tags.\n * Used inside PostPage.\n *\n * Props:\n * post — Post object\n * onVote — (postId, vote) => void\n * isDark — boolean\n */\n\nimport { VoteButton, AuthorMeta, TagChip, CategoryBadge } from './shared.jsx';\n\nexport function PostDetail({ post, onVote, isDark = true }) {\n const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';\n const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';\n\n return (\n <div style={{ borderRadius: 'var(--mn-radius-lg, 0.75rem)', backgroundColor: card, border: `1px solid ${border}`, overflow: 'hidden' }}>\n {/* Post header */}\n <div style={{ padding: '20px 24px', borderBottom: `1px solid ${border}` }}>\n {/* Badges */}\n <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 12 }}>\n {post.isPinned && <StatusBadge color=\"warning\">📌 Pinned</StatusBadge>}\n {post.isSolved && <StatusBadge color=\"success\">✓ Solved</StatusBadge>}\n {post.isLocked && <StatusBadge color=\"muted\">🔒 Locked</StatusBadge>}\n <CategoryBadge category={post.category} />\n </div>\n\n {/* Title */}\n <h1 style={{ margin: '0 0 14px', fontSize: 'var(--mn-font-size-xl, 1.25rem)', fontWeight: 700, color: textPri, lineHeight: 1.4 }}>\n {post.title}\n </h1>\n\n {/* Author + date + stats */}\n <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>\n <AuthorMeta author={post.author} date={post.createdAt} prefix=\"posted\" isDark={isDark} />\n <span style={{ fontSize: '0.8125rem', color: textSec }}>👁 {post.viewCount ?? 0} views</span>\n <span style={{ fontSize: '0.8125rem', color: textSec }}>💬 {post.replyCount ?? 0} replies</span>\n </div>\n </div>\n\n {/* Post body + vote */}\n <div style={{ display: 'flex', gap: 0 }}>\n {/* Vote column */}\n <div style={{ padding: '20px 16px', borderRight: `1px solid ${border}`, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4, flexShrink: 0 }}>\n <VoteButton\n count={post.votes}\n userVote={post.userVote ?? 0}\n onChange={v => onVote?.(post.id, v)}\n vertical\n isDark={isDark}\n />\n </div>\n\n {/* Body */}\n <div style={{ flex: 1, padding: '20px 24px' }}>\n <div style={{ fontSize: '0.9375rem', color: textPri, lineHeight: 1.7, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>\n {post.body}\n </div>\n\n {/* Tags */}\n {post.tags?.length > 0 && (\n <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 18 }}>\n {post.tags.map(t => <TagChip key={t} label={t} isDark={isDark} />)}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n\nfunction StatusBadge({ color, children }) {\n const COLORS = {\n warning: { bg: 'rgba(245,158,11,0.12)', text: '#FCD34D', border: 'rgba(245,158,11,0.25)' },\n success: { bg: 'rgba(16,185,129,0.12)', text: '#34D399', border: 'rgba(16,185,129,0.25)' },\n muted: { bg: 'rgba(107,114,128,0.12)', text: '#9CA3AF', border: 'rgba(107,114,128,0.2)' },\n };\n const c = COLORS[color] ?? COLORS.muted;\n return (\n <span style={{ fontSize: '0.7rem', fontWeight: 600, padding: '2px 8px', borderRadius: 9999, backgroundColor: c.bg, color: c.text, border: `1px solid ${c.border}` }}>\n {children}\n </span>\n );\n}\n","/**\n * ReplyThread — @mounaji_npm/forum\n *\n * Exports:\n * ReplyCard — single reply with vote + accepted badge\n * ReplyThread — ordered list of ReplyCards + ReplyComposer\n * ReplyComposer — textarea + submit button for writing a reply\n */\n\nimport { useState } from 'react';\nimport { VoteButton, AuthorMeta, Avatar } from './shared.jsx';\n\n// ─── ReplyCard ────────────────────────────────────────────────────────────────\n\n/**\n * Props:\n * reply — Reply object { id, body, author, votes, userVote, createdAt, isAccepted }\n * onVote — (replyId, vote) => void\n * onAccept — (replyId) => void (marks as accepted answer)\n * canAccept — boolean (only post author can accept)\n * isDark — boolean\n */\nexport function ReplyCard({ reply, onVote, onAccept, canAccept = false, isDark = true }) {\n const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';\n const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';\n\n const acceptedBorder = reply.isAccepted ? 'rgba(16,185,129,0.35)' : border;\n const acceptedBg = reply.isAccepted\n ? (isDark ? 'rgba(16,185,129,0.05)' : 'rgba(16,185,129,0.04)')\n : card;\n\n return (\n <div style={{ display: 'flex', borderRadius: 'var(--mn-radius-lg, 0.75rem)', backgroundColor: acceptedBg, border: `1px solid ${acceptedBorder}`, overflow: 'hidden' }}>\n {/* Vote column */}\n <div style={{ padding: '16px 14px', borderRight: `1px solid ${border}`, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4, flexShrink: 0 }}>\n <VoteButton\n count={reply.votes}\n userVote={reply.userVote ?? 0}\n onChange={v => onVote?.(reply.id, v)}\n vertical\n isDark={isDark}\n size=\"sm\"\n />\n {/* Accept button */}\n {(reply.isAccepted || canAccept) && (\n <button\n onClick={() => onAccept?.(reply.id)}\n title={reply.isAccepted ? 'Accepted answer' : 'Mark as accepted'}\n style={{\n marginTop: 6, width: 28, height: 28, borderRadius: 6, border: 'none', cursor: canAccept ? 'pointer' : 'default',\n backgroundColor: reply.isAccepted ? 'rgba(16,185,129,0.15)' : 'transparent',\n color: reply.isAccepted ? '#10B981' : textSec,\n display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14,\n transition: 'background 100ms',\n }}\n onMouseEnter={e => { if (canAccept && !reply.isAccepted) e.currentTarget.style.backgroundColor = 'rgba(16,185,129,0.1)'; }}\n onMouseLeave={e => { if (!reply.isAccepted) e.currentTarget.style.backgroundColor = 'transparent'; }}\n >\n ✓\n </button>\n )}\n </div>\n\n {/* Content */}\n <div style={{ flex: 1, padding: '16px 18px', minWidth: 0 }}>\n {reply.isAccepted && (\n <div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: '0.75rem', fontWeight: 600, color: '#10B981', marginBottom: 10 }}>\n <span>✓</span> Accepted Answer\n </div>\n )}\n\n <div style={{ fontSize: '0.9375rem', color: textPri, lineHeight: 1.65, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>\n {reply.body}\n </div>\n\n <div style={{ marginTop: 12 }}>\n <AuthorMeta author={reply.author} date={reply.createdAt} prefix=\"replied\" isDark={isDark} size=\"sm\" />\n </div>\n </div>\n </div>\n );\n}\n\n// ─── ReplyThread ──────────────────────────────────────────────────────────────\n\n/**\n * Props:\n * replies — Reply[]\n * onVote — (replyId, vote) => void\n * onAccept — (replyId) => void\n * canAccept — boolean\n * currentUser — { id, name, avatar? } | null\n * onSubmit — (body: string) => void | Promise<void>\n * isLocked — boolean\n * isDark — boolean\n */\nexport function ReplyThread({ replies = [], onVote, onAccept, canAccept = false, currentUser, onSubmit, isLocked = false, isDark = true }) {\n const textPri = isDark ? '#F0F4FF' : '#1A1A2E';\n const textSec = isDark ? '#94A3B8' : '#64748B';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n\n // Pin accepted answer to top, then sort by votes\n const sorted = [...replies].sort((a, b) => {\n if (a.isAccepted && !b.isAccepted) return -1;\n if (!a.isAccepted && b.isAccepted) return 1;\n return b.votes - a.votes;\n });\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>\n {/* Header */}\n {replies.length > 0 && (\n <div style={{ paddingBottom: 12, borderBottom: `1px solid ${border}` }}>\n <h2 style={{ margin: 0, fontSize: '1rem', fontWeight: 600, color: textPri }}>\n {replies.length} {replies.length === 1 ? 'Reply' : 'Replies'}\n </h2>\n </div>\n )}\n\n {/* Reply cards */}\n {sorted.length > 0 && (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>\n {sorted.map(reply => (\n <ReplyCard\n key={reply.id}\n reply={reply}\n onVote={onVote}\n onAccept={onAccept}\n canAccept={canAccept}\n isDark={isDark}\n />\n ))}\n </div>\n )}\n\n {/* Composer or locked notice */}\n {isLocked ? (\n <div style={{ textAlign: 'center', padding: '20px', color: textSec, fontSize: '0.875rem', backgroundColor: isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.03)', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: `1px solid ${border}` }}>\n 🔒 This thread is locked — no new replies allowed.\n </div>\n ) : (\n <ReplyComposer\n currentUser={currentUser}\n onSubmit={onSubmit}\n isDark={isDark}\n placeholder={replies.length === 0 ? 'Be the first to reply…' : 'Write a reply…'}\n />\n )}\n </div>\n );\n}\n\n// ─── ReplyComposer ────────────────────────────────────────────────────────────\n\n/**\n * Props:\n * currentUser — { name, avatar? } | null\n * onSubmit — (body: string) => void | Promise<void>\n * placeholder — string\n * isDark — boolean\n */\nexport function ReplyComposer({ currentUser, onSubmit, placeholder = 'Write a reply…', isDark = true }) {\n const [body, setBody] = useState('');\n const [submitting, setSub] = useState(false);\n const [error, setError] = useState('');\n\n const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const textPri = isDark ? '#F0F4FF' : '#1A1A2E';\n const textSec = isDark ? '#94A3B8' : '#64748B';\n const inputBg = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)';\n\n async function handleSubmit() {\n const trimmed = body.trim();\n if (!trimmed) { setError('Reply cannot be empty.'); return; }\n setError('');\n setSub(true);\n try {\n await onSubmit?.(trimmed);\n setBody('');\n } finally {\n setSub(false);\n }\n }\n\n return (\n <div style={{ borderRadius: 'var(--mn-radius-lg, 0.75rem)', backgroundColor: card, border: `1px solid ${border}`, padding: '16px 18px', display: 'flex', flexDirection: 'column', gap: 12 }}>\n {/* Composer header */}\n <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>\n <Avatar name={currentUser?.name ?? '?'} size={26} src={currentUser?.avatar} />\n <span style={{ fontSize: '0.8125rem', fontWeight: 600, color: textPri }}>\n {currentUser ? currentUser.name : 'Reply as Guest'}\n </span>\n </div>\n\n {/* Textarea */}\n <textarea\n value={body}\n onChange={e => { setBody(e.target.value); if (error) setError(''); }}\n placeholder={placeholder}\n rows={4}\n style={{\n width: '100%', boxSizing: 'border-box',\n padding: '10px 12px', borderRadius: 'var(--mn-radius-md, 0.5rem)',\n backgroundColor: inputBg, border: `1px solid ${error ? 'rgba(239,68,68,0.4)' : border}`,\n color: textPri, fontSize: '0.9375rem', lineHeight: 1.6, resize: 'vertical',\n fontFamily: 'var(--mn-font-family, system-ui, sans-serif)', outline: 'none',\n transition: 'border-color 120ms',\n }}\n onFocus={e => { e.target.style.borderColor = 'var(--mn-color-primary, #3B82F6)'; }}\n onBlur={e => { e.target.style.borderColor = error ? 'rgba(239,68,68,0.4)' : border; }}\n />\n\n {error && <p style={{ margin: 0, fontSize: '0.8125rem', color: '#F87171' }}>{error}</p>}\n\n {/* Footer */}\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n <span style={{ fontSize: '0.75rem', color: textSec }}>{body.length} / 8000</span>\n <button\n onClick={handleSubmit}\n disabled={submitting || !body.trim()}\n style={{\n padding: '7px 18px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: 'none',\n cursor: submitting || !body.trim() ? 'not-allowed' : 'pointer',\n backgroundColor: submitting || !body.trim() ? 'rgba(59,130,246,0.4)' : 'var(--mn-color-primary, #3B82F6)',\n color: '#fff', fontSize: '0.875rem', fontWeight: 600, fontFamily: 'inherit',\n transition: 'background 120ms', display: 'flex', alignItems: 'center', gap: 7,\n }}\n >\n {submitting && <Spinner />}\n {submitting ? 'Posting…' : 'Post Reply'}\n </button>\n </div>\n </div>\n );\n}\n\nfunction Spinner() {\n return <span style={{ display: 'inline-block', width: 12, height: 12, border: '2px solid rgba(255,255,255,0.3)', borderTopColor: '#fff', borderRadius: '50%', animation: 'mn-spin 0.6s linear infinite' }}>\n <style>{`@keyframes mn-spin { to { transform: rotate(360deg); } }`}</style>\n </span>;\n}\n","/**\n * PostPage — @mounaji_npm/forum\n *\n * Full post detail page: back button, post content, reply thread.\n * No router assumptions — navigation via callbacks.\n *\n * Props:\n * post — Post object (required)\n * replies — Reply[]\n * currentUser — { id, name, avatar? } | null\n * onBack — () => void\n * onVotePost — (postId, vote) => void\n * onVoteReply — (replyId, vote) => void\n * onAccept — (replyId) => void\n * onSubmitReply — (body: string) => void | Promise<void>\n * isLoading — boolean\n * isDark — boolean (default: true)\n * style — CSSProperties\n */\n\nimport { PostDetail } from './components/PostDetail.jsx';\nimport { ReplyThread } from './components/ReplyThread.jsx';\nimport { DEMO_POSTS, DEMO_REPLIES } from './demo.js';\n\nexport function PostPage({\n post = DEMO_POSTS[0],\n replies = DEMO_REPLIES,\n currentUser = null,\n onBack,\n onVotePost,\n onVoteReply,\n onAccept,\n onSubmitReply,\n isLoading = false,\n isDark = true,\n style,\n}) {\n const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';\n const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';\n\n // Only the post author can accept replies\n const canAccept = currentUser && post?.author?.id === currentUser?.id;\n\n if (isLoading) {\n return (\n <div style={{ padding: 'var(--mn-spacing-xl, 32px)', fontFamily: 'var(--mn-font-family, system-ui, sans-serif)', ...style }}>\n <PostSkeleton isDark={isDark} />\n </div>\n );\n }\n\n if (!post) {\n return (\n <div style={{ padding: 'var(--mn-spacing-xl, 32px)', textAlign: 'center', color: textSec, fontFamily: 'var(--mn-font-family, system-ui, sans-serif)', ...style }}>\n <p style={{ fontSize: 32, margin: '0 0 12px' }}>🔍</p>\n <p style={{ margin: 0, fontWeight: 600, color: textPri }}>Post not found</p>\n </div>\n );\n }\n\n return (\n <div style={{\n padding: 'var(--mn-spacing-xl, 32px)',\n display: 'flex', flexDirection: 'column', gap: 20, maxWidth: 860,\n fontFamily: 'var(--mn-font-family, system-ui, sans-serif)',\n ...style,\n }}>\n {/* Back nav */}\n <button\n onClick={onBack}\n style={{\n alignSelf: 'flex-start', display: 'flex', alignItems: 'center', gap: 6,\n padding: '6px 12px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: 'none',\n cursor: 'pointer', backgroundColor: isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)',\n color: textSec, fontSize: '0.875rem', fontFamily: 'inherit', transition: 'background 100ms',\n }}\n onMouseEnter={e => { e.currentTarget.style.backgroundColor = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'; }}\n onMouseLeave={e => { e.currentTarget.style.backgroundColor = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)'; }}\n >\n ← Back to Forum\n </button>\n\n {/* Post content */}\n <PostDetail\n post={post}\n onVote={onVotePost}\n isDark={isDark}\n />\n\n {/* Reply thread */}\n <ReplyThread\n replies={replies}\n onVote={onVoteReply}\n onAccept={onAccept}\n canAccept={canAccept}\n currentUser={currentUser}\n onSubmit={onSubmitReply}\n isLocked={post.isLocked}\n isDark={isDark}\n />\n </div>\n );\n}\n\nfunction PostSkeleton({ isDark }) {\n const bg = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';\n const S = (w, h = 12) => ({ width: w, height: h, borderRadius: 6, backgroundColor: bg, animation: 'mn-pulse 1.4s ease infinite' });\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>\n <div style={S(80)} />\n <div style={{ borderRadius: 'var(--mn-radius-lg, 0.75rem)', backgroundColor: card, border: `1px solid ${border}`, padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 14 }}>\n <div style={S('30%', 8)} />\n <div style={S('70%', 20)} />\n <div style={S('45%', 10)} />\n <div style={{ height: 80, borderRadius: 6, backgroundColor: bg }} />\n </div>\n <style>{`@keyframes mn-pulse { 0%,100%{opacity:.5} 50%{opacity:1} }`}</style>\n </div>\n );\n}\n","/**\n * CreatePostPage — @mounaji_npm/forum\n *\n * New post form: title, body, category selector, tags input.\n * No router assumptions.\n *\n * Props:\n * categories — Category[]\n * onSubmit — (post: { title, body, categoryId, tags }) => void | Promise\n * onCancel — () => void\n * currentUser — { name, avatar? } | null\n * isDark — boolean (default: true)\n * style — CSSProperties\n */\n\nimport { useState } from 'react';\nimport { DEMO_CATEGORIES } from './demo.js';\nimport { Avatar } from './components/shared.jsx';\n\nexport function CreatePostPage({\n categories = DEMO_CATEGORIES.filter(c => c.id !== 'all'),\n onSubmit,\n onCancel,\n currentUser = null,\n isDark = true,\n style,\n}) {\n const [title, setTitle] = useState('');\n const [body, setBody] = useState('');\n const [categoryId, setCategory] = useState('');\n const [tagInput, setTagInput] = useState('');\n const [tags, setTags] = useState([]);\n const [submitting, setSub] = useState(false);\n const [errors, setErrors] = useState({});\n\n const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';\n const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';\n const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';\n const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';\n const inputBg = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)';\n\n function validate() {\n const e = {};\n if (!title.trim()) e.title = 'Title is required.';\n if (title.length > 200) e.title = 'Title must be under 200 characters.';\n if (!body.trim()) e.body = 'Body is required.';\n if (!categoryId) e.category = 'Please choose a category.';\n return e;\n }\n\n async function handleSubmit(e) {\n e.preventDefault();\n const errs = validate();\n if (Object.keys(errs).length) { setErrors(errs); return; }\n setErrors({});\n setSub(true);\n try {\n await onSubmit?.({ title: title.trim(), body: body.trim(), categoryId, tags });\n } finally {\n setSub(false);\n }\n }\n\n function handleTagKeyDown(e) {\n if ((e.key === 'Enter' || e.key === ',') && tagInput.trim()) {\n e.preventDefault();\n const newTag = tagInput.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');\n if (!tags.includes(newTag) && tags.length < 5) {\n setTags(prev => [...prev, newTag]);\n }\n setTagInput('');\n }\n if (e.key === 'Backspace' && !tagInput && tags.length) {\n setTags(prev => prev.slice(0, -1));\n }\n }\n\n const inputStyle = (hasError) => ({\n width: '100%', boxSizing: 'border-box', padding: '10px 12px',\n borderRadius: 'var(--mn-radius-md, 0.5rem)',\n backgroundColor: inputBg, border: `1px solid ${hasError ? 'rgba(239,68,68,0.4)' : border}`,\n color: textPri, fontSize: '0.9375rem', fontFamily: 'var(--mn-font-family, system-ui, sans-serif)',\n outline: 'none', transition: 'border-color 120ms',\n });\n\n return (\n <div style={{\n padding: 'var(--mn-spacing-xl, 32px)',\n display: 'flex', flexDirection: 'column', gap: 20,\n maxWidth: 780, fontFamily: 'var(--mn-font-family, system-ui, sans-serif)',\n ...style,\n }}>\n {/* Header */}\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>\n <div>\n <h1 style={{ margin: 0, fontSize: 'var(--mn-font-size-xl, 1.25rem)', fontWeight: 700, color: textPri }}>New Post</h1>\n <p style={{ margin: '4px 0 0', fontSize: '0.875rem', color: textSec }}>Share a question, idea, or discussion with the community.</p>\n </div>\n {onCancel && (\n <button\n onClick={onCancel}\n style={{ padding: '6px 14px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: `1px solid ${border}`, cursor: 'pointer', backgroundColor: 'transparent', color: textSec, fontSize: '0.875rem', fontFamily: 'inherit' }}\n >\n Cancel\n </button>\n )}\n </div>\n\n {/* Form card */}\n <form\n onSubmit={handleSubmit}\n style={{ borderRadius: 'var(--mn-radius-lg, 0.75rem)', backgroundColor: card, border: `1px solid ${border}`, padding: '24px', display: 'flex', flexDirection: 'column', gap: 20 }}\n >\n {/* Author hint */}\n {currentUser && (\n <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', borderRadius: 'var(--mn-radius-md, 0.5rem)', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)', border: `1px solid ${border}` }}>\n <Avatar name={currentUser.name} size={24} src={currentUser.avatar} />\n <span style={{ fontSize: '0.875rem', color: textPri }}>Posting as <strong>{currentUser.name}</strong></span>\n </div>\n )}\n\n {/* Title */}\n <Field label=\"Title\" error={errors.title} required>\n <input\n value={title}\n onChange={e => { setTitle(e.target.value); if (errors.title) setErrors(p => ({ ...p, title: '' })); }}\n placeholder=\"What is your question or topic?\"\n style={inputStyle(errors.title)}\n onFocus={e => { e.target.style.borderColor = 'var(--mn-color-primary, #3B82F6)'; }}\n onBlur={e => { e.target.style.borderColor = errors.title ? 'rgba(239,68,68,0.4)' : border; }}\n />\n <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 4 }}>\n <span style={{ fontSize: '0.75rem', color: title.length > 180 ? '#F87171' : textSec }}>{title.length}/200</span>\n </div>\n </Field>\n\n {/* Category */}\n <Field label=\"Category\" error={errors.category} required>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>\n {categories.map(cat => {\n const isSelected = categoryId === cat.id;\n return (\n <button\n key={cat.id}\n type=\"button\"\n onClick={() => { setCategory(cat.id); if (errors.category) setErrors(p => ({ ...p, category: '' })); }}\n style={{\n display: 'flex', alignItems: 'center', gap: 6, padding: '7px 14px',\n borderRadius: 'var(--mn-radius-md, 0.5rem)', border: `1px solid ${isSelected ? 'var(--mn-color-primary, #3B82F6)' : border}`,\n cursor: 'pointer', fontFamily: 'inherit', fontSize: '0.875rem',\n backgroundColor: isSelected ? 'rgba(59,130,246,0.1)' : inputBg,\n color: isSelected ? 'var(--mn-color-primary, #3B82F6)' : textPri,\n transition: 'all 100ms', fontWeight: isSelected ? 600 : 400,\n }}\n >\n {cat.icon && <span>{cat.icon}</span>}\n {cat.label}\n </button>\n );\n })}\n </div>\n </Field>\n\n {/* Body */}\n <Field label=\"Body\" error={errors.body} required>\n <textarea\n value={body}\n onChange={e => { setBody(e.target.value); if (errors.body) setErrors(p => ({ ...p, body: '' })); }}\n placeholder=\"Describe your question or topic in detail. The more context you provide, the better the community can help.\"\n rows={8}\n style={{ ...inputStyle(errors.body), resize: 'vertical', lineHeight: 1.65 }}\n onFocus={e => { e.target.style.borderColor = 'var(--mn-color-primary, #3B82F6)'; }}\n onBlur={e => { e.target.style.borderColor = errors.body ? 'rgba(239,68,68,0.4)' : border; }}\n />\n </Field>\n\n {/* Tags */}\n <Field label=\"Tags\" hint=\"Up to 5 tags. Press Enter or comma to add.\">\n <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', padding: '8px 10px', borderRadius: 'var(--mn-radius-md, 0.5rem)', backgroundColor: inputBg, border: `1px solid ${border}`, minHeight: 42, alignItems: 'center' }}>\n {tags.map(t => (\n <span key={t} style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: '0.75rem', padding: '2px 8px', borderRadius: 9999, backgroundColor: isDark ? 'rgba(59,130,246,0.1)' : 'rgba(59,130,246,0.07)', color: '#60A5FA', border: '1px solid rgba(59,130,246,0.2)' }}>\n #{t}\n <button type=\"button\" onClick={() => setTags(prev => prev.filter(x => x !== t))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'inherit', padding: 0, fontSize: 12, lineHeight: 1, display: 'flex', alignItems: 'center' }}>×</button>\n </span>\n ))}\n {tags.length < 5 && (\n <input\n value={tagInput}\n onChange={e => setTagInput(e.target.value)}\n onKeyDown={handleTagKeyDown}\n placeholder={tags.length === 0 ? 'e.g. bug, question, feature' : ''}\n style={{ border: 'none', outline: 'none', backgroundColor: 'transparent', color: textPri, fontSize: '0.875rem', fontFamily: 'inherit', flex: 1, minWidth: 120 }}\n />\n )}\n </div>\n </Field>\n\n {/* Submit */}\n <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, paddingTop: 4, borderTop: `1px solid ${border}`, marginTop: 4 }}>\n {onCancel && (\n <button type=\"button\" onClick={onCancel} style={{ padding: '9px 20px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: `1px solid ${border}`, cursor: 'pointer', backgroundColor: 'transparent', color: textSec, fontSize: '0.875rem', fontFamily: 'inherit' }}>\n Cancel\n </button>\n )}\n <button\n type=\"submit\"\n disabled={submitting}\n style={{ padding: '9px 24px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: 'none', cursor: submitting ? 'wait' : 'pointer', backgroundColor: submitting ? 'rgba(59,130,246,0.5)' : 'var(--mn-color-primary, #3B82F6)', color: '#fff', fontSize: '0.875rem', fontWeight: 600, fontFamily: 'inherit', display: 'flex', alignItems: 'center', gap: 8 }}\n >\n {submitting && <Spinner />}\n {submitting ? 'Publishing…' : 'Publish Post'}\n </button>\n </div>\n </form>\n </div>\n );\n}\n\nfunction Field({ label, children, error, hint, required }) {\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>\n <label style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--mn-text-primary-dark, #F0F4FF)', display: 'flex', gap: 4 }}>\n {label}\n {required && <span style={{ color: '#F87171' }}>*</span>}\n </label>\n {children}\n {hint && !error && <p style={{ margin: 0, fontSize: '0.75rem', color: 'var(--mn-text-secondary-dark, #94A3B8)' }}>{hint}</p>}\n {error && <p style={{ margin: 0, fontSize: '0.8125rem', color: '#F87171' }}>{error}</p>}\n </div>\n );\n}\n\nfunction Spinner() {\n return <span style={{ display: 'inline-block', width: 12, height: 12, border: '2px solid rgba(255,255,255,0.3)', borderTopColor: '#fff', borderRadius: '50%', animation: 'mn-spin 0.6s linear infinite' }}>\n <style>{`@keyframes mn-spin { to { transform: rotate(360deg); } }`}</style>\n </span>;\n}\n"],"names":["CategoryNav","categories","active","onChange","isDark","title","style","card","border","textPri","textSec","jsxs","jsx","cat","isActive","e","VoteButton","count","userVote","vertical","size","optimistic","setOptimistic","useState","vote","total","upCol","downCol","numCol","btnSz","iconSz","cast","v","next","wrapStyle","VBtn","icon","color","onClick","AuthorMeta","author","date","prefix","avatarSz","fontSize","Avatar","Fragment","formatRelative","TagChip","label","bg","CAT_COLORS","CategoryBadge","category","c","name","src","initials","h","d","diff","PostCard","post","onVote","compact","_a","t","MetaCount","value","PostList","posts","onPostClick","isLoading","_","SkeletonCard","EmptyPostList","message","cta","onCta","S","w","DEMO_CATEGORIES","DEMO_POSTS","DEMO_REPLIES","SORTS","ForumPage","activeCategory","searchQuery","sortBy","onCategoryChange","onSearch","onSortChange","onNewPost","header","subtitle","localSearch","setLocalSearch","localSort","setLocalSort","localCategory","setLocalCategory","inputBg","handleSearch","q","handleSort","s","handleCategory","id","displayed","useMemo","list","p","a","b","PostDetail","StatusBadge","children","COLORS","ReplyCard","reply","onAccept","canAccept","acceptedBorder","acceptedBg","ReplyThread","replies","currentUser","onSubmit","isLocked","sorted","ReplyComposer","placeholder","body","setBody","submitting","setSub","error","setError","handleSubmit","trimmed","Spinner","PostPage","onBack","onVotePost","onVoteReply","onSubmitReply","PostSkeleton","CreatePostPage","onCancel","setTitle","categoryId","setCategory","tagInput","setTagInput","tags","setTags","errors","setErrors","validate","errs","handleTagKeyDown","newTag","prev","inputStyle","hasError","Field","isSelected","x","hint","required"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mounaji_npm/forum",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Modular forum and posts system — PostCard, PostList, PostDetail, ReplyThread, CategoryNav, and full page scaffolds",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"forum",
|
|
7
|
+
"posts",
|
|
8
|
+
"community",
|
|
9
|
+
"threads",
|
|
10
|
+
"replies",
|
|
11
|
+
"react",
|
|
12
|
+
"mounaji"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
},
|
|
20
|
+
"main": "./src/index.js",
|
|
21
|
+
"module": "./src/index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./src/index.js",
|
|
25
|
+
"require": "./src/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "vite build",
|
|
34
|
+
"dev": "vite build --watch"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=17.0.0",
|
|
38
|
+
"react-dom": ">=17.0.0",
|
|
39
|
+
"@mounaji_npm/tokens": ">=0.1.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
43
|
+
"react": "^19.0.0",
|
|
44
|
+
"react-dom": "^19.0.0",
|
|
45
|
+
"vite": "^6.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreatePostPage — @mounaji_npm/forum
|
|
3
|
+
*
|
|
4
|
+
* New post form: title, body, category selector, tags input.
|
|
5
|
+
* No router assumptions.
|
|
6
|
+
*
|
|
7
|
+
* Props:
|
|
8
|
+
* categories — Category[]
|
|
9
|
+
* onSubmit — (post: { title, body, categoryId, tags }) => void | Promise
|
|
10
|
+
* onCancel — () => void
|
|
11
|
+
* currentUser — { name, avatar? } | null
|
|
12
|
+
* isDark — boolean (default: true)
|
|
13
|
+
* style — CSSProperties
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useState } from 'react';
|
|
17
|
+
import { DEMO_CATEGORIES } from './demo.js';
|
|
18
|
+
import { Avatar } from './components/shared.jsx';
|
|
19
|
+
|
|
20
|
+
export function CreatePostPage({
|
|
21
|
+
categories = DEMO_CATEGORIES.filter(c => c.id !== 'all'),
|
|
22
|
+
onSubmit,
|
|
23
|
+
onCancel,
|
|
24
|
+
currentUser = null,
|
|
25
|
+
isDark = true,
|
|
26
|
+
style,
|
|
27
|
+
}) {
|
|
28
|
+
const [title, setTitle] = useState('');
|
|
29
|
+
const [body, setBody] = useState('');
|
|
30
|
+
const [categoryId, setCategory] = useState('');
|
|
31
|
+
const [tagInput, setTagInput] = useState('');
|
|
32
|
+
const [tags, setTags] = useState([]);
|
|
33
|
+
const [submitting, setSub] = useState(false);
|
|
34
|
+
const [errors, setErrors] = useState({});
|
|
35
|
+
|
|
36
|
+
const card = isDark ? 'var(--mn-color-card-dark, #0B0F23)' : 'var(--mn-color-card-light, #FAFAF8)';
|
|
37
|
+
const border = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.08)';
|
|
38
|
+
const textPri = isDark ? 'var(--mn-text-primary-dark, #F0F4FF)' : 'var(--mn-text-primary-light, #1A1A2E)';
|
|
39
|
+
const textSec = isDark ? 'var(--mn-text-secondary-dark, #94A3B8)' : 'var(--mn-text-secondary-light, #64748B)';
|
|
40
|
+
const inputBg = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)';
|
|
41
|
+
|
|
42
|
+
function validate() {
|
|
43
|
+
const e = {};
|
|
44
|
+
if (!title.trim()) e.title = 'Title is required.';
|
|
45
|
+
if (title.length > 200) e.title = 'Title must be under 200 characters.';
|
|
46
|
+
if (!body.trim()) e.body = 'Body is required.';
|
|
47
|
+
if (!categoryId) e.category = 'Please choose a category.';
|
|
48
|
+
return e;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function handleSubmit(e) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
const errs = validate();
|
|
54
|
+
if (Object.keys(errs).length) { setErrors(errs); return; }
|
|
55
|
+
setErrors({});
|
|
56
|
+
setSub(true);
|
|
57
|
+
try {
|
|
58
|
+
await onSubmit?.({ title: title.trim(), body: body.trim(), categoryId, tags });
|
|
59
|
+
} finally {
|
|
60
|
+
setSub(false);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleTagKeyDown(e) {
|
|
65
|
+
if ((e.key === 'Enter' || e.key === ',') && tagInput.trim()) {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
const newTag = tagInput.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
68
|
+
if (!tags.includes(newTag) && tags.length < 5) {
|
|
69
|
+
setTags(prev => [...prev, newTag]);
|
|
70
|
+
}
|
|
71
|
+
setTagInput('');
|
|
72
|
+
}
|
|
73
|
+
if (e.key === 'Backspace' && !tagInput && tags.length) {
|
|
74
|
+
setTags(prev => prev.slice(0, -1));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const inputStyle = (hasError) => ({
|
|
79
|
+
width: '100%', boxSizing: 'border-box', padding: '10px 12px',
|
|
80
|
+
borderRadius: 'var(--mn-radius-md, 0.5rem)',
|
|
81
|
+
backgroundColor: inputBg, border: `1px solid ${hasError ? 'rgba(239,68,68,0.4)' : border}`,
|
|
82
|
+
color: textPri, fontSize: '0.9375rem', fontFamily: 'var(--mn-font-family, system-ui, sans-serif)',
|
|
83
|
+
outline: 'none', transition: 'border-color 120ms',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div style={{
|
|
88
|
+
padding: 'var(--mn-spacing-xl, 32px)',
|
|
89
|
+
display: 'flex', flexDirection: 'column', gap: 20,
|
|
90
|
+
maxWidth: 780, fontFamily: 'var(--mn-font-family, system-ui, sans-serif)',
|
|
91
|
+
...style,
|
|
92
|
+
}}>
|
|
93
|
+
{/* Header */}
|
|
94
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
|
95
|
+
<div>
|
|
96
|
+
<h1 style={{ margin: 0, fontSize: 'var(--mn-font-size-xl, 1.25rem)', fontWeight: 700, color: textPri }}>New Post</h1>
|
|
97
|
+
<p style={{ margin: '4px 0 0', fontSize: '0.875rem', color: textSec }}>Share a question, idea, or discussion with the community.</p>
|
|
98
|
+
</div>
|
|
99
|
+
{onCancel && (
|
|
100
|
+
<button
|
|
101
|
+
onClick={onCancel}
|
|
102
|
+
style={{ padding: '6px 14px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: `1px solid ${border}`, cursor: 'pointer', backgroundColor: 'transparent', color: textSec, fontSize: '0.875rem', fontFamily: 'inherit' }}
|
|
103
|
+
>
|
|
104
|
+
Cancel
|
|
105
|
+
</button>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Form card */}
|
|
110
|
+
<form
|
|
111
|
+
onSubmit={handleSubmit}
|
|
112
|
+
style={{ borderRadius: 'var(--mn-radius-lg, 0.75rem)', backgroundColor: card, border: `1px solid ${border}`, padding: '24px', display: 'flex', flexDirection: 'column', gap: 20 }}
|
|
113
|
+
>
|
|
114
|
+
{/* Author hint */}
|
|
115
|
+
{currentUser && (
|
|
116
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', borderRadius: 'var(--mn-radius-md, 0.5rem)', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)', border: `1px solid ${border}` }}>
|
|
117
|
+
<Avatar name={currentUser.name} size={24} src={currentUser.avatar} />
|
|
118
|
+
<span style={{ fontSize: '0.875rem', color: textPri }}>Posting as <strong>{currentUser.name}</strong></span>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{/* Title */}
|
|
123
|
+
<Field label="Title" error={errors.title} required>
|
|
124
|
+
<input
|
|
125
|
+
value={title}
|
|
126
|
+
onChange={e => { setTitle(e.target.value); if (errors.title) setErrors(p => ({ ...p, title: '' })); }}
|
|
127
|
+
placeholder="What is your question or topic?"
|
|
128
|
+
style={inputStyle(errors.title)}
|
|
129
|
+
onFocus={e => { e.target.style.borderColor = 'var(--mn-color-primary, #3B82F6)'; }}
|
|
130
|
+
onBlur={e => { e.target.style.borderColor = errors.title ? 'rgba(239,68,68,0.4)' : border; }}
|
|
131
|
+
/>
|
|
132
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 4 }}>
|
|
133
|
+
<span style={{ fontSize: '0.75rem', color: title.length > 180 ? '#F87171' : textSec }}>{title.length}/200</span>
|
|
134
|
+
</div>
|
|
135
|
+
</Field>
|
|
136
|
+
|
|
137
|
+
{/* Category */}
|
|
138
|
+
<Field label="Category" error={errors.category} required>
|
|
139
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
140
|
+
{categories.map(cat => {
|
|
141
|
+
const isSelected = categoryId === cat.id;
|
|
142
|
+
return (
|
|
143
|
+
<button
|
|
144
|
+
key={cat.id}
|
|
145
|
+
type="button"
|
|
146
|
+
onClick={() => { setCategory(cat.id); if (errors.category) setErrors(p => ({ ...p, category: '' })); }}
|
|
147
|
+
style={{
|
|
148
|
+
display: 'flex', alignItems: 'center', gap: 6, padding: '7px 14px',
|
|
149
|
+
borderRadius: 'var(--mn-radius-md, 0.5rem)', border: `1px solid ${isSelected ? 'var(--mn-color-primary, #3B82F6)' : border}`,
|
|
150
|
+
cursor: 'pointer', fontFamily: 'inherit', fontSize: '0.875rem',
|
|
151
|
+
backgroundColor: isSelected ? 'rgba(59,130,246,0.1)' : inputBg,
|
|
152
|
+
color: isSelected ? 'var(--mn-color-primary, #3B82F6)' : textPri,
|
|
153
|
+
transition: 'all 100ms', fontWeight: isSelected ? 600 : 400,
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{cat.icon && <span>{cat.icon}</span>}
|
|
157
|
+
{cat.label}
|
|
158
|
+
</button>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</div>
|
|
162
|
+
</Field>
|
|
163
|
+
|
|
164
|
+
{/* Body */}
|
|
165
|
+
<Field label="Body" error={errors.body} required>
|
|
166
|
+
<textarea
|
|
167
|
+
value={body}
|
|
168
|
+
onChange={e => { setBody(e.target.value); if (errors.body) setErrors(p => ({ ...p, body: '' })); }}
|
|
169
|
+
placeholder="Describe your question or topic in detail. The more context you provide, the better the community can help."
|
|
170
|
+
rows={8}
|
|
171
|
+
style={{ ...inputStyle(errors.body), resize: 'vertical', lineHeight: 1.65 }}
|
|
172
|
+
onFocus={e => { e.target.style.borderColor = 'var(--mn-color-primary, #3B82F6)'; }}
|
|
173
|
+
onBlur={e => { e.target.style.borderColor = errors.body ? 'rgba(239,68,68,0.4)' : border; }}
|
|
174
|
+
/>
|
|
175
|
+
</Field>
|
|
176
|
+
|
|
177
|
+
{/* Tags */}
|
|
178
|
+
<Field label="Tags" hint="Up to 5 tags. Press Enter or comma to add.">
|
|
179
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', padding: '8px 10px', borderRadius: 'var(--mn-radius-md, 0.5rem)', backgroundColor: inputBg, border: `1px solid ${border}`, minHeight: 42, alignItems: 'center' }}>
|
|
180
|
+
{tags.map(t => (
|
|
181
|
+
<span key={t} style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: '0.75rem', padding: '2px 8px', borderRadius: 9999, backgroundColor: isDark ? 'rgba(59,130,246,0.1)' : 'rgba(59,130,246,0.07)', color: '#60A5FA', border: '1px solid rgba(59,130,246,0.2)' }}>
|
|
182
|
+
#{t}
|
|
183
|
+
<button type="button" onClick={() => setTags(prev => prev.filter(x => x !== t))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'inherit', padding: 0, fontSize: 12, lineHeight: 1, display: 'flex', alignItems: 'center' }}>×</button>
|
|
184
|
+
</span>
|
|
185
|
+
))}
|
|
186
|
+
{tags.length < 5 && (
|
|
187
|
+
<input
|
|
188
|
+
value={tagInput}
|
|
189
|
+
onChange={e => setTagInput(e.target.value)}
|
|
190
|
+
onKeyDown={handleTagKeyDown}
|
|
191
|
+
placeholder={tags.length === 0 ? 'e.g. bug, question, feature' : ''}
|
|
192
|
+
style={{ border: 'none', outline: 'none', backgroundColor: 'transparent', color: textPri, fontSize: '0.875rem', fontFamily: 'inherit', flex: 1, minWidth: 120 }}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
</Field>
|
|
197
|
+
|
|
198
|
+
{/* Submit */}
|
|
199
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, paddingTop: 4, borderTop: `1px solid ${border}`, marginTop: 4 }}>
|
|
200
|
+
{onCancel && (
|
|
201
|
+
<button type="button" onClick={onCancel} style={{ padding: '9px 20px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: `1px solid ${border}`, cursor: 'pointer', backgroundColor: 'transparent', color: textSec, fontSize: '0.875rem', fontFamily: 'inherit' }}>
|
|
202
|
+
Cancel
|
|
203
|
+
</button>
|
|
204
|
+
)}
|
|
205
|
+
<button
|
|
206
|
+
type="submit"
|
|
207
|
+
disabled={submitting}
|
|
208
|
+
style={{ padding: '9px 24px', borderRadius: 'var(--mn-radius-md, 0.5rem)', border: 'none', cursor: submitting ? 'wait' : 'pointer', backgroundColor: submitting ? 'rgba(59,130,246,0.5)' : 'var(--mn-color-primary, #3B82F6)', color: '#fff', fontSize: '0.875rem', fontWeight: 600, fontFamily: 'inherit', display: 'flex', alignItems: 'center', gap: 8 }}
|
|
209
|
+
>
|
|
210
|
+
{submitting && <Spinner />}
|
|
211
|
+
{submitting ? 'Publishing…' : 'Publish Post'}
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
</form>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function Field({ label, children, error, hint, required }) {
|
|
220
|
+
return (
|
|
221
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
|
|
222
|
+
<label style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--mn-text-primary-dark, #F0F4FF)', display: 'flex', gap: 4 }}>
|
|
223
|
+
{label}
|
|
224
|
+
{required && <span style={{ color: '#F87171' }}>*</span>}
|
|
225
|
+
</label>
|
|
226
|
+
{children}
|
|
227
|
+
{hint && !error && <p style={{ margin: 0, fontSize: '0.75rem', color: 'var(--mn-text-secondary-dark, #94A3B8)' }}>{hint}</p>}
|
|
228
|
+
{error && <p style={{ margin: 0, fontSize: '0.8125rem', color: '#F87171' }}>{error}</p>}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function Spinner() {
|
|
234
|
+
return <span style={{ display: 'inline-block', width: 12, height: 12, border: '2px solid rgba(255,255,255,0.3)', borderTopColor: '#fff', borderRadius: '50%', animation: 'mn-spin 0.6s linear infinite' }}>
|
|
235
|
+
<style>{`@keyframes mn-spin { to { transform: rotate(360deg); } }`}</style>
|
|
236
|
+
</span>;
|
|
237
|
+
}
|