blodemd 0.0.5 → 0.0.7

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 (187) hide show
  1. package/README.md +2 -2
  2. package/dev-server/app/[[...slug]]/page.tsx +139 -0
  3. package/dev-server/app/blodemd-dev/invalidate/route.ts +12 -0
  4. package/dev-server/app/blodemd-dev/static/[...path]/route.ts +32 -0
  5. package/dev-server/app/blodemd-dev/version/route.ts +14 -0
  6. package/dev-server/app/blodemd-internal/proxy/route.ts +86 -0
  7. package/dev-server/app/error.tsx +24 -0
  8. package/dev-server/app/favicon.ico +0 -0
  9. package/dev-server/app/globals.css +4 -0
  10. package/dev-server/app/layout.tsx +38 -0
  11. package/dev-server/app/not-found.tsx +18 -0
  12. package/dev-server/app/search/route.ts +17 -0
  13. package/dev-server/components/dev-reload-script.tsx +86 -0
  14. package/dev-server/components/providers.tsx +15 -0
  15. package/dev-server/lib/dev-state.ts +8 -0
  16. package/dev-server/lib/local-content-source.ts +103 -0
  17. package/dev-server/lib/local-runtime.tsx +558 -0
  18. package/dev-server/next-env.d.ts +5 -0
  19. package/dev-server/next.config.js +46 -0
  20. package/dev-server/package.json +57 -0
  21. package/dev-server/postcss.config.mjs +7 -0
  22. package/dev-server/public/glide-variable.woff2 +0 -0
  23. package/dev-server/tsconfig.json +50 -0
  24. package/dist/cli.mjs +311 -86
  25. package/dist/cli.mjs.map +1 -1
  26. package/docs/app/globals.css +457 -0
  27. package/docs/components/api/api-playground.tsx +295 -0
  28. package/docs/components/api/api-reference.tsx +121 -0
  29. package/docs/components/content/collection-index.tsx +114 -0
  30. package/docs/components/docs/contextual-menu.tsx +406 -0
  31. package/docs/components/docs/copy-page-menu.tsx +255 -0
  32. package/docs/components/docs/doc-header.tsx +210 -0
  33. package/docs/components/docs/doc-shell.tsx +313 -0
  34. package/docs/components/docs/doc-sidebar.tsx +211 -0
  35. package/docs/components/docs/doc-toc.tsx +45 -0
  36. package/docs/components/docs/mobile-nav.tsx +205 -0
  37. package/docs/components/icons/doc-icon.tsx +96 -0
  38. package/docs/components/mdx/accordion.tsx +83 -0
  39. package/docs/components/mdx/badge.tsx +79 -0
  40. package/docs/components/mdx/callout.tsx +88 -0
  41. package/docs/components/mdx/card.tsx +110 -0
  42. package/docs/components/mdx/code-block.tsx +75 -0
  43. package/docs/components/mdx/code-group.tsx +94 -0
  44. package/docs/components/mdx/color.tsx +87 -0
  45. package/docs/components/mdx/columns.tsx +25 -0
  46. package/docs/components/mdx/expandable.tsx +45 -0
  47. package/docs/components/mdx/field-layout.tsx +77 -0
  48. package/docs/components/mdx/frame.tsx +23 -0
  49. package/docs/components/mdx/get-text-content.ts +18 -0
  50. package/docs/components/mdx/icon.tsx +12 -0
  51. package/docs/components/mdx/index.tsx +107 -0
  52. package/docs/components/mdx/installer.tsx +20 -0
  53. package/docs/components/mdx/panel.tsx +11 -0
  54. package/docs/components/mdx/param-field.tsx +56 -0
  55. package/docs/components/mdx/preview.tsx +36 -0
  56. package/docs/components/mdx/prompt.tsx +63 -0
  57. package/docs/components/mdx/request-example.tsx +27 -0
  58. package/docs/components/mdx/response-field.tsx +42 -0
  59. package/docs/components/mdx/steps.tsx +92 -0
  60. package/docs/components/mdx/tabs.tsx +88 -0
  61. package/docs/components/mdx/tile.tsx +43 -0
  62. package/docs/components/mdx/tooltip.tsx +71 -0
  63. package/docs/components/mdx/tree.tsx +120 -0
  64. package/docs/components/mdx/type-table.tsx +71 -0
  65. package/docs/components/mdx/update.tsx +44 -0
  66. package/docs/components/mdx/video.tsx +12 -0
  67. package/docs/components/mdx/view.tsx +66 -0
  68. package/docs/components/providers.tsx +15 -0
  69. package/docs/components/ui/breadcrumb.tsx +92 -0
  70. package/docs/components/ui/button.tsx +90 -0
  71. package/docs/components/ui/card.tsx +92 -0
  72. package/docs/components/ui/command.tsx +139 -0
  73. package/docs/components/ui/dialog.tsx +97 -0
  74. package/docs/components/ui/field.tsx +237 -0
  75. package/docs/components/ui/input.tsx +105 -0
  76. package/docs/components/ui/label.tsx +22 -0
  77. package/docs/components/ui/popover.tsx +72 -0
  78. package/docs/components/ui/search.tsx +384 -0
  79. package/docs/components/ui/separator.tsx +26 -0
  80. package/docs/components/ui/sheet.tsx +104 -0
  81. package/docs/components/ui/sidebar.tsx +433 -0
  82. package/docs/components/ui/theme-toggle.tsx +62 -0
  83. package/docs/components/ui/tooltip.tsx +53 -0
  84. package/docs/lib/contextual-options.ts +193 -0
  85. package/docs/lib/docs-collection.ts +22 -0
  86. package/docs/lib/mdx.ts +87 -0
  87. package/docs/lib/navigation.ts +288 -0
  88. package/docs/lib/openapi.ts +158 -0
  89. package/docs/lib/routes.ts +44 -0
  90. package/docs/lib/server-cache.ts +83 -0
  91. package/docs/lib/shiki.ts +40 -0
  92. package/docs/lib/theme.ts +29 -0
  93. package/docs/lib/toc.ts +2 -0
  94. package/docs/lib/utils.ts +5 -0
  95. package/package.json +43 -6
  96. package/packages/@repo/common/dist/index.d.ts +9 -0
  97. package/packages/@repo/common/dist/index.d.ts.map +1 -0
  98. package/packages/@repo/common/dist/index.js +42 -0
  99. package/packages/@repo/common/package.json +34 -0
  100. package/packages/@repo/common/src/index.ts +51 -0
  101. package/packages/@repo/contracts/dist/api-key.d.ts +30 -0
  102. package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
  103. package/packages/@repo/contracts/dist/api-key.js +20 -0
  104. package/packages/@repo/contracts/dist/dates.d.ts +4 -0
  105. package/packages/@repo/contracts/dist/dates.d.ts.map +1 -0
  106. package/packages/@repo/contracts/dist/dates.js +2 -0
  107. package/packages/@repo/contracts/dist/deployment.d.ts +71 -0
  108. package/packages/@repo/contracts/dist/deployment.d.ts.map +1 -0
  109. package/packages/@repo/contracts/dist/deployment.js +46 -0
  110. package/packages/@repo/contracts/dist/domain.d.ts +94 -0
  111. package/packages/@repo/contracts/dist/domain.d.ts.map +1 -0
  112. package/packages/@repo/contracts/dist/domain.js +36 -0
  113. package/packages/@repo/contracts/dist/ids.d.ts +14 -0
  114. package/packages/@repo/contracts/dist/ids.d.ts.map +1 -0
  115. package/packages/@repo/contracts/dist/ids.js +10 -0
  116. package/packages/@repo/contracts/dist/index.d.ts +10 -0
  117. package/packages/@repo/contracts/dist/index.d.ts.map +1 -0
  118. package/packages/@repo/contracts/dist/index.js +11 -0
  119. package/packages/@repo/contracts/dist/pagination.d.ts +23 -0
  120. package/packages/@repo/contracts/dist/pagination.d.ts.map +1 -0
  121. package/packages/@repo/contracts/dist/pagination.js +15 -0
  122. package/packages/@repo/contracts/dist/project.d.ts +25 -0
  123. package/packages/@repo/contracts/dist/project.d.ts.map +1 -0
  124. package/packages/@repo/contracts/dist/project.js +23 -0
  125. package/packages/@repo/contracts/dist/tenant.d.ts +111 -0
  126. package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -0
  127. package/packages/@repo/contracts/dist/tenant.js +56 -0
  128. package/packages/@repo/contracts/dist/user.d.ts +9 -0
  129. package/packages/@repo/contracts/dist/user.d.ts.map +1 -0
  130. package/packages/@repo/contracts/dist/user.js +9 -0
  131. package/packages/@repo/contracts/package.json +37 -0
  132. package/packages/@repo/contracts/src/api-key.ts +27 -0
  133. package/packages/@repo/contracts/src/dates.ts +4 -0
  134. package/packages/@repo/contracts/src/deployment.ts +73 -0
  135. package/packages/@repo/contracts/src/domain.ts +51 -0
  136. package/packages/@repo/contracts/src/ids.ts +22 -0
  137. package/packages/@repo/contracts/src/index.ts +11 -0
  138. package/packages/@repo/contracts/src/pagination.ts +21 -0
  139. package/packages/@repo/contracts/src/project.ts +30 -0
  140. package/packages/@repo/contracts/src/tenant.ts +92 -0
  141. package/packages/@repo/contracts/src/user.ts +12 -0
  142. package/packages/@repo/models/dist/docs-config.d.ts +985 -0
  143. package/packages/@repo/models/dist/docs-config.d.ts.map +1 -0
  144. package/packages/@repo/models/dist/docs-config.js +548 -0
  145. package/packages/@repo/models/dist/index.d.ts +3 -0
  146. package/packages/@repo/models/dist/index.d.ts.map +1 -0
  147. package/packages/@repo/models/dist/index.js +3 -0
  148. package/packages/@repo/models/dist/tenant.d.ts +25 -0
  149. package/packages/@repo/models/dist/tenant.d.ts.map +1 -0
  150. package/packages/@repo/models/dist/tenant.js +1 -0
  151. package/packages/@repo/models/package.json +37 -0
  152. package/packages/@repo/models/src/docs-config.ts +648 -0
  153. package/packages/@repo/models/src/index.ts +3 -0
  154. package/packages/@repo/models/src/tenant.ts +29 -0
  155. package/packages/@repo/prebuild/dist/index.d.ts +2 -0
  156. package/packages/@repo/prebuild/dist/index.d.ts.map +1 -0
  157. package/packages/@repo/prebuild/dist/index.js +2 -0
  158. package/packages/@repo/prebuild/dist/openapi.d.ts +43 -0
  159. package/packages/@repo/prebuild/dist/openapi.d.ts.map +1 -0
  160. package/packages/@repo/prebuild/dist/openapi.js +58 -0
  161. package/packages/@repo/prebuild/package.json +39 -0
  162. package/packages/@repo/prebuild/src/index.ts +2 -0
  163. package/packages/@repo/prebuild/src/openapi.ts +116 -0
  164. package/packages/@repo/previewing/dist/blob-source.d.ts +16 -0
  165. package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -0
  166. package/packages/@repo/previewing/dist/blob-source.js +110 -0
  167. package/packages/@repo/previewing/dist/content-source.d.ts +12 -0
  168. package/packages/@repo/previewing/dist/content-source.d.ts.map +1 -0
  169. package/packages/@repo/previewing/dist/content-source.js +1 -0
  170. package/packages/@repo/previewing/dist/fs-source.d.ts +11 -0
  171. package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -0
  172. package/packages/@repo/previewing/dist/fs-source.js +72 -0
  173. package/packages/@repo/previewing/dist/index.d.ts +120 -0
  174. package/packages/@repo/previewing/dist/index.d.ts.map +1 -0
  175. package/packages/@repo/previewing/dist/index.js +984 -0
  176. package/packages/@repo/previewing/package.json +41 -0
  177. package/packages/@repo/previewing/src/blob-source.ts +167 -0
  178. package/packages/@repo/previewing/src/content-source.ts +12 -0
  179. package/packages/@repo/previewing/src/fs-source.ts +104 -0
  180. package/packages/@repo/previewing/src/index.ts +1490 -0
  181. package/packages/@repo/validation/dist/index.d.ts +12 -0
  182. package/packages/@repo/validation/dist/index.d.ts.map +1 -0
  183. package/packages/@repo/validation/dist/index.js +30 -0
  184. package/packages/@repo/validation/package.json +37 -0
  185. package/packages/@repo/validation/src/index.ts +59 -0
  186. package/packages/@repo/validation/src/mintlify-docs-schema.json +5016 -0
  187. package/scripts/prepare-package.mjs +39 -0
