radiant-docs 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.
Files changed (61) hide show
  1. package/dist/index.js +312 -0
  2. package/package.json +38 -0
  3. package/template/.vscode/extensions.json +4 -0
  4. package/template/.vscode/launch.json +11 -0
  5. package/template/astro.config.mjs +216 -0
  6. package/template/ec.config.mjs +51 -0
  7. package/template/package-lock.json +12546 -0
  8. package/template/package.json +51 -0
  9. package/template/public/favicon.svg +9 -0
  10. package/template/src/assets/icons/check.svg +33 -0
  11. package/template/src/assets/icons/danger.svg +37 -0
  12. package/template/src/assets/icons/info.svg +36 -0
  13. package/template/src/assets/icons/lightbulb.svg +74 -0
  14. package/template/src/assets/icons/warning.svg +37 -0
  15. package/template/src/components/Header.astro +176 -0
  16. package/template/src/components/MdxPage.astro +49 -0
  17. package/template/src/components/OpenApiPage.astro +270 -0
  18. package/template/src/components/Search.astro +362 -0
  19. package/template/src/components/Sidebar.astro +19 -0
  20. package/template/src/components/SidebarDropdown.astro +149 -0
  21. package/template/src/components/SidebarGroup.astro +51 -0
  22. package/template/src/components/SidebarLink.astro +56 -0
  23. package/template/src/components/SidebarMenu.astro +46 -0
  24. package/template/src/components/SidebarSubgroup.astro +136 -0
  25. package/template/src/components/TableOfContents.astro +480 -0
  26. package/template/src/components/ThemeSwitcher.astro +84 -0
  27. package/template/src/components/endpoint/PlaygroundBar.astro +68 -0
  28. package/template/src/components/endpoint/PlaygroundButton.astro +44 -0
  29. package/template/src/components/endpoint/PlaygroundField.astro +54 -0
  30. package/template/src/components/endpoint/PlaygroundForm.astro +203 -0
  31. package/template/src/components/endpoint/RequestSnippets.astro +308 -0
  32. package/template/src/components/endpoint/ResponseDisplay.astro +177 -0
  33. package/template/src/components/endpoint/ResponseFields.astro +224 -0
  34. package/template/src/components/endpoint/ResponseSnippets.astro +247 -0
  35. package/template/src/components/sidebar/SidebarEndpointLink.astro +51 -0
  36. package/template/src/components/sidebar/SidebarOpenApi.astro +207 -0
  37. package/template/src/components/ui/Field.astro +69 -0
  38. package/template/src/components/ui/Tag.astro +5 -0
  39. package/template/src/components/ui/demo/CodeDemo.astro +15 -0
  40. package/template/src/components/ui/demo/Demo.astro +3 -0
  41. package/template/src/components/ui/demo/UiDisplay.astro +13 -0
  42. package/template/src/components/user/Accordian.astro +69 -0
  43. package/template/src/components/user/AccordianGroup.astro +13 -0
  44. package/template/src/components/user/Callout.astro +101 -0
  45. package/template/src/components/user/Step.astro +51 -0
  46. package/template/src/components/user/Steps.astro +9 -0
  47. package/template/src/components/user/Tab.astro +25 -0
  48. package/template/src/components/user/Tabs.astro +122 -0
  49. package/template/src/content.config.ts +11 -0
  50. package/template/src/entrypoint.ts +9 -0
  51. package/template/src/layouts/Layout.astro +92 -0
  52. package/template/src/lib/component-error.ts +163 -0
  53. package/template/src/lib/frontmatter-schema.ts +9 -0
  54. package/template/src/lib/oas.ts +24 -0
  55. package/template/src/lib/pagefind.ts +88 -0
  56. package/template/src/lib/routes.ts +316 -0
  57. package/template/src/lib/utils.ts +59 -0
  58. package/template/src/lib/validation.ts +1097 -0
  59. package/template/src/pages/[...slug].astro +77 -0
  60. package/template/src/styles/global.css +209 -0
  61. package/template/tsconfig.json +5 -0
