@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.
Files changed (52) hide show
  1. package/components/hooks/use-click-outside.ts +10 -3
  2. package/components/hooks/use-modal-behavior.ts +24 -0
  3. package/components/hooks/use-navigation-history.ts +7 -2
  4. package/components/hooks/use-resizable-sidebar.ts +38 -0
  5. package/components/lib/form-colors.ts +40 -0
  6. package/components/sections/captured-issues/captured-issues-panel.tsx +3 -3
  7. package/components/sections/captured-issues/use-captured-issues.ts +9 -3
  8. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +4 -40
  9. package/components/sections/prompt-editor/index.ts +0 -7
  10. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +4 -40
  11. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +4 -36
  12. package/components/sections/snippets-editor/snippets-editor.tsx +6 -39
  13. package/components/settings/SettingsHeader.tsx +0 -1
  14. package/components/settings/SettingsTreeNav.tsx +9 -12
  15. package/components/ui/action-dialog.tsx +7 -51
  16. package/components/ui/badge.tsx +4 -20
  17. package/components/ui/breadcrumb.tsx +6 -66
  18. package/components/ui/checkbox.tsx +2 -16
  19. package/components/ui/collapsible-section.tsx +3 -41
  20. package/components/ui/confirm-badge.tsx +3 -20
  21. package/components/ui/cookie-consent.tsx +17 -1
  22. package/components/ui/debounce-border-overlay.tsx +31 -0
  23. package/components/ui/detail-section.tsx +2 -19
  24. package/components/ui/editor-placeholder-card.tsx +10 -9
  25. package/components/ui/execution-details-panel.tsx +2 -7
  26. package/components/ui/file-structure-section.tsx +3 -18
  27. package/components/ui/file-tree.tsx +2 -14
  28. package/components/ui/files-panel.tsx +3 -11
  29. package/components/ui/form-actions.tsx +6 -5
  30. package/components/ui/icon-button.tsx +2 -1
  31. package/components/ui/input.tsx +10 -26
  32. package/components/ui/label.tsx +3 -17
  33. package/components/ui/modal.tsx +3 -15
  34. package/components/ui/nav-card.tsx +2 -17
  35. package/components/ui/navigation-bar.tsx +8 -69
  36. package/components/ui/registry-browser.tsx +6 -20
  37. package/components/ui/registry-card.tsx +3 -7
  38. package/components/ui/resizable-textarea.tsx +13 -35
  39. package/components/ui/segmented-toggle.tsx +2 -1
  40. package/components/ui/select.tsx +2 -11
  41. package/components/ui/selection-grid.tsx +2 -50
  42. package/components/ui/setting-row.tsx +1 -3
  43. package/components/ui/settings-info-box.tsx +5 -22
  44. package/components/ui/status-card.tsx +2 -13
  45. package/components/ui/tab-bar.tsx +3 -29
  46. package/components/ui/toggle.tsx +3 -19
  47. package/components/ui/tooltip.tsx +6 -18
  48. package/dist/index.d.ts +60 -137
  49. package/dist/index.js +1426 -2334
  50. package/index.ts +8 -7
  51. package/package.json +1 -1
  52. 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
- ArrowLeft, ArrowRight, ArrowUp, ArrowDown,
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, type ExecutionDetailRow } from './execution-details-panel.tsx'
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: ExecutionDetailRow[]
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
- useEffect(() => {
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 ? dialogIconMap[icon] : null
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
@@ -13,7 +13,9 @@
13
13
  * - 5 size variants (xss, xs, sm, md, lg)
14
14
  */
15
15
 
16
- export type BadgeColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
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 ${colorClasses[color]} ${sizeClasses[size]} ${className}`}
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
- ChevronRight,
5
- Settings, Folder, File, Code, Terminal, Database,
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 = iconSubset[icon]
35
+ const Icon = iconMap[icon]
96
36
  if (!Icon) return null
97
37
  const s = sizeConfig[size]
98
- const c = color && colorMap[color] ? colorMap[color] : null
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 && colorMap[segment.color] ? colorMap[segment.color] : null
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
- ChevronRight, Settings, Folder, Code, Terminal, Database,
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 ? iconSubset[icon] : undefined
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 = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
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 ${colorClasses[color]} ${sizeClasses[size]} ${className}`}
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 bg-${accentColor}-600 border-${accentColor}-500 hover:bg-${accentColor}-500`}
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 ? iconSubset[icon] : undefined
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 [isCopied, setIsCopied] = useState(false)
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
- setIsCopied(true)
82
- setTimeout(() => setIsCopied(false), 1500)
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
- setIsCopied(true)
93
- setTimeout(() => setIsCopied(false), 1500)
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={isCopied ? 'check' : 'copy'}
123
+ icon={isPlaceholderCopied ? 'check' : 'copy'}
123
124
  onClick={handleCopyPlaceholder}
124
125
  size="sm"
125
- color={isCopied ? 'green' : 'neutral'}
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={isCopied ? 'check' : 'copy'}
133
+ icon={isValueCopied ? 'check' : 'copy'}
133
134
  onClick={handleCopyValue}
134
135
  size="sm"
135
- color={isCopied ? 'green' : 'neutral'}
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: ExecutionDetailRow[]
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 { CollapseButton, type IconButtonColor } from './icon-button.tsx'
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={ACCENT_BUTTON[accentColor]}
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 { IconName } from './icon-button.tsx'
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 && iconSubset[entry.icon]) return iconSubset[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 && iconSubset[entry.icon]) return iconSubset[entry.icon]!
91
+ if (entry.icon && iconMap[entry.icon]) return iconMap[entry.icon]!
100
92
  return expanded ? FolderOpen : Folder
101
93
  }
102
94