@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.
- package/ai-manifest.json +35 -20
- package/components/composites/dashboard-list-item.tsx +172 -0
- package/components/composites/dashboard-panel.tsx +218 -0
- package/components/content/info-panel-primitives.tsx +9 -8
- package/components/diagrams/diagram-utils.tsx +2 -1
- package/components/hooks/use-dropdown-portal.ts +39 -0
- package/components/hooks/use-modal-behavior.ts +32 -3
- package/components/lib/accent-context.ts +10 -0
- package/components/lib/{ai-tools.tsx → coding-agents.tsx} +23 -8
- package/components/lib/custom-icons.tsx +37 -0
- package/components/lib/git-providers.tsx +39 -0
- package/components/lib/theme-engine.ts +59 -10
- package/components/lib/toolr-brand.tsx +23 -9
- package/components/sections/captured-issues/captured-issues-panel.tsx +17 -8
- package/components/sections/{ai-tools-paths/tools-paths-panel.tsx → coding-agent-paths/agent-paths-panel.tsx} +70 -62
- package/components/sections/coding-agent-paths/index.ts +37 -0
- package/components/sections/{ai-tools-paths → coding-agent-paths}/types.ts +28 -28
- package/components/sections/coding-agent-paths/use-agent-paths.ts +159 -0
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +11 -10
- package/components/sections/golden-snapshots/golden-sync-panel.tsx +12 -3
- package/components/sections/golden-snapshots/snapshot-manager.tsx +9 -7
- package/components/sections/golden-snapshots/status-overview.tsx +8 -8
- package/components/sections/golden-snapshots/version-manager.tsx +6 -6
- package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +3 -3
- package/components/sections/prompt-editor/index.ts +1 -1
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +13 -5
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +18 -10
- package/components/sections/prompt-editor/types.ts +2 -2
- package/components/sections/report-bug/report-bug-form.tsx +12 -4
- package/components/sections/report-bug/screenshot-uploader.tsx +11 -3
- package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +12 -4
- package/components/sections/snapshot-browser/snapshot-tree.tsx +5 -4
- package/components/sections/snapshot-browser/types.ts +1 -1
- package/components/sections/snippets-editor/snippets-editor.tsx +16 -9
- package/components/settings/SettingsHeader.tsx +2 -2
- package/components/settings/SettingsPanel.tsx +11 -3
- package/components/settings/SettingsTreeNav.tsx +15 -9
- package/components/ui/action-dialog.tsx +37 -35
- package/components/ui/ai-action-button.tsx +12 -11
- package/components/ui/ai-execution-action-buttons.tsx +13 -5
- package/components/ui/badge.tsx +17 -6
- package/components/ui/bottom-panel-header.tsx +9 -5
- package/components/ui/breadcrumb.tsx +14 -6
- package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +14 -6
- package/components/ui/checkbox.tsx +23 -14
- package/components/ui/collapsible-section.tsx +38 -28
- package/components/ui/confirm-badge.tsx +17 -6
- package/components/ui/cookie-consent.tsx +13 -7
- package/components/ui/detail-section.tsx +24 -16
- package/components/ui/detail-view-wrapper.tsx +30 -22
- package/components/ui/editor-placeholder-card.tsx +28 -24
- package/components/ui/editor-toolbar.tsx +7 -4
- package/components/ui/execution-details-panel.tsx +10 -5
- package/components/ui/file-structure-section.tsx +3 -3
- package/components/ui/file-tree.tsx +7 -5
- package/components/ui/files-panel.tsx +147 -27
- package/components/ui/filter-dropdown.tsx +88 -75
- package/components/ui/form-actions.tsx +21 -11
- package/components/ui/frontmatter-form-header.tsx +10 -2
- package/components/ui/icon-button.tsx +27 -14
- package/components/ui/input.tsx +15 -7
- package/components/ui/label.tsx +9 -5
- package/components/ui/layout-tab-bar.tsx +11 -9
- package/components/ui/modal.tsx +26 -8
- package/components/ui/nav-card.tsx +7 -4
- package/components/ui/navigation-bar.tsx +40 -12
- package/components/ui/number-input.tsx +14 -4
- package/components/ui/project-explorer.tsx +666 -0
- package/components/ui/registry-browser.tsx +12 -1
- package/components/ui/registry-card.tsx +49 -42
- package/components/ui/registry-detail.tsx +34 -11
- package/components/ui/resizable-textarea.tsx +18 -11
- package/components/ui/scope-badge.tsx +18 -11
- package/components/ui/segmented-toggle.tsx +7 -2
- package/components/ui/select.tsx +17 -11
- package/components/ui/selection-grid.tsx +40 -37
- package/components/ui/setting-row.tsx +6 -4
- package/components/ui/settings-card.tsx +12 -5
- package/components/ui/settings-info-box.tsx +9 -6
- package/components/ui/settings-section-title.tsx +14 -2
- package/components/ui/snapshot-card.tsx +10 -2
- package/components/ui/snippets-panel.tsx +4 -2
- package/components/ui/sort-dropdown.tsx +45 -32
- package/components/ui/status-card.tsx +9 -1
- package/components/ui/tab-bar.tsx +26 -13
- package/components/ui/toggle.tsx +31 -17
- package/components/ui/tooltip.tsx +14 -6
- package/dist/content.js +8 -8
- package/dist/diagrams.d.ts +0 -1
- package/dist/index.d.ts +431 -186
- package/dist/index.js +3119 -1724
- package/dist/tokens/primitives.css +28 -6
- package/dist/tokens/semantic.css +15 -15
- package/dist/tokens/theme.css +23 -0
- package/index.ts +25 -11
- package/package.json +9 -1
- package/tokens/primitives.css +28 -6
- package/tokens/semantic.css +15 -15
- package/tokens/theme.css +23 -0
- package/components/sections/ai-tools-paths/index.ts +0 -37
- package/components/sections/ai-tools-paths/use-tools-paths.ts +0 -159
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { useState,
|
|
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 {
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
43
|
-
const menuRef = useDropdownMaxHeight<HTMLDivElement>(isOpen)
|
|
46
|
+
const { triggerRef, menuRef, position } = useDropdownPortal(isOpen)
|
|
44
47
|
const v = VARIANT_CLASSES[variant]
|
|
45
48
|
|
|
46
|
-
useClickOutside(
|
|
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={
|
|
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
|
|
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
|
-
<
|
|
80
|
-
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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-
|
|
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}
|
|
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-
|
|
120
|
-
: 'bg-transparent border-transparent text-neutral-500 hover:text-neutral-400 hover:bg-neutral-
|
|
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}
|
|
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-
|
|
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-
|
|
227
|
-
: 'bg-transparent border-transparent text-neutral-500 hover:text-neutral-400 hover:bg-neutral-
|
|
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-
|
|
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',
|
package/components/ui/toggle.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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[
|
|
102
|
-
const kc = KNOB_COLORS[
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
121
|
-
|
|
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' &&
|
|
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 (
|
|
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
|
-
|
|
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
|
-
{
|
|
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-
|
|
17
|
-
/* @__PURE__ */ jsx("div", { className: `py-2 border-b border-neutral-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
":"
|