@toolr/ui-design 0.1.6 → 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/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/form-colors.ts +40 -0
- package/components/sections/captured-issues/captured-issues-panel.tsx +3 -3
- package/components/sections/captured-issues/use-captured-issues.ts +9 -3
- package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +4 -40
- package/components/sections/prompt-editor/index.ts +0 -7
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +4 -40
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +4 -36
- package/components/sections/snippets-editor/snippets-editor.tsx +6 -39
- package/components/settings/SettingsHeader.tsx +0 -1
- package/components/settings/SettingsTreeNav.tsx +9 -12
- package/components/ui/action-dialog.tsx +7 -51
- package/components/ui/badge.tsx +4 -20
- package/components/ui/breadcrumb.tsx +6 -66
- package/components/ui/checkbox.tsx +2 -16
- package/components/ui/collapsible-section.tsx +3 -41
- package/components/ui/confirm-badge.tsx +3 -20
- package/components/ui/cookie-consent.tsx +17 -1
- package/components/ui/debounce-border-overlay.tsx +31 -0
- package/components/ui/detail-section.tsx +2 -19
- package/components/ui/editor-placeholder-card.tsx +10 -9
- package/components/ui/execution-details-panel.tsx +2 -7
- package/components/ui/file-structure-section.tsx +3 -18
- package/components/ui/file-tree.tsx +2 -14
- package/components/ui/files-panel.tsx +3 -11
- package/components/ui/form-actions.tsx +6 -5
- package/components/ui/icon-button.tsx +2 -1
- package/components/ui/input.tsx +10 -26
- package/components/ui/label.tsx +3 -17
- package/components/ui/modal.tsx +3 -15
- package/components/ui/nav-card.tsx +2 -17
- package/components/ui/navigation-bar.tsx +8 -69
- package/components/ui/registry-browser.tsx +6 -20
- package/components/ui/registry-card.tsx +3 -7
- package/components/ui/resizable-textarea.tsx +13 -35
- package/components/ui/segmented-toggle.tsx +2 -1
- package/components/ui/select.tsx +2 -11
- package/components/ui/selection-grid.tsx +2 -50
- package/components/ui/setting-row.tsx +1 -3
- package/components/ui/settings-info-box.tsx +5 -22
- package/components/ui/status-card.tsx +2 -13
- package/components/ui/tab-bar.tsx +3 -29
- package/components/ui/toggle.tsx +3 -19
- package/components/ui/tooltip.tsx +6 -18
- package/dist/index.d.ts +60 -137
- package/dist/index.js +1426 -2334
- package/index.ts +8 -7
- package/package.json +1 -1
- package/components/sections/prompt-editor/use-prompt-editor.ts +0 -131
|
@@ -11,49 +11,16 @@
|
|
|
11
11
|
* Footer: FormActions (status text, cancel/submit)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { useEffect } from 'react'
|
|
15
14
|
import { createPortal } from 'react-dom'
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
ChevronLeft, ChevronRight, ChevronUp, ChevronDown,
|
|
19
|
-
Check, X, Plus, Minus, Pencil, Trash2, Copy, Save,
|
|
20
|
-
RefreshCw, RotateCcw, Undo2, Redo2,
|
|
21
|
-
Search, Filter, Download, Upload, ExternalLink, Link2,
|
|
22
|
-
Eye, EyeOff, Lock, Unlock, Settings, MoreHorizontal, MoreVertical,
|
|
23
|
-
Info, HelpCircle,
|
|
24
|
-
User, Users, Folder, File, Image, Code, Terminal,
|
|
25
|
-
Star, Heart, Bell, Bookmark, Tag, Pin, Mail, Send,
|
|
26
|
-
Globe, Database, Cloud,
|
|
27
|
-
Wand2, Shield, ShieldCheck, Zap, Sparkles,
|
|
28
|
-
Play, Pause, Square, StopCircle,
|
|
29
|
-
Menu, GripVertical, Maximize2, Minimize2,
|
|
30
|
-
Scan, Webhook, Bot, Puzzle, Plug,
|
|
31
|
-
} from 'lucide-react'
|
|
32
|
-
import type { LucideIcon } from 'lucide-react'
|
|
33
|
-
import { IconButton } from './icon-button.tsx'
|
|
15
|
+
import { useModalBehavior } from '../hooks/use-modal-behavior.ts'
|
|
16
|
+
import { iconMap, IconButton } from './icon-button.tsx'
|
|
34
17
|
import type { IconName } from './icon-button.tsx'
|
|
35
18
|
import { FormActions } from './form-actions.tsx'
|
|
36
19
|
import { SelectionGrid, type SelectionCardItem, type CodingToolPresetConfig } from './selection-grid.tsx'
|
|
37
|
-
import { ExecutionDetailsPanel
|
|
20
|
+
import { ExecutionDetailsPanel } from './execution-details-panel.tsx'
|
|
21
|
+
import type { DetailRow } from './detail-section.tsx'
|
|
38
22
|
import { cn } from '../lib/cn.ts'
|
|
39
23
|
|
|
40
|
-
const dialogIconMap: Record<string, LucideIcon> = {
|
|
41
|
-
'arrow-left': ArrowLeft, 'arrow-right': ArrowRight, 'arrow-up': ArrowUp, 'arrow-down': ArrowDown,
|
|
42
|
-
'chevron-left': ChevronLeft, 'chevron-right': ChevronRight, 'chevron-up': ChevronUp, 'chevron-down': ChevronDown,
|
|
43
|
-
'check': Check, 'x': X, 'plus': Plus, 'minus': Minus, 'pencil': Pencil, 'trash': Trash2, 'copy': Copy, 'save': Save,
|
|
44
|
-
'refresh': RefreshCw, 'rotate': RotateCcw, 'undo': Undo2, 'redo': Redo2,
|
|
45
|
-
'search': Search, 'filter': Filter, 'download': Download, 'upload': Upload, 'external-link': ExternalLink, 'link': Link2,
|
|
46
|
-
'eye': Eye, 'eye-off': EyeOff, 'lock': Lock, 'unlock': Unlock, 'settings': Settings, 'more-h': MoreHorizontal, 'more-v': MoreVertical,
|
|
47
|
-
'info': Info, 'help': HelpCircle,
|
|
48
|
-
'user': User, 'users': Users, 'folder': Folder, 'file': File, 'image': Image, 'code': Code, 'terminal': Terminal,
|
|
49
|
-
'star': Star, 'heart': Heart, 'bell': Bell, 'bookmark': Bookmark, 'tag': Tag, 'pin': Pin, 'mail': Mail, 'send': Send,
|
|
50
|
-
'globe': Globe, 'database': Database, 'cloud': Cloud,
|
|
51
|
-
'wand': Wand2, 'shield': Shield, 'shield-check': ShieldCheck, 'zap': Zap, 'sparkles': Sparkles,
|
|
52
|
-
'play': Play, 'pause': Pause, 'stop': Square, 'stop-circle': StopCircle, 'scan': Scan,
|
|
53
|
-
'menu': Menu, 'grip': GripVertical, 'maximize': Maximize2, 'minimize': Minimize2,
|
|
54
|
-
'webhook': Webhook, 'bot': Bot, 'puzzle': Puzzle, 'plug': Plug,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
24
|
export interface ActionDialogProps {
|
|
58
25
|
/** Dialog title */
|
|
59
26
|
title: string
|
|
@@ -114,7 +81,7 @@ export interface ActionDialogProps {
|
|
|
114
81
|
|
|
115
82
|
// ── Execution details section (mandatory) ────────────────────────────────
|
|
116
83
|
/** Execution detail rows (Tool, Permissions, Output, CLI Flags, Changes) */
|
|
117
|
-
executionDetails:
|
|
84
|
+
executionDetails: DetailRow[]
|
|
118
85
|
/** Whether direct file edits are allowed */
|
|
119
86
|
allowDirectEdits?: boolean
|
|
120
87
|
/** Callback to toggle direct edits - shows the toggle when provided */
|
|
@@ -163,20 +130,9 @@ export function ActionDialog({
|
|
|
163
130
|
children,
|
|
164
131
|
className,
|
|
165
132
|
}: ActionDialogProps) {
|
|
166
|
-
|
|
167
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
168
|
-
if (e.key === 'Escape') onCancel?.()
|
|
169
|
-
}
|
|
170
|
-
document.addEventListener('keydown', handleEscape)
|
|
171
|
-
return () => document.removeEventListener('keydown', handleEscape)
|
|
172
|
-
}, [onCancel])
|
|
173
|
-
|
|
174
|
-
useEffect(() => {
|
|
175
|
-
document.body.style.overflow = 'hidden'
|
|
176
|
-
return () => { document.body.style.overflow = '' }
|
|
177
|
-
}, [])
|
|
133
|
+
useModalBehavior(true, () => onCancel?.())
|
|
178
134
|
|
|
179
|
-
const Icon = icon ?
|
|
135
|
+
const Icon = icon ? iconMap[icon] : null
|
|
180
136
|
const hasSelection = ((items && items.length > 0) || (presets && presets.length > 0)) && selectedIds && onSelect
|
|
181
137
|
const hasScenarios = scenarios && scenarios.length > 0 && selectedScenarioIds && onSelectScenarios
|
|
182
138
|
const hasExecutionDetails = executionDetails.length > 0 || onAllowDirectEditsChange || executionWarning
|
package/components/ui/badge.tsx
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* - 5 size variants (xss, xs, sm, md, lg)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import { FORM_COLORS, type AccentColor } from '../lib/form-colors.ts'
|
|
17
|
+
|
|
18
|
+
export type BadgeColor = AccentColor
|
|
17
19
|
|
|
18
20
|
export interface BadgeProps {
|
|
19
21
|
value: number | string
|
|
@@ -23,24 +25,6 @@ export interface BadgeProps {
|
|
|
23
25
|
testId?: string
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const colorClasses: Record<BadgeColor, string> = {
|
|
27
|
-
green: 'border-green-500/30 text-green-400',
|
|
28
|
-
red: 'border-red-500/30 text-red-400',
|
|
29
|
-
blue: 'border-blue-500/30 text-blue-400',
|
|
30
|
-
orange: 'border-orange-500/30 text-orange-400',
|
|
31
|
-
cyan: 'border-cyan-500/30 text-cyan-400',
|
|
32
|
-
yellow: 'border-yellow-500/30 text-yellow-400',
|
|
33
|
-
purple: 'border-purple-500/30 text-purple-400',
|
|
34
|
-
indigo: 'border-indigo-500/30 text-indigo-400',
|
|
35
|
-
emerald: 'border-emerald-500/30 text-emerald-400',
|
|
36
|
-
amber: 'border-amber-500/30 text-amber-400',
|
|
37
|
-
violet: 'border-violet-500/30 text-violet-400',
|
|
38
|
-
neutral: 'border-neutral-500/30 text-neutral-400',
|
|
39
|
-
sky: 'border-sky-500/30 text-sky-400',
|
|
40
|
-
pink: 'border-pink-500/30 text-pink-400',
|
|
41
|
-
teal: 'border-teal-500/30 text-teal-400',
|
|
42
|
-
}
|
|
43
|
-
|
|
44
28
|
const sizeClasses = {
|
|
45
29
|
xss: 'min-w-[14px] h-[14px] px-0.5 text-xss',
|
|
46
30
|
xs: 'min-w-[16px] h-[16px] px-1 text-xs',
|
|
@@ -61,7 +45,7 @@ export function Badge({
|
|
|
61
45
|
return (
|
|
62
46
|
<span
|
|
63
47
|
data-testid={testId}
|
|
64
|
-
className={`inline-flex items-center justify-center border rounded-full font-medium leading-none tabular-nums ${
|
|
48
|
+
className={`inline-flex items-center justify-center border rounded-full font-medium leading-none tabular-nums ${FORM_COLORS[color].border} ${FORM_COLORS[color].accent} ${sizeClasses[size]} ${className}`}
|
|
65
49
|
>
|
|
66
50
|
{display}
|
|
67
51
|
</span>
|
|
@@ -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
|
|
@@ -73,29 +30,12 @@ const sizeConfig = {
|
|
|
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)}>
|
|
@@ -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',
|
|
@@ -61,7 +77,7 @@ export function CookieConsent({
|
|
|
61
77
|
</button>
|
|
62
78
|
<button
|
|
63
79
|
onClick={() => handleConsent('accepted')}
|
|
64
|
-
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
|
|
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,7 +27,7 @@ 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}>
|
|
@@ -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
|
}
|
|
@@ -119,20 +120,20 @@ export function EditorPlaceholderCard({
|
|
|
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
|
/>
|
|
@@ -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
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef, useMemo, type ReactNode } from 'react'
|
|
2
2
|
import { FileCode, FolderTree, Loader2, AlertCircle, AlignLeft, Code2, Type } from 'lucide-react'
|
|
3
|
-
import {
|
|
3
|
+
import { type AccentColor, ACCENT_ICON } from '../lib/form-colors.ts'
|
|
4
|
+
import { CollapseButton } from './icon-button.tsx'
|
|
4
5
|
import { SegmentedToggle } from './segmented-toggle.tsx'
|
|
5
6
|
import { FileTree, collectDirPaths, type FileTreeNode } from './file-tree.tsx'
|
|
6
7
|
|
|
7
8
|
export type PreviewMode = 'format' | 'language' | 'plain'
|
|
8
|
-
export type AccentColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
|
|
9
|
-
|
|
10
|
-
const ACCENT_ICON: Record<AccentColor, string> = {
|
|
11
|
-
blue: 'text-blue-400', green: 'text-green-400', red: 'text-red-400',
|
|
12
|
-
orange: 'text-orange-400', cyan: 'text-cyan-400', yellow: 'text-yellow-400',
|
|
13
|
-
purple: 'text-purple-400', indigo: 'text-indigo-400', emerald: 'text-emerald-400',
|
|
14
|
-
amber: 'text-amber-400', violet: 'text-violet-400', neutral: 'text-neutral-400',
|
|
15
|
-
sky: 'text-sky-400', pink: 'text-pink-400', teal: 'text-teal-400',
|
|
16
|
-
}
|
|
17
9
|
|
|
18
10
|
const ACCENT_BORDER: Record<AccentColor, string> = {
|
|
19
11
|
blue: 'border-blue-500/25', green: 'border-green-500/25', red: 'border-red-500/25',
|
|
@@ -23,13 +15,6 @@ const ACCENT_BORDER: Record<AccentColor, string> = {
|
|
|
23
15
|
sky: 'border-sky-500/25', pink: 'border-pink-500/25', teal: 'border-teal-500/25',
|
|
24
16
|
}
|
|
25
17
|
|
|
26
|
-
const ACCENT_BUTTON: Record<AccentColor, IconButtonColor> = {
|
|
27
|
-
blue: 'blue', green: 'green', red: 'red',
|
|
28
|
-
orange: 'orange', cyan: 'cyan', yellow: 'yellow',
|
|
29
|
-
purple: 'purple', indigo: 'indigo', emerald: 'emerald',
|
|
30
|
-
amber: 'amber', violet: 'violet', neutral: 'neutral',
|
|
31
|
-
sky: 'sky', pink: 'pink', teal: 'teal',
|
|
32
|
-
}
|
|
33
18
|
|
|
34
19
|
const ACCENT_HANDLE: Record<AccentColor, string> = {
|
|
35
20
|
blue: 'bg-blue-500/30 group-hover:bg-blue-400/50', green: 'bg-green-500/30 group-hover:bg-green-400/50', red: 'bg-red-500/30 group-hover:bg-red-400/50',
|
|
@@ -350,7 +335,7 @@ export function FileStructureSection({
|
|
|
350
335
|
<CollapseButton
|
|
351
336
|
collapsed={allCollapsed}
|
|
352
337
|
onToggle={() => setExpandedPaths(allCollapsed ? new Set(allDirPaths) : new Set())}
|
|
353
|
-
color={
|
|
338
|
+
color={accentColor}
|
|
354
339
|
/>
|
|
355
340
|
</div>
|
|
356
341
|
<div className={`${variant === 'split' ? 'flex-1 overflow-y-auto' : ''} p-3`}>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FileCode, Folder, FolderOpen, ChevronRight, ChevronDown } from 'lucide-react'
|
|
2
|
+
import { ACCENT_ICON, type AccentColor } from '../lib/form-colors.ts'
|
|
2
3
|
|
|
3
4
|
export interface FileTreeNode {
|
|
4
5
|
name: string
|
|
@@ -30,19 +31,6 @@ const ACCENT_SELECTED: Record<string, string> = {
|
|
|
30
31
|
violet: 'bg-violet-400/20 text-neutral-200',
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const ACCENT_ICON: Record<string, string> = {
|
|
34
|
-
blue: 'text-blue-400',
|
|
35
|
-
purple: 'text-purple-400',
|
|
36
|
-
orange: 'text-orange-400',
|
|
37
|
-
green: 'text-green-400',
|
|
38
|
-
pink: 'text-pink-400',
|
|
39
|
-
amber: 'text-amber-400',
|
|
40
|
-
emerald: 'text-emerald-400',
|
|
41
|
-
teal: 'text-teal-400',
|
|
42
|
-
sky: 'text-sky-400',
|
|
43
|
-
violet: 'text-violet-400',
|
|
44
|
-
}
|
|
45
|
-
|
|
46
34
|
function nodeHasFiles(node: FileTreeNode): boolean {
|
|
47
35
|
if (node.type === 'file') return true
|
|
48
36
|
return !!node.children?.some(nodeHasFiles)
|
|
@@ -124,7 +112,7 @@ function FileTreeNodeItem({ node, path, selectedPath, onSelectFile, expandedPath
|
|
|
124
112
|
const expanded = isDir && expandedPaths.has(path)
|
|
125
113
|
const base = 'flex items-center gap-1.5 py-0.5 px-1 rounded text-sm transition-colors overflow-hidden whitespace-nowrap'
|
|
126
114
|
const selectedClass = ACCENT_SELECTED[accentColor] ?? ACCENT_SELECTED.blue
|
|
127
|
-
const iconColorClass = ACCENT_ICON[accentColor] ?? ACCENT_ICON.blue
|
|
115
|
+
const iconColorClass = ACCENT_ICON[accentColor as AccentColor] ?? ACCENT_ICON.blue
|
|
128
116
|
const rowClass = isSelected
|
|
129
117
|
? `${base} ${selectedClass}`
|
|
130
118
|
: isDir
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useState, useMemo } from 'react'
|
|
4
4
|
import { Folder, FolderOpen, File, FileCode, FileText, FileJson, Image, ChevronRight, Search, MoreVertical } from 'lucide-react'
|
|
5
5
|
import type { LucideIcon } from 'lucide-react'
|
|
6
|
-
import type
|
|
6
|
+
import { iconMap, type IconName } from './icon-button.tsx'
|
|
7
7
|
import { cn } from '../lib/cn.ts'
|
|
8
8
|
|
|
9
9
|
const ACCENT_SELECTED: Record<string, string> = {
|
|
@@ -19,14 +19,6 @@ const ACCENT_SELECTED: Record<string, string> = {
|
|
|
19
19
|
violet: 'bg-violet-400/15 text-violet-400',
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const iconSubset: Partial<Record<IconName, LucideIcon>> = {
|
|
23
|
-
folder: Folder,
|
|
24
|
-
file: File,
|
|
25
|
-
code: FileCode,
|
|
26
|
-
image: Image,
|
|
27
|
-
search: Search,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
22
|
const EXTENSION_ICON_MAP: Record<string, LucideIcon> = {
|
|
31
23
|
ts: FileCode,
|
|
32
24
|
tsx: FileCode,
|
|
@@ -88,7 +80,7 @@ function countFiles(entries: FileEntry[]): number {
|
|
|
88
80
|
}
|
|
89
81
|
|
|
90
82
|
function getFileIcon(entry: FileEntry): LucideIcon {
|
|
91
|
-
if (entry.icon &&
|
|
83
|
+
if (entry.icon && iconMap[entry.icon]) return iconMap[entry.icon]!
|
|
92
84
|
if (entry.type === 'folder') return Folder
|
|
93
85
|
const ext = entry.name.split('.').pop()?.toLowerCase()
|
|
94
86
|
if (ext && EXTENSION_ICON_MAP[ext]) return EXTENSION_ICON_MAP[ext]
|
|
@@ -96,7 +88,7 @@ function getFileIcon(entry: FileEntry): LucideIcon {
|
|
|
96
88
|
}
|
|
97
89
|
|
|
98
90
|
function getFolderIcon(expanded: boolean, entry: FileEntry): LucideIcon {
|
|
99
|
-
if (entry.icon &&
|
|
91
|
+
if (entry.icon && iconMap[entry.icon]) return iconMap[entry.icon]!
|
|
100
92
|
return expanded ? FolderOpen : Folder
|
|
101
93
|
}
|
|
102
94
|
|