@tayacrystals/lore 0.1.2 → 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.
Files changed (49) hide show
  1. package/README.md +7 -0
  2. package/package.json +48 -36
  3. package/src/build.ts +148 -0
  4. package/src/config.ts +26 -0
  5. package/src/dev.ts +112 -0
  6. package/src/files.ts +193 -0
  7. package/src/i18n.ts +49 -0
  8. package/src/icons.ts +59 -0
  9. package/src/index.ts +28 -0
  10. package/src/mdx.ts +281 -0
  11. package/src/parse.ts +53 -0
  12. package/src/routing.ts +46 -0
  13. package/src/serve.ts +72 -0
  14. package/src/template.ts +747 -0
  15. package/src/types.ts +51 -0
  16. package/src/version.ts +33 -0
  17. package/components/docs/Breadcrumbs.astro +0 -41
  18. package/components/docs/PrevNext.astro +0 -50
  19. package/components/docs/Sidebar.astro +0 -28
  20. package/components/docs/SidebarGroup.astro +0 -55
  21. package/components/docs/SidebarItem.astro +0 -26
  22. package/components/docs/TableOfContents.astro +0 -82
  23. package/components/global/SearchModal.astro +0 -159
  24. package/components/mdx/Accordion.astro +0 -20
  25. package/components/mdx/Callout.astro +0 -53
  26. package/components/mdx/Card.astro +0 -26
  27. package/components/mdx/CardGrid.astro +0 -16
  28. package/components/mdx/CodeTabs.astro +0 -129
  29. package/components/mdx/FileTree.astro +0 -117
  30. package/components/mdx/Step.astro +0 -18
  31. package/components/mdx/Steps.astro +0 -6
  32. package/components/mdx/Tab.astro +0 -11
  33. package/components/mdx/Tabs.astro +0 -73
  34. package/components.ts +0 -11
  35. package/config.ts +0 -42
  36. package/index.ts +0 -2
  37. package/integration.ts +0 -68
  38. package/layouts/DocsLayout.astro +0 -277
  39. package/loaders.ts +0 -5
  40. package/routes/docs.astro +0 -201
  41. package/schema.ts +0 -13
  42. package/styles/global.css +0 -78
  43. package/styles/prose.css +0 -148
  44. package/utils/navigation.ts +0 -32
  45. package/utils/rehype-file-tree.ts +0 -229
  46. package/utils/sidebar.ts +0 -97
  47. package/utils/toc.ts +0 -28
  48. package/virtual.d.ts +0 -9
  49. package/vite-plugin.ts +0 -28
