@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.
Files changed (82) hide show
  1. package/agent-rules.json +91 -0
  2. package/ai-manifest.json +190 -0
  3. package/components/content/info-panel-primitives.tsx +14 -14
  4. package/components/hooks/use-click-outside.ts +10 -3
  5. package/components/hooks/use-modal-behavior.ts +24 -0
  6. package/components/hooks/use-navigation-history.ts +7 -2
  7. package/components/hooks/use-resizable-sidebar.ts +38 -0
  8. package/components/lib/ai-tools.tsx +1 -1
  9. package/components/lib/form-colors.ts +40 -0
  10. package/components/sections/ai-tools-paths/tools-paths-panel.tsx +7 -7
  11. package/components/sections/captured-issues/captured-issues-panel.tsx +13 -13
  12. package/components/sections/captured-issues/use-captured-issues.ts +9 -3
  13. package/components/sections/golden-snapshots/file-diff-viewer.tsx +13 -13
  14. package/components/sections/golden-snapshots/golden-sync-panel.tsx +5 -5
  15. package/components/sections/golden-snapshots/snapshot-manager.tsx +11 -11
  16. package/components/sections/golden-snapshots/status-overview.tsx +20 -20
  17. package/components/sections/golden-snapshots/version-manager.tsx +8 -8
  18. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +8 -44
  19. package/components/sections/prompt-editor/index.ts +0 -7
  20. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +9 -45
  21. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +11 -43
  22. package/components/sections/report-bug/report-bug-form.tsx +14 -14
  23. package/components/sections/report-bug/screenshot-uploader.tsx +6 -6
  24. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +3 -3
  25. package/components/sections/snapshot-browser/snapshot-tree.tsx +8 -8
  26. package/components/sections/snippets-editor/snippets-editor.tsx +74 -48
  27. package/components/settings/SettingsHeader.tsx +1 -2
  28. package/components/settings/SettingsTreeNav.tsx +31 -16
  29. package/components/ui/action-dialog.tsx +12 -56
  30. package/components/ui/badge.tsx +8 -24
  31. package/components/ui/bottom-panel-header.tsx +4 -4
  32. package/components/ui/breadcrumb.tsx +8 -68
  33. package/components/ui/checkbox.tsx +2 -16
  34. package/components/ui/collapsible-section.tsx +4 -42
  35. package/components/ui/confirm-badge.tsx +3 -20
  36. package/components/ui/cookie-consent.tsx +21 -5
  37. package/components/ui/debounce-border-overlay.tsx +31 -0
  38. package/components/ui/detail-section.tsx +5 -22
  39. package/components/ui/editor-placeholder-card.tsx +17 -16
  40. package/components/ui/editor-toolbar.tsx +12 -0
  41. package/components/ui/execution-details-panel.tsx +8 -13
  42. package/components/ui/extension-list-card.tsx +3 -3
  43. package/components/ui/file-structure-section.tsx +20 -35
  44. package/components/ui/file-tree.tsx +4 -14
  45. package/components/ui/files-panel.tsx +28 -18
  46. package/components/ui/filter-dropdown.tsx +5 -5
  47. package/components/ui/form-actions.tsx +7 -6
  48. package/components/ui/frontmatter-form-header.tsx +4 -4
  49. package/components/ui/icon-button.tsx +3 -2
  50. package/components/ui/input.tsx +15 -31
  51. package/components/ui/label.tsx +7 -21
  52. package/components/ui/layout-tab-bar.tsx +4 -4
  53. package/components/ui/modal.tsx +5 -17
  54. package/components/ui/nav-card.tsx +5 -20
  55. package/components/ui/navigation-bar.tsx +13 -74
  56. package/components/ui/number-input.tsx +4 -4
  57. package/components/ui/registry-browser.tsx +10 -24
  58. package/components/ui/registry-card.tsx +16 -20
  59. package/components/ui/registry-detail.tsx +6 -6
  60. package/components/ui/resizable-textarea.tsx +13 -35
  61. package/components/ui/segmented-toggle.tsx +6 -5
  62. package/components/ui/select.tsx +7 -16
  63. package/components/ui/selection-grid.tsx +6 -54
  64. package/components/ui/setting-row.tsx +2 -4
  65. package/components/ui/settings-card.tsx +3 -3
  66. package/components/ui/settings-info-box.tsx +6 -23
  67. package/components/ui/settings-section-title.tsx +1 -1
  68. package/components/ui/snapshot-card.tsx +7 -7
  69. package/components/ui/snippets-panel.tsx +10 -10
  70. package/components/ui/sort-dropdown.tsx +2 -2
  71. package/components/ui/status-card.tsx +6 -17
  72. package/components/ui/tab-bar.tsx +5 -31
  73. package/components/ui/toggle.tsx +3 -19
  74. package/components/ui/tooltip.tsx +9 -21
  75. package/dist/content.js +14 -14
  76. package/dist/index.d.ts +71 -141
  77. package/dist/index.js +1634 -2450
  78. package/dist/tokens/primitives.css +9 -2
  79. package/index.ts +8 -7
  80. package/package.json +13 -3
  81. package/tokens/primitives.css +9 -2
  82. package/components/sections/prompt-editor/use-prompt-editor.ts +0 -131
