@handled-ai/design-system 0.9.27 → 0.10.0

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.
Files changed (74) hide show
  1. package/dist/components/badge.d.ts +1 -1
  2. package/dist/components/button.d.ts +1 -1
  3. package/dist/components/compliance-badge.d.ts +10 -0
  4. package/dist/components/compliance-badge.js +95 -0
  5. package/dist/components/compliance-badge.js.map +1 -0
  6. package/dist/components/contact-chip.d.ts +12 -0
  7. package/dist/components/contact-chip.js +98 -0
  8. package/dist/components/contact-chip.js.map +1 -0
  9. package/dist/components/empty-state.d.ts +11 -0
  10. package/dist/components/empty-state.js +46 -0
  11. package/dist/components/empty-state.js.map +1 -0
  12. package/dist/components/filter-chip.d.ts +9 -0
  13. package/dist/components/filter-chip.js +67 -0
  14. package/dist/components/filter-chip.js.map +1 -0
  15. package/dist/components/inline-banner.d.ts +10 -0
  16. package/dist/components/inline-banner.js +97 -0
  17. package/dist/components/inline-banner.js.map +1 -0
  18. package/dist/components/kbd-hint.d.ts +5 -0
  19. package/dist/components/kbd-hint.js +51 -0
  20. package/dist/components/kbd-hint.js.map +1 -0
  21. package/dist/components/rich-text-toolbar.d.ts +9 -0
  22. package/dist/components/rich-text-toolbar.js +103 -0
  23. package/dist/components/rich-text-toolbar.js.map +1 -0
  24. package/dist/components/signal-feedback-inline.d.ts +13 -2
  25. package/dist/components/signal-feedback-inline.js +33 -4
  26. package/dist/components/signal-feedback-inline.js.map +1 -1
  27. package/dist/components/step-timeline.d.ts +19 -0
  28. package/dist/components/step-timeline.js +134 -0
  29. package/dist/components/step-timeline.js.map +1 -0
  30. package/dist/components/sticky-action-bar.d.ts +10 -0
  31. package/dist/components/sticky-action-bar.js +56 -0
  32. package/dist/components/sticky-action-bar.js.map +1 -0
  33. package/dist/components/switch.d.ts +6 -0
  34. package/dist/components/switch.js +66 -0
  35. package/dist/components/switch.js.map +1 -0
  36. package/dist/components/tabs.d.ts +1 -1
  37. package/dist/components/variable-autocomplete.d.ts +21 -0
  38. package/dist/components/variable-autocomplete.js +171 -0
  39. package/dist/components/variable-autocomplete.js.map +1 -0
  40. package/dist/index.d.ts +12 -1
  41. package/dist/index.js +12 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/prototype/prototype-config.d.ts +2 -1
  44. package/dist/prototype/prototype-inbox-view.d.ts +1 -1
  45. package/dist/prototype/prototype-inbox-view.js +1 -1
  46. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  47. package/package.json +2 -1
  48. package/src/components/__tests__/compliance-badge.test.tsx +88 -0
  49. package/src/components/__tests__/contact-chip.test.tsx +88 -0
  50. package/src/components/__tests__/empty-state.test.tsx +76 -0
  51. package/src/components/__tests__/filter-chip.test.tsx +73 -0
  52. package/src/components/__tests__/inline-banner.test.tsx +110 -0
  53. package/src/components/__tests__/kbd-hint.test.tsx +29 -0
  54. package/src/components/__tests__/rich-text-toolbar.test.tsx +92 -0
  55. package/src/components/__tests__/step-timeline.test.tsx +174 -0
  56. package/src/components/__tests__/sticky-action-bar.test.tsx +52 -0
  57. package/src/components/__tests__/switch.test.tsx +39 -0
  58. package/src/components/__tests__/variable-autocomplete.test.tsx +155 -0
  59. package/src/components/compliance-badge.tsx +68 -0
  60. package/src/components/contact-chip.tsx +68 -0
  61. package/src/components/empty-state.tsx +37 -0
  62. package/src/components/filter-chip.tsx +37 -0
  63. package/src/components/inline-banner.tsx +69 -0
  64. package/src/components/kbd-hint.tsx +21 -0
  65. package/src/components/rich-text-toolbar.tsx +90 -0
  66. package/src/components/signal-feedback-inline.tsx +51 -5
  67. package/src/components/step-timeline.tsx +149 -0
  68. package/src/components/sticky-action-bar.tsx +36 -0
  69. package/src/components/switch.tsx +29 -0
  70. package/src/components/variable-autocomplete.tsx +178 -0
  71. package/src/index.ts +12 -1
  72. package/src/prototype/prototype-config.ts +2 -1
  73. package/src/prototype/prototype-inbox-view.tsx +2 -2
  74. package/src/styles/globals.css +60 -0
