@toolr/ui-design 0.1.9 → 0.1.11
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/components/lib/form-colors.ts +16 -16
- package/components/lib/toolr-brand.tsx +1 -1
- package/components/ui/breadcrumb.tsx +5 -5
- package/components/ui/file-structure-section.tsx +5 -2
- package/components/ui/input.tsx +1 -1
- package/components/ui/navigation-bar.tsx +129 -75
- package/components/ui/registry-detail.tsx +9 -9
- package/components/ui/tooltip.tsx +23 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +155 -92
- package/package.json +1 -1
|
@@ -38,22 +38,22 @@ export const ACCENT_TEXT: Record<AccentColor, string> = {
|
|
|
38
38
|
|
|
39
39
|
export const ACCENT_ICON = ACCENT_TEXT
|
|
40
40
|
|
|
41
|
-
export const ACCENT_NAV: Record<AccentColor, { bg: string; text: string }> = {
|
|
42
|
-
blue: { bg: 'bg-blue-500/10', text: 'text-blue-400' },
|
|
43
|
-
green: { bg: 'bg-green-500/10', text: 'text-green-400' },
|
|
44
|
-
red: { bg: 'bg-red-500/10', text: 'text-red-400' },
|
|
45
|
-
orange: { bg: 'bg-orange-500/10', text: 'text-orange-400' },
|
|
46
|
-
cyan: { bg: 'bg-cyan-500/10', text: 'text-cyan-400' },
|
|
47
|
-
yellow: { bg: 'bg-yellow-500/10', text: 'text-yellow-400' },
|
|
48
|
-
purple: { bg: 'bg-purple-500/10', text: 'text-purple-400' },
|
|
49
|
-
indigo: { bg: 'bg-indigo-500/10', text: 'text-indigo-400' },
|
|
50
|
-
emerald: { bg: 'bg-emerald-500/10', text: 'text-emerald-400' },
|
|
51
|
-
amber: { bg: 'bg-amber-500/10', text: 'text-amber-400' },
|
|
52
|
-
violet: { bg: 'bg-violet-500/10', text: 'text-violet-400' },
|
|
53
|
-
neutral: { bg: 'bg-neutral-500/10', text: 'text-neutral-400' },
|
|
54
|
-
sky: { bg: 'bg-sky-500/10', text: 'text-sky-400' },
|
|
55
|
-
pink: { bg: 'bg-pink-500/10', text: 'text-pink-400' },
|
|
56
|
-
teal: { bg: 'bg-teal-500/10', text: 'text-teal-400' },
|
|
41
|
+
export const ACCENT_NAV: Record<AccentColor, { bg: string; text: string; border: string }> = {
|
|
42
|
+
blue: { bg: 'bg-blue-500/10', text: 'text-blue-400', border: 'border-blue-500/30' },
|
|
43
|
+
green: { bg: 'bg-green-500/10', text: 'text-green-400', border: 'border-green-500/30' },
|
|
44
|
+
red: { bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/30' },
|
|
45
|
+
orange: { bg: 'bg-orange-500/10', text: 'text-orange-400', border: 'border-orange-500/30' },
|
|
46
|
+
cyan: { bg: 'bg-cyan-500/10', text: 'text-cyan-400', border: 'border-cyan-500/30' },
|
|
47
|
+
yellow: { bg: 'bg-yellow-500/10', text: 'text-yellow-400', border: 'border-yellow-500/30' },
|
|
48
|
+
purple: { bg: 'bg-purple-500/10', text: 'text-purple-400', border: 'border-purple-500/30' },
|
|
49
|
+
indigo: { bg: 'bg-indigo-500/10', text: 'text-indigo-400', border: 'border-indigo-500/30' },
|
|
50
|
+
emerald: { bg: 'bg-emerald-500/10', text: 'text-emerald-400', border: 'border-emerald-500/30' },
|
|
51
|
+
amber: { bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/30' },
|
|
52
|
+
violet: { bg: 'bg-violet-500/10', text: 'text-violet-400', border: 'border-violet-500/30' },
|
|
53
|
+
neutral: { bg: 'bg-neutral-500/10', text: 'text-neutral-400', border: 'border-neutral-500/30' },
|
|
54
|
+
sky: { bg: 'bg-sky-500/10', text: 'text-sky-400', border: 'border-sky-500/30' },
|
|
55
|
+
pink: { bg: 'bg-pink-500/10', text: 'text-pink-400', border: 'border-pink-500/30' },
|
|
56
|
+
teal: { bg: 'bg-teal-500/10', text: 'text-teal-400', border: 'border-teal-500/30' },
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export const FORM_COLORS: Record<FormColor, FormColorConfig> = {
|
|
@@ -9,7 +9,7 @@ export const TOOLR_APPS: Record<ToolrAppId, { name: string; accent: string }> =
|
|
|
9
9
|
configr: { name: 'Configr', accent: 'violet' }, // #8b5cf6
|
|
10
10
|
reviewr: { name: 'Reviewr', accent: 'orange' }, // #f97316
|
|
11
11
|
learnr: { name: 'Learnr', accent: 'yellow' }, // #eab308
|
|
12
|
-
seedr: { name: 'Seedr', accent: '
|
|
12
|
+
seedr: { name: 'Seedr', accent: 'green' }, // #22c55e
|
|
13
13
|
planr: { name: 'Planr', accent: 'pink' }, // #ec4899 (not in ACCENT_DEFS, fallback hex)
|
|
14
14
|
vibr: { name: 'Vibr', accent: 'red' }, // #ef4444
|
|
15
15
|
}
|
|
@@ -77,13 +77,13 @@ export function Breadcrumb({
|
|
|
77
77
|
<nav className={cn('flex items-center min-w-0', className)}>
|
|
78
78
|
<div className={cn(
|
|
79
79
|
'flex items-center gap-1',
|
|
80
|
-
isBox && [s.px, s.py, 'bg-neutral-960/50
|
|
80
|
+
isBox && [s.px, s.py, 'bg-neutral-960/50 rounded-lg'],
|
|
81
81
|
)}>
|
|
82
82
|
{segments.map((segment, index) => {
|
|
83
83
|
const isLast = index === segments.length - 1
|
|
84
84
|
const isClickable = !isLast && !!segment.onClick
|
|
85
85
|
const colors = segment.color && ACCENT_NAV[segment.color as AccentColor] ? ACCENT_NAV[segment.color as AccentColor] : null
|
|
86
|
-
const
|
|
86
|
+
const isFirst = !isBox && index === 0
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
89
|
<div key={segment.id} className="flex items-center gap-1 min-w-0">
|
|
@@ -94,7 +94,7 @@ export function Breadcrumb({
|
|
|
94
94
|
onClick={segment.onClick}
|
|
95
95
|
className={cn(
|
|
96
96
|
'flex items-center gap-1.5 pr-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0',
|
|
97
|
-
|
|
97
|
+
isFirst ? 'pl-0' : 'pl-2',
|
|
98
98
|
s.text,
|
|
99
99
|
'font-medium hover:text-white',
|
|
100
100
|
colors ? [colors.text, `hover:${colors.bg}`] : ['text-neutral-300', 'hover:bg-neutral-700/50'],
|
|
@@ -107,10 +107,10 @@ export function Breadcrumb({
|
|
|
107
107
|
<div
|
|
108
108
|
className={cn(
|
|
109
109
|
'flex items-center gap-1.5 pr-2 py-0.5 rounded-md min-w-0',
|
|
110
|
-
|
|
110
|
+
isFirst ? 'pl-0' : 'pl-2',
|
|
111
111
|
s.text,
|
|
112
112
|
isLast
|
|
113
|
-
?
|
|
113
|
+
? 'font-medium text-white'
|
|
114
114
|
: ['font-medium', colors ? colors.text : 'text-neutral-300'],
|
|
115
115
|
)}
|
|
116
116
|
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef, useMemo, type ReactNode } from 'react'
|
|
2
2
|
import { FileCode, FolderTree, Loader2, AlertCircle, AlignLeft, Code2, Type } from 'lucide-react'
|
|
3
3
|
import { type AccentColor, ACCENT_ICON } from '../lib/form-colors.ts'
|
|
4
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
4
5
|
import { CollapseButton } from './icon-button.tsx'
|
|
5
6
|
import { SegmentedToggle } from './segmented-toggle.tsx'
|
|
6
7
|
import { FileTree, collectDirPaths, type FileTreeNode } from './file-tree.tsx'
|
|
@@ -124,10 +125,12 @@ export function FileStructureSection({
|
|
|
124
125
|
format,
|
|
125
126
|
language,
|
|
126
127
|
default: defaultMode,
|
|
127
|
-
accentColor
|
|
128
|
+
accentColor: accentColorProp,
|
|
128
129
|
renderPreview,
|
|
129
130
|
initialHeight,
|
|
130
131
|
}: FileStructureSectionProps) {
|
|
132
|
+
const contextAccent = useAccentColor()
|
|
133
|
+
const accentColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
131
134
|
const [selectedFilePath, setSelectedFilePath] = useState<string | null>(null)
|
|
132
135
|
const [fileContent, setFileContent] = useState<string | null>(null)
|
|
133
136
|
const [fetchedFilePath, setFetchedFilePath] = useState<string | null>(null)
|
|
@@ -335,7 +338,7 @@ export function FileStructureSection({
|
|
|
335
338
|
<CollapseButton
|
|
336
339
|
collapsed={allCollapsed}
|
|
337
340
|
onToggle={() => setExpandedPaths(allCollapsed ? new Set(allDirPaths) : new Set())}
|
|
338
|
-
accentColor=
|
|
341
|
+
accentColor="neutral"
|
|
339
342
|
/>
|
|
340
343
|
</div>
|
|
341
344
|
<div className={`${variant === 'split' ? 'flex-1 overflow-y-auto' : ''} p-3`}>
|
package/components/ui/input.tsx
CHANGED
|
@@ -26,7 +26,7 @@ import { useAccentColor } from '../lib/accent-context.ts'
|
|
|
26
26
|
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size' | 'type'> {
|
|
27
27
|
value: string
|
|
28
28
|
onChange: (value: string) => void
|
|
29
|
-
type?: 'text' | 'search' | 'password'
|
|
29
|
+
type?: 'text' | 'search' | 'password' | 'email'
|
|
30
30
|
debounceMs?: number
|
|
31
31
|
error?: boolean | string
|
|
32
32
|
variant?: 'filled' | 'outline'
|
|
@@ -18,10 +18,13 @@ export interface NavigationBarProps {
|
|
|
18
18
|
onForward?: () => void
|
|
19
19
|
showHistory?: boolean
|
|
20
20
|
historyEntries?: BreadcrumbSegment[][]
|
|
21
|
+
currentHistoryIndex?: number
|
|
21
22
|
onHistorySelect?: (index: number) => void
|
|
22
23
|
leadingAction?: { icon: IconName; onClick?: () => void }
|
|
23
24
|
separator?: 'chevron' | 'slash' | 'dot'
|
|
24
25
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
26
|
+
/** 'bar' (default): all-in-one bordered box. 'header': nav controls left-aligned, breadcrumb absolutely centered. */
|
|
27
|
+
layout?: 'bar' | 'header'
|
|
25
28
|
accentColor?: AccentColor
|
|
26
29
|
className?: string
|
|
27
30
|
}
|
|
@@ -113,10 +116,12 @@ export function NavigationBar({
|
|
|
113
116
|
onForward,
|
|
114
117
|
showHistory = false,
|
|
115
118
|
historyEntries,
|
|
119
|
+
currentHistoryIndex,
|
|
116
120
|
onHistorySelect,
|
|
117
121
|
leadingAction,
|
|
118
122
|
separator = 'chevron',
|
|
119
123
|
size = 'sm',
|
|
124
|
+
layout = 'bar',
|
|
120
125
|
accentColor,
|
|
121
126
|
className,
|
|
122
127
|
}: NavigationBarProps) {
|
|
@@ -135,6 +140,128 @@ export function NavigationBar({
|
|
|
135
140
|
|
|
136
141
|
const hasHistoryEntries = historyEntries && historyEntries.length > 0
|
|
137
142
|
|
|
143
|
+
const renderSegments = () => segments.map((segment, index) => {
|
|
144
|
+
const isLast = index === segments.length - 1
|
|
145
|
+
const isClickable = !isLast && !!segment.onClick
|
|
146
|
+
const colors = segment.color && ACCENT_NAV[segment.color as AccentColor] ? ACCENT_NAV[segment.color as AccentColor] : null
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div key={segment.id} className="flex items-center gap-1 min-w-0">
|
|
150
|
+
{index > 0 && <SegmentSeparator type={separator} size={size} />}
|
|
151
|
+
{isClickable ? (
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={segment.onClick}
|
|
155
|
+
className={cn(
|
|
156
|
+
'flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0',
|
|
157
|
+
s.text,
|
|
158
|
+
'font-medium hover:text-white',
|
|
159
|
+
colors ? [colors.text, `hover:${colors.bg}`] : ['text-neutral-300', 'hover:bg-neutral-700/50'],
|
|
160
|
+
)}
|
|
161
|
+
>
|
|
162
|
+
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
163
|
+
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
164
|
+
</button>
|
|
165
|
+
) : (
|
|
166
|
+
<div
|
|
167
|
+
className={cn(
|
|
168
|
+
'flex items-center gap-1.5 px-2 py-0.5 rounded-md min-w-0',
|
|
169
|
+
s.text,
|
|
170
|
+
isLast
|
|
171
|
+
? ['font-medium bg-neutral-700/50', colors ? colors.text : 'text-white']
|
|
172
|
+
: ['font-medium', colors ? colors.text : 'text-neutral-300'],
|
|
173
|
+
)}
|
|
174
|
+
>
|
|
175
|
+
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
176
|
+
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const renderHistoryDropdown = () => {
|
|
184
|
+
if (!historyOpen || !hasHistoryEntries || !historyEntries) return null
|
|
185
|
+
const entries = layout === 'header' ? [...historyEntries].reverse() : historyEntries
|
|
186
|
+
return (
|
|
187
|
+
<div className="absolute left-0 top-full mt-1 w-max min-w-[200px] max-w-[420px] bg-black/80 backdrop-blur-sm border border-neutral-700 rounded-lg shadow-lg z-50">
|
|
188
|
+
<div className="px-3 py-1.5 border-b border-neutral-700/50">
|
|
189
|
+
<p className="text-sm font-medium text-neutral-500">History{layout === 'header' && ` (${historyEntries.length})`}</p>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="max-h-[300px] overflow-y-auto py-1">
|
|
192
|
+
{entries.map((entry, entryIdx) => {
|
|
193
|
+
const actualIdx = layout === 'header' ? historyEntries.length - 1 - entryIdx : entryIdx
|
|
194
|
+
const isCurrent = currentHistoryIndex !== undefined && actualIdx === currentHistoryIndex
|
|
195
|
+
return (
|
|
196
|
+
<button
|
|
197
|
+
key={actualIdx}
|
|
198
|
+
type="button"
|
|
199
|
+
onClick={() => {
|
|
200
|
+
onHistorySelect?.(actualIdx)
|
|
201
|
+
setHistoryOpen(false)
|
|
202
|
+
}}
|
|
203
|
+
className={cn(
|
|
204
|
+
'w-full px-3 py-1.5 flex items-center gap-1 text-left transition-colors cursor-pointer',
|
|
205
|
+
isCurrent ? 'bg-green-500/10' : 'hover:bg-neutral-800',
|
|
206
|
+
)}
|
|
207
|
+
>
|
|
208
|
+
{entry.map((seg, segIdx) => (
|
|
209
|
+
<span key={seg.id} className="flex items-center gap-1 min-w-0">
|
|
210
|
+
{segIdx > 0 && <ChevronRight className="w-2.5 h-2.5 text-neutral-600 flex-shrink-0" />}
|
|
211
|
+
{seg.icon && <SegmentIcon icon={seg.icon} color={seg.color} size="xs" />}
|
|
212
|
+
<span className={cn(
|
|
213
|
+
'text-sm truncate',
|
|
214
|
+
seg.color && ACCENT_NAV[seg.color as AccentColor] ? ACCENT_NAV[seg.color as AccentColor].text : 'text-neutral-300',
|
|
215
|
+
)}>
|
|
216
|
+
{seg.label}
|
|
217
|
+
</span>
|
|
218
|
+
</span>
|
|
219
|
+
))}
|
|
220
|
+
{isCurrent && <span className="ml-auto pl-4 text-xs text-green-400 flex-shrink-0">Current</span>}
|
|
221
|
+
</button>
|
|
222
|
+
)
|
|
223
|
+
})}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (layout === 'header') {
|
|
230
|
+
return (
|
|
231
|
+
<nav className={cn('relative flex items-center h-full', className)}>
|
|
232
|
+
{/* Left: nav controls */}
|
|
233
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
234
|
+
{hasNav && (
|
|
235
|
+
<>
|
|
236
|
+
<NavButton icon={ChevronLeft} onClick={onBack} disabled={!canGoBack} size={size} colorStyle={colorStyle} />
|
|
237
|
+
<NavButton icon={ChevronRight} onClick={onForward} disabled={!canGoForward} size={size} colorStyle={colorStyle} />
|
|
238
|
+
</>
|
|
239
|
+
)}
|
|
240
|
+
{showHistory && (
|
|
241
|
+
<div className="relative" ref={historyRef}>
|
|
242
|
+
<NavButton
|
|
243
|
+
icon={History}
|
|
244
|
+
onClick={() => setHistoryOpen(o => !o)}
|
|
245
|
+
disabled={!hasHistoryEntries}
|
|
246
|
+
size={size}
|
|
247
|
+
active={historyOpen}
|
|
248
|
+
colorStyle={colorStyle}
|
|
249
|
+
/>
|
|
250
|
+
{renderHistoryDropdown()}
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{/* Center: breadcrumb absolutely centered */}
|
|
256
|
+
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
257
|
+
<div className={cn('pointer-events-auto flex items-center gap-1', s.px, s.py, 'bg-neutral-960/50 rounded-lg')}>
|
|
258
|
+
{renderSegments()}
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</nav>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
138
265
|
return (
|
|
139
266
|
<nav className={cn('flex items-center', className)}>
|
|
140
267
|
<div className={cn('flex items-center gap-1', s.px, s.py, 'bg-neutral-960/50 border rounded-lg', colorStyle.border)}>
|
|
@@ -166,86 +293,13 @@ export function NavigationBar({
|
|
|
166
293
|
active={historyOpen}
|
|
167
294
|
colorStyle={colorStyle}
|
|
168
295
|
/>
|
|
169
|
-
{
|
|
170
|
-
<div className="absolute left-0 top-full mt-1 w-max min-w-[200px] max-w-[420px] bg-neutral-960 border border-neutral-700 rounded-lg shadow-lg z-50">
|
|
171
|
-
<div className="px-3 py-1.5 border-b border-neutral-700/50">
|
|
172
|
-
<p className="text-sm font-medium text-neutral-500">History</p>
|
|
173
|
-
</div>
|
|
174
|
-
<div className="max-h-[300px] overflow-y-auto py-1">
|
|
175
|
-
{historyEntries.map((entry, i) => (
|
|
176
|
-
<button
|
|
177
|
-
key={i}
|
|
178
|
-
type="button"
|
|
179
|
-
onClick={() => {
|
|
180
|
-
onHistorySelect?.(i)
|
|
181
|
-
setHistoryOpen(false)
|
|
182
|
-
}}
|
|
183
|
-
className="w-full px-3 py-1.5 flex items-center gap-1 text-left hover:bg-neutral-960 transition-colors cursor-pointer"
|
|
184
|
-
>
|
|
185
|
-
{entry.map((seg, segIdx) => (
|
|
186
|
-
<span key={seg.id} className="flex items-center gap-1 min-w-0">
|
|
187
|
-
{segIdx > 0 && <ChevronRight className="w-2.5 h-2.5 text-neutral-600 flex-shrink-0" />}
|
|
188
|
-
{seg.icon && <SegmentIcon icon={seg.icon} color={seg.color} size="xs" />}
|
|
189
|
-
<span className={cn(
|
|
190
|
-
'text-sm truncate',
|
|
191
|
-
seg.color && ACCENT_NAV[seg.color as AccentColor] ? ACCENT_NAV[seg.color as AccentColor].text : 'text-neutral-300',
|
|
192
|
-
)}>
|
|
193
|
-
{seg.label}
|
|
194
|
-
</span>
|
|
195
|
-
</span>
|
|
196
|
-
))}
|
|
197
|
-
</button>
|
|
198
|
-
))}
|
|
199
|
-
</div>
|
|
200
|
-
<div className="px-3 py-1.5 border-t border-neutral-700/50">
|
|
201
|
-
<p className="text-sm text-neutral-600">Click to navigate</p>
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
296
|
+
{renderHistoryDropdown()}
|
|
205
297
|
</div>
|
|
206
298
|
<Divider size={size} />
|
|
207
299
|
</>
|
|
208
300
|
)}
|
|
209
301
|
|
|
210
|
-
{
|
|
211
|
-
const isLast = index === segments.length - 1
|
|
212
|
-
const isClickable = !isLast && !!segment.onClick
|
|
213
|
-
const colors = segment.color && ACCENT_NAV[segment.color as AccentColor] ? ACCENT_NAV[segment.color as AccentColor] : null
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<div key={segment.id} className="flex items-center gap-1 min-w-0">
|
|
217
|
-
{index > 0 && <SegmentSeparator type={separator} size={size} />}
|
|
218
|
-
{isClickable ? (
|
|
219
|
-
<button
|
|
220
|
-
type="button"
|
|
221
|
-
onClick={segment.onClick}
|
|
222
|
-
className={cn(
|
|
223
|
-
'flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0',
|
|
224
|
-
s.text,
|
|
225
|
-
'font-medium hover:text-white',
|
|
226
|
-
colors ? [colors.text, `hover:${colors.bg}`] : ['text-neutral-300', 'hover:bg-neutral-700/50'],
|
|
227
|
-
)}
|
|
228
|
-
>
|
|
229
|
-
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
230
|
-
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
231
|
-
</button>
|
|
232
|
-
) : (
|
|
233
|
-
<div
|
|
234
|
-
className={cn(
|
|
235
|
-
'flex items-center gap-1.5 px-2 py-0.5 rounded-md min-w-0',
|
|
236
|
-
s.text,
|
|
237
|
-
isLast
|
|
238
|
-
? ['font-medium bg-neutral-700/50', colors ? colors.text : 'text-white']
|
|
239
|
-
: ['font-medium', colors ? colors.text : 'text-neutral-300'],
|
|
240
|
-
)}
|
|
241
|
-
>
|
|
242
|
-
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
243
|
-
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
244
|
-
</div>
|
|
245
|
-
)}
|
|
246
|
-
</div>
|
|
247
|
-
)
|
|
248
|
-
})}
|
|
302
|
+
{renderSegments()}
|
|
249
303
|
</div>
|
|
250
304
|
</nav>
|
|
251
305
|
)
|
|
@@ -3,6 +3,7 @@ import { ChevronsUpDown, ChevronsDownUp } from 'lucide-react'
|
|
|
3
3
|
import { IconButton } from './icon-button.tsx'
|
|
4
4
|
import { Label, type LabelProps } from './label.tsx'
|
|
5
5
|
import { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey } from '../lib/coding-agents.tsx'
|
|
6
|
+
import { Tooltip } from './tooltip.tsx'
|
|
6
7
|
import { FileStructureSection, type FileStructureSectionProps } from './file-structure-section.tsx'
|
|
7
8
|
import { type AccentColor } from '../lib/form-colors.ts'
|
|
8
9
|
import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
|
|
@@ -24,7 +25,7 @@ export interface RegistryDetailProps {
|
|
|
24
25
|
|
|
25
26
|
// Standard sections (rendered in order before children)
|
|
26
27
|
description?: string
|
|
27
|
-
longDescription?:
|
|
28
|
+
longDescription?: ReactNode
|
|
28
29
|
integration?: boolean
|
|
29
30
|
compatibleTools?: string[]
|
|
30
31
|
|
|
@@ -56,7 +57,7 @@ const MARKDOWN_CLASSES = 'text-md text-neutral-400 leading-relaxed [&_strong]:te
|
|
|
56
57
|
|
|
57
58
|
const COLLAPSED_MAX_HEIGHT = 240
|
|
58
59
|
|
|
59
|
-
function CollapsibleTextSection({ children, header }: { children:
|
|
60
|
+
function CollapsibleTextSection({ children, header }: { children: ReactNode; header?: string }) {
|
|
60
61
|
const contentRef = useRef<HTMLDivElement>(null)
|
|
61
62
|
const [overflows, setOverflows] = useState(false)
|
|
62
63
|
const [expanded, setExpanded] = useState(false)
|
|
@@ -107,7 +108,7 @@ function CollapsibleTextSection({ children, header }: { children: string; header
|
|
|
107
108
|
className={MARKDOWN_CLASSES}
|
|
108
109
|
style={showCollapsed ? { maxHeight: COLLAPSED_MAX_HEIGHT, overflow: 'hidden' } : undefined}
|
|
109
110
|
>
|
|
110
|
-
<
|
|
111
|
+
<div>{children}</div>
|
|
111
112
|
</div>
|
|
112
113
|
|
|
113
114
|
{showCollapsed && (
|
|
@@ -128,10 +129,9 @@ function CompatibleWithSection({ agents }: { agents: string[] }) {
|
|
|
128
129
|
<h3 className="text-sm font-medium text-neutral-500 uppercase tracking-wider mb-3">Compatible with</h3>
|
|
129
130
|
<div className="flex items-start gap-3">
|
|
130
131
|
{agents.map((agent) => (
|
|
131
|
-
<
|
|
132
|
-
<CodingAgentIcon agent={agent} size={
|
|
133
|
-
|
|
134
|
-
</div>
|
|
132
|
+
<Tooltip key={agent} content={{ description: CODING_AGENT_NAMES[agent as CodingAgentKey] ?? agent }} position="top">
|
|
133
|
+
<CodingAgentIcon agent={agent} size={24} />
|
|
134
|
+
</Tooltip>
|
|
135
135
|
))}
|
|
136
136
|
</div>
|
|
137
137
|
</div>
|
|
@@ -166,8 +166,8 @@ export function RegistryDetail({
|
|
|
166
166
|
const resolvedAccent = accentColor ?? contextAccent
|
|
167
167
|
|
|
168
168
|
const content = (
|
|
169
|
-
<div className="h-full overflow-y-auto
|
|
170
|
-
<div className={`${maxWidth} mx-auto space-y-
|
|
169
|
+
<div className="h-full overflow-y-auto">
|
|
170
|
+
<div className={`${maxWidth} mx-auto px-4 py-6 space-y-8`}>
|
|
171
171
|
{/* Above header badges */}
|
|
172
172
|
{aboveHeader}
|
|
173
173
|
{/* Header row */}
|
|
@@ -247,6 +247,29 @@ export function Tooltip({
|
|
|
247
247
|
return () => window.removeEventListener('scroll', handleScroll, true)
|
|
248
248
|
}, [trigger, isVisible, position, align])
|
|
249
249
|
|
|
250
|
+
// Dismiss hover tooltips on scroll, keyboard, click elsewhere, or window blur to prevent orphans
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
if (trigger !== 'hover' || !isVisible) return
|
|
253
|
+
const dismiss = () => setIsVisible(false)
|
|
254
|
+
window.addEventListener('scroll', dismiss, true)
|
|
255
|
+
window.addEventListener('keydown', dismiss)
|
|
256
|
+
window.addEventListener('pointerdown', dismiss)
|
|
257
|
+
window.addEventListener('blur', dismiss)
|
|
258
|
+
return () => {
|
|
259
|
+
window.removeEventListener('scroll', dismiss, true)
|
|
260
|
+
window.removeEventListener('keydown', dismiss)
|
|
261
|
+
window.removeEventListener('pointerdown', dismiss)
|
|
262
|
+
window.removeEventListener('blur', dismiss)
|
|
263
|
+
}
|
|
264
|
+
}, [trigger, isVisible])
|
|
265
|
+
|
|
266
|
+
// Clear pending hide timeout on unmount
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
return () => {
|
|
269
|
+
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current)
|
|
270
|
+
}
|
|
271
|
+
}, [])
|
|
272
|
+
|
|
250
273
|
const resolvedPos = position === 'auto' ? actualPosition : position
|
|
251
274
|
const arrowClasses = getArrowClasses(resolvedPos, align)
|
|
252
275
|
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ declare const ACCENT_ICON: Record<FormColor, string>;
|
|
|
24
24
|
declare const ACCENT_NAV: Record<AccentColor, {
|
|
25
25
|
bg: string;
|
|
26
26
|
text: string;
|
|
27
|
+
border: string;
|
|
27
28
|
}>;
|
|
28
29
|
declare const FORM_COLORS: Record<FormColor, FormColorConfig>;
|
|
29
30
|
|
|
@@ -399,7 +400,7 @@ declare function Toggle({ checked, onChange, disabled, size, className, accentCo
|
|
|
399
400
|
interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size' | 'type'> {
|
|
400
401
|
value: string;
|
|
401
402
|
onChange: (value: string) => void;
|
|
402
|
-
type?: 'text' | 'search' | 'password';
|
|
403
|
+
type?: 'text' | 'search' | 'password' | 'email';
|
|
403
404
|
debounceMs?: number;
|
|
404
405
|
error?: boolean | string;
|
|
405
406
|
variant?: 'filled' | 'outline';
|
|
@@ -1030,7 +1031,7 @@ interface FileStructureSectionProps {
|
|
|
1030
1031
|
initialHeight?: number;
|
|
1031
1032
|
}
|
|
1032
1033
|
declare function getLanguageFromPath(filePath: string): string;
|
|
1033
|
-
declare function FileStructureSection({ files, rootName, variant, isLoading, error, onFetchContent, format, language, default: defaultMode, accentColor, renderPreview, initialHeight, }: FileStructureSectionProps): react_jsx_runtime.JSX.Element | null;
|
|
1034
|
+
declare function FileStructureSection({ files, rootName, variant, isLoading, error, onFetchContent, format, language, default: defaultMode, accentColor: accentColorProp, renderPreview, initialHeight, }: FileStructureSectionProps): react_jsx_runtime.JSX.Element | null;
|
|
1034
1035
|
|
|
1035
1036
|
interface RegistryDetailProps {
|
|
1036
1037
|
icon: LucideIcon;
|
|
@@ -1041,7 +1042,7 @@ interface RegistryDetailProps {
|
|
|
1041
1042
|
actionButton?: ReactNode;
|
|
1042
1043
|
labels?: LabelProps[];
|
|
1043
1044
|
description?: string;
|
|
1044
|
-
longDescription?:
|
|
1045
|
+
longDescription?: ReactNode;
|
|
1045
1046
|
integration?: boolean;
|
|
1046
1047
|
compatibleTools?: string[];
|
|
1047
1048
|
files?: FileStructureSectionProps['files'];
|
|
@@ -2540,6 +2541,7 @@ interface NavigationBarProps {
|
|
|
2540
2541
|
onForward?: () => void;
|
|
2541
2542
|
showHistory?: boolean;
|
|
2542
2543
|
historyEntries?: BreadcrumbSegment[][];
|
|
2544
|
+
currentHistoryIndex?: number;
|
|
2543
2545
|
onHistorySelect?: (index: number) => void;
|
|
2544
2546
|
leadingAction?: {
|
|
2545
2547
|
icon: IconName;
|
|
@@ -2547,10 +2549,12 @@ interface NavigationBarProps {
|
|
|
2547
2549
|
};
|
|
2548
2550
|
separator?: 'chevron' | 'slash' | 'dot';
|
|
2549
2551
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg';
|
|
2552
|
+
/** 'bar' (default): all-in-one bordered box. 'header': nav controls left-aligned, breadcrumb absolutely centered. */
|
|
2553
|
+
layout?: 'bar' | 'header';
|
|
2550
2554
|
accentColor?: AccentColor;
|
|
2551
2555
|
className?: string;
|
|
2552
2556
|
}
|
|
2553
|
-
declare function NavigationBar({ segments, canGoBack, canGoForward, onBack, onForward, showHistory, historyEntries, onHistorySelect, leadingAction, separator, size, accentColor, className, }: NavigationBarProps): react_jsx_runtime.JSX.Element;
|
|
2557
|
+
declare function NavigationBar({ segments, canGoBack, canGoForward, onBack, onForward, showHistory, historyEntries, currentHistoryIndex, onHistorySelect, leadingAction, separator, size, layout, accentColor, className, }: NavigationBarProps): react_jsx_runtime.JSX.Element;
|
|
2554
2558
|
|
|
2555
2559
|
interface Tab {
|
|
2556
2560
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -18,21 +18,21 @@ var ACCENT_TEXT = {
|
|
|
18
18
|
};
|
|
19
19
|
var ACCENT_ICON = ACCENT_TEXT;
|
|
20
20
|
var ACCENT_NAV = {
|
|
21
|
-
blue: { bg: "bg-blue-500/10", text: "text-blue-400" },
|
|
22
|
-
green: { bg: "bg-green-500/10", text: "text-green-400" },
|
|
23
|
-
red: { bg: "bg-red-500/10", text: "text-red-400" },
|
|
24
|
-
orange: { bg: "bg-orange-500/10", text: "text-orange-400" },
|
|
25
|
-
cyan: { bg: "bg-cyan-500/10", text: "text-cyan-400" },
|
|
26
|
-
yellow: { bg: "bg-yellow-500/10", text: "text-yellow-400" },
|
|
27
|
-
purple: { bg: "bg-purple-500/10", text: "text-purple-400" },
|
|
28
|
-
indigo: { bg: "bg-indigo-500/10", text: "text-indigo-400" },
|
|
29
|
-
emerald: { bg: "bg-emerald-500/10", text: "text-emerald-400" },
|
|
30
|
-
amber: { bg: "bg-amber-500/10", text: "text-amber-400" },
|
|
31
|
-
violet: { bg: "bg-violet-500/10", text: "text-violet-400" },
|
|
32
|
-
neutral: { bg: "bg-neutral-500/10", text: "text-neutral-400" },
|
|
33
|
-
sky: { bg: "bg-sky-500/10", text: "text-sky-400" },
|
|
34
|
-
pink: { bg: "bg-pink-500/10", text: "text-pink-400" },
|
|
35
|
-
teal: { bg: "bg-teal-500/10", text: "text-teal-400" }
|
|
21
|
+
blue: { bg: "bg-blue-500/10", text: "text-blue-400", border: "border-blue-500/30" },
|
|
22
|
+
green: { bg: "bg-green-500/10", text: "text-green-400", border: "border-green-500/30" },
|
|
23
|
+
red: { bg: "bg-red-500/10", text: "text-red-400", border: "border-red-500/30" },
|
|
24
|
+
orange: { bg: "bg-orange-500/10", text: "text-orange-400", border: "border-orange-500/30" },
|
|
25
|
+
cyan: { bg: "bg-cyan-500/10", text: "text-cyan-400", border: "border-cyan-500/30" },
|
|
26
|
+
yellow: { bg: "bg-yellow-500/10", text: "text-yellow-400", border: "border-yellow-500/30" },
|
|
27
|
+
purple: { bg: "bg-purple-500/10", text: "text-purple-400", border: "border-purple-500/30" },
|
|
28
|
+
indigo: { bg: "bg-indigo-500/10", text: "text-indigo-400", border: "border-indigo-500/30" },
|
|
29
|
+
emerald: { bg: "bg-emerald-500/10", text: "text-emerald-400", border: "border-emerald-500/30" },
|
|
30
|
+
amber: { bg: "bg-amber-500/10", text: "text-amber-400", border: "border-amber-500/30" },
|
|
31
|
+
violet: { bg: "bg-violet-500/10", text: "text-violet-400", border: "border-violet-500/30" },
|
|
32
|
+
neutral: { bg: "bg-neutral-500/10", text: "text-neutral-400", border: "border-neutral-500/30" },
|
|
33
|
+
sky: { bg: "bg-sky-500/10", text: "text-sky-400", border: "border-sky-500/30" },
|
|
34
|
+
pink: { bg: "bg-pink-500/10", text: "text-pink-400", border: "border-pink-500/30" },
|
|
35
|
+
teal: { bg: "bg-teal-500/10", text: "text-teal-400", border: "border-teal-500/30" }
|
|
36
36
|
};
|
|
37
37
|
var FORM_COLORS = {
|
|
38
38
|
blue: { border: "border-blue-500/30", hover: "hover:bg-blue-500/20 hover:border-blue-500/40", focus: "focus:border-blue-500", selectedBg: "bg-blue-600/20", accent: "text-blue-400" },
|
|
@@ -193,8 +193,8 @@ var TOOLR_APPS = {
|
|
|
193
193
|
// #f97316
|
|
194
194
|
learnr: { name: "Learnr", accent: "yellow" },
|
|
195
195
|
// #eab308
|
|
196
|
-
seedr: { name: "Seedr", accent: "
|
|
197
|
-
// #
|
|
196
|
+
seedr: { name: "Seedr", accent: "green" },
|
|
197
|
+
// #22c55e
|
|
198
198
|
planr: { name: "Planr", accent: "pink" },
|
|
199
199
|
// #ec4899 (not in ACCENT_DEFS, fallback hex)
|
|
200
200
|
vibr: { name: "Vibr", accent: "red" }
|
|
@@ -566,6 +566,25 @@ function Tooltip({
|
|
|
566
566
|
window.addEventListener("scroll", handleScroll, true);
|
|
567
567
|
return () => window.removeEventListener("scroll", handleScroll, true);
|
|
568
568
|
}, [trigger, isVisible, position, align]);
|
|
569
|
+
useEffect2(() => {
|
|
570
|
+
if (trigger !== "hover" || !isVisible) return;
|
|
571
|
+
const dismiss = () => setIsVisible(false);
|
|
572
|
+
window.addEventListener("scroll", dismiss, true);
|
|
573
|
+
window.addEventListener("keydown", dismiss);
|
|
574
|
+
window.addEventListener("pointerdown", dismiss);
|
|
575
|
+
window.addEventListener("blur", dismiss);
|
|
576
|
+
return () => {
|
|
577
|
+
window.removeEventListener("scroll", dismiss, true);
|
|
578
|
+
window.removeEventListener("keydown", dismiss);
|
|
579
|
+
window.removeEventListener("pointerdown", dismiss);
|
|
580
|
+
window.removeEventListener("blur", dismiss);
|
|
581
|
+
};
|
|
582
|
+
}, [trigger, isVisible]);
|
|
583
|
+
useEffect2(() => {
|
|
584
|
+
return () => {
|
|
585
|
+
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
|
|
586
|
+
};
|
|
587
|
+
}, []);
|
|
569
588
|
const resolvedPos = position === "auto" ? actualPosition : position;
|
|
570
589
|
const arrowClasses = getArrowClasses(resolvedPos, align);
|
|
571
590
|
const tooltipContent = /* @__PURE__ */ jsxs3(
|
|
@@ -4377,10 +4396,12 @@ function FileStructureSection({
|
|
|
4377
4396
|
format,
|
|
4378
4397
|
language,
|
|
4379
4398
|
default: defaultMode,
|
|
4380
|
-
accentColor
|
|
4399
|
+
accentColor: accentColorProp,
|
|
4381
4400
|
renderPreview,
|
|
4382
4401
|
initialHeight
|
|
4383
4402
|
}) {
|
|
4403
|
+
const contextAccent = useAccentColor();
|
|
4404
|
+
const accentColor = accentColorProp ?? contextAccent ?? "blue";
|
|
4384
4405
|
const [selectedFilePath, setSelectedFilePath] = useState14(null);
|
|
4385
4406
|
const [fileContent, setFileContent] = useState14(null);
|
|
4386
4407
|
const [fetchedFilePath, setFetchedFilePath] = useState14(null);
|
|
@@ -4550,7 +4571,7 @@ function FileStructureSection({
|
|
|
4550
4571
|
{
|
|
4551
4572
|
collapsed: allCollapsed,
|
|
4552
4573
|
onToggle: () => setExpandedPaths(allCollapsed ? new Set(allDirPaths) : /* @__PURE__ */ new Set()),
|
|
4553
|
-
accentColor
|
|
4574
|
+
accentColor: "neutral"
|
|
4554
4575
|
}
|
|
4555
4576
|
)
|
|
4556
4577
|
] }),
|
|
@@ -4661,7 +4682,7 @@ function CollapsibleTextSection({ children, header }) {
|
|
|
4661
4682
|
ref: contentRef,
|
|
4662
4683
|
className: MARKDOWN_CLASSES,
|
|
4663
4684
|
style: showCollapsed ? { maxHeight: COLLAPSED_MAX_HEIGHT, overflow: "hidden" } : void 0,
|
|
4664
|
-
children: /* @__PURE__ */ jsx34("
|
|
4685
|
+
children: /* @__PURE__ */ jsx34("div", { children })
|
|
4665
4686
|
}
|
|
4666
4687
|
),
|
|
4667
4688
|
showCollapsed && /* @__PURE__ */ jsx34("div", { className: "absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-neutral-960 to-transparent pointer-events-none" })
|
|
@@ -4672,10 +4693,7 @@ function CompatibleWithSection({ agents }) {
|
|
|
4672
4693
|
if (agents.length === 0) return null;
|
|
4673
4694
|
return /* @__PURE__ */ jsxs26("div", { children: [
|
|
4674
4695
|
/* @__PURE__ */ jsx34("h3", { className: "text-sm font-medium text-neutral-500 uppercase tracking-wider mb-3", children: "Compatible with" }),
|
|
4675
|
-
/* @__PURE__ */ jsx34("div", { className: "flex items-start gap-3", children: agents.map((agent) => /* @__PURE__ */
|
|
4676
|
-
/* @__PURE__ */ jsx34(CodingAgentIcon, { agent, size: 18 }),
|
|
4677
|
-
/* @__PURE__ */ jsx34("span", { className: "text-xs text-neutral-400", children: CODING_AGENT_NAMES[agent] ?? agent })
|
|
4678
|
-
] }, agent)) })
|
|
4696
|
+
/* @__PURE__ */ jsx34("div", { className: "flex items-start gap-3", children: agents.map((agent) => /* @__PURE__ */ jsx34(Tooltip, { content: { description: CODING_AGENT_NAMES[agent] ?? agent }, position: "top", children: /* @__PURE__ */ jsx34(CodingAgentIcon, { agent, size: 24 }) }, agent)) })
|
|
4679
4697
|
] });
|
|
4680
4698
|
}
|
|
4681
4699
|
function RegistryDetail({
|
|
@@ -4702,7 +4720,7 @@ function RegistryDetail({
|
|
|
4702
4720
|
}) {
|
|
4703
4721
|
const contextAccent = useAccentColor();
|
|
4704
4722
|
const resolvedAccent = accentColor ?? contextAccent;
|
|
4705
|
-
const content = /* @__PURE__ */ jsx34("div", { className: "h-full overflow-y-auto
|
|
4723
|
+
const content = /* @__PURE__ */ jsx34("div", { className: "h-full overflow-y-auto", children: /* @__PURE__ */ jsxs26("div", { className: `${maxWidth} mx-auto px-4 py-6 space-y-8`, children: [
|
|
4706
4724
|
aboveHeader,
|
|
4707
4725
|
/* @__PURE__ */ jsxs26("div", { className: "flex items-start justify-between gap-4", children: [
|
|
4708
4726
|
/* @__PURE__ */ jsxs26("div", { className: "min-w-0", children: [
|
|
@@ -9886,12 +9904,12 @@ function Breadcrumb({
|
|
|
9886
9904
|
const isBox = variant === "box";
|
|
9887
9905
|
return /* @__PURE__ */ jsx56(AccentColorProvider, { value: effectiveColor, children: /* @__PURE__ */ jsx56("nav", { className: cn("flex items-center min-w-0", className), children: /* @__PURE__ */ jsx56("div", { className: cn(
|
|
9888
9906
|
"flex items-center gap-1",
|
|
9889
|
-
isBox && [s.px, s.py, "bg-neutral-960/50
|
|
9907
|
+
isBox && [s.px, s.py, "bg-neutral-960/50 rounded-lg"]
|
|
9890
9908
|
), children: segments.map((segment, index) => {
|
|
9891
9909
|
const isLast = index === segments.length - 1;
|
|
9892
9910
|
const isClickable = !isLast && !!segment.onClick;
|
|
9893
9911
|
const colors = segment.color && ACCENT_NAV[segment.color] ? ACCENT_NAV[segment.color] : null;
|
|
9894
|
-
const
|
|
9912
|
+
const isFirst = !isBox && index === 0;
|
|
9895
9913
|
return /* @__PURE__ */ jsxs47("div", { className: "flex items-center gap-1 min-w-0", children: [
|
|
9896
9914
|
index > 0 && /* @__PURE__ */ jsx56(Separator, { type: separator, size }),
|
|
9897
9915
|
isClickable ? /* @__PURE__ */ jsxs47(
|
|
@@ -9901,7 +9919,7 @@ function Breadcrumb({
|
|
|
9901
9919
|
onClick: segment.onClick,
|
|
9902
9920
|
className: cn(
|
|
9903
9921
|
"flex items-center gap-1.5 pr-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0",
|
|
9904
|
-
|
|
9922
|
+
isFirst ? "pl-0" : "pl-2",
|
|
9905
9923
|
s.text,
|
|
9906
9924
|
"font-medium hover:text-white",
|
|
9907
9925
|
colors ? [colors.text, `hover:${colors.bg}`] : ["text-neutral-300", "hover:bg-neutral-700/50"]
|
|
@@ -9916,9 +9934,9 @@ function Breadcrumb({
|
|
|
9916
9934
|
{
|
|
9917
9935
|
className: cn(
|
|
9918
9936
|
"flex items-center gap-1.5 pr-2 py-0.5 rounded-md min-w-0",
|
|
9919
|
-
|
|
9937
|
+
isFirst ? "pl-0" : "pl-2",
|
|
9920
9938
|
s.text,
|
|
9921
|
-
isLast ?
|
|
9939
|
+
isLast ? "font-medium text-white" : ["font-medium", colors ? colors.text : "text-neutral-300"]
|
|
9922
9940
|
),
|
|
9923
9941
|
children: [
|
|
9924
9942
|
segment.icon && /* @__PURE__ */ jsx56(SegmentIcon, { icon: segment.icon, color: segment.color, size }),
|
|
@@ -9998,10 +10016,12 @@ function NavigationBar({
|
|
|
9998
10016
|
onForward,
|
|
9999
10017
|
showHistory = false,
|
|
10000
10018
|
historyEntries,
|
|
10019
|
+
currentHistoryIndex,
|
|
10001
10020
|
onHistorySelect,
|
|
10002
10021
|
leadingAction,
|
|
10003
10022
|
separator = "chevron",
|
|
10004
10023
|
size = "sm",
|
|
10024
|
+
layout = "bar",
|
|
10005
10025
|
accentColor,
|
|
10006
10026
|
className
|
|
10007
10027
|
}) {
|
|
@@ -10016,6 +10036,109 @@ function NavigationBar({
|
|
|
10016
10036
|
const closeHistory = useCallback20(() => setHistoryOpen(false), []);
|
|
10017
10037
|
useClickOutside(historyRef, historyOpen, closeHistory);
|
|
10018
10038
|
const hasHistoryEntries = historyEntries && historyEntries.length > 0;
|
|
10039
|
+
const renderSegments = () => segments.map((segment, index) => {
|
|
10040
|
+
const isLast = index === segments.length - 1;
|
|
10041
|
+
const isClickable = !isLast && !!segment.onClick;
|
|
10042
|
+
const colors = segment.color && ACCENT_NAV[segment.color] ? ACCENT_NAV[segment.color] : null;
|
|
10043
|
+
return /* @__PURE__ */ jsxs48("div", { className: "flex items-center gap-1 min-w-0", children: [
|
|
10044
|
+
index > 0 && /* @__PURE__ */ jsx57(SegmentSeparator, { type: separator, size }),
|
|
10045
|
+
isClickable ? /* @__PURE__ */ jsxs48(
|
|
10046
|
+
"button",
|
|
10047
|
+
{
|
|
10048
|
+
type: "button",
|
|
10049
|
+
onClick: segment.onClick,
|
|
10050
|
+
className: cn(
|
|
10051
|
+
"flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0",
|
|
10052
|
+
s.text,
|
|
10053
|
+
"font-medium hover:text-white",
|
|
10054
|
+
colors ? [colors.text, `hover:${colors.bg}`] : ["text-neutral-300", "hover:bg-neutral-700/50"]
|
|
10055
|
+
),
|
|
10056
|
+
children: [
|
|
10057
|
+
segment.icon && /* @__PURE__ */ jsx57(SegmentIcon2, { icon: segment.icon, color: segment.color, size }),
|
|
10058
|
+
/* @__PURE__ */ jsx57("span", { className: "truncate max-w-[200px]", children: segment.label })
|
|
10059
|
+
]
|
|
10060
|
+
}
|
|
10061
|
+
) : /* @__PURE__ */ jsxs48(
|
|
10062
|
+
"div",
|
|
10063
|
+
{
|
|
10064
|
+
className: cn(
|
|
10065
|
+
"flex items-center gap-1.5 px-2 py-0.5 rounded-md min-w-0",
|
|
10066
|
+
s.text,
|
|
10067
|
+
isLast ? ["font-medium bg-neutral-700/50", colors ? colors.text : "text-white"] : ["font-medium", colors ? colors.text : "text-neutral-300"]
|
|
10068
|
+
),
|
|
10069
|
+
children: [
|
|
10070
|
+
segment.icon && /* @__PURE__ */ jsx57(SegmentIcon2, { icon: segment.icon, color: segment.color, size }),
|
|
10071
|
+
/* @__PURE__ */ jsx57("span", { className: "truncate max-w-[200px]", children: segment.label })
|
|
10072
|
+
]
|
|
10073
|
+
}
|
|
10074
|
+
)
|
|
10075
|
+
] }, segment.id);
|
|
10076
|
+
});
|
|
10077
|
+
const renderHistoryDropdown = () => {
|
|
10078
|
+
if (!historyOpen || !hasHistoryEntries || !historyEntries) return null;
|
|
10079
|
+
const entries = layout === "header" ? [...historyEntries].reverse() : historyEntries;
|
|
10080
|
+
return /* @__PURE__ */ jsxs48("div", { className: "absolute left-0 top-full mt-1 w-max min-w-[200px] max-w-[420px] bg-black/80 backdrop-blur-sm border border-neutral-700 rounded-lg shadow-lg z-50", children: [
|
|
10081
|
+
/* @__PURE__ */ jsx57("div", { className: "px-3 py-1.5 border-b border-neutral-700/50", children: /* @__PURE__ */ jsxs48("p", { className: "text-sm font-medium text-neutral-500", children: [
|
|
10082
|
+
"History",
|
|
10083
|
+
layout === "header" && ` (${historyEntries.length})`
|
|
10084
|
+
] }) }),
|
|
10085
|
+
/* @__PURE__ */ jsx57("div", { className: "max-h-[300px] overflow-y-auto py-1", children: entries.map((entry, entryIdx) => {
|
|
10086
|
+
const actualIdx = layout === "header" ? historyEntries.length - 1 - entryIdx : entryIdx;
|
|
10087
|
+
const isCurrent = currentHistoryIndex !== void 0 && actualIdx === currentHistoryIndex;
|
|
10088
|
+
return /* @__PURE__ */ jsxs48(
|
|
10089
|
+
"button",
|
|
10090
|
+
{
|
|
10091
|
+
type: "button",
|
|
10092
|
+
onClick: () => {
|
|
10093
|
+
onHistorySelect?.(actualIdx);
|
|
10094
|
+
setHistoryOpen(false);
|
|
10095
|
+
},
|
|
10096
|
+
className: cn(
|
|
10097
|
+
"w-full px-3 py-1.5 flex items-center gap-1 text-left transition-colors cursor-pointer",
|
|
10098
|
+
isCurrent ? "bg-green-500/10" : "hover:bg-neutral-800"
|
|
10099
|
+
),
|
|
10100
|
+
children: [
|
|
10101
|
+
entry.map((seg, segIdx) => /* @__PURE__ */ jsxs48("span", { className: "flex items-center gap-1 min-w-0", children: [
|
|
10102
|
+
segIdx > 0 && /* @__PURE__ */ jsx57(ChevronRight10, { className: "w-2.5 h-2.5 text-neutral-600 flex-shrink-0" }),
|
|
10103
|
+
seg.icon && /* @__PURE__ */ jsx57(SegmentIcon2, { icon: seg.icon, color: seg.color, size: "xs" }),
|
|
10104
|
+
/* @__PURE__ */ jsx57("span", { className: cn(
|
|
10105
|
+
"text-sm truncate",
|
|
10106
|
+
seg.color && ACCENT_NAV[seg.color] ? ACCENT_NAV[seg.color].text : "text-neutral-300"
|
|
10107
|
+
), children: seg.label })
|
|
10108
|
+
] }, seg.id)),
|
|
10109
|
+
isCurrent && /* @__PURE__ */ jsx57("span", { className: "ml-auto pl-4 text-xs text-green-400 flex-shrink-0", children: "Current" })
|
|
10110
|
+
]
|
|
10111
|
+
},
|
|
10112
|
+
actualIdx
|
|
10113
|
+
);
|
|
10114
|
+
}) })
|
|
10115
|
+
] });
|
|
10116
|
+
};
|
|
10117
|
+
if (layout === "header") {
|
|
10118
|
+
return /* @__PURE__ */ jsxs48("nav", { className: cn("relative flex items-center h-full", className), children: [
|
|
10119
|
+
/* @__PURE__ */ jsxs48("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
10120
|
+
hasNav && /* @__PURE__ */ jsxs48(Fragment12, { children: [
|
|
10121
|
+
/* @__PURE__ */ jsx57(NavButton, { icon: ChevronLeft2, onClick: onBack, disabled: !canGoBack, size, colorStyle }),
|
|
10122
|
+
/* @__PURE__ */ jsx57(NavButton, { icon: ChevronRight10, onClick: onForward, disabled: !canGoForward, size, colorStyle })
|
|
10123
|
+
] }),
|
|
10124
|
+
showHistory && /* @__PURE__ */ jsxs48("div", { className: "relative", ref: historyRef, children: [
|
|
10125
|
+
/* @__PURE__ */ jsx57(
|
|
10126
|
+
NavButton,
|
|
10127
|
+
{
|
|
10128
|
+
icon: History3,
|
|
10129
|
+
onClick: () => setHistoryOpen((o) => !o),
|
|
10130
|
+
disabled: !hasHistoryEntries,
|
|
10131
|
+
size,
|
|
10132
|
+
active: historyOpen,
|
|
10133
|
+
colorStyle
|
|
10134
|
+
}
|
|
10135
|
+
),
|
|
10136
|
+
renderHistoryDropdown()
|
|
10137
|
+
] })
|
|
10138
|
+
] }),
|
|
10139
|
+
/* @__PURE__ */ jsx57("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsx57("div", { className: cn("pointer-events-auto flex items-center gap-1", s.px, s.py, "bg-neutral-960/50 rounded-lg"), children: renderSegments() }) })
|
|
10140
|
+
] });
|
|
10141
|
+
}
|
|
10019
10142
|
return /* @__PURE__ */ jsx57("nav", { className: cn("flex items-center", className), children: /* @__PURE__ */ jsxs48("div", { className: cn("flex items-center gap-1", s.px, s.py, "bg-neutral-960/50 border rounded-lg", colorStyle.border), children: [
|
|
10020
10143
|
leadingAction && LeadIcon && /* @__PURE__ */ jsxs48(Fragment12, { children: [
|
|
10021
10144
|
/* @__PURE__ */ jsx57(NavButton, { icon: LeadIcon, onClick: leadingAction.onClick, size, colorStyle }),
|
|
@@ -10041,71 +10164,11 @@ function NavigationBar({
|
|
|
10041
10164
|
colorStyle
|
|
10042
10165
|
}
|
|
10043
10166
|
),
|
|
10044
|
-
|
|
10045
|
-
/* @__PURE__ */ jsx57("div", { className: "px-3 py-1.5 border-b border-neutral-700/50", children: /* @__PURE__ */ jsx57("p", { className: "text-sm font-medium text-neutral-500", children: "History" }) }),
|
|
10046
|
-
/* @__PURE__ */ jsx57("div", { className: "max-h-[300px] overflow-y-auto py-1", children: historyEntries.map((entry, i) => /* @__PURE__ */ jsx57(
|
|
10047
|
-
"button",
|
|
10048
|
-
{
|
|
10049
|
-
type: "button",
|
|
10050
|
-
onClick: () => {
|
|
10051
|
-
onHistorySelect?.(i);
|
|
10052
|
-
setHistoryOpen(false);
|
|
10053
|
-
},
|
|
10054
|
-
className: "w-full px-3 py-1.5 flex items-center gap-1 text-left hover:bg-neutral-960 transition-colors cursor-pointer",
|
|
10055
|
-
children: entry.map((seg, segIdx) => /* @__PURE__ */ jsxs48("span", { className: "flex items-center gap-1 min-w-0", children: [
|
|
10056
|
-
segIdx > 0 && /* @__PURE__ */ jsx57(ChevronRight10, { className: "w-2.5 h-2.5 text-neutral-600 flex-shrink-0" }),
|
|
10057
|
-
seg.icon && /* @__PURE__ */ jsx57(SegmentIcon2, { icon: seg.icon, color: seg.color, size: "xs" }),
|
|
10058
|
-
/* @__PURE__ */ jsx57("span", { className: cn(
|
|
10059
|
-
"text-sm truncate",
|
|
10060
|
-
seg.color && ACCENT_NAV[seg.color] ? ACCENT_NAV[seg.color].text : "text-neutral-300"
|
|
10061
|
-
), children: seg.label })
|
|
10062
|
-
] }, seg.id))
|
|
10063
|
-
},
|
|
10064
|
-
i
|
|
10065
|
-
)) }),
|
|
10066
|
-
/* @__PURE__ */ jsx57("div", { className: "px-3 py-1.5 border-t border-neutral-700/50", children: /* @__PURE__ */ jsx57("p", { className: "text-sm text-neutral-600", children: "Click to navigate" }) })
|
|
10067
|
-
] })
|
|
10167
|
+
renderHistoryDropdown()
|
|
10068
10168
|
] }),
|
|
10069
10169
|
/* @__PURE__ */ jsx57(Divider, { size })
|
|
10070
10170
|
] }),
|
|
10071
|
-
|
|
10072
|
-
const isLast = index === segments.length - 1;
|
|
10073
|
-
const isClickable = !isLast && !!segment.onClick;
|
|
10074
|
-
const colors = segment.color && ACCENT_NAV[segment.color] ? ACCENT_NAV[segment.color] : null;
|
|
10075
|
-
return /* @__PURE__ */ jsxs48("div", { className: "flex items-center gap-1 min-w-0", children: [
|
|
10076
|
-
index > 0 && /* @__PURE__ */ jsx57(SegmentSeparator, { type: separator, size }),
|
|
10077
|
-
isClickable ? /* @__PURE__ */ jsxs48(
|
|
10078
|
-
"button",
|
|
10079
|
-
{
|
|
10080
|
-
type: "button",
|
|
10081
|
-
onClick: segment.onClick,
|
|
10082
|
-
className: cn(
|
|
10083
|
-
"flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0",
|
|
10084
|
-
s.text,
|
|
10085
|
-
"font-medium hover:text-white",
|
|
10086
|
-
colors ? [colors.text, `hover:${colors.bg}`] : ["text-neutral-300", "hover:bg-neutral-700/50"]
|
|
10087
|
-
),
|
|
10088
|
-
children: [
|
|
10089
|
-
segment.icon && /* @__PURE__ */ jsx57(SegmentIcon2, { icon: segment.icon, color: segment.color, size }),
|
|
10090
|
-
/* @__PURE__ */ jsx57("span", { className: "truncate max-w-[200px]", children: segment.label })
|
|
10091
|
-
]
|
|
10092
|
-
}
|
|
10093
|
-
) : /* @__PURE__ */ jsxs48(
|
|
10094
|
-
"div",
|
|
10095
|
-
{
|
|
10096
|
-
className: cn(
|
|
10097
|
-
"flex items-center gap-1.5 px-2 py-0.5 rounded-md min-w-0",
|
|
10098
|
-
s.text,
|
|
10099
|
-
isLast ? ["font-medium bg-neutral-700/50", colors ? colors.text : "text-white"] : ["font-medium", colors ? colors.text : "text-neutral-300"]
|
|
10100
|
-
),
|
|
10101
|
-
children: [
|
|
10102
|
-
segment.icon && /* @__PURE__ */ jsx57(SegmentIcon2, { icon: segment.icon, color: segment.color, size }),
|
|
10103
|
-
/* @__PURE__ */ jsx57("span", { className: "truncate max-w-[200px]", children: segment.label })
|
|
10104
|
-
]
|
|
10105
|
-
}
|
|
10106
|
-
)
|
|
10107
|
-
] }, segment.id);
|
|
10108
|
-
})
|
|
10171
|
+
renderSegments()
|
|
10109
10172
|
] }) });
|
|
10110
10173
|
}
|
|
10111
10174
|
|