@barodoc/theme-docs 3.0.0 → 6.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 (63) hide show
  1. package/package.json +6 -2
  2. package/src/components/Breadcrumb.astro +8 -8
  3. package/src/components/CodeCopy.astro +14 -13
  4. package/src/components/Contributors.astro +71 -0
  5. package/src/components/DocHeader.tsx +24 -24
  6. package/src/components/DocsSidebar.tsx +11 -15
  7. package/src/components/KeyboardShortcuts.astro +108 -0
  8. package/src/components/LanguageSwitcher.astro +3 -3
  9. package/src/components/MobileNavSheet.tsx +1 -1
  10. package/src/components/Search.tsx +10 -10
  11. package/src/components/SearchDialog.tsx +8 -8
  12. package/src/components/TableOfContents.astro +5 -5
  13. package/src/components/ThemeToggle.tsx +4 -4
  14. package/src/components/VersionSwitcher.tsx +79 -0
  15. package/src/components/api/ApiEndpoint.astro +5 -5
  16. package/src/components/api/ApiParam.astro +4 -4
  17. package/src/components/api/ApiParams.astro +3 -3
  18. package/src/components/api/ApiResponse.astro +2 -2
  19. package/src/components/index.ts +5 -0
  20. package/src/components/mdx/Accordion.tsx +6 -6
  21. package/src/components/mdx/ApiPlayground.tsx +200 -0
  22. package/src/components/mdx/Badge.tsx +1 -1
  23. package/src/components/mdx/Callout.astro +20 -20
  24. package/src/components/mdx/Card.astro +5 -5
  25. package/src/components/mdx/CodeGroup.astro +23 -20
  26. package/src/components/mdx/CodeGroup.tsx +6 -6
  27. package/src/components/mdx/DocAccordion.tsx +5 -5
  28. package/src/components/mdx/DocCard.tsx +2 -2
  29. package/src/components/mdx/DocTabs.tsx +3 -3
  30. package/src/components/mdx/Expandable.tsx +11 -11
  31. package/src/components/mdx/FileTree.tsx +6 -6
  32. package/src/components/mdx/Frame.tsx +2 -2
  33. package/src/components/mdx/ImageZoom.tsx +35 -0
  34. package/src/components/mdx/Mermaid.tsx +3 -3
  35. package/src/components/mdx/ParamField.tsx +7 -7
  36. package/src/components/mdx/ResponseField.tsx +6 -6
  37. package/src/components/mdx/Step.astro +2 -2
  38. package/src/components/mdx/Steps.astro +3 -3
  39. package/src/components/mdx/Steps.tsx +10 -19
  40. package/src/components/mdx/Tabs.tsx +5 -8
  41. package/src/components/mdx/Tooltip.tsx +20 -19
  42. package/src/components/mdx/Video.tsx +71 -0
  43. package/src/components/ui/accordion.tsx +2 -2
  44. package/src/components/ui/alert.tsx +1 -1
  45. package/src/components/ui/button.tsx +3 -3
  46. package/src/components/ui/card.tsx +2 -2
  47. package/src/components/ui/dialog.tsx +2 -2
  48. package/src/components/ui/scroll-area.tsx +1 -1
  49. package/src/components/ui/separator.tsx +1 -1
  50. package/src/components/ui/sheet.tsx +3 -3
  51. package/src/components/ui/tabs.tsx +2 -2
  52. package/src/components/ui/tooltip.tsx +1 -1
  53. package/src/index.ts +33 -1
  54. package/src/layouts/BaseLayout.astro +10 -1
  55. package/src/layouts/BlogLayout.astro +93 -0
  56. package/src/layouts/DocsLayout.astro +72 -23
  57. package/src/pages/404.astro +5 -5
  58. package/src/pages/blog/[...slug].astro +39 -0
  59. package/src/pages/blog/index.astro +92 -0
  60. package/src/pages/changelog/index.astro +72 -0
  61. package/src/pages/docs/[...slug].astro +6 -2
  62. package/src/pages/index.astro +21 -21
  63. package/src/styles/global.css +1041 -166
