boltdocs 1.3.2 → 1.4.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.
@@ -611,7 +611,7 @@ async function getStarsRepo(repo) {
611
611
  return "0";
612
612
  }
613
613
  }
614
- var formatStars;
614
+ var formatStars, copyToClipboard;
615
615
  var init_utils = __esm({
616
616
  "src/client/utils.ts"() {
617
617
  "use strict";
@@ -621,6 +621,22 @@ var init_utils = __esm({
621
621
  compactDisplay: "short"
622
622
  }).format(count);
623
623
  };
624
+ copyToClipboard = async (text) => {
625
+ try {
626
+ await navigator.clipboard.writeText(text);
627
+ return true;
628
+ } catch {
629
+ const textarea = document.createElement("textarea");
630
+ textarea.value = text;
631
+ textarea.style.position = "fixed";
632
+ textarea.style.opacity = "0";
633
+ document.body.appendChild(textarea);
634
+ textarea.select();
635
+ document.execCommand("copy");
636
+ document.body.removeChild(textarea);
637
+ return true;
638
+ }
639
+ };
624
640
  }
625
641
  });
626
642
 
@@ -1703,37 +1719,20 @@ function CodeBlock({ children, ...props }) {
1703
1719
  }
1704
1720
  const handleCopy = (0, import_react13.useCallback)(async () => {
1705
1721
  const code = preRef.current?.textContent || "";
1706
- try {
1707
- await navigator.clipboard.writeText(code);
1708
- setCopied(true);
1709
- setTimeout(() => setCopied(false), 2e3);
1710
- } catch {
1711
- const textarea = document.createElement("textarea");
1712
- textarea.value = code;
1713
- textarea.style.position = "fixed";
1714
- textarea.style.opacity = "0";
1715
- document.body.appendChild(textarea);
1716
- textarea.select();
1717
- document.execCommand("copy");
1718
- document.body.removeChild(textarea);
1719
- setCopied(true);
1720
- setTimeout(() => setCopied(false), 2e3);
1721
- }
1722
+ copyToClipboard(code);
1723
+ setCopied(true);
1724
+ setTimeout(() => setCopied(false), 2e3);
1722
1725
  }, []);
1723
1726
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "code-block-wrapper", children: [
1724
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "code-block-header", children: [
1725
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "code-block-lang", children: language || "code" }),
1726
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1727
- "button",
1728
- {
1729
- className: `code-block-copy ${copied ? "copied" : ""}`,
1730
- onClick: handleCopy,
1731
- type: "button",
1732
- "aria-label": "Copy code",
1733
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_jsx_runtime20.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Check, { size: 20 }) }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_jsx_runtime20.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Copy, { size: 20 }) })
1734
- }
1735
- )
1736
- ] }),
1727
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1728
+ "button",
1729
+ {
1730
+ className: `code-block-copy ${copied ? "copied" : ""}`,
1731
+ onClick: handleCopy,
1732
+ "aria-label": "Copy code",
1733
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Check, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Copy, { size: 16 })
1734
+ }
1735
+ ),
1737
1736
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("pre", { ref: preRef, ...props, children })
1738
1737
  ] });
1739
1738
  }
@@ -1743,6 +1742,7 @@ var init_CodeBlock = __esm({
1743
1742
  "use strict";
1744
1743
  import_react13 = __toESM(require("react"));
1745
1744
  import_lucide_react12 = require("lucide-react");
1745
+ init_utils();
1746
1746
  import_jsx_runtime20 = require("react/jsx-runtime");
1747
1747
  }
1748
1748
  });
