@hanzo/ui 5.3.26 → 5.3.28

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 (326) hide show
  1. package/content/index.ts +26 -0
  2. package/docs/_registry/index.ts +426 -0
  3. package/docs/_registry/layout/docs-min.tsx +197 -0
  4. package/docs/_registry/layout/page-min.tsx +128 -0
  5. package/docs/components/accordion.tsx +118 -0
  6. package/docs/components/banner.tsx +144 -0
  7. package/docs/components/callout.tsx +112 -0
  8. package/docs/components/card.tsx +52 -0
  9. package/docs/components/codeblock.tsx +258 -0
  10. package/docs/components/dialog/search-algolia.tsx +132 -0
  11. package/docs/components/dialog/search-default.tsx +131 -0
  12. package/docs/components/dialog/search-orama.tsx +143 -0
  13. package/docs/components/dialog/search.tsx +529 -0
  14. package/docs/components/dynamic-codeblock.tsx +129 -0
  15. package/docs/components/files.tsx +81 -0
  16. package/docs/components/github-info.tsx +107 -0
  17. package/docs/components/heading.tsx +33 -0
  18. package/docs/components/image-zoom.css +77 -0
  19. package/docs/components/image-zoom.tsx +58 -0
  20. package/docs/components/index.ts +7 -0
  21. package/docs/components/inline-toc.tsx +48 -0
  22. package/docs/components/sidebar/base.tsx +451 -0
  23. package/docs/components/sidebar/link-item.tsx +65 -0
  24. package/docs/components/sidebar/page-tree.tsx +113 -0
  25. package/docs/components/sidebar/tabs/dropdown.tsx +109 -0
  26. package/docs/components/sidebar/tabs/index.tsx +89 -0
  27. package/docs/components/steps.tsx +9 -0
  28. package/docs/components/tabs.tsx +203 -0
  29. package/docs/components/toc/clerk.tsx +173 -0
  30. package/docs/components/toc/default.tsx +57 -0
  31. package/docs/components/toc/index.tsx +136 -0
  32. package/docs/components/type-table.tsx +174 -0
  33. package/docs/components/ui/accordion.tsx +88 -0
  34. package/docs/components/ui/button.tsx +28 -0
  35. package/docs/components/ui/collapsible.tsx +42 -0
  36. package/docs/components/ui/navigation-menu.tsx +83 -0
  37. package/docs/components/ui/popover.tsx +32 -0
  38. package/docs/components/ui/scroll-area.tsx +59 -0
  39. package/docs/components/ui/tabs.tsx +145 -0
  40. package/docs/contexts/i18n.tsx +56 -0
  41. package/docs/contexts/search.tsx +165 -0
  42. package/docs/contexts/tree.tsx +65 -0
  43. package/docs/css/black.css +39 -0
  44. package/docs/css/catppuccin.css +49 -0
  45. package/docs/css/colors/index.css +51 -0
  46. package/docs/css/dusk.css +47 -0
  47. package/docs/css/layouts/docs.css +1 -0
  48. package/docs/css/layouts/home.css +1 -0
  49. package/docs/css/layouts/notebook.css +1 -0
  50. package/docs/css/neutral.css +7 -0
  51. package/docs/css/ocean.css +48 -0
  52. package/docs/css/preset.css +305 -0
  53. package/docs/css/purple.css +39 -0
  54. package/docs/css/shadcn.css +36 -0
  55. package/docs/css/shiki.css +90 -0
  56. package/docs/css/solar.css +75 -0
  57. package/docs/css/style.css +9 -0
  58. package/docs/css/vitepress.css +77 -0
  59. package/docs/i18n.tsx +30 -0
  60. package/docs/icons.tsx +354 -0
  61. package/docs/layouts/docs/client.tsx +129 -0
  62. package/docs/layouts/docs/index.tsx +321 -0
  63. package/docs/layouts/docs/page/client.tsx +376 -0
  64. package/docs/layouts/docs/page/index.tsx +251 -0
  65. package/docs/layouts/docs/sidebar.tsx +265 -0
  66. package/docs/layouts/home/client.tsx +375 -0
  67. package/docs/layouts/home/index.tsx +51 -0
  68. package/docs/layouts/home/navbar.tsx +55 -0
  69. package/docs/layouts/notebook/client.tsx +281 -0
  70. package/docs/layouts/notebook/index.tsx +461 -0
  71. package/docs/layouts/notebook/page/client.tsx +375 -0
  72. package/docs/layouts/notebook/page/index.tsx +251 -0
  73. package/docs/layouts/notebook/sidebar.tsx +248 -0
  74. package/docs/layouts/shared/index.tsx +89 -0
  75. package/docs/layouts/shared/language-toggle.tsx +66 -0
  76. package/docs/layouts/shared/link-item.tsx +119 -0
  77. package/docs/layouts/shared/search-toggle.tsx +78 -0
  78. package/docs/layouts/shared/theme-toggle.tsx +86 -0
  79. package/docs/mdx.server.tsx +37 -0
  80. package/docs/mdx.tsx +97 -0
  81. package/docs/og.tsx +101 -0
  82. package/docs/page.tsx +85 -0
  83. package/docs/provider/base.tsx +173 -0
  84. package/docs/provider/next.tsx +23 -0
  85. package/docs/provider/react-router.tsx +23 -0
  86. package/docs/provider/tanstack.tsx +23 -0
  87. package/docs/provider/waku.tsx +23 -0
  88. package/docs/source.ts +3 -0
  89. package/docs/theme/typography/LICENSE +21 -0
  90. package/docs/theme/typography/index.ts +201 -0
  91. package/docs/theme/typography/styles.ts +449 -0
  92. package/docs/utils/cn.ts +1 -0
  93. package/docs/utils/is-active.ts +23 -0
  94. package/docs/utils/merge-refs.ts +15 -0
  95. package/docs/utils/use-copy-button.ts +39 -0
  96. package/docs/utils/use-footer-items.ts +27 -0
  97. package/docs/utils/use-is-scroll-top.ts +21 -0
  98. package/package.json +4 -2
  99. package/dist/3d/button.js +0 -298
  100. package/dist/3d/button.mjs +0 -273
  101. package/dist/3d/card.js +0 -234
  102. package/dist/3d/card.mjs +0 -207
  103. package/dist/3d/carousel.js +0 -371
  104. package/dist/3d/carousel.mjs +0 -344
  105. package/dist/3d/grid.js +0 -362
  106. package/dist/3d/grid.mjs +0 -337
  107. package/dist/3d/index.js +0 -1518
  108. package/dist/3d/index.mjs +0 -1472
  109. package/dist/3d/marquee.js +0 -352
  110. package/dist/3d/marquee.mjs +0 -327
  111. package/dist/3d/pin.js +0 -46
  112. package/dist/3d/pin.mjs +0 -24
  113. package/dist/accordion.js +0 -80
  114. package/dist/accordion.mjs +0 -55
  115. package/dist/alert-dialog.js +0 -220
  116. package/dist/alert-dialog.mjs +0 -187
  117. package/dist/alert.js +0 -68
  118. package/dist/alert.mjs +0 -64
  119. package/dist/animation/animated-background.js +0 -424
  120. package/dist/animation/animated-background.mjs +0 -418
  121. package/dist/animation/animated-beam.js +0 -119
  122. package/dist/animation/animated-beam.mjs +0 -97
  123. package/dist/animation/animated-cursor.js +0 -275
  124. package/dist/animation/animated-cursor.mjs +0 -270
  125. package/dist/animation/animated-icon.js +0 -357
  126. package/dist/animation/animated-icon.mjs +0 -351
  127. package/dist/animation/animated-list.js +0 -339
  128. package/dist/animation/animated-list.mjs +0 -333
  129. package/dist/animation/animated-number.js +0 -283
  130. package/dist/animation/animated-number.mjs +0 -277
  131. package/dist/animation/animated-testimonials.js +0 -97
  132. package/dist/animation/animated-testimonials.mjs +0 -75
  133. package/dist/animation/animated-tooltip.js +0 -67
  134. package/dist/animation/animated-tooltip.mjs +0 -45
  135. package/dist/animation/apple-cards-carousel.js +0 -308
  136. package/dist/animation/apple-cards-carousel.mjs +0 -285
  137. package/dist/animation/apple-hello-effect.js +0 -60
  138. package/dist/animation/apple-hello-effect.mjs +0 -38
  139. package/dist/animation/index.js +0 -1952
  140. package/dist/animation/index.mjs +0 -1921
  141. package/dist/avatar.js +0 -71
  142. package/dist/avatar.mjs +0 -47
  143. package/dist/badge.js +0 -66
  144. package/dist/badge.mjs +0 -40
  145. package/dist/blocks/index.js +0 -1665
  146. package/dist/blocks/index.mjs +0 -1626
  147. package/dist/breadcrumb.js +0 -107
  148. package/dist/breadcrumb.mjs +0 -99
  149. package/dist/calendar.js +0 -189
  150. package/dist/calendar.mjs +0 -164
  151. package/dist/carousel.js +0 -278
  152. package/dist/carousel.mjs +0 -249
  153. package/dist/checkbox.js +0 -60
  154. package/dist/checkbox.mjs +0 -35
  155. package/dist/code/block.js +0 -226
  156. package/dist/code/block.mjs +0 -203
  157. package/dist/code/compare.js +0 -446
  158. package/dist/code/compare.mjs +0 -423
  159. package/dist/code/diff.js +0 -430
  160. package/dist/code/diff.mjs +0 -407
  161. package/dist/code/editor.js +0 -243
  162. package/dist/code/editor.mjs +0 -218
  163. package/dist/code/explorer.js +0 -291
  164. package/dist/code/explorer.mjs +0 -268
  165. package/dist/code/index.js +0 -2551
  166. package/dist/code/index.mjs +0 -2510
  167. package/dist/code/preview.js +0 -400
  168. package/dist/code/preview.mjs +0 -377
  169. package/dist/code/snippet.js +0 -274
  170. package/dist/code/snippet.mjs +0 -250
  171. package/dist/code/tabs.js +0 -75
  172. package/dist/code/tabs.mjs +0 -53
  173. package/dist/code/terminal.js +0 -437
  174. package/dist/code/terminal.mjs +0 -414
  175. package/dist/collapsible.js +0 -33
  176. package/dist/collapsible.mjs +0 -9
  177. package/dist/command.js +0 -262
  178. package/dist/command.mjs +0 -232
  179. package/dist/context-menu.js +0 -207
  180. package/dist/context-menu.mjs +0 -171
  181. package/dist/device/index.js +0 -3
  182. package/dist/device/index.mjs +0 -2
  183. package/dist/dialog.js +0 -151
  184. package/dist/dialog.mjs +0 -121
  185. package/dist/dock/basic.js +0 -174
  186. package/dist/dock/basic.mjs +0 -151
  187. package/dist/dock/index.js +0 -628
  188. package/dist/dock/index.mjs +0 -601
  189. package/dist/dock/limelight-nav.js +0 -295
  190. package/dist/dock/limelight-nav.mjs +0 -274
  191. package/dist/dock/macos.js +0 -141
  192. package/dist/dock/macos.mjs +0 -118
  193. package/dist/dock/menu.js +0 -70
  194. package/dist/dock/menu.mjs +0 -48
  195. package/dist/dock/message.js +0 -144
  196. package/dist/dock/message.mjs +0 -122
  197. package/dist/drawer.js +0 -115
  198. package/dist/drawer.mjs +0 -103
  199. package/dist/dropdown-menu.js +0 -202
  200. package/dist/dropdown-menu.mjs +0 -166
  201. package/dist/finance/AdvancedChart.js +0 -48
  202. package/dist/finance/AdvancedChart.mjs +0 -46
  203. package/dist/finance/CompanyProfile.js +0 -48
  204. package/dist/finance/CompanyProfile.mjs +0 -46
  205. package/dist/finance/CryptoScreener.js +0 -45
  206. package/dist/finance/CryptoScreener.mjs +0 -43
  207. package/dist/finance/Financials.js +0 -52
  208. package/dist/finance/Financials.mjs +0 -50
  209. package/dist/finance/ForexScreener.js +0 -46
  210. package/dist/finance/ForexScreener.mjs +0 -44
  211. package/dist/finance/MarketOverview.js +0 -104
  212. package/dist/finance/MarketOverview.mjs +0 -102
  213. package/dist/finance/NewsTimeline.js +0 -44
  214. package/dist/finance/NewsTimeline.mjs +0 -42
  215. package/dist/finance/OrderEntry.js +0 -131
  216. package/dist/finance/OrderEntry.mjs +0 -129
  217. package/dist/finance/OrdersHistory.js +0 -64
  218. package/dist/finance/OrdersHistory.mjs +0 -62
  219. package/dist/finance/PositionsList.js +0 -80
  220. package/dist/finance/PositionsList.mjs +0 -78
  221. package/dist/finance/StockScreener.js +0 -46
  222. package/dist/finance/StockScreener.mjs +0 -44
  223. package/dist/finance/SymbolInfo.js +0 -46
  224. package/dist/finance/SymbolInfo.mjs +0 -44
  225. package/dist/finance/TechnicalAnalysis.js +0 -54
  226. package/dist/finance/TechnicalAnalysis.mjs +0 -52
  227. package/dist/finance/TickerTape.js +0 -56
  228. package/dist/finance/TickerTape.mjs +0 -54
  229. package/dist/finance/TradingPanel.js +0 -191
  230. package/dist/finance/TradingPanel.mjs +0 -189
  231. package/dist/finance/index.js +0 -930
  232. package/dist/finance/index.mjs +0 -914
  233. package/dist/form/index.js +0 -155
  234. package/dist/form/index.mjs +0 -125
  235. package/dist/form.js +0 -172
  236. package/dist/form.mjs +0 -142
  237. package/dist/hover-card.js +0 -58
  238. package/dist/hover-card.mjs +0 -34
  239. package/dist/index.js +0 -10179
  240. package/dist/index.mjs +0 -9785
  241. package/dist/input-otp.js +0 -79
  242. package/dist/input-otp.mjs +0 -54
  243. package/dist/lib/utils.js +0 -28
  244. package/dist/lib/utils.mjs +0 -24
  245. package/dist/navigation/index.js +0 -98
  246. package/dist/navigation/index.mjs +0 -79
  247. package/dist/navigation-menu.js +0 -149
  248. package/dist/navigation-menu.mjs +0 -116
  249. package/dist/pattern/grid-pattern.js +0 -333
  250. package/dist/pattern/grid-pattern.mjs +0 -310
  251. package/dist/pattern/index.js +0 -333
  252. package/dist/pattern/index.mjs +0 -310
  253. package/dist/popover.js +0 -63
  254. package/dist/popover.mjs +0 -37
  255. package/dist/primitives/index.js +0 -10179
  256. package/dist/primitives/index.mjs +0 -9785
  257. package/dist/primitives-export.js +0 -10179
  258. package/dist/primitives-export.mjs +0 -9785
  259. package/dist/progress.js +0 -62
  260. package/dist/progress.mjs +0 -37
  261. package/dist/project/gantt.js +0 -65
  262. package/dist/project/gantt.mjs +0 -43
  263. package/dist/project/index.js +0 -636
  264. package/dist/project/index.mjs +0 -611
  265. package/dist/project/kanban.js +0 -597
  266. package/dist/project/kanban.mjs +0 -572
  267. package/dist/project/list.js +0 -35
  268. package/dist/project/list.mjs +0 -12
  269. package/dist/radio-group.js +0 -68
  270. package/dist/radio-group.mjs +0 -45
  271. package/dist/resizable.js +0 -72
  272. package/dist/resizable.mjs +0 -48
  273. package/dist/scroll-area.js +0 -89
  274. package/dist/scroll-area.mjs +0 -66
  275. package/dist/select.js +0 -140
  276. package/dist/select.mjs +0 -111
  277. package/dist/separator.js +0 -59
  278. package/dist/separator.mjs +0 -34
  279. package/dist/sheet.js +0 -148
  280. package/dist/sheet.mjs +0 -117
  281. package/dist/skeleton.js +0 -32
  282. package/dist/skeleton.mjs +0 -27
  283. package/dist/slider.js +0 -99
  284. package/dist/slider.mjs +0 -73
  285. package/dist/sonner.js +0 -34
  286. package/dist/sonner.mjs +0 -29
  287. package/dist/switch.js +0 -62
  288. package/dist/switch.mjs +0 -37
  289. package/dist/table.js +0 -110
  290. package/dist/table.mjs +0 -101
  291. package/dist/tabs.js +0 -82
  292. package/dist/tabs.mjs +0 -57
  293. package/dist/tailwind/index.js +0 -2023
  294. package/dist/tailwind/index.mjs +0 -2011
  295. package/dist/textarea.js +0 -78
  296. package/dist/textarea.mjs +0 -56
  297. package/dist/toggle-group.js +0 -118
  298. package/dist/toggle-group.mjs +0 -93
  299. package/dist/toggle.js +0 -71
  300. package/dist/toggle.mjs +0 -48
  301. package/dist/tooltip.js +0 -67
  302. package/dist/tooltip.mjs +0 -40
  303. package/dist/types/index.js +0 -57
  304. package/dist/types/index.mjs +0 -51
  305. package/dist/ui/announcement.js +0 -129
  306. package/dist/ui/announcement.mjs +0 -107
  307. package/dist/ui/avatar-group.js +0 -88
  308. package/dist/ui/avatar-group.mjs +0 -65
  309. package/dist/ui/banner.js +0 -85
  310. package/dist/ui/banner.mjs +0 -62
  311. package/dist/ui/cursor.js +0 -78
  312. package/dist/ui/cursor.mjs +0 -56
  313. package/dist/ui/index.js +0 -475
  314. package/dist/ui/index.mjs +0 -442
  315. package/dist/ui/marquee.js +0 -74
  316. package/dist/ui/marquee.mjs +0 -52
  317. package/dist/ui/pill.js +0 -85
  318. package/dist/ui/pill.mjs +0 -62
  319. package/dist/ui/spinner.js +0 -28
  320. package/dist/ui/spinner.mjs +0 -26
  321. package/dist/ui/tags.js +0 -101
  322. package/dist/ui/tags.mjs +0 -79
  323. package/dist/ui/ticker.js +0 -73
  324. package/dist/ui/ticker.mjs +0 -51
  325. package/dist/util/index.js +0 -457
  326. package/dist/util/index.mjs +0 -399
