blodemd 0.0.5 → 0.0.6

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 (184) hide show
  1. package/dev-server/app/[[...slug]]/page.tsx +139 -0
  2. package/dev-server/app/blodemd-dev/invalidate/route.ts +12 -0
  3. package/dev-server/app/blodemd-dev/static/[...path]/route.ts +32 -0
  4. package/dev-server/app/blodemd-dev/version/route.ts +14 -0
  5. package/dev-server/app/blodemd-internal/proxy/route.ts +86 -0
  6. package/dev-server/app/error.tsx +24 -0
  7. package/dev-server/app/globals.css +4 -0
  8. package/dev-server/app/layout.tsx +38 -0
  9. package/dev-server/app/not-found.tsx +18 -0
  10. package/dev-server/app/search/route.ts +17 -0
  11. package/dev-server/components/dev-reload-script.tsx +86 -0
  12. package/dev-server/components/providers.tsx +15 -0
  13. package/dev-server/lib/dev-state.ts +8 -0
  14. package/dev-server/lib/local-content-source.ts +103 -0
  15. package/dev-server/lib/local-runtime.tsx +558 -0
  16. package/dev-server/next.config.js +46 -0
  17. package/dev-server/package.json +57 -0
  18. package/dev-server/postcss.config.mjs +7 -0
  19. package/dev-server/public/glide-variable.woff2 +0 -0
  20. package/dev-server/tsconfig.json +49 -0
  21. package/dist/cli.mjs +108 -39
  22. package/dist/cli.mjs.map +1 -1
  23. package/docs/app/globals.css +455 -0
  24. package/docs/components/api/api-playground.tsx +295 -0
  25. package/docs/components/api/api-reference.tsx +121 -0
  26. package/docs/components/content/collection-index.tsx +114 -0
  27. package/docs/components/docs/contextual-menu.tsx +406 -0
  28. package/docs/components/docs/copy-page-menu.tsx +255 -0
  29. package/docs/components/docs/doc-header.tsx +192 -0
  30. package/docs/components/docs/doc-shell.tsx +289 -0
  31. package/docs/components/docs/doc-sidebar.tsx +206 -0
  32. package/docs/components/docs/doc-toc.tsx +45 -0
  33. package/docs/components/docs/mobile-nav.tsx +207 -0
  34. package/docs/components/mdx/accordion.tsx +83 -0
  35. package/docs/components/mdx/badge.tsx +79 -0
  36. package/docs/components/mdx/callout.tsx +88 -0
  37. package/docs/components/mdx/card.tsx +104 -0
  38. package/docs/components/mdx/code-block.tsx +75 -0
  39. package/docs/components/mdx/code-group.tsx +94 -0
  40. package/docs/components/mdx/color.tsx +87 -0
  41. package/docs/components/mdx/columns.tsx +25 -0
  42. package/docs/components/mdx/expandable.tsx +45 -0
  43. package/docs/components/mdx/field-layout.tsx +77 -0
  44. package/docs/components/mdx/frame.tsx +23 -0
  45. package/docs/components/mdx/get-text-content.ts +18 -0
  46. package/docs/components/mdx/icon.tsx +56 -0
  47. package/docs/components/mdx/index.tsx +96 -0
  48. package/docs/components/mdx/installer.tsx +20 -0
  49. package/docs/components/mdx/panel.tsx +11 -0
  50. package/docs/components/mdx/param-field.tsx +56 -0
  51. package/docs/components/mdx/preview.tsx +36 -0
  52. package/docs/components/mdx/prompt.tsx +63 -0
  53. package/docs/components/mdx/request-example.tsx +27 -0
  54. package/docs/components/mdx/response-field.tsx +42 -0
  55. package/docs/components/mdx/steps.tsx +92 -0
  56. package/docs/components/mdx/tabs.tsx +88 -0
  57. package/docs/components/mdx/tile.tsx +43 -0
  58. package/docs/components/mdx/tooltip.tsx +71 -0
  59. package/docs/components/mdx/tree.tsx +120 -0
  60. package/docs/components/mdx/type-table.tsx +71 -0
  61. package/docs/components/mdx/update.tsx +44 -0
  62. package/docs/components/mdx/video.tsx +12 -0
  63. package/docs/components/mdx/view.tsx +66 -0
  64. package/docs/components/providers.tsx +15 -0
  65. package/docs/components/ui/breadcrumb.tsx +92 -0
  66. package/docs/components/ui/button.tsx +90 -0
  67. package/docs/components/ui/card.tsx +92 -0
  68. package/docs/components/ui/command.tsx +139 -0
  69. package/docs/components/ui/dialog.tsx +97 -0
  70. package/docs/components/ui/field.tsx +237 -0
  71. package/docs/components/ui/input.tsx +105 -0
  72. package/docs/components/ui/label.tsx +22 -0
  73. package/docs/components/ui/popover.tsx +72 -0
  74. package/docs/components/ui/search.tsx +380 -0
  75. package/docs/components/ui/separator.tsx +26 -0
  76. package/docs/components/ui/sheet.tsx +104 -0
  77. package/docs/components/ui/sidebar.tsx +433 -0
  78. package/docs/components/ui/theme-toggle.tsx +62 -0
  79. package/docs/components/ui/tooltip.tsx +53 -0
  80. package/docs/lib/contextual-options.ts +193 -0
  81. package/docs/lib/docs-collection.ts +22 -0
  82. package/docs/lib/mdx.ts +90 -0
  83. package/docs/lib/navigation.ts +288 -0
  84. package/docs/lib/openapi.ts +158 -0
  85. package/docs/lib/routes.ts +10 -0
  86. package/docs/lib/server-cache.ts +83 -0
  87. package/docs/lib/shiki.ts +35 -0
  88. package/docs/lib/theme.ts +29 -0
  89. package/docs/lib/toc.ts +2 -0
  90. package/docs/lib/utils.ts +5 -0
  91. package/package.json +33 -4
  92. package/packages/@repo/common/dist/index.d.ts +9 -0
  93. package/packages/@repo/common/dist/index.d.ts.map +1 -0
  94. package/packages/@repo/common/dist/index.js +42 -0
  95. package/packages/@repo/common/package.json +34 -0
  96. package/packages/@repo/common/src/common.unit.test.ts +55 -0
  97. package/packages/@repo/common/src/index.ts +51 -0
  98. package/packages/@repo/contracts/dist/api-key.d.ts +30 -0
  99. package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
  100. package/packages/@repo/contracts/dist/api-key.js +20 -0
  101. package/packages/@repo/contracts/dist/dates.d.ts +4 -0
  102. package/packages/@repo/contracts/dist/dates.d.ts.map +1 -0
  103. package/packages/@repo/contracts/dist/dates.js +2 -0
  104. package/packages/@repo/contracts/dist/deployment.d.ts +71 -0
  105. package/packages/@repo/contracts/dist/deployment.d.ts.map +1 -0
  106. package/packages/@repo/contracts/dist/deployment.js +46 -0
  107. package/packages/@repo/contracts/dist/domain.d.ts +94 -0
  108. package/packages/@repo/contracts/dist/domain.d.ts.map +1 -0
  109. package/packages/@repo/contracts/dist/domain.js +36 -0
  110. package/packages/@repo/contracts/dist/ids.d.ts +14 -0
  111. package/packages/@repo/contracts/dist/ids.d.ts.map +1 -0
  112. package/packages/@repo/contracts/dist/ids.js +10 -0
  113. package/packages/@repo/contracts/dist/index.d.ts +10 -0
  114. package/packages/@repo/contracts/dist/index.d.ts.map +1 -0
  115. package/packages/@repo/contracts/dist/index.js +11 -0
  116. package/packages/@repo/contracts/dist/pagination.d.ts +23 -0
  117. package/packages/@repo/contracts/dist/pagination.d.ts.map +1 -0
  118. package/packages/@repo/contracts/dist/pagination.js +15 -0
  119. package/packages/@repo/contracts/dist/project.d.ts +25 -0
  120. package/packages/@repo/contracts/dist/project.d.ts.map +1 -0
  121. package/packages/@repo/contracts/dist/project.js +23 -0
  122. package/packages/@repo/contracts/dist/tenant.d.ts +99 -0
  123. package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -0
  124. package/packages/@repo/contracts/dist/tenant.js +36 -0
  125. package/packages/@repo/contracts/dist/user.d.ts +9 -0
  126. package/packages/@repo/contracts/dist/user.d.ts.map +1 -0
  127. package/packages/@repo/contracts/dist/user.js +9 -0
  128. package/packages/@repo/contracts/package.json +37 -0
  129. package/packages/@repo/contracts/src/api-key.ts +27 -0
  130. package/packages/@repo/contracts/src/dates.ts +4 -0
  131. package/packages/@repo/contracts/src/deployment.ts +73 -0
  132. package/packages/@repo/contracts/src/domain.ts +51 -0
  133. package/packages/@repo/contracts/src/ids.ts +22 -0
  134. package/packages/@repo/contracts/src/index.ts +11 -0
  135. package/packages/@repo/contracts/src/pagination.ts +21 -0
  136. package/packages/@repo/contracts/src/project.ts +30 -0
  137. package/packages/@repo/contracts/src/tenant.ts +54 -0
  138. package/packages/@repo/contracts/src/user.ts +12 -0
  139. package/packages/@repo/models/dist/docs-config.d.ts +985 -0
  140. package/packages/@repo/models/dist/docs-config.d.ts.map +1 -0
  141. package/packages/@repo/models/dist/docs-config.js +548 -0
  142. package/packages/@repo/models/dist/index.d.ts +3 -0
  143. package/packages/@repo/models/dist/index.d.ts.map +1 -0
  144. package/packages/@repo/models/dist/index.js +3 -0
  145. package/packages/@repo/models/dist/tenant.d.ts +25 -0
  146. package/packages/@repo/models/dist/tenant.d.ts.map +1 -0
  147. package/packages/@repo/models/dist/tenant.js +1 -0
  148. package/packages/@repo/models/package.json +37 -0
  149. package/packages/@repo/models/src/docs-config.ts +648 -0
  150. package/packages/@repo/models/src/index.ts +3 -0
  151. package/packages/@repo/models/src/tenant.ts +29 -0
  152. package/packages/@repo/prebuild/dist/index.d.ts +2 -0
  153. package/packages/@repo/prebuild/dist/index.d.ts.map +1 -0
  154. package/packages/@repo/prebuild/dist/index.js +2 -0
  155. package/packages/@repo/prebuild/dist/openapi.d.ts +43 -0
  156. package/packages/@repo/prebuild/dist/openapi.d.ts.map +1 -0
  157. package/packages/@repo/prebuild/dist/openapi.js +58 -0
  158. package/packages/@repo/prebuild/package.json +39 -0
  159. package/packages/@repo/prebuild/src/index.ts +2 -0
  160. package/packages/@repo/prebuild/src/openapi.ts +116 -0
  161. package/packages/@repo/previewing/dist/blob-source.d.ts +16 -0
  162. package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -0
  163. package/packages/@repo/previewing/dist/blob-source.js +110 -0
  164. package/packages/@repo/previewing/dist/content-source.d.ts +12 -0
  165. package/packages/@repo/previewing/dist/content-source.d.ts.map +1 -0
  166. package/packages/@repo/previewing/dist/content-source.js +1 -0
  167. package/packages/@repo/previewing/dist/fs-source.d.ts +11 -0
  168. package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -0
  169. package/packages/@repo/previewing/dist/fs-source.js +79 -0
  170. package/packages/@repo/previewing/dist/index.d.ts +120 -0
  171. package/packages/@repo/previewing/dist/index.d.ts.map +1 -0
  172. package/packages/@repo/previewing/dist/index.js +984 -0
  173. package/packages/@repo/previewing/package.json +41 -0
  174. package/packages/@repo/previewing/src/blob-source.ts +167 -0
  175. package/packages/@repo/previewing/src/content-source.ts +12 -0
  176. package/packages/@repo/previewing/src/fs-source.ts +111 -0
  177. package/packages/@repo/previewing/src/index.ts +1490 -0
  178. package/packages/@repo/previewing/src/index.unit.test.ts +290 -0
  179. package/packages/@repo/validation/dist/index.d.ts +12 -0
  180. package/packages/@repo/validation/dist/index.d.ts.map +1 -0
  181. package/packages/@repo/validation/dist/index.js +30 -0
  182. package/packages/@repo/validation/package.json +37 -0
  183. package/packages/@repo/validation/src/index.ts +59 -0
  184. package/packages/@repo/validation/src/mintlify-docs-schema.json +5016 -0