@@ -2099,22 +2099,9 @@ function PackageManagerTabs({
2099
2099
  const [copied, setCopied] = (0, import_react15.useState)(false);
2100
2100
  const activeCommand = getCommandForManager(activeTab, command, pkg);
2101
2101
  const handleCopy = (0, import_react15.useCallback)(async () => {
2102
- try {
2103
- await navigator.clipboard.writeText(activeCommand);
2104
- setCopied(true);
2105
- setTimeout(() => setCopied(false), 2e3);
2106
- } catch {
2107
- const textarea = document.createElement("textarea");
2108
- textarea.value = activeCommand;
2109
- textarea.style.position = "fixed";
2110
- textarea.style.opacity = "0";
2111
- document.body.appendChild(textarea);
2112
- textarea.select();
2113
- document.execCommand("copy");
2114
- document.body.removeChild(textarea);
2115
- setCopied(true);
2116
- setTimeout(() => setCopied(false), 2e3);
2117
- }
2102
+ copyToClipboard(activeCommand);
2103
+ setCopied(true);
2104
+ setTimeout(() => setCopied(false), 2e3);
2118
2105
  }, [activeCommand]);
2119
2106
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: `pkg-tabs-wrapper ${className}`, children: [
2120
2107
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "pkg-tabs-header", children: MANAGERS.map((mgr) => {
@@ -2136,19 +2123,16 @@ function PackageManagerTabs({
2136
2123
  );
2137
2124
  }) }),
2138
2125
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "code-block-wrapper pkg-tabs-content", children: [
2139
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "code-block-header", children: [
2140
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "code-block-lang", children: "bash" }),
2141
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2142
- "button",
2143
- {
2144
- className: `code-block-copy ${copied ? "copied" : ""}`,
2145
- onClick: handleCopy,
2146
- type: "button",
2147
- "aria-label": "Copy code",
2148
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_jsx_runtime26.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react13.Check, { size: 12 }) }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_jsx_runtime26.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react13.Copy, { size: 12 }) })
2149
- }
2150
- )
2151
- ] }),
2126
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2127
+ "button",
2128
+ {
2129
+ className: `code-block-copy ${copied ? "copied" : ""}`,
2130
+ onClick: handleCopy,
2131
+ type: "button",
2132
+ "aria-label": "Copy code",
2133
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react13.Check, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react13.Copy, { size: 14 })
2134
+ }
2135
+ ),
2152
2136
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("code", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "line", children: activeCommand }) }) })
2153
2137
  ] })
2154
2138
  ] });
@@ -2163,6 +2147,7 @@ var init_PackageManagerTabs = __esm({
2163
2147
  init_pnpm();
2164
2148
  init_bun();
2165
2149
  init_deno();
2150
+ init_utils();
2166
2151
  import_jsx_runtime26 = require("react/jsx-runtime");
2167
2152
  MANAGERS = [
2168
2153
  { id: "npm", label: "npm", icon: NPM },
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AppShell
3
- } from "../chunk-NS7WHDYA.mjs";
3
+ } from "../chunk-D7YBQG6H.mjs";
4
+ import "../chunk-FMTOYQLO.mjs";
4
5
 
5
6
  // src/client/ssr.tsx
6
7
  import React from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boltdocs",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "A lightweight documentation generator for React projects.",
5
5
  "main": "dist/node/index.js",
6
6
  "module": "dist/node/index.mjs",
@@ -1,5 +1,6 @@
1
1
  export type { BoltdocsConfig, BoltdocsThemeConfig } from "../node/config";
2
2
  export type { ComponentRoute, CreateBoltdocsAppOptions } from "./types";
3
+ import { PackageManagerTabs } from "./theme/components/PackageManagerTabs";
3
4
  export { createBoltdocsApp } from "./app";
4
5
  export { ThemeLayout } from "./theme/ui/Layout";
5
6
  export { Navbar } from "./theme/ui/Navbar";
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useRef, useCallback } from "react";
2
2
  import { Copy, Check } from "lucide-react";
3
+ import { copyToClipboard } from "../../../utils";
3
4
 
