@luciodale/docs-ui-kit 0.2.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.
@@ -0,0 +1,120 @@
1
+ ---
2
+ import type { SiteConfig } from "../types/config";
3
+
4
+ type Props = {
5
+ config: SiteConfig;
6
+ title: string;
7
+ description?: string;
8
+ currentPath: string;
9
+ type?: "website" | "article";
10
+ publishedDate?: string;
11
+ modifiedDate?: string;
12
+ noindex?: boolean;
13
+ };
14
+
15
+ const {
16
+ config,
17
+ title,
18
+ description = config.description,
19
+ currentPath,
20
+ type = "website",
21
+ publishedDate,
22
+ modifiedDate,
23
+ noindex = false,
24
+ } = Astro.props;
25
+
26
+ const canonicalUrl = new URL(currentPath, config.siteUrl).href;
27
+ const ogImageUrl = config.ogImage ? new URL(config.ogImage, config.siteUrl).href : undefined;
28
+ const fullTitle = currentPath === "/" ? title : `${title} | ${config.title}`;
29
+
30
+ const jsonLd =
31
+ type === "article"
32
+ ? {
33
+ "@context": "https://schema.org",
34
+ "@type": "TechArticle",
35
+ headline: title,
36
+ description,
37
+ url: canonicalUrl,
38
+ ...(ogImageUrl && { image: ogImageUrl }),
39
+ ...(config.author && {
40
+ author: { "@type": "Person", name: config.author },
41
+ }),
42
+ ...(publishedDate && { datePublished: publishedDate }),
43
+ ...(modifiedDate && { dateModified: modifiedDate }),
44
+ publisher: { "@type": "Organization", name: config.title },
45
+ isPartOf: {
46
+ "@type": "WebSite",
47
+ name: config.title,
48
+ url: config.siteUrl,
49
+ },
50
+ }
51
+ : {
52
+ "@context": "https://schema.org",
53
+ "@type": "WebSite",
54
+ name: config.title,
55
+ url: config.siteUrl,
56
+ description,
57
+ };
58
+
59
+ const breadcrumbs = currentPath === "/" ? null : {
60
+ "@context": "https://schema.org",
61
+ "@type": "BreadcrumbList",
62
+ itemListElement: currentPath
63
+ .split("/")
64
+ .filter(Boolean)
65
+ .map((segment, i, arr) => ({
66
+ "@type": "ListItem",
67
+ position: i + 1,
68
+ name: segment.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
69
+ item: new URL(arr.slice(0, i + 1).join("/"), config.siteUrl).href,
70
+ })),
71
+ };
72
+
73
+ const safeJsonLd = JSON.stringify(jsonLd).replace(/</g, "\\u003c");
74
+ const safeBreadcrumbs = breadcrumbs ? JSON.stringify(breadcrumbs).replace(/</g, "\\u003c") : null;
75
+ ---
76
+
77
+ <!-- Primary -->
78
+ <title>{fullTitle}</title>
79
+ <meta name="description" content={description} />
80
+ {config.author && <meta name="author" content={config.author} />}
81
+ <meta name="robots" content={noindex ? "noindex, nofollow" : "index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"} />
82
+ <link rel="canonical" href={canonicalUrl} />
83
+
84
+ <!-- Favicon -->
85
+ {config.faviconSrc && <link rel="icon" href={config.faviconSrc} />}
86
+
87
+ <!-- Open Graph -->
88
+ <meta property="og:type" content={type === "article" ? "article" : "website"} />
89
+ <meta property="og:url" content={canonicalUrl} />
90
+ <meta property="og:title" content={fullTitle} />
91
+ <meta property="og:description" content={description} />
92
+ <meta property="og:site_name" content={config.title} />
93
+ <meta property="og:locale" content="en_US" />
94
+ {ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
95
+ {ogImageUrl && <meta property="og:image:width" content="1200" />}
96
+ {ogImageUrl && <meta property="og:image:height" content="630" />}
97
+ {ogImageUrl && <meta property="og:image:alt" content={title} />}
98
+
99
+ <!-- Twitter -->
100
+ <meta name="twitter:card" content={ogImageUrl ? "summary_large_image" : "summary"} />
101
+ <meta name="twitter:title" content={fullTitle} />
102
+ <meta name="twitter:description" content={description} />
103
+ {ogImageUrl && <meta name="twitter:image" content={ogImageUrl} />}
104
+ {config.twitterHandle && <meta name="twitter:site" content={config.twitterHandle} />}
105
+ {config.twitterHandle && <meta name="twitter:creator" content={config.twitterHandle} />}
106
+
107
+ <!-- Article -->
108
+ {type === "article" && publishedDate && <meta property="article:published_time" content={publishedDate} />}
109
+ {type === "article" && modifiedDate && <meta property="article:modified_time" content={modifiedDate} />}
110
+ {type === "article" && config.author && <meta property="article:author" content={config.author} />}
111
+
112
+ <!-- Theme -->
113
+ <meta name="theme-color" content="#000000" />
114
+ <meta name="color-scheme" content="dark" />
115
+
116
+ <!-- JSON-LD: Page -->
117
+ <script type="application/ld+json" set:html={safeJsonLd} />
118
+
119
+ <!-- JSON-LD: Breadcrumbs -->
120
+ {safeBreadcrumbs && <script type="application/ld+json" set:html={safeBreadcrumbs} />}
@@ -0,0 +1,278 @@
1
+ ---
2
+ // Search dialog. Uses Pagefind (loaded dynamically from /_pagefind/).
3
+ // The consumer must run `npx pagefind --site <dist>` after building.
4
+ // Trigger buttons live in Navbar.astro; this component renders only the dialog.
5
+ ---
6
+ <dialog id="search-dialog">
7
+ <div class="search-dialog-panel">
8
+ <div class="search-dialog-header">
9
+ <svg class="search-dialog-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
10
+ <circle cx="11" cy="11" r="7" />
11
+ <line x1="16.5" y1="16.5" x2="21" y2="21" />
12
+ </svg>
13
+ <input
14
+ id="search-input"
15
+ type="text"
16
+ placeholder="Search documentation..."
17
+ autocomplete="off"
18
+ class="search-dialog-input"
19
+ />
20
+ <kbd class="search-dialog-kbd">esc</kbd>
21
+ </div>
22
+ <div id="search-results" class="search-dialog-results">
23
+ <p class="search-dialog-placeholder">Type to search...</p>
24
+ </div>
25
+ </div>
26
+ </dialog>
27
+
28
+ <style>
29
+ #search-dialog {
30
+ position: fixed;
31
+ inset: 0;
32
+ z-index: 100;
33
+ margin: 0;
34
+ padding: 0;
35
+ height: 100dvh;
36
+ width: 100dvw;
37
+ max-height: 100dvh;
38
+ max-width: 100dvw;
39
+ background: transparent;
40
+ border: none;
41
+ display: none;
42
+ }
43
+
44
+ #search-dialog[open] {
45
+ display: flex;
46
+ align-items: flex-start;
47
+ justify-content: center;
48
+ padding-top: 12vh;
49
+ }
50
+
51
+ #search-dialog::backdrop {
52
+ background: rgb(0 0 0 / 0.6);
53
+ backdrop-filter: blur(8px);
54
+ }
55
+
56
+ .search-dialog-panel {
57
+ width: 100%;
58
+ max-width: 36rem;
59
+ margin-inline: 1rem;
60
+ border-radius: 0.75rem;
61
+ border: 1px solid rgb(255 255 255 / 0.1);
62
+ background: #0a0a0a;
63
+ box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
64
+ overflow: hidden;
65
+ }
66
+
67
+ .search-dialog-header {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 0.75rem;
71
+ padding: 0.75rem 1rem;
72
+ border-bottom: 1px solid rgb(255 255 255 / 0.06);
73
+ }
74
+
75
+ .search-dialog-icon {
76
+ width: 0.875rem;
77
+ height: 0.875rem;
78
+ color: rgb(255 255 255 / 0.3);
79
+ flex-shrink: 0;
80
+ }
81
+
82
+ .search-dialog-input {
83
+ flex: 1;
84
+ background: transparent;
85
+ font-size: 0.875rem;
86
+ color: white;
87
+ outline: none;
88
+ }
89
+
90
+ .search-dialog-input::placeholder {
91
+ color: rgb(255 255 255 / 0.3);
92
+ }
93
+
94
+ .search-dialog-kbd {
95
+ border-radius: 0.25rem;
96
+ border: 1px solid rgb(255 255 255 / 0.1);
97
+ background: rgb(255 255 255 / 0.04);
98
+ padding: 0.125rem 0.375rem;
99
+ font-size: 10px;
100
+ font-family: ui-monospace, monospace;
101
+ color: rgb(255 255 255 / 0.3);
102
+ }
103
+
104
+ .search-dialog-results {
105
+ max-height: 50vh;
106
+ overflow-y: auto;
107
+ padding: 0.5rem;
108
+ }
109
+
110
+ .search-dialog-placeholder {
111
+ padding: 1.5rem 0.75rem;
112
+ text-align: center;
113
+ font-size: 0.875rem;
114
+ color: rgb(255 255 255 / 0.3);
115
+ }
116
+
117
+ #search-results :global(mark) {
118
+ background: transparent;
119
+ color: var(--color-accent);
120
+ font-weight: 600;
121
+ }
122
+ </style>
123
+
124
+ <script>
125
+ type PagefindSubResult = {
126
+ title: string;
127
+ url: string;
128
+ excerpt: string;
129
+ };
130
+
131
+ type PagefindResult = {
132
+ url: string;
133
+ excerpt: string;
134
+ meta?: { title?: string };
135
+ sub_results?: PagefindSubResult[];
136
+ };
137
+
138
+ type PagefindAPI = {
139
+ init: () => Promise<void>;
140
+ search: (query: string) => Promise<{ results: Array<{ data: () => Promise<PagefindResult> }> }>;
141
+ };
142
+
143
+ let pagefind: PagefindAPI | null = null;
144
+
145
+ async function loadPagefind(): Promise<PagefindAPI | null> {
146
+ if (pagefind) return pagefind;
147
+ try {
148
+ const path = "/_pagefind/pagefind.js";
149
+ pagefind = await import(/* @vite-ignore */ path) as PagefindAPI;
150
+ await pagefind.init();
151
+ return pagefind;
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+
157
+ type FlatResult = { title: string; section?: string; url: string; excerpt: string };
158
+
159
+ function flattenResults(items: PagefindResult[]): FlatResult[] {
160
+ const flat: FlatResult[] = [];
161
+ for (const item of items) {
162
+ const pageTitle = item.meta?.title ?? item.url;
163
+ if (item.sub_results && item.sub_results.length > 0) {
164
+ for (const sub of item.sub_results) {
165
+ flat.push({
166
+ title: sub.title || pageTitle,
167
+ section: sub.title && sub.title !== pageTitle ? pageTitle : undefined,
168
+ url: sub.url,
169
+ excerpt: sub.excerpt,
170
+ });
171
+ }
172
+ } else {
173
+ flat.push({ title: pageTitle, url: item.url, excerpt: item.excerpt });
174
+ }
175
+ }
176
+ return flat.slice(0, 10);
177
+ }
178
+
179
+ function renderResults(items: PagefindResult[], query: string) {
180
+ const container = document.getElementById("search-results");
181
+ if (!container) return;
182
+
183
+ const placeholderStyle = "padding: 1.5rem 0.75rem; text-align: center; font-size: 0.875rem; color: rgb(255 255 255 / 0.3);";
184
+
185
+ if (!query.trim()) {
186
+ container.innerHTML = `<p style="${placeholderStyle}">Type to search...</p>`;
187
+ return;
188
+ }
189
+
190
+ const flat = flattenResults(items);
191
+
192
+ if (flat.length === 0) {
193
+ container.innerHTML = `<p style="${placeholderStyle}">No results for &ldquo;${query.replace(/</g, "&lt;")}&rdquo;</p>`;
194
+ return;
195
+ }
196
+
197
+ container.innerHTML = flat
198
+ .map(
199
+ (r) => `
200
+ <a href="${r.url}" style="display: block; border-radius: 0.5rem; padding: 0.625rem 0.75rem; transition: background-color 150ms; text-decoration: none;" onmouseenter="this.style.background='rgb(255 255 255 / 0.04)'" onmouseleave="this.style.background='transparent'" data-search-result>
201
+ <div style="display: flex; align-items: baseline; gap: 0.5rem; margin-bottom: 0.125rem;">
202
+ <span style="font-size: 0.875rem; font-weight: 500; color: rgb(255 255 255 / 0.8);">${r.title}</span>
203
+ ${r.section ? `<span style="font-size: 11px; color: rgb(255 255 255 / 0.25);">${r.section}</span>` : ""}
204
+ </div>
205
+ <div style="font-size: 0.75rem; color: rgb(255 255 255 / 0.4); line-height: 1.625; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;">${r.excerpt}</div>
206
+ </a>`,
207
+ )
208
+ .join("");
209
+ }
210
+
211
+ let debounceTimer: ReturnType<typeof setTimeout>;
212
+
213
+ async function handleSearch(query: string) {
214
+ const pf = await loadPagefind();
215
+ if (!pf) {
216
+ const container = document.getElementById("search-results");
217
+ if (container) {
218
+ container.innerHTML = '<p style="padding: 1.5rem 0.75rem; text-align: center; font-size: 0.875rem; color: rgb(255 255 255 / 0.3);">Search not available. Run <code style="font-family: ui-monospace, monospace; color: rgb(255 255 255 / 0.5);">npx pagefind</code> after building.</p>';
219
+ }
220
+ return;
221
+ }
222
+
223
+ if (!query.trim()) {
224
+ renderResults([], query);
225
+ return;
226
+ }
227
+
228
+ const { results } = await pf.search(query);
229
+ const items = await Promise.all(results.slice(0, 6).map((r) => r.data()));
230
+ renderResults(items, query);
231
+ }
232
+
233
+ function initSearch() {
234
+ const triggers = [
235
+ document.getElementById("search-trigger"),
236
+ document.getElementById("search-trigger-mobile"),
237
+ ];
238
+ const dialog = document.getElementById("search-dialog") as HTMLDialogElement | null;
239
+ const input = document.getElementById("search-input") as HTMLInputElement | null;
240
+
241
+ function openSearch() {
242
+ dialog?.showModal();
243
+ input?.focus();
244
+ }
245
+
246
+ for (const trigger of triggers) {
247
+ trigger?.addEventListener("click", openSearch);
248
+ }
249
+
250
+ dialog?.addEventListener("click", (e) => {
251
+ if (e.target === dialog) dialog.close();
252
+ });
253
+
254
+ input?.addEventListener("input", () => {
255
+ clearTimeout(debounceTimer);
256
+ debounceTimer = setTimeout(() => handleSearch(input.value), 150);
257
+ });
258
+
259
+ document.getElementById("search-results")?.addEventListener("click", (e) => {
260
+ const link = (e.target as HTMLElement).closest("[data-search-result]");
261
+ if (link) dialog?.close();
262
+ });
263
+
264
+ document.addEventListener("keydown", (e) => {
265
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
266
+ e.preventDefault();
267
+ if (dialog?.open) {
268
+ dialog.close();
269
+ } else {
270
+ openSearch();
271
+ }
272
+ }
273
+ });
274
+ }
275
+
276
+ initSearch();
277
+ document.addEventListener("astro:after-swap", initSearch);
278
+ </script>
@@ -0,0 +1,11 @@
1
+ ---
2
+ type Props = {
3
+ class?: string;
4
+ };
5
+
6
+ const { class: className } = Astro.props;
7
+ ---
8
+
9
+ <h2 class:list={["text-2xl md:text-3xl font-semibold tracking-tight text-white mb-6", className]}>
10
+ <slot />
11
+ </h2>
@@ -0,0 +1,19 @@
1
+ ---
2
+ import type { SidebarSection as SidebarSectionType } from "../types/config";
3
+ import SidebarSection from "./SidebarSection.astro";
4
+
5
+ type Props = {
6
+ sections: Array<SidebarSectionType>;
7
+ currentPath: string;
8
+ };
9
+
10
+ const { sections, currentPath } = Astro.props;
11
+ ---
12
+
13
+ <aside class="w-64 shrink-0 border-r border-white/10 pt-8 pb-10 pl-6 pr-4 overflow-y-auto hidden md:block sticky top-16 h-[calc(100dvh-4rem)]">
14
+ <nav>
15
+ {sections.map((section) => (
16
+ <SidebarSection section={section} currentPath={currentPath} />
17
+ ))}
18
+ </nav>
19
+ </aside>
@@ -0,0 +1,39 @@
1
+ ---
2
+ import type { SidebarSection } from "../types/config";
3
+
4
+ type Props = {
5
+ section: SidebarSection;
6
+ currentPath: string;
7
+ };
8
+
9
+ const { section, currentPath } = Astro.props;
10
+ ---
11
+
12
+ <details class="mb-6 group" open>
13
+ <summary class="flex items-center justify-between w-full py-1.5 text-xs font-semibold uppercase tracking-widest text-white/30 hover:text-white/50 transition-colors cursor-pointer list-none [&::-webkit-details-marker]:hidden">
14
+ {section.title}
15
+ <svg
16
+ class="w-3 h-3 transition-transform duration-150 group-open:rotate-180"
17
+ fill="none"
18
+ stroke="currentColor"
19
+ viewBox="0 0 24 24"
20
+ >
21
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
22
+ </svg>
23
+ </summary>
24
+ <div class="flex flex-col mt-2">
25
+ {section.links.map((link) => (
26
+ <a
27
+ href={link.href}
28
+ class:list={[
29
+ "block py-1.5 pl-3 text-sm border-l-2 transition-colors",
30
+ currentPath === link.href
31
+ ? "border-accent text-white font-medium"
32
+ : "border-transparent text-white/50 hover:text-white/80 hover:border-white/20",
33
+ ]}
34
+ >
35
+ {link.label}
36
+ </a>
37
+ ))}
38
+ </div>
39
+ </details>
@@ -0,0 +1,60 @@
1
+ ---
2
+ // TOC is built client-side from [data-toc-label] elements in the DOM.
3
+ // No props needed.
4
+ ---
5
+
6
+ <nav id="toc" class="w-44 shrink-0 hidden xl:block sticky top-16 h-[calc(100dvh-4rem)] pt-8 pr-4 overflow-y-auto">
7
+ <div class="text-xs font-semibold uppercase tracking-widest text-white/30 mb-3">On this page</div>
8
+ <ul id="toc-list" class="flex flex-col gap-0.5"></ul>
9
+ </nav>
10
+
11
+ <script>
12
+ const ACTIVE_CLASS =
13
+ "block py-1 text-sm border-l-2 pl-3 transition-colors border-accent text-white font-medium";
14
+ const INACTIVE_CLASS =
15
+ "block py-1 text-sm border-l-2 pl-3 transition-colors border-transparent text-white/40 hover:text-white/60 hover:border-white/20";
16
+
17
+ function initToc() {
18
+ const tocList = document.getElementById("toc-list");
19
+ const sections = document.querySelectorAll("[data-toc-label]");
20
+ if (!tocList || sections.length === 0) return;
21
+
22
+ tocList.innerHTML = "";
23
+ const links: HTMLAnchorElement[] = [];
24
+
25
+ sections.forEach((section) => {
26
+ const label = section.getAttribute("data-toc-label");
27
+ const id = section.id;
28
+ if (!label || !id) return;
29
+
30
+ const li = document.createElement("li");
31
+ const a = document.createElement("a");
32
+ a.href = `#${id}`;
33
+ a.textContent = label;
34
+ a.className = INACTIVE_CLASS;
35
+ li.appendChild(a);
36
+ tocList.appendChild(li);
37
+ links.push(a);
38
+ });
39
+
40
+ const observer = new IntersectionObserver(
41
+ (entries) => {
42
+ for (const entry of entries) {
43
+ if (!entry.isIntersecting) continue;
44
+ const id = entry.target.id;
45
+ for (const link of links) {
46
+ const isActive = link.getAttribute("href") === `#${id}`;
47
+ link.className = isActive ? ACTIVE_CLASS : INACTIVE_CLASS;
48
+ if (isActive) link.scrollIntoView({ block: "nearest", behavior: "smooth" });
49
+ }
50
+ }
51
+ },
52
+ { rootMargin: "-80px 0px -60% 0px", threshold: 0 },
53
+ );
54
+
55
+ sections.forEach((section) => observer.observe(section));
56
+ }
57
+
58
+ initToc();
59
+ document.addEventListener("astro:after-swap", initToc);
60
+ </script>
@@ -0,0 +1,9 @@
1
+ ---
2
+ type Props = { class?: string };
3
+ const { class: className = "w-3.5 h-3.5" } = Astro.props;
4
+ ---
5
+
6
+ <svg class={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7
+ <path d="M7.86 2h8.28L22 7.86v8.28L16.14 22H7.86L2 16.14V7.86L7.86 2z" />
8
+ <path d="M15 9l-6 6M9 9l6 6" />
9
+ </svg>
@@ -0,0 +1,9 @@
1
+ ---
2
+ type Props = { class?: string };
3
+ const { class: className = "w-3.5 h-3.5" } = Astro.props;
4
+ ---
5
+
6
+ <svg class={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7
+ <circle cx="12" cy="12" r="10" />
8
+ <path d="M12 16v-4M12 8h.01" />
9
+ </svg>
@@ -0,0 +1,8 @@
1
+ ---
2
+ type Props = { class?: string };
3
+ const { class: className = "w-3.5 h-3.5" } = Astro.props;
4
+ ---
5
+
6
+ <svg class={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7
+ <path d="M9 18h6M10 22h4M12 2a7 7 0 014.9 12H7.1A7 7 0 0112 2z" />
8
+ </svg>
@@ -0,0 +1,9 @@
1
+ ---
2
+ type Props = { class?: string };
3
+ const { class: className = "w-3.5 h-3.5" } = Astro.props;
4
+ ---
5
+
6
+ <svg class={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7
+ <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
8
+ <path d="M12 9v4M12 17h.01" />
9
+ </svg>
@@ -0,0 +1,78 @@
1
+ @theme {
2
+ /* Core Colors - docs-ui-kit brand palette */
3
+ --color-primary: #42170c;
4
+ --color-secondary: #6b7280;
5
+ --color-accent: #ef4723;
6
+ --color-accent-hover: #d63d1f;
7
+ --color-surface: #f9fafb;
8
+ --color-border: #e5e7eb;
9
+ --color-muted: #848d9f;
10
+
11
+ /* Typography */
12
+ --font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
13
+ --font-family-mono: "JetBrains Mono", "Fira Code", "Monaco", "Cascadia Code", monospace;
14
+ }
15
+
16
+ /* Base Styles */
17
+ * {
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ html {
22
+ scroll-behavior: smooth;
23
+ font-size: 16px;
24
+ font-family: var(--font-family);
25
+ scroll-padding-top: 100px;
26
+ }
27
+
28
+ body,
29
+ html {
30
+ margin: 0;
31
+ padding: 0;
32
+ }
33
+
34
+ body {
35
+ background-color: black;
36
+ color: white;
37
+ min-height: 100dvh;
38
+ }
39
+
40
+ @media (max-width: 768px) {
41
+ .prose table {
42
+ display: block;
43
+ width: 100%;
44
+ overflow-x: auto;
45
+ }
46
+
47
+ .prose table thead,
48
+ .prose table tbody {
49
+ width: 100%;
50
+ }
51
+ }
52
+
53
+ /* Liquid Glass Border Effect */
54
+ .liquid-glass-border {
55
+ position: relative;
56
+ background-clip: padding-box;
57
+ }
58
+
59
+ .liquid-glass-border::before {
60
+ content: "";
61
+ position: absolute;
62
+ inset: 0;
63
+ border-radius: 0.5rem;
64
+ padding: 0.5px;
65
+ background: linear-gradient(
66
+ 135deg,
67
+ rgba(255, 255, 255, 0.4) 0%,
68
+ rgba(255, 255, 255, 0.15) 25%,
69
+ rgba(255, 255, 255, 0.05) 50%,
70
+ rgba(255, 255, 255, 0.15) 75%,
71
+ rgba(255, 255, 255, 0.4) 100%
72
+ );
73
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
74
+ -webkit-mask-composite: xor;
75
+ mask-composite: exclude;
76
+ pointer-events: none;
77
+ z-index: 1;
78
+ }
@@ -0,0 +1,32 @@
1
+ export type NavLink = {
2
+ href: string;
3
+ label: string;
4
+ };
5
+
6
+ export type SocialLinks = {
7
+ github: string;
8
+ linkedin?: string;
9
+ };
10
+
11
+ export type SidebarSection = {
12
+ title: string;
13
+ links: Array<NavLink>;
14
+ };
15
+
16
+ export type SiteConfig = {
17
+ title: string;
18
+ description: string;
19
+ siteUrl: string;
20
+ installCommand: string;
21
+ logoSrc?: string;
22
+ logoAlt?: string;
23
+ ogImage?: string;
24
+ faviconSrc?: string;
25
+ githubUrl: string;
26
+ author?: string;
27
+ twitterHandle?: string;
28
+ socialLinks: SocialLinks;
29
+ navLinks: Array<NavLink>;
30
+ sidebarSections: Array<SidebarSection>;
31
+ copyright?: string;
32
+ };
@@ -0,0 +1,7 @@
1
+ export type PropsTableRow = {
2
+ name: string;
3
+ type: string;
4
+ defaultValue?: string;
5
+ description: string;
6
+ required?: boolean;
7
+ };