@@ -1,2510 +0,0 @@
1
- "use client";
2
- import * as React6 from 'react';
3
- import { cva } from 'class-variance-authority';
4
- import { FileText, Check, Copy, MoreHorizontal, RotateCcw, Settings, GitCompare, Plus, Minus, Square, SplitSquareHorizontal, ChevronDown, FolderOpen, Folder, ChevronRight, Search, X, Eye, EyeOff, Terminal, RefreshCw, Play, AlertTriangle, Download, Minimize2, Maximize2, File, FileImage, FileCode } from 'lucide-react';
5
- import { codeToHtml, bundledLanguages } from 'shiki';
6
- import { cn } from '@hanzo/ui/lib/utils';
7
- import { Badge } from '@hanzo/ui/badge';
8
- import { Button } from '@hanzo/ui/button';
9
- import { Skeleton } from '@hanzo/ui/skeleton';
10
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
- import { CodeDiff } from '@hanzo/ui/code/diff';
12
- import { CodeSnippet } from '@hanzo/ui/code/snippet';
13
- import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@hanzo/ui/dropdown-menu';
14
- import { ScrollArea } from '@hanzo/ui/scroll-area';
15
- import { Tabs, TabsList, TabsTrigger, TabsContent } from '@hanzo/ui/tabs';
16
- import { ToggleGroup, ToggleGroupItem } from '@hanzo/ui/toggle-group';
17
- import Editor from '@monaco-editor/react';
18
- import { useTheme } from 'next-themes';
19
- import { Input } from '@hanzo/ui/input';
20
- import { Separator } from '@hanzo/ui/separator';
21
- import { Toggle } from '@hanzo/ui/toggle';
22
-
23
- var __defProp = Object.defineProperty;
24
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
25
- var codeBlockVariants = cva(
26
- "relative overflow-hidden rounded-lg border bg-muted/50",
27
- {
28
- variants: {
29
- theme: {
30
- dark: "bg-slate-950 border-slate-800",
31
- light: "bg-slate-50 border-slate-200",
32
- github: "bg-white border-slate-200",
33
- "github-dark": "bg-slate-900 border-slate-700",
34
- "vs-dark": "bg-slate-950 border-slate-800",
35
- monokai: "bg-slate-900 border-slate-700",
36
- dracula: "bg-slate-900 border-purple-800/30",
37
- nord: "bg-slate-800 border-slate-600"
38
- },
39
- size: {
40
- sm: "text-xs",
41
- default: "text-sm",
42
- lg: "text-base"
43
- }
44
- },
45
- defaultVariants: {
46
- theme: "dark",
47
- size: "default"
48
- }
49
- }
50
- );
51
- var CodeLine = React6.memo(
52
- ({
53
- line,
54
- lineNumber,
55
- showLineNumbers,
56
- isHighlighted = false,
57
- isDiffAdded = false,
58
- isDiffRemoved = false
59
- }) => {
60
- return /* @__PURE__ */ jsxs(
61
- "div",
62
- {
63
- className: cn(
64
- "group relative flex min-h-[1.5rem] items-center px-4 py-0.5",
65
- isHighlighted && "bg-blue-500/10 border-l-2 border-l-blue-500",
66
- isDiffAdded && "bg-green-500/10 border-l-2 border-l-green-500",
67
- isDiffRemoved && "bg-red-500/10 border-l-2 border-l-red-500"
68
- ),
69
- children: [
70
- showLineNumbers && /* @__PURE__ */ jsx("span", { className: "mr-4 inline-block w-8 select-none text-right text-xs text-muted-foreground", children: lineNumber }),
71
- (isDiffAdded || isDiffRemoved) && /* @__PURE__ */ jsx("span", { className: "mr-2 select-none text-xs", children: isDiffAdded ? "+" : isDiffRemoved ? "-" : " " }),
72
- /* @__PURE__ */ jsx(
73
- "div",
74
- {
75
- className: "flex-1 overflow-x-auto",
76
- dangerouslySetInnerHTML: { __html: line }
77
- }
78
- )
79
- ]
80
- }
81
- );
82
- }
83
- );
84
- CodeLine.displayName = "CodeLine";
85
- var CodeBlock = React6.forwardRef(
86
- ({
87
- className,
88
- theme,
89
- size,
90
- code,
91
- language = "text",
92
- filename,
93
- showLineNumbers = true,
94
- highlightLines = [],
95
- showCopyButton = true,
96
- maxHeight = "400px",
97
- diff,
98
- ...props
99
- }, ref) => {
100
- const [highlightedCode, setHighlightedCode] = React6.useState("");
101
- const [copied, setCopied] = React6.useState(false);
102
- const [isLoading, setIsLoading] = React6.useState(true);
103
- const themeMap = {
104
- dark: "github-dark",
105
- light: "github-light",
106
- github: "github-light",
107
- "github-dark": "github-dark",
108
- "vs-dark": "dark-plus",
109
- monokai: "monokai",
110
- dracula: "dracula",
111
- nord: "nord"
112
- };
113
- React6.useEffect(() => {
114
- const highlightCode = /* @__PURE__ */ __name(async () => {
115
- setIsLoading(true);
116
- try {
117
- const highlighted = await codeToHtml(code, {
118
- lang: language,
119
- theme: themeMap[theme || "dark"],
120
- transformers: [
121
- {
122
- code(node) {
123
- const codeElement2 = node.children.find(
124
- (child) => child.type === "element" && child.tagName === "code"
125
- );
126
- if (codeElement2 && "children" in codeElement2 && codeElement2.children) {
127
- node.children = codeElement2.children;
128
- }
129
- }
130
- }
131
- ]
132
- });
133
- const tempDiv = document.createElement("div");
134
- tempDiv.innerHTML = highlighted;
135
- const codeElement = tempDiv.querySelector("code");
136
- setHighlightedCode(codeElement?.innerHTML || highlighted);
137
- } catch (error) {
138
- console.error("Failed to highlight code:", error);
139
- setHighlightedCode(code);
140
- } finally {
141
- setIsLoading(false);
142
- }
143
- }, "highlightCode");
144
- highlightCode();
145
- }, [code, language, theme]);
146
- const copyToClipboard = React6.useCallback(async () => {
147
- try {
148
- await navigator.clipboard.writeText(code);
149
- setCopied(true);
150
- setTimeout(() => setCopied(false), 2e3);
151
- } catch (error) {
152
- console.error("Failed to copy code:", error);
153
- }
154
- }, [code]);
155
- const lines = highlightedCode.split("\n");
156
- return /* @__PURE__ */ jsxs(
157
- "div",
158
- {
159
- ref,
160
- "data-testid": "code-block",
161
- className: cn(codeBlockVariants({ theme, size, className })),
162
- ...props,
163
- children: [
164
- (filename || language || showCopyButton) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2", children: [
165
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
166
- filename && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
167
- /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5 text-muted-foreground" }),
168
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: filename })
169
- ] }),
170
- language && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: language })
171
- ] }),
172
- showCopyButton && /* @__PURE__ */ jsxs(
173
- Button,
174
- {
175
- variant: "ghost",
176
- size: "sm",
177
- onClick: copyToClipboard,
178
- className: "h-8 w-8 p-0",
179
- children: [
180
- copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }),
181
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: copied ? "Copied" : "Copy code" })
182
- ]
183
- }
184
- )
185
- ] }),
186
- /* @__PURE__ */ jsx("div", { className: "overflow-auto", style: { maxHeight }, children: isLoading ? /* @__PURE__ */ jsx("div", { className: "p-4 space-y-2", children: Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
187
- showLineNumbers && /* @__PURE__ */ jsx(Skeleton, { "data-testid": "skeleton", className: "h-4 w-8" }),
188
- /* @__PURE__ */ jsx(Skeleton, { "data-testid": "skeleton", className: "h-4 flex-1" })
189
- ] }, i)) }) : /* @__PURE__ */ jsx("div", { className: "font-mono", children: lines.map((line, index) => {
190
- const lineNumber = index + 1;
191
- const isHighlighted = highlightLines.includes(lineNumber);
192
- const isDiffAdded = diff?.added?.includes(lineNumber);
193
- const isDiffRemoved = diff?.removed?.includes(lineNumber);
194
- return /* @__PURE__ */ jsx(
195
- CodeLine,
196
- {
197
- line,
198
- lineNumber,
199
- showLineNumbers,
200
- isHighlighted,
201
- isDiffAdded,
202
- isDiffRemoved
203
- },
204
- index
205
- );
206
- }) }) })
207
- ]
208
- }
209
- );
210
- }
211
- );
212
- CodeBlock.displayName = "CodeBlock";
213
- var codeCompareVariants = cva(
214
- "relative flex flex-col overflow-hidden rounded-lg border bg-background",
215
- {
216
- variants: {
217
- layout: {
218
- horizontal: "flex-col",
219
- vertical: "flex-row"
220
- },
221
- size: {
222
- sm: "text-xs",
223
- default: "text-sm",
224
- lg: "text-base"
225
- }
226
- },
227
- defaultVariants: {
228
- layout: "horizontal",
229
- size: "default"
230
- }
231
- }
232
- );
233
- var CodeCompare = React6.forwardRef(
234
- ({
235
- className,
236
- layout,
237
- size,
238
- files,
239
- defaultView = "side-by-side",
240
- showLineNumbers = true,
241
- showCopyButton = true,
242
- allowMerge = false,
243
- conflictMarkers = [],
244
- onMergeConflict,
245
- onFileSelect,
246
- height = "600px",
247
- syncScroll = true,
248
- ...props
249
- }, ref) => {
250
- const [view, setView] = React6.useState(defaultView);
251
- const [selectedFiles, setSelectedFiles] = React6.useState(() => {
252
- if (files.length >= 2) {
253
- return [files[0].id, files[1].id];
254
- }
255
- return files.length > 0 ? [files[0].id] : [];
256
- });
257
- const [mergeState, setMergeState] = React6.useState({
258
- resolved: {},
259
- conflicts: conflictMarkers
260
- });
261
- const [copied, setCopied] = React6.useState(false);
262
- const scrollRefs = React6.useRef({});
263
- const handleScroll = React6.useCallback(
264
- (sourceId, scrollTop) => {
265
- if (!syncScroll) return;
266
- Object.entries(scrollRefs.current).forEach(([id, ref2]) => {
267
- if (id !== sourceId && ref2) {
268
- ref2.scrollTop = scrollTop;
269
- }
270
- });
271
- },
272
- [syncScroll]
273
- );
274
- const copyComparison = React6.useCallback(async () => {
275
- const comparison = selectedFiles.map((fileId) => {
276
- const file = files.find((f) => f.id === fileId);
277
- return file ? `=== ${file.filename} ===
278
- ${file.content}` : "";
279
- }).join("\n\n");
280
- try {
281
- await navigator.clipboard.writeText(comparison);
282
- setCopied(true);
283
- setTimeout(() => setCopied(false), 2e3);
284
- } catch (error) {
285
- console.error("Failed to copy comparison:", error);
286
- }
287
- }, [files, selectedFiles]);
288
- const resolveConflict = React6.useCallback(
289
- (conflictIndex, resolution) => {
290
- setMergeState((prev) => ({
291
- ...prev,
292
- resolved: {
293
- ...prev.resolved,
294
- [conflictIndex]: resolution
295
- }
296
- }));
297
- onMergeConflict?.(resolution);
298
- },
299
- [onMergeConflict]
300
- );
301
- const getFileStats = React6.useCallback(
302
- (file1, file2) => {
303
- const lines1 = file1.content.split("\n");
304
- const lines2 = file2.content.split("\n");
305
- let added = 0;
306
- let removed = 0;
307
- let modified = 0;
308
- const maxLines = Math.max(lines1.length, lines2.length);
309
- for (let i = 0; i < maxLines; i++) {
310
- const line1 = lines1[i];
311
- const line2 = lines2[i];
312
- if (line1 === void 0) {
313
- added++;
314
- } else if (line2 === void 0) {
315
- removed++;
316
- } else if (line1 !== line2) {
317
- modified++;
318
- }
319
- }
320
- return { added, removed, modified };
321
- },
322
- []
323
- );
324
- const renderFileSelector = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap", children: files.map((file, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
325
- /* @__PURE__ */ jsxs(
326
- Button,
327
- {
328
- variant: selectedFiles.includes(file.id) ? "default" : "outline",
329
- size: "sm",
330
- onClick: () => {
331
- if (view === "three-way") {
332
- setSelectedFiles((prev) => {
333
- if (prev.includes(file.id)) {
334
- return prev.filter((id) => id !== file.id);
335
- }
336
- return prev.length < 3 ? [...prev, file.id] : [prev[1], prev[2], file.id];
337
- });
338
- } else {
339
- setSelectedFiles((prev) => {
340
- if (prev.includes(file.id)) {
341
- return prev.filter((id) => id !== file.id);
342
- }
343
- return prev.length < 2 ? [...prev, file.id] : [prev[1], file.id];
344
- });
345
- }
346
- onFileSelect?.(file.id);
347
- },
348
- className: "gap-2",
349
- children: [
350
- file.label || file.filename,
351
- file.version && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: file.version })
352
- ]
353
- }
354
- ),
355
- index < files.length - 1 && view !== "three-way" && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "vs" })
356
- ] }, file.id)) }), "renderFileSelector");
357
- const renderSideBySide = /* @__PURE__ */ __name(() => {
358
- const [file1, file2] = selectedFiles.map((id) => files.find((f) => f.id === id)).filter(Boolean);
359
- if (!file1 || !file2) {
360
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-32 text-muted-foreground", children: "Select two files to compare" });
361
- }
362
- const stats = getFileStats(file1, file2);
363
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
364
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between border-b px-4 py-2 bg-muted/30", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
365
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Comparison" }),
366
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs", children: [
367
- /* @__PURE__ */ jsxs("span", { className: "text-green-600", children: [
368
- "+",
369
- stats.added
370
- ] }),
371
- /* @__PURE__ */ jsxs("span", { className: "text-red-600", children: [
372
- "-",
373
- stats.removed
374
- ] }),
375
- /* @__PURE__ */ jsxs("span", { className: "text-yellow-600", children: [
376
- "~",
377
- stats.modified
378
- ] })
379
- ] })
380
- ] }) }),
381
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(
382
- CodeDiff,
383
- {
384
- oldCode: file1.content,
385
- newCode: file2.content,
386
- language: file1.language,
387
- oldFilename: file1.filename,
388
- newFilename: file2.filename,
389
- showLineNumbers,
390
- showCopyButton: false,
391
- maxHeight: "100%",
392
- defaultView: "split",
393
- className: "border-0 rounded-none h-full"
394
- }
395
- ) })
396
- ] });
397
- }, "renderSideBySide");
398
- const renderUnified = /* @__PURE__ */ __name(() => {
399
- const [file1, file2] = selectedFiles.map((id) => files.find((f) => f.id === id)).filter(Boolean);
400
- if (!file1 || !file2) {
401
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-32 text-muted-foreground", children: "Select two files to compare" });
402
- }
403
- return /* @__PURE__ */ jsx(
404
- CodeDiff,
405
- {
406
- oldCode: file1.content,
407
- newCode: file2.content,
408
- language: file1.language,
409
- filename: `${file1.filename} \u2192 ${file2.filename}`,
410
- showLineNumbers,
411
- showCopyButton: false,
412
- maxHeight: "100%",
413
- defaultView: "unified",
414
- className: "border-0 rounded-none"
415
- }
416
- );
417
- }, "renderUnified");
418
- const renderThreeWay = /* @__PURE__ */ __name(() => {
419
- const [base, current, incoming] = selectedFiles.map((id) => files.find((f) => f.id === id)).filter(Boolean);
420
- if (!base || !current || !incoming) {
421
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-32 text-muted-foreground", children: "Select three files for three-way merge (base, current, incoming)" });
422
- }
423
- return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-0 h-full", children: [
424
- /* @__PURE__ */ jsxs("div", { className: "border-r", children: [
425
- /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-2 bg-muted/30", children: /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
426
- "Base (",
427
- base.filename,
428
- ")"
429
- ] }) }),
430
- /* @__PURE__ */ jsx(
431
- ScrollArea,
432
- {
433
- className: "h-full",
434
- ref: (ref2) => {
435
- scrollRefs.current["base"] = ref2;
436
- },
437
- onScrollCapture: (e) => {
438
- const target = e.target;
439
- handleScroll("base", target.scrollTop);
440
- },
441
- children: /* @__PURE__ */ jsx(
442
- CodeSnippet,
443
- {
444
- code: base.content,
445
- language: base.language,
446
- showLineNumbers,
447
- showCopyButton: false,
448
- showHeader: false,
449
- className: "border-0 rounded-none"
450
- }
451
- )
452
- }
453
- )
454
- ] }),
455
- /* @__PURE__ */ jsxs("div", { className: "border-r", children: [
456
- /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-2 bg-blue-500/10", children: /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-blue-600 dark:text-blue-400", children: [
457
- "Current (",
458
- current.filename,
459
- ")"
460
- ] }) }),
461
- /* @__PURE__ */ jsx(
462
- ScrollArea,
463
- {
464
- className: "h-full",
465
- ref: (ref2) => {
466
- scrollRefs.current["current"] = ref2;
467
- },
468
- onScrollCapture: (e) => {
469
- const target = e.target;
470
- handleScroll("current", target.scrollTop);
471
- },
472
- children: /* @__PURE__ */ jsx(
473
- CodeSnippet,
474
- {
475
- code: current.content,
476
- language: current.language,
477
- showLineNumbers,
478
- showCopyButton: false,
479
- showHeader: false,
480
- className: "border-0 rounded-none"
481
- }
482
- )
483
- }
484
- )
485
- ] }),
486
- /* @__PURE__ */ jsxs("div", { children: [
487
- /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-2 bg-green-500/10", children: /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-green-600 dark:text-green-400", children: [
488
- "Incoming (",
489
- incoming.filename,
490
- ")"
491
- ] }) }),
492
- /* @__PURE__ */ jsx(
493
- ScrollArea,
494
- {
495
- className: "h-full",
496
- ref: (ref2) => {
497
- scrollRefs.current["incoming"] = ref2;
498
- },
499
- onScrollCapture: (e) => {
500
- const target = e.target;
501
- handleScroll("incoming", target.scrollTop);
502
- },
503
- children: /* @__PURE__ */ jsx(
504
- CodeSnippet,
505
- {
506
- code: incoming.content,
507
- language: incoming.language,
508
- showLineNumbers,
509
- showCopyButton: false,
510
- showHeader: false,
511
- className: "border-0 rounded-none"
512
- }
513
- )
514
- }
515
- )
516
- ] })
517
- ] });
518
- }, "renderThreeWay");
519
- const renderMergeConflicts = /* @__PURE__ */ __name(() => {
520
- if (!allowMerge || mergeState.conflicts.length === 0) return null;
521
- return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/30", children: /* @__PURE__ */ jsxs("div", { className: "px-4 py-2", children: [
522
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
523
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Merge Conflicts" }),
524
- /* @__PURE__ */ jsx(Badge, { variant: "destructive", className: "text-xs", children: mergeState.conflicts.length })
525
- ] }),
526
- /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: mergeState.conflicts.map((conflict, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
527
- /* @__PURE__ */ jsxs("span", { children: [
528
- "Lines ",
529
- conflict.startLine,
530
- "-",
531
- conflict.endLine
532
- ] }),
533
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
534
- /* @__PURE__ */ jsx(
535
- Button,
536
- {
537
- variant: "outline",
538
- size: "sm",
539
- onClick: () => resolveConflict(index, "current"),
540
- className: "h-6 text-xs",
541
- children: "Keep Current"
542
- }
543
- ),
544
- /* @__PURE__ */ jsx(
545
- Button,
546
- {
547
- variant: "outline",
548
- size: "sm",
549
- onClick: () => resolveConflict(index, "incoming"),
550
- className: "h-6 text-xs",
551
- children: "Accept Incoming"
552
- }
553
- )
554
- ] })
555
- ] }, index)) })
556
- ] }) });
557
- }, "renderMergeConflicts");
558
- return /* @__PURE__ */ jsxs(
559
- "div",
560
- {
561
- ref,
562
- className: cn(codeCompareVariants({ layout, size }), className),
563
- style: { height },
564
- ...props,
565
- children: [
566
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2", children: [
567
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: renderFileSelector() }),
568
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
569
- /* @__PURE__ */ jsx(Tabs, { value: view, onValueChange: (value) => setView(value), children: /* @__PURE__ */ jsxs(TabsList, { className: "grid w-full grid-cols-3", children: [
570
- /* @__PURE__ */ jsx(TabsTrigger, { value: "side-by-side", className: "text-xs", children: "Side by Side" }),
571
- /* @__PURE__ */ jsx(TabsTrigger, { value: "unified", className: "text-xs", children: "Unified" }),
572
- /* @__PURE__ */ jsx(TabsTrigger, { value: "three-way", className: "text-xs", children: "Three-way" })
573
- ] }) }),
574
- showCopyButton && /* @__PURE__ */ jsx(
575
- Button,
576
- {
577
- variant: "ghost",
578
- size: "sm",
579
- onClick: copyComparison,
580
- className: "h-8 w-8 p-0",
581
- children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
582
- }
583
- ),
584
- /* @__PURE__ */ jsxs(DropdownMenu, { children: [
585
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "h-8 w-8 p-0", children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-3.5 w-3.5" }) }) }),
586
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
587
- /* @__PURE__ */ jsxs(
588
- DropdownMenuItem,
589
- {
590
- onClick: () => setSelectedFiles([]),
591
- className: "gap-2",
592
- children: [
593
- /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" }),
594
- "Reset Selection"
595
- ]
596
- }
597
- ),
598
- /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "gap-2", children: [
599
- /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }),
600
- "Settings"
601
- ] })
602
- ] })
603
- ] })
604
- ] })
605
- ] }),
606
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0", children: [
607
- view === "side-by-side" && renderSideBySide(),
608
- view === "unified" && renderUnified(),
609
- view === "three-way" && renderThreeWay()
610
- ] }),
611
- renderMergeConflicts()
612
- ]
613
- }
614
- );
615
- }
616
- );
617
- CodeCompare.displayName = "CodeCompare";
618
- var codeDiffVariants = cva(
619
- "relative overflow-hidden rounded-lg border bg-muted/50",
620
- {
621
- variants: {
622
- theme: {
623
- dark: "bg-slate-950 border-slate-800",
624
- light: "bg-slate-50 border-slate-200",
625
- github: "bg-white border-slate-200",
626
- "github-dark": "bg-slate-900 border-slate-700"
627
- },
628
- size: {
629
- sm: "text-xs",
630
- default: "text-sm",
631
- lg: "text-base"
632
- }
633
- },
634
- defaultVariants: {
635
- theme: "dark",
636
- size: "default"
637
- }
638
- }
639
- );
640
- var DiffLine = React6.memo(
641
- ({
642
- change,
643
- showLineNumbers,
644
- isUnified = false,
645
- highlightedContent
646
- }) => {
647
- const getLineClasses = /* @__PURE__ */ __name(() => {
648
- const base = "group relative flex min-h-[1.5rem] items-center py-0.5";
649
- if (isUnified) {
650
- switch (change.type) {
651
- case "add":
652
- return cn(base, "bg-green-500/10 border-l-2 border-l-green-500");
653
- case "remove":
654
- return cn(base, "bg-red-500/10 border-l-2 border-l-red-500");
655
- default:
656
- return cn(base, "hover:bg-muted/50");
657
- }
658
- }
659
- return cn(base, "hover:bg-muted/50");
660
- }, "getLineClasses");
661
- const getLinePrefix = /* @__PURE__ */ __name(() => {
662
- if (!isUnified) return null;
663
- switch (change.type) {
664
- case "add":
665
- return /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3 text-green-500 mr-2 flex-shrink-0" });
666
- case "remove":
667
- return /* @__PURE__ */ jsx(Minus, { className: "h-3 w-3 text-red-500 mr-2 flex-shrink-0" });
668
- default:
669
- return /* @__PURE__ */ jsx("span", { className: "w-3 mr-2 flex-shrink-0" });
670
- }
671
- }, "getLinePrefix");
672
- return /* @__PURE__ */ jsx("div", { className: getLineClasses(), children: /* @__PURE__ */ jsxs("div", { className: "flex items-center px-4 w-full", children: [
673
- showLineNumbers && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 mr-4", children: !isUnified ? /* @__PURE__ */ jsxs(Fragment, { children: [
674
- /* @__PURE__ */ jsx("span", { className: "inline-block w-8 select-none text-right text-xs text-muted-foreground", children: change.oldLineNumber || "" }),
675
- /* @__PURE__ */ jsx("span", { className: "inline-block w-8 select-none text-right text-xs text-muted-foreground", children: change.newLineNumber || "" })
676
- ] }) : /* @__PURE__ */ jsx("span", { className: "inline-block w-8 select-none text-right text-xs text-muted-foreground", children: change.lineNumber }) }),
677
- getLinePrefix(),
678
- /* @__PURE__ */ jsx(
679
- "div",
680
- {
681
- className: "flex-1 overflow-x-auto font-mono",
682
- dangerouslySetInnerHTML: {
683
- __html: highlightedContent || change.content
684
- }
685
- }
686
- )
687
- ] }) });
688
- }
689
- );
690
- DiffLine.displayName = "DiffLine";
691
- function generateDiff(oldCode, newCode) {
692
- const oldLines = oldCode.split("\n");
693
- const newLines = newCode.split("\n");
694
- const changes = [];
695
- let oldIndex = 0;
696
- let newIndex = 0;
697
- let lineNumber = 1;
698
- while (oldIndex < oldLines.length || newIndex < newLines.length) {
699
- const oldLine = oldLines[oldIndex];
700
- const newLine = newLines[newIndex];
701
- if (oldIndex >= oldLines.length) {
702
- changes.push({
703
- type: "add",
704
- lineNumber: lineNumber++,
705
- content: newLine,
706
- newLineNumber: newIndex + 1
707
- });
708
- newIndex++;
709
- } else if (newIndex >= newLines.length) {
710
- changes.push({
711
- type: "remove",
712
- lineNumber: lineNumber++,
713
- content: oldLine,
714
- oldLineNumber: oldIndex + 1
715
- });
716
- oldIndex++;
717
- } else if (oldLine === newLine) {
718
- changes.push({
719
- type: "unchanged",
720
- lineNumber: lineNumber++,
721
- content: oldLine,
722
- oldLineNumber: oldIndex + 1,
723
- newLineNumber: newIndex + 1
724
- });
725
- oldIndex++;
726
- newIndex++;
727
- } else {
728
- changes.push({
729
- type: "remove",
730
- lineNumber: lineNumber++,
731
- content: oldLine,
732
- oldLineNumber: oldIndex + 1
733
- });
734
- changes.push({
735
- type: "add",
736
- lineNumber: lineNumber++,
737
- content: newLine,
738
- newLineNumber: newIndex + 1
739
- });
740
- oldIndex++;
741
- newIndex++;
742
- }
743
- }
744
- return changes;
745
- }
746
- __name(generateDiff, "generateDiff");
747
- var CodeDiff2 = React6.forwardRef(
748
- ({
749
- className,
750
- theme,
751
- size,
752
- oldCode,
753
- newCode,
754
- language = "text",
755
- filename,
756
- oldFilename,
757
- newFilename,
758
- showLineNumbers = true,
759
- showCopyButton = true,
760
- maxHeight = "500px",
761
- defaultView = "unified",
762
- collapseUnchanged = false,
763
- contextLines = 3,
764
- ...props
765
- }, ref) => {
766
- const [view, setView] = React6.useState(defaultView);
767
- const [copied, setCopied] = React6.useState(false);
768
- const [isLoading, setIsLoading] = React6.useState(true);
769
- const [highlightedOldCode, setHighlightedOldCode] = React6.useState("");
770
- const [highlightedNewCode, setHighlightedNewCode] = React6.useState("");
771
- const [changes, setChanges] = React6.useState([]);
772
- const themeMap = {
773
- dark: "github-dark",
774
- light: "github-light",
775
- github: "github-light",
776
- "github-dark": "github-dark"
777
- };
778
- React6.useEffect(() => {
779
- const highlightCode = /* @__PURE__ */ __name(async () => {
780
- setIsLoading(true);
781
- try {
782
- const [oldHighlighted, newHighlighted] = await Promise.all([
783
- codeToHtml(oldCode, {
784
- lang: language,
785
- theme: themeMap[theme || "dark"]
786
- }),
787
- codeToHtml(newCode, {
788
- lang: language,
789
- theme: themeMap[theme || "dark"]
790
- })
791
- ]);
792
- const extractContent = /* @__PURE__ */ __name((html) => {
793
- const tempDiv = document.createElement("div");
794
- tempDiv.innerHTML = html;
795
- const codeElement = tempDiv.querySelector("code");
796
- return codeElement?.innerHTML || html;
797
- }, "extractContent");
798
- setHighlightedOldCode(extractContent(oldHighlighted));
799
- setHighlightedNewCode(extractContent(newHighlighted));
800
- const diffChanges = generateDiff(oldCode, newCode);
801
- setChanges(diffChanges);
802
- } catch (error) {
803
- console.error("Failed to highlight code:", error);
804
- setHighlightedOldCode(oldCode);
805
- setHighlightedNewCode(newCode);
806
- setChanges(generateDiff(oldCode, newCode));
807
- } finally {
808
- setIsLoading(false);
809
- }
810
- }, "highlightCode");
811
- highlightCode();
812
- }, [oldCode, newCode, language, theme]);
813
- const copyToClipboard = React6.useCallback(async () => {
814
- try {
815
- const diffText = view === "unified" ? changes.map((change) => {
816
- const prefix = change.type === "add" ? "+" : change.type === "remove" ? "-" : " ";
817
- return `${prefix}${change.content}`;
818
- }).join("\n") : `--- ${oldFilename || "old"}
819
- +++ ${newFilename || "new"}
820
- ${oldCode}
821
-
822
- ${newCode}`;
823
- await navigator.clipboard.writeText(diffText);
824
- setCopied(true);
825
- setTimeout(() => setCopied(false), 2e3);
826
- } catch (error) {
827
- console.error("Failed to copy diff:", error);
828
- }
829
- }, [changes, view, oldCode, newCode, oldFilename, newFilename]);
830
- const filteredChanges = React6.useMemo(() => {
831
- if (!collapseUnchanged) return changes;
832
- const result = [];
833
- let i = 0;
834
- while (i < changes.length) {
835
- const change = changes[i];
836
- if (change.type !== "unchanged") {
837
- result.push(change);
838
- i++;
839
- continue;
840
- }
841
- const unchangedStart = i;
842
- while (i < changes.length && changes[i].type === "unchanged") {
843
- i++;
844
- }
845
- const unchangedCount = i - unchangedStart;
846
- if (unchangedCount <= contextLines * 2) {
847
- for (let j = unchangedStart; j < i; j++) {
848
- result.push(changes[j]);
849
- }
850
- } else {
851
- for (let j = unchangedStart; j < unchangedStart + contextLines; j++) {
852
- result.push(changes[j]);
853
- }
854
- result.push({
855
- type: "unchanged",
856
- lineNumber: -1,
857
- content: `... ${unchangedCount - contextLines * 2} unchanged lines ...`
858
- });
859
- for (let j = i - contextLines; j < i; j++) {
860
- result.push(changes[j]);
861
- }
862
- }
863
- }
864
- return result;
865
- }, [changes, collapseUnchanged, contextLines]);
866
- const stats = React6.useMemo(() => {
867
- const additions = changes.filter((c) => c.type === "add").length;
868
- const deletions = changes.filter((c) => c.type === "remove").length;
869
- return { additions, deletions };
870
- }, [changes]);
871
- const renderUnifiedView = /* @__PURE__ */ __name(() => {
872
- const oldLines = highlightedOldCode.split("\n");
873
- const newLines = highlightedNewCode.split("\n");
874
- return /* @__PURE__ */ jsx("div", { className: "font-mono", children: filteredChanges.map((change, index) => {
875
- if (change.lineNumber === -1) {
876
- return /* @__PURE__ */ jsx(
877
- "div",
878
- {
879
- className: "flex items-center justify-center py-2 text-muted-foreground bg-muted/30",
880
- children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: change.content })
881
- },
882
- index
883
- );
884
- }
885
- let highlightedContent = change.content;
886
- if (change.type === "add" && change.newLineNumber) {
887
- highlightedContent = newLines[change.newLineNumber - 1] || change.content;
888
- } else if (change.type === "remove" && change.oldLineNumber) {
889
- highlightedContent = oldLines[change.oldLineNumber - 1] || change.content;
890
- } else if (change.type === "unchanged" && change.oldLineNumber) {
891
- highlightedContent = oldLines[change.oldLineNumber - 1] || change.content;
892
- }
893
- return /* @__PURE__ */ jsx(
894
- DiffLine,
895
- {
896
- change,
897
- showLineNumbers,
898
- isUnified: true,
899
- highlightedContent
900
- },
901
- index
902
- );
903
- }) });
904
- }, "renderUnifiedView");
905
- const renderSplitView = /* @__PURE__ */ __name(() => {
906
- const oldLines = highlightedOldCode.split("\n");
907
- const newLines = highlightedNewCode.split("\n");
908
- return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-0", children: [
909
- /* @__PURE__ */ jsxs("div", { className: "border-r", children: [
910
- /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-2 bg-red-500/10", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-red-600 dark:text-red-400", children: oldFilename || filename || "Old" }) }),
911
- /* @__PURE__ */ jsx("div", { className: "font-mono", children: oldLines.map((line, index) => /* @__PURE__ */ jsx(
912
- DiffLine,
913
- {
914
- change: {
915
- type: "remove",
916
- lineNumber: index + 1,
917
- content: line,
918
- oldLineNumber: index + 1
919
- },
920
- showLineNumbers,
921
- highlightedContent: line
922
- },
923
- `old-${index}`
924
- )) })
925
- ] }),
926
- /* @__PURE__ */ jsxs("div", { children: [
927
- /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-2 bg-green-500/10", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-green-600 dark:text-green-400", children: newFilename || filename || "New" }) }),
928
- /* @__PURE__ */ jsx("div", { className: "font-mono", children: newLines.map((line, index) => /* @__PURE__ */ jsx(
929
- DiffLine,
930
- {
931
- change: {
932
- type: "add",
933
- lineNumber: index + 1,
934
- content: line,
935
- newLineNumber: index + 1
936
- },
937
- showLineNumbers,
938
- highlightedContent: line
939
- },
940
- `new-${index}`
941
- )) })
942
- ] })
943
- ] });
944
- }, "renderSplitView");
945
- return /* @__PURE__ */ jsxs(
946
- "div",
947
- {
948
- ref,
949
- className: cn(codeDiffVariants({ theme, size, className })),
950
- ...props,
951
- children: [
952
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2", children: [
953
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
954
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
955
- /* @__PURE__ */ jsx(GitCompare, { className: "h-4 w-4 text-muted-foreground" }),
956
- filename && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: filename }),
957
- language && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: language })
958
- ] }),
959
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
960
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
961
- /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3 text-green-500" }),
962
- stats.additions
963
- ] }),
964
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
965
- /* @__PURE__ */ jsx(Minus, { className: "h-3 w-3 text-red-500" }),
966
- stats.deletions
967
- ] })
968
- ] })
969
- ] }),
970
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
971
- /* @__PURE__ */ jsxs(
972
- ToggleGroup,
973
- {
974
- type: "single",
975
- value: view,
976
- onValueChange: (value) => value && setView(value),
977
- size: "sm",
978
- children: [
979
- /* @__PURE__ */ jsx(ToggleGroupItem, { value: "unified", "aria-label": "Unified view", children: /* @__PURE__ */ jsx(Square, { className: "h-3 w-3" }) }),
980
- /* @__PURE__ */ jsx(ToggleGroupItem, { value: "split", "aria-label": "Split view", children: /* @__PURE__ */ jsx(SplitSquareHorizontal, { className: "h-3 w-3" }) })
981
- ]
982
- }
983
- ),
984
- showCopyButton && /* @__PURE__ */ jsxs(
985
- Button,
986
- {
987
- variant: "ghost",
988
- size: "sm",
989
- onClick: copyToClipboard,
990
- className: "h-8 w-8 p-0",
991
- children: [
992
- copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }),
993
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: copied ? "Copied" : "Copy diff" })
994
- ]
995
- }
996
- )
997
- ] })
998
- ] }),
999
- /* @__PURE__ */ jsx("div", { className: "overflow-auto", style: { maxHeight }, children: isLoading ? /* @__PURE__ */ jsx("div", { className: "p-4 space-y-2", children: Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
1000
- showLineNumbers && /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-8" }),
1001
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 flex-1" })
1002
- ] }, i)) }) : view === "unified" ? renderUnifiedView() : renderSplitView() })
1003
- ]
1004
- }
1005
- );
1006
- }
1007
- );
1008
- CodeDiff2.displayName = "CodeDiff";
1009
- var defaultLanguages = [
1010
- "javascript",
1011
- "typescript",
1012
- "python",
1013
- "java",
1014
- "csharp",
1015
- "cpp",
1016
- "c",
1017
- "go",
1018
- "rust",
1019
- "ruby",
1020
- "php",
1021
- "swift",
1022
- "kotlin",
1023
- "dart",
1024
- "html",
1025
- "css",
1026
- "scss",
1027
- "json",
1028
- "xml",
1029
- "yaml",
1030
- "sql",
1031
- "markdown",
1032
- "shell",
1033
- "plaintext"
1034
- ];
1035
- var languageDisplayNames = {
1036
- javascript: "JavaScript",
1037
- typescript: "TypeScript",
1038
- python: "Python",
1039
- java: "Java",
1040
- csharp: "C#",
1041
- cpp: "C++",
1042
- c: "C",
1043
- go: "Go",
1044
- rust: "Rust",
1045
- ruby: "Ruby",
1046
- php: "PHP",
1047
- swift: "Swift",
1048
- kotlin: "Kotlin",
1049
- dart: "Dart",
1050
- html: "HTML",
1051
- css: "CSS",
1052
- scss: "SCSS",
1053
- json: "JSON",
1054
- xml: "XML",
1055
- yaml: "YAML",
1056
- sql: "SQL",
1057
- markdown: "Markdown",
1058
- shell: "Shell",
1059
- plaintext: "Plain Text"
1060
- };
1061
- var CodeEditor = React6.forwardRef(
1062
- ({
1063
- value,
1064
- defaultValue = "",
1065
- language = "javascript",
1066
- height = "400px",
1067
- theme: themeProp = "auto",
1068
- onChange,
1069
- onMount,
1070
- readOnly = false,
1071
- lineNumbers = true,
1072
- minimap = false,
1073
- wordWrap = "on",
1074
- fontSize = 14,
1075
- className,
1076
- showCopyButton = true,
1077
- showLanguageSelector = true,
1078
- availableLanguages = defaultLanguages
1079
- }, ref) => {
1080
- const { theme: systemTheme } = useTheme();
1081
- const [selectedLanguage, setSelectedLanguage] = React6.useState(language);
1082
- const [copied, setCopied] = React6.useState(false);
1083
- const [editorValue, setEditorValue] = React6.useState(value || defaultValue);
1084
- React6.useEffect(() => {
1085
- if (value !== void 0) {
1086
- setEditorValue(value);
1087
- }
1088
- }, [value]);
1089
- const handleCopy = React6.useCallback(async () => {
1090
- try {
1091
- await navigator.clipboard.writeText(editorValue);
1092
- setCopied(true);
1093
- setTimeout(() => setCopied(false), 2e3);
1094
- } catch (err) {
1095
- console.error("Failed to copy:", err);
1096
- }
1097
- }, [editorValue]);
1098
- const handleChange = React6.useCallback(
1099
- (newValue, ev) => {
1100
- setEditorValue(newValue || "");
1101
- if (onChange) {
1102
- onChange(newValue, ev);
1103
- }
1104
- },
1105
- [onChange]
1106
- );
1107
- const resolveTheme = React6.useMemo(() => {
1108
- if (themeProp === "auto") {
1109
- return systemTheme === "dark" ? "vs-dark" : "light";
1110
- }
1111
- return themeProp === "dark" ? "vs-dark" : "light";
1112
- }, [themeProp, systemTheme]);
1113
- return /* @__PURE__ */ jsxs(
1114
- "div",
1115
- {
1116
- ref,
1117
- className: cn(
1118
- "relative overflow-hidden rounded-md border bg-background",
1119
- className
1120
- ),
1121
- children: [
1122
- (showLanguageSelector || showCopyButton) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b bg-muted/50 px-3 py-2", children: [
1123
- showLanguageSelector && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1124
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1125
- Button,
1126
- {
1127
- variant: "ghost",
1128
- size: "sm",
1129
- className: "h-8 gap-1 px-2 text-xs font-mono",
1130
- children: [
1131
- languageDisplayNames[selectedLanguage] || selectedLanguage,
1132
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" })
1133
- ]
1134
- }
1135
- ) }),
1136
- /* @__PURE__ */ jsx(
1137
- DropdownMenuContent,
1138
- {
1139
- align: "start",
1140
- className: "max-h-80 overflow-auto",
1141
- children: availableLanguages.map((lang) => /* @__PURE__ */ jsx(
1142
- DropdownMenuItem,
1143
- {
1144
- onClick: () => setSelectedLanguage(lang),
1145
- className: "font-mono text-xs",
1146
- children: languageDisplayNames[lang] || lang
1147
- },
1148
- lang
1149
- ))
1150
- }
1151
- )
1152
- ] }),
1153
- showCopyButton && /* @__PURE__ */ jsx(
1154
- Button,
1155
- {
1156
- variant: "ghost",
1157
- size: "sm",
1158
- className: "h-8 gap-1.5 px-2 text-xs",
1159
- onClick: handleCopy,
1160
- disabled: !editorValue,
1161
- children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
1162
- /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
1163
- "Copied!"
1164
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1165
- /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" }),
1166
- "Copy"
1167
- ] })
1168
- }
1169
- )
1170
- ] }),
1171
- /* @__PURE__ */ jsx(
1172
- Editor,
1173
- {
1174
- height,
1175
- defaultLanguage: language,
1176
- language: selectedLanguage,
1177
- value: editorValue,
1178
- defaultValue,
1179
- theme: resolveTheme,
1180
- onChange: handleChange,
1181
- onMount,
1182
- options: {
1183
- readOnly,
1184
- lineNumbers: lineNumbers ? "on" : "off",
1185
- minimap: {
1186
- enabled: minimap
1187
- },
1188
- wordWrap,
1189
- fontSize,
1190
- scrollBeyondLastLine: false,
1191
- automaticLayout: true,
1192
- tabSize: 2,
1193
- insertSpaces: true,
1194
- formatOnPaste: true,
1195
- formatOnType: true,
1196
- scrollbar: {
1197
- vertical: "auto",
1198
- horizontal: "auto",
1199
- verticalScrollbarSize: 10,
1200
- horizontalScrollbarSize: 10
1201
- },
1202
- padding: {
1203
- top: 16,
1204
- bottom: 16
1205
- }
1206
- }
1207
- }
1208
- )
1209
- ]
1210
- }
1211
- );
1212
- }
1213
- );
1214
- CodeEditor.displayName = "CodeEditor";
1215
- var codeExplorerVariants = cva(
1216
- "relative flex flex-col overflow-hidden rounded-lg border bg-background",
1217
- {
1218
- variants: {
1219
- size: {
1220
- sm: "text-xs",
1221
- default: "text-sm",
1222
- lg: "text-base"
1223
- }
1224
- },
1225
- defaultVariants: {
1226
- size: "default"
1227
- }
1228
- }
1229
- );
1230
- var getFileIcon = /* @__PURE__ */ __name((fileName, isFolder = false) => {
1231
- if (isFolder) return Folder;
1232
- const ext = fileName.split(".").pop()?.toLowerCase();
1233
- switch (ext) {
1234
- case "js":
1235
- case "jsx":
1236
- case "ts":
1237
- case "tsx":
1238
- case "vue":
1239
- case "svelte":
1240
- return FileCode;
1241
- case "json":
1242
- case "xml":
1243
- case "yaml":
1244
- case "yml":
1245
- case "toml":
1246
- case "ini":
1247
- return FileText;
1248
- case "png":
1249
- case "jpg":
1250
- case "jpeg":
1251
- case "gif":
1252
- case "svg":
1253
- case "webp":
1254
- case "ico":
1255
- return FileImage;
1256
- default:
1257
- return File;
1258
- }
1259
- }, "getFileIcon");
1260
- var formatFileSize = /* @__PURE__ */ __name((bytes) => {
1261
- if (!bytes) return "";
1262
- const units = ["B", "KB", "MB", "GB"];
1263
- let size = bytes;
1264
- let unitIndex = 0;
1265
- while (size >= 1024 && unitIndex < units.length - 1) {
1266
- size /= 1024;
1267
- unitIndex++;
1268
- }
1269
- return `${size.toFixed(1)} ${units[unitIndex]}`;
1270
- }, "formatFileSize");
1271
- var formatModifiedDate = /* @__PURE__ */ __name((date) => {
1272
- if (!date) return "";
1273
- const now = /* @__PURE__ */ new Date();
1274
- const diff = now.getTime() - date.getTime();
1275
- const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
1276
- if (days === 0) return "Today";
1277
- if (days === 1) return "Yesterday";
1278
- if (days < 7) return `${days} days ago`;
1279
- if (days < 30) return `${Math.floor(days / 7)} weeks ago`;
1280
- if (days < 365) return `${Math.floor(days / 30)} months ago`;
1281
- return `${Math.floor(days / 365)} years ago`;
1282
- }, "formatModifiedDate");
1283
- var CodeExplorer = React6.forwardRef(
1284
- ({
1285
- className,
1286
- size,
1287
- files,
1288
- onFileSelect,
1289
- onFolderToggle,
1290
- selectedFile,
1291
- showSearch = true,
1292
- showFileIcons = true,
1293
- showFileSize = false,
1294
- showModifiedDate = false,
1295
- height = "400px",
1296
- defaultExpanded = [],
1297
- searchPlaceholder = "Search files...",
1298
- emptyMessage = "No files found",
1299
- ...props
1300
- }, ref) => {
1301
- const [searchTerm, setSearchTerm] = React6.useState("");
1302
- const [expandedFolders, setExpandedFolders] = React6.useState(
1303
- new Set(defaultExpanded)
1304
- );
1305
- const buildFileTree = React6.useCallback(
1306
- (nodes) => {
1307
- return nodes.map((node) => ({
1308
- ...node,
1309
- isExpanded: expandedFolders.has(node.id),
1310
- children: node.children ? buildFileTree(node.children) : void 0
1311
- }));
1312
- },
1313
- [expandedFolders]
1314
- );
1315
- const filterFiles = React6.useCallback(
1316
- (nodes, term) => {
1317
- if (!term) return nodes;
1318
- const filtered = [];
1319
- for (const node of nodes) {
1320
- if (node.name.toLowerCase().includes(term.toLowerCase())) {
1321
- filtered.push(node);
1322
- } else if (node.type === "folder" && node.children) {
1323
- const filteredChildren = filterFiles(node.children, term);
1324
- if (filteredChildren.length > 0) {
1325
- filtered.push({
1326
- ...node,
1327
- children: filteredChildren,
1328
- isExpanded: true
1329
- // Auto-expand folders with matches
1330
- });
1331
- }
1332
- }
1333
- }
1334
- return filtered;
1335
- },
1336
- []
1337
- );
1338
- const processedFiles = React6.useMemo(() => {
1339
- const withExpansion = buildFileTree(files);
1340
- return searchTerm ? filterFiles(withExpansion, searchTerm) : withExpansion;
1341
- }, [files, searchTerm, buildFileTree, filterFiles]);
1342
- const toggleFolder = React6.useCallback(
1343
- (folder) => {
1344
- setExpandedFolders((prev) => {
1345
- const newSet = new Set(prev);
1346
- if (newSet.has(folder.id)) {
1347
- newSet.delete(folder.id);
1348
- } else {
1349
- newSet.add(folder.id);
1350
- }
1351
- return newSet;
1352
- });
1353
- onFolderToggle?.(folder);
1354
- },
1355
- [onFolderToggle]
1356
- );
1357
- const renderFileNode = React6.useCallback(
1358
- (node, depth = 0) => {
1359
- const Icon = showFileIcons ? getFileIcon(node.name, node.type === "folder") : null;
1360
- const FolderIcon = node.type === "folder" && node.isExpanded ? FolderOpen : Folder;
1361
- const isSelected = selectedFile === node.id || selectedFile === node.path;
1362
- return /* @__PURE__ */ jsxs("div", { children: [
1363
- /* @__PURE__ */ jsxs(
1364
- "div",
1365
- {
1366
- className: cn(
1367
- "flex items-center gap-2 px-2 py-1 hover:bg-muted/50 cursor-pointer transition-colors",
1368
- isSelected && "bg-muted",
1369
- "rounded-sm"
1370
- ),
1371
- style: { paddingLeft: `${depth * 16 + 8}px` },
1372
- onClick: () => {
1373
- if (node.type === "folder") {
1374
- toggleFolder(node);
1375
- } else {
1376
- onFileSelect?.(node);
1377
- }
1378
- },
1379
- children: [
1380
- node.type === "folder" && /* @__PURE__ */ jsx("div", { className: "w-4 h-4 flex items-center justify-center", children: node.isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3" }) }),
1381
- showFileIcons && /* @__PURE__ */ jsx("div", { className: "w-4 h-4 flex items-center justify-center flex-shrink-0", children: node.type === "folder" ? /* @__PURE__ */ jsx(FolderIcon, { className: "h-4 w-4 text-blue-500" }) : Icon ? /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground" }) : null }),
1382
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: node.name }),
1383
- showFileSize && node.type === "file" && node.size && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground flex-shrink-0", children: formatFileSize(node.size) }),
1384
- showModifiedDate && node.modified && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground flex-shrink-0", children: formatModifiedDate(node.modified) })
1385
- ]
1386
- }
1387
- ),
1388
- node.type === "folder" && node.isExpanded && node.children && /* @__PURE__ */ jsx("div", { children: node.children.map((child) => renderFileNode(child, depth + 1)) })
1389
- ] }, node.id);
1390
- },
1391
- [
1392
- showFileIcons,
1393
- showFileSize,
1394
- showModifiedDate,
1395
- selectedFile,
1396
- toggleFolder,
1397
- onFileSelect
1398
- ]
1399
- );
1400
- const clearSearch = /* @__PURE__ */ __name(() => {
1401
- setSearchTerm("");
1402
- }, "clearSearch");
1403
- return /* @__PURE__ */ jsxs(
1404
- "div",
1405
- {
1406
- ref,
1407
- className: cn(codeExplorerVariants({ size }), className),
1408
- style: { height },
1409
- ...props,
1410
- children: [
1411
- showSearch && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 border-b px-4 py-2", children: /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
1412
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
1413
- /* @__PURE__ */ jsx(
1414
- Input,
1415
- {
1416
- value: searchTerm,
1417
- onChange: (e) => setSearchTerm(e.target.value),
1418
- placeholder: searchPlaceholder,
1419
- className: "pl-8 pr-8 h-8"
1420
- }
1421
- ),
1422
- searchTerm && /* @__PURE__ */ jsx(
1423
- Button,
1424
- {
1425
- variant: "ghost",
1426
- size: "sm",
1427
- onClick: clearSearch,
1428
- className: "absolute right-1 top-1/2 transform -translate-y-1/2 h-6 w-6 p-0",
1429
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
1430
- }
1431
- )
1432
- ] }) }),
1433
- /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx("div", { className: "p-2", children: processedFiles.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-24 text-muted-foreground", children: searchTerm ? `No files match "${searchTerm}"` : emptyMessage }) : /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: processedFiles.map((node) => renderFileNode(node, 0)) }) }) }),
1434
- /* @__PURE__ */ jsxs("div", { className: "border-t px-4 py-2 text-xs text-muted-foreground", children: [
1435
- (() => {
1436
- const countFiles = /* @__PURE__ */ __name((nodes) => {
1437
- let files2 = 0;
1438
- let folders = 0;
1439
- for (const node of nodes) {
1440
- if (node.type === "file") {
1441
- files2++;
1442
- } else {
1443
- folders++;
1444
- if (node.children) {
1445
- const childCounts = countFiles(node.children);
1446
- files2 += childCounts.files;
1447
- folders += childCounts.folders;
1448
- }
1449
- }
1450
- }
1451
- return { files: files2, folders };
1452
- }, "countFiles");
1453
- const { files: fileCount, folders: folderCount } = countFiles(processedFiles);
1454
- const parts = [];
1455
- if (fileCount > 0)
1456
- parts.push(`${fileCount} file${fileCount === 1 ? "" : "s"}`);
1457
- if (folderCount > 0)
1458
- parts.push(`${folderCount} folder${folderCount === 1 ? "" : "s"}`);
1459
- return parts.join(", ") || "Empty";
1460
- })(),
1461
- searchTerm && /* @__PURE__ */ jsx("span", { children: " (filtered)" })
1462
- ] })
1463
- ]
1464
- }
1465
- );
1466
- }
1467
- );
1468
- CodeExplorer.displayName = "CodeExplorer";
1469
- var codePreviewVariants = cva(
1470
- "relative overflow-hidden rounded-lg border bg-background",
1471
- {
1472
- variants: {
1473
- size: {
1474
- sm: "text-xs",
1475
- default: "text-sm",
1476
- lg: "text-base"
1477
- }
1478
- },
1479
- defaultVariants: {
1480
- size: "default"
1481
- }
1482
- }
1483
- );
1484
- var runHTMLCode = /* @__PURE__ */ __name((files) => {
1485
- const htmlFile = files.find((f) => f.type === "html");
1486
- const cssFiles = files.filter((f) => f.type === "css");
1487
- const jsFiles = files.filter((f) => f.type === "js");
1488
- let html = htmlFile?.content || "<div>No HTML file provided</div>";
1489
- if (cssFiles.length > 0) {
1490
- const cssContent = cssFiles.map((f) => f.content).join("\n");
1491
- html = `<style>${cssContent}</style>
1492
- ${html}`;
1493
- }
1494
- if (jsFiles.length > 0) {
1495
- const jsContent = jsFiles.map((f) => f.content).join("\n");
1496
- html = `${html}
1497
- <script>${jsContent}</script>`;
1498
- }
1499
- return html;
1500
- }, "runHTMLCode");
1501
- var consoleScript = `
1502
- <script>
1503
- const originalConsole = window.console;
1504
- const logs = [];
1505
-
1506
- ['log', 'warn', 'error', 'info'].forEach(method => {
1507
- window.console[method] = function(...args) {
1508
- logs.push({
1509
- type: method,
1510
- content: args.map(arg =>
1511
- typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
1512
- ).join(' '),
1513
- timestamp: Date.now()
1514
- });
1515
-
1516
- // Also call original console method
1517
- originalConsole[method].apply(originalConsole, arguments);
1518
-
1519
- // Send to parent
1520
- window.parent.postMessage({
1521
- type: 'console',
1522
- data: logs[logs.length - 1]
1523
- }, '*');
1524
- };
1525
- });
1526
-
1527
- window.addEventListener('error', (e) => {
1528
- window.parent.postMessage({
1529
- type: 'error',
1530
- data: {
1531
- type: 'error',
1532
- content: e.message + ' at ' + e.filename + ':' + e.lineno,
1533
- timestamp: Date.now()
1534
- }
1535
- }, '*');
1536
- });
1537
- </script>
1538
- `;
1539
- var CodePreview = React6.forwardRef(
1540
- ({
1541
- className,
1542
- size,
1543
- files,
1544
- defaultFile,
1545
- showPreview = true,
1546
- showConsole = true,
1547
- autoRun = false,
1548
- maxHeight = "600px",
1549
- previewHeight = "300px",
1550
- consoleHeight = "150px",
1551
- allowFullscreen = true,
1552
- customRunner,
1553
- ...props
1554
- }, ref) => {
1555
- const [activeFile, setActiveFile] = React6.useState(
1556
- defaultFile || files[0]?.filename || ""
1557
- );
1558
- const [previewContent, setPreviewContent] = React6.useState("");
1559
- const [isRunning, setIsRunning] = React6.useState(false);
1560
- const [results, setResults] = React6.useState([]);
1561
- const [isPreviewVisible, setIsPreviewVisible] = React6.useState(showPreview);
1562
- const [isConsoleVisible, setIsConsoleVisible] = React6.useState(showConsole);
1563
- const [isFullscreen, setIsFullscreen] = React6.useState(false);
1564
- const [copied, setCopied] = React6.useState({});
1565
- const iframeRef = React6.useRef(null);
1566
- files.find((f) => f.filename === activeFile) || files[0];
1567
- React6.useEffect(() => {
1568
- const handleMessage = /* @__PURE__ */ __name((event) => {
1569
- if (event.data?.type === "console" || event.data?.type === "error") {
1570
- setResults((prev) => [...prev, event.data.data]);
1571
- }
1572
- }, "handleMessage");
1573
- window.addEventListener("message", handleMessage);
1574
- return () => window.removeEventListener("message", handleMessage);
1575
- }, []);
1576
- const runCode = React6.useCallback(async () => {
1577
- setIsRunning(true);
1578
- setResults([]);
1579
- try {
1580
- let output;
1581
- if (customRunner) {
1582
- output = await customRunner(files);
1583
- } else {
1584
- output = runHTMLCode(files);
1585
- }
1586
- output = consoleScript + output;
1587
- setPreviewContent(output);
1588
- if (iframeRef.current) {
1589
- const doc = iframeRef.current.contentDocument;
1590
- if (doc) {
1591
- doc.open();
1592
- doc.write(output);
1593
- doc.close();
1594
- }
1595
- }
1596
- } catch (error) {
1597
- setResults([
1598
- {
1599
- type: "error",
1600
- content: error instanceof Error ? error.message : String(error),
1601
- timestamp: Date.now()
1602
- }
1603
- ]);
1604
- } finally {
1605
- setIsRunning(false);
1606
- }
1607
- }, [files, customRunner]);
1608
- React6.useEffect(() => {
1609
- if (autoRun) {
1610
- const timer = setTimeout(runCode, 1e3);
1611
- return () => clearTimeout(timer);
1612
- }
1613
- }, [files, autoRun, runCode]);
1614
- const copyFileContent = React6.useCallback(
1615
- async (filename) => {
1616
- const file = files.find((f) => f.filename === filename);
1617
- if (!file) return;
1618
- try {
1619
- await navigator.clipboard.writeText(file.content);
1620
- setCopied((prev) => ({ ...prev, [filename]: true }));
1621
- setTimeout(() => {
1622
- setCopied((prev) => ({ ...prev, [filename]: false }));
1623
- }, 2e3);
1624
- } catch (error) {
1625
- console.error("Failed to copy code:", error);
1626
- }
1627
- },
1628
- [files]
1629
- );
1630
- const clearConsole = /* @__PURE__ */ __name(() => {
1631
- setResults([]);
1632
- }, "clearConsole");
1633
- const renderConsole = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
1634
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2 bg-muted/30", children: [
1635
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1636
- /* @__PURE__ */ jsx(Terminal, { className: "h-4 w-4" }),
1637
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Console" }),
1638
- results.length > 0 && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: results.length })
1639
- ] }),
1640
- /* @__PURE__ */ jsx(
1641
- Button,
1642
- {
1643
- variant: "ghost",
1644
- size: "sm",
1645
- onClick: clearConsole,
1646
- className: "h-8 w-8 p-0",
1647
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
1648
- }
1649
- )
1650
- ] }),
1651
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto p-2 space-y-1 font-mono text-xs", children: results.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-muted-foreground italic", children: "No output" }) : results.map((result, index) => /* @__PURE__ */ jsxs(
1652
- "div",
1653
- {
1654
- className: cn(
1655
- "flex items-start gap-2 p-2 rounded",
1656
- result.type === "error" && "bg-red-500/10 text-red-600 dark:text-red-400",
1657
- result.type === "log" && "bg-blue-500/10 text-blue-600 dark:text-blue-400",
1658
- result.type === "output" && "bg-green-500/10 text-green-600 dark:text-green-400"
1659
- ),
1660
- children: [
1661
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(result.timestamp).toLocaleTimeString() }),
1662
- /* @__PURE__ */ jsx("pre", { className: "flex-1 whitespace-pre-wrap break-words", children: result.content })
1663
- ]
1664
- },
1665
- index
1666
- )) })
1667
- ] }), "renderConsole");
1668
- const layout = isFullscreen ? "fixed inset-0 z-50 bg-background" : "";
1669
- return /* @__PURE__ */ jsxs(
1670
- "div",
1671
- {
1672
- ref,
1673
- className: cn(codePreviewVariants({ size }), layout, className),
1674
- style: { maxHeight: isFullscreen ? "100vh" : maxHeight },
1675
- ...props,
1676
- children: [
1677
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2", children: [
1678
- /* @__PURE__ */ jsx(
1679
- Tabs,
1680
- {
1681
- value: activeFile,
1682
- onValueChange: setActiveFile,
1683
- className: "flex-1",
1684
- children: /* @__PURE__ */ jsx(TabsList, { className: "h-auto p-0 bg-transparent", children: files.map((file) => /* @__PURE__ */ jsxs(
1685
- TabsTrigger,
1686
- {
1687
- value: file.filename,
1688
- className: "relative flex items-center gap-2 data-[state=active]:bg-muted",
1689
- children: [
1690
- /* @__PURE__ */ jsx("span", { children: file.filename }),
1691
- /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: file.language }),
1692
- /* @__PURE__ */ jsx(
1693
- Button,
1694
- {
1695
- variant: "ghost",
1696
- size: "sm",
1697
- onClick: (e) => {
1698
- e.stopPropagation();
1699
- copyFileContent(file.filename);
1700
- },
1701
- className: "h-6 w-6 p-0 opacity-0 group-hover:opacity-100",
1702
- children: copied[file.filename] ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
1703
- }
1704
- )
1705
- ]
1706
- },
1707
- file.filename
1708
- )) })
1709
- }
1710
- ),
1711
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1712
- /* @__PURE__ */ jsx(
1713
- Toggle,
1714
- {
1715
- pressed: isPreviewVisible,
1716
- onPressedChange: setIsPreviewVisible,
1717
- "aria-label": "Toggle preview",
1718
- size: "sm",
1719
- children: isPreviewVisible ? /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4" })
1720
- }
1721
- ),
1722
- /* @__PURE__ */ jsx(
1723
- Toggle,
1724
- {
1725
- pressed: isConsoleVisible,
1726
- onPressedChange: setIsConsoleVisible,
1727
- "aria-label": "Toggle console",
1728
- size: "sm",
1729
- children: /* @__PURE__ */ jsx(Terminal, { className: "h-4 w-4" })
1730
- }
1731
- ),
1732
- /* @__PURE__ */ jsxs(
1733
- Button,
1734
- {
1735
- variant: "outline",
1736
- size: "sm",
1737
- onClick: runCode,
1738
- disabled: isRunning,
1739
- className: "gap-2",
1740
- children: [
1741
- isRunning ? /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Play, { className: "h-4 w-4" }),
1742
- "Run"
1743
- ]
1744
- }
1745
- ),
1746
- allowFullscreen && /* @__PURE__ */ jsx(
1747
- Button,
1748
- {
1749
- variant: "ghost",
1750
- size: "sm",
1751
- onClick: () => setIsFullscreen(!isFullscreen),
1752
- className: "h-8 w-8 p-0",
1753
- children: isFullscreen ? /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" })
1754
- }
1755
- )
1756
- ] })
1757
- ] }),
1758
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
1759
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(Tabs, { value: activeFile, onValueChange: setActiveFile, children: files.map((file) => /* @__PURE__ */ jsx(
1760
- TabsContent,
1761
- {
1762
- value: file.filename,
1763
- className: "m-0 h-full",
1764
- children: /* @__PURE__ */ jsx(
1765
- CodeSnippet,
1766
- {
1767
- code: file.content,
1768
- language: file.language,
1769
- showLineNumbers: true,
1770
- showCopyButton: false,
1771
- showHeader: false,
1772
- maxHeight: "100%",
1773
- className: "border-0 rounded-none"
1774
- }
1775
- )
1776
- },
1777
- file.filename
1778
- )) }) }),
1779
- (isPreviewVisible || isConsoleVisible) && /* @__PURE__ */ jsxs(Fragment, { children: [
1780
- /* @__PURE__ */ jsx(Separator, {}),
1781
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col lg:flex-row min-h-0", children: [
1782
- isPreviewVisible && /* @__PURE__ */ jsxs(
1783
- "div",
1784
- {
1785
- className: "flex-1 min-h-0 border-r last:border-r-0",
1786
- style: { height: previewHeight },
1787
- children: [
1788
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-4 py-2 bg-muted/30", children: [
1789
- /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }),
1790
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Preview" }),
1791
- results.filter((r) => r.type === "error").length > 0 && /* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4 text-red-500" })
1792
- ] }),
1793
- /* @__PURE__ */ jsx(
1794
- "iframe",
1795
- {
1796
- ref: iframeRef,
1797
- className: "w-full h-full bg-white",
1798
- sandbox: "allow-scripts allow-same-origin",
1799
- srcDoc: previewContent
1800
- }
1801
- )
1802
- ]
1803
- }
1804
- ),
1805
- isConsoleVisible && /* @__PURE__ */ jsx(
1806
- "div",
1807
- {
1808
- className: "flex-1 min-h-0",
1809
- style: { height: consoleHeight },
1810
- children: renderConsole()
1811
- }
1812
- )
1813
- ] })
1814
- ] })
1815
- ] }),
1816
- isFullscreen && /* @__PURE__ */ jsx(
1817
- "div",
1818
- {
1819
- className: "absolute inset-0 bg-black/50",
1820
- onClick: () => setIsFullscreen(false)
1821
- }
1822
- )
1823
- ]
1824
- }
1825
- );
1826
- }
1827
- );
1828
- CodePreview.displayName = "CodePreview";
1829
- var codeSnippetVariants = cva("relative group", {
1830
- variants: {
1831
- variant: {
1832
- default: "rounded-lg border bg-muted/50",
1833
- inline: "inline-block rounded-md bg-muted px-2 py-1",
1834
- minimal: "bg-transparent"
1835
- },
1836
- theme: {
1837
- dark: "bg-slate-950 border-slate-800",
1838
- light: "bg-slate-50 border-slate-200",
1839
- github: "bg-white border-slate-200",
1840
- "github-dark": "bg-slate-900 border-slate-700",
1841
- terminal: "bg-black border-green-500/30 text-green-400",
1842
- retro: "bg-amber-50 border-amber-200 text-amber-900"
1843
- },
1844
- size: {
1845
- xs: "text-xs",
1846
- sm: "text-sm",
1847
- default: "text-sm",
1848
- lg: "text-base"
1849
- }
1850
- },
1851
- defaultVariants: {
1852
- variant: "default",
1853
- theme: "dark",
1854
- size: "default"
1855
- }
1856
- });
1857
- var CodeSnippet3 = React6.forwardRef(
1858
- ({
1859
- className,
1860
- variant,
1861
- theme,
1862
- size,
1863
- code,
1864
- language = "text",
1865
- filename,
1866
- showLineNumbers = false,
1867
- highlightLines = [],
1868
- showCopyButton = true,
1869
- showLanguageBadge = true,
1870
- showHeader = true,
1871
- maxHeight,
1872
- expandable = false,
1873
- collapsedHeight = "200px",
1874
- wrapLines = false,
1875
- startLineNumber = 1,
1876
- ...props
1877
- }, ref) => {
1878
- const [highlightedCode, setHighlightedCode] = React6.useState("");
1879
- const [copied, setCopied] = React6.useState(false);
1880
- const [isLoading, setIsLoading] = React6.useState(true);
1881
- const [isExpanded, setIsExpanded] = React6.useState(!expandable);
1882
- const themeMap = {
1883
- dark: "github-dark",
1884
- light: "github-light",
1885
- github: "github-light",
1886
- "github-dark": "github-dark",
1887
- terminal: "github-dark",
1888
- retro: "github-light"
1889
- };
1890
- React6.useEffect(() => {
1891
- const highlightCode = /* @__PURE__ */ __name(async () => {
1892
- setIsLoading(true);
1893
- try {
1894
- const supportedLang = Object.keys(bundledLanguages).includes(language);
1895
- const highlighted = await codeToHtml(code, {
1896
- lang: supportedLang ? language : "text",
1897
- theme: themeMap[theme || "dark"]
1898
- });
1899
- const tempDiv = document.createElement("div");
1900
- tempDiv.innerHTML = highlighted;
1901
- const codeElement = tempDiv.querySelector("code");
1902
- setHighlightedCode(codeElement?.innerHTML || highlighted);
1903
- } catch (error) {
1904
- console.error("Failed to highlight code:", error);
1905
- const escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1906
- setHighlightedCode(escaped);
1907
- } finally {
1908
- setIsLoading(false);
1909
- }
1910
- }, "highlightCode");
1911
- highlightCode();
1912
- }, [code, language, theme]);
1913
- const copyToClipboard = React6.useCallback(async () => {
1914
- try {
1915
- await navigator.clipboard.writeText(code);
1916
- setCopied(true);
1917
- setTimeout(() => setCopied(false), 2e3);
1918
- } catch (error) {
1919
- console.error("Failed to copy code:", error);
1920
- }
1921
- }, [code]);
1922
- const lines = highlightedCode.split("\n").filter((line, index, array) => {
1923
- return line.trim() !== "" || index < array.length - 1;
1924
- });
1925
- const renderInline = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx(
1926
- "code",
1927
- {
1928
- className: cn(
1929
- codeSnippetVariants({ variant, theme, size }),
1930
- "font-mono",
1931
- className
1932
- ),
1933
- dangerouslySetInnerHTML: { __html: highlightedCode }
1934
- }
1935
- ), "renderInline");
1936
- const renderBlock = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsxs(
1937
- "div",
1938
- {
1939
- ref,
1940
- className: cn(
1941
- codeSnippetVariants({ variant, theme, size }),
1942
- "overflow-hidden",
1943
- className
1944
- ),
1945
- ...props,
1946
- children: [
1947
- showHeader && (filename || language || showCopyButton || expandable) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2 bg-muted/30", children: [
1948
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1949
- filename && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1950
- /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5 text-muted-foreground" }),
1951
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: filename })
1952
- ] }),
1953
- showLanguageBadge && language && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: language })
1954
- ] }),
1955
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1956
- expandable && /* @__PURE__ */ jsxs(
1957
- Button,
1958
- {
1959
- variant: "ghost",
1960
- size: "sm",
1961
- onClick: () => setIsExpanded(!isExpanded),
1962
- className: "h-8 w-8 p-0",
1963
- children: [
1964
- isExpanded ? /* @__PURE__ */ jsx(Minimize2, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Maximize2, { className: "h-3.5 w-3.5" }),
1965
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: isExpanded ? "Collapse" : "Expand" })
1966
- ]
1967
- }
1968
- ),
1969
- showCopyButton && /* @__PURE__ */ jsxs(
1970
- Button,
1971
- {
1972
- variant: "ghost",
1973
- size: "sm",
1974
- onClick: copyToClipboard,
1975
- className: "h-8 w-8 p-0 opacity-0 group-hover:opacity-100 transition-opacity",
1976
- children: [
1977
- copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }),
1978
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: copied ? "Copied" : "Copy code" })
1979
- ]
1980
- }
1981
- )
1982
- ] })
1983
- ] }),
1984
- /* @__PURE__ */ jsx(
1985
- "div",
1986
- {
1987
- className: cn("overflow-auto", !wrapLines && "overflow-x-auto"),
1988
- style: {
1989
- maxHeight: isExpanded ? maxHeight : collapsedHeight
1990
- },
1991
- children: isLoading ? /* @__PURE__ */ jsx("div", { className: "p-4 space-y-2", children: Array.from({ length: Math.min(lines.length || 5, 10) }).map(
1992
- (_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
1993
- showLineNumbers && /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-8" }),
1994
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 flex-1" })
1995
- ] }, i)
1996
- ) }) : /* @__PURE__ */ jsx("div", { className: "font-mono", children: lines.map((line, index) => {
1997
- const lineNumber = index + startLineNumber;
1998
- const isHighlighted = highlightLines.includes(lineNumber);
1999
- return /* @__PURE__ */ jsxs(
2000
- "div",
2001
- {
2002
- className: cn(
2003
- "group relative flex min-h-[1.5rem] items-center px-4 py-0.5",
2004
- isHighlighted && "bg-blue-500/10 border-l-2 border-l-blue-500",
2005
- !wrapLines && "whitespace-nowrap"
2006
- ),
2007
- children: [
2008
- showLineNumbers && /* @__PURE__ */ jsx("span", { className: "mr-4 inline-block w-8 select-none text-right text-xs text-muted-foreground", children: lineNumber }),
2009
- /* @__PURE__ */ jsx(
2010
- "div",
2011
- {
2012
- className: cn(
2013
- "flex-1",
2014
- wrapLines ? "whitespace-pre-wrap break-words" : "overflow-x-auto"
2015
- ),
2016
- dangerouslySetInnerHTML: { __html: line }
2017
- }
2018
- )
2019
- ]
2020
- },
2021
- index
2022
- );
2023
- }) })
2024
- }
2025
- ),
2026
- expandable && !isExpanded && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-background to-transparent pointer-events-none" }),
2027
- variant === "minimal" && showCopyButton && /* @__PURE__ */ jsx(
2028
- Button,
2029
- {
2030
- variant: "ghost",
2031
- size: "sm",
2032
- onClick: copyToClipboard,
2033
- className: "absolute top-2 right-2 h-8 w-8 p-0 opacity-0 group-hover:opacity-100 transition-opacity",
2034
- children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
2035
- }
2036
- )
2037
- ]
2038
- }
2039
- ), "renderBlock");
2040
- if (variant === "inline") {
2041
- return renderInline();
2042
- }
2043
- return renderBlock();
2044
- }
2045
- );
2046
- CodeSnippet3.displayName = "CodeSnippet";
2047
- var InlineCode = React6.forwardRef(({ children, language, className, ...props }, ref) => {
2048
- return /* @__PURE__ */ jsx(
2049
- CodeSnippet3,
2050
- {
2051
- ref,
2052
- code: children,
2053
- language,
2054
- variant: "inline",
2055
- showCopyButton: false,
2056
- showLanguageBadge: false,
2057
- showHeader: false,
2058
- className,
2059
- ...props
2060
- }
2061
- );
2062
- });
2063
- InlineCode.displayName = "InlineCode";
2064
- var CodeTabs = React6.forwardRef(
2065
- ({ className, tabs = [], defaultTab = 0, ...props }, ref) => {
2066
- const [activeTab, setActiveTab] = React6.useState(defaultTab);
2067
- if (tabs.length === 0) {
2068
- return /* @__PURE__ */ jsx(
2069
- "div",
2070
- {
2071
- ref,
2072
- className: cn(
2073
- "rounded-lg border bg-card p-6 text-card-foreground shadow-sm",
2074
- className
2075
- ),
2076
- ...props,
2077
- children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No code tabs available. Add tabs to display code snippets." })
2078
- }
2079
- );
2080
- }
2081
- return /* @__PURE__ */ jsxs(
2082
- "div",
2083
- {
2084
- ref,
2085
- className: cn(
2086
- "rounded-lg border bg-card text-card-foreground shadow-sm",
2087
- className
2088
- ),
2089
- ...props,
2090
- children: [
2091
- /* @__PURE__ */ jsx("div", { className: "border-b", children: /* @__PURE__ */ jsx("div", { className: "flex gap-1 p-2", children: tabs.map((tab, index) => /* @__PURE__ */ jsx(
2092
- "button",
2093
- {
2094
- onClick: () => setActiveTab(index),
2095
- className: cn(
2096
- "rounded-md px-4 py-2 text-sm font-medium transition-colors",
2097
- activeTab === index ? "bg-primary text-primary-foreground" : "hover:bg-muted"
2098
- ),
2099
- children: tab.label
2100
- },
2101
- index
2102
- )) }) }),
2103
- /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded-md bg-muted p-4", children: /* @__PURE__ */ jsx("code", { className: "text-sm", children: tabs[activeTab]?.code }) }) })
2104
- ]
2105
- }
2106
- );
2107
- }
2108
- );
2109
- CodeTabs.displayName = "CodeTabs";
2110
- var codeTerminalVariants = cva(
2111
- "relative flex flex-col overflow-hidden rounded-lg border",
2112
- {
2113
- variants: {
2114
- theme: {
2115
- dark: "bg-black border-gray-700 text-green-400",
2116
- light: "bg-white border-gray-300 text-gray-900",
2117
- matrix: "bg-black border-green-500 text-green-400",
2118
- hacker: "bg-gray-900 border-yellow-500 text-yellow-400",
2119
- retro: "bg-amber-900 border-amber-600 text-amber-100",
2120
- modern: "bg-slate-900 border-slate-700 text-slate-100"
2121
- },
2122
- size: {
2123
- sm: "text-xs",
2124
- default: "text-sm",
2125
- lg: "text-base"
2126
- }
2127
- },
2128
- defaultVariants: {
2129
- theme: "dark",
2130
- size: "default"
2131
- }
2132
- }
2133
- );
2134
- var ansiColors = {
2135
- black: "text-black",
2136
- red: "text-red-500",
2137
- green: "text-green-500",
2138
- yellow: "text-yellow-500",
2139
- blue: "text-blue-500",
2140
- magenta: "text-magenta-500",
2141
- cyan: "text-cyan-500",
2142
- white: "text-white",
2143
- gray: "text-gray-500",
2144
- brightRed: "text-red-400",
2145
- brightGreen: "text-green-400",
2146
- brightYellow: "text-yellow-400",
2147
- brightBlue: "text-blue-400",
2148
- brightMagenta: "text-magenta-400",
2149
- brightCyan: "text-cyan-400",
2150
- brightWhite: "text-gray-100"
2151
- };
2152
- var parseAnsiContent = /* @__PURE__ */ __name((content) => {
2153
- const ansiRegex = /\x1b\[([0-9;]*)m/g;
2154
- const parts = [];
2155
- let lastIndex = 0;
2156
- let currentClasses = [];
2157
- let match;
2158
- while ((match = ansiRegex.exec(content)) !== null) {
2159
- if (match.index > lastIndex) {
2160
- parts.push({
2161
- text: content.slice(lastIndex, match.index),
2162
- classes: [...currentClasses]
2163
- });
2164
- }
2165
- const codes = match[1].split(";").map((code) => parseInt(code) || 0);
2166
- codes.forEach((code) => {
2167
- switch (code) {
2168
- case 0:
2169
- currentClasses = [];
2170
- break;
2171
- // reset
2172
- case 30:
2173
- currentClasses.push(ansiColors.black);
2174
- break;
2175
- case 31:
2176
- currentClasses.push(ansiColors.red);
2177
- break;
2178
- case 32:
2179
- currentClasses.push(ansiColors.green);
2180
- break;
2181
- case 33:
2182
- currentClasses.push(ansiColors.yellow);
2183
- break;
2184
- case 34:
2185
- currentClasses.push(ansiColors.blue);
2186
- break;
2187
- case 35:
2188
- currentClasses.push(ansiColors.magenta);
2189
- break;
2190
- case 36:
2191
- currentClasses.push(ansiColors.cyan);
2192
- break;
2193
- case 37:
2194
- currentClasses.push(ansiColors.white);
2195
- break;
2196
- case 90:
2197
- currentClasses.push(ansiColors.gray);
2198
- break;
2199
- case 91:
2200
- currentClasses.push(ansiColors.brightRed);
2201
- break;
2202
- case 92:
2203
- currentClasses.push(ansiColors.brightGreen);
2204
- break;
2205
- case 93:
2206
- currentClasses.push(ansiColors.brightYellow);
2207
- break;
2208
- case 94:
2209
- currentClasses.push(ansiColors.brightBlue);
2210
- break;
2211
- case 95:
2212
- currentClasses.push(ansiColors.brightMagenta);
2213
- break;
2214
- case 96:
2215
- currentClasses.push(ansiColors.brightCyan);
2216
- break;
2217
- case 97:
2218
- currentClasses.push(ansiColors.brightWhite);
2219
- break;
2220
- }
2221
- });
2222
- lastIndex = match.index + match[0].length;
2223
- }
2224
- if (lastIndex < content.length) {
2225
- parts.push({
2226
- text: content.slice(lastIndex),
2227
- classes: [...currentClasses]
2228
- });
2229
- }
2230
- return parts.map((part, index) => /* @__PURE__ */ jsx("span", { className: cn(part.classes), children: part.text }, index));
2231
- }, "parseAnsiContent");
2232
- var CodeTerminal = React6.forwardRef(
2233
- ({
2234
- className,
2235
- theme,
2236
- size,
2237
- title = "Terminal",
2238
- prompt = "$",
2239
- maxLines = 1e3,
2240
- showLineNumbers = false,
2241
- allowInput = true,
2242
- allowClear = true,
2243
- allowCopy = true,
2244
- allowDownload = false,
2245
- allowFullscreen = true,
2246
- height = "400px",
2247
- autoScroll = true,
2248
- history = [],
2249
- onCommand,
2250
- onClear,
2251
- initialLines = [],
2252
- ...props
2253
- }, ref) => {
2254
- const [lines, setLines] = React6.useState(initialLines);
2255
- const [input, setInput] = React6.useState("");
2256
- const [historyIndex, setHistoryIndex] = React6.useState(-1);
2257
- const [isFullscreen, setIsFullscreen] = React6.useState(false);
2258
- const [commandHistory, setCommandHistory] = React6.useState(history);
2259
- const scrollAreaRef = React6.useRef(null);
2260
- const inputRef = React6.useRef(null);
2261
- React6.useEffect(() => {
2262
- if (autoScroll && scrollAreaRef.current) {
2263
- scrollAreaRef.current.scrollTop = scrollAreaRef.current.scrollHeight;
2264
- }
2265
- }, [lines, autoScroll]);
2266
- const addLine = React6.useCallback(
2267
- (line) => {
2268
- const newLine = {
2269
- ...line,
2270
- id: `line-${Date.now()}-${Math.random()}`,
2271
- timestamp: Date.now()
2272
- };
2273
- setLines((prev) => {
2274
- const updated = [...prev, newLine];
2275
- return updated.length > maxLines ? updated.slice(-maxLines) : updated;
2276
- });
2277
- },
2278
- [maxLines]
2279
- );
2280
- const handleCommand = React6.useCallback(
2281
- async (command) => {
2282
- if (!command.trim()) return;
2283
- setCommandHistory((prev) => [...prev, command]);
2284
- setHistoryIndex(-1);
2285
- addLine({
2286
- type: "input",
2287
- content: `${prompt} ${command}`,
2288
- command
2289
- });
2290
- if (command === "clear") {
2291
- if (onClear) {
2292
- onClear();
2293
- } else {
2294
- setLines([]);
2295
- }
2296
- return;
2297
- }
2298
- if (command === "history") {
2299
- commandHistory.forEach((cmd, index) => {
2300
- addLine({
2301
- type: "output",
2302
- content: `${index + 1} ${cmd}`
2303
- });
2304
- });
2305
- return;
2306
- }
2307
- if (command.startsWith("echo ")) {
2308
- addLine({
2309
- type: "output",
2310
- content: command.slice(5)
2311
- });
2312
- return;
2313
- }
2314
- if (onCommand) {
2315
- try {
2316
- await onCommand(command);
2317
- } catch (error) {
2318
- addLine({
2319
- type: "error",
2320
- content: error instanceof Error ? error.message : String(error),
2321
- exitCode: 1
2322
- });
2323
- }
2324
- } else {
2325
- addLine({
2326
- type: "error",
2327
- content: `Command not found: ${command}`,
2328
- exitCode: 127
2329
- });
2330
- }
2331
- },
2332
- [prompt, addLine, onCommand, onClear, commandHistory]
2333
- );
2334
- const handleInputSubmit = /* @__PURE__ */ __name((e) => {
2335
- e.preventDefault();
2336
- if (input.trim()) {
2337
- handleCommand(input);
2338
- setInput("");
2339
- }
2340
- }, "handleInputSubmit");
2341
- const handleKeyDown = /* @__PURE__ */ __name((e) => {
2342
- if (e.key === "ArrowUp") {
2343
- e.preventDefault();
2344
- if (historyIndex < commandHistory.length - 1) {
2345
- const newIndex = historyIndex + 1;
2346
- setHistoryIndex(newIndex);
2347
- setInput(commandHistory[commandHistory.length - 1 - newIndex] || "");
2348
- }
2349
- } else if (e.key === "ArrowDown") {
2350
- e.preventDefault();
2351
- if (historyIndex > 0) {
2352
- const newIndex = historyIndex - 1;
2353
- setHistoryIndex(newIndex);
2354
- setInput(commandHistory[commandHistory.length - 1 - newIndex] || "");
2355
- } else if (historyIndex === 0) {
2356
- setHistoryIndex(-1);
2357
- setInput("");
2358
- }
2359
- }
2360
- }, "handleKeyDown");
2361
- const copyContent = React6.useCallback(async () => {
2362
- const content = lines.map((line) => line.content).join("\n");
2363
- try {
2364
- await navigator.clipboard.writeText(content);
2365
- } catch (error) {
2366
- console.error("Failed to copy terminal content:", error);
2367
- }
2368
- }, [lines]);
2369
- const downloadContent = React6.useCallback(() => {
2370
- const content = lines.map(
2371
- (line) => `[${new Date(line.timestamp).toISOString()}] ${line.content}`
2372
- ).join("\n");
2373
- const blob = new Blob([content], { type: "text/plain" });
2374
- const url = URL.createObjectURL(blob);
2375
- const a = document.createElement("a");
2376
- a.href = url;
2377
- a.download = `terminal-${Date.now()}.txt`;
2378
- document.body.appendChild(a);
2379
- a.click();
2380
- document.body.removeChild(a);
2381
- URL.revokeObjectURL(url);
2382
- }, [lines]);
2383
- const clearTerminal = /* @__PURE__ */ __name(() => {
2384
- if (onClear) {
2385
- onClear();
2386
- } else {
2387
- setLines([]);
2388
- }
2389
- }, "clearTerminal");
2390
- const handleTerminalClick = /* @__PURE__ */ __name(() => {
2391
- if (allowInput && inputRef.current) {
2392
- inputRef.current.focus();
2393
- }
2394
- }, "handleTerminalClick");
2395
- return /* @__PURE__ */ jsxs(
2396
- "div",
2397
- {
2398
- ref,
2399
- className: cn(
2400
- codeTerminalVariants({ theme, size }),
2401
- isFullscreen && "fixed inset-0 z-50",
2402
- className
2403
- ),
2404
- style: { height: isFullscreen ? "100vh" : height },
2405
- onClick: handleTerminalClick,
2406
- ...props,
2407
- children: [
2408
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2 bg-muted/30", children: [
2409
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2410
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
2411
- /* @__PURE__ */ jsx("div", { className: "w-3 h-3 rounded-full bg-red-500" }),
2412
- /* @__PURE__ */ jsx("div", { className: "w-3 h-3 rounded-full bg-yellow-500" }),
2413
- /* @__PURE__ */ jsx("div", { className: "w-3 h-3 rounded-full bg-green-500" })
2414
- ] }),
2415
- /* @__PURE__ */ jsx(Terminal, { className: "h-4 w-4" }),
2416
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: title }),
2417
- lines.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
2418
- lines.length,
2419
- " lines"
2420
- ] })
2421
- ] }),
2422
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2423
- allowClear && /* @__PURE__ */ jsx(
2424
- Button,
2425
- {
2426
- variant: "ghost",
2427
- size: "sm",
2428
- onClick: clearTerminal,
2429
- className: "h-8 w-8 p-0",
2430
- children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-3.5 w-3.5" })
2431
- }
2432
- ),
2433
- allowCopy && /* @__PURE__ */ jsx(
2434
- Button,
2435
- {
2436
- variant: "ghost",
2437
- size: "sm",
2438
- onClick: copyContent,
2439
- className: "h-8 w-8 p-0",
2440
- children: /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
2441
- }
2442
- ),
2443
- allowDownload && /* @__PURE__ */ jsx(
2444
- Button,
2445
- {
2446
- variant: "ghost",
2447
- size: "sm",
2448
- onClick: downloadContent,
2449
- className: "h-8 w-8 p-0",
2450
- children: /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" })
2451
- }
2452
- ),
2453
- allowFullscreen && /* @__PURE__ */ jsx(
2454
- Button,
2455
- {
2456
- variant: "ghost",
2457
- size: "sm",
2458
- onClick: () => setIsFullscreen(!isFullscreen),
2459
- className: "h-8 w-8 p-0",
2460
- children: isFullscreen ? /* @__PURE__ */ jsx(Minimize2, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Maximize2, { className: "h-3.5 w-3.5" })
2461
- }
2462
- )
2463
- ] })
2464
- ] }),
2465
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col min-h-0", children: [
2466
- /* @__PURE__ */ jsx(ScrollArea, { ref: scrollAreaRef, className: "flex-1 p-4", children: /* @__PURE__ */ jsx("div", { className: "space-y-1 font-mono", children: lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
2467
- showLineNumbers && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground w-12 text-right flex-shrink-0", children: index + 1 }),
2468
- /* @__PURE__ */ jsx(
2469
- "div",
2470
- {
2471
- className: cn(
2472
- "flex-1 whitespace-pre-wrap break-words",
2473
- line.type === "input" && "text-inherit",
2474
- line.type === "output" && "text-muted-foreground",
2475
- line.type === "error" && "text-red-400",
2476
- line.type === "system" && "text-blue-400"
2477
- ),
2478
- children: typeof line.content === "string" && line.content.includes("\x1B[") ? parseAnsiContent(line.content) : line.content
2479
- }
2480
- )
2481
- ] }, line.id)) }) }),
2482
- allowInput && /* @__PURE__ */ jsxs(Fragment, { children: [
2483
- /* @__PURE__ */ jsx(Separator, {}),
2484
- /* @__PURE__ */ jsx("form", { onSubmit: handleInputSubmit, className: "p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 font-mono", children: [
2485
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 flex-shrink-0" }),
2486
- /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: prompt }),
2487
- /* @__PURE__ */ jsx(
2488
- Input,
2489
- {
2490
- ref: inputRef,
2491
- value: input,
2492
- onChange: (e) => setInput(e.target.value),
2493
- onKeyDown: handleKeyDown,
2494
- className: "flex-1 border-0 bg-transparent p-0 focus-visible:ring-0 font-mono",
2495
- placeholder: "Enter command...",
2496
- autoComplete: "off",
2497
- autoFocus: true
2498
- }
2499
- )
2500
- ] }) })
2501
- ] })
2502
- ] })
2503
- ]
2504
- }
2505
- );
2506
- }
2507
- );
2508
- CodeTerminal.displayName = "CodeTerminal";
2509
-
2510
- export { CodeBlock, CodeCompare, CodeDiff2 as CodeDiff, CodeEditor, CodeExplorer, CodePreview, CodeSnippet3 as CodeSnippet, CodeTabs, CodeTerminal, InlineCode, codeBlockVariants, codeCompareVariants, codeDiffVariants, codeExplorerVariants, codePreviewVariants, codeSnippetVariants, codeTerminalVariants };