@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,63 @@
1
+ import React from 'react';
2
+ import { type QuickLabel, getLabelColors } from '../utils/quickLabels';
3
+
4
+ /**
5
+ * Shared vertical label list used by both FloatingQuickLabelPicker
6
+ * and the AnnotationToolbar's inline dropdown.
7
+ *
8
+ * Context-menu style: single column, colored accent bars, full-width rows.
9
+ */
10
+ export const QuickLabelDropdown: React.FC<{
11
+ labels: QuickLabel[];
12
+ onSelect: (label: QuickLabel) => void;
13
+ /** Enable staggered row entrance animation */
14
+ animate?: boolean;
15
+ }> = ({ labels, onSelect, animate = false }) => {
16
+ return (
17
+ <div className="py-1" onMouseDown={(e) => e.stopPropagation()}>
18
+ {animate && (
19
+ <style>{`
20
+ @keyframes qld-row-in {
21
+ from { opacity: 0; transform: translateX(-3px); }
22
+ to { opacity: 1; transform: translateX(0); }
23
+ }
24
+ `}</style>
25
+ )}
26
+ {labels.map((label, index) => {
27
+ const colors = getLabelColors(label.color);
28
+ return (
29
+ <button
30
+ key={label.id}
31
+ onClick={() => onSelect(label)}
32
+ className="group w-full flex items-center gap-2 px-2 py-[5px] text-left transition-colors hover:bg-muted/60 active:bg-muted"
33
+ style={animate ? {
34
+ animationDelay: `${index * 18}ms`,
35
+ animationName: 'qld-row-in',
36
+ animationDuration: '0.1s',
37
+ animationFillMode: 'both',
38
+ animationTimingFunction: 'ease-out',
39
+ } : undefined}
40
+ >
41
+ {/* Color accent bar */}
42
+ <span
43
+ className="w-[3px] self-stretch rounded-full flex-shrink-0"
44
+ style={{ backgroundColor: colors.text }}
45
+ />
46
+ {/* Emoji */}
47
+ <span className="text-xs leading-none flex-shrink-0">{label.emoji}</span>
48
+ {/* Label text */}
49
+ <span className="text-[11px] leading-tight text-foreground/85 group-hover:text-foreground truncate flex-1">
50
+ {label.text}
51
+ </span>
52
+ {/* Shortcut hint */}
53
+ {index < 10 && (
54
+ <span className="text-[9px] tabular-nums text-muted-foreground/40 group-hover:text-muted-foreground/60 flex-shrink-0 font-mono">
55
+ {index === 9 ? '0' : index + 1}
56
+ </span>
57
+ )}
58
+ </button>
59
+ );
60
+ })}
61
+ </div>
62
+ );
63
+ };
@@ -0,0 +1,54 @@
1
+ import React, { useMemo } from 'react';
2
+ import { parseMarkdownToBlocks, groupBlocks, computeListIndices } from '../utils/parser';
3
+ import { BlockRenderer } from './BlockRenderer';
4
+
5
+ /**
6
+ * Full markdown renderer for surfaces outside the plan editor (e.g. the PR
7
+ * description). Reuses the shared {@link BlockRenderer} — so tables, HTML,
8
+ * callouts, code, and `data-block-id` all come along — without dragging in the
9
+ * plan Viewer's diagram/toolbar/lightbox machinery.
10
+ *
11
+ * Sizing/spacing is inherited from the wrapper: pass `className="md-compact"`
12
+ * (or any scope) to override the renderer's plan-sized defaults via CSS.
13
+ */
14
+ export interface RenderedMarkdownProps {
15
+ markdown: string;
16
+ className?: string;
17
+ onImageClick?: (src: string, alt: string) => void;
18
+ onOpenLinkedDoc?: (path: string) => void;
19
+ onOpenCodeFile?: (path: string) => void;
20
+ githubRepo?: string;
21
+ }
22
+
23
+ export const RenderedMarkdown: React.FC<RenderedMarkdownProps> = ({
24
+ markdown,
25
+ className,
26
+ onImageClick,
27
+ onOpenLinkedDoc,
28
+ onOpenCodeFile,
29
+ githubRepo,
30
+ }) => {
31
+ const groups = useMemo(() => groupBlocks(parseMarkdownToBlocks(markdown)), [markdown]);
32
+ const cb = { onImageClick, onOpenLinkedDoc, onOpenCodeFile, githubRepo };
33
+
34
+ return (
35
+ <div className={className}>
36
+ {groups.map((group) =>
37
+ group.type === 'list-group' ? (
38
+ (() => {
39
+ const indices = computeListIndices(group.blocks);
40
+ return (
41
+ <div key={group.key}>
42
+ {group.blocks.map((block, i) => (
43
+ <BlockRenderer key={block.id} block={block} orderedIndex={indices[i]} {...cb} />
44
+ ))}
45
+ </div>
46
+ );
47
+ })()
48
+ ) : (
49
+ <BlockRenderer key={group.block.id} block={group.block} {...cb} />
50
+ ),
51
+ )}
52
+ </div>
53
+ );
54
+ };
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+
3
+ export const RepoIcon: React.FC<{ className?: string }> = ({ className }) => (
4
+ <svg
5
+ className={className}
6
+ viewBox="0 0 768 1024"
7
+ fill="currentColor"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ >
10
+ <path d="M320 256h-64v64h64v-64z m0-128h-64v64h64v-64z m384-128c-32 0-608 0-640 0s-64 32-64 64 0 736 0 768 32 64 64 64 128 0 128 0v128l96-96 96 96v-128s288 0 320 0 64-32 64-64 0-736 0-768-32-64-64-64z m0 800c0 16-15 32-32 32s-288 0-288 0v-64h-192v64s-79 0-96 0-32-17-32-32 0-96 0-96h640s0 80 0 96z m0-160h-512v-576h513l-1 576z m-384-128h-64v64h64v-64z m0-128h-64v64h64v-64z" />
11
+ </svg>
12
+ );
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+ import type { ResizeHandleProps as BaseProps } from '../hooks/useResizablePanel';
3
+
4
+ interface Props extends BaseProps {
5
+ className?: string;
6
+ /** When provided, renders a hover-reveal collapse button centered on the
7
+ * handle (prototype's gutter affordance). Chevron direction follows `side`:
8
+ * a left sidebar collapses leftward, a right sidebar rightward. */
9
+ onCollapse?: () => void;
10
+ /**
11
+ * Which panel this handle resizes, not which side of the boundary it's on.
12
+ *
13
+ * The touch-area is an absolutely-positioned child of a `w-0` parent, so
14
+ * its actual width is `parent - left - right`. Because the parent is zero
15
+ * wide, any combination where `left` and `right` cancel produces a 0-px
16
+ * element and the handle becomes undraggable. See issue #354.
17
+ *
18
+ * 'left' — resizes a left sidebar. Touch area extends slightly into the
19
+ * sidebar (leftward) and slightly past the boundary (rightward).
20
+ * 12px total: `-left-1` (-4px) + `-right-2` (-8px) → width 12.
21
+ * 'right' — resizes a right panel. Touch area must NOT extend leftward,
22
+ * because the adjacent content area's overlay scrollbar lives
23
+ * in that region (right edge of the content, just left of the
24
+ * boundary). `left-0 -right-3` → width 12, entirely to the
25
+ * right of the boundary. DO NOT push `left` positive —
26
+ * `left-3 -right-3` evaluates to width 0 and kills the drag.
27
+ */
28
+ side?: 'left' | 'right';
29
+ }
30
+
31
+ export const ResizeHandle: React.FC<Props> = ({
32
+ isDragging,
33
+ onPointerDown,
34
+ onDoubleClick,
35
+ style,
36
+ className,
37
+ side,
38
+ onCollapse,
39
+ }) => (
40
+ <div
41
+ className={`relative w-0 cursor-col-resize flex-shrink-0 group${className ? ` ${className}` : ''}`}
42
+ >
43
+ {/* Visible track — 4px wide, centered on the zero-width layout box,
44
+ invisible until hover/drag. */}
45
+ <div className={`absolute inset-y-0 -left-0.5 -right-0.5 transition-colors ${
46
+ isDragging ? 'bg-transparent' : 'group-hover:bg-border'
47
+ }`} />
48
+ {/* Wider grab/touch zone — must never have zero width (see `side` docs).
49
+ Pointer events + setPointerCapture live here; touch-action:none (from
50
+ style) stops touch drags from scroll-hijacking. */}
51
+ <div
52
+ className={`absolute inset-y-0 ${
53
+ side === 'left' ? '-right-2 -left-1' :
54
+ side === 'right' ? '-right-3 left-0' :
55
+ '-inset-x-2'
56
+ }`}
57
+ style={style}
58
+ onPointerDown={onPointerDown}
59
+ onDoubleClick={onDoubleClick}
60
+ />
61
+ {/* Hover-reveal collapse button, centered on the handle. stopPropagation on
62
+ pointerdown so clicking it never starts a drag. Hidden mid-drag. */}
63
+ {onCollapse && !isDragging && (
64
+ <button
65
+ type="button"
66
+ onPointerDown={(e) => e.stopPropagation()}
67
+ onClick={(e) => {
68
+ e.stopPropagation();
69
+ onCollapse();
70
+ }}
71
+ title="Collapse sidebar"
72
+ aria-label="Collapse sidebar"
73
+ data-collapse={side}
74
+ className="absolute top-1/2 left-1/2 z-20 flex h-6 w-4 -translate-x-1/2 -translate-y-1/2 items-center justify-center before:absolute before:-inset-2 before:content-[''] rounded-sm bg-surface-1 text-muted-foreground/60 opacity-0 ring-1 ring-border/40 transition-opacity hover:text-foreground group-hover:opacity-100 group-hover/sidebar:opacity-100"
75
+ >
76
+ <svg className="size-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
77
+ <path
78
+ strokeLinecap="round"
79
+ strokeLinejoin="round"
80
+ d={side === 'right' ? 'M9 5l7 7-7 7' : 'M15 19l-7-7 7-7'}
81
+ />
82
+ </svg>
83
+ </button>
84
+ )}
85
+ </div>
86
+ );
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ interface ReviewAgentsIconProps {
4
+ className?: string;
5
+ }
6
+
7
+ /**
8
+ * Magnifying glass with cog — icon for the Review Agents tab.
9
+ * Uses currentColor for stroke so it inherits from parent text color.
10
+ */
11
+ export const ReviewAgentsIcon: React.FC<ReviewAgentsIconProps> = ({ className = 'w-3.5 h-3.5' }) => (
12
+ <svg className={`${className} flex-shrink-0`} viewBox="0 0 64 64" fill="none" stroke="currentColor">
13
+ <path d="M40.5 40.5L55 55" strokeWidth={7} strokeLinecap="round" />
14
+ <circle cx="27" cy="27" r="20" strokeWidth={4} />
15
+ <path d="M27 14L29.2 14.3L30.2 17.2L32.6 18.6L35.5 17.5L37.2 19.2L36.1 22.1L37.5 24.5L40.4 25.5L40.7 27.7L40.4 29.9L37.5 30.9L36.1 33.3L37.2 36.2L35.5 37.9L32.6 36.8L30.2 38.2L29.2 41.1L27 41.4L24.8 41.1L23.8 38.2L21.4 36.8L18.5 37.9L16.8 36.2L17.9 33.3L16.5 30.9L13.6 29.9L13.3 27.7L13.6 25.5L16.5 24.5L17.9 22.1L16.8 19.2L18.5 17.5L21.4 18.6L23.8 17.2L24.8 14.3Z" strokeWidth={1.5} strokeLinejoin="round" />
16
+ <circle cx="27" cy="27.7" r="7" strokeWidth={1.5} />
17
+ </svg>
18
+ );
@@ -0,0 +1,157 @@
1
+ import React, { useState, useMemo, useRef, useCallback, type ReactNode } from 'react';
2
+ import { Popover, PopoverTrigger, PopoverContent } from './Popover';
3
+
4
+ interface SearchableSelectProps<T extends { id: string }> {
5
+ items: T[];
6
+ selectedId?: string;
7
+ onSelect: (item: T) => void;
8
+ filterFn: (item: T, query: string) => boolean;
9
+ renderItem: (item: T, state: { isSelected: boolean; isFocused: boolean }) => ReactNode;
10
+ renderTrigger: (state: { open: boolean }) => ReactNode;
11
+ headerContent?: ReactNode;
12
+ placeholder?: string;
13
+ emptyMessage?: string;
14
+ align?: 'start' | 'center' | 'end';
15
+ width?: string;
16
+ onOpenChange?: (open: boolean) => void;
17
+ }
18
+
19
+ export function SearchableSelect<T extends { id: string }>({
20
+ items,
21
+ selectedId,
22
+ onSelect,
23
+ filterFn,
24
+ renderItem,
25
+ renderTrigger,
26
+ headerContent,
27
+ placeholder = 'Search...',
28
+ emptyMessage = 'No results',
29
+ align = 'start',
30
+ width = 'w-64',
31
+ onOpenChange,
32
+ }: SearchableSelectProps<T>) {
33
+ const [open, setOpen] = useState(false);
34
+ const [search, setSearch] = useState('');
35
+ const [focusedIndex, setFocusedIndex] = useState(-1);
36
+ const listRef = useRef<HTMLDivElement>(null);
37
+ const inputRef = useRef<HTMLInputElement>(null);
38
+
39
+ const filtered = useMemo(
40
+ () => (search ? items.filter((item) => filterFn(item, search)) : items),
41
+ [items, search, filterFn],
42
+ );
43
+
44
+ const handleOpenChange = useCallback(
45
+ (next: boolean) => {
46
+ setOpen(next);
47
+ if (!next) {
48
+ setSearch('');
49
+ setFocusedIndex(-1);
50
+ }
51
+ onOpenChange?.(next);
52
+ },
53
+ [onOpenChange],
54
+ );
55
+
56
+ const scrollToIndex = useCallback((i: number) => {
57
+ listRef.current?.querySelector(`[data-index="${i}"]`)?.scrollIntoView({ block: 'nearest' });
58
+ }, []);
59
+
60
+ const handleKeyDown = useCallback(
61
+ (e: React.KeyboardEvent<HTMLInputElement>) => {
62
+ const len = filtered.length;
63
+ if (len === 0) return;
64
+
65
+ if (e.key === 'ArrowDown') {
66
+ e.preventDefault();
67
+ const next = focusedIndex < len - 1 ? focusedIndex + 1 : 0;
68
+ setFocusedIndex(next);
69
+ scrollToIndex(next);
70
+ } else if (e.key === 'ArrowUp') {
71
+ e.preventDefault();
72
+ const next = focusedIndex > 0 ? focusedIndex - 1 : len - 1;
73
+ setFocusedIndex(next);
74
+ scrollToIndex(next);
75
+ } else if (e.key === 'Enter') {
76
+ e.preventDefault();
77
+ if (focusedIndex >= 0 && filtered[focusedIndex]) {
78
+ onSelect(filtered[focusedIndex]);
79
+ handleOpenChange(false);
80
+ }
81
+ }
82
+ },
83
+ [filtered, focusedIndex, onSelect, handleOpenChange, scrollToIndex],
84
+ );
85
+
86
+ return (
87
+ <Popover open={open} onOpenChange={handleOpenChange}>
88
+ <PopoverTrigger asChild>{renderTrigger({ open })}</PopoverTrigger>
89
+ <PopoverContent
90
+ align={align}
91
+ className={`${width} p-0`}
92
+ onOpenAutoFocus={(e) => {
93
+ e.preventDefault();
94
+ inputRef.current?.focus();
95
+ }}
96
+ >
97
+ {/* Search row */}
98
+ <div className="flex items-center gap-2 border-b border-border/50 px-3 py-2">
99
+ <svg
100
+ className="w-3 h-3 flex-shrink-0 text-muted-foreground/60"
101
+ fill="none"
102
+ viewBox="0 0 24 24"
103
+ stroke="currentColor"
104
+ strokeWidth={2.5}
105
+ >
106
+ <path
107
+ strokeLinecap="round"
108
+ strokeLinejoin="round"
109
+ d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
110
+ />
111
+ </svg>
112
+ <input
113
+ ref={inputRef}
114
+ value={search}
115
+ onChange={(e) => {
116
+ setSearch(e.target.value);
117
+ setFocusedIndex(-1);
118
+ }}
119
+ onKeyDown={handleKeyDown}
120
+ placeholder={placeholder}
121
+ className="w-full bg-transparent text-xs text-foreground placeholder:text-muted-foreground/50 outline-none"
122
+ />
123
+ </div>
124
+
125
+ {headerContent}
126
+
127
+ {/* Item list */}
128
+ <div ref={listRef} className="max-h-60 overflow-y-auto py-1">
129
+ {filtered.length === 0 ? (
130
+ <p className="px-3 py-4 text-center text-xs text-muted-foreground/60">{emptyMessage}</p>
131
+ ) : (
132
+ filtered.map((item, i) => (
133
+ <button
134
+ key={item.id}
135
+ type="button"
136
+ data-index={i}
137
+ onClick={() => {
138
+ onSelect(item);
139
+ handleOpenChange(false);
140
+ }}
141
+ onMouseEnter={() => setFocusedIndex(i)}
142
+ className={`w-full text-left px-3 py-1.5 text-xs transition-colors ${
143
+ focusedIndex === i ? 'bg-muted/50' : 'hover:bg-muted/40'
144
+ }`}
145
+ >
146
+ {renderItem(item, {
147
+ isSelected: item.id === selectedId,
148
+ isFocused: focusedIndex === i,
149
+ })}
150
+ </button>
151
+ ))
152
+ )}
153
+ </div>
154
+ </PopoverContent>
155
+ </Popover>
156
+ );
157
+ }