@@ -0,0 +1,149 @@
1
+ ---
2
+ import { Icon } from "astro-icon/components";
3
+ import type { NavMenu } from "../lib/validation";
4
+ import SidebarMenu from "./SidebarMenu.astro";
5
+ import { slugify } from "../lib/utils";
6
+ import { getAllRoutes } from "../lib/routes";
7
+
8
+ interface Props {
9
+ menu: NavMenu;
10
+ parentSlug?: string;
11
+ }
12
+
13
+ const { menu, parentSlug = "" } = Astro.props;
14
+
15
+ const pathname = Astro.url.pathname;
16
+
17
+ let currentMenuIndex = menu.items.findIndex(
18
+ (i) => slugify(i.label) === pathname.split("/")[1]
19
+ );
20
+ currentMenuIndex = currentMenuIndex === -1 ? 0 : currentMenuIndex;
21
+
22
+ const routes = await getAllRoutes();
23
+
24
+ let firstHrefOfMenuItems: string[] = [];
25
+
26
+ const seen = new Set();
27
+
28
+ for (const route of routes) {
29
+ const slug = route.slug;
30
+
31
+ const parts = slug.split("/");
32
+ const topLevel = parts[0];
33
+
34
+ if (!seen.has(topLevel)) {
35
+ seen.add(topLevel);
36
+ firstHrefOfMenuItems.push(slug);
37
+ }
38
+ }
39
+
40
+ // Calculate the parentSlug for the currently selected menu item
41
+ const selectedMenuItem = menu.items[currentMenuIndex];
42
+ const menuItemSlug = slugify(selectedMenuItem.label);
43
+ const currentPrefix = parentSlug
44
+ ? `${parentSlug}/${menuItemSlug}`
45
+ : menuItemSlug;
46
+ ---
47
+
48
+ <div x-data=`{
49
+ open: false,
50
+ }`>
51
+ <div
52
+ class:list={[
53
+ "mt-3 mx-2",
54
+ menu.label &&
55
+ "rounded-lg bg-neutral-100/80 px-0.5 pb-0.5 inset-shadow-xs inset-shadow-neutral-700/5 border border-neutral-200/60",
56
+ ]}
57
+ >
58
+ {
59
+ menu.label && (
60
+ <label class="font-semibold text-xs px-2 py-1.5 block">
61
+ {menu.label}
62
+ </label>
63
+ )
64
+ }
65
+ <div class="relative">
66
+ <button
67
+ class="flex items-center w-full text-sm bg-white border border-neutral-200 rounded-lg shadow-xs px-3 py-2 cursor-pointer"
68
+ x-on:click="open = true"
69
+ aria-haspopup="menu"
70
+ aria-expanded
71
+ >
72
+ {
73
+ menu.items[currentMenuIndex].icon && (
74
+ <Icon
75
+ class="mr-2 size-4"
76
+ name={`lucide:${menu.items[currentMenuIndex].icon}`}
77
+ />
78
+ )
79
+ }
80
+ <span class="font-medium">{menu.items[currentMenuIndex].label}</span>
81
+ <Icon class="ml-auto" name="lucide:chevrons-up-down" />
82
+ </button>
83
+ <div class="fixed inset-0 z-50" x-show="open" x-on:click="open = false">
84
+ </div>
85
+ <ul
86
+ class:list={[
87
+ "z-50 absolute bg-white border border-neutral-200 rounded-lg inset-x-0 top-full py-[3px] shadow-xl overflow-hidden",
88
+ menu.label ? "mt-1.5" : "mt-1",
89
+ ]}
90
+ x-init
91
+ role="menu"
92
+ x-show="open"
93
+ x-transition.origin.top
94
+ x-cloak
95
+ >
96
+ {
97
+ menu.items.map(({ label }, index) => {
98
+ return (
99
+ <li
100
+ x-data={`{
101
+ index: ${index}
102
+ }`}
103
+ role="menuitem"
104
+ >
105
+ <a
106
+ class="flex items-center px-3 py-2 cursor-pointer text-sm text-neutral-700 relative z-0 before:-z-10 before:absolute before:inset-x-1 before:inset-y-px before:rounded-md before:duration-150"
107
+ class:list={[
108
+ index === currentMenuIndex
109
+ ? "before:bg-neutral-200/50 text-neutral-900"
110
+ : "hover:before:bg-neutral-100/70",
111
+ ]}
112
+ href={`/${firstHrefOfMenuItems[index]}`}
113
+ >
114
+ {menu.items[index].icon && (
115
+ <Icon
116
+ class="mr-2 size-4"
117
+ name={`lucide:${menu.items[index].icon}`}
118
+ />
119
+ )}
120
+ {label}
121
+ {index === currentMenuIndex && (
122
+ <Icon
123
+ class="ml-auto text-neutral-900 [&_path]:stroke-3"
124
+ name="lucide:check"
125
+ stroke-width={10}
126
+ />
127
+ )}
128
+ </a>
129
+ </li>
130
+ );
131
+ })
132
+ }
133
+ </ul>
134
+ </div>
135
+ </div>
136
+ <div
137
+ class:list={[
138
+ "relative overflow-y-auto",
139
+ menu.label
140
+ ? "h-[calc(100vh-4px-64px-12px-70px-52px)]"
141
+ : "h-[calc(100vh-4px-64px-12px-38px-52px)]",
142
+ ]}
143
+ >
144
+ <SidebarMenu
145
+ navigation={menu.items[currentMenuIndex].submenu}
146
+ parentSlug={currentPrefix}
147
+ />
148
+ </div>
149
+ </div>
@@ -0,0 +1,51 @@
1
+ ---
2
+ import type { NavGroup } from "../lib/validation";
3
+ import SidebarLink from "./SidebarLink.astro";
4
+ import SidebarSubgroup from "./SidebarSubgroup.astro";
5
+ import { slugify } from "../lib/utils";
6
+ import Tag from "./ui/Tag.astro";
7
+ import { Icon } from "astro-icon/components";
8
+
9
+ interface Props {
10
+ item: NavGroup;
11
+ parentSlug?: string;
12
+ }
13
+
14
+ const { item, parentSlug = "" } = Astro.props;
15
+
16
+ const groupSlug = slugify(item.group);
17
+ const currentPrefix = parentSlug ? `${parentSlug}/${groupSlug}` : groupSlug;
18
+ ---
19
+
20
+ <li>
21
+ <div class:list={["text-sm font-semibold mb-2 flex items-center gap-2 px-2"]}>
22
+ {
23
+ item.icon && (
24
+ <Icon name={`lucide:${item.icon}`} class="size-4 text-neutral-500" />
25
+ )
26
+ }
27
+ {item.group}
28
+ {item.tag && <Tag>{item.tag}</Tag>}
29
+ </div>
30
+
31
+ <ul>
32
+ {
33
+ item.pages.map((child) => (
34
+ <li>
35
+ {typeof child === "string" ? (
36
+ <SidebarLink path={child} groupSlug={currentPrefix} />
37
+ ) : "page" in child ? (
38
+ <SidebarLink
39
+ path={child.page}
40
+ icon={child.icon}
41
+ tag={child.tag}
42
+ groupSlug={currentPrefix}
43
+ />
44
+ ) : (
45
+ <SidebarSubgroup item={child} parentSlug={currentPrefix} />
46
+ )}
47
+ </li>
48
+ ))
49
+ }
50
+ </ul>
51
+ </li>
@@ -0,0 +1,56 @@
1
+ ---
2
+ import { deriveTitleFromEntryId, slugify } from "../lib/utils";
3
+ import { Icon } from "astro-icon/components";
4
+ import Tag from "./ui/Tag.astro";
5
+ import { getCollection } from "astro:content";
6
+
7
+ interface Props {
8
+ path: string;
9
+ groupSlug?: string;
10
+ icon?: string;
11
+ tag?: string;
12
+ }
13
+
14
+ const { path: filePath, groupSlug = "", icon, tag } = Astro.props;
15
+
16
+ const docs = await getCollection("docs");
17
+
18
+ // Find the entry matching this path
19
+ const entry = docs.find((doc: any) => {
20
+ const docPath = doc.id.replace(/\.mdx$/, "");
21
+ return docPath === filePath;
22
+ });
23
+
24
+ if (!entry) {
25
+ throw new Error("Entry not found");
26
+ }
27
+
28
+ // Generate a nice label from the filename (e.g. "writing-content/page" -> "Page")
29
+ const filename = filePath ? filePath.split("/").pop() || filePath : "";
30
+ const text = entry.data.title || deriveTitleFromEntryId(entry.id);
31
+
32
+ const pageSlug = slugify(filename);
33
+ // If groupSlug exists, join it. Otherwise just use pageSlug (though practically groupSlug should exist from hierarchy)
34
+ const href = groupSlug ? `/${groupSlug}/${pageSlug}` : `/${pageSlug}`;
35
+
36
+ // Normalize paths for comparison (remove trailing slashes)
37
+ const currentPath = Astro.url.pathname.replace(/\/$/, "");
38
+ const targetPath = href.replace(/\/$/, "");
39
+ const isActive = currentPath === targetPath;
40
+ ---
41
+
42
+ <a
43
+ href={href}
44
+ class:list={[
45
+ "block px-2 py-[7px] text-sm relative z-0 font-[450] before:-z-10 before:absolute before:inset-x-0 before:inset-y-px before:rounded-md before:duration-150",
46
+ isActive
47
+ ? "before:bg-neutral-200/50 dark:before:bg-neutral-800 text-neutral-900 dark:text-neutral-200"
48
+ : "text-neutral-600 dark:text-neutral-400 hover:before:bg-neutral-100/70 dark:hover:before:bg-neutral-800/50 hover:text-neutral-900 dark:hover:text-neutral-300",
49
+ ]}
50
+ >
51
+ <div class="flex items-center gap-2">
52
+ {icon && <Icon name={`lucide:${icon}`} class="size-4 opacity-75" />}
53
+ {text}
54
+ {tag && <Tag>{tag}</Tag>}
55
+ </div>
56
+ </a>
@@ -0,0 +1,46 @@
1
+ ---
2
+ import type { NavGroup, NavigationItem, NavPage } from "../lib/validation";
3
+ import SidebarOpenApi from "./sidebar/SidebarOpenApi.astro";
4
+ import SidebarDropdown from "./SidebarDropdown.astro";
5
+ import SidebarGroup from "./SidebarGroup.astro";
6
+ import SidebarLink from "./SidebarLink.astro";
7
+
8
+ interface Props {
9
+ navigation: NavigationItem;
10
+ parentSlug?: string;
11
+ }
12
+
13
+ let { navigation, parentSlug = "" } = Astro.props;
14
+ ---
15
+
16
+ {
17
+ navigation.pages ? (
18
+ <ul class="px-2 pt-4 pb-3">
19
+ {navigation.pages.map((item) => (
20
+ <li>
21
+ {typeof item === "string" ? (
22
+ <SidebarLink path={item} />
23
+ ) : (
24
+ <SidebarLink
25
+ path={(item as NavPage).page}
26
+ icon={(item as NavPage).icon}
27
+ tag={(item as NavPage).tag}
28
+ />
29
+ )}
30
+ </li>
31
+ ))}
32
+ </ul>
33
+ ) : navigation.groups ? (
34
+ <ul class="px-2 pt-4 pb-3 [&>:nth-child(n+2)]:mt-5 [&>:nth-child(n+2)]:pt-[25px] [&>:nth-child(n+2)]:relative [&>:nth-child(n+2)]:before:absolute [&>:nth-child(n+2)]:before:top-0 [&>:nth-child(n+2)]:before:inset-x-0 [&>:nth-child(n+2)]:before:h-px [&>:nth-child(n+2)]:before:bg-linear-[90deg,transparent,var(--color-neutral-200)_20%,var(--color-neutral-200)_80%,transparent] dark:[&>:nth-child(n+2)]:before:bg-linear-[90deg,transparent,var(--color-neutral-700)_20%,var(--color-neutral-700)_80%,transparent]">
35
+ {navigation.groups.map((item) => (
36
+ <SidebarGroup item={item as NavGroup} parentSlug={parentSlug} />
37
+ ))}
38
+ </ul>
39
+ ) : navigation.menu ? (
40
+ !navigation.menu.type || navigation.menu.type === "dropdown" ? (
41
+ <SidebarDropdown menu={navigation.menu} parentSlug={parentSlug} />
42
+ ) : navigation.menu.type === "collapsible" ? null : null
43
+ ) : navigation.openapi ? (
44
+ <SidebarOpenApi openapi={navigation.openapi} parentSlug={parentSlug} />
45
+ ) : null
46
+ }
@@ -0,0 +1,136 @@
1
+ ---
2
+ import type { NavGroup } from "../lib/validation";
3
+ import SidebarLink from "./SidebarLink.astro";
4
+ import { slugify } from "../lib/utils";
5
+ import Tag from "./ui/Tag.astro";
6
+ import { Icon } from "astro-icon/components";
7
+
8
+ interface Props {
9
+ item: NavGroup;
10
+ parentSlug: string;
11
+ }
12
+
13
+ const { item, parentSlug } = Astro.props;
14
+
15
+ const groupSlug = slugify(item.group);
16
+ const currentPrefix = `${parentSlug}/${groupSlug}`;
17
+
18
+ const groupId = `group-${currentPrefix}`;
19
+ const listId = `list-${groupId}`;
20
+
21
+ // Check if any child page is active (shallow check, no recursion needed)
22
+ const containsActivePage = item.pages.some((child) => {
23
+ const pagePath =
24
+ typeof child === "string" ? child : "pages" in child ? null : child.page;
25
+
26
+ if (pagePath) {
27
+ const filename = pagePath.split("/").pop() || pagePath;
28
+ const pageSlug = slugify(filename);
29
+ const href = `/${currentPrefix}/${pageSlug}`;
30
+
31
+ // Normalize paths for comparison (remove trailing slashes)
32
+ const normalizedCurrent = Astro.url.pathname.replace(/\/$/, "");
33
+ const normalizedTarget = href.replace(/\/$/, "");
34
+ return normalizedCurrent === normalizedTarget;
35
+ }
36
+ return false;
37
+ });
38
+ ---
39
+
40
+ <div
41
+ class="group"
42
+ x-data={`{
43
+ expanded: $persist(${item.expanded ?? false}).as('sidebar-state-${groupId}'),
44
+ init() {
45
+ if (${containsActivePage}) {
46
+ this.expanded = true;
47
+ }
48
+ }
49
+ }`}
50
+ >
51
+ <button
52
+ type="button"
53
+ @click="expanded = !expanded"
54
+ class:list={[
55
+ "flex items-center justify-between gap-2 w-full px-2 py-[7px] text-sm font-[450] text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-300 cursor-pointer relative z-0 before:-z-10 before:absolute before:inset-x-0 before:inset-y-px before:rounded-md before:transition-colors hover:before:bg-neutral-100/70 dark:hover:before:bg-neutral-800/50",
56
+ ]}
57
+ >
58
+ <div class="flex items-center gap-2">
59
+ {
60
+ item.icon && (
61
+ <Icon name={`lucide:${item.icon}`} class="size-4 opacity-75" />
62
+ )
63
+ }
64
+ {item.group}
65
+ {item.tag && <Tag>{item.tag}</Tag>}
66
+ </div>
67
+ <svg
68
+ xmlns="http://www.w3.org/2000/svg"
69
+ width="14"
70
+ height="14"
71
+ viewBox="0 0 24 24"
72
+ fill="none"
73
+ stroke="currentColor"
74
+ stroke-width="2"
75
+ stroke-linecap="round"
76
+ stroke-linejoin="round"
77
+ class="transition-transform duration-200 text-inherit opacity-80"
78
+ :class="expanded ? 'rotate-90' : ''"
79
+ >
80
+ <path d="m9 18 6-6-6-6"></path>
81
+ </svg>
82
+ </button>
83
+
84
+ <ul
85
+ id={listId}
86
+ x-show="expanded"
87
+ x-collapse
88
+ x-cloak
89
+ class="border-l border-neutral-300/80 ml-[15.5px] pl-2"
90
+ >
91
+ {
92
+ item.pages.map((child) => (
93
+ <li>
94
+ {typeof child === "string" ? (
95
+ <SidebarLink path={child} groupSlug={currentPrefix} />
96
+ ) : "page" in child ? (
97
+ <SidebarLink
98
+ path={child.page}
99
+ icon={child.icon}
100
+ tag={child.tag}
101
+ groupSlug={currentPrefix}
102
+ />
103
+ ) : null}
104
+ </li>
105
+ ))
106
+ }
107
+ </ul>
108
+
109
+ {/* Anti-flash script to prevent content jumping */}
110
+ <script define:vars={{ listId, groupId, containsActivePage }}>
111
+ (function () {
112
+ try {
113
+ const storageKey = "sidebar-state-" + groupId;
114
+
115
+ // 1. Force state in localStorage if this is the active group
116
+ if (containsActivePage) {
117
+ localStorage.setItem(storageKey, "true");
118
+ }
119
+
120
+ // 2. Read state
121
+ const storedState = localStorage.getItem(storageKey);
122
+ const shouldBeVisible = storedState === "true";
123
+
124
+ // 3. Remove x-cloak immediately if it should be visible
125
+ if (shouldBeVisible) {
126
+ const list = document.getElementById(listId);
127
+ if (list) {
128
+ list.removeAttribute("x-cloak");
129
+ }
130
+ }
131
+ } catch (e) {
132
+ console.error(e);
133
+ }
134
+ })();
135
+ </script>
136
+ </div>