@toolr/ui-design 0.1.8 → 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/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 +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 +9 -1
- 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 +3 -3
- 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 +37 -9
- 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 +34 -11
- 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 +421 -182
- package/dist/index.js +2984 -1691
- 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
|
@@ -3,6 +3,7 @@ import { RefreshCw } from 'lucide-react'
|
|
|
3
3
|
import { IconButton, type IconName, type ActionItem, iconMap } from './icon-button.tsx'
|
|
4
4
|
import { Badge, type BadgeColor } from './badge.tsx'
|
|
5
5
|
import { Tooltip } from './tooltip.tsx'
|
|
6
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
6
7
|
|
|
7
8
|
/** Status banner configuration for outdated/info messages */
|
|
8
9
|
export interface StatusBanner {
|
|
@@ -59,11 +60,12 @@ export interface BottomPanelHeaderProps<T extends string = string> {
|
|
|
59
60
|
statusBanner?: StatusBanner
|
|
60
61
|
/** Callback when collapse button is clicked */
|
|
61
62
|
onCollapse?: () => void
|
|
63
|
+
accentColor?: FormColor
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// Default styling classes
|
|
65
67
|
const DEFAULT_ACTIVE_TEXT = 'text-neutral-300'
|
|
66
|
-
const DEFAULT_INACTIVE_CLASSES = 'border-transparent text-neutral-500 hover:text-neutral-300 hover:bg-neutral-
|
|
68
|
+
const DEFAULT_INACTIVE_CLASSES = 'border-transparent text-neutral-500 hover:text-neutral-300 hover:bg-neutral-960/30'
|
|
67
69
|
|
|
68
70
|
// Layout mode: full → compact banner → compact tabs
|
|
69
71
|
type LayoutMode = 'full' | 'compact-banner' | 'compact-all'
|
|
@@ -93,6 +95,7 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
93
95
|
customLeftContent,
|
|
94
96
|
statusBanner,
|
|
95
97
|
onCollapse,
|
|
98
|
+
accentColor: _accentColor,
|
|
96
99
|
}: BottomPanelHeaderProps<T>) {
|
|
97
100
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
98
101
|
const actionsRef = useRef<HTMLDivElement>(null)
|
|
@@ -140,7 +143,7 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
140
143
|
return (
|
|
141
144
|
<div
|
|
142
145
|
ref={containerRef}
|
|
143
|
-
className={`flex-shrink-0 h-[41px] flex justify-between bg-neutral-
|
|
146
|
+
className={`flex-shrink-0 h-[41px] flex justify-between bg-neutral-980 border-b border-neutral-960 ${className}`}
|
|
144
147
|
>
|
|
145
148
|
{/* Tabs or custom content - left aligned */}
|
|
146
149
|
<div className="flex flex-shrink-0">
|
|
@@ -156,7 +159,7 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
156
159
|
? tab.activeBorderClass || 'border-current'
|
|
157
160
|
: ''
|
|
158
161
|
const baseClasses = isActive
|
|
159
|
-
? `${textClass} ${borderClass} bg-neutral-
|
|
162
|
+
? `${textClass} ${borderClass} bg-neutral-960/50`
|
|
160
163
|
: DEFAULT_INACTIVE_CLASSES
|
|
161
164
|
const tabButton = (
|
|
162
165
|
<button
|
|
@@ -170,7 +173,7 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
170
173
|
{TabIcon && <TabIcon className="w-[18px] h-[18px]" />}
|
|
171
174
|
{tab.count !== undefined && (
|
|
172
175
|
<span className="absolute -top-1.5 -right-2">
|
|
173
|
-
<Badge value={tab.count}
|
|
176
|
+
<Badge value={tab.count} accentColor={tab.countColor} size="xss" />
|
|
174
177
|
</span>
|
|
175
178
|
)}
|
|
176
179
|
</span>
|
|
@@ -185,7 +188,7 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
185
188
|
<span>{tab.label}</span>
|
|
186
189
|
)}
|
|
187
190
|
{tab.count !== undefined && (
|
|
188
|
-
<Badge value={tab.count}
|
|
191
|
+
<Badge value={tab.count} accentColor={tab.countColor} size="xss" />
|
|
189
192
|
)}
|
|
190
193
|
</>
|
|
191
194
|
)}
|
|
@@ -231,6 +234,7 @@ export function BottomPanelHeader<T extends string = string>({
|
|
|
231
234
|
icon="panel-bottom-close"
|
|
232
235
|
onClick={onCollapse}
|
|
233
236
|
size="sm"
|
|
237
|
+
accentColor="neutral"
|
|
234
238
|
tooltip={{ description: 'Collapse panel' }}
|
|
235
239
|
/>
|
|
236
240
|
)}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { ChevronRight } from 'lucide-react'
|
|
4
4
|
import { iconMap, type IconName } from './icon-button.tsx'
|
|
5
5
|
import { ACCENT_NAV, type AccentColor } from '../lib/form-colors.ts'
|
|
6
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
7
|
+
import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
|
|
6
8
|
import { cn } from '../lib/cn.ts'
|
|
7
9
|
|
|
8
10
|
export interface BreadcrumbSegment {
|
|
@@ -20,6 +22,7 @@ export interface BreadcrumbProps {
|
|
|
20
22
|
/** 'box' (default) wraps in a bordered container; 'plain' renders inline with no background */
|
|
21
23
|
variant?: 'box' | 'plain'
|
|
22
24
|
className?: string
|
|
25
|
+
accentColor?: FormColor
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
const sizeConfig = {
|
|
@@ -62,15 +65,19 @@ export function Breadcrumb({
|
|
|
62
65
|
size = 'sm',
|
|
63
66
|
variant = 'box',
|
|
64
67
|
className,
|
|
68
|
+
accentColor,
|
|
65
69
|
}: BreadcrumbProps) {
|
|
70
|
+
const contextAccent = useAccentColor()
|
|
71
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
66
72
|
const s = sizeConfig[size]
|
|
67
73
|
const isBox = variant === 'box'
|
|
68
74
|
|
|
69
75
|
return (
|
|
76
|
+
<AccentColorProvider value={effectiveColor}>
|
|
70
77
|
<nav className={cn('flex items-center min-w-0', className)}>
|
|
71
78
|
<div className={cn(
|
|
72
79
|
'flex items-center gap-1',
|
|
73
|
-
isBox && [s.px, s.py, 'bg-neutral-
|
|
80
|
+
isBox && [s.px, s.py, 'bg-neutral-960/50 border border-neutral-700/50 rounded-lg'],
|
|
74
81
|
)}>
|
|
75
82
|
{segments.map((segment, index) => {
|
|
76
83
|
const isLast = index === segments.length - 1
|
|
@@ -116,5 +123,6 @@ export function Breadcrumb({
|
|
|
116
123
|
})}
|
|
117
124
|
</div>
|
|
118
125
|
</nav>
|
|
126
|
+
</AccentColorProvider>
|
|
119
127
|
)
|
|
120
128
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* CapabilityListCard — Standardized card for capability list view items.
|
|
3
3
|
*
|
|
4
4
|
* Used across apps for skill, command, hook, agent, plugin, MCP server,
|
|
5
5
|
* Gemini extension, and marketplace cards.
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
import { memo, useState, useRef, useCallback, useEffect, type ReactNode, type ElementType } from 'react'
|
|
16
16
|
import { cn } from '../lib/cn.ts'
|
|
17
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
18
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
17
19
|
|
|
18
|
-
export interface
|
|
20
|
+
export interface CapabilityListCardProps {
|
|
19
21
|
/** Lucide icon component */
|
|
20
22
|
icon: ElementType
|
|
21
23
|
/** Tailwind color class for icon (e.g. 'text-teal-400') */
|
|
@@ -38,9 +40,10 @@ export interface ExtensionListCardProps {
|
|
|
38
40
|
isInactive?: boolean
|
|
39
41
|
/** Test ID for E2E testing */
|
|
40
42
|
testId?: string
|
|
43
|
+
accentColor?: FormColor
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
export const
|
|
46
|
+
export const CapabilityListCard = memo(function CapabilityListCard({
|
|
44
47
|
icon: Icon,
|
|
45
48
|
iconColor,
|
|
46
49
|
borderColor,
|
|
@@ -52,7 +55,10 @@ export const ExtensionListCard = memo(function ExtensionListCard({
|
|
|
52
55
|
metadata,
|
|
53
56
|
isInactive,
|
|
54
57
|
testId,
|
|
55
|
-
|
|
58
|
+
accentColor: accentColorProp,
|
|
59
|
+
}: CapabilityListCardProps) {
|
|
60
|
+
const contextAccent = useAccentColor()
|
|
61
|
+
const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
|
|
56
62
|
const [isHovered, setIsHovered] = useState(false)
|
|
57
63
|
const hoverTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined)
|
|
58
64
|
useEffect(() => () => clearTimeout(hoverTimerRef.current), [])
|
|
@@ -65,9 +71,10 @@ export const ExtensionListCard = memo(function ExtensionListCard({
|
|
|
65
71
|
}, [])
|
|
66
72
|
|
|
67
73
|
return (
|
|
74
|
+
<AccentColorProvider value={effectiveColor}>
|
|
68
75
|
<div
|
|
69
76
|
className={cn(
|
|
70
|
-
'relative flex flex-col p-3 bg-neutral-
|
|
77
|
+
'relative flex flex-col p-3 bg-neutral-960/50 rounded-lg border border-neutral-700/50 border-l-4 hover:bg-neutral-960 transition-colors',
|
|
71
78
|
borderColor,
|
|
72
79
|
isInactive && 'opacity-60',
|
|
73
80
|
isHovered ? 'h-auto z-10' : 'h-full',
|
|
@@ -101,5 +108,6 @@ export const ExtensionListCard = memo(function ExtensionListCard({
|
|
|
101
108
|
</div>
|
|
102
109
|
)}
|
|
103
110
|
</div>
|
|
111
|
+
</AccentColorProvider>
|
|
104
112
|
)
|
|
105
113
|
})
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { Check } from 'lucide-react'
|
|
11
11
|
import { type AccentColor } from '../lib/form-colors.ts'
|
|
12
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
12
13
|
import { cn } from '../lib/cn.ts'
|
|
13
14
|
|
|
14
15
|
export type CheckboxSize = 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
@@ -48,7 +49,7 @@ export interface CheckboxProps {
|
|
|
48
49
|
onChange: (checked: boolean) => void
|
|
49
50
|
disabled?: boolean
|
|
50
51
|
size?: CheckboxSize
|
|
51
|
-
|
|
52
|
+
accentColor?: CheckboxColor
|
|
52
53
|
variant?: CheckboxVariant
|
|
53
54
|
className?: string
|
|
54
55
|
/** Accessible label — required for screen readers */
|
|
@@ -62,14 +63,16 @@ export function Checkbox({
|
|
|
62
63
|
onChange,
|
|
63
64
|
disabled = false,
|
|
64
65
|
size = 'sm',
|
|
65
|
-
|
|
66
|
+
accentColor,
|
|
66
67
|
variant = 'outline',
|
|
67
68
|
className,
|
|
68
69
|
'aria-label': ariaLabel,
|
|
69
70
|
testId,
|
|
70
71
|
}: CheckboxProps) {
|
|
72
|
+
const contextAccent = useAccentColor()
|
|
73
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
71
74
|
const s = CHECKBOX_SIZES[size]
|
|
72
|
-
const c = CHECKBOX_COLORS[
|
|
75
|
+
const c = CHECKBOX_COLORS[effectiveColor]
|
|
73
76
|
return (
|
|
74
77
|
<button
|
|
75
78
|
type="button"
|
|
@@ -3,6 +3,9 @@ import { ChevronRight } from 'lucide-react'
|
|
|
3
3
|
import { iconMap, type IconName } from './icon-button.tsx'
|
|
4
4
|
import { Badge, type BadgeColor } from './badge.tsx'
|
|
5
5
|
import { cn } from '../lib/cn.ts'
|
|
6
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
7
|
+
import { AccentColorProvider } from '../lib/accent-context.ts'
|
|
8
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
6
9
|
|
|
7
10
|
export interface CollapsibleSectionProps {
|
|
8
11
|
title: string
|
|
@@ -13,6 +16,7 @@ export interface CollapsibleSectionProps {
|
|
|
13
16
|
badgeColor?: BadgeColor
|
|
14
17
|
children: React.ReactNode
|
|
15
18
|
className?: string
|
|
19
|
+
accentColor?: FormColor
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
export function CollapsibleSection({
|
|
@@ -24,40 +28,45 @@ export function CollapsibleSection({
|
|
|
24
28
|
badgeColor,
|
|
25
29
|
children,
|
|
26
30
|
className,
|
|
31
|
+
accentColor,
|
|
27
32
|
}: CollapsibleSectionProps) {
|
|
33
|
+
const contextAccent = useAccentColor()
|
|
34
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
28
35
|
const [open, setOpen] = useState(defaultOpen)
|
|
29
36
|
const Icon = icon ? iconMap[icon] : undefined
|
|
30
37
|
|
|
31
38
|
return (
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
<AccentColorProvider value={effectiveColor}>
|
|
40
|
+
<div className={cn('border-b border-neutral-700', className)}>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
aria-expanded={open}
|
|
44
|
+
onClick={() => setOpen(!open)}
|
|
45
|
+
className="flex w-full items-center gap-2 py-2.5 px-1 text-left hover:bg-neutral-700/30 transition-colors cursor-pointer"
|
|
46
|
+
>
|
|
47
|
+
<ChevronRight
|
|
48
|
+
className={cn(
|
|
49
|
+
'w-3.5 h-3.5 text-neutral-500 transition-transform duration-150',
|
|
50
|
+
open && 'rotate-90',
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
{Icon && (
|
|
54
|
+
<span
|
|
55
|
+
className="flex items-center justify-center w-5 h-5 rounded bg-neutral-700/60"
|
|
56
|
+
style={iconColor ? { color: iconColor } : undefined}
|
|
57
|
+
>
|
|
58
|
+
<Icon className="w-3.5 h-3.5" />
|
|
59
|
+
</span>
|
|
43
60
|
)}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{badge !== undefined && (
|
|
55
|
-
<span className="ml-auto">
|
|
56
|
-
<Badge value={badge} color={badgeColor} size="xss" />
|
|
57
|
-
</span>
|
|
58
|
-
)}
|
|
59
|
-
</button>
|
|
60
|
-
{open && <div className="pb-3 pl-7 pr-2">{children}</div>}
|
|
61
|
-
</div>
|
|
61
|
+
<span className="text-md font-medium text-neutral-200">{title}</span>
|
|
62
|
+
{badge !== undefined && (
|
|
63
|
+
<span className="ml-auto">
|
|
64
|
+
<Badge value={badge} accentColor={badgeColor} size="xss" />
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
</button>
|
|
68
|
+
{open && <div className="pb-3 pl-7 pr-2">{children}</div>}
|
|
69
|
+
</div>
|
|
70
|
+
</AccentColorProvider>
|
|
62
71
|
)
|
|
63
72
|
}
|
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
import { memo } from 'react'
|
|
15
15
|
import { Check } from 'lucide-react'
|
|
16
16
|
import { FORM_COLORS, type AccentColor } from '../lib/form-colors.ts'
|
|
17
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
17
18
|
import { cn } from '../lib/cn.ts'
|
|
18
19
|
|
|
19
20
|
export type ConfirmBadgeColor = AccentColor
|
|
20
21
|
|
|
21
22
|
export interface ConfirmBadgeProps {
|
|
22
|
-
|
|
23
|
+
accentColor?: ConfirmBadgeColor
|
|
23
24
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
24
25
|
className?: string
|
|
25
26
|
testId?: string
|
|
@@ -42,18 +43,20 @@ const iconSizeClasses = {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export const ConfirmBadge = memo(function ConfirmBadge({
|
|
45
|
-
|
|
46
|
+
accentColor,
|
|
46
47
|
size = 'sm',
|
|
47
48
|
className,
|
|
48
49
|
testId,
|
|
49
50
|
}: ConfirmBadgeProps) {
|
|
51
|
+
const contextAccent = useAccentColor()
|
|
52
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'neutral'
|
|
50
53
|
return (
|
|
51
54
|
<span
|
|
52
55
|
data-testid={testId}
|
|
53
56
|
className={cn(
|
|
54
57
|
'inline-flex items-center justify-center border',
|
|
55
|
-
FORM_COLORS[
|
|
56
|
-
FORM_COLORS[
|
|
58
|
+
FORM_COLORS[effectiveColor].border,
|
|
59
|
+
FORM_COLORS[effectiveColor].accent,
|
|
57
60
|
sizeClasses[size],
|
|
58
61
|
className,
|
|
59
62
|
)}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
import { IconButton } from './icon-button.tsx'
|
|
3
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
4
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
3
5
|
|
|
4
6
|
export type ConsentChoice = 'accepted' | 'declined' | 'essential'
|
|
5
7
|
|
|
@@ -7,7 +9,7 @@ export interface CookieConsentProps {
|
|
|
7
9
|
/** localStorage key used to persist the choice */
|
|
8
10
|
storageKey: string
|
|
9
11
|
/** Accent color for the "Accept All" button (default: 'cyan') */
|
|
10
|
-
accentColor?:
|
|
12
|
+
accentColor?: FormColor
|
|
11
13
|
/** Heading text (default: 'We value your privacy') */
|
|
12
14
|
heading?: string
|
|
13
15
|
/** Description text */
|
|
@@ -30,15 +32,19 @@ const ACCENT_BUTTON_CLASSES: Record<string, string> = {
|
|
|
30
32
|
teal: 'bg-teal-600 border-teal-500 hover:bg-teal-500',
|
|
31
33
|
violet: 'bg-violet-600 border-violet-500 hover:bg-violet-500',
|
|
32
34
|
sky: 'bg-sky-600 border-sky-500 hover:bg-sky-500',
|
|
35
|
+
yellow: 'bg-yellow-600 border-yellow-500 hover:bg-yellow-500',
|
|
36
|
+
neutral: 'bg-neutral-600 border-neutral-500 hover:bg-neutral-500',
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export function CookieConsent({
|
|
36
40
|
storageKey,
|
|
37
|
-
accentColor
|
|
41
|
+
accentColor,
|
|
38
42
|
heading = 'We value your privacy',
|
|
39
43
|
description = 'This site uses only essential cookies for functionality. No tracking or analytics.',
|
|
40
44
|
onConsent,
|
|
41
45
|
}: CookieConsentProps) {
|
|
46
|
+
const contextAccent = useAccentColor()
|
|
47
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'cyan'
|
|
42
48
|
const [isVisible, setIsVisible] = useState(false)
|
|
43
49
|
|
|
44
50
|
useEffect(() => {
|
|
@@ -55,7 +61,7 @@ export function CookieConsent({
|
|
|
55
61
|
if (!isVisible) return null
|
|
56
62
|
|
|
57
63
|
return (
|
|
58
|
-
<div className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-neutral-
|
|
64
|
+
<div className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-neutral-980 border-t border-neutral-700/50">
|
|
59
65
|
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row items-start sm:items-center gap-4">
|
|
60
66
|
<div className="flex-grow">
|
|
61
67
|
<p className="text-md text-neutral-200 mb-1">{heading}</p>
|
|
@@ -65,19 +71,19 @@ export function CookieConsent({
|
|
|
65
71
|
<div className="flex items-center gap-2 flex-shrink-0">
|
|
66
72
|
<button
|
|
67
73
|
onClick={() => handleConsent('declined')}
|
|
68
|
-
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-
|
|
74
|
+
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-960 transition-colors"
|
|
69
75
|
>
|
|
70
76
|
Decline
|
|
71
77
|
</button>
|
|
72
78
|
<button
|
|
73
79
|
onClick={() => handleConsent('essential')}
|
|
74
|
-
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-
|
|
80
|
+
className="px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-neutral-400 border border-transparent hover:text-neutral-200 hover:border-neutral-600 hover:bg-neutral-960 transition-colors"
|
|
75
81
|
>
|
|
76
82
|
Essential Only
|
|
77
83
|
</button>
|
|
78
84
|
<button
|
|
79
85
|
onClick={() => handleConsent('accepted')}
|
|
80
|
-
className={`px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-white border transition-colors ${ACCENT_BUTTON_CLASSES[
|
|
86
|
+
className={`px-3 py-1.5 text-md h-[26px] inline-flex items-center justify-center font-medium rounded-md cursor-pointer text-white border transition-colors ${ACCENT_BUTTON_CLASSES[effectiveColor] ?? ACCENT_BUTTON_CLASSES.cyan}`}
|
|
81
87
|
>
|
|
82
88
|
Accept All
|
|
83
89
|
</button>
|
|
@@ -85,7 +91,7 @@ export function CookieConsent({
|
|
|
85
91
|
|
|
86
92
|
<IconButton
|
|
87
93
|
icon="x"
|
|
88
|
-
|
|
94
|
+
accentColor="neutral"
|
|
89
95
|
size="xs"
|
|
90
96
|
tooltip={{ description: 'Dismiss' }}
|
|
91
97
|
tooltipPosition="top"
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import { iconMap, type IconName } from './icon-button.tsx'
|
|
11
11
|
import { cn } from '../lib/cn.ts'
|
|
12
|
+
import { useAccentColor } from '../lib/accent-context.ts'
|
|
13
|
+
import { AccentColorProvider } from '../lib/accent-context.ts'
|
|
14
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
12
15
|
|
|
13
16
|
export interface DetailRow {
|
|
14
17
|
label: string
|
|
@@ -24,27 +27,32 @@ export interface DetailSectionProps {
|
|
|
24
27
|
/** Detail rows to display */
|
|
25
28
|
rows: DetailRow[]
|
|
26
29
|
className?: string
|
|
30
|
+
accentColor?: FormColor
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
export function DetailSection({ title, icon, rows, className }: DetailSectionProps) {
|
|
33
|
+
export function DetailSection({ title, icon, rows, className, accentColor }: DetailSectionProps) {
|
|
34
|
+
const contextAccent = useAccentColor()
|
|
35
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
30
36
|
const Icon = icon ? iconMap[icon] : undefined
|
|
31
37
|
|
|
32
38
|
return (
|
|
33
|
-
<
|
|
34
|
-
<div className=
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
<AccentColorProvider value={effectiveColor}>
|
|
40
|
+
<div className={className}>
|
|
41
|
+
<div className="flex items-center gap-2 mb-3">
|
|
42
|
+
{Icon && <Icon className="w-4 h-4 text-neutral-500" />}
|
|
43
|
+
<span className="text-md font-medium text-neutral-400">{title}</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="space-y-2">
|
|
46
|
+
{rows.map((row) => (
|
|
47
|
+
<div key={row.label} className="flex items-start gap-3">
|
|
48
|
+
<span className="text-sm text-neutral-500 w-24 shrink-0">{row.label}:</span>
|
|
49
|
+
<span className={cn('text-sm text-neutral-400', row.mono && 'font-mono')}>
|
|
50
|
+
{row.value}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
37
55
|
</div>
|
|
38
|
-
|
|
39
|
-
{rows.map((row) => (
|
|
40
|
-
<div key={row.label} className="flex items-start gap-3">
|
|
41
|
-
<span className="text-sm text-neutral-500 w-24 shrink-0">{row.label}:</span>
|
|
42
|
-
<span className={cn('text-sm text-neutral-400', row.mono && 'font-mono')}>
|
|
43
|
-
{row.value}
|
|
44
|
-
</span>
|
|
45
|
-
</div>
|
|
46
|
-
))}
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
56
|
+
</AccentColorProvider>
|
|
49
57
|
)
|
|
50
58
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react'
|
|
2
|
+
import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
|
|
3
|
+
import type { FormColor } from '../lib/form-colors.ts'
|
|
2
4
|
|
|
3
5
|
export interface DetailViewWrapperProps {
|
|
4
6
|
/** Main editor content */
|
|
@@ -12,6 +14,7 @@ export interface DetailViewWrapperProps {
|
|
|
12
14
|
/** Optional right sidebar */
|
|
13
15
|
rightSidebar?: ReactNode
|
|
14
16
|
showRightSidebar?: boolean
|
|
17
|
+
accentColor?: FormColor
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export function DetailViewWrapper({
|
|
@@ -20,36 +23,41 @@ export function DetailViewWrapper({
|
|
|
20
23
|
bottomPanel,
|
|
21
24
|
rightSidebar,
|
|
22
25
|
showRightSidebar = false,
|
|
26
|
+
accentColor,
|
|
23
27
|
}: DetailViewWrapperProps) {
|
|
28
|
+
const contextAccent = useAccentColor()
|
|
29
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
24
30
|
|
|
25
31
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<div className="flex items-center
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
<AccentColorProvider value={effectiveColor}>
|
|
33
|
+
<div className="flex-1 flex flex-col overflow-hidden min-h-0">
|
|
34
|
+
{actions && (
|
|
35
|
+
<div className="flex items-center justify-end border-b border-neutral-960 bg-neutral-990/50 px-2 h-[36px]">
|
|
36
|
+
<div className="flex items-center gap-1">{actions}</div>
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
{/* Main content area */}
|
|
41
|
+
<div className="flex-1 flex overflow-hidden min-h-0">
|
|
42
|
+
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
43
|
+
{editorContent && (
|
|
44
|
+
<div className="flex-1 flex flex-col overflow-hidden">{editorContent}</div>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Optional right sidebar */}
|
|
49
|
+
{showRightSidebar && rightSidebar && (
|
|
50
|
+
<div className="w-[300px] border-l border-neutral-960 overflow-hidden flex-shrink-0">
|
|
51
|
+
{rightSidebar}
|
|
52
|
+
</div>
|
|
38
53
|
)}
|
|
39
54
|
</div>
|
|
40
55
|
|
|
41
|
-
{/* Optional
|
|
42
|
-
{
|
|
43
|
-
<div className="
|
|
44
|
-
{rightSidebar}
|
|
45
|
-
</div>
|
|
56
|
+
{/* Optional bottom panel */}
|
|
57
|
+
{bottomPanel && (
|
|
58
|
+
<div className="border-t border-neutral-960">{bottomPanel}</div>
|
|
46
59
|
)}
|
|
47
60
|
</div>
|
|
48
|
-
|
|
49
|
-
{/* Optional bottom panel */}
|
|
50
|
-
{bottomPanel && (
|
|
51
|
-
<div className="border-t border-neutral-800">{bottomPanel}</div>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
61
|
+
</AccentColorProvider>
|
|
54
62
|
)
|
|
55
63
|
}
|