@toolr/ui-design 0.1.5 → 0.1.7
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/agent-rules.json +91 -0
- package/ai-manifest.json +190 -0
- package/components/content/info-panel-primitives.tsx +14 -14
- package/components/hooks/use-click-outside.ts +10 -3
- package/components/hooks/use-modal-behavior.ts +24 -0
- package/components/hooks/use-navigation-history.ts +7 -2
- package/components/hooks/use-resizable-sidebar.ts +38 -0
- package/components/lib/ai-tools.tsx +1 -1
- package/components/lib/form-colors.ts +40 -0
- package/components/sections/ai-tools-paths/tools-paths-panel.tsx +7 -7
- package/components/sections/captured-issues/captured-issues-panel.tsx +13 -13
- package/components/sections/captured-issues/use-captured-issues.ts +9 -3
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +13 -13
- package/components/sections/golden-snapshots/golden-sync-panel.tsx +5 -5
- package/components/sections/golden-snapshots/snapshot-manager.tsx +11 -11
- package/components/sections/golden-snapshots/status-overview.tsx +20 -20
- package/components/sections/golden-snapshots/version-manager.tsx +8 -8
- package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +8 -44
- package/components/sections/prompt-editor/index.ts +0 -7
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +9 -45
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +11 -43
- package/components/sections/report-bug/report-bug-form.tsx +14 -14
- package/components/sections/report-bug/screenshot-uploader.tsx +6 -6
- package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +3 -3
- package/components/sections/snapshot-browser/snapshot-tree.tsx +8 -8
- package/components/sections/snippets-editor/snippets-editor.tsx +74 -48
- package/components/settings/SettingsHeader.tsx +1 -2
- package/components/settings/SettingsTreeNav.tsx +31 -16
- package/components/ui/action-dialog.tsx +12 -56
- package/components/ui/badge.tsx +8 -24
- package/components/ui/bottom-panel-header.tsx +4 -4
- package/components/ui/breadcrumb.tsx +8 -68
- package/components/ui/checkbox.tsx +2 -16
- package/components/ui/collapsible-section.tsx +4 -42
- package/components/ui/confirm-badge.tsx +3 -20
- package/components/ui/cookie-consent.tsx +21 -5
- package/components/ui/debounce-border-overlay.tsx +31 -0
- package/components/ui/detail-section.tsx +5 -22
- package/components/ui/editor-placeholder-card.tsx +17 -16
- package/components/ui/editor-toolbar.tsx +12 -0
- package/components/ui/execution-details-panel.tsx +8 -13
- package/components/ui/extension-list-card.tsx +3 -3
- package/components/ui/file-structure-section.tsx +20 -35
- package/components/ui/file-tree.tsx +4 -14
- package/components/ui/files-panel.tsx +28 -18
- package/components/ui/filter-dropdown.tsx +5 -5
- package/components/ui/form-actions.tsx +7 -6
- package/components/ui/frontmatter-form-header.tsx +4 -4
- package/components/ui/icon-button.tsx +3 -2
- package/components/ui/input.tsx +15 -31
- package/components/ui/label.tsx +7 -21
- package/components/ui/layout-tab-bar.tsx +4 -4
- package/components/ui/modal.tsx +5 -17
- package/components/ui/nav-card.tsx +5 -20
- package/components/ui/navigation-bar.tsx +13 -74
- package/components/ui/number-input.tsx +4 -4
- package/components/ui/registry-browser.tsx +10 -24
- package/components/ui/registry-card.tsx +16 -20
- package/components/ui/registry-detail.tsx +6 -6
- package/components/ui/resizable-textarea.tsx +13 -35
- package/components/ui/segmented-toggle.tsx +6 -5
- package/components/ui/select.tsx +7 -16
- package/components/ui/selection-grid.tsx +6 -54
- package/components/ui/setting-row.tsx +2 -4
- package/components/ui/settings-card.tsx +3 -3
- package/components/ui/settings-info-box.tsx +6 -23
- package/components/ui/settings-section-title.tsx +1 -1
- package/components/ui/snapshot-card.tsx +7 -7
- package/components/ui/snippets-panel.tsx +10 -10
- package/components/ui/sort-dropdown.tsx +2 -2
- package/components/ui/status-card.tsx +6 -17
- package/components/ui/tab-bar.tsx +5 -31
- package/components/ui/toggle.tsx +3 -19
- package/components/ui/tooltip.tsx +9 -21
- package/dist/content.js +14 -14
- package/dist/index.d.ts +71 -141
- package/dist/index.js +1634 -2450
- package/dist/tokens/primitives.css +9 -2
- package/index.ts +8 -7
- package/package.json +13 -3
- package/tokens/primitives.css +9 -2
- package/components/sections/prompt-editor/use-prompt-editor.ts +0 -131
|
@@ -1,53 +1,10 @@
|
|
|
1
1
|
/** Breadcrumb navigation with clickable segments, color-coded icons, and configurable separators. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Globe, Star, Users, User, Tag, Search, Heart,
|
|
7
|
-
Zap, Shield, ShieldCheck, Sparkles, Eye, Lock,
|
|
8
|
-
Cloud, Wand2, Bell, Bookmark, Pin, Mail, Send,
|
|
9
|
-
Image, Bot, Puzzle, Plug, Webhook, House, Package,
|
|
10
|
-
} from 'lucide-react'
|
|
11
|
-
import type { LucideIcon } from 'lucide-react'
|
|
12
|
-
import type { IconName } from './icon-button.tsx'
|
|
3
|
+
import { ChevronRight } from 'lucide-react'
|
|
4
|
+
import { iconMap, type IconName } from './icon-button.tsx'
|
|
5
|
+
import { ACCENT_NAV, type AccentColor } from '../lib/form-colors.ts'
|
|
13
6
|
import { cn } from '../lib/cn.ts'
|
|
14
7
|
|
|
15
|
-
const iconSubset: Partial<Record<IconName, LucideIcon>> = {
|
|
16
|
-
folder: Folder,
|
|
17
|
-
file: File,
|
|
18
|
-
settings: Settings,
|
|
19
|
-
code: Code,
|
|
20
|
-
terminal: Terminal,
|
|
21
|
-
database: Database,
|
|
22
|
-
globe: Globe,
|
|
23
|
-
star: Star,
|
|
24
|
-
users: Users,
|
|
25
|
-
user: User,
|
|
26
|
-
tag: Tag,
|
|
27
|
-
zap: Zap,
|
|
28
|
-
shield: Shield,
|
|
29
|
-
'shield-check': ShieldCheck,
|
|
30
|
-
sparkles: Sparkles,
|
|
31
|
-
eye: Eye,
|
|
32
|
-
lock: Lock,
|
|
33
|
-
search: Search,
|
|
34
|
-
heart: Heart,
|
|
35
|
-
cloud: Cloud,
|
|
36
|
-
wand: Wand2,
|
|
37
|
-
bell: Bell,
|
|
38
|
-
bookmark: Bookmark,
|
|
39
|
-
pin: Pin,
|
|
40
|
-
mail: Mail,
|
|
41
|
-
send: Send,
|
|
42
|
-
image: Image,
|
|
43
|
-
bot: Bot,
|
|
44
|
-
puzzle: Puzzle,
|
|
45
|
-
plug: Plug,
|
|
46
|
-
webhook: Webhook,
|
|
47
|
-
home: House,
|
|
48
|
-
'package': Package,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
8
|
export interface BreadcrumbSegment {
|
|
52
9
|
id: string
|
|
53
10
|
label: string
|
|
@@ -67,35 +24,18 @@ export interface BreadcrumbProps {
|
|
|
67
24
|
|
|
68
25
|
const sizeConfig = {
|
|
69
26
|
xss: { text: 'text-xss', icon: 'w-2.5 h-2.5', px: 'px-1', py: 'py-0.5', gap: 'gap-0.5', sep: 'w-2 h-2' },
|
|
70
|
-
xs: { text: 'text-
|
|
71
|
-
sm: { text: 'text-
|
|
27
|
+
xs: { text: 'text-sm', icon: 'w-3 h-3', px: 'px-1.5', py: 'py-0.5', gap: 'gap-1', sep: 'w-2.5 h-2.5' },
|
|
28
|
+
sm: { text: 'text-md', icon: 'w-3.5 h-3.5', px: 'px-2', py: 'py-1', gap: 'gap-1.5', sep: 'w-3 h-3' },
|
|
72
29
|
md: { text: 'text-base', icon: 'w-4 h-4', px: 'px-2.5', py: 'py-1', gap: 'gap-1.5', sep: 'w-3.5 h-3.5' },
|
|
73
30
|
lg: { text: 'text-lg', icon: 'w-5 h-5', px: 'px-3', py: 'py-1.5', gap: 'gap-2', sep: 'w-4 h-4' },
|
|
74
31
|
}
|
|
75
32
|
|
|
76
|
-
const colorMap: Record<string, { bg: string; text: string }> = {
|
|
77
|
-
blue: { bg: 'bg-blue-500/10', text: 'text-blue-400' },
|
|
78
|
-
green: { bg: 'bg-green-500/10', text: 'text-green-400' },
|
|
79
|
-
purple: { bg: 'bg-purple-500/10', text: 'text-purple-400' },
|
|
80
|
-
red: { bg: 'bg-red-500/10', text: 'text-red-400' },
|
|
81
|
-
orange: { bg: 'bg-orange-500/10', text: 'text-orange-400' },
|
|
82
|
-
cyan: { bg: 'bg-cyan-500/10', text: 'text-cyan-400' },
|
|
83
|
-
yellow: { bg: 'bg-yellow-500/10', text: 'text-yellow-400' },
|
|
84
|
-
amber: { bg: 'bg-amber-500/10', text: 'text-amber-400' },
|
|
85
|
-
emerald: { bg: 'bg-emerald-500/10', text: 'text-emerald-400' },
|
|
86
|
-
indigo: { bg: 'bg-indigo-500/10', text: 'text-indigo-400' },
|
|
87
|
-
violet: { bg: 'bg-violet-500/10', text: 'text-violet-400' },
|
|
88
|
-
sky: { bg: 'bg-sky-500/10', text: 'text-sky-400' },
|
|
89
|
-
pink: { bg: 'bg-pink-500/10', text: 'text-pink-400' },
|
|
90
|
-
teal: { bg: 'bg-teal-500/10', text: 'text-teal-400' },
|
|
91
|
-
neutral: { bg: 'bg-neutral-500/10', text: 'text-neutral-400' },
|
|
92
|
-
}
|
|
93
33
|
|
|
94
34
|
function SegmentIcon({ icon, color, size }: { icon: IconName; color?: string; size: 'xss' | 'xs' | 'sm' | 'md' | 'lg' }) {
|
|
95
|
-
const Icon =
|
|
35
|
+
const Icon = iconMap[icon]
|
|
96
36
|
if (!Icon) return null
|
|
97
37
|
const s = sizeConfig[size]
|
|
98
|
-
const c = color &&
|
|
38
|
+
const c = color && ACCENT_NAV[color as AccentColor] ? ACCENT_NAV[color as AccentColor] : null
|
|
99
39
|
|
|
100
40
|
return (
|
|
101
41
|
<span className={c?.text || ''}>
|
|
@@ -135,7 +75,7 @@ export function Breadcrumb({
|
|
|
135
75
|
{segments.map((segment, index) => {
|
|
136
76
|
const isLast = index === segments.length - 1
|
|
137
77
|
const isClickable = !isLast && !!segment.onClick
|
|
138
|
-
const colors = segment.color &&
|
|
78
|
+
const colors = segment.color && ACCENT_NAV[segment.color as AccentColor] ? ACCENT_NAV[segment.color as AccentColor] : null
|
|
139
79
|
const isFirstPlain = !isBox && index === 0
|
|
140
80
|
|
|
141
81
|
return (
|
|
@@ -8,25 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Check } from 'lucide-react'
|
|
11
|
+
import { type AccentColor } from '../lib/form-colors.ts'
|
|
11
12
|
|
|
12
13
|
export type CheckboxSize = 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
13
14
|
|
|
14
|
-
export type CheckboxColor =
|
|
15
|
-
| 'blue'
|
|
16
|
-
| 'green'
|
|
17
|
-
| 'red'
|
|
18
|
-
| 'orange'
|
|
19
|
-
| 'cyan'
|
|
20
|
-
| 'yellow'
|
|
21
|
-
| 'purple'
|
|
22
|
-
| 'indigo'
|
|
23
|
-
| 'emerald'
|
|
24
|
-
| 'amber'
|
|
25
|
-
| 'violet'
|
|
26
|
-
| 'neutral'
|
|
27
|
-
| 'sky'
|
|
28
|
-
| 'pink'
|
|
29
|
-
| 'teal'
|
|
15
|
+
export type CheckboxColor = AccentColor
|
|
30
16
|
|
|
31
17
|
const CHECKBOX_COLORS: Record<CheckboxColor, { bg: string; border: string; icon: string; hover: string }> = {
|
|
32
18
|
blue: { bg: 'bg-blue-500/20', border: 'border-blue-500/40', icon: 'text-blue-300', hover: 'hover:bg-blue-500/15 hover:border-blue-500/30' },
|
|
@@ -1,47 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
Star, Heart, Bell, Bookmark, Tag, Pin, Mail, Globe, Cloud,
|
|
5
|
-
Shield, Zap, Sparkles, Search, Filter, Eye, Lock, User, Users,
|
|
6
|
-
File, Image, Download, Upload, Play, Pause,
|
|
7
|
-
} from 'lucide-react'
|
|
8
|
-
import type { LucideIcon } from 'lucide-react'
|
|
9
|
-
import type { IconName } from './icon-button.tsx'
|
|
2
|
+
import { ChevronRight } from 'lucide-react'
|
|
3
|
+
import { iconMap, type IconName } from './icon-button.tsx'
|
|
10
4
|
import { Badge, type BadgeColor } from './badge.tsx'
|
|
11
5
|
import { cn } from '../lib/cn.ts'
|
|
12
6
|
|
|
13
|
-
const iconSubset: Partial<Record<IconName, LucideIcon>> = {
|
|
14
|
-
settings: Settings,
|
|
15
|
-
folder: Folder,
|
|
16
|
-
code: Code,
|
|
17
|
-
terminal: Terminal,
|
|
18
|
-
database: Database,
|
|
19
|
-
star: Star,
|
|
20
|
-
heart: Heart,
|
|
21
|
-
bell: Bell,
|
|
22
|
-
bookmark: Bookmark,
|
|
23
|
-
tag: Tag,
|
|
24
|
-
pin: Pin,
|
|
25
|
-
mail: Mail,
|
|
26
|
-
globe: Globe,
|
|
27
|
-
cloud: Cloud,
|
|
28
|
-
shield: Shield,
|
|
29
|
-
zap: Zap,
|
|
30
|
-
sparkles: Sparkles,
|
|
31
|
-
search: Search,
|
|
32
|
-
filter: Filter,
|
|
33
|
-
eye: Eye,
|
|
34
|
-
lock: Lock,
|
|
35
|
-
user: User,
|
|
36
|
-
users: Users,
|
|
37
|
-
file: File,
|
|
38
|
-
image: Image,
|
|
39
|
-
download: Download,
|
|
40
|
-
upload: Upload,
|
|
41
|
-
play: Play,
|
|
42
|
-
pause: Pause,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
7
|
export interface CollapsibleSectionProps {
|
|
46
8
|
title: string
|
|
47
9
|
icon?: IconName
|
|
@@ -64,7 +26,7 @@ export function CollapsibleSection({
|
|
|
64
26
|
className,
|
|
65
27
|
}: CollapsibleSectionProps) {
|
|
66
28
|
const [open, setOpen] = useState(defaultOpen)
|
|
67
|
-
const Icon = icon ?
|
|
29
|
+
const Icon = icon ? iconMap[icon] : undefined
|
|
68
30
|
|
|
69
31
|
return (
|
|
70
32
|
<div className={cn('border-b border-neutral-700', className)}>
|
|
@@ -87,7 +49,7 @@ export function CollapsibleSection({
|
|
|
87
49
|
<Icon className="w-3.5 h-3.5" />
|
|
88
50
|
</span>
|
|
89
51
|
)}
|
|
90
|
-
<span className="text-
|
|
52
|
+
<span className="text-md font-medium text-neutral-200">{title}</span>
|
|
91
53
|
{badge !== undefined && (
|
|
92
54
|
<span className="ml-auto">
|
|
93
55
|
<Badge value={badge} color={badgeColor} size="xss" />
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { Check } from 'lucide-react'
|
|
15
|
+
import { FORM_COLORS, type AccentColor } from '../lib/form-colors.ts'
|
|
15
16
|
|
|
16
|
-
export type ConfirmBadgeColor =
|
|
17
|
+
export type ConfirmBadgeColor = AccentColor
|
|
17
18
|
|
|
18
19
|
export interface ConfirmBadgeProps {
|
|
19
20
|
color?: ConfirmBadgeColor
|
|
@@ -22,24 +23,6 @@ export interface ConfirmBadgeProps {
|
|
|
22
23
|
testId?: string
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const colorClasses: Record<ConfirmBadgeColor, string> = {
|
|
26
|
-
green: 'border-green-500/30 text-green-400',
|
|
27
|
-
red: 'border-red-500/30 text-red-400',
|
|
28
|
-
blue: 'border-blue-500/30 text-blue-400',
|
|
29
|
-
orange: 'border-orange-500/30 text-orange-400',
|
|
30
|
-
cyan: 'border-cyan-500/30 text-cyan-400',
|
|
31
|
-
yellow: 'border-yellow-500/30 text-yellow-400',
|
|
32
|
-
purple: 'border-purple-500/30 text-purple-400',
|
|
33
|
-
indigo: 'border-indigo-500/30 text-indigo-400',
|
|
34
|
-
emerald: 'border-emerald-500/30 text-emerald-400',
|
|
35
|
-
amber: 'border-amber-500/30 text-amber-400',
|
|
36
|
-
violet: 'border-violet-500/30 text-violet-400',
|
|
37
|
-
neutral: 'border-neutral-500/30 text-neutral-400',
|
|
38
|
-
sky: 'border-sky-500/30 text-sky-400',
|
|
39
|
-
pink: 'border-pink-500/30 text-pink-400',
|
|
40
|
-
teal: 'border-teal-500/30 text-teal-400',
|
|
41
|
-
}
|
|
42
|
-
|
|
43
26
|
const sizeClasses = {
|
|
44
27
|
xss: 'w-[14px] h-[14px] rounded-full',
|
|
45
28
|
xs: 'w-[16px] h-[16px] rounded-full',
|
|
@@ -65,7 +48,7 @@ export function ConfirmBadge({
|
|
|
65
48
|
return (
|
|
66
49
|
<span
|
|
67
50
|
data-testid={testId}
|
|
68
|
-
className={`inline-flex items-center justify-center border ${
|
|
51
|
+
className={`inline-flex items-center justify-center border ${FORM_COLORS[color].border} ${FORM_COLORS[color].accent} ${sizeClasses[size]} ${className}`}
|
|
69
52
|
>
|
|
70
53
|
<Check className={iconSizeClasses[size]} />
|
|
71
54
|
</span>
|
|
@@ -16,6 +16,22 @@ export interface CookieConsentProps {
|
|
|
16
16
|
onConsent?: (choice: ConsentChoice) => void
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const ACCENT_BUTTON_CLASSES: Record<string, string> = {
|
|
20
|
+
cyan: 'bg-cyan-600 border-cyan-500 hover:bg-cyan-500',
|
|
21
|
+
blue: 'bg-blue-600 border-blue-500 hover:bg-blue-500',
|
|
22
|
+
green: 'bg-green-600 border-green-500 hover:bg-green-500',
|
|
23
|
+
purple: 'bg-purple-600 border-purple-500 hover:bg-purple-500',
|
|
24
|
+
orange: 'bg-orange-600 border-orange-500 hover:bg-orange-500',
|
|
25
|
+
red: 'bg-red-600 border-red-500 hover:bg-red-500',
|
|
26
|
+
amber: 'bg-amber-600 border-amber-500 hover:bg-amber-500',
|
|
27
|
+
emerald: 'bg-emerald-600 border-emerald-500 hover:bg-emerald-500',
|
|
28
|
+
indigo: 'bg-indigo-600 border-indigo-500 hover:bg-indigo-500',
|
|
29
|
+
pink: 'bg-pink-600 border-pink-500 hover:bg-pink-500',
|
|
30
|
+
teal: 'bg-teal-600 border-teal-500 hover:bg-teal-500',
|
|
31
|
+
violet: 'bg-violet-600 border-violet-500 hover:bg-violet-500',
|
|
32
|
+
sky: 'bg-sky-600 border-sky-500 hover:bg-sky-500',
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
export function CookieConsent({
|
|
20
36
|
storageKey,
|
|
21
37
|
accentColor = 'cyan',
|
|
@@ -42,26 +58,26 @@ export function CookieConsent({
|
|
|
42
58
|
<div className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-neutral-900/95 backdrop-blur-sm border-t border-neutral-700/50">
|
|
43
59
|
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row items-start sm:items-center gap-4">
|
|
44
60
|
<div className="flex-grow">
|
|
45
|
-
<p className="text-
|
|
46
|
-
<p className="text-
|
|
61
|
+
<p className="text-md text-neutral-200 mb-1">{heading}</p>
|
|
62
|
+
<p className="text-sm text-neutral-400">{description}</p>
|
|
47
63
|
</div>
|
|
48
64
|
|
|
49
65
|
<div className="flex items-center gap-2 flex-shrink-0">
|
|
50
66
|
<button
|
|
51
67
|
onClick={() => handleConsent('declined')}
|
|
52
|
-
className="px-3 py-1.5 text-
|
|
68
|
+
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-800 transition-colors"
|
|
53
69
|
>
|
|
54
70
|
Decline
|
|
55
71
|
</button>
|
|
56
72
|
<button
|
|
57
73
|
onClick={() => handleConsent('essential')}
|
|
58
|
-
className="px-3 py-1.5 text-
|
|
74
|
+
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-800 transition-colors"
|
|
59
75
|
>
|
|
60
76
|
Essential Only
|
|
61
77
|
</button>
|
|
62
78
|
<button
|
|
63
79
|
onClick={() => handleConsent('accepted')}
|
|
64
|
-
className={`px-3 py-1.5 text-
|
|
80
|
+
className={`px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-white border transition-colors ${ACCENT_BUTTON_CLASSES[accentColor] ?? ACCENT_BUTTON_CLASSES.cyan}`}
|
|
65
81
|
>
|
|
66
82
|
Accept All
|
|
67
83
|
</button>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface DebounceBorderOverlayProps {
|
|
2
|
+
debounceKey: number
|
|
3
|
+
durationMs: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function DebounceBorderOverlay({ debounceKey, durationMs }: DebounceBorderOverlayProps) {
|
|
7
|
+
if (debounceKey <= 0) return null
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<svg
|
|
11
|
+
key={debounceKey}
|
|
12
|
+
className="absolute inset-0 pointer-events-none text-emerald-400/70"
|
|
13
|
+
style={{ width: '100%', height: '100%' }}
|
|
14
|
+
>
|
|
15
|
+
<rect
|
|
16
|
+
x="1" y="1" rx="5" ry="5"
|
|
17
|
+
fill="none"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeWidth="1.5"
|
|
20
|
+
pathLength="100"
|
|
21
|
+
strokeDasharray="100"
|
|
22
|
+
strokeDashoffset="0"
|
|
23
|
+
style={{
|
|
24
|
+
width: 'calc(100% - 2px)',
|
|
25
|
+
height: 'calc(100% - 2px)',
|
|
26
|
+
animation: `debounce-border-drain ${durationMs}ms linear forwards`,
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -7,26 +7,9 @@
|
|
|
7
7
|
* - Any key-value metadata layout
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
Info, Settings, Code, Shield, Terminal, Database, Globe, Zap,
|
|
12
|
-
Star, Cloud, Bell, Heart, Sparkles, Bot, Plug, Lock, Eye,
|
|
13
|
-
File, Folder, User, Users, Tag, Bookmark, Mail, Send, Search,
|
|
14
|
-
Play, ShieldCheck, Wand2, Copy,
|
|
15
|
-
} from 'lucide-react'
|
|
16
|
-
import type { LucideIcon } from 'lucide-react'
|
|
17
|
-
import type { IconName } from './icon-button.tsx'
|
|
10
|
+
import { iconMap, type IconName } from './icon-button.tsx'
|
|
18
11
|
import { cn } from '../lib/cn.ts'
|
|
19
12
|
|
|
20
|
-
const iconSubset: Partial<Record<IconName, LucideIcon>> = {
|
|
21
|
-
info: Info, settings: Settings, code: Code, shield: Shield,
|
|
22
|
-
terminal: Terminal, database: Database, globe: Globe, zap: Zap,
|
|
23
|
-
star: Star, cloud: Cloud, bell: Bell, heart: Heart,
|
|
24
|
-
sparkles: Sparkles, bot: Bot, plug: Plug, lock: Lock, eye: Eye,
|
|
25
|
-
file: File, folder: Folder, user: User, users: Users, tag: Tag,
|
|
26
|
-
bookmark: Bookmark, mail: Mail, send: Send, search: Search,
|
|
27
|
-
play: Play, 'shield-check': ShieldCheck, wand: Wand2, copy: Copy,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
13
|
export interface DetailRow {
|
|
31
14
|
label: string
|
|
32
15
|
value: string
|
|
@@ -44,19 +27,19 @@ export interface DetailSectionProps {
|
|
|
44
27
|
}
|
|
45
28
|
|
|
46
29
|
export function DetailSection({ title, icon, rows, className }: DetailSectionProps) {
|
|
47
|
-
const Icon = icon ?
|
|
30
|
+
const Icon = icon ? iconMap[icon] : undefined
|
|
48
31
|
|
|
49
32
|
return (
|
|
50
33
|
<div className={className}>
|
|
51
34
|
<div className="flex items-center gap-2 mb-3">
|
|
52
35
|
{Icon && <Icon className="w-4 h-4 text-neutral-500" />}
|
|
53
|
-
<span className="text-
|
|
36
|
+
<span className="text-md font-medium text-neutral-400">{title}</span>
|
|
54
37
|
</div>
|
|
55
38
|
<div className="space-y-2">
|
|
56
39
|
{rows.map((row) => (
|
|
57
40
|
<div key={row.label} className="flex items-start gap-3">
|
|
58
|
-
<span className="text-
|
|
59
|
-
<span className={cn('text-
|
|
41
|
+
<span className="text-sm text-neutral-500 w-24 shrink-0">{row.label}:</span>
|
|
42
|
+
<span className={cn('text-sm text-neutral-400', row.mono && 'font-mono')}>
|
|
60
43
|
{row.value}
|
|
61
44
|
</span>
|
|
62
45
|
</div>
|
|
@@ -60,7 +60,8 @@ export function EditorPlaceholderCard({
|
|
|
60
60
|
className = '',
|
|
61
61
|
}: EditorPlaceholderCardProps) {
|
|
62
62
|
const [isExpanded, setIsExpanded] = useState(false)
|
|
63
|
-
const [
|
|
63
|
+
const [isPlaceholderCopied, setIsPlaceholderCopied] = useState(false)
|
|
64
|
+
const [isValueCopied, setIsValueCopied] = useState(false)
|
|
64
65
|
const [isOverflowing, setIsOverflowing] = useState(false)
|
|
65
66
|
const valueRef = useRef<HTMLDivElement>(null)
|
|
66
67
|
|
|
@@ -78,8 +79,8 @@ export function EditorPlaceholderCard({
|
|
|
78
79
|
const handleCopyPlaceholder = async () => {
|
|
79
80
|
try {
|
|
80
81
|
await navigator.clipboard.writeText(`{{${name}}}`)
|
|
81
|
-
|
|
82
|
-
setTimeout(() =>
|
|
82
|
+
setIsPlaceholderCopied(true)
|
|
83
|
+
setTimeout(() => setIsPlaceholderCopied(false), 1500)
|
|
83
84
|
} catch {
|
|
84
85
|
// Clipboard API not available
|
|
85
86
|
}
|
|
@@ -89,8 +90,8 @@ export function EditorPlaceholderCard({
|
|
|
89
90
|
if (!value) return
|
|
90
91
|
try {
|
|
91
92
|
await navigator.clipboard.writeText(value)
|
|
92
|
-
|
|
93
|
-
setTimeout(() =>
|
|
93
|
+
setIsValueCopied(true)
|
|
94
|
+
setTimeout(() => setIsValueCopied(false), 1500)
|
|
94
95
|
} catch {
|
|
95
96
|
// Clipboard API not available
|
|
96
97
|
}
|
|
@@ -102,37 +103,37 @@ export function EditorPlaceholderCard({
|
|
|
102
103
|
<div className="flex items-start justify-between gap-2">
|
|
103
104
|
<div className="flex-1 min-w-0">
|
|
104
105
|
{/* Placeholder name with {{ }} */}
|
|
105
|
-
<code className={`text-
|
|
106
|
+
<code className={`text-sm font-mono px-1.5 py-0.5 rounded ${colors.name} ${colors.nameBg}`}>
|
|
106
107
|
{'{{' + name + '}}'}
|
|
107
108
|
</code>
|
|
108
109
|
{/* Required badge */}
|
|
109
110
|
{required && (
|
|
110
|
-
<span className="ml-2 inline-block px-1.5 py-0.5 text-
|
|
111
|
+
<span className="ml-2 inline-block px-1.5 py-0.5 text-sm font-semibold uppercase bg-red-500/15 text-red-400 border border-red-500/30 rounded">
|
|
111
112
|
Required
|
|
112
113
|
</span>
|
|
113
114
|
)}
|
|
114
115
|
{/* Description */}
|
|
115
|
-
<p className="text-
|
|
116
|
+
<p className="text-sm text-neutral-500 mt-1.5 line-clamp-2">{description}</p>
|
|
116
117
|
</div>
|
|
117
118
|
|
|
118
119
|
{/* Actions (copy for templates, edit/delete for settings) */}
|
|
119
120
|
<div className="flex items-center gap-1 shrink-0">
|
|
120
121
|
{showCopyPlaceholder && (
|
|
121
122
|
<IconButton
|
|
122
|
-
icon={
|
|
123
|
+
icon={isPlaceholderCopied ? 'check' : 'copy'}
|
|
123
124
|
onClick={handleCopyPlaceholder}
|
|
124
125
|
size="sm"
|
|
125
|
-
color={
|
|
126
|
+
color={isPlaceholderCopied ? 'green' : 'neutral'}
|
|
126
127
|
tooltip={{ description: `Copy {{${name}}}` }}
|
|
127
128
|
tooltipPosition="left"
|
|
128
129
|
/>
|
|
129
130
|
)}
|
|
130
131
|
{showCopyValue && hasValue && (
|
|
131
132
|
<IconButton
|
|
132
|
-
icon={
|
|
133
|
+
icon={isValueCopied ? 'check' : 'copy'}
|
|
133
134
|
onClick={handleCopyValue}
|
|
134
135
|
size="sm"
|
|
135
|
-
color={
|
|
136
|
+
color={isValueCopied ? 'green' : 'neutral'}
|
|
136
137
|
tooltip={{ description: 'Copy value to clipboard' }}
|
|
137
138
|
tooltipPosition="left"
|
|
138
139
|
/>
|
|
@@ -146,7 +147,7 @@ export function EditorPlaceholderCard({
|
|
|
146
147
|
<div className="mt-2">
|
|
147
148
|
{hideValue ? (
|
|
148
149
|
<>
|
|
149
|
-
<span className="text-
|
|
150
|
+
<span className="text-sm text-neutral-500 font-medium">{valueLabel}</span>
|
|
150
151
|
<div className="mt-1.5">
|
|
151
152
|
<Input
|
|
152
153
|
type="password"
|
|
@@ -163,7 +164,7 @@ export function EditorPlaceholderCard({
|
|
|
163
164
|
) : (
|
|
164
165
|
<>
|
|
165
166
|
<div className="flex items-center justify-between">
|
|
166
|
-
<span className="text-
|
|
167
|
+
<span className="text-sm text-neutral-500 font-medium">{valueLabel}</span>
|
|
167
168
|
{(isOverflowing || isExpanded) && (
|
|
168
169
|
<IconButton
|
|
169
170
|
icon={isExpanded ? 'chevron-up' : 'chevron-down'}
|
|
@@ -175,7 +176,7 @@ export function EditorPlaceholderCard({
|
|
|
175
176
|
</div>
|
|
176
177
|
<div
|
|
177
178
|
ref={valueRef}
|
|
178
|
-
className={`mt-1.5 px-2 py-1.5 bg-neutral-800/50 rounded text-
|
|
179
|
+
className={`mt-1.5 px-2 py-1.5 bg-neutral-800/50 rounded text-sm text-neutral-400 font-mono ${
|
|
179
180
|
isExpanded
|
|
180
181
|
? 'whitespace-pre-wrap break-all max-h-[190px] overflow-y-auto'
|
|
181
182
|
: 'truncate'
|
|
@@ -190,7 +191,7 @@ export function EditorPlaceholderCard({
|
|
|
190
191
|
|
|
191
192
|
{/* No value hint */}
|
|
192
193
|
{!hasValue && (
|
|
193
|
-
<p className="mt-1.5 text-
|
|
194
|
+
<p className="mt-1.5 text-sm text-neutral-600 italic">No value set - add one in Settings</p>
|
|
194
195
|
)}
|
|
195
196
|
</div>
|
|
196
197
|
)
|
|
@@ -4,6 +4,10 @@ import { Label } from './label.tsx'
|
|
|
4
4
|
import { ConfirmModal } from './modal.tsx'
|
|
5
5
|
|
|
6
6
|
export interface EditorToolbarProps {
|
|
7
|
+
/** Optional title displayed in the toolbar */
|
|
8
|
+
title?: string
|
|
9
|
+
/** Optional description displayed below the title */
|
|
10
|
+
description?: string
|
|
7
11
|
/** Whether content has unsaved changes */
|
|
8
12
|
isDirty: boolean
|
|
9
13
|
/** Whether save operation is in progress */
|
|
@@ -30,6 +34,8 @@ export interface EditorToolbarProps {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export function EditorToolbar({
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
33
39
|
isDirty,
|
|
34
40
|
isSaving = false,
|
|
35
41
|
onSave,
|
|
@@ -66,6 +72,12 @@ export function EditorToolbar({
|
|
|
66
72
|
<div className="flex items-center justify-between px-4 py-1.5 bg-neutral-900 border-b border-neutral-800">
|
|
67
73
|
{/* Left side */}
|
|
68
74
|
<div className="flex items-center gap-2">
|
|
75
|
+
{(title || description) && (
|
|
76
|
+
<div className="flex flex-col mr-2">
|
|
77
|
+
{title && <span className="text-sm font-medium text-neutral-200">{title}</span>}
|
|
78
|
+
{description && <span className="text-xs text-neutral-500">{description}</span>}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
69
81
|
{leftActions?.map((a, i) => <IconButton key={i} {...a} />)}
|
|
70
82
|
</div>
|
|
71
83
|
|
|
@@ -9,15 +9,10 @@
|
|
|
9
9
|
import { AlertTriangle, Info } from 'lucide-react'
|
|
10
10
|
import { Checkbox } from './checkbox.tsx'
|
|
11
11
|
import { cn } from '../lib/cn.ts'
|
|
12
|
-
|
|
13
|
-
export interface ExecutionDetailRow {
|
|
14
|
-
label: string
|
|
15
|
-
value: string
|
|
16
|
-
mono?: boolean
|
|
17
|
-
}
|
|
12
|
+
import type { DetailRow } from './detail-section.tsx'
|
|
18
13
|
|
|
19
14
|
export interface ExecutionDetailsPanelProps {
|
|
20
|
-
details:
|
|
15
|
+
details: DetailRow[]
|
|
21
16
|
/** Show the "Allow direct edits" toggle */
|
|
22
17
|
allowDirectEdits?: boolean
|
|
23
18
|
onAllowDirectEditsChange?: (value: boolean) => void
|
|
@@ -40,7 +35,7 @@ export function ExecutionDetailsPanel({
|
|
|
40
35
|
{/* Header */}
|
|
41
36
|
<div className="flex items-center gap-2">
|
|
42
37
|
<Info className="w-4 h-4 text-neutral-500" />
|
|
43
|
-
<span className="font-medium text-neutral-400 text-
|
|
38
|
+
<span className="font-medium text-neutral-400 text-md">Execution Details</span>
|
|
44
39
|
</div>
|
|
45
40
|
|
|
46
41
|
{/* Direct edits toggle */}
|
|
@@ -57,8 +52,8 @@ export function ExecutionDetailsPanel({
|
|
|
57
52
|
/>
|
|
58
53
|
</div>
|
|
59
54
|
<div>
|
|
60
|
-
<span className="text-neutral-300 text-
|
|
61
|
-
<p className="text-neutral-500 text-
|
|
55
|
+
<span className="text-neutral-300 text-md">Allow direct file edits</span>
|
|
56
|
+
<p className="text-neutral-500 text-sm mt-0.5">
|
|
62
57
|
{allowDirectEdits
|
|
63
58
|
? 'AI will modify files directly. Changes saved immediately.'
|
|
64
59
|
: 'Changes will be shown in editor for review.'}
|
|
@@ -72,7 +67,7 @@ export function ExecutionDetailsPanel({
|
|
|
72
67
|
<div className="rounded border border-red-500/50 bg-red-500/10 p-2">
|
|
73
68
|
<div className="flex items-start gap-2">
|
|
74
69
|
<AlertTriangle className="h-4 w-4 text-red-400 shrink-0 mt-0.5" />
|
|
75
|
-
<p className="text-red-300 text-
|
|
70
|
+
<p className="text-red-300 text-sm">{warningMessage}</p>
|
|
76
71
|
</div>
|
|
77
72
|
</div>
|
|
78
73
|
)}
|
|
@@ -82,8 +77,8 @@ export function ExecutionDetailsPanel({
|
|
|
82
77
|
<div className="space-y-2">
|
|
83
78
|
{details.map((row) => (
|
|
84
79
|
<div key={row.label} className="flex items-start gap-3">
|
|
85
|
-
<span className="text-neutral-500 text-
|
|
86
|
-
<span className={cn('text-neutral-300 text-
|
|
80
|
+
<span className="text-neutral-500 text-sm w-24 shrink-0">{row.label}:</span>
|
|
81
|
+
<span className={cn('text-neutral-300 text-sm', row.mono && 'font-mono')}>{row.value}</span>
|
|
87
82
|
</div>
|
|
88
83
|
))}
|
|
89
84
|
</div>
|
|
@@ -81,11 +81,11 @@ export const ExtensionListCard = memo(function ExtensionListCard({
|
|
|
81
81
|
<Icon className={cn('w-5 h-5 shrink-0', iconColor)} />
|
|
82
82
|
<div className="min-w-0 flex-1">
|
|
83
83
|
<div className="flex items-center gap-2 flex-wrap">
|
|
84
|
-
<span className={cn('text-
|
|
84
|
+
<span className={cn('text-md font-medium', titleClassName)}>{title}</span>
|
|
85
85
|
{badges}
|
|
86
86
|
</div>
|
|
87
87
|
{description && (
|
|
88
|
-
<div className={cn('text-
|
|
88
|
+
<div className={cn('text-sm text-neutral-500 mt-1', !isHovered && 'line-clamp-2')}>{description}</div>
|
|
89
89
|
)}
|
|
90
90
|
</div>
|
|
91
91
|
</div>
|
|
@@ -96,7 +96,7 @@ export const ExtensionListCard = memo(function ExtensionListCard({
|
|
|
96
96
|
)}
|
|
97
97
|
</div>
|
|
98
98
|
{metadata != null && (
|
|
99
|
-
<div className="flex items-center justify-between mt-2 ml-8 mr-2 text-
|
|
99
|
+
<div className="flex items-center justify-between mt-2 ml-8 mr-2 text-sm text-neutral-500">
|
|
100
100
|
{typeof metadata === 'function' ? metadata(isHovered) : metadata}
|
|
101
101
|
</div>
|
|
102
102
|
)}
|