@toolr/ui-design 0.1.7 → 0.1.9
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/ai-manifest.json +35 -20
- package/components/composites/dashboard-list-item.tsx +172 -0
- package/components/composites/dashboard-panel.tsx +218 -0
- package/components/content/info-panel-primitives.tsx +9 -8
- package/components/diagrams/diagram-utils.tsx +2 -1
- package/components/hooks/use-dropdown-portal.ts +39 -0
- package/components/hooks/use-modal-behavior.ts +32 -3
- package/components/lib/accent-context.ts +10 -0
- package/components/lib/{ai-tools.tsx → coding-agents.tsx} +23 -8
- package/components/lib/custom-icons.tsx +37 -0
- package/components/lib/git-providers.tsx +39 -0
- package/components/lib/theme-engine.ts +59 -10
- package/components/lib/toolr-brand.tsx +23 -9
- package/components/sections/captured-issues/captured-issues-panel.tsx +17 -8
- package/components/sections/{ai-tools-paths/tools-paths-panel.tsx → coding-agent-paths/agent-paths-panel.tsx} +70 -62
- package/components/sections/coding-agent-paths/index.ts +37 -0
- package/components/sections/{ai-tools-paths → coding-agent-paths}/types.ts +28 -28
- package/components/sections/coding-agent-paths/use-agent-paths.ts +159 -0
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +11 -10
- package/components/sections/golden-snapshots/golden-sync-panel.tsx +12 -3
- package/components/sections/golden-snapshots/snapshot-manager.tsx +9 -7
- package/components/sections/golden-snapshots/status-overview.tsx +8 -8
- package/components/sections/golden-snapshots/version-manager.tsx +6 -6
- package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +3 -3
- package/components/sections/prompt-editor/index.ts +1 -1
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +13 -5
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +18 -10
- package/components/sections/prompt-editor/types.ts +2 -2
- package/components/sections/report-bug/report-bug-form.tsx +12 -4
- package/components/sections/report-bug/screenshot-uploader.tsx +11 -3
- package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +12 -4
- package/components/sections/snapshot-browser/snapshot-tree.tsx +5 -4
- package/components/sections/snapshot-browser/types.ts +1 -1
- package/components/sections/snippets-editor/snippets-editor.tsx +16 -9
- package/components/settings/SettingsHeader.tsx +2 -2
- package/components/settings/SettingsPanel.tsx +11 -3
- package/components/settings/SettingsTreeNav.tsx +15 -9
- package/components/ui/action-dialog.tsx +37 -35
- package/components/ui/ai-action-button.tsx +12 -11
- package/components/ui/ai-execution-action-buttons.tsx +13 -5
- package/components/ui/badge.tsx +17 -6
- package/components/ui/bottom-panel-header.tsx +9 -5
- package/components/ui/breadcrumb.tsx +14 -6
- package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +14 -6
- package/components/ui/checkbox.tsx +23 -14
- package/components/ui/collapsible-section.tsx +38 -28
- package/components/ui/confirm-badge.tsx +17 -6
- package/components/ui/cookie-consent.tsx +13 -7
- package/components/ui/detail-section.tsx +24 -16
- package/components/ui/detail-view-wrapper.tsx +30 -22
- package/components/ui/editor-placeholder-card.tsx +28 -24
- package/components/ui/editor-toolbar.tsx +7 -4
- package/components/ui/execution-details-panel.tsx +10 -5
- package/components/ui/file-structure-section.tsx +3 -3
- package/components/ui/file-tree.tsx +7 -5
- package/components/ui/files-panel.tsx +147 -27
- package/components/ui/filter-dropdown.tsx +88 -75
- package/components/ui/form-actions.tsx +21 -11
- package/components/ui/frontmatter-form-header.tsx +10 -2
- package/components/ui/icon-button.tsx +27 -14
- package/components/ui/input.tsx +15 -7
- package/components/ui/label.tsx +9 -5
- package/components/ui/layout-tab-bar.tsx +11 -9
- package/components/ui/modal.tsx +26 -8
- package/components/ui/nav-card.tsx +7 -4
- package/components/ui/navigation-bar.tsx +40 -12
- package/components/ui/number-input.tsx +14 -4
- package/components/ui/project-explorer.tsx +666 -0
- package/components/ui/registry-browser.tsx +12 -1
- package/components/ui/registry-card.tsx +49 -42
- package/components/ui/registry-detail.tsx +34 -11
- package/components/ui/resizable-textarea.tsx +18 -11
- package/components/ui/scope-badge.tsx +18 -11
- package/components/ui/segmented-toggle.tsx +7 -2
- package/components/ui/select.tsx +17 -11
- package/components/ui/selection-grid.tsx +40 -37
- package/components/ui/setting-row.tsx +6 -4
- package/components/ui/settings-card.tsx +12 -5
- package/components/ui/settings-info-box.tsx +9 -6
- package/components/ui/settings-section-title.tsx +14 -2
- package/components/ui/snapshot-card.tsx +10 -2
- package/components/ui/snippets-panel.tsx +4 -2
- package/components/ui/sort-dropdown.tsx +45 -32
- package/components/ui/status-card.tsx +9 -1
- package/components/ui/tab-bar.tsx +26 -13
- package/components/ui/toggle.tsx +31 -17
- package/components/ui/tooltip.tsx +14 -6
- package/dist/content.js +8 -8
- package/dist/diagrams.d.ts +0 -1
- package/dist/index.d.ts +431 -186
- package/dist/index.js +3119 -1724
- package/dist/tokens/primitives.css +28 -6
- package/dist/tokens/semantic.css +15 -15
- package/dist/tokens/theme.css +23 -0
- package/index.ts +25 -11
- package/package.json +9 -1
- package/tokens/primitives.css +28 -6
- package/tokens/semantic.css +15 -15
- package/tokens/theme.css +23 -0
- package/components/sections/ai-tools-paths/index.ts +0 -37
- package/components/sections/ai-tools-paths/use-tools-paths.ts +0 -159
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Home, Folder, Lock, Eye } from 'lucide-react'
|
|
2
2
|
import { Label, type LabelColor } from './label.tsx'
|
|
3
|
+
import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
|
|
4
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
3
5
|
|
|
4
6
|
export type ScopeType = 'user' | 'project' | 'local' | 'read-only'
|
|
5
7
|
|
|
6
8
|
interface ScopeBadgeProps {
|
|
7
9
|
scope: ScopeType
|
|
8
10
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
11
|
+
accentColor?: FormColor
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
const scopeConfig: Record<
|
|
@@ -48,20 +51,24 @@ const scopeConfig: Record<
|
|
|
48
51
|
},
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
export function ScopeBadge({ scope, size = 'xs' }: ScopeBadgeProps) {
|
|
54
|
+
export function ScopeBadge({ scope, size = 'xs', accentColor }: ScopeBadgeProps) {
|
|
55
|
+
const contextAccent = useAccentColor()
|
|
56
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
52
57
|
const config = scopeConfig[scope]
|
|
53
58
|
const Icon = config.icon
|
|
54
59
|
|
|
55
60
|
return (
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
<AccentColorProvider value={effectiveColor}>
|
|
62
|
+
<Label
|
|
63
|
+
text={config.label}
|
|
64
|
+
accentColor={config.color}
|
|
65
|
+
IconComponent={Icon}
|
|
66
|
+
tooltip={{
|
|
67
|
+
title: config.title,
|
|
68
|
+
description: config.description,
|
|
69
|
+
}}
|
|
70
|
+
size={size}
|
|
71
|
+
/>
|
|
72
|
+
</AccentColorProvider>
|
|
66
73
|
)
|
|
67
74
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
import type { AccentColor } from '../lib/form-colors.ts'
|
|
3
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
3
4
|
import { Tooltip, type TooltipContent, type TooltipPosition } from './tooltip.tsx'
|
|
4
5
|
|
|
5
6
|
export interface SegmentedToggleOption<T extends string> {
|
|
@@ -108,7 +109,7 @@ export function SegmentedToggle<T extends string>({
|
|
|
108
109
|
options,
|
|
109
110
|
value,
|
|
110
111
|
onChange,
|
|
111
|
-
accentColor
|
|
112
|
+
accentColor: accentColorProp,
|
|
112
113
|
variant = 'outline',
|
|
113
114
|
size = 'sm',
|
|
114
115
|
tooltipPosition = 'bottom',
|
|
@@ -116,10 +117,12 @@ export function SegmentedToggle<T extends string>({
|
|
|
116
117
|
disabled = false,
|
|
117
118
|
disabledTooltip,
|
|
118
119
|
}: SegmentedToggleProps<T>) {
|
|
120
|
+
const contextAccent = useAccentColor()
|
|
121
|
+
const accentColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
119
122
|
const isOutline = variant === 'outline'
|
|
120
123
|
const containerClasses = isOutline
|
|
121
124
|
? OUTLINE_CONTAINER[accentColor] || OUTLINE_CONTAINER.blue
|
|
122
|
-
: 'flex items-center bg-neutral-
|
|
125
|
+
: 'flex items-center bg-neutral-960/50 border border-neutral-700 rounded-md'
|
|
123
126
|
|
|
124
127
|
const toggle = (
|
|
125
128
|
<div
|
|
@@ -136,6 +139,8 @@ export function SegmentedToggle<T extends string>({
|
|
|
136
139
|
return (
|
|
137
140
|
<Tooltip key={option.value} content={option.tooltip} position={tooltipPosition}>
|
|
138
141
|
<button
|
|
142
|
+
aria-pressed={isActive}
|
|
143
|
+
aria-label={option.label || (typeof option.tooltip.description === 'string' ? option.tooltip.description : undefined)}
|
|
139
144
|
onClick={() => onChange(option.value)}
|
|
140
145
|
disabled={disabled}
|
|
141
146
|
className={`flex items-center justify-center ${sizeClass} ${rounding} font-medium transition-all cursor-pointer ${
|
package/components/ui/select.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { ChevronDown, Check } from 'lucide-react'
|
|
|
4
4
|
import { useClickOutside } from '../hooks/use-click-outside.ts'
|
|
5
5
|
import { useDropdownMaxHeight } from '../hooks/use-dropdown-max-height.ts'
|
|
6
6
|
import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
|
|
7
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
7
8
|
|
|
8
9
|
export interface SelectOption<T extends string | number = string> {
|
|
9
10
|
value: T
|
|
@@ -17,7 +18,7 @@ export interface SelectProps<T extends string | number = string> {
|
|
|
17
18
|
onChange: (value: T) => void
|
|
18
19
|
placeholder?: string
|
|
19
20
|
variant?: 'filled' | 'outline'
|
|
20
|
-
|
|
21
|
+
accentColor?: FormColor
|
|
21
22
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
22
23
|
align?: 'left' | 'right'
|
|
23
24
|
disabled?: boolean
|
|
@@ -25,7 +26,7 @@ export interface SelectProps<T extends string | number = string> {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const VARIANT_CLASSES = {
|
|
28
|
-
filled: { bg: 'bg-neutral-
|
|
29
|
+
filled: { bg: 'bg-neutral-960', hoverBg: 'hover:bg-neutral-700', menuBg: 'bg-[var(--popover)]' },
|
|
29
30
|
outline: { bg: 'bg-transparent', hoverBg: 'hover:bg-neutral-700', menuBg: 'bg-[var(--popover)]' },
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -43,12 +44,14 @@ export function Select<T extends string | number = string>({
|
|
|
43
44
|
onChange,
|
|
44
45
|
placeholder = 'Select...',
|
|
45
46
|
variant = 'outline',
|
|
46
|
-
|
|
47
|
+
accentColor,
|
|
47
48
|
size = 'sm',
|
|
48
49
|
align = 'left',
|
|
49
50
|
disabled = false,
|
|
50
51
|
className = '',
|
|
51
52
|
}: SelectProps<T>) {
|
|
53
|
+
const contextAccent = useAccentColor()
|
|
54
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
52
55
|
const [isOpen, setIsOpen] = useState(false)
|
|
53
56
|
const [highlightIdx, setHighlightIdx] = useState(-1)
|
|
54
57
|
const ref = useRef<HTMLDivElement>(null)
|
|
@@ -110,14 +113,16 @@ export function Select<T extends string | number = string>({
|
|
|
110
113
|
<button
|
|
111
114
|
ref={buttonRef}
|
|
112
115
|
type="button"
|
|
116
|
+
aria-expanded={isOpen}
|
|
117
|
+
aria-haspopup="listbox"
|
|
113
118
|
onClick={() => !disabled && (isOpen ? close() : open())}
|
|
114
119
|
disabled={disabled}
|
|
115
|
-
className={`flex items-center gap-1.5 min-w-0 rounded-lg border ${v.bg} ${FORM_COLORS[
|
|
116
|
-
disabled ? 'opacity-50 cursor-not-allowed' : `cursor-pointer ${FORM_COLORS[
|
|
120
|
+
className={`flex items-center gap-1.5 min-w-0 rounded-lg border ${v.bg} ${FORM_COLORS[effectiveColor].border} text-neutral-200 focus:outline-none ${FORM_COLORS[effectiveColor].focus} transition-colors ${
|
|
121
|
+
disabled ? 'opacity-50 cursor-not-allowed' : `cursor-pointer ${FORM_COLORS[effectiveColor].hover}`
|
|
117
122
|
} ${s}`}
|
|
118
123
|
>
|
|
119
124
|
{selectedOption?.icon}
|
|
120
|
-
<span className={`
|
|
125
|
+
<span className={`truncate ${selectedOption ? '' : 'text-neutral-500'}`}>
|
|
121
126
|
{selectedOption?.label ?? placeholder}
|
|
122
127
|
</span>
|
|
123
128
|
<ChevronDown className={`w-3 h-3 ml-auto text-neutral-500 transition-transform shrink-0 ${isOpen ? 'rotate-180' : ''}`} />
|
|
@@ -125,7 +130,8 @@ export function Select<T extends string | number = string>({
|
|
|
125
130
|
{isOpen && menuPos && createPortal(
|
|
126
131
|
<div
|
|
127
132
|
ref={menuRef}
|
|
128
|
-
|
|
133
|
+
role="listbox"
|
|
134
|
+
className={`fixed z-[9999] whitespace-nowrap ${v.menuBg} border ${FORM_COLORS[effectiveColor].border} rounded-lg shadow-lg overflow-hidden`}
|
|
129
135
|
style={{
|
|
130
136
|
top: menuPos.top,
|
|
131
137
|
left: align === 'right' ? undefined : menuPos.left,
|
|
@@ -145,13 +151,13 @@ export function Select<T extends string | number = string>({
|
|
|
145
151
|
onPointerEnter={() => setHighlightIdx(idx)}
|
|
146
152
|
className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm text-left transition-colors cursor-pointer ${
|
|
147
153
|
isHighlighted
|
|
148
|
-
? `${FORM_COLORS[
|
|
149
|
-
: isSelected ? `${FORM_COLORS[
|
|
154
|
+
? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200`
|
|
155
|
+
: isSelected ? `${FORM_COLORS[effectiveColor].selectedBg} text-neutral-200` : `text-neutral-400 ${v.hoverBg}`
|
|
150
156
|
}`}
|
|
151
157
|
>
|
|
152
|
-
<Check className={`w-3 h-3 shrink-0 ${isSelected ? FORM_COLORS[
|
|
158
|
+
<Check className={`w-3 h-3 shrink-0 ${isSelected ? FORM_COLORS[effectiveColor].accent : 'invisible'}`} />
|
|
153
159
|
{opt.icon}
|
|
154
|
-
<span>{opt.label}</span>
|
|
160
|
+
<span className="truncate">{opt.label}</span>
|
|
155
161
|
</button>
|
|
156
162
|
)
|
|
157
163
|
})}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { iconMap, type IconName } from './icon-button.tsx'
|
|
2
2
|
import type { ConfirmBadgeColor } from './confirm-badge.tsx'
|
|
3
|
+
import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
|
|
4
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
3
5
|
import { cn } from '../lib/cn.ts'
|
|
4
|
-
import {
|
|
6
|
+
import { FORM_COLORS } from '../lib/form-colors.ts'
|
|
7
|
+
import { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey } from '../lib/coding-agents.tsx'
|
|
5
8
|
|
|
6
|
-
/* ── Preset logos (shared
|
|
9
|
+
/* ── Preset logos (shared CodingAgentIcon) ────────────────── */
|
|
7
10
|
|
|
8
11
|
type IconProps = { className?: string; style?: React.CSSProperties }
|
|
9
12
|
|
|
10
|
-
function
|
|
11
|
-
function
|
|
12
|
-
return <
|
|
13
|
+
function makeAgentLogo(agentKey: CodingAgentKey): React.ComponentType<IconProps> {
|
|
14
|
+
function AgentLogo({ className, style }: IconProps) {
|
|
15
|
+
return <CodingAgentIcon agent={agentKey} className={className} style={style} />
|
|
13
16
|
}
|
|
14
|
-
return
|
|
17
|
+
return AgentLogo
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
/* ── Preset types & data ──────────────────────────────────── */
|
|
@@ -24,12 +27,14 @@ export type CodingToolPresetConfig = CodingToolId | {
|
|
|
24
27
|
disabled?: boolean
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
// Brand colors use Tailwind accent hex values (violet-400, orange-500, blue-400, cyan-400, neutral-400)
|
|
31
|
+
// applied via inline style because the color is dynamic per-item
|
|
27
32
|
const CODING_TOOL_PRESETS: Record<CodingToolId, { name: string; description: string; color: string; badgeColor: ConfirmBadgeColor; Logo: React.ComponentType<IconProps> }> = {
|
|
28
|
-
'claude-code': { name:
|
|
29
|
-
'github-copilot': { name:
|
|
30
|
-
'codex-cli': { name:
|
|
31
|
-
'gemini-cli': { name:
|
|
32
|
-
'opencode': { name:
|
|
33
|
+
'claude-code': { name: CODING_AGENT_NAMES.claude, description: 'Security & architecture focus', color: '#a78bfa', badgeColor: 'violet', Logo: makeAgentLogo('claude') },
|
|
34
|
+
'github-copilot': { name: CODING_AGENT_NAMES.copilot, description: 'Code quality & patterns', color: '#f97316', badgeColor: 'orange', Logo: makeAgentLogo('copilot') },
|
|
35
|
+
'codex-cli': { name: CODING_AGENT_NAMES.codex, description: 'Performance & optimization', color: '#60a5fa', badgeColor: 'blue', Logo: makeAgentLogo('codex') },
|
|
36
|
+
'gemini-cli': { name: CODING_AGENT_NAMES.gemini, description: 'Best practices & docs', color: '#22d3ee', badgeColor: 'cyan', Logo: makeAgentLogo('gemini') },
|
|
37
|
+
'opencode': { name: CODING_AGENT_NAMES.opencode, description: 'Provider-agnostic AI coding agent', color: '#a1a1aa', badgeColor: 'neutral', Logo: makeAgentLogo('opencode') },
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
function resolvePresets(presets: CodingToolPresetConfig[]): SelectionCardItem[] {
|
|
@@ -85,16 +90,11 @@ export interface SelectionGridProps {
|
|
|
85
90
|
/** Number of columns for grid layout (auto if not set) */
|
|
86
91
|
columns?: number
|
|
87
92
|
className?: string
|
|
93
|
+
accentColor?: FormColor
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/* ── Helpers ───────────────────────────────────────────────── */
|
|
91
97
|
|
|
92
|
-
const DEFAULT_COLOR = 'blue-400'
|
|
93
|
-
|
|
94
|
-
function resolveColor(color?: string): string {
|
|
95
|
-
return color || DEFAULT_COLOR
|
|
96
|
-
}
|
|
97
|
-
|
|
98
98
|
function autoColumns(itemCount: number): number {
|
|
99
99
|
return Math.min(itemCount, 5)
|
|
100
100
|
}
|
|
@@ -110,7 +110,10 @@ export function SelectionGrid({
|
|
|
110
110
|
layout = 'grid',
|
|
111
111
|
columns,
|
|
112
112
|
className,
|
|
113
|
+
accentColor,
|
|
113
114
|
}: SelectionGridProps) {
|
|
115
|
+
const contextAccent = useAccentColor()
|
|
116
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
114
117
|
const items = itemsProp || (presets ? resolvePresets(presets) : [])
|
|
115
118
|
|
|
116
119
|
function handleClick(item: SelectionCardItem) {
|
|
@@ -132,6 +135,7 @@ export function SelectionGrid({
|
|
|
132
135
|
|
|
133
136
|
if (layout === 'list') {
|
|
134
137
|
return (
|
|
138
|
+
<AccentColorProvider value={effectiveColor}>
|
|
135
139
|
<div
|
|
136
140
|
className={cn('grid gap-3', className)}
|
|
137
141
|
style={{ gridTemplateColumns: `repeat(${columns || 1}, 1fr)` }}
|
|
@@ -145,10 +149,12 @@ export function SelectionGrid({
|
|
|
145
149
|
/>
|
|
146
150
|
))}
|
|
147
151
|
</div>
|
|
152
|
+
</AccentColorProvider>
|
|
148
153
|
)
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
return (
|
|
157
|
+
<AccentColorProvider value={effectiveColor}>
|
|
152
158
|
<div
|
|
153
159
|
className={cn('grid gap-3', className)}
|
|
154
160
|
style={{ gridTemplateColumns: `repeat(${cols}, 1fr)` }}
|
|
@@ -162,6 +168,7 @@ export function SelectionGrid({
|
|
|
162
168
|
/>
|
|
163
169
|
))}
|
|
164
170
|
</div>
|
|
171
|
+
</AccentColorProvider>
|
|
165
172
|
)
|
|
166
173
|
}
|
|
167
174
|
|
|
@@ -174,32 +181,30 @@ interface CardProps {
|
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
function GridCard({ item, selected, onClick }: CardProps) {
|
|
177
|
-
const
|
|
184
|
+
const accent = useAccentColor() ?? 'blue'
|
|
185
|
+
const accentStyles = FORM_COLORS[accent]
|
|
178
186
|
const Icon = item.IconComponent || (item.icon ? iconMap[item.icon] : undefined)
|
|
179
187
|
|
|
180
188
|
return (
|
|
181
189
|
<button
|
|
182
190
|
type="button"
|
|
191
|
+
aria-pressed={selected}
|
|
192
|
+
aria-label={item.name}
|
|
183
193
|
onClick={onClick}
|
|
184
194
|
disabled={item.disabled}
|
|
185
195
|
className={cn(
|
|
186
196
|
'relative p-3 rounded-lg text-center transition-all border-2 flex flex-col items-center',
|
|
187
197
|
item.disabled
|
|
188
|
-
? 'opacity-30 cursor-not-allowed border-neutral-
|
|
198
|
+
? 'opacity-30 cursor-not-allowed border-neutral-960 bg-[var(--background)]'
|
|
189
199
|
: selected
|
|
190
|
-
?
|
|
191
|
-
: 'bg-neutral-
|
|
200
|
+
? `cursor-pointer ${accentStyles.selectedBg} ${accentStyles.border}`
|
|
201
|
+
: 'bg-neutral-960/50 border-neutral-700 hover:bg-[var(--surface-hover)] hover:border-neutral-600 cursor-pointer',
|
|
192
202
|
)}
|
|
193
|
-
style={
|
|
194
|
-
selected && !item.disabled
|
|
195
|
-
? { borderColor: color }
|
|
196
|
-
: undefined
|
|
197
|
-
}
|
|
198
203
|
>
|
|
199
204
|
{Icon && (
|
|
200
205
|
<Icon
|
|
201
206
|
className="w-7 h-7 mb-1"
|
|
202
|
-
style={{ color: item.disabled ? undefined :
|
|
207
|
+
style={{ color: item.disabled ? undefined : item.color }}
|
|
203
208
|
/>
|
|
204
209
|
)}
|
|
205
210
|
|
|
@@ -213,32 +218,30 @@ function GridCard({ item, selected, onClick }: CardProps) {
|
|
|
213
218
|
}
|
|
214
219
|
|
|
215
220
|
function ListCard({ item, selected, onClick }: CardProps) {
|
|
216
|
-
const
|
|
221
|
+
const accent = useAccentColor() ?? 'blue'
|
|
222
|
+
const accentStyles = FORM_COLORS[accent]
|
|
217
223
|
const Icon = item.IconComponent || (item.icon ? iconMap[item.icon] : undefined)
|
|
218
224
|
|
|
219
225
|
return (
|
|
220
226
|
<button
|
|
221
227
|
type="button"
|
|
228
|
+
aria-pressed={selected}
|
|
229
|
+
aria-label={item.name}
|
|
222
230
|
onClick={onClick}
|
|
223
231
|
disabled={item.disabled}
|
|
224
232
|
className={cn(
|
|
225
233
|
'relative p-3 rounded-lg transition-all border-2 flex items-center gap-3 text-left',
|
|
226
234
|
item.disabled
|
|
227
|
-
? 'opacity-30 cursor-not-allowed border-neutral-
|
|
235
|
+
? 'opacity-30 cursor-not-allowed border-neutral-960 bg-[var(--background)]'
|
|
228
236
|
: selected
|
|
229
|
-
?
|
|
230
|
-
: 'bg-neutral-
|
|
237
|
+
? `cursor-pointer ${accentStyles.selectedBg} ${accentStyles.border}`
|
|
238
|
+
: 'bg-neutral-960/50 border-neutral-700 hover:bg-[var(--surface-hover)] hover:border-neutral-600 cursor-pointer',
|
|
231
239
|
)}
|
|
232
|
-
style={
|
|
233
|
-
selected && !item.disabled
|
|
234
|
-
? { borderColor: color }
|
|
235
|
-
: undefined
|
|
236
|
-
}
|
|
237
240
|
>
|
|
238
241
|
{Icon && (
|
|
239
242
|
<Icon
|
|
240
243
|
className="w-5 h-5 flex-shrink-0"
|
|
241
|
-
style={{ color: item.disabled ? undefined :
|
|
244
|
+
style={{ color: item.disabled ? undefined : item.color }}
|
|
242
245
|
/>
|
|
243
246
|
)}
|
|
244
247
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
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
|
+
import { cn } from '../lib/cn.ts'
|
|
17
18
|
|
|
18
19
|
interface SettingRowBase {
|
|
19
20
|
label: string
|
|
@@ -26,7 +27,7 @@ interface SettingRowToggle extends SettingRowBase {
|
|
|
26
27
|
type: 'toggle'
|
|
27
28
|
checked: boolean
|
|
28
29
|
onChange: (checked: boolean) => void
|
|
29
|
-
|
|
30
|
+
accentColor?: ToggleColor
|
|
30
31
|
size?: ToggleSize
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -51,10 +52,10 @@ interface SettingRowInput extends SettingRowBase {
|
|
|
51
52
|
export type SettingRowProps = SettingRowToggle | SettingRowSelect | SettingRowInput
|
|
52
53
|
|
|
53
54
|
export function SettingRow(props: SettingRowProps) {
|
|
54
|
-
const { label, description, disabled, className
|
|
55
|
+
const { label, description, disabled, className } = props
|
|
55
56
|
|
|
56
57
|
return (
|
|
57
|
-
<div className={
|
|
58
|
+
<div className={cn('flex items-start justify-between gap-4', className)}>
|
|
58
59
|
<div>
|
|
59
60
|
<label className="text-neutral-200 leading-7">{label}</label>
|
|
60
61
|
{description && <p className="text-md text-neutral-500">{description}</p>}
|
|
@@ -64,8 +65,9 @@ export function SettingRow(props: SettingRowProps) {
|
|
|
64
65
|
checked={props.checked}
|
|
65
66
|
onChange={props.onChange}
|
|
66
67
|
disabled={disabled}
|
|
67
|
-
|
|
68
|
+
accentColor={props.accentColor}
|
|
68
69
|
size={props.size}
|
|
70
|
+
aria-label={label}
|
|
69
71
|
/>
|
|
70
72
|
)}
|
|
71
73
|
{props.type === 'select' && (
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { cn } from '../lib/cn.ts'
|
|
2
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
3
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
2
4
|
|
|
3
5
|
export interface SettingsCardProps {
|
|
4
6
|
children: React.ReactNode
|
|
@@ -6,22 +8,27 @@ export interface SettingsCardProps {
|
|
|
6
8
|
title?: string
|
|
7
9
|
description?: string
|
|
8
10
|
testId?: string
|
|
11
|
+
accentColor?: FormColor
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
export function SettingsCard({ children, className, title, description, testId }: SettingsCardProps) {
|
|
14
|
+
export function SettingsCard({ children, className, title, description, testId, accentColor }: SettingsCardProps) {
|
|
15
|
+
const contextAccent = useAccentColor()
|
|
16
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
12
17
|
return (
|
|
13
18
|
<div
|
|
14
|
-
className={cn('bg-neutral-
|
|
19
|
+
className={cn('bg-neutral-980 border border-neutral-960 rounded-lg p-4 space-y-4', className)}
|
|
15
20
|
data-testid={testId}
|
|
16
21
|
>
|
|
17
22
|
{title && (
|
|
18
23
|
<div>
|
|
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>}
|
|
24
|
+
<h3 className="text-md font-medium text-neutral-200 truncate">{title}</h3>
|
|
25
|
+
{description && <p className="text-md text-neutral-500 mt-1 line-clamp-2">{description}</p>}
|
|
21
26
|
</div>
|
|
22
27
|
)}
|
|
23
28
|
{!title && description && <p className="text-md text-neutral-500">{description}</p>}
|
|
24
|
-
{
|
|
29
|
+
<AccentColorProvider value={effectiveColor}>
|
|
30
|
+
{children}
|
|
31
|
+
</AccentColorProvider>
|
|
25
32
|
</div>
|
|
26
33
|
)
|
|
27
34
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Info, AlertTriangle, CheckCircle, AlertCircle } from 'lucide-react'
|
|
2
|
-
import { ACCENT_TEXT, type AccentColor } from '../lib/form-colors.ts'
|
|
2
|
+
import { ACCENT_TEXT, type AccentColor, type FormColor } from '../lib/form-colors.ts'
|
|
3
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
3
4
|
import { cn } from '../lib/cn.ts'
|
|
4
5
|
|
|
5
6
|
export type SettingsInfoBoxColor = AccentColor
|
|
@@ -7,6 +8,7 @@ export type SettingsInfoBoxColor = AccentColor
|
|
|
7
8
|
export interface SettingsInfoBoxProps {
|
|
8
9
|
children: React.ReactNode
|
|
9
10
|
color?: SettingsInfoBoxColor
|
|
11
|
+
accentColor?: FormColor
|
|
10
12
|
className?: string
|
|
11
13
|
testId?: string
|
|
12
14
|
}
|
|
@@ -47,16 +49,17 @@ const borderColorMap: Record<SettingsInfoBoxColor, string> = {
|
|
|
47
49
|
teal: 'border-l-teal-500',
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
export function SettingsInfoBox({ children, color
|
|
51
|
-
const
|
|
52
|
+
export function SettingsInfoBox({ children, color, accentColor, className, testId }: SettingsInfoBoxProps) {
|
|
53
|
+
const contextAccent = useAccentColor()
|
|
54
|
+
const effectiveColor = (color ?? accentColor ?? contextAccent ?? 'neutral') as SettingsInfoBoxColor
|
|
55
|
+
const Icon = INFO_BOX_ICONS[effectiveColor]
|
|
52
56
|
|
|
53
57
|
return (
|
|
54
58
|
<div
|
|
55
|
-
className={cn('flex items-start gap-3 border-l-2', borderColorMap[
|
|
56
|
-
style={{ paddingLeft: 10 }}
|
|
59
|
+
className={cn('flex items-start gap-3 border-l-2 pl-2.5', borderColorMap[effectiveColor], className)}
|
|
57
60
|
data-testid={testId}
|
|
58
61
|
>
|
|
59
|
-
<Icon className={cn('w-4 h-4 mt-0.5 shrink-0', ACCENT_TEXT[
|
|
62
|
+
<Icon className={cn('w-4 h-4 mt-0.5 shrink-0', ACCENT_TEXT[effectiveColor])} />
|
|
60
63
|
<div className="text-md text-neutral-500">{children}</div>
|
|
61
64
|
</div>
|
|
62
65
|
)
|
|
@@ -6,16 +6,28 @@
|
|
|
6
6
|
* - Configuration panels - section dividers
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { ACCENT_TEXT, type AccentColor, type FormColor } from '../lib/form-colors.ts'
|
|
10
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
11
|
+
import { cn } from '../lib/cn.ts'
|
|
12
|
+
|
|
9
13
|
export interface SettingsSectionTitleProps {
|
|
10
14
|
children: React.ReactNode
|
|
11
15
|
className?: string
|
|
12
16
|
testId?: string
|
|
17
|
+
accentColor?: FormColor
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
export function SettingsSectionTitle({ children, className
|
|
20
|
+
export function SettingsSectionTitle({ children, className, testId, accentColor }: SettingsSectionTitleProps) {
|
|
21
|
+
const contextAccent = useAccentColor()
|
|
22
|
+
const effectiveColor = accentColor ?? contextAccent
|
|
23
|
+
|
|
16
24
|
return (
|
|
17
25
|
<h3
|
|
18
|
-
className={
|
|
26
|
+
className={cn(
|
|
27
|
+
'text-sm font-medium uppercase tracking-wider',
|
|
28
|
+
effectiveColor ? ACCENT_TEXT[effectiveColor as AccentColor] : 'text-neutral-400',
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
19
31
|
data-testid={testId}
|
|
20
32
|
>
|
|
21
33
|
{children}
|
|
@@ -4,6 +4,8 @@ import { RefreshCw, Eye } from 'lucide-react'
|
|
|
4
4
|
import { cn } from '../lib/cn.ts'
|
|
5
5
|
import { Label, type LabelColor } from './label.tsx'
|
|
6
6
|
import type { IconName } from './icon-button.tsx'
|
|
7
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
8
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
7
9
|
|
|
8
10
|
type SnapshotStatus = 'synced' | 'pending' | 'conflict' | 'outdated'
|
|
9
11
|
|
|
@@ -16,6 +18,7 @@ export interface SnapshotCardProps {
|
|
|
16
18
|
onSync?: () => void
|
|
17
19
|
onView?: () => void
|
|
18
20
|
className?: string
|
|
21
|
+
accentColor?: FormColor
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const statusStripeColor: Record<SnapshotStatus, string> = {
|
|
@@ -41,9 +44,13 @@ export function SnapshotCard({
|
|
|
41
44
|
onSync,
|
|
42
45
|
onView,
|
|
43
46
|
className,
|
|
47
|
+
accentColor: accentColorProp,
|
|
44
48
|
}: SnapshotCardProps) {
|
|
49
|
+
const contextAccent = useAccentColor()
|
|
50
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
45
51
|
return (
|
|
46
|
-
<
|
|
52
|
+
<AccentColorProvider value={effectiveColor}>
|
|
53
|
+
<div className={cn('rounded-lg border border-neutral-700 bg-neutral-960 overflow-hidden', className)}>
|
|
47
54
|
<div className={cn('h-1', statusStripeColor[status])} />
|
|
48
55
|
|
|
49
56
|
<div className="p-4">
|
|
@@ -51,7 +58,7 @@ export function SnapshotCard({
|
|
|
51
58
|
<h3 className="text-md font-medium text-neutral-200 truncate">{title}</h3>
|
|
52
59
|
<Label
|
|
53
60
|
text={statusLabelConfig[status].text}
|
|
54
|
-
|
|
61
|
+
accentColor={statusLabelConfig[status].color}
|
|
55
62
|
icon={statusLabelConfig[status].icon}
|
|
56
63
|
tooltip={{ description: statusLabelConfig[status].tooltip }}
|
|
57
64
|
size="xs"
|
|
@@ -103,5 +110,6 @@ export function SnapshotCard({
|
|
|
103
110
|
)}
|
|
104
111
|
</div>
|
|
105
112
|
</div>
|
|
113
|
+
</AccentColorProvider>
|
|
106
114
|
)
|
|
107
115
|
}
|
|
@@ -61,7 +61,7 @@ export function SnippetsPanel({
|
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
|
-
<div className={cn('flex flex-col bg-neutral-
|
|
64
|
+
<div className={cn('flex flex-col bg-neutral-960 rounded-lg overflow-hidden', className)}>
|
|
65
65
|
<div className="flex items-center justify-between px-3 py-2 border-b border-neutral-700">
|
|
66
66
|
<span className="text-xs font-semibold uppercase tracking-wider text-neutral-500">Snippets</span>
|
|
67
67
|
<span className="text-xs text-neutral-500">{snippets.length} snippets</span>
|
|
@@ -107,7 +107,7 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
|
|
|
107
107
|
const langColor = snippet.language ? LANGUAGE_COLORS[snippet.language.toLowerCase()] ?? '#6b7280' : '#6b7280'
|
|
108
108
|
|
|
109
109
|
return (
|
|
110
|
-
<div className="rounded-md border border-neutral-700 bg-neutral-
|
|
110
|
+
<div className="rounded-md border border-neutral-700 bg-neutral-980 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
113
|
<span className="text-sm font-medium text-neutral-200 truncate">{snippet.label}</span>
|
|
@@ -126,6 +126,7 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
|
|
|
126
126
|
icon="arrow-down-to-line"
|
|
127
127
|
onClick={() => onInsert(snippet.id)}
|
|
128
128
|
size="xss"
|
|
129
|
+
accentColor="neutral"
|
|
129
130
|
tooltip={{ title: 'Insert snippet', description: 'Insert this snippet' }}
|
|
130
131
|
/>
|
|
131
132
|
)}
|
|
@@ -134,6 +135,7 @@ function SnippetCard({ snippet, onInsert, onCopy }: SnippetCardProps) {
|
|
|
134
135
|
icon="copy"
|
|
135
136
|
onClick={() => onCopy(snippet.id)}
|
|
136
137
|
size="xss"
|
|
138
|
+
accentColor="neutral"
|
|
137
139
|
tooltip={{ title: 'Copy snippet', description: 'Copy to clipboard' }}
|
|
138
140
|
/>
|
|
139
141
|
)}
|