boltdocs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/CodeBlock-37XMKCYY.mjs +7 -0
  2. package/dist/PackageManagerTabs-4NWXLXQO.mjs +314 -0
  3. package/dist/Playground-OE2OE6B6.mjs +7 -0
  4. package/dist/SearchDialog-FTOQZ763.mjs +187 -0
  5. package/dist/SearchDialog-ZAZXYIFX.css +2147 -0
  6. package/dist/Video-I6QY4X7J.mjs +7 -0
  7. package/dist/chunk-2YRDWM6O.mjs +56 -0
  8. package/dist/chunk-PN4GCTYG.mjs +67 -0
  9. package/dist/chunk-X2TDGMTR.mjs +64 -0
  10. package/dist/chunk-X6BYQHVC.mjs +12 -0
  11. package/dist/chunk-Z7JHYNAS.mjs +57 -0
  12. package/dist/chunk-ZFCOLEXN.mjs +1644 -0
  13. package/dist/client/index.css +2147 -0
  14. package/dist/client/index.d.mts +298 -0
  15. package/dist/client/index.d.ts +298 -0
  16. package/dist/client/index.js +2793 -0
  17. package/dist/client/index.mjs +63 -0
  18. package/dist/client/ssr.css +2147 -0
  19. package/dist/client/ssr.d.mts +25 -0
  20. package/dist/client/ssr.d.ts +25 -0
  21. package/dist/client/ssr.js +2727 -0
  22. package/dist/client/ssr.mjs +32 -0
  23. package/dist/config-D2XmHJYe.d.mts +122 -0
  24. package/dist/config-D2XmHJYe.d.ts +122 -0
  25. package/dist/index-CRQKWAeo.d.mts +82 -0
  26. package/dist/index-CRQKWAeo.d.ts +82 -0
  27. package/dist/node/cli/index.d.mts +1 -0
  28. package/dist/node/cli/index.d.ts +1 -0
  29. package/dist/node/cli/index.js +199 -0
  30. package/dist/node/cli/index.mjs +154 -0
  31. package/dist/node/index.d.mts +79 -0
  32. package/dist/node/index.d.ts +79 -0
  33. package/dist/node/index.js +797 -0
  34. package/dist/node/index.mjs +719 -0
  35. package/package.json +79 -0
  36. package/src/client/app/index.tsx +422 -0
  37. package/src/client/app/preload.tsx +56 -0
  38. package/src/client/index.ts +40 -0
  39. package/src/client/ssr.tsx +50 -0
  40. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -0
  41. package/src/client/theme/components/CodeBlock/index.ts +1 -0
  42. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -0
  43. package/src/client/theme/components/PackageManagerTabs/index.ts +1 -0
  44. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -0
  45. package/src/client/theme/components/Playground/Playground.tsx +86 -0
  46. package/src/client/theme/components/Playground/index.ts +1 -0
  47. package/src/client/theme/components/Playground/playground.css +168 -0
  48. package/src/client/theme/components/Video/Video.tsx +84 -0
  49. package/src/client/theme/components/Video/index.ts +1 -0
  50. package/src/client/theme/components/Video/video.css +41 -0
  51. package/src/client/theme/components/mdx/Admonition.tsx +80 -0
  52. package/src/client/theme/components/mdx/Badge.tsx +31 -0
  53. package/src/client/theme/components/mdx/Button.tsx +50 -0
  54. package/src/client/theme/components/mdx/Card.tsx +80 -0
  55. package/src/client/theme/components/mdx/List.tsx +57 -0
  56. package/src/client/theme/components/mdx/Tabs.tsx +94 -0
  57. package/src/client/theme/components/mdx/index.ts +18 -0
  58. package/src/client/theme/components/mdx/mdx-components.css +405 -0
  59. package/src/client/theme/icons/bun.tsx +62 -0
  60. package/src/client/theme/icons/deno.tsx +20 -0
  61. package/src/client/theme/icons/discord.tsx +12 -0
  62. package/src/client/theme/icons/github.tsx +15 -0
  63. package/src/client/theme/icons/npm.tsx +13 -0
  64. package/src/client/theme/icons/pnpm.tsx +72 -0
  65. package/src/client/theme/icons/twitter.tsx +12 -0
  66. package/src/client/theme/styles/home.css +60 -0
  67. package/src/client/theme/styles/markdown.css +343 -0
  68. package/src/client/theme/styles/variables.css +162 -0
  69. package/src/client/theme/styles.css +38 -0
  70. package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -0
  71. package/src/client/theme/ui/BackgroundGradient/index.ts +1 -0
  72. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -0
  73. package/src/client/theme/ui/Breadcrumbs/index.ts +1 -0
  74. package/src/client/theme/ui/Footer/footer.css +32 -0
  75. package/src/client/theme/ui/Head/Head.tsx +69 -0
  76. package/src/client/theme/ui/Head/index.ts +1 -0
  77. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -0
  78. package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -0
  79. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -0
  80. package/src/client/theme/ui/Layout/Layout.tsx +213 -0
  81. package/src/client/theme/ui/Layout/base.css +76 -0
  82. package/src/client/theme/ui/Layout/index.ts +2 -0
  83. package/src/client/theme/ui/Layout/pagination.css +72 -0
  84. package/src/client/theme/ui/Layout/responsive.css +40 -0
  85. package/src/client/theme/ui/Link/Link.tsx +202 -0
  86. package/src/client/theme/ui/Link/index.ts +2 -0
  87. package/src/client/theme/ui/Loading/Loading.tsx +10 -0
  88. package/src/client/theme/ui/Loading/index.ts +1 -0
  89. package/src/client/theme/ui/Loading/loading.css +30 -0
  90. package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -0
  91. package/src/client/theme/ui/Navbar/Navbar.tsx +145 -0
  92. package/src/client/theme/ui/Navbar/index.ts +2 -0
  93. package/src/client/theme/ui/Navbar/navbar.css +233 -0
  94. package/src/client/theme/ui/NotFound/NotFound.tsx +20 -0
  95. package/src/client/theme/ui/NotFound/index.ts +1 -0
  96. package/src/client/theme/ui/NotFound/not-found.css +64 -0
  97. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +192 -0
  98. package/src/client/theme/ui/OnThisPage/index.ts +1 -0
  99. package/src/client/theme/ui/OnThisPage/toc.css +132 -0
  100. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -0
  101. package/src/client/theme/ui/PoweredBy/index.ts +1 -0
  102. package/src/client/theme/ui/PoweredBy/powered-by.css +76 -0
  103. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -0
  104. package/src/client/theme/ui/SearchDialog/index.ts +1 -0
  105. package/src/client/theme/ui/SearchDialog/search.css +152 -0
  106. package/src/client/theme/ui/Sidebar/Sidebar.tsx +200 -0
  107. package/src/client/theme/ui/Sidebar/index.ts +1 -0
  108. package/src/client/theme/ui/Sidebar/sidebar.css +269 -0
  109. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -0
  110. package/src/client/theme/ui/ThemeToggle/index.ts +1 -0
  111. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -0
  112. package/src/client/theme/ui/VersionSwitcher/index.ts +1 -0
  113. package/src/client/utils.ts +26 -0
  114. package/src/node/cache.ts +94 -0
  115. package/src/node/cli/commands/config.ts +15 -0
  116. package/src/node/cli/commands/generate-css.ts +24 -0
  117. package/src/node/cli/constants.ts +70 -0
  118. package/src/node/cli/index.ts +22 -0
  119. package/src/node/config.ts +185 -0
  120. package/src/node/index.ts +21 -0
  121. package/src/node/mdx.ts +41 -0
  122. package/src/node/plugin/entry.ts +58 -0
  123. package/src/node/plugin/html.ts +55 -0
  124. package/src/node/plugin/index.ts +190 -0
  125. package/src/node/plugin/types.ts +11 -0
  126. package/src/node/routes/cache.ts +24 -0
  127. package/src/node/routes/index.ts +152 -0
  128. package/src/node/routes/parser.ts +127 -0
  129. package/src/node/routes/sorter.ts +42 -0
  130. package/src/node/routes/types.ts +49 -0
  131. package/src/node/ssg/index.ts +110 -0
  132. package/src/node/ssg/meta.ts +34 -0
  133. package/src/node/ssg/options.ts +13 -0
  134. package/src/node/ssg/sitemap.ts +54 -0
  135. package/src/node/utils.ts +134 -0
  136. package/tsconfig.json +20 -0
  137. package/tsup.config.ts +22 -0
