@toolr/ui-design 0.1.5 → 0.1.6
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/agent-rules.json +91 -0
- package/ai-manifest.json +190 -0
- package/components/content/info-panel-primitives.tsx +14 -14
- package/components/lib/ai-tools.tsx +1 -1
- package/components/sections/ai-tools-paths/tools-paths-panel.tsx +7 -7
- package/components/sections/captured-issues/captured-issues-panel.tsx +11 -11
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +13 -13
- package/components/sections/golden-snapshots/golden-sync-panel.tsx +5 -5
- package/components/sections/golden-snapshots/snapshot-manager.tsx +11 -11
- package/components/sections/golden-snapshots/status-overview.tsx +20 -20
- package/components/sections/golden-snapshots/version-manager.tsx +8 -8
- package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +4 -4
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +5 -5
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +7 -7
- package/components/sections/report-bug/report-bug-form.tsx +14 -14
- package/components/sections/report-bug/screenshot-uploader.tsx +6 -6
- package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +3 -3
- package/components/sections/snapshot-browser/snapshot-tree.tsx +8 -8
- package/components/sections/snippets-editor/snippets-editor.tsx +81 -22
- package/components/settings/SettingsHeader.tsx +1 -1
- package/components/settings/SettingsTreeNav.tsx +22 -4
- package/components/ui/action-dialog.tsx +5 -5
- package/components/ui/badge.tsx +4 -4
- package/components/ui/bottom-panel-header.tsx +4 -4
- package/components/ui/breadcrumb.tsx +2 -2
- package/components/ui/collapsible-section.tsx +1 -1
- package/components/ui/cookie-consent.tsx +5 -5
- package/components/ui/detail-section.tsx +3 -3
- package/components/ui/editor-placeholder-card.tsx +7 -7
- package/components/ui/editor-toolbar.tsx +12 -0
- package/components/ui/execution-details-panel.tsx +6 -6
- package/components/ui/extension-list-card.tsx +3 -3
- package/components/ui/file-structure-section.tsx +17 -17
- package/components/ui/file-tree.tsx +3 -1
- package/components/ui/files-panel.tsx +27 -9
- package/components/ui/filter-dropdown.tsx +5 -5
- package/components/ui/form-actions.tsx +1 -1
- package/components/ui/frontmatter-form-header.tsx +4 -4
- package/components/ui/icon-button.tsx +1 -1
- package/components/ui/input.tsx +5 -5
- package/components/ui/label.tsx +4 -4
- package/components/ui/layout-tab-bar.tsx +4 -4
- package/components/ui/modal.tsx +2 -2
- package/components/ui/nav-card.tsx +3 -3
- package/components/ui/navigation-bar.tsx +5 -5
- package/components/ui/number-input.tsx +4 -4
- package/components/ui/registry-browser.tsx +4 -4
- package/components/ui/registry-card.tsx +13 -13
- package/components/ui/registry-detail.tsx +6 -6
- package/components/ui/segmented-toggle.tsx +4 -4
- package/components/ui/select.tsx +5 -5
- package/components/ui/selection-grid.tsx +4 -4
- package/components/ui/setting-row.tsx +1 -1
- package/components/ui/settings-card.tsx +3 -3
- package/components/ui/settings-info-box.tsx +1 -1
- package/components/ui/settings-section-title.tsx +1 -1
- package/components/ui/snapshot-card.tsx +7 -7
- package/components/ui/snippets-panel.tsx +10 -10
- package/components/ui/sort-dropdown.tsx +2 -2
- package/components/ui/status-card.tsx +4 -4
- package/components/ui/tab-bar.tsx +2 -2
- package/components/ui/tooltip.tsx +3 -3
- package/dist/content.js +14 -14
- package/dist/index.d.ts +11 -4
- package/dist/index.js +428 -336
- package/dist/tokens/primitives.css +9 -2
- package/package.json +13 -3
- package/tokens/primitives.css +9 -2
|
@@ -70,7 +70,7 @@ type LayoutMode = 'full' | 'compact-banner' | 'compact-all'
|
|
|
70
70
|
|
|
71
71
|
// Width estimates for layout calculation
|
|
72
72
|
const TAB_ICON_PADDING = 56 // icon(16) + gap(8) + px-4(32)
|
|
73
|
-
const CHAR_WIDTH = 7.5 // approximate char width at text-
|
|
73
|
+
const CHAR_WIDTH = 7.5 // approximate char width at text-md
|
|
74
74
|
const COUNT_BADGE_WIDTH = 40 // badge with count
|
|
75
75
|
const BANNER_FULL_WIDTH = 200 // icon + text + padding
|
|
76
76
|
const BANNER_COMPACT_WIDTH = 36 // icon only
|
|
@@ -163,7 +163,7 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
163
163
|
key={tab.id}
|
|
164
164
|
onClick={() => onTabChange(tab.id)}
|
|
165
165
|
data-testid={tab.testId}
|
|
166
|
-
className={`h-[41px] flex items-center justify-center gap-2 ${compactTabs ? 'px-3' : 'px-4'} text-
|
|
166
|
+
className={`h-[41px] flex items-center justify-center gap-2 ${compactTabs ? 'px-3' : 'px-4'} text-md border-b-2 transition-colors cursor-pointer ${baseClasses}`}
|
|
167
167
|
>
|
|
168
168
|
{compactTabs ? (
|
|
169
169
|
<span className="relative flex items-center justify-center w-[18px] h-[18px] flex-shrink-0">
|
|
@@ -209,12 +209,12 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
209
209
|
{statusBanner && (
|
|
210
210
|
compactBanner ? (
|
|
211
211
|
<Tooltip content={{ description: statusBanner.message }} position="bottom">
|
|
212
|
-
<div className={`flex items-center px-2 py-1.5 ${bannerStyles} rounded text-
|
|
212
|
+
<div className={`flex items-center px-2 py-1.5 ${bannerStyles} rounded text-sm`}>
|
|
213
213
|
<RefreshCw className="w-3 h-3 flex-shrink-0" />
|
|
214
214
|
</div>
|
|
215
215
|
</Tooltip>
|
|
216
216
|
) : (
|
|
217
|
-
<div className={`flex items-center gap-2 px-2.5 py-1.5 ${bannerStyles} rounded text-
|
|
217
|
+
<div className={`flex items-center gap-2 px-2.5 py-1.5 ${bannerStyles} rounded text-sm max-w-full`}>
|
|
218
218
|
<RefreshCw className="w-3 h-3 flex-shrink-0" />
|
|
219
219
|
<span className="truncate">{statusBanner.message}</span>
|
|
220
220
|
</div>
|
|
@@ -67,8 +67,8 @@ export interface BreadcrumbProps {
|
|
|
67
67
|
|
|
68
68
|
const sizeConfig = {
|
|
69
69
|
xss: { text: 'text-xss', icon: 'w-2.5 h-2.5', px: 'px-1', py: 'py-0.5', gap: 'gap-0.5', sep: 'w-2 h-2' },
|
|
70
|
-
xs: { text: 'text-
|
|
71
|
-
sm: { text: 'text-
|
|
70
|
+
xs: { text: 'text-sm', icon: 'w-3 h-3', px: 'px-1.5', py: 'py-0.5', gap: 'gap-1', sep: 'w-2.5 h-2.5' },
|
|
71
|
+
sm: { text: 'text-md', icon: 'w-3.5 h-3.5', px: 'px-2', py: 'py-1', gap: 'gap-1.5', sep: 'w-3 h-3' },
|
|
72
72
|
md: { text: 'text-base', icon: 'w-4 h-4', px: 'px-2.5', py: 'py-1', gap: 'gap-1.5', sep: 'w-3.5 h-3.5' },
|
|
73
73
|
lg: { text: 'text-lg', icon: 'w-5 h-5', px: 'px-3', py: 'py-1.5', gap: 'gap-2', sep: 'w-4 h-4' },
|
|
74
74
|
}
|
|
@@ -87,7 +87,7 @@ export function CollapsibleSection({
|
|
|
87
87
|
<Icon className="w-3.5 h-3.5" />
|
|
88
88
|
</span>
|
|
89
89
|
)}
|
|
90
|
-
<span className="text-
|
|
90
|
+
<span className="text-md font-medium text-neutral-200">{title}</span>
|
|
91
91
|
{badge !== undefined && (
|
|
92
92
|
<span className="ml-auto">
|
|
93
93
|
<Badge value={badge} color={badgeColor} size="xss" />
|
|
@@ -42,26 +42,26 @@ export function CookieConsent({
|
|
|
42
42
|
<div className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-neutral-900/95 backdrop-blur-sm border-t border-neutral-700/50">
|
|
43
43
|
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row items-start sm:items-center gap-4">
|
|
44
44
|
<div className="flex-grow">
|
|
45
|
-
<p className="text-
|
|
46
|
-
<p className="text-
|
|
45
|
+
<p className="text-md text-neutral-200 mb-1">{heading}</p>
|
|
46
|
+
<p className="text-sm text-neutral-400">{description}</p>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
<div className="flex items-center gap-2 flex-shrink-0">
|
|
50
50
|
<button
|
|
51
51
|
onClick={() => handleConsent('declined')}
|
|
52
|
-
className="px-3 py-1.5 text-
|
|
52
|
+
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-800 transition-colors"
|
|
53
53
|
>
|
|
54
54
|
Decline
|
|
55
55
|
</button>
|
|
56
56
|
<button
|
|
57
57
|
onClick={() => handleConsent('essential')}
|
|
58
|
-
className="px-3 py-1.5 text-
|
|
58
|
+
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-800 transition-colors"
|
|
59
59
|
>
|
|
60
60
|
Essential Only
|
|
61
61
|
</button>
|
|
62
62
|
<button
|
|
63
63
|
onClick={() => handleConsent('accepted')}
|
|
64
|
-
className={`px-3 py-1.5 text-
|
|
64
|
+
className={`px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-white border transition-colors bg-${accentColor}-600 border-${accentColor}-500 hover:bg-${accentColor}-500`}
|
|
65
65
|
>
|
|
66
66
|
Accept All
|
|
67
67
|
</button>
|
|
@@ -50,13 +50,13 @@ export function DetailSection({ title, icon, rows, className }: DetailSectionPro
|
|
|
50
50
|
<div className={className}>
|
|
51
51
|
<div className="flex items-center gap-2 mb-3">
|
|
52
52
|
{Icon && <Icon className="w-4 h-4 text-neutral-500" />}
|
|
53
|
-
<span className="text-
|
|
53
|
+
<span className="text-md font-medium text-neutral-400">{title}</span>
|
|
54
54
|
</div>
|
|
55
55
|
<div className="space-y-2">
|
|
56
56
|
{rows.map((row) => (
|
|
57
57
|
<div key={row.label} className="flex items-start gap-3">
|
|
58
|
-
<span className="text-
|
|
59
|
-
<span className={cn('text-
|
|
58
|
+
<span className="text-sm text-neutral-500 w-24 shrink-0">{row.label}:</span>
|
|
59
|
+
<span className={cn('text-sm text-neutral-400', row.mono && 'font-mono')}>
|
|
60
60
|
{row.value}
|
|
61
61
|
</span>
|
|
62
62
|
</div>
|
|
@@ -102,17 +102,17 @@ export function EditorPlaceholderCard({
|
|
|
102
102
|
<div className="flex items-start justify-between gap-2">
|
|
103
103
|
<div className="flex-1 min-w-0">
|
|
104
104
|
{/* Placeholder name with {{ }} */}
|
|
105
|
-
<code className={`text-
|
|
105
|
+
<code className={`text-sm font-mono px-1.5 py-0.5 rounded ${colors.name} ${colors.nameBg}`}>
|
|
106
106
|
{'{{' + name + '}}'}
|
|
107
107
|
</code>
|
|
108
108
|
{/* Required badge */}
|
|
109
109
|
{required && (
|
|
110
|
-
<span className="ml-2 inline-block px-1.5 py-0.5 text-
|
|
110
|
+
<span className="ml-2 inline-block px-1.5 py-0.5 text-sm font-semibold uppercase bg-red-500/15 text-red-400 border border-red-500/30 rounded">
|
|
111
111
|
Required
|
|
112
112
|
</span>
|
|
113
113
|
)}
|
|
114
114
|
{/* Description */}
|
|
115
|
-
<p className="text-
|
|
115
|
+
<p className="text-sm text-neutral-500 mt-1.5 line-clamp-2">{description}</p>
|
|
116
116
|
</div>
|
|
117
117
|
|
|
118
118
|
{/* Actions (copy for templates, edit/delete for settings) */}
|
|
@@ -146,7 +146,7 @@ export function EditorPlaceholderCard({
|
|
|
146
146
|
<div className="mt-2">
|
|
147
147
|
{hideValue ? (
|
|
148
148
|
<>
|
|
149
|
-
<span className="text-
|
|
149
|
+
<span className="text-sm text-neutral-500 font-medium">{valueLabel}</span>
|
|
150
150
|
<div className="mt-1.5">
|
|
151
151
|
<Input
|
|
152
152
|
type="password"
|
|
@@ -163,7 +163,7 @@ export function EditorPlaceholderCard({
|
|
|
163
163
|
) : (
|
|
164
164
|
<>
|
|
165
165
|
<div className="flex items-center justify-between">
|
|
166
|
-
<span className="text-
|
|
166
|
+
<span className="text-sm text-neutral-500 font-medium">{valueLabel}</span>
|
|
167
167
|
{(isOverflowing || isExpanded) && (
|
|
168
168
|
<IconButton
|
|
169
169
|
icon={isExpanded ? 'chevron-up' : 'chevron-down'}
|
|
@@ -175,7 +175,7 @@ export function EditorPlaceholderCard({
|
|
|
175
175
|
</div>
|
|
176
176
|
<div
|
|
177
177
|
ref={valueRef}
|
|
178
|
-
className={`mt-1.5 px-2 py-1.5 bg-neutral-800/50 rounded text-
|
|
178
|
+
className={`mt-1.5 px-2 py-1.5 bg-neutral-800/50 rounded text-sm text-neutral-400 font-mono ${
|
|
179
179
|
isExpanded
|
|
180
180
|
? 'whitespace-pre-wrap break-all max-h-[190px] overflow-y-auto'
|
|
181
181
|
: 'truncate'
|
|
@@ -190,7 +190,7 @@ export function EditorPlaceholderCard({
|
|
|
190
190
|
|
|
191
191
|
{/* No value hint */}
|
|
192
192
|
{!hasValue && (
|
|
193
|
-
<p className="mt-1.5 text-
|
|
193
|
+
<p className="mt-1.5 text-sm text-neutral-600 italic">No value set - add one in Settings</p>
|
|
194
194
|
)}
|
|
195
195
|
</div>
|
|
196
196
|
)
|
|
@@ -4,6 +4,10 @@ import { Label } from './label.tsx'
|
|
|
4
4
|
import { ConfirmModal } from './modal.tsx'
|
|
5
5
|
|
|
6
6
|
export interface EditorToolbarProps {
|
|
7
|
+
/** Optional title displayed in the toolbar */
|
|
8
|
+
title?: string
|
|
9
|
+
/** Optional description displayed below the title */
|
|
10
|
+
description?: string
|
|
7
11
|
/** Whether content has unsaved changes */
|
|
8
12
|
isDirty: boolean
|
|
9
13
|
/** Whether save operation is in progress */
|
|
@@ -30,6 +34,8 @@ export interface EditorToolbarProps {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export function EditorToolbar({
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
33
39
|
isDirty,
|
|
34
40
|
isSaving = false,
|
|
35
41
|
onSave,
|
|
@@ -66,6 +72,12 @@ export function EditorToolbar({
|
|
|
66
72
|
<div className="flex items-center justify-between px-4 py-1.5 bg-neutral-900 border-b border-neutral-800">
|
|
67
73
|
{/* Left side */}
|
|
68
74
|
<div className="flex items-center gap-2">
|
|
75
|
+
{(title || description) && (
|
|
76
|
+
<div className="flex flex-col mr-2">
|
|
77
|
+
{title && <span className="text-sm font-medium text-neutral-200">{title}</span>}
|
|
78
|
+
{description && <span className="text-xs text-neutral-500">{description}</span>}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
69
81
|
{leftActions?.map((a, i) => <IconButton key={i} {...a} />)}
|
|
70
82
|
</div>
|
|
71
83
|
|
|
@@ -40,7 +40,7 @@ export function ExecutionDetailsPanel({
|
|
|
40
40
|
{/* Header */}
|
|
41
41
|
<div className="flex items-center gap-2">
|
|
42
42
|
<Info className="w-4 h-4 text-neutral-500" />
|
|
43
|
-
<span className="font-medium text-neutral-400 text-
|
|
43
|
+
<span className="font-medium text-neutral-400 text-md">Execution Details</span>
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
{/* Direct edits toggle */}
|
|
@@ -57,8 +57,8 @@ export function ExecutionDetailsPanel({
|
|
|
57
57
|
/>
|
|
58
58
|
</div>
|
|
59
59
|
<div>
|
|
60
|
-
<span className="text-neutral-300 text-
|
|
61
|
-
<p className="text-neutral-500 text-
|
|
60
|
+
<span className="text-neutral-300 text-md">Allow direct file edits</span>
|
|
61
|
+
<p className="text-neutral-500 text-sm mt-0.5">
|
|
62
62
|
{allowDirectEdits
|
|
63
63
|
? 'AI will modify files directly. Changes saved immediately.'
|
|
64
64
|
: 'Changes will be shown in editor for review.'}
|
|
@@ -72,7 +72,7 @@ export function ExecutionDetailsPanel({
|
|
|
72
72
|
<div className="rounded border border-red-500/50 bg-red-500/10 p-2">
|
|
73
73
|
<div className="flex items-start gap-2">
|
|
74
74
|
<AlertTriangle className="h-4 w-4 text-red-400 shrink-0 mt-0.5" />
|
|
75
|
-
<p className="text-red-300 text-
|
|
75
|
+
<p className="text-red-300 text-sm">{warningMessage}</p>
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
)}
|
|
@@ -82,8 +82,8 @@ export function ExecutionDetailsPanel({
|
|
|
82
82
|
<div className="space-y-2">
|
|
83
83
|
{details.map((row) => (
|
|
84
84
|
<div key={row.label} className="flex items-start gap-3">
|
|
85
|
-
<span className="text-neutral-500 text-
|
|
86
|
-
<span className={cn('text-neutral-300 text-
|
|
85
|
+
<span className="text-neutral-500 text-sm w-24 shrink-0">{row.label}:</span>
|
|
86
|
+
<span className={cn('text-neutral-300 text-sm', row.mono && 'font-mono')}>{row.value}</span>
|
|
87
87
|
</div>
|
|
88
88
|
))}
|
|
89
89
|
</div>
|
|
@@ -81,11 +81,11 @@ export const ExtensionListCard = memo(function ExtensionListCard({
|
|
|
81
81
|
<Icon className={cn('w-5 h-5 shrink-0', iconColor)} />
|
|
82
82
|
<div className="min-w-0 flex-1">
|
|
83
83
|
<div className="flex items-center gap-2 flex-wrap">
|
|
84
|
-
<span className={cn('text-
|
|
84
|
+
<span className={cn('text-md font-medium', titleClassName)}>{title}</span>
|
|
85
85
|
{badges}
|
|
86
86
|
</div>
|
|
87
87
|
{description && (
|
|
88
|
-
<div className={cn('text-
|
|
88
|
+
<div className={cn('text-sm text-neutral-500 mt-1', !isHovered && 'line-clamp-2')}>{description}</div>
|
|
89
89
|
)}
|
|
90
90
|
</div>
|
|
91
91
|
</div>
|
|
@@ -96,7 +96,7 @@ export const ExtensionListCard = memo(function ExtensionListCard({
|
|
|
96
96
|
)}
|
|
97
97
|
</div>
|
|
98
98
|
{metadata != null && (
|
|
99
|
-
<div className="flex items-center justify-between mt-2 ml-8 mr-2 text-
|
|
99
|
+
<div className="flex items-center justify-between mt-2 ml-8 mr-2 text-sm text-neutral-500">
|
|
100
100
|
{typeof metadata === 'function' ? metadata(isHovered) : metadata}
|
|
101
101
|
</div>
|
|
102
102
|
)}
|
|
@@ -89,7 +89,7 @@ function renderMarkdownContent(content: string) {
|
|
|
89
89
|
while (i < lines.length && lines[i] !== '---') { fmLines.push(lines[i]); i++ }
|
|
90
90
|
i++ // skip closing ---
|
|
91
91
|
nodes.push(
|
|
92
|
-
<div key="fm" className="mb-3 font-mono text-
|
|
92
|
+
<div key="fm" className="mb-3 font-mono text-xs text-neutral-500 border-l-2 border-neutral-700 pl-2 py-0.5">
|
|
93
93
|
<div className="text-neutral-600">---</div>
|
|
94
94
|
{fmLines.map((l, j) => <div key={j}>{l}</div>)}
|
|
95
95
|
<div className="text-neutral-600">---</div>
|
|
@@ -104,20 +104,20 @@ function renderMarkdownContent(content: string) {
|
|
|
104
104
|
i++
|
|
105
105
|
while (i < lines.length && !lines[i].startsWith('```')) { codeLines.push(lines[i]); i++ }
|
|
106
106
|
nodes.push(
|
|
107
|
-
<pre key={i} className="mb-2 p-2 bg-[var(--background)]/30 rounded text-
|
|
107
|
+
<pre key={i} className="mb-2 p-2 bg-[var(--background)]/30 rounded text-xs font-mono text-neutral-300 overflow-x-auto">
|
|
108
108
|
{codeLines.join('\n')}
|
|
109
109
|
</pre>
|
|
110
110
|
)
|
|
111
111
|
} else if (line.startsWith('### ')) {
|
|
112
|
-
nodes.push(<h3 key={i} className="text-
|
|
112
|
+
nodes.push(<h3 key={i} className="text-xs font-semibold text-neutral-300 mt-2 mb-0.5">{line.slice(4)}</h3>)
|
|
113
113
|
} else if (line.startsWith('## ')) {
|
|
114
|
-
nodes.push(<h2 key={i} className="text-
|
|
114
|
+
nodes.push(<h2 key={i} className="text-sm font-semibold text-neutral-200 mt-2.5 mb-1">{line.slice(3)}</h2>)
|
|
115
115
|
} else if (line.startsWith('# ')) {
|
|
116
|
-
nodes.push(<h1 key={i} className="text-
|
|
116
|
+
nodes.push(<h1 key={i} className="text-md font-semibold text-neutral-100 mb-1.5">{line.slice(2)}</h1>)
|
|
117
117
|
} else if (line === '' || line === '\r') {
|
|
118
118
|
nodes.push(<div key={i} className="h-1.5" />)
|
|
119
119
|
} else {
|
|
120
|
-
nodes.push(<p key={i} className="text-
|
|
120
|
+
nodes.push(<p key={i} className="text-xs text-neutral-400 leading-relaxed">{line}</p>)
|
|
121
121
|
}
|
|
122
122
|
i++
|
|
123
123
|
}
|
|
@@ -302,8 +302,8 @@ export function FileStructureSection({
|
|
|
302
302
|
if (isLoading) {
|
|
303
303
|
return (
|
|
304
304
|
<div>
|
|
305
|
-
<h3 className="text-
|
|
306
|
-
<div className="flex items-center gap-2 text-
|
|
305
|
+
<h3 className="text-sm font-medium text-neutral-500 uppercase tracking-wider mb-2">File Structure</h3>
|
|
306
|
+
<div className="flex items-center gap-2 text-sm text-neutral-500 py-4">
|
|
307
307
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
308
308
|
Loading file tree...
|
|
309
309
|
</div>
|
|
@@ -318,8 +318,8 @@ export function FileStructureSection({
|
|
|
318
318
|
if (error) {
|
|
319
319
|
return (
|
|
320
320
|
<div>
|
|
321
|
-
<h3 className="text-
|
|
322
|
-
<div className="flex items-center gap-2 text-
|
|
321
|
+
<h3 className="text-sm font-medium text-neutral-500 uppercase tracking-wider mb-2">File Structure</h3>
|
|
322
|
+
<div className="flex items-center gap-2 text-sm text-red-400 py-4">
|
|
323
323
|
<AlertCircle className="w-3.5 h-3.5 shrink-0" />
|
|
324
324
|
{error}
|
|
325
325
|
</div>
|
|
@@ -336,7 +336,7 @@ export function FileStructureSection({
|
|
|
336
336
|
if (mode === 'format') return renderMarkdownContent(content)
|
|
337
337
|
if (mode === 'language' && renderPreview) return renderPreview(content, filePath, resolvedLanguage)
|
|
338
338
|
return (
|
|
339
|
-
<pre className="p-3 text-
|
|
339
|
+
<pre className="p-3 text-sm font-mono text-white leading-relaxed whitespace-pre-wrap">
|
|
340
340
|
<code>{content}</code>
|
|
341
341
|
</pre>
|
|
342
342
|
)
|
|
@@ -346,7 +346,7 @@ export function FileStructureSection({
|
|
|
346
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
347
|
<div className={`flex items-center px-3 py-2 border-b ${ACCENT_BORDER[accentColor]} shrink-0 gap-2 min-w-0`}>
|
|
348
348
|
<FolderTree className={`w-3.5 h-3.5 shrink-0 ${ACCENT_ICON[accentColor]}`} />
|
|
349
|
-
<span className="text-
|
|
349
|
+
<span className="text-sm text-neutral-200 truncate flex-1">Files</span>
|
|
350
350
|
<CollapseButton
|
|
351
351
|
collapsed={allCollapsed}
|
|
352
352
|
onToggle={() => setExpandedPaths(allCollapsed ? new Set(allDirPaths) : new Set())}
|
|
@@ -371,7 +371,7 @@ export function FileStructureSection({
|
|
|
371
371
|
<div className={`flex-1 flex flex-col bg-neutral-900 border ${ACCENT_BORDER[accentColor]} rounded-lg overflow-hidden`}>
|
|
372
372
|
<div className={`flex items-center px-3 py-2 border-b ${ACCENT_BORDER[accentColor]} shrink-0 gap-2 min-w-0`}>
|
|
373
373
|
<FileCode className={`w-3.5 h-3.5 shrink-0 ${ACCENT_ICON[accentColor]}`} />
|
|
374
|
-
<span className="text-
|
|
374
|
+
<span className="text-sm text-neutral-200 truncate flex-1">{selectedFileName}</span>
|
|
375
375
|
{showToggle && (
|
|
376
376
|
<SegmentedToggle
|
|
377
377
|
options={toggleOptions}
|
|
@@ -384,12 +384,12 @@ export function FileStructureSection({
|
|
|
384
384
|
</div>
|
|
385
385
|
<div className="flex-1 overflow-auto">
|
|
386
386
|
{fileIsLoading ? (
|
|
387
|
-
<div className="flex items-center gap-2 text-
|
|
387
|
+
<div className="flex items-center gap-2 text-sm text-neutral-500 p-3">
|
|
388
388
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
389
389
|
Loading...
|
|
390
390
|
</div>
|
|
391
391
|
) : fileError ? (
|
|
392
|
-
<p className="text-
|
|
392
|
+
<p className="text-sm text-red-400 p-3">{fileError}</p>
|
|
393
393
|
) : fileContent !== null ? (
|
|
394
394
|
renderContent(fileContent, effectiveFilePath)
|
|
395
395
|
) : null}
|
|
@@ -409,7 +409,7 @@ export function FileStructureSection({
|
|
|
409
409
|
if (variant === 'list') {
|
|
410
410
|
return (
|
|
411
411
|
<div ref={sectionRef}>
|
|
412
|
-
<h3 className="text-
|
|
412
|
+
<h3 className="text-sm font-medium text-neutral-500 uppercase tracking-wider mb-2">File Structure</h3>
|
|
413
413
|
<div className="space-y-3">
|
|
414
414
|
{treePanel}
|
|
415
415
|
{previewPanel && (
|
|
@@ -427,7 +427,7 @@ export function FileStructureSection({
|
|
|
427
427
|
|
|
428
428
|
return (
|
|
429
429
|
<div ref={sectionRef}>
|
|
430
|
-
<h3 className="text-
|
|
430
|
+
<h3 className="text-sm font-medium text-neutral-500 uppercase tracking-wider mb-2">File Structure</h3>
|
|
431
431
|
<div className="flex gap-3" style={{ height: `${effectiveHeight}px` }}>
|
|
432
432
|
{treePanel}
|
|
433
433
|
{previewPanel}
|
|
@@ -27,6 +27,7 @@ const ACCENT_SELECTED: Record<string, string> = {
|
|
|
27
27
|
emerald: 'bg-emerald-400/20 text-neutral-200',
|
|
28
28
|
teal: 'bg-teal-400/20 text-neutral-200',
|
|
29
29
|
sky: 'bg-sky-400/20 text-neutral-200',
|
|
30
|
+
violet: 'bg-violet-400/20 text-neutral-200',
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const ACCENT_ICON: Record<string, string> = {
|
|
@@ -39,6 +40,7 @@ const ACCENT_ICON: Record<string, string> = {
|
|
|
39
40
|
emerald: 'text-emerald-400',
|
|
40
41
|
teal: 'text-teal-400',
|
|
41
42
|
sky: 'text-sky-400',
|
|
43
|
+
violet: 'text-violet-400',
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function nodeHasFiles(node: FileTreeNode): boolean {
|
|
@@ -120,7 +122,7 @@ function FileTreeNodeItem({ node, path, selectedPath, onSelectFile, expandedPath
|
|
|
120
122
|
const isDir = node.type === 'directory'
|
|
121
123
|
const isSelected = !isDir && selectedPath === path
|
|
122
124
|
const expanded = isDir && expandedPaths.has(path)
|
|
123
|
-
const base = 'flex items-center gap-1.5 py-0.5 px-1 rounded text-
|
|
125
|
+
const base = 'flex items-center gap-1.5 py-0.5 px-1 rounded text-sm transition-colors overflow-hidden whitespace-nowrap'
|
|
124
126
|
const selectedClass = ACCENT_SELECTED[accentColor] ?? ACCENT_SELECTED.blue
|
|
125
127
|
const iconColorClass = ACCENT_ICON[accentColor] ?? ACCENT_ICON.blue
|
|
126
128
|
const rowClass = isSelected
|
|
@@ -6,6 +6,19 @@ import type { LucideIcon } from 'lucide-react'
|
|
|
6
6
|
import type { IconName } from './icon-button.tsx'
|
|
7
7
|
import { cn } from '../lib/cn.ts'
|
|
8
8
|
|
|
9
|
+
const ACCENT_SELECTED: Record<string, string> = {
|
|
10
|
+
blue: 'bg-blue-400/15 text-blue-400',
|
|
11
|
+
purple: 'bg-purple-400/15 text-purple-400',
|
|
12
|
+
orange: 'bg-orange-400/15 text-orange-400',
|
|
13
|
+
green: 'bg-green-400/15 text-green-400',
|
|
14
|
+
pink: 'bg-pink-400/15 text-pink-400',
|
|
15
|
+
amber: 'bg-amber-400/15 text-amber-400',
|
|
16
|
+
emerald: 'bg-emerald-400/15 text-emerald-400',
|
|
17
|
+
teal: 'bg-teal-400/15 text-teal-400',
|
|
18
|
+
sky: 'bg-sky-400/15 text-sky-400',
|
|
19
|
+
violet: 'bg-violet-400/15 text-violet-400',
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
const iconSubset: Partial<Record<IconName, LucideIcon>> = {
|
|
10
23
|
folder: Folder,
|
|
11
24
|
file: File,
|
|
@@ -48,6 +61,7 @@ export interface FilesPanelProps {
|
|
|
48
61
|
onAction?: (action: string, path: string) => void
|
|
49
62
|
showSearch?: boolean
|
|
50
63
|
className?: string
|
|
64
|
+
accentColor?: string
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
function collectAllFolderPaths(entries: FileEntry[]): Set<string> {
|
|
@@ -110,9 +124,10 @@ interface FileNodeProps {
|
|
|
110
124
|
onToggleExpand: (path: string) => void
|
|
111
125
|
onSelect?: (path: string) => void
|
|
112
126
|
onAction?: (action: string, path: string) => void
|
|
127
|
+
accentColor: string
|
|
113
128
|
}
|
|
114
129
|
|
|
115
|
-
function FileNode({ entry, depth, selectedPath, expandedPaths, onToggleExpand, onSelect, onAction }: FileNodeProps) {
|
|
130
|
+
function FileNode({ entry, depth, selectedPath, expandedPaths, onToggleExpand, onSelect, onAction, accentColor }: FileNodeProps) {
|
|
116
131
|
const isFolder = entry.type === 'folder'
|
|
117
132
|
const isExpanded = isFolder && expandedPaths.has(entry.path)
|
|
118
133
|
const isSelected = !isFolder && selectedPath === entry.path
|
|
@@ -124,9 +139,9 @@ function FileNode({ entry, depth, selectedPath, expandedPaths, onToggleExpand, o
|
|
|
124
139
|
type="button"
|
|
125
140
|
onClick={isFolder ? () => onToggleExpand(entry.path) : () => onSelect?.(entry.path)}
|
|
126
141
|
className={cn(
|
|
127
|
-
'group flex items-center gap-1.5 w-full py-1 px-2 rounded text-
|
|
142
|
+
'group flex items-center gap-1.5 w-full py-1 px-2 rounded text-sm transition-colors cursor-pointer',
|
|
128
143
|
isSelected
|
|
129
|
-
?
|
|
144
|
+
? ACCENT_SELECTED[accentColor] ?? ACCENT_SELECTED.blue
|
|
130
145
|
: 'text-neutral-400 hover:bg-neutral-700/40 hover:text-neutral-200',
|
|
131
146
|
)}
|
|
132
147
|
style={{ paddingLeft: `${depth * 16 + 8}px` }}
|
|
@@ -144,7 +159,7 @@ function FileNode({ entry, depth, selectedPath, expandedPaths, onToggleExpand, o
|
|
|
144
159
|
/>
|
|
145
160
|
<span className="truncate">{entry.name}</span>
|
|
146
161
|
{entry.badge && (
|
|
147
|
-
<span className="ml-auto shrink-0 px-1.5 py-0.5 text-
|
|
162
|
+
<span className="ml-auto shrink-0 px-1.5 py-0.5 text-xs rounded bg-neutral-700 text-neutral-500">
|
|
148
163
|
{entry.badge}
|
|
149
164
|
</span>
|
|
150
165
|
)}
|
|
@@ -172,6 +187,7 @@ function FileNode({ entry, depth, selectedPath, expandedPaths, onToggleExpand, o
|
|
|
172
187
|
onToggleExpand={onToggleExpand}
|
|
173
188
|
onSelect={onSelect}
|
|
174
189
|
onAction={onAction}
|
|
190
|
+
accentColor={accentColor}
|
|
175
191
|
/>
|
|
176
192
|
))}
|
|
177
193
|
</ul>
|
|
@@ -187,6 +203,7 @@ export function FilesPanel({
|
|
|
187
203
|
onAction,
|
|
188
204
|
showSearch = false,
|
|
189
205
|
className,
|
|
206
|
+
accentColor = 'blue',
|
|
190
207
|
}: FilesPanelProps) {
|
|
191
208
|
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => collectAllFolderPaths(files))
|
|
192
209
|
const [searchQuery, setSearchQuery] = useState('')
|
|
@@ -210,19 +227,19 @@ export function FilesPanel({
|
|
|
210
227
|
return (
|
|
211
228
|
<div className={cn('flex flex-col bg-neutral-800 rounded-lg overflow-hidden', className)}>
|
|
212
229
|
<div className="flex items-center justify-between px-3 py-2 border-b border-neutral-700">
|
|
213
|
-
<span className="text-
|
|
214
|
-
<span className="text-
|
|
230
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-neutral-500">Files</span>
|
|
231
|
+
<span className="text-xs text-neutral-500">{fileCount} files</span>
|
|
215
232
|
</div>
|
|
216
233
|
{showSearch && (
|
|
217
234
|
<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-[var(--background)] border border-neutral-700 rounded text-
|
|
235
|
+
<div className="flex items-center gap-1.5 px-2 py-1 bg-[var(--background)] border border-neutral-700 rounded text-sm">
|
|
219
236
|
<Search className="w-3 h-3 text-neutral-500 shrink-0" />
|
|
220
237
|
<input
|
|
221
238
|
type="text"
|
|
222
239
|
placeholder="Search files..."
|
|
223
240
|
value={searchQuery}
|
|
224
241
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
225
|
-
className="flex-1 bg-transparent text-neutral-200 placeholder-neutral-500 outline-none text-
|
|
242
|
+
className="flex-1 bg-transparent text-neutral-200 placeholder-neutral-500 outline-none text-sm"
|
|
226
243
|
/>
|
|
227
244
|
</div>
|
|
228
245
|
</div>
|
|
@@ -239,11 +256,12 @@ export function FilesPanel({
|
|
|
239
256
|
onToggleExpand={handleToggleExpand}
|
|
240
257
|
onSelect={onSelect}
|
|
241
258
|
onAction={onAction}
|
|
259
|
+
accentColor={accentColor}
|
|
242
260
|
/>
|
|
243
261
|
))}
|
|
244
262
|
</ul>
|
|
245
263
|
{displayedFiles.length === 0 && (
|
|
246
|
-
<p className="text-
|
|
264
|
+
<p className="text-xs text-neutral-500 text-center py-4">No files found</p>
|
|
247
265
|
)}
|
|
248
266
|
</div>
|
|
249
267
|
</div>
|
|
@@ -89,7 +89,7 @@ export function FilterDropdown({
|
|
|
89
89
|
<div className="relative flex items-center" ref={ref} onKeyDown={handleKeyDown}>
|
|
90
90
|
<button
|
|
91
91
|
onClick={() => setIsOpen(!isOpen)}
|
|
92
|
-
className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-
|
|
92
|
+
className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-sm transition-colors cursor-pointer ${
|
|
93
93
|
isActive
|
|
94
94
|
? `${clearable ? 'rounded-r-none border-r-0' : ''} ${FORM_COLORS[color].border} text-neutral-200 ${FORM_COLORS[color].hover}`
|
|
95
95
|
: isOpen
|
|
@@ -124,7 +124,7 @@ export function FilterDropdown({
|
|
|
124
124
|
onChange={(e) => setSearch(e.target.value)}
|
|
125
125
|
onKeyDown={handleKeyDown}
|
|
126
126
|
placeholder="Search..."
|
|
127
|
-
className={`w-full pl-6 pr-2 py-1 text-
|
|
127
|
+
className={`w-full pl-6 pr-2 py-1 text-sm bg-[var(--popover)] border border-neutral-600 rounded text-neutral-200 placeholder-neutral-500 outline-none ${FORM_COLORS[color].focus}`}
|
|
128
128
|
/>
|
|
129
129
|
</div>
|
|
130
130
|
</div>
|
|
@@ -133,7 +133,7 @@ export function FilterDropdown({
|
|
|
133
133
|
<button
|
|
134
134
|
data-idx={0}
|
|
135
135
|
onClick={() => handleSelect('all')}
|
|
136
|
-
className={`w-full flex items-center gap-2 px-3 py-1.5 text-
|
|
136
|
+
className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm 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}`
|
|
@@ -152,7 +152,7 @@ export function FilterDropdown({
|
|
|
152
152
|
key={opt.value}
|
|
153
153
|
data-idx={idx}
|
|
154
154
|
onClick={() => handleSelect(opt.value)}
|
|
155
|
-
className={`w-full flex items-center gap-2 px-3 py-1.5 text-
|
|
155
|
+
className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm 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}`
|
|
@@ -164,7 +164,7 @@ export function FilterDropdown({
|
|
|
164
164
|
)
|
|
165
165
|
})}
|
|
166
166
|
{showSearch && search && filtered.length === 0 && (
|
|
167
|
-
<div className="px-3 py-2 text-
|
|
167
|
+
<div className="px-3 py-2 text-sm text-neutral-500">No matches</div>
|
|
168
168
|
)}
|
|
169
169
|
</div>
|
|
170
170
|
)}
|
|
@@ -83,7 +83,7 @@ export function FormActions({
|
|
|
83
83
|
tooltip={{ description: backTooltip }}
|
|
84
84
|
/>
|
|
85
85
|
)}
|
|
86
|
-
{statusText && <span className="text-
|
|
86
|
+
{statusText && <span className="text-sm text-neutral-500">{statusText}</span>}
|
|
87
87
|
</div>
|
|
88
88
|
)}
|
|
89
89
|
<div className="flex items-center gap-2">
|
|
@@ -39,16 +39,16 @@ export function FrontmatterFormHeader({
|
|
|
39
39
|
collapsed ? '' : 'rotate-90'
|
|
40
40
|
}`}
|
|
41
41
|
/>
|
|
42
|
-
<span className="text-
|
|
42
|
+
<span className="text-sm font-medium text-neutral-400 uppercase tracking-wide">
|
|
43
43
|
Configuration
|
|
44
44
|
</span>
|
|
45
45
|
{collapsed && hasFm && (
|
|
46
|
-
<span className="text-
|
|
46
|
+
<span className="text-xs 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-xs text-neutral-600 ml-2">No frontmatter</span>
|
|
52
52
|
)}
|
|
53
53
|
</button>
|
|
54
54
|
|
|
@@ -64,7 +64,7 @@ export function FrontmatterFormHeader({
|
|
|
64
64
|
disabled={readOnly}
|
|
65
65
|
/>
|
|
66
66
|
<span
|
|
67
|
-
className="text-
|
|
67
|
+
className="text-sm text-neutral-400 cursor-pointer"
|
|
68
68
|
onClick={() => !readOnly && onFrontmatterToggle(!hasFm)}
|
|
69
69
|
>
|
|
70
70
|
Add YAML frontmatter to file
|
|
@@ -323,7 +323,7 @@ export function IconButton({
|
|
|
323
323
|
</span>
|
|
324
324
|
{badge !== undefined && (
|
|
325
325
|
<span
|
|
326
|
-
className={`absolute -top-1 -right-1 min-w-[18px] h-[18px] flex items-center justify-center px-1 text-
|
|
326
|
+
className={`absolute -top-1 -right-1 min-w-[18px] h-[18px] flex items-center justify-center px-1 text-sm font-bold text-white rounded-full ${badgeColorClasses[badgeColor]}`}
|
|
327
327
|
>
|
|
328
328
|
{typeof badge === 'number' && badge > 99 ? '99+' : badge}
|
|
329
329
|
</span>
|
package/components/ui/input.tsx
CHANGED
|
@@ -39,10 +39,10 @@ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
|
|
|
39
39
|
|
|
40
40
|
const sizeClasses = {
|
|
41
41
|
xss: 'px-1 py-0.5 text-xss',
|
|
42
|
-
xs: 'px-1.5 py-0.5 text-
|
|
43
|
-
sm: 'px-2 py-1 text-
|
|
44
|
-
md: 'px-3 py-1.5 text-
|
|
45
|
-
lg: 'px-3 py-2 text-
|
|
42
|
+
xs: 'px-1.5 py-0.5 text-sm',
|
|
43
|
+
sm: 'px-2 py-1 text-sm',
|
|
44
|
+
md: 'px-3 py-1.5 text-md',
|
|
45
|
+
lg: 'px-3 py-2 text-md',
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const variantClasses = {
|
|
@@ -204,7 +204,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input({
|
|
|
204
204
|
)}
|
|
205
205
|
</div>
|
|
206
206
|
{typeof error === 'string' && error && (
|
|
207
|
-
<p className="text-
|
|
207
|
+
<p className="text-sm text-red-400 mt-1 text-right">{error}</p>
|
|
208
208
|
)}
|
|
209
209
|
</div>
|
|
210
210
|
)
|