@toolr/ui-design 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/ai-manifest.json +35 -20
  2. package/components/composites/dashboard-list-item.tsx +172 -0
  3. package/components/composites/dashboard-panel.tsx +218 -0
  4. package/components/content/info-panel-primitives.tsx +9 -8
  5. package/components/diagrams/diagram-utils.tsx +2 -1
  6. package/components/hooks/use-dropdown-portal.ts +39 -0
  7. package/components/lib/accent-context.ts +10 -0
  8. package/components/lib/{ai-tools.tsx → coding-agents.tsx} +23 -8
  9. package/components/lib/custom-icons.tsx +37 -0
  10. package/components/lib/git-providers.tsx +39 -0
  11. package/components/lib/theme-engine.ts +59 -10
  12. package/components/lib/toolr-brand.tsx +23 -9
  13. package/components/sections/captured-issues/captured-issues-panel.tsx +17 -8
  14. package/components/sections/{ai-tools-paths/tools-paths-panel.tsx → coding-agent-paths/agent-paths-panel.tsx} +70 -62
  15. package/components/sections/coding-agent-paths/index.ts +37 -0
  16. package/components/sections/{ai-tools-paths → coding-agent-paths}/types.ts +28 -28
  17. package/components/sections/coding-agent-paths/use-agent-paths.ts +159 -0
  18. package/components/sections/golden-snapshots/file-diff-viewer.tsx +10 -9
  19. package/components/sections/golden-snapshots/golden-sync-panel.tsx +12 -3
  20. package/components/sections/golden-snapshots/snapshot-manager.tsx +9 -7
  21. package/components/sections/golden-snapshots/status-overview.tsx +8 -8
  22. package/components/sections/golden-snapshots/version-manager.tsx +6 -6
  23. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +3 -3
  24. package/components/sections/prompt-editor/index.ts +1 -1
  25. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +13 -5
  26. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +18 -10
  27. package/components/sections/prompt-editor/types.ts +2 -2
  28. package/components/sections/report-bug/report-bug-form.tsx +12 -4
  29. package/components/sections/report-bug/screenshot-uploader.tsx +11 -3
  30. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +12 -4
  31. package/components/sections/snapshot-browser/snapshot-tree.tsx +5 -4
  32. package/components/sections/snapshot-browser/types.ts +1 -1
  33. package/components/sections/snippets-editor/snippets-editor.tsx +16 -9
  34. package/components/settings/SettingsHeader.tsx +2 -2
  35. package/components/settings/SettingsPanel.tsx +11 -3
  36. package/components/settings/SettingsTreeNav.tsx +15 -9
  37. package/components/ui/action-dialog.tsx +24 -30
  38. package/components/ui/ai-action-button.tsx +10 -7
  39. package/components/ui/ai-execution-action-buttons.tsx +13 -5
  40. package/components/ui/badge.tsx +7 -4
  41. package/components/ui/bottom-panel-header.tsx +9 -5
  42. package/components/ui/breadcrumb.tsx +9 -1
  43. package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +13 -5
  44. package/components/ui/checkbox.tsx +6 -3
  45. package/components/ui/collapsible-section.tsx +38 -29
  46. package/components/ui/confirm-badge.tsx +7 -4
  47. package/components/ui/cookie-consent.tsx +13 -7
  48. package/components/ui/detail-section.tsx +24 -16
  49. package/components/ui/detail-view-wrapper.tsx +30 -22
  50. package/components/ui/editor-placeholder-card.tsx +28 -24
  51. package/components/ui/editor-toolbar.tsx +7 -4
  52. package/components/ui/execution-details-panel.tsx +10 -5
  53. package/components/ui/file-structure-section.tsx +3 -3
  54. package/components/ui/file-tree.tsx +3 -1
  55. package/components/ui/files-panel.tsx +147 -27
  56. package/components/ui/filter-dropdown.tsx +84 -74
  57. package/components/ui/form-actions.tsx +14 -6
  58. package/components/ui/frontmatter-form-header.tsx +10 -2
  59. package/components/ui/icon-button.tsx +22 -9
  60. package/components/ui/input.tsx +7 -4
  61. package/components/ui/label.tsx +5 -5
  62. package/components/ui/layout-tab-bar.tsx +7 -5
  63. package/components/ui/modal.tsx +18 -4
  64. package/components/ui/nav-card.tsx +6 -3
  65. package/components/ui/navigation-bar.tsx +37 -9
  66. package/components/ui/number-input.tsx +8 -4
  67. package/components/ui/project-explorer.tsx +666 -0
  68. package/components/ui/registry-browser.tsx +12 -1
  69. package/components/ui/registry-card.tsx +49 -42
  70. package/components/ui/registry-detail.tsx +34 -11
  71. package/components/ui/resizable-textarea.tsx +18 -11
  72. package/components/ui/scope-badge.tsx +18 -11
  73. package/components/ui/segmented-toggle.tsx +5 -2
  74. package/components/ui/select.tsx +12 -9
  75. package/components/ui/selection-grid.tsx +36 -37
  76. package/components/ui/setting-row.tsx +2 -2
  77. package/components/ui/settings-card.tsx +10 -3
  78. package/components/ui/settings-info-box.tsx +9 -5
  79. package/components/ui/settings-section-title.tsx +14 -2
  80. package/components/ui/snapshot-card.tsx +10 -2
  81. package/components/ui/snippets-panel.tsx +4 -2
  82. package/components/ui/sort-dropdown.tsx +39 -29
  83. package/components/ui/status-card.tsx +9 -1
  84. package/components/ui/tab-bar.tsx +12 -9
  85. package/components/ui/toggle.tsx +13 -7
  86. package/components/ui/tooltip.tsx +9 -1
  87. package/dist/content.js +8 -8
  88. package/dist/diagrams.d.ts +0 -1
  89. package/dist/index.d.ts +421 -182
  90. package/dist/index.js +2984 -1691
  91. package/dist/tokens/primitives.css +28 -6
  92. package/dist/tokens/semantic.css +15 -15
  93. package/dist/tokens/theme.css +23 -0
  94. package/index.ts +25 -11
  95. package/package.json +1 -1
  96. package/tokens/primitives.css +28 -6
  97. package/tokens/semantic.css +15 -15
  98. package/tokens/theme.css +23 -0
  99. package/components/sections/ai-tools-paths/index.ts +0 -37
  100. package/components/sections/ai-tools-paths/use-tools-paths.ts +0 -159
