@handled-ai/design-system 0.18.4 → 0.18.5
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/charts/chart.d.ts +1 -1
- package/dist/charts/empty-chart-state.d.ts +11 -0
- package/dist/charts/empty-chart-state.js +70 -0
- package/dist/charts/empty-chart-state.js.map +1 -0
- package/dist/charts/index.d.ts +1 -0
- package/dist/charts/index.js +1 -0
- package/dist/charts/index.js.map +1 -1
- package/dist/charts/pipeline-overview.d.ts +2 -1
- package/dist/charts/pipeline-overview.js +32 -1
- package/dist/charts/pipeline-overview.js.map +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/days-open-cell.d.ts +16 -0
- package/dist/components/days-open-cell.js +73 -0
- package/dist/components/days-open-cell.js.map +1 -0
- package/dist/components/detail-drawer.d.ts +16 -0
- package/dist/components/detail-drawer.js +45 -0
- package/dist/components/detail-drawer.js.map +1 -0
- package/dist/components/feedback-primitives.d.ts +2 -41
- package/dist/components/feedback-primitives.js +6 -241
- package/dist/components/feedback-primitives.js.map +1 -1
- package/dist/components/insights-filter-bar.d.ts +2 -1
- package/dist/components/insights-filter-bar.js +13 -5
- package/dist/components/insights-filter-bar.js.map +1 -1
- package/dist/components/linked-entity-cell.d.ts +14 -0
- package/dist/components/linked-entity-cell.js +96 -0
- package/dist/components/linked-entity-cell.js.map +1 -0
- package/dist/components/metric-card.d.ts +14 -1
- package/dist/components/metric-card.js +97 -0
- package/dist/components/metric-card.js.map +1 -1
- package/dist/components/pill.d.ts +26 -0
- package/dist/components/pill.js +77 -0
- package/dist/components/pill.js.map +1 -0
- package/dist/components/quick-segment.d.ts +13 -0
- package/dist/components/quick-segment.js +96 -0
- package/dist/components/quick-segment.js.map +1 -0
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/score-why-chips.js +5 -26
- package/dist/components/score-why-chips.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/signal-priority-popover.js +6 -32
- package/dist/components/signal-priority-popover.js.map +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/prototype/index.d.ts +1 -1
- package/dist/prototype/prototype-accounts-view.d.ts +1 -1
- package/dist/prototype/prototype-admin-view.d.ts +1 -1
- package/dist/prototype/prototype-config.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.js +1 -4
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +1 -1
- package/dist/prototype/prototype-shell.d.ts +1 -1
- package/dist/{signal-priority-popover-DWaAMhPI.d.ts → signal-priority-popover-DQ_VuHac.d.ts} +2 -26
- package/package.json +1 -3
- package/src/charts/__tests__/insights-charts.test.tsx +62 -0
- package/src/charts/empty-chart-state.tsx +44 -0
- package/src/charts/index.ts +1 -0
- package/src/charts/pipeline-overview.tsx +41 -1
- package/src/components/__tests__/insights-primitives.test.tsx +135 -0
- package/src/components/days-open-cell.tsx +50 -0
- package/src/components/detail-drawer.tsx +60 -0
- package/src/components/feedback-primitives.tsx +26 -333
- package/src/components/insights-filter-bar.tsx +13 -4
- package/src/components/linked-entity-cell.tsx +74 -0
- package/src/components/metric-card.tsx +98 -0
- package/src/components/pill.tsx +67 -0
- package/src/components/quick-segment.tsx +68 -0
- package/src/components/score-why-chips.tsx +2 -28
- package/src/components/signal-priority-popover.tsx +4 -44
- package/src/index.ts +7 -2
- package/src/prototype/prototype-config.ts +1 -11
- package/src/prototype/prototype-inbox-view.tsx +0 -3
- package/src/components/__tests__/wit-636-feedback-states.test.tsx +0 -546
|
@@ -23,6 +23,7 @@ export interface FilterDefinition {
|
|
|
23
23
|
|
|
24
24
|
export interface InsightsFilterBarProps {
|
|
25
25
|
filters: FilterDefinition[]
|
|
26
|
+
variant?: "default" | "compact"
|
|
26
27
|
values: Record<string, string>
|
|
27
28
|
onChange: (filterId: string, value: string) => void
|
|
28
29
|
onClearAll?: () => void
|
|
@@ -45,6 +46,7 @@ function InsightsFilterBar({
|
|
|
45
46
|
onChange,
|
|
46
47
|
onClearAll,
|
|
47
48
|
className,
|
|
49
|
+
variant = "default",
|
|
48
50
|
}: InsightsFilterBarProps) {
|
|
49
51
|
const showClearAll = onClearAll && hasNonDefaultValue(filters, values)
|
|
50
52
|
|
|
@@ -52,11 +54,12 @@ function InsightsFilterBar({
|
|
|
52
54
|
<div
|
|
53
55
|
data-slot="insights-filter-bar"
|
|
54
56
|
className={cn(
|
|
55
|
-
"flex flex-wrap items-center
|
|
57
|
+
"flex flex-wrap items-center rounded-md border border-border bg-card shadow-sm",
|
|
58
|
+
variant === "compact" ? "gap-2 p-2" : "gap-3 p-4",
|
|
56
59
|
className
|
|
57
60
|
)}
|
|
58
61
|
>
|
|
59
|
-
<div className="flex items-center gap-2">
|
|
62
|
+
<div className={cn("flex items-center gap-2", variant === "compact" && "sr-only")}>
|
|
60
63
|
<FilterIcon className="h-4 w-4 text-muted-foreground" />
|
|
61
64
|
<span className="text-sm font-medium text-muted-foreground">
|
|
62
65
|
Filters:
|
|
@@ -80,7 +83,10 @@ function InsightsFilterBar({
|
|
|
80
83
|
<Button
|
|
81
84
|
variant="outline"
|
|
82
85
|
size="sm"
|
|
83
|
-
className=
|
|
86
|
+
className={cn(
|
|
87
|
+
"gap-1.5 text-xs font-normal shadow-none",
|
|
88
|
+
variant === "compact" ? "h-7 px-2" : "h-8"
|
|
89
|
+
)}
|
|
84
90
|
>
|
|
85
91
|
{IconComp ? (
|
|
86
92
|
<IconComp className="h-3.5 w-3.5 text-muted-foreground" />
|
|
@@ -118,7 +124,10 @@ function InsightsFilterBar({
|
|
|
118
124
|
<Button
|
|
119
125
|
variant="ghost"
|
|
120
126
|
size="sm"
|
|
121
|
-
className=
|
|
127
|
+
className={cn(
|
|
128
|
+
"text-xs text-destructive hover:text-destructive",
|
|
129
|
+
variant === "compact" ? "h-7 px-2" : "h-8"
|
|
130
|
+
)}
|
|
122
131
|
onClick={onClearAll}
|
|
123
132
|
>
|
|
124
133
|
Clear All
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ExternalLink } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
export interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
name: React.ReactNode
|
|
10
|
+
href?: string
|
|
11
|
+
subtitle?: React.ReactNode
|
|
12
|
+
meta?: React.ReactNode
|
|
13
|
+
icon?: React.ReactNode
|
|
14
|
+
external?: boolean
|
|
15
|
+
onNavigate?: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function LinkedEntityCell({
|
|
19
|
+
name,
|
|
20
|
+
href,
|
|
21
|
+
subtitle,
|
|
22
|
+
meta,
|
|
23
|
+
icon,
|
|
24
|
+
external = false,
|
|
25
|
+
onNavigate,
|
|
26
|
+
className,
|
|
27
|
+
...props
|
|
28
|
+
}: LinkedEntityCellProps) {
|
|
29
|
+
const content = (
|
|
30
|
+
<>
|
|
31
|
+
<span className="truncate">{name}</span>
|
|
32
|
+
{external ? <ExternalLink className="h-3 w-3 shrink-0 opacity-60" aria-hidden="true" /> : null}
|
|
33
|
+
</>
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
data-slot="linked-entity-cell"
|
|
39
|
+
className={cn("flex min-w-0 items-center gap-2", className)}
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
{icon ? (
|
|
43
|
+
<span data-slot="linked-entity-cell-icon" className="shrink-0 text-muted-foreground">
|
|
44
|
+
{icon}
|
|
45
|
+
</span>
|
|
46
|
+
) : null}
|
|
47
|
+
<div className="min-w-0 flex-1">
|
|
48
|
+
{href ? (
|
|
49
|
+
<a
|
|
50
|
+
data-slot="linked-entity-cell-link"
|
|
51
|
+
href={href}
|
|
52
|
+
target={external ? "_blank" : undefined}
|
|
53
|
+
rel={external ? "noreferrer" : undefined}
|
|
54
|
+
onClick={onNavigate}
|
|
55
|
+
className="inline-flex max-w-full items-center gap-1 truncate font-medium text-foreground underline-offset-4 hover:text-primary hover:underline"
|
|
56
|
+
>
|
|
57
|
+
{content}
|
|
58
|
+
</a>
|
|
59
|
+
) : (
|
|
60
|
+
<span data-slot="linked-entity-cell-name" className="block truncate font-medium text-foreground">
|
|
61
|
+
{name}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
{subtitle || meta ? (
|
|
65
|
+
<div data-slot="linked-entity-cell-meta" className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
66
|
+
{subtitle}
|
|
67
|
+
{subtitle && meta ? <span className="px-1">·</span> : null}
|
|
68
|
+
{meta}
|
|
69
|
+
</div>
|
|
70
|
+
) : null}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { ArrowUp, ArrowDown, Info, ExternalLink } from "lucide-react"
|
|
3
|
+
import type { LucideIcon } from "lucide-react"
|
|
3
4
|
import { cn } from "../lib/utils"
|
|
4
5
|
|
|
5
6
|
export interface MetricDataPoint {
|
|
@@ -24,6 +25,103 @@ export interface MetricCardProps {
|
|
|
24
25
|
showInfo?: boolean
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
export interface KpiStripItem {
|
|
29
|
+
id?: string
|
|
30
|
+
label: React.ReactNode
|
|
31
|
+
value: React.ReactNode
|
|
32
|
+
unit?: React.ReactNode
|
|
33
|
+
subtitle?: React.ReactNode
|
|
34
|
+
change?: MetricCardProps["change"]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface KpiStripProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
38
|
+
items: KpiStripItem[]
|
|
39
|
+
columns?: 2 | 3 | 4
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getChangePresentation(change: MetricCardProps["change"]): {
|
|
43
|
+
icon: LucideIcon | null
|
|
44
|
+
className: string
|
|
45
|
+
} | null {
|
|
46
|
+
if (!change) return null
|
|
47
|
+
if (change.direction === "neutral") {
|
|
48
|
+
return { icon: null, className: "text-muted-foreground" }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isGoodDirection = change.isGood !== undefined
|
|
52
|
+
? change.isGood
|
|
53
|
+
: change.direction === "up"
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
icon: change.direction === "down" ? ArrowDown : ArrowUp,
|
|
57
|
+
className: isGoodDirection ? "text-emerald-600" : "text-red-600",
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function KpiStrip({ items, columns = 4, className, ...props }: KpiStripProps) {
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
data-slot="kpi-strip"
|
|
65
|
+
className={cn(
|
|
66
|
+
"grid gap-3 rounded-xl border border-border bg-card p-3 shadow-sm",
|
|
67
|
+
columns === 2 && "sm:grid-cols-2",
|
|
68
|
+
columns === 3 && "sm:grid-cols-3",
|
|
69
|
+
columns === 4 && "sm:grid-cols-2 lg:grid-cols-4",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{items.map((item, index) => {
|
|
75
|
+
const changePresentation = getChangePresentation(item.change)
|
|
76
|
+
const ChangeIcon = changePresentation?.icon
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
key={item.id ?? index}
|
|
81
|
+
data-slot="kpi-strip-item"
|
|
82
|
+
className="min-w-0 rounded-lg bg-muted/40 px-3 py-2"
|
|
83
|
+
>
|
|
84
|
+
<div data-slot="kpi-strip-label" className="truncate text-xs font-medium text-muted-foreground">
|
|
85
|
+
{item.label}
|
|
86
|
+
</div>
|
|
87
|
+
<div className="mt-1 flex items-baseline gap-1">
|
|
88
|
+
<span data-slot="kpi-strip-value" className="truncate text-2xl font-bold tracking-tight text-foreground">
|
|
89
|
+
{item.value}
|
|
90
|
+
</span>
|
|
91
|
+
{item.unit ? (
|
|
92
|
+
<span data-slot="kpi-strip-unit" className="text-sm font-semibold text-muted-foreground">
|
|
93
|
+
{item.unit}
|
|
94
|
+
</span>
|
|
95
|
+
) : null}
|
|
96
|
+
</div>
|
|
97
|
+
{item.subtitle || item.change ? (
|
|
98
|
+
<div className="mt-1 flex items-center gap-2 text-xs">
|
|
99
|
+
{item.change ? (
|
|
100
|
+
<span
|
|
101
|
+
data-slot="kpi-strip-change"
|
|
102
|
+
className={cn(
|
|
103
|
+
"inline-flex items-center gap-0.5 font-semibold",
|
|
104
|
+
changePresentation?.className
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
{ChangeIcon ? <ChangeIcon className="h-3 w-3 stroke-[3]" /> : null}
|
|
108
|
+
{item.change.value}
|
|
109
|
+
</span>
|
|
110
|
+
) : null}
|
|
111
|
+
{item.subtitle ? (
|
|
112
|
+
<span data-slot="kpi-strip-subtitle" className="truncate text-muted-foreground">
|
|
113
|
+
{item.subtitle}
|
|
114
|
+
</span>
|
|
115
|
+
) : null}
|
|
116
|
+
</div>
|
|
117
|
+
) : null}
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
27
125
|
export function MetricCard({
|
|
28
126
|
title,
|
|
29
127
|
value,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Insights-friendly pill convenience wrappers.
|
|
8
|
+
*
|
|
9
|
+
* Pill and StatusPill are small, rounded wrappers for dense Insights surfaces
|
|
10
|
+
* such as tables, KPI strips, and filter summaries. They intentionally wrap the
|
|
11
|
+
* existing Badge/StatusBadge visual language and are not a replacement for all
|
|
12
|
+
* Badge usage across the design system.
|
|
13
|
+
*/
|
|
14
|
+
export type PillStatus = "success" | "warning" | "error" | "neutral" | "info"
|
|
15
|
+
|
|
16
|
+
const pillVariants = cva(
|
|
17
|
+
"inline-flex w-fit shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors [&>svg]:size-3",
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
22
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
23
|
+
destructive: "border-transparent bg-destructive text-white dark:bg-destructive/60",
|
|
24
|
+
outline: "border-border bg-background text-foreground",
|
|
25
|
+
ghost: "border-transparent bg-transparent text-muted-foreground",
|
|
26
|
+
success: "border-transparent bg-green-100 text-green-950 dark:bg-green-950 dark:text-green-100",
|
|
27
|
+
warning: "border-transparent bg-yellow-100 text-yellow-950 dark:bg-yellow-950 dark:text-yellow-100",
|
|
28
|
+
error: "border-transparent bg-red-100 text-red-950 dark:bg-red-950 dark:text-red-100",
|
|
29
|
+
neutral: "border-transparent bg-muted text-foreground",
|
|
30
|
+
info: "border-transparent bg-blue-100 text-blue-950 dark:bg-blue-950 dark:text-blue-100",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "neutral",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
export interface PillProps
|
|
40
|
+
extends React.ComponentProps<"span">,
|
|
41
|
+
VariantProps<typeof pillVariants> {}
|
|
42
|
+
|
|
43
|
+
export function Pill({ className, variant = "neutral", ...props }: PillProps) {
|
|
44
|
+
return (
|
|
45
|
+
<span
|
|
46
|
+
data-slot="pill"
|
|
47
|
+
data-variant={variant}
|
|
48
|
+
className={cn(pillVariants({ variant }), className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface StatusPillProps extends Omit<PillProps, "variant"> {
|
|
55
|
+
status: React.ReactNode
|
|
56
|
+
intent?: PillStatus
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function StatusPill({ status, intent = "neutral", children, ...props }: StatusPillProps) {
|
|
60
|
+
return (
|
|
61
|
+
<Pill data-slot="status-pill" variant={intent} {...props}>
|
|
62
|
+
{children ?? status}
|
|
63
|
+
</Pill>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { pillVariants }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
export interface QuickSegmentProps
|
|
8
|
+
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect" | "value"> {
|
|
9
|
+
label: React.ReactNode
|
|
10
|
+
value: string
|
|
11
|
+
selected?: boolean
|
|
12
|
+
count?: number | string
|
|
13
|
+
description?: React.ReactNode
|
|
14
|
+
onSelect?: (value: string) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function QuickSegment({
|
|
18
|
+
label,
|
|
19
|
+
value,
|
|
20
|
+
selected = false,
|
|
21
|
+
count,
|
|
22
|
+
description,
|
|
23
|
+
onSelect,
|
|
24
|
+
className,
|
|
25
|
+
type = "button",
|
|
26
|
+
...props
|
|
27
|
+
}: QuickSegmentProps) {
|
|
28
|
+
return (
|
|
29
|
+
<button
|
|
30
|
+
data-slot="quick-segment"
|
|
31
|
+
data-selected={selected ? "true" : "false"}
|
|
32
|
+
type={type}
|
|
33
|
+
aria-pressed={selected}
|
|
34
|
+
className={cn(
|
|
35
|
+
"inline-flex min-h-8 items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-medium transition-colors",
|
|
36
|
+
selected
|
|
37
|
+
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
|
38
|
+
: "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
onClick={(event) => {
|
|
42
|
+
props.onClick?.(event)
|
|
43
|
+
if (!event.defaultPrevented) onSelect?.(value)
|
|
44
|
+
}}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<span data-slot="quick-segment-label">{label}</span>
|
|
48
|
+
{count !== undefined ? (
|
|
49
|
+
<span
|
|
50
|
+
data-slot="quick-segment-count"
|
|
51
|
+
className={cn(
|
|
52
|
+
"rounded-full px-1.5 py-0.5 text-[11px] leading-none",
|
|
53
|
+
selected
|
|
54
|
+
? "bg-primary-foreground/20 text-primary-foreground"
|
|
55
|
+
: "bg-muted text-muted-foreground"
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{count}
|
|
59
|
+
</span>
|
|
60
|
+
) : null}
|
|
61
|
+
{description ? (
|
|
62
|
+
<span data-slot="quick-segment-description" className="sr-only">
|
|
63
|
+
{description}
|
|
64
|
+
</span>
|
|
65
|
+
) : null}
|
|
66
|
+
</button>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "lucide-react"
|
|
16
16
|
import type { LucideIcon } from "lucide-react"
|
|
17
17
|
import { FeedbackFooter } from "./feedback-primitives"
|
|
18
|
-
import type { FeedbackChipTree, FeedbackSubmitData
|
|
18
|
+
import type { FeedbackChipTree, FeedbackSubmitData } from "./feedback-primitives"
|
|
19
19
|
import { cn } from "../lib/utils"
|
|
20
20
|
import type {
|
|
21
21
|
QueueItem,
|
|
@@ -266,7 +266,6 @@ function StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket
|
|
|
266
266
|
const IconComponent = resolveIcon(signal.signalTypeName)
|
|
267
267
|
const toneClass = tone ? (SIGNAL_TONE_CLASSES[tone] ?? DEFAULT_TONE_CLASS) : DEFAULT_TONE_CLASS
|
|
268
268
|
const isCombined = signal.signalTypeName === "combined_signal" && signal.components && signal.components.length > 0
|
|
269
|
-
const hasBalance = Boolean(signal.currentBalance || signal.balanceContext)
|
|
270
269
|
|
|
271
270
|
const rowContent = (
|
|
272
271
|
<>
|
|
@@ -305,26 +304,6 @@ function StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket
|
|
|
305
304
|
|
|
306
305
|
{/* Slot 5: Chevron */}
|
|
307
306
|
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5 group-hover:text-foreground/50" />
|
|
308
|
-
|
|
309
|
-
{/* Balance context strip — spans full row below grid columns */}
|
|
310
|
-
{hasBalance && (
|
|
311
|
-
<div
|
|
312
|
-
className="col-span-full mt-0.5 text-[10px] text-muted-foreground/70"
|
|
313
|
-
data-testid="balance-context-strip"
|
|
314
|
-
>
|
|
315
|
-
{signal.currentBalance && (
|
|
316
|
-
<span>
|
|
317
|
-
Current balance <span className="font-medium text-muted-foreground">{signal.currentBalance}</span>
|
|
318
|
-
</span>
|
|
319
|
-
)}
|
|
320
|
-
{signal.balanceContext && (
|
|
321
|
-
<span>
|
|
322
|
-
{signal.currentBalance ? " · " : ""}
|
|
323
|
-
{signal.balanceContext}
|
|
324
|
-
</span>
|
|
325
|
-
)}
|
|
326
|
-
</div>
|
|
327
|
-
)}
|
|
328
307
|
</>
|
|
329
308
|
)
|
|
330
309
|
|
|
@@ -426,11 +405,9 @@ interface WhyCardProps {
|
|
|
426
405
|
panelId: string
|
|
427
406
|
onOpenSignalBucket?: ScoreWhyChipsProps["onOpenSignalBucket"]
|
|
428
407
|
onBucketFeedback?: (bucketKey: string, data: FeedbackSubmitData) => void
|
|
429
|
-
/** Persisted bucket-level feedback to hydrate from. */
|
|
430
|
-
initialBucketFeedback?: PersistedFeedbackData | null
|
|
431
408
|
}
|
|
432
409
|
|
|
433
|
-
function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback
|
|
410
|
+
function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback }: WhyCardProps) {
|
|
434
411
|
const [showAll, setShowAll] = React.useState(false)
|
|
435
412
|
const [bucketFeedback, setBucketFeedback] = React.useState<"positive" | "negative" | null>(null)
|
|
436
413
|
const totalCount = bucket.signalCount ?? signals.length
|
|
@@ -511,8 +488,6 @@ function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketF
|
|
|
511
488
|
negativeChips={BUCKET_NEGATIVE_CHIPS}
|
|
512
489
|
negativePrompt="Was this bucket useful?"
|
|
513
490
|
positivePrompt="Thanks! What was useful about this bucket?"
|
|
514
|
-
initialFeedback={initialBucketFeedback}
|
|
515
|
-
feedbackKey={bucket.key}
|
|
516
491
|
/>
|
|
517
492
|
</div>
|
|
518
493
|
)}
|
|
@@ -586,7 +561,6 @@ export function ScoreWhyChips({
|
|
|
586
561
|
panelId={selectedPanelId}
|
|
587
562
|
onOpenSignalBucket={onOpenSignalBucket}
|
|
588
563
|
onBucketFeedback={signalData.onBucketFeedback}
|
|
589
|
-
initialBucketFeedback={signalData.initialBucketFeedback?.[selectedBucket.key]}
|
|
590
564
|
/>
|
|
591
565
|
)}
|
|
592
566
|
</div>
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
Info,
|
|
20
20
|
} from "lucide-react"
|
|
21
21
|
import { cn } from "../lib/utils"
|
|
22
|
-
import { FeedbackFooter
|
|
23
|
-
import type { FeedbackChipTree, FeedbackSubmitData
|
|
22
|
+
import { FeedbackFooter } from "./feedback-primitives"
|
|
23
|
+
import type { FeedbackChipTree, FeedbackSubmitData } from "./feedback-primitives"
|
|
24
24
|
import type { SignalScoreUrgencyLabel } from "../prototype/prototype-config"
|
|
25
25
|
import { getSignalScoreUrgencyLabel, scoreRangeForUrgency, SIGNAL_TONE_CLASSES } from "./score-why-chips"
|
|
26
26
|
|
|
@@ -58,12 +58,6 @@ export interface SignalPriorityPopoverProps {
|
|
|
58
58
|
feedbackChips?: FeedbackChipTree[]
|
|
59
59
|
onFeedbackSubmit?: (data: FeedbackSubmitData) => void
|
|
60
60
|
className?: string
|
|
61
|
-
/** Persisted factor-level feedback (keyed by factor key). */
|
|
62
|
-
initialFactorFeedback?: Record<string, { type: "up" | "down"; detail: string; ownershipLabel?: string }>
|
|
63
|
-
/** Callback when user submits factor-level feedback. */
|
|
64
|
-
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
65
|
-
/** Persisted priority-level feedback for the footer. */
|
|
66
|
-
initialPriorityFeedback?: PersistedFeedbackData | null
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
// ---------------------------------------------------------------------------
|
|
@@ -158,13 +152,7 @@ function DirectionIcon({ direction }: { direction: PriorityFactor["direction"] }
|
|
|
158
152
|
// PriorityFactorRow
|
|
159
153
|
// ---------------------------------------------------------------------------
|
|
160
154
|
|
|
161
|
-
|
|
162
|
-
factor: PriorityFactor
|
|
163
|
-
initialFeedback?: { type: "up" | "down"; detail: string; ownershipLabel?: string }
|
|
164
|
-
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: PriorityFactorRowProps) {
|
|
155
|
+
function PriorityFactorRow({ factor }: { factor: PriorityFactor }) {
|
|
168
156
|
const IconComponent = FACTOR_ICONS[factor.icon] ?? Activity
|
|
169
157
|
const toneClasses = TONE_ICON_CLASSES[factor.tone]
|
|
170
158
|
const directionClasses = DIRECTION_CLASSES[factor.direction]
|
|
@@ -236,21 +224,6 @@ function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: Priori
|
|
|
236
224
|
|
|
237
225
|
{/* empty grid cell under score column */}
|
|
238
226
|
<div />
|
|
239
|
-
|
|
240
|
-
{/* Factor-level feedback row (spans icon + content columns) */}
|
|
241
|
-
{onFactorFeedback && (
|
|
242
|
-
<>
|
|
243
|
-
<div />
|
|
244
|
-
<div className="col-span-2 mt-1">
|
|
245
|
-
<InlineFeedbackControl
|
|
246
|
-
feedbackKey={factor.key}
|
|
247
|
-
initialFeedback={initialFeedback}
|
|
248
|
-
onFeedback={onFactorFeedback}
|
|
249
|
-
testIdPrefix="factor"
|
|
250
|
-
/>
|
|
251
|
-
</div>
|
|
252
|
-
</>
|
|
253
|
-
)}
|
|
254
227
|
</div>
|
|
255
228
|
)
|
|
256
229
|
}
|
|
@@ -268,9 +241,6 @@ export function SignalPriorityPopover({
|
|
|
268
241
|
feedbackChips,
|
|
269
242
|
onFeedbackSubmit,
|
|
270
243
|
className,
|
|
271
|
-
initialFactorFeedback,
|
|
272
|
-
onFactorFeedback,
|
|
273
|
-
initialPriorityFeedback,
|
|
274
244
|
}: SignalPriorityPopoverProps) {
|
|
275
245
|
const urgencyLabel = getSignalScoreUrgencyLabel(score, providedLabel)
|
|
276
246
|
const scoreRange = scoreRangeForUrgency(urgencyLabel)
|
|
@@ -282,9 +252,6 @@ export function SignalPriorityPopover({
|
|
|
282
252
|
const triggerHover = URGENCY_TRIGGER_HOVER[urgencyLabel]
|
|
283
253
|
const triggerOpen = URGENCY_TRIGGER_OPEN[urgencyLabel]
|
|
284
254
|
|
|
285
|
-
// Derive a stable feedbackKey for the footer from score + urgencyLabel
|
|
286
|
-
const footerFeedbackKey = `priority-${score}-${urgencyLabel}`
|
|
287
|
-
|
|
288
255
|
return (
|
|
289
256
|
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
|
|
290
257
|
<PopoverPrimitive.Trigger asChild>
|
|
@@ -365,12 +332,7 @@ export function SignalPriorityPopover({
|
|
|
365
332
|
{/* Factor rows */}
|
|
366
333
|
<div className="divide-y divide-border/40">
|
|
367
334
|
{factors.map((factor) => (
|
|
368
|
-
<PriorityFactorRow
|
|
369
|
-
key={factor.key}
|
|
370
|
-
factor={factor}
|
|
371
|
-
initialFeedback={initialFactorFeedback?.[factor.key]}
|
|
372
|
-
onFactorFeedback={onFactorFeedback}
|
|
373
|
-
/>
|
|
335
|
+
<PriorityFactorRow key={factor.key} factor={factor} />
|
|
374
336
|
))}
|
|
375
337
|
</div>
|
|
376
338
|
</>
|
|
@@ -387,8 +349,6 @@ export function SignalPriorityPopover({
|
|
|
387
349
|
negativeChips={feedbackChips ?? DEFAULT_PRIORITY_FEEDBACK_CHIPS}
|
|
388
350
|
positivePrompt="Thanks. Anything to keep about this score?"
|
|
389
351
|
className="px-4 py-3"
|
|
390
|
-
initialFeedback={initialPriorityFeedback}
|
|
391
|
-
feedbackKey={footerFeedbackKey}
|
|
392
352
|
/>
|
|
393
353
|
</div>
|
|
394
354
|
)}
|
package/src/index.ts
CHANGED
|
@@ -33,12 +33,13 @@ export * from "./components/data-table-filter"
|
|
|
33
33
|
export * from "./components/data-table-quick-views"
|
|
34
34
|
export * from "./components/data-table-toolbar"
|
|
35
35
|
export * from "./components/detail-view"
|
|
36
|
+
export * from "./components/detail-drawer"
|
|
36
37
|
export * from "./components/dialog"
|
|
37
38
|
export * from "./components/dropdown-menu"
|
|
38
39
|
export * from "./components/empty-state"
|
|
39
40
|
export * from "./components/entity-panel"
|
|
40
|
-
export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions
|
|
41
|
-
export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData
|
|
41
|
+
export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions } from "./components/feedback-primitives"
|
|
42
|
+
export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData } from "./components/feedback-primitives"
|
|
42
43
|
export { SignalPriorityPopover } from "./components/signal-priority-popover"
|
|
43
44
|
export type { SignalPriorityPopoverProps, PriorityFactor } from "./components/signal-priority-popover"
|
|
44
45
|
export * from "./components/filter-chip"
|
|
@@ -47,6 +48,8 @@ export * from "./components/inbox-toolbar"
|
|
|
47
48
|
export * from "./components/inline-banner"
|
|
48
49
|
export * from "./components/input"
|
|
49
50
|
export * from "./components/insights-filter-bar"
|
|
51
|
+
export * from "./components/days-open-cell"
|
|
52
|
+
export * from "./components/linked-entity-cell"
|
|
50
53
|
export * from "./components/item-list"
|
|
51
54
|
export * from "./components/item-list-display"
|
|
52
55
|
export * from "./components/item-list-filter"
|
|
@@ -56,9 +59,11 @@ export * from "./components/label"
|
|
|
56
59
|
export * from "./components/message"
|
|
57
60
|
export * from "./components/metric-card"
|
|
58
61
|
export * from "./components/performance-metrics-table"
|
|
62
|
+
export * from "./components/pill"
|
|
59
63
|
export * from "./components/preview-list"
|
|
60
64
|
export * from "./components/progress"
|
|
61
65
|
export * from "./components/quick-action-chat-area"
|
|
66
|
+
export * from "./components/quick-segment"
|
|
62
67
|
export {
|
|
63
68
|
QuickActionModal,
|
|
64
69
|
type QuickActionPriority,
|
|
@@ -16,7 +16,7 @@ import type { TimelineEvent } from "../components/timeline-activity"
|
|
|
16
16
|
import type { ApprovalState } from "../components/signal-feedback-inline"
|
|
17
17
|
import type { LucideIcon } from "lucide-react"
|
|
18
18
|
import type { PriorityFactor } from "../components/signal-priority-popover"
|
|
19
|
-
import type { FeedbackChipTree, FeedbackSubmitData
|
|
19
|
+
import type { FeedbackChipTree, FeedbackSubmitData } from "../components/feedback-primitives"
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
// Shared
|
|
@@ -57,10 +57,6 @@ export interface SignalScoreExplanationSignal {
|
|
|
57
57
|
counterparty?: string
|
|
58
58
|
/** Component breakdown for combined signals. */
|
|
59
59
|
components?: Array<{ type: string; count: number }>
|
|
60
|
-
/** Current balance value (e.g., "$3.0M") for balance context strip. */
|
|
61
|
-
currentBalance?: string
|
|
62
|
-
/** Additional balance context text (e.g., "down from $23M"). */
|
|
63
|
-
balanceContext?: string
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
export interface SignalScoreExplanationBucket {
|
|
@@ -101,12 +97,6 @@ export interface SignalScoreData {
|
|
|
101
97
|
/** @deprecated The compact score UX no longer renders score-level thumbs by default. */
|
|
102
98
|
initialScoreFeedback?: { type: "up" | "down"; pills: string[]; detail: string } | null
|
|
103
99
|
initialFactorFeedback?: Record<string, { type: "up" | "down"; detail: string }>
|
|
104
|
-
/** Factor-level feedback for the priority popover rows (keyed by factor key). */
|
|
105
|
-
initialFactorPopoverFeedback?: Record<string, { type: "up" | "down"; detail: string; ownershipLabel?: string }>
|
|
106
|
-
/** Persisted bucket-level feedback, keyed by bucket key. */
|
|
107
|
-
initialBucketFeedback?: Record<string, PersistedFeedbackData>
|
|
108
|
-
/** Persisted priority-level feedback for the popover footer. */
|
|
109
|
-
initialPriorityFeedback?: PersistedFeedbackData | null
|
|
110
100
|
/** Priority factors for the popover breakdown. */
|
|
111
101
|
priorityFactors?: PriorityFactor[]
|
|
112
102
|
/** Negative feedback chip tree for the priority popover. */
|
|
@@ -272,9 +272,6 @@ export function DetailView({
|
|
|
272
272
|
metaText={undefined}
|
|
273
273
|
feedbackChips={signalData.priorityFeedbackChips}
|
|
274
274
|
onFeedbackSubmit={signalData.onPriorityFeedback}
|
|
275
|
-
initialFactorFeedback={signalData.initialFactorPopoverFeedback}
|
|
276
|
-
onFactorFeedback={signalData.onFactorFeedback}
|
|
277
|
-
initialPriorityFeedback={signalData.initialPriorityFeedback}
|
|
278
275
|
/>
|
|
279
276
|
{signalData.timeChipLabel && (
|
|
280
277
|
<Badge variant="outline" title={signalData.timeChipDetail ?? undefined}>
|