@stigmer/react 3.1.2 → 3.1.4
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/composer/SessionComposer.d.ts +37 -5
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -5
- package/composer/SessionComposer.js.map +1 -1
- package/execution/ArtifactApplyButton.d.ts +25 -0
- package/execution/ArtifactApplyButton.d.ts.map +1 -0
- package/execution/ArtifactApplyButton.js +21 -0
- package/execution/ArtifactApplyButton.js.map +1 -0
- package/execution/ArtifactContentBody.d.ts +36 -0
- package/execution/ArtifactContentBody.d.ts.map +1 -0
- package/execution/ArtifactContentBody.js +62 -0
- package/execution/ArtifactContentBody.js.map +1 -0
- package/execution/ArtifactDocument.d.ts +48 -0
- package/execution/ArtifactDocument.d.ts.map +1 -0
- package/execution/ArtifactDocument.js +89 -0
- package/execution/ArtifactDocument.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +9 -9
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +18 -145
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactRow.d.ts +49 -0
- package/execution/ArtifactRow.d.ts.map +1 -0
- package/execution/ArtifactRow.js +56 -0
- package/execution/ArtifactRow.js.map +1 -0
- package/execution/ArtifactsWidget.d.ts +6 -9
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +15 -19
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/FileChangeProgressBar.d.ts +47 -0
- package/execution/FileChangeProgressBar.d.ts.map +1 -0
- package/execution/FileChangeProgressBar.js +49 -0
- package/execution/FileChangeProgressBar.js.map +1 -0
- package/execution/FileChangesView.d.ts.map +1 -1
- package/execution/FileChangesView.js +19 -4
- package/execution/FileChangesView.js.map +1 -1
- package/execution/FileReviewAtoms.d.ts +27 -0
- package/execution/FileReviewAtoms.d.ts.map +1 -0
- package/execution/FileReviewAtoms.js +64 -0
- package/execution/FileReviewAtoms.js.map +1 -0
- package/execution/FileReviewCard.d.ts +2 -2
- package/execution/FileReviewCard.d.ts.map +1 -1
- package/execution/FileReviewCard.js +32 -62
- package/execution/FileReviewCard.js.map +1 -1
- package/execution/MessageEntry.d.ts +0 -9
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +1 -11
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +3 -4
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +25 -11
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts +16 -10
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +31 -7
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanCompletionCard.d.ts +4 -4
- package/execution/PlanCompletionCard.d.ts.map +1 -1
- package/execution/PlanCompletionCard.js +4 -4
- package/execution/PlanCompletionCard.js.map +1 -1
- package/execution/PlanStreamingCard.d.ts +7 -2
- package/execution/PlanStreamingCard.d.ts.map +1 -1
- package/execution/PlanStreamingCard.js +11 -6
- package/execution/PlanStreamingCard.js.map +1 -1
- package/execution/index.d.ts +12 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +6 -1
- package/execution/index.js.map +1 -1
- package/execution/use-build-from-plan-hotkey.d.ts +1 -1
- package/execution/use-build-from-plan-hotkey.js +1 -1
- package/execution/useArtifactContent.d.ts +6 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +7 -2
- package/execution/useArtifactContent.js.map +1 -1
- package/execution/useArtifactCopy.d.ts +51 -0
- package/execution/useArtifactCopy.d.ts.map +1 -0
- package/execution/useArtifactCopy.js +75 -0
- package/execution/useArtifactCopy.js.map +1 -0
- package/execution/useArtifactDownload.d.ts.map +1 -1
- package/execution/useArtifactDownload.js +17 -5
- package/execution/useArtifactDownload.js.map +1 -1
- package/execution/useArtifactInspection.d.ts +85 -0
- package/execution/useArtifactInspection.d.ts.map +1 -0
- package/execution/useArtifactInspection.js +143 -0
- package/execution/useArtifactInspection.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +16 -3
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +1 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/MermaidDiagram.d.ts +47 -0
- package/internal/MermaidDiagram.d.ts.map +1 -0
- package/internal/MermaidDiagram.js +118 -0
- package/internal/MermaidDiagram.js.map +1 -0
- package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
- package/internal/file-tree/FileTreeNode.js +2 -2
- package/internal/file-tree/FileTreeNode.js.map +1 -1
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +34 -0
- package/internal/markdown-components.js.map +1 -1
- package/internal/mermaid-loader.d.ts +26 -0
- package/internal/mermaid-loader.d.ts.map +1 -0
- package/internal/mermaid-loader.js +34 -0
- package/internal/mermaid-loader.js.map +1 -0
- package/internal/mermaid-theme-css.d.ts +41 -0
- package/internal/mermaid-theme-css.d.ts.map +1 -0
- package/internal/mermaid-theme-css.js +179 -0
- package/internal/mermaid-theme-css.js.map +1 -0
- package/internal/slug.d.ts +9 -5
- package/internal/slug.d.ts.map +1 -1
- package/internal/slug.js +10 -6
- package/internal/slug.js.map +1 -1
- package/library/detect-plan-artifact.d.ts +27 -9
- package/library/detect-plan-artifact.d.ts.map +1 -1
- package/library/detect-plan-artifact.js +26 -6
- package/library/detect-plan-artifact.js.map +1 -1
- package/library/index.d.ts +1 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js +1 -1
- package/library/index.js.map +1 -1
- package/package.json +5 -4
- package/session/PlanEditor.js +1 -1
- package/session/PlanEditor.js.map +1 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +122 -30
- package/session/SessionViewer.js.map +1 -1
- package/session/artifact-document.d.ts +13 -0
- package/session/artifact-document.d.ts.map +1 -0
- package/session/artifact-document.js +22 -0
- package/session/artifact-document.js.map +1 -0
- package/session/facets/ArtifactsTab.d.ts +30 -4
- package/session/facets/ArtifactsTab.d.ts.map +1 -1
- package/session/facets/ArtifactsTab.js +32 -10
- package/session/facets/ArtifactsTab.js.map +1 -1
- package/session/index.d.ts +2 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +4 -1
- package/session/index.js.map +1 -1
- package/session/plan-document.d.ts +14 -2
- package/session/plan-document.d.ts.map +1 -1
- package/session/plan-document.js +14 -3
- package/session/plan-document.js.map +1 -1
- package/session/usePlanDraft.d.ts +1 -1
- package/session/useSessionArtifacts.d.ts +17 -0
- package/session/useSessionArtifacts.d.ts.map +1 -1
- package/session/useSessionArtifacts.js +16 -5
- package/session/useSessionArtifacts.js.map +1 -1
- package/session/useSessionConversation.d.ts +26 -3
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +51 -6
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useSessionPanel.d.ts +20 -0
- package/session/useSessionPanel.d.ts.map +1 -1
- package/session/useSessionPanel.js +13 -0
- package/session/useSessionPanel.js.map +1 -1
- package/session/useSessionRailViews.d.ts +13 -1
- package/session/useSessionRailViews.d.ts.map +1 -1
- package/session/useSessionRailViews.js +5 -3
- package/session/useSessionRailViews.js.map +1 -1
- package/src/composer/SessionComposer.tsx +76 -7
- package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
- package/src/execution/ArtifactApplyButton.tsx +79 -0
- package/src/execution/ArtifactContentBody.tsx +255 -0
- package/src/execution/ArtifactDocument.tsx +344 -0
- package/src/execution/ArtifactPreviewModal.tsx +49 -415
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/FileChangeProgressBar.tsx +121 -0
- package/src/execution/FileChangesView.tsx +18 -4
- package/src/execution/FileReviewAtoms.tsx +89 -0
- package/src/execution/FileReviewCard.tsx +57 -90
- package/src/execution/MessageEntry.tsx +0 -62
- package/src/execution/MessageThread.tsx +29 -15
- package/src/execution/PlanArtifactCard.tsx +130 -52
- package/src/execution/PlanCompletionCard.tsx +5 -5
- package/src/execution/PlanStreamingCard.tsx +15 -11
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
- package/src/execution/__tests__/MessageThread.test.tsx +1 -1
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
- package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
- package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
- package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
- package/src/execution/__tests__/message-entry.test.tsx +56 -39
- package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/index.ts +20 -2
- package/src/execution/use-build-from-plan-hotkey.ts +1 -1
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactCopy.ts +109 -0
- package/src/execution/useArtifactDownload.ts +17 -5
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +17 -3
- package/src/index.ts +17 -2
- package/src/internal/MermaidDiagram.tsx +164 -0
- package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
- package/src/internal/__tests__/markdown-components.test.tsx +93 -1
- package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
- package/src/internal/file-tree/FileTreeNode.tsx +13 -2
- package/src/internal/markdown-components.tsx +36 -1
- package/src/internal/mermaid-loader.ts +44 -0
- package/src/internal/mermaid-theme-css.ts +178 -0
- package/src/internal/slug.ts +10 -6
- package/src/library/__tests__/detect-plan-artifact.test.ts +21 -0
- package/src/library/detect-plan-artifact.ts +32 -10
- package/src/library/index.ts +2 -0
- package/src/session/PlanEditor.tsx +1 -1
- package/src/session/SessionViewer.tsx +227 -51
- package/src/session/__tests__/PlanEditor.test.tsx +4 -4
- package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
- package/src/session/__tests__/useSessionPanel.test.ts +70 -1
- package/src/session/artifact-document.ts +23 -0
- package/src/session/facets/ArtifactsTab.tsx +66 -18
- package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
- package/src/session/index.ts +5 -1
- package/src/session/plan-document.ts +14 -3
- package/src/session/usePlanDraft.ts +1 -1
- package/src/session/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +80 -10
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/src/workspace/EditorTabs.tsx +38 -9
- package/src/workspace/ExplorerTree.tsx +10 -4
- package/src/workspace/FileViewer.tsx +27 -6
- package/src/workspace/WorkspaceContentSearch.tsx +44 -20
- package/src/workspace/WorkspaceEditor.tsx +19 -16
- package/src/workspace/WorkspaceFileSearch.tsx +51 -28
- package/src/workspace/WorkspaceSurface.tsx +106 -48
- package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
- package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
- package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
- package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
- package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
- package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -0
- package/src/workspace/__tests__/a11y/harness.tsx +140 -0
- package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
- package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
- package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
- package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
- package/styles.css +1 -1
- package/workspace/EditorTabs.d.ts +21 -3
- package/workspace/EditorTabs.d.ts.map +1 -1
- package/workspace/EditorTabs.js +18 -6
- package/workspace/EditorTabs.js.map +1 -1
- package/workspace/ExplorerTree.js +8 -4
- package/workspace/ExplorerTree.js.map +1 -1
- package/workspace/FileViewer.d.ts.map +1 -1
- package/workspace/FileViewer.js +19 -8
- package/workspace/FileViewer.js.map +1 -1
- package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
- package/workspace/WorkspaceContentSearch.js +23 -2
- package/workspace/WorkspaceContentSearch.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +8 -10
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
- package/workspace/WorkspaceFileSearch.js +23 -3
- package/workspace/WorkspaceFileSearch.js.map +1 -1
- package/workspace/WorkspaceSurface.d.ts.map +1 -1
- package/workspace/WorkspaceSurface.js +54 -18
- package/workspace/WorkspaceSurface.js.map +1 -1
- package/execution/ArtifactCard.d.ts +0 -81
- package/execution/ArtifactCard.d.ts.map +0 -1
- package/execution/ArtifactCard.js +0 -130
- package/execution/ArtifactCard.js.map +0 -1
- package/src/execution/ArtifactCard.tsx +0 -320
|
@@ -4,6 +4,17 @@ import { useCallback, type KeyboardEvent, type MouseEvent } from "react";
|
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { editorKey, type OpenEditor } from "../internal/store/index.js";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Deterministic, space-free DOM id for a tab, built from an instance-scoped
|
|
9
|
+
* `prefix` (a `useId()` value) and the editor `key`. Shared by the tab (its
|
|
10
|
+
* `id`) and the editor body (its `aria-labelledby`) so the two associate; the
|
|
11
|
+
* key is percent-encoded because editor keys embed file paths that may contain
|
|
12
|
+
* characters (spaces) illegal in a space-separated IDREF.
|
|
13
|
+
*/
|
|
14
|
+
export function editorTabDomId(prefix: string, key: string): string {
|
|
15
|
+
return `${prefix}${encodeURIComponent(key)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
/** Props for {@link EditorTabs}. */
|
|
8
19
|
export interface EditorTabsProps {
|
|
9
20
|
/** Open editors in tab order. */
|
|
@@ -16,6 +27,14 @@ export interface EditorTabsProps {
|
|
|
16
27
|
readonly onPin: (entryId: string, path: string) => void;
|
|
17
28
|
/** Close an editor (close button or middle click). */
|
|
18
29
|
readonly onClose: (entryId: string, path: string) => void;
|
|
30
|
+
/**
|
|
31
|
+
* DOM id of the editor body (`role="tabpanel"`). When provided together with
|
|
32
|
+
* {@link tabIdPrefix}, each tab gets a stable `id` and `aria-controls` linking
|
|
33
|
+
* it to that panel. Optional so the strip degrades gracefully without a panel.
|
|
34
|
+
*/
|
|
35
|
+
readonly panelId?: string;
|
|
36
|
+
/** Instance-scoped prefix for per-tab ids (see {@link editorTabDomId}). */
|
|
37
|
+
readonly tabIdPrefix?: string;
|
|
19
38
|
/** Additional CSS classes for the root element. */
|
|
20
39
|
readonly className?: string;
|
|
21
40
|
}
|
|
@@ -28,8 +47,10 @@ export interface EditorTabsProps {
|
|
|
28
47
|
*
|
|
29
48
|
* A `role="tablist"` of `role="tab"` items; the matching `tabpanel` is the
|
|
30
49
|
* editor body rendered by the surface. Arrow keys move focus between tabs and
|
|
31
|
-
* activate; the
|
|
32
|
-
*
|
|
50
|
+
* activate; Delete/Backspace closes the focused tab. The visual close "X" is a
|
|
51
|
+
* presentational (`aria-hidden`) mouse affordance — assistive tech closes via
|
|
52
|
+
* the keyboard, so the X is not a nested interactive control (WCAG 4.1.2). All
|
|
53
|
+
* visual properties flow through `--stgm-*` tokens (DD-005).
|
|
33
54
|
*/
|
|
34
55
|
export function EditorTabs({
|
|
35
56
|
editors,
|
|
@@ -37,6 +58,8 @@ export function EditorTabs({
|
|
|
37
58
|
onActivate,
|
|
38
59
|
onPin,
|
|
39
60
|
onClose,
|
|
61
|
+
panelId,
|
|
62
|
+
tabIdPrefix,
|
|
40
63
|
className,
|
|
41
64
|
}: EditorTabsProps) {
|
|
42
65
|
const move = useCallback(
|
|
@@ -94,7 +117,9 @@ export function EditorTabs({
|
|
|
94
117
|
<div
|
|
95
118
|
key={key}
|
|
96
119
|
role="tab"
|
|
120
|
+
id={tabIdPrefix ? editorTabDomId(tabIdPrefix, key) : undefined}
|
|
97
121
|
aria-selected={isActive}
|
|
122
|
+
aria-controls={panelId}
|
|
98
123
|
tabIndex={isActive ? 0 : -1}
|
|
99
124
|
title={editor.path}
|
|
100
125
|
onClick={() => onActivate(editor.entryId, editor.path)}
|
|
@@ -112,22 +137,26 @@ export function EditorTabs({
|
|
|
112
137
|
<span className={cn("truncate", editor.preview && "italic")}>
|
|
113
138
|
{basename}
|
|
114
139
|
</span>
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
{/* The visual close "X" is a presentational mouse affordance, NOT a
|
|
141
|
+
nested interactive control: a `<button>` inside `role="tab"` would
|
|
142
|
+
violate WCAG 4.1.2 (axe `nested-interactive`). Assistive tech and
|
|
143
|
+
keyboard users close the focused tab via Delete/Backspace (handled
|
|
144
|
+
above), so `aria-hidden` here removes the redundant nested control
|
|
145
|
+
without losing the close action for anyone. */}
|
|
146
|
+
<span
|
|
147
|
+
aria-hidden="true"
|
|
148
|
+
title={`Close ${basename}`}
|
|
117
149
|
onClick={(e) => {
|
|
118
150
|
e.stopPropagation();
|
|
119
151
|
onClose(editor.entryId, editor.path);
|
|
120
152
|
}}
|
|
121
|
-
aria-label={`Close ${basename}`}
|
|
122
|
-
tabIndex={-1}
|
|
123
153
|
className={cn(
|
|
124
|
-
"shrink-0 rounded p-0.5 text-muted-foreground transition-opacity hover:bg-accent-hover hover:text-foreground",
|
|
125
|
-
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
154
|
+
"shrink-0 cursor-pointer rounded p-0.5 text-muted-foreground transition-opacity hover:bg-accent-hover hover:text-foreground",
|
|
126
155
|
isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100",
|
|
127
156
|
)}
|
|
128
157
|
>
|
|
129
158
|
<CloseIcon />
|
|
130
|
-
</
|
|
159
|
+
</span>
|
|
131
160
|
</div>
|
|
132
161
|
);
|
|
133
162
|
})}
|
|
@@ -101,7 +101,10 @@ function ExplorerRoot({
|
|
|
101
101
|
)}
|
|
102
102
|
title={entry.type === "local" ? entry.localPath : entry.gitUrl}
|
|
103
103
|
>
|
|
104
|
-
<span
|
|
104
|
+
<span
|
|
105
|
+
aria-hidden="true"
|
|
106
|
+
className="text-[10px] text-muted-foreground-subtle"
|
|
107
|
+
>
|
|
105
108
|
{expanded ? "▼" : "▶"}
|
|
106
109
|
</span>
|
|
107
110
|
<span className="truncate">{entry.name}</span>
|
|
@@ -167,6 +170,10 @@ function ExplorerRootBody({
|
|
|
167
170
|
if (isLoading) {
|
|
168
171
|
return (
|
|
169
172
|
<div
|
|
173
|
+
// role="status" both announces the loading state to assistive tech and
|
|
174
|
+
// makes aria-label permitted here (a role-less generic div cannot carry
|
|
175
|
+
// an accessible name — axe `aria-prohibited-attr`).
|
|
176
|
+
role="status"
|
|
170
177
|
className="space-y-1.5 px-2 py-2"
|
|
171
178
|
aria-busy="true"
|
|
172
179
|
aria-label={`Loading files for ${entryName}`}
|
|
@@ -190,7 +197,7 @@ function ExplorerRootBody({
|
|
|
190
197
|
<button
|
|
191
198
|
type="button"
|
|
192
199
|
onClick={onRetry}
|
|
193
|
-
className="ml-2 underline transition-colors hover:text-destructive"
|
|
200
|
+
className="ml-2 rounded-sm underline transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
194
201
|
>
|
|
195
202
|
Retry
|
|
196
203
|
</button>
|
|
@@ -226,8 +233,7 @@ function ExplorerRootBody({
|
|
|
226
233
|
</nav>
|
|
227
234
|
{truncated && (
|
|
228
235
|
<p className="px-3 py-1.5 text-[0.65rem] text-muted-foreground">
|
|
229
|
-
Showing a partial listing —
|
|
230
|
-
in full.
|
|
236
|
+
Showing a partial listing — too many files to load in full.
|
|
231
237
|
</p>
|
|
232
238
|
)}
|
|
233
239
|
</>
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useCallback,
|
|
6
6
|
useEffect,
|
|
7
7
|
useImperativeHandle,
|
|
8
|
+
useRef,
|
|
8
9
|
useState,
|
|
9
10
|
type KeyboardEvent,
|
|
10
11
|
} from "react";
|
|
@@ -194,7 +195,10 @@ export const FileViewer = forwardRef<FileViewerHandle, FileViewerProps>(
|
|
|
194
195
|
<div
|
|
195
196
|
role="region"
|
|
196
197
|
aria-label="File viewer"
|
|
197
|
-
|
|
198
|
+
// Focusable (programmatically / on click) so the Escape-to-close handler
|
|
199
|
+
// fires even when the read-only body holds no natively focusable element.
|
|
200
|
+
tabIndex={-1}
|
|
201
|
+
className={cn("flex h-full flex-col focus:outline-none", className)}
|
|
198
202
|
onKeyDown={handleKeyDown}
|
|
199
203
|
>
|
|
200
204
|
{showHeader && (
|
|
@@ -260,17 +264,26 @@ function ViewerModeToggle({
|
|
|
260
264
|
{ value: "file", label: "File" },
|
|
261
265
|
];
|
|
262
266
|
|
|
267
|
+
// Selection follows focus, matching ActivityRail / SearchModeToggle.
|
|
268
|
+
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
269
|
+
const selectValue = useCallback(
|
|
270
|
+
(next: "diff" | "file") => {
|
|
271
|
+
onChange(next);
|
|
272
|
+
buttonRefs.current[next === "diff" ? 0 : 1]?.focus();
|
|
273
|
+
},
|
|
274
|
+
[onChange],
|
|
275
|
+
);
|
|
263
276
|
const handleKeyDown = useCallback(
|
|
264
277
|
(e: KeyboardEvent<HTMLButtonElement>) => {
|
|
265
278
|
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
266
279
|
e.preventDefault();
|
|
267
|
-
|
|
280
|
+
selectValue("file");
|
|
268
281
|
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
269
282
|
e.preventDefault();
|
|
270
|
-
|
|
283
|
+
selectValue("diff");
|
|
271
284
|
}
|
|
272
285
|
},
|
|
273
|
-
[
|
|
286
|
+
[selectValue],
|
|
274
287
|
);
|
|
275
288
|
|
|
276
289
|
return (
|
|
@@ -280,11 +293,14 @@ function ViewerModeToggle({
|
|
|
280
293
|
aria-label="File view"
|
|
281
294
|
className="inline-flex rounded-md bg-muted p-0.5"
|
|
282
295
|
>
|
|
283
|
-
{options.map((option) => {
|
|
296
|
+
{options.map((option, index) => {
|
|
284
297
|
const isSelected = value === option.value;
|
|
285
298
|
return (
|
|
286
299
|
<button
|
|
287
300
|
key={option.value}
|
|
301
|
+
ref={(el) => {
|
|
302
|
+
buttonRefs.current[index] = el;
|
|
303
|
+
}}
|
|
288
304
|
type="button"
|
|
289
305
|
role="radio"
|
|
290
306
|
aria-checked={isSelected}
|
|
@@ -340,7 +356,12 @@ function FileViewerBody({
|
|
|
340
356
|
|
|
341
357
|
if (isLoading) {
|
|
342
358
|
return (
|
|
343
|
-
<div
|
|
359
|
+
<div
|
|
360
|
+
role="status"
|
|
361
|
+
className="space-y-2 p-4"
|
|
362
|
+
aria-busy="true"
|
|
363
|
+
aria-label="Loading file"
|
|
364
|
+
>
|
|
344
365
|
{SKELETON_LINE_WIDTHS.map((width, i) => (
|
|
345
366
|
<div
|
|
346
367
|
key={i}
|
|
@@ -161,8 +161,28 @@ export function WorkspaceContentSearch({
|
|
|
161
161
|
const belowMinLength = trimmed.length > 0 && trimmed.length < MIN_QUERY_LENGTH;
|
|
162
162
|
const showEntryHeaders = groups.length > 1;
|
|
163
163
|
|
|
164
|
+
// Screen-reader-only summary in a persistent polite live region: status swaps
|
|
165
|
+
// (searching → results/none) happen across separate unmounting branches, so a
|
|
166
|
+
// stable region is what actually gets announced. Terse by design.
|
|
167
|
+
const statusMessage = isUnsupported
|
|
168
|
+
? ""
|
|
169
|
+
: trimmed.length === 0
|
|
170
|
+
? ""
|
|
171
|
+
: belowMinLength
|
|
172
|
+
? `Type at least ${MIN_QUERY_LENGTH} characters to search.`
|
|
173
|
+
: isLoading
|
|
174
|
+
? "Searching file contents…"
|
|
175
|
+
: groups.length === 0
|
|
176
|
+
? `No files containing ${trimmed}.`
|
|
177
|
+
: totalMatches > flatResults.length
|
|
178
|
+
? `Showing the first ${flatResults.length} of ${totalMatches} matches.`
|
|
179
|
+
: `${totalMatches} ${totalMatches === 1 ? "match" : "matches"}.`;
|
|
180
|
+
|
|
164
181
|
return (
|
|
165
182
|
<div className={cn("flex h-full flex-col", className)}>
|
|
183
|
+
<div role="status" aria-live="polite" className="sr-only">
|
|
184
|
+
{statusMessage}
|
|
185
|
+
</div>
|
|
166
186
|
<div className="flex items-center gap-1.5 border-b border-border px-2 py-1.5">
|
|
167
187
|
<SearchIcon />
|
|
168
188
|
<input
|
|
@@ -336,27 +356,31 @@ function LineRow({
|
|
|
336
356
|
readonly isOpen: boolean;
|
|
337
357
|
readonly onOpen: () => void;
|
|
338
358
|
}) {
|
|
359
|
+
// The `role="option"` IS the interactive leaf of the listbox (keyboard
|
|
360
|
+
// activation flows through the combobox input's Enter handler +
|
|
361
|
+
// aria-activedescendant). It must not nest another interactive control — a
|
|
362
|
+
// button inside would violate WCAG 4.1.2 (axe `nested-interactive`). So the
|
|
363
|
+
// click handler and row styling live on the option itself.
|
|
339
364
|
return (
|
|
340
|
-
<li
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
</button>
|
|
365
|
+
<li
|
|
366
|
+
id={id}
|
|
367
|
+
role="option"
|
|
368
|
+
aria-selected={isFocused}
|
|
369
|
+
aria-current={isOpen ? "true" : undefined}
|
|
370
|
+
onClick={onOpen}
|
|
371
|
+
className={cn(
|
|
372
|
+
"flex cursor-pointer items-baseline gap-2 py-0.5 pl-6 pr-3 text-xs transition-colors",
|
|
373
|
+
isFocused && "bg-muted",
|
|
374
|
+
isOpen && "bg-muted",
|
|
375
|
+
!isFocused && !isOpen && "hover:bg-muted",
|
|
376
|
+
)}
|
|
377
|
+
>
|
|
378
|
+
<span className="w-8 shrink-0 text-right font-mono text-[0.65rem] tabular-nums text-muted-foreground-subtle">
|
|
379
|
+
{match.line}
|
|
380
|
+
</span>
|
|
381
|
+
<span className="min-w-0 flex-1 truncate font-mono text-muted-foreground">
|
|
382
|
+
<HighlightedPreview text={match.preview} query={query} />
|
|
383
|
+
</span>
|
|
360
384
|
</li>
|
|
361
385
|
);
|
|
362
386
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback, useEffect, type KeyboardEvent } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
4
5
|
import type { UseWorkspaceEntriesReturn } from "./useWorkspaceEntries.js";
|
|
5
6
|
import type { UseGitHubConnectionReturn } from "../github/useGitHubConnection.js";
|
|
6
7
|
import { GitHubRepoPicker } from "../github/GitHubRepoPicker.js";
|
|
@@ -136,11 +137,11 @@ export function WorkspaceEditor({
|
|
|
136
137
|
|
|
137
138
|
if (activePanel === "github" && enableGitHub) {
|
|
138
139
|
return (
|
|
139
|
-
<div className={
|
|
140
|
+
<div className={cn("space-y-2", className)}>
|
|
140
141
|
<button
|
|
141
142
|
type="button"
|
|
142
143
|
onClick={goBack}
|
|
143
|
-
className="inline-flex items-center gap-1 text-[0.65rem] text-muted-foreground hover:text-foreground
|
|
144
|
+
className="inline-flex items-center gap-1 rounded-sm text-[0.65rem] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
144
145
|
>
|
|
145
146
|
<ChevronLeftIcon />
|
|
146
147
|
Back
|
|
@@ -180,7 +181,7 @@ export function WorkspaceEditor({
|
|
|
180
181
|
// ---------------------------------------------------------------------------
|
|
181
182
|
|
|
182
183
|
return (
|
|
183
|
-
<div className={
|
|
184
|
+
<div className={cn("space-y-2", className)}>
|
|
184
185
|
{/* Entry list */}
|
|
185
186
|
{workspace.entries.length > 0 && (
|
|
186
187
|
<div className="relative">
|
|
@@ -202,10 +203,10 @@ export function WorkspaceEditor({
|
|
|
202
203
|
</span>
|
|
203
204
|
)}
|
|
204
205
|
<span
|
|
205
|
-
className={
|
|
206
|
+
className={cn(
|
|
206
207
|
"min-w-0 flex-1 truncate text-foreground",
|
|
207
|
-
entry.type === "local"
|
|
208
|
-
|
|
208
|
+
entry.type === "local" && "[direction:rtl] text-left",
|
|
209
|
+
)}
|
|
209
210
|
title={entry.name}
|
|
210
211
|
>
|
|
211
212
|
<bdi>{entry.name}</bdi>
|
|
@@ -214,7 +215,7 @@ export function WorkspaceEditor({
|
|
|
214
215
|
type="button"
|
|
215
216
|
onClick={() => workspace.remove(entry.id)}
|
|
216
217
|
disabled={disabled}
|
|
217
|
-
className="shrink-0 text-muted-foreground hover:text-destructive disabled:pointer-events-none"
|
|
218
|
+
className="shrink-0 rounded-sm text-muted-foreground transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none"
|
|
218
219
|
aria-label={`Remove ${entry.name}`}
|
|
219
220
|
>
|
|
220
221
|
<XIcon />
|
|
@@ -237,7 +238,7 @@ export function WorkspaceEditor({
|
|
|
237
238
|
if (path) workspace.addLocalPath(path);
|
|
238
239
|
}}
|
|
239
240
|
disabled={disabled}
|
|
240
|
-
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover disabled:pointer-events-none disabled:opacity-40"
|
|
241
|
+
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40"
|
|
241
242
|
>
|
|
242
243
|
<FolderIcon />
|
|
243
244
|
<span className="flex-1 text-left">Browse Folder</span>
|
|
@@ -248,7 +249,7 @@ export function WorkspaceEditor({
|
|
|
248
249
|
type="button"
|
|
249
250
|
onClick={() => setActivePanel("github")}
|
|
250
251
|
disabled={disabled}
|
|
251
|
-
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover disabled:pointer-events-none disabled:opacity-40"
|
|
252
|
+
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40"
|
|
252
253
|
>
|
|
253
254
|
<GitHubIcon />
|
|
254
255
|
<span className="flex-1 text-left">Connect GitHub</span>
|
|
@@ -307,14 +308,14 @@ function GitHubPanel({
|
|
|
307
308
|
<button
|
|
308
309
|
type="button"
|
|
309
310
|
onClick={() => connection.connect(redirectUri, { popup: true })}
|
|
310
|
-
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground hover:text-foreground
|
|
311
|
+
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
311
312
|
>
|
|
312
313
|
Try again
|
|
313
314
|
</button>
|
|
314
315
|
<button
|
|
315
316
|
type="button"
|
|
316
317
|
onClick={() => connection.connect(redirectUri)}
|
|
317
|
-
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background hover:bg-foreground-hover
|
|
318
|
+
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background transition-colors hover:bg-foreground-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
318
319
|
>
|
|
319
320
|
<GitHubIcon />
|
|
320
321
|
<span>Continue with redirect</span>
|
|
@@ -325,7 +326,7 @@ function GitHubPanel({
|
|
|
325
326
|
<button
|
|
326
327
|
type="button"
|
|
327
328
|
onClick={() => connection.connect(redirectUri, { popup: true })}
|
|
328
|
-
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background hover:bg-foreground-hover
|
|
329
|
+
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background transition-colors hover:bg-foreground-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
329
330
|
>
|
|
330
331
|
<GitHubIcon />
|
|
331
332
|
<span>Connect GitHub</span>
|
|
@@ -353,7 +354,7 @@ function GitHubPanel({
|
|
|
353
354
|
<button
|
|
354
355
|
type="button"
|
|
355
356
|
onClick={connection.disconnect}
|
|
356
|
-
className="text-[0.6rem] text-muted-foreground hover:text-destructive
|
|
357
|
+
className="rounded-sm text-[0.6rem] text-muted-foreground transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
357
358
|
>
|
|
358
359
|
Disconnect
|
|
359
360
|
</button>
|
|
@@ -392,6 +393,7 @@ function ManualGitPanel({
|
|
|
392
393
|
<div className="space-y-2">
|
|
393
394
|
<input
|
|
394
395
|
type="url"
|
|
396
|
+
aria-label="Git repository URL"
|
|
395
397
|
placeholder="https://github.com/org/repo.git"
|
|
396
398
|
value={url}
|
|
397
399
|
onChange={(e) => onUrlChange(e.target.value)}
|
|
@@ -401,6 +403,7 @@ function ManualGitPanel({
|
|
|
401
403
|
/>
|
|
402
404
|
<input
|
|
403
405
|
type="text"
|
|
406
|
+
aria-label="Branch (optional)"
|
|
404
407
|
placeholder="Branch (optional)"
|
|
405
408
|
value={branch}
|
|
406
409
|
onChange={(e) => onBranchChange(e.target.value)}
|
|
@@ -411,7 +414,7 @@ function ManualGitPanel({
|
|
|
411
414
|
<button
|
|
412
415
|
type="button"
|
|
413
416
|
onClick={onCancel}
|
|
414
|
-
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground hover:text-foreground
|
|
417
|
+
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
415
418
|
>
|
|
416
419
|
Cancel
|
|
417
420
|
</button>
|
|
@@ -419,7 +422,7 @@ function ManualGitPanel({
|
|
|
419
422
|
type="button"
|
|
420
423
|
onClick={onAdd}
|
|
421
424
|
disabled={!url.trim()}
|
|
422
|
-
className="rounded-md bg-primary px-2.5 py-1 text-xs text-primary-foreground hover:bg-primary-hover
|
|
425
|
+
className="rounded-md bg-primary px-2.5 py-1 text-xs text-primary-foreground transition-colors hover:bg-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40"
|
|
423
426
|
>
|
|
424
427
|
Add
|
|
425
428
|
</button>
|
|
@@ -453,7 +456,7 @@ function GitHubConnectingState({ onCancel }: { onCancel: () => void }) {
|
|
|
453
456
|
<button
|
|
454
457
|
type="button"
|
|
455
458
|
onClick={onCancel}
|
|
456
|
-
className="text-[0.65rem] text-muted-foreground hover:text-foreground
|
|
459
|
+
className="rounded-sm text-[0.65rem] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
457
460
|
>
|
|
458
461
|
Cancel
|
|
459
462
|
</button>
|
|
@@ -132,8 +132,27 @@ export function WorkspaceFileSearch({
|
|
|
132
132
|
const trimmed = query.trim();
|
|
133
133
|
const showGroupHeaders = groups.length > 1;
|
|
134
134
|
|
|
135
|
+
// A concise, screen-reader-only summary of the current result state. It lives
|
|
136
|
+
// in a persistent polite live region (below) so status changes are announced
|
|
137
|
+
// even as the visible states — which live in separate, unmounting branches —
|
|
138
|
+
// swap. Deliberately terse: never the result list itself, only its shape.
|
|
139
|
+
const statusMessage = isUnsupported
|
|
140
|
+
? ""
|
|
141
|
+
: trimmed.length === 0
|
|
142
|
+
? ""
|
|
143
|
+
: isLoading && totalMatches === 0 && groups.length === 0
|
|
144
|
+
? "Searching files…"
|
|
145
|
+
: groups.length === 0
|
|
146
|
+
? `No files matching ${trimmed}.`
|
|
147
|
+
: totalMatches > flatResults.length
|
|
148
|
+
? `Showing the first ${flatResults.length} of ${totalMatches} matching files.`
|
|
149
|
+
: `${flatResults.length} matching ${flatResults.length === 1 ? "file" : "files"}.`;
|
|
150
|
+
|
|
135
151
|
return (
|
|
136
152
|
<div className={cn("flex h-full flex-col", className)}>
|
|
153
|
+
<div role="status" aria-live="polite" className="sr-only">
|
|
154
|
+
{statusMessage}
|
|
155
|
+
</div>
|
|
137
156
|
<div className="flex items-center gap-1.5 border-b border-border px-2 py-1.5">
|
|
138
157
|
<SearchIcon />
|
|
139
158
|
<input
|
|
@@ -264,7 +283,7 @@ function renderRows({
|
|
|
264
283
|
if (group.truncated) {
|
|
265
284
|
rows.push(
|
|
266
285
|
<GroupNotice key={`t:${group.entry.id}`}>
|
|
267
|
-
Partial listing —
|
|
286
|
+
Partial listing — too many files to search in full.
|
|
268
287
|
</GroupNotice>,
|
|
269
288
|
);
|
|
270
289
|
}
|
|
@@ -299,39 +318,43 @@ function ResultRow({
|
|
|
299
318
|
const basename = match.path.slice(lastSlash + 1);
|
|
300
319
|
const dir = lastSlash >= 0 ? match.path.slice(0, lastSlash) : "";
|
|
301
320
|
|
|
321
|
+
// The `role="option"` IS the interactive leaf of the listbox (keyboard
|
|
322
|
+
// activation flows through the combobox input's Enter handler +
|
|
323
|
+
// aria-activedescendant). It must not nest another interactive control — a
|
|
324
|
+
// button inside would violate WCAG 4.1.2 (axe `nested-interactive`). So the
|
|
325
|
+
// click handler and row styling live on the option itself.
|
|
302
326
|
return (
|
|
303
|
-
<li
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
327
|
+
<li
|
|
328
|
+
id={id}
|
|
329
|
+
role="option"
|
|
330
|
+
aria-selected={isFocused}
|
|
331
|
+
aria-current={isOpen ? "true" : undefined}
|
|
332
|
+
onClick={onOpen}
|
|
333
|
+
className={cn(
|
|
334
|
+
"flex cursor-pointer items-baseline gap-1.5 px-3 py-1 text-xs transition-colors",
|
|
335
|
+
isFocused && "bg-muted",
|
|
336
|
+
isOpen && "bg-muted font-medium text-foreground",
|
|
337
|
+
!isFocused && !isOpen && "hover:bg-muted",
|
|
338
|
+
)}
|
|
339
|
+
>
|
|
340
|
+
<span className="truncate text-foreground">
|
|
341
|
+
<HighlightedPath
|
|
342
|
+
text={basename}
|
|
343
|
+
offset={lastSlash + 1}
|
|
344
|
+
matchStart={match.matchStart}
|
|
345
|
+
matchEnd={match.matchEnd}
|
|
346
|
+
/>
|
|
347
|
+
</span>
|
|
348
|
+
{dir && (
|
|
349
|
+
<span className="truncate text-[0.65rem] text-muted-foreground">
|
|
317
350
|
<HighlightedPath
|
|
318
|
-
text={
|
|
319
|
-
offset={
|
|
351
|
+
text={dir}
|
|
352
|
+
offset={0}
|
|
320
353
|
matchStart={match.matchStart}
|
|
321
354
|
matchEnd={match.matchEnd}
|
|
322
355
|
/>
|
|
323
356
|
</span>
|
|
324
|
-
|
|
325
|
-
<span className="truncate text-[0.65rem] text-muted-foreground">
|
|
326
|
-
<HighlightedPath
|
|
327
|
-
text={dir}
|
|
328
|
-
offset={0}
|
|
329
|
-
matchStart={match.matchStart}
|
|
330
|
-
matchEnd={match.matchEnd}
|
|
331
|
-
/>
|
|
332
|
-
</span>
|
|
333
|
-
)}
|
|
334
|
-
</button>
|
|
357
|
+
)}
|
|
335
358
|
</li>
|
|
336
359
|
);
|
|
337
360
|
}
|