@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,13 @@
1
- import { useState, useRef, useEffect } from 'react'
1
+ import { useState, useEffect } from 'react'
2
+ import { createPortal } from 'react-dom'
2
3
  import { ArrowUp, ArrowDown, ChevronDown, Check } 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 VARIANT_CLASSES = {
8
- filled: { bg: 'bg-neutral-800', hoverBg: 'hover:bg-neutral-700' },
10
+ filled: { bg: 'bg-neutral-960', hoverBg: 'hover:bg-neutral-700' },
9
11
  outline: { bg: 'bg-transparent', hoverBg: 'hover:bg-neutral-700' },
10
12
  }
11
13
 
@@ -25,7 +27,7 @@ export interface SortDropdownProps {
25
27
  onToggleDirection: () => void
26
28
  fields: SortField[]
27
29
  variant?: 'filled' | 'outline'
28
- color?: FormColor
30
+ accentColor?: FormColor
29
31
  }
30
32
 
31
33
  export function SortDropdown({
@@ -35,15 +37,16 @@ export function SortDropdown({
35
37
  onToggleDirection,
36
38
  fields,
37
39
  variant = 'outline',
38
- color = 'blue',
40
+ accentColor,
39
41
  }: SortDropdownProps) {
42
+ const contextAccent = useAccentColor()
43
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
40
44
  const [isOpen, setIsOpen] = useState(false)
41
45
  const [highlightIdx, setHighlightIdx] = useState(-1)
42
- const ref = useRef<HTMLDivElement>(null)
43
- const menuRef = useDropdownMaxHeight<HTMLDivElement>(isOpen)
46
+ const { triggerRef, menuRef, position } = useDropdownPortal(isOpen)
44
47
  const v = VARIANT_CLASSES[variant]
45
48
 
46
- useClickOutside(ref, isOpen, () => setIsOpen(false))
49
+ useClickOutside([triggerRef, menuRef], isOpen, () => setIsOpen(false))
47
50
 
48
51
  useEffect(() => {
49
52
  if (isOpen) setHighlightIdx(fields.findIndex((f) => f.value === field))
@@ -70,40 +73,50 @@ export function SortDropdown({
70
73
  const current = fields.find((f) => f.value === field) ?? fields[0]
71
74
  const DirIcon = ascending ? ArrowUp : ArrowDown
72
75
 
76
+ const menu = isOpen && createPortal(
77
+ <div
78
+ ref={menuRef}
79
+ role="listbox"
80
+ style={{ position: 'fixed', top: position.top, left: position.left, minWidth: position.minWidth, zIndex: 9999 }}
81
+ className={`whitespace-nowrap bg-[var(--popover)] border ${FORM_COLORS[effectiveColor].border} rounded-lg shadow-lg overflow-hidden`}
82
+ >
83
+ {fields.map((f, idx) => (
84
+ <button
85
+ key={f.value}
86
+ onClick={() => { onFieldChange(f.value); setIsOpen(false) }}
87
+ onPointerEnter={() => setHighlightIdx(idx)}
88
+ className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
89
+ idx === highlightIdx ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200` : field === f.value ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
90
+ }`}
91
+ >
92
+ <Check className={`w-3 h-3 shrink-0 ${field === f.value ? FORM_COLORS[effectiveColor].accent : 'invisible'}`} />
93
+ <span>{f.label}</span>
94
+ </button>
95
+ ))}
96
+ </div>,
97
+ document.body,
98
+ )
99
+
73
100
  return (
74
- <div className="relative flex items-center" ref={ref} onKeyDown={handleKeyDown}>
101
+ <div className="relative flex items-center" ref={triggerRef} onKeyDown={handleKeyDown}>
75
102
  <button
103
+ aria-expanded={isOpen}
104
+ aria-haspopup="listbox"
76
105
  onClick={() => setIsOpen(!isOpen)}
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}`}
106
+ className={`flex items-center gap-1.5 py-1 px-2 rounded-md border ${v.bg} text-sm transition-colors cursor-pointer ${FORM_COLORS[effectiveColor].border} text-neutral-200 ${FORM_COLORS[effectiveColor].hover}`}
78
107
  >
79
- <span
80
- className={`${FORM_COLORS[color].accent} hover:brightness-125 transition-colors`}
108
+ <button
109
+ type="button"
110
+ aria-label={ascending ? 'Sort descending' : 'Sort ascending'}
111
+ className={`${FORM_COLORS[effectiveColor].accent} hover:brightness-125 transition-colors cursor-pointer`}
81
112
  onClick={(e) => { e.stopPropagation(); onToggleDirection() }}
82
- role="button"
83
113
  >
84
114
  <DirIcon className="w-3 h-3" />
85
- </span>
115
+ </button>
86
116
  <span className="whitespace-nowrap">{current.label}</span>
87
117
  <ChevronDown className={`w-3 h-3 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
88
118
  </button>
89
-
90
- {isOpen && (
91
- <div ref={menuRef} className={`absolute right-0 top-full z-50 mt-1 min-w-[140px] bg-[var(--popover)] border ${FORM_COLORS[color].border} rounded-lg shadow-xl overflow-hidden`}>
92
- {fields.map((f, idx) => (
93
- <button
94
- key={f.value}
95
- onClick={() => { onFieldChange(f.value); setIsOpen(false) }}
96
- onPointerEnter={() => setHighlightIdx(idx)}
97
- className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
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
- }`}
100
- >
101
- <Check className={`w-3 h-3 shrink-0 ${field === f.value ? FORM_COLORS[color].accent : 'invisible'}`} />
102
- <span>{f.label}</span>
103
- </button>
104
- ))}
105
- </div>
106
- )}
119
+ {menu}
107
120
  </div>
108
121
  )
109
122
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { iconMap, type IconName } from './icon-button.tsx'
4
4
  import { cn } from '../lib/cn.ts'
5
+ import type { FormColor } from '../lib/form-colors.ts'
6
+ import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
5
7
 
6
8
  type StatusType = 'success' | 'warning' | 'error' | 'info' | 'neutral'
7
9
 
@@ -18,6 +20,7 @@ export interface StatusCardProps {
18
20
  items: StatusItem[]
19
21
  action?: { label: string; onClick: () => void }
20
22
  className?: string
23
+ accentColor?: FormColor
21
24
  }
22
25
 
23
26
  const statusDotColor: Record<StatusType, string> = {
@@ -43,11 +46,15 @@ export function StatusCard({
43
46
  items,
44
47
  action,
45
48
  className,
49
+ accentColor: accentColorProp,
46
50
  }: StatusCardProps) {
51
+ const contextAccent = useAccentColor()
52
+ const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
47
53
  const Icon = icon ? iconMap[icon] : undefined
48
54
 
49
55
  return (
50
- <div className={cn('rounded-lg border border-neutral-700 bg-neutral-800 overflow-hidden', className)}>
56
+ <AccentColorProvider value={effectiveColor}>
57
+ <div className={cn('rounded-lg border border-neutral-700 bg-neutral-960 overflow-hidden', className)}>
51
58
  <div className="flex items-center gap-2.5 px-4 py-3 border-b border-neutral-700">
52
59
  {Icon && (
53
60
  <Icon className="w-4 h-4 shrink-0" style={{ color: iconColor }} />
@@ -81,5 +88,6 @@ export function StatusCard({
81
88
  </div>
82
89
  )}
83
90
  </div>
91
+ </AccentColorProvider>
84
92
  )
85
93
  }
@@ -6,6 +6,7 @@ import { iconMap, type IconName } from './icon-button.tsx'
6
6
  import { Badge, type BadgeColor } from './badge.tsx'
7
7
  import { cn } from '../lib/cn.ts'
8
8
  import { Tooltip } from './tooltip.tsx'
9
+ import type { FormColor } from '../lib/form-colors.ts'
9
10
 
10
11
  export interface Tab {
11
12
  id: string
@@ -25,6 +26,7 @@ export interface TabBarProps {
25
26
  variant?: 'underline' | 'pill' | 'card'
26
27
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
27
28
  className?: string
29
+ accentColor?: FormColor
28
30
  }
29
31
 
30
32
  const sizeConfig = {
@@ -32,7 +34,7 @@ const sizeConfig = {
32
34
  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
35
  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' },
34
36
  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' },
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' },
37
+ lg: { text: 'text-lg', icon: 'w-5 h-5', px: 'px-4', py: 'py-2.5', close: 'w-4 h-4', badgeSize: 'lg' as const, gap: 'gap-2' },
36
38
  }
37
39
 
38
40
  const colorMap: Record<string, { active: string; icon: string; indicator: string }> = {
@@ -85,14 +87,15 @@ function TabIcon({ icon, size, color }: { icon: IconName; size: 'xss' | 'xs' | '
85
87
 
86
88
  function TabBadge({ badge, size, badgeColor }: { badge: number | string; size: 'xss' | 'xs' | 'sm' | 'md' | 'lg'; badgeColor?: BadgeColor }) {
87
89
  const s = sizeConfig[size]
88
- return <Badge value={badge} color={badgeColor} size={s.badgeSize} className="flex-shrink-0" />
90
+ return <Badge value={badge} accentColor={badgeColor} size={s.badgeSize} className="flex-shrink-0" />
89
91
  }
90
92
 
91
- function CloseButton({ size, onClick }: { size: 'xss' | 'xs' | 'sm' | 'md' | 'lg'; onClick: () => void }) {
93
+ function CloseButton({ size, onClick, tabLabel }: { size: 'xss' | 'xs' | 'sm' | 'md' | 'lg'; onClick: () => void; tabLabel: string }) {
92
94
  const s = sizeConfig[size]
93
95
  return (
94
96
  <button
95
97
  type="button"
98
+ aria-label={`Close ${tabLabel}`}
96
99
  onClick={(e) => {
97
100
  e.stopPropagation()
98
101
  onClick()
@@ -116,13 +119,15 @@ function CompactTab({
116
119
  ? cn('bg-neutral-700 font-medium', c.active)
117
120
  : 'text-neutral-500 hover:text-neutral-400 hover:bg-neutral-700/50',
118
121
  card: isActive
119
- ? cn('bg-neutral-800 border-neutral-700', c.active)
120
- : 'bg-transparent border-transparent text-neutral-500 hover:text-neutral-400 hover:bg-neutral-900',
122
+ ? cn('bg-neutral-960 border-neutral-700', c.active)
123
+ : 'bg-transparent border-transparent text-neutral-500 hover:text-neutral-400 hover:bg-neutral-980',
121
124
  }[variant]
122
125
 
123
126
  return (
124
127
  <button
125
128
  type="button"
129
+ role="tab"
130
+ aria-selected={isActive}
126
131
  onClick={onSelect}
127
132
  className={cn(
128
133
  'relative flex items-center justify-center transition-colors cursor-pointer',
@@ -136,7 +141,7 @@ function CompactTab({
136
141
  {tab.icon && <TabIcon icon={tab.icon} size={size} color={isActive ? tab.color : undefined} />}
137
142
  {tab.badge !== undefined && (
138
143
  <span className="absolute -top-1.5 -right-2.5">
139
- <Badge value={tab.badge} color={tab.badgeColor} size="xss" />
144
+ <Badge value={tab.badge} accentColor={tab.badgeColor} size="xss" />
140
145
  </span>
141
146
  )}
142
147
  </span>
@@ -144,7 +149,7 @@ function CompactTab({
144
149
  <span className={cn('absolute bottom-0 left-0 right-0 h-0.5 rounded-full', c.indicator)} />
145
150
  )}
146
151
  {isActive && variant === 'card' && (
147
- <span className="absolute -bottom-px left-0 right-0 h-px bg-neutral-800" />
152
+ <span className="absolute -bottom-px left-0 right-0 h-px bg-neutral-960" />
148
153
  )}
149
154
  </button>
150
155
  )
@@ -160,6 +165,8 @@ function UnderlineTab({
160
165
  return (
161
166
  <button
162
167
  type="button"
168
+ role="tab"
169
+ aria-selected={isActive}
163
170
  onClick={onSelect}
164
171
  className={cn(
165
172
  'group relative flex items-center whitespace-nowrap transition-colors cursor-pointer',
@@ -171,7 +178,7 @@ function UnderlineTab({
171
178
  {tab.icon && <TabIcon icon={tab.icon} size={size} color={isActive ? tab.color : undefined} />}
172
179
  <span>{tab.label}</span>
173
180
  {tab.badge !== undefined && <TabBadge badge={tab.badge} size={size} badgeColor={tab.badgeColor} />}
174
- {showClose && <CloseButton size={size} onClick={onClose!} />}
181
+ {showClose && <CloseButton size={size} onClick={onClose!} tabLabel={tab.label} />}
175
182
  {isActive && (
176
183
  <span className={cn('absolute bottom-0 left-0 right-0 h-0.5 rounded-full', c.indicator)} />
177
184
  )}
@@ -189,6 +196,8 @@ function PillTab({
189
196
  return (
190
197
  <button
191
198
  type="button"
199
+ role="tab"
200
+ aria-selected={isActive}
192
201
  onClick={onSelect}
193
202
  className={cn(
194
203
  'group flex items-center whitespace-nowrap rounded-md transition-colors cursor-pointer',
@@ -202,7 +211,7 @@ function PillTab({
202
211
  {tab.icon && <TabIcon icon={tab.icon} size={size} color={isActive ? tab.color : undefined} />}
203
212
  <span>{tab.label}</span>
204
213
  {tab.badge !== undefined && <TabBadge badge={tab.badge} size={size} badgeColor={tab.badgeColor} />}
205
- {showClose && <CloseButton size={size} onClick={onClose!} />}
214
+ {showClose && <CloseButton size={size} onClick={onClose!} tabLabel={tab.label} />}
206
215
  </button>
207
216
  )
208
217
  }
@@ -217,22 +226,24 @@ function CardTab({
217
226
  return (
218
227
  <button
219
228
  type="button"
229
+ role="tab"
230
+ aria-selected={isActive}
220
231
  onClick={onSelect}
221
232
  className={cn(
222
233
  'group relative flex items-center whitespace-nowrap transition-colors cursor-pointer rounded-t-lg border border-b-0',
223
234
  s.text, s.px, s.py, s.gap,
224
235
  isActive && 'is-active',
225
236
  isActive
226
- ? cn('bg-neutral-800 border-neutral-700', c.active)
227
- : 'bg-transparent border-transparent text-neutral-500 hover:text-neutral-400 hover:bg-neutral-900',
237
+ ? cn('bg-neutral-960 border-neutral-700', c.active)
238
+ : 'bg-transparent border-transparent text-neutral-500 hover:text-neutral-400 hover:bg-neutral-980',
228
239
  )}
229
240
  >
230
241
  {tab.icon && <TabIcon icon={tab.icon} size={size} color={isActive ? tab.color : undefined} />}
231
242
  <span>{tab.label}</span>
232
243
  {tab.badge !== undefined && <TabBadge badge={tab.badge} size={size} badgeColor={tab.badgeColor} />}
233
- {showClose && <CloseButton size={size} onClick={onClose!} />}
244
+ {showClose && <CloseButton size={size} onClick={onClose!} tabLabel={tab.label} />}
234
245
  {isActive && (
235
- <span className="absolute -bottom-px left-0 right-0 h-px bg-neutral-800" />
246
+ <span className="absolute -bottom-px left-0 right-0 h-px bg-neutral-960" />
236
247
  )}
237
248
  </button>
238
249
  )
@@ -252,6 +263,7 @@ export function TabBar({
252
263
  variant = 'underline',
253
264
  size = 'sm',
254
265
  className,
266
+ accentColor: _accentColor,
255
267
  }: TabBarProps) {
256
268
  const containerRef = useRef<HTMLDivElement>(null)
257
269
  const [compact, setCompact] = useState(false)
@@ -277,6 +289,7 @@ export function TabBar({
277
289
  return (
278
290
  <div
279
291
  ref={containerRef}
292
+ role="tablist"
280
293
  className={cn(
281
294
  'flex items-end',
282
295
  variant === 'underline' && 'border-b border-neutral-700',
@@ -8,10 +8,13 @@
8
8
  */
9
9
 
10
10
  import { type AccentColor } from '../lib/form-colors.ts'
11
+ import { useAccentColor } from '../lib/accent-context.ts'
12
+ import { cn } from '../lib/cn.ts'
11
13
 
12
14
  export type ToggleColor = AccentColor
13
15
 
14
- // Border colors per accent
16
+ // Border colors per accent — rgba values required for inline style with precise opacity control.
17
+ // Base RGB values map to Tailwind accent-500 palette shades.
15
18
  const BORDER_COLORS: Record<ToggleColor, { idle: string; active: string }> = {
16
19
  blue: { idle: 'rgba(59,130,246,0.3)', active: 'rgba(59,130,246,0.4)' },
17
20
  green: { idle: 'rgba(34,197,94,0.3)', active: 'rgba(34,197,94,0.4)' },
@@ -30,7 +33,9 @@ const BORDER_COLORS: Record<ToggleColor, { idle: string; active: string }> = {
30
33
  teal: { idle: 'rgba(20,184,166,0.3)', active: 'rgba(20,184,166,0.4)' },
31
34
  }
32
35
 
33
- // Knob colors: checked = accent color, unchecked = gray
36
+ // Knob colors: checked = accent color, unchecked = gray.
37
+ // Hex values required — applied via inline style for the toggle knob element.
38
+ // Values map to Tailwind accent-400 palette shades.
34
39
  const KNOB_COLORS: Record<ToggleColor, { on: string; off: string }> = {
35
40
  blue: { on: '#60a5fa', off: 'rgba(96,165,250,0.35)' },
36
41
  green: { on: '#4ade80', off: 'rgba(74,222,128,0.35)' },
@@ -83,7 +88,9 @@ export interface ToggleProps {
83
88
  disabled?: boolean
84
89
  size?: ToggleSize
85
90
  className?: string
86
- color?: ToggleColor
91
+ accentColor?: ToggleColor
92
+ /** Accessible label — required for screen readers */
93
+ 'aria-label'?: string
87
94
  /** Test ID for E2E testing */
88
95
  testId?: string
89
96
  }
@@ -93,33 +100,40 @@ export function Toggle({
93
100
  onChange,
94
101
  disabled = false,
95
102
  size = 'sm',
96
- className = '',
97
- color = 'blue',
103
+ className,
104
+ accentColor,
105
+ 'aria-label': ariaLabel,
98
106
  testId,
99
107
  }: ToggleProps) {
108
+ const contextAccent = useAccentColor()
109
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
100
110
  const s = TOGGLE_SIZES[size]
101
- const bc = BORDER_COLORS[color]
102
- const kc = KNOB_COLORS[color]
111
+ const bc = BORDER_COLORS[effectiveColor]
112
+ const kc = KNOB_COLORS[effectiveColor]
103
113
  return (
104
114
  <button
105
115
  type="button"
116
+ role="switch"
117
+ aria-checked={checked}
118
+ aria-label={ariaLabel}
106
119
  onClick={() => !disabled && onChange(!checked)}
107
120
  disabled={disabled}
108
121
  data-testid={testId}
109
122
  style={{ boxShadow: `inset 0 0 0 1px ${checked ? bc.active : bc.idle}` }}
110
- className={`
111
- relative ${s.track} rounded-full transition-all flex-shrink-0
112
- cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed
113
- ${checked ? TOGGLE_CHECKED_TRACK[color] : TOGGLE_UNCHECKED_TRACK[color]}
114
- ${className}
115
- `}
123
+ className={cn(
124
+ 'relative rounded-full transition-all flex-shrink-0 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed',
125
+ s.track,
126
+ checked ? TOGGLE_CHECKED_TRACK[effectiveColor] : TOGGLE_UNCHECKED_TRACK[effectiveColor],
127
+ className,
128
+ )}
116
129
  >
117
130
  <span
118
131
  style={{ backgroundColor: checked ? kc.on : kc.off }}
119
- className={`
120
- block absolute top-0.5 left-0.5 ${s.knob} rounded-full transition-transform
121
- ${checked ? s.translate : 'translate-x-0'}
122
- `}
132
+ className={cn(
133
+ 'block absolute top-0.5 left-0.5 rounded-full transition-transform',
134
+ s.knob,
135
+ checked ? s.translate : 'translate-x-0',
136
+ )}
123
137
  />
124
138
  </button>
125
139
  )
@@ -2,6 +2,8 @@ import { useState, useRef, useLayoutEffect, useEffect, useCallback } from 'react
2
2
  import type { ReactNode } from 'react'
3
3
  import { createPortal } from 'react-dom'
4
4
  import { useClickOutside } from '../hooks/use-click-outside.ts'
5
+ import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
6
+ import type { FormColor } from '../lib/form-colors.ts'
5
7
 
6
8
  export interface TooltipContent {
7
9
  title?: string
@@ -32,6 +34,7 @@ interface TooltipProps {
32
34
  trigger?: 'hover' | 'click'
33
35
  /** Additional classes for the wrapper element (e.g., 'h-full' for flex containers) */
34
36
  wrapperClassName?: string
37
+ accentColor?: FormColor
35
38
  }
36
39
 
37
40
  const TOOLTIP_GAP = 8
@@ -176,7 +179,10 @@ export function Tooltip({
176
179
  trigger = 'hover',
177
180
  interactive = false,
178
181
  wrapperClassName = '',
182
+ accentColor,
179
183
  }: TooltipProps) {
184
+ const contextAccent = useAccentColor()
185
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
180
186
  const [isVisible, setIsVisible] = useState(false)
181
187
  const [coords, setCoords] = useState({ top: 0, left: 0 })
182
188
  const [actualPosition, setActualPosition] = useState<ResolvedPosition>(position === 'auto' ? 'top' : position)
@@ -209,19 +215,18 @@ export function Tooltip({
209
215
  if (!triggerRef.current) return
210
216
  const triggerRect = triggerRef.current.getBoundingClientRect()
211
217
  const tooltipEl = tooltipRef.current
218
+ const tooltipRect = tooltipEl?.getBoundingClientRect()
212
219
 
213
220
  let resolvedPosition: ResolvedPosition = position === 'auto' ? 'top' : position
214
221
 
215
- if (position === 'auto' && tooltipEl) {
216
- const tooltipRect = tooltipEl.getBoundingClientRect()
222
+ if (position === 'auto' && tooltipRect) {
217
223
  resolvedPosition = resolveAutoPosition(triggerRect, tooltipRect)
218
224
  setActualPosition(resolvedPosition)
219
225
  }
220
226
 
221
227
  let newCoords = calculateBasePosition(triggerRect, resolvedPosition, align)
222
228
 
223
- if (tooltipEl) {
224
- const tooltipRect = tooltipEl.getBoundingClientRect()
229
+ if (tooltipRect) {
225
230
  newCoords = adjustForTooltipSize(newCoords, tooltipRect, resolvedPosition, align)
226
231
  newCoords = clampToViewport(newCoords, tooltipRect)
227
232
  }
@@ -248,7 +253,8 @@ export function Tooltip({
248
253
  const tooltipContent = (
249
254
  <div
250
255
  ref={tooltipRef}
251
- className={`fixed px-3 py-1.5 bg-[var(--popover)] border border-neutral-600 rounded-lg shadow-xl z-[9999] ${interactive || trigger === 'click' ? '' : 'pointer-events-none'} ${multiline ? 'whitespace-pre-line' : 'whitespace-nowrap'}`}
256
+ role="tooltip"
257
+ className={`fixed px-3 py-1.5 bg-[var(--popover)] border border-neutral-600 rounded-lg shadow-lg z-[9999] ${interactive || trigger === 'click' ? '' : 'pointer-events-none'} ${multiline ? 'whitespace-pre-line' : 'whitespace-nowrap'}`}
252
258
  style={{
253
259
  top: coords.top,
254
260
  left: coords.left,
@@ -276,7 +282,9 @@ export function Tooltip({
276
282
  onClick={trigger === 'click' ? () => setIsVisible((v) => !v) : undefined}
277
283
  className={wrapperClassName || 'inline-flex'}
278
284
  >
279
- {children}
285
+ <AccentColorProvider value={effectiveColor}>
286
+ {children}
287
+ </AccentColorProvider>
280
288
  </div>
281
289
  {isVisible && createPortal(tooltipContent, document.body)}
282
290
  </>
package/dist/content.js CHANGED
@@ -13,8 +13,8 @@ function DL({ children }) {
13
13
  function DLRow({ term, children, even }) {
14
14
  const bg = even ? "bg-white/[0.015]" : "";
15
15
  return /* @__PURE__ */ jsxs(Fragment, { children: [
16
- /* @__PURE__ */ jsx("div", { className: `py-2 border-b border-neutral-800/60 ${bg} font-semibold text-md whitespace-nowrap`, children: term }),
17
- /* @__PURE__ */ jsx("div", { className: `py-2 border-b border-neutral-800/60 ${bg} text-md text-neutral-400`, children })
16
+ /* @__PURE__ */ jsx("div", { className: `py-2 border-b border-neutral-960/60 ${bg} font-semibold text-md whitespace-nowrap`, children: term }),
17
+ /* @__PURE__ */ jsx("div", { className: `py-2 border-b border-neutral-960/60 ${bg} text-md text-neutral-400`, children })
18
18
  ] });
19
19
  }
20
20
  function UL({ children }) {
@@ -65,17 +65,17 @@ function Callout({ color, children }) {
65
65
  }
66
66
  function CalloutCode({ color, children }) {
67
67
  const c = CALLOUT_COLORS[color] ?? CALLOUT_COLORS.blue;
68
- return /* @__PURE__ */ jsx("code", { className: `block bg-neutral-800/80 px-2 py-1 rounded mt-1.5 text-md ${c.codeText}`, children });
68
+ return /* @__PURE__ */ jsx("code", { className: `block bg-neutral-960/80 px-2 py-1 rounded mt-1.5 text-md ${c.codeText}`, children });
69
69
  }
70
70
  function CalloutDim({ children }) {
71
71
  return /* @__PURE__ */ jsx("p", { className: "text-neutral-500 mt-1.5", children });
72
72
  }
73
73
  function CodeBlock({ children }) {
74
- return /* @__PURE__ */ jsx("div", { className: "bg-neutral-900/60 rounded-md p-3 font-mono text-sm text-neutral-400 mb-5 whitespace-pre overflow-x-auto leading-normal", children });
74
+ return /* @__PURE__ */ jsx("div", { className: "bg-neutral-980/60 rounded-md p-3 font-mono text-sm text-neutral-400 mb-5 whitespace-pre overflow-x-auto leading-normal", children });
75
75
  }
76
76
  function CK({ color, children }) {
77
77
  const textColor = color ? `text-${color}-400` : "";
78
- return /* @__PURE__ */ jsx("code", { className: `bg-neutral-800/80 px-1.5 py-px rounded text-[0.9em] ${textColor}`, children });
78
+ return /* @__PURE__ */ jsx("code", { className: `bg-neutral-960/80 px-1.5 py-px rounded text-[0.9em] ${textColor}`, children });
79
79
  }
80
80
  function ExternalLink({ href, children }) {
81
81
  return /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline cursor-pointer", children });
@@ -91,7 +91,7 @@ function LocationItem({
91
91
  }) {
92
92
  const bg = even ? "bg-white/[0.015]" : "";
93
93
  return /* @__PURE__ */ jsxs(Fragment, { children: [
94
- /* @__PURE__ */ jsxs("div", { className: `py-2 border-b border-neutral-800/60 ${bg} flex items-center gap-1.5`, children: [
94
+ /* @__PURE__ */ jsxs("div", { className: `py-2 border-b border-neutral-960/60 ${bg} flex items-center gap-1.5`, children: [
95
95
  /* @__PURE__ */ jsx(
96
96
  "span",
97
97
  {
@@ -100,7 +100,7 @@ function LocationItem({
100
100
  ),
101
101
  /* @__PURE__ */ jsx("span", { className: `text-${color}-400 text-md font-semibold`, children: label })
102
102
  ] }),
103
- /* @__PURE__ */ jsx("div", { className: `py-2 border-b border-neutral-800/60 ${bg} text-md text-neutral-400`, children })
103
+ /* @__PURE__ */ jsx("div", { className: `py-2 border-b border-neutral-960/60 ${bg} text-md text-neutral-400`, children })
104
104
  ] });
105
105
  }
106
106
  function TitledLI({ color, title, children }) {
@@ -115,7 +115,7 @@ function TitledLI({ color, title, children }) {
115
115
  ] });
116
116
  }
117
117
  function CalloutDialog({ color, lines }) {
118
- return /* @__PURE__ */ jsx("div", { className: "bg-neutral-800/80 rounded px-2 py-1 mt-1.5 flex flex-col gap-0.5 text-md", children: lines.map((line, idx) => /* @__PURE__ */ jsxs("div", { children: [
118
+ return /* @__PURE__ */ jsx("div", { className: "bg-neutral-960/80 rounded px-2 py-1 mt-1.5 flex flex-col gap-0.5 text-md", children: lines.map((line, idx) => /* @__PURE__ */ jsxs("div", { children: [
119
119
  /* @__PURE__ */ jsxs("span", { className: `text-${color}-300 font-semibold mr-1`, children: [
120
120
  line.speaker,
121
121
  ":"
@@ -66,7 +66,6 @@ declare const stroke: {
66
66
  readonly thin: 1;
67
67
  readonly hairline: 0.5;
68
68
  };
69
- /** Semantic text colors. */
70
69
  declare const color: {
71
70
  readonly textBright: "#e5e7eb";
72
71
  readonly textMedium: "#9ca3af";