@@ -0,0 +1,211 @@
1
+ import { normalizePath } from "@repo/common";
2
+ import { ArrowUpRightIcon } from "blode-icons-react";
3
+ import Image from "next/image";
4
+ import Link from "next/link";
5
+
6
+ import { DocIcon } from "@/components/icons/doc-icon";
7
+ import { getNavPageHref, getNavPageTitle } from "@/lib/navigation";
8
+ import type { NavEntry, NavPage } from "@/lib/navigation";
9
+ import { isExternalHref, toDocHref } from "@/lib/routes";
10
+ import { cn } from "@/lib/utils";
11
+
12
+ const MENU_BUTTON_CLASS =
13
+ "relative flex min-h-[30px] items-center gap-2 overflow-visible rounded-md border border-transparent px-2 text-[0.8rem] font-medium transition-colors hover:bg-accent/70 hover:text-foreground";
14
+
15
+ const NavIcon = ({ icon }: { icon: string }) => {
16
+ if (icon.startsWith("http") || icon.startsWith("/")) {
17
+ return (
18
+ <Image
19
+ alt=""
20
+ className="size-4 shrink-0"
21
+ height={16}
22
+ src={icon}
23
+ unoptimized
24
+ width={16}
25
+ />
26
+ );
27
+ }
28
+ return (
29
+ <DocIcon
30
+ className="size-4 shrink-0 text-muted-foreground"
31
+ icon={icon}
32
+ size={16}
33
+ />
34
+ );
35
+ };
36
+
37
+ const NavPageLink = ({
38
+ item,
39
+ basePath,
40
+ isActive,
41
+ }: {
42
+ item: NavPage;
43
+ basePath: string;
44
+ isActive: boolean;
45
+ }) => {
46
+ const displayTitle = getNavPageTitle(item);
47
+ const href = getNavPageHref(item, basePath);
48
+ const isExternal = Boolean(item.url && isExternalHref(item.url));
49
+
50
+ const linkContent = (
51
+ <>
52
+ {item.icon ? <NavIcon icon={item.icon} /> : null}
53
+ <span className={item.deprecated ? "line-through opacity-60" : undefined}>
54
+ {displayTitle}
55
+ </span>
56
+ {item.tag ? (
57
+ <span className="ml-auto shrink-0 rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium leading-none text-primary">
58
+ {item.tag}
59
+ </span>
60
+ ) : null}
61
+ {item.deprecated && !item.tag ? (
62
+ <span className="ml-auto shrink-0 rounded bg-yellow-100 px-1.5 py-0.5 text-[10px] font-medium leading-none text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300">
63
+ Deprecated
64
+ </span>
65
+ ) : null}
66
+ {isExternal ? (
67
+ <ArrowUpRightIcon
68
+ aria-hidden="true"
69
+ className="ml-auto size-3 shrink-0 text-muted-foreground"
70
+ />
71
+ ) : null}
72
+ </>
73
+ );
74
+
75
+ const className = cn(
76
+ MENU_BUTTON_CLASS,
77
+ isActive && "border-accent bg-accent text-foreground"
78
+ );
79
+
80
+ if (isExternal) {
81
+ return (
82
+ <a
83
+ className={className}
84
+ href={href}
85
+ rel="noopener noreferrer"
86
+ target="_blank"
87
+ >
88
+ {linkContent}
89
+ </a>
90
+ );
91
+ }
92
+
93
+ return (
94
+ <Link className={className} href={href}>
95
+ {linkContent}
96
+ </Link>
97
+ );
98
+ };
99
+
100
+ const Section = ({
101
+ title,
102
+ children,
103
+ paddedTop = false,
104
+ }: {
105
+ title?: string;
106
+ children: React.ReactNode;
107
+ paddedTop?: boolean;
108
+ }) => (
109
+ <section
110
+ className={cn(
111
+ "relative flex w-full min-w-0 flex-col p-2",
112
+ paddedTop && "pt-6"
113
+ )}
114
+ >
115
+ {title ? (
116
+ <div className="mb-2 px-2 font-medium text-muted-foreground text-xs">
117
+ {title}
118
+ </div>
119
+ ) : null}
120
+ <div className="w-full text-sm">{children}</div>
121
+ </section>
122
+ );
123
+
124
+ export const DocSidebar = ({
125
+ entries,
126
+ currentPath,
127
+ anchors,
128
+ basePath,
129
+ }: {
130
+ entries: NavEntry[];
131
+ currentPath: string;
132
+ anchors?: { label: string; href: string }[];
133
+ basePath: string;
134
+ }) => {
135
+ const activePath = normalizePath(currentPath);
136
+ const isActive = (path: string) => normalizePath(path) === activePath;
137
+
138
+ return (
139
+ <aside
140
+ className="sticky top-[calc(var(--header-height)+0.6rem)] z-30 hidden h-[calc(100svh-10rem)] w-[calc(var(--spacing)*56)] shrink-0 flex-col overscroll-none bg-transparent lg:flex"
141
+ aria-label="Documentation navigation"
142
+ >
143
+ <div className="h-9" />
144
+ <div className="absolute top-8 z-10 h-8 w-full shrink-0 bg-gradient-to-b from-background via-background/80 to-background/50 blur-xs" />
145
+ <div className="absolute top-12 right-2 bottom-0 hidden h-full w-px bg-gradient-to-b from-transparent via-border to-transparent lg:flex" />
146
+ <div className="no-scrollbar mx-auto flex min-h-0 w-full flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden px-2">
147
+ {anchors?.length ? (
148
+ <Section paddedTop title="Pinned">
149
+ <ul className="space-y-1">
150
+ {anchors.map((anchor) => (
151
+ <li key={anchor.href}>
152
+ <a
153
+ className={cn(MENU_BUTTON_CLASS, "text-foreground")}
154
+ href={
155
+ anchor.href.startsWith("http")
156
+ ? anchor.href
157
+ : toDocHref(anchor.href, basePath)
158
+ }
159
+ >
160
+ {anchor.label}
161
+ </a>
162
+ </li>
163
+ ))}
164
+ </ul>
165
+ </Section>
166
+ ) : null}
167
+ {entries.map((entry, index) => {
168
+ if (entry.type === "page") {
169
+ return (
170
+ <Section
171
+ key={entry.path}
172
+ paddedTop={index === 0 && !anchors?.length}
173
+ >
174
+ <ul>
175
+ <li>
176
+ <NavPageLink
177
+ basePath={basePath}
178
+ isActive={isActive(entry.path)}
179
+ item={entry}
180
+ />
181
+ </li>
182
+ </ul>
183
+ </Section>
184
+ );
185
+ }
186
+
187
+ return (
188
+ <Section
189
+ key={entry.title}
190
+ paddedTop={index === 0 && !anchors?.length}
191
+ title={entry.title}
192
+ >
193
+ <ul className="space-y-0.5">
194
+ {entry.items.map((item) => (
195
+ <li key={item.path}>
196
+ <NavPageLink
197
+ basePath={basePath}
198
+ isActive={isActive(item.path)}
199
+ item={item}
200
+ />
201
+ </li>
202
+ ))}
203
+ </ul>
204
+ </Section>
205
+ );
206
+ })}
207
+ <div className="sticky -bottom-1 z-10 h-16 shrink-0 bg-gradient-to-t from-background via-background/80 to-background/50 blur-xs" />
208
+ </div>
209
+ </aside>
210
+ );
211
+ };
@@ -0,0 +1,45 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import type { TocItem } from "@/lib/toc";
4
+
5
+ export const DocToc = ({
6
+ toc,
7
+ contextualItems,
8
+ }: {
9
+ toc: TocItem[];
10
+ contextualItems?: ReactNode;
11
+ }) => {
12
+ if (!toc.length && !contextualItems) {
13
+ return null;
14
+ }
15
+
16
+ return (
17
+ <nav
18
+ aria-label="Table of contents"
19
+ className="sticky top-[calc(var(--header-height,4rem)+1px)] z-30 ml-auto hidden h-[90svh] w-56 shrink-0 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex"
20
+ >
21
+ <div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">
22
+ <div className="flex flex-col gap-2 p-4 pt-0 text-sm">
23
+ {toc.length > 0 ? (
24
+ <>
25
+ <p className="sticky top-0 h-6 bg-background font-medium text-muted-foreground text-xs">
26
+ On This Page
27
+ </p>
28
+ {toc.map((item) => (
29
+ <a
30
+ className="text-[0.8rem] text-muted-foreground no-underline transition-colors hover:text-foreground data-[depth=3]:pl-4 data-[depth=4]:pl-6"
31
+ data-depth={item.level}
32
+ href={`#${item.id}`}
33
+ key={item.id}
34
+ >
35
+ {item.title}
36
+ </a>
37
+ ))}
38
+ </>
39
+ ) : null}
40
+ {contextualItems}
41
+ </div>
42
+ </div>
43
+ </nav>
44
+ );
45
+ };
@@ -0,0 +1,205 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useCallback, useState } from "react";
5
+ import type { ReactNode } from "react";
6
+
7
+ import { Button } from "@/components/ui/button";
8
+ import {
9
+ Popover,
10
+ PopoverContent,
11
+ PopoverTrigger,
12
+ } from "@/components/ui/popover";
13
+ import { getNavPageHref, getNavPageTitle } from "@/lib/navigation";
14
+ import type { NavEntry, NavPage, NavTab } from "@/lib/navigation";
15
+ import { isExternalHref, resolveHref, toDocHref } from "@/lib/routes";
16
+ import { cn } from "@/lib/utils";
17
+
18
+ const MobileLink = ({
19
+ href,
20
+ onOpenChange,
21
+ className,
22
+ children,
23
+ onClick,
24
+ ...props
25
+ }: React.ComponentProps<typeof Link> & {
26
+ onOpenChange?: (open: boolean) => void;
27
+ children: ReactNode;
28
+ className?: string;
29
+ }) => {
30
+ const handleClick = useCallback(
31
+ (event: React.MouseEvent<HTMLAnchorElement>) => {
32
+ onClick?.(event);
33
+ onOpenChange?.(false);
34
+ },
35
+ [onClick, onOpenChange]
36
+ );
37
+
38
+ return (
39
+ <Link
40
+ className={cn("flex items-center gap-2 text-2xl font-medium", className)}
41
+ href={href}
42
+ onClick={handleClick}
43
+ {...props}
44
+ >
45
+ {children}
46
+ </Link>
47
+ );
48
+ };
49
+
50
+ export const MobileNav = ({
51
+ entries,
52
+ globalLinks,
53
+ basePath,
54
+ tabs,
55
+ activeTabIndex,
56
+ className,
57
+ }: {
58
+ entries: NavEntry[];
59
+ globalLinks: { label: string; href: string }[];
60
+ basePath: string;
61
+ tabs?: NavTab[] | null;
62
+ activeTabIndex?: number;
63
+ className?: string;
64
+ }) => {
65
+ const [open, setOpen] = useState(false);
66
+
67
+ const renderPageLink = (page: NavPage) => {
68
+ const href = getNavPageHref(page, basePath);
69
+ const isExternal = Boolean(page.url && isExternalHref(page.url));
70
+
71
+ return (
72
+ <MobileLink
73
+ href={href}
74
+ key={page.path}
75
+ onOpenChange={setOpen}
76
+ rel={isExternal ? "noopener noreferrer" : undefined}
77
+ target={isExternal ? "_blank" : undefined}
78
+ >
79
+ {getNavPageTitle(page)}
80
+ </MobileLink>
81
+ );
82
+ };
83
+
84
+ return (
85
+ <Popover onOpenChange={setOpen} open={open}>
86
+ <PopoverTrigger asChild>
87
+ <Button
88
+ className={cn(
89
+ "extend-touch-target !p-0 h-8 touch-manipulation items-center justify-start gap-2.5 hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 active:bg-transparent dark:hover:bg-transparent",
90
+ className
91
+ )}
92
+ variant="ghost"
93
+ >
94
+ <div className="relative flex h-8 w-4 items-center justify-center">
95
+ <div className="relative size-4">
96
+ <span
97
+ className={cn(
98
+ "absolute left-0 block h-0.5 w-4 bg-foreground transition-all duration-100",
99
+ open ? "top-[0.4rem] -rotate-45" : "top-1"
100
+ )}
101
+ />
102
+ <span
103
+ className={cn(
104
+ "absolute left-0 block h-0.5 w-4 bg-foreground transition-all duration-100",
105
+ open ? "top-[0.4rem] rotate-45" : "top-2.5"
106
+ )}
107
+ />
108
+ </div>
109
+ <span className="sr-only">Toggle Menu</span>
110
+ </div>
111
+ <span className="flex h-8 items-center text-lg font-medium leading-none">
112
+ Menu
113
+ </span>
114
+ </Button>
115
+ </PopoverTrigger>
116
+ <PopoverContent
117
+ align="start"
118
+ alignOffset={-16}
119
+ className="no-scrollbar h-(--available-height) w-(--available-width) overflow-y-auto rounded-none border-none bg-background/90 p-0 shadow-none backdrop-blur duration-100 data-open:animate-none!"
120
+ side="bottom"
121
+ sideOffset={14}
122
+ >
123
+ <div className="flex flex-col gap-12 overflow-auto px-6 py-6">
124
+ {tabs?.length ? (
125
+ <div className="flex flex-col gap-4">
126
+ <div className="text-sm font-medium text-muted-foreground">
127
+ Sections
128
+ </div>
129
+ <div className="flex flex-col gap-3">
130
+ {tabs.map((tab, index) => {
131
+ const href =
132
+ (tab.href ? resolveHref(tab.href, basePath) : undefined) ??
133
+ (tab.slugPrefix
134
+ ? toDocHref(tab.slugPrefix, basePath)
135
+ : undefined);
136
+ if (!href) {
137
+ return null;
138
+ }
139
+ const isActive = index === activeTabIndex;
140
+
141
+ return (
142
+ <MobileLink
143
+ className={isActive ? "text-primary" : ""}
144
+ href={href}
145
+ key={tab.label}
146
+ onOpenChange={setOpen}
147
+ rel={
148
+ tab.href && isExternalHref(tab.href)
149
+ ? "noopener noreferrer"
150
+ : undefined
151
+ }
152
+ target={
153
+ tab.href && isExternalHref(tab.href)
154
+ ? "_blank"
155
+ : undefined
156
+ }
157
+ >
158
+ {tab.label}
159
+ </MobileLink>
160
+ );
161
+ })}
162
+ </div>
163
+ </div>
164
+ ) : null}
165
+ {globalLinks.length > 0 ? (
166
+ <div className="flex flex-col gap-4">
167
+ <div className="text-sm font-medium text-muted-foreground">
168
+ Menu
169
+ </div>
170
+ <div className="flex flex-col gap-3">
171
+ {globalLinks.map((link) => (
172
+ <MobileLink
173
+ href={link.href}
174
+ key={link.label}
175
+ onOpenChange={setOpen}
176
+ rel="noopener noreferrer"
177
+ target="_blank"
178
+ >
179
+ {link.label}
180
+ </MobileLink>
181
+ ))}
182
+ </div>
183
+ </div>
184
+ ) : null}
185
+ {entries.map((entry) => {
186
+ if (entry.type === "page") {
187
+ return renderPageLink(entry);
188
+ }
189
+
190
+ return (
191
+ <div className="flex flex-col gap-4" key={entry.title}>
192
+ <div className="text-sm font-medium text-muted-foreground">
193
+ {entry.title}
194
+ </div>
195
+ <div className="flex flex-col gap-3">
196
+ {entry.items.map((item) => renderPageLink(item))}
197
+ </div>
198
+ </div>
199
+ );
200
+ })}
201
+ </div>
202
+ </PopoverContent>
203
+ </Popover>
204
+ );
205
+ };
@@ -0,0 +1,96 @@
1
+ import ArrowRightIcon from "blode-icons-react/icons/arrow-right";
2
+ import ArrowUpRightIcon from "blode-icons-react/icons/arrow-up-right";
3
+ import CheckIcon from "blode-icons-react/icons/check";
4
+ import CodeIcon from "blode-icons-react/icons/code";
5
+ import ConsoleIcon from "blode-icons-react/icons/console";
6
+ import FlagIcon from "blode-icons-react/icons/flag-1";
7
+ import FolderAddRightIcon from "blode-icons-react/icons/folder-add-right";
8
+ import GlobusIcon from "blode-icons-react/icons/globus";
9
+ import ImacIcon from "blode-icons-react/icons/imac";
10
+ import InfoIcon from "blode-icons-react/icons/info";
11
+ import KeyIcon from "blode-icons-react/icons/key";
12
+ import LockIcon from "blode-icons-react/icons/lock";
13
+ import MagnifyingGlassIcon from "blode-icons-react/icons/magnifying-glass";
14
+ import PlayIcon from "blode-icons-react/icons/play";
15
+ import PuzzleIcon from "blode-icons-react/icons/puzzle";
16
+ import RocketIcon from "blode-icons-react/icons/rocket";
17
+ import SparkleIcon from "blode-icons-react/icons/sparkle";
18
+ import StarIcon from "blode-icons-react/icons/star";
19
+ import TriangleExclamationIcon from "blode-icons-react/icons/triangle-exclamation";
20
+ import type { ComponentType, ReactElement, SVGProps } from "react";
21
+
22
+ import { cn } from "@/lib/utils";
23
+
24
+ type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;
25
+ interface DocIconProps {
26
+ icon: string;
27
+ color?: string;
28
+ size?: number;
29
+ className?: string;
30
+ }
31
+
32
+ const DOC_ICON_MAP: Record<string, IconComponent> = {
33
+ "alert-triangle": TriangleExclamationIcon,
34
+ "arrow-right": ArrowRightIcon,
35
+ check: CheckIcon,
36
+ code: CodeIcon,
37
+ "external-link": ArrowUpRightIcon,
38
+ flag: FlagIcon,
39
+ "folder-plus": FolderAddRightIcon,
40
+ globe: GlobusIcon,
41
+ info: InfoIcon,
42
+ key: KeyIcon,
43
+ lock: LockIcon,
44
+ monitor: ImacIcon,
45
+ play: PlayIcon,
46
+ puzzle: PuzzleIcon,
47
+ rocket: RocketIcon,
48
+ search: MagnifyingGlassIcon,
49
+ sparkles: SparkleIcon,
50
+ star: StarIcon,
51
+ terminal: ConsoleIcon,
52
+ };
53
+
54
+ const getIconComponent = (icon: string): IconComponent | null =>
55
+ DOC_ICON_MAP[icon.trim().toLowerCase()] ?? null;
56
+
57
+ export const DocIcon = ({
58
+ icon,
59
+ color,
60
+ size = 16,
61
+ className,
62
+ }: DocIconProps): ReactElement => {
63
+ const IconComponent = getIconComponent(icon);
64
+
65
+ if (!IconComponent) {
66
+ return (
67
+ <span
68
+ aria-hidden
69
+ className={cn(
70
+ "inline-flex items-center justify-center font-medium uppercase leading-none",
71
+ className
72
+ )}
73
+ style={{
74
+ color: color ?? undefined,
75
+ fontSize: Math.max(10, Math.round(size * 0.55)),
76
+ height: size,
77
+ width: size,
78
+ }}
79
+ >
80
+ {icon.slice(0, 2)}
81
+ </span>
82
+ );
83
+ }
84
+
85
+ return (
86
+ <IconComponent
87
+ aria-hidden
88
+ className={cn("shrink-0", className)}
89
+ style={{
90
+ color: color ?? undefined,
91
+ height: size,
92
+ width: size,
93
+ }}
94
+ />
95
+ );
96
+ };
@@ -0,0 +1,83 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { ReactNode } from "react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ interface AccordionProps {
9
+ title: string;
10
+ description?: string;
11
+ defaultOpen?: boolean;
12
+ id?: string;
13
+ icon?: ReactNode;
14
+ children: ReactNode;
15
+ }
16
+
17
+ export const Accordion = ({
18
+ title,
19
+ description,
20
+ defaultOpen = false,
21
+ id,
22
+ icon,
23
+ children,
24
+ }: AccordionProps) => {
25
+ const [open, setOpen] = useState(defaultOpen);
26
+ const anchorId = id ?? title.toLowerCase().replaceAll(/\s+/g, "-");
27
+
28
+ const toggle = useCallback(() => setOpen((prev) => !prev), []);
29
+
30
+ return (
31
+ <div className="border-b border-border last:border-b-0" id={anchorId}>
32
+ <button
33
+ aria-expanded={open}
34
+ className="flex w-full items-center gap-3 py-4 text-left"
35
+ onClick={toggle}
36
+ type="button"
37
+ >
38
+ {icon ? (
39
+ <span className="flex size-5 shrink-0 items-center justify-center text-muted-foreground">
40
+ {icon}
41
+ </span>
42
+ ) : null}
43
+ <div className="min-w-0 flex-1">
44
+ <div className="font-medium">{title}</div>
45
+ {description ? (
46
+ <div className="mt-0.5 text-sm text-muted-foreground">
47
+ {description}
48
+ </div>
49
+ ) : null}
50
+ </div>
51
+ <svg
52
+ aria-hidden
53
+ className={cn(
54
+ "size-4 shrink-0 text-muted-foreground transition-transform duration-200",
55
+ open && "rotate-180"
56
+ )}
57
+ fill="none"
58
+ stroke="currentColor"
59
+ strokeWidth={2}
60
+ viewBox="0 0 24 24"
61
+ >
62
+ <path d="m6 9 6 6 6-6" strokeLinecap="round" strokeLinejoin="round" />
63
+ </svg>
64
+ </button>
65
+ <div
66
+ className={cn(
67
+ "grid transition-[grid-template-rows] duration-200",
68
+ open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
69
+ )}
70
+ >
71
+ <div className="overflow-hidden">
72
+ <div className="pb-4 text-sm text-muted-foreground">{children}</div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ );
77
+ };
78
+
79
+ export const AccordionGroup = ({ children }: { children: ReactNode }) => (
80
+ <div className="my-4 divide-y divide-border rounded-xl border border-border px-4">
81
+ {children}
82
+ </div>
83
+ );