maudocs 1.0.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 +99 -0
- package/bin/cli.js +85 -0
- package/maudocs-1.0.0.tgz +0 -0
- package/package.json +35 -0
- package/template/components.js +1 -0
- package/template/index.html +895 -0
- package/template/src/Accordion.jsx +42 -0
- package/template/src/Alert.jsx +82 -0
- package/template/src/CodeBlock.jsx +41 -0
- package/template/src/Header.jsx +131 -0
- package/template/src/Sidebar.jsx +88 -0
- package/template/src/index.js +48 -0
- package/vite.config.js +24 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks';
|
|
2
|
+
|
|
3
|
+
export default function Accordion({ title = '', open = false, children }) {
|
|
4
|
+
const isInitiallyOpen = open === true || open === 'true' || open === '';
|
|
5
|
+
const [isOpen, setIsOpen] = useState(isInitiallyOpen);
|
|
6
|
+
|
|
7
|
+
// Re-run Lucide icons after rendering
|
|
8
|
+
if (window.lucide && typeof window.lucide.createIcons === 'function') {
|
|
9
|
+
setTimeout(() => window.lucide.createIcons(), 0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="border border-slate-200 dark:border-slate-800/80 rounded-xl overflow-hidden bg-white dark:bg-slate-900 transition-all duration-300 my-2">
|
|
14
|
+
<button
|
|
15
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
16
|
+
className="w-full flex items-center justify-between p-4 text-left font-semibold text-sm text-slate-900 dark:text-white hover:bg-slate-50 dark:hover:bg-slate-800/40 transition-colors focus:outline-none"
|
|
17
|
+
aria-expanded={isOpen}
|
|
18
|
+
>
|
|
19
|
+
<span>{title}</span>
|
|
20
|
+
<i
|
|
21
|
+
data-lucide="chevron-down"
|
|
22
|
+
className={`w-4 h-4 text-slate-500 dark:text-slate-400 transition-transform duration-300 shrink-0 ${
|
|
23
|
+
isOpen ? 'rotate-180' : ''
|
|
24
|
+
}`}
|
|
25
|
+
></i>
|
|
26
|
+
</button>
|
|
27
|
+
<div
|
|
28
|
+
className={`transition-all duration-350 ease-in-out overflow-hidden ${
|
|
29
|
+
isOpen ? 'max-h-[1000px] border-t border-slate-100 dark:border-slate-800/40' : 'max-h-0'
|
|
30
|
+
}`}
|
|
31
|
+
>
|
|
32
|
+
<div className="p-4 text-sm text-slate-600 dark:text-slate-400 leading-relaxed content-area">
|
|
33
|
+
{typeof children === 'string' ? (
|
|
34
|
+
<span dangerouslySetInnerHTML={{ __html: children }} />
|
|
35
|
+
) : (
|
|
36
|
+
children
|
|
37
|
+
)}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks';
|
|
2
|
+
|
|
3
|
+
export default function Alert({
|
|
4
|
+
type = 'info',
|
|
5
|
+
title = '',
|
|
6
|
+
dismissible = false,
|
|
7
|
+
className = '',
|
|
8
|
+
icon = null,
|
|
9
|
+
children
|
|
10
|
+
}) {
|
|
11
|
+
const [visible, setVisible] = useState(true);
|
|
12
|
+
|
|
13
|
+
if (!visible) return null;
|
|
14
|
+
|
|
15
|
+
let border = 'border-blue-100 dark:border-blue-900/30';
|
|
16
|
+
let bg = 'bg-blue-50/40 dark:bg-blue-950/10';
|
|
17
|
+
let text = 'text-blue-900 dark:text-blue-200';
|
|
18
|
+
let iconColor = 'text-blue-600 dark:text-blue-400';
|
|
19
|
+
let defaultIcon = 'info';
|
|
20
|
+
|
|
21
|
+
// Handle boolean conversions when attributes are passed from Web Component
|
|
22
|
+
const isDismissible = dismissible === true || dismissible === 'true' || dismissible === '';
|
|
23
|
+
|
|
24
|
+
switch (type) {
|
|
25
|
+
case 'warning':
|
|
26
|
+
border = 'border-amber-100 dark:border-amber-900/30';
|
|
27
|
+
bg = 'bg-amber-50/40 dark:bg-amber-950/10';
|
|
28
|
+
text = 'text-amber-900 dark:text-amber-200';
|
|
29
|
+
iconColor = 'text-amber-600 dark:text-amber-400';
|
|
30
|
+
defaultIcon = 'alert-triangle';
|
|
31
|
+
break;
|
|
32
|
+
case 'success':
|
|
33
|
+
border = 'border-emerald-100 dark:border-emerald-900/30';
|
|
34
|
+
bg = 'bg-emerald-50/40 dark:bg-emerald-950/10';
|
|
35
|
+
text = 'text-emerald-900 dark:text-emerald-200';
|
|
36
|
+
iconColor = 'text-emerald-600 dark:text-emerald-400';
|
|
37
|
+
defaultIcon = 'check-circle-2';
|
|
38
|
+
break;
|
|
39
|
+
case 'danger':
|
|
40
|
+
case 'error':
|
|
41
|
+
border = 'border-red-100 dark:border-red-900/30';
|
|
42
|
+
bg = 'bg-red-50/40 dark:bg-red-950/10';
|
|
43
|
+
text = 'text-red-900 dark:text-red-200';
|
|
44
|
+
iconColor = 'text-red-600 dark:text-red-400';
|
|
45
|
+
defaultIcon = 'x-circle';
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const finalIcon = icon || defaultIcon;
|
|
50
|
+
|
|
51
|
+
// Re-run Lucide icons after rendering
|
|
52
|
+
if (window.lucide && typeof window.lucide.createIcons === 'function') {
|
|
53
|
+
setTimeout(() => window.lucide.createIcons(), 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className={`block my-4 transition-all duration-300 ${className}`}>
|
|
58
|
+
<div className={`relative flex gap-3 p-4 rounded-xl border ${border} ${bg} ${text}`}>
|
|
59
|
+
<i data-lucide={finalIcon} className={`w-5 h-5 ${iconColor} shrink-0 mt-0.5`}></i>
|
|
60
|
+
<div className="text-sm flex-1">
|
|
61
|
+
{title && <span className="font-semibold block mb-0.5">{title}</span>}
|
|
62
|
+
<div className="leading-relaxed content-area">
|
|
63
|
+
{typeof children === 'string' ? (
|
|
64
|
+
<span dangerouslySetInnerHTML={{ __html: children }} />
|
|
65
|
+
) : (
|
|
66
|
+
children
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
{isDismissible && (
|
|
71
|
+
<button
|
|
72
|
+
onClick={() => setVisible(false)}
|
|
73
|
+
className="close-btn p-1 -mt-1 -mr-1 rounded-lg text-current hover:bg-black/5 dark:hover:bg-white/5 opacity-60 hover:opacity-100 self-start transition-all"
|
|
74
|
+
aria-label="Close Alert"
|
|
75
|
+
>
|
|
76
|
+
<i data-lucide="x" className="w-4 h-4"></i>
|
|
77
|
+
</button>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks';
|
|
2
|
+
|
|
3
|
+
export default function CodeBlock({ language = 'plaintext', code = '' }) {
|
|
4
|
+
const [copied, setCopied] = useState(false);
|
|
5
|
+
|
|
6
|
+
const handleCopy = async () => {
|
|
7
|
+
try {
|
|
8
|
+
await navigator.clipboard.writeText(code);
|
|
9
|
+
setCopied(true);
|
|
10
|
+
setTimeout(() => setCopied(false), 2000);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
console.error('Failed to copy text: ', err);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Re-run Lucide icons after rendering
|
|
17
|
+
if (window.lucide && typeof window.lucide.createIcons === 'function') {
|
|
18
|
+
setTimeout(() => window.lucide.createIcons(), 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="relative group my-4 rounded-xl overflow-hidden border border-slate-800 bg-slate-950 text-slate-200 font-mono text-sm shadow-md">
|
|
23
|
+
{/* Code Header bar */}
|
|
24
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-slate-900 bg-slate-900/60 text-xs text-slate-400 select-none">
|
|
25
|
+
<span className="font-semibold uppercase tracking-wider">{language}</span>
|
|
26
|
+
<button
|
|
27
|
+
onClick={handleCopy}
|
|
28
|
+
className="flex items-center gap-1.5 px-2 py-1 rounded bg-slate-800 hover:bg-slate-700 active:bg-slate-800 text-slate-350 hover:text-white transition-all focus:outline-none"
|
|
29
|
+
>
|
|
30
|
+
<i data-lucide={copied ? 'check' : 'copy'} className="w-3.5 h-3.5"></i>
|
|
31
|
+
<span>{copied ? 'Copied!' : 'Copy'}</span>
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{/* Code Area */}
|
|
36
|
+
<pre className="p-4 overflow-x-auto m-0 leading-relaxed font-mono text-xs sm:text-sm">
|
|
37
|
+
<code>{code}</code>
|
|
38
|
+
</pre>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { useState, useEffect } from 'preact/hooks';
|
|
2
|
+
|
|
3
|
+
export default function Header({
|
|
4
|
+
logoText = 'Dev',
|
|
5
|
+
logoHighlight = 'Docs',
|
|
6
|
+
logoHref = '#',
|
|
7
|
+
githubUrl = 'https://github.com',
|
|
8
|
+
searchPlaceholder = 'Tìm kiếm tài liệu...'
|
|
9
|
+
}) {
|
|
10
|
+
const [isDark, setIsDark] = useState(false);
|
|
11
|
+
|
|
12
|
+
// Sync state with DOM on mount
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const hasDarkClass = document.documentElement.classList.contains('dark');
|
|
15
|
+
setIsDark(hasDarkClass);
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
// Run Lucide icons rendering
|
|
19
|
+
if (window.lucide && typeof window.lucide.createIcons === 'function') {
|
|
20
|
+
setTimeout(() => window.lucide.createIcons(), 0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const toggleTheme = () => {
|
|
24
|
+
const nextDark = !isDark;
|
|
25
|
+
setIsDark(nextDark);
|
|
26
|
+
|
|
27
|
+
if (nextDark) {
|
|
28
|
+
document.documentElement.classList.add('dark');
|
|
29
|
+
localStorage.setItem('color-theme', 'dark');
|
|
30
|
+
} else {
|
|
31
|
+
document.documentElement.classList.remove('dark');
|
|
32
|
+
localStorage.setItem('color-theme', 'light');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Custom events/updates if chart or mermaid needs refresh
|
|
36
|
+
if (window.myChart) {
|
|
37
|
+
window.myChart.options.plugins.legend.labels.color = nextDark ? '#cbd5e1' : '#334155';
|
|
38
|
+
window.myChart.options.scales.y.grid.color = nextDark ? '#334155' : '#e2e8f0';
|
|
39
|
+
window.myChart.options.scales.y.ticks.color = nextDark ? '#cbd5e1' : '#334155';
|
|
40
|
+
window.myChart.options.scales.x.ticks.color = nextDark ? '#cbd5e1' : '#334155';
|
|
41
|
+
window.myChart.update();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof window.mermaid !== 'undefined' && typeof window.renderMermaid === 'function') {
|
|
45
|
+
window.renderMermaid();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleSidebarToggle = () => {
|
|
50
|
+
const sidebar = document.getElementById('sidebar');
|
|
51
|
+
const backdrop = document.getElementById('sidebar-backdrop');
|
|
52
|
+
if (sidebar) {
|
|
53
|
+
sidebar.classList.remove('-translate-x-full');
|
|
54
|
+
}
|
|
55
|
+
if (backdrop) {
|
|
56
|
+
backdrop.classList.remove('hidden');
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<header className="sticky top-0 z-40 w-full border-b border-slate-200/80 bg-white/80 backdrop-blur-md dark:border-slate-800/80 dark:bg-slate-900/80 transition-colors duration-200">
|
|
62
|
+
<div className="max-w-[90rem] mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between gap-4">
|
|
63
|
+
|
|
64
|
+
{/* Logo & Hamburger Menu (Mobile Only) */}
|
|
65
|
+
<div className="flex items-center gap-3">
|
|
66
|
+
<button
|
|
67
|
+
onClick={handleSidebarToggle}
|
|
68
|
+
id="mobile-sidebar-toggle"
|
|
69
|
+
className="p-2 -ml-2 rounded-lg text-slate-500 hover:text-slate-800 hover:bg-slate-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800 md:hidden transition-colors"
|
|
70
|
+
aria-label="Toggle navigation"
|
|
71
|
+
>
|
|
72
|
+
<i data-lucide="menu" className="w-6 h-6"></i>
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
<a href={logoHref} className="flex items-center gap-2.5 font-bold text-lg tracking-tight text-slate-900 dark:text-white">
|
|
76
|
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-tr from-brand-600 to-indigo-500 flex items-center justify-center text-white shadow-md shadow-brand-500/20">
|
|
77
|
+
<i data-lucide="book-open" className="w-4.5 h-4.5"></i>
|
|
78
|
+
</div>
|
|
79
|
+
<span>
|
|
80
|
+
{logoText}
|
|
81
|
+
<span className="text-brand-600 dark:text-brand-400">{logoHighlight}</span>
|
|
82
|
+
</span>
|
|
83
|
+
</a>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Search Bar */}
|
|
87
|
+
<div className="hidden sm:flex flex-1 max-w-md relative">
|
|
88
|
+
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
89
|
+
<i data-lucide="search" className="h-4 w-4 text-slate-400"></i>
|
|
90
|
+
</div>
|
|
91
|
+
<input
|
|
92
|
+
type="text"
|
|
93
|
+
id="search-input"
|
|
94
|
+
placeholder={searchPlaceholder}
|
|
95
|
+
className="w-full rounded-full border border-slate-200 bg-slate-50/50 py-1.5 pl-9 pr-4 text-sm text-slate-900 placeholder-slate-400 focus:border-brand-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-slate-800 dark:bg-slate-900/50 dark:text-slate-100 dark:placeholder-slate-500 dark:focus:bg-slate-900 transition-all"
|
|
96
|
+
/>
|
|
97
|
+
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
98
|
+
<kbd className="hidden md:inline-block rounded border border-slate-200 bg-white px-1.5 font-mono text-[10px] font-medium text-slate-400 dark:border-slate-800 dark:bg-slate-900">Ctrl K</kbd>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Actions (Theme Toggle, Social Links) */}
|
|
103
|
+
<div className="flex items-center gap-2">
|
|
104
|
+
{/* Theme Toggle */}
|
|
105
|
+
<button
|
|
106
|
+
onClick={toggleTheme}
|
|
107
|
+
className="p-2.5 rounded-full text-slate-500 hover:text-slate-800 hover:bg-slate-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800 transition-all"
|
|
108
|
+
aria-label="Toggle dark mode"
|
|
109
|
+
>
|
|
110
|
+
{isDark ? (
|
|
111
|
+
<i data-lucide="sun" className="w-5 h-5"></i>
|
|
112
|
+
) : (
|
|
113
|
+
<i data-lucide="moon" className="w-5 h-5"></i>
|
|
114
|
+
)}
|
|
115
|
+
</button>
|
|
116
|
+
|
|
117
|
+
{/* GitHub */}
|
|
118
|
+
<a
|
|
119
|
+
href={githubUrl}
|
|
120
|
+
target="_blank"
|
|
121
|
+
rel="noopener noreferrer"
|
|
122
|
+
className="p-2.5 rounded-full text-slate-500 hover:text-slate-800 hover:bg-slate-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800 transition-all"
|
|
123
|
+
aria-label="GitHub"
|
|
124
|
+
>
|
|
125
|
+
<i data-lucide="github" className="w-5 h-5"></i>
|
|
126
|
+
</a>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</header>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useEffect } from 'preact/hooks';
|
|
2
|
+
|
|
3
|
+
export default function Sidebar({
|
|
4
|
+
mobileTitle = 'Điều hướng',
|
|
5
|
+
searchPlaceholder = 'Tìm kiếm...',
|
|
6
|
+
children
|
|
7
|
+
}) {
|
|
8
|
+
|
|
9
|
+
// Re-run Lucide icons after rendering
|
|
10
|
+
if (window.lucide && typeof window.lucide.createIcons === 'function') {
|
|
11
|
+
setTimeout(() => window.lucide.createIcons(), 0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const handleSidebarClose = () => {
|
|
15
|
+
const sidebar = document.getElementById('sidebar');
|
|
16
|
+
const backdrop = document.getElementById('sidebar-backdrop');
|
|
17
|
+
if (sidebar) {
|
|
18
|
+
sidebar.classList.add('-translate-x-full');
|
|
19
|
+
}
|
|
20
|
+
if (backdrop) {
|
|
21
|
+
backdrop.classList.add('hidden');
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Close sidebar when clicking any navigation link (mobile only)
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const handleLinkClick = (e) => {
|
|
28
|
+
// Check if clicked element or parent has .nav-link class
|
|
29
|
+
const link = e.target.closest('.nav-link');
|
|
30
|
+
if (link) {
|
|
31
|
+
handleSidebarClose();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
document.addEventListener('click', handleLinkClick);
|
|
36
|
+
return () => {
|
|
37
|
+
document.removeEventListener('click', handleLinkClick);
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
{/* Backdrop for mobile */}
|
|
44
|
+
<div
|
|
45
|
+
id="sidebar-backdrop"
|
|
46
|
+
onClick={handleSidebarClose}
|
|
47
|
+
className="fixed inset-0 z-30 bg-slate-900/40 backdrop-blur-sm hidden md:hidden"
|
|
48
|
+
></div>
|
|
49
|
+
|
|
50
|
+
{/* Main Sidebar Aside */}
|
|
51
|
+
<aside
|
|
52
|
+
id="sidebar"
|
|
53
|
+
className="fixed inset-y-0 left-0 z-30 w-64 bg-white border-r border-slate-200 dark:bg-slate-900 dark:border-slate-800 transform -translate-x-full md:translate-x-0 md:static md:z-0 md:h-[calc(100vh-4rem)] md:sticky md:top-16 transition-all duration-200 overflow-y-auto p-6 flex flex-col gap-6"
|
|
54
|
+
>
|
|
55
|
+
{/* Mobile Close Button & Search */}
|
|
56
|
+
<div className="flex items-center justify-between md:hidden pb-2 border-b border-slate-100 dark:border-slate-800">
|
|
57
|
+
<span className="font-bold text-slate-900 dark:text-white">{mobileTitle}</span>
|
|
58
|
+
<button
|
|
59
|
+
onClick={handleSidebarClose}
|
|
60
|
+
id="mobile-sidebar-close"
|
|
61
|
+
className="p-1 rounded-lg text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800"
|
|
62
|
+
>
|
|
63
|
+
<i data-lucide="x" className="w-5 h-5"></i>
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Mobile-only Search Box */}
|
|
68
|
+
<div className="sm:hidden relative">
|
|
69
|
+
<i data-lucide="search" className="absolute left-3 top-2.5 h-4 w-4 text-slate-400"></i>
|
|
70
|
+
<input
|
|
71
|
+
type="text"
|
|
72
|
+
placeholder={searchPlaceholder}
|
|
73
|
+
className="w-full rounded-lg border border-slate-200 bg-slate-50/50 py-2 pl-9 pr-4 text-sm focus:outline-none dark:border-slate-800 dark:bg-slate-900"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Navigation Link Tree */}
|
|
78
|
+
<nav className="flex-1 flex flex-col gap-6 text-sm">
|
|
79
|
+
{typeof children === 'string' ? (
|
|
80
|
+
<div dangerouslySetInnerHTML={{ __html: children }} className="contents" />
|
|
81
|
+
) : (
|
|
82
|
+
children
|
|
83
|
+
)}
|
|
84
|
+
</nav>
|
|
85
|
+
</aside>
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import register from 'preact-custom-element';
|
|
2
|
+
import { render, h } from 'preact';
|
|
3
|
+
import Alert from './Alert';
|
|
4
|
+
import Accordion from './Accordion';
|
|
5
|
+
import CodeBlock from './CodeBlock';
|
|
6
|
+
import Header from './Header';
|
|
7
|
+
import Sidebar from './Sidebar';
|
|
8
|
+
|
|
9
|
+
// Register custom elements without Shadow DOM so they inherit Tailwind CSS from the host page
|
|
10
|
+
register(Alert, 'mau-alert', ['type', 'title', 'dismissible', 'className', 'icon'], { shadow: false });
|
|
11
|
+
register(Accordion, 'mau-accordion', ['title', 'open'], { shadow: false });
|
|
12
|
+
register(CodeBlock, 'mau-code-block', ['language', 'code'], { shadow: false });
|
|
13
|
+
register(Header, 'mau-header', ['logoText', 'logoHighlight', 'logoHref', 'githubUrl', 'searchPlaceholder'], { shadow: false });
|
|
14
|
+
register(Sidebar, 'mau-sidebar', ['mobileTitle', 'searchPlaceholder'], { shadow: false });
|
|
15
|
+
|
|
16
|
+
// Expose DevDocs helper methods to window for JS Functional rendering
|
|
17
|
+
const DevDocs = {
|
|
18
|
+
Alert: (props) => {
|
|
19
|
+
const container = document.createElement('div');
|
|
20
|
+
const componentProps = { ...props };
|
|
21
|
+
render(h(Alert, componentProps), container);
|
|
22
|
+
return container.firstElementChild || container;
|
|
23
|
+
},
|
|
24
|
+
Accordion: (props) => {
|
|
25
|
+
const container = document.createElement('div');
|
|
26
|
+
render(h(Accordion, props), container);
|
|
27
|
+
return container.firstElementChild || container;
|
|
28
|
+
},
|
|
29
|
+
CodeBlock: (props) => {
|
|
30
|
+
const container = document.createElement('div');
|
|
31
|
+
render(h(CodeBlock, props), container);
|
|
32
|
+
return container.firstElementChild || container;
|
|
33
|
+
},
|
|
34
|
+
Header: (props) => {
|
|
35
|
+
const container = document.createElement('div');
|
|
36
|
+
render(h(Header, props), container);
|
|
37
|
+
return container.firstElementChild || container;
|
|
38
|
+
},
|
|
39
|
+
Sidebar: (props) => {
|
|
40
|
+
const container = document.createElement('div');
|
|
41
|
+
render(h(Sidebar, props), container);
|
|
42
|
+
return container.firstElementChild || container;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
window.DevDocs = DevDocs;
|
|
47
|
+
export { Alert, Accordion, CodeBlock, Header, Sidebar };
|
|
48
|
+
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import preact from '@preact/preset-vite';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [preact()],
|
|
7
|
+
build: {
|
|
8
|
+
lib: {
|
|
9
|
+
entry: resolve(__dirname, 'template/src/index.js'),
|
|
10
|
+
name: 'DevDocs',
|
|
11
|
+
fileName: () => 'components.js',
|
|
12
|
+
formats: ['iife']
|
|
13
|
+
},
|
|
14
|
+
outDir: resolve(__dirname, 'template'),
|
|
15
|
+
emptyOutDir: false,
|
|
16
|
+
rollupOptions: {
|
|
17
|
+
// We bundle everything (including preact) so the output is 100% self-contained
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
define: {
|
|
21
|
+
// Set process.env.NODE_ENV to production for Preact builds
|
|
22
|
+
'process.env.NODE_ENV': JSON.stringify('production')
|
|
23
|
+
}
|
|
24
|
+
});
|