@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
|
@@ -4,6 +4,8 @@ import { DebounceBorderOverlay } from './debounce-border-overlay.tsx'
|
|
|
4
4
|
import { IconButton } from './icon-button.tsx'
|
|
5
5
|
import { Input } from './input.tsx'
|
|
6
6
|
import { RegistryCard, type RegistryCardProps } from './registry-card.tsx'
|
|
7
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
8
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
7
9
|
|
|
8
10
|
const PAGE_SIZE = 60
|
|
9
11
|
|
|
@@ -50,6 +52,8 @@ export interface RegistryBrowserProps {
|
|
|
50
52
|
|
|
51
53
|
// Grid items
|
|
52
54
|
items: RegistryCardProps[]
|
|
55
|
+
/** Accent color for themed sub-components */
|
|
56
|
+
accentColor?: FormColor
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
export function RegistryBrowser({
|
|
@@ -76,7 +80,10 @@ export function RegistryBrowser({
|
|
|
76
80
|
onScrollChange,
|
|
77
81
|
maxWidth = 'max-w-[1440px]',
|
|
78
82
|
items,
|
|
83
|
+
accentColor: accentColorProp,
|
|
79
84
|
}: RegistryBrowserProps) {
|
|
85
|
+
const contextAccent = useAccentColor()
|
|
86
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
80
87
|
// Incremental rendering: render a page at a time, load more on scroll
|
|
81
88
|
const totalCount = items.length
|
|
82
89
|
const needsPaging = totalCount > PAGE_SIZE
|
|
@@ -152,6 +159,7 @@ export function RegistryBrowser({
|
|
|
152
159
|
const hasMore = visibleCount < totalCount
|
|
153
160
|
|
|
154
161
|
return (
|
|
162
|
+
<AccentColorProvider value={effectiveColor}>
|
|
155
163
|
<div ref={scrollRef} className="h-full overflow-y-auto p-4 pb-8" onScroll={handleScroll}>
|
|
156
164
|
{/* Toolbar */}
|
|
157
165
|
<div className="sticky top-0 flex items-center gap-2 pb-4">
|
|
@@ -184,7 +192,7 @@ export function RegistryBrowser({
|
|
|
184
192
|
icon={<ArrowRight className="w-3.5 h-3.5" />}
|
|
185
193
|
onClick={onSearchSubmit}
|
|
186
194
|
size="sm"
|
|
187
|
-
|
|
195
|
+
accentColor="neutral"
|
|
188
196
|
tooltip={{ description: 'Search' }}
|
|
189
197
|
/>
|
|
190
198
|
)}
|
|
@@ -199,6 +207,7 @@ export function RegistryBrowser({
|
|
|
199
207
|
icon={<RefreshCw className="w-3.5 h-3.5" />}
|
|
200
208
|
onClick={onRefresh}
|
|
201
209
|
size="sm"
|
|
210
|
+
accentColor="neutral"
|
|
202
211
|
tooltip={refreshTooltip ?? { description: 'Refresh registry data' }}
|
|
203
212
|
/>
|
|
204
213
|
)}
|
|
@@ -210,6 +219,7 @@ export function RegistryBrowser({
|
|
|
210
219
|
{/* Content */}
|
|
211
220
|
<div className={`${maxWidth} mx-auto relative z-10`}>
|
|
212
221
|
{isLoading ? (
|
|
222
|
+
/* py-12: empty/loading/error states need extra vertical breathing room beyond the p-6 scale max */
|
|
213
223
|
<div className="flex items-center justify-center py-12 text-neutral-500">
|
|
214
224
|
<Loader2 className="w-5 h-5 animate-spin mr-2" />
|
|
215
225
|
{loadingMessage}
|
|
@@ -243,5 +253,6 @@ export function RegistryBrowser({
|
|
|
243
253
|
)}
|
|
244
254
|
</div>
|
|
245
255
|
</div>
|
|
256
|
+
</AccentColorProvider>
|
|
246
257
|
)
|
|
247
258
|
}
|
|
@@ -5,14 +5,16 @@ import {
|
|
|
5
5
|
} from 'lucide-react'
|
|
6
6
|
import { Tooltip } from './tooltip.tsx'
|
|
7
7
|
import { IconButton, type IconName } from './icon-button.tsx'
|
|
8
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
9
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
8
10
|
import { Label, type LabelColor, smartCapitalize } from './label.tsx'
|
|
9
|
-
import {
|
|
10
|
-
export {
|
|
11
|
+
import { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey } from '../lib/coding-agents.tsx'
|
|
12
|
+
export { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey }
|
|
11
13
|
|
|
12
14
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
13
15
|
|
|
14
16
|
export type RegistryItemType = 'skill' | 'hook' | 'agent' | 'command' | 'plugin' | 'mcp' | 'settings'
|
|
15
|
-
export type
|
|
17
|
+
export type CapabilitySource = 'project' | 'plugin' | 'user' | 'local'
|
|
16
18
|
|
|
17
19
|
const TYPE_ICONS: Record<string, typeof Sparkles> = {
|
|
18
20
|
skill: Sparkles,
|
|
@@ -98,7 +100,7 @@ function formatCount(n?: number): string {
|
|
|
98
100
|
// ── Scope helpers ─────────────────────────────────────────────────────────────
|
|
99
101
|
|
|
100
102
|
function computeSeedrScopes(
|
|
101
|
-
installedScopes: ReadonlySet<
|
|
103
|
+
installedScopes: ReadonlySet<CapabilitySource> | undefined,
|
|
102
104
|
isUserScope: boolean,
|
|
103
105
|
) {
|
|
104
106
|
const isInstalledInViewScope = (() => {
|
|
@@ -107,7 +109,7 @@ function computeSeedrScopes(
|
|
|
107
109
|
return installedScopes.has('project') || installedScopes.has('local')
|
|
108
110
|
})()
|
|
109
111
|
|
|
110
|
-
const removeScope:
|
|
112
|
+
const removeScope: CapabilitySource | null = (() => {
|
|
111
113
|
if (!installedScopes || installedScopes.size === 0) return null
|
|
112
114
|
if (isUserScope && installedScopes.has('user')) return 'user'
|
|
113
115
|
if (!isUserScope) {
|
|
@@ -150,7 +152,7 @@ function CategoryBadge({ category, onFilter }: { category: string; onFilter?: (c
|
|
|
150
152
|
return (
|
|
151
153
|
<Label
|
|
152
154
|
text={smartCapitalize(category)}
|
|
153
|
-
|
|
155
|
+
accentColor={getCategoryLabelColor(category)}
|
|
154
156
|
icon="tag"
|
|
155
157
|
tooltip={{ description: onFilter ? `${smartCapitalize(category)} \u00b7 Click to filter` : smartCapitalize(category) }}
|
|
156
158
|
size="sm"
|
|
@@ -198,7 +200,7 @@ function RegistryCardBadgeRow({ sourceType, pluginType, category, onFilterCatego
|
|
|
198
200
|
{sourceType && SOURCE_TYPE_BADGES[sourceType] && (
|
|
199
201
|
<Label
|
|
200
202
|
text={sourceType}
|
|
201
|
-
|
|
203
|
+
accentColor={SOURCE_TYPE_BADGES[sourceType].color}
|
|
202
204
|
icon={SOURCE_TYPE_BADGES[sourceType].icon}
|
|
203
205
|
tooltip={{ description: `Source: ${sourceType}` }}
|
|
204
206
|
size="sm"
|
|
@@ -208,7 +210,7 @@ function RegistryCardBadgeRow({ sourceType, pluginType, category, onFilterCatego
|
|
|
208
210
|
{pluginType && PLUGIN_TYPE_BADGES[pluginType] && (
|
|
209
211
|
<Label
|
|
210
212
|
text={pluginType}
|
|
211
|
-
|
|
213
|
+
accentColor={PLUGIN_TYPE_BADGES[pluginType].color}
|
|
212
214
|
icon={PLUGIN_TYPE_BADGES[pluginType].icon}
|
|
213
215
|
tooltip={{ description: `Plugin type: ${pluginType}` }}
|
|
214
216
|
size="sm"
|
|
@@ -219,20 +221,20 @@ function RegistryCardBadgeRow({ sourceType, pluginType, category, onFilterCatego
|
|
|
219
221
|
{isInstalled && !isDisabled && (
|
|
220
222
|
<Label
|
|
221
223
|
text="Added"
|
|
222
|
-
|
|
224
|
+
accentColor="green"
|
|
223
225
|
icon={scopeIcons.length > 0 ? scopeIcons : 'check'}
|
|
224
226
|
tooltip={{ description: `Installed in: ${scopes.join(', ')}` }}
|
|
225
227
|
size="sm"
|
|
226
228
|
/>
|
|
227
229
|
)}
|
|
228
230
|
{isDisabled && (
|
|
229
|
-
<Label text="Disabled"
|
|
231
|
+
<Label text="Disabled" accentColor="red" icon="x" tooltip={{ description: 'Installed but disabled' }} size="sm" />
|
|
230
232
|
)}
|
|
231
233
|
{status?.updateAvailable && (
|
|
232
|
-
<Label text="Update"
|
|
234
|
+
<Label text="Update" accentColor="amber" icon="download" tooltip={{ description: 'Update available' }} size="sm" />
|
|
233
235
|
)}
|
|
234
236
|
{status?.modified && (
|
|
235
|
-
<Label text="Modified"
|
|
237
|
+
<Label text="Modified" accentColor="violet" icon="pencil" tooltip={{ description: 'Locally modified' }} size="sm" />
|
|
236
238
|
)}
|
|
237
239
|
</div>
|
|
238
240
|
)
|
|
@@ -256,6 +258,7 @@ interface RegistryCardBase {
|
|
|
256
258
|
isInstalling?: boolean
|
|
257
259
|
updatedAt?: string
|
|
258
260
|
onDateClick?: () => void
|
|
261
|
+
accentColor?: FormColor
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
interface SeedrVariant extends RegistryCardBase {
|
|
@@ -264,14 +267,14 @@ interface SeedrVariant extends RegistryCardBase {
|
|
|
264
267
|
sourceType: string
|
|
265
268
|
pluginType?: string
|
|
266
269
|
wrapper?: string
|
|
267
|
-
installedScopes?: ReadonlySet<
|
|
270
|
+
installedScopes?: ReadonlySet<CapabilitySource>
|
|
268
271
|
viewScope?: 'user' | 'project'
|
|
269
272
|
status?: SeedrItemStatus
|
|
270
273
|
compatibility: string[]
|
|
271
274
|
packageCounts?: Record<string, number>
|
|
272
275
|
isDisabled?: boolean
|
|
273
276
|
onActivate?: () => void
|
|
274
|
-
onRemove?: (scope:
|
|
277
|
+
onRemove?: (scope: CapabilitySource) => void
|
|
275
278
|
onFilterSource?: () => void
|
|
276
279
|
onFilterPluginType?: (pluginType: string) => void
|
|
277
280
|
/** Render scope confirmation modal */
|
|
@@ -284,7 +287,7 @@ interface ClaudePluginsVariant extends RegistryCardBase {
|
|
|
284
287
|
category?: string
|
|
285
288
|
stars?: number
|
|
286
289
|
downloads?: number
|
|
287
|
-
installedScopes?: ReadonlySet<
|
|
290
|
+
installedScopes?: ReadonlySet<CapabilitySource>
|
|
288
291
|
viewScope?: 'user' | 'project'
|
|
289
292
|
onRemove?: () => void
|
|
290
293
|
onSortBy?: (field: string) => void
|
|
@@ -298,7 +301,7 @@ interface ClaudePluginsVariant extends RegistryCardBase {
|
|
|
298
301
|
interface AitmplVariant extends RegistryCardBase {
|
|
299
302
|
variant: 'aitmpl'
|
|
300
303
|
category: string
|
|
301
|
-
installedScopes?: ReadonlySet<
|
|
304
|
+
installedScopes?: ReadonlySet<CapabilitySource>
|
|
302
305
|
viewScope?: 'user' | 'project'
|
|
303
306
|
onRemove?: () => void
|
|
304
307
|
onFilterCategory?: (category: string) => void
|
|
@@ -310,7 +313,7 @@ interface SkillsShVariant extends RegistryCardBase {
|
|
|
310
313
|
variant: 'skills-sh'
|
|
311
314
|
author: string
|
|
312
315
|
installs?: number
|
|
313
|
-
installedScopes?: ReadonlySet<
|
|
316
|
+
installedScopes?: ReadonlySet<CapabilitySource>
|
|
314
317
|
viewScope?: 'user' | 'project'
|
|
315
318
|
onRemove?: () => void
|
|
316
319
|
/** All tool keys to show in bottom row */
|
|
@@ -324,7 +327,9 @@ export type RegistryCardProps = SeedrVariant | ClaudePluginsVariant | AitmplVari
|
|
|
324
327
|
// ── Main Component ────────────────────────────────────────────────────────────
|
|
325
328
|
|
|
326
329
|
export function RegistryCard(props: RegistryCardProps) {
|
|
327
|
-
const { onClick, flash, name, type, description, errorMessage, isInstalling, updatedAt, onDateClick } = props
|
|
330
|
+
const { onClick, flash, name, type, description, errorMessage, isInstalling, updatedAt, onDateClick, accentColor: accentColorProp } = props
|
|
331
|
+
const contextAccent = useAccentColor()
|
|
332
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
328
333
|
const [showScopeConfirm, setShowScopeConfirm] = useState(false)
|
|
329
334
|
|
|
330
335
|
const TypeIcon = TYPE_ICONS[type] || TYPE_ICONS.extension
|
|
@@ -363,7 +368,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
363
368
|
icon={<Power className="w-3.5 h-3.5" />}
|
|
364
369
|
onClick={() => props.onActivate?.()}
|
|
365
370
|
size="sm"
|
|
366
|
-
|
|
371
|
+
accentColor="green"
|
|
367
372
|
tooltip={{ description: `Activate ${name}` }}
|
|
368
373
|
/>
|
|
369
374
|
) : isInstalledInViewScope && removeScope ? (
|
|
@@ -371,7 +376,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
371
376
|
icon={<Trash2 className="w-3.5 h-3.5" />}
|
|
372
377
|
onClick={() => props.onRemove?.(removeScope)}
|
|
373
378
|
size="sm"
|
|
374
|
-
|
|
379
|
+
accentColor="red"
|
|
375
380
|
tooltip={{ description: `Remove ${name} from ${removeScope}` }}
|
|
376
381
|
/>
|
|
377
382
|
) : installedElsewhere ? (
|
|
@@ -379,7 +384,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
379
384
|
icon={<FolderPlus className="w-3.5 h-3.5" />}
|
|
380
385
|
onClick={() => setShowScopeConfirm(true)}
|
|
381
386
|
size="sm"
|
|
382
|
-
|
|
387
|
+
accentColor="blue"
|
|
383
388
|
tooltip={{ description: ALREADY_AT_USER }}
|
|
384
389
|
/>
|
|
385
390
|
) : (
|
|
@@ -387,14 +392,14 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
387
392
|
icon={<Plus className="w-3.5 h-3.5" />}
|
|
388
393
|
onClick={props.onInstall}
|
|
389
394
|
size="sm"
|
|
390
|
-
|
|
395
|
+
accentColor="green"
|
|
391
396
|
tooltip={{ description: `Add ${name}` }}
|
|
392
397
|
/>
|
|
393
398
|
)
|
|
394
399
|
|
|
395
|
-
bottomLogos = props.compatibility.filter((t) => t in
|
|
396
|
-
<Tooltip key={tool} content={{ description: `Compatible with ${
|
|
397
|
-
<
|
|
400
|
+
bottomLogos = props.compatibility.filter((t) => t in CODING_AGENT_NAMES).map((tool) => (
|
|
401
|
+
<Tooltip key={tool} content={{ description: `Compatible with ${CODING_AGENT_NAMES[tool as CodingAgentKey] ?? tool}` }} position="top">
|
|
402
|
+
<CodingAgentIcon agent={tool} size={16} />
|
|
398
403
|
</Tooltip>
|
|
399
404
|
))
|
|
400
405
|
|
|
@@ -434,7 +439,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
434
439
|
icon={<Trash2 className="w-3.5 h-3.5" />}
|
|
435
440
|
onClick={() => props.onRemove?.()}
|
|
436
441
|
size="sm"
|
|
437
|
-
|
|
442
|
+
accentColor="red"
|
|
438
443
|
tooltip={{ description: `Remove ${name} from ${removeScope}` }}
|
|
439
444
|
/>
|
|
440
445
|
) : installedElsewhere ? (
|
|
@@ -442,7 +447,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
442
447
|
icon={<FolderPlus className="w-3.5 h-3.5" />}
|
|
443
448
|
onClick={() => setShowScopeConfirm(true)}
|
|
444
449
|
size="sm"
|
|
445
|
-
|
|
450
|
+
accentColor="blue"
|
|
446
451
|
tooltip={{ description: ALREADY_AT_USER }}
|
|
447
452
|
/>
|
|
448
453
|
) : (
|
|
@@ -450,15 +455,15 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
450
455
|
icon={<Plus className="w-3.5 h-3.5" />}
|
|
451
456
|
onClick={props.onInstall}
|
|
452
457
|
size="sm"
|
|
453
|
-
|
|
458
|
+
accentColor="green"
|
|
454
459
|
tooltip={{ description: `Add ${name}` }}
|
|
455
460
|
/>
|
|
456
461
|
)
|
|
457
462
|
|
|
458
463
|
const compatibleTools = props.compatibleTools ?? []
|
|
459
464
|
bottomLogos = compatibleTools.map((tool) => (
|
|
460
|
-
<Tooltip key={tool} content={{ description: `Compatible with ${
|
|
461
|
-
<
|
|
465
|
+
<Tooltip key={tool} content={{ description: `Compatible with ${CODING_AGENT_NAMES[tool as CodingAgentKey] ?? tool}` }} position="top">
|
|
466
|
+
<CodingAgentIcon agent={tool} size={16} />
|
|
462
467
|
</Tooltip>
|
|
463
468
|
))
|
|
464
469
|
bottomStats = [
|
|
@@ -502,7 +507,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
502
507
|
icon={<Trash2 className="w-3.5 h-3.5" />}
|
|
503
508
|
onClick={() => props.onRemove?.()}
|
|
504
509
|
size="sm"
|
|
505
|
-
|
|
510
|
+
accentColor="red"
|
|
506
511
|
tooltip={{ description: `Remove ${name} from ${removeScope}` }}
|
|
507
512
|
/>
|
|
508
513
|
) : installedElsewhere ? (
|
|
@@ -510,7 +515,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
510
515
|
icon={<FolderPlus className="w-3.5 h-3.5" />}
|
|
511
516
|
onClick={() => setShowScopeConfirm(true)}
|
|
512
517
|
size="sm"
|
|
513
|
-
|
|
518
|
+
accentColor="blue"
|
|
514
519
|
tooltip={{ description: ALREADY_AT_USER }}
|
|
515
520
|
/>
|
|
516
521
|
) : (
|
|
@@ -518,14 +523,14 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
518
523
|
icon={<Plus className="w-3.5 h-3.5" />}
|
|
519
524
|
onClick={props.onInstall}
|
|
520
525
|
size="sm"
|
|
521
|
-
|
|
526
|
+
accentColor="green"
|
|
522
527
|
tooltip={{ description: `Add ${name}` }}
|
|
523
528
|
/>
|
|
524
529
|
)
|
|
525
530
|
|
|
526
531
|
bottomLogos = [(
|
|
527
532
|
<Tooltip key="claude" content={{ description: 'Compatible with Claude Code' }} position="top">
|
|
528
|
-
<
|
|
533
|
+
<CodingAgentIcon agent="claude" size={16} />
|
|
529
534
|
</Tooltip>
|
|
530
535
|
)]
|
|
531
536
|
} else if (props.variant === 'skills-sh') {
|
|
@@ -547,7 +552,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
547
552
|
icon={<Trash2 className="w-3.5 h-3.5" />}
|
|
548
553
|
onClick={() => props.onRemove?.()}
|
|
549
554
|
size="sm"
|
|
550
|
-
|
|
555
|
+
accentColor="red"
|
|
551
556
|
tooltip={{ description: `Remove ${name} from ${removeScope}` }}
|
|
552
557
|
/>
|
|
553
558
|
) : installedElsewhere ? (
|
|
@@ -555,7 +560,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
555
560
|
icon={<FolderPlus className="w-3.5 h-3.5" />}
|
|
556
561
|
onClick={() => setShowScopeConfirm(true)}
|
|
557
562
|
size="sm"
|
|
558
|
-
|
|
563
|
+
accentColor="blue"
|
|
559
564
|
tooltip={{ description: ALREADY_AT_USER }}
|
|
560
565
|
/>
|
|
561
566
|
) : (
|
|
@@ -563,15 +568,15 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
563
568
|
icon={<Plus className="w-3.5 h-3.5" />}
|
|
564
569
|
onClick={props.onInstall}
|
|
565
570
|
size="sm"
|
|
566
|
-
|
|
571
|
+
accentColor="green"
|
|
567
572
|
tooltip={{ description: `Add ${name}` }}
|
|
568
573
|
/>
|
|
569
574
|
)
|
|
570
575
|
|
|
571
576
|
const allTools = props.allToolKeys ?? []
|
|
572
577
|
bottomLogos = allTools.map((tool) => (
|
|
573
|
-
<Tooltip key={tool} content={{ description: `Compatible with ${
|
|
574
|
-
<
|
|
578
|
+
<Tooltip key={tool} content={{ description: `Compatible with ${CODING_AGENT_NAMES[tool as CodingAgentKey] ?? tool}` }} position="top">
|
|
579
|
+
<CodingAgentIcon agent={tool} size={14} />
|
|
575
580
|
</Tooltip>
|
|
576
581
|
))
|
|
577
582
|
if (props.installs != null && props.installs > 0) {
|
|
@@ -597,10 +602,11 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
597
602
|
: 'border-neutral-700'
|
|
598
603
|
|
|
599
604
|
return (
|
|
605
|
+
<AccentColorProvider value={effectiveColor}>
|
|
600
606
|
<div
|
|
601
607
|
role="button"
|
|
602
608
|
tabIndex={0}
|
|
603
|
-
className={`group text-left w-full cursor-pointer border rounded-lg p-3 transition-all duration-500 hover:border-neutral-600 flex flex-col bg-neutral-
|
|
609
|
+
className={`group text-left w-full cursor-pointer border rounded-lg p-3 transition-all duration-500 hover:border-neutral-600 flex flex-col bg-neutral-960/50 hover:bg-neutral-960 ${borderClass}`}
|
|
604
610
|
onClick={onClick}
|
|
605
611
|
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick() } }}
|
|
606
612
|
>
|
|
@@ -614,7 +620,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
614
620
|
<Loader2 className="w-4 h-4 text-blue-400 animate-spin" />
|
|
615
621
|
) : (
|
|
616
622
|
<>
|
|
617
|
-
<Tooltip content={{ description: `${typeLabel}
|
|
623
|
+
<Tooltip content={{ description: `${typeLabel} capability` }} position="top">
|
|
618
624
|
<TypeIcon className={`w-4 h-4 group-hover:opacity-0 transition-opacity ${typeIconColor}`} />
|
|
619
625
|
</Tooltip>
|
|
620
626
|
{installButton && (
|
|
@@ -687,7 +693,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
687
693
|
return (
|
|
688
694
|
<div onClick={(e) => e.stopPropagation()}>
|
|
689
695
|
<div className="fixed inset-0 bg-[var(--background)]/50 z-50 flex items-center justify-center" onClick={() => setShowScopeConfirm(false)}>
|
|
690
|
-
<div className="bg-neutral-
|
|
696
|
+
<div className="bg-neutral-960 border border-neutral-700 rounded-lg p-4 max-w-sm" onClick={(e) => e.stopPropagation()}>
|
|
691
697
|
<h3 className="text-md font-medium text-neutral-200 mb-2">{ALREADY_AT_USER}</h3>
|
|
692
698
|
<p className="text-sm text-neutral-400 mb-4">
|
|
693
699
|
<strong className="text-neutral-300">{name}</strong> is already installed at user level and available to all projects. Do you also want to install it at project level?
|
|
@@ -702,5 +708,6 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
702
708
|
)
|
|
703
709
|
})()}
|
|
704
710
|
</div>
|
|
711
|
+
</AccentColorProvider>
|
|
705
712
|
)
|
|
706
713
|
}
|
|
@@ -2,8 +2,10 @@ import { type ReactNode, useState, useRef, useEffect, useCallback } from 'react'
|
|
|
2
2
|
import { ChevronsUpDown, ChevronsDownUp } from 'lucide-react'
|
|
3
3
|
import { IconButton } from './icon-button.tsx'
|
|
4
4
|
import { Label, type LabelProps } from './label.tsx'
|
|
5
|
-
import {
|
|
5
|
+
import { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey } from '../lib/coding-agents.tsx'
|
|
6
6
|
import { FileStructureSection, type FileStructureSectionProps } from './file-structure-section.tsx'
|
|
7
|
+
import { type AccentColor } from '../lib/form-colors.ts'
|
|
8
|
+
import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
|
|
7
9
|
import type { LucideIcon } from 'lucide-react'
|
|
8
10
|
|
|
9
11
|
export interface RegistryDetailProps {
|
|
@@ -35,6 +37,12 @@ export interface RegistryDetailProps {
|
|
|
35
37
|
// Above header (e.g. badge row)
|
|
36
38
|
aboveHeader?: ReactNode
|
|
37
39
|
|
|
40
|
+
// Below header (e.g. status messages)
|
|
41
|
+
belowHeader?: ReactNode
|
|
42
|
+
|
|
43
|
+
// Accent color for internal buttons and file structure
|
|
44
|
+
accentColor?: AccentColor
|
|
45
|
+
|
|
38
46
|
// Max width class
|
|
39
47
|
maxWidth?: string
|
|
40
48
|
|
|
@@ -42,7 +50,7 @@ export interface RegistryDetailProps {
|
|
|
42
50
|
children?: ReactNode
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
const MARKDOWN_CLASSES = 'text-md text-neutral-400 leading-relaxed [&_strong]:text-neutral-200 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:bg-neutral-700/40 [&_code]:border [&_code]:border-neutral-500/40 [&_code]:text-neutral-200 [&_code]:font-mono [&_code]:text-sm [&_h1]:text-lg [&_h1]:font-semibold [&_h1]:text-neutral-200 [&_h1]:mb-2 [&_h2]:text-base [&_h2]:font-semibold [&_h2]:text-neutral-200 [&_h2]:mb-2 [&_h3]:text-md [&_h3]:font-medium [&_h3]:text-neutral-200 [&_h3]:mb-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:mb-1 [&_p]:mb-2 [&_pre]:bg-neutral-
|
|
53
|
+
const MARKDOWN_CLASSES = 'text-md text-neutral-400 leading-relaxed [&_strong]:text-neutral-200 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:bg-neutral-700/40 [&_code]:border [&_code]:border-neutral-500/40 [&_code]:text-neutral-200 [&_code]:font-mono [&_code]:text-sm [&_h1]:text-lg [&_h1]:font-semibold [&_h1]:text-neutral-200 [&_h1]:mb-2 [&_h2]:text-base [&_h2]:font-semibold [&_h2]:text-neutral-200 [&_h2]:mb-2 [&_h3]:text-md [&_h3]:font-medium [&_h3]:text-neutral-200 [&_h3]:mb-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:mb-1 [&_p]:mb-2 [&_pre]:bg-neutral-980 [&_pre]:rounded [&_pre]:p-3 [&_pre]:overflow-x-auto [&_pre]:text-sm'
|
|
46
54
|
|
|
47
55
|
// ── CollapsibleSection ────────────────────────────────────────────────────────
|
|
48
56
|
|
|
@@ -103,7 +111,7 @@ function CollapsibleTextSection({ children, header }: { children: string; header
|
|
|
103
111
|
</div>
|
|
104
112
|
|
|
105
113
|
{showCollapsed && (
|
|
106
|
-
<div className="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-neutral-
|
|
114
|
+
<div className="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-neutral-960 to-transparent pointer-events-none" />
|
|
107
115
|
)}
|
|
108
116
|
</div>
|
|
109
117
|
</div>
|
|
@@ -112,17 +120,17 @@ function CollapsibleTextSection({ children, header }: { children: string; header
|
|
|
112
120
|
|
|
113
121
|
// ── CompatibleWithSection ─────────────────────────────────────────────────────
|
|
114
122
|
|
|
115
|
-
function CompatibleWithSection({
|
|
116
|
-
if (
|
|
123
|
+
function CompatibleWithSection({ agents }: { agents: string[] }) {
|
|
124
|
+
if (agents.length === 0) return null
|
|
117
125
|
|
|
118
126
|
return (
|
|
119
127
|
<div>
|
|
120
128
|
<h3 className="text-sm font-medium text-neutral-500 uppercase tracking-wider mb-3">Compatible with</h3>
|
|
121
129
|
<div className="flex items-start gap-3">
|
|
122
|
-
{
|
|
123
|
-
<div key={
|
|
124
|
-
<
|
|
125
|
-
<span className="text-xs text-neutral-400">{
|
|
130
|
+
{agents.map((agent) => (
|
|
131
|
+
<div key={agent} className="flex flex-col items-center gap-1">
|
|
132
|
+
<CodingAgentIcon agent={agent} size={18} />
|
|
133
|
+
<span className="text-xs text-neutral-400">{CODING_AGENT_NAMES[agent as CodingAgentKey] ?? agent}</span>
|
|
126
134
|
</div>
|
|
127
135
|
))}
|
|
128
136
|
</div>
|
|
@@ -149,10 +157,15 @@ export function RegistryDetail({
|
|
|
149
157
|
fileStructureVariant,
|
|
150
158
|
onFetchContent,
|
|
151
159
|
aboveHeader,
|
|
160
|
+
belowHeader,
|
|
161
|
+
accentColor,
|
|
152
162
|
maxWidth = 'max-w-[1440px]',
|
|
153
163
|
children,
|
|
154
164
|
}: RegistryDetailProps) {
|
|
155
|
-
|
|
165
|
+
const contextAccent = useAccentColor()
|
|
166
|
+
const resolvedAccent = accentColor ?? contextAccent
|
|
167
|
+
|
|
168
|
+
const content = (
|
|
156
169
|
<div className="h-full overflow-y-auto p-6">
|
|
157
170
|
<div className={`${maxWidth} mx-auto space-y-6`}>
|
|
158
171
|
{/* Above header badges */}
|
|
@@ -178,6 +191,9 @@ export function RegistryDetail({
|
|
|
178
191
|
)}
|
|
179
192
|
</div>
|
|
180
193
|
|
|
194
|
+
{/* Below header (status messages, etc.) */}
|
|
195
|
+
{belowHeader}
|
|
196
|
+
|
|
181
197
|
{/* Description */}
|
|
182
198
|
{description && (
|
|
183
199
|
<div>
|
|
@@ -206,7 +222,7 @@ export function RegistryDetail({
|
|
|
206
222
|
|
|
207
223
|
{/* Compatible with */}
|
|
208
224
|
{compatibleTools && compatibleTools.length > 0 && (
|
|
209
|
-
<CompatibleWithSection
|
|
225
|
+
<CompatibleWithSection agents={compatibleTools} />
|
|
210
226
|
)}
|
|
211
227
|
|
|
212
228
|
{/* File structure */}
|
|
@@ -216,6 +232,7 @@ export function RegistryDetail({
|
|
|
216
232
|
files={files}
|
|
217
233
|
rootName={rootName}
|
|
218
234
|
onFetchContent={onFetchContent}
|
|
235
|
+
accentColor={resolvedAccent}
|
|
219
236
|
/>
|
|
220
237
|
)}
|
|
221
238
|
|
|
@@ -224,4 +241,10 @@ export function RegistryDetail({
|
|
|
224
241
|
</div>
|
|
225
242
|
</div>
|
|
226
243
|
)
|
|
244
|
+
|
|
245
|
+
if (resolvedAccent) {
|
|
246
|
+
return <AccentColorProvider value={resolvedAccent}>{content}</AccentColorProvider>
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return content
|
|
227
250
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from 'react'
|
|
2
2
|
import Editor from '@monaco-editor/react'
|
|
3
3
|
import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
|
|
4
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
4
5
|
|
|
5
6
|
const variantClasses = {
|
|
6
|
-
filled: 'bg-neutral-
|
|
7
|
+
filled: 'bg-neutral-960 border text-neutral-200 placeholder-neutral-500',
|
|
7
8
|
outline: 'bg-transparent border text-neutral-200 placeholder-neutral-500',
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -11,7 +12,7 @@ interface ResizableTextareaBaseProps {
|
|
|
11
12
|
wrapperClassName?: string
|
|
12
13
|
resizable?: boolean
|
|
13
14
|
variant?: 'filled' | 'outline'
|
|
14
|
-
|
|
15
|
+
accentColor?: FormColor
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
interface ResizableTextareaFieldProps
|
|
@@ -72,8 +73,8 @@ export function ResizableTextarea(props: ResizableTextareaProps) {
|
|
|
72
73
|
return <ResizableChildren {...props} />
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
const { wrapperClassName, resizable = true, variant,
|
|
76
|
-
return <ResizableField wrapperClassName={wrapperClassName} resizable={resizable} variant={variant}
|
|
76
|
+
const { wrapperClassName, resizable = true, variant, accentColor, ...rest } = props
|
|
77
|
+
return <ResizableField wrapperClassName={wrapperClassName} resizable={resizable} variant={variant} accentColor={accentColor} {...rest} />
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// ---------------------------------------------------------------------------
|
|
@@ -119,7 +120,7 @@ const MONACO_THEME_PREFIX = 'resizable-textarea'
|
|
|
119
120
|
const registeredThemes = new Set<string>()
|
|
120
121
|
|
|
121
122
|
const wrapperVariantClasses = {
|
|
122
|
-
filled: 'bg-neutral-
|
|
123
|
+
filled: 'bg-neutral-960 border rounded-lg overflow-hidden',
|
|
123
124
|
outline: 'bg-transparent border rounded-lg overflow-hidden',
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -129,17 +130,19 @@ function ResizableCode({
|
|
|
129
130
|
language = 'plaintext',
|
|
130
131
|
readOnly = false,
|
|
131
132
|
variant = 'outline',
|
|
132
|
-
|
|
133
|
+
accentColor,
|
|
133
134
|
resizable = true,
|
|
134
135
|
wrapperClassName,
|
|
135
136
|
minHeight = 80,
|
|
136
137
|
onHeightChange,
|
|
137
138
|
}: ResizableTextareaCodeProps) {
|
|
139
|
+
const contextAccent = useAccentColor()
|
|
140
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
138
141
|
const { height, onResizeStart } = useResize(minHeight, onHeightChange)
|
|
139
142
|
|
|
140
143
|
return (
|
|
141
144
|
<div
|
|
142
|
-
className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[
|
|
145
|
+
className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[effectiveColor].border} ${wrapperClassName ?? ''}`}
|
|
143
146
|
data-resizable-wrapper
|
|
144
147
|
style={{ height: height ?? minHeight }}
|
|
145
148
|
>
|
|
@@ -208,10 +211,12 @@ function ResizableChildren({
|
|
|
208
211
|
wrapperClassName,
|
|
209
212
|
resizable = true,
|
|
210
213
|
variant = 'outline',
|
|
211
|
-
|
|
214
|
+
accentColor,
|
|
212
215
|
minHeight = 40,
|
|
213
216
|
onHeightChange,
|
|
214
217
|
}: ResizableTextareaChildrenProps) {
|
|
218
|
+
const contextAccent = useAccentColor()
|
|
219
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
215
220
|
const { height, onResizeStart } = useResize(minHeight, onHeightChange)
|
|
216
221
|
|
|
217
222
|
if (!resizable) {
|
|
@@ -220,7 +225,7 @@ function ResizableChildren({
|
|
|
220
225
|
|
|
221
226
|
return (
|
|
222
227
|
<div
|
|
223
|
-
className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[
|
|
228
|
+
className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[effectiveColor].border} ${wrapperClassName ?? ''}`}
|
|
224
229
|
data-resizable-wrapper
|
|
225
230
|
style={height != null ? { height } : undefined}
|
|
226
231
|
>
|
|
@@ -243,12 +248,14 @@ function ResizableField({
|
|
|
243
248
|
wrapperClassName,
|
|
244
249
|
resizable = true,
|
|
245
250
|
variant = 'outline',
|
|
246
|
-
|
|
251
|
+
accentColor,
|
|
247
252
|
...props
|
|
248
253
|
}: ResizableTextareaBaseProps & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'color'>) {
|
|
254
|
+
const contextAccent = useAccentColor()
|
|
255
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
249
256
|
const { height, onResizeStart } = useResize(40)
|
|
250
257
|
|
|
251
|
-
const className = `w-full rounded-lg focus:outline-none transition-colors ${variantClasses[variant]} ${FORM_COLORS[
|
|
258
|
+
const className = `w-full rounded-lg focus:outline-none transition-colors ${variantClasses[variant]} ${FORM_COLORS[effectiveColor].border} ${FORM_COLORS[effectiveColor].focus} ${props.className ?? ''}`
|
|
252
259
|
|
|
253
260
|
if (!resizable) {
|
|
254
261
|
return <textarea {...props} className={className} style={{ resize: 'none', ...props.style }} />
|