@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.
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/compliance-badge.d.ts +10 -0
- package/dist/components/compliance-badge.js +95 -0
- package/dist/components/compliance-badge.js.map +1 -0
- package/dist/components/contact-chip.d.ts +12 -0
- package/dist/components/contact-chip.js +98 -0
- package/dist/components/contact-chip.js.map +1 -0
- package/dist/components/empty-state.d.ts +11 -0
- package/dist/components/empty-state.js +46 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/filter-chip.d.ts +9 -0
- package/dist/components/filter-chip.js +67 -0
- package/dist/components/filter-chip.js.map +1 -0
- package/dist/components/inline-banner.d.ts +10 -0
- package/dist/components/inline-banner.js +97 -0
- package/dist/components/inline-banner.js.map +1 -0
- package/dist/components/kbd-hint.d.ts +5 -0
- package/dist/components/kbd-hint.js +51 -0
- package/dist/components/kbd-hint.js.map +1 -0
- package/dist/components/rich-text-toolbar.d.ts +9 -0
- package/dist/components/rich-text-toolbar.js +103 -0
- package/dist/components/rich-text-toolbar.js.map +1 -0
- package/dist/components/signal-feedback-inline.d.ts +13 -2
- package/dist/components/signal-feedback-inline.js +33 -4
- package/dist/components/signal-feedback-inline.js.map +1 -1
- package/dist/components/step-timeline.d.ts +19 -0
- package/dist/components/step-timeline.js +134 -0
- package/dist/components/step-timeline.js.map +1 -0
- package/dist/components/sticky-action-bar.d.ts +10 -0
- package/dist/components/sticky-action-bar.js +56 -0
- package/dist/components/sticky-action-bar.js.map +1 -0
- package/dist/components/switch.d.ts +6 -0
- package/dist/components/switch.js +66 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/variable-autocomplete.d.ts +21 -0
- package/dist/components/variable-autocomplete.js +171 -0
- package/dist/components/variable-autocomplete.js.map +1 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/prototype/prototype-config.d.ts +2 -1
- package/dist/prototype/prototype-inbox-view.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.js +1 -1
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/package.json +2 -1
- package/src/components/__tests__/compliance-badge.test.tsx +88 -0
- package/src/components/__tests__/contact-chip.test.tsx +88 -0
- package/src/components/__tests__/empty-state.test.tsx +76 -0
- package/src/components/__tests__/filter-chip.test.tsx +73 -0
- package/src/components/__tests__/inline-banner.test.tsx +110 -0
- package/src/components/__tests__/kbd-hint.test.tsx +29 -0
- package/src/components/__tests__/rich-text-toolbar.test.tsx +92 -0
- package/src/components/__tests__/step-timeline.test.tsx +174 -0
- package/src/components/__tests__/sticky-action-bar.test.tsx +52 -0
- package/src/components/__tests__/switch.test.tsx +39 -0
- package/src/components/__tests__/variable-autocomplete.test.tsx +155 -0
- package/src/components/compliance-badge.tsx +68 -0
- package/src/components/contact-chip.tsx +68 -0
- package/src/components/empty-state.tsx +37 -0
- package/src/components/filter-chip.tsx +37 -0
- package/src/components/inline-banner.tsx +69 -0
- package/src/components/kbd-hint.tsx +21 -0
- package/src/components/rich-text-toolbar.tsx +90 -0
- package/src/components/signal-feedback-inline.tsx +51 -5
- package/src/components/step-timeline.tsx +149 -0
- package/src/components/sticky-action-bar.tsx +36 -0
- package/src/components/switch.tsx +29 -0
- package/src/components/variable-autocomplete.tsx +178 -0
- package/src/index.ts +12 -1
- package/src/prototype/prototype-config.ts +2 -1
- package/src/prototype/prototype-inbox-view.tsx +2 -2
- 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 ?? `
|
|
292
|
+
: introOpt ?? `Signals indicate a potential opportunity for ${item.company}.`
|
|
293
293
|
return (
|
|
294
294
|
<div className="mb-8">
|
|
295
295
|
{briefHeading ? (
|
package/src/styles/globals.css
CHANGED
|
@@ -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);
|