@@ -0,0 +1,149 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import type { LucideIcon } from "lucide-react"
5
+ import { Mail, Phone, CheckSquare, Clock, Circle, ChevronUp, ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "../lib/utils"
8
+
9
+ type StepType = "email" | "call" | "task" | "wait" | (string & {})
10
+
11
+ interface TimelineStep {
12
+ id: string
13
+ type: StepType
14
+ label: string
15
+ }
16
+
17
+ interface StepTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
18
+ steps: TimelineStep[]
19
+ expandedStepId?: string
20
+ onStepClick?: (stepId: string) => void
21
+ onInsert?: (index: number) => void
22
+ renderStepBody?: (step: TimelineStep) => React.ReactNode
23
+ renderStepAccessory?: (step: TimelineStep) => React.ReactNode
24
+ }
25
+
26
+ const stepTypeConfig: Record<string, { icon: LucideIcon; classes: string }> = {
27
+ email: { icon: Mail, classes: "bg-blue-500/10 text-blue-600" },
28
+ call: { icon: Phone, classes: "bg-emerald-500/10 text-emerald-600" },
29
+ task: { icon: CheckSquare, classes: "bg-purple-500/10 text-purple-600" },
30
+ wait: { icon: Clock, classes: "bg-muted text-muted-foreground" },
31
+ }
32
+
33
+ const defaultStepConfig: { icon: LucideIcon; classes: string } = { icon: Circle, classes: "bg-muted text-muted-foreground" }
34
+
35
+ function StepTimeline({
36
+ steps,
37
+ expandedStepId,
38
+ onStepClick,
39
+ onInsert,
40
+ renderStepBody,
41
+ renderStepAccessory,
42
+ className,
43
+ ...rest
44
+ }: StepTimelineProps) {
45
+ return (
46
+ <div data-slot="step-timeline" className={cn("relative ml-5", className)} {...rest}>
47
+ <div className="absolute left-0 top-0 bottom-0 border-l-2 border-border" />
48
+
49
+ {steps.map((step, index) => {
50
+ const expanded = step.id === expandedStepId
51
+ const config = stepTypeConfig[step.type] ?? defaultStepConfig
52
+ const TypeIcon = config.icon
53
+ const isInteractive = !!(onStepClick || renderStepBody)
54
+ const bodyId = `step-timeline-body-${step.id}`
55
+
56
+ const headerContent = (
57
+ <>
58
+ <div className={cn("w-8 h-8 rounded-full flex items-center justify-center shrink-0", config.classes)}>
59
+ <TypeIcon size={16} />
60
+ </div>
61
+ <div className="flex flex-col min-w-0">
62
+ <span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">
63
+ {step.type}
64
+ </span>
65
+ <span className="text-sm font-medium">{step.label}</span>
66
+ </div>
67
+ </>
68
+ )
69
+
70
+ return (
71
+ <React.Fragment key={step.id}>
72
+ {index > 0 && onInsert && (
73
+ <div className="relative pl-8 py-1">
74
+ <button
75
+ type="button"
76
+ data-slot="step-timeline-insert"
77
+ onClick={() => onInsert(index)}
78
+ className="border border-dashed border-border/50 rounded-lg py-2 text-center text-xs text-muted-foreground/50 hover:text-muted-foreground hover:border-border cursor-pointer transition-colors w-full"
79
+ >
80
+ + Add Step
81
+ </button>
82
+ </div>
83
+ )}
84
+
85
+ <div data-slot="step-timeline-step" className="relative pl-8 py-2">
86
+ <div className="absolute left-0 top-1/2 -translate-x-1/2 -translate-y-1/2 w-3 h-3 rounded-full border-2 border-border bg-background z-10" />
87
+
88
+ <div data-slot="step-timeline-card" className="border border-border rounded-lg overflow-hidden">
89
+ {isInteractive ? (
90
+ <div className="flex items-center w-full px-4 py-3 gap-3">
91
+ <button
92
+ type="button"
93
+ aria-expanded={expanded}
94
+ aria-controls={bodyId}
95
+ onClick={() => onStepClick?.(step.id)}
96
+ className="flex items-center gap-3 flex-1 min-w-0 cursor-pointer hover:opacity-80 transition-opacity text-left"
97
+ >
98
+ {headerContent}
99
+ </button>
100
+ <div className="ml-auto flex items-center gap-2 shrink-0">
101
+ {renderStepAccessory && (
102
+ <div className="shrink-0">
103
+ {renderStepAccessory(step)}
104
+ </div>
105
+ )}
106
+ <div className="shrink-0 text-muted-foreground">
107
+ {expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ ) : (
112
+ <div className="w-full px-4 py-3 flex items-center gap-3">
113
+ {headerContent}
114
+ {renderStepAccessory && (
115
+ <div className="ml-auto shrink-0">
116
+ {renderStepAccessory(step)}
117
+ </div>
118
+ )}
119
+ </div>
120
+ )}
121
+
122
+ {expanded && renderStepBody && (
123
+ <div id={bodyId} data-slot="step-timeline-body">
124
+ {renderStepBody(step)}
125
+ </div>
126
+ )}
127
+ </div>
128
+ </div>
129
+ </React.Fragment>
130
+ )
131
+ })}
132
+
133
+ {onInsert && (
134
+ <div className="relative pl-8 py-1">
135
+ <button
136
+ type="button"
137
+ data-slot="step-timeline-insert"
138
+ onClick={() => onInsert(steps.length)}
139
+ className="border border-dashed border-border/50 rounded-lg py-2 text-center text-xs text-muted-foreground/50 hover:text-muted-foreground hover:border-border cursor-pointer transition-colors w-full"
140
+ >
141
+ + Add Step
142
+ </button>
143
+ </div>
144
+ )}
145
+ </div>
146
+ )
147
+ }
148
+
149
+ export { StepTimeline, type StepTimelineProps, type TimelineStep, type StepType }
@@ -0,0 +1,36 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ interface StickyActionBarProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ left?: React.ReactNode
7
+ right?: React.ReactNode
8
+ bordered?: boolean
9
+ }
10
+
11
+ function StickyActionBar({ left, right, bordered = true, className, ...rest }: StickyActionBarProps) {
12
+ return (
13
+ <div
14
+ data-slot="sticky-action-bar"
15
+ className={cn(
16
+ "flex items-center justify-between sticky bottom-0 bg-background",
17
+ bordered && "pt-3 border-t border-border/50",
18
+ className
19
+ )}
20
+ {...rest}
21
+ >
22
+ {left && (
23
+ <div data-slot="sticky-action-bar-left" className="flex items-center gap-1">
24
+ {left}
25
+ </div>
26
+ )}
27
+ {right && (
28
+ <div data-slot="sticky-action-bar-right" className="ml-auto flex items-center gap-2">
29
+ {right}
30
+ </div>
31
+ )}
32
+ </div>
33
+ )
34
+ }
35
+
36
+ export { StickyActionBar, type StickyActionBarProps }
@@ -0,0 +1,29 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Switch as SwitchPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ function Switch({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
12
+ return (
13
+ <SwitchPrimitive.Root
14
+ data-slot="switch"
15
+ className={cn(
16
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-switch-background",
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <SwitchPrimitive.Thumb
22
+ data-slot="switch-thumb"
23
+ className="pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
24
+ />
25
+ </SwitchPrimitive.Root>
26
+ )
27
+ }
28
+
29
+ export { Switch }
@@ -0,0 +1,178 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../lib/utils"
6
+
7
+ interface VariableDef {
8
+ name: string
9
+ description?: string
10
+ source?: string
11
+ }
12
+
13
+ interface VariableGroup {
14
+ label: string
15
+ variables: VariableDef[]
16
+ }
17
+
18
+ interface VariableAutocompleteProps
19
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> {
20
+ groups: VariableGroup[]
21
+ filter?: string
22
+ onSelect: (variable: VariableDef) => void
23
+ onClose: () => void
24
+ maxResults?: number
25
+ }
26
+
27
+ function VariableAutocomplete({
28
+ groups,
29
+ filter = "",
30
+ onSelect,
31
+ onClose,
32
+ maxResults = 10,
33
+ className,
34
+ ...rest
35
+ }: VariableAutocompleteProps) {
36
+ const containerRef = React.useRef<HTMLDivElement>(null)
37
+ const [activeIndex, setActiveIndex] = React.useState(-1)
38
+
39
+ // Build filtered results preserving group structure
40
+ const filteredGroups = React.useMemo(
41
+ () =>
42
+ groups
43
+ .map((group) => ({
44
+ ...group,
45
+ variables: group.variables.filter((v) =>
46
+ v.name.toLowerCase().startsWith(filter.toLowerCase())
47
+ ),
48
+ }))
49
+ .filter((group) => group.variables.length > 0),
50
+ [groups, filter]
51
+ )
52
+
53
+ // Flatten for indexing / keyboard navigation, respecting maxResults
54
+ const allFiltered = React.useMemo(() => {
55
+ const result: { group: VariableGroup; variable: VariableDef }[] = []
56
+ for (const group of filteredGroups) {
57
+ for (const variable of group.variables) {
58
+ if (result.length >= maxResults) break
59
+ result.push({ group, variable })
60
+ }
61
+ if (result.length >= maxResults) break
62
+ }
63
+ return result
64
+ }, [filteredGroups, maxResults])
65
+
66
+ // Reset active index when results change
67
+ React.useEffect(() => {
68
+ setActiveIndex(-1)
69
+ }, [filter])
70
+
71
+ React.useEffect(() => {
72
+ function handleMouseDown(e: MouseEvent) {
73
+ if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
74
+ onClose()
75
+ }
76
+ }
77
+ function handleKeyDown(e: KeyboardEvent) {
78
+ if (e.key === "Escape") {
79
+ onClose()
80
+ }
81
+ if (e.key === "ArrowDown") {
82
+ e.preventDefault()
83
+ setActiveIndex((prev) => (prev < allFiltered.length - 1 ? prev + 1 : 0))
84
+ }
85
+ if (e.key === "ArrowUp") {
86
+ e.preventDefault()
87
+ setActiveIndex((prev) => (prev > 0 ? prev - 1 : allFiltered.length - 1))
88
+ }
89
+ if (e.key === "Enter" && activeIndex >= 0 && activeIndex < allFiltered.length) {
90
+ e.preventDefault()
91
+ onSelect(allFiltered[activeIndex].variable)
92
+ }
93
+ }
94
+ document.addEventListener("mousedown", handleMouseDown)
95
+ document.addEventListener("keydown", handleKeyDown)
96
+ return () => {
97
+ document.removeEventListener("mousedown", handleMouseDown)
98
+ document.removeEventListener("keydown", handleKeyDown)
99
+ }
100
+ }, [onClose, onSelect, allFiltered, activeIndex])
101
+
102
+ if (allFiltered.length === 0) {
103
+ return null
104
+ }
105
+
106
+ // Group the capped results back for rendering
107
+ const groupsToRender: { label: string; items: { variable: VariableDef; flatIndex: number }[] }[] = []
108
+ let flatIndex = 0
109
+ for (const group of filteredGroups) {
110
+ const items: { variable: VariableDef; flatIndex: number }[] = []
111
+ for (const variable of group.variables) {
112
+ if (flatIndex >= maxResults) break
113
+ items.push({ variable, flatIndex })
114
+ flatIndex++
115
+ }
116
+ if (items.length > 0) {
117
+ groupsToRender.push({ label: group.label, items })
118
+ }
119
+ if (flatIndex >= maxResults) break
120
+ }
121
+
122
+ return (
123
+ <div
124
+ ref={containerRef}
125
+ data-slot="variable-autocomplete"
126
+ className={cn(
127
+ "absolute z-50 bg-popover border border-border rounded-lg shadow-lg w-64 overflow-hidden",
128
+ className
129
+ )}
130
+ {...rest}
131
+ >
132
+ <div data-slot="variable-autocomplete-header" className="px-3 py-1.5 border-b border-border">
133
+ <span className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
134
+ Insert variable
135
+ </span>
136
+ </div>
137
+ <div role="listbox" aria-label="Variables" className="overflow-y-auto" style={{ maxHeight: "312px" }}>
138
+ {groupsToRender.map((group) => (
139
+ <div key={group.label} role="group" aria-label={group.label}>
140
+ <div data-slot="variable-autocomplete-group-label" className="px-3 py-1 text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wider">
141
+ {group.label}
142
+ </div>
143
+ {group.items.map(({ variable, flatIndex: idx }) => (
144
+ <button
145
+ type="button"
146
+ key={`${group.label}:${variable.name}`}
147
+ role="option"
148
+ aria-selected={idx === activeIndex}
149
+ data-slot="variable-autocomplete-item"
150
+ onMouseDown={(e) => e.preventDefault()}
151
+ onClick={() => onSelect(variable)}
152
+ onMouseEnter={() => setActiveIndex(idx)}
153
+ className={cn(
154
+ "w-full text-left px-3 py-2 cursor-pointer transition-colors flex items-start justify-between gap-2",
155
+ idx === activeIndex ? "bg-muted/50" : "hover:bg-muted/30"
156
+ )}
157
+ >
158
+ <div className="min-w-0">
159
+ <div className="text-xs font-mono text-blue-600">{`{{${variable.name}}}`}</div>
160
+ {variable.description && (
161
+ <div className="text-[10px] text-muted-foreground">{variable.description}</div>
162
+ )}
163
+ </div>
164
+ {variable.source && (
165
+ <span className="text-[9px] px-1.5 py-0.5 rounded bg-muted/30 text-muted-foreground shrink-0 mt-0.5 capitalize">
166
+ {variable.source}
167
+ </span>
168
+ )}
169
+ </button>
170
+ ))}
171
+ </div>
172
+ ))}
173
+ </div>
174
+ </div>
175
+ )
176
+ }
177
+
178
+ export { VariableAutocomplete, type VariableAutocompleteProps, type VariableDef, type VariableGroup }
package/src/index.ts CHANGED
@@ -19,6 +19,8 @@ export * from "./components/avatar"
19
19
  export * from "./components/badge"
20
20
  export * from "./components/button"
21
21
  export * from "./components/card"
22
+ export * from "./components/compliance-badge"
23
+ export * from "./components/contact-chip"
22
24
  export * from "./components/contact-list"
23
25
  export * from "./components/dashboard-cards"
24
26
  export * from "./components/data-table"
@@ -29,15 +31,19 @@ export * from "./components/data-table-toolbar"
29
31
  export * from "./components/detail-view"
30
32
  export * from "./components/dialog"
31
33
  export * from "./components/dropdown-menu"
34
+ export * from "./components/empty-state"
32
35
  export * from "./components/entity-panel"
36
+ export * from "./components/filter-chip"
33
37
  export * from "./components/inbox-row"
34
38
  export * from "./components/inbox-toolbar"
39
+ export * from "./components/inline-banner"
35
40
  export * from "./components/input"
36
41
  export * from "./components/insights-filter-bar"
37
42
  export * from "./components/item-list"
38
43
  export * from "./components/item-list-display"
39
44
  export * from "./components/item-list-filter"
40
45
  export * from "./components/item-list-toolbar"
46
+ export * from "./components/kbd-hint"
41
47
  export * from "./components/label"
42
48
  export * from "./components/message"
43
49
  export * from "./components/metric-card"
@@ -54,6 +60,7 @@ export {
54
60
  export * from "./components/quick-action-sidebar-nav"
55
61
  export * from "./components/recommended-actions-section"
56
62
  export * from "./components/report-card"
63
+ export * from "./components/rich-text-toolbar"
57
64
  export * from "./components/score-analysis-modal"
58
65
  export * from "./components/score-breakdown"
59
66
  export * from "./components/score-feedback"
@@ -65,17 +72,21 @@ export * from "./components/sheet"
65
72
  export * from "./components/sidebar"
66
73
  export * from "./components/signal-feedback-inline"
67
74
  export * from "./components/simple-data-table"
68
- export * from "./components/virtualized-data-table"
69
75
  export * from "./components/skeleton"
70
76
  export * from "./components/status-badge"
77
+ export * from "./components/step-timeline"
78
+ export * from "./components/sticky-action-bar"
71
79
  export * from "./components/styled-bar-list"
72
80
  export * from "./components/suggested-actions"
81
+ export * from "./components/switch"
73
82
  export * from "./components/table"
74
83
  export * from "./components/tabs"
75
84
  export * from "./components/textarea"
76
85
  export * from "./components/timeline-activity"
77
86
  export * from "./components/tooltip"
87
+ export * from "./components/variable-autocomplete"
78
88
  export * from "./components/view-mode-toggle"
89
+ export * from "./components/virtualized-data-table"
79
90
 
80
91
  // Charts (re-exported for backward compatibility with root imports)
81
92
  export * from "./charts/index"
@@ -85,7 +85,7 @@ export interface InboxViewConfig {
85
85
  quickFilterTabs?: Array<{ id: string; label: string; matchValue?: string; count?: number }>
86
86
  hideAccountsButton?: boolean
87
87
  accountDetailsLabel?: string
88
- onSignalApprove?: (item: QueueItem) => void
88
+ onSignalApprove?: (item: QueueItem) => void | Promise<boolean>
89
89
  getSignalApprovalState?: (item: QueueItem) => ApprovalState | undefined
90
90
  signalLabels?: {
91
91
  approveButton?: string
@@ -94,6 +94,7 @@ export interface InboxViewConfig {
94
94
  dismissedStatus?: string
95
95
  opportunityCreated?: string
96
96
  confirmPrompt?: string
97
+ creatingStatus?: string
97
98
  }
98
99
  /** When true, the approve/create-opportunity button is hidden but the dismiss button remains. */
99
100
  hideApproveButton?: boolean
@@ -123,7 +123,7 @@ export interface DetailViewProps {
123
123
  onOpenEntityPanel?: () => void
124
124
  onOpenRecentActivity?: () => void
125
125
  onSuggestedActionFeedback?: (actionId: number | string, feedback: string, actionTitle?: string) => void
126
- onSignalApprove?: (item: QueueItem) => void
126
+ onSignalApprove?: (item: QueueItem) => void | Promise<boolean>
127
127
  getSignalApprovalState?: (item: QueueItem) => ApprovalState | undefined
128
128
  signalLabels?: InboxViewConfig["signalLabels"]
129
129
  hideApproveButton?: boolean
@@ -289,7 +289,7 @@ export function DetailView({
289
289
  ? null
290
290
  : typeof introOpt === "function"
291
291
  ? introOpt(item)
292
- : introOpt ?? `We detected signals that suggest a potential opportunity with ${item.company}.`
292
+ : introOpt ?? `Signals indicate a potential opportunity for ${item.company}.`
293
293
  return (
294
294
  <div className="mb-8">
295
295
  {briefHeading ? (
@@ -65,6 +65,28 @@
65
65
  /* Brand colors */
66
66
  --brand-purple: #6B5FCF;
67
67
 
68
+ /* Semantic status colors */
69
+ --status-active-bg: #ecfdf5;
70
+ --status-active-fg: #059669;
71
+ --status-active-border: #a7f3d0;
72
+
73
+ --status-pending-bg: #fffbeb;
74
+ --status-pending-fg: #d97706;
75
+ --status-pending-border: #fde68a;
76
+
77
+ /* Intentionally identical to status-pending — same visual, different semantic meaning */
78
+ --status-warning-bg: #fffbeb;
79
+ --status-warning-fg: #d97706;
80
+ --status-warning-border: #fde68a;
81
+
82
+ --status-info-bg: #eff6ff;
83
+ --status-info-fg: #2563eb;
84
+ --status-info-border: #bfdbfe;
85
+
86
+ --status-destructive-bg: #fef2f2;
87
+ --status-destructive-fg: #dc2626;
88
+ --status-destructive-border: #fecaca;
89
+
68
90
  --chart-1: var(--gray-900);
69
91
  --chart-2: var(--gray-700);
70
92
  --chart-3: var(--gray-500);
@@ -129,6 +151,28 @@
129
151
  /* Brand colors */
130
152
  --brand-purple: #6B5FCF;
131
153
 
154
+ /* Semantic status colors (dark) */
155
+ --status-active-bg: #022c22;
156
+ --status-active-fg: #34d399;
157
+ --status-active-border: #065f46;
158
+
159
+ --status-pending-bg: #451a03;
160
+ --status-pending-fg: #fbbf24;
161
+ --status-pending-border: #92400e;
162
+
163
+ /* Intentionally identical to status-pending — same visual, different semantic meaning */
164
+ --status-warning-bg: #451a03;
165
+ --status-warning-fg: #fbbf24;
166
+ --status-warning-border: #92400e;
167
+
168
+ --status-info-bg: #1e1b4b;
169
+ --status-info-fg: #60a5fa;
170
+ --status-info-border: #1e40af;
171
+
172
+ --status-destructive-bg: #450a0a;
173
+ --status-destructive-fg: #f87171;
174
+ --status-destructive-border: #991b1b;
175
+
132
176
  --chart-1: var(--gray-900);
133
177
  --chart-2: var(--gray-700);
134
178
  --chart-3: var(--gray-600);
@@ -196,6 +240,22 @@
196
240
 
197
241
  --color-brand-purple: var(--brand-purple);
198
242
 
243
+ --color-status-active-bg: var(--status-active-bg);
244
+ --color-status-active-fg: var(--status-active-fg);
245
+ --color-status-active-border: var(--status-active-border);
246
+ --color-status-pending-bg: var(--status-pending-bg);
247
+ --color-status-pending-fg: var(--status-pending-fg);
248
+ --color-status-pending-border: var(--status-pending-border);
249
+ --color-status-warning-bg: var(--status-warning-bg);
250
+ --color-status-warning-fg: var(--status-warning-fg);
251
+ --color-status-warning-border: var(--status-warning-border);
252
+ --color-status-info-bg: var(--status-info-bg);
253
+ --color-status-info-fg: var(--status-info-fg);
254
+ --color-status-info-border: var(--status-info-border);
255
+ --color-status-destructive-bg: var(--status-destructive-bg);
256
+ --color-status-destructive-fg: var(--status-destructive-fg);
257
+ --color-status-destructive-border: var(--status-destructive-border);
258
+
199
259
  --radius-sm: calc(var(--radius) - 4px);
200
260
  --radius-md: calc(var(--radius) - 2px);
201
261
  --radius-lg: var(--radius);