@toolr/ui-design 0.1.2 → 0.1.4
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/README.md +0 -7
- package/components/content/info-panel-primitives.tsx +3 -3
- package/components/lib/ai-tools.tsx +1 -1
- package/components/lib/form-colors.ts +3 -0
- package/components/lib/theme-engine.ts +10 -0
- package/components/sections/captured-issues/captured-issues-panel.tsx +1 -1
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +2 -2
- package/components/sections/golden-snapshots/snapshot-manager.tsx +3 -3
- package/components/sections/golden-snapshots/status-overview.tsx +4 -4
- package/components/sections/golden-snapshots/version-manager.tsx +3 -3
- package/components/sections/report-bug/screenshot-uploader.tsx +2 -2
- package/components/sections/snapshot-browser/snapshot-tree.tsx +1 -1
- package/components/sections/snippets-editor/snippets-editor.tsx +5 -5
- package/components/ui/action-dialog.tsx +1 -1
- package/components/ui/badge.tsx +8 -6
- package/components/ui/breadcrumb.tsx +1 -1
- package/components/ui/confirm-badge.tsx +4 -2
- package/components/ui/file-structure-section.tsx +139 -74
- package/components/ui/file-tree.tsx +2 -2
- package/components/ui/files-panel.tsx +5 -5
- package/components/ui/filter-dropdown.tsx +3 -3
- package/components/ui/frontmatter-form-header.tsx +2 -2
- package/components/ui/icon-button.tsx +6 -2
- package/components/ui/input.tsx +1 -1
- package/components/ui/label.tsx +8 -5
- package/components/ui/modal.tsx +1 -1
- package/components/ui/nav-card.tsx +1 -1
- package/components/ui/navigation-bar.tsx +1 -1
- package/components/ui/number-input.tsx +1 -1
- package/components/ui/registry-card.tsx +7 -7
- package/components/ui/registry-detail.tsx +5 -2
- package/components/ui/resizable-textarea.tsx +2 -2
- package/components/ui/segmented-toggle.tsx +36 -18
- package/components/ui/select.tsx +3 -3
- package/components/ui/selection-grid.tsx +7 -19
- package/components/ui/settings-card.tsx +27 -0
- package/components/ui/settings-info-box.tsx +80 -0
- package/components/ui/settings-section-title.tsx +24 -0
- package/components/ui/snapshot-card.tsx +2 -2
- package/components/ui/snippets-panel.tsx +9 -9
- package/components/ui/sort-dropdown.tsx +1 -1
- package/components/ui/tab-bar.tsx +1 -1
- package/components/ui/tooltip.tsx +1 -1
- package/dist/content.js +3 -3
- package/dist/index.d.ts +93 -46
- package/dist/index.js +1452 -1236
- package/dist/tokens/primitives.css +10 -0
- package/dist/tokens/semantic.css +3 -0
- package/index.ts +4 -1
- package/package.json +1 -7
- package/tokens/primitives.css +10 -0
- package/tokens/semantic.css +3 -0
- package/dist/preset.d.ts +0 -24
- package/dist/preset.js +0 -17
- package/tailwind-preset.ts +0 -22
|
@@ -1,21 +1,48 @@
|
|
|
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
|
-
import { CollapseButton } from './icon-button.tsx'
|
|
3
|
+
import { CollapseButton, type IconButtonColor } from './icon-button.tsx'
|
|
4
4
|
import { SegmentedToggle } from './segmented-toggle.tsx'
|
|
5
5
|
import { FileTree, collectDirPaths, type FileTreeNode } from './file-tree.tsx'
|
|
6
6
|
|
|
7
7
|
export type PreviewMode = 'format' | 'language' | 'plain'
|
|
8
|
-
export type AccentColor = 'blue' | '
|
|
8
|
+
export type AccentColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
|
|
9
9
|
|
|
10
10
|
const ACCENT_ICON: Record<AccentColor, string> = {
|
|
11
|
-
blue: 'text-blue-400',
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
blue: 'text-blue-400', green: 'text-green-400', red: 'text-red-400',
|
|
12
|
+
orange: 'text-orange-400', cyan: 'text-cyan-400', yellow: 'text-yellow-400',
|
|
13
|
+
purple: 'text-purple-400', indigo: 'text-indigo-400', emerald: 'text-emerald-400',
|
|
14
|
+
amber: 'text-amber-400', violet: 'text-violet-400', neutral: 'text-neutral-400',
|
|
15
|
+
sky: 'text-sky-400', pink: 'text-pink-400', teal: 'text-teal-400',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ACCENT_BORDER: Record<AccentColor, string> = {
|
|
19
|
+
blue: 'border-blue-500/25', green: 'border-green-500/25', red: 'border-red-500/25',
|
|
20
|
+
orange: 'border-orange-500/25', cyan: 'border-cyan-500/25', yellow: 'border-yellow-500/25',
|
|
21
|
+
purple: 'border-purple-500/25', indigo: 'border-indigo-500/25', emerald: 'border-emerald-500/25',
|
|
22
|
+
amber: 'border-amber-500/25', violet: 'border-violet-500/25', neutral: 'border-neutral-500/25',
|
|
23
|
+
sky: 'border-sky-500/25', pink: 'border-pink-500/25', teal: 'border-teal-500/25',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ACCENT_BUTTON: Record<AccentColor, IconButtonColor> = {
|
|
27
|
+
blue: 'blue', green: 'green', red: 'red',
|
|
28
|
+
orange: 'orange', cyan: 'cyan', yellow: 'yellow',
|
|
29
|
+
purple: 'purple', indigo: 'indigo', emerald: 'emerald',
|
|
30
|
+
amber: 'amber', violet: 'violet', neutral: 'neutral',
|
|
31
|
+
sky: 'sky', pink: 'pink', teal: 'teal',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ACCENT_HANDLE: Record<AccentColor, string> = {
|
|
35
|
+
blue: 'bg-blue-500/30 group-hover:bg-blue-400/50', green: 'bg-green-500/30 group-hover:bg-green-400/50', red: 'bg-red-500/30 group-hover:bg-red-400/50',
|
|
36
|
+
orange: 'bg-orange-500/30 group-hover:bg-orange-400/50', cyan: 'bg-cyan-500/30 group-hover:bg-cyan-400/50', yellow: 'bg-yellow-500/30 group-hover:bg-yellow-400/50',
|
|
37
|
+
purple: 'bg-purple-500/30 group-hover:bg-purple-400/50', indigo: 'bg-indigo-500/30 group-hover:bg-indigo-400/50', emerald: 'bg-emerald-500/30 group-hover:bg-emerald-400/50',
|
|
38
|
+
amber: 'bg-amber-500/30 group-hover:bg-amber-400/50', violet: 'bg-violet-500/30 group-hover:bg-violet-400/50', neutral: 'bg-neutral-500/30 group-hover:bg-neutral-400/50',
|
|
39
|
+
sky: 'bg-sky-500/30 group-hover:bg-sky-400/50', pink: 'bg-pink-500/30 group-hover:bg-pink-400/50', teal: 'bg-teal-500/30 group-hover:bg-teal-400/50',
|
|
14
40
|
}
|
|
15
41
|
|
|
16
42
|
export interface FileStructureSectionProps {
|
|
17
43
|
files: FileTreeNode[] | null
|
|
18
44
|
rootName: string
|
|
45
|
+
variant?: 'split' | 'list'
|
|
19
46
|
isLoading?: boolean
|
|
20
47
|
error?: string | null
|
|
21
48
|
onFetchContent: (relativePath: string) => Promise<string>
|
|
@@ -33,6 +60,8 @@ export interface FileStructureSectionProps {
|
|
|
33
60
|
accentColor?: AccentColor
|
|
34
61
|
/** Custom renderer called when mode is 'language'. Receives the resolved language as third arg. */
|
|
35
62
|
renderPreview?: (content: string, filePath: string, language: string) => ReactNode
|
|
63
|
+
/** Initial height in pixels. Defaults to 400 for 'list', 250 for 'split'. */
|
|
64
|
+
initialHeight?: number
|
|
36
65
|
}
|
|
37
66
|
|
|
38
67
|
export function getLanguageFromPath(filePath: string): string {
|
|
@@ -60,7 +89,7 @@ function renderMarkdownContent(content: string) {
|
|
|
60
89
|
while (i < lines.length && lines[i] !== '---') { fmLines.push(lines[i]); i++ }
|
|
61
90
|
i++ // skip closing ---
|
|
62
91
|
nodes.push(
|
|
63
|
-
<div key="fm" className="mb-3 font-mono text-
|
|
92
|
+
<div key="fm" className="mb-3 font-mono text-xss text-neutral-500 border-l-2 border-neutral-700 pl-2 py-0.5">
|
|
64
93
|
<div className="text-neutral-600">---</div>
|
|
65
94
|
{fmLines.map((l, j) => <div key={j}>{l}</div>)}
|
|
66
95
|
<div className="text-neutral-600">---</div>
|
|
@@ -75,12 +104,12 @@ function renderMarkdownContent(content: string) {
|
|
|
75
104
|
i++
|
|
76
105
|
while (i < lines.length && !lines[i].startsWith('```')) { codeLines.push(lines[i]); i++ }
|
|
77
106
|
nodes.push(
|
|
78
|
-
<pre key={i} className="mb-2 p-2 bg-
|
|
107
|
+
<pre key={i} className="mb-2 p-2 bg-[var(--background)]/30 rounded text-xss font-mono text-neutral-300 overflow-x-auto">
|
|
79
108
|
{codeLines.join('\n')}
|
|
80
109
|
</pre>
|
|
81
110
|
)
|
|
82
111
|
} else if (line.startsWith('### ')) {
|
|
83
|
-
nodes.push(<h3 key={i} className="text-
|
|
112
|
+
nodes.push(<h3 key={i} className="text-xss font-semibold text-neutral-300 mt-2 mb-0.5">{line.slice(4)}</h3>)
|
|
84
113
|
} else if (line.startsWith('## ')) {
|
|
85
114
|
nodes.push(<h2 key={i} className="text-xs font-semibold text-neutral-200 mt-2.5 mb-1">{line.slice(3)}</h2>)
|
|
86
115
|
} else if (line.startsWith('# ')) {
|
|
@@ -88,7 +117,7 @@ function renderMarkdownContent(content: string) {
|
|
|
88
117
|
} else if (line === '' || line === '\r') {
|
|
89
118
|
nodes.push(<div key={i} className="h-1.5" />)
|
|
90
119
|
} else {
|
|
91
|
-
nodes.push(<p key={i} className="text-
|
|
120
|
+
nodes.push(<p key={i} className="text-xss text-neutral-400 leading-relaxed">{line}</p>)
|
|
92
121
|
}
|
|
93
122
|
i++
|
|
94
123
|
}
|
|
@@ -103,6 +132,7 @@ const PLAIN_OPTION = { value: 'plain' as const, icon: <Type className="w-3 h-3"
|
|
|
103
132
|
export function FileStructureSection({
|
|
104
133
|
files,
|
|
105
134
|
rootName,
|
|
135
|
+
variant = 'split',
|
|
106
136
|
isLoading,
|
|
107
137
|
error,
|
|
108
138
|
onFetchContent,
|
|
@@ -111,6 +141,7 @@ export function FileStructureSection({
|
|
|
111
141
|
default: defaultMode,
|
|
112
142
|
accentColor = 'blue',
|
|
113
143
|
renderPreview,
|
|
144
|
+
initialHeight,
|
|
114
145
|
}: FileStructureSectionProps) {
|
|
115
146
|
const [selectedFilePath, setSelectedFilePath] = useState<string | null>(null)
|
|
116
147
|
const [fileContent, setFileContent] = useState<string | null>(null)
|
|
@@ -168,7 +199,7 @@ export function FileStructureSection({
|
|
|
168
199
|
const startHeight = useRef(0)
|
|
169
200
|
|
|
170
201
|
useEffect(() => {
|
|
171
|
-
if (treeHeight !== null || !sectionRef.current) return
|
|
202
|
+
if (variant === 'list' || treeHeight !== null || !sectionRef.current) return
|
|
172
203
|
const el = sectionRef.current
|
|
173
204
|
const scrollParent = el.closest('.overflow-y-auto') as HTMLElement | null
|
|
174
205
|
if (!scrollParent) return
|
|
@@ -185,9 +216,9 @@ export function FileStructureSection({
|
|
|
185
216
|
})
|
|
186
217
|
observer.observe(scrollParent)
|
|
187
218
|
return () => observer.disconnect()
|
|
188
|
-
}, [treeHeight, files])
|
|
219
|
+
}, [variant, treeHeight, files])
|
|
189
220
|
|
|
190
|
-
const effectiveHeight = treeHeight ?? 250
|
|
221
|
+
const effectiveHeight = treeHeight ?? initialHeight ?? (variant === 'list' ? 400 : 250)
|
|
191
222
|
|
|
192
223
|
const handleResizeStart = useCallback((e: React.MouseEvent) => {
|
|
193
224
|
e.preventDefault()
|
|
@@ -224,7 +255,9 @@ export function FileStructureSection({
|
|
|
224
255
|
return findFirst(files, rootName)
|
|
225
256
|
}, [files, rootName])
|
|
226
257
|
|
|
227
|
-
const effectiveFilePath =
|
|
258
|
+
const effectiveFilePath = variant === 'list'
|
|
259
|
+
? selectedFilePath
|
|
260
|
+
: (selectedFilePath ?? firstFilePath)
|
|
228
261
|
const fileIsLoading = effectiveFilePath != null && effectiveFilePath !== fetchedFilePath
|
|
229
262
|
|
|
230
263
|
useEffect(() => {
|
|
@@ -255,10 +288,16 @@ export function FileStructureSection({
|
|
|
255
288
|
}, [effectiveFilePath, rootName, onFetchContent])
|
|
256
289
|
|
|
257
290
|
const handleSelectFile = useCallback((filePath: string) => {
|
|
291
|
+
if (variant === 'list' && selectedFilePath === filePath) {
|
|
292
|
+
setSelectedFilePath(null)
|
|
293
|
+
setFileContent(null)
|
|
294
|
+
setFileError(null)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
258
297
|
setSelectedFilePath(filePath)
|
|
259
298
|
setFileContent(null)
|
|
260
299
|
setFileError(null)
|
|
261
|
-
}, [])
|
|
300
|
+
}, [variant, selectedFilePath])
|
|
262
301
|
|
|
263
302
|
if (isLoading) {
|
|
264
303
|
return (
|
|
@@ -303,71 +342,97 @@ export function FileStructureSection({
|
|
|
303
342
|
)
|
|
304
343
|
}
|
|
305
344
|
|
|
306
|
-
|
|
307
|
-
<div
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
<
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
345
|
+
const treePanel = (
|
|
346
|
+
<div className={`flex flex-col bg-neutral-900 border ${ACCENT_BORDER[accentColor]} rounded-lg overflow-hidden ${variant === 'split' && effectiveFilePath ? 'w-1/3 shrink-0' : 'flex-1'}`}>
|
|
347
|
+
<div className={`flex items-center px-3 py-2 border-b ${ACCENT_BORDER[accentColor]} shrink-0 gap-2 min-w-0`}>
|
|
348
|
+
<FolderTree className={`w-3.5 h-3.5 shrink-0 ${ACCENT_ICON[accentColor]}`} />
|
|
349
|
+
<span className="text-xs text-neutral-200 truncate flex-1">Files</span>
|
|
350
|
+
<CollapseButton
|
|
351
|
+
collapsed={allCollapsed}
|
|
352
|
+
onToggle={() => setExpandedPaths(allCollapsed ? new Set(allDirPaths) : new Set())}
|
|
353
|
+
color={ACCENT_BUTTON[accentColor]}
|
|
354
|
+
/>
|
|
355
|
+
</div>
|
|
356
|
+
<div className={`${variant === 'split' ? 'flex-1 overflow-y-auto' : ''} p-3`}>
|
|
357
|
+
<FileTree
|
|
358
|
+
nodes={files}
|
|
359
|
+
rootName={rootName}
|
|
360
|
+
selectedPath={effectiveFilePath}
|
|
361
|
+
onSelectFile={handleSelectFile}
|
|
362
|
+
expandedPaths={expandedPaths}
|
|
363
|
+
onTogglePath={togglePath}
|
|
364
|
+
accentColor={accentColor}
|
|
365
|
+
/>
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
const previewPanel = effectiveFilePath ? (
|
|
371
|
+
<div className={`flex-1 flex flex-col bg-neutral-900 border ${ACCENT_BORDER[accentColor]} rounded-lg overflow-hidden`}>
|
|
372
|
+
<div className={`flex items-center px-3 py-2 border-b ${ACCENT_BORDER[accentColor]} shrink-0 gap-2 min-w-0`}>
|
|
373
|
+
<FileCode className={`w-3.5 h-3.5 shrink-0 ${ACCENT_ICON[accentColor]}`} />
|
|
374
|
+
<span className="text-xs text-neutral-200 truncate flex-1">{selectedFileName}</span>
|
|
375
|
+
{showToggle && (
|
|
376
|
+
<SegmentedToggle
|
|
377
|
+
options={toggleOptions}
|
|
378
|
+
value={mode}
|
|
379
|
+
onChange={setMode}
|
|
380
|
+
accentColor={accentColor}
|
|
381
|
+
size="xss"
|
|
382
|
+
/>
|
|
383
|
+
)}
|
|
384
|
+
</div>
|
|
385
|
+
<div className="flex-1 overflow-auto">
|
|
386
|
+
{fileIsLoading ? (
|
|
387
|
+
<div className="flex items-center gap-2 text-xs text-neutral-500 p-3">
|
|
388
|
+
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
389
|
+
Loading...
|
|
330
390
|
</div>
|
|
331
|
-
|
|
391
|
+
) : fileError ? (
|
|
392
|
+
<p className="text-xs text-red-400 p-3">{fileError}</p>
|
|
393
|
+
) : fileContent !== null ? (
|
|
394
|
+
renderContent(fileContent, effectiveFilePath)
|
|
395
|
+
) : null}
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
) : null
|
|
399
|
+
|
|
400
|
+
const resizeHandle = (
|
|
401
|
+
<div
|
|
402
|
+
onMouseDown={handleResizeStart}
|
|
403
|
+
className="h-4 -mt-1.5 cursor-grab active:cursor-grabbing flex items-center justify-center group"
|
|
404
|
+
>
|
|
405
|
+
<div className={`w-10 h-1 rounded-full ${ACCENT_HANDLE[accentColor]} group-hover:w-14 group-hover:h-1.5 transition-all`} />
|
|
406
|
+
</div>
|
|
407
|
+
)
|
|
332
408
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
size="xss"
|
|
346
|
-
/>
|
|
347
|
-
)}
|
|
348
|
-
</div>
|
|
349
|
-
<div className="flex-1 overflow-auto">
|
|
350
|
-
{fileIsLoading ? (
|
|
351
|
-
<div className="flex items-center gap-2 text-xs text-neutral-500 p-3">
|
|
352
|
-
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
353
|
-
Loading...
|
|
354
|
-
</div>
|
|
355
|
-
) : fileError ? (
|
|
356
|
-
<p className="text-xs text-red-400 p-3">{fileError}</p>
|
|
357
|
-
) : fileContent !== null ? (
|
|
358
|
-
renderContent(fileContent, effectiveFilePath)
|
|
359
|
-
) : null}
|
|
409
|
+
if (variant === 'list') {
|
|
410
|
+
return (
|
|
411
|
+
<div ref={sectionRef}>
|
|
412
|
+
<h3 className="text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">File Structure</h3>
|
|
413
|
+
<div className="space-y-3">
|
|
414
|
+
{treePanel}
|
|
415
|
+
{previewPanel && (
|
|
416
|
+
<div>
|
|
417
|
+
<div className="flex flex-col" style={{ height: `${effectiveHeight}px` }}>
|
|
418
|
+
{previewPanel}
|
|
419
|
+
</div>
|
|
420
|
+
{resizeHandle}
|
|
360
421
|
</div>
|
|
361
|
-
|
|
362
|
-
|
|
422
|
+
)}
|
|
423
|
+
</div>
|
|
363
424
|
</div>
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<div ref={sectionRef}>
|
|
430
|
+
<h3 className="text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">File Structure</h3>
|
|
431
|
+
<div className="flex gap-3" style={{ height: `${effectiveHeight}px` }}>
|
|
432
|
+
{treePanel}
|
|
433
|
+
{previewPanel}
|
|
370
434
|
</div>
|
|
435
|
+
{resizeHandle}
|
|
371
436
|
</div>
|
|
372
437
|
)
|
|
373
438
|
}
|
|
@@ -126,8 +126,8 @@ function FileTreeNodeItem({ node, path, selectedPath, onSelectFile, expandedPath
|
|
|
126
126
|
const rowClass = isSelected
|
|
127
127
|
? `${base} ${selectedClass}`
|
|
128
128
|
: isDir
|
|
129
|
-
? `${base} cursor-pointer hover:text-neutral-200
|
|
130
|
-
: `${base} cursor-pointer hover:bg-neutral-700/50 hover:text-neutral-200
|
|
129
|
+
? `${base} cursor-pointer text-white hover:text-neutral-200`
|
|
130
|
+
: `${base} cursor-pointer text-white hover:bg-neutral-700/50 hover:text-neutral-200`
|
|
131
131
|
|
|
132
132
|
return (
|
|
133
133
|
<li>
|
|
@@ -144,7 +144,7 @@ function FileNode({ entry, depth, selectedPath, expandedPaths, onToggleExpand, o
|
|
|
144
144
|
/>
|
|
145
145
|
<span className="truncate">{entry.name}</span>
|
|
146
146
|
{entry.badge && (
|
|
147
|
-
<span className="ml-auto shrink-0 px-1.5 py-0.5 text-
|
|
147
|
+
<span className="ml-auto shrink-0 px-1.5 py-0.5 text-xss rounded bg-neutral-700 text-neutral-500">
|
|
148
148
|
{entry.badge}
|
|
149
149
|
</span>
|
|
150
150
|
)}
|
|
@@ -210,12 +210,12 @@ export function FilesPanel({
|
|
|
210
210
|
return (
|
|
211
211
|
<div className={cn('flex flex-col bg-neutral-800 rounded-lg overflow-hidden', className)}>
|
|
212
212
|
<div className="flex items-center justify-between px-3 py-2 border-b border-neutral-700">
|
|
213
|
-
<span className="text-
|
|
214
|
-
<span className="text-
|
|
213
|
+
<span className="text-xss font-semibold uppercase tracking-wider text-neutral-500">Files</span>
|
|
214
|
+
<span className="text-xss text-neutral-500">{fileCount} files</span>
|
|
215
215
|
</div>
|
|
216
216
|
{showSearch && (
|
|
217
217
|
<div className="px-2 py-2 border-b border-neutral-700">
|
|
218
|
-
<div className="flex items-center gap-1.5 px-2 py-1 bg-
|
|
218
|
+
<div className="flex items-center gap-1.5 px-2 py-1 bg-[var(--background)] border border-neutral-700 rounded text-xs">
|
|
219
219
|
<Search className="w-3 h-3 text-neutral-500 shrink-0" />
|
|
220
220
|
<input
|
|
221
221
|
type="text"
|
|
@@ -243,7 +243,7 @@ export function FilesPanel({
|
|
|
243
243
|
))}
|
|
244
244
|
</ul>
|
|
245
245
|
{displayedFiles.length === 0 && (
|
|
246
|
-
<p className="text-
|
|
246
|
+
<p className="text-xss text-neutral-500 text-center py-4">No files found</p>
|
|
247
247
|
)}
|
|
248
248
|
</div>
|
|
249
249
|
</div>
|
|
@@ -112,7 +112,7 @@ export function FilterDropdown({
|
|
|
112
112
|
)}
|
|
113
113
|
|
|
114
114
|
{isOpen && (
|
|
115
|
-
<div ref={menuRef} className={`absolute right-0 top-full z-50 mt-1 min-w-[140px] whitespace-nowrap bg-[var(--popover)]
|
|
115
|
+
<div ref={menuRef} className={`absolute right-0 top-full z-50 mt-1 min-w-[140px] whitespace-nowrap bg-[var(--popover)] border ${FORM_COLORS[color].border} rounded-lg shadow-xl overflow-hidden`}>
|
|
116
116
|
{showSearch && (
|
|
117
117
|
<div className={`sticky top-0 p-1.5 bg-[var(--popover)] border-b ${FORM_COLORS[color].border} z-10`}>
|
|
118
118
|
<div className="relative">
|
|
@@ -135,7 +135,7 @@ export function FilterDropdown({
|
|
|
135
135
|
onClick={() => handleSelect('all')}
|
|
136
136
|
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors cursor-pointer ${
|
|
137
137
|
highlightIdx === 0
|
|
138
|
-
?
|
|
138
|
+
? `${FORM_COLORS[color].selectedBg} text-neutral-200`
|
|
139
139
|
: !isActive ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
|
|
140
140
|
}`}
|
|
141
141
|
>
|
|
@@ -154,7 +154,7 @@ export function FilterDropdown({
|
|
|
154
154
|
onClick={() => handleSelect(opt.value)}
|
|
155
155
|
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors cursor-pointer ${
|
|
156
156
|
isHighlighted
|
|
157
|
-
?
|
|
157
|
+
? `${FORM_COLORS[color].selectedBg} text-neutral-200`
|
|
158
158
|
: isSelected ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
|
|
159
159
|
}`}
|
|
160
160
|
>
|
|
@@ -43,12 +43,12 @@ export function FrontmatterFormHeader({
|
|
|
43
43
|
Configuration
|
|
44
44
|
</span>
|
|
45
45
|
{collapsed && hasFm && (
|
|
46
|
-
<span className="text-
|
|
46
|
+
<span className="text-xss text-neutral-500 font-mono ml-2 truncate">
|
|
47
47
|
{renderSummary()}
|
|
48
48
|
</span>
|
|
49
49
|
)}
|
|
50
50
|
{collapsed && !hasFm && (
|
|
51
|
-
<span className="text-
|
|
51
|
+
<span className="text-xss text-neutral-600 ml-2">No frontmatter</span>
|
|
52
52
|
)}
|
|
53
53
|
</button>
|
|
54
54
|
|
|
@@ -159,7 +159,7 @@ export interface ActionItem {
|
|
|
159
159
|
|
|
160
160
|
export type IconButtonStatus = 'loading' | 'success' | 'warning' | 'error'
|
|
161
161
|
|
|
162
|
-
export type IconButtonColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky'
|
|
162
|
+
export type IconButtonColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
|
|
163
163
|
export type IconButtonVariant = 'filled' | 'outline'
|
|
164
164
|
|
|
165
165
|
export interface IconButtonProps {
|
|
@@ -216,6 +216,8 @@ const colorClasses = {
|
|
|
216
216
|
violet: { text: 'text-violet-400', border: 'border-violet-500/30', hover: 'hover:bg-violet-500/20 hover:border-violet-500/40 hover:text-violet-300', active: 'bg-violet-500/20 text-violet-300 border-violet-500/40' },
|
|
217
217
|
neutral: { text: 'text-neutral-400', border: 'border-neutral-500/30', hover: 'hover:bg-neutral-500/20 hover:border-neutral-500/40 hover:text-neutral-300', active: 'bg-neutral-500/20 text-neutral-300 border-neutral-500/40' },
|
|
218
218
|
sky: { text: 'text-sky-400', border: 'border-sky-500/30', hover: 'hover:bg-sky-500/20 hover:border-sky-500/40 hover:text-sky-300', active: 'bg-sky-500/20 text-sky-300 border-sky-500/40' },
|
|
219
|
+
pink: { text: 'text-pink-400', border: 'border-pink-500/30', hover: 'hover:bg-pink-500/20 hover:border-pink-500/40 hover:text-pink-300', active: 'bg-pink-500/20 text-pink-300 border-pink-500/40' },
|
|
220
|
+
teal: { text: 'text-teal-400', border: 'border-teal-500/30', hover: 'hover:bg-teal-500/20 hover:border-teal-500/40 hover:text-teal-300', active: 'bg-teal-500/20 text-teal-300 border-teal-500/40' },
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
const badgeColorClasses = {
|
|
@@ -369,14 +371,16 @@ export interface CollapseButtonProps {
|
|
|
369
371
|
collapsed: boolean
|
|
370
372
|
onToggle: () => void
|
|
371
373
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
374
|
+
color?: IconButtonColor
|
|
372
375
|
tooltipPosition?: 'bottom' | 'bottom-left' | 'left' | 'right' | 'top' | 'top-left' | 'top-right'
|
|
373
376
|
}
|
|
374
377
|
|
|
375
|
-
export function CollapseButton({ collapsed, onToggle, size = 'xss', tooltipPosition = 'bottom-left' }: CollapseButtonProps) {
|
|
378
|
+
export function CollapseButton({ collapsed, onToggle, size = 'xss', color, tooltipPosition = 'bottom-left' }: CollapseButtonProps) {
|
|
376
379
|
return (
|
|
377
380
|
<IconButton
|
|
378
381
|
icon={collapsed ? 'chevrons-up-down' : 'chevrons-down-up'}
|
|
379
382
|
onClick={onToggle}
|
|
383
|
+
color={color}
|
|
380
384
|
tooltip={{
|
|
381
385
|
title: collapsed ? 'Expand all' : 'Collapse all',
|
|
382
386
|
description: collapsed ? 'Expand all folders' : 'Collapse all folders',
|
package/components/ui/input.tsx
CHANGED
|
@@ -38,7 +38,7 @@ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const sizeClasses = {
|
|
41
|
-
xss: 'px-1 py-0.5 text-
|
|
41
|
+
xss: 'px-1 py-0.5 text-xss',
|
|
42
42
|
xs: 'px-1.5 py-0.5 text-xs',
|
|
43
43
|
sm: 'px-2 py-1 text-xs',
|
|
44
44
|
md: 'px-3 py-1.5 text-sm',
|
package/components/ui/label.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Matches the SeedrBadges border-focused style: colored border + text, no background.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
|
-
* -
|
|
8
|
+
* - 15 color variants
|
|
9
9
|
* - Required leading icon and tooltip (always cursor-help)
|
|
10
10
|
* - Optional trailing icons
|
|
11
11
|
* - Text transform with smart capitalize (handles after-dash)
|
|
@@ -30,6 +30,7 @@ export type LabelColor =
|
|
|
30
30
|
| 'teal'
|
|
31
31
|
| 'violet'
|
|
32
32
|
| 'pink'
|
|
33
|
+
| 'sky'
|
|
33
34
|
|
|
34
35
|
export interface LabelProps {
|
|
35
36
|
text: string
|
|
@@ -63,6 +64,7 @@ const colorClasses: Record<LabelColor, string> = {
|
|
|
63
64
|
teal: 'border-teal-500/50 text-teal-400',
|
|
64
65
|
violet: 'border-violet-500/50 text-violet-400',
|
|
65
66
|
pink: 'border-pink-500/50 text-pink-400',
|
|
67
|
+
sky: 'border-sky-500/50 text-sky-400',
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
const progressFillColors: Record<LabelColor, string> = {
|
|
@@ -80,13 +82,14 @@ const progressFillColors: Record<LabelColor, string> = {
|
|
|
80
82
|
teal: 'bg-teal-500/20',
|
|
81
83
|
violet: 'bg-violet-500/20',
|
|
82
84
|
pink: 'bg-pink-500/20',
|
|
85
|
+
sky: 'bg-sky-500/20',
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
const sizeConfig = {
|
|
86
|
-
xss: { height: 14, padding: 'px-1', text: 'text-
|
|
87
|
-
xs: { height: 16, padding: 'px-1.5', text: 'text-
|
|
88
|
-
sm: { height: 18, padding: 'px-1.5', text: 'text-
|
|
89
|
-
md: { height: 20, padding: 'px-1.5', text: 'text-
|
|
89
|
+
xss: { height: 14, padding: 'px-1', text: 'text-xss', iconSize: 'w-2 h-2', gap: 'gap-0.5' },
|
|
90
|
+
xs: { height: 16, padding: 'px-1.5', text: 'text-xss', iconSize: 'w-2.5 h-2.5', gap: 'gap-1' },
|
|
91
|
+
sm: { height: 18, padding: 'px-1.5', text: 'text-xss', iconSize: 'w-2.5 h-2.5', gap: 'gap-1.5' },
|
|
92
|
+
md: { height: 20, padding: 'px-1.5', text: 'text-xss', iconSize: 'w-3 h-3', gap: 'gap-1' },
|
|
90
93
|
lg: { height: 22, padding: 'px-2', text: 'text-xs', iconSize: 'w-3 h-3', gap: 'gap-1' },
|
|
91
94
|
}
|
|
92
95
|
|
package/components/ui/modal.tsx
CHANGED
|
@@ -61,7 +61,7 @@ function Modal({ isOpen, onClose, title, children, kind = 'info', size = 'md', h
|
|
|
61
61
|
|
|
62
62
|
return createPortal(
|
|
63
63
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
64
|
-
<div className="absolute inset-0 bg-
|
|
64
|
+
<div className="absolute inset-0 bg-[var(--dialog-backdrop)] backdrop-blur-sm" onClick={onClose} />
|
|
65
65
|
<div
|
|
66
66
|
ref={modalRef}
|
|
67
67
|
data-testid={testId}
|
|
@@ -51,7 +51,7 @@ export function NavCard({
|
|
|
51
51
|
disabled={disabled}
|
|
52
52
|
className={cn(
|
|
53
53
|
'relative w-full text-left rounded-lg border border-neutral-700 bg-neutral-800 p-4 transition-all duration-200 cursor-pointer',
|
|
54
|
-
!disabled && 'hover:-translate-y-0.5 hover:
|
|
54
|
+
!disabled && 'hover:-translate-y-0.5 hover:border-neutral-600 hover:bg-neutral-700',
|
|
55
55
|
disabled && 'opacity-50 cursor-not-allowed',
|
|
56
56
|
className,
|
|
57
57
|
)}
|
|
@@ -69,7 +69,7 @@ export interface NavigationBarProps {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const sizeConfig = {
|
|
72
|
-
xss: { text: 'text-
|
|
72
|
+
xss: { text: 'text-xss', segIcon: 'w-2.5 h-2.5', navIcon: 'w-2.5 h-2.5', navBtn: 'w-[18px] h-[18px] rounded-[3px]', px: 'px-1', py: 'py-0.5', sep: 'w-2 h-2', divH: 'h-3' },
|
|
73
73
|
xs: { text: 'text-xs', segIcon: 'w-3 h-3', navIcon: 'w-3 h-3', navBtn: 'w-6 h-6 rounded-[5px]', px: 'px-1.5', py: 'py-0.5', sep: 'w-2.5 h-2.5', divH: 'h-3.5' },
|
|
74
74
|
sm: { text: 'text-sm', segIcon: 'w-3.5 h-3.5', navIcon: 'w-3.5 h-3.5', navBtn: 'w-7 h-7 rounded-md', px: 'px-2', py: 'py-1', sep: 'w-3 h-3', divH: 'h-4' },
|
|
75
75
|
md: { text: 'text-base', segIcon: 'w-4 h-4', navIcon: 'w-4 h-4', navBtn: 'w-8 h-8 rounded-md', px: 'px-2.5', py: 'py-1', sep: 'w-3.5 h-3.5', divH: 'h-5' },
|
|
@@ -16,7 +16,7 @@ export interface NumberInputProps {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const SIZE_CONFIG = {
|
|
19
|
-
xss: { wrapper: 'h-[18px]', input: 'px-1 text-
|
|
19
|
+
xss: { wrapper: 'h-[18px]', input: 'px-1 text-xss', chevron: 'w-2.5 h-2.5', stepperW: 'w-4' },
|
|
20
20
|
xs: { wrapper: 'h-6', input: 'px-1.5 text-xs', chevron: 'w-2.5 h-2.5', stepperW: 'w-5' },
|
|
21
21
|
sm: { wrapper: 'h-7', input: 'px-2 text-xs', chevron: 'w-3 h-3', stepperW: 'w-5' },
|
|
22
22
|
md: { wrapper: 'h-8', input: 'px-3 text-sm', chevron: 'w-3 h-3', stepperW: 'w-6' },
|
|
@@ -412,7 +412,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
412
412
|
<Tooltip key={`pkg-${key}`} content={{ description: `${count} ${count === 1 ? label : labelPlural}` }} position="top">
|
|
413
413
|
<span className="flex items-center gap-0.5">
|
|
414
414
|
<Icon className={`w-3 h-3 ${color}`} />
|
|
415
|
-
<span className="text-
|
|
415
|
+
<span className="text-xss text-neutral-500">{count}</span>
|
|
416
416
|
</span>
|
|
417
417
|
</Tooltip>
|
|
418
418
|
)]
|
|
@@ -469,7 +469,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
469
469
|
...(props.stars != null && props.stars > 0 ? [(
|
|
470
470
|
<CardClickable key="stars" onClick={() => props.onSortBy?.('stars')}>
|
|
471
471
|
<Tooltip content={{ description: `${props.stars.toLocaleString()} stars \u00b7 Click to sort` }} position="top">
|
|
472
|
-
<span className="flex items-center gap-1 text-
|
|
472
|
+
<span className="flex items-center gap-1 text-xss text-amber-400/80">
|
|
473
473
|
<Star className="w-3 h-3" />
|
|
474
474
|
{formatCount(props.stars)}
|
|
475
475
|
</span>
|
|
@@ -479,7 +479,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
479
479
|
...(props.downloads != null && props.downloads > 0 ? [(
|
|
480
480
|
<CardClickable key="downloads" onClick={() => props.onSortBy?.('downloads')}>
|
|
481
481
|
<Tooltip content={{ description: `${props.downloads.toLocaleString()} downloads \u00b7 Click to sort` }} position="top">
|
|
482
|
-
<span className="flex items-center gap-1 text-
|
|
482
|
+
<span className="flex items-center gap-1 text-xss text-emerald-400/80">
|
|
483
483
|
<Download className="w-3 h-3" />
|
|
484
484
|
{formatCount(props.downloads)}
|
|
485
485
|
</span>
|
|
@@ -581,7 +581,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
581
581
|
if (props.installs != null && props.installs > 0) {
|
|
582
582
|
bottomStats = [(
|
|
583
583
|
<Tooltip key="installs" content={{ description: `${props.installs.toLocaleString()} installs` }} position="top">
|
|
584
|
-
<span className="flex items-center gap-1 text-
|
|
584
|
+
<span className="flex items-center gap-1 text-xss text-neutral-500">
|
|
585
585
|
<Download className="w-3 h-3" />
|
|
586
586
|
{props.installs.toLocaleString()}
|
|
587
587
|
</span>
|
|
@@ -642,7 +642,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
642
642
|
|
|
643
643
|
{/* Error/warning message */}
|
|
644
644
|
{errorMessage && (
|
|
645
|
-
<p className={`text-
|
|
645
|
+
<p className={`text-xss mb-2 break-all ${flash === 'warning' ? 'text-amber-400' : 'text-red-400'}`}>{errorMessage}</p>
|
|
646
646
|
)}
|
|
647
647
|
|
|
648
648
|
{/* Bottom row */}
|
|
@@ -650,7 +650,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
650
650
|
const dateNode = updatedAt ? (
|
|
651
651
|
<Tooltip content={{ description: onDateClick ? `Last updated ${formatFullDate(updatedAt)} \u00b7 Click to sort by date` : `Last updated ${formatFullDate(updatedAt)}` }} position="top">
|
|
652
652
|
<span
|
|
653
|
-
className={`flex items-center gap-1 text-
|
|
653
|
+
className={`flex items-center gap-1 text-xss text-neutral-500 whitespace-nowrap${onDateClick ? ' cursor-pointer hover:brightness-125 transition-all' : ''}`}
|
|
654
654
|
onClick={onDateClick ? (e: MouseEvent) => { e.stopPropagation(); onDateClick() } : undefined}
|
|
655
655
|
>
|
|
656
656
|
<Clock className="w-3 h-3" />
|
|
@@ -690,7 +690,7 @@ export function RegistryCard(props: RegistryCardProps) {
|
|
|
690
690
|
// Basic fallback: just install on confirm
|
|
691
691
|
return (
|
|
692
692
|
<div onClick={(e) => e.stopPropagation()}>
|
|
693
|
-
<div className="fixed inset-0 bg-
|
|
693
|
+
<div className="fixed inset-0 bg-[var(--background)]/50 z-50 flex items-center justify-center" onClick={() => setShowScopeConfirm(false)}>
|
|
694
694
|
<div className="bg-neutral-800 border border-neutral-700 rounded-lg p-4 max-w-sm" onClick={(e) => e.stopPropagation()}>
|
|
695
695
|
<h3 className="text-sm font-medium text-neutral-200 mb-2">{ALREADY_AT_USER}</h3>
|
|
696
696
|
<p className="text-xs text-neutral-400 mb-4">
|