@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
interface PortalPosition {
|
|
4
|
+
top: number
|
|
5
|
+
left: number
|
|
6
|
+
minWidth: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Positions a portal-based dropdown menu below its trigger element.
|
|
11
|
+
* Returns a ref for the trigger container and computed position for the portal.
|
|
12
|
+
*/
|
|
13
|
+
export function useDropdownPortal(isOpen: boolean, margin = 16) {
|
|
14
|
+
const triggerRef = useRef<HTMLDivElement>(null)
|
|
15
|
+
const menuRef = useRef<HTMLDivElement>(null)
|
|
16
|
+
const [position, setPosition] = useState<PortalPosition>({ top: 0, left: 0, minWidth: 0 })
|
|
17
|
+
|
|
18
|
+
useLayoutEffect(() => {
|
|
19
|
+
if (!isOpen || !triggerRef.current) return
|
|
20
|
+
|
|
21
|
+
const rect = triggerRef.current.getBoundingClientRect()
|
|
22
|
+
setPosition({
|
|
23
|
+
top: rect.bottom + 4,
|
|
24
|
+
left: rect.left,
|
|
25
|
+
minWidth: rect.width,
|
|
26
|
+
})
|
|
27
|
+
}, [isOpen])
|
|
28
|
+
|
|
29
|
+
// Constrain max-height to viewport
|
|
30
|
+
useLayoutEffect(() => {
|
|
31
|
+
if (!isOpen || !menuRef.current) return
|
|
32
|
+
const rect = menuRef.current.getBoundingClientRect()
|
|
33
|
+
menuRef.current.style.maxHeight = `${window.innerHeight - rect.top - margin}px`
|
|
34
|
+
menuRef.current.style.overflowY = 'auto'
|
|
35
|
+
menuRef.current.style.overscrollBehavior = 'contain'
|
|
36
|
+
}, [isOpen, margin, position])
|
|
37
|
+
|
|
38
|
+
return { triggerRef, menuRef, position }
|
|
39
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
1
|
+
import { useEffect, type RefObject } from 'react'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Shared modal behavior: Escape key to close + body overflow lock.
|
|
4
|
+
* Shared modal behavior: Escape key to close + body overflow lock + focus trap.
|
|
5
5
|
*/
|
|
6
|
-
export function useModalBehavior(isOpen: boolean, onClose: () => void): void {
|
|
6
|
+
export function useModalBehavior(isOpen: boolean, onClose: () => void, containerRef?: RefObject<HTMLElement | null>): void {
|
|
7
7
|
// Escape key handler
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
if (!isOpen) return
|
|
@@ -21,4 +21,33 @@ export function useModalBehavior(isOpen: boolean, onClose: () => void): void {
|
|
|
21
21
|
document.body.style.overflow = 'hidden'
|
|
22
22
|
return () => { document.body.style.overflow = prev }
|
|
23
23
|
}, [isOpen])
|
|
24
|
+
|
|
25
|
+
// Focus trap: keep Tab/Shift+Tab within the modal
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!isOpen || !containerRef?.current) return
|
|
28
|
+
const container = containerRef.current
|
|
29
|
+
|
|
30
|
+
// Auto-focus first focusable element
|
|
31
|
+
const focusable = container.querySelectorAll<HTMLElement>(
|
|
32
|
+
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
33
|
+
)
|
|
34
|
+
if (focusable.length > 0) focusable[0].focus()
|
|
35
|
+
|
|
36
|
+
const handler = (e: KeyboardEvent) => {
|
|
37
|
+
if (e.key !== 'Tab') return
|
|
38
|
+
const nodes = container.querySelectorAll<HTMLElement>(
|
|
39
|
+
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
40
|
+
)
|
|
41
|
+
if (nodes.length === 0) return
|
|
42
|
+
const first = nodes[0]
|
|
43
|
+
const last = nodes[nodes.length - 1]
|
|
44
|
+
if (e.shiftKey) {
|
|
45
|
+
if (document.activeElement === first) { e.preventDefault(); last.focus() }
|
|
46
|
+
} else {
|
|
47
|
+
if (document.activeElement === last) { e.preventDefault(); first.focus() }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
document.addEventListener('keydown', handler)
|
|
51
|
+
return () => document.removeEventListener('keydown', handler)
|
|
52
|
+
}, [isOpen, containerRef])
|
|
24
53
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
import type { FormColor } from './form-colors.ts'
|
|
3
|
+
|
|
4
|
+
const AccentColorContext = createContext<FormColor | undefined>(undefined)
|
|
5
|
+
|
|
6
|
+
export const AccentColorProvider = AccentColorContext.Provider
|
|
7
|
+
|
|
8
|
+
export function useAccentColor(): FormColor | undefined {
|
|
9
|
+
return useContext(AccentColorContext)
|
|
10
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { useAccentColor } from './accent-context.ts'
|
|
2
|
+
import { cn } from './cn.ts'
|
|
3
|
+
import { FORM_COLORS, type FormColor } from './form-colors.ts'
|
|
4
|
+
|
|
5
|
+
export const CODING_AGENT_LOGOS = {
|
|
2
6
|
claude: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAJFBMVEVHcEzZd1fZd1fZd1fZd1fad1jZd1fZd1fZd1fZd1fZd1fZd1deZDooAAAADHRSTlMA//F3mhLfyjpWsiMDGU5mAAABH0lEQVQokXVSWXbEIAzDuw33v28FJCnpTP3BA6+yRGvbLK39a0F9RUvHZ5CIazZwkm+VpCi1nZP1GpJEnq2NdabzU594N0XpzInRhq/7jvm8wsOjFfVmcQG4guTWZKYL8HSiA1XFHDjgHMqCpKfpNPgIXiZVVvSJ9yazOJARxBiYf/ZMoIUxrYGjRHs/di2nbdzDZxIb1maPriold3QlyFJCAonMd08A7/WhkN19PWDolSeiXU7URWPm8S3ewMCuvGpB+kigVXvWZKlgIVycF0Hjl6DIoVRCGKz9pE8W0QKXkgkIEmjzUDuh11TSuVkn348DLHs1Y7/kzTi+ksX8GLn0wBRrdvCQS949C43fstj6FhfM8Unfqqwv3gf3+/kDMJgHC0kwnjEAAAAASUVORK5CYII=',
|
|
3
7
|
copilot: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHJ4PSI2IiBmaWxsPSIjMWYxZjFmIi8+PHBhdGggZD0iTTE0IDVjLTQuOTcgMC05IDQuMDMtOSA5IDAgMy45NyAyLjU2IDcuMzQgNi4xMiA4LjUzLjQ1LjA4LjYxLS4xOS42MS0uNDN2LTEuNjdjLTIuNDkuNTQtMy4wMS0xLjA2LTMuMDEtMS4wNi0uNDEtMS4wNC0uOTktMS4zMS0uOTktMS4zMS0uODEtLjU2LjA2LS41NC4wNi0uNTQuOS4wNiAxLjM3LjkyIDEuMzcuOTIuOCAxLjM3IDIuMDkuOTggMi42Ljc0LjA4LS41OC4zMS0uOTguNTctMS4yLTEuOTktLjIzLTQuMDgtLjk5LTQuMDgtNC40MiAwLS45OC4zNS0xLjc4LjkyLTIuNDEtLjA5LS4yMy0uNC0xLjE0LjA5LTIuMzcgMCAwIC43NS0uMjQgMi40Ni45Mi43MS0uMiAxLjQ4LS4zIDIuMjQtLjNzMS41My4xIDIuMjQuMzFjMS43MS0xLjE2IDIuNDYtLjkyIDIuNDYtLjkyLjQ5IDEuMjMuMTggMi4xNC4wOSAyLjM3LjU3LjYzLjkyIDEuNDMuOTIgMi40MSAwIDMuNDQtMi4xIDQuMTktNC4wOSA0LjQxLjMyLjI4LjYxLjgyLjYxIDEuNjZ2Mi40NmMwIC4yNC4xNi41MS42MS40M0MxOS40NCAyMS4zNCAyMiAxOC4wMyAyMiAxNGMwLTQuOTctNC4wMy05LTktOVoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=',
|
|
4
8
|
codex: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAP1BMVEVHcEwAgPgAgPgAgPcAgPcAgPcAgPcAgPcAgPcAgPcAd/dzsvq+2/2Kvvtfpvru+P/Z6/77/v83lvmnzfwbjPhuMd/TAAAACnRSTlMAF0adyun/9k+LLZxzywAAAXZJREFUeAFkUlmWwyAMC03qZITxAtz/rPNiaLpEvwhJCC1vpMe6PYn2bT3Sckf6e9KF55pux/SDvy9K2uiG7YNx7BQAgDdjP677r/PMXOhN2tOXPkSN2byUkis+XV75vBFA7mqugpE0BKY+M0DNuQIoXgbjlFinv1Zk0wxUAppeEin66ebe4Q2o7J6pq4zG0nLEfTPRDhUUNSlu3fKQeAwH0V5PQlPV1kHFfcZcl20GPAnuuWe1DIjx7HPZw+EkEOJ1cDcBtCBCLBGhGVVlimTQ3FxQbHgEgaoLuoVx6FiG+CQ8h4RWUFYVdHYhy8iDsEfI6IEjvTlrEJgxQq7DQ8W0kbgKgiAu85mPQYiiPZo+LdRnUcesGsZAzxVozp2MBa+q5293bx2gs2kgK2hgvb4b4mpsWgggbS9C+hgMcmtFrRT2ck37e9IAajHjjJ9hz9FO0n201+wn7rP/H76MQzjrIZQwI2deTqz5m4OZhR0oycLMhCQNACpMHlEZrAKTAAAAAElFTkSuQmCC',
|
|
@@ -6,9 +10,9 @@ export const AI_TOOL_LOGOS = {
|
|
|
6
10
|
opencode: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAABl0lEQVRYCe1XS2rDMBAd/70xBJoubJqVF83Sm+YooVcIPUDJTUpOUHIa71Ov0ht4Y/yJ6zEYHFnyZBKKNx4IkTSjNw/pzQhrT4tFDROaPmHuNvVMYPITMCkNVFVFhYz6DcMY9SsJ1HUNmqbB1+EAYRiOgqicSZLAx24HHZYsTkmgC46iCF7X627K+vc8j4wnNVAUBQmiCrhlL3kCIjilCerORTwWgTzP4X27hd/zGcRESOxltYLv4xFs2xbzKOcsAiimn9MJUFymeb21LEvIsqwVnDKbxHGNIgkQl8ymrDC5SADj0Mc1NgGjSW5Z1oAAliz6uMbaoes6vG027V3LNOD7PmAMx1gEENhxHHBdVypC9HGNTQCF2P36yWRrfb9qzDsvFcoD6zMBtgaw48nasWqduh0WAaz1IAjAalqtIZRbdbnA83LZPuFU0r6fRQC73+d+398/GP97H+AmGDAUFsgqwLZ7r92yl7yCOI4hTdO7OOCrSZlGfRnJFE+B9v3im9H34Zg8AQpABOTOSQ1wAbnxM4HJT+APfgFr0DSNhC0AAAAASUVORK5CYII=',
|
|
7
11
|
} as const
|
|
8
12
|
|
|
9
|
-
export type
|
|
13
|
+
export type CodingAgentKey = 'claude' | 'gemini' | 'codex' | 'copilot' | 'opencode'
|
|
10
14
|
|
|
11
|
-
export const
|
|
15
|
+
export const CODING_AGENT_NAMES: Record<CodingAgentKey, string> = {
|
|
12
16
|
claude: 'Claude Code',
|
|
13
17
|
gemini: 'Gemini CLI',
|
|
14
18
|
codex: 'Codex CLI',
|
|
@@ -16,21 +20,32 @@ export const AI_TOOL_NAMES: Record<AiToolKey, string> = {
|
|
|
16
20
|
opencode: 'Opencode',
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
export function
|
|
20
|
-
|
|
23
|
+
export function CodingAgentIcon({ agent, size, showName, selected, accentColor, className, style }: {
|
|
24
|
+
agent: string
|
|
21
25
|
size?: number
|
|
22
26
|
showName?: boolean
|
|
27
|
+
selected?: boolean
|
|
28
|
+
accentColor?: FormColor
|
|
23
29
|
className?: string
|
|
24
30
|
style?: React.CSSProperties
|
|
25
31
|
}) {
|
|
26
|
-
const
|
|
32
|
+
const contextAccent = useAccentColor()
|
|
33
|
+
const src = CODING_AGENT_LOGOS[agent as keyof typeof CODING_AGENT_LOGOS]
|
|
27
34
|
if (!src) return null
|
|
35
|
+
|
|
36
|
+
const effectiveAccent = accentColor ?? contextAccent ?? 'blue'
|
|
37
|
+
const accentStyles = selected ? FORM_COLORS[effectiveAccent] : undefined
|
|
38
|
+
|
|
28
39
|
const img = (
|
|
29
40
|
<img
|
|
30
41
|
src={src}
|
|
31
42
|
alt=""
|
|
32
43
|
{...(size ? { width: size, height: size } : {})}
|
|
33
|
-
className={
|
|
44
|
+
className={cn(
|
|
45
|
+
selected && 'rounded-md border-2 p-0.5',
|
|
46
|
+
selected && accentStyles?.border,
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
34
49
|
style={{ objectFit: 'contain', ...style }}
|
|
35
50
|
/>
|
|
36
51
|
)
|
|
@@ -38,7 +53,7 @@ export function AiToolIcon({ tool, size, showName, className, style }: {
|
|
|
38
53
|
return (
|
|
39
54
|
<span style={{ display: 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
|
|
40
55
|
{img}
|
|
41
|
-
<span className="text-xs text-neutral-400">{
|
|
56
|
+
<span className="text-xs text-neutral-400">{CODING_AGENT_NAMES[agent as CodingAgentKey] ?? agent}</span>
|
|
42
57
|
</span>
|
|
43
58
|
)
|
|
44
59
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { cn } from './cn.ts'
|
|
2
|
+
|
|
3
|
+
export type CustomIconKey = 'gerrit'
|
|
4
|
+
|
|
5
|
+
export const CUSTOM_ICON_NAMES: Record<CustomIconKey, string> = {
|
|
6
|
+
gerrit: 'Gerrit',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const GERRIT_PATH = 'M12.648 2.678l-.245-.266c.004-.004.29-.268.413-.41.121-.146.342-.484.346-.486l.301.195c-.014.016-.234.359-.375.522-.137.165-.428.432-.44.445zm1.577 10.597c-.012-.004-.24-.154-.365-.221-.117-.059-.32-.146-.422-.191l.213-.612-.898-.444-.286.871c-.174.004-.713.053-1.51.389-.959.4-1.688 1.025-1.695 1.029l-.143.125.641.025.02-.016c.006-.006.721-.535 1.119-.705.088-.037.207-.074.33-.105-.209.105-.439.227-.6.32-.199.119-.57.381-.586.393l-.186.129.682.016.018-.01c.012-.008 1.164-.623 1.789-.76l.196-.047c.145-.037.246-.064.422-.064.156 0 .369.021.688.07.398.059.66.158.664.16l.24.094-.322-.436-.012-.008.003-.002zm-8.98-7.298h-.029l-1.006.916v.03c-.016.266.078.52.258.716.182.196.42.309.686.319h.043c.531 0 .965-.413.992-.94.023-.545-.399-1.01-.944-1.041zM5.2 7.808h-.041c-.117-.005-.23-.032-.33-.085.045.008.098.01.148.002.284-.035.481-.291.448-.575-.035-.279-.293-.48-.576-.442-.264.034-.457.267-.446.531-.031-.09-.045-.183-.045-.28l.912-.833c.446.042.79.431.768.882s-.391.8-.842.8H5.2zm9.329-3.725l-.293-.195-.41.265-.395-.285-.301.18.404.291-.416.27.297.18.4-.254.387.28.309-.169-.407-.296M24 19.525c-.213-.209-.418-.416-.629-.627-.48-.488-.957-.984-1.418-1.486-.547-.598-1.082-1.207-1.582-1.844-.225-.283-.441-.58-.646-.881-.254-.387-.469-.795-.668-1.215-.211-.445-.398-.9-.576-1.356.24.21.463.444.664.692.021-.215.041-.43.059-.648l.023-.322c0-.033.012-.066-.008-.096-.016-.029-.033-.051-.053-.075-.121-.149-.264-.282-.406-.413-.189-.181-.387-.36-.584-.533l-.18-.156c-.027-.027-.057-.051-.086-.074-.031-.03-.041-.096-.055-.136l-.154-.479c.607.285 1.109.741 1.578 1.215.004-.219.004-.436 0-.652 0-.114-.002-.229-.008-.346 0-.029.006-.1-.016-.127-.012-.02-.031-.036-.043-.055-.109-.117-.234-.217-.357-.314-.172-.143-.35-.277-.527-.408-.156-.117-.318-.232-.477-.345-.018-.016-.076-.039-.064-.06l.039-.08c.02-.035.014-.045.053-.029l.17.059c.406.156.779.368 1.143.608-.123-.45-.311-.885-.525-1.296-.389-.762-.893-1.468-1.463-2.105-.379-.426-.785-.824-1.219-1.193-.223-.19-.457-.369-.699-.533-.113-.074-.225-.149-.346-.217-.049-.03-.105-.056-.15-.094-.18-.144-.365-.284-.549-.429.238.098.469.21.689.338-.127-.194-.342-.331-.545-.436-.307-.159-.637-.276-.967-.378.113-.12.234-.228.346-.348.113-.12.223-.246.33-.372.127-.15.252-.3.375-.455l-1.156-.726-.48-.303c-.123.246-.277.476-.458.68-.18.203-.391.369-.6.539-.191.156-.379.316-.566.472-.047.04-.092.085-.145.12-.025.021-.096.004-.127.004H10.7c-.297.006-.596.029-.891.068-.058.007-.113.013-.175.008l-.178-.01c-.137-.008-.271-.016-.408-.016-.289-.004-.58 0-.871.025-.244.022-.489.055-.729.112-.238.056-.461.172-.66.312-.193.141-.387.32-.504.53s-.17.456-.213.689c-.135.013-.268.01-.4.016-.141.008-.277.021-.416.039-.27.037-.535.096-.795.18-.496.154-.945.405-1.336.75-.195.181-.377.38-.539.596-.02.025-.037.051-.063.068-.01.009-.027.016-.033.027-.015.027-.033.052-.051.076l-.134.212c-.168.285-.276.595-.383.906l-.045.123.033-.021c-.01.067-.02.135-.025.202l-.008.105v.053l-.066.013c-.135.026-.271.06-.4.101-.12.039-.233.086-.337.15-.105.066-.195.153-.285.239-.318.31-.562.698-.687 1.128-.1.348-.137.735-.059 1.092.021.09.049.188.107.262.074.09.199.126.313.095.131-.036.248-.124.371-.187l.314-.157c.455-.226.93-.446 1.438-.525.035-.005.1.067.131.091.049.037.1.074.148.104.109.069.221.129.334.181.262.114.541.174.818.231.502.105 1.014.171 1.529.18.207.004.416 0 .625-.018.225-.02.451-.049.678-.051.25-.005.496.025.74.055.254.031.51.068.764.105.518.079 1.031.169 1.543.279-.115.18-.227.362-.338.545-.008.013-.072 0-.088 0-.041-.002-.086-.002-.127 0-.074 0-.15.008-.225.019-.211.03-.416.083-.615.156-.49.181-.938.483-1.326.833-.221.195-.43.408-.609.641-.049.064-.094.129-.139.193.105-.023.211-.045.318-.07.061-.016.117-.027.176-.039.021-.008.029-.016.049-.027.219-.203.451-.393.695-.563.111-.08.225-.154.348-.215.166-.083.354-.131.533-.174-.33.18-.678.35-.977.584-.172.139-.328.291-.49.439l.654-.104c.01 0 .014-.004.023-.01l.17-.094c.113-.064.23-.125.346-.186.24-.123.482-.24.732-.34.236-.094.48-.178.732-.225.221-.046.428-.052.648 0 .342.074.67.232.969.414.021.014.035.033.055.014.016-.014.098-.074.09-.092l-.18-.334c-.016-.029-.023-.049-.051-.066l-.131-.077c-.176-.104-.348-.21-.51-.331-.037-.023-.078-.039-.082-.089-.004-.046.014-.093.029-.136.033-.09.08-.169.131-.248.033-.058.072-.111.109-.166.014-.018.01-.021.031-.014l.119.034c.236.077.469.159.711.22.27.068.545.123.82.176l.048.01c-.056-.03-.091-.09-.132-.136-.023-.03-.035-.05-.074-.06l-.105-.02c-.074-.016-.148-.03-.225-.049-.262-.057-.525-.119-.777-.209-.375-.13-.758-.232-1.145-.322-.486-.107-.977-.194-1.465-.275-.25-.041-.498-.074-.75-.109-.246-.034-.496-.07-.746-.087-.539-.034-1.074.087-1.615.081-.365-.004-.734-.055-1.096-.105l-.068-.009c.252-.093.5-.188.75-.285.236-.095.471-.192.705-.289.127-.052.25-.105.373-.157.113-.051.225-.1.328-.17.408-.279.676-.727.975-1.109.273-.361.586-.7.99-.908.129-.066.262-.117.4-.156-.283-.439-.549-.93-.623-1.455.029.012.061.025.094.037.035.012.064.016.072.045l.029.135c.026.091.053.176.086.262.072.191.16.375.26.551.219.396.484.766.766 1.12.539.686 1.145 1.305 1.736 1.941.145.158.275.324.41.492.15.187.303.375.457.561.121.141.234.285.356.426l.094.112c.016.018.037.026.061.036.604.3 1.213.6 1.822.895.434.209.869.42 1.309.623.236.109.471.219.711.32.029.014.066.029.098.039.016.01.029.016.047.023l.016.049c.027.072.051.145.076.217.049.141.098.279.148.416.33.912.695 1.814 1.145 2.676.346.656.715 1.301 1.09 1.939.4.68.811 1.354 1.225 2.025.332.535.666 1.072 1.008 1.605.146.227.289.459.439.688l.063.094.775-1.141.191-.283c.008-.006.082-.102.076-.109l-.156-.24c-.291-.451-.584-.898-.871-1.35l-.828-1.283-.105-.166.656.799 1.115 1.35c.121.146.236.289.354.438.219-.402.439-.801.662-1.201.086-.164.176-.33.266-.492M12.13 1.915c.309-.246.563-.563.757-.906l1.178.743.18.112c-.246.3-.496.604-.775.873-.035.034-.074.069-.107.104-.021.025-.047.051-.07.075l-.033.038c-.023-.007-.045-.016-.068-.02-.146-.041-.289-.08-.436-.111-.292-.066-.589-.117-.886-.152-.191-.021-.385-.04-.578-.058.275-.234.557-.467.838-.698m-1.754 1.37c.232-.096.482-.15.73-.191.49-.082.994-.094 1.489-.04.354.038.701.108 1.041.21l-.141.115-.225-.061c-.141-.029-.281-.06-.422-.082-.246-.037-.497-.06-.749-.065-.461-.008-.926.042-1.371.159-.301.078-.604.188-.879.332-.286.154-.555.346-.78.582-.134.137-.257.289-.359.455-.057.09-.105.18-.15.275-.024.047-.043.096-.061.144l-.029.075c-.004.015-.008.026-.012.038L8.39 5.22l-.082-.011c.234-.719.763-1.286 1.418-1.649.207-.114.426-.218.65-.279m-3.93.374c.266-.381.686-.649 1.139-.743.502-.101 1.016-.119 1.525-.107-.525.131-1.05.326-1.488.652-.34.075-.664.24-.934.459-.09.071-.17.15-.246.236-.035.037-.066.075-.097.116-.014.015-.027.03-.037.049h-.084c.023-.135.055-.27.098-.4.029-.09.064-.18.119-.26m.882.056c-.145.143-.27.303-.391.469-.047.069-.098.153-.18.191-.085.039-.194-.005-.28-.031.224-.279.521-.494.851-.629M2.308 6.585c.08-.16.182-.315.275-.465.016-.027.053-.046.074-.063.049-.034.096-.069.143-.106.309-.225.621-.446.939-.656.166-.107.33-.213.502-.307.182-.101.369-.18.565-.244.347-.112.707-.189 1.068-.244.174-.025.361-.057.531-.015-.33.188-.658.375-.99.558-.342.191-.689.367-1.035.552-.332.18-.66.375-.981.577-.336.206-.67.419-1.002.629-.08.051-.16.105-.24.155.045-.125.09-.251.151-.371m1.009 3.056c-.029.056-.134.042-.187.042-.094 0-.186 0-.279.006-.258.015-.51.06-.758.138-.475.147-.922.375-1.365.604-.1.055-.203.135-.316.162-.061.015-.131-.008-.166-.063-.035-.061-.053-.131-.064-.198-.027-.12-.035-.245-.033-.368 0-.33.076-.66.215-.96.096-.21.221-.401.371-.574.15-.175.326-.351.539-.45.355-.162.775-.213 1.164-.235.072-.005.145-.007.217-.007.1 0 .217-.016.311.019.074.027.121.105.15.173.045.105.074.225.1.336.061.26.123.525.145.791.01.119.016.239.004.359-.009.073-.014.156-.05.223m2.829-1.973c-.194.123-.403.218-.616.298-.215.077-.436.144-.66.181-.097.013-.195.025-.293.021-.101-.003-.193-.036-.293-.067-.195-.063-.393-.13-.57-.233-.064-.039-.135-.084-.174-.15-.029-.052-.029-.116-.023-.174.012-.231.094-.458.203-.66.187-.343.482-.612.84-.775.879-.396 1.865-.029 2.611.49l.08.061c-.154.165-.311.321-.473.477-.199.189-.404.381-.634.531M8.31 5.739c-.475-.203-.926-.458-1.356-.738.449.188.934.3 1.414.37.258.037.521.072.781.08.281.008.564-.021.84-.075.545-.103 1.068-.305 1.566-.551.494-.245.964-.537 1.413-.859.217-.155.43-.315.633-.487.021-.016.174-.161.184-.154l.041.031.537.416c.328.254.658.51.988.762-.906.326-1.826.629-2.752.904-.519.156-1.038.301-1.565.42-.412.098-.834.189-1.256.21-.507.022-1.006-.135-1.47-.33m8.85 3.942c.076.021.145.045.215.067l.094.033c.016.006.031.015.045.02l.021.06c.045.146.09.289.139.432-.15-.127-.301-.254-.451-.379l-.09-.074c-.021-.016-.045-.021-.029-.046l.059-.114m-.671 1.444l.035-.063.027-.046c.012-.018.008-.022.029-.012.129.054.258.111.385.17.24.11.475.23.703.364.107.065.217.135.322.205l.15.105.074.057c.033.027.041.063.057.102.104.282.219.564.338.844.078.189.162.379.248.566-.293-.371-.621-.715-.957-1.045-.346-.346-.705-.671-1.078-.981l-.323-.264m3.746 6.42l-.121.09.008.016.063.094.271.42.904 1.402c.311.48.621.963.932 1.445l.309.48.084.133c.004.004.029.041.029.045-.26.385-.523.77-.783 1.154-.027.037-.051.076-.074.111-.24-.373-.479-.744-.715-1.117-.4-.635-.795-1.277-1.184-1.916-.434-.709-.855-1.418-1.264-2.141-.383-.674-.75-1.361-1.059-2.076-.301-.697-.563-1.408-.811-2.121.486.193.98.367 1.48.521.145.045.289.09.436.127l.063.018c.008 0 .014-.039.018-.049.018-.064.031-.129.045-.195.031-.125.051-.254.074-.381.205.428.436.844.701 1.236.215.314.445.621.686.92.521.656 1.074 1.283 1.643 1.898.463.494.934.984 1.408 1.465l.389.389c.006.006.039.031.037.041l-.031.053-.148.275-.588 1.068c-.18-.219-.361-.436-.541-.658l-1.125-1.361c-.314-.387-.637-.773-.953-1.16l-.186-.225'
|
|
10
|
+
|
|
11
|
+
const CUSTOM_ICON_SVGS: Record<CustomIconKey, { path: string; fill: boolean }> = {
|
|
12
|
+
gerrit: { path: GERRIT_PATH, fill: true },
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function CustomIcon({ icon, size = 16, className }: {
|
|
16
|
+
icon: string
|
|
17
|
+
size?: number
|
|
18
|
+
className?: string
|
|
19
|
+
}) {
|
|
20
|
+
const key = icon as CustomIconKey
|
|
21
|
+
const svg = CUSTOM_ICON_SVGS[key]
|
|
22
|
+
if (!svg) return null
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<svg
|
|
26
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
27
|
+
width={size}
|
|
28
|
+
height={size}
|
|
29
|
+
viewBox="0 0 24 24"
|
|
30
|
+
fill={svg.fill ? 'currentColor' : 'none'}
|
|
31
|
+
stroke={svg.fill ? 'none' : 'currentColor'}
|
|
32
|
+
className={cn(className)}
|
|
33
|
+
>
|
|
34
|
+
<path d={svg.path} />
|
|
35
|
+
</svg>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { iconMap } from '../ui/icon-button.tsx'
|
|
2
|
+
import { CustomIcon } from './custom-icons.tsx'
|
|
3
|
+
import { cn } from './cn.ts'
|
|
4
|
+
|
|
5
|
+
export type GitProviderKey = 'github' | 'gitlab' | 'gerrit'
|
|
6
|
+
|
|
7
|
+
export const GIT_PROVIDER_NAMES: Record<GitProviderKey, string> = {
|
|
8
|
+
github: 'GitHub',
|
|
9
|
+
gitlab: 'GitLab',
|
|
10
|
+
gerrit: 'Gerrit',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const GIT_PROVIDER_COLORS: Record<GitProviderKey, string> = {
|
|
14
|
+
github: 'text-neutral-300',
|
|
15
|
+
gitlab: 'text-orange-400',
|
|
16
|
+
gerrit: 'text-green-400',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const GithubIcon = iconMap['github']
|
|
20
|
+
const GitlabIcon = iconMap['gitlab']
|
|
21
|
+
|
|
22
|
+
export function GitProviderIcon({ provider, size = 16, className }: {
|
|
23
|
+
provider: string
|
|
24
|
+
size?: number
|
|
25
|
+
className?: string
|
|
26
|
+
}) {
|
|
27
|
+
const key = provider as GitProviderKey
|
|
28
|
+
const color = GIT_PROVIDER_COLORS[key]
|
|
29
|
+
if (!color) return null
|
|
30
|
+
|
|
31
|
+
if (key === 'gerrit') {
|
|
32
|
+
return <CustomIcon icon="gerrit" size={size} className={cn(color, className)} />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Icon = key === 'github' ? GithubIcon : key === 'gitlab' ? GitlabIcon : null
|
|
36
|
+
if (!Icon) return null
|
|
37
|
+
|
|
38
|
+
return <Icon className={cn(color, className)} style={{ width: size, height: size }} />
|
|
39
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { FormColor } from './form-colors.ts'
|
|
|
2
2
|
|
|
3
3
|
/* ── Neutral scale types ──────────────────────────────────── */
|
|
4
4
|
|
|
5
|
-
export const SCALE_KEYS = ['black', '950', '900', '850', '800', '750', '700', '600', '500', '400', '300', '200'] as const
|
|
5
|
+
export const SCALE_KEYS = ['black', '990', '980', '970', '960', '950', '940', '930', '920', '910', '900', '850', '800', '750', '700', '650', '600', '550', '500', '450', '400', '350', '300', '250', '200', '150', '100', '090', '080', '070', '060', '050', '040', '030', '020', '010'] as const
|
|
6
6
|
export type ScaleKey = (typeof SCALE_KEYS)[number]
|
|
7
7
|
|
|
8
8
|
/* ── Theme definitions ────────────────────────────────────── */
|
|
@@ -13,8 +13,8 @@ export const DARK_THEMES: ThemeId[] = ['dark']
|
|
|
13
13
|
export const LIGHT_THEMES: ThemeId[] = ['light']
|
|
14
14
|
|
|
15
15
|
export const BASE_THEMES: Record<ThemeId, { label: string; maxSat: number; lightness: Record<ScaleKey, number> }> = {
|
|
16
|
-
dark: { label: 'Dark', maxSat: 22, lightness: { black: 0,
|
|
17
|
-
light: { label: 'Light', maxSat: 15, lightness: { black: 98, '
|
|
16
|
+
dark: { label: 'Dark', maxSat: 22, lightness: { black: 0, '990': 2, '980': 4, '970': 6, '960': 8, '950': 10, '940': 10.5, '930': 11, '920': 11.5, '910': 12, '900': 13, '850': 15, '800': 18, '750': 20, '700': 22, '650': 20, '600': 28, '550': 36, '500': 46, '450': 55, '400': 64, '350': 74, '300': 83, '250': 87, '200': 90, '150': 93, '100': 94, '090': 95, '080': 96, '070': 97, '060': 97.5, '050': 98, '040': 98.5, '030': 99, '020': 99.3, '010': 99.6 } },
|
|
17
|
+
light: { label: 'Light', maxSat: 15, lightness: { black: 98, '990': 97, '980': 96, '970': 95, '960': 94, '950': 93, '940': 92.5, '930': 92, '920': 91.5, '910': 91, '900': 90, '850': 88, '800': 85, '750': 83, '700': 82, '650': 74, '600': 65, '550': 56, '500': 50, '450': 44, '400': 38, '350': 30, '300': 22, '250': 17, '200': 12, '150': 10, '100': 8, '090': 7, '080': 6, '070': 5, '060': 4, '050': 3, '040': 2.5, '030': 2, '020': 1.5, '010': 1 } },
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/* ── Accent definitions ───────────────────────────────────── */
|
|
@@ -44,7 +44,9 @@ export const ACCENT_DEFS: AccentDef[] = [
|
|
|
44
44
|
/* ── Color utilities ──────────────────────────────────────── */
|
|
45
45
|
|
|
46
46
|
export function satCurve(l: number): number {
|
|
47
|
-
|
|
47
|
+
const ramp = Math.min(l / 10, 1)
|
|
48
|
+
if (l <= 50) return ramp
|
|
49
|
+
return ramp * Math.max(0.5, 1 - (l - 50) / 130)
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export function hslToHex(h: number, s: number, l: number): string {
|
|
@@ -63,10 +65,10 @@ function alphaToHex(alpha: number): string {
|
|
|
63
65
|
|
|
64
66
|
/* ── Dim (glass surfaces) ────────────────────────────────── */
|
|
65
67
|
|
|
66
|
-
export const SURFACE_KEYS = ['950', '900', '850', '800', '750'] as const
|
|
68
|
+
export const SURFACE_KEYS = ['990', '980', '970', '960', '950', '940', '930', '920', '910', '900', '850', '800', '750'] as const
|
|
67
69
|
export type SurfaceKey = (typeof SURFACE_KEYS)[number]
|
|
68
70
|
|
|
69
|
-
export const DEFAULT_DIMS: Record<SurfaceKey, number> = { '
|
|
71
|
+
export const DEFAULT_DIMS: Record<SurfaceKey, number> = { '990': 1, '980': 2, '970': 3, '960': 4, '950': 5, '940': 6, '930': 7, '920': 8, '910': 9, '900': 10, '850': 15, '800': 20, '750': 25 }
|
|
70
72
|
export const DEFAULT_OUTLINE = 30
|
|
71
73
|
|
|
72
74
|
/* ── Scale generation ─────────────────────────────────────── */
|
|
@@ -74,6 +76,12 @@ export const DEFAULT_OUTLINE = 30
|
|
|
74
76
|
export function generateScale(theme: ThemeId, hue: number | null, maxSat: number, dims: Record<SurfaceKey, number> = DEFAULT_DIMS, outline: number = DEFAULT_OUTLINE): Record<ScaleKey, string> {
|
|
75
77
|
const lightness = BASE_THEMES[theme].lightness
|
|
76
78
|
const glassBase = hue !== null ? hslToHex(hue, 80, 50) : null
|
|
79
|
+
const accentRgb = glassBase ? [parseInt(glassBase.slice(1, 3), 16), parseInt(glassBase.slice(3, 5), 16), parseInt(glassBase.slice(5, 7), 16)] : null
|
|
80
|
+
// Steps from white (end of scale) — used for alpha stepping
|
|
81
|
+
const stepsFromWhite: Partial<Record<ScaleKey, number>> = {}
|
|
82
|
+
for (let i = SCALE_KEYS.length - 1, step = 1; i >= 0; i--, step++) {
|
|
83
|
+
stepsFromWhite[SCALE_KEYS[i]] = step
|
|
84
|
+
}
|
|
77
85
|
const result = {} as Record<ScaleKey, string>
|
|
78
86
|
|
|
79
87
|
for (const key of SCALE_KEYS) {
|
|
@@ -92,12 +100,32 @@ export function generateScale(theme: ThemeId, hue: number | null, maxSat: number
|
|
|
92
100
|
continue
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
//
|
|
103
|
+
// Neutral/achromatic: solid grayscale
|
|
96
104
|
if (hue === null || maxSat === 0) {
|
|
97
105
|
const g = Math.round(255 * l / 100).toString(16).padStart(2, '0')
|
|
98
106
|
result[key] = `#${g}${g}${g}`
|
|
107
|
+
} else if (key === 'black') {
|
|
108
|
+
result[key] = '#000000'
|
|
109
|
+
} else if (l <= 50) {
|
|
110
|
+
// Dark-to-mid: accent at increasing opacity
|
|
111
|
+
result[key] = `${glassBase}${alphaToHex(l / 50)}`
|
|
99
112
|
} else {
|
|
100
|
-
|
|
113
|
+
// Light half: cross-fade — accent fades out, white fades in
|
|
114
|
+
const t = (l - 50) / 50
|
|
115
|
+
const aa = 1 - t // accent alpha: 1→0
|
|
116
|
+
const wa = t // white alpha: 0→1
|
|
117
|
+
const ta = wa + aa * (1 - wa) // composite alpha
|
|
118
|
+
const [ar, ag, ab] = accentRgb!
|
|
119
|
+
const r = Math.round((255 * wa + ar * aa * (1 - wa)) / ta)
|
|
120
|
+
const g = Math.round((255 * wa + ag * aa * (1 - wa)) / ta)
|
|
121
|
+
const b = Math.round((255 * wa + ab * aa * (1 - wa)) / ta)
|
|
122
|
+
const hex = (c: number) => Math.min(255, c).toString(16).padStart(2, '0')
|
|
123
|
+
|
|
124
|
+
// White variants (010–090): 5% opacity step per position
|
|
125
|
+
// 100–500: natural cross-fade alpha for smooth transition
|
|
126
|
+
const keyNum = parseInt(key)
|
|
127
|
+
const alpha = (keyNum > 0 && keyNum <= 90) ? 1 - stepsFromWhite[key]! * 0.05 : ta
|
|
128
|
+
result[key] = `#${hex(r)}${hex(g)}${hex(b)}${alphaToHex(alpha)}`
|
|
101
129
|
}
|
|
102
130
|
}
|
|
103
131
|
return result
|
|
@@ -128,8 +156,29 @@ export function applyTheme(themeId: ThemeId, accentHue: number | null, dims: Rec
|
|
|
128
156
|
root.style.setProperty(prop, scale[key])
|
|
129
157
|
}
|
|
130
158
|
root.style.setProperty('--color-white', light ? '#0a0a0a' : '#ffffff')
|
|
131
|
-
|
|
132
|
-
|
|
159
|
+
|
|
160
|
+
// Text-range neutrals (600→010): white+alpha for dark, black+alpha for light
|
|
161
|
+
// Surfaces/borders (990→650, 700) keep their accent tint from the scale
|
|
162
|
+
const lightness = BASE_THEMES[themeId].lightness
|
|
163
|
+
const TEXT_KEYS: ScaleKey[] = ['600', '550', '500', '450', '400', '350', '300', '250', '200', '150', '100', '090', '080', '070', '060', '050', '040', '030', '020', '010']
|
|
164
|
+
for (const key of TEXT_KEYS) {
|
|
165
|
+
const l = lightness[key]
|
|
166
|
+
const a = light ? (100 - l) / 100 : l / 100
|
|
167
|
+
root.style.setProperty(`--color-neutral-${key}`, `rgba(${light ? '0, 0, 0' : '255, 255, 255'}, ${a.toFixed(2)})`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Semantic text tokens: white+alpha for dark, black+alpha for light
|
|
171
|
+
const tb = light ? '0, 0, 0' : '255, 255, 255'
|
|
172
|
+
root.style.setProperty('--foreground', `rgba(${tb}, 0.83)`)
|
|
173
|
+
root.style.setProperty('--surface-foreground', `rgba(${tb}, 0.83)`)
|
|
174
|
+
root.style.setProperty('--popover-foreground', `rgba(${tb}, 0.83)`)
|
|
175
|
+
root.style.setProperty('--muted-foreground', `rgba(${tb}, 0.46)`)
|
|
176
|
+
root.style.setProperty('--text-primary', `rgba(${tb}, 0.83)`)
|
|
177
|
+
root.style.setProperty('--text-secondary', `rgba(${tb}, 0.46)`)
|
|
178
|
+
root.style.setProperty('--text-tertiary', `rgba(${tb}, 0.64)`)
|
|
179
|
+
root.style.setProperty('--text-emphasis', light ? '#0a0a0a' : '#ffffff')
|
|
180
|
+
root.style.setProperty('--primary-foreground', light ? '#0a0a0a' : '#ffffff')
|
|
181
|
+
root.style.setProperty('--destructive-foreground', light ? '#0a0a0a' : '#ffffff')
|
|
133
182
|
|
|
134
183
|
// Surface hover: accent glass at 10% for visible hover states
|
|
135
184
|
const glassBase = accentHue !== null ? hslToHex(accentHue, 80, 50) : null
|
|
@@ -1,17 +1,31 @@
|
|
|
1
|
+
import { ACCENT_DEFS } from './theme-engine.ts'
|
|
2
|
+
import { useAccentColor } from './accent-context.ts'
|
|
3
|
+
|
|
1
4
|
export type ToolrAppId = 'toolr' | 'configr' | 'reviewr' | 'vibr' | 'learnr' | 'planr' | 'seedr'
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
// Accent color ID per app — resolved to hex via ACCENT_DEFS at render time
|
|
7
|
+
export const TOOLR_APPS: Record<ToolrAppId, { name: string; accent: string }> = {
|
|
8
|
+
toolr: { name: 'Toolr', accent: 'blue' }, // #3b82f6
|
|
9
|
+
configr: { name: 'Configr', accent: 'violet' }, // #8b5cf6
|
|
10
|
+
reviewr: { name: 'Reviewr', accent: 'orange' }, // #f97316
|
|
11
|
+
learnr: { name: 'Learnr', accent: 'yellow' }, // #eab308
|
|
12
|
+
seedr: { name: 'Seedr', accent: 'cyan' }, // #06b6d4
|
|
13
|
+
planr: { name: 'Planr', accent: 'pink' }, // #ec4899 (not in ACCENT_DEFS, fallback hex)
|
|
14
|
+
vibr: { name: 'Vibr', accent: 'red' }, // #ef4444
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ACCENT_HEX_MAP: Record<string, string> = Object.fromEntries(ACCENT_DEFS.map((a) => [a.id, a.dotColor]))
|
|
18
|
+
// Fallback hex for accents not in ACCENT_DEFS
|
|
19
|
+
ACCENT_HEX_MAP['pink'] = '#ec4899'
|
|
20
|
+
|
|
21
|
+
/** Resolve an accent ID to its hex color */
|
|
22
|
+
export function resolveAccentHex(accent: string): string {
|
|
23
|
+
return ACCENT_HEX_MAP[accent] ?? ACCENT_HEX_MAP['blue']
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
export function ToolrAppLogo({ app, color: colorOverride, size, className }: { app?: ToolrAppId; color?: string; size?: number; className?: string }) {
|
|
14
|
-
const
|
|
27
|
+
const accentColor = useAccentColor()
|
|
28
|
+
const color = colorOverride ?? (app ? resolveAccentHex(TOOLR_APPS[app]?.accent) : undefined) ?? (accentColor ? resolveAccentHex(accentColor) : undefined) ?? ACCENT_HEX_MAP['blue']
|
|
15
29
|
return (
|
|
16
30
|
<svg
|
|
17
31
|
{...(size ? { width: size, height: size } : {})}
|
|
@@ -28,6 +28,8 @@ import { cn } from '../../lib/cn.ts'
|
|
|
28
28
|
import { Input } from '../../ui/input.tsx'
|
|
29
29
|
import { ResizableTextarea } from '../../ui/resizable-textarea.tsx'
|
|
30
30
|
import { IconButton } from '../../ui/icon-button.tsx'
|
|
31
|
+
import { useAccentColor, AccentColorProvider } from '../../lib/accent-context.ts'
|
|
32
|
+
import type { FormColor } from '../../lib/form-colors.ts'
|
|
31
33
|
import { useCapturedIssues } from './use-captured-issues.ts'
|
|
32
34
|
import type { CapturedError, CapturedIssuesApi } from './types.ts'
|
|
33
35
|
|
|
@@ -40,6 +42,7 @@ export interface CapturedIssuesPanelProps {
|
|
|
40
42
|
onDismiss?: () => void
|
|
41
43
|
/** Called after successful submission */
|
|
42
44
|
onSubmitSuccess?: (issueId?: string) => void
|
|
45
|
+
accentColor?: FormColor
|
|
43
46
|
className?: string
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -48,8 +51,11 @@ export function CapturedIssuesPanel({
|
|
|
48
51
|
errors,
|
|
49
52
|
onDismiss,
|
|
50
53
|
onSubmitSuccess,
|
|
54
|
+
accentColor,
|
|
51
55
|
className,
|
|
52
56
|
}: CapturedIssuesPanelProps) {
|
|
57
|
+
const contextAccent = useAccentColor()
|
|
58
|
+
const effectiveColor = accentColor ?? contextAccent ?? 'blue'
|
|
53
59
|
const {
|
|
54
60
|
errorCount,
|
|
55
61
|
warnCount,
|
|
@@ -68,9 +74,10 @@ export function CapturedIssuesPanel({
|
|
|
68
74
|
const hasErrors = errorCount > 0 || warnCount > 0
|
|
69
75
|
|
|
70
76
|
return (
|
|
77
|
+
<AccentColorProvider value={effectiveColor}>
|
|
71
78
|
<div className={cn('space-y-6', className)}>
|
|
72
79
|
{hasErrors ? (
|
|
73
|
-
<div className="bg-neutral-
|
|
80
|
+
<div className="bg-neutral-980 border border-neutral-700 rounded-lg p-4 space-y-4">
|
|
74
81
|
{/* Error Summary */}
|
|
75
82
|
<div className="flex items-center justify-between">
|
|
76
83
|
<div className="flex items-center gap-2">
|
|
@@ -94,7 +101,7 @@ export function CapturedIssuesPanel({
|
|
|
94
101
|
{/* Error List */}
|
|
95
102
|
<div className="space-y-2">
|
|
96
103
|
<p className="text-md text-neutral-400">Captured Errors</p>
|
|
97
|
-
<div className="max-h-48 overflow-y-auto bg-neutral-
|
|
104
|
+
<div className="max-h-48 overflow-y-auto bg-neutral-990 border border-neutral-700 rounded-lg p-3 space-y-1">
|
|
98
105
|
{errors.map((error) => (
|
|
99
106
|
<div key={error.fingerprint} className="text-sm font-mono break-words">
|
|
100
107
|
<span className={cn('mr-2 shrink-0', error.level === 'warning' ? 'text-yellow-400' : 'text-red-400')}>
|
|
@@ -119,7 +126,7 @@ export function CapturedIssuesPanel({
|
|
|
119
126
|
onChange={(e) => setDescription(e.target.value)}
|
|
120
127
|
placeholder="What were you doing when this happened?"
|
|
121
128
|
rows={3}
|
|
122
|
-
className="w-full px-3 py-1.5 bg-neutral-
|
|
129
|
+
className="w-full px-3 py-1.5 bg-neutral-960 border border-neutral-600 rounded-lg text-neutral-300 placeholder-neutral-500 focus:outline-none focus:border-blue-500 transition-colors resize-none text-md"
|
|
123
130
|
/>
|
|
124
131
|
<Input
|
|
125
132
|
type="text"
|
|
@@ -136,6 +143,7 @@ export function CapturedIssuesPanel({
|
|
|
136
143
|
onClick={handleDismiss}
|
|
137
144
|
disabled={isSubmitting}
|
|
138
145
|
size="sm"
|
|
146
|
+
accentColor="red"
|
|
139
147
|
tooltip={{ title: 'Dismiss', description: 'Ignore these errors' }}
|
|
140
148
|
tooltipPosition="top"
|
|
141
149
|
/>
|
|
@@ -150,14 +158,14 @@ export function CapturedIssuesPanel({
|
|
|
150
158
|
onClick={handleSubmit}
|
|
151
159
|
disabled={isSubmitting}
|
|
152
160
|
size="sm"
|
|
153
|
-
|
|
161
|
+
accentColor="cyan"
|
|
154
162
|
tooltip={{ title: 'Send Report', description: 'Submit error report to developers' }}
|
|
155
163
|
tooltipPosition="top"
|
|
156
164
|
/>
|
|
157
165
|
</div>
|
|
158
166
|
</div>
|
|
159
167
|
) : (
|
|
160
|
-
<div className="bg-neutral-
|
|
168
|
+
<div className="bg-neutral-980 border border-neutral-700 rounded-lg p-6">
|
|
161
169
|
<div className="flex items-center gap-4">
|
|
162
170
|
<div className="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center">
|
|
163
171
|
<Check className="w-5 h-5 text-green-400" />
|
|
@@ -172,9 +180,9 @@ export function CapturedIssuesPanel({
|
|
|
172
180
|
|
|
173
181
|
{/* Previously Reported */}
|
|
174
182
|
{submittedErrors.length > 0 && (
|
|
175
|
-
<div className="bg-neutral-
|
|
183
|
+
<div className="bg-neutral-980 border border-neutral-700 rounded-lg overflow-hidden">
|
|
176
184
|
<details className="group">
|
|
177
|
-
<summary className="flex items-center justify-between p-4 cursor-pointer hover:bg-neutral-
|
|
185
|
+
<summary className="flex items-center justify-between p-4 cursor-pointer hover:bg-neutral-960 transition-colors">
|
|
178
186
|
<div className="flex items-center gap-2">
|
|
179
187
|
<Check className="w-4 h-4 text-green-400" />
|
|
180
188
|
<span className="text-md text-neutral-300">Previously Reported</span>
|
|
@@ -186,7 +194,7 @@ export function CapturedIssuesPanel({
|
|
|
186
194
|
{submittedErrors.map((error) => (
|
|
187
195
|
<div
|
|
188
196
|
key={error.fingerprint}
|
|
189
|
-
className="flex items-start gap-3 p-3 bg-neutral-
|
|
197
|
+
className="flex items-start gap-3 p-3 bg-neutral-990 border border-neutral-700 rounded-lg"
|
|
190
198
|
>
|
|
191
199
|
<Check className="w-4 h-4 text-green-400 mt-0.5 shrink-0" />
|
|
192
200
|
<div className="flex-1 min-w-0">
|
|
@@ -210,5 +218,6 @@ export function CapturedIssuesPanel({
|
|
|
210
218
|
</div>
|
|
211
219
|
)}
|
|
212
220
|
</div>
|
|
221
|
+
</AccentColorProvider>
|
|
213
222
|
)
|
|
214
223
|
}
|