@@ -1,13 +1,15 @@
1
- import { useState, useEffect, useRef, type ReactNode } from 'react'
1
+ import { useState, useEffect, type ReactNode } from 'react'
2
+ import { createPortal } from 'react-dom'
2
3
  import { ChevronDown, Check, X, Search, Filter } from 'lucide-react'
3
4
  import { useClickOutside } from '../hooks/use-click-outside.ts'
4
- import { useDropdownMaxHeight } from '../hooks/use-dropdown-max-height.ts'
5
+ import { useDropdownPortal } from '../hooks/use-dropdown-portal.ts'
5
6
  import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
7
+ import { useAccentColor } from '../lib/accent-context.ts'
6
8
 
7
9
  const SEARCH_THRESHOLD = 20
8
10
 
9
11
  const VARIANT_CLASSES = {
10
- filled: { bg: 'bg-neutral-800', hoverBg: 'hover:bg-neutral-700' },
12
+ filled: { bg: 'bg-neutral-960', hoverBg: 'hover:bg-neutral-700' },
11
13
  outline: { bg: 'bg-transparent', hoverBg: 'hover:bg-neutral-700' },
12
14
  }
13
15
 
@@ -19,7 +21,7 @@ export interface FilterDropdownProps {
19
21
  labelExtra?: ReactNode
20
22
  clearable?: boolean
21
23
  variant?: 'filled' | 'outline'
22
- color?: FormColor
24
+ accentColor?: FormColor
23
25
  }
24
26
 