@@ -0,0 +1,76 @@
1
+ import React, { useState, useRef, useCallback } from "react";
2
+ import { Copy, Check } from "lucide-react";
3
+
4
+ interface CodeBlockProps {
5
+ children?: React.ReactNode;
6
+ className?: string;
7
+ [key: string]: any;
8
+ }
9
+
10
+ /**
11
+ * A specialized wrapper for code snippets compiled from MDX blocks.
12
+ * Provides syntax highlighting styling scaffolding and a "Copy to Clipboard" button.
13
+ */
14
+ export function CodeBlock({ children, ...props }: CodeBlockProps) {
15
+ const [copied, setCopied] = useState(false);
16
+ const preRef = useRef<HTMLPreElement>(null);
17
+
18
+ // Extract language from the child <code> element's data-language or className
19
+ let language = "";
20
+ if (React.isValidElement(children)) {
21
+ const childProps = children.props as any;
22
+ language = childProps?.["data-language"] || "";
23
+ if (!language && childProps?.className) {
24
+ const match = childProps.className.match(/language-(\w+)/);
25
+ if (match) language = match[1];
26
+ }
27
+ }
28
+
29
+ const handleCopy = useCallback(async () => {
30
+ const code = preRef.current?.textContent || "";
31
+ try {
32
+ await navigator.clipboard.writeText(code);
33
+ setCopied(true);
34
+ setTimeout(() => setCopied(false), 2000);
35
+ } catch {
36
+ // Fallback
37
+ const textarea = document.createElement("textarea");
38
+ textarea.value = code;
39
+ textarea.style.position = "fixed";
40
+ textarea.style.opacity = "0";
41
+ document.body.appendChild(textarea);
42
+ textarea.select();
43
+ document.execCommand("copy");
44
+ document.body.removeChild(textarea);
45
+ setCopied(true);
46
+ setTimeout(() => setCopied(false), 2000);
47
+ }
48
+ }, []);
49
+
50
+ return (
51
+ <div className="code-block-wrapper">
52
+ <div className="code-block-header">
53
+ <span className="code-block-lang">{language || "code"}</span>
54
+ <button
55
+ className={`code-block-copy ${copied ? "copied" : ""}`}
56
+ onClick={handleCopy}
57
+ type="button"
58
+ aria-label="Copy code"
59
+ >
60
+ {copied ? (
61
+ <>
62
+ <Check size={20} />
63
+ </>
64
+ ) : (
65
+ <>
66
+ <Copy size={20} />
67
+ </>
68
+ )}
69
+ </button>
70
+ </div>
71
+ <pre ref={preRef} {...props}>
72
+ {children}
73
+ </pre>
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1 @@
1
+ export { CodeBlock } from "./CodeBlock";
@@ -0,0 +1,154 @@
1
+ import { useState, useCallback } from "react";
2
+ import { Copy, Check } from "lucide-react";
3
+ import { NPM } from "../../icons/npm";
4
+ import { Pnpm } from "../../icons/pnpm";
5
+ import { Bun } from "../../icons/bun";
6
+ import { Deno } from "../../icons/deno";
7
+
8
+ interface PackageManagerTabsProps {
9
+ command: string;
10
+ pkg?: string;
11
+ className?: string;
12
+ }
13
+
14
+ type PackageManager = "npm" | "pnpm" | "bun" | "deno";
15
+
16
+ const MANAGERS: {
17
+ id: PackageManager;
18
+ label: string;
19
+ icon: any;
20
+ }[] = [
21
+ { id: "npm", label: "npm", icon: NPM },
22
+ { id: "pnpm", label: "pnpm", icon: Pnpm },
23
+ { id: "bun", label: "bun", icon: Bun },
24
+ { id: "deno", label: "deno", icon: Deno },
25
+ ];
26
+
27
+ /**
28
+ * Returns the exact command for a specific package manager based on the action.
29
+ * Maps generic actions like 'install' to their specific permutations (e.g., yarn add, npm install).
30
+ */
31
+ function getCommandForManager(
32
+ manager: PackageManager,
33
+ command: string,
34
+ pkg: string,
35
+ ): string {
36
+ const isInstall =
37
+ command === "install" || command === "add" || command === "i";
38
+ const isCreate = command === "create" || command === "init";
39
+ const isRun = command === "run" || command === "exec";
40
+
41
+ // Installation commands
42
+ if (isInstall) {
43
+ const pkgArgs = pkg ? ` ${pkg}` : "";
44
+ if (manager === "npm") return `npm install${pkgArgs}`;
45
+ if (manager === "pnpm") return pkg ? `pnpm add${pkgArgs}` : `pnpm install`;
46
+ if (manager === "bun") return pkg ? `bun add${pkgArgs}` : `bun install`;
47
+ if (manager === "deno")
48
+ return pkg ? `deno install npm:${pkg}` : `deno install`;
49
+ }
50
+
51
+ // Create/Init commands
52
+ if (isCreate) {
53
+ const pkgArgs = pkg ? ` ${pkg}` : "";
54
+ if (manager === "npm") return `npm create${pkgArgs}`;
55
+ if (manager === "pnpm") return `pnpm create${pkgArgs}`;
56
+ if (manager === "bun") return `bun create${pkgArgs}`;
57
+ if (manager === "deno") return `deno run -A npm:create-${pkg}`; // Approximation
58
+ }
59
+
60
+ // Run/Exec commands
61
+ if (isRun) {
62
+ const pkgArgs = pkg ? ` ${pkg}` : "";
63
+ if (manager === "npm") return `npm run${pkgArgs}`;
64
+ if (manager === "pnpm") return `pnpm run${pkgArgs}`;
65
+ if (manager === "bun") return `bun run${pkgArgs}`;
66
+ if (manager === "deno") return `deno task ${pkg}`;
67
+ }
68
+
69
+ // Fallback: just prefix the manager
70
+ const pkgArgs = pkg ? ` ${pkg}` : "";
71
+ return `${manager} ${command}${pkgArgs}`;
72
+ }
73
+
74
+ export function PackageManagerTabs({
75
+ command,
76
+ pkg = "",
77
+ className = "",
78
+ }: PackageManagerTabsProps) {
79
+ const [activeTab, setActiveTab] = useState<PackageManager>("npm");
80
+ const [copied, setCopied] = useState(false);
81
+
82
+ const activeCommand = getCommandForManager(activeTab, command, pkg);
83
+
84
+ const handleCopy = useCallback(async () => {
85
+ try {
86
+ await navigator.clipboard.writeText(activeCommand);
87
+ setCopied(true);
88
+ setTimeout(() => setCopied(false), 2000);
89
+ } catch {
90
+ const textarea = document.createElement("textarea");
91
+ textarea.value = activeCommand;
92
+ textarea.style.position = "fixed";
93
+ textarea.style.opacity = "0";
94
+ document.body.appendChild(textarea);
95
+ textarea.select();
96
+ document.execCommand("copy");
97
+ document.body.removeChild(textarea);
98
+ setCopied(true);
99
+ setTimeout(() => setCopied(false), 2000);
100
+ }
101
+ }, [activeCommand]);
102
+
103
+ return (
104
+ <div className={`pkg-tabs-wrapper ${className}`}>
105
+ {/* Tab Headers */}
106
+ <div className="pkg-tabs-header">
107
+ {MANAGERS.map((mgr) => {
108
+ const Icon = mgr.icon;
109
+ const isActive = activeTab === mgr.id;
110
+ return (
111
+ <button
112
+ key={mgr.id}
113
+ className={`pkg-tab-btn ${isActive ? "active" : ""}`}
114
+ onClick={() => setActiveTab(mgr.id)}
115
+ aria-selected={isActive}
116
+ role="tab"
117
+ >
118
+ <Icon className="pkg-tab-icon" width="16" height="16" />
119
+ <span>{mgr.label}</span>
120
+ </button>
121
+ );
122
+ })}
123
+ </div>
124
+
125
+ {/* Code Block Content */}
126
+ <div className="code-block-wrapper pkg-tabs-content">
127
+ <div className="code-block-header">
128
+ <span className="code-block-lang">bash</span>
129
+ <button
130
+ className={`code-block-copy ${copied ? "copied" : ""}`}
131
+ onClick={handleCopy}
132
+ type="button"
133
+ aria-label="Copy code"
134
+ >
135
+ {copied ? (
136
+ <>
137
+ <Check size={12} />
138
+ </>
139
+ ) : (
140
+ <>
141
+ <Copy size={12} />
142
+ </>
143
+ )}
144
+ </button>
145
+ </div>
146
+ <pre>
147
+ <code>
148
+ <span className="line">{activeCommand}</span>
149
+ </code>
150
+ </pre>
151
+ </div>
152
+ </div>
153
+ );
154
+ }
@@ -0,0 +1 @@
1
+ export { PackageManagerTabs } from "./PackageManagerTabs";
@@ -0,0 +1,64 @@
1
+
2
+ /* ═══════════════════════════════════════════════════════════
3
+ PACKAGE MANAGER TABS
4
+ ═══════════════════════════════════════════════════════════ */
5
+ .pkg-tabs-wrapper {
6
+ margin: 1.5rem 0;
7
+ }
8
+
9
+ .pkg-tabs-header {
10
+ display: flex;
11
+ gap: 0.25rem;
12
+ overflow-x: auto;
13
+ padding-bottom: 0.5rem;
14
+ margin-bottom: -0.5rem; /* pull down to meet code block */
15
+ scrollbar-width: none;
16
+ }
17
+
18
+ .pkg-tabs-header::-webkit-scrollbar {
19
+ display: none;
20
+ }
21
+
22
+ .pkg-tab-btn {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ gap: 0.4rem;
26
+ padding: 0.5rem 1rem;
27
+ background-color: var(--ld-bg-soft);
28
+ border: 1px solid var(--ld-border-subtle);
29
+ border-bottom: none;
30
+ border-radius: var(--ld-radius-md) var(--ld-radius-md) 0 0;
31
+ color: var(--ld-text-muted);
32
+ font-family: var(--ld-font-sans);
33
+ font-size: 0.8125rem;
34
+ font-weight: 500;
35
+ cursor: pointer;
36
+ transition: all 0.2s;
37
+ opacity: 0.7;
38
+ }
39
+
40
+ .pkg-tab-btn:hover {
41
+ color: var(--ld-text-main);
42
+ background-color: var(--ld-bg-mute);
43
+ opacity: 1;
44
+ }
45
+
46
+ .pkg-tab-btn.active {
47
+ background-color: var(--ld-code-bg);
48
+ color: var(--ld-text-main);
49
+ border-color: var(--ld-border-subtle);
50
+ opacity: 1;
51
+ position: relative;
52
+ z-index: 2;
53
+ margin-bottom: -1px; /* Overlap border */
54
+ }
55
+
56
+ .pkg-tab-icon {
57
+ display: inline-block;
58
+ }
59
+
60
+ /* Ensure tab content sits seamlessly below the tabs */
61
+ .pkg-tabs-content {
62
+ margin-top: 0 !important;
63
+ border-top-left-radius: 0 !important;
64
+ }
@@ -0,0 +1,86 @@
1
+ import React, { useState } from "react";
2
+ import { LiveProvider, LiveEditor, LiveError, LivePreview } from "react-live";
3
+ import { Copy, Check, Terminal, Play } from "lucide-react";
4
+
5
+ interface PlaygroundProps {
6
+ code?: string;
7
+ children?: string | React.ReactNode;
8
+ scope?: Record<string, any>;
9
+ readonly?: boolean;
10
+ }
11
+
12
+ /**
13
+ * A live React playground component.
14
+ * Features a split layout with a live editor and a preview section.
15
+ */
16
+ export function Playground({
17
+ code,
18
+ children,
19
+ scope = {},
20
+ readonly = false,
21
+ }: PlaygroundProps) {
22
+ // Extract code from either `code` prop or `children`
23
+ let initialCode = code || "";
24
+ if (!initialCode && typeof children === "string") {
25
+ initialCode = children;
26
+ }
27
+
28
+ const [copied, setCopied] = useState(false);
29
+ const [activeCode, setActiveCode] = useState(initialCode.trim());
30
+
31
+ const handleCopy = () => {
32
+ navigator.clipboard.writeText(activeCode);
33
+ setCopied(true);
34
+ setTimeout(() => setCopied(false), 2000);
35
+ };
36
+
37
+ // Provide React generically
38
+ const extendedScope = { React, ...scope };
39
+
40
+ return (
41
+ <div className="boltdocs-playground" data-readonly={readonly}>
42
+ <LiveProvider
43
+ code={activeCode}
44
+ scope={extendedScope}
45
+ theme={undefined}
46
+ noInline={false}
47
+ >
48
+ <div className="playground-split-container">
49
+ {/* Editor Side */}
50
+ <div className="playground-panel playground-editor-panel">
51
+ <div className="playground-panel-header">
52
+ <div className="playground-panel-title">
53
+ <Terminal size={14} />
54
+ <span>{readonly ? "Code Example" : "Live Editor"}</span>
55
+ </div>
56
+ <button
57
+ className="playground-copy-btn"
58
+ onClick={handleCopy}
59
+ title="Copy code"
60
+ >
61
+ {copied ? <Check size={14} /> : <Copy size={14} />}
62
+ </button>
63
+ </div>
64
+ <div className="playground-panel-content playground-editor">
65
+ <LiveEditor disabled={readonly} onChange={setActiveCode} />
66
+ </div>
67
+ </div>
68
+
69
+ {/* Preview Side */}
70
+ <div className="playground-panel playground-preview-panel">
71
+ <div className="playground-panel-header">
72
+ <div className="playground-panel-title">
73
+ <Play size={14} />
74
+ <span>Preview</span>
75
+ </div>
76
+ </div>
77
+ <div className="playground-panel-content playground-preview">
78
+ <LivePreview />
79
+ <LiveError className="playground-error" />
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </LiveProvider>
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1 @@
1
+ export { Playground } from "./Playground";
@@ -0,0 +1,168 @@
1
+ /* ═══════════════════════════════════════════════════════════
2
+ PLAYGROUND (SPLIT UI)
3
+ ═══════════════════════════════════════════════════════════ */
4
+ .boltdocs-playground {
5
+ display: flex;
6
+ flex-direction: column;
7
+ margin: 2rem 0;
8
+ border-radius: var(--ld-radius-lg);
9
+ border: 1px solid var(--ld-border-subtle);
10
+ background: var(--ld-code-bg);
11
+ overflow: hidden;
12
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
13
+ backdrop-filter: blur(10px);
14
+ }
15
+
16
+ .playground-split-container {
17
+ display: flex;
18
+ flex-direction: column;
19
+ width: 100%;
20
+ }
21
+
22
+ @media (min-width: 1024px) {
23
+ .playground-split-container {
24
+ flex-direction: row;
25
+ min-height: 350px;
26
+ align-items: stretch;
27
+ }
28
+ }
29
+
30
+ .playground-panel {
31
+ display: flex;
32
+ flex-direction: column;
33
+ flex: 1;
34
+ min-width: 0;
35
+ }
36
+
37
+ .playground-editor-panel {
38
+ border-bottom: 1px solid var(--ld-border-subtle);
39
+ background: var(--ld-code-bg);
40
+ }
41
+
42
+ @media (min-width: 1024px) {
43
+ .playground-editor-panel {
44
+ border-bottom: none;
45
+ border-right: 1px solid var(--ld-border-subtle);
46
+ }
47
+ }
48
+
49
+ .playground-preview-panel {
50
+ background: var(--ld-bg-mute);
51
+ position: relative;
52
+ }
53
+
54
+ /* Header */
55
+ .playground-panel-header {
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: space-between;
59
+ padding: 0.5rem 1rem;
60
+ border-bottom: 1px solid var(--ld-border-subtle);
61
+ background: rgba(0, 0, 0, 0.1);
62
+ }
63
+
64
+ .playground-panel-title {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ font-size: 0.75rem;
69
+ font-weight: 600;
70
+ color: var(--ld-text-muted);
71
+ text-transform: uppercase;
72
+ letter-spacing: 0.05em;
73
+ }
74
+
75
+ /* Copy Button */
76
+ .playground-copy-btn {
77
+ background: transparent;
78
+ border: none;
79
+ color: var(--ld-text-muted);
80
+ cursor: pointer;
81
+ padding: 0.25rem;
82
+ border-radius: var(--ld-radius-sm);
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ transition: all 0.2s;
87
+ }
88
+
89
+ .playground-copy-btn:hover {
90
+ background: rgba(255, 255, 255, 0.1);
91
+ color: var(--ld-text-main);
92
+ }
93
+
94
+ /* Content Area */
95
+ .playground-panel-content {
96
+ flex: 1;
97
+ overflow: auto;
98
+ position: relative;
99
+ display: flex;
100
+ flex-direction: column;
101
+ }
102
+
103
+ /* Editor Specifics */
104
+ .playground-editor {
105
+ background: transparent;
106
+ }
107
+
108
+ .playground-editor > div {
109
+ font-family: var(--ld-font-mono) !important;
110
+ font-size: 0.85rem !important;
111
+ line-height: 1.5 !important;
112
+ min-height: 100%;
113
+ }
114
+
115
+ .playground-editor pre {
116
+ padding: 1.5rem !important;
117
+ margin: 0 !important;
118
+ background: transparent !important;
119
+ }
120
+
121
+ .playground-editor textarea {
122
+ outline: none !important;
123
+ padding: 1.5rem !important;
124
+ }
125
+
126
+ /* Prevent editing styles if read-only */
127
+ .boltdocs-playground[data-readonly="true"] .playground-editor textarea {
128
+ cursor: default !important;
129
+ }
130
+
131
+ /* Preview Specifics */
132
+ .playground-preview {
133
+ padding: 2rem;
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ background-color: var(--ld-bg-mute);
138
+ background-image: radial-gradient(
139
+ var(--ld-border-subtle) 1.5px,
140
+ transparent 1.5px
141
+ );
142
+ background-size: 24px 24px;
143
+ color: var(--ld-text-main);
144
+ min-height: 200px;
145
+ }
146
+
147
+ /* Error Specifics */
148
+ .playground-error {
149
+ margin: 0;
150
+ padding: 1rem;
151
+ background: #7f1d1d;
152
+ color: #fca5a5;
153
+ font-size: 0.8rem;
154
+ font-family: var(--ld-font-mono);
155
+ white-space: pre-wrap;
156
+ border-top: 1px solid #991b1b;
157
+ z-index: 10;
158
+ }
159
+
160
+ /* Loading Skeleton */
161
+ .playground-skeleton {
162
+ height: 350px;
163
+ background: var(--ld-bg-mute);
164
+ border-radius: var(--ld-radius-lg);
165
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
166
+ margin: 2rem 0;
167
+ border: 1px solid var(--ld-border-subtle);
168
+ }
@@ -0,0 +1,84 @@
1
+ import React, { useRef, useState, useEffect } from "react";
2
+
3
+ interface VideoProps {
4
+ /** Video source URL */
5
+ src?: string;
6
+ /** Poster/thumbnail image URL */
7
+ poster?: string;
8
+ /** Alt text for accessibility */
9
+ alt?: string;
10
+ /** Show native video controls (default: true) */
11
+ controls?: boolean;
12
+ /** Preload strategy (default: 'none') */
13
+ preload?: string;
14
+ /** Children (e.g. <source> elements) */
15
+ children?: React.ReactNode;
16
+ /** Allow any additional HTML video attributes */
17
+ [key: string]: any;
18
+ }
19
+
20
+ /**
21
+ * Optimized video component with lazy loading via IntersectionObserver.
22
+ * The `<video>` element is only rendered once it enters the viewport,
23
+ * preventing unnecessary network downloads on initial page load.
24
+ *
25
+ * Usage in MDX:
26
+ * ```mdx
27
+ * <video src="/demo.mp4" poster="/demo-thumb.jpg" />
28
+ * ```
29
+ */
30
+ export function Video({
31
+ src,
32
+ poster,
33
+ alt,
34
+ children,
35
+ controls,
36
+ preload = "metadata",
37
+ ...rest
38
+ }: VideoProps) {
39
+ const containerRef = useRef<HTMLDivElement>(null);
40
+ const [isVisible, setIsVisible] = useState(false);
41
+
42
+ useEffect(() => {
43
+ const el = containerRef.current;
44
+ if (!el) return;
45
+
46
+ const observer = new IntersectionObserver(
47
+ ([entry]) => {
48
+ if (entry.isIntersecting) {
49
+ setIsVisible(true);
50
+ observer.disconnect();
51
+ }
52
+ },
53
+ { rootMargin: "200px" },
54
+ );
55
+
56
+ observer.observe(el);
57
+ return () => observer.disconnect();
58
+ }, []);
59
+
60
+ return (
61
+ <div ref={containerRef} className="boltdocs-video-wrapper">
62
+ {isVisible ? (
63
+ <video
64
+ className="boltdocs-video"
65
+ src={src}
66
+ poster={poster}
67
+ controls={true}
68
+ preload={preload}
69
+ playsInline
70
+ {...rest}
71
+ >
72
+ {children}
73
+ Your browser does not support the video tag.
74
+ </video>
75
+ ) : (
76
+ <div
77
+ className="boltdocs-video-placeholder"
78
+ role="img"
79
+ aria-label={alt || "Video"}
80
+ />
81
+ )}
82
+ </div>
83
+ );
84
+ }
@@ -0,0 +1 @@
1
+ export { Video } from "./Video";
@@ -0,0 +1,41 @@
1
+ /* ─── Video ──────────────────────────────────────────────── */
2
+ .boltdocs-video-wrapper {
3
+ margin: 1.5rem 0;
4
+ max-width: 100%;
5
+ width: 100%;
6
+ box-sizing: border-box;
7
+ border-radius: var(--ld-radius-md);
8
+ overflow: hidden;
9
+ border: 1px solid var(--ld-border-subtle);
10
+ background-color: var(--ld-surface);
11
+ }
12
+
13
+ .boltdocs-video {
14
+ display: block;
15
+ width: 100%;
16
+ max-width: 100%;
17
+ height: auto;
18
+ border-radius: 0;
19
+ animation: ld-video-fade-in 0.4s ease-out;
20
+ }
21
+
22
+ .boltdocs-video-placeholder {
23
+ width: 100%;
24
+ aspect-ratio: 16 / 9;
25
+ background-color: var(--ld-surface);
26
+ }
27
+
28
+ /* Ensure video doesn't overflow boltdocs-page container */
29
+ .boltdocs-page video {
30
+ max-width: 100%;
31
+ height: auto;
32
+ }
33
+
34
+ @keyframes ld-video-fade-in {
35
+ from {
36
+ opacity: 0;
37
+ }
38
+ to {
39
+ opacity: 1;
40
+ }
41
+ }