@toolr/ui-design 0.1.7 → 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 (101) 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/hooks/use-modal-behavior.ts +32 -3
  8. package/components/lib/accent-context.ts +10 -0
  9. package/components/lib/{ai-tools.tsx → coding-agents.tsx} +23 -8
  10. package/components/lib/custom-icons.tsx +37 -0
  11. package/components/lib/git-providers.tsx +39 -0
  12. package/components/lib/theme-engine.ts +59 -10
  13. package/components/lib/toolr-brand.tsx +23 -9
  14. package/components/sections/captured-issues/captured-issues-panel.tsx +17 -8
  15. package/components/sections/{ai-tools-paths/tools-paths-panel.tsx → coding-agent-paths/agent-paths-panel.tsx} +70 -62
  16. package/components/sections/coding-agent-paths/index.ts +37 -0
  17. package/components/sections/{ai-tools-paths → coding-agent-paths}/types.ts +28 -28
  18. package/components/sections/coding-agent-paths/use-agent-paths.ts +159 -0
  19. package/components/sections/golden-snapshots/file-diff-viewer.tsx +11 -10
  20. package/components/sections/golden-snapshots/golden-sync-panel.tsx +12 -3
  21. package/components/sections/golden-snapshots/snapshot-manager.tsx +9 -7
  22. package/components/sections/golden-snapshots/status-overview.tsx +8 -8
  23. package/components/sections/golden-snapshots/version-manager.tsx +6 -6
  24. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +3 -3
  25. package/components/sections/prompt-editor/index.ts +1 -1
  26. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +13 -5
  27. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +18 -10
  28. package/components/sections/prompt-editor/types.ts +2 -2
  29. package/components/sections/report-bug/report-bug-form.tsx +12 -4
  30. package/components/sections/report-bug/screenshot-uploader.tsx +11 -3
  31. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +12 -4
  32. package/components/sections/snapshot-browser/snapshot-tree.tsx +5 -4
  33. package/components/sections/snapshot-browser/types.ts +1 -1
  34. package/components/sections/snippets-editor/snippets-editor.tsx +16 -9
  35. package/components/settings/SettingsHeader.tsx +2 -2
  36. package/components/settings/SettingsPanel.tsx +11 -3
  37. package/components/settings/SettingsTreeNav.tsx +15 -9
  38. package/components/ui/action-dialog.tsx +37 -35
  39. package/components/ui/ai-action-button.tsx +12 -11
  40. package/components/ui/ai-execution-action-buttons.tsx +13 -5
  41. package/components/ui/badge.tsx +17 -6
  42. package/components/ui/bottom-panel-header.tsx +9 -5
  43. package/components/ui/breadcrumb.tsx +14 -6
  44. package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +14 -6
  45. package/components/ui/checkbox.tsx +23 -14
  46. package/components/ui/collapsible-section.tsx +38 -28
  47. package/components/ui/confirm-badge.tsx +17 -6
  48. package/components/ui/cookie-consent.tsx +13 -7
  49. package/components/ui/detail-section.tsx +24 -16
  50. package/components/ui/detail-view-wrapper.tsx +30 -22
  51. package/components/ui/editor-placeholder-card.tsx +28 -24
  52. package/components/ui/editor-toolbar.tsx +7 -4
  53. package/components/ui/execution-details-panel.tsx +10 -5
  54. package/components/ui/file-structure-section.tsx +3 -3
  55. package/components/ui/file-tree.tsx +7 -5
  56. package/components/ui/files-panel.tsx +147 -27
  57. package/components/ui/filter-dropdown.tsx +88 -75
  58. package/components/ui/form-actions.tsx +21 -11
  59. package/components/ui/frontmatter-form-header.tsx +10 -2
  60. package/components/ui/icon-button.tsx +27 -14
  61. package/components/ui/input.tsx +15 -7
  62. package/components/ui/label.tsx +9 -5
  63. package/components/ui/layout-tab-bar.tsx +11 -9
  64. package/components/ui/modal.tsx +26 -8
  65. package/components/ui/nav-card.tsx +7 -4
  66. package/components/ui/navigation-bar.tsx +40 -12
  67. package/components/ui/number-input.tsx +14 -4
  68. package/components/ui/project-explorer.tsx +666 -0
  69. package/components/ui/registry-browser.tsx +12 -1
  70. package/components/ui/registry-card.tsx +49 -42
  71. package/components/ui/registry-detail.tsx +34 -11
  72. package/components/ui/resizable-textarea.tsx +18 -11
  73. package/components/ui/scope-badge.tsx +18 -11
  74. package/components/ui/segmented-toggle.tsx +7 -2
  75. package/components/ui/select.tsx +17 -11
  76. package/components/ui/selection-grid.tsx +40 -37
  77. package/components/ui/setting-row.tsx +6 -4
  78. package/components/ui/settings-card.tsx +12 -5
  79. package/components/ui/settings-info-box.tsx +9 -6
  80. package/components/ui/settings-section-title.tsx +14 -2
  81. package/components/ui/snapshot-card.tsx +10 -2
  82. package/components/ui/snippets-panel.tsx +4 -2
  83. package/components/ui/sort-dropdown.tsx +45 -32
  84. package/components/ui/status-card.tsx +9 -1
  85. package/components/ui/tab-bar.tsx +26 -13
  86. package/components/ui/toggle.tsx +31 -17
  87. package/components/ui/tooltip.tsx +14 -6
  88. package/dist/content.js +8 -8
  89. package/dist/diagrams.d.ts +0 -1
  90. package/dist/index.d.ts +431 -186
  91. package/dist/index.js +3119 -1724
  92. package/dist/tokens/primitives.css +28 -6
  93. package/dist/tokens/semantic.css +15 -15
  94. package/dist/tokens/theme.css +23 -0
  95. package/index.ts +25 -11
  96. package/package.json +9 -1
  97. package/tokens/primitives.css +28 -6
  98. package/tokens/semantic.css +15 -15
  99. package/tokens/theme.css +23 -0
  100. package/components/sections/ai-tools-paths/index.ts +0 -37
  101. package/components/sections/ai-tools-paths/use-tools-paths.ts +0 -159