25
27
  export function FilterDropdown({
@@ -30,19 +32,20 @@ export function FilterDropdown({
30
32
  labelExtra,
31
33
  clearable = true,
32
34
  variant = 'outline',
33
- color = 'blue',
35
+ accentColor,
34
36
  }: FilterDropdownProps) {
37
+ const contextAccent = useAccentColor()
38
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
35
39
  const [isOpen, setIsOpen] = useState(false)
36
40
  const [search, setSearch] = useState('')
37
41
  const [highlightIdx, setHighlightIdx] = useState(-1)
38
- const ref = useRef<HTMLDivElement>(null)
39
- const menuRef = useDropdownMaxHeight<HTMLDivElement>(isOpen)
40
- const searchRef = useRef<HTMLInputElement>(null)
42
+ const { triggerRef, menuRef, position } = useDropdownPortal(isOpen)
43
+ const searchRef = { current: null as HTMLInputElement | null }
41
44
  const isActive = value !== 'all'
42
45
  const showSearch = options.length > SEARCH_THRESHOLD
43
46
  const v = VARIANT_CLASSES[variant]
44
47
 
45
- useClickOutside(ref, isOpen, () => setIsOpen(false))
48
+ useClickOutside([triggerRef, menuRef], isOpen, () => setIsOpen(false))
46
49
 
47
50
  useEffect(() => {
48
51
  if (!isOpen) { setSearch(''); setHighlightIdx(-1) }
@@ -85,21 +88,85 @@ export function FilterDropdown({
85
88
  }
86
89
  }
87
90
 
91
+ const menu = isOpen && createPortal(
92
+ <div
93
+ ref={menuRef}
94
+ role="listbox"
95
+ style={{ position: 'fixed', top: position.top, left: position.left, minWidth: position.minWidth, zIndex: 9999 }}
96
+ className={`whitespace-nowrap bg-[var(--popover)] border ${FORM_COLORS[effectiveColor].border} rounded-lg shadow-lg overflow-hidden`}
97
+ >
98
+ {showSearch && (
99
+ <div className={`sticky top-0 p-1.5 bg-[var(--popover)] border-b ${FORM_COLORS[effectiveColor].border} z-10`}>
100
+ <div className="relative">
101
+ <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 text-neutral-500" />
102
+ <input
103
+ ref={(el) => { searchRef.current = el }}
104
+ type="text"
105
+ value={search}
106
+ onChange={(e) => setSearch(e.target.value)}
107
+ onKeyDown={handleKeyDown}
108
+ placeholder="Search..."
109
+ className={`w-full pl-6 pr-2 py-1 text-sm bg-[var(--popover)] border border-neutral-600 rounded text-neutral-200 placeholder-neutral-500 outline-none ${FORM_COLORS[effectiveColor].focus}`}
110
+ />
111
+ </div>
112
+ </div>
113
+ )}
114
+ {hasAllOption && (
115
+ <button
116
+ data-idx={0}
117
+ onClick={() => handleSelect('all')}
118
+ className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
119
+ highlightIdx === 0
120
+ ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200`
121
+ : !isActive ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
122
+ }`}
123
+ >
124
+ <Check className={`w-3 h-3 shrink-0 ${!isActive ? FORM_COLORS[effectiveColor].accent : 'invisible'}`} />
125
+ <span>All</span>
126
+ </button>
127
+ )}
128
+ {filtered.map((opt, i) => {
129
+ const idx = i + (hasAllOption ? 1 : 0)
130
+ const isHighlighted = highlightIdx === idx
131
+ const isSelected = value === opt.value
132
+ return (
133
+ <button
134
+ key={opt.value}
135
+ data-idx={idx}
136
+ onClick={() => handleSelect(opt.value)}
137
+ className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
138
+ isHighlighted
139
+ ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200`
140
+ : isSelected ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
141
+ }`}
142
+ >
143
+ <Check className={`w-3 h-3 shrink-0 ${isSelected ? FORM_COLORS[effectiveColor].accent : 'invisible'}`} />
144
+ <span>{opt.label}</span>
145
+ </button>
146
+ )
147
+ })}
148
+ {showSearch && search && filtered.length === 0 && (
149
+ <div className="px-3 py-2 text-sm text-neutral-500">No matches</div>
150
+ )}
151
+ </div>,
152
+ document.body,
153
+ )
154
+
88
155
  return (
89
- <div className="relative flex items-center" ref={ref} onKeyDown={handleKeyDown}>
156
+ <div className="relative flex items-center" ref={triggerRef} onKeyDown={handleKeyDown}>
90
157
  <button
91
158
  aria-expanded={isOpen}
92
159
  aria-haspopup="listbox"
93
160
  onClick={() => setIsOpen(!isOpen)}
94
- className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-sm transition-colors cursor-pointer ${
161
+ className={`flex items-center gap-1.5 py-1 px-2 rounded-md border ${v.bg} text-sm transition-colors cursor-pointer ${
95
162
  isActive
96
- ? `${clearable ? 'rounded-r-none border-r-0' : ''} ${FORM_COLORS[color].border} text-neutral-200 ${FORM_COLORS[color].hover}`
163
+ ? `${clearable ? 'rounded-r-none border-r-0' : ''} ${FORM_COLORS[effectiveColor].border} text-neutral-200 ${FORM_COLORS[effectiveColor].hover}`
97
164
  : isOpen
98
- ? `${FORM_COLORS[color].border} text-neutral-200`
99
- : `${FORM_COLORS[color].border} text-neutral-400 ${FORM_COLORS[color].hover} hover:text-neutral-200`
165
+ ? `${FORM_COLORS[effectiveColor].border} text-neutral-200`
166
+ : `${FORM_COLORS[effectiveColor].border} text-neutral-400 ${FORM_COLORS[effectiveColor].hover} hover:text-neutral-200`
100
167
  }`}
101
168
  >
102
- <Filter className={`w-3 h-3 ${isActive ? FORM_COLORS[color].accent : ''}`} />
169
+ <Filter className={`w-3 h-3 ${isActive ? FORM_COLORS[effectiveColor].accent : ''}`} />
103
170
  {labelExtra}
104
171
  <span className="truncate">{selectedLabel}</span>
105
172
  <ChevronDown className={`w-3 h-3 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
@@ -108,69 +175,12 @@ export function FilterDropdown({
108
175
  <button
109
176
  aria-label="Clear filter"
110
177
  onClick={() => onChange('all')}
111
- className={`flex items-center justify-center h-7 px-1.5 rounded-r-md border border-l-0 ${FORM_COLORS[color].border} ${v.bg} text-neutral-400 ${FORM_COLORS[color].hover} hover:text-neutral-200 transition-colors cursor-pointer`}
178
+ className={`flex items-center justify-center py-1 px-1.5 rounded-r-md border border-l-0 ${FORM_COLORS[effectiveColor].border} ${v.bg} text-neutral-400 ${FORM_COLORS[effectiveColor].hover} hover:text-neutral-200 transition-colors cursor-pointer`}
112
179
  >
113
180
  <X className="w-3 h-3" />
114
181
  </button>
115
182
  )}
116
-
117
- {isOpen && (
118
- <div ref={menuRef} role="listbox" className={`absolute right-0 top-full z-50 mt-1 min-w-[140px] whitespace-nowrap bg-[var(--popover)] border ${FORM_COLORS[color].border} rounded-lg shadow-lg overflow-hidden`}>
119
- {showSearch && (
120
- <div className={`sticky top-0 p-1.5 bg-[var(--popover)] border-b ${FORM_COLORS[color].border} z-10`}>
121
- <div className="relative">
122
- <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 text-neutral-500" />
123
- <input
124
- ref={searchRef}
125
- type="text"
126
- value={search}
127
- onChange={(e) => setSearch(e.target.value)}
128
- onKeyDown={handleKeyDown}
129
- placeholder="Search..."
130
- className={`w-full pl-6 pr-2 py-1 text-sm bg-[var(--popover)] border border-neutral-600 rounded text-neutral-200 placeholder-neutral-500 outline-none ${FORM_COLORS[color].focus}`}
131
- />
132
- </div>
133
- </div>
134
- )}
135
- {hasAllOption && (
136
- <button
137
- data-idx={0}
138
- onClick={() => handleSelect('all')}
139
- className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
140
- highlightIdx === 0
141
- ? `${FORM_COLORS[color].selectedBg} text-neutral-200`
142
- : !isActive ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
143
- }`}
144
- >
145
- <Check className={`w-3 h-3 shrink-0 ${!isActive ? FORM_COLORS[color].accent : 'invisible'}`} />
146
- <span>All</span>
147
- </button>
148
- )}
149
- {filtered.map((opt, i) => {
150
- const idx = i + (hasAllOption ? 1 : 0)
151
- const isHighlighted = highlightIdx === idx
152
- const isSelected = value === opt.value
153
- return (
154
- <button
155
- key={opt.value}
156
- data-idx={idx}
157
- onClick={() => handleSelect(opt.value)}
158
- className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
159
- isHighlighted
160
- ? `${FORM_COLORS[color].selectedBg} text-neutral-200`
161
- : isSelected ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
162
- }`}
163
- >
164
- <Check className={`w-3 h-3 shrink-0 ${isSelected ? FORM_COLORS[color].accent : 'invisible'}`} />
165
- <span>{opt.label}</span>
166
- </button>
167
- )
168
- })}
169
- {showSearch && search && filtered.length === 0 && (
170
- <div className="px-3 py-2 text-sm text-neutral-500">No matches</div>
171
- )}
172
- </div>
173
- )}
183
+ {menu}
174
184
  </div>
175
185
  )
176
186
  }
@@ -1,4 +1,6 @@
1
1
  import { IconButton, type IconName, type IconButtonProps, type IconButtonStatus } from './icon-button.tsx'
2
+ import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
3
+ import type { FormColor } from '../lib/form-colors.ts'
2
4
  import { cn } from '../lib/cn.ts'
3
5
 
4
6
  export interface FormActionsProps {
@@ -18,7 +20,7 @@ export interface FormActionsProps {
18
20
  onConfirm?: () => void
19
21
  confirmTooltip?: string
20
22
  confirmIcon?: IconName
21
- confirmColor?: IconButtonProps['color']
23
+ confirmColor?: IconButtonProps['accentColor']
22
24
  confirmDisabled?: boolean
23
25
  confirmStatus?: IconButtonStatus
24
26
 
@@ -31,6 +33,7 @@ export interface FormActionsProps {
31
33
 
32
34
  border?: boolean
33
35
  padding?: 'compact' | 'normal' | 'modal'
36
+ accentColor?: FormColor
34
37
  }
35
38
 
36
39
  const PADDING_CLASSES = {
@@ -65,11 +68,15 @@ export function FormActions({
65
68
  statusText,
66
69
  border,
67
70
  padding = 'normal',
71
+ accentColor,
68
72
  }: FormActionsProps) {
73
+ const contextAccent = useAccentColor()
74
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
69
75
  const showBorder = border ?? DEFAULT_BORDER[padding]
70
76
  const hasLeft = onBack || statusText
71
77
 
72
78
  return (
79
+ <AccentColorProvider value={effectiveColor}>
73
80
  <div className={cn(
74
81
  'flex items-center gap-2',
75
82
  hasLeft ? 'justify-between' : 'justify-end',
@@ -81,7 +88,7 @@ export function FormActions({
81
88
  {onBack && (
82
89
  <IconButton
83
90
  icon="arrow-left"
84
- color="neutral"
91
+ accentColor="neutral"
85
92
  onClick={onBack}
86
93
  tooltip={{ description: backTooltip }}
87
94
  />
@@ -93,7 +100,7 @@ export function FormActions({
93
100
  {onMinimize && (
94
101
  <IconButton
95
102
  icon="minimize"
96
- color="neutral"
103
+ accentColor="neutral"
97
104
  onClick={onMinimize}
98
105
  tooltip={{ description: minimizeTooltip }}
99
106
  />
@@ -101,7 +108,7 @@ export function FormActions({
101
108
  {onCancel && (
102
109
  <IconButton
103
110
  icon="x"
104
- color="neutral"
111
+ accentColor="neutral"
105
112
  onClick={onCancel}
106
113
  tooltip={{ description: cancelTooltip }}
107
114
  />
@@ -109,7 +116,7 @@ export function FormActions({
109
116
  {onConfirm && (
110
117
  <IconButton
111
118
  icon={confirmIcon}
112
- color={confirmColor}
119
+ accentColor={confirmColor}
113
120
  onClick={onConfirm}
114
121
  disabled={confirmDisabled}
115
122
  status={confirmStatus}
@@ -119,12 +126,13 @@ export function FormActions({
119
126
  {onNext && (
120
127
  <IconButton
121
128
  icon="arrow-right"
122
- color="blue"
129
+ accentColor="neutral"
123
130
  onClick={onNext}
124
131
  tooltip={{ description: nextTooltip }}
125
132
  />
126
133
  )}
127
134
  </div>
128
135
  </div>
136
+ </AccentColorProvider>
129
137
  )
130
138
  }
@@ -1,6 +1,8 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import { ChevronRight } from 'lucide-react'
3
3
  import { Checkbox } from './checkbox.tsx'
4
+ import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
5
+ import type { FormColor } from '../lib/form-colors.ts'
4
6
 
5
7
  export interface FrontmatterFormHeaderProps {
6
8
  collapsed: boolean
@@ -13,6 +15,7 @@ export interface FrontmatterFormHeaderProps {
13
15
  /** Toggle frontmatter on/off */
14
16
  onFrontmatterToggle?: (enabled: boolean) => void
15
17
  readOnly?: boolean
18
+ accentColor?: FormColor
16
19
  }
17
20
 
18
21
  export function FrontmatterFormHeader({
@@ -23,16 +26,20 @@ export function FrontmatterFormHeader({
23
26
  frontmatterEnabled,
24
27
  onFrontmatterToggle,
25
28
  readOnly,
29
+ accentColor,
26
30
  }: FrontmatterFormHeaderProps) {
31
+ const contextAccent = useAccentColor()
32
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
27
33
  const hasFm = frontmatterEnabled !== false
28
34
 
29
35
  return (
30
- <div className="bg-neutral-900 border-b border-neutral-800 select-none">
36
+ <AccentColorProvider value={effectiveColor}>
37
+ <div className="bg-neutral-980 border-b border-neutral-960 select-none">
31
38
  {/* Header bar — always visible, always expandable */}
32
39
  <button
33
40
  type="button"
34
41
  onClick={onToggle}
35
- className="flex items-center gap-2 w-full px-3 py-3 hover:bg-neutral-800/50 cursor-pointer transition-colors"
42
+ className="flex items-center gap-2 w-full px-3 py-3 hover:bg-neutral-960/50 cursor-pointer transition-colors"
36
43
  >
37
44
  <ChevronRight
38
45
  className={`w-3.5 h-3.5 text-neutral-500 transition-transform duration-150 ${
@@ -76,5 +83,6 @@ export function FrontmatterFormHeader({
76
83
  </div>
77
84
  )}
78
85
  </div>
86
+ </AccentColorProvider>
79
87
  )
80
88
  }
@@ -37,9 +37,12 @@ import {
37
37
  PanelBottomClose,
38
38
  Package, Wrench, Store, ScrollText, Cpu, FlaskConical, Layers, Timer, Camera,
39
39
  AlertCircle, FileCode, Gauge, Home, PieChart, Settings2,
40
+ FolderSearch, PanelLeftClose, History,
41
+ Github, Gitlab, GitBranch,
40
42
  } from 'lucide-react'
41
43
  import type { LucideIcon } from 'lucide-react'
42
44
  import { type AccentColor } from '../lib/form-colors.ts'
45
+ import { useAccentColor } from '../lib/accent-context.ts'
43
46
  import { Tooltip, type TooltipContent } from './tooltip.tsx'
44
47
 
45
48
  export const iconMap = {
@@ -140,6 +143,14 @@ export const iconMap = {
140
143
  'home': Home,
141
144
  'pie-chart': PieChart,
142
145
  'settings-2': Settings2,
146
+ 'folder-search': FolderSearch,
147
+ 'panel-left-close': PanelLeftClose,
148
+ 'trash-2': Trash2,
149
+ 'rotate-ccw': RotateCcw,
150
+ 'history': History,
151
+ 'github': Github,
152
+ 'gitlab': Gitlab,
153
+ 'git-branch': GitBranch,
143
154
  } as const
144
155
 
145
156
  export type IconName = keyof typeof iconMap
@@ -147,7 +158,7 @@ export type IconName = keyof typeof iconMap
147
158
  export interface ActionItem {
148
159
  icon: IconName
149
160
  onClick: () => void
150
- color?: IconButtonColor
161
+ accentColor?: IconButtonColor
151
162
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
152
163
  variant?: IconButtonVariant
153
164
  tooltip?: TooltipContent
@@ -169,11 +180,11 @@ export interface IconButtonProps {
169
180
  /** When provided, renders an <a> tag instead of <button>. Opens in a new tab. */
170
181
  href?: string
171
182
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
172
- color?: IconButtonColor
183
+ accentColor?: IconButtonColor
173
184
  variant?: IconButtonVariant
174
185
  active?: boolean
175
186
  disabled?: boolean
176
- /** Async action status. Overrides icon, color, and active state when set. */
187
+ /** Async action status. Overrides icon, accentColor, and active state when set. */
177
188
  status?: IconButtonStatus
178
189
  /** Tooltip shown on hover. Title and description are required. */
179
190
  tooltip?: TooltipContent
@@ -276,7 +287,7 @@ export function IconButton({
276
287
  onClick,
277
288
  href,
278
289
  size = 'sm',
279
- color = 'neutral',
290
+ accentColor,
280
291
  variant = 'outline',
281
292
  active = false,
282
293
  disabled = false,
@@ -289,7 +300,9 @@ export function IconButton({
289
300
  className = '',
290
301
  testId,
291
302
  }: IconButtonProps) {
292
- const resolvedColor = status ? (statusConfig[status].color ?? color) : color
303
+ const contextAccent = useAccentColor()
304
+ const effectiveColor = accentColor ?? contextAccent ?? 'neutral'
305
+ const resolvedColor = status ? (statusConfig[status].color ?? effectiveColor) : effectiveColor
293
306
  const resolvedActive = status ? statusConfig[status].active : active
294
307
 
295
308
  const colorStyle = colorClasses[resolvedColor] ?? colorClasses.neutral
@@ -299,7 +312,7 @@ export function IconButton({
299
312
  const borderClass = isOutline ? colorStyle.border : 'border-neutral-600'
300
313
 
301
314
  const sharedClassName = `
302
- relative flex items-center justify-center border transition-colors ${isOutline ? '' : 'bg-neutral-800'}
315
+ relative flex items-center justify-center border transition-colors ${isOutline ? '' : 'bg-neutral-960'}
303
316
  ${sizeClasses[size]}
304
317
  ${colorStyle.text} ${borderClass}
305
318
  ${resolvedActive ? colorStyle.active : ''}
@@ -372,16 +385,16 @@ export interface CollapseButtonProps {
372
385
  collapsed: boolean
373
386
  onToggle: () => void
374
387
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
375
- color?: IconButtonColor
388
+ accentColor?: IconButtonColor
376
389
  tooltipPosition?: 'bottom' | 'bottom-left' | 'left' | 'right' | 'top' | 'top-left' | 'top-right'
377
390
  }
378
391
 
379
- export function CollapseButton({ collapsed, onToggle, size = 'xss', color, tooltipPosition = 'bottom-left' }: CollapseButtonProps) {
392
+ export function CollapseButton({ collapsed, onToggle, size = 'xss', accentColor, tooltipPosition = 'bottom-left' }: CollapseButtonProps) {
380
393
  return (
381
394
  <IconButton
382
395
  icon={collapsed ? 'chevrons-up-down' : 'chevrons-down-up'}
383
396
  onClick={onToggle}
384
- color={color}
397
+ accentColor={accentColor}
385
398
  tooltip={{
386
399
  title: collapsed ? 'Expand all' : 'Collapse all',
387
400
  description: collapsed ? 'Expand all folders' : 'Collapse all folders',
@@ -21,6 +21,7 @@ import { forwardRef, useEffect, useId, useRef, useState, type InputHTMLAttribute
21
21
  import { Search, X, Eye, EyeOff } from 'lucide-react'
22
22
  import { DebounceBorderOverlay } from './debounce-border-overlay.tsx'
23
23
  import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
24
+ import { useAccentColor } from '../lib/accent-context.ts'
24
25
 
25
26
  export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size' | 'type'> {
26
27
  value: string
@@ -29,7 +30,7 @@ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
29
30
  debounceMs?: number
30
31
  error?: boolean | string
31
32
  variant?: 'filled' | 'outline'
32
- color?: FormColor
33
+ accentColor?: FormColor
33
34
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
34
35
  mono?: boolean
35
36
  /** Test ID for E2E testing */
@@ -47,7 +48,7 @@ const sizeClasses = {
47
48
  }
48
49
 
49
50
  const variantClasses = {
50
- filled: 'bg-neutral-800',
51
+ filled: 'bg-neutral-960',
51
52
  outline: 'bg-transparent',
52
53
  }
53
54
 
@@ -65,7 +66,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input({
65
66
  debounceMs = 0,
66
67
  error,
67
68
  variant = 'outline',
68
- color = 'blue',
69
+ accentColor,
69
70
  size = 'sm',
70
71
  disabled = false,
71
72
  className = '',
@@ -76,6 +77,8 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input({
76
77
  onBlur: onBlurProp,
77
78
  ...props
78
79
  }, ref) {
80
+ const contextAccent = useAccentColor()
81
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
79
82
  const hasError = Boolean(error)
80
83
  const hasAction = actionSlot && !disabled
81
84
  const [hovered, setHovered] = useState(false)
@@ -156,7 +159,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input({
156
159
  focus:outline-none transition-colors
157
160
  disabled:opacity-50 disabled:cursor-not-allowed
158
161
  ${sizeClasses[size]}
159
- ${hasError ? 'border-red-500 focus:border-red-500' : `${variantClasses[variant]} ${FORM_COLORS[color].border} ${FORM_COLORS[color].focus}`}
162
+ ${hasError ? 'border-red-500 focus:border-red-500' : `${variantClasses[variant]} ${FORM_COLORS[effectiveColor].border} ${FORM_COLORS[effectiveColor].focus}`}
160
163
  ${mono ? 'font-mono' : ''}
161
164
  ${isSearch ? 'pl-8 pr-8' : (hasAction || isPassword) ? 'pr-8' : ''}
162
165
  ${className}
@@ -20,7 +20,7 @@ export type LabelColor = AccentColor
20
20
 
21
21
  export interface LabelProps {
22
22
  text: string
23
- color: LabelColor
23
+ accentColor: LabelColor
24
24
  /** Leading icon(s). Pass a single name or an array for multiple icons before text. */
25
25
  icon?: IconName | IconName[]
26
26
  /** Custom icon component. Takes precedence over icon. */
@@ -91,7 +91,7 @@ function transformText(text: string, textTransform?: 'capitalize' | 'lowercase'
91
91
 
92
92
  export function Label({
93
93
  text,
94
- color,
94
+ accentColor,
95
95
  icon,
96
96
  IconComponent: CustomIcon,
97
97
  tooltip,
@@ -112,7 +112,7 @@ export function Label({
112
112
  s.text,
113
113
  'border rounded font-medium leading-none',
114
114
  hasProgress ? 'relative overflow-hidden' : '',
115
- colorClasses[color],
115
+ colorClasses[accentColor],
116
116
  onClick ? 'cursor-pointer hover:brightness-125 transition-all' : 'cursor-help',
117
117
  className,
118
118
  ]
@@ -129,7 +129,7 @@ export function Label({
129
129
  aria-valuenow={Math.min(progress, 100)}
130
130
  aria-valuemin={0}
131
131
  aria-valuemax={100}
132
- className={`absolute inset-y-0 left-0 ${progressFillColors[color]} rounded-[inherit]`}
132
+ className={`absolute inset-y-0 left-0 ${progressFillColors[accentColor]} rounded-[inherit]`}
133
133
  style={{ width: `${Math.min(progress, 100)}%` }}
134
134
  />
135
135
  )}
@@ -142,7 +142,7 @@ export function Label({
142
142
  <span key={i} className={`${s.iconSize} flex-shrink-0 ${hasProgress ? 'relative' : ''}`}><Icon className={s.iconSize} /></span>
143
143
  ) : null
144
144
  })}
145
- <span className={`min-w-0 truncate ${hasProgress ? 'relative' : ''}`} style={cssTransform ? { textTransform: cssTransform } : undefined}>
145
+ <span className={`min-w-0 truncate -mt-px ${hasProgress ? 'relative' : ''}`} style={cssTransform ? { textTransform: cssTransform } : undefined}>
146
146
  {transformText(text, textTransform)}
147
147
  </span>
148
148
  </>
@@ -3,6 +3,7 @@
3
3
  import { useState, useRef, useCallback, useEffect, type ReactNode } from 'react'
4
4
  import { ChevronLeft, ChevronRight, X } from 'lucide-react'
5
5
  import { cn } from '../lib/cn.ts'
6
+ import type { FormColor } from '../lib/form-colors.ts'
6
7
 
7
8
  export interface LayoutTab {
8
9
  id: string
@@ -26,6 +27,7 @@ export interface LayoutTabBarProps {
26
27
  onClose?: (id: string) => void
27
28
  onReorder?: (fromIndex: number, toIndex: number) => void
28
29
  className?: string
30
+ accentColor?: FormColor
29
31
  }
30
32
 
31
33
  const COLORS: Record<string, { border: string; text: string }> = {
@@ -77,7 +79,7 @@ function getGradient(color?: string, isActive?: boolean): string | undefined {
77
79
  : `linear-gradient(135deg, rgba(${rgb}, 0.36) 0%, rgba(${rgb}, 0.06) 100%)`
78
80
  }
79
81
 
80
- export function LayoutTabBar({ tabs, activeId, onSelect, onClose, onReorder, className }: LayoutTabBarProps) {
82
+ export function LayoutTabBar({ tabs, activeId, onSelect, onClose, onReorder, className, accentColor: _accentColor }: LayoutTabBarProps) {
81
83
  const scrollRef = useRef<HTMLDivElement>(null)
82
84
  const [showLeft, setShowLeft] = useState(false)
83
85
  const [showRight, setShowRight] = useState(false)
@@ -167,7 +169,7 @@ export function LayoutTabBar({ tabs, activeId, onSelect, onClose, onReorder, cla
167
169
  // ── Render ────────────────────────────────────────────
168
170
 
169
171
  return (
170
- <div className={cn('relative flex items-center min-h-12 flex-shrink-0 bg-neutral-900/80 border-b border-neutral-700/50', className)}>
172
+ <div className={cn('relative flex items-center min-h-12 flex-shrink-0 bg-neutral-980/80 border-b border-neutral-700/50', className)}>
171
173
  {showLeft && (
172
174
  <div className="flex items-center px-2 shrink-0">
173
175
  <button type="button" onClick={() => scroll('left')} className="text-neutral-500 hover:text-neutral-400 transition-colors cursor-pointer">
@@ -206,8 +208,8 @@ export function LayoutTabBar({ tabs, activeId, onSelect, onClose, onReorder, cla
206
208
  className={cn(
207
209
  'group flex flex-col justify-start gap-0.5 px-2.5 rounded-t-lg cursor-pointer transition-all shrink-0 select-none relative border-t-2',
208
210
  isActive
209
- ? cn('bg-neutral-800', borderC)
210
- : 'bg-neutral-800/50 text-neutral-400 hover:bg-neutral-800/80 hover:text-neutral-300 border-transparent opacity-60 hover:opacity-90',
211
+ ? cn('bg-neutral-960', borderC)
212
+ : 'bg-neutral-960/50 text-neutral-400 hover:bg-neutral-960/80 hover:text-neutral-300 border-transparent opacity-60 hover:opacity-90',
211
213
  isDragged && 'opacity-50',
212
214
  )}
213
215
  style={{
@@ -269,7 +271,7 @@ export function LayoutTabBar({ tabs, activeId, onSelect, onClose, onReorder, cla
269
271
  const ghostSubIconC = getTextClass(t.subtitleIconColor || t.subtitleColor || t.selectedColor || t.color)
270
272
  return (
271
273
  <div className="fixed z-50 pointer-events-none opacity-90" style={{ left: drag.x + 12, top: drag.y - 20 }}>
272
- <div className={cn('flex flex-col gap-0.5 px-2.5 py-1.5 rounded-t-lg border-t-2 shadow-lg bg-neutral-800', ghostBorder)}>
274
+ <div className={cn('flex flex-col gap-0.5 px-2.5 py-1.5 rounded-t-lg border-t-2 shadow-lg bg-neutral-960', ghostBorder)}>
273
275
  <div className="flex items-center gap-1.5">
274
276
  {t.icon && <span className={cn('shrink-0 inline-flex', ghostIconC)} style={{ width: 14, height: 14 }}>{t.icon}</span>}
275
277
  <span className={cn('text-md font-medium', ghostTitleC)}>{t.title}</span>