@toolr/ui-design 0.1.7 → 0.1.9
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/ai-manifest.json +35 -20
- package/components/composites/dashboard-list-item.tsx +172 -0
- package/components/composites/dashboard-panel.tsx +218 -0
- package/components/content/info-panel-primitives.tsx +9 -8
- package/components/diagrams/diagram-utils.tsx +2 -1
- package/components/hooks/use-dropdown-portal.ts +39 -0
- package/components/hooks/use-modal-behavior.ts +32 -3
- package/components/lib/accent-context.ts +10 -0
- package/components/lib/{ai-tools.tsx → coding-agents.tsx} +23 -8
- package/components/lib/custom-icons.tsx +37 -0
- package/components/lib/git-providers.tsx +39 -0
- package/components/lib/theme-engine.ts +59 -10
- package/components/lib/toolr-brand.tsx +23 -9
- package/components/sections/captured-issues/captured-issues-panel.tsx +17 -8
- package/components/sections/{ai-tools-paths/tools-paths-panel.tsx → coding-agent-paths/agent-paths-panel.tsx} +70 -62
- package/components/sections/coding-agent-paths/index.ts +37 -0
- package/components/sections/{ai-tools-paths → coding-agent-paths}/types.ts +28 -28
- package/components/sections/coding-agent-paths/use-agent-paths.ts +159 -0
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +11 -10
- package/components/sections/golden-snapshots/golden-sync-panel.tsx +12 -3
- package/components/sections/golden-snapshots/snapshot-manager.tsx +9 -7
- package/components/sections/golden-snapshots/status-overview.tsx +8 -8
- package/components/sections/golden-snapshots/version-manager.tsx +6 -6
- package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +3 -3
- package/components/sections/prompt-editor/index.ts +1 -1
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +13 -5
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +18 -10
- package/components/sections/prompt-editor/types.ts +2 -2
- package/components/sections/report-bug/report-bug-form.tsx +12 -4
- package/components/sections/report-bug/screenshot-uploader.tsx +11 -3
- package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +12 -4
- package/components/sections/snapshot-browser/snapshot-tree.tsx +5 -4
- package/components/sections/snapshot-browser/types.ts +1 -1
- package/components/sections/snippets-editor/snippets-editor.tsx +16 -9
- package/components/settings/SettingsHeader.tsx +2 -2
- package/components/settings/SettingsPanel.tsx +11 -3
- package/components/settings/SettingsTreeNav.tsx +15 -9
- package/components/ui/action-dialog.tsx +37 -35
- package/components/ui/ai-action-button.tsx +12 -11
- package/components/ui/ai-execution-action-buttons.tsx +13 -5
- package/components/ui/badge.tsx +17 -6
- package/components/ui/bottom-panel-header.tsx +9 -5
- package/components/ui/breadcrumb.tsx +14 -6
- package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +14 -6
- package/components/ui/checkbox.tsx +23 -14
- package/components/ui/collapsible-section.tsx +38 -28
- package/components/ui/confirm-badge.tsx +17 -6
- package/components/ui/cookie-consent.tsx +13 -7
- package/components/ui/detail-section.tsx +24 -16
- package/components/ui/detail-view-wrapper.tsx +30 -22
- package/components/ui/editor-placeholder-card.tsx +28 -24
- package/components/ui/editor-toolbar.tsx +7 -4
- package/components/ui/execution-details-panel.tsx +10 -5
- package/components/ui/file-structure-section.tsx +3 -3
- package/components/ui/file-tree.tsx +7 -5
- package/components/ui/files-panel.tsx +147 -27
- package/components/ui/filter-dropdown.tsx +88 -75
- package/components/ui/form-actions.tsx +21 -11
- package/components/ui/frontmatter-form-header.tsx +10 -2
- package/components/ui/icon-button.tsx +27 -14
- package/components/ui/input.tsx +15 -7
- package/components/ui/label.tsx +9 -5
- package/components/ui/layout-tab-bar.tsx +11 -9
- package/components/ui/modal.tsx +26 -8
- package/components/ui/nav-card.tsx +7 -4
- package/components/ui/navigation-bar.tsx +40 -12
- package/components/ui/number-input.tsx +14 -4
- package/components/ui/project-explorer.tsx +666 -0
- package/components/ui/registry-browser.tsx +12 -1
- package/components/ui/registry-card.tsx +49 -42
- package/components/ui/registry-detail.tsx +34 -11
- package/components/ui/resizable-textarea.tsx +18 -11
- package/components/ui/scope-badge.tsx +18 -11
- package/components/ui/segmented-toggle.tsx +7 -2
- package/components/ui/select.tsx +17 -11
- package/components/ui/selection-grid.tsx +40 -37
- package/components/ui/setting-row.tsx +6 -4
- package/components/ui/settings-card.tsx +12 -5
- package/components/ui/settings-info-box.tsx +9 -6
- package/components/ui/settings-section-title.tsx +14 -2
- package/components/ui/snapshot-card.tsx +10 -2
- package/components/ui/snippets-panel.tsx +4 -2
- package/components/ui/sort-dropdown.tsx +45 -32
- package/components/ui/status-card.tsx +9 -1
- package/components/ui/tab-bar.tsx +26 -13
- package/components/ui/toggle.tsx +31 -17
- package/components/ui/tooltip.tsx +14 -6
- package/dist/content.js +8 -8
- package/dist/diagrams.d.ts +0 -1
- package/dist/index.d.ts +431 -186
- package/dist/index.js +3119 -1724
- package/dist/tokens/primitives.css +28 -6
- package/dist/tokens/semantic.css +15 -15
- package/dist/tokens/theme.css +23 -0
- package/index.ts +25 -11
- package/package.json +9 -1
- package/tokens/primitives.css +28 -6
- package/tokens/semantic.css +15 -15
- package/tokens/theme.css +23 -0
- package/components/sections/ai-tools-paths/index.ts +0 -37
- package/components/sections/ai-tools-paths/use-tools-paths.ts +0 -159
|
@@ -28,6 +28,8 @@ import { Trash2, HelpCircle } from 'lucide-react'
|
|
|
28
28
|
import { cn } from '../../lib/cn.ts'
|
|
29
29
|
import { Select } from '../../ui/select.tsx'
|
|
30
30
|
import { IconButton } from '../../ui/icon-button.tsx'
|
|
31
|
+
import { useAccentColor, AccentColorProvider } from '../../lib/accent-context.ts'
|
|
32
|
+
import type { FormColor } from '../../lib/form-colors.ts'
|
|
31
33
|
import { SnapshotTree } from './snapshot-tree.tsx'
|
|
32
34
|
import { useSnapshotBrowser } from './use-snapshot-browser.ts'
|
|
33
35
|
import type { SnapshotScope, SnapshotBrowserApi } from './types.ts'
|
|
@@ -43,6 +45,7 @@ export interface SnapshotBrowserPanelProps {
|
|
|
43
45
|
snapshotLimit: number
|
|
44
46
|
onSnapshotLimitChange: (limit: number) => void
|
|
45
47
|
onClearAll?: () => void
|
|
48
|
+
accentColor?: FormColor
|
|
46
49
|
className?: string
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -52,8 +55,11 @@ export function SnapshotBrowserPanel({
|
|
|
52
55
|
snapshotLimit,
|
|
53
56
|
onSnapshotLimitChange,
|
|
54
57
|
onClearAll,
|
|
58
|
+
accentColor,
|
|
55
59
|
className,
|
|
56
60
|
}: SnapshotBrowserPanelProps) {
|
|
61
|
+
const contextAccent = useAccentColor()
|
|
62
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
57
63
|
const {
|
|
58
64
|
searchQuery,
|
|
59
65
|
setSearchQuery,
|
|
@@ -69,9 +75,10 @@ export function SnapshotBrowserPanel({
|
|
|
69
75
|
} = useSnapshotBrowser({ scopes, api })
|
|
70
76
|
|
|
71
77
|
return (
|
|
78
|
+
<AccentColorProvider value={effectiveColor}>
|
|
72
79
|
<div className={cn('space-y-6', className)}>
|
|
73
80
|
{/* Snapshot Limit */}
|
|
74
|
-
<div className="bg-neutral-
|
|
81
|
+
<div className="bg-neutral-980 border border-neutral-700 rounded-lg p-4">
|
|
75
82
|
<div className="flex items-center justify-between">
|
|
76
83
|
<div>
|
|
77
84
|
<label className="text-neutral-300">Snapshot Limit</label>
|
|
@@ -88,7 +95,7 @@ export function SnapshotBrowserPanel({
|
|
|
88
95
|
</div>
|
|
89
96
|
|
|
90
97
|
{/* Snapshot Browser */}
|
|
91
|
-
<div className="bg-neutral-
|
|
98
|
+
<div className="bg-neutral-980 border border-neutral-700 rounded-lg p-4">
|
|
92
99
|
<div className="flex items-center justify-between mb-3">
|
|
93
100
|
<div>
|
|
94
101
|
<label className="text-neutral-300">Browse Snapshots</label>
|
|
@@ -103,7 +110,7 @@ export function SnapshotBrowserPanel({
|
|
|
103
110
|
onClick={() => onClearAll?.()}
|
|
104
111
|
disabled={totalSnapshotCount === 0 || !onClearAll}
|
|
105
112
|
size="sm"
|
|
106
|
-
|
|
113
|
+
accentColor="red"
|
|
107
114
|
tooltip={{
|
|
108
115
|
title: 'Clear all snapshots',
|
|
109
116
|
description: 'Delete all saved snapshots',
|
|
@@ -126,7 +133,7 @@ export function SnapshotBrowserPanel({
|
|
|
126
133
|
</div>
|
|
127
134
|
|
|
128
135
|
{/* Help */}
|
|
129
|
-
<div className="bg-neutral-
|
|
136
|
+
<div className="bg-neutral-980/50 border border-neutral-700 rounded-lg p-4">
|
|
130
137
|
<div className="flex items-start gap-3">
|
|
131
138
|
<HelpCircle className="w-4 h-4 text-neutral-500 mt-0.5 shrink-0" />
|
|
132
139
|
<div className="text-md text-neutral-500 space-y-2">
|
|
@@ -143,5 +150,6 @@ export function SnapshotBrowserPanel({
|
|
|
143
150
|
</div>
|
|
144
151
|
</div>
|
|
145
152
|
</div>
|
|
153
|
+
</AccentColorProvider>
|
|
146
154
|
)
|
|
147
155
|
}
|
|
@@ -211,7 +211,7 @@ function SnapshotEntryRow({
|
|
|
211
211
|
onDelete(scopeId, categoryId, itemId, entry.id)
|
|
212
212
|
}}
|
|
213
213
|
size="sm"
|
|
214
|
-
|
|
214
|
+
accentColor="red"
|
|
215
215
|
className={cn('transition-opacity', isDeleting ? 'opacity-50' : 'opacity-0 group-hover:opacity-100')}
|
|
216
216
|
disabled={isDeleting}
|
|
217
217
|
tooltip={{ title: 'Delete', description: 'Delete this snapshot' }}
|
|
@@ -322,6 +322,7 @@ export function SnapshotTree({
|
|
|
322
322
|
|
|
323
323
|
if (totalCount === 0) {
|
|
324
324
|
return (
|
|
325
|
+
// py-8: empty state needs extra vertical breathing room beyond the p-6 scale max
|
|
325
326
|
<div className={cn('text-md text-neutral-500 py-8 text-center', className)}>
|
|
326
327
|
No snapshots saved yet.
|
|
327
328
|
<br />
|
|
@@ -352,7 +353,7 @@ export function SnapshotTree({
|
|
|
352
353
|
<IconButton
|
|
353
354
|
icon={<X className="w-3.5 h-3.5" />}
|
|
354
355
|
onClick={() => onSearchChange('')}
|
|
355
|
-
|
|
356
|
+
accentColor="neutral"
|
|
356
357
|
size="xss"
|
|
357
358
|
className="absolute right-2 top-1/2 -translate-y-1/2"
|
|
358
359
|
tooltip={{ title: 'Clear', description: 'Clear search' }}
|
|
@@ -365,7 +366,7 @@ export function SnapshotTree({
|
|
|
365
366
|
icon={allExpanded ? <ChevronsDownUp className="w-3.5 h-3.5" /> : <ChevronsUpDown className="w-3.5 h-3.5" />}
|
|
366
367
|
onClick={allExpanded ? onCollapseAll : onExpandAll}
|
|
367
368
|
size="sm"
|
|
368
|
-
|
|
369
|
+
accentColor="neutral"
|
|
369
370
|
tooltip={{
|
|
370
371
|
title: allExpanded ? 'Collapse All' : 'Expand All',
|
|
371
372
|
description: allExpanded ? 'Collapse all sections' : 'Expand all sections',
|
|
@@ -375,7 +376,7 @@ export function SnapshotTree({
|
|
|
375
376
|
</div>
|
|
376
377
|
|
|
377
378
|
{/* Tree */}
|
|
378
|
-
<div className="bg-neutral-
|
|
379
|
+
<div className="bg-neutral-980 border border-neutral-700 rounded-lg p-2 min-h-[200px] max-h-[60vh] overflow-y-auto">
|
|
379
380
|
{filteredScopes.length === 0 && searchQuery ? (
|
|
380
381
|
<p className="text-sm text-neutral-500 text-center py-4">
|
|
381
382
|
No snapshots match “{searchQuery}”
|
|
@@ -49,7 +49,7 @@ export interface SnapshotCategory {
|
|
|
49
49
|
items: SnapshotItem[]
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** A top-level scope containing categories (e.g. "Settings", "
|
|
52
|
+
/** A top-level scope containing categories (e.g. "Settings", "Capabilities") */
|
|
53
53
|
export interface SnapshotScope {
|
|
54
54
|
id: string
|
|
55
55
|
name: string
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Part of: Sections > Snippets Editor
|
|
5
5
|
*
|
|
6
|
-
* Replicates the configr "Settings >
|
|
6
|
+
* Replicates the configr "Settings > Capabilities > [type] > Snippets" page
|
|
7
7
|
* as a reusable, self-contained component. Left column shows the snippet
|
|
8
8
|
* list with search; right column shows the editor form.
|
|
9
9
|
*
|
|
@@ -25,10 +25,12 @@
|
|
|
25
25
|
import { useResizableSidebar } from '../../hooks/use-resizable-sidebar.ts'
|
|
26
26
|
import { Plus, X, Braces, Trash2, RotateCcw, Save } from 'lucide-react'
|
|
27
27
|
import { ACCENT_TEXT, type AccentColor } from '../../lib/form-colors.ts'
|
|
28
|
+
import type { FormColor } from '../../lib/form-colors.ts'
|
|
28
29
|
import { cn } from '../../lib/cn.ts'
|
|
29
30
|
import { Input } from '../../ui/input.tsx'
|
|
30
31
|
import { ResizableTextarea } from '../../ui/resizable-textarea.tsx'
|
|
31
32
|
import { IconButton } from '../../ui/icon-button.tsx'
|
|
33
|
+
import { useAccentColor, AccentColorProvider } from '../../lib/accent-context.ts'
|
|
32
34
|
import { useSnippetsEditor } from './use-snippets-editor.ts'
|
|
33
35
|
import type { SnippetData, SnippetsEditorApi } from './types.ts'
|
|
34
36
|
|
|
@@ -40,7 +42,7 @@ export interface SnippetsEditorProps {
|
|
|
40
42
|
/** Section description, e.g. "Define snippets to reuse in skills prompts..." */
|
|
41
43
|
description?: string
|
|
42
44
|
className?: string
|
|
43
|
-
accentColor?:
|
|
45
|
+
accentColor?: FormColor
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
const ACCENT_DIVIDER_HOVER: Record<string, string> = {
|
|
@@ -90,8 +92,10 @@ export function SnippetsEditor({
|
|
|
90
92
|
title = 'Snippets',
|
|
91
93
|
description = 'Define reusable snippets for prompts using {{SNIPPET_NAME}} syntax.',
|
|
92
94
|
className,
|
|
93
|
-
accentColor
|
|
95
|
+
accentColor: accentColorProp,
|
|
94
96
|
}: SnippetsEditorProps) {
|
|
97
|
+
const contextAccent = useAccentColor()
|
|
98
|
+
const accentColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
95
99
|
const {
|
|
96
100
|
selectedName,
|
|
97
101
|
selectSnippet,
|
|
@@ -119,7 +123,8 @@ export function SnippetsEditor({
|
|
|
119
123
|
)
|
|
120
124
|
|
|
121
125
|
return (
|
|
122
|
-
<
|
|
126
|
+
<AccentColorProvider value={accentColor}>
|
|
127
|
+
<div className={cn('flex flex-col bg-neutral-980 border border-neutral-700 rounded-lg overflow-hidden', className)}>
|
|
123
128
|
{/* Header */}
|
|
124
129
|
<div className="flex items-center justify-between px-4 py-3 border-b border-neutral-700 bg-purple-500/5">
|
|
125
130
|
<div className="flex items-center gap-2">
|
|
@@ -151,7 +156,7 @@ export function SnippetsEditor({
|
|
|
151
156
|
icon={<Plus className="w-3.5 h-3.5" />}
|
|
152
157
|
onClick={startAdd}
|
|
153
158
|
size="xs"
|
|
154
|
-
|
|
159
|
+
accentColor="cyan"
|
|
155
160
|
tooltip={{ title: 'Add Snippet', description: 'Create a new snippet' }}
|
|
156
161
|
disabled={isAdding}
|
|
157
162
|
/>
|
|
@@ -160,6 +165,7 @@ export function SnippetsEditor({
|
|
|
160
165
|
{/* Snippet list */}
|
|
161
166
|
<div className="flex-1 overflow-y-auto">
|
|
162
167
|
{filteredSnippets.length === 0 && !isAdding && (
|
|
168
|
+
// py-10: empty state needs extra vertical breathing room beyond the p-6 scale max
|
|
163
169
|
<div className="text-center py-10 px-4">
|
|
164
170
|
<Braces className="w-8 h-8 mx-auto text-purple-400/40 mb-3" />
|
|
165
171
|
<p className="text-sm text-neutral-500 mb-1">
|
|
@@ -222,6 +228,7 @@ export function SnippetsEditor({
|
|
|
222
228
|
</div>
|
|
223
229
|
</div>
|
|
224
230
|
</div>
|
|
231
|
+
</AccentColorProvider>
|
|
225
232
|
)
|
|
226
233
|
}
|
|
227
234
|
|
|
@@ -243,8 +250,8 @@ function SnippetListItem({ snippet, selected, onSelect, onDelete, accentColor }:
|
|
|
243
250
|
className={cn(
|
|
244
251
|
'group flex items-start gap-2 px-3 py-2.5 cursor-pointer transition-colors border-l-2',
|
|
245
252
|
selected
|
|
246
|
-
? `bg-neutral-
|
|
247
|
-
: 'border-l-transparent hover:bg-neutral-
|
|
253
|
+
? `bg-neutral-970 ${ACCENT_BORDER[accentColor] ?? ACCENT_BORDER.blue}`
|
|
254
|
+
: 'border-l-transparent hover:bg-neutral-970/50',
|
|
248
255
|
)}
|
|
249
256
|
onClick={onSelect}
|
|
250
257
|
>
|
|
@@ -366,7 +373,7 @@ function SnippetForm({
|
|
|
366
373
|
icon={<Trash2 className="w-3.5 h-3.5" />}
|
|
367
374
|
onClick={onDelete}
|
|
368
375
|
size="sm"
|
|
369
|
-
|
|
376
|
+
accentColor="red"
|
|
370
377
|
disabled={isSaving}
|
|
371
378
|
tooltip={{ title: 'Delete', description: 'Remove this snippet' }}
|
|
372
379
|
/>
|
|
@@ -387,7 +394,7 @@ function SnippetForm({
|
|
|
387
394
|
icon={<RotateCcw className="w-3.5 h-3.5" />}
|
|
388
395
|
onClick={onReset}
|
|
389
396
|
size="sm"
|
|
390
|
-
|
|
397
|
+
accentColor="orange"
|
|
391
398
|
disabled={isSaving}
|
|
392
399
|
tooltip={{ title: 'Reset', description: 'Revert to saved values' }}
|
|
393
400
|
/>
|
|
@@ -49,7 +49,7 @@ export function SettingsHeader({
|
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
51
|
<div
|
|
52
|
-
className={`bg-neutral-
|
|
52
|
+
className={`bg-neutral-990 border border-neutral-700 border-l-2 ${styles.border} rounded-lg p-4 flex items-center gap-4`}
|
|
53
53
|
>
|
|
54
54
|
<div className={`flex-shrink-0 w-8 h-8 rounded-lg ${styles.iconBg} flex items-center justify-center`}>
|
|
55
55
|
{icon ?? <Info className={`w-4 h-4 ${styles.iconColor}`} />}
|
|
@@ -62,7 +62,7 @@ export function SettingsHeader({
|
|
|
62
62
|
icon={<RotateCcw className="w-4 h-4" />}
|
|
63
63
|
onClick={onReset}
|
|
64
64
|
size="sm"
|
|
65
|
-
|
|
65
|
+
accentColor="orange"
|
|
66
66
|
tooltip={
|
|
67
67
|
resetTooltip ?? {
|
|
68
68
|
title: 'Reset to Defaults',
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
import { SettingsTreeNav } from './SettingsTreeNav.tsx'
|
|
3
3
|
import type { SettingsTreeNode } from './settings-tree-utils.ts'
|
|
4
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
5
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
4
6
|
|
|
5
7
|
export interface SettingsPanelProps {
|
|
6
8
|
tree: SettingsTreeNode[]
|
|
7
9
|
selectedPath: string
|
|
8
10
|
onSelectPath: (path: string) => void
|
|
9
11
|
children: ReactNode
|
|
12
|
+
accentColor?: FormColor
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
export function SettingsPanel({ tree, selectedPath, onSelectPath, children }: SettingsPanelProps) {
|
|
15
|
+
export function SettingsPanel({ tree, selectedPath, onSelectPath, children, accentColor }: SettingsPanelProps) {
|
|
16
|
+
const contextAccent = useAccentColor()
|
|
17
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
18
|
+
|
|
13
19
|
return (
|
|
14
20
|
<div className="flex-1 flex overflow-hidden">
|
|
15
|
-
<SettingsTreeNav tree={tree} selectedPath={selectedPath} onSelectPath={onSelectPath} />
|
|
21
|
+
<SettingsTreeNav tree={tree} selectedPath={selectedPath} onSelectPath={onSelectPath} accentColor={effectiveColor} />
|
|
16
22
|
<div className="flex-1 overflow-y-auto">
|
|
17
|
-
{
|
|
23
|
+
<AccentColorProvider value={effectiveColor}>
|
|
24
|
+
{children}
|
|
25
|
+
</AccentColorProvider>
|
|
18
26
|
</div>
|
|
19
27
|
</div>
|
|
20
28
|
)
|
|
@@ -9,12 +9,16 @@ import {
|
|
|
9
9
|
getParentPaths,
|
|
10
10
|
filterTree,
|
|
11
11
|
} from './settings-tree-utils.ts'
|
|
12
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
13
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
12
14
|
|
|
13
15
|
export interface SettingsTreeNavProps {
|
|
14
16
|
tree: SettingsTreeNode[]
|
|
15
17
|
selectedPath: string
|
|
16
18
|
onSelectPath: (path: string) => void
|
|
17
|
-
accentColor?:
|
|
19
|
+
accentColor?: FormColor
|
|
20
|
+
className?: string
|
|
21
|
+
searchPlaceholder?: string
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const ACCENT_SELECTED: Record<string, string> = {
|
|
@@ -90,7 +94,7 @@ function TreeNode({
|
|
|
90
94
|
className={`
|
|
91
95
|
w-full flex items-center gap-2 px-2 py-1.5 text-md rounded-md transition-colors cursor-pointer
|
|
92
96
|
${isSelected ? ACCENT_SELECTED[accentColor] ?? ACCENT_SELECTED.blue : isInSelectedPath ? 'text-neutral-300' : 'text-neutral-400'}
|
|
93
|
-
${!isSelected && 'hover:bg-neutral-
|
|
97
|
+
${!isSelected && 'hover:bg-neutral-960 hover:text-neutral-200'}
|
|
94
98
|
`}
|
|
95
99
|
style={{ paddingLeft: `${depth * 12 + 8}px` }}
|
|
96
100
|
>
|
|
@@ -135,7 +139,9 @@ function TreeNode({
|
|
|
135
139
|
)
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
|
|
142
|
+
export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor, className, searchPlaceholder }: SettingsTreeNavProps) {
|
|
143
|
+
const contextAccent = useAccentColor()
|
|
144
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
139
145
|
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => {
|
|
140
146
|
const expanded = new Set<string>()
|
|
141
147
|
if (selectedPath) {
|
|
@@ -203,12 +209,12 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
|
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
return (
|
|
206
|
-
<nav className="w-56 flex-shrink-0 bg-neutral-
|
|
207
|
-
<div className="p-2 border-b border-neutral-
|
|
212
|
+
<nav className={className ?? "w-56 flex-shrink-0 bg-neutral-980 border-r border-neutral-960 flex flex-col overflow-hidden"}>
|
|
213
|
+
<div className="p-2 border-b border-neutral-960">
|
|
208
214
|
<div className="flex items-center gap-1">
|
|
209
215
|
<Input
|
|
210
216
|
type="search"
|
|
211
|
-
placeholder="Search..."
|
|
217
|
+
placeholder={searchPlaceholder ?? "Search..."}
|
|
212
218
|
value={searchQuery}
|
|
213
219
|
onChange={setSearchQuery}
|
|
214
220
|
size="sm"
|
|
@@ -220,7 +226,7 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
|
|
|
220
226
|
icon={<ChevronsUpDown className="w-3.5 h-3.5" />}
|
|
221
227
|
onClick={handleExpandAll}
|
|
222
228
|
size="sm"
|
|
223
|
-
|
|
229
|
+
accentColor="neutral"
|
|
224
230
|
tooltipPosition="left"
|
|
225
231
|
tooltip={{
|
|
226
232
|
title: 'Expand All',
|
|
@@ -233,7 +239,7 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
|
|
|
233
239
|
icon={<ChevronsDownUp className="w-3.5 h-3.5" />}
|
|
234
240
|
onClick={handleCollapseAll}
|
|
235
241
|
size="sm"
|
|
236
|
-
|
|
242
|
+
accentColor="neutral"
|
|
237
243
|
tooltipPosition="left"
|
|
238
244
|
tooltip={{
|
|
239
245
|
title: 'Collapse All',
|
|
@@ -261,7 +267,7 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
|
|
|
261
267
|
onToggleExpand={handleToggleExpand}
|
|
262
268
|
depth={0}
|
|
263
269
|
searchQuery={searchQuery}
|
|
264
|
-
accentColor={
|
|
270
|
+
accentColor={effectiveColor}
|
|
265
271
|
/>
|
|
266
272
|
))
|
|
267
273
|
)}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Footer: FormActions (status text, cancel/submit)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { useId, useRef } from 'react'
|
|
14
15
|
import { createPortal } from 'react-dom'
|
|
15
16
|
import { useModalBehavior } from '../hooks/use-modal-behavior.ts'
|
|
16
17
|
import { iconMap, IconButton } from './icon-button.tsx'
|
|
@@ -20,6 +21,8 @@ import { SelectionGrid, type SelectionCardItem, type CodingToolPresetConfig } fr
|
|
|
20
21
|
import { ExecutionDetailsPanel } from './execution-details-panel.tsx'
|
|
21
22
|
import type { DetailRow } from './detail-section.tsx'
|
|
22
23
|
import { cn } from '../lib/cn.ts'
|
|
24
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
25
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
23
26
|
|
|
24
27
|
export interface ActionDialogProps {
|
|
25
28
|
/** Dialog title */
|
|
@@ -48,11 +51,11 @@ export interface ActionDialogProps {
|
|
|
48
51
|
statusText?: string
|
|
49
52
|
|
|
50
53
|
// ── Selection section (optional) ─────────────────────────────────────────
|
|
51
|
-
/** Label above the selection grid (e.g. "
|
|
54
|
+
/** Label above the selection grid (e.g. "Coding Agent:") */
|
|
52
55
|
selectionLabel?: string
|
|
53
56
|
/** Custom selection items - renders SelectionGrid when provided */
|
|
54
57
|
items?: SelectionCardItem[]
|
|
55
|
-
/** Built-in
|
|
58
|
+
/** Built-in coding agent presets. Ignored when `items` is provided. */
|
|
56
59
|
presets?: CodingToolPresetConfig[]
|
|
57
60
|
/** Currently selected item IDs */
|
|
58
61
|
selectedIds?: string[]
|
|
@@ -94,6 +97,8 @@ export interface ActionDialogProps {
|
|
|
94
97
|
children?: React.ReactNode
|
|
95
98
|
/** Additional className */
|
|
96
99
|
className?: string
|
|
100
|
+
/** Accent color for themed sub-components */
|
|
101
|
+
accentColor?: FormColor
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
export function ActionDialog({
|
|
@@ -129,49 +134,49 @@ export function ActionDialog({
|
|
|
129
134
|
executionWarning,
|
|
130
135
|
children,
|
|
131
136
|
className,
|
|
137
|
+
accentColor: accentColorProp,
|
|
132
138
|
}: ActionDialogProps) {
|
|
133
|
-
|
|
139
|
+
const contextAccent = useAccentColor()
|
|
140
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
141
|
+
const dialogRef = useRef<HTMLDivElement>(null)
|
|
142
|
+
const titleId = useId()
|
|
143
|
+
|
|
144
|
+
useModalBehavior(true, () => onCancel?.(), dialogRef)
|
|
134
145
|
|
|
135
146
|
const Icon = icon ? iconMap[icon] : null
|
|
136
147
|
const hasSelection = ((items && items.length > 0) || (presets && presets.length > 0)) && selectedIds && onSelect
|
|
137
148
|
const hasScenarios = scenarios && scenarios.length > 0 && selectedScenarioIds && onSelectScenarios
|
|
138
149
|
const hasExecutionDetails = executionDetails.length > 0 || onAllowDirectEditsChange || executionWarning
|
|
139
150
|
|
|
140
|
-
const allScenariosSelected = hasScenarios && selectedScenarioIds!.length === scenarios!.filter(s => !s.disabled).length
|
|
141
|
-
|
|
142
|
-
function handleSelectAllScenarios() {
|
|
143
|
-
if (!scenarios || !onSelectScenarios) return
|
|
144
|
-
if (allScenariosSelected) {
|
|
145
|
-
onSelectScenarios([])
|
|
146
|
-
} else {
|
|
147
|
-
onSelectScenarios(scenarios.filter(s => !s.disabled).map(s => s.id))
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
151
|
return createPortal(
|
|
152
|
+
<AccentColorProvider value={effectiveColor}>
|
|
152
153
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
153
|
-
<div className="absolute inset-0 bg-[var(--dialog-backdrop)]
|
|
154
|
+
<div className="absolute inset-0 bg-[var(--dialog-backdrop)]" onClick={onCancel} aria-hidden="true" />
|
|
154
155
|
<div
|
|
156
|
+
ref={dialogRef}
|
|
157
|
+
role="dialog"
|
|
158
|
+
aria-modal="true"
|
|
159
|
+
aria-labelledby={titleId}
|
|
155
160
|
className={cn(
|
|
156
|
-
'relative bg-neutral-
|
|
161
|
+
'relative bg-neutral-990 border border-neutral-700 rounded-lg shadow-lg w-full max-w-[800px] mx-4 flex flex-col',
|
|
157
162
|
'max-h-[80vh]',
|
|
158
163
|
className,
|
|
159
164
|
)}
|
|
160
165
|
>
|
|
161
166
|
{/* Header */}
|
|
162
|
-
<div className="flex items-center gap-3 px-4 py-3
|
|
167
|
+
<div className="flex items-center gap-3 px-4 py-3 flex-shrink-0">
|
|
163
168
|
{Icon && (
|
|
164
169
|
<Icon
|
|
165
170
|
className="w-4 h-4 flex-shrink-0"
|
|
166
171
|
style={iconColor ? { color: iconColor } : undefined}
|
|
167
172
|
/>
|
|
168
173
|
)}
|
|
169
|
-
<div className="flex flex-col">
|
|
170
|
-
<span className="text-md font-semibold text-neutral-200">
|
|
174
|
+
<div className="flex flex-col min-w-0">
|
|
175
|
+
<span id={titleId} className="text-md font-semibold text-neutral-200 truncate">
|
|
171
176
|
{title}
|
|
172
177
|
</span>
|
|
173
178
|
{subtitle && (
|
|
174
|
-
<span className="text-sm text-neutral-500">{subtitle}</span>
|
|
179
|
+
<span className="text-sm text-neutral-500 truncate">{subtitle}</span>
|
|
175
180
|
)}
|
|
176
181
|
</div>
|
|
177
182
|
<div className="flex-1" />
|
|
@@ -179,7 +184,7 @@ export function ActionDialog({
|
|
|
179
184
|
<IconButton
|
|
180
185
|
icon="settings"
|
|
181
186
|
size="sm"
|
|
182
|
-
|
|
187
|
+
accentColor="neutral"
|
|
183
188
|
onClick={onSettings}
|
|
184
189
|
tooltip={{ description: 'Open settings' }}
|
|
185
190
|
/>
|
|
@@ -192,7 +197,7 @@ export function ActionDialog({
|
|
|
192
197
|
{hasSelection && (
|
|
193
198
|
<div>
|
|
194
199
|
{selectionLabel && (
|
|
195
|
-
<div className="
|
|
200
|
+
<div className="font-medium text-neutral-400 text-md mb-2">{selectionLabel}</div>
|
|
196
201
|
)}
|
|
197
202
|
<SelectionGrid
|
|
198
203
|
items={items}
|
|
@@ -209,16 +214,7 @@ export function ActionDialog({
|
|
|
209
214
|
{/* Built-in SelectionGrid for scenarios */}
|
|
210
215
|
{hasScenarios && (
|
|
211
216
|
<div>
|
|
212
|
-
<div className="
|
|
213
|
-
<span className="text-sm text-neutral-500">{scenarioLabel}</span>
|
|
214
|
-
<button
|
|
215
|
-
type="button"
|
|
216
|
-
onClick={handleSelectAllScenarios}
|
|
217
|
-
className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer"
|
|
218
|
-
>
|
|
219
|
-
{allScenariosSelected ? 'Deselect All' : 'Select All'}
|
|
220
|
-
</button>
|
|
221
|
-
</div>
|
|
217
|
+
<div className="font-medium text-neutral-400 text-md mb-2">{scenarioLabel}</div>
|
|
222
218
|
<SelectionGrid
|
|
223
219
|
items={scenarios}
|
|
224
220
|
selectedIds={selectedScenarioIds}
|
|
@@ -252,14 +248,20 @@ export function ActionDialog({
|
|
|
252
248
|
onCancel={onCancel}
|
|
253
249
|
cancelTooltip="Close this dialog"
|
|
254
250
|
onConfirm={onSubmit}
|
|
255
|
-
confirmTooltip={
|
|
251
|
+
confirmTooltip={
|
|
252
|
+
hasScenarios && selectedScenarioIds!.length === 0
|
|
253
|
+
? 'Select at least one scenario to run'
|
|
254
|
+
: submitLabel
|
|
255
|
+
}
|
|
256
256
|
confirmIcon={submitIcon}
|
|
257
257
|
confirmColor={submitColor as 'blue'}
|
|
258
|
-
confirmDisabled={submitDisabled}
|
|
258
|
+
confirmDisabled={submitDisabled || (hasScenarios && selectedScenarioIds!.length === 0)}
|
|
259
|
+
border={false}
|
|
259
260
|
/>
|
|
260
261
|
</div>
|
|
261
262
|
</div>
|
|
262
|
-
</div
|
|
263
|
+
</div>
|
|
264
|
+
</AccentColorProvider>,
|
|
263
265
|
document.body,
|
|
264
266
|
)
|
|
265
267
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { IconButton, type IconButtonProps, type IconName } from './icon-button.tsx'
|
|
3
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
3
4
|
|
|
4
5
|
export type AiActionStatus = 'idle' | 'running' | 'completed'
|
|
5
6
|
export type AiCompletionResult = 'success' | 'partial' | 'error'
|
|
@@ -32,16 +33,16 @@ export interface AiActionButtonProps {
|
|
|
32
33
|
disabledReason?: string
|
|
33
34
|
|
|
34
35
|
/** Color in default state */
|
|
35
|
-
|
|
36
|
+
accentColor?: IconButtonProps['accentColor']
|
|
36
37
|
/** Color when completed successfully (defaults to 'green') */
|
|
37
|
-
completedColor?: IconButtonProps['
|
|
38
|
+
completedColor?: IconButtonProps['accentColor']
|
|
38
39
|
|
|
39
40
|
size?: IconButtonProps['size']
|
|
40
41
|
className?: string
|
|
41
42
|
testId?: string
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
const COMPLETION_COLORS: Record<AiCompletionResult, IconButtonProps['
|
|
45
|
+
const COMPLETION_COLORS: Record<AiCompletionResult, IconButtonProps['accentColor']> = {
|
|
45
46
|
success: 'green',
|
|
46
47
|
partial: 'amber',
|
|
47
48
|
error: 'red',
|
|
@@ -65,12 +66,14 @@ export function AiActionButton({
|
|
|
65
66
|
onClick,
|
|
66
67
|
disabled: forceDisabled = false,
|
|
67
68
|
disabledReason,
|
|
68
|
-
|
|
69
|
+
accentColor,
|
|
69
70
|
completedColor = 'green',
|
|
70
71
|
size = 'sm',
|
|
71
72
|
className,
|
|
72
73
|
testId,
|
|
73
74
|
}: AiActionButtonProps) {
|
|
75
|
+
const contextAccent = useAccentColor()
|
|
76
|
+
const effectiveAccent = accentColor ?? contextAccent ?? 'purple'
|
|
74
77
|
const isRunning = status === 'running'
|
|
75
78
|
const isCompleted = status === 'completed'
|
|
76
79
|
|
|
@@ -90,8 +93,8 @@ export function AiActionButton({
|
|
|
90
93
|
? completedColor
|
|
91
94
|
: COMPLETION_COLORS[completionResult]
|
|
92
95
|
}
|
|
93
|
-
return
|
|
94
|
-
}, [isCompleted, completionResult, completedColor,
|
|
96
|
+
return effectiveAccent
|
|
97
|
+
}, [isCompleted, completionResult, completedColor, effectiveAccent])
|
|
95
98
|
|
|
96
99
|
const resolvedTooltip = useMemo(() => {
|
|
97
100
|
if (forceDisabled && disabledReason) {
|
|
@@ -119,18 +122,16 @@ export function AiActionButton({
|
|
|
119
122
|
}, [tooltip, forceDisabled, disabledReason, isRunning, isCompleted, runningTooltipTitle, completedTooltipTitle])
|
|
120
123
|
|
|
121
124
|
const isDisabled = forceDisabled
|
|
122
|
-
const blinkClass = isCompleted ? 'animate-pulse' : ''
|
|
123
|
-
|
|
124
125
|
return (
|
|
125
126
|
<IconButton
|
|
126
127
|
icon={resolvedIcon}
|
|
127
|
-
|
|
128
|
+
accentColor={resolvedColor}
|
|
128
129
|
size={size}
|
|
129
130
|
disabled={isDisabled}
|
|
130
131
|
onClick={isDisabled ? () => {} : onClick}
|
|
131
132
|
tooltip={resolvedTooltip}
|
|
132
|
-
active={isRunning}
|
|
133
|
-
className={
|
|
133
|
+
active={isRunning || isCompleted}
|
|
134
|
+
className={className}
|
|
134
135
|
testId={testId}
|
|
135
136
|
/>
|
|
136
137
|
)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { IconButton, type IconName } from './icon-button.tsx'
|
|
2
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
3
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
2
4
|
|
|
3
5
|
export type ExecutionStatus = 'running' | 'success' | 'partial' | 'error'
|
|
4
6
|
|
|
@@ -17,6 +19,8 @@ export interface AiExecutionActionButtonsProps {
|
|
|
17
19
|
status?: ExecutionStatus
|
|
18
20
|
/** Prefix for test IDs (e.g., 'scan-modal' results in 'scan-modal-minimize-btn') */
|
|
19
21
|
testIdPrefix?: string
|
|
22
|
+
/** Accent color for themed sub-components */
|
|
23
|
+
accentColor?: FormColor
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function AiExecutionActionButtons({
|
|
@@ -27,8 +31,12 @@ export function AiExecutionActionButtons({
|
|
|
27
31
|
onClose,
|
|
28
32
|
status = 'success',
|
|
29
33
|
testIdPrefix,
|
|
34
|
+
accentColor,
|
|
30
35
|
}: AiExecutionActionButtonsProps) {
|
|
31
|
-
const
|
|
36
|
+
const contextAccent = useAccentColor()
|
|
37
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
38
|
+
|
|
39
|
+
const getDoneButtonConfig = (): { icon: IconName; color: FormColor; tooltip: { title?: string; description: string } } => {
|
|
32
40
|
if (!allDone) {
|
|
33
41
|
return {
|
|
34
42
|
icon: 'check-circle',
|
|
@@ -52,7 +60,7 @@ export function AiExecutionActionButtons({
|
|
|
52
60
|
default:
|
|
53
61
|
return {
|
|
54
62
|
icon: 'check-circle',
|
|
55
|
-
color:
|
|
63
|
+
color: effectiveColor,
|
|
56
64
|
tooltip: { description: 'All scans completed successfully' },
|
|
57
65
|
}
|
|
58
66
|
}
|
|
@@ -69,7 +77,7 @@ export function AiExecutionActionButtons({
|
|
|
69
77
|
icon="minus"
|
|
70
78
|
onClick={onMinimize}
|
|
71
79
|
size="sm"
|
|
72
|
-
|
|
80
|
+
accentColor="neutral"
|
|
73
81
|
tooltip={{ description: 'Hide modal. Process will continue in background.' }}
|
|
74
82
|
tooltipPosition="top"
|
|
75
83
|
testId={testIdPrefix ? `${testIdPrefix}-minimize-btn` : undefined}
|
|
@@ -81,7 +89,7 @@ export function AiExecutionActionButtons({
|
|
|
81
89
|
icon="stop-circle"
|
|
82
90
|
onClick={onCancel}
|
|
83
91
|
size="sm"
|
|
84
|
-
|
|
92
|
+
accentColor="red"
|
|
85
93
|
tooltip={{ description: 'Stop and kill the running process' }}
|
|
86
94
|
tooltipPosition="top"
|
|
87
95
|
testId={testIdPrefix ? `${testIdPrefix}-cancel-btn` : undefined}
|
|
@@ -96,7 +104,7 @@ export function AiExecutionActionButtons({
|
|
|
96
104
|
onClick={onClose}
|
|
97
105
|
disabled={isRunning && !allDone}
|
|
98
106
|
size="sm"
|
|
99
|
-
|
|
107
|
+
accentColor={doneConfig.color}
|
|
100
108
|
tooltip={doneConfig.tooltip}
|
|
101
109
|
tooltipPosition="top"
|
|
102
110
|
testId={testIdPrefix ? `${testIdPrefix}-close-btn` : undefined}
|