@toolr/ui-design 0.1.8 → 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/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 +10 -9
- 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 +24 -30
- package/components/ui/ai-action-button.tsx +10 -7
- package/components/ui/ai-execution-action-buttons.tsx +13 -5
- package/components/ui/badge.tsx +7 -4
- package/components/ui/bottom-panel-header.tsx +9 -5
- package/components/ui/breadcrumb.tsx +9 -1
- package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +13 -5
- package/components/ui/checkbox.tsx +6 -3
- package/components/ui/collapsible-section.tsx +38 -29
- package/components/ui/confirm-badge.tsx +7 -4
- 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 +3 -1
- package/components/ui/files-panel.tsx +147 -27
- package/components/ui/filter-dropdown.tsx +84 -74
- package/components/ui/form-actions.tsx +14 -6
- package/components/ui/frontmatter-form-header.tsx +10 -2
- package/components/ui/icon-button.tsx +22 -9
- package/components/ui/input.tsx +7 -4
- package/components/ui/label.tsx +5 -5
- package/components/ui/layout-tab-bar.tsx +7 -5
- package/components/ui/modal.tsx +18 -4
- package/components/ui/nav-card.tsx +6 -3
- package/components/ui/navigation-bar.tsx +37 -9
- package/components/ui/number-input.tsx +8 -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 +5 -2
- package/components/ui/select.tsx +12 -9
- package/components/ui/selection-grid.tsx +36 -37
- package/components/ui/setting-row.tsx +2 -2
- package/components/ui/settings-card.tsx +10 -3
- package/components/ui/settings-info-box.tsx +9 -5
- 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 +39 -29
- package/components/ui/status-card.tsx +9 -1
- package/components/ui/tab-bar.tsx +12 -9
- package/components/ui/toggle.tsx +13 -7
- package/components/ui/tooltip.tsx +9 -1
- package/dist/content.js +8 -8
- package/dist/diagrams.d.ts +0 -1
- package/dist/index.d.ts +421 -182
- package/dist/index.js +2984 -1691
- 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 +1 -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
|
)}
|
|
@@ -21,6 +21,8 @@ import { SelectionGrid, type SelectionCardItem, type CodingToolPresetConfig } fr
|
|
|
21
21
|
import { ExecutionDetailsPanel } from './execution-details-panel.tsx'
|
|
22
22
|
import type { DetailRow } from './detail-section.tsx'
|
|
23
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'
|
|
24
26
|
|
|
25
27
|
export interface ActionDialogProps {
|
|
26
28
|
/** Dialog title */
|
|
@@ -49,11 +51,11 @@ export interface ActionDialogProps {
|
|
|
49
51
|
statusText?: string
|
|
50
52
|
|
|
51
53
|
// ── Selection section (optional) ─────────────────────────────────────────
|
|
52
|
-
/** Label above the selection grid (e.g. "
|
|
54
|
+
/** Label above the selection grid (e.g. "Coding Agent:") */
|
|
53
55
|
selectionLabel?: string
|
|
54
56
|
/** Custom selection items - renders SelectionGrid when provided */
|
|
55
57
|
items?: SelectionCardItem[]
|
|
56
|
-
/** Built-in
|
|
58
|
+
/** Built-in coding agent presets. Ignored when `items` is provided. */
|
|
57
59
|
presets?: CodingToolPresetConfig[]
|
|
58
60
|
/** Currently selected item IDs */
|
|
59
61
|
selectedIds?: string[]
|
|
@@ -95,6 +97,8 @@ export interface ActionDialogProps {
|
|
|
95
97
|
children?: React.ReactNode
|
|
96
98
|
/** Additional className */
|
|
97
99
|
className?: string
|
|
100
|
+
/** Accent color for themed sub-components */
|
|
101
|
+
accentColor?: FormColor
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
export function ActionDialog({
|
|
@@ -130,7 +134,10 @@ export function ActionDialog({
|
|
|
130
134
|
executionWarning,
|
|
131
135
|
children,
|
|
132
136
|
className,
|
|
137
|
+
accentColor: accentColorProp,
|
|
133
138
|
}: ActionDialogProps) {
|
|
139
|
+
const contextAccent = useAccentColor()
|
|
140
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
134
141
|
const dialogRef = useRef<HTMLDivElement>(null)
|
|
135
142
|
const titleId = useId()
|
|
136
143
|
|
|
@@ -141,18 +148,8 @@ export function ActionDialog({
|
|
|
141
148
|
const hasScenarios = scenarios && scenarios.length > 0 && selectedScenarioIds && onSelectScenarios
|
|
142
149
|
const hasExecutionDetails = executionDetails.length > 0 || onAllowDirectEditsChange || executionWarning
|
|
143
150
|
|
|
144
|
-
const allScenariosSelected = hasScenarios && selectedScenarioIds!.length === scenarios!.filter(s => !s.disabled).length
|
|
145
|
-
|
|
146
|
-
function handleSelectAllScenarios() {
|
|
147
|
-
if (!scenarios || !onSelectScenarios) return
|
|
148
|
-
if (allScenariosSelected) {
|
|
149
|
-
onSelectScenarios([])
|
|
150
|
-
} else {
|
|
151
|
-
onSelectScenarios(scenarios.filter(s => !s.disabled).map(s => s.id))
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
151
|
return createPortal(
|
|
152
|
+
<AccentColorProvider value={effectiveColor}>
|
|
156
153
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
157
154
|
<div className="absolute inset-0 bg-[var(--dialog-backdrop)]" onClick={onCancel} aria-hidden="true" />
|
|
158
155
|
<div
|
|
@@ -161,13 +158,13 @@ export function ActionDialog({
|
|
|
161
158
|
aria-modal="true"
|
|
162
159
|
aria-labelledby={titleId}
|
|
163
160
|
className={cn(
|
|
164
|
-
'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',
|
|
165
162
|
'max-h-[80vh]',
|
|
166
163
|
className,
|
|
167
164
|
)}
|
|
168
165
|
>
|
|
169
166
|
{/* Header */}
|
|
170
|
-
<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">
|
|
171
168
|
{Icon && (
|
|
172
169
|
<Icon
|
|
173
170
|
className="w-4 h-4 flex-shrink-0"
|
|
@@ -187,7 +184,7 @@ export function ActionDialog({
|
|
|
187
184
|
<IconButton
|
|
188
185
|
icon="settings"
|
|
189
186
|
size="sm"
|
|
190
|
-
|
|
187
|
+
accentColor="neutral"
|
|
191
188
|
onClick={onSettings}
|
|
192
189
|
tooltip={{ description: 'Open settings' }}
|
|
193
190
|
/>
|
|
@@ -200,7 +197,7 @@ export function ActionDialog({
|
|
|
200
197
|
{hasSelection && (
|
|
201
198
|
<div>
|
|
202
199
|
{selectionLabel && (
|
|
203
|
-
<div className="
|
|
200
|
+
<div className="font-medium text-neutral-400 text-md mb-2">{selectionLabel}</div>
|
|
204
201
|
)}
|
|
205
202
|
<SelectionGrid
|
|
206
203
|
items={items}
|
|
@@ -217,16 +214,7 @@ export function ActionDialog({
|
|
|
217
214
|
{/* Built-in SelectionGrid for scenarios */}
|
|
218
215
|
{hasScenarios && (
|
|
219
216
|
<div>
|
|
220
|
-
<div className="
|
|
221
|
-
<span className="text-sm text-neutral-500">{scenarioLabel}</span>
|
|
222
|
-
<button
|
|
223
|
-
type="button"
|
|
224
|
-
onClick={handleSelectAllScenarios}
|
|
225
|
-
className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer"
|
|
226
|
-
>
|
|
227
|
-
{allScenariosSelected ? 'Deselect All' : 'Select All'}
|
|
228
|
-
</button>
|
|
229
|
-
</div>
|
|
217
|
+
<div className="font-medium text-neutral-400 text-md mb-2">{scenarioLabel}</div>
|
|
230
218
|
<SelectionGrid
|
|
231
219
|
items={scenarios}
|
|
232
220
|
selectedIds={selectedScenarioIds}
|
|
@@ -260,14 +248,20 @@ export function ActionDialog({
|
|
|
260
248
|
onCancel={onCancel}
|
|
261
249
|
cancelTooltip="Close this dialog"
|
|
262
250
|
onConfirm={onSubmit}
|
|
263
|
-
confirmTooltip={
|
|
251
|
+
confirmTooltip={
|
|
252
|
+
hasScenarios && selectedScenarioIds!.length === 0
|
|
253
|
+
? 'Select at least one scenario to run'
|
|
254
|
+
: submitLabel
|
|
255
|
+
}
|
|
264
256
|
confirmIcon={submitIcon}
|
|
265
257
|
confirmColor={submitColor as 'blue'}
|
|
266
|
-
confirmDisabled={submitDisabled}
|
|
258
|
+
confirmDisabled={submitDisabled || (hasScenarios && selectedScenarioIds!.length === 0)}
|
|
259
|
+
border={false}
|
|
267
260
|
/>
|
|
268
261
|
</div>
|
|
269
262
|
</div>
|
|
270
|
-
</div
|
|
263
|
+
</div>
|
|
264
|
+
</AccentColorProvider>,
|
|
271
265
|
document.body,
|
|
272
266
|
)
|
|
273
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) {
|
|
@@ -122,7 +125,7 @@ export function AiActionButton({
|
|
|
122
125
|
return (
|
|
123
126
|
<IconButton
|
|
124
127
|
icon={resolvedIcon}
|
|
125
|
-
|
|
128
|
+
accentColor={resolvedColor}
|
|
126
129
|
size={size}
|
|
127
130
|
disabled={isDisabled}
|
|
128
131
|
onClick={isDisabled ? () => {} : onClick}
|
|
@@ -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}
|
package/components/ui/badge.tsx
CHANGED
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
import { memo } from 'react'
|
|
17
17
|
import { FORM_COLORS, type AccentColor } from '../lib/form-colors.ts'
|
|
18
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
18
19
|
import { cn } from '../lib/cn.ts'
|
|
19
20
|
|
|
20
21
|
export type BadgeColor = AccentColor
|
|
21
22
|
|
|
22
23
|
export interface BadgeProps {
|
|
23
24
|
value: number | string
|
|
24
|
-
|
|
25
|
+
accentColor?: BadgeColor
|
|
25
26
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
26
27
|
className?: string
|
|
27
28
|
testId?: string
|
|
@@ -37,11 +38,13 @@ const sizeClasses = {
|
|
|
37
38
|
|
|
38
39
|
export const Badge = memo(function Badge({
|
|
39
40
|
value,
|
|
40
|
-
|
|
41
|
+
accentColor,
|
|
41
42
|
size = 'sm',
|
|
42
43
|
className,
|
|
43
44
|
testId,
|
|
44
45
|
}: BadgeProps) {
|
|
46
|
+
const contextAccent = useAccentColor()
|
|
47
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'neutral'
|
|
45
48
|
const display = typeof value === 'number' && value > 99 ? '99+' : value
|
|
46
49
|
|
|
47
50
|
return (
|
|
@@ -49,8 +52,8 @@ export const Badge = memo(function Badge({
|
|
|
49
52
|
data-testid={testId}
|
|
50
53
|
className={cn(
|
|
51
54
|
'inline-flex items-center justify-center border rounded-full font-medium leading-none tabular-nums',
|
|
52
|
-
FORM_COLORS[
|
|
53
|
-
FORM_COLORS[
|
|
55
|
+
FORM_COLORS[effectiveColor].border,
|
|
56
|
+
FORM_COLORS[effectiveColor].accent,
|
|
54
57
|
sizeClasses[size],
|
|
55
58
|
className,
|
|
56
59
|
)}
|