@@ -43,19 +43,19 @@ export function SearchDialog({ className }: SearchDialogProps) {
43
43
  return (
44
44
  <Dialog open={open} onOpenChange={setOpen}>
45
45
  <DialogContent className={cn("sm:max-w-2xl p-0 gap-0", className)}>
46
- <DialogHeader className="px-4 py-3 border-b border-[var(--color-border)]">
46
+ <DialogHeader className="px-4 py-3 border-b border-[var(--bd-border)]">
47
47
  <DialogTitle className="sr-only">Search documentation</DialogTitle>
48
48
  <div className="flex items-center gap-3">
49
- <SearchIcon className="h-5 w-5 text-[var(--color-text-muted)]" />
49
+ <SearchIcon className="h-5 w-5 text-[var(--bd-text-muted)]" />
50
50
  <input
51
51
  type="text"
52
52
  placeholder="Search documentation..."
53
53
  value={query}
54
54
  onChange={(e) => setQuery(e.target.value)}
55
- className="flex-1 bg-transparent text-base outline-none placeholder:text-[var(--color-text-muted)]"
55
+ className="flex-1 bg-transparent text-base outline-none placeholder:text-[var(--bd-text-muted)]"
56
56
  autoFocus
57
57
  />
58
- <kbd className="hidden sm:inline-flex items-center gap-1 px-2 py-1 text-xs font-medium bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded text-[var(--color-text-muted)]">
58
+ <kbd className="hidden sm:inline-flex items-center gap-1 px-2 py-1 text-xs font-medium bg-[var(--bd-bg-subtle)] border border-[var(--bd-border)] rounded text-[var(--bd-text-muted)]">
59
59
  Esc
60
60
  </kbd>
61
61
  </div>
@@ -65,12 +65,12 @@ export function SearchDialog({ className }: SearchDialogProps) {
65
65
  <div id="pagefind-results" className="pagefind-ui" />
66
66
  ) : (
67
67
  <div className="flex flex-col items-center justify-center h-full py-12 text-center">
68
- <SearchIcon className="h-12 w-12 text-[var(--color-text-muted)] mb-4" />
69
- <p className="text-sm text-[var(--color-text-secondary)]">
68
+ <SearchIcon className="h-12 w-12 text-[var(--bd-text-muted)] mb-4" />
69
+ <p className="text-sm text-[var(--bd-text-secondary)]">
70
70
  Start typing to search the documentation
71
71
  </p>
72
- <p className="text-xs text-[var(--color-text-muted)] mt-2">
73
- Press <kbd className="px-1.5 py-0.5 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded text-xs">⌘K</kbd> to open search
72
+ <p className="text-xs text-[var(--bd-text-muted)] mt-2">
73
+ Press <kbd className="px-1.5 py-0.5 bg-[var(--bd-bg-subtle)] border border-[var(--bd-border)] rounded text-xs">⌘K</kbd> to open search
74
74
  </p>
75
75
  </div>
76
76
  )}
@@ -17,19 +17,19 @@ const filteredHeadings = headings.filter(h => h.depth >= 2 && h.depth <= 3);
17
17
 
18
18
  {filteredHeadings.length > 0 && (
19
19
  <div class="toc-container">
20
- <h4 class="text-xs font-semibold uppercase tracking-wider text-[var(--color-text-muted)] mb-4">
20
+ <h4 class="text-[11px] font-semibold uppercase tracking-widest text-[var(--bd-text-muted)] mb-3">
21
21
  On this page
22
22
  </h4>
23
23
  <nav class="relative">
24
- <div class="absolute left-0 top-0 bottom-0 w-px bg-[var(--color-border)]"></div>
25
- <ul class="space-y-1">
24
+ <div class="absolute left-0 top-0 bottom-0 w-px bg-[var(--bd-border)]"></div>
25
+ <ul class="space-y-0.5">
26
26
  {filteredHeadings.map((heading) => (
27
27
  <li class="relative">
28
28
  <a
29
29
  href={`#${heading.slug}`}
30
30
  class:list={[
31
- "toc-link block text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-all duration-150",
32
- heading.depth === 2 ? "py-1.5 pl-4" : "py-1 pl-6 text-xs"
31
+ "toc-link block text-[var(--bd-text-muted)] hover:text-[var(--bd-text)] transition-all duration-150",
32
+ heading.depth === 2 ? "py-1 pl-3.5 text-[13px]" : "py-0.5 pl-6 text-xs"
33
33
  ]}
34
34
  data-heading={heading.slug}
35
35
  >
@@ -33,7 +33,7 @@ export function ThemeToggle() {
33
33
  if (!mounted) {
34
34
  return (
35
35
  <button
36
- className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
36
+ className="p-2 rounded-lg hover:bg-[var(--bd-bg-hover)] transition-colors"
37
37
  aria-label="Toggle theme"
38
38
  >
39
39
  <div className="w-5 h-5" />
@@ -44,13 +44,13 @@ export function ThemeToggle() {
44
44
  return (
45
45
  <button
46
46
  onClick={toggleTheme}
47
- className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
47
+ className="p-2 rounded-lg hover:bg-[var(--bd-bg-hover)] transition-colors"
48
48
  aria-label={theme === "light" ? "Switch to dark mode" : "Switch to light mode"}
49
49
  >
50
50
  {theme === "light" ? (
51
- <Moon className="w-5 h-5 text-gray-600" />
51
+ <Moon className="w-5 h-5 text-[var(--bd-text-secondary)]" />
52
52
  ) : (
53
- <Sun className="w-5 h-5 text-gray-300" />
53
+ <Sun className="w-5 h-5 text-[var(--bd-text-secondary)]" />
54
54
  )}
55
55
  </button>
56
56
  );
@@ -0,0 +1,79 @@
1
+ import * as React from "react";
2
+ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
3
+
4
+ interface VersionConfig {
5
+ label: string;
6
+ path: string;
7
+ }
8
+
9
+ interface VersionSwitcherProps {
10
+ versions: VersionConfig[];
11
+ currentPath: string;
12
+ }
13
+
14
+ export function VersionSwitcher({ versions, currentPath }: VersionSwitcherProps) {
15
+ if (!versions || versions.length <= 1) return null;
16
+
17
+ const current = versions.find((v) => currentPath.includes(`/docs/${v.path}/`)) || versions[0];
18
+
19
+ function switchVersion(target: VersionConfig) {
20
+ const regex = /\/docs\/([^/]+)\//;
21
+ const match = currentPath.match(regex);
22
+ if (match) {
23
+ const newPath = currentPath.replace(`/docs/${match[1]}/`, `/docs/${target.path}/`);
24
+ window.location.href = newPath;
25
+ } else {
26
+ window.location.href = `/docs/${target.path}/`;
27
+ }
28
+ }
29
+
30
+ return (
31
+ <DropdownMenu.Root>
32
+ <DropdownMenu.Trigger asChild>
33
+ <button className="bd-version-trigger">
34
+ {current.label}
35
+ <svg
36
+ width="12"
37
+ height="12"
38
+ viewBox="0 0 24 24"
39
+ fill="none"
40
+ stroke="currentColor"
41
+ strokeWidth="2"
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ >
45
+ <path d="m6 9 6 6 6-6" />
46
+ </svg>
47
+ </button>
48
+ </DropdownMenu.Trigger>
49
+
50
+ <DropdownMenu.Portal>
51
+ <DropdownMenu.Content className="bd-version-menu" sideOffset={4} align="start">
52
+ {versions.map((v) => (
53
+ <DropdownMenu.Item
54
+ key={v.path}
55
+ className={`bd-version-item ${v.path === current.path ? "bd-version-active" : ""}`}
56
+ onSelect={() => switchVersion(v)}
57
+ >
58
+ {v.label}
59
+ {v.path === current.path && (
60
+ <svg
61
+ width="14"
62
+ height="14"
63
+ viewBox="0 0 24 24"
64
+ fill="none"
65
+ stroke="currentColor"
66
+ strokeWidth="2.5"
67
+ strokeLinecap="round"
68
+ strokeLinejoin="round"
69
+ >
70
+ <polyline points="20 6 9 17 4 12" />
71
+ </svg>
72
+ )}
73
+ </DropdownMenu.Item>
74
+ ))}
75
+ </DropdownMenu.Content>
76
+ </DropdownMenu.Portal>
77
+ </DropdownMenu.Root>
78
+ );
79
+ }
@@ -16,17 +16,17 @@ const methodColors = {
16
16
  };
17
17
  ---
18
18
 
19
- <div class="not-prose my-6 rounded-lg border border-[var(--color-border)] overflow-hidden">
20
- <div class="flex items-center gap-3 px-4 py-3 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border)]">
19
+ <div class="not-prose my-6 rounded-lg border border-[var(--bd-border)] overflow-hidden">
20
+ <div class="flex items-center gap-3 px-4 py-3 bg-[var(--bd-bg-subtle)] border-b border-[var(--bd-border)]">
21
21
  <span class:list={["px-2 py-1 text-xs font-bold rounded uppercase", methodColors[method]]}>
22
22
  {method}
23
23
  </span>
24
- <code class="text-sm font-mono text-[var(--color-text)]">{path}</code>
24
+ <code class="text-sm font-mono text-[var(--bd-text)]">{path}</code>
25
25
  </div>
26
26
 
27
27
  {description && (
28
- <div class="px-4 py-3 border-b border-[var(--color-border)]">
29
- <p class="text-sm text-[var(--color-text-secondary)]">{description}</p>
28
+ <div class="px-4 py-3 border-b border-[var(--bd-border)]">
29
+ <p class="text-sm text-[var(--bd-text-secondary)]">{description}</p>
30
30
  </div>
31
31
  )}
32
32
 
@@ -9,10 +9,10 @@ interface Props {
9
9
  const { name, type, required = false, description } = Astro.props;
10
10
  ---
11
11
 
12
- <div class="flex flex-col gap-1 py-3 border-b border-[var(--color-border)] last:border-b-0">
12
+ <div class="flex flex-col gap-1 py-3 border-b border-[var(--bd-border)] last:border-b-0">
13
13
  <div class="flex items-center gap-2">
14
- <code class="text-sm font-semibold text-[var(--color-text)]">{name}</code>
15
- <span class="text-xs text-[var(--color-text-secondary)]">{type}</span>
14
+ <code class="text-sm font-semibold text-[var(--bd-text)]">{name}</code>
15
+ <span class="text-xs text-[var(--bd-text-secondary)]">{type}</span>
16
16
  {required && (
17
17
  <span class="px-1.5 py-0.5 text-xs font-medium text-red-600 dark:text-red-400 bg-red-100 dark:bg-red-900/30 rounded">
18
18
  required
@@ -20,7 +20,7 @@ const { name, type, required = false, description } = Astro.props;
20
20
  )}
21
21
  </div>
22
22
  {description && (
23
- <p class="text-sm text-[var(--color-text-secondary)]">{description}</p>
23
+ <p class="text-sm text-[var(--bd-text-secondary)]">{description}</p>
24
24
  )}
25
25
  <slot />
26
26
  </div>
@@ -7,9 +7,9 @@ const { title = "Parameters" } = Astro.props;
7
7
  ---
8
8
 
9
9
  <div class="my-4 px-2">
10
- <h4 class="text-sm font-semibold text-[var(--color-text)] mb-2">{title}</h4>
11
- <div class="border border-[var(--color-border)] rounded-lg overflow-hidden">
12
- <div class="divide-y divide-[var(--color-border)] px-4">
10
+ <h4 class="text-sm font-semibold text-[var(--bd-text)] mb-2">{title}</h4>
11
+ <div class="border border-[var(--bd-border)] rounded-lg overflow-hidden">
12
+ <div class="divide-y divide-[var(--bd-border)] px-4">
13
13
  <slot />
14
14
  </div>
15
15
  </div>
@@ -26,10 +26,10 @@ const statusColor = statusColors[status] || "bg-gray-100 dark:bg-gray-900/30 tex
26
26
  {status}
27
27
  </span>
28
28
  {description && (
29
- <span class="text-sm text-[var(--color-text-secondary)]">{description}</span>
29
+ <span class="text-sm text-[var(--bd-text-secondary)]">{description}</span>
30
30
  )}
31
31
  </div>
32
- <div class="border border-[var(--color-border)] rounded-lg overflow-hidden px-2">
32
+ <div class="border border-[var(--bd-border)] rounded-lg overflow-hidden px-2">
33
33
  <slot />
34
34
  </div>
35
35
  </div>
@@ -24,6 +24,11 @@ export { Expandable, ExpandableList, ExpandableItem } from "./mdx/Expandable.tsx
24
24
  export { Icon, CheckIcon, XIcon, InfoIcon, WarningIcon } from "./mdx/Icon.tsx";
25
25
  export { Steps, Step } from "./mdx/Steps.tsx";
26
26
  export { Mermaid } from "./mdx/Mermaid.tsx";
27
+ export { ImageZoom } from "./mdx/ImageZoom.tsx";
28
+ export { Video } from "./mdx/Video.tsx";
29
+ export { ApiPlayground } from "./mdx/ApiPlayground.tsx";
30
+
31
+ export { VersionSwitcher } from "./VersionSwitcher.tsx";
27
32
 
28
33
  // Legacy exports for backwards compatibility
29
34
  export { Tabs, Tab } from "./mdx/Tabs.tsx";
@@ -11,21 +11,21 @@ export function Accordion({ title, icon, defaultOpen = false, children }: Accord
11
11
  const [isOpen, setIsOpen] = useState(defaultOpen);
12
12
 
13
13
  return (
14
- <div className="accordion-item border border-[var(--color-border)] rounded-xl overflow-hidden bg-[var(--color-bg)] my-3">
14
+ <div className="accordion-item border border-[var(--bd-border)] rounded-xl overflow-hidden bg-[var(--bd-bg)] my-3">
15
15
  <button
16
16
  type="button"
17
17
  onClick={() => setIsOpen(!isOpen)}
18
- className="w-full flex items-center gap-3 px-5 py-4 text-left hover:bg-[var(--color-bg-secondary)] transition-colors"
18
+ className="w-full flex items-center gap-3 px-5 py-4 text-left hover:bg-[var(--bd-bg-subtle)] transition-colors"
19
19
  aria-expanded={isOpen}
20
20
  >
21
21
  {icon && (
22
- <span className="flex items-center justify-center w-8 h-8 rounded-lg bg-[var(--color-bg-secondary)] text-lg shrink-0">
22
+ <span className="flex items-center justify-center w-8 h-8 rounded-lg bg-[var(--bd-bg-subtle)] text-lg shrink-0">
23
23
  {icon}
24
24
  </span>
25
25
  )}
26
- <span className="flex-1 font-medium text-[var(--color-text)]">{title}</span>
26
+ <span className="flex-1 font-medium text-[var(--bd-text)]">{title}</span>
27
27
  <svg
28
- className={`w-5 h-5 text-[var(--color-text-secondary)] transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
28
+ className={`w-5 h-5 text-[var(--bd-text-secondary)] transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
29
29
  fill="none"
30
30
  stroke="currentColor"
31
31
  viewBox="0 0 24 24"
@@ -38,7 +38,7 @@ export function Accordion({ title, icon, defaultOpen = false, children }: Accord
38
38
  isOpen ? "max-h-[1000px] opacity-100" : "max-h-0 opacity-0"
39
39
  }`}
40
40
  >
41
- <div className="px-5 pb-5 pt-1 text-sm text-[var(--color-text-secondary)] leading-relaxed">
41
+ <div className="px-5 pb-5 pt-1 text-sm text-[var(--bd-text-secondary)] leading-relaxed">
42
42
  {children}
43
43
  </div>
44
44
  </div>
@@ -0,0 +1,200 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../lib/utils.js";
3
+
4
+ interface ParamDef {
5
+ name: string;
6
+ in: "query" | "path" | "header" | "body";
7
+ type?: string;
8
+ required?: boolean;
9
+ defaultValue?: string;
10
+ description?: string;
11
+ }
12
+
13
+ interface ApiPlaygroundProps {
14
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
15
+ url: string;
16
+ params?: ParamDef[];
17
+ body?: string;
18
+ headers?: Record<string, string>;
19
+ className?: string;
20
+ }
21
+
22
+ const methodColors: Record<string, string> = {
23
+ GET: "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-300",
24
+ POST: "bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300",
25
+ PUT: "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300",
26
+ PATCH: "bg-orange-100 text-orange-800 dark:bg-orange-950 dark:text-orange-300",
27
+ DELETE: "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300",
28
+ };
29
+
30
+ export function ApiPlayground({
31
+ method = "GET",
32
+ url,
33
+ params = [],
34
+ body: initialBody,
35
+ headers: initialHeaders = {},
36
+ className,
37
+ }: ApiPlaygroundProps) {
38
+ const [paramValues, setParamValues] = React.useState<Record<string, string>>(() => {
39
+ const vals: Record<string, string> = {};
40
+ for (const p of params) {
41
+ vals[p.name] = p.defaultValue || "";
42
+ }
43
+ return vals;
44
+ });
45
+ const [bodyValue, setBodyValue] = React.useState(initialBody || "");
46
+ const [response, setResponse] = React.useState<{
47
+ status: number;
48
+ statusText: string;
49
+ body: string;
50
+ time: number;
51
+ } | null>(null);
52
+ const [loading, setLoading] = React.useState(false);
53
+ const [error, setError] = React.useState<string | null>(null);
54
+
55
+ function buildUrl(): string {
56
+ let finalUrl = url;
57
+ const queryParams = new URLSearchParams();
58
+
59
+ for (const p of params) {
60
+ const val = paramValues[p.name] || "";
61
+ if (p.in === "path") {
62
+ finalUrl = finalUrl.replace(`{${p.name}}`, encodeURIComponent(val));
63
+ } else if (p.in === "query" && val) {
64
+ queryParams.set(p.name, val);
65
+ }
66
+ }
67
+
68
+ const qs = queryParams.toString();
69
+ return qs ? `${finalUrl}?${qs}` : finalUrl;
70
+ }
71
+
72
+ async function sendRequest() {
73
+ setLoading(true);
74
+ setError(null);
75
+ setResponse(null);
76
+
77
+ const start = performance.now();
78
+ const reqUrl = buildUrl();
79
+
80
+ const headers: Record<string, string> = { ...initialHeaders };
81
+ for (const p of params) {
82
+ if (p.in === "header" && paramValues[p.name]) {
83
+ headers[p.name] = paramValues[p.name];
84
+ }
85
+ }
86
+
87
+ const hasBody = ["POST", "PUT", "PATCH"].includes(method) && bodyValue;
88
+ if (hasBody && !headers["Content-Type"]) {
89
+ headers["Content-Type"] = "application/json";
90
+ }
91
+
92
+ try {
93
+ const res = await fetch(reqUrl, {
94
+ method,
95
+ headers,
96
+ body: hasBody ? bodyValue : undefined,
97
+ });
98
+
99
+ const elapsed = Math.round(performance.now() - start);
100
+ let text: string;
101
+ try {
102
+ const json = await res.json();
103
+ text = JSON.stringify(json, null, 2);
104
+ } catch {
105
+ text = await res.text();
106
+ }
107
+
108
+ setResponse({ status: res.status, statusText: res.statusText, body: text, time: elapsed });
109
+ } catch (err) {
110
+ setError(err instanceof Error ? err.message : "Request failed");
111
+ } finally {
112
+ setLoading(false);
113
+ }
114
+ }
115
+
116
+ return (
117
+ <div className={cn("bd-playground", className)}>
118
+ {/* Header */}
119
+ <div className="bd-playground-header">
120
+ <span className={cn("bd-playground-method", methodColors[method])}>
121
+ {method}
122
+ </span>
123
+ <code className="bd-playground-url">{url}</code>
124
+ <button
125
+ className="bd-playground-send"
126
+ onClick={sendRequest}
127
+ disabled={loading}
128
+ >
129
+ {loading ? "Sending..." : "Send"}
130
+ </button>
131
+ </div>
132
+
133
+ {/* Parameters */}
134
+ {params.length > 0 && (
135
+ <div className="bd-playground-params">
136
+ {params.map((p) => (
137
+ <div key={p.name} className="bd-playground-param">
138
+ <label className="bd-playground-label">
139
+ <span>{p.name}</span>
140
+ <span className="bd-playground-param-meta">
141
+ {p.in}
142
+ {p.required && <span className="bd-playground-required">*</span>}
143
+ </span>
144
+ </label>
145
+ <input
146
+ type="text"
147
+ className="bd-playground-input"
148
+ value={paramValues[p.name] || ""}
149
+ placeholder={p.description || p.type || ""}
150
+ onChange={(e) =>
151
+ setParamValues((prev) => ({ ...prev, [p.name]: e.target.value }))
152
+ }
153
+ />
154
+ </div>
155
+ ))}
156
+ </div>
157
+ )}
158
+
159
+ {/* Body */}
160
+ {["POST", "PUT", "PATCH"].includes(method) && (
161
+ <div className="bd-playground-body-section">
162
+ <label className="bd-playground-label">Request Body</label>
163
+ <textarea
164
+ className="bd-playground-textarea"
165
+ value={bodyValue}
166
+ onChange={(e) => setBodyValue(e.target.value)}
167
+ rows={6}
168
+ placeholder='{ "key": "value" }'
169
+ />
170
+ </div>
171
+ )}
172
+
173
+ {/* Response */}
174
+ {(response || error) && (
175
+ <div className="bd-playground-response">
176
+ {error ? (
177
+ <div className="bd-playground-error">{error}</div>
178
+ ) : response ? (
179
+ <>
180
+ <div className="bd-playground-response-header">
181
+ <span
182
+ className={cn(
183
+ "bd-playground-status",
184
+ response.status < 300 ? "bd-status-ok" : "bd-status-err"
185
+ )}
186
+ >
187
+ {response.status} {response.statusText}
188
+ </span>
189
+ <span className="bd-playground-time">{response.time}ms</span>
190
+ </div>
191
+ <pre className="bd-playground-pre">
192
+ <code>{response.body}</code>
193
+ </pre>
194
+ </>
195
+ ) : null}
196
+ </div>
197
+ )}
198
+ </div>
199
+ );
200
+ }
@@ -13,7 +13,7 @@ const badgeVariants = cva(
13
13
  warning: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300",
14
14
  error: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300",
15
15
  info: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300",
16
- outline: "border border-[var(--color-border)] text-[var(--color-text-secondary)]",
16
+ outline: "border border-[var(--bd-border)] text-[var(--bd-text-secondary)]",
17
17
  },
18
18
  },
19
19
  defaultVariants: {
@@ -8,34 +8,34 @@ const { type = "info", title } = Astro.props;
8
8
 
9
9
  const styles = {
10
10
  info: {
11
- container: "bg-blue-50/70 dark:bg-blue-950/30 border-l-blue-500",
12
- iconBg: "bg-blue-100 dark:bg-blue-900/50",
11
+ container: "bg-blue-50/70 dark:bg-blue-500/10 border-l-blue-500",
12
+ iconBg: "bg-blue-100 dark:bg-blue-500/20",
13
13
  icon: "text-blue-600 dark:text-blue-400",
14
- title: "text-blue-900 dark:text-blue-100",
14
+ title: "text-blue-900 dark:text-blue-200",
15
15
  },
16
16
  note: {
17
- container: "bg-gray-50/70 dark:bg-gray-950/30 border-l-gray-400",
18
- iconBg: "bg-gray-100 dark:bg-gray-900/50",
17
+ container: "bg-gray-50/70 dark:bg-gray-500/10 border-l-gray-400",
18
+ iconBg: "bg-gray-100 dark:bg-gray-500/20",
19
19
  icon: "text-gray-600 dark:text-gray-400",
20
- title: "text-gray-900 dark:text-gray-100",
20
+ title: "text-gray-900 dark:text-gray-200",
21
21
  },
22
22
  warning: {
23
- container: "bg-orange-50/70 dark:bg-orange-950/30 border-l-orange-500",
24
- iconBg: "bg-orange-100 dark:bg-orange-900/50",
23
+ container: "bg-orange-50/70 dark:bg-orange-500/10 border-l-orange-500",
24
+ iconBg: "bg-orange-100 dark:bg-orange-500/20",
25
25
  icon: "text-orange-600 dark:text-orange-400",
26
- title: "text-orange-900 dark:text-orange-100",
26
+ title: "text-orange-900 dark:text-orange-200",
27
27
  },
28
28
  tip: {
29
- container: "bg-green-50/70 dark:bg-green-950/30 border-l-green-500",
30
- iconBg: "bg-green-100 dark:bg-green-900/50",
29
+ container: "bg-green-50/70 dark:bg-green-500/10 border-l-green-500",
30
+ iconBg: "bg-green-100 dark:bg-green-500/20",
31
31
  icon: "text-green-600 dark:text-green-400",
32
- title: "text-green-900 dark:text-green-100",
32
+ title: "text-green-900 dark:text-green-200",
33
33
  },
34
34
  danger: {
35
- container: "bg-red-50/70 dark:bg-red-950/30 border-l-red-500",
36
- iconBg: "bg-red-100 dark:bg-red-900/50",
35
+ container: "bg-red-50/70 dark:bg-red-500/10 border-l-red-500",
36
+ iconBg: "bg-red-100 dark:bg-red-500/20",
37
37
  icon: "text-red-600 dark:text-red-400",
38
- title: "text-red-900 dark:text-red-100",
38
+ title: "text-red-900 dark:text-red-200",
39
39
  },
40
40
  };
41
41
 
@@ -60,17 +60,17 @@ const icon = icons[type];
60
60
  const displayTitle = title || defaultTitles[type];
61
61
  ---
62
62
 
63
- <div class:list={["not-prose my-4 rounded-md border-l-4 overflow-hidden", style.container]}>
64
- <div class="p-3">
63
+ <div class:list={["not-prose my-5 rounded-lg border-l-4 overflow-hidden", style.container]}>
64
+ <div class="px-4 py-3.5">
65
65
  <div class="flex items-start gap-3">
66
- <div class:list={["shrink-0 flex items-center justify-center w-6 h-6 rounded", style.iconBg]}>
66
+ <div class:list={["shrink-0 flex items-center justify-center w-6 h-6 rounded-md", style.iconBg]}>
67
67
  <div class:list={[style.icon]} set:html={icon} />
68
68
  </div>
69
69
  <div class="flex-1 min-w-0">
70
70
  {displayTitle && (
71
- <p class:list={["font-semibold text-sm mb-1", style.title]}>{displayTitle}</p>
71
+ <p class:list={["font-semibold text-[13px] mb-1", style.title]}>{displayTitle}</p>
72
72
  )}
73
- <div class="text-sm text-[var(--color-text-secondary)] leading-relaxed [&>p]:m-0 [&>p+p]:mt-1">
73
+ <div class="text-[13px] text-zinc-700 dark:text-zinc-300 leading-relaxed [&>p]:m-0 [&>p+p]:mt-1.5">
74
74
  <slot />
75
75
  </div>
76
76
  </div>
@@ -13,27 +13,27 @@ const Tag = href ? "a" : "div";
13
13
  <Tag
14
14
  href={href}
15
15
  class:list={[
16
- "card-component not-prose group block p-4 rounded-lg border border-[var(--color-border)] bg-[var(--color-bg)] no-underline",
16
+ "card-component not-prose group block p-4 rounded-lg border border-[var(--bd-border)] bg-[var(--bd-bg)] no-underline",
17
17
  href && "cursor-pointer hover:border-primary-300 dark:hover:border-primary-600 hover:shadow-md transition-all duration-200"
18
18
  ]}
19
19
  style={href ? "cursor: pointer;" : ""}
20
20
  >
21
21
  <div class="flex flex-col gap-3">
22
22
  {icon && (
23
- <div class="flex items-center justify-center w-10 h-10 rounded-lg bg-[var(--color-bg-secondary)] group-hover:bg-primary-50 dark:group-hover:bg-primary-950/50 transition-colors">
23
+ <div class="flex items-center justify-center w-10 h-10 rounded-lg bg-[var(--bd-bg-subtle)] group-hover:bg-primary-50 dark:group-hover:bg-primary-950/50 transition-colors">
24
24
  <span class="text-xl">{icon}</span>
25
25
  </div>
26
26
  )}
27
27
  <div class="flex-1 min-w-0">
28
28
  <div class="flex items-center gap-2">
29
- <h3 class="font-semibold text-[var(--color-text)] group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors">{title}</h3>
29
+ <h3 class="font-semibold text-[var(--bd-text)] group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors">{title}</h3>
30
30
  {href && (
31
- <svg class="w-4 h-4 text-[var(--color-text-muted)] opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
31
+ <svg class="w-4 h-4 text-[var(--bd-text-muted)] opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
32
32
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
33
33
  </svg>
34
34
  )}
35
35
  </div>
36
- <div class="mt-1 text-sm text-[var(--color-text-secondary)] group-hover:text-[var(--color-text)] leading-relaxed transition-colors">
36
+ <div class="mt-1 text-sm text-[var(--bd-text-secondary)] group-hover:text-[var(--bd-text)] leading-relaxed transition-colors">
37
37
  <slot />
38
38
  </div>
39
39
  </div>