@toolr/ui-design 0.1.8 → 0.1.10
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/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/form-colors.ts +16 -16
- 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 +10 -9
- 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 +24 -30
- package/components/ui/ai-action-button.tsx +10 -7
- package/components/ui/ai-execution-action-buttons.tsx +13 -5
- package/components/ui/badge.tsx +7 -4
- package/components/ui/bottom-panel-header.tsx +9 -5
- package/components/ui/breadcrumb.tsx +13 -5
- package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +13 -5
- package/components/ui/checkbox.tsx +6 -3
- package/components/ui/collapsible-section.tsx +38 -29
- package/components/ui/confirm-badge.tsx +7 -4
- 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 +7 -4
- package/components/ui/file-tree.tsx +3 -1
- package/components/ui/files-panel.tsx +147 -27
- package/components/ui/filter-dropdown.tsx +84 -74
- package/components/ui/form-actions.tsx +14 -6
- package/components/ui/frontmatter-form-header.tsx +10 -2
- package/components/ui/icon-button.tsx +22 -9
- package/components/ui/input.tsx +7 -4
- package/components/ui/label.tsx +5 -5
- package/components/ui/layout-tab-bar.tsx +7 -5
- package/components/ui/modal.tsx +18 -4
- package/components/ui/nav-card.tsx +6 -3
- package/components/ui/navigation-bar.tsx +164 -82
- package/components/ui/number-input.tsx +8 -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 +40 -17
- package/components/ui/resizable-textarea.tsx +18 -11
- package/components/ui/scope-badge.tsx +18 -11
- package/components/ui/segmented-toggle.tsx +5 -2
- package/components/ui/select.tsx +12 -9
- package/components/ui/selection-grid.tsx +36 -37
- package/components/ui/setting-row.tsx +2 -2
- package/components/ui/settings-card.tsx +10 -3
- package/components/ui/settings-info-box.tsx +9 -5
- 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 +39 -29
- package/components/ui/status-card.tsx +9 -1
- package/components/ui/tab-bar.tsx +12 -9
- package/components/ui/toggle.tsx +13 -7
- package/components/ui/tooltip.tsx +9 -1
- package/dist/content.js +8 -8
- package/dist/diagrams.d.ts +0 -1
- package/dist/index.d.ts +427 -184
- package/dist/index.js +3098 -1761
- 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 +1 -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
package/components/ui/modal.tsx
CHANGED
|
@@ -5,6 +5,8 @@ import { useModalBehavior } from '../hooks/use-modal-behavior.ts'
|
|
|
5
5
|
import { IconButton, type ActionItem } from './icon-button.tsx'
|
|
6
6
|
import { FormActions } from './form-actions.tsx'
|
|
7
7
|
import type { ReactNode } from 'react'
|
|
8
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
9
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
8
10
|
|
|
9
11
|
export type ModalKind = 'info' | 'warning' | 'error' | 'orange' | 'success'
|
|
10
12
|
export type ModalSize = 'sm' | 'md' | 'lg' | 'xl'
|
|
@@ -57,9 +59,9 @@ function Modal({ isOpen, onClose, title, children, kind = 'info', size = 'md', h
|
|
|
57
59
|
aria-modal="true"
|
|
58
60
|
aria-labelledby={titleId}
|
|
59
61
|
data-testid={testId}
|
|
60
|
-
className={`relative bg-neutral-
|
|
62
|
+
className={`relative bg-neutral-980 border border-neutral-700 rounded-lg shadow-lg ${SIZE_CLASSES[size]} w-full mx-4 overflow-hidden`}
|
|
61
63
|
>
|
|
62
|
-
<div className="flex items-center gap-3 px-
|
|
64
|
+
<div className="flex items-center gap-3 px-4 py-4 border-b border-neutral-960">
|
|
63
65
|
{KIND_ICON[kind]}
|
|
64
66
|
<h3 id={titleId} className="text-lg font-semibold text-white flex-1 min-w-0 truncate">{title}</h3>
|
|
65
67
|
{headerActions?.map((a, i) => <IconButton key={i} {...a} />)}
|
|
@@ -68,13 +70,13 @@ function Modal({ isOpen, onClose, title, children, kind = 'info', size = 'md', h
|
|
|
68
70
|
icon="x"
|
|
69
71
|
onClick={onClose}
|
|
70
72
|
size="sm"
|
|
71
|
-
|
|
73
|
+
accentColor="neutral"
|
|
72
74
|
tooltip={{ description: 'Close this modal' }}
|
|
73
75
|
testId="modal-close"
|
|
74
76
|
/>
|
|
75
77
|
)}
|
|
76
78
|
</div>
|
|
77
|
-
<div className="px-
|
|
79
|
+
<div className="px-4 py-4">{children}</div>
|
|
78
80
|
</div>
|
|
79
81
|
</div>,
|
|
80
82
|
document.body,
|
|
@@ -93,6 +95,7 @@ export interface ConfirmModalProps {
|
|
|
93
95
|
confirmColor?: 'red' | 'blue' | 'orange' | 'yellow'
|
|
94
96
|
isLoading?: boolean
|
|
95
97
|
confirmDisabled?: boolean
|
|
98
|
+
accentColor?: FormColor
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
export function ConfirmModal({
|
|
@@ -107,7 +110,10 @@ export function ConfirmModal({
|
|
|
107
110
|
confirmColor = 'blue',
|
|
108
111
|
isLoading = false,
|
|
109
112
|
confirmDisabled = false,
|
|
113
|
+
accentColor: accentColorProp,
|
|
110
114
|
}: ConfirmModalProps) {
|
|
115
|
+
const contextAccent = useAccentColor()
|
|
116
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
111
117
|
const [isConfirming, setIsConfirming] = useState(false)
|
|
112
118
|
|
|
113
119
|
const isDisabled = isLoading || isConfirming || confirmDisabled
|
|
@@ -126,6 +132,7 @@ export function ConfirmModal({
|
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
return (
|
|
135
|
+
<AccentColorProvider value={effectiveColor}>
|
|
129
136
|
<Modal isOpen={isOpen} onClose={onClose} title={title} kind={kind} hideCloseButton>
|
|
130
137
|
<div className="text-neutral-300 mb-6">
|
|
131
138
|
{message}
|
|
@@ -154,6 +161,7 @@ export function ConfirmModal({
|
|
|
154
161
|
confirmStatus={isInProgress ? 'loading' : undefined}
|
|
155
162
|
/>
|
|
156
163
|
</Modal>
|
|
164
|
+
</AccentColorProvider>
|
|
157
165
|
)
|
|
158
166
|
}
|
|
159
167
|
|
|
@@ -163,6 +171,7 @@ export interface AlertModalProps {
|
|
|
163
171
|
title: string
|
|
164
172
|
message: string
|
|
165
173
|
kind?: ModalKind
|
|
174
|
+
accentColor?: FormColor
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
export function AlertModal({
|
|
@@ -171,8 +180,12 @@ export function AlertModal({
|
|
|
171
180
|
title,
|
|
172
181
|
message,
|
|
173
182
|
kind = 'info',
|
|
183
|
+
accentColor: accentColorProp,
|
|
174
184
|
}: AlertModalProps) {
|
|
185
|
+
const contextAccent = useAccentColor()
|
|
186
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
175
187
|
return (
|
|
188
|
+
<AccentColorProvider value={effectiveColor}>
|
|
176
189
|
<Modal isOpen={isOpen} onClose={onClose} title={title} kind={kind} hideCloseButton>
|
|
177
190
|
<div className="text-neutral-300 mb-6">{message}</div>
|
|
178
191
|
<FormActions
|
|
@@ -182,5 +195,6 @@ export function AlertModal({
|
|
|
182
195
|
confirmTooltip="Dismiss this alert"
|
|
183
196
|
/>
|
|
184
197
|
</Modal>
|
|
198
|
+
</AccentColorProvider>
|
|
185
199
|
)
|
|
186
200
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { iconMap, type IconName } from './icon-button.tsx'
|
|
4
4
|
import { Label, type LabelColor } from './label.tsx'
|
|
5
5
|
import { cn } from '../lib/cn.ts'
|
|
6
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
6
7
|
|
|
7
8
|
export interface NavCardProps {
|
|
8
9
|
title: string
|
|
@@ -11,11 +12,12 @@ export interface NavCardProps {
|
|
|
11
12
|
/** Custom icon component. Takes precedence over icon name. */
|
|
12
13
|
IconComponent?: React.ComponentType<{ className?: string }>
|
|
13
14
|
iconColor?: string
|
|
14
|
-
label?: { text: string; color: LabelColor; tooltip: { description: string } }
|
|
15
|
+
label?: { text: string; color: LabelColor; icon?: IconName; tooltip: { description: string } }
|
|
15
16
|
stats?: string
|
|
16
17
|
onClick?: () => void
|
|
17
18
|
disabled?: boolean
|
|
18
19
|
className?: string
|
|
20
|
+
accentColor?: FormColor
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export function NavCard({
|
|
@@ -29,6 +31,7 @@ export function NavCard({
|
|
|
29
31
|
onClick,
|
|
30
32
|
disabled = false,
|
|
31
33
|
className,
|
|
34
|
+
accentColor: _accentColor,
|
|
32
35
|
}: NavCardProps) {
|
|
33
36
|
const Icon = IconComponent ?? (icon ? iconMap[icon] : undefined)
|
|
34
37
|
|
|
@@ -38,7 +41,7 @@ export function NavCard({
|
|
|
38
41
|
onClick={disabled ? undefined : onClick}
|
|
39
42
|
disabled={disabled}
|
|
40
43
|
className={cn(
|
|
41
|
-
'relative w-full text-left rounded-lg border border-neutral-700 bg-neutral-
|
|
44
|
+
'relative w-full text-left rounded-lg border border-neutral-700 bg-neutral-960 p-4 transition-all duration-200 cursor-pointer',
|
|
42
45
|
!disabled && 'hover:-translate-y-0.5 hover:border-neutral-600 hover:bg-neutral-700',
|
|
43
46
|
disabled && 'opacity-50 cursor-not-allowed',
|
|
44
47
|
className,
|
|
@@ -46,7 +49,7 @@ export function NavCard({
|
|
|
46
49
|
>
|
|
47
50
|
{label && (
|
|
48
51
|
<span className="absolute top-3 right-3">
|
|
49
|
-
<Label text={label.text}
|
|
52
|
+
<Label text={label.text} accentColor={label.color} icon={label.icon} size="xs" tooltip={label.tooltip} />
|
|
50
53
|
</span>
|
|
51
54
|
)}
|
|
52
55
|
|
|
@@ -6,6 +6,7 @@ import type { LucideIcon } from 'lucide-react'
|
|
|
6
6
|
import { iconMap, type IconName } from './icon-button.tsx'
|
|
7
7
|
import type { BreadcrumbSegment } from './breadcrumb.tsx'
|
|
8
8
|
import { ACCENT_NAV, type AccentColor } from '../lib/form-colors.ts'
|
|
9
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
9
10
|
import { cn } from '../lib/cn.ts'
|
|
10
11
|
import { useClickOutside } from '../hooks/use-click-outside.ts'
|
|
11
12
|
|
|
@@ -17,10 +18,14 @@ export interface NavigationBarProps {
|
|
|
17
18
|
onForward?: () => void
|
|
18
19
|
showHistory?: boolean
|
|
19
20
|
historyEntries?: BreadcrumbSegment[][]
|
|
21
|
+
currentHistoryIndex?: number
|
|
20
22
|
onHistorySelect?: (index: number) => void
|
|
21
23
|
leadingAction?: { icon: IconName; onClick?: () => void }
|
|
22
24
|
separator?: 'chevron' | 'slash' | 'dot'
|
|
23
25
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
26
|
+
/** 'bar' (default): all-in-one bordered box. 'header': nav controls left-aligned, breadcrumb absolutely centered. */
|
|
27
|
+
layout?: 'bar' | 'header'
|
|
28
|
+
accentColor?: AccentColor
|
|
24
29
|
className?: string
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -33,12 +38,32 @@ const sizeConfig = {
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
// Accent color classes for NavButton — same pattern as IconButton's colorClasses
|
|
42
|
+
const navColorClasses: Record<AccentColor, { text: string; border: string; hover: string; active: string }> = {
|
|
43
|
+
green: { text: 'text-green-400', border: 'border-green-500/30', hover: 'hover:bg-green-500/20 hover:border-green-500/40 hover:text-green-300', active: 'bg-green-500/20 text-green-300 border-green-500/40' },
|
|
44
|
+
red: { text: 'text-red-400', border: 'border-red-500/30', hover: 'hover:bg-red-500/20 hover:border-red-500/40 hover:text-red-300', active: 'bg-red-500/20 text-red-300 border-red-500/40' },
|
|
45
|
+
blue: { text: 'text-blue-400', border: 'border-blue-500/30', hover: 'hover:bg-blue-500/20 hover:border-blue-500/40 hover:text-blue-300', active: 'bg-blue-500/20 text-blue-300 border-blue-500/40' },
|
|
46
|
+
orange: { text: 'text-orange-400', border: 'border-orange-500/30', hover: 'hover:bg-orange-500/20 hover:border-orange-500/40 hover:text-orange-300', active: 'bg-orange-500/20 text-orange-300 border-orange-500/40' },
|
|
47
|
+
cyan: { text: 'text-cyan-400', border: 'border-cyan-500/30', hover: 'hover:bg-cyan-500/20 hover:border-cyan-500/40 hover:text-cyan-300', active: 'bg-cyan-500/20 text-cyan-300 border-cyan-500/40' },
|
|
48
|
+
yellow: { text: 'text-yellow-400', border: 'border-yellow-500/30', hover: 'hover:bg-yellow-500/20 hover:border-yellow-500/40 hover:text-yellow-300', active: 'bg-yellow-500/20 text-yellow-300 border-yellow-500/40' },
|
|
49
|
+
purple: { text: 'text-purple-400', border: 'border-purple-500/30', hover: 'hover:bg-purple-500/20 hover:border-purple-500/40 hover:text-purple-300', active: 'bg-purple-500/20 text-purple-300 border-purple-500/40' },
|
|
50
|
+
indigo: { text: 'text-indigo-400', border: 'border-indigo-500/30', hover: 'hover:bg-indigo-500/20 hover:border-indigo-500/40 hover:text-indigo-300', active: 'bg-indigo-500/20 text-indigo-300 border-indigo-500/40' },
|
|
51
|
+
emerald: { text: 'text-emerald-400', border: 'border-emerald-500/30', hover: 'hover:bg-emerald-500/20 hover:border-emerald-500/40 hover:text-emerald-300', active: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/40' },
|
|
52
|
+
amber: { text: 'text-amber-400', border: 'border-amber-500/30', hover: 'hover:bg-amber-500/20 hover:border-amber-500/40 hover:text-amber-300', active: 'bg-amber-500/20 text-amber-300 border-amber-500/40' },
|
|
53
|
+
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' },
|
|
54
|
+
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' },
|
|
55
|
+
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' },
|
|
56
|
+
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' },
|
|
57
|
+
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' },
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function NavButton({ icon: Icon, onClick, disabled, size, active, colorStyle }: {
|
|
37
61
|
icon: LucideIcon
|
|
38
62
|
onClick?: () => void
|
|
39
63
|
disabled?: boolean
|
|
40
64
|
size: keyof typeof sizeConfig
|
|
41
65
|
active?: boolean
|
|
66
|
+
colorStyle: typeof navColorClasses[AccentColor]
|
|
42
67
|
}) {
|
|
43
68
|
const s = sizeConfig[size]
|
|
44
69
|
return (
|
|
@@ -52,8 +77,8 @@ function NavButton({ icon: Icon, onClick, disabled, size, active }: {
|
|
|
52
77
|
disabled
|
|
53
78
|
? 'text-neutral-600 cursor-not-allowed'
|
|
54
79
|
: active
|
|
55
|
-
? '
|
|
56
|
-
: '
|
|
80
|
+
? cn('border cursor-pointer', colorStyle.active)
|
|
81
|
+
: cn('border cursor-pointer', colorStyle.text, colorStyle.border, colorStyle.hover),
|
|
57
82
|
)}
|
|
58
83
|
>
|
|
59
84
|
<Icon className={s.navIcon} />
|
|
@@ -91,12 +116,19 @@ export function NavigationBar({
|
|
|
91
116
|
onForward,
|
|
92
117
|
showHistory = false,
|
|
93
118
|
historyEntries,
|
|
119
|
+
currentHistoryIndex,
|
|
94
120
|
onHistorySelect,
|
|
95
121
|
leadingAction,
|
|
96
122
|
separator = 'chevron',
|
|
97
123
|
size = 'sm',
|
|
124
|
+
layout = 'bar',
|
|
125
|
+
accentColor,
|
|
98
126
|
className,
|
|
99
127
|
}: NavigationBarProps) {
|
|
128
|
+
const contextAccentColor = useAccentColor()
|
|
129
|
+
const effectiveColor = accentColor ?? contextAccentColor ?? 'neutral'
|
|
130
|
+
const colorStyle = navColorClasses[effectiveColor]
|
|
131
|
+
|
|
100
132
|
const s = sizeConfig[size]
|
|
101
133
|
const hasNav = !!(onBack || onForward)
|
|
102
134
|
const LeadIcon = leadingAction ? iconMap[leadingAction.icon] : null
|
|
@@ -108,12 +140,134 @@ export function NavigationBar({
|
|
|
108
140
|
|
|
109
141
|
const hasHistoryEntries = historyEntries && historyEntries.length > 0
|
|
110
142
|
|
|
143
|
+
const renderSegments = () => segments.map((segment, index) => {
|
|
144
|
+
const isLast = index === segments.length - 1
|
|
145
|
+
const isClickable = !isLast && !!segment.onClick
|
|
146
|
+
const colors = segment.color && ACCENT_NAV[segment.color as AccentColor] ? ACCENT_NAV[segment.color as AccentColor] : null
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div key={segment.id} className="flex items-center gap-1 min-w-0">
|
|
150
|
+
{index > 0 && <SegmentSeparator type={separator} size={size} />}
|
|
151
|
+
{isClickable ? (
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={segment.onClick}
|
|
155
|
+
className={cn(
|
|
156
|
+
'flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0',
|
|
157
|
+
s.text,
|
|
158
|
+
'font-medium hover:text-white',
|
|
159
|
+
colors ? [colors.text, `hover:${colors.bg}`] : ['text-neutral-300', 'hover:bg-neutral-700/50'],
|
|
160
|
+
)}
|
|
161
|
+
>
|
|
162
|
+
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
163
|
+
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
164
|
+
</button>
|
|
165
|
+
) : (
|
|
166
|
+
<div
|
|
167
|
+
className={cn(
|
|
168
|
+
'flex items-center gap-1.5 px-2 py-0.5 rounded-md min-w-0',
|
|
169
|
+
s.text,
|
|
170
|
+
isLast
|
|
171
|
+
? ['font-medium bg-neutral-700/50', colors ? colors.text : 'text-white']
|
|
172
|
+
: ['font-medium', colors ? colors.text : 'text-neutral-300'],
|
|
173
|
+
)}
|
|
174
|
+
>
|
|
175
|
+
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
176
|
+
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const renderHistoryDropdown = () => {
|
|
184
|
+
if (!historyOpen || !hasHistoryEntries || !historyEntries) return null
|
|
185
|
+
const entries = layout === 'header' ? [...historyEntries].reverse() : historyEntries
|
|
186
|
+
return (
|
|
187
|
+
<div className="absolute left-0 top-full mt-1 w-max min-w-[200px] max-w-[420px] bg-black/80 backdrop-blur-sm border border-neutral-700 rounded-lg shadow-lg z-50">
|
|
188
|
+
<div className="px-3 py-1.5 border-b border-neutral-700/50">
|
|
189
|
+
<p className="text-sm font-medium text-neutral-500">History{layout === 'header' && ` (${historyEntries.length})`}</p>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="max-h-[300px] overflow-y-auto py-1">
|
|
192
|
+
{entries.map((entry, entryIdx) => {
|
|
193
|
+
const actualIdx = layout === 'header' ? historyEntries.length - 1 - entryIdx : entryIdx
|
|
194
|
+
const isCurrent = currentHistoryIndex !== undefined && actualIdx === currentHistoryIndex
|
|
195
|
+
return (
|
|
196
|
+
<button
|
|
197
|
+
key={actualIdx}
|
|
198
|
+
type="button"
|
|
199
|
+
onClick={() => {
|
|
200
|
+
onHistorySelect?.(actualIdx)
|
|
201
|
+
setHistoryOpen(false)
|
|
202
|
+
}}
|
|
203
|
+
className={cn(
|
|
204
|
+
'w-full px-3 py-1.5 flex items-center gap-1 text-left transition-colors cursor-pointer',
|
|
205
|
+
isCurrent ? 'bg-green-500/10' : 'hover:bg-neutral-800',
|
|
206
|
+
)}
|
|
207
|
+
>
|
|
208
|
+
{entry.map((seg, segIdx) => (
|
|
209
|
+
<span key={seg.id} className="flex items-center gap-1 min-w-0">
|
|
210
|
+
{segIdx > 0 && <ChevronRight className="w-2.5 h-2.5 text-neutral-600 flex-shrink-0" />}
|
|
211
|
+
{seg.icon && <SegmentIcon icon={seg.icon} color={seg.color} size="xs" />}
|
|
212
|
+
<span className={cn(
|
|
213
|
+
'text-sm truncate',
|
|
214
|
+
seg.color && ACCENT_NAV[seg.color as AccentColor] ? ACCENT_NAV[seg.color as AccentColor].text : 'text-neutral-300',
|
|
215
|
+
)}>
|
|
216
|
+
{seg.label}
|
|
217
|
+
</span>
|
|
218
|
+
</span>
|
|
219
|
+
))}
|
|
220
|
+
{isCurrent && <span className="ml-auto pl-4 text-xs text-green-400 flex-shrink-0">Current</span>}
|
|
221
|
+
</button>
|
|
222
|
+
)
|
|
223
|
+
})}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (layout === 'header') {
|
|
230
|
+
return (
|
|
231
|
+
<nav className={cn('relative flex items-center h-full', className)}>
|
|
232
|
+
{/* Left: nav controls */}
|
|
233
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
234
|
+
{hasNav && (
|
|
235
|
+
<>
|
|
236
|
+
<NavButton icon={ChevronLeft} onClick={onBack} disabled={!canGoBack} size={size} colorStyle={colorStyle} />
|
|
237
|
+
<NavButton icon={ChevronRight} onClick={onForward} disabled={!canGoForward} size={size} colorStyle={colorStyle} />
|
|
238
|
+
</>
|
|
239
|
+
)}
|
|
240
|
+
{showHistory && (
|
|
241
|
+
<div className="relative" ref={historyRef}>
|
|
242
|
+
<NavButton
|
|
243
|
+
icon={History}
|
|
244
|
+
onClick={() => setHistoryOpen(o => !o)}
|
|
245
|
+
disabled={!hasHistoryEntries}
|
|
246
|
+
size={size}
|
|
247
|
+
active={historyOpen}
|
|
248
|
+
colorStyle={colorStyle}
|
|
249
|
+
/>
|
|
250
|
+
{renderHistoryDropdown()}
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{/* Center: breadcrumb absolutely centered */}
|
|
256
|
+
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
257
|
+
<div className={cn('pointer-events-auto flex items-center gap-1', s.px, s.py, 'bg-neutral-960/50 rounded-lg')}>
|
|
258
|
+
{renderSegments()}
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</nav>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
111
265
|
return (
|
|
112
266
|
<nav className={cn('flex items-center', className)}>
|
|
113
|
-
<div className={cn('flex items-center gap-1', s.px, s.py, 'bg-neutral-
|
|
267
|
+
<div className={cn('flex items-center gap-1', s.px, s.py, 'bg-neutral-960/50 border rounded-lg', colorStyle.border)}>
|
|
114
268
|
{leadingAction && LeadIcon && (
|
|
115
269
|
<>
|
|
116
|
-
<NavButton icon={LeadIcon} onClick={leadingAction.onClick} size={size} />
|
|
270
|
+
<NavButton icon={LeadIcon} onClick={leadingAction.onClick} size={size} colorStyle={colorStyle} />
|
|
117
271
|
<Divider size={size} />
|
|
118
272
|
</>
|
|
119
273
|
)}
|
|
@@ -121,8 +275,8 @@ export function NavigationBar({
|
|
|
121
275
|
{hasNav && (
|
|
122
276
|
<>
|
|
123
277
|
<div className="flex items-center gap-0.5">
|
|
124
|
-
<NavButton icon={ChevronLeft} onClick={onBack} disabled={!canGoBack} size={size} />
|
|
125
|
-
<NavButton icon={ChevronRight} onClick={onForward} disabled={!canGoForward} size={size} />
|
|
278
|
+
<NavButton icon={ChevronLeft} onClick={onBack} disabled={!canGoBack} size={size} colorStyle={colorStyle} />
|
|
279
|
+
<NavButton icon={ChevronRight} onClick={onForward} disabled={!canGoForward} size={size} colorStyle={colorStyle} />
|
|
126
280
|
</div>
|
|
127
281
|
<Divider size={size} />
|
|
128
282
|
</>
|
|
@@ -137,87 +291,15 @@ export function NavigationBar({
|
|
|
137
291
|
disabled={!hasHistoryEntries}
|
|
138
292
|
size={size}
|
|
139
293
|
active={historyOpen}
|
|
294
|
+
colorStyle={colorStyle}
|
|
140
295
|
/>
|
|
141
|
-
{
|
|
142
|
-
<div className="absolute left-0 top-full mt-1 w-max min-w-[200px] max-w-[420px] bg-neutral-800 border border-neutral-700 rounded-lg shadow-lg z-50">
|
|
143
|
-
<div className="px-3 py-1.5 border-b border-neutral-700/50">
|
|
144
|
-
<p className="text-sm font-medium text-neutral-500">History</p>
|
|
145
|
-
</div>
|
|
146
|
-
<div className="max-h-[300px] overflow-y-auto py-1">
|
|
147
|
-
{historyEntries.map((entry, i) => (
|
|
148
|
-
<button
|
|
149
|
-
key={i}
|
|
150
|
-
type="button"
|
|
151
|
-
onClick={() => {
|
|
152
|
-
onHistorySelect?.(i)
|
|
153
|
-
setHistoryOpen(false)
|
|
154
|
-
}}
|
|
155
|
-
className="w-full px-3 py-1.5 flex items-center gap-1 text-left hover:bg-neutral-800 transition-colors cursor-pointer"
|
|
156
|
-
>
|
|
157
|
-
{entry.map((seg, segIdx) => (
|
|
158
|
-
<span key={seg.id} className="flex items-center gap-1 min-w-0">
|
|
159
|
-
{segIdx > 0 && <ChevronRight className="w-2.5 h-2.5 text-neutral-600 flex-shrink-0" />}
|
|
160
|
-
{seg.icon && <SegmentIcon icon={seg.icon} color={seg.color} size="xs" />}
|
|
161
|
-
<span className={cn(
|
|
162
|
-
'text-sm truncate',
|
|
163
|
-
seg.color && ACCENT_NAV[seg.color as AccentColor] ? ACCENT_NAV[seg.color as AccentColor].text : 'text-neutral-300',
|
|
164
|
-
)}>
|
|
165
|
-
{seg.label}
|
|
166
|
-
</span>
|
|
167
|
-
</span>
|
|
168
|
-
))}
|
|
169
|
-
</button>
|
|
170
|
-
))}
|
|
171
|
-
</div>
|
|
172
|
-
<div className="px-3 py-1.5 border-t border-neutral-700/50">
|
|
173
|
-
<p className="text-sm text-neutral-600">Click to navigate</p>
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
)}
|
|
296
|
+
{renderHistoryDropdown()}
|
|
177
297
|
</div>
|
|
178
298
|
<Divider size={size} />
|
|
179
299
|
</>
|
|
180
300
|
)}
|
|
181
301
|
|
|
182
|
-
{
|
|
183
|
-
const isLast = index === segments.length - 1
|
|
184
|
-
const isClickable = !isLast && !!segment.onClick
|
|
185
|
-
const colors = segment.color && ACCENT_NAV[segment.color as AccentColor] ? ACCENT_NAV[segment.color as AccentColor] : null
|
|
186
|
-
|
|
187
|
-
return (
|
|
188
|
-
<div key={segment.id} className="flex items-center gap-1 min-w-0">
|
|
189
|
-
{index > 0 && <SegmentSeparator type={separator} size={size} />}
|
|
190
|
-
{isClickable ? (
|
|
191
|
-
<button
|
|
192
|
-
type="button"
|
|
193
|
-
onClick={segment.onClick}
|
|
194
|
-
className={cn(
|
|
195
|
-
'flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0',
|
|
196
|
-
s.text,
|
|
197
|
-
'font-medium hover:text-white',
|
|
198
|
-
colors ? [colors.text, `hover:${colors.bg}`] : ['text-neutral-300', 'hover:bg-neutral-700/50'],
|
|
199
|
-
)}
|
|
200
|
-
>
|
|
201
|
-
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
202
|
-
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
203
|
-
</button>
|
|
204
|
-
) : (
|
|
205
|
-
<div
|
|
206
|
-
className={cn(
|
|
207
|
-
'flex items-center gap-1.5 px-2 py-0.5 rounded-md min-w-0',
|
|
208
|
-
s.text,
|
|
209
|
-
isLast
|
|
210
|
-
? ['font-medium bg-neutral-700/50', colors ? colors.text : 'text-white']
|
|
211
|
-
: ['font-medium', colors ? colors.text : 'text-neutral-300'],
|
|
212
|
-
)}
|
|
213
|
-
>
|
|
214
|
-
{segment.icon && <SegmentIcon icon={segment.icon} color={segment.color} size={size} />}
|
|
215
|
-
<span className="truncate max-w-[200px]">{segment.label}</span>
|
|
216
|
-
</div>
|
|
217
|
-
)}
|
|
218
|
-
</div>
|
|
219
|
-
)
|
|
220
|
-
})}
|
|
302
|
+
{renderSegments()}
|
|
221
303
|
</div>
|
|
222
304
|
</nav>
|
|
223
305
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, type KeyboardEvent } from 'react'
|
|
2
2
|
import { ChevronUp, ChevronDown } from 'lucide-react'
|
|
3
3
|
import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
|
|
4
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
4
5
|
|
|
5
6
|
export interface NumberInputProps {
|
|
6
7
|
value: number
|
|
@@ -9,7 +10,7 @@ export interface NumberInputProps {
|
|
|
9
10
|
max?: number
|
|
10
11
|
step?: number
|
|
11
12
|
variant?: 'filled' | 'outline'
|
|
12
|
-
|
|
13
|
+
accentColor?: FormColor
|
|
13
14
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
14
15
|
disabled?: boolean
|
|
15
16
|
className?: string
|
|
@@ -26,7 +27,7 @@ const SIZE_CONFIG = {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const VARIANT_CLASSES = {
|
|
29
|
-
filled: 'bg-neutral-
|
|
30
|
+
filled: 'bg-neutral-960',
|
|
30
31
|
outline: 'bg-transparent',
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -37,7 +38,7 @@ export function NumberInput({
|
|
|
37
38
|
max,
|
|
38
39
|
step = 1,
|
|
39
40
|
variant = 'outline',
|
|
40
|
-
|
|
41
|
+
accentColor,
|
|
41
42
|
size = 'sm',
|
|
42
43
|
disabled = false,
|
|
43
44
|
className = '',
|
|
@@ -78,8 +79,11 @@ export function NumberInput({
|
|
|
78
79
|
setFocused(false)
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
const contextAccent = useAccentColor()
|
|
83
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
84
|
+
|
|
81
85
|
const sc = SIZE_CONFIG[size]
|
|
82
|
-
const fc = FORM_COLORS[
|
|
86
|
+
const fc = FORM_COLORS[effectiveColor]
|
|
83
87
|
|
|
84
88
|
return (
|
|
85
89
|
<div
|