@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.
- package/README.md +60 -0
- package/assets/diff-options.png +0 -0
- package/assets/icon-codex.png +0 -0
- package/assets/look-flat.png +0 -0
- package/assets/look-grid.png +0 -0
- package/assets/review-sections.png +0 -0
- package/assets/review-tree.png +0 -0
- package/assets/workspaces.webp +0 -0
- package/components/AISettingsTab.tsx +145 -0
- package/components/ActionMenu.tsx +101 -0
- package/components/AgentControls.tsx +216 -0
- package/components/AgentsTab.tsx +1294 -0
- package/components/AnnotationPanel.tsx +731 -0
- package/components/AnnotationSidebar.tsx +86 -0
- package/components/AnnotationToolbar.tsx +323 -0
- package/components/AnnotationToolstrip.tsx +355 -0
- package/components/ApproveDropdown.tsx +170 -0
- package/components/AttachmentsButton.tsx +410 -0
- package/components/BlockRenderer.tsx +163 -0
- package/components/BorderTrail.tsx +38 -0
- package/components/CodeFilePicker.tsx +64 -0
- package/components/CodeFilePopout.tsx +602 -0
- package/components/CodePathValidationContext.tsx +14 -0
- package/components/CommentPopover.tsx +512 -0
- package/components/CompletionOverlay.tsx +99 -0
- package/components/ConfirmDialog.tsx +128 -0
- package/components/DocBadges.tsx +231 -0
- package/components/EditorAnnotationCard.tsx +65 -0
- package/components/ExportModal.tsx +565 -0
- package/components/FloatingQuickLabelPicker.tsx +144 -0
- package/components/GitHubIcon.tsx +16 -0
- package/components/GitLabIcon.tsx +30 -0
- package/components/GraphvizBlock.tsx +511 -0
- package/components/ImageAnnotator/Canvas.tsx +118 -0
- package/components/ImageAnnotator/Toolbar.tsx +218 -0
- package/components/ImageAnnotator/index.tsx +272 -0
- package/components/ImageAnnotator/types.ts +39 -0
- package/components/ImageAnnotator/utils.ts +147 -0
- package/components/ImageThumbnail.tsx +126 -0
- package/components/ImportModal.tsx +146 -0
- package/components/InlineMarkdown.tsx +1094 -0
- package/components/KeyboardShortcuts.tsx +208 -0
- package/components/Landing.tsx +496 -0
- package/components/ListItemBody.tsx +62 -0
- package/components/ListMarker.tsx +73 -0
- package/components/LookAndFeelAnnouncementDialog.tsx +230 -0
- package/components/MarkdownEditor.tsx +44 -0
- package/components/MenuVersionSection.tsx +91 -0
- package/components/MermaidBlock.tsx +579 -0
- package/components/ModeToggle.tsx +68 -0
- package/components/OpenInAppButton.tsx +299 -0
- package/components/OverlayScrollArea.tsx +82 -0
- package/components/PermissionModeSetup.tsx +92 -0
- package/components/PinpointOverlay.tsx +99 -0
- package/components/PlanAIAnnouncementDialog.tsx +187 -0
- package/components/PlanHeaderMenu.tsx +299 -0
- package/components/PopoutDialog.tsx +91 -0
- package/components/Popover.tsx +27 -0
- package/components/ProviderIcons.tsx +51 -0
- package/components/PullRequestIcon.tsx +12 -0
- package/components/QuickLabelDropdown.tsx +63 -0
- package/components/RenderedMarkdown.tsx +54 -0
- package/components/RepoIcon.tsx +12 -0
- package/components/ResizeHandle.tsx +86 -0
- package/components/ReviewAgentsIcon.tsx +18 -0
- package/components/SearchableSelect.tsx +157 -0
- package/components/Settings.tsx +2220 -0
- package/components/SparklesIcon.tsx +45 -0
- package/components/StickyHeaderLane.tsx +260 -0
- package/components/TableOfContents.tsx +157 -0
- package/components/TaterSpritePullup.tsx +34 -0
- package/components/TaterSpriteRunning.tsx +54 -0
- package/components/TaterSpriteSitting.tsx +37 -0
- package/components/TextShimmer.tsx +57 -0
- package/components/ThemeProvider.tsx +162 -0
- package/components/ThemeTab.tsx +139 -0
- package/components/ToolbarButtons.tsx +116 -0
- package/components/Tooltip.tsx +45 -0
- package/components/Viewer.tsx +977 -0
- package/components/ai/AIProviderBar.tsx +95 -0
- package/components/ai/DocumentAIChatPanel.tsx +313 -0
- package/components/blocks/AlertBlock.tsx +58 -0
- package/components/blocks/Callout.tsx +60 -0
- package/components/blocks/CodeBlock.tsx +75 -0
- package/components/blocks/HtmlBlock.tsx +122 -0
- package/components/blocks/MathBlock.tsx +36 -0
- package/components/blocks/TableBlock.tsx +146 -0
- package/components/blocks/TablePopout.tsx +279 -0
- package/components/blocks/TableToolbar.tsx +153 -0
- package/components/blocks/proseBody.tsx +102 -0
- package/components/core/button.tsx +44 -0
- package/components/core/textarea.tsx +26 -0
- package/components/diagramLanguages.ts +14 -0
- package/components/goal-setup/GoalSetupSurface.tsx +1354 -0
- package/components/html-viewer/HtmlViewer.tsx +386 -0
- package/components/html-viewer/bridge-script.ts +505 -0
- package/components/html-viewer/index.ts +1 -0
- package/components/html-viewer/useHtmlAnnotation.ts +400 -0
- package/components/icons/AgentIcons.tsx +66 -0
- package/components/icons/AppIcon.tsx +56 -0
- package/components/icons/MessagesIcon.tsx +11 -0
- package/components/icons/ObsidianIcons.tsx +208 -0
- package/components/icons/app/android-studio.svg +369 -0
- package/components/icons/app/antigravity.svg +97 -0
- package/components/icons/app/cursor.svg +16 -0
- package/components/icons/app/file-explorer.svg +20 -0
- package/components/icons/app/finder.png +0 -0
- package/components/icons/app/ghostty.svg +13 -0
- package/components/icons/app/iterm2.svg +13 -0
- package/components/icons/app/powershell.svg +14 -0
- package/components/icons/app/sublime-text.svg +17 -0
- package/components/icons/app/terminal.png +0 -0
- package/components/icons/app/textmate.png +0 -0
- package/components/icons/app/vscode.svg +39 -0
- package/components/icons/app/warp.png +0 -0
- package/components/icons/app/xcode.png +0 -0
- package/components/icons/app/zed-dark.svg +15 -0
- package/components/icons/app/zed.svg +15 -0
- package/components/icons/themeIcons.tsx +47 -0
- package/components/mermaidSvg.ts +33 -0
- package/components/plan-diff/PlanCleanDiffView.tsx +884 -0
- package/components/plan-diff/PlanDiffBadge.tsx +48 -0
- package/components/plan-diff/PlanDiffModeSwitcher.tsx +103 -0
- package/components/plan-diff/PlanDiffViewer.tsx +214 -0
- package/components/plan-diff/PlanRawDiffView.tsx +102 -0
- package/components/plan-diff/VSCodeIcon.tsx +132 -0
- package/components/settings/HooksTab.tsx +208 -0
- package/components/sidebar/ArchiveBrowser.tsx +98 -0
- package/components/sidebar/CountBadge.tsx +12 -0
- package/components/sidebar/FileBrowser.tsx +498 -0
- package/components/sidebar/MessagesBrowser.tsx +109 -0
- package/components/sidebar/SidebarContainer.tsx +353 -0
- package/components/sidebar/SidebarTabs.tsx +155 -0
- package/components/sidebar/VersionBrowser.tsx +141 -0
- package/components/types.d.ts +7 -0
- package/components/ui/badge.tsx +44 -0
- package/components/ui/button.tsx +83 -0
- package/components/ui/card.tsx +58 -0
- package/components/ui/dialog.tsx +106 -0
- package/components/ui/dropdown-menu.tsx +240 -0
- package/components/ui/state-pill.tsx +45 -0
- package/components/ui/tabs.tsx +46 -0
- package/components/ui/textarea.tsx +25 -0
- package/config/configStore.ts +214 -0
- package/config/index.ts +4 -0
- package/config/reviewView.ts +42 -0
- package/config/settings.ts +310 -0
- package/config/useConfig.ts +20 -0
- package/configure.ts +69 -0
- package/globals.d.ts +14 -0
- package/hooks/pfm/useCodeFilePopout.ts +111 -0
- package/hooks/useAIChat.ts +532 -0
- package/hooks/useAIProviderConfig.ts +115 -0
- package/hooks/useActiveSection.ts +78 -0
- package/hooks/useAgentJobs.ts +306 -0
- package/hooks/useAgentSettings.ts +579 -0
- package/hooks/useAgents.ts +90 -0
- package/hooks/useAnnotationDraft.ts +523 -0
- package/hooks/useAnnotationHighlighter.ts +1170 -0
- package/hooks/useArchive.ts +171 -0
- package/hooks/useAutoClose.ts +104 -0
- package/hooks/useCodeAnnotationDraft.ts +194 -0
- package/hooks/useCodeFilePopout.ts +1 -0
- package/hooks/useDismissOnOutsideAndEscape.ts +50 -0
- package/hooks/useDraggable.ts +108 -0
- package/hooks/useEditorAnnotations.ts +64 -0
- package/hooks/useExternalAnnotationHighlights.ts +105 -0
- package/hooks/useExternalAnnotations.ts +268 -0
- package/hooks/useFileBrowser.ts +402 -0
- package/hooks/useInputMethodSwitch.ts +89 -0
- package/hooks/useIsMobile.ts +17 -0
- package/hooks/useLinkedDoc.ts +494 -0
- package/hooks/useOverlayViewport.ts +36 -0
- package/hooks/usePinpoint.ts +182 -0
- package/hooks/usePlanDiff.ts +176 -0
- package/hooks/usePrintMode.ts +27 -0
- package/hooks/useResizablePanel.ts +173 -0
- package/hooks/useScrollViewport.ts +38 -0
- package/hooks/useSharing.ts +441 -0
- package/hooks/useSidebar.ts +55 -0
- package/hooks/useUpdateCheck.ts +132 -0
- package/hooks/useValidatedCodePaths.ts +94 -0
- package/icons/GitUser.tsx +17 -0
- package/lib/utils.ts +11 -0
- package/package.json +117 -0
- package/plannotator.webp +0 -0
- package/print.css +427 -0
- package/shortcuts/code-review/ai.shortcuts.ts +25 -0
- package/shortcuts/code-review/allFilesDiff.shortcuts.ts +46 -0
- package/shortcuts/code-review/annotationToolbar.shortcuts.ts +30 -0
- package/shortcuts/code-review/fileTree.shortcuts.ts +35 -0
- package/shortcuts/code-review/prComments.shortcuts.ts +18 -0
- package/shortcuts/code-review/suggestionModal.shortcuts.ts +25 -0
- package/shortcuts/code-review/tourDialog.shortcuts.ts +18 -0
- package/shortcuts/core.ts +399 -0
- package/shortcuts/index.ts +21 -0
- package/shortcuts/plan-review/annotationPanel.shortcuts.ts +25 -0
- package/shortcuts/plan-review/annotationToolbar.shortcuts.ts +39 -0
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +21 -0
- package/shortcuts/plan-review/goalSetup.shortcuts.ts +10 -0
- package/shortcuts/plan-review/imageAnnotator.shortcuts.ts +42 -0
- package/shortcuts/plan-review/inputMethod.shortcuts.ts +30 -0
- package/shortcuts/plan-review/sidebar.shortcuts.ts +34 -0
- package/shortcuts/plan-review/viewer.shortcuts.ts +24 -0
- package/shortcuts/runtime.ts +259 -0
- package/sprite_package_additional/index.html +34 -0
- package/sprite_package_additional/sprite.png +0 -0
- package/sprite_package_new/index.html +34 -0
- package/sprite_package_new/sprite.png +0 -0
- package/sprite_package_pulluphang/index.html +34 -0
- package/sprite_package_pulluphang/sprite.png +0 -0
- package/styles.css +1 -0
- package/theme.css +893 -0
- package/themes/adwaita.css +62 -0
- package/themes/andromeeda.css +66 -0
- package/themes/aurora-x.css +66 -0
- package/themes/ayu-dark.css +66 -0
- package/themes/caffeine.css +113 -0
- package/themes/catppuccin.css +62 -0
- package/themes/claude-plus.css +60 -0
- package/themes/cursor-hc.css +34 -0
- package/themes/cursor-midnight.css +34 -0
- package/themes/cursor.css +62 -0
- package/themes/dark-plus.css +66 -0
- package/themes/doom-64.css +109 -0
- package/themes/dracula.css +33 -0
- package/themes/everforest-hard.css +62 -0
- package/themes/everforest-soft.css +62 -0
- package/themes/everforest.css +62 -0
- package/themes/github.css +66 -0
- package/themes/gruvbox.css +62 -0
- package/themes/houston.css +66 -0
- package/themes/kanagawa-dragon.css +34 -0
- package/themes/kanagawa-lotus.css +34 -0
- package/themes/kanagawa-wave.css +34 -0
- package/themes/laserwave.css +66 -0
- package/themes/material.css +62 -0
- package/themes/min.css +66 -0
- package/themes/monokai-pro.css +34 -0
- package/themes/neutral.css +59 -0
- package/themes/night-owl.css +66 -0
- package/themes/nord.css +66 -0
- package/themes/one-dark-pro.css +66 -0
- package/themes/one-light.css +66 -0
- package/themes/paulmillr.css +34 -0
- package/themes/plannotator.css +60 -0
- package/themes/plastic.css +66 -0
- package/themes/poimandres.css +66 -0
- package/themes/quantum-rose.css +109 -0
- package/themes/red.css +66 -0
- package/themes/rose-pine.css +62 -0
- package/themes/simple.css +124 -0
- package/themes/slack.css +66 -0
- package/themes/snazzy-light.css +66 -0
- package/themes/soft-pop.css +60 -0
- package/themes/solar-dusk.css +109 -0
- package/themes/solarized.css +66 -0
- package/themes/synthwave-84.css +34 -0
- package/themes/terminal.css +62 -0
- package/themes/tinacious.css +57 -0
- package/themes/tokyo-night.css +62 -0
- package/themes/vesper.css +62 -0
- package/themes/vitesse-black.css +66 -0
- package/themes/vitesse.css +62 -0
- package/types.ts +264 -0
- package/utils/agentSwitch.ts +71 -0
- package/utils/aiChatFormat.ts +33 -0
- package/utils/aiPrompt.ts +29 -0
- package/utils/aiProvider.ts +217 -0
- package/utils/anchors.ts +11 -0
- package/utils/annotateAgentTerminal.ts +29 -0
- package/utils/annotationHelpers.ts +101 -0
- package/utils/bear.ts +59 -0
- package/utils/blockTargeting.ts +240 -0
- package/utils/callback.ts +99 -0
- package/utils/commentContent.ts +8 -0
- package/utils/defaultNotesApp.ts +20 -0
- package/utils/diffFonts.ts +33 -0
- package/utils/editorMode.ts +31 -0
- package/utils/fileBrowser.ts +40 -0
- package/utils/generateId.ts +7 -0
- package/utils/generateIdentity.ts +29 -0
- package/utils/identity.ts +112 -0
- package/utils/inlineTransforms.ts +41 -0
- package/utils/inputMethod.ts +17 -0
- package/utils/lookAndFeelAnnouncement.ts +18 -0
- package/utils/obsidian.ts +201 -0
- package/utils/octarine.ts +53 -0
- package/utils/parser.ts +991 -0
- package/utils/permissionMode.ts +77 -0
- package/utils/planAIAnnouncement.ts +17 -0
- package/utils/planAgentInstructions.ts +137 -0
- package/utils/planDiffEngine.ts +590 -0
- package/utils/planSave.ts +49 -0
- package/utils/platform.ts +11 -0
- package/utils/quickLabels.ts +77 -0
- package/utils/reviewAgentInstructions.ts +182 -0
- package/utils/sanitizeHtml.ts +45 -0
- package/utils/sharing.ts +350 -0
- package/utils/slugify.ts +37 -0
- package/utils/storage.ts +141 -0
- package/utils/themeRegistry.ts +567 -0
- package/utils/uiPreferences.ts +34 -0
- package/utils/upload.ts +56 -0
- 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
|
+
}
|