@plannotator/ui 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/README.md +60 -0
  2. package/assets/diff-options.png +0 -0
  3. package/assets/icon-codex.png +0 -0
  4. package/assets/look-flat.png +0 -0
  5. package/assets/look-grid.png +0 -0
  6. package/assets/review-sections.png +0 -0
  7. package/assets/review-tree.png +0 -0
  8. package/assets/workspaces.webp +0 -0
  9. package/components/AISettingsTab.tsx +145 -0
  10. package/components/ActionMenu.tsx +101 -0
  11. package/components/AgentControls.tsx +216 -0
  12. package/components/AgentsTab.tsx +1294 -0
  13. package/components/AnnotationPanel.tsx +731 -0
  14. package/components/AnnotationSidebar.tsx +86 -0
  15. package/components/AnnotationToolbar.tsx +323 -0
  16. package/components/AnnotationToolstrip.tsx +355 -0
  17. package/components/ApproveDropdown.tsx +170 -0
  18. package/components/AttachmentsButton.tsx +410 -0
  19. package/components/BlockRenderer.tsx +163 -0
  20. package/components/BorderTrail.tsx +38 -0
  21. package/components/CodeFilePicker.tsx +64 -0
  22. package/components/CodeFilePopout.tsx +602 -0
  23. package/components/CodePathValidationContext.tsx +14 -0
  24. package/components/CommentPopover.tsx +512 -0
  25. package/components/CompletionOverlay.tsx +99 -0
  26. package/components/ConfirmDialog.tsx +128 -0
  27. package/components/DocBadges.tsx +231 -0
  28. package/components/EditorAnnotationCard.tsx +65 -0
  29. package/components/ExportModal.tsx +565 -0
  30. package/components/FloatingQuickLabelPicker.tsx +144 -0
  31. package/components/GitHubIcon.tsx +16 -0
  32. package/components/GitLabIcon.tsx +30 -0
  33. package/components/GraphvizBlock.tsx +511 -0
  34. package/components/ImageAnnotator/Canvas.tsx +118 -0
  35. package/components/ImageAnnotator/Toolbar.tsx +218 -0
  36. package/components/ImageAnnotator/index.tsx +272 -0
  37. package/components/ImageAnnotator/types.ts +39 -0
  38. package/components/ImageAnnotator/utils.ts +147 -0
  39. package/components/ImageThumbnail.tsx +126 -0
  40. package/components/ImportModal.tsx +146 -0
  41. package/components/InlineMarkdown.tsx +1094 -0
  42. package/components/KeyboardShortcuts.tsx +208 -0
  43. package/components/Landing.tsx +496 -0
  44. package/components/ListItemBody.tsx +62 -0
  45. package/components/ListMarker.tsx +73 -0
  46. package/components/LookAndFeelAnnouncementDialog.tsx +230 -0
  47. package/components/MarkdownEditor.tsx +44 -0
  48. package/components/MenuVersionSection.tsx +91 -0
  49. package/components/MermaidBlock.tsx +579 -0
  50. package/components/ModeToggle.tsx +68 -0
  51. package/components/OpenInAppButton.tsx +299 -0
  52. package/components/OverlayScrollArea.tsx +82 -0
  53. package/components/PermissionModeSetup.tsx +92 -0
  54. package/components/PinpointOverlay.tsx +99 -0
  55. package/components/PlanAIAnnouncementDialog.tsx +187 -0
  56. package/components/PlanHeaderMenu.tsx +299 -0
  57. package/components/PopoutDialog.tsx +91 -0
  58. package/components/Popover.tsx +27 -0
  59. package/components/ProviderIcons.tsx +51 -0
  60. package/components/PullRequestIcon.tsx +12 -0
  61. package/components/QuickLabelDropdown.tsx +63 -0
  62. package/components/RenderedMarkdown.tsx +54 -0
  63. package/components/RepoIcon.tsx +12 -0
  64. package/components/ResizeHandle.tsx +86 -0
  65. package/components/ReviewAgentsIcon.tsx +18 -0
  66. package/components/SearchableSelect.tsx +157 -0
  67. package/components/Settings.tsx +2220 -0
  68. package/components/SparklesIcon.tsx +45 -0
  69. package/components/StickyHeaderLane.tsx +260 -0
  70. package/components/TableOfContents.tsx +157 -0
  71. package/components/TaterSpritePullup.tsx +34 -0
  72. package/components/TaterSpriteRunning.tsx +54 -0
  73. package/components/TaterSpriteSitting.tsx +37 -0
  74. package/components/TextShimmer.tsx +57 -0
  75. package/components/ThemeProvider.tsx +162 -0
  76. package/components/ThemeTab.tsx +139 -0
  77. package/components/ToolbarButtons.tsx +116 -0
  78. package/components/Tooltip.tsx +45 -0
  79. package/components/Viewer.tsx +977 -0
  80. package/components/ai/AIProviderBar.tsx +95 -0
  81. package/components/ai/DocumentAIChatPanel.tsx +313 -0
  82. package/components/blocks/AlertBlock.tsx +58 -0
  83. package/components/blocks/Callout.tsx +60 -0
  84. package/components/blocks/CodeBlock.tsx +75 -0
  85. package/components/blocks/HtmlBlock.tsx +122 -0
  86. package/components/blocks/MathBlock.tsx +36 -0
  87. package/components/blocks/TableBlock.tsx +146 -0
  88. package/components/blocks/TablePopout.tsx +279 -0
  89. package/components/blocks/TableToolbar.tsx +153 -0
  90. package/components/blocks/proseBody.tsx +102 -0
  91. package/components/core/button.tsx +44 -0
  92. package/components/core/textarea.tsx +26 -0
  93. package/components/diagramLanguages.ts +14 -0
  94. package/components/goal-setup/GoalSetupSurface.tsx +1354 -0
  95. package/components/html-viewer/HtmlViewer.tsx +386 -0
  96. package/components/html-viewer/bridge-script.ts +505 -0
  97. package/components/html-viewer/index.ts +1 -0
  98. package/components/html-viewer/useHtmlAnnotation.ts +400 -0
  99. package/components/icons/AgentIcons.tsx +66 -0
  100. package/components/icons/AppIcon.tsx +56 -0
  101. package/components/icons/MessagesIcon.tsx +11 -0
  102. package/components/icons/ObsidianIcons.tsx +208 -0
  103. package/components/icons/app/android-studio.svg +369 -0
  104. package/components/icons/app/antigravity.svg +97 -0
  105. package/components/icons/app/cursor.svg +16 -0
  106. package/components/icons/app/file-explorer.svg +20 -0
  107. package/components/icons/app/finder.png +0 -0
  108. package/components/icons/app/ghostty.svg +13 -0
  109. package/components/icons/app/iterm2.svg +13 -0
  110. package/components/icons/app/powershell.svg +14 -0
  111. package/components/icons/app/sublime-text.svg +17 -0
  112. package/components/icons/app/terminal.png +0 -0
  113. package/components/icons/app/textmate.png +0 -0
  114. package/components/icons/app/vscode.svg +39 -0
  115. package/components/icons/app/warp.png +0 -0
  116. package/components/icons/app/xcode.png +0 -0
  117. package/components/icons/app/zed-dark.svg +15 -0
  118. package/components/icons/app/zed.svg +15 -0
  119. package/components/icons/themeIcons.tsx +47 -0
  120. package/components/mermaidSvg.ts +33 -0
  121. package/components/plan-diff/PlanCleanDiffView.tsx +884 -0
  122. package/components/plan-diff/PlanDiffBadge.tsx +48 -0
  123. package/components/plan-diff/PlanDiffModeSwitcher.tsx +103 -0
  124. package/components/plan-diff/PlanDiffViewer.tsx +214 -0
  125. package/components/plan-diff/PlanRawDiffView.tsx +102 -0
  126. package/components/plan-diff/VSCodeIcon.tsx +132 -0
  127. package/components/settings/HooksTab.tsx +208 -0
  128. package/components/sidebar/ArchiveBrowser.tsx +98 -0
  129. package/components/sidebar/CountBadge.tsx +12 -0
  130. package/components/sidebar/FileBrowser.tsx +498 -0
  131. package/components/sidebar/MessagesBrowser.tsx +109 -0
  132. package/components/sidebar/SidebarContainer.tsx +353 -0
  133. package/components/sidebar/SidebarTabs.tsx +155 -0
  134. package/components/sidebar/VersionBrowser.tsx +141 -0
  135. package/components/types.d.ts +7 -0
  136. package/components/ui/badge.tsx +44 -0
  137. package/components/ui/button.tsx +83 -0
  138. package/components/ui/card.tsx +58 -0
  139. package/components/ui/dialog.tsx +106 -0
  140. package/components/ui/dropdown-menu.tsx +240 -0
  141. package/components/ui/state-pill.tsx +45 -0
  142. package/components/ui/tabs.tsx +46 -0
  143. package/components/ui/textarea.tsx +25 -0
  144. package/config/configStore.ts +214 -0
  145. package/config/index.ts +4 -0
  146. package/config/reviewView.ts +42 -0
  147. package/config/settings.ts +310 -0
  148. package/config/useConfig.ts +20 -0
  149. package/configure.ts +69 -0
  150. package/globals.d.ts +14 -0
  151. package/hooks/pfm/useCodeFilePopout.ts +111 -0
  152. package/hooks/useAIChat.ts +532 -0
  153. package/hooks/useAIProviderConfig.ts +115 -0
  154. package/hooks/useActiveSection.ts +78 -0
  155. package/hooks/useAgentJobs.ts +306 -0
  156. package/hooks/useAgentSettings.ts +579 -0
  157. package/hooks/useAgents.ts +90 -0
  158. package/hooks/useAnnotationDraft.ts +523 -0
  159. package/hooks/useAnnotationHighlighter.ts +1170 -0
  160. package/hooks/useArchive.ts +171 -0
  161. package/hooks/useAutoClose.ts +104 -0
  162. package/hooks/useCodeAnnotationDraft.ts +194 -0
  163. package/hooks/useCodeFilePopout.ts +1 -0
  164. package/hooks/useDismissOnOutsideAndEscape.ts +50 -0
  165. package/hooks/useDraggable.ts +108 -0
  166. package/hooks/useEditorAnnotations.ts +64 -0
  167. package/hooks/useExternalAnnotationHighlights.ts +105 -0
  168. package/hooks/useExternalAnnotations.ts +268 -0
  169. package/hooks/useFileBrowser.ts +402 -0
  170. package/hooks/useInputMethodSwitch.ts +89 -0
  171. package/hooks/useIsMobile.ts +17 -0
  172. package/hooks/useLinkedDoc.ts +494 -0
  173. package/hooks/useOverlayViewport.ts +36 -0
  174. package/hooks/usePinpoint.ts +182 -0
  175. package/hooks/usePlanDiff.ts +176 -0
  176. package/hooks/usePrintMode.ts +27 -0
  177. package/hooks/useResizablePanel.ts +173 -0
  178. package/hooks/useScrollViewport.ts +38 -0
  179. package/hooks/useSharing.ts +441 -0
  180. package/hooks/useSidebar.ts +55 -0
  181. package/hooks/useUpdateCheck.ts +132 -0
  182. package/hooks/useValidatedCodePaths.ts +94 -0
  183. package/icons/GitUser.tsx +17 -0
  184. package/lib/utils.ts +11 -0
  185. package/package.json +117 -0
  186. package/plannotator.webp +0 -0
  187. package/print.css +427 -0
  188. package/shortcuts/code-review/ai.shortcuts.ts +25 -0
  189. package/shortcuts/code-review/allFilesDiff.shortcuts.ts +46 -0
  190. package/shortcuts/code-review/annotationToolbar.shortcuts.ts +30 -0
  191. package/shortcuts/code-review/fileTree.shortcuts.ts +35 -0
  192. package/shortcuts/code-review/prComments.shortcuts.ts +18 -0
  193. package/shortcuts/code-review/suggestionModal.shortcuts.ts +25 -0
  194. package/shortcuts/code-review/tourDialog.shortcuts.ts +18 -0
  195. package/shortcuts/core.ts +399 -0
  196. package/shortcuts/index.ts +21 -0
  197. package/shortcuts/plan-review/annotationPanel.shortcuts.ts +25 -0
  198. package/shortcuts/plan-review/annotationToolbar.shortcuts.ts +39 -0
  199. package/shortcuts/plan-review/commentPopover.shortcuts.ts +21 -0
  200. package/shortcuts/plan-review/goalSetup.shortcuts.ts +10 -0
  201. package/shortcuts/plan-review/imageAnnotator.shortcuts.ts +42 -0
  202. package/shortcuts/plan-review/inputMethod.shortcuts.ts +30 -0
  203. package/shortcuts/plan-review/sidebar.shortcuts.ts +34 -0
  204. package/shortcuts/plan-review/viewer.shortcuts.ts +24 -0
  205. package/shortcuts/runtime.ts +259 -0
  206. package/sprite_package_additional/index.html +34 -0
  207. package/sprite_package_additional/sprite.png +0 -0
  208. package/sprite_package_new/index.html +34 -0
  209. package/sprite_package_new/sprite.png +0 -0
  210. package/sprite_package_pulluphang/index.html +34 -0
  211. package/sprite_package_pulluphang/sprite.png +0 -0
  212. package/styles.css +1 -0
  213. package/theme.css +893 -0
  214. package/themes/adwaita.css +62 -0
  215. package/themes/andromeeda.css +66 -0
  216. package/themes/aurora-x.css +66 -0
  217. package/themes/ayu-dark.css +66 -0
  218. package/themes/caffeine.css +113 -0
  219. package/themes/catppuccin.css +62 -0
  220. package/themes/claude-plus.css +60 -0
  221. package/themes/cursor-hc.css +34 -0
  222. package/themes/cursor-midnight.css +34 -0
  223. package/themes/cursor.css +62 -0
  224. package/themes/dark-plus.css +66 -0
  225. package/themes/doom-64.css +109 -0
  226. package/themes/dracula.css +33 -0
  227. package/themes/everforest-hard.css +62 -0
  228. package/themes/everforest-soft.css +62 -0
  229. package/themes/everforest.css +62 -0
  230. package/themes/github.css +66 -0
  231. package/themes/gruvbox.css +62 -0
  232. package/themes/houston.css +66 -0
  233. package/themes/kanagawa-dragon.css +34 -0
  234. package/themes/kanagawa-lotus.css +34 -0
  235. package/themes/kanagawa-wave.css +34 -0
  236. package/themes/laserwave.css +66 -0
  237. package/themes/material.css +62 -0
  238. package/themes/min.css +66 -0
  239. package/themes/monokai-pro.css +34 -0
  240. package/themes/neutral.css +59 -0
  241. package/themes/night-owl.css +66 -0
  242. package/themes/nord.css +66 -0
  243. package/themes/one-dark-pro.css +66 -0
  244. package/themes/one-light.css +66 -0
  245. package/themes/paulmillr.css +34 -0
  246. package/themes/plannotator.css +60 -0
  247. package/themes/plastic.css +66 -0
  248. package/themes/poimandres.css +66 -0
  249. package/themes/quantum-rose.css +109 -0
  250. package/themes/red.css +66 -0
  251. package/themes/rose-pine.css +62 -0
  252. package/themes/simple.css +124 -0
  253. package/themes/slack.css +66 -0
  254. package/themes/snazzy-light.css +66 -0
  255. package/themes/soft-pop.css +60 -0
  256. package/themes/solar-dusk.css +109 -0
  257. package/themes/solarized.css +66 -0
  258. package/themes/synthwave-84.css +34 -0
  259. package/themes/terminal.css +62 -0
  260. package/themes/tinacious.css +57 -0
  261. package/themes/tokyo-night.css +62 -0
  262. package/themes/vesper.css +62 -0
  263. package/themes/vitesse-black.css +66 -0
  264. package/themes/vitesse.css +62 -0
  265. package/types.ts +264 -0
  266. package/utils/agentSwitch.ts +71 -0
  267. package/utils/aiChatFormat.ts +33 -0
  268. package/utils/aiPrompt.ts +29 -0
  269. package/utils/aiProvider.ts +217 -0
  270. package/utils/anchors.ts +11 -0
  271. package/utils/annotateAgentTerminal.ts +29 -0
  272. package/utils/annotationHelpers.ts +101 -0
  273. package/utils/bear.ts +59 -0
  274. package/utils/blockTargeting.ts +240 -0
  275. package/utils/callback.ts +99 -0
  276. package/utils/commentContent.ts +8 -0
  277. package/utils/defaultNotesApp.ts +20 -0
  278. package/utils/diffFonts.ts +33 -0
  279. package/utils/editorMode.ts +31 -0
  280. package/utils/fileBrowser.ts +40 -0
  281. package/utils/generateId.ts +7 -0
  282. package/utils/generateIdentity.ts +29 -0
  283. package/utils/identity.ts +112 -0
  284. package/utils/inlineTransforms.ts +41 -0
  285. package/utils/inputMethod.ts +17 -0
  286. package/utils/lookAndFeelAnnouncement.ts +18 -0
  287. package/utils/obsidian.ts +201 -0
  288. package/utils/octarine.ts +53 -0
  289. package/utils/parser.ts +991 -0
  290. package/utils/permissionMode.ts +77 -0
  291. package/utils/planAIAnnouncement.ts +17 -0
  292. package/utils/planAgentInstructions.ts +137 -0
  293. package/utils/planDiffEngine.ts +590 -0
  294. package/utils/planSave.ts +49 -0
  295. package/utils/platform.ts +11 -0
  296. package/utils/quickLabels.ts +77 -0
  297. package/utils/reviewAgentInstructions.ts +182 -0
  298. package/utils/sanitizeHtml.ts +45 -0
  299. package/utils/sharing.ts +350 -0
  300. package/utils/slugify.ts +37 -0
  301. package/utils/storage.ts +141 -0
  302. package/utils/themeRegistry.ts +567 -0
  303. package/utils/uiPreferences.ts +34 -0
  304. package/utils/upload.ts +56 -0
  305. package/utils/wideMode.ts +48 -0