package/src/types.ts ADDED
@@ -0,0 +1,51 @@
1
+ export interface Config {
2
+ title?: string;
3
+ description?: string;
4
+ color?: string;
5
+ links?: (string | { url: string; icon: string })[];
6
+ plugins?: string[];
7
+ versioning?: boolean;
8
+ internationalization?: boolean;
9
+ defaultVersion?: string;
10
+ defaultLocale?: string;
11
+ }
12
+
13
+ export interface VersionInfo {
14
+ name: string;
15
+ label?: string;
16
+ }
17
+
18
+ export interface LocaleInfo {
19
+ code: string;
20
+ label?: string;
21
+ }
22
+
23
+ export interface PageContext {
24
+ version?: string;
25
+ locale?: string;
26
+ translationOf?: string;
27
+ }
28
+
29
+ export interface PageInfo {
30
+ filePath: string;
31
+ url: string;
32
+ title: string;
33
+ description?: string;
34
+ content: string;
35
+ context: PageContext;
36
+ }
37
+
38
+ export interface SidebarPage {
39
+ type: "page";
40
+ title: string;
41
+ url: string;
42
+ }
43
+
44
+ export interface SidebarSection {
45
+ type: "section";
46
+ title: string;
47
+ url?: string; // set if the section has an index.mdx
48
+ items: SidebarItem[];
49
+ }
50
+
51
+ export type SidebarItem = SidebarPage | SidebarSection;
package/src/version.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { VersionInfo } from "./types.ts";
4
+ import type { Config } from "./types.ts";
5
+
6
+ export async function detectVersions(docsDir: string): Promise<VersionInfo[]> {
7
+ const entries = await readdir(docsDir);
8
+ const versions: VersionInfo[] = [];
9
+
10
+ for (const entry of entries) {
11
+ const fullPath = path.join(docsDir, entry);
12
+ const s = await stat(fullPath);
13
+
14
+ if (s.isDirectory() && entry.startsWith("v")) {
15
+ versions.push({ name: entry });
16
+ }
17
+ }
18
+
19
+ return versions.sort((a, b) => {
20
+ const numA = parseInt(a.name.slice(1), 10);
21
+ const numB = parseInt(b.name.slice(1), 10);
22
+ const numAIsNaN = isNaN(numA);
23
+ const numBIsNaN = isNaN(numB);
24
+ if (numAIsNaN && numBIsNaN) return a.name.localeCompare(b.name);
25
+ if (numAIsNaN) return 1;
26
+ if (numBIsNaN) return -1;
27
+ return numA - numB;
28
+ });
29
+ }
30
+
31
+ export function getDefaultVersion(config: Config): string {
32
+ return config.defaultVersion ?? "latest";
33
+ }
@@ -1,41 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
-
4
- const BASE_URL = import.meta.env.BASE_URL || "/";
5
- const path = Astro.url.pathname.replace(BASE_URL.replace(/\/$/, ""), "").replace(/\/$/, "");
6
- const segments = path.split("/").filter(Boolean);
7
-
8
- function titleCase(s: string) {
9
- return s
10
- .split("-")
11
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
12
- .join(" ");
13
- }
14
-
15
- const crumbs = segments.map((seg, i) => ({
16
- label: titleCase(seg),
17
- href: BASE_URL + segments.slice(0, i + 1).join("/"),
18
- current: i === segments.length - 1,
19
- }));
20
- ---
21
-
22
- {
23
- crumbs.length > 1 && (
24
- <nav class="flex items-center gap-1 text-[13px] text-fd-muted-foreground mb-6" aria-label="Breadcrumb">
25
- {crumbs.map((crumb, i) => (
26
- <>
27
- {i > 0 && (
28
- <Icon name="lucide:chevron-right" class="w-3 h-3 opacity-50" />
29
- )}
30
- {crumb.current ? (
31
- <span class="text-fd-foreground">{crumb.label}</span>
32
- ) : (
33
- <a href={crumb.href} class="hover:text-fd-foreground transition-colors">
34
- {crumb.label}
35
- </a>
36
- )}
37
- </>
38
- ))}
39
- </nav>
40
- )
41
- }
@@ -1,50 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
- import type { FlatNavItem } from "../../utils/navigation";
4
-
5
- interface Props {
6
- prev: FlatNavItem | null;
7
- next: FlatNavItem | null;
8
- }
9
-
10
- const { prev, next } = Astro.props;
11
- ---
12
-
13
- {
14
- (prev || next) && (
15
- <div class="flex flex-col sm:flex-row justify-between gap-3 mt-12 pt-6 border-t border-fd-border">
16
- {prev ? (
17
- <a
18
- href={prev.href}
19
- class="group flex items-center gap-3 px-4 py-3 rounded-lg border border-fd-border hover:bg-fd-muted/50 transition-colors flex-1"
20
- >
21
- <Icon name="lucide:chevron-left" class="w-4 h-4 text-fd-muted-foreground shrink-0" />
22
- <div class="min-w-0">
23
- <span class="text-xs text-fd-muted-foreground block">Previous</span>
24
- <span class="text-sm font-medium text-fd-foreground group-hover:text-fd-primary transition-colors truncate block">
25
- {prev.label}
26
- </span>
27
- </div>
28
- </a>
29
- ) : (
30
- <div class="flex-1" />
31
- )}
32
- {next ? (
33
- <a
34
- href={next.href}
35
- class="group flex items-center justify-end gap-3 px-4 py-3 rounded-lg border border-fd-border hover:bg-fd-muted/50 transition-colors flex-1"
36
- >
37
- <div class="min-w-0 text-right">
38
- <span class="text-xs text-fd-muted-foreground block">Next</span>
39
- <span class="text-sm font-medium text-fd-foreground group-hover:text-fd-primary transition-colors truncate block">
40
- {next.label}
41
- </span>
42
- </div>
43
- <Icon name="lucide:chevron-right" class="w-4 h-4 text-fd-muted-foreground shrink-0" />
44
- </a>
45
- ) : (
46
- <div class="flex-1" />
47
- )}
48
- </div>
49
- )
50
- }
@@ -1,28 +0,0 @@
1
- ---
2
- import SidebarItem from "./SidebarItem.astro";
3
- import SidebarGroup from "./SidebarGroup.astro";
4
- import type { SidebarEntry } from "../../utils/sidebar";
5
-
6
- interface Props {
7
- entries: SidebarEntry[];
8
- currentPath: string;
9
- }
10
-
11
- const { entries, currentPath } = Astro.props;
12
- const BASE_URL = import.meta.env.BASE_URL || "/";
13
- const docsHref = `${BASE_URL}docs`;
14
- ---
15
-
16
- <div class="space-y-1">
17
- <SidebarItem label="Overview" href={docsHref} active={currentPath === docsHref} />
18
- {
19
- entries.map((entry) => {
20
- if (entry.type === "link") {
21
- return <SidebarItem label={entry.label} href={entry.href} active={entry.href === currentPath} icon={entry.icon} />;
22
- }
23
- return (
24
- <SidebarGroup label={entry.label} items={entry.items} currentPath={currentPath} depth={0} />
25
- );
26
- })
27
- }
28
- </div>
@@ -1,55 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
- import SidebarItem from "./SidebarItem.astro";
4
- import type { SidebarEntry } from "../../utils/sidebar";
5
-
6
- interface Props {
7
- label: string;
8
- items: SidebarEntry[];
9
- currentPath: string;
10
- depth?: number;
11
- }
12
-
13
- const { label, items, currentPath, depth = 0 } = Astro.props;
14
-
15
- function hasActiveDescendant(entries: SidebarEntry[], path: string): boolean {
16
- for (const entry of entries) {
17
- if (entry.type === "link" && entry.href === path) return true;
18
- if (entry.type === "group" && hasActiveDescendant(entry.items, path)) return true;
19
- }
20
- return false;
21
- }
22
-
23
- const isOpen = hasActiveDescendant(items, currentPath);
24
- ---
25
-
26
- {depth === 0 ? (
27
- <div class="mt-4 first:mt-0">
28
- <p class="px-2 mb-1 text-xs font-semibold text-fd-foreground/80 tracking-wide">{label}</p>
29
- <div class="space-y-0.5">
30
- {items.map((item) =>
31
- item.type === "link" ? (
32
- <SidebarItem label={item.label} href={item.href} active={item.href === currentPath} icon={item.icon} />
33
- ) : (
34
- <Astro.self label={item.label} items={item.items} currentPath={currentPath} depth={depth + 1} />
35
- )
36
- )}
37
- </div>
38
- </div>
39
- ) : (
40
- <details class="group/sub" open={isOpen}>
41
- <summary class="flex items-center gap-1 px-2 py-1.5 text-[13px] font-medium text-fd-muted-foreground hover:text-fd-foreground cursor-pointer select-none list-none [&::-webkit-details-marker]:hidden">
42
- <Icon name="lucide:chevron-right" class="w-3 h-3 shrink-0 transition-transform group-open/sub:rotate-90" />
43
- {label}
44
- </summary>
45
- <div class="ml-3 pl-2 border-l border-fd-border/50 space-y-0.5 mt-0.5">
46
- {items.map((item) =>
47
- item.type === "link" ? (
48
- <SidebarItem label={item.label} href={item.href} active={item.href === currentPath} icon={item.icon} />
49
- ) : (
50
- <Astro.self label={item.label} items={item.items} currentPath={currentPath} depth={depth + 1} />
51
- )
52
- )}
53
- </div>
54
- </details>
55
- )}
@@ -1,26 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
-
4
- interface Props {
5
- label: string;
6
- href: string;
7
- active: boolean;
8
- icon?: string;
9
- }
10
-
11
- const { label, href, active, icon } = Astro.props;
12
- ---
13
-
14
- <a
15
- href={href}
16
- class:list={[
17
- "flex items-center gap-2 px-2 py-1.5 text-[13px] rounded-md transition-colors",
18
- active
19
- ? "bg-fd-primary/10 text-fd-primary font-medium"
20
- : "text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-muted/60",
21
- ]}
22
- aria-current={active ? "page" : undefined}
23
- >
24
- {icon && <Icon name={icon} class="w-3.5 h-3.5 shrink-0 opacity-60" />}
25
- {label}
26
- </a>
@@ -1,82 +0,0 @@
1
- ---
2
- import type { TocItem } from "../../utils/toc";
3
-
4
- interface Props {
5
- items: TocItem[];
6
- }
7
-
8
- const { items } = Astro.props;
9
- ---
10
-
11
- <nav aria-label="Table of contents">
12
- <p class="text-xs font-semibold text-fd-foreground/80 mb-3 tracking-wide">On this page</p>
13
- <ul class="space-y-0.5 text-[13px] border-l border-fd-border" id="toc-list">
14
- {
15
- items.map((item) => (
16
- <li>
17
- <a
18
- href={`#${item.slug}`}
19
- class="toc-link block py-1 pl-3 -ml-px border-l border-transparent text-fd-muted-foreground hover:text-fd-foreground transition-colors"
20
- data-slug={item.slug}
21
- >
22
- {item.text}
23
- </a>
24
- {item.children.length > 0 && (
25
- <ul class="space-y-0.5">
26
- {item.children.map((child) => (
27
- <li>
28
- <a
29
- href={`#${child.slug}`}
30
- class="toc-link block py-1 pl-6 -ml-px border-l border-transparent text-fd-muted-foreground hover:text-fd-foreground transition-colors text-[12px]"
31
- data-slug={child.slug}
32
- >
33
- {child.text}
34
- </a>
35
- </li>
36
- ))}
37
- </ul>
38
- )}
39
- </li>
40
- ))
41
- }
42
- </ul>
43
- </nav>
44
-
45
- <script>
46
- const links = document.querySelectorAll<HTMLAnchorElement>(".toc-link");
47
- const headings: { id: string; el: Element }[] = [];
48
-
49
- links.forEach((link) => {
50
- const slug = link.dataset.slug;
51
- if (slug) {
52
- const el = document.getElementById(slug);
53
- if (el) headings.push({ id: slug, el });
54
- }
55
- });
56
-
57
- function setActive(slug: string) {
58
- links.forEach((link) => {
59
- if (link.dataset.slug === slug) {
60
- link.classList.add("text-fd-primary", "font-medium", "!border-fd-primary");
61
- link.classList.remove("text-fd-muted-foreground", "border-transparent");
62
- } else {
63
- link.classList.remove("text-fd-primary", "font-medium", "!border-fd-primary");
64
- link.classList.add("text-fd-muted-foreground", "border-transparent");
65
- }
66
- });
67
- }
68
-
69
- const observer = new IntersectionObserver(
70
- (entries) => {
71
- for (const entry of entries) {
72
- if (entry.isIntersecting) {
73
- setActive(entry.target.id);
74
- break;
75
- }
76
- }
77
- },
78
- { rootMargin: "-80px 0px -70% 0px" },
79
- );
80
-
81
- headings.forEach(({ el }) => observer.observe(el));
82
- </script>
@@ -1,159 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
- ---
4
-
5
- <dialog
6
- id="search-dialog"
7
- class="fixed inset-0 z-50 m-0 w-full h-full max-w-full max-h-full bg-transparent p-0 open:flex items-start justify-center pt-[15vh]"
8
- >
9
- <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" id="search-backdrop"></div>
10
- <div class="relative w-full max-w-xl mx-4 bg-fd-popover rounded-2xl shadow-2xl border border-fd-border overflow-hidden">
11
- <!-- Search header -->
12
- <div class="flex items-center gap-3 px-4 border-b border-fd-border">
13
- <Icon name="lucide:search" class="w-5 h-5 text-fd-muted-foreground shrink-0" />
14
- <input
15
- id="search-input"
16
- type="search"
17
- placeholder="Search documentation..."
18
- class="flex-1 py-4 bg-transparent text-fd-foreground placeholder:text-fd-muted-foreground outline-none text-sm"
19
- autocomplete="off"
20
- />
21
- <kbd class="hidden sm:inline-flex items-center rounded border border-fd-border bg-fd-muted px-1.5 py-0.5 text-[10px] font-mono text-fd-muted-foreground">
22
- ESC
23
- </kbd>
24
- </div>
25
-
26
- <!-- Search results -->
27
- <div id="search-results" class="max-h-80 overflow-y-auto p-2 scrollbar-thin">
28
- <div class="text-center py-8 text-sm text-fd-muted-foreground" id="search-empty">
29
- Start typing to search...
30
- </div>
31
- </div>
32
-
33
- <!-- Footer -->
34
- <div class="flex items-center gap-4 px-4 py-2.5 border-t border-fd-border text-xs text-fd-muted-foreground">
35
- <span class="flex items-center gap-1">
36
- <kbd class="inline-flex items-center rounded border border-fd-border bg-fd-muted px-1 py-0.5 font-mono">↵</kbd>
37
- to select
38
- </span>
39
- <span class="flex items-center gap-1">
40
- <kbd class="inline-flex items-center rounded border border-fd-border bg-fd-muted px-1 py-0.5 font-mono">↑↓</kbd>
41
- to navigate
42
- </span>
43
- <span class="flex items-center gap-1">
44
- <kbd class="inline-flex items-center rounded border border-fd-border bg-fd-muted px-1 py-0.5 font-mono">esc</kbd>
45
- to close
46
- </span>
47
- </div>
48
- </div>
49
- </dialog>
50
-
51
- <script>
52
- const dialog = document.getElementById("search-dialog") as HTMLDialogElement;
53
- const input = document.getElementById("search-input") as HTMLInputElement;
54
- const backdrop = document.getElementById("search-backdrop");
55
- const results = document.getElementById("search-results");
56
- const empty = document.getElementById("search-empty");
57
- let pagefind: any = null;
58
-
59
- async function loadPagefind() {
60
- if (pagefind) return pagefind;
61
- try {
62
- // pagefind = await import("/pagefind/pagefind.js");
63
- // await pagefind.init();
64
- } catch {
65
- // Pagefind not available in dev mode
66
- pagefind = null;
67
- }
68
- return pagefind;
69
- }
70
-
71
- function openSearch() {
72
- dialog?.showModal();
73
- input?.focus();
74
- loadPagefind();
75
- }
76
-
77
- function closeSearch() {
78
- dialog?.close();
79
- if (input) input.value = "";
80
- if (results && empty) {
81
- results.innerHTML = "";
82
- results.appendChild(empty);
83
- empty.textContent = "Start typing to search...";
84
- }
85
- }
86
-
87
- // Triggers
88
- document.getElementById("search-trigger")?.addEventListener("click", openSearch);
89
- document.getElementById("search-trigger-mobile")?.addEventListener("click", openSearch);
90
- backdrop?.addEventListener("click", closeSearch);
91
-
92
- // Keyboard shortcut
93
- document.addEventListener("keydown", (e) => {
94
- if ((e.metaKey || e.ctrlKey) && e.key === "k") {
95
- e.preventDefault();
96
- if (dialog?.open) closeSearch();
97
- else openSearch();
98
- }
99
- if (e.key === "Escape" && dialog?.open) {
100
- closeSearch();
101
- }
102
- });
103
-
104
- // Search input
105
- let debounce: ReturnType<typeof setTimeout>;
106
- input?.addEventListener("input", () => {
107
- clearTimeout(debounce);
108
- debounce = setTimeout(async () => {
109
- const query = input.value.trim();
110
- if (!query || !results) {
111
- if (results && empty) {
112
- results.innerHTML = "";
113
- results.appendChild(empty);
114
- empty.textContent = query ? "No results found." : "Start typing to search...";
115
- }
116
- return;
117
- }
118
-
119
- const pf = await loadPagefind();
120
- if (!pf) {
121
- if (results && empty) {
122
- results.innerHTML = "";
123
- results.appendChild(empty);
124
- empty.textContent = "Search is available after building the site.";
125
- }
126
- return;
127
- }
128
-
129
- const search = await pf.search(query);
130
- results.innerHTML = "";
131
-
132
- if (search.results.length === 0) {
133
- results.appendChild(empty!);
134
- empty!.textContent = "No results found.";
135
- return;
136
- }
137
-
138
- for (const result of search.results.slice(0, 8)) {
139
- const data = await result.data();
140
- const a = document.createElement("a");
141
- a.href = data.url;
142
- a.className =
143
- "block px-3 py-2.5 rounded-lg hover:bg-fd-muted transition-colors group";
144
- a.innerHTML = `
145
- <div class="font-medium text-sm text-fd-foreground group-hover:text-fd-primary transition-colors">${data.meta.title || "Untitled"}</div>
146
- <div class="text-xs text-fd-muted-foreground mt-0.5 line-clamp-1">${data.excerpt}</div>
147
- `;
148
- a.addEventListener("click", closeSearch);
149
- results.appendChild(a);
150
- }
151
- }, 200);
152
- });
153
- </script>
154
-
155
- <style>
156
- dialog::backdrop {
157
- background: transparent;
158
- }
159
- </style>
@@ -1,20 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
-
4
- interface Props {
5
- title: string;
6
- open?: boolean;
7
- }
8
-
9
- const { title, open = false } = Astro.props;
10
- ---
11
-
12
- <details class="my-4 rounded-lg border border-fd-border group" open={open}>
13
- <summary class="flex items-center justify-between cursor-pointer px-4 py-3 text-sm font-medium hover:bg-fd-muted/50 transition-colors rounded-lg select-none list-none [&::-webkit-details-marker]:hidden">
14
- <span>{title}</span>
15
- <Icon name="lucide:chevron-down" class="w-4 h-4 text-fd-muted-foreground transition-transform group-open:rotate-180" />
16
- </summary>
17
- <div class="px-4 pb-4 text-sm [&>p]:mb-2">
18
- <slot />
19
- </div>
20
- </details>
@@ -1,53 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
-
4
- interface Props {
5
- type?: "note" | "warning" | "tip" | "danger";
6
- title?: string;
7
- }
8
-
9
- const { type = "note", title } = Astro.props;
10
-
11
- const styles = {
12
- note: {
13
- bg: "bg-blue-500/5 dark:bg-blue-500/10",
14
- border: "border-blue-500/20",
15
- iconColor: "text-blue-500",
16
- icon: "lucide:info",
17
- },
18
- warning: {
19
- bg: "bg-amber-500/5 dark:bg-amber-500/10",
20
- border: "border-amber-500/20",
21
- iconColor: "text-amber-500",
22
- icon: "lucide:triangle-alert",
23
- },
24
- tip: {
25
- bg: "bg-emerald-500/5 dark:bg-emerald-500/10",
26
- border: "border-emerald-500/20",
27
- iconColor: "text-emerald-500",
28
- icon: "lucide:lightbulb",
29
- },
30
- danger: {
31
- bg: "bg-red-500/5 dark:bg-red-500/10",
32
- border: "border-red-500/20",
33
- iconColor: "text-red-500",
34
- icon: "lucide:octagon-alert",
35
- },
36
- };
37
-
38
- const c = styles[type];
39
- ---
40
-
41
- <div class:list={[
42
- "my-4 flex gap-3 rounded-lg border px-4 py-3",
43
- title ? "items-start" : "items-center",
44
- c.border, c.bg,
45
- ]}>
46
- <Icon name={c.icon} class={`w-5 h-5 shrink-0 ${c.iconColor}`} />
47
- <div class="min-w-0 flex-1">
48
- {title && <p class={`font-medium text-sm mb-1 ${c.iconColor}`}>{title}</p>}
49
- <div class="callout-content text-sm text-fd-foreground/90">
50
- <slot />
51
- </div>
52
- </div>
53
- </div>
@@ -1,26 +0,0 @@
1
- ---
2
- import { Icon } from "astro-icon/components";
3
-
4
- interface Props {
5
- title: string;
6
- href?: string;
7
- icon?: string;
8
- }
9
-
10
- const { title, href, icon } = Astro.props;
11
- const Tag = href ? "a" : "div";
12
- ---
13
-
14
- <Tag
15
- href={href}
16
- class:list={[
17
- "block rounded-lg border border-fd-border p-4 transition-colors",
18
- href && "hover:border-fd-primary/50 hover:bg-fd-muted/50",
19
- ]}
20
- >
21
- {icon && <Icon name={icon} class="w-5 h-5 mb-2 text-fd-primary" />}
22
- <h3 class="font-semibold text-sm mb-1">{title}</h3>
23
- <div class="text-sm text-fd-muted-foreground [&>p]:mb-0">
24
- <slot />
25
- </div>
26
- </Tag>
@@ -1,16 +0,0 @@
1
- ---
2
- interface Props {
3
- cols?: 2 | 3;
4
- }
5
-
6
- const { cols = 2 } = Astro.props;
7
- ---
8
-
9
- <div
10
- class:list={[
11
- "grid gap-4 my-5 not-prose",
12
- cols === 3 ? "sm:grid-cols-2 lg:grid-cols-3" : "sm:grid-cols-2",
13
- ]}
14
- >
15
- <slot />
16
- </div>