@toolr/ui-design 0.1.5 → 0.1.7
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/hooks/use-click-outside.ts +10 -3
- package/components/hooks/use-modal-behavior.ts +24 -0
- package/components/hooks/use-navigation-history.ts +7 -2
- package/components/hooks/use-resizable-sidebar.ts +38 -0
- package/components/lib/ai-tools.tsx +1 -1
- package/components/lib/form-colors.ts +40 -0
- package/components/sections/ai-tools-paths/tools-paths-panel.tsx +7 -7
- package/components/sections/captured-issues/captured-issues-panel.tsx +13 -13
- package/components/sections/captured-issues/use-captured-issues.ts +9 -3
- 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 +8 -44
- package/components/sections/prompt-editor/index.ts +0 -7
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +9 -45
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +11 -43
- 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 +74 -48
- package/components/settings/SettingsHeader.tsx +1 -2
- package/components/settings/SettingsTreeNav.tsx +31 -16
- package/components/ui/action-dialog.tsx +12 -56
- package/components/ui/badge.tsx +8 -24
- package/components/ui/bottom-panel-header.tsx +4 -4
- package/components/ui/breadcrumb.tsx +8 -68
- package/components/ui/checkbox.tsx +2 -16
- package/components/ui/collapsible-section.tsx +4 -42
- package/components/ui/confirm-badge.tsx +3 -20
- package/components/ui/cookie-consent.tsx +21 -5
- package/components/ui/debounce-border-overlay.tsx +31 -0
- package/components/ui/detail-section.tsx +5 -22
- package/components/ui/editor-placeholder-card.tsx +17 -16
- package/components/ui/editor-toolbar.tsx +12 -0
- package/components/ui/execution-details-panel.tsx +8 -13
- package/components/ui/extension-list-card.tsx +3 -3
- package/components/ui/file-structure-section.tsx +20 -35
- package/components/ui/file-tree.tsx +4 -14
- package/components/ui/files-panel.tsx +28 -18
- package/components/ui/filter-dropdown.tsx +5 -5
- package/components/ui/form-actions.tsx +7 -6
- package/components/ui/frontmatter-form-header.tsx +4 -4
- package/components/ui/icon-button.tsx +3 -2
- package/components/ui/input.tsx +15 -31
- package/components/ui/label.tsx +7 -21
- package/components/ui/layout-tab-bar.tsx +4 -4
- package/components/ui/modal.tsx +5 -17
- package/components/ui/nav-card.tsx +5 -20
- package/components/ui/navigation-bar.tsx +13 -74
- package/components/ui/number-input.tsx +4 -4
- package/components/ui/registry-browser.tsx +10 -24
- package/components/ui/registry-card.tsx +16 -20
- package/components/ui/registry-detail.tsx +6 -6
- package/components/ui/resizable-textarea.tsx +13 -35
- package/components/ui/segmented-toggle.tsx +6 -5
- package/components/ui/select.tsx +7 -16
- package/components/ui/selection-grid.tsx +6 -54
- package/components/ui/setting-row.tsx +2 -4
- package/components/ui/settings-card.tsx +3 -3
- package/components/ui/settings-info-box.tsx +6 -23
- 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 +6 -17
- package/components/ui/tab-bar.tsx +5 -31
- package/components/ui/toggle.tsx +3 -19
- package/components/ui/tooltip.tsx +9 -21
- package/dist/content.js +14 -14
- package/dist/index.d.ts +71 -141
- package/dist/index.js +1634 -2450
- package/dist/tokens/primitives.css +9 -2
- package/index.ts +8 -7
- package/package.json +13 -3
- package/tokens/primitives.css +9 -2
- package/components/sections/prompt-editor/use-prompt-editor.ts +0 -131
package/components/ui/select.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useCallback, type ReactNode } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
3
|
import { ChevronDown, Check } from 'lucide-react'
|
|
4
|
+
import { useClickOutside } from '../hooks/use-click-outside.ts'
|
|
4
5
|
import { useDropdownMaxHeight } from '../hooks/use-dropdown-max-height.ts'
|
|
5
6
|
import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
|
|
6
7
|
|
|
@@ -30,10 +31,10 @@ const VARIANT_CLASSES = {
|
|
|
30
31
|
|
|
31
32
|
const SIZE_CLASSES = {
|
|
32
33
|
xss: 'h-[18px] px-1.5 text-xss',
|
|
33
|
-
xs: 'h-6 px-2 text-
|
|
34
|
-
sm: 'h-7 px-2 text-
|
|
35
|
-
md: 'h-8 px-3 text-
|
|
36
|
-
lg: 'h-9 px-3 text-
|
|
34
|
+
xs: 'h-6 px-2 text-sm',
|
|
35
|
+
sm: 'h-7 px-2 text-sm',
|
|
36
|
+
md: 'h-8 px-3 text-md',
|
|
37
|
+
lg: 'h-9 px-3 text-md',
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export function Select<T extends string | number = string>({
|
|
@@ -74,17 +75,7 @@ export function Select<T extends string | number = string>({
|
|
|
74
75
|
}, [])
|
|
75
76
|
|
|
76
77
|
// Close on click outside both the trigger and the portal menu
|
|
77
|
-
|
|
78
|
-
if (!isOpen) return
|
|
79
|
-
const handleClick = (event: MouseEvent) => {
|
|
80
|
-
const target = event.target as Node
|
|
81
|
-
if (ref.current?.contains(target)) return
|
|
82
|
-
if (menuRef.current?.contains(target)) return
|
|
83
|
-
close()
|
|
84
|
-
}
|
|
85
|
-
document.addEventListener('mousedown', handleClick)
|
|
86
|
-
return () => document.removeEventListener('mousedown', handleClick)
|
|
87
|
-
}, [isOpen, close])
|
|
78
|
+
useClickOutside([ref, menuRef], isOpen, close)
|
|
88
79
|
|
|
89
80
|
useEffect(() => {
|
|
90
81
|
if (highlightIdx >= 0 && menuRef.current) {
|
|
@@ -152,7 +143,7 @@ export function Select<T extends string | number = string>({
|
|
|
152
143
|
data-idx={idx}
|
|
153
144
|
onClick={() => { onChange(opt.value); close() }}
|
|
154
145
|
onPointerEnter={() => setHighlightIdx(idx)}
|
|
155
|
-
className={`w-full flex items-center gap-2 px-3 py-1.5 text-
|
|
146
|
+
className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
|
|
156
147
|
isHighlighted
|
|
157
148
|
? `${FORM_COLORS[color].selectedBg} text-neutral-200`
|
|
158
149
|
: isSelected ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
|
|
@@ -1,54 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Check, Settings, Code, Folder, File, Terminal, Globe, Database, Cloud,
|
|
3
|
-
Sparkles, Zap, Shield, ShieldCheck, Wand2, Star, Heart, Bell,
|
|
4
|
-
Search, Filter, Eye, Lock, User, Users, Image, Tag, Pin, Mail,
|
|
5
|
-
Send, Bookmark, Play, Pause, Bot, Plug, Puzzle, Webhook, Scan,
|
|
6
|
-
} from 'lucide-react'
|
|
7
|
-
import type { LucideIcon } from 'lucide-react'
|
|
8
|
-
import type { IconName } from './icon-button.tsx'
|
|
1
|
+
import { iconMap, type IconName } from './icon-button.tsx'
|
|
9
2
|
import type { ConfirmBadgeColor } from './confirm-badge.tsx'
|
|
10
3
|
import { cn } from '../lib/cn.ts'
|
|
11
4
|
import { AiToolIcon, AI_TOOL_NAMES, type AiToolKey } from '../lib/ai-tools.tsx'
|
|
12
5
|
|
|
13
|
-
const iconMap: Partial<Record<IconName, LucideIcon>> = {
|
|
14
|
-
'check': Check,
|
|
15
|
-
'settings': Settings,
|
|
16
|
-
'code': Code,
|
|
17
|
-
'folder': Folder,
|
|
18
|
-
'file': File,
|
|
19
|
-
'terminal': Terminal,
|
|
20
|
-
'globe': Globe,
|
|
21
|
-
'database': Database,
|
|
22
|
-
'cloud': Cloud,
|
|
23
|
-
'sparkles': Sparkles,
|
|
24
|
-
'zap': Zap,
|
|
25
|
-
'shield': Shield,
|
|
26
|
-
'shield-check': ShieldCheck,
|
|
27
|
-
'wand': Wand2,
|
|
28
|
-
'star': Star,
|
|
29
|
-
'heart': Heart,
|
|
30
|
-
'bell': Bell,
|
|
31
|
-
'search': Search,
|
|
32
|
-
'filter': Filter,
|
|
33
|
-
'eye': Eye,
|
|
34
|
-
'lock': Lock,
|
|
35
|
-
'user': User,
|
|
36
|
-
'users': Users,
|
|
37
|
-
'image': Image,
|
|
38
|
-
'tag': Tag,
|
|
39
|
-
'pin': Pin,
|
|
40
|
-
'mail': Mail,
|
|
41
|
-
'send': Send,
|
|
42
|
-
'bookmark': Bookmark,
|
|
43
|
-
'play': Play,
|
|
44
|
-
'pause': Pause,
|
|
45
|
-
'bot': Bot,
|
|
46
|
-
'plug': Plug,
|
|
47
|
-
'puzzle': Puzzle,
|
|
48
|
-
'webhook': Webhook,
|
|
49
|
-
'scan': Scan,
|
|
50
|
-
}
|
|
51
|
-
|
|
52
6
|
/* ── Preset logos (shared AiToolIcon) ─────────────────────── */
|
|
53
7
|
|
|
54
8
|
type IconProps = { className?: string; style?: React.CSSProperties }
|
|
@@ -142,9 +96,7 @@ function resolveColor(color?: string): string {
|
|
|
142
96
|
}
|
|
143
97
|
|
|
144
98
|
function autoColumns(itemCount: number): number {
|
|
145
|
-
|
|
146
|
-
if (itemCount <= 4) return itemCount
|
|
147
|
-
return 5
|
|
99
|
+
return Math.min(itemCount, 5)
|
|
148
100
|
}
|
|
149
101
|
|
|
150
102
|
/* ── Component ─────────────────────────────────────────────── */
|
|
@@ -251,10 +203,10 @@ function GridCard({ item, selected, onClick }: CardProps) {
|
|
|
251
203
|
/>
|
|
252
204
|
)}
|
|
253
205
|
|
|
254
|
-
<span className="text-
|
|
206
|
+
<span className="text-sm font-medium text-neutral-200 block">{item.name}</span>
|
|
255
207
|
|
|
256
208
|
{item.description && (
|
|
257
|
-
<span className="text-
|
|
209
|
+
<span className="text-sm text-neutral-500 block mt-0.5">{item.description}</span>
|
|
258
210
|
)}
|
|
259
211
|
</button>
|
|
260
212
|
)
|
|
@@ -291,9 +243,9 @@ function ListCard({ item, selected, onClick }: CardProps) {
|
|
|
291
243
|
)}
|
|
292
244
|
|
|
293
245
|
<div className="min-w-0">
|
|
294
|
-
<span className="text-
|
|
246
|
+
<span className="text-sm font-medium text-neutral-200 block">{item.name}</span>
|
|
295
247
|
{item.description && (
|
|
296
|
-
<span className="text-
|
|
248
|
+
<span className="text-sm text-neutral-500 block mt-0.5">{item.description}</span>
|
|
297
249
|
)}
|
|
298
250
|
</div>
|
|
299
251
|
</button>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - input: renders a text Input
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { Toggle, type ToggleColor, type ToggleSize
|
|
14
|
+
import { Toggle, type ToggleColor, type ToggleSize } from './toggle.tsx'
|
|
15
15
|
import { Select, type SelectOption } from './select.tsx'
|
|
16
16
|
import { Input } from './input.tsx'
|
|
17
17
|
|
|
@@ -28,7 +28,6 @@ interface SettingRowToggle extends SettingRowBase {
|
|
|
28
28
|
onChange: (checked: boolean) => void
|
|
29
29
|
color?: ToggleColor
|
|
30
30
|
size?: ToggleSize
|
|
31
|
-
variant?: ToggleVariant
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
interface SettingRowSelect extends SettingRowBase {
|
|
@@ -58,7 +57,7 @@ export function SettingRow(props: SettingRowProps) {
|
|
|
58
57
|
<div className={`flex items-start justify-between gap-4 ${className}`}>
|
|
59
58
|
<div>
|
|
60
59
|
<label className="text-neutral-200 leading-7">{label}</label>
|
|
61
|
-
{description && <p className="text-
|
|
60
|
+
{description && <p className="text-md text-neutral-500">{description}</p>}
|
|
62
61
|
</div>
|
|
63
62
|
{props.type === 'toggle' && (
|
|
64
63
|
<Toggle
|
|
@@ -67,7 +66,6 @@ export function SettingRow(props: SettingRowProps) {
|
|
|
67
66
|
disabled={disabled}
|
|
68
67
|
color={props.color}
|
|
69
68
|
size={props.size}
|
|
70
|
-
variant={props.variant}
|
|
71
69
|
/>
|
|
72
70
|
)}
|
|
73
71
|
{props.type === 'select' && (
|
|
@@ -16,11 +16,11 @@ export function SettingsCard({ children, className, title, description, testId }
|
|
|
16
16
|
>
|
|
17
17
|
{title && (
|
|
18
18
|
<div>
|
|
19
|
-
<h3 className="text-
|
|
20
|
-
{description && <p className="text-
|
|
19
|
+
<h3 className="text-md font-medium text-neutral-200">{title}</h3>
|
|
20
|
+
{description && <p className="text-md text-neutral-500 mt-1">{description}</p>}
|
|
21
21
|
</div>
|
|
22
22
|
)}
|
|
23
|
-
{!title && description && <p className="text-
|
|
23
|
+
{!title && description && <p className="text-md text-neutral-500">{description}</p>}
|
|
24
24
|
{children}
|
|
25
25
|
</div>
|
|
26
26
|
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Info, AlertTriangle, CheckCircle, AlertCircle } from 'lucide-react'
|
|
2
|
+
import { ACCENT_TEXT, type AccentColor } from '../lib/form-colors.ts'
|
|
2
3
|
import { cn } from '../lib/cn.ts'
|
|
3
4
|
|
|
4
|
-
export type SettingsInfoBoxColor =
|
|
5
|
+
export type SettingsInfoBoxColor = AccentColor
|
|
5
6
|
|
|
6
7
|
export interface SettingsInfoBoxProps {
|
|
7
8
|
children: React.ReactNode
|
|
@@ -10,7 +11,7 @@ export interface SettingsInfoBoxProps {
|
|
|
10
11
|
testId?: string
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
+
const INFO_BOX_ICONS: Record<SettingsInfoBoxColor, typeof Info> = {
|
|
14
15
|
neutral: Info,
|
|
15
16
|
blue: Info,
|
|
16
17
|
amber: AlertTriangle,
|
|
@@ -28,24 +29,6 @@ const iconMap: Record<SettingsInfoBoxColor, typeof Info> = {
|
|
|
28
29
|
teal: Info,
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const iconColorMap: Record<SettingsInfoBoxColor, string> = {
|
|
32
|
-
neutral: 'text-neutral-500',
|
|
33
|
-
blue: 'text-blue-400',
|
|
34
|
-
amber: 'text-amber-400',
|
|
35
|
-
green: 'text-green-400',
|
|
36
|
-
red: 'text-red-400',
|
|
37
|
-
orange: 'text-orange-400',
|
|
38
|
-
cyan: 'text-cyan-400',
|
|
39
|
-
yellow: 'text-yellow-400',
|
|
40
|
-
purple: 'text-purple-400',
|
|
41
|
-
indigo: 'text-indigo-400',
|
|
42
|
-
emerald: 'text-emerald-400',
|
|
43
|
-
violet: 'text-violet-400',
|
|
44
|
-
sky: 'text-sky-400',
|
|
45
|
-
pink: 'text-pink-400',
|
|
46
|
-
teal: 'text-teal-400',
|
|
47
|
-
}
|
|
48
|
-
|
|
49
32
|
const borderColorMap: Record<SettingsInfoBoxColor, string> = {
|
|
50
33
|
neutral: 'border-l-neutral-600',
|
|
51
34
|
blue: 'border-l-blue-500',
|
|
@@ -65,7 +48,7 @@ const borderColorMap: Record<SettingsInfoBoxColor, string> = {
|
|
|
65
48
|
}
|
|
66
49
|
|
|
67
50
|
export function SettingsInfoBox({ children, color = 'neutral', className, testId }: SettingsInfoBoxProps) {
|
|
68
|
-
const Icon =
|
|
51
|
+
const Icon = INFO_BOX_ICONS[color]
|
|
69
52
|
|
|
70
53
|
return (
|
|
71
54
|
<div
|
|
@@ -73,8 +56,8 @@ export function SettingsInfoBox({ children, color = 'neutral', className, testId
|
|
|
73
56
|
style={{ paddingLeft: 10 }}
|
|
74
57
|
data-testid={testId}
|
|
75
58
|
>
|
|
76
|
-
<Icon className={cn('w-4 h-4 mt-0.5 shrink-0',
|
|
77
|
-
<div className="text-
|
|
59
|
+
<Icon className={cn('w-4 h-4 mt-0.5 shrink-0', ACCENT_TEXT[color])} />
|
|
60
|
+
<div className="text-md text-neutral-500">{children}</div>
|
|
78
61
|
</div>
|
|
79
62
|
)
|
|
80
63
|
}
|
|
@@ -15,7 +15,7 @@ export interface SettingsSectionTitleProps {
|
|
|
15
15
|
export function SettingsSectionTitle({ children, className = '', testId }: SettingsSectionTitleProps) {
|
|
16
16
|
return (
|
|
17
17
|
<h3
|
|
18
|
-
className={`text-
|
|
18
|
+
className={`text-sm font-medium text-neutral-400 uppercase tracking-wider ${className}`}
|
|
19
19
|
data-testid={testId}
|
|
20
20
|
>
|
|
21
21
|
{children}
|
|
@@ -48,7 +48,7 @@ export function SnapshotCard({
|
|
|
48
48
|
|
|
49
49
|
<div className="p-4">
|
|
50
50
|
<div className="flex items-start justify-between gap-2">
|
|
51
|
-
<h3 className="text-
|
|
51
|
+
<h3 className="text-md font-medium text-neutral-200 truncate">{title}</h3>
|
|
52
52
|
<Label
|
|
53
53
|
text={statusLabelConfig[status].text}
|
|
54
54
|
color={statusLabelConfig[status].color}
|
|
@@ -59,19 +59,19 @@ export function SnapshotCard({
|
|
|
59
59
|
</div>
|
|
60
60
|
|
|
61
61
|
{timestamp && (
|
|
62
|
-
<p className="mt-1 text-
|
|
62
|
+
<p className="mt-1 text-xs text-neutral-500">{timestamp}</p>
|
|
63
63
|
)}
|
|
64
64
|
|
|
65
65
|
{description && (
|
|
66
|
-
<p className="mt-2 text-
|
|
66
|
+
<p className="mt-2 text-sm text-neutral-500 leading-relaxed line-clamp-2">{description}</p>
|
|
67
67
|
)}
|
|
68
68
|
|
|
69
69
|
{stats && stats.length > 0 && (
|
|
70
70
|
<div className="mt-3 grid grid-cols-2 gap-x-4 gap-y-2">
|
|
71
71
|
{stats.map((stat) => (
|
|
72
72
|
<div key={stat.label}>
|
|
73
|
-
<p className="text-
|
|
74
|
-
<p className="text-
|
|
73
|
+
<p className="text-xs text-neutral-500">{stat.label}</p>
|
|
74
|
+
<p className="text-sm font-medium text-neutral-200">{stat.value}</p>
|
|
75
75
|
</div>
|
|
76
76
|
))}
|
|
77
77
|
</div>
|
|
@@ -83,7 +83,7 @@ export function SnapshotCard({
|
|
|
83
83
|
<button
|
|
84
84
|
type="button"
|
|
85
85
|
onClick={onSync}
|
|
86
|
-
className="flex items-center gap-1.5 px-2.5 py-1 text-
|
|
86
|
+
className="flex items-center gap-1.5 px-2.5 py-1 text-sm font-medium rounded-md bg-blue-400/15 text-blue-400 hover:bg-blue-400/25 transition-colors cursor-pointer"
|
|
87
87
|
>
|
|
88
88
|
<RefreshCw className={cn('w-3 h-3', status === 'pending' && 'animate-spin')} />
|
|
89
89
|
Sync
|
|
@@ -93,7 +93,7 @@ export function SnapshotCard({
|
|
|
93
93
|
<button
|
|
94
94
|
type="button"
|
|
95
95
|
onClick={onView}
|
|
96
|
-
className="flex items-center gap-1.5 px-2.5 py-1 text-
|
|
96
|
+
className="flex items-center gap-1.5 px-2.5 py-1 text-sm font-medium rounded-md text-neutral-400 hover:bg-neutral-700 transition-colors cursor-pointer"
|
|
97
97
|
>
|
|
98
98
|
<Eye className="w-3 h-3" />
|
|
99
99
|
View
|
|
@@ -63,19 +63,19 @@ export function SnippetsPanel({
|
|
|
63
63
|
return (
|
|
64
64
|
<div className={cn('flex flex-col bg-neutral-800 rounded-lg overflow-hidden', className)}>
|
|
65
65
|
<div className="flex items-center justify-between px-3 py-2 border-b border-neutral-700">
|
|
66
|
-
<span className="text-
|
|
67
|
-
<span className="text-
|
|
66
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-neutral-500">Snippets</span>
|
|
67
|
+
<span className="text-xs text-neutral-500">{snippets.length} snippets</span>
|
|
68
68
|
</div>
|
|
69
69
|
{showSearch && (
|
|
70
70
|
<div className="px-2 py-2 border-b border-neutral-700">
|
|
71
|
-
<div className="flex items-center gap-1.5 px-2 py-1 bg-[var(--background)] border border-neutral-700 rounded text-
|
|
71
|
+
<div className="flex items-center gap-1.5 px-2 py-1 bg-[var(--background)] border border-neutral-700 rounded text-sm">
|
|
72
72
|
<Search className="w-3 h-3 text-neutral-500 shrink-0" />
|
|
73
73
|
<input
|
|
74
74
|
type="text"
|
|
75
75
|
placeholder="Search snippets..."
|
|
76
76
|
value={searchQuery}
|
|
77
77
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
78
|
-
className="flex-1 bg-transparent text-neutral-200 placeholder-neutral-500 outline-none text-
|
|
78
|
+
className="flex-1 bg-transparent text-neutral-200 placeholder-neutral-500 outline-none text-sm"
|
|
79
79
|
/>
|
|
80
80
|
</div>
|
|
81
81
|
</div>
|
|
@@ -90,7 +90,7 @@ export function SnippetsPanel({
|
|
|
90
90
|
/>
|
|
91
91
|
))}
|
|
92
92
|
{filteredSnippets.length === 0 && (
|
|
93
|
-
<p className="text-
|
|
93
|
+
<p className="text-xs text-neutral-500 text-center py-4">No snippets found</p>
|
|
94
94
|
)}
|
|
95
95
|
</div>
|
|
96
96
|
</div>
|
|
@@ -110,10 +110,10 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
|
|
|
110
110
|
<div className="rounded-md border border-neutral-700 bg-neutral-900 hover:border-neutral-600 transition-colors">
|
|
111
111
|
<div className="flex items-center justify-between gap-2 px-2.5 py-1.5">
|
|
112
112
|
<div className="flex items-center gap-2 min-w-0">
|
|
113
|
-
<span className="text-
|
|
113
|
+
<span className="text-sm font-medium text-neutral-200 truncate">{snippet.label}</span>
|
|
114
114
|
{snippet.language && (
|
|
115
115
|
<span
|
|
116
|
-
className="px-1.5 py-0.5 text-
|
|
116
|
+
className="px-1.5 py-0.5 text-xs font-medium rounded shrink-0"
|
|
117
117
|
style={{ color: langColor, backgroundColor: `${langColor}20` }}
|
|
118
118
|
>
|
|
119
119
|
{snippet.language}
|
|
@@ -140,17 +140,17 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
|
|
|
140
140
|
</div>
|
|
141
141
|
</div>
|
|
142
142
|
{snippet.description && (
|
|
143
|
-
<p className="px-2.5 pb-1.5 text-
|
|
143
|
+
<p className="px-2.5 pb-1.5 text-xs text-neutral-400 leading-relaxed">{snippet.description}</p>
|
|
144
144
|
)}
|
|
145
145
|
<div className="mx-2.5 mb-2 rounded bg-[var(--background)] border border-neutral-700 overflow-hidden">
|
|
146
|
-
<pre className="p-2 text-
|
|
146
|
+
<pre className="p-2 text-xs text-neutral-400 leading-relaxed overflow-x-auto max-h-24">
|
|
147
147
|
<code>{snippet.code}</code>
|
|
148
148
|
</pre>
|
|
149
149
|
</div>
|
|
150
150
|
{snippet.tags && snippet.tags.length > 0 && (
|
|
151
151
|
<div className="flex flex-wrap gap-1 px-2.5 pb-2">
|
|
152
152
|
{snippet.tags.map((tag) => (
|
|
153
|
-
<span key={tag} className="px-1.5 py-0.5 text-
|
|
153
|
+
<span key={tag} className="px-1.5 py-0.5 text-xs rounded bg-neutral-700 text-neutral-500">
|
|
154
154
|
{tag}
|
|
155
155
|
</span>
|
|
156
156
|
))}
|
|
@@ -74,7 +74,7 @@ export function SortDropdown({
|
|
|
74
74
|
<div className="relative flex items-center" ref={ref} onKeyDown={handleKeyDown}>
|
|
75
75
|
<button
|
|
76
76
|
onClick={() => setIsOpen(!isOpen)}
|
|
77
|
-
className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-
|
|
77
|
+
className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-sm transition-colors cursor-pointer ${FORM_COLORS[color].border} text-neutral-200 ${FORM_COLORS[color].hover}`}
|
|
78
78
|
>
|
|
79
79
|
<span
|
|
80
80
|
className={`${FORM_COLORS[color].accent} hover:brightness-125 transition-colors`}
|
|
@@ -94,7 +94,7 @@ export function SortDropdown({
|
|
|
94
94
|
key={f.value}
|
|
95
95
|
onClick={() => { onFieldChange(f.value); setIsOpen(false) }}
|
|
96
96
|
onPointerEnter={() => setHighlightIdx(idx)}
|
|
97
|
-
className={`w-full flex items-center gap-2 px-3 py-1.5 text-
|
|
97
|
+
className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
|
|
98
98
|
idx === highlightIdx ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : field === f.value ? `${FORM_COLORS[color].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
|
|
99
99
|
}`}
|
|
100
100
|
>
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
/** Status card displaying a list of labeled items with color-coded status indicators. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
Settings, Shield, Database, Globe, Zap, Code, Terminal,
|
|
5
|
-
Star, Cloud, Bell, Heart, Sparkles, Bot, Plug,
|
|
6
|
-
} from 'lucide-react'
|
|
7
|
-
import type { LucideIcon } from 'lucide-react'
|
|
8
|
-
import type { IconName } from './icon-button.tsx'
|
|
3
|
+
import { iconMap, type IconName } from './icon-button.tsx'
|
|
9
4
|
import { cn } from '../lib/cn.ts'
|
|
10
5
|
|
|
11
|
-
const iconSubset: Partial<Record<IconName, LucideIcon>> = {
|
|
12
|
-
settings: Settings, shield: Shield, database: Database, globe: Globe,
|
|
13
|
-
zap: Zap, code: Code, terminal: Terminal, star: Star, cloud: Cloud,
|
|
14
|
-
bell: Bell, heart: Heart, sparkles: Sparkles, bot: Bot, plug: Plug,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
6
|
type StatusType = 'success' | 'warning' | 'error' | 'info' | 'neutral'
|
|
18
7
|
|
|
19
8
|
export interface StatusItem {
|
|
@@ -55,7 +44,7 @@ export function StatusCard({
|
|
|
55
44
|
action,
|
|
56
45
|
className,
|
|
57
46
|
}: StatusCardProps) {
|
|
58
|
-
const Icon = icon ?
|
|
47
|
+
const Icon = icon ? iconMap[icon] : undefined
|
|
59
48
|
|
|
60
49
|
return (
|
|
61
50
|
<div className={cn('rounded-lg border border-neutral-700 bg-neutral-800 overflow-hidden', className)}>
|
|
@@ -63,15 +52,15 @@ export function StatusCard({
|
|
|
63
52
|
{Icon && (
|
|
64
53
|
<Icon className="w-4 h-4 shrink-0" style={{ color: iconColor }} />
|
|
65
54
|
)}
|
|
66
|
-
<h3 className="text-
|
|
55
|
+
<h3 className="text-md font-medium text-neutral-200">{title}</h3>
|
|
67
56
|
</div>
|
|
68
57
|
|
|
69
58
|
<div className="divide-y divide-neutral-700/60">
|
|
70
59
|
{items.map((item) => (
|
|
71
60
|
<div key={item.label} className="flex items-center justify-between px-4 py-2.5">
|
|
72
|
-
<span className="text-
|
|
61
|
+
<span className="text-sm text-neutral-400">{item.label}</span>
|
|
73
62
|
<div className="flex items-center gap-2">
|
|
74
|
-
<span className={cn('text-
|
|
63
|
+
<span className={cn('text-sm font-medium', statusValueColor[item.status])}>
|
|
75
64
|
{item.value}
|
|
76
65
|
</span>
|
|
77
66
|
<span className={cn('w-2 h-2 rounded-full shrink-0', statusDotColor[item.status])} />
|
|
@@ -85,7 +74,7 @@ export function StatusCard({
|
|
|
85
74
|
<button
|
|
86
75
|
type="button"
|
|
87
76
|
onClick={action.onClick}
|
|
88
|
-
className="text-
|
|
77
|
+
className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer"
|
|
89
78
|
>
|
|
90
79
|
{action.label}
|
|
91
80
|
</button>
|
|
@@ -1,38 +1,12 @@
|
|
|
1
1
|
/** Tab bar with underline, pill, and card variants, closable tabs, color-coded icons, and auto-collapse to icon-only. */
|
|
2
2
|
|
|
3
3
|
import { useRef, useState, useEffect, useCallback } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
Globe, Star, Users, User, Tag, X,
|
|
7
|
-
Zap, Shield, Sparkles, Eye, Lock, Search, Heart,
|
|
8
|
-
} from 'lucide-react'
|
|
9
|
-
import type { LucideIcon } from 'lucide-react'
|
|
10
|
-
import type { IconName } from './icon-button.tsx'
|
|
4
|
+
import { X } from 'lucide-react'
|
|
5
|
+
import { iconMap, type IconName } from './icon-button.tsx'
|
|
11
6
|
import { Badge, type BadgeColor } from './badge.tsx'
|
|
12
7
|
import { cn } from '../lib/cn.ts'
|
|
13
8
|
import { Tooltip } from './tooltip.tsx'
|
|
14
9
|
|
|
15
|
-
const iconSubset: Partial<Record<IconName, LucideIcon>> = {
|
|
16
|
-
folder: Folder,
|
|
17
|
-
file: File,
|
|
18
|
-
settings: Settings,
|
|
19
|
-
code: Code,
|
|
20
|
-
terminal: Terminal,
|
|
21
|
-
database: Database,
|
|
22
|
-
globe: Globe,
|
|
23
|
-
star: Star,
|
|
24
|
-
users: Users,
|
|
25
|
-
user: User,
|
|
26
|
-
tag: Tag,
|
|
27
|
-
zap: Zap,
|
|
28
|
-
shield: Shield,
|
|
29
|
-
sparkles: Sparkles,
|
|
30
|
-
eye: Eye,
|
|
31
|
-
lock: Lock,
|
|
32
|
-
search: Search,
|
|
33
|
-
heart: Heart,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
10
|
export interface Tab {
|
|
37
11
|
id: string
|
|
38
12
|
label: string
|
|
@@ -55,8 +29,8 @@ export interface TabBarProps {
|
|
|
55
29
|
|
|
56
30
|
const sizeConfig = {
|
|
57
31
|
xss: { text: 'text-xss', icon: 'w-2.5 h-2.5', px: 'px-1.5', py: 'py-1', close: 'w-2.5 h-2.5', badgeSize: 'xss' as const, gap: 'gap-1' },
|
|
58
|
-
xs: { text: 'text-
|
|
59
|
-
sm: { text: 'text-
|
|
32
|
+
xs: { text: 'text-sm', icon: 'w-3 h-3', px: 'px-2', py: 'py-1', close: 'w-3 h-3', badgeSize: 'xs' as const, gap: 'gap-1' },
|
|
33
|
+
sm: { text: 'text-md', icon: 'w-3.5 h-3.5', px: 'px-3', py: 'py-1.5', close: 'w-3 h-3', badgeSize: 'sm' as const, gap: 'gap-1.5' },
|
|
60
34
|
md: { text: 'text-base', icon: 'w-4 h-4', px: 'px-4', py: 'py-2', close: 'w-3.5 h-3.5', badgeSize: 'md' as const, gap: 'gap-2' },
|
|
61
35
|
lg: { text: 'text-lg', icon: 'w-5 h-5', px: 'px-5', py: 'py-2.5', close: 'w-4 h-4', badgeSize: 'lg' as const, gap: 'gap-2' },
|
|
62
36
|
}
|
|
@@ -102,7 +76,7 @@ function estimateTabsWidth(tabs: Tab[], size: keyof typeof sizeConfig): number {
|
|
|
102
76
|
}
|
|
103
77
|
|
|
104
78
|
function TabIcon({ icon, size, color }: { icon: IconName; size: 'xss' | 'xs' | 'sm' | 'md' | 'lg'; color?: string }) {
|
|
105
|
-
const Icon =
|
|
79
|
+
const Icon = iconMap[icon]
|
|
106
80
|
if (!Icon) return null
|
|
107
81
|
const s = sizeConfig[size]
|
|
108
82
|
const c = getColors(color)
|
package/components/ui/toggle.tsx
CHANGED
|
@@ -7,22 +7,9 @@
|
|
|
7
7
|
* - List items - inline toggle controls
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
| 'red'
|
|
14
|
-
| 'orange'
|
|
15
|
-
| 'cyan'
|
|
16
|
-
| 'yellow'
|
|
17
|
-
| 'purple'
|
|
18
|
-
| 'indigo'
|
|
19
|
-
| 'emerald'
|
|
20
|
-
| 'amber'
|
|
21
|
-
| 'violet'
|
|
22
|
-
| 'neutral'
|
|
23
|
-
| 'sky'
|
|
24
|
-
| 'pink'
|
|
25
|
-
| 'teal'
|
|
10
|
+
import { type AccentColor } from '../lib/form-colors.ts'
|
|
11
|
+
|
|
12
|
+
export type ToggleColor = AccentColor
|
|
26
13
|
|
|
27
14
|
// Border colors per accent
|
|
28
15
|
const BORDER_COLORS: Record<ToggleColor, { idle: string; active: string }> = {
|
|
@@ -90,8 +77,6 @@ const TOGGLE_SIZES: Record<ToggleSize, { track: string; knob: string; translate:
|
|
|
90
77
|
lg: { track: 'w-14 h-7', knob: 'w-6 h-6', translate: 'translate-x-7' },
|
|
91
78
|
}
|
|
92
79
|
|
|
93
|
-
export type ToggleVariant = 'outline' | 'filled'
|
|
94
|
-
|
|
95
80
|
export interface ToggleProps {
|
|
96
81
|
checked: boolean
|
|
97
82
|
onChange: (checked: boolean) => void
|
|
@@ -99,7 +84,6 @@ export interface ToggleProps {
|
|
|
99
84
|
size?: ToggleSize
|
|
100
85
|
className?: string
|
|
101
86
|
color?: ToggleColor
|
|
102
|
-
variant?: ToggleVariant
|
|
103
87
|
/** Test ID for E2E testing */
|
|
104
88
|
testId?: string
|
|
105
89
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useState, useRef, useLayoutEffect, useEffect } from 'react'
|
|
1
|
+
import { useState, useRef, useLayoutEffect, useEffect, useCallback } from 'react'
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
3
|
import { createPortal } from 'react-dom'
|
|
4
|
+
import { useClickOutside } from '../hooks/use-click-outside.ts'
|
|
4
5
|
|
|
5
6
|
export interface TooltipContent {
|
|
6
7
|
title?: string
|
|
@@ -201,19 +202,8 @@ export function Tooltip({
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
// Click-outside dismissal for click trigger mode
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
207
|
-
if (
|
|
208
|
-
triggerRef.current && !triggerRef.current.contains(e.target as Node) &&
|
|
209
|
-
tooltipRef.current && !tooltipRef.current.contains(e.target as Node)
|
|
210
|
-
) {
|
|
211
|
-
setIsVisible(false)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
document.addEventListener('mousedown', handleClickOutside)
|
|
215
|
-
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
216
|
-
}, [trigger, isVisible])
|
|
205
|
+
const dismissClickTooltip = useCallback(() => setIsVisible(false), [])
|
|
206
|
+
useClickOutside([triggerRef, tooltipRef], isVisible && trigger === 'click', dismissClickTooltip)
|
|
217
207
|
|
|
218
208
|
const updatePosition = () => {
|
|
219
209
|
if (!triggerRef.current) return
|
|
@@ -262,18 +252,16 @@ export function Tooltip({
|
|
|
262
252
|
style={{
|
|
263
253
|
top: coords.top,
|
|
264
254
|
left: coords.left,
|
|
265
|
-
opacity:
|
|
266
|
-
visibility: isVisible ? 'visible' : 'hidden',
|
|
267
|
-
transition: 'opacity 150ms, visibility 150ms',
|
|
255
|
+
opacity: 1,
|
|
268
256
|
...(multiline ? { maxWidth: maxWidth ?? '80vw' } : {}),
|
|
269
257
|
...(maxHeight ? { maxHeight, overflowY: 'auto' as const } : {}),
|
|
270
258
|
}}
|
|
271
259
|
onMouseEnter={interactive && trigger !== 'click' ? show : undefined}
|
|
272
260
|
onMouseLeave={interactive && trigger !== 'click' ? hide : undefined}
|
|
273
261
|
>
|
|
274
|
-
{content.title && <p className="text-
|
|
275
|
-
<div className={`text-
|
|
276
|
-
{content.extra && <p className="text-
|
|
262
|
+
{content.title && <p className="text-md text-neutral-200 font-medium">{content.title}</p>}
|
|
263
|
+
<div className={`text-sm text-neutral-400 ${content.title ? 'mt-0.5' : ''}`}>{content.description}</div>
|
|
264
|
+
{content.extra && <p className="text-sm text-orange-400/70 mt-0.5">{content.extra}</p>}
|
|
277
265
|
<div className={arrowClasses} />
|
|
278
266
|
</div>
|
|
279
267
|
)
|
|
@@ -290,7 +278,7 @@ export function Tooltip({
|
|
|
290
278
|
>
|
|
291
279
|
{children}
|
|
292
280
|
</div>
|
|
293
|
-
{createPortal(tooltipContent, document.body)}
|
|
281
|
+
{isVisible && createPortal(tooltipContent, document.body)}
|
|
294
282
|
</>
|
|
295
283
|
)
|
|
296
284
|
}
|