@@ -0,0 +1,153 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { buildCsv } from './TableBlock';
4
+
5
+ interface TableToolbarProps {
6
+ element: HTMLElement;
7
+ markdown: string;
8
+ isExiting?: boolean;
9
+ onMouseEnter?: () => void;
10
+ onMouseLeave?: () => void;
11
+ onExpand?: () => void;
12
+ }
13
+
14
+ // Floats above a table on hover. Mirrors AnnotationToolbar's positioning +
15
+ // portal + entry/exit animation pattern so hover-triggered block toolbars
16
+ // all look and move the same way. Content is table-specific (copy markdown
17
+ // for now; CSV text button and expand icon planned). When a third use case
18
+ // appears we should factor the positioning shell into a shared hook.
19
+ export const TableToolbar: React.FC<TableToolbarProps> = ({
20
+ element,
21
+ markdown,
22
+ isExiting = false,
23
+ onMouseEnter,
24
+ onMouseLeave,
25
+ onExpand,
26
+ }) => {
27
+ const [position, setPosition] = useState<{ top: number; right: number } | null>(null);
28
+ const [copiedMd, setCopiedMd] = useState(false);
29
+ const [copiedCsv, setCopiedCsv] = useState(false);
30
+ const toolbarRef = useRef<HTMLDivElement>(null);
31
+
32
+ useEffect(() => {
33
+ const updatePosition = () => {
34
+ const rect = element.getBoundingClientRect();
35
+ setPosition({
36
+ top: rect.top - 40,
37
+ right: window.innerWidth - rect.right,
38
+ });
39
+ };
40
+ updatePosition();
41
+ window.addEventListener('scroll', updatePosition, true);
42
+ window.addEventListener('resize', updatePosition);
43
+ return () => {
44
+ window.removeEventListener('scroll', updatePosition, true);
45
+ window.removeEventListener('resize', updatePosition);
46
+ };
47
+ }, [element]);
48
+
49
+ useEffect(() => {
50
+ setCopiedMd(false);
51
+ setCopiedCsv(false);
52
+ }, [element]);
53
+
54
+ const handleCopyMarkdown = async () => {
55
+ try {
56
+ await navigator.clipboard.writeText(markdown);
57
+ setCopiedMd(true);
58
+ setTimeout(() => setCopiedMd(false), 1500);
59
+ } catch (err) {
60
+ console.error('Failed to copy:', err);
61
+ }
62
+ };
63
+
64
+ const handleCopyCsv = async () => {
65
+ try {
66
+ await navigator.clipboard.writeText(buildCsv(markdown));
67
+ setCopiedCsv(true);
68
+ setTimeout(() => setCopiedCsv(false), 1500);
69
+ } catch (err) {
70
+ console.error('Failed to copy:', err);
71
+ }
72
+ };
73
+
74
+ if (!position) return null;
75
+
76
+ const style: React.CSSProperties = {
77
+ top: position.top,
78
+ right: position.right,
79
+ animation: isExiting
80
+ ? 'table-toolbar-out 0.15s ease-in forwards'
81
+ : 'table-toolbar-in 0.15s ease-out',
82
+ };
83
+
84
+ return createPortal(
85
+ <div
86
+ ref={toolbarRef}
87
+ className="fixed z-[100] bg-popover border border-border rounded-md shadow-md"
88
+ style={style}
89
+ onMouseDown={(e) => e.stopPropagation()}
90
+ onMouseEnter={onMouseEnter}
91
+ onMouseLeave={onMouseLeave}
92
+ >
93
+ <style>{`
94
+ @keyframes table-toolbar-in {
95
+ from { opacity: 0; transform: translateY(12px); }
96
+ to { opacity: 1; transform: translateY(0); }
97
+ }
98
+ @keyframes table-toolbar-out {
99
+ from { opacity: 1; transform: translateY(0); }
100
+ to { opacity: 0; transform: translateY(8px); }
101
+ }
102
+ `}</style>
103
+ <div className="flex items-center p-0.5 gap-0.5">
104
+ <button
105
+ onClick={handleCopyMarkdown}
106
+ title={copiedMd ? 'Copied!' : 'Copy as markdown'}
107
+ className={`p-1 rounded transition-colors ${
108
+ copiedMd ? 'text-success' : 'text-muted-foreground hover:bg-muted hover:text-foreground'
109
+ }`}
110
+ >
111
+ {copiedMd ? <CheckIcon /> : <CopyIcon />}
112
+ </button>
113
+ <button
114
+ onClick={handleCopyCsv}
115
+ title={copiedCsv ? 'Copied as CSV!' : 'Copy as CSV'}
116
+ className={`px-1.5 py-1 rounded text-[10px] font-bold tracking-tight uppercase leading-none transition-colors ${
117
+ copiedCsv ? 'text-success' : 'text-primary hover:bg-primary/10'
118
+ }`}
119
+ >
120
+ {copiedCsv ? '✓' : 'CSV'}
121
+ </button>
122
+ {onExpand && (
123
+ <button
124
+ onClick={onExpand}
125
+ title="Expand table"
126
+ className="p-1 rounded text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
127
+ >
128
+ <ExpandIcon />
129
+ </button>
130
+ )}
131
+ </div>
132
+ </div>,
133
+ document.body,
134
+ );
135
+ };
136
+
137
+ const CopyIcon = () => (
138
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
139
+ <path strokeLinecap="round" strokeLinejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
140
+ </svg>
141
+ );
142
+
143
+ const CheckIcon = () => (
144
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
145
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
146
+ </svg>
147
+ );
148
+
149
+ const ExpandIcon = () => (
150
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
151
+ <path strokeLinecap="round" strokeLinejoin="round" d="M4 8V4h4M20 8V4h-4M4 16v4h4M20 16v4h-4" />
152
+ </svg>
153
+ );
@@ -0,0 +1,102 @@
1
+ import React from 'react';
2
+ import { InlineMarkdown } from '../InlineMarkdown';
3
+
4
+ // Shared block-level renderer for alert and directive bodies. Handles
5
+ // paragraphs, unordered lists, and ordered lists — the shapes GitHub alerts
6
+ // and Docusaurus / VitePress directives commonly carry. More exotic content
7
+ // (headings, tables, code fences, nested alerts) falls back to inline text;
8
+ // add cases here when a real use case turns up.
9
+ //
10
+ // Paragraph lines are joined with '\n' (not ' ') so InlineMarkdown's hard
11
+ // line-break handling still fires for ` \n` inside a callout body.
12
+ export function renderProseBody(args: {
13
+ body: string;
14
+ paragraphClassName?: string;
15
+ listClassName?: string;
16
+ imageBaseDir?: string;
17
+ onImageClick?: (src: string, alt: string) => void;
18
+ onOpenLinkedDoc?: (path: string) => void;
19
+ onOpenCodeFile?: (path: string) => void;
20
+ onNavigateAnchor?: (hash: string) => void;
21
+ githubRepo?: string;
22
+ }): React.ReactNode {
23
+ const {
24
+ body,
25
+ paragraphClassName = 'text-[15px] leading-relaxed text-foreground/90',
26
+ listClassName = 'text-[15px] leading-relaxed text-foreground/90',
27
+ imageBaseDir,
28
+ onImageClick,
29
+ onOpenLinkedDoc,
30
+ onOpenCodeFile,
31
+ onNavigateAnchor,
32
+ githubRepo,
33
+ } = args;
34
+
35
+ const inline = (text: string) => (
36
+ <InlineMarkdown
37
+ imageBaseDir={imageBaseDir}
38
+ onImageClick={onImageClick}
39
+ text={text}
40
+ onOpenLinkedDoc={onOpenLinkedDoc}
41
+ onOpenCodeFile={onOpenCodeFile}
42
+ onNavigateAnchor={onNavigateAnchor}
43
+ githubRepo={githubRepo}
44
+ />
45
+ );
46
+
47
+ const lines = body.split('\n');
48
+ const out: React.ReactNode[] = [];
49
+ let paraLines: string[] = [];
50
+ let list: { ordered: boolean; items: string[] } | null = null;
51
+ let key = 0;
52
+
53
+ const flushPara = () => {
54
+ if (paraLines.length === 0) return;
55
+ const text = paraLines.join('\n');
56
+ if (text.trim()) {
57
+ out.push(
58
+ <p key={`p-${key++}`} className={`${paragraphClassName} ${out.length > 0 ? 'mt-2' : ''}`}>
59
+ {inline(text)}
60
+ </p>,
61
+ );
62
+ }
63
+ paraLines = [];
64
+ };
65
+ const flushList = () => {
66
+ if (!list) return;
67
+ const Tag = list.ordered ? 'ol' : 'ul';
68
+ const className = `${list.ordered ? 'list-decimal' : 'list-disc'} pl-5 ${listClassName} ${out.length > 0 ? 'mt-2' : ''}`;
69
+ out.push(
70
+ <Tag key={`l-${key++}`} className={className}>
71
+ {list.items.map((item, i) => (
72
+ <li key={i} className="my-0.5">{inline(item)}</li>
73
+ ))}
74
+ </Tag>,
75
+ );
76
+ list = null;
77
+ };
78
+
79
+ for (const line of lines) {
80
+ if (line.trim() === '') {
81
+ flushPara();
82
+ flushList();
83
+ continue;
84
+ }
85
+ const listMatch = line.match(/^\s*(\*|-|\d+\.)\s+(.*)$/);
86
+ if (listMatch) {
87
+ flushPara();
88
+ const ordered = /\d/.test(listMatch[1]);
89
+ if (!list || list.ordered !== ordered) {
90
+ flushList();
91
+ list = { ordered, items: [] };
92
+ }
93
+ list.items.push(listMatch[2]);
94
+ } else {
95
+ flushList();
96
+ paraLines.push(line);
97
+ }
98
+ }
99
+ flushPara();
100
+ flushList();
101
+ return out;
102
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+
3
+ type ButtonVariant = 'primary' | 'outline' | 'ghost' | 'icon' | 'danger';
4
+ type ButtonSize = 'sm' | 'md' | 'icon';
5
+
6
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
7
+ variant?: ButtonVariant;
8
+ size?: ButtonSize;
9
+ active?: boolean;
10
+ }
11
+
12
+ function cx(...classes: Array<string | false | null | undefined>): string {
13
+ return classes.filter(Boolean).join(' ');
14
+ }
15
+
16
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({
17
+ className,
18
+ variant = 'outline',
19
+ size = 'md',
20
+ active = false,
21
+ type = 'button',
22
+ ...props
23
+ }, ref) => (
24
+ <button
25
+ ref={ref}
26
+ type={type}
27
+ className={cx(
28
+ 'inline-flex items-center justify-center gap-2 rounded-md text-xs font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50',
29
+ size === 'sm' && 'h-8 px-2.5',
30
+ size === 'md' && 'h-9 px-3',
31
+ size === 'icon' && 'h-8 w-8',
32
+ variant === 'primary' && 'bg-primary text-primary-foreground hover:opacity-90',
33
+ variant === 'outline' && 'border border-border bg-card text-foreground hover:bg-muted',
34
+ variant === 'ghost' && 'text-muted-foreground hover:bg-muted hover:text-foreground',
35
+ variant === 'icon' && (active
36
+ ? 'border border-primary/50 bg-primary/10 text-primary'
37
+ : 'border border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground'),
38
+ variant === 'danger' && 'border border-border bg-background text-muted-foreground hover:border-destructive/50 hover:bg-destructive/10 hover:text-destructive',
39
+ className
40
+ )}
41
+ {...props}
42
+ />
43
+ ));
44
+ Button.displayName = 'Button';
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
4
+
5
+ const fieldSizing = { fieldSizing: 'content' } as React.CSSProperties;
6
+
7
+ function cx(...classes: Array<string | false | null | undefined>): string {
8
+ return classes.filter(Boolean).join(' ');
9
+ }
10
+
11
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({
12
+ className,
13
+ style,
14
+ ...props
15
+ }, ref) => (
16
+ <textarea
17
+ ref={ref}
18
+ className={cx(
19
+ 'min-h-24 w-full resize-none rounded-md border border-border bg-background px-3 py-2 text-sm leading-6 text-foreground outline-none transition-colors placeholder:text-muted-foreground/70 focus:border-primary/60 focus:ring-2 focus:ring-primary/15',
20
+ className
21
+ )}
22
+ style={{ ...fieldSizing, ...style }}
23
+ {...props}
24
+ />
25
+ ));
26
+ Textarea.displayName = 'Textarea';
@@ -0,0 +1,14 @@
1
+ const GRAPHVIZ_LANGUAGES = new Set(['dot', 'graphviz', 'gv']);
2
+
3
+ function getFenceLanguage(language?: string): string | undefined {
4
+ return language?.trim().split(/\s+/, 1)[0]?.toLowerCase();
5
+ }
6
+
7
+ export function isMermaidLanguage(language?: string): boolean {
8
+ return getFenceLanguage(language) === 'mermaid';
9
+ }
10
+
11
+ export function isGraphvizLanguage(language?: string): boolean {
12
+ const normalizedLanguage = getFenceLanguage(language);
13
+ return normalizedLanguage ? GRAPHVIZ_LANGUAGES.has(normalizedLanguage) : false;
14
+ }