@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.
@@ -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
  }
@@ -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
- * - 13 color variants
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
- * - 13 color variants
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' | 'purple' | 'orange' | 'green' | 'pink' | 'amber' | 'emerald' | 'teal' | 'sky'
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', purple: 'text-purple-400', orange: 'text-orange-400',
12
- green: 'text-green-400', pink: 'text-pink-400', amber: 'text-amber-400',
13
- emerald: 'text-emerald-400', teal: 'text-teal-400', sky: 'text-sky-400',
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 = selectedFilePath ?? firstFilePath
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
- return (
307
- <div ref={sectionRef}>
308
- <h3 className="text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">File Structure</h3>
309
- <div className="flex gap-3" style={{ height: `${effectiveHeight}px` }}>
310
- {/* Tree panel */}
311
- <div className={`flex flex-col bg-neutral-900 border border-neutral-700 rounded-lg overflow-hidden ${effectiveFilePath ? 'w-1/3 shrink-0' : 'flex-1'}`}>
312
- <div className="flex items-center px-3 py-2 border-b border-neutral-700 shrink-0 gap-2 min-w-0">
313
- <FolderTree className={`w-3.5 h-3.5 shrink-0 ${ACCENT_ICON[accentColor]}`} />
314
- <span className="text-xs text-neutral-200 truncate flex-1">Files</span>
315
- <CollapseButton
316
- collapsed={allCollapsed}
317
- onToggle={() => setExpandedPaths(allCollapsed ? new Set(allDirPaths) : new Set())}
318
- />
319
- </div>
320
- <div className="flex-1 overflow-y-auto p-3">
321
- <FileTree
322
- nodes={files}
323
- rootName={rootName}
324
- selectedPath={effectiveFilePath}
325
- onSelectFile={handleSelectFile}
326
- expandedPaths={expandedPaths}
327
- onTogglePath={togglePath}
328
- accentColor={accentColor}
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
- </div>
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
- {/* Preview panel */}
334
- {effectiveFilePath && (
335
- <div className="flex-1 flex flex-col bg-neutral-900 border border-neutral-700 rounded-lg overflow-hidden">
336
- <div className="flex items-center px-3 py-2 border-b border-neutral-700 shrink-0 gap-2 min-w-0">
337
- <FileCode className={`w-3.5 h-3.5 shrink-0 ${ACCENT_ICON[accentColor]}`} />
338
- <span className="text-xs text-neutral-200 truncate flex-1">{selectedFileName}</span>
339
- {showToggle && (
340
- <SegmentedToggle
341
- options={toggleOptions}
342
- value={mode}
343
- onChange={setMode}
344
- accentColor={accentColor}
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
- </div>
362
- )}
422
+ )}
423
+ </div>
363
424
  </div>
364
- {/* Resize handle */}
365
- <div
366
- onMouseDown={handleResizeStart}
367
- className="h-4 -mt-1.5 cursor-grab active:cursor-grabbing flex items-center justify-center group"
368
- >
369
- <div className="w-10 h-1 rounded-full bg-neutral-700 group-hover:bg-neutral-500 group-hover:w-14 group-hover:h-1.5 transition-all" />
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 ${iconColorClass}`
130
- : `${base} cursor-pointer hover:bg-neutral-700/50 hover:text-neutral-200 ${iconColorClass}`
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)] backdrop-blur border ${FORM_COLORS[color].border} rounded-lg shadow-xl overflow-hidden`}>
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',
@@ -5,7 +5,7 @@
5
5
  * Matches the SeedrBadges border-focused style: colored border + text, no background.
6
6
  *
7
7
  * Features:
8
- * - 14 color variants
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' ? '#262626' : '#00000000',
180
- 'editorGutter.background': variant === 'filled' ? '#262626' : '#00000000',
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' | 'purple' | 'orange' | 'green' | 'pink' | 'amber' | 'emerald' | 'teal' | 'sky'
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
- pink: 'bg-pink-500/20 text-pink-300 border-pink-500/40',
58
- amber: 'bg-amber-500/20 text-amber-300 border-amber-500/40',
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
- teal: 'bg-teal-500/20 text-teal-300 border-teal-500/40',
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
- pink: 'hover:bg-pink-500/15 hover:text-pink-300',
70
- amber: 'hover:bg-amber-500/15 hover:text-amber-300',
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
- teal: 'hover:bg-teal-500/15 hover:text-teal-300',
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
- pink: 'flex items-center border border-pink-500/50 rounded-md',
82
- amber: 'flex items-center border border-amber-500/50 rounded-md',
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
- teal: 'flex items-center border border-teal-500/50 rounded-md',
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>({
@@ -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} backdrop-blur border ${FORM_COLORS[color].border} rounded-lg shadow-xl overflow-hidden`}
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
+ }