@@ -1,11 +1,14 @@
1
1
  import { Home, Folder, Lock, Eye } from 'lucide-react'
2
2
  import { Label, type LabelColor } from './label.tsx'
3
+ import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
4
+ import type { FormColor } from '../lib/form-colors.ts'
3
5
 
4
6
  export type ScopeType = 'user' | 'project' | 'local' | 'read-only'
5
7
 
6
8
  interface ScopeBadgeProps {
7
9
  scope: ScopeType
8
10
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
11
+ accentColor?: FormColor
9
12
  }
10
13
 
11
14
  const scopeConfig: Record<
@@ -48,20 +51,24 @@ const scopeConfig: Record<
48
51
  },
49
52
  }
50
53
 
51
- export function ScopeBadge({ scope, size = 'xs' }: ScopeBadgeProps) {
54
+ export function ScopeBadge({ scope, size = 'xs', accentColor }: ScopeBadgeProps) {
55
+ const contextAccent = useAccentColor()
56
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
52
57
  const config = scopeConfig[scope]
53
58
  const Icon = config.icon
54
59
 
55
60
  return (
56
- <Label
57
- text={config.label}
58
- color={config.color}
59
- IconComponent={Icon}
60
- tooltip={{
61
- title: config.title,
62
- description: config.description,
63
- }}
64
- size={size}
65
- />
61
+ <AccentColorProvider value={effectiveColor}>
62
+ <Label
63
+ text={config.label}
64
+ accentColor={config.color}
65
+ IconComponent={Icon}
66
+ tooltip={{
67
+ title: config.title,
68
+ description: config.description,
69
+ }}
70
+ size={size}
71
+ />
72
+ </AccentColorProvider>
66
73
  )
67
74
  }
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import type { AccentColor } from '../lib/form-colors.ts'
3
+ import { useAccentColor } from '../lib/accent-context.ts'
3
4
  import { Tooltip, type TooltipContent, type TooltipPosition } from './tooltip.tsx'
4
5
 
5
6
  export interface SegmentedToggleOption<T extends string> {
@@ -108,7 +109,7 @@ export function SegmentedToggle<T extends string>({
108
109
  options,
109
110
  value,
110
111
  onChange,
111
- accentColor = 'blue',
112
+ accentColor: accentColorProp,
112
113
  variant = 'outline',
113
114
  size = 'sm',
114
115
  tooltipPosition = 'bottom',
@@ -116,10 +117,12 @@ export function SegmentedToggle<T extends string>({
116
117
  disabled = false,
117
118
  disabledTooltip,
118
119
  }: SegmentedToggleProps<T>) {
120
+ const contextAccent = useAccentColor()
121
+ const accentColor = accentColorProp ?? contextAccent ?? 'blue'
119
122
  const isOutline = variant === 'outline'
120
123
  const containerClasses = isOutline
121
124
  ? OUTLINE_CONTAINER[accentColor] || OUTLINE_CONTAINER.blue
122
- : 'flex items-center bg-neutral-800/50 border border-neutral-700 rounded-md'
125
+ : 'flex items-center bg-neutral-960/50 border border-neutral-700 rounded-md'
123
126
 
124
127
  const toggle = (
125
128
  <div
@@ -136,6 +139,8 @@ export function SegmentedToggle<T extends string>({
136
139
  return (
137
140
  <Tooltip key={option.value} content={option.tooltip} position={tooltipPosition}>
138
141
  <button
142
+ aria-pressed={isActive}
143
+ aria-label={option.label || (typeof option.tooltip.description === 'string' ? option.tooltip.description : undefined)}
139
144
  onClick={() => onChange(option.value)}
140
145
  disabled={disabled}
141
146
  className={`flex items-center justify-center ${sizeClass} ${rounding} font-medium transition-all cursor-pointer ${
@@ -4,6 +4,7 @@ import { ChevronDown, Check } from 'lucide-react'
4
4
  import { useClickOutside } from '../hooks/use-click-outside.ts'
5
5
  import { useDropdownMaxHeight } from '../hooks/use-dropdown-max-height.ts'
6
6
  import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
7
+ import { useAccentColor } from '../lib/accent-context.ts'
7
8
 
8
9
  export interface SelectOption<T extends string | number = string> {
9
10
  value: T
@@ -17,7 +18,7 @@ export interface SelectProps<T extends string | number = string> {
17
18
  onChange: (value: T) => void
18
19
  placeholder?: string
19
20
  variant?: 'filled' | 'outline'
20
- color?: FormColor
21
+ accentColor?: FormColor
21
22
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
22
23
  align?: 'left' | 'right'
23
24
  disabled?: boolean
@@ -25,7 +26,7 @@ export interface SelectProps<T extends string | number = string> {
25
26
  }
26
27
 
27
28
  const VARIANT_CLASSES = {
28
- filled: { bg: 'bg-neutral-800', hoverBg: 'hover:bg-neutral-700', menuBg: 'bg-[var(--popover)]' },
29
+ filled: { bg: 'bg-neutral-960', hoverBg: 'hover:bg-neutral-700', menuBg: 'bg-[var(--popover)]' },
29
30
  outline: { bg: 'bg-transparent', hoverBg: 'hover:bg-neutral-700', menuBg: 'bg-[var(--popover)]' },
30
31
  }
31
32
 
@@ -43,12 +44,14 @@ export function Select<T extends string | number = string>({
43
44
  onChange,
44
45
  placeholder = 'Select...',
45
46
  variant = 'outline',
46
- color = 'blue',
47
+ accentColor,
47
48
  size = 'sm',
48
49
  align = 'left',
49
50
  disabled = false,
50
51
  className = '',
51
52
  }: SelectProps<T>) {
53
+ const contextAccent = useAccentColor()
54
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
52
55
  const [isOpen, setIsOpen] = useState(false)
53
56
  const [highlightIdx, setHighlightIdx] = useState(-1)
54
57
  const ref = useRef<HTMLDivElement>(null)
@@ -110,14 +113,16 @@ export function Select<T extends string | number = string>({
110
113
  <button
111
114
  ref={buttonRef}
112
115
  type="button"
116
+ aria-expanded={isOpen}
117
+ aria-haspopup="listbox"
113
118
  onClick={() => !disabled && (isOpen ? close() : open())}
114
119
  disabled={disabled}
115
- className={`flex items-center gap-1.5 min-w-0 rounded-lg border ${v.bg} ${FORM_COLORS[color].border} text-neutral-200 focus:outline-none ${FORM_COLORS[color].focus} transition-colors ${
116
- disabled ? 'opacity-50 cursor-not-allowed' : `cursor-pointer ${FORM_COLORS[color].hover}`
120
+ className={`flex items-center gap-1.5 min-w-0 rounded-lg border ${v.bg} ${FORM_COLORS[effectiveColor].border} text-neutral-200 focus:outline-none ${FORM_COLORS[effectiveColor].focus} transition-colors ${
121
+ disabled ? 'opacity-50 cursor-not-allowed' : `cursor-pointer ${FORM_COLORS[effectiveColor].hover}`
117
122
  } ${s}`}
118
123
  >
119
124
  {selectedOption?.icon}
120
- <span className={`whitespace-nowrap ${selectedOption ? '' : 'text-neutral-500'}`}>
125
+ <span className={`truncate ${selectedOption ? '' : 'text-neutral-500'}`}>
121
126
  {selectedOption?.label ?? placeholder}
122
127
  </span>
123
128
  <ChevronDown className={`w-3 h-3 ml-auto text-neutral-500 transition-transform shrink-0 ${isOpen ? 'rotate-180' : ''}`} />
@@ -125,7 +130,8 @@ export function Select<T extends string | number = string>({
125
130
  {isOpen && menuPos && createPortal(
126
131
  <div
127
132
  ref={menuRef}
128
- className={`fixed z-[9999] whitespace-nowrap ${v.menuBg} border ${FORM_COLORS[color].border} rounded-lg shadow-xl overflow-hidden`}
133
+ role="listbox"
134
+ className={`fixed z-[9999] whitespace-nowrap ${v.menuBg} border ${FORM_COLORS[effectiveColor].border} rounded-lg shadow-lg overflow-hidden`}
129
135
  style={{
130
136
  top: menuPos.top,
131
137
  left: align === 'right' ? undefined : menuPos.left,
@@ -145,13 +151,13 @@ export function Select<T extends string | number = string>({
145
151
  onPointerEnter={() => setHighlightIdx(idx)}
146
152
  className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
147
153
  isHighlighted
148
- ? `${FORM_COLORS[color].selectedBg} text-neutral-200`
149
- : isSelected ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
154
+ ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200`
155
+ : isSelected ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
150
156
  }`}
151
157
  >
152
- <Check className={`w-3 h-3 shrink-0 ${isSelected ? FORM_COLORS[color].accent : 'invisible'}`} />
158
+ <Check className={`w-3 h-3 shrink-0 ${isSelected ? FORM_COLORS[effectiveColor].accent : 'invisible'}`} />
153
159
  {opt.icon}
154
- <span>{opt.label}</span>
160
+ <span className="truncate">{opt.label}</span>
155
161
  </button>
156
162
  )
157
163
  })}
@@ -1,17 +1,20 @@
1
1
  import { iconMap, type IconName } from './icon-button.tsx'
2
2
  import type { ConfirmBadgeColor } from './confirm-badge.tsx'
3
+ import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
4
+ import type { FormColor } from '../lib/form-colors.ts'
3
5
  import { cn } from '../lib/cn.ts'
4
- import { AiToolIcon, AI_TOOL_NAMES, type AiToolKey } from '../lib/ai-tools.tsx'
6
+ import { FORM_COLORS } from '../lib/form-colors.ts'
7
+ import { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey } from '../lib/coding-agents.tsx'
5
8
 
6
- /* ── Preset logos (shared AiToolIcon) ─────────────────────── */
9
+ /* ── Preset logos (shared CodingAgentIcon) ────────────────── */
7
10
 
8
11
  type IconProps = { className?: string; style?: React.CSSProperties }
9
12
 
10
- function makeToolLogo(toolKey: AiToolKey): React.ComponentType<IconProps> {
11
- function ToolLogo({ className, style }: IconProps) {
12
- return <AiToolIcon tool={toolKey} className={className} style={style} />
13
+ function makeAgentLogo(agentKey: CodingAgentKey): React.ComponentType<IconProps> {
14
+ function AgentLogo({ className, style }: IconProps) {
15
+ return <CodingAgentIcon agent={agentKey} className={className} style={style} />
13
16
  }
14
- return ToolLogo
17
+ return AgentLogo
15
18
  }
16
19
 
17
20
  /* ── Preset types & data ──────────────────────────────────── */
@@ -24,12 +27,14 @@ export type CodingToolPresetConfig = CodingToolId | {
24
27
  disabled?: boolean
25
28
  }
26
29
 
30
+ // Brand colors use Tailwind accent hex values (violet-400, orange-500, blue-400, cyan-400, neutral-400)
31
+ // applied via inline style because the color is dynamic per-item
27
32
  const CODING_TOOL_PRESETS: Record<CodingToolId, { name: string; description: string; color: string; badgeColor: ConfirmBadgeColor; Logo: React.ComponentType<IconProps> }> = {
28
- 'claude-code': { name: AI_TOOL_NAMES.claude, description: 'Security & architecture focus', color: '#a78bfa', badgeColor: 'violet', Logo: makeToolLogo('claude') },
29
- 'github-copilot': { name: AI_TOOL_NAMES.copilot, description: 'Code quality & patterns', color: '#f97316', badgeColor: 'orange', Logo: makeToolLogo('copilot') },
30
- 'codex-cli': { name: AI_TOOL_NAMES.codex, description: 'Performance & optimization', color: '#60a5fa', badgeColor: 'blue', Logo: makeToolLogo('codex') },
31
- 'gemini-cli': { name: AI_TOOL_NAMES.gemini, description: 'Best practices & docs', color: '#22d3ee', badgeColor: 'cyan', Logo: makeToolLogo('gemini') },
32
- 'opencode': { name: AI_TOOL_NAMES.opencode, description: 'Provider-agnostic AI coding agent', color: '#a1a1aa', badgeColor: 'neutral', Logo: makeToolLogo('opencode') },
33
+ 'claude-code': { name: CODING_AGENT_NAMES.claude, description: 'Security & architecture focus', color: '#a78bfa', badgeColor: 'violet', Logo: makeAgentLogo('claude') },
34
+ 'github-copilot': { name: CODING_AGENT_NAMES.copilot, description: 'Code quality & patterns', color: '#f97316', badgeColor: 'orange', Logo: makeAgentLogo('copilot') },
35
+ 'codex-cli': { name: CODING_AGENT_NAMES.codex, description: 'Performance & optimization', color: '#60a5fa', badgeColor: 'blue', Logo: makeAgentLogo('codex') },
36
+ 'gemini-cli': { name: CODING_AGENT_NAMES.gemini, description: 'Best practices & docs', color: '#22d3ee', badgeColor: 'cyan', Logo: makeAgentLogo('gemini') },
37
+ 'opencode': { name: CODING_AGENT_NAMES.opencode, description: 'Provider-agnostic AI coding agent', color: '#a1a1aa', badgeColor: 'neutral', Logo: makeAgentLogo('opencode') },
33
38
  }
34
39
 
35
40
  function resolvePresets(presets: CodingToolPresetConfig[]): SelectionCardItem[] {
@@ -85,16 +90,11 @@ export interface SelectionGridProps {
85
90
  /** Number of columns for grid layout (auto if not set) */
86
91
  columns?: number
87
92
  className?: string
93
+ accentColor?: FormColor
88
94
  }
89
95
 
90
96
  /* ── Helpers ───────────────────────────────────────────────── */
91
97
 
92
- const DEFAULT_COLOR = 'blue-400'
93
-
94
- function resolveColor(color?: string): string {
95
- return color || DEFAULT_COLOR
96
- }
97
-
98
98
  function autoColumns(itemCount: number): number {
99
99
  return Math.min(itemCount, 5)
100
100
  }
@@ -110,7 +110,10 @@ export function SelectionGrid({
110
110
  layout = 'grid',
111
111
  columns,
112
112
  className,
113
+ accentColor,
113
114
  }: SelectionGridProps) {
115
+ const contextAccent = useAccentColor()
116
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
114
117
  const items = itemsProp || (presets ? resolvePresets(presets) : [])
115
118
 
116
119
  function handleClick(item: SelectionCardItem) {
@@ -132,6 +135,7 @@ export function SelectionGrid({
132
135
 
133
136
  if (layout === 'list') {
134
137
  return (
138
+ <AccentColorProvider value={effectiveColor}>
135
139
  <div
136
140
  className={cn('grid gap-3', className)}
137
141
  style={{ gridTemplateColumns: `repeat(${columns || 1}, 1fr)` }}
@@ -145,10 +149,12 @@ export function SelectionGrid({
145
149
  />
146
150
  ))}
147
151
  </div>
152
+ </AccentColorProvider>
148
153
  )
149
154
  }
150
155
 
151
156
  return (
157
+ <AccentColorProvider value={effectiveColor}>
152
158
  <div
153
159
  className={cn('grid gap-3', className)}
154
160
  style={{ gridTemplateColumns: `repeat(${cols}, 1fr)` }}
@@ -162,6 +168,7 @@ export function SelectionGrid({
162
168
  />
163
169
  ))}
164
170
  </div>
171
+ </AccentColorProvider>
165
172
  )
166
173
  }
167
174
 
@@ -174,32 +181,30 @@ interface CardProps {
174
181
  }
175
182
 
176
183
  function GridCard({ item, selected, onClick }: CardProps) {
177
- const color = resolveColor(item.color)
184
+ const accent = useAccentColor() ?? 'blue'
185
+ const accentStyles = FORM_COLORS[accent]
178
186
  const Icon = item.IconComponent || (item.icon ? iconMap[item.icon] : undefined)
179
187
 
180
188
  return (
181
189
  <button
182
190
  type="button"
191
+ aria-pressed={selected}
192
+ aria-label={item.name}
183
193
  onClick={onClick}
184
194
  disabled={item.disabled}
185
195
  className={cn(
186
196
  'relative p-3 rounded-lg text-center transition-all border-2 flex flex-col items-center',
187
197
  item.disabled
188
- ? 'opacity-30 cursor-not-allowed border-neutral-800 bg-[var(--background)]'
198
+ ? 'opacity-30 cursor-not-allowed border-neutral-960 bg-[var(--background)]'
189
199
  : selected
190
- ? 'cursor-pointer'
191
- : 'bg-neutral-800/50 border-neutral-700 hover:bg-[var(--surface-hover)] hover:border-neutral-600 cursor-pointer',
200
+ ? `cursor-pointer ${accentStyles.selectedBg} ${accentStyles.border}`
201
+ : 'bg-neutral-960/50 border-neutral-700 hover:bg-[var(--surface-hover)] hover:border-neutral-600 cursor-pointer',
192
202
  )}
193
- style={
194
- selected && !item.disabled
195
- ? { borderColor: color }
196
- : undefined
197
- }
198
203
  >
199
204
  {Icon && (
200
205
  <Icon
201
206
  className="w-7 h-7 mb-1"
202
- style={{ color: item.disabled ? undefined : (item.color || '#9ca3af') }}
207
+ style={{ color: item.disabled ? undefined : item.color }}
203
208
  />
204
209
  )}
205
210
 
@@ -213,32 +218,30 @@ function GridCard({ item, selected, onClick }: CardProps) {
213
218
  }
214
219
 
215
220
  function ListCard({ item, selected, onClick }: CardProps) {
216
- const color = resolveColor(item.color)
221
+ const accent = useAccentColor() ?? 'blue'
222
+ const accentStyles = FORM_COLORS[accent]
217
223
  const Icon = item.IconComponent || (item.icon ? iconMap[item.icon] : undefined)
218
224
 
219
225
  return (
220
226
  <button
221
227
  type="button"
228
+ aria-pressed={selected}
229
+ aria-label={item.name}
222
230
  onClick={onClick}
223
231
  disabled={item.disabled}
224
232
  className={cn(
225
233
  'relative p-3 rounded-lg transition-all border-2 flex items-center gap-3 text-left',
226
234
  item.disabled
227
- ? 'opacity-30 cursor-not-allowed border-neutral-800 bg-[var(--background)]'
235
+ ? 'opacity-30 cursor-not-allowed border-neutral-960 bg-[var(--background)]'
228
236
  : selected
229
- ? 'cursor-pointer'
230
- : 'bg-neutral-800/50 border-neutral-700 hover:bg-[var(--surface-hover)] hover:border-neutral-600 cursor-pointer',
237
+ ? `cursor-pointer ${accentStyles.selectedBg} ${accentStyles.border}`
238
+ : 'bg-neutral-960/50 border-neutral-700 hover:bg-[var(--surface-hover)] hover:border-neutral-600 cursor-pointer',
231
239
  )}
232
- style={
233
- selected && !item.disabled
234
- ? { borderColor: color }
235
- : undefined
236
- }
237
240
  >
238
241
  {Icon && (
239
242
  <Icon
240
243
  className="w-5 h-5 flex-shrink-0"
241
- style={{ color: item.disabled ? undefined : (item.color || '#9ca3af') }}
244
+ style={{ color: item.disabled ? undefined : item.color }}
242
245
  />
243
246
  )}
244
247
 
@@ -14,6 +14,7 @@
14
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
+ import { cn } from '../lib/cn.ts'
17
18
 
18
19
  interface SettingRowBase {
19
20
  label: string
@@ -26,7 +27,7 @@ interface SettingRowToggle extends SettingRowBase {
26
27
  type: 'toggle'
27
28
  checked: boolean
28
29
  onChange: (checked: boolean) => void
29
- color?: ToggleColor
30
+ accentColor?: ToggleColor
30
31
  size?: ToggleSize
31
32
  }
32
33
 
@@ -51,10 +52,10 @@ interface SettingRowInput extends SettingRowBase {
51
52
  export type SettingRowProps = SettingRowToggle | SettingRowSelect | SettingRowInput
52
53
 
53
54
  export function SettingRow(props: SettingRowProps) {
54
- const { label, description, disabled, className = '' } = props
55
+ const { label, description, disabled, className } = props
55
56
 
56
57
  return (
57
- <div className={`flex items-start justify-between gap-4 ${className}`}>
58
+ <div className={cn('flex items-start justify-between gap-4', className)}>
58
59
  <div>
59
60
  <label className="text-neutral-200 leading-7">{label}</label>
60
61
  {description && <p className="text-md text-neutral-500">{description}</p>}
@@ -64,8 +65,9 @@ export function SettingRow(props: SettingRowProps) {
64
65
  checked={props.checked}
65
66
  onChange={props.onChange}
66
67
  disabled={disabled}
67
- color={props.color}
68
+ accentColor={props.accentColor}
68
69
  size={props.size}
70
+ aria-label={label}
69
71
  />
70
72
  )}
71
73
  {props.type === 'select' && (
@@ -1,4 +1,6 @@
1
1
  import { cn } from '../lib/cn.ts'
2
+ import type { FormColor } from '../lib/form-colors.ts'
3
+ import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
2
4
 
3
5
  export interface SettingsCardProps {
4
6
  children: React.ReactNode
@@ -6,22 +8,27 @@ export interface SettingsCardProps {
6
8
  title?: string
7
9
  description?: string
8
10
  testId?: string
11
+ accentColor?: FormColor
9
12
  }
10
13
 
11
- export function SettingsCard({ children, className, title, description, testId }: SettingsCardProps) {
14
+ export function SettingsCard({ children, className, title, description, testId, accentColor }: SettingsCardProps) {
15
+ const contextAccent = useAccentColor()
16
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
12
17
  return (
13
18
  <div
14
- className={cn('bg-neutral-900 border border-neutral-800 rounded-lg p-4 space-y-4', className)}
19
+ className={cn('bg-neutral-980 border border-neutral-960 rounded-lg p-4 space-y-4', className)}
15
20
  data-testid={testId}
16
21
  >
17
22
  {title && (
18
23
  <div>
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>}
24
+ <h3 className="text-md font-medium text-neutral-200 truncate">{title}</h3>
25
+ {description && <p className="text-md text-neutral-500 mt-1 line-clamp-2">{description}</p>}
21
26
  </div>
22
27
  )}
23
28
  {!title && description && <p className="text-md text-neutral-500">{description}</p>}
24
- {children}
29
+ <AccentColorProvider value={effectiveColor}>
30
+ {children}
31
+ </AccentColorProvider>
25
32
  </div>
26
33
  )
27
34
  }
@@ -1,5 +1,6 @@
1
1
  import { Info, AlertTriangle, CheckCircle, AlertCircle } from 'lucide-react'
2
- import { ACCENT_TEXT, type AccentColor } from '../lib/form-colors.ts'
2
+ import { ACCENT_TEXT, type AccentColor, type FormColor } from '../lib/form-colors.ts'
3
+ import { useAccentColor } from '../lib/accent-context.ts'
3
4
  import { cn } from '../lib/cn.ts'
4
5
 
5
6
  export type SettingsInfoBoxColor = AccentColor
@@ -7,6 +8,7 @@ export type SettingsInfoBoxColor = AccentColor
7
8
  export interface SettingsInfoBoxProps {
8
9
  children: React.ReactNode
9
10
  color?: SettingsInfoBoxColor
11
+ accentColor?: FormColor
10
12
  className?: string
11
13
  testId?: string
12
14
  }
@@ -47,16 +49,17 @@ const borderColorMap: Record<SettingsInfoBoxColor, string> = {
47
49
  teal: 'border-l-teal-500',
48
50
  }
49
51
 
50
- export function SettingsInfoBox({ children, color = 'neutral', className, testId }: SettingsInfoBoxProps) {
51
- const Icon = INFO_BOX_ICONS[color]
52
+ export function SettingsInfoBox({ children, color, accentColor, className, testId }: SettingsInfoBoxProps) {
53
+ const contextAccent = useAccentColor()
54
+ const effectiveColor = (color ?? accentColor ?? contextAccent ?? 'neutral') as SettingsInfoBoxColor
55
+ const Icon = INFO_BOX_ICONS[effectiveColor]
52
56
 
53
57
  return (
54
58
  <div
55
- className={cn('flex items-start gap-3 border-l-2', borderColorMap[color], className)}
56
- style={{ paddingLeft: 10 }}
59
+ className={cn('flex items-start gap-3 border-l-2 pl-2.5', borderColorMap[effectiveColor], className)}
57
60
  data-testid={testId}
58
61
  >
59
- <Icon className={cn('w-4 h-4 mt-0.5 shrink-0', ACCENT_TEXT[color])} />
62
+ <Icon className={cn('w-4 h-4 mt-0.5 shrink-0', ACCENT_TEXT[effectiveColor])} />
60
63
  <div className="text-md text-neutral-500">{children}</div>
61
64
  </div>
62
65
  )
@@ -6,16 +6,28 @@
6
6
  * - Configuration panels - section dividers
7
7
  */
8
8
 
9
+ import { ACCENT_TEXT, type AccentColor, type FormColor } from '../lib/form-colors.ts'
10
+ import { useAccentColor } from '../lib/accent-context.ts'
11
+ import { cn } from '../lib/cn.ts'
12
+
9
13
  export interface SettingsSectionTitleProps {
10
14
  children: React.ReactNode
11
15
  className?: string
12
16
  testId?: string
17
+ accentColor?: FormColor
13
18
  }
14
19
 
15
- export function SettingsSectionTitle({ children, className = '', testId }: SettingsSectionTitleProps) {
20
+ export function SettingsSectionTitle({ children, className, testId, accentColor }: SettingsSectionTitleProps) {
21
+ const contextAccent = useAccentColor()
22
+ const effectiveColor = accentColor ?? contextAccent
23
+
16
24
  return (
17
25
  <h3
18
- className={`text-sm font-medium text-neutral-400 uppercase tracking-wider ${className}`}
26
+ className={cn(
27
+ 'text-sm font-medium uppercase tracking-wider',
28
+ effectiveColor ? ACCENT_TEXT[effectiveColor as AccentColor] : 'text-neutral-400',
29
+ className,
30
+ )}
19
31
  data-testid={testId}
20
32
  >
21
33
  {children}
@@ -4,6 +4,8 @@ import { RefreshCw, Eye } from 'lucide-react'
4
4
  import { cn } from '../lib/cn.ts'
5
5
  import { Label, type LabelColor } from './label.tsx'
6
6
  import type { IconName } from './icon-button.tsx'
7
+ import type { FormColor } from '../lib/form-colors.ts'
8
+ import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
7
9
 
8
10
  type SnapshotStatus = 'synced' | 'pending' | 'conflict' | 'outdated'
9
11
 
@@ -16,6 +18,7 @@ export interface SnapshotCardProps {
16
18
  onSync?: () => void
17
19
  onView?: () => void
18
20
  className?: string
21
+ accentColor?: FormColor
19
22
  }
20
23
 
21
24
  const statusStripeColor: Record<SnapshotStatus, string> = {
@@ -41,9 +44,13 @@ export function SnapshotCard({
41
44
  onSync,
42
45
  onView,
43
46
  className,
47
+ accentColor: accentColorProp,
44
48
  }: SnapshotCardProps) {
49
+ const contextAccent = useAccentColor()
50
+ const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
45
51
  return (
46
- <div className={cn('rounded-lg border border-neutral-700 bg-neutral-800 overflow-hidden', className)}>
52
+ <AccentColorProvider value={effectiveColor}>
53
+ <div className={cn('rounded-lg border border-neutral-700 bg-neutral-960 overflow-hidden', className)}>
47
54
  <div className={cn('h-1', statusStripeColor[status])} />
48
55
 
49
56
  <div className="p-4">
@@ -51,7 +58,7 @@ export function SnapshotCard({
51
58
  <h3 className="text-md font-medium text-neutral-200 truncate">{title}</h3>
52
59
  <Label
53
60
  text={statusLabelConfig[status].text}
54
- color={statusLabelConfig[status].color}
61
+ accentColor={statusLabelConfig[status].color}
55
62
  icon={statusLabelConfig[status].icon}
56
63
  tooltip={{ description: statusLabelConfig[status].tooltip }}
57
64
  size="xs"
@@ -103,5 +110,6 @@ export function SnapshotCard({
103
110
  )}
104
111
  </div>
105
112
  </div>
113
+ </AccentColorProvider>
106
114
  )
107
115
  }
@@ -61,7 +61,7 @@ export function SnippetsPanel({
61
61
  )
62
62
 
63
63
  return (
64
- <div className={cn('flex flex-col bg-neutral-800 rounded-lg overflow-hidden', className)}>
64
+ <div className={cn('flex flex-col bg-neutral-960 rounded-lg overflow-hidden', className)}>
65
65
  <div className="flex items-center justify-between px-3 py-2 border-b border-neutral-700">
66
66
  <span className="text-xs font-semibold uppercase tracking-wider text-neutral-500">Snippets</span>
67
67
  <span className="text-xs text-neutral-500">{snippets.length} snippets</span>
@@ -107,7 +107,7 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
107
107
  const langColor = snippet.language ? LANGUAGE_COLORS[snippet.language.toLowerCase()] ?? '#6b7280' : '#6b7280'
108
108
 
109
109
  return (
110
- <div className="rounded-md border border-neutral-700 bg-neutral-900 hover:border-neutral-600 transition-colors">
110
+ <div className="rounded-md border border-neutral-700 bg-neutral-980 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
113
  <span className="text-sm font-medium text-neutral-200 truncate">{snippet.label}</span>
@@ -126,6 +126,7 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
126
126
  icon="arrow-down-to-line"
127
127
  onClick={() => onInsert(snippet.id)}
128
128
  size="xss"
129
+ accentColor="neutral"
129
130
  tooltip={{ title: 'Insert snippet', description: 'Insert this snippet' }}
130
131
  />
131
132
  )}
@@ -134,6 +135,7 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
134
135
  icon="copy"
135
136
  onClick={() => onCopy(snippet.id)}
136
137
  size="xss"
138
+ accentColor="neutral"
137
139
  tooltip={{ title: 'Copy snippet', description: 'Copy to clipboard' }}
138
140
  />
139
141
  )}