@handled-ai/design-system 0.18.4 → 0.18.6

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 (48) hide show
  1. package/dist/charts/chart.d.ts +1 -1
  2. package/dist/charts/empty-chart-state.d.ts +11 -0
  3. package/dist/charts/empty-chart-state.js +70 -0
  4. package/dist/charts/empty-chart-state.js.map +1 -0
  5. package/dist/charts/index.d.ts +1 -0
  6. package/dist/charts/index.js +1 -0
  7. package/dist/charts/index.js.map +1 -1
  8. package/dist/charts/pipeline-overview.d.ts +2 -1
  9. package/dist/charts/pipeline-overview.js +32 -1
  10. package/dist/charts/pipeline-overview.js.map +1 -1
  11. package/dist/components/days-open-cell.d.ts +16 -0
  12. package/dist/components/days-open-cell.js +73 -0
  13. package/dist/components/days-open-cell.js.map +1 -0
  14. package/dist/components/detail-drawer.d.ts +16 -0
  15. package/dist/components/detail-drawer.js +45 -0
  16. package/dist/components/detail-drawer.js.map +1 -0
  17. package/dist/components/insights-filter-bar.d.ts +2 -1
  18. package/dist/components/insights-filter-bar.js +13 -5
  19. package/dist/components/insights-filter-bar.js.map +1 -1
  20. package/dist/components/linked-entity-cell.d.ts +14 -0
  21. package/dist/components/linked-entity-cell.js +96 -0
  22. package/dist/components/linked-entity-cell.js.map +1 -0
  23. package/dist/components/metric-card.d.ts +14 -1
  24. package/dist/components/metric-card.js +97 -0
  25. package/dist/components/metric-card.js.map +1 -1
  26. package/dist/components/pill.d.ts +26 -0
  27. package/dist/components/pill.js +77 -0
  28. package/dist/components/pill.js.map +1 -0
  29. package/dist/components/quick-segment.d.ts +13 -0
  30. package/dist/components/quick-segment.js +96 -0
  31. package/dist/components/quick-segment.js.map +1 -0
  32. package/dist/index.d.ts +7 -1
  33. package/dist/index.js +5 -0
  34. package/dist/index.js.map +1 -1
  35. package/package.json +1 -3
  36. package/src/charts/__tests__/insights-charts.test.tsx +62 -0
  37. package/src/charts/empty-chart-state.tsx +44 -0
  38. package/src/charts/index.ts +1 -0
  39. package/src/charts/pipeline-overview.tsx +41 -1
  40. package/src/components/__tests__/insights-primitives.test.tsx +135 -0
  41. package/src/components/days-open-cell.tsx +50 -0
  42. package/src/components/detail-drawer.tsx +60 -0
  43. package/src/components/insights-filter-bar.tsx +13 -4
  44. package/src/components/linked-entity-cell.tsx +74 -0
  45. package/src/components/metric-card.tsx +98 -0
  46. package/src/components/pill.tsx +67 -0
  47. package/src/components/quick-segment.tsx +68 -0
  48. package/src/index.ts +5 -0
@@ -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
+ }
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ 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"
@@ -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,