@@ -1,6 +1,7 @@
1
1
  import { useState, useRef, useEffect, useCallback, type ReactNode } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
3
  import { ChevronDown, Check } from 'lucide-react'
4
+ import { useClickOutside } from '../hooks/use-click-outside.ts'
4
5
  import { useDropdownMaxHeight } from '../hooks/use-dropdown-max-height.ts'
5
6
  import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
6
7
 
@@ -30,10 +31,10 @@ const VARIANT_CLASSES = {
30
31
 
31
32
  const SIZE_CLASSES = {
32
33
  xss: 'h-[18px] px-1.5 text-xss',
33
- xs: 'h-6 px-2 text-xs',
34
- sm: 'h-7 px-2 text-xs',
35
- md: 'h-8 px-3 text-sm',
36
- lg: 'h-9 px-3 text-sm',
34
+ xs: 'h-6 px-2 text-sm',
35
+ sm: 'h-7 px-2 text-sm',
36
+ md: 'h-8 px-3 text-md',
37
+ lg: 'h-9 px-3 text-md',
37
38
  }
38
39
 
39
40
  export function Select<T extends string | number = string>({
@@ -74,17 +75,7 @@ export function Select<T extends string | number = string>({
74
75
  }, [])
75
76
 
76
77
  // Close on click outside both the trigger and the portal menu
77
- useEffect(() => {
78
- if (!isOpen) return
79
- const handleClick = (event: MouseEvent) => {
80
- const target = event.target as Node
81
- if (ref.current?.contains(target)) return
82
- if (menuRef.current?.contains(target)) return
83
- close()
84
- }
85
- document.addEventListener('mousedown', handleClick)
86
- return () => document.removeEventListener('mousedown', handleClick)
87
- }, [isOpen, close])
78
+ useClickOutside([ref, menuRef], isOpen, close)
88
79
 
89
80
  useEffect(() => {
90
81
  if (highlightIdx >= 0 && menuRef.current) {
@@ -152,7 +143,7 @@ export function Select<T extends string | number = string>({
152
143
  data-idx={idx}
153
144
  onClick={() => { onChange(opt.value); close() }}
154
145
  onPointerEnter={() => setHighlightIdx(idx)}
155
- className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors cursor-pointer ${
146
+ className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
156
147
  isHighlighted
157
148
  ? `${FORM_COLORS[color].selectedBg} text-neutral-200`
158
149
  : isSelected ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
@@ -1,54 +1,8 @@
1
- import {
2
- Check, Settings, Code, Folder, File, Terminal, Globe, Database, Cloud,
3
- Sparkles, Zap, Shield, ShieldCheck, Wand2, Star, Heart, Bell,
4
- Search, Filter, Eye, Lock, User, Users, Image, Tag, Pin, Mail,
5
- Send, Bookmark, Play, Pause, Bot, Plug, Puzzle, Webhook, Scan,
6
- } from 'lucide-react'
7
- import type { LucideIcon } from 'lucide-react'
8
- import type { IconName } from './icon-button.tsx'
1
+ import { iconMap, type IconName } from './icon-button.tsx'
9
2
  import type { ConfirmBadgeColor } from './confirm-badge.tsx'
10
3
  import { cn } from '../lib/cn.ts'
11
4
  import { AiToolIcon, AI_TOOL_NAMES, type AiToolKey } from '../lib/ai-tools.tsx'
12
5
 
13
- const iconMap: Partial<Record<IconName, LucideIcon>> = {
14
- 'check': Check,
15
- 'settings': Settings,
16
- 'code': Code,
17
- 'folder': Folder,
18
- 'file': File,
19
- 'terminal': Terminal,
20
- 'globe': Globe,
21
- 'database': Database,
22
- 'cloud': Cloud,
23
- 'sparkles': Sparkles,
24
- 'zap': Zap,
25
- 'shield': Shield,
26
- 'shield-check': ShieldCheck,
27
- 'wand': Wand2,
28
- 'star': Star,
29
- 'heart': Heart,
30
- 'bell': Bell,
31
- 'search': Search,
32
- 'filter': Filter,
33
- 'eye': Eye,
34
- 'lock': Lock,
35
- 'user': User,
36
- 'users': Users,
37
- 'image': Image,
38
- 'tag': Tag,
39
- 'pin': Pin,
40
- 'mail': Mail,
41
- 'send': Send,
42
- 'bookmark': Bookmark,
43
- 'play': Play,
44
- 'pause': Pause,
45
- 'bot': Bot,
46
- 'plug': Plug,
47
- 'puzzle': Puzzle,
48
- 'webhook': Webhook,
49
- 'scan': Scan,
50
- }
51
-
52
6
  /* ── Preset logos (shared AiToolIcon) ─────────────────────── */
53
7
 
54
8
  type IconProps = { className?: string; style?: React.CSSProperties }
@@ -142,9 +96,7 @@ function resolveColor(color?: string): string {
142
96
  }
143
97
 
144
98
  function autoColumns(itemCount: number): number {
145
- if (itemCount <= 2) return itemCount
146
- if (itemCount <= 4) return itemCount
147
- return 5
99
+ return Math.min(itemCount, 5)
148
100
  }
149
101
 
150
102
  /* ── Component ─────────────────────────────────────────────── */
@@ -251,10 +203,10 @@ function GridCard({ item, selected, onClick }: CardProps) {
251
203
  />
252
204
  )}
253
205
 
254
- <span className="text-xs font-medium text-neutral-200 block">{item.name}</span>
206
+ <span className="text-sm font-medium text-neutral-200 block">{item.name}</span>
255
207
 
256
208
  {item.description && (
257
- <span className="text-xs text-neutral-500 block mt-0.5">{item.description}</span>
209
+ <span className="text-sm text-neutral-500 block mt-0.5">{item.description}</span>
258
210
  )}
259
211
  </button>
260
212
  )
@@ -291,9 +243,9 @@ function ListCard({ item, selected, onClick }: CardProps) {
291
243
  )}
292
244
 
293
245
  <div className="min-w-0">
294
- <span className="text-xs font-medium text-neutral-200 block">{item.name}</span>
246
+ <span className="text-sm font-medium text-neutral-200 block">{item.name}</span>
295
247
  {item.description && (
296
- <span className="text-xs text-neutral-500 block mt-0.5">{item.description}</span>
248
+ <span className="text-sm text-neutral-500 block mt-0.5">{item.description}</span>
297
249
  )}
298
250
  </div>
299
251
  </button>
@@ -11,7 +11,7 @@
11
11
  * - input: renders a text Input
12
12
  */
13
13
 
14
- import { Toggle, type ToggleColor, type ToggleSize, type ToggleVariant } from './toggle.tsx'
14
+ import { Toggle, type ToggleColor, type ToggleSize } from './toggle.tsx'
15
15
  import { Select, type SelectOption } from './select.tsx'
16
16
  import { Input } from './input.tsx'
17
17
 
@@ -28,7 +28,6 @@ interface SettingRowToggle extends SettingRowBase {
28
28
  onChange: (checked: boolean) => void
29
29
  color?: ToggleColor
30
30
  size?: ToggleSize
31
- variant?: ToggleVariant
32
31
  }
33
32
 
34
33
  interface SettingRowSelect extends SettingRowBase {
@@ -58,7 +57,7 @@ export function SettingRow(props: SettingRowProps) {
58
57
  <div className={`flex items-start justify-between gap-4 ${className}`}>
59
58
  <div>
60
59
  <label className="text-neutral-200 leading-7">{label}</label>
61
- {description && <p className="text-sm text-neutral-500">{description}</p>}
60
+ {description && <p className="text-md text-neutral-500">{description}</p>}
62
61
  </div>
63
62
  {props.type === 'toggle' && (
64
63
  <Toggle
@@ -67,7 +66,6 @@ export function SettingRow(props: SettingRowProps) {
67
66
  disabled={disabled}
68
67
  color={props.color}
69
68
  size={props.size}
70
- variant={props.variant}
71
69
  />
72
70
  )}
73
71
  {props.type === 'select' && (
@@ -16,11 +16,11 @@ export function SettingsCard({ children, className, title, description, testId }
16
16
  >
17
17
  {title && (
18
18
  <div>
19
- <h3 className="text-sm font-medium text-neutral-200">{title}</h3>
20
- {description && <p className="text-sm text-neutral-500 mt-1">{description}</p>}
19
+ <h3 className="text-md font-medium text-neutral-200">{title}</h3>
20
+ {description && <p className="text-md text-neutral-500 mt-1">{description}</p>}
21
21
  </div>
22
22
  )}
23
- {!title && description && <p className="text-sm text-neutral-500">{description}</p>}
23
+ {!title && description && <p className="text-md text-neutral-500">{description}</p>}
24
24
  {children}
25
25
  </div>
26
26
  )
@@ -1,7 +1,8 @@
1
1
  import { Info, AlertTriangle, CheckCircle, AlertCircle } from 'lucide-react'
2
+ import { ACCENT_TEXT, type AccentColor } from '../lib/form-colors.ts'
2
3
  import { cn } from '../lib/cn.ts'
3
4
 
4
- export type SettingsInfoBoxColor = 'neutral' | 'blue' | 'amber' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'violet' | 'sky' | 'pink' | 'teal'
5
+ export type SettingsInfoBoxColor = AccentColor
5
6
 
6
7
  export interface SettingsInfoBoxProps {
7
8
  children: React.ReactNode
@@ -10,7 +11,7 @@ export interface SettingsInfoBoxProps {
10
11
  testId?: string
11
12
  }
12
13
 
13
- const iconMap: Record<SettingsInfoBoxColor, typeof Info> = {
14
+ const INFO_BOX_ICONS: Record<SettingsInfoBoxColor, typeof Info> = {
14
15
  neutral: Info,
15
16
  blue: Info,
16
17
  amber: AlertTriangle,
@@ -28,24 +29,6 @@ const iconMap: Record<SettingsInfoBoxColor, typeof Info> = {
28
29
  teal: Info,
29
30
  }
30
31
 
31
- const iconColorMap: Record<SettingsInfoBoxColor, string> = {
32
- neutral: 'text-neutral-500',
33
- blue: 'text-blue-400',
34
- amber: 'text-amber-400',
35
- green: 'text-green-400',
36
- red: 'text-red-400',
37
- orange: 'text-orange-400',
38
- cyan: 'text-cyan-400',
39
- yellow: 'text-yellow-400',
40
- purple: 'text-purple-400',
41
- indigo: 'text-indigo-400',
42
- emerald: 'text-emerald-400',
43
- violet: 'text-violet-400',
44
- sky: 'text-sky-400',
45
- pink: 'text-pink-400',
46
- teal: 'text-teal-400',
47
- }
48
-
49
32
  const borderColorMap: Record<SettingsInfoBoxColor, string> = {
50
33
  neutral: 'border-l-neutral-600',
51
34
  blue: 'border-l-blue-500',
@@ -65,7 +48,7 @@ const borderColorMap: Record<SettingsInfoBoxColor, string> = {
65
48
  }
66
49
 
67
50
  export function SettingsInfoBox({ children, color = 'neutral', className, testId }: SettingsInfoBoxProps) {
68
- const Icon = iconMap[color]
51
+ const Icon = INFO_BOX_ICONS[color]
69
52
 
70
53
  return (
71
54
  <div
@@ -73,8 +56,8 @@ export function SettingsInfoBox({ children, color = 'neutral', className, testId
73
56
  style={{ paddingLeft: 10 }}
74
57
  data-testid={testId}
75
58
  >
76
- <Icon className={cn('w-4 h-4 mt-0.5 shrink-0', iconColorMap[color])} />
77
- <div className="text-sm text-neutral-500">{children}</div>
59
+ <Icon className={cn('w-4 h-4 mt-0.5 shrink-0', ACCENT_TEXT[color])} />
60
+ <div className="text-md text-neutral-500">{children}</div>
78
61
  </div>
79
62
  )
80
63
  }
@@ -15,7 +15,7 @@ export interface SettingsSectionTitleProps {
15
15
  export function SettingsSectionTitle({ children, className = '', testId }: SettingsSectionTitleProps) {
16
16
  return (
17
17
  <h3
18
- className={`text-xs font-medium text-neutral-400 uppercase tracking-wider ${className}`}
18
+ className={`text-sm font-medium text-neutral-400 uppercase tracking-wider ${className}`}
19
19
  data-testid={testId}
20
20
  >
21
21
  {children}
@@ -48,7 +48,7 @@ export function SnapshotCard({
48
48
 
49
49
  <div className="p-4">
50
50
  <div className="flex items-start justify-between gap-2">
51
- <h3 className="text-sm font-medium text-neutral-200 truncate">{title}</h3>
51
+ <h3 className="text-md font-medium text-neutral-200 truncate">{title}</h3>
52
52
  <Label
53
53
  text={statusLabelConfig[status].text}
54
54
  color={statusLabelConfig[status].color}
@@ -59,19 +59,19 @@ export function SnapshotCard({
59
59
  </div>
60
60
 
61
61
  {timestamp && (
62
- <p className="mt-1 text-xss text-neutral-500">{timestamp}</p>
62
+ <p className="mt-1 text-xs text-neutral-500">{timestamp}</p>
63
63
  )}
64
64
 
65
65
  {description && (
66
- <p className="mt-2 text-xs text-neutral-500 leading-relaxed line-clamp-2">{description}</p>
66
+ <p className="mt-2 text-sm text-neutral-500 leading-relaxed line-clamp-2">{description}</p>
67
67
  )}
68
68
 
69
69
  {stats && stats.length > 0 && (
70
70
  <div className="mt-3 grid grid-cols-2 gap-x-4 gap-y-2">
71
71
  {stats.map((stat) => (
72
72
  <div key={stat.label}>
73
- <p className="text-xss text-neutral-500">{stat.label}</p>
74
- <p className="text-xs font-medium text-neutral-200">{stat.value}</p>
73
+ <p className="text-xs text-neutral-500">{stat.label}</p>
74
+ <p className="text-sm font-medium text-neutral-200">{stat.value}</p>
75
75
  </div>
76
76
  ))}
77
77
  </div>
@@ -83,7 +83,7 @@ export function SnapshotCard({
83
83
  <button
84
84
  type="button"
85
85
  onClick={onSync}
86
- className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md bg-blue-400/15 text-blue-400 hover:bg-blue-400/25 transition-colors cursor-pointer"
86
+ className="flex items-center gap-1.5 px-2.5 py-1 text-sm font-medium rounded-md bg-blue-400/15 text-blue-400 hover:bg-blue-400/25 transition-colors cursor-pointer"
87
87
  >
88
88
  <RefreshCw className={cn('w-3 h-3', status === 'pending' && 'animate-spin')} />
89
89
  Sync
@@ -93,7 +93,7 @@ export function SnapshotCard({
93
93
  <button
94
94
  type="button"
95
95
  onClick={onView}
96
- className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md text-neutral-400 hover:bg-neutral-700 transition-colors cursor-pointer"
96
+ className="flex items-center gap-1.5 px-2.5 py-1 text-sm font-medium rounded-md text-neutral-400 hover:bg-neutral-700 transition-colors cursor-pointer"
97
97
  >
98
98
  <Eye className="w-3 h-3" />
99
99
  View
@@ -63,19 +63,19 @@ export function SnippetsPanel({
63
63
  return (
64
64
  <div className={cn('flex flex-col bg-neutral-800 rounded-lg overflow-hidden', className)}>
65
65
  <div className="flex items-center justify-between px-3 py-2 border-b border-neutral-700">
66
- <span className="text-xss font-semibold uppercase tracking-wider text-neutral-500">Snippets</span>
67
- <span className="text-xss text-neutral-500">{snippets.length} snippets</span>
66
+ <span className="text-xs font-semibold uppercase tracking-wider text-neutral-500">Snippets</span>
67
+ <span className="text-xs text-neutral-500">{snippets.length} snippets</span>
68
68
  </div>
69
69
  {showSearch && (
70
70
  <div className="px-2 py-2 border-b border-neutral-700">
71
- <div className="flex items-center gap-1.5 px-2 py-1 bg-[var(--background)] border border-neutral-700 rounded text-xs">
71
+ <div className="flex items-center gap-1.5 px-2 py-1 bg-[var(--background)] border border-neutral-700 rounded text-sm">
72
72
  <Search className="w-3 h-3 text-neutral-500 shrink-0" />
73
73
  <input
74
74
  type="text"
75
75
  placeholder="Search snippets..."
76
76
  value={searchQuery}
77
77
  onChange={(e) => setSearchQuery(e.target.value)}
78
- className="flex-1 bg-transparent text-neutral-200 placeholder-neutral-500 outline-none text-xs"
78
+ className="flex-1 bg-transparent text-neutral-200 placeholder-neutral-500 outline-none text-sm"
79
79
  />
80
80
  </div>
81
81
  </div>
@@ -90,7 +90,7 @@ export function SnippetsPanel({
90
90
  />
91
91
  ))}
92
92
  {filteredSnippets.length === 0 && (
93
- <p className="text-xss text-neutral-500 text-center py-4">No snippets found</p>
93
+ <p className="text-xs text-neutral-500 text-center py-4">No snippets found</p>
94
94
  )}
95
95
  </div>
96
96
  </div>
@@ -110,10 +110,10 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
110
110
  <div className="rounded-md border border-neutral-700 bg-neutral-900 hover:border-neutral-600 transition-colors">
111
111
  <div className="flex items-center justify-between gap-2 px-2.5 py-1.5">
112
112
  <div className="flex items-center gap-2 min-w-0">
113
- <span className="text-xs font-medium text-neutral-200 truncate">{snippet.label}</span>
113
+ <span className="text-sm font-medium text-neutral-200 truncate">{snippet.label}</span>
114
114
  {snippet.language && (
115
115
  <span
116
- className="px-1.5 py-0.5 text-xss font-medium rounded shrink-0"
116
+ className="px-1.5 py-0.5 text-xs font-medium rounded shrink-0"
117
117
  style={{ color: langColor, backgroundColor: `${langColor}20` }}
118
118
  >
119
119
  {snippet.language}
@@ -140,17 +140,17 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
140
140
  </div>
141
141
  </div>
142
142
  {snippet.description && (
143
- <p className="px-2.5 pb-1.5 text-xss text-neutral-400 leading-relaxed">{snippet.description}</p>
143
+ <p className="px-2.5 pb-1.5 text-xs text-neutral-400 leading-relaxed">{snippet.description}</p>
144
144
  )}
145
145
  <div className="mx-2.5 mb-2 rounded bg-[var(--background)] border border-neutral-700 overflow-hidden">
146
- <pre className="p-2 text-xss text-neutral-400 leading-relaxed overflow-x-auto max-h-24">
146
+ <pre className="p-2 text-xs text-neutral-400 leading-relaxed overflow-x-auto max-h-24">
147
147
  <code>{snippet.code}</code>
148
148
  </pre>
149
149
  </div>
150
150
  {snippet.tags && snippet.tags.length > 0 && (
151
151
  <div className="flex flex-wrap gap-1 px-2.5 pb-2">
152
152
  {snippet.tags.map((tag) => (
153
- <span key={tag} className="px-1.5 py-0.5 text-xss rounded bg-neutral-700 text-neutral-500">
153
+ <span key={tag} className="px-1.5 py-0.5 text-xs rounded bg-neutral-700 text-neutral-500">
154
154
  {tag}
155
155
  </span>
156
156
  ))}
@@ -74,7 +74,7 @@ export function SortDropdown({
74
74
  <div className="relative flex items-center" ref={ref} onKeyDown={handleKeyDown}>
75
75
  <button
76
76
  onClick={() => setIsOpen(!isOpen)}
77
- className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-xs transition-colors cursor-pointer ${FORM_COLORS[color].border} text-neutral-200 ${FORM_COLORS[color].hover}`}
77
+ className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-sm transition-colors cursor-pointer ${FORM_COLORS[color].border} text-neutral-200 ${FORM_COLORS[color].hover}`}
78
78
  >
79
79
  <span
80
80
  className={`${FORM_COLORS[color].accent} hover:brightness-125 transition-colors`}
@@ -94,7 +94,7 @@ export function SortDropdown({
94
94
  key={f.value}
95
95
  onClick={() => { onFieldChange(f.value); setIsOpen(false) }}
96
96
  onPointerEnter={() => setHighlightIdx(idx)}
97
- className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors cursor-pointer ${
97
+ className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
98
98
  idx === highlightIdx ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : field === f.value ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
99
99
  }`}
100
100
  >
@@ -1,19 +1,8 @@
1
1
  /** Status card displaying a list of labeled items with color-coded status indicators. */
2
2
 
3
- import {
4
- Settings, Shield, Database, Globe, Zap, Code, Terminal,
5
- Star, Cloud, Bell, Heart, Sparkles, Bot, Plug,
6
- } from 'lucide-react'
7
- import type { LucideIcon } from 'lucide-react'
8
- import type { IconName } from './icon-button.tsx'
3
+ import { iconMap, type IconName } from './icon-button.tsx'
9
4
  import { cn } from '../lib/cn.ts'
10
5
 
11
- const iconSubset: Partial<Record<IconName, LucideIcon>> = {
12
- settings: Settings, shield: Shield, database: Database, globe: Globe,
13
- zap: Zap, code: Code, terminal: Terminal, star: Star, cloud: Cloud,
14
- bell: Bell, heart: Heart, sparkles: Sparkles, bot: Bot, plug: Plug,
15
- }
16
-
17
6
  type StatusType = 'success' | 'warning' | 'error' | 'info' | 'neutral'
18
7
 
19
8
  export interface StatusItem {
@@ -55,7 +44,7 @@ export function StatusCard({
55
44
  action,
56
45
  className,
57
46
  }: StatusCardProps) {
58
- const Icon = icon ? iconSubset[icon] : undefined
47
+ const Icon = icon ? iconMap[icon] : undefined
59
48
 
60
49
  return (
61
50
  <div className={cn('rounded-lg border border-neutral-700 bg-neutral-800 overflow-hidden', className)}>
@@ -63,15 +52,15 @@ export function StatusCard({
63
52
  {Icon && (
64
53
  <Icon className="w-4 h-4 shrink-0" style={{ color: iconColor }} />
65
54
  )}
66
- <h3 className="text-sm font-medium text-neutral-200">{title}</h3>
55
+ <h3 className="text-md font-medium text-neutral-200">{title}</h3>
67
56
  </div>
68
57
 
69
58
  <div className="divide-y divide-neutral-700/60">
70
59
  {items.map((item) => (
71
60
  <div key={item.label} className="flex items-center justify-between px-4 py-2.5">
72
- <span className="text-xs text-neutral-400">{item.label}</span>
61
+ <span className="text-sm text-neutral-400">{item.label}</span>
73
62
  <div className="flex items-center gap-2">
74
- <span className={cn('text-xs font-medium', statusValueColor[item.status])}>
63
+ <span className={cn('text-sm font-medium', statusValueColor[item.status])}>
75
64
  {item.value}
76
65
  </span>
77
66
  <span className={cn('w-2 h-2 rounded-full shrink-0', statusDotColor[item.status])} />
@@ -85,7 +74,7 @@ export function StatusCard({
85
74
  <button
86
75
  type="button"
87
76
  onClick={action.onClick}
88
- className="text-xs text-blue-400 hover:text-blue-300 transition-colors cursor-pointer"
77
+ className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer"
89
78
  >
90
79
  {action.label}
91
80
  </button>
@@ -1,38 +1,12 @@
1
1
  /** Tab bar with underline, pill, and card variants, closable tabs, color-coded icons, and auto-collapse to icon-only. */
2
2
 
3
3
  import { useRef, useState, useEffect, useCallback } from 'react'
4
- import {
5
- Settings, Folder, File, Code, Terminal, Database,
6
- Globe, Star, Users, User, Tag, X,
7
- Zap, Shield, Sparkles, Eye, Lock, Search, Heart,
8
- } from 'lucide-react'
9
- import type { LucideIcon } from 'lucide-react'
10
- import type { IconName } from './icon-button.tsx'
4
+ import { X } from 'lucide-react'
5
+ import { iconMap, type IconName } from './icon-button.tsx'
11
6
  import { Badge, type BadgeColor } from './badge.tsx'
12
7
  import { cn } from '../lib/cn.ts'
13
8
  import { Tooltip } from './tooltip.tsx'
14
9
 
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
- sparkles: Sparkles,
30
- eye: Eye,
31
- lock: Lock,
32
- search: Search,
33
- heart: Heart,
34
- }
35
-
36
10
  export interface Tab {
37
11
  id: string
38
12
  label: string
@@ -55,8 +29,8 @@ export interface TabBarProps {
55
29
 
56
30
  const sizeConfig = {
57
31
  xss: { text: 'text-xss', icon: 'w-2.5 h-2.5', px: 'px-1.5', py: 'py-1', close: 'w-2.5 h-2.5', badgeSize: 'xss' as const, gap: 'gap-1' },
58
- xs: { text: 'text-xs', icon: 'w-3 h-3', px: 'px-2', py: 'py-1', close: 'w-3 h-3', badgeSize: 'xs' as const, gap: 'gap-1' },
59
- sm: { text: 'text-sm', icon: 'w-3.5 h-3.5', px: 'px-3', py: 'py-1.5', close: 'w-3 h-3', badgeSize: 'sm' as const, gap: 'gap-1.5' },
32
+ xs: { text: 'text-sm', icon: 'w-3 h-3', px: 'px-2', py: 'py-1', close: 'w-3 h-3', badgeSize: 'xs' as const, gap: 'gap-1' },
33
+ sm: { text: 'text-md', icon: 'w-3.5 h-3.5', px: 'px-3', py: 'py-1.5', close: 'w-3 h-3', badgeSize: 'sm' as const, gap: 'gap-1.5' },
60
34
  md: { text: 'text-base', icon: 'w-4 h-4', px: 'px-4', py: 'py-2', close: 'w-3.5 h-3.5', badgeSize: 'md' as const, gap: 'gap-2' },
61
35
  lg: { text: 'text-lg', icon: 'w-5 h-5', px: 'px-5', py: 'py-2.5', close: 'w-4 h-4', badgeSize: 'lg' as const, gap: 'gap-2' },
62
36
  }
@@ -102,7 +76,7 @@ function estimateTabsWidth(tabs: Tab[], size: keyof typeof sizeConfig): number {
102
76
  }
103
77
 
104
78
  function TabIcon({ icon, size, color }: { icon: IconName; size: 'xss' | 'xs' | 'sm' | 'md' | 'lg'; color?: string }) {
105
- const Icon = iconSubset[icon]
79
+ const Icon = iconMap[icon]
106
80
  if (!Icon) return null
107
81
  const s = sizeConfig[size]
108
82
  const c = getColors(color)
@@ -7,22 +7,9 @@
7
7
  * - List items - inline toggle controls
8
8
  */
9
9
 
10
- export type ToggleColor =
11
- | 'blue'
12
- | 'green'
13
- | 'red'
14
- | 'orange'
15
- | 'cyan'
16
- | 'yellow'
17
- | 'purple'
18
- | 'indigo'
19
- | 'emerald'
20
- | 'amber'
21
- | 'violet'
22
- | 'neutral'
23
- | 'sky'
24
- | 'pink'
25
- | 'teal'
10
+ import { type AccentColor } from '../lib/form-colors.ts'
11
+
12
+ export type ToggleColor = AccentColor
26
13
 
27
14
  // Border colors per accent
28
15
  const BORDER_COLORS: Record<ToggleColor, { idle: string; active: string }> = {
@@ -90,8 +77,6 @@ const TOGGLE_SIZES: Record<ToggleSize, { track: string; knob: string; translate:
90
77
  lg: { track: 'w-14 h-7', knob: 'w-6 h-6', translate: 'translate-x-7' },
91
78
  }
92
79
 
93
- export type ToggleVariant = 'outline' | 'filled'
94
-
95
80
  export interface ToggleProps {
96
81
  checked: boolean
97
82
  onChange: (checked: boolean) => void
@@ -99,7 +84,6 @@ export interface ToggleProps {
99
84
  size?: ToggleSize
100
85
  className?: string
101
86
  color?: ToggleColor
102
- variant?: ToggleVariant
103
87
  /** Test ID for E2E testing */
104
88
  testId?: string
105
89
  }
@@ -1,6 +1,7 @@
1
- import { useState, useRef, useLayoutEffect, useEffect } from 'react'
1
+ import { useState, useRef, useLayoutEffect, useEffect, useCallback } from 'react'
2
2
  import type { ReactNode } from 'react'
3
3
  import { createPortal } from 'react-dom'
4
+ import { useClickOutside } from '../hooks/use-click-outside.ts'
4
5
 
5
6
  export interface TooltipContent {
6
7
  title?: string
@@ -201,19 +202,8 @@ export function Tooltip({
201
202
  }
202
203
 
203
204
  // Click-outside dismissal for click trigger mode
204
- useEffect(() => {
205
- if (trigger !== 'click' || !isVisible) return
206
- const handleClickOutside = (e: MouseEvent) => {
207
- if (
208
- triggerRef.current && !triggerRef.current.contains(e.target as Node) &&
209
- tooltipRef.current && !tooltipRef.current.contains(e.target as Node)
210
- ) {
211
- setIsVisible(false)
212
- }
213
- }
214
- document.addEventListener('mousedown', handleClickOutside)
215
- return () => document.removeEventListener('mousedown', handleClickOutside)
216
- }, [trigger, isVisible])
205
+ const dismissClickTooltip = useCallback(() => setIsVisible(false), [])
206
+ useClickOutside([triggerRef, tooltipRef], isVisible && trigger === 'click', dismissClickTooltip)
217
207
 
218
208
  const updatePosition = () => {
219
209
  if (!triggerRef.current) return
@@ -262,18 +252,16 @@ export function Tooltip({
262
252
  style={{
263
253
  top: coords.top,
264
254
  left: coords.left,
265
- opacity: isVisible ? 1 : 0,
266
- visibility: isVisible ? 'visible' : 'hidden',
267
- transition: 'opacity 150ms, visibility 150ms',
255
+ opacity: 1,
268
256
  ...(multiline ? { maxWidth: maxWidth ?? '80vw' } : {}),
269
257
  ...(maxHeight ? { maxHeight, overflowY: 'auto' as const } : {}),
270
258
  }}
271
259
  onMouseEnter={interactive && trigger !== 'click' ? show : undefined}
272
260
  onMouseLeave={interactive && trigger !== 'click' ? hide : undefined}
273
261
  >
274
- {content.title && <p className="text-sm text-neutral-200 font-medium">{content.title}</p>}
275
- <div className={`text-xs text-neutral-400 ${content.title ? 'mt-0.5' : ''}`}>{content.description}</div>
276
- {content.extra && <p className="text-xs text-orange-400/70 mt-0.5">{content.extra}</p>}
262
+ {content.title && <p className="text-md text-neutral-200 font-medium">{content.title}</p>}
263
+ <div className={`text-sm text-neutral-400 ${content.title ? 'mt-0.5' : ''}`}>{content.description}</div>
264
+ {content.extra && <p className="text-sm text-orange-400/70 mt-0.5">{content.extra}</p>}
277
265
  <div className={arrowClasses} />
278
266
  </div>
279
267
  )
@@ -290,7 +278,7 @@ export function Tooltip({
290
278
  >
291
279
  {children}
292
280
  </div>
293
- {createPortal(tooltipContent, document.body)}
281
+ {isVisible && createPortal(tooltipContent, document.body)}
294
282
  </>
295
283
  )
296
284
  }