4
5
  interface CodeBlockProps {
5
6
  children?: React.ReactNode;
@@ -28,46 +29,20 @@ export function CodeBlock({ children, ...props }: CodeBlockProps) {
28
29
 
29
30
  const handleCopy = useCallback(async () => {
30
31
  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
- }
32
+ copyToClipboard(code);
33
+ setCopied(true);
34
+ setTimeout(() => setCopied(false), 2000);
48
35
  }, []);
49
36
 
50
37
  return (
51
38
  <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>
39
+ <button
40
+ className={`code-block-copy ${copied ? "copied" : ""}`}
41
+ onClick={handleCopy}
42
+ aria-label="Copy code"
43
+ >
44
+ {copied ? <Check size={16} /> : <Copy size={16} />}
45
+ </button>
71
46
  <pre ref={preRef} {...props}>
72
47
  {children}
73
48
  </pre>
@@ -4,6 +4,7 @@ import { NPM } from "../../icons/npm";
4
4
  import { Pnpm } from "../../icons/pnpm";
5
5
  import { Bun } from "../../icons/bun";
6
6
  import { Deno } from "../../icons/deno";
7
+ import { copyToClipboard } from "../../../utils";
7
8
 
8
9
  interface PackageManagerTabsProps {
9
10
  command: string;
@@ -82,22 +83,9 @@ export function PackageManagerTabs({
82
83
  const activeCommand = getCommandForManager(activeTab, command, pkg);
83
84
 
84
85
  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
- }
86
+ copyToClipboard(activeCommand);
87
+ setCopied(true);
88
+ setTimeout(() => setCopied(false), 2000);
101
89
  }, [activeCommand]);
102
90
 
103
91
  return (
@@ -124,25 +112,14 @@ export function PackageManagerTabs({
124
112
 
125
113
  {/* Code Block Content */}
126
114
  <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>
115
+ <button
116
+ className={`code-block-copy ${copied ? "copied" : ""}`}
117
+ onClick={handleCopy}
118
+ type="button"
119
+ aria-label="Copy code"
120
+ >
121
+ {copied ? <Check size={14} /> : <Copy size={14} />}
122
+ </button>
146
123
  <pre>
147
124
  <code>
148
125
  <span className="line">{activeCommand}</span>
@@ -1,4 +1,10 @@
1
1
  import React, { useState, Children, isValidElement, useRef } from "react";
2
+ import { CodeBlock } from "../CodeBlock";
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
+ import { Yarn } from "../../icons/yarn";
2
8
 
3
9
  /* ─── Tab (individual panel) ──────────────────────────────── */
4
10
  export interface TabProps {
@@ -15,7 +21,17 @@ export interface TabProps {
15
21
  * ```
16
22
  */
17
23
  export function Tab({ children }: TabProps) {
18
- return <div className="ld-tab-panel">{children}</div>;
24
+ // If children is a simple string, wrap it in a CodeBlock for syntax highlighting
25
+ const content =
26
+ typeof children === "string" ? (
27
+ <CodeBlock className="language-bash">
28
+ <code>{children.trim()}</code>
29
+ </CodeBlock>
30
+ ) : (
31
+ children
32
+ );
33
+
34
+ return <div className="ld-tab-panel">{content}</div>;
19
35
  }
20
36
 
21
37
  /* ─── Tabs (container) ────────────────────────────────────── */
@@ -25,6 +41,16 @@ export interface TabsProps {
25
41
  children: React.ReactNode;
26
42
  }
27
43
 
44
+ const getIconForLabel = (label: string) => {
45
+ const l = label.toLowerCase();
46
+ if (l.includes("npm")) return <NPM />;
47
+ if (l.includes("pnpm")) return <Pnpm />;
48
+ if (l.includes("yarn")) return <Yarn />;
49
+ if (l.includes("bun")) return <Bun />;
50
+ if (l.includes("deno")) return <Deno />;
51
+ return null;
52
+ };
53
+
28
54
  /**
29
55
  * Tab container that manages active state.
30
56
  *
@@ -64,6 +90,7 @@ export function Tabs({ defaultIndex = 0, children }: TabsProps) {
64
90
  <div className="ld-tabs__bar" role="tablist" onKeyDown={handleKeyDown}>
65
91
  {tabs.map((child, i) => {
66
92
  const label = (child as React.ReactElement<TabProps>).props.label;
93
+ const Icon = getIconForLabel(label);
67
94
  return (
68
95
  <button
69
96
  key={i}
@@ -72,11 +99,16 @@ export function Tabs({ defaultIndex = 0, children }: TabsProps) {
72
99
  aria-controls={`tabpanel-${i}`}
73
100
  id={`tab-${i}`}
74
101
  tabIndex={i === active ? 0 : -1}
75
- ref={(el) => { tabRefs.current[i] = el; }}
76
- className={`ld-tabs__trigger ${i === active ? "ld-tabs__trigger--active" : ""}`}
102
+ ref={(el) => {
103
+ tabRefs.current[i] = el;
104
+ }}
105
+ className={`ld-tabs__trigger ${
106
+ i === active ? "ld-tabs__trigger--active" : ""
107
+ }`}
77
108
  onClick={() => setActive(i)}
78
109
  >
79
- {label}
110
+ {Icon}
111
+ <span>{label}</span>
80
112
  </button>
81
113
  );
82
114
  })}
@@ -276,6 +276,9 @@
276
276
 
277
277
  .ld-tabs__trigger {
278
278
  position: relative;
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 0.5rem;
279
282
  padding: 0.65rem 1.25rem;
280
283
  background: none;
281
284
  border: none;
@@ -288,6 +291,11 @@
288
291
  transition: all 0.2s ease;
289
292
  white-space: nowrap;
290
293
  }
294
+
295
+ .ld-tabs__trigger svg {
296
+ width: 1rem;
297
+ height: 1rem;
298
+ }
291
299
  .ld-tabs__trigger:hover {
292
300
  color: var(--ld-text-main);
293
301
  background-color: rgba(255, 255, 255, 0.03);
@@ -0,0 +1,16 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ const Yarn = (props: SVGProps<SVGSVGElement>) => (
4
+ <svg {...props} viewBox="0 0 256 256">
5
+ <path
6
+ fill="#2C8EBB"
7
+ d="M128 0C57.307 0 0 57.307 0 128s57.307 128 128 128 128-57.307 128-128S198.693 0 128 0zm0 234.667C69.195 234.667 21.333 186.805 21.333 128S69.195 21.333 128 21.333 234.667 69.195 234.667 128 186.805 234.667 128 234.667z"
8
+ />
9
+ <path
10
+ fill="#2C8EBB"
11
+ d="M173.045 74.053c-4.632-4.632-12.144-4.632-16.776 0L128 102.323l-28.269-28.27c-4.632-4.632-12.144-4.632-16.776 0-4.632 4.632-4.632 12.144 0 16.776L111.224 119.1l-28.269 28.269c-4.632 4.632-4.632 12.144 0 16.776 2.316 2.316 5.352 3.474 8.388 3.474s6.072-1.158 8.388-3.474L128 135.877l28.269 28.268c2.316 2.316 5.352 3.474 8.388 3.474s6.072-1.158 8.388-3.474c4.632-4.632 4.632-12.144 0-16.776L144.776 119.1l28.269-28.271 c4.632-4.632 4.632-12.144 0-16.776z"
12
+ />
13
+ </svg>
14
+ );
15
+
16
+ export { Yarn };
@@ -206,55 +206,52 @@
206
206
  overflow: hidden;
207
207
  border: 1px solid var(--ld-border-subtle);
208
208
  background-color: var(--ld-code-bg);
209
- }
210
-
211
- .code-block-header {
212
- display: flex;
213
- align-items: center;
214
- justify-content: space-between;
215
- padding: 0.5rem 1.25rem;
216
- background-color: var(--ld-code-header);
217
- border-bottom: 1px solid var(--ld-border-subtle);
218
- font-size: 0.75rem;
219
- }
220
-
221
- .code-block-lang {
222
- color: var(--ld-text-dim);
223
- font-family: var(--ld-font-mono);
224
- font-weight: 500;
225
- text-transform: uppercase;
226
- letter-spacing: 0.05em;
209
+ position: relative;
227
210
  }
228
211
 
229
212
  .code-block-copy {
230
213
  display: flex;
231
214
  align-items: center;
232
- gap: 0.35rem;
233
- padding: 0.2rem 0.55rem;
234
- background: none;
215
+ justify-content: center;
216
+ position: absolute;
217
+ top: 0.75rem;
218
+ right: 0.75rem;
219
+ z-index: 50;
220
+ padding: 0.4rem;
221
+ background-color: rgba(20, 20, 30, 0.8);
222
+ backdrop-filter: blur(8px);
223
+ -webkit-backdrop-filter: blur(8px);
235
224
  border: 1px solid var(--ld-border-subtle);
236
- border-radius: 5px;
225
+ border-radius: 6px;
237
226
  color: var(--ld-text-dim);
238
- font-family: var(--ld-font-sans);
239
- font-size: 0.6875rem;
240
227
  cursor: pointer;
241
- transition: all 0.2s;
228
+ transition: all 0.2s ease;
229
+ opacity: 0;
230
+ visibility: hidden;
231
+ pointer-events: none;
232
+ }
233
+
234
+ .code-block-wrapper:hover .code-block-copy {
235
+ opacity: 1;
236
+ visibility: visible;
237
+ pointer-events: auto;
242
238
  }
243
239
 
244
240
  .code-block-copy:hover {
245
241
  color: var(--ld-text-main);
246
242
  border-color: var(--ld-border-strong);
247
- background-color: rgba(255, 255, 255, 0.04);
243
+ background-color: rgba(255, 255, 255, 0.08);
248
244
  }
249
245
 
250
246
  .code-block-copy.copied {
251
247
  color: #22c55e;
252
- border-color: rgba(34, 197, 94, 0.3);
248
+ border-color: rgba(34, 197, 94, 0.4);
249
+ opacity: 1;
253
250
  }
254
251
 
255
252
  .code-block-copy svg {
256
- width: 18px;
257
- height: 18px;
253
+ width: 16px;
254
+ height: 16px;
258
255
  }
259
256
 
260
257
  .code-block-wrapper pre {
@@ -24,3 +24,26 @@ const formatStars = (count: number): string => {
24
24
  compactDisplay: "short",
25
25
  }).format(count);
26
26
  };
27
+
28
+ /**
29
+ * Copy text to clipboard.
30
+ * @param text - The text to copy.
31
+ * @returns True if the text was copied successfully.
32
+ */
33
+ export const copyToClipboard = async (text: string) => {
34
+ try {
35
+ await navigator.clipboard.writeText(text);
36
+ return true;
37
+ } catch {
38
+ // Fallback
39
+ const textarea = document.createElement("textarea");
40
+ textarea.value = text;
41
+ textarea.style.position = "fixed";
42
+ textarea.style.opacity = "0";
43
+ document.body.appendChild(textarea);
44
+ textarea.select();
45
+ document.execCommand("copy");
46
+ document.body.removeChild(textarea);
47
+ return true;
48
+ }
49
+ };
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
- "moduleResolution": "Node",
5
+ "moduleResolution": "Bundler",
6
6
  "esModuleInterop": true,
7
7
  "strict": true,
8
8
  "skipLibCheck": true,
@@ -1,6 +0,0 @@
1
- import {
2
- CodeBlock
3
- } from "./chunk-2YRDWM6O.mjs";
4
- export {
5
- CodeBlock
6
- };
@@ -1,56 +0,0 @@
1
- // src/client/theme/components/CodeBlock/CodeBlock.tsx
2
- import React, { useState, useRef, useCallback } from "react";
3
- import { Copy, Check } from "lucide-react";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- function CodeBlock({ children, ...props }) {
6
- const [copied, setCopied] = useState(false);
7
- const preRef = useRef(null);
8
- let language = "";
9
- if (React.isValidElement(children)) {
10
- const childProps = children.props;
11
- language = childProps?.["data-language"] || "";
12
- if (!language && childProps?.className) {
13
- const match = childProps.className.match(/language-(\w+)/);
14
- if (match) language = match[1];
15
- }
16
- }
17
- const handleCopy = useCallback(async () => {
18
- const code = preRef.current?.textContent || "";
19
- try {
20
- await navigator.clipboard.writeText(code);
21
- setCopied(true);
22
- setTimeout(() => setCopied(false), 2e3);
23
- } catch {
24
- const textarea = document.createElement("textarea");
25
- textarea.value = code;
26
- textarea.style.position = "fixed";
27
- textarea.style.opacity = "0";
28
- document.body.appendChild(textarea);
29
- textarea.select();
30
- document.execCommand("copy");
31
- document.body.removeChild(textarea);
32
- setCopied(true);
33
- setTimeout(() => setCopied(false), 2e3);
34
- }
35
- }, []);
36
- return /* @__PURE__ */ jsxs("div", { className: "code-block-wrapper", children: [
37
- /* @__PURE__ */ jsxs("div", { className: "code-block-header", children: [
38
- /* @__PURE__ */ jsx("span", { className: "code-block-lang", children: language || "code" }),
39
- /* @__PURE__ */ jsx(
40
- "button",
41
- {
42
- className: `code-block-copy ${copied ? "copied" : ""}`,
43
- onClick: handleCopy,
44
- type: "button",
45
- "aria-label": "Copy code",
46
- children: copied ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Check, { size: 20 }) }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Copy, { size: 20 }) })
47
- }
48
- )
49
- ] }),
50
- /* @__PURE__ */ jsx("pre", { ref: preRef, ...props, children })
51
- ] });
52
- }
53
-
54
- export {
55
- CodeBlock
56
- };