@toolr/ui-design 0.1.2 → 0.1.3
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 +3 -0
- package/components/ui/badge.tsx +4 -2
- package/components/ui/confirm-badge.tsx +4 -2
- package/components/ui/file-structure-section.tsx +135 -70
- package/components/ui/file-tree.tsx +2 -2
- package/components/ui/filter-dropdown.tsx +1 -1
- package/components/ui/icon-button.tsx +6 -2
- package/components/ui/label.tsx +4 -1
- package/components/ui/registry-detail.tsx +3 -0
- package/components/ui/resizable-textarea.tsx +2 -2
- package/components/ui/segmented-toggle.tsx +34 -16
- package/components/ui/select.tsx +1 -1
- 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/sort-dropdown.tsx +1 -1
- package/components/ui/tooltip.tsx +1 -1
- package/dist/index.d.ts +83 -46
- package/dist/index.js +1423 -1205
- package/index.ts +4 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type FormColor =
|
|
2
2
|
| 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow'
|
|
3
3
|
| 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky'
|
|
4
|
+
| 'pink' | 'teal'
|
|
4
5
|
|
|
5
6
|
interface FormColorConfig {
|
|
6
7
|
/** Idle border class (e.g. 'border-blue-500/30') */
|
|
@@ -29,4 +30,6 @@ export const FORM_COLORS: Record<FormColor, FormColorConfig> = {
|
|
|
29
30
|
violet: { border: 'border-violet-500/30', hover: 'hover:bg-violet-500/20 hover:border-violet-500/40', focus: 'focus:border-violet-500', selectedBg: 'bg-violet-600/20', accent: 'text-violet-400' },
|
|
30
31
|
neutral: { border: 'border-neutral-500/30', hover: 'hover:bg-neutral-500/20 hover:border-neutral-500/40', focus: 'focus:border-neutral-500', selectedBg: 'bg-neutral-600/20', accent: 'text-neutral-400' },
|
|
31
32
|
sky: { border: 'border-sky-500/30', hover: 'hover:bg-sky-500/20 hover:border-sky-500/40', focus: 'focus:border-sky-500', selectedBg: 'bg-sky-600/20', accent: 'text-sky-400' },
|
|
33
|
+
pink: { border: 'border-pink-500/30', hover: 'hover:bg-pink-500/20 hover:border-pink-500/40', focus: 'focus:border-pink-500', selectedBg: 'bg-pink-600/20', accent: 'text-pink-400' },
|
|
34
|
+
teal: { border: 'border-teal-500/30', hover: 'hover:bg-teal-500/20 hover:border-teal-500/40', focus: 'focus:border-teal-500', selectedBg: 'bg-teal-600/20', accent: 'text-teal-400' },
|
|
32
35
|
}
|
package/components/ui/badge.tsx
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
* Features:
|
|
10
10
|
* - Outline variant matching IconButton outline style (border + text, no fill)
|
|
11
11
|
* - Accepts numbers (auto-caps at 99+) or short strings ("New")
|
|
12
|
-
* -
|
|
12
|
+
* - 15 color variants
|
|
13
13
|
* - 5 size variants (xss, xs, sm, md, lg)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
export type BadgeColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky'
|
|
16
|
+
export type BadgeColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
|
|
17
17
|
|
|
18
18
|
export interface BadgeProps {
|
|
19
19
|
value: number | string
|
|
@@ -37,6 +37,8 @@ const colorClasses: Record<BadgeColor, string> = {
|
|
|
37
37
|
violet: 'border-violet-500/30 text-violet-400',
|
|
38
38
|
neutral: 'border-neutral-500/30 text-neutral-400',
|
|
39
39
|
sky: 'border-sky-500/30 text-sky-400',
|
|
40
|
+
pink: 'border-pink-500/30 text-pink-400',
|
|
41
|
+
teal: 'border-teal-500/30 text-teal-400',
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
const sizeClasses = {
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Features:
|
|
9
9
|
* - Outline variant matching IconButton outline style (border + text, no fill)
|
|
10
|
-
* -
|
|
10
|
+
* - 15 color variants
|
|
11
11
|
* - 5 size variants (xss, xs, sm, md, lg)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { Check } from 'lucide-react'
|
|
15
15
|
|
|
16
|
-
export type ConfirmBadgeColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky'
|
|
16
|
+
export type ConfirmBadgeColor = 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
|
|
17
17
|
|
|
18
18
|
export interface ConfirmBadgeProps {
|
|
19
19
|
color?: ConfirmBadgeColor
|
|
@@ -36,6 +36,8 @@ const colorClasses: Record<ConfirmBadgeColor, string> = {
|
|
|
36
36
|
violet: 'border-violet-500/30 text-violet-400',
|
|
37
37
|
neutral: 'border-neutral-500/30 text-neutral-400',
|
|
38
38
|
sky: 'border-sky-500/30 text-sky-400',
|
|
39
|
+
pink: 'border-pink-500/30 text-pink-400',
|
|
40
|
+
teal: 'border-teal-500/30 text-teal-400',
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const sizeClasses = {
|
|
@@ -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 {
|
|
@@ -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>
|
|
@@ -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">
|
|
@@ -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/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,6 +82,7 @@ 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 = {
|
|
@@ -29,6 +29,7 @@ export interface RegistryDetailProps {
|
|
|
29
29
|
// File structure (rendered when files + onFetchContent are provided)
|
|
30
30
|
files?: FileStructureSectionProps['files']
|
|
31
31
|
rootName?: string
|
|
32
|
+
fileStructureVariant?: FileStructureSectionProps['variant']
|
|
32
33
|
onFetchContent?: FileStructureSectionProps['onFetchContent']
|
|
33
34
|
|
|
34
35
|
// Above header (e.g. badge row)
|
|
@@ -145,6 +146,7 @@ export function RegistryDetail({
|
|
|
145
146
|
compatibleTools,
|
|
146
147
|
files,
|
|
147
148
|
rootName,
|
|
149
|
+
fileStructureVariant,
|
|
148
150
|
onFetchContent,
|
|
149
151
|
aboveHeader,
|
|
150
152
|
maxWidth = 'max-w-[1440px]',
|
|
@@ -210,6 +212,7 @@ export function RegistryDetail({
|
|
|
210
212
|
{/* File structure */}
|
|
211
213
|
{files && onFetchContent && rootName && (
|
|
212
214
|
<FileStructureSection
|
|
215
|
+
variant={fileStructureVariant}
|
|
213
216
|
files={files}
|
|
214
217
|
rootName={rootName}
|
|
215
218
|
onFetchContent={onFetchContent}
|
|
@@ -176,8 +176,8 @@ function ResizableCode({
|
|
|
176
176
|
inherit: true,
|
|
177
177
|
rules: [],
|
|
178
178
|
colors: {
|
|
179
|
-
'editor.background': variant === 'filled' ? '#
|
|
180
|
-
'editorGutter.background': variant === 'filled' ? '#
|
|
179
|
+
'editor.background': variant === 'filled' ? '#000000' : '#00000000',
|
|
180
|
+
'editorGutter.background': variant === 'filled' ? '#000000' : '#00000000',
|
|
181
181
|
'editor.lineHighlightBackground': '#00000000',
|
|
182
182
|
'editor.lineHighlightBorder': '#00000000',
|
|
183
183
|
},
|
|
@@ -12,7 +12,7 @@ export interface SegmentedToggleProps<T extends string> {
|
|
|
12
12
|
options: SegmentedToggleOption<T>[]
|
|
13
13
|
value: T
|
|
14
14
|
onChange: (value: T) => void
|
|
15
|
-
accentColor?: 'blue' | '
|
|
15
|
+
accentColor?: 'blue' | 'green' | 'red' | 'orange' | 'cyan' | 'yellow' | 'purple' | 'indigo' | 'emerald' | 'amber' | 'violet' | 'neutral' | 'sky' | 'pink' | 'teal'
|
|
16
16
|
/** Visual style: 'filled' (default) has a container background, 'outline' is transparent */
|
|
17
17
|
variant?: 'filled' | 'outline'
|
|
18
18
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
@@ -51,38 +51,56 @@ const ROUNDING_CLASSES = {
|
|
|
51
51
|
|
|
52
52
|
const ACTIVE_COLORS: Record<string, string> = {
|
|
53
53
|
blue: 'bg-blue-500/20 text-blue-300 border-blue-500/40',
|
|
54
|
-
purple: 'bg-purple-500/20 text-purple-300 border-purple-500/40',
|
|
55
|
-
orange: 'bg-orange-500/20 text-orange-300 border-orange-500/40',
|
|
56
54
|
green: 'bg-green-500/20 text-green-300 border-green-500/40',
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
red: 'bg-red-500/20 text-red-300 border-red-500/40',
|
|
56
|
+
orange: 'bg-orange-500/20 text-orange-300 border-orange-500/40',
|
|
57
|
+
cyan: 'bg-cyan-500/20 text-cyan-300 border-cyan-500/40',
|
|
58
|
+
yellow: 'bg-yellow-500/20 text-yellow-300 border-yellow-500/40',
|
|
59
|
+
purple: 'bg-purple-500/20 text-purple-300 border-purple-500/40',
|
|
60
|
+
indigo: 'bg-indigo-500/20 text-indigo-300 border-indigo-500/40',
|
|
59
61
|
emerald: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/40',
|
|
60
|
-
|
|
62
|
+
amber: 'bg-amber-500/20 text-amber-300 border-amber-500/40',
|
|
63
|
+
violet: 'bg-violet-500/20 text-violet-300 border-violet-500/40',
|
|
64
|
+
neutral: 'bg-neutral-500/20 text-neutral-300 border-neutral-500/40',
|
|
61
65
|
sky: 'bg-sky-500/20 text-sky-300 border-sky-500/40',
|
|
66
|
+
pink: 'bg-pink-500/20 text-pink-300 border-pink-500/40',
|
|
67
|
+
teal: 'bg-teal-500/20 text-teal-300 border-teal-500/40',
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
const HOVER_COLORS: Record<string, string> = {
|
|
65
71
|
blue: 'hover:bg-blue-500/15 hover:text-blue-300',
|
|
66
|
-
purple: 'hover:bg-purple-500/15 hover:text-purple-300',
|
|
67
|
-
orange: 'hover:bg-orange-500/15 hover:text-orange-300',
|
|
68
72
|
green: 'hover:bg-green-500/15 hover:text-green-300',
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
red: 'hover:bg-red-500/15 hover:text-red-300',
|
|
74
|
+
orange: 'hover:bg-orange-500/15 hover:text-orange-300',
|
|
75
|
+
cyan: 'hover:bg-cyan-500/15 hover:text-cyan-300',
|
|
76
|
+
yellow: 'hover:bg-yellow-500/15 hover:text-yellow-300',
|
|
77
|
+
purple: 'hover:bg-purple-500/15 hover:text-purple-300',
|
|
78
|
+
indigo: 'hover:bg-indigo-500/15 hover:text-indigo-300',
|
|
71
79
|
emerald: 'hover:bg-emerald-500/15 hover:text-emerald-300',
|
|
72
|
-
|
|
80
|
+
amber: 'hover:bg-amber-500/15 hover:text-amber-300',
|
|
81
|
+
violet: 'hover:bg-violet-500/15 hover:text-violet-300',
|
|
82
|
+
neutral: 'hover:bg-neutral-500/15 hover:text-neutral-300',
|
|
73
83
|
sky: 'hover:bg-sky-500/15 hover:text-sky-300',
|
|
84
|
+
pink: 'hover:bg-pink-500/15 hover:text-pink-300',
|
|
85
|
+
teal: 'hover:bg-teal-500/15 hover:text-teal-300',
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
const OUTLINE_CONTAINER: Record<string, string> = {
|
|
77
89
|
blue: 'flex items-center border border-blue-500/50 rounded-md',
|
|
78
|
-
purple: 'flex items-center border border-purple-500/50 rounded-md',
|
|
79
|
-
orange: 'flex items-center border border-orange-500/50 rounded-md',
|
|
80
90
|
green: 'flex items-center border border-green-500/50 rounded-md',
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
red: 'flex items-center border border-red-500/50 rounded-md',
|
|
92
|
+
orange: 'flex items-center border border-orange-500/50 rounded-md',
|
|
93
|
+
cyan: 'flex items-center border border-cyan-500/50 rounded-md',
|
|
94
|
+
yellow: 'flex items-center border border-yellow-500/50 rounded-md',
|
|
95
|
+
purple: 'flex items-center border border-purple-500/50 rounded-md',
|
|
96
|
+
indigo: 'flex items-center border border-indigo-500/50 rounded-md',
|
|
83
97
|
emerald: 'flex items-center border border-emerald-500/50 rounded-md',
|
|
84
|
-
|
|
98
|
+
amber: 'flex items-center border border-amber-500/50 rounded-md',
|
|
99
|
+
violet: 'flex items-center border border-violet-500/50 rounded-md',
|
|
100
|
+
neutral: 'flex items-center border border-neutral-500/50 rounded-md',
|
|
85
101
|
sky: 'flex items-center border border-sky-500/50 rounded-md',
|
|
102
|
+
pink: 'flex items-center border border-pink-500/50 rounded-md',
|
|
103
|
+
teal: 'flex items-center border border-teal-500/50 rounded-md',
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
export function SegmentedToggle<T extends string>({
|
package/components/ui/select.tsx
CHANGED
|
@@ -134,7 +134,7 @@ export function Select<T extends string | number = string>({
|
|
|
134
134
|
{isOpen && menuPos && createPortal(
|
|
135
135
|
<div
|
|
136
136
|
ref={menuRef}
|
|
137
|
-
className={`fixed z-[9999] whitespace-nowrap ${v.menuBg}
|
|
137
|
+
className={`fixed z-[9999] whitespace-nowrap ${v.menuBg} border ${FORM_COLORS[color].border} rounded-lg shadow-xl overflow-hidden`}
|
|
138
138
|
style={{
|
|
139
139
|
top: menuPos.top,
|
|
140
140
|
left: align === 'right' ? undefined : menuPos.left,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cn } from '../lib/cn.ts'
|
|
2
|
+
|
|
3
|
+
export interface SettingsCardProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
className?: string
|
|
6
|
+
title?: string
|
|
7
|
+
description?: string
|
|
8
|
+
testId?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function SettingsCard({ children, className, title, description, testId }: SettingsCardProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={cn('bg-neutral-900 border border-neutral-800 rounded-lg p-4 space-y-4', className)}
|
|
15
|
+
data-testid={testId}
|
|
16
|
+
>
|
|
17
|
+
{title && (
|
|
18
|
+
<div>
|
|
19
|
+
<h3 className="text-sm font-medium text-neutral-200">{title}</h3>
|
|
20
|
+
{description && <p className="text-sm text-neutral-500 mt-1">{description}</p>}
|
|
21
|
+
</div>
|
|
22
|
+
)}
|
|
23
|
+
{!title && description && <p className="text-sm text-neutral-500">{description}</p>}
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|