@@ -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,207 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useCallback, useEffect, useState } from "react";
5
+ import type { ReactNode } from "react";
6
+
7
+ import { getNavPageHref, getNavPageTitle } from "@/lib/navigation";
8
+ import type { NavEntry, NavPage, NavTab } from "@/lib/navigation";
9
+ import { toDocHref } from "@/lib/routes";
10
+ import { cn } from "@/lib/utils";
11
+
12
+ const MobileLink = ({
13
+ href,
14
+ onClose,
15
+ className,
16
+ children,
17
+ ...props
18
+ }: {
19
+ href: string;
20
+ onClose: () => void;
21
+ className?: string;
22
+ children: ReactNode;
23
+ } & Omit<React.ComponentProps<typeof Link>, "href" | "onClick">) => (
24
+ <Link
25
+ className={cn("flex items-center gap-2 text-2xl font-medium", className)}
26
+ href={href}
27
+ onClick={onClose}
28
+ {...props}
29
+ >
30
+ {children}
31
+ </Link>
32
+ );
33
+
34
+ export const MobileNav = ({
35
+ entries,
36
+ globalLinks,
37
+ basePath,
38
+ tabs,
39
+ activeTabIndex,
40
+ }: {
41
+ entries: NavEntry[];
42
+ globalLinks: { label: string; href: string }[];
43
+ basePath: string;
44
+ tabs?: NavTab[] | null;
45
+ activeTabIndex?: number;
46
+ }) => {
47
+ const [open, setOpen] = useState(false);
48
+ const handleClose = useCallback(() => setOpen(false), []);
49
+ const handleOpen = useCallback(() => setOpen(true), []);
50
+
51
+ useEffect(() => {
52
+ if (!open) {
53
+ return;
54
+ }
55
+
56
+ const previousOverflow = document.body.style.overflow;
57
+ document.body.style.overflow = "hidden";
58
+
59
+ const handleKeydown = (event: KeyboardEvent) => {
60
+ if (event.key === "Escape") {
61
+ setOpen(false);
62
+ }
63
+ };
64
+
65
+ document.addEventListener("keydown", handleKeydown);
66
+
67
+ return () => {
68
+ document.body.style.overflow = previousOverflow;
69
+ document.removeEventListener("keydown", handleKeydown);
70
+ };
71
+ }, [open]);
72
+
73
+ const renderPageLink = (page: NavPage) => {
74
+ const href = getNavPageHref(page, basePath);
75
+ const isExternal = Boolean(page.url);
76
+
77
+ return (
78
+ <MobileLink
79
+ href={href}
80
+ key={page.path}
81
+ onClose={handleClose}
82
+ rel={isExternal ? "noopener noreferrer" : undefined}
83
+ target={isExternal ? "_blank" : undefined}
84
+ >
85
+ {getNavPageTitle(page)}
86
+ </MobileLink>
87
+ );
88
+ };
89
+
90
+ return (
91
+ <>
92
+ <button
93
+ aria-label="Toggle menu"
94
+ className="inline-flex size-8 items-center justify-center gap-2.5 rounded-md hover:bg-accent lg:hidden"
95
+ onClick={handleOpen}
96
+ type="button"
97
+ >
98
+ <div className="relative size-4">
99
+ <span className="absolute left-0 top-1 block h-0.5 w-4 bg-foreground" />
100
+ <span className="absolute left-0 top-2.5 block h-0.5 w-4 bg-foreground" />
101
+ </div>
102
+ </button>
103
+ {open ? (
104
+ <div className="fixed inset-0 z-50 lg:hidden">
105
+ <button
106
+ aria-label="Close menu"
107
+ className="absolute inset-0 bg-background/90 backdrop-blur-sm"
108
+ onClick={handleClose}
109
+ type="button"
110
+ />
111
+ <div className="relative flex h-full flex-col overflow-y-auto px-6 py-6">
112
+ <div className="flex items-center justify-between">
113
+ <span className="text-sm font-medium text-muted-foreground">
114
+ Navigation
115
+ </span>
116
+ <button
117
+ aria-label="Close menu"
118
+ className="inline-flex size-9 items-center justify-center rounded-full border border-border text-lg text-muted-foreground hover:bg-accent hover:text-foreground"
119
+ onClick={handleClose}
120
+ type="button"
121
+ >
122
+ X
123
+ </button>
124
+ </div>
125
+ <div className="mt-8 flex flex-col gap-12">
126
+ {tabs?.length ? (
127
+ <div className="flex flex-col gap-4">
128
+ <div className="text-sm font-medium text-muted-foreground">
129
+ Sections
130
+ </div>
131
+ <div className="flex flex-col gap-3">
132
+ {tabs.map((tab, index) => {
133
+ const href =
134
+ tab.href ??
135
+ (tab.slugPrefix
136
+ ? toDocHref(tab.slugPrefix, basePath)
137
+ : undefined);
138
+ if (!href) {
139
+ return null;
140
+ }
141
+ const isActive = index === activeTabIndex;
142
+
143
+ return (
144
+ <MobileLink
145
+ className={isActive ? "text-primary" : ""}
146
+ href={href}
147
+ key={tab.label}
148
+ onClose={handleClose}
149
+ rel={
150
+ tab.href?.startsWith("http")
151
+ ? "noopener noreferrer"
152
+ : undefined
153
+ }
154
+ target={
155
+ tab.href?.startsWith("http") ? "_blank" : 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
+ onClose={handleClose}
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
+ </div>
203
+ </div>
204
+ ) : null}
205
+ </>
206
+ );
207
+ };
@@ -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
+ );
@@ -0,0 +1,79 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const colorStyles: Record<string, string> = {
6
+ blue: "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300",
7
+ gray: "bg-muted text-muted-foreground",
8
+ green: "bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300",
9
+ orange:
10
+ "bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-300",
11
+ purple:
12
+ "bg-purple-100 text-purple-800 dark:bg-purple-900/40 dark:text-purple-300",
13
+ red: "bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300",
14
+ surface: "bg-card text-card-foreground",
15
+ white: "bg-white text-foreground dark:bg-white dark:text-black",
16
+ yellow:
17
+ "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300",
18
+ };
19
+
20
+ const strokeStyles: Record<string, string> = {
21
+ blue: "border-blue-300 text-blue-700 dark:border-blue-700 dark:text-blue-300",
22
+ gray: "border-border text-muted-foreground",
23
+ green:
24
+ "border-green-300 text-green-700 dark:border-green-700 dark:text-green-300",
25
+ orange:
26
+ "border-orange-300 text-orange-700 dark:border-orange-700 dark:text-orange-300",
27
+ purple:
28
+ "border-purple-300 text-purple-700 dark:border-purple-700 dark:text-purple-300",
29
+ red: "border-red-300 text-red-700 dark:border-red-700 dark:text-red-300",
30
+ surface: "border-border text-card-foreground",
31
+ white: "border-border text-foreground",
32
+ yellow:
33
+ "border-yellow-300 text-yellow-700 dark:border-yellow-700 dark:text-yellow-300",
34
+ };
35
+
36
+ const sizeStyles: Record<string, string> = {
37
+ lg: "px-3 py-1.5 text-sm",
38
+ md: "px-2.5 py-1 text-xs",
39
+ sm: "px-2 py-0.5 text-xs",
40
+ xs: "px-1.5 py-0.5 text-[10px]",
41
+ };
42
+
43
+ interface BadgeProps {
44
+ color?: string;
45
+ size?: "xs" | "sm" | "md" | "lg";
46
+ shape?: "rounded" | "pill";
47
+ icon?: ReactNode;
48
+ stroke?: boolean;
49
+ disabled?: boolean;
50
+ className?: string;
51
+ children: ReactNode;
52
+ }
53
+
54
+ export const Badge = ({
55
+ color = "gray",
56
+ size = "md",
57
+ shape = "rounded",
58
+ icon,
59
+ stroke = false,
60
+ disabled = false,
61
+ className,
62
+ children,
63
+ }: BadgeProps) => (
64
+ <span
65
+ className={cn(
66
+ "inline-flex items-center gap-1 font-medium",
67
+ shape === "pill" ? "rounded-full" : "rounded-md",
68
+ stroke
69
+ ? cn("border bg-transparent", strokeStyles[color] ?? strokeStyles.gray)
70
+ : (colorStyles[color] ?? colorStyles.gray),
71
+ sizeStyles[size] ?? sizeStyles.md,
72
+ disabled && "opacity-50",
73
+ className
74
+ )}
75
+ >
76
+ {icon ? <span className="shrink-0">{icon}</span> : null}
77
+ {children}
78
+ </span>
79
+ );
@@ -0,0 +1,88 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const variantStyles: Record<string, string> = {
6
+ check:
7
+ "border-emerald-600 bg-emerald-100 dark:border-emerald-400 dark:bg-emerald-900",
8
+ danger: "border-red-600 bg-red-100 dark:border-red-400 dark:bg-red-900",
9
+ info: "bg-card border-border",
10
+ note: "bg-card border-border",
11
+ success:
12
+ "border-emerald-600 bg-emerald-100 dark:border-emerald-400 dark:bg-emerald-900",
13
+ tip: "border-blue-600 bg-blue-100 dark:border-blue-400 dark:bg-blue-900",
14
+ warning:
15
+ "border-amber-600 bg-amber-100 dark:border-amber-400 dark:bg-amber-900",
16
+ };
17
+
18
+ interface CalloutProps {
19
+ type?: "info" | "success" | "warning" | "danger" | "note" | "tip" | "check";
20
+ title?: string;
21
+ icon?: ReactNode;
22
+ color?: string;
23
+ children: ReactNode;
24
+ }
25
+
26
+ export const Callout = ({
27
+ type = "info",
28
+ title,
29
+ icon,
30
+ color,
31
+ children,
32
+ }: CalloutProps) => (
33
+ <div
34
+ data-slot="alert"
35
+ role="alert"
36
+ data-variant="default"
37
+ className={cn(
38
+ "relative grid grid-cols-[0_1fr] items-start gap-y-0.5 rounded-xl border px-4 py-3 text-sm text-surface-foreground",
39
+ "has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3",
40
+ "[&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
41
+ "**:[code]:border md:-mx-1",
42
+ variantStyles[type]
43
+ )}
44
+ style={
45
+ color
46
+ ? {
47
+ backgroundColor: `${color}10`,
48
+ borderColor: color,
49
+ }
50
+ : undefined
51
+ }
52
+ >
53
+ {icon}
54
+ <div
55
+ data-slot="alert-description"
56
+ className="col-start-2 grid justify-items-start gap-1 text-sm text-foreground [&_p]:leading-relaxed"
57
+ >
58
+ {title ? <strong className="font-medium">{title}</strong> : null}
59
+ <div className="text-sm [&>p:first-child]:mt-0 [&>p:last-child]:mb-0 [&_p]:leading-relaxed [&_p:not(:first-child)]:mt-6">
60
+ {children}
61
+ </div>
62
+ </div>
63
+ </div>
64
+ );
65
+
66
+ export const Note = ({ children }: { children: ReactNode }) => (
67
+ <Callout type="note">{children}</Callout>
68
+ );
69
+
70
+ export const Warning = ({ children }: { children: ReactNode }) => (
71
+ <Callout type="warning">{children}</Callout>
72
+ );
73
+
74
+ export const Info = ({ children }: { children: ReactNode }) => (
75
+ <Callout type="info">{children}</Callout>
76
+ );
77
+
78
+ export const Tip = ({ children }: { children: ReactNode }) => (
79
+ <Callout type="tip">{children}</Callout>
80
+ );
81
+
82
+ export const Check = ({ children }: { children: ReactNode }) => (
83
+ <Callout type="check">{children}</Callout>
84
+ );
85
+
86
+ export const Danger = ({ children }: { children: ReactNode }) => (
87
+ <Callout type="danger">{children}</Callout>
88
+ );
@@ -0,0 +1,104 @@
1
+ import Image from "next/image";
2
+ import Link from "next/link";
3
+ import type { ReactNode } from "react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ interface CardProps {
8
+ title?: string;
9
+ icon?: ReactNode;
10
+ color?: string;
11
+ href?: string;
12
+ horizontal?: boolean;
13
+ img?: string;
14
+ cta?: string;
15
+ children?: ReactNode;
16
+ }
17
+
18
+ const CardInner = ({
19
+ title,
20
+ icon,
21
+ color,
22
+ horizontal,
23
+ img,
24
+ cta,
25
+ href,
26
+ children,
27
+ }: CardProps) => (
28
+ <div
29
+ className={cn(
30
+ "group/card rounded-xl border border-border bg-card p-4 transition-colors",
31
+ href && "hover:border-primary/30 hover:bg-accent/50",
32
+ horizontal && "flex items-start gap-4"
33
+ )}
34
+ >
35
+ {img && !horizontal ? (
36
+ <div className="relative mb-3 aspect-video overflow-hidden rounded-lg">
37
+ <Image
38
+ alt={title ?? ""}
39
+ className="object-cover"
40
+ fill
41
+ sizes="(max-width: 768px) 100vw, 50vw"
42
+ src={img}
43
+ unoptimized
44
+ />
45
+ </div>
46
+ ) : null}
47
+ {icon ? (
48
+ <div
49
+ className="mb-3 flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground"
50
+ style={color ? { color } : undefined}
51
+ >
52
+ {icon}
53
+ </div>
54
+ ) : null}
55
+ <div className="min-w-0 flex-1">
56
+ {title ? (
57
+ <div className="mb-1 font-medium text-foreground">{title}</div>
58
+ ) : null}
59
+ {children ? (
60
+ <div className="text-sm text-muted-foreground [&>p:first-child]:mt-0 [&>p:last-child]:mb-0">
61
+ {children}
62
+ </div>
63
+ ) : null}
64
+ {cta ? (
65
+ <div className="mt-2 text-sm font-medium text-primary">{cta}</div>
66
+ ) : null}
67
+ </div>
68
+ {href ? (
69
+ <svg
70
+ aria-hidden
71
+ className="mt-0.5 size-4 shrink-0 text-muted-foreground transition-transform group-hover/card:translate-x-0.5"
72
+ fill="none"
73
+ stroke="currentColor"
74
+ strokeWidth={2}
75
+ viewBox="0 0 24 24"
76
+ >
77
+ <path
78
+ d="M5 12h14M12 5l7 7-7 7"
79
+ strokeLinecap="round"
80
+ strokeLinejoin="round"
81
+ />
82
+ </svg>
83
+ ) : null}
84
+ </div>
85
+ );
86
+
87
+ export const Card = (props: CardProps) => {
88
+ if (props.href) {
89
+ const isExternal = props.href.startsWith("http");
90
+ if (isExternal) {
91
+ return (
92
+ <a href={props.href} rel="noopener noreferrer" target="_blank">
93
+ <CardInner {...props} />
94
+ </a>
95
+ );
96
+ }
97
+ return (
98
+ <Link href={props.href}>
99
+ <CardInner {...props} />
100
+ </Link>
101
+ );
102
+ }
103
+ return <CardInner {...props} />;
104
+ };
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import { CheckIcon, ClipboardIcon } from "blode-icons-react";
4
+ import { useCallback, useEffect, useMemo, useState } from "react";
5
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
6
+
7
+ import { Button } from "@/components/ui/button";
8
+ import { cn } from "@/lib/utils";
9
+
10
+ import { getTextContent } from "./get-text-content";
11
+
12
+ export const CodeBlock = ({
13
+ children,
14
+ className,
15
+ style,
16
+ tabIndex,
17
+ ...props
18
+ }: ComponentPropsWithoutRef<"pre"> & {
19
+ children: ReactNode;
20
+ }) => {
21
+ const [copied, setCopied] = useState(false);
22
+ const code = useMemo(() => getTextContent(children), [children]);
23
+
24
+ useEffect(() => {
25
+ if (copied) {
26
+ const timer = setTimeout(() => setCopied(false), 2000);
27
+ return () => clearTimeout(timer);
28
+ }
29
+ }, [copied]);
30
+
31
+ const handleCopy = useCallback(async () => {
32
+ if (!code) {
33
+ return;
34
+ }
35
+ await navigator.clipboard.writeText(code);
36
+ setCopied(true);
37
+ }, [code]);
38
+
39
+ const preStyle = style ? { ...style } : undefined;
40
+ if (preStyle) {
41
+ delete preStyle.backgroundColor;
42
+ }
43
+
44
+ return (
45
+ <figure data-rehype-pretty-code-figure="">
46
+ <pre
47
+ className={cn(
48
+ "no-scrollbar min-w-0 overflow-x-auto overflow-y-auto overscroll-y-auto overscroll-x-contain px-4 py-3.5 outline-none has-[[data-slot=tabs]]:p-0 has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0",
49
+ className
50
+ )}
51
+ style={preStyle}
52
+ tabIndex={tabIndex ?? 0}
53
+ {...props}
54
+ >
55
+ <Button
56
+ className="absolute top-3 right-2 z-10 size-7 bg-code hover:opacity-100 focus-visible:opacity-100"
57
+ data-copied={copied}
58
+ data-slot="copy-button"
59
+ onClick={handleCopy}
60
+ size="icon"
61
+ type="button"
62
+ variant="ghost"
63
+ >
64
+ <span className="sr-only">{copied ? "Copied" : "Copy"}</span>
65
+ {copied ? (
66
+ <CheckIcon aria-hidden="true" />
67
+ ) : (
68
+ <ClipboardIcon aria-hidden="true" />
69
+ )}
70
+ </Button>
71
+ {children}
72
+ </pre>
73
+ </figure>
74
+ );
75
+ };