@handled-ai/design-system 0.17.1 → 0.18.1

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 (101) hide show
  1. package/dist/charts/empty-chart-state.d.ts +11 -0
  2. package/dist/charts/empty-chart-state.js +70 -0
  3. package/dist/charts/empty-chart-state.js.map +1 -0
  4. package/dist/charts/index.d.ts +1 -0
  5. package/dist/charts/index.js +1 -0
  6. package/dist/charts/index.js.map +1 -1
  7. package/dist/charts/pipeline-overview.d.ts +2 -1
  8. package/dist/charts/pipeline-overview.js +29 -1
  9. package/dist/charts/pipeline-overview.js.map +1 -1
  10. package/dist/components/actor-byline.d.ts +3 -0
  11. package/dist/components/actor-byline.js +5 -0
  12. package/dist/components/actor-byline.js.map +1 -0
  13. package/dist/components/days-open-cell.d.ts +16 -0
  14. package/dist/components/days-open-cell.js +73 -0
  15. package/dist/components/days-open-cell.js.map +1 -0
  16. package/dist/components/detail-drawer.d.ts +16 -0
  17. package/dist/components/detail-drawer.js +45 -0
  18. package/dist/components/detail-drawer.js.map +1 -0
  19. package/dist/components/feedback-primitives.d.ts +66 -0
  20. package/dist/components/feedback-primitives.js +295 -0
  21. package/dist/components/feedback-primitives.js.map +1 -0
  22. package/dist/components/insights-filter-bar.d.ts +2 -1
  23. package/dist/components/insights-filter-bar.js +13 -5
  24. package/dist/components/insights-filter-bar.js.map +1 -1
  25. package/dist/components/linked-entity-cell.d.ts +14 -0
  26. package/dist/components/linked-entity-cell.js +96 -0
  27. package/dist/components/linked-entity-cell.js.map +1 -0
  28. package/dist/components/metric-card.d.ts +14 -1
  29. package/dist/components/metric-card.js +86 -0
  30. package/dist/components/metric-card.js.map +1 -1
  31. package/dist/components/performance-metrics-table.d.ts +2 -1
  32. package/dist/components/performance-metrics-table.js +78 -46
  33. package/dist/components/performance-metrics-table.js.map +1 -1
  34. package/dist/components/pill.d.ts +26 -0
  35. package/dist/components/pill.js +77 -0
  36. package/dist/components/pill.js.map +1 -0
  37. package/dist/components/quick-segment.d.ts +13 -0
  38. package/dist/components/quick-segment.js +96 -0
  39. package/dist/components/quick-segment.js.map +1 -0
  40. package/dist/components/score-why-chips.d.ts +8 -17
  41. package/dist/components/score-why-chips.js +266 -180
  42. package/dist/components/score-why-chips.js.map +1 -1
  43. package/dist/components/signal-priority-popover.d.ts +17 -0
  44. package/dist/components/signal-priority-popover.js +247 -0
  45. package/dist/components/signal-priority-popover.js.map +1 -0
  46. package/dist/components/user-display.d.ts +22 -0
  47. package/dist/components/user-display.js +138 -0
  48. package/dist/components/user-display.js.map +1 -0
  49. package/dist/components/user-pill.d.ts +3 -0
  50. package/dist/components/user-pill.js +5 -0
  51. package/dist/components/user-pill.js.map +1 -0
  52. package/dist/index.d.ts +13 -4
  53. package/dist/index.js +17 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/lib/user-display.d.ts +31 -0
  56. package/dist/lib/user-display.js +57 -0
  57. package/dist/lib/user-display.js.map +1 -0
  58. package/dist/prototype/index.d.ts +2 -1
  59. package/dist/prototype/prototype-accounts-view.d.ts +2 -1
  60. package/dist/prototype/prototype-admin-view.d.ts +2 -1
  61. package/dist/prototype/prototype-config.d.ts +15 -332
  62. package/dist/prototype/prototype-inbox-view.d.ts +2 -1
  63. package/dist/prototype/prototype-inbox-view.js +11 -12
  64. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  65. package/dist/prototype/prototype-insights-view.d.ts +2 -1
  66. package/dist/prototype/prototype-shell.d.ts +2 -1
  67. package/dist/signal-priority-popover-DQ_VuHac.d.ts +390 -0
  68. package/package.json +1 -1
  69. package/src/charts/__tests__/insights-charts.test.tsx +62 -0
  70. package/src/charts/empty-chart-state.tsx +44 -0
  71. package/src/charts/index.ts +1 -0
  72. package/src/charts/pipeline-overview.tsx +38 -1
  73. package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +99 -188
  74. package/src/components/__tests__/feedback-primitives.test.tsx +509 -0
  75. package/src/components/__tests__/insights-primitives.test.tsx +117 -0
  76. package/src/components/__tests__/performance-metrics-table.test.tsx +54 -0
  77. package/src/components/__tests__/score-why-chips.test.tsx +540 -0
  78. package/src/components/__tests__/signal-priority-popover.test.tsx +312 -0
  79. package/src/components/__tests__/user-display.test.tsx +75 -0
  80. package/src/components/actor-byline.tsx +1 -0
  81. package/src/components/days-open-cell.tsx +50 -0
  82. package/src/components/detail-drawer.tsx +60 -0
  83. package/src/components/feedback-primitives.tsx +424 -0
  84. package/src/components/insights-filter-bar.tsx +13 -4
  85. package/src/components/linked-entity-cell.tsx +74 -0
  86. package/src/components/metric-card.tsx +82 -0
  87. package/src/components/performance-metrics-table.tsx +99 -63
  88. package/src/components/pill.tsx +67 -0
  89. package/src/components/quick-segment.tsx +68 -0
  90. package/src/components/score-why-chips.tsx +413 -203
  91. package/src/components/signal-priority-popover.tsx +359 -0
  92. package/src/components/user-display.tsx +96 -0
  93. package/src/components/user-pill.tsx +1 -0
  94. package/src/index.ts +11 -0
  95. package/src/lib/__tests__/user-display.test.ts +85 -0
  96. package/src/lib/user-display.ts +88 -0
  97. package/src/prototype/__tests__/detail-view-score-why.test.tsx +33 -29
  98. package/src/prototype/__tests__/detail-view-title-slots.test.tsx +65 -0
  99. package/src/prototype/prototype-config.ts +28 -4
  100. package/src/prototype/prototype-inbox-view.tsx +8 -10
  101. package/src/prototype/__tests__/detail-view-title-subtext.test.tsx +0 -72
@@ -0,0 +1,295 @@
1
+ "use client"
2
+
3
+ "use client";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import * as React from "react";
6
+ import { ThumbsUp, ThumbsDown } from "lucide-react";
7
+ import { cn } from "../lib/utils.js";
8
+ const CHIP_SELECTED_CLASSES = {
9
+ negative: "bg-red-50 text-red-700 border-red-200",
10
+ positive: "bg-muted text-foreground border-border"
11
+ };
12
+ const CHIP_IDLE_CLASS = "bg-background text-muted-foreground border-border hover:bg-muted/50";
13
+ function FeedbackChipGroup({
14
+ chips,
15
+ selected,
16
+ onToggle,
17
+ flavor,
18
+ className
19
+ }) {
20
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-1.5", className), children: chips.map((chip) => {
21
+ const isSelected = selected.includes(chip);
22
+ return /* @__PURE__ */ jsx(
23
+ "button",
24
+ {
25
+ type: "button",
26
+ onClick: () => onToggle(chip),
27
+ className: cn(
28
+ "rounded-md px-2.5 py-1 text-[11px] font-medium border transition-colors",
29
+ isSelected ? CHIP_SELECTED_CLASSES[flavor] : CHIP_IDLE_CLASS
30
+ ),
31
+ children: chip
32
+ },
33
+ chip
34
+ );
35
+ }) });
36
+ }
37
+ function FeedbackInput({
38
+ placeholder,
39
+ value,
40
+ onChange,
41
+ onSubmit,
42
+ className
43
+ }) {
44
+ return /* @__PURE__ */ jsx(
45
+ "input",
46
+ {
47
+ type: "text",
48
+ value,
49
+ onChange: (e) => onChange(e.target.value),
50
+ onKeyDown: (e) => {
51
+ if (e.key === "Enter" && onSubmit) {
52
+ e.preventDefault();
53
+ onSubmit();
54
+ }
55
+ },
56
+ placeholder,
57
+ className: cn(
58
+ "w-full text-xs bg-background border border-border rounded-md px-2.5 py-2 text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring",
59
+ className
60
+ )
61
+ }
62
+ );
63
+ }
64
+ function FeedbackActions({
65
+ onSubmit,
66
+ onCancel,
67
+ submitDisabled = false,
68
+ submitLabel = "Submit",
69
+ cancelLabel = "Cancel",
70
+ hint,
71
+ className
72
+ }) {
73
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
74
+ /* @__PURE__ */ jsx(
75
+ "button",
76
+ {
77
+ type: "button",
78
+ onClick: onSubmit,
79
+ disabled: submitDisabled,
80
+ className: "bg-foreground text-background rounded-md px-3 py-1.5 text-xs font-semibold disabled:opacity-50",
81
+ children: submitLabel
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx(
85
+ "button",
86
+ {
87
+ type: "button",
88
+ onClick: onCancel,
89
+ className: "border border-border rounded-md px-3 py-1.5 text-xs font-medium",
90
+ children: cancelLabel
91
+ }
92
+ ),
93
+ hint && /* @__PURE__ */ jsx("span", { className: "ml-auto text-[11px] text-muted-foreground", children: hint })
94
+ ] });
95
+ }
96
+ const SENTIMENT_BUTTON_ACTIVE = {
97
+ negative: "text-red-600 bg-red-50 border-red-200",
98
+ positive: "text-foreground bg-muted border-border"
99
+ };
100
+ const SENTIMENT_BUTTON_IDLE = "text-muted-foreground hover:text-foreground";
101
+ function FeedbackFooter({
102
+ feedback,
103
+ onFeedbackChange,
104
+ onSubmit,
105
+ metaText,
106
+ positivePrompt = "Thanks! Anything to keep about this score?",
107
+ negativePrompt = "What's the issue?",
108
+ negativeChips = [],
109
+ positiveChips = [],
110
+ className
111
+ }) {
112
+ const [expanded, setExpanded] = React.useState(false);
113
+ const [selectedTier1, setSelectedTier1] = React.useState(null);
114
+ const [selectedTier2, setSelectedTier2] = React.useState(null);
115
+ const [additionalPills, setAdditionalPills] = React.useState([]);
116
+ const [detailText, setDetailText] = React.useState("");
117
+ const [activeTreeIndex, setActiveTreeIndex] = React.useState(
118
+ null
119
+ );
120
+ const resetState = React.useCallback(() => {
121
+ setExpanded(false);
122
+ setSelectedTier1(null);
123
+ setSelectedTier2(null);
124
+ setAdditionalPills([]);
125
+ setDetailText("");
126
+ setActiveTreeIndex(null);
127
+ }, []);
128
+ const handleSentimentClick = React.useCallback(
129
+ (sentiment) => {
130
+ onFeedbackChange(sentiment);
131
+ resetState();
132
+ setExpanded(true);
133
+ },
134
+ [onFeedbackChange, resetState]
135
+ );
136
+ const handleTier1Toggle = React.useCallback(
137
+ (chipLabel) => {
138
+ if (selectedTier1 === chipLabel) {
139
+ setSelectedTier1(null);
140
+ setSelectedTier2(null);
141
+ setActiveTreeIndex(null);
142
+ } else if (selectedTier1 === null) {
143
+ setSelectedTier1(chipLabel);
144
+ setSelectedTier2(null);
145
+ const idx = negativeChips.findIndex((c) => c.label === chipLabel);
146
+ if (idx !== -1 && negativeChips[idx].subChips) {
147
+ setActiveTreeIndex(idx);
148
+ } else {
149
+ setActiveTreeIndex(null);
150
+ }
151
+ } else {
152
+ setAdditionalPills(
153
+ (prev) => prev.includes(chipLabel) ? prev.filter((p) => p !== chipLabel) : [...prev, chipLabel]
154
+ );
155
+ }
156
+ },
157
+ [selectedTier1, negativeChips]
158
+ );
159
+ const handleTier2Toggle = React.useCallback((subChip) => {
160
+ setSelectedTier2((prev) => prev === subChip ? null : subChip);
161
+ }, []);
162
+ const handlePositiveChipToggle = React.useCallback(
163
+ (chip) => {
164
+ if (selectedTier1 === chip) {
165
+ setSelectedTier1(null);
166
+ } else if (selectedTier1 === null) {
167
+ setSelectedTier1(chip);
168
+ } else {
169
+ setAdditionalPills(
170
+ (prev) => prev.includes(chip) ? prev.filter((p) => p !== chip) : [...prev, chip]
171
+ );
172
+ }
173
+ },
174
+ [selectedTier1]
175
+ );
176
+ const handleSubmit = React.useCallback(() => {
177
+ if (!feedback) return;
178
+ onSubmit({
179
+ sentiment: feedback,
180
+ reasonTop: selectedTier1 != null ? selectedTier1 : void 0,
181
+ reasonSub: selectedTier2 != null ? selectedTier2 : void 0,
182
+ pills: additionalPills,
183
+ detail: detailText
184
+ });
185
+ resetState();
186
+ }, [
187
+ feedback,
188
+ selectedTier1,
189
+ selectedTier2,
190
+ additionalPills,
191
+ detailText,
192
+ onSubmit,
193
+ resetState
194
+ ]);
195
+ const handleCancel = React.useCallback(() => {
196
+ resetState();
197
+ onFeedbackChange(null);
198
+ }, [resetState, onFeedbackChange]);
199
+ const allSelectedChips = React.useMemo(() => {
200
+ const result = [];
201
+ if (selectedTier1) result.push(selectedTier1);
202
+ result.push(...additionalPills);
203
+ return result;
204
+ }, [selectedTier1, additionalPills]);
205
+ const activeTree = activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null;
206
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
207
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
208
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
209
+ /* @__PURE__ */ jsxs(
210
+ "button",
211
+ {
212
+ type: "button",
213
+ onClick: () => handleSentimentClick("positive"),
214
+ className: cn(
215
+ "flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors",
216
+ feedback === "positive" ? SENTIMENT_BUTTON_ACTIVE.positive : SENTIMENT_BUTTON_IDLE
217
+ ),
218
+ children: [
219
+ /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[11px] w-[11px]" }),
220
+ "Helpful"
221
+ ]
222
+ }
223
+ ),
224
+ /* @__PURE__ */ jsxs(
225
+ "button",
226
+ {
227
+ type: "button",
228
+ onClick: () => handleSentimentClick("negative"),
229
+ className: cn(
230
+ "flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors",
231
+ feedback === "negative" ? SENTIMENT_BUTTON_ACTIVE.negative : SENTIMENT_BUTTON_IDLE
232
+ ),
233
+ children: [
234
+ /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[11px] w-[11px]" }),
235
+ "Not helpful"
236
+ ]
237
+ }
238
+ )
239
+ ] }),
240
+ metaText && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: metaText })
241
+ ] }),
242
+ expanded && feedback && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
243
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: feedback === "negative" ? negativePrompt : positivePrompt }),
244
+ feedback === "negative" && negativeChips.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
245
+ /* @__PURE__ */ jsx(
246
+ FeedbackChipGroup,
247
+ {
248
+ chips: negativeChips.map((c) => c.label),
249
+ selected: allSelectedChips,
250
+ onToggle: handleTier1Toggle,
251
+ flavor: "negative"
252
+ }
253
+ ),
254
+ activeTree && activeTree.subChips && /* @__PURE__ */ jsxs("div", { className: "pl-3 space-y-1.5", children: [
255
+ activeTree.subPrompt && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: activeTree.subPrompt }),
256
+ /* @__PURE__ */ jsx(
257
+ FeedbackChipGroup,
258
+ {
259
+ chips: activeTree.subChips,
260
+ selected: selectedTier2 ? [selectedTier2] : [],
261
+ onToggle: handleTier2Toggle,
262
+ flavor: "negative"
263
+ }
264
+ )
265
+ ] })
266
+ ] }),
267
+ feedback === "positive" && positiveChips.length > 0 && /* @__PURE__ */ jsx(
268
+ FeedbackChipGroup,
269
+ {
270
+ chips: positiveChips,
271
+ selected: allSelectedChips,
272
+ onToggle: handlePositiveChipToggle,
273
+ flavor: "positive"
274
+ }
275
+ ),
276
+ /* @__PURE__ */ jsx(
277
+ FeedbackInput,
278
+ {
279
+ placeholder: "Add optional detail\u2026",
280
+ value: detailText,
281
+ onChange: setDetailText,
282
+ onSubmit: handleSubmit
283
+ }
284
+ ),
285
+ /* @__PURE__ */ jsx(FeedbackActions, { onSubmit: handleSubmit, onCancel: handleCancel })
286
+ ] })
287
+ ] });
288
+ }
289
+ export {
290
+ FeedbackActions,
291
+ FeedbackChipGroup,
292
+ FeedbackFooter,
293
+ FeedbackInput
294
+ };
295
+ //# sourceMappingURL=feedback-primitives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/feedback-primitives.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Structured feedback data shape.\n *\n * Preserves the tree structure for DB persistence:\n * reasonTop -> tier-1 chip label (maps to case_feedback.reason_top)\n * reasonSub -> tier-2 sub-chip label (maps to case_feedback.reason_sub)\n * pills -> any additional selected chips (maps to case_feedback.pills)\n * detail -> free-text input (maps to case_feedback.free_text)\n */\nexport interface FeedbackSubmitData {\n sentiment: \"positive\" | \"negative\"\n reasonTop?: string\n reasonSub?: string\n pills: string[]\n detail: string\n}\n\n/**\n * Defines a tier-1 chip that may have tier-2 sub-chips.\n */\nexport interface FeedbackChipTree {\n label: string\n subPrompt?: string\n subChips?: string[]\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackChipGroup\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackChipGroupProps {\n chips: string[]\n selected: string[]\n onToggle: (chip: string) => void\n flavor: \"positive\" | \"negative\"\n className?: string\n}\n\nconst CHIP_SELECTED_CLASSES: Record<\"positive\" | \"negative\", string> = {\n negative: \"bg-red-50 text-red-700 border-red-200\",\n positive: \"bg-muted text-foreground border-border\",\n}\n\nconst CHIP_IDLE_CLASS =\n \"bg-background text-muted-foreground border-border hover:bg-muted/50\"\n\nexport function FeedbackChipGroup({\n chips,\n selected,\n onToggle,\n flavor,\n className,\n}: FeedbackChipGroupProps) {\n return (\n <div className={cn(\"flex flex-wrap gap-1.5\", className)}>\n {chips.map((chip) => {\n const isSelected = selected.includes(chip)\n return (\n <button\n key={chip}\n type=\"button\"\n onClick={() => onToggle(chip)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-[11px] font-medium border transition-colors\",\n isSelected ? CHIP_SELECTED_CLASSES[flavor] : CHIP_IDLE_CLASS,\n )}\n >\n {chip}\n </button>\n )\n })}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackInput\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackInputProps {\n placeholder?: string\n value: string\n onChange: (value: string) => void\n onSubmit?: () => void\n className?: string\n}\n\nexport function FeedbackInput({\n placeholder,\n value,\n onChange,\n onSubmit,\n className,\n}: FeedbackInputProps) {\n return (\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && onSubmit) {\n e.preventDefault()\n onSubmit()\n }\n }}\n placeholder={placeholder}\n className={cn(\n \"w-full text-xs bg-background border border-border rounded-md px-2.5 py-2 text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring\",\n className,\n )}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackActions\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackActionsProps {\n onSubmit: () => void\n onCancel: () => void\n submitDisabled?: boolean\n submitLabel?: string\n cancelLabel?: string\n hint?: string\n className?: string\n}\n\nexport function FeedbackActions({\n onSubmit,\n onCancel,\n submitDisabled = false,\n submitLabel = \"Submit\",\n cancelLabel = \"Cancel\",\n hint,\n className,\n}: FeedbackActionsProps) {\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <button\n type=\"button\"\n onClick={onSubmit}\n disabled={submitDisabled}\n className=\"bg-foreground text-background rounded-md px-3 py-1.5 text-xs font-semibold disabled:opacity-50\"\n >\n {submitLabel}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"border border-border rounded-md px-3 py-1.5 text-xs font-medium\"\n >\n {cancelLabel}\n </button>\n {hint && (\n <span className=\"ml-auto text-[11px] text-muted-foreground\">\n {hint}\n </span>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackFooter\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackFooterProps {\n feedback: \"positive\" | \"negative\" | null\n onFeedbackChange: (value: \"positive\" | \"negative\" | null) => void\n onSubmit: (data: FeedbackSubmitData) => void\n metaText?: string\n positivePrompt?: string\n negativePrompt?: string\n negativeChips?: FeedbackChipTree[]\n positiveChips?: string[]\n className?: string\n}\n\nconst SENTIMENT_BUTTON_ACTIVE: Record<\"positive\" | \"negative\", string> = {\n negative: \"text-red-600 bg-red-50 border-red-200\",\n positive: \"text-foreground bg-muted border-border\",\n}\n\nconst SENTIMENT_BUTTON_IDLE =\n \"text-muted-foreground hover:text-foreground\"\n\nexport function FeedbackFooter({\n feedback,\n onFeedbackChange,\n onSubmit,\n metaText,\n positivePrompt = \"Thanks! Anything to keep about this score?\",\n negativePrompt = \"What's the issue?\",\n negativeChips = [],\n positiveChips = [],\n className,\n}: FeedbackFooterProps) {\n const [expanded, setExpanded] = React.useState(false)\n const [selectedTier1, setSelectedTier1] = React.useState<string | null>(null)\n const [selectedTier2, setSelectedTier2] = React.useState<string | null>(null)\n const [additionalPills, setAdditionalPills] = React.useState<string[]>([])\n const [detailText, setDetailText] = React.useState(\"\")\n const [activeTreeIndex, setActiveTreeIndex] = React.useState<number | null>(\n null,\n )\n\n // Reset state when feedback collapses\n const resetState = React.useCallback(() => {\n setExpanded(false)\n setSelectedTier1(null)\n setSelectedTier2(null)\n setAdditionalPills([])\n setDetailText(\"\")\n setActiveTreeIndex(null)\n }, [])\n\n const handleSentimentClick = React.useCallback(\n (sentiment: \"positive\" | \"negative\") => {\n onFeedbackChange(sentiment)\n // Reset chip state when switching sentiment, then expand\n resetState()\n setExpanded(true)\n },\n [onFeedbackChange, resetState],\n )\n\n const handleTier1Toggle = React.useCallback(\n (chipLabel: string) => {\n if (selectedTier1 === chipLabel) {\n // Deselect the tier-1 chip\n setSelectedTier1(null)\n setSelectedTier2(null)\n setActiveTreeIndex(null)\n } else if (selectedTier1 === null) {\n // First selection becomes the primary reasonTop\n setSelectedTier1(chipLabel)\n setSelectedTier2(null)\n // Find the chip's tree index to show sub-chips\n const idx = negativeChips.findIndex((c) => c.label === chipLabel)\n if (idx !== -1 && negativeChips[idx].subChips) {\n setActiveTreeIndex(idx)\n } else {\n setActiveTreeIndex(null)\n }\n } else {\n // Additional selections become pills\n setAdditionalPills((prev) =>\n prev.includes(chipLabel)\n ? prev.filter((p) => p !== chipLabel)\n : [...prev, chipLabel],\n )\n }\n },\n [selectedTier1, negativeChips],\n )\n\n const handleTier2Toggle = React.useCallback((subChip: string) => {\n setSelectedTier2((prev) => (prev === subChip ? null : subChip))\n }, [])\n\n const handlePositiveChipToggle = React.useCallback(\n (chip: string) => {\n if (selectedTier1 === chip) {\n setSelectedTier1(null)\n } else if (selectedTier1 === null) {\n setSelectedTier1(chip)\n } else {\n setAdditionalPills((prev) =>\n prev.includes(chip)\n ? prev.filter((p) => p !== chip)\n : [...prev, chip],\n )\n }\n },\n [selectedTier1],\n )\n\n const handleSubmit = React.useCallback(() => {\n if (!feedback) return\n onSubmit({\n sentiment: feedback,\n reasonTop: selectedTier1 ?? undefined,\n reasonSub: selectedTier2 ?? undefined,\n pills: additionalPills,\n detail: detailText,\n })\n resetState()\n }, [\n feedback,\n selectedTier1,\n selectedTier2,\n additionalPills,\n detailText,\n onSubmit,\n resetState,\n ])\n\n const handleCancel = React.useCallback(() => {\n resetState()\n onFeedbackChange(null)\n }, [resetState, onFeedbackChange])\n\n // Determine which chips are selected (combining tier1 + additionalPills)\n const allSelectedChips = React.useMemo(() => {\n const result: string[] = []\n if (selectedTier1) result.push(selectedTier1)\n result.push(...additionalPills)\n return result\n }, [selectedTier1, additionalPills])\n\n // Active tier-1 chip tree (for showing sub-chips)\n const activeTree =\n activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {/* Sentiment buttons + meta text bar */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => handleSentimentClick(\"positive\")}\n className={cn(\n \"flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors\",\n feedback === \"positive\"\n ? SENTIMENT_BUTTON_ACTIVE.positive\n : SENTIMENT_BUTTON_IDLE,\n )}\n >\n <ThumbsUp className=\"h-[11px] w-[11px]\" />\n Helpful\n </button>\n <button\n type=\"button\"\n onClick={() => handleSentimentClick(\"negative\")}\n className={cn(\n \"flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors\",\n feedback === \"negative\"\n ? SENTIMENT_BUTTON_ACTIVE.negative\n : SENTIMENT_BUTTON_IDLE,\n )}\n >\n <ThumbsDown className=\"h-[11px] w-[11px]\" />\n Not helpful\n </button>\n </div>\n {metaText && (\n <span className=\"text-[11px] text-muted-foreground\">{metaText}</span>\n )}\n </div>\n\n {/* Expanded feedback area */}\n {expanded && feedback && (\n <div className=\"space-y-3\">\n {/* Prompt text */}\n <p className=\"text-xs text-muted-foreground\">\n {feedback === \"negative\" ? negativePrompt : positivePrompt}\n </p>\n\n {/* Chip area */}\n {feedback === \"negative\" && negativeChips.length > 0 && (\n <div className=\"space-y-2\">\n {/* Tier-1 chips */}\n <FeedbackChipGroup\n chips={negativeChips.map((c) => c.label)}\n selected={allSelectedChips}\n onToggle={handleTier1Toggle}\n flavor=\"negative\"\n />\n\n {/* Tier-2 sub-chips (shown when a tier-1 with sub-chips is active) */}\n {activeTree && activeTree.subChips && (\n <div className=\"pl-3 space-y-1.5\">\n {activeTree.subPrompt && (\n <p className=\"text-[11px] text-muted-foreground\">\n {activeTree.subPrompt}\n </p>\n )}\n <FeedbackChipGroup\n chips={activeTree.subChips}\n selected={selectedTier2 ? [selectedTier2] : []}\n onToggle={handleTier2Toggle}\n flavor=\"negative\"\n />\n </div>\n )}\n </div>\n )}\n\n {feedback === \"positive\" && positiveChips.length > 0 && (\n <FeedbackChipGroup\n chips={positiveChips}\n selected={allSelectedChips}\n onToggle={handlePositiveChipToggle}\n flavor=\"positive\"\n />\n )}\n\n {/* Detail text input */}\n <FeedbackInput\n placeholder=\"Add optional detail…\"\n value={detailText}\n onChange={setDetailText}\n onSubmit={handleSubmit}\n />\n\n {/* Action buttons */}\n <FeedbackActions onSubmit={handleSubmit} onCancel={handleCancel} />\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
@@ -11,11 +11,12 @@ interface FilterDefinition {
11
11
  }
12
12
  interface InsightsFilterBarProps {
13
13
  filters: FilterDefinition[];
14
+ variant?: "default" | "compact";
14
15
  values: Record<string, string>;
15
16
  onChange: (filterId: string, value: string) => void;
16
17
  onClearAll?: () => void;
17
18
  className?: string;
18
19
  }
19
- declare function InsightsFilterBar({ filters, values, onChange, onClearAll, className, }: InsightsFilterBarProps): React.JSX.Element;
20
+ declare function InsightsFilterBar({ filters, values, onChange, onClearAll, className, variant, }: InsightsFilterBarProps): React.JSX.Element;
20
21
 
21
22
  export { type FilterDefinition, InsightsFilterBar, type InsightsFilterBarProps };
@@ -24,7 +24,8 @@ function InsightsFilterBar({
24
24
  values,
25
25
  onChange,
26
26
  onClearAll,
27
- className
27
+ className,
28
+ variant = "default"
28
29
  }) {
29
30
  const showClearAll = onClearAll && hasNonDefaultValue(filters, values);
30
31
  return /* @__PURE__ */ jsxs(
@@ -32,11 +33,12 @@ function InsightsFilterBar({
32
33
  {
33
34
  "data-slot": "insights-filter-bar",
34
35
  className: cn(
35
- "flex flex-wrap items-center gap-3 rounded-md border border-border bg-card p-4 shadow-sm",
36
+ "flex flex-wrap items-center rounded-md border border-border bg-card shadow-sm",
37
+ variant === "compact" ? "gap-2 p-2" : "gap-3 p-4",
36
38
  className
37
39
  ),
38
40
  children: [
39
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
41
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", variant === "compact" && "sr-only"), children: [
40
42
  /* @__PURE__ */ jsx(FilterIcon, { className: "h-4 w-4 text-muted-foreground" }),
41
43
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: "Filters:" })
42
44
  ] }),
@@ -51,7 +53,10 @@ function InsightsFilterBar({
51
53
  {
52
54
  variant: "outline",
53
55
  size: "sm",
54
- className: "h-8 gap-1.5 text-xs font-normal shadow-none",
56
+ className: cn(
57
+ "gap-1.5 text-xs font-normal shadow-none",
58
+ variant === "compact" ? "h-7 px-2" : "h-8"
59
+ ),
55
60
  children: [
56
61
  IconComp ? /* @__PURE__ */ jsx(IconComp, { className: "h-3.5 w-3.5 text-muted-foreground" }) : null,
57
62
  filter.label,
@@ -84,7 +89,10 @@ function InsightsFilterBar({
84
89
  {
85
90
  variant: "ghost",
86
91
  size: "sm",
87
- className: "h-8 text-xs text-destructive hover:text-destructive",
92
+ className: cn(
93
+ "text-xs text-destructive hover:text-destructive",
94
+ variant === "compact" ? "h-7 px-2" : "h-8"
95
+ ),
88
96
  onClick: onClearAll,
89
97
  children: "Clear All"
90
98
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/insights-filter-bar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarIcon, ChevronDownIcon, FilterIcon } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { Button } from \"./button\"\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\"\n\nexport interface FilterDefinition {\n id: string\n label: string\n options: string[]\n defaultValue?: string\n icon?: \"calendar\" | React.ComponentType<{ className?: string }>\n}\n\nexport interface InsightsFilterBarProps {\n filters: FilterDefinition[]\n values: Record<string, string>\n onChange: (filterId: string, value: string) => void\n onClearAll?: () => void\n className?: string\n}\n\nfunction hasNonDefaultValue(\n filters: FilterDefinition[],\n values: Record<string, string>\n) {\n return filters.some((filter) => {\n const defaultVal = filter.defaultValue ?? filter.options[0] ?? \"All\"\n return values[filter.id] !== undefined && values[filter.id] !== defaultVal\n })\n}\n\nfunction InsightsFilterBar({\n filters,\n values,\n onChange,\n onClearAll,\n className,\n}: InsightsFilterBarProps) {\n const showClearAll = onClearAll && hasNonDefaultValue(filters, values)\n\n return (\n <div\n data-slot=\"insights-filter-bar\"\n className={cn(\n \"flex flex-wrap items-center gap-3 rounded-md border border-border bg-card p-4 shadow-sm\",\n className\n )}\n >\n <div className=\"flex items-center gap-2\">\n <FilterIcon className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-sm font-medium text-muted-foreground\">\n Filters:\n </span>\n </div>\n\n {filters.map((filter) => {\n const current = values[filter.id] ?? filter.defaultValue ?? \"All\"\n const isCheckbox = filter.options.length > 0\n\n const IconComp =\n filter.icon === \"calendar\"\n ? CalendarIcon\n : typeof filter.icon === \"function\"\n ? filter.icon\n : null\n\n return (\n <DropdownMenu key={filter.id}>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 gap-1.5 text-xs font-normal shadow-none\"\n >\n {IconComp ? (\n <IconComp className=\"h-3.5 w-3.5 text-muted-foreground\" />\n ) : null}\n {filter.label}: {current}\n <ChevronDownIcon className=\"h-3.5 w-3.5 opacity-50\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {isCheckbox\n ? filter.options.map((option) => (\n <DropdownMenuCheckboxItem\n key={option}\n checked={current === option}\n onCheckedChange={() => onChange(filter.id, option)}\n >\n {option}\n </DropdownMenuCheckboxItem>\n ))\n : filter.options.map((option) => (\n <DropdownMenuItem\n key={option}\n onSelect={() => onChange(filter.id, option)}\n >\n {option}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n })}\n\n {showClearAll ? (\n <div className=\"ml-auto\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 text-xs text-destructive hover:text-destructive\"\n onClick={onClearAll}\n >\n Clear All\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n\nexport { InsightsFilterBar }\n"],"mappings":";AA0DM,SACE,KADF;AAvDN,SAAS,cAAc,iBAAiB,kBAAkB;AAE1D,SAAS,UAAU;AACnB,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAkBP,SAAS,mBACP,SACA,QACA;AACA,SAAO,QAAQ,KAAK,CAAC,WAAW;AAnClC;AAoCI,UAAM,cAAa,kBAAO,iBAAP,YAAuB,OAAO,QAAQ,CAAC,MAAvC,YAA4C;AAC/D,WAAO,OAAO,OAAO,EAAE,MAAM,UAAa,OAAO,OAAO,EAAE,MAAM;AAAA,EAClE,CAAC;AACH;AAEA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,eAAe,cAAc,mBAAmB,SAAS,MAAM;AAErE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,cAAW,WAAU,iCAAgC;AAAA,UACtD,oBAAC,UAAK,WAAU,6CAA4C,sBAE5D;AAAA,WACF;AAAA,QAEC,QAAQ,IAAI,CAAC,WAAW;AAjE/B;AAkEQ,gBAAM,WAAU,kBAAO,OAAO,EAAE,MAAhB,YAAqB,OAAO,iBAA5B,YAA4C;AAC5D,gBAAM,aAAa,OAAO,QAAQ,SAAS;AAE3C,gBAAM,WACJ,OAAO,SAAS,aACZ,eACA,OAAO,OAAO,SAAS,aACrB,OAAO,OACP;AAER,iBACE,qBAAC,gBACC;AAAA,gCAAC,uBAAoB,SAAO,MAC1B;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,WAAU;AAAA,gBAET;AAAA,6BACC,oBAAC,YAAS,WAAU,qCAAoC,IACtD;AAAA,kBACH,OAAO;AAAA,kBAAM;AAAA,kBAAG;AAAA,kBACjB,oBAAC,mBAAgB,WAAU,0BAAyB;AAAA;AAAA;AAAA,YACtD,GACF;AAAA,YACA,oBAAC,uBAAoB,OAAM,SACxB,uBACG,OAAO,QAAQ,IAAI,CAAC,WAClB;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS,YAAY;AAAA,gBACrB,iBAAiB,MAAM,SAAS,OAAO,IAAI,MAAM;AAAA,gBAEhD;AAAA;AAAA,cAJI;AAAA,YAKP,CACD,IACD,OAAO,QAAQ,IAAI,CAAC,WAClB;AAAA,cAAC;AAAA;AAAA,gBAEC,UAAU,MAAM,SAAS,OAAO,IAAI,MAAM;AAAA,gBAEzC;AAAA;AAAA,cAHI;AAAA,YAIP,CACD,GACP;AAAA,eAjCiB,OAAO,EAkC1B;AAAA,QAEJ,CAAC;AAAA,QAEA,eACC,oBAAC,SAAI,WAAU,WACb;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS;AAAA,YACV;AAAA;AAAA,QAED,GACF,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/insights-filter-bar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarIcon, ChevronDownIcon, FilterIcon } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { Button } from \"./button\"\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\"\n\nexport interface FilterDefinition {\n id: string\n label: string\n options: string[]\n defaultValue?: string\n icon?: \"calendar\" | React.ComponentType<{ className?: string }>\n}\n\nexport interface InsightsFilterBarProps {\n filters: FilterDefinition[]\n variant?: \"default\" | \"compact\"\n values: Record<string, string>\n onChange: (filterId: string, value: string) => void\n onClearAll?: () => void\n className?: string\n}\n\nfunction hasNonDefaultValue(\n filters: FilterDefinition[],\n values: Record<string, string>\n) {\n return filters.some((filter) => {\n const defaultVal = filter.defaultValue ?? filter.options[0] ?? \"All\"\n return values[filter.id] !== undefined && values[filter.id] !== defaultVal\n })\n}\n\nfunction InsightsFilterBar({\n filters,\n values,\n onChange,\n onClearAll,\n className,\n variant = \"default\",\n}: InsightsFilterBarProps) {\n const showClearAll = onClearAll && hasNonDefaultValue(filters, values)\n\n return (\n <div\n data-slot=\"insights-filter-bar\"\n className={cn(\n \"flex flex-wrap items-center rounded-md border border-border bg-card shadow-sm\",\n variant === \"compact\" ? \"gap-2 p-2\" : \"gap-3 p-4\",\n className\n )}\n >\n <div className={cn(\"flex items-center gap-2\", variant === \"compact\" && \"sr-only\")}>\n <FilterIcon className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-sm font-medium text-muted-foreground\">\n Filters:\n </span>\n </div>\n\n {filters.map((filter) => {\n const current = values[filter.id] ?? filter.defaultValue ?? \"All\"\n const isCheckbox = filter.options.length > 0\n\n const IconComp =\n filter.icon === \"calendar\"\n ? CalendarIcon\n : typeof filter.icon === \"function\"\n ? filter.icon\n : null\n\n return (\n <DropdownMenu key={filter.id}>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className={cn(\n \"gap-1.5 text-xs font-normal shadow-none\",\n variant === \"compact\" ? \"h-7 px-2\" : \"h-8\"\n )}\n >\n {IconComp ? (\n <IconComp className=\"h-3.5 w-3.5 text-muted-foreground\" />\n ) : null}\n {filter.label}: {current}\n <ChevronDownIcon className=\"h-3.5 w-3.5 opacity-50\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {isCheckbox\n ? filter.options.map((option) => (\n <DropdownMenuCheckboxItem\n key={option}\n checked={current === option}\n onCheckedChange={() => onChange(filter.id, option)}\n >\n {option}\n </DropdownMenuCheckboxItem>\n ))\n : filter.options.map((option) => (\n <DropdownMenuItem\n key={option}\n onSelect={() => onChange(filter.id, option)}\n >\n {option}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n })}\n\n {showClearAll ? (\n <div className=\"ml-auto\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className={cn(\n \"text-xs text-destructive hover:text-destructive\",\n variant === \"compact\" ? \"h-7 px-2\" : \"h-8\"\n )}\n onClick={onClearAll}\n >\n Clear All\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n\nexport { InsightsFilterBar }\n"],"mappings":";AA6DM,SACE,KADF;AA1DN,SAAS,cAAc,iBAAiB,kBAAkB;AAE1D,SAAS,UAAU;AACnB,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAmBP,SAAS,mBACP,SACA,QACA;AACA,SAAO,QAAQ,KAAK,CAAC,WAAW;AApClC;AAqCI,UAAM,cAAa,kBAAO,iBAAP,YAAuB,OAAO,QAAQ,CAAC,MAAvC,YAA4C;AAC/D,WAAO,OAAO,OAAO,EAAE,MAAM,UAAa,OAAO,OAAO,EAAE,MAAM;AAAA,EAClE,CAAC;AACH;AAEA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACZ,GAA2B;AACzB,QAAM,eAAe,cAAc,mBAAmB,SAAS,MAAM;AAErE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA,YAAY,YAAY,cAAc;AAAA,QACtC;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,SAAI,WAAW,GAAG,2BAA2B,YAAY,aAAa,SAAS,GAC9E;AAAA,8BAAC,cAAW,WAAU,iCAAgC;AAAA,UACtD,oBAAC,UAAK,WAAU,6CAA4C,sBAE5D;AAAA,WACF;AAAA,QAEC,QAAQ,IAAI,CAAC,WAAW;AApE/B;AAqEQ,gBAAM,WAAU,kBAAO,OAAO,EAAE,MAAhB,YAAqB,OAAO,iBAA5B,YAA4C;AAC5D,gBAAM,aAAa,OAAO,QAAQ,SAAS;AAE3C,gBAAM,WACJ,OAAO,SAAS,aACZ,eACA,OAAO,OAAO,SAAS,aACrB,OAAO,OACP;AAER,iBACE,qBAAC,gBACC;AAAA,gCAAC,uBAAoB,SAAO,MAC1B;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,WAAW;AAAA,kBACT;AAAA,kBACA,YAAY,YAAY,aAAa;AAAA,gBACvC;AAAA,gBAEC;AAAA,6BACC,oBAAC,YAAS,WAAU,qCAAoC,IACtD;AAAA,kBACH,OAAO;AAAA,kBAAM;AAAA,kBAAG;AAAA,kBACjB,oBAAC,mBAAgB,WAAU,0BAAyB;AAAA;AAAA;AAAA,YACtD,GACF;AAAA,YACA,oBAAC,uBAAoB,OAAM,SACxB,uBACG,OAAO,QAAQ,IAAI,CAAC,WAClB;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS,YAAY;AAAA,gBACrB,iBAAiB,MAAM,SAAS,OAAO,IAAI,MAAM;AAAA,gBAEhD;AAAA;AAAA,cAJI;AAAA,YAKP,CACD,IACD,OAAO,QAAQ,IAAI,CAAC,WAClB;AAAA,cAAC;AAAA;AAAA,gBAEC,UAAU,MAAM,SAAS,OAAO,IAAI,MAAM;AAAA,gBAEzC;AAAA;AAAA,cAHI;AAAA,YAIP,CACD,GACP;AAAA,eApCiB,OAAO,EAqC1B;AAAA,QAEJ,CAAC;AAAA,QAEA,eACC,oBAAC,SAAI,WAAU,WACb;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAW;AAAA,cACT;AAAA,cACA,YAAY,YAAY,aAAa;AAAA,YACvC;AAAA,YACA,SAAS;AAAA,YACV;AAAA;AAAA,QAED,GACF,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+
3
+ interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ name: React.ReactNode;
5
+ href?: string;
6
+ subtitle?: React.ReactNode;
7
+ meta?: React.ReactNode;
8
+ icon?: React.ReactNode;
9
+ external?: boolean;
10
+ onNavigate?: () => void;
11
+ }
12
+ declare function LinkedEntityCell({ name, href, subtitle, meta, icon, external, onNavigate, className, ...props }: LinkedEntityCellProps): React.JSX.Element;
13
+
14
+ export { LinkedEntityCell, type LinkedEntityCellProps };
@@ -0,0 +1,96 @@
1
+ "use client"
2
+
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __defProps = Object.defineProperties;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __objRest = (source, exclude) => {
24
+ var target = {};
25
+ for (var prop in source)
26
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
27
+ target[prop] = source[prop];
28
+ if (source != null && __getOwnPropSymbols)
29
+ for (var prop of __getOwnPropSymbols(source)) {
30
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
31
+ target[prop] = source[prop];
32
+ }
33
+ return target;
34
+ };
35
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
36
+ import { ExternalLink } from "lucide-react";
37
+ import { cn } from "../lib/utils.js";
38
+ function LinkedEntityCell(_a) {
39
+ var _b = _a, {
40
+ name,
41
+ href,
42
+ subtitle,
43
+ meta,
44
+ icon,
45
+ external = false,
46
+ onNavigate,
47
+ className
48
+ } = _b, props = __objRest(_b, [
49
+ "name",
50
+ "href",
51
+ "subtitle",
52
+ "meta",
53
+ "icon",
54
+ "external",
55
+ "onNavigate",
56
+ "className"
57
+ ]);
58
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
59
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: name }),
60
+ external ? /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 shrink-0 opacity-60", "aria-hidden": "true" }) : null
61
+ ] });
62
+ return /* @__PURE__ */ jsxs(
63
+ "div",
64
+ __spreadProps(__spreadValues({
65
+ "data-slot": "linked-entity-cell",
66
+ className: cn("flex min-w-0 items-center gap-2", className)
67
+ }, props), {
68
+ children: [
69
+ icon ? /* @__PURE__ */ jsx("span", { "data-slot": "linked-entity-cell-icon", className: "shrink-0 text-muted-foreground", children: icon }) : null,
70
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
71
+ href ? /* @__PURE__ */ jsx(
72
+ "a",
73
+ {
74
+ "data-slot": "linked-entity-cell-link",
75
+ href,
76
+ target: external ? "_blank" : void 0,
77
+ rel: external ? "noreferrer" : void 0,
78
+ onClick: onNavigate,
79
+ className: "inline-flex max-w-full items-center gap-1 truncate font-medium text-foreground underline-offset-4 hover:text-primary hover:underline",
80
+ children: content
81
+ }
82
+ ) : /* @__PURE__ */ jsx("span", { "data-slot": "linked-entity-cell-name", className: "block truncate font-medium text-foreground", children: name }),
83
+ subtitle || meta ? /* @__PURE__ */ jsxs("div", { "data-slot": "linked-entity-cell-meta", className: "mt-0.5 truncate text-xs text-muted-foreground", children: [
84
+ subtitle,
85
+ subtitle && meta ? /* @__PURE__ */ jsx("span", { className: "px-1", children: "\xB7" }) : null,
86
+ meta
87
+ ] }) : null
88
+ ] })
89
+ ]
90
+ })
91
+ );
92
+ }
93
+ export {
94
+ LinkedEntityCell
95
+ };
96
+ //# sourceMappingURL=linked-entity-cell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/linked-entity-cell.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ExternalLink } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\n\nexport interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivElement> {\n name: React.ReactNode\n href?: string\n subtitle?: React.ReactNode\n meta?: React.ReactNode\n icon?: React.ReactNode\n external?: boolean\n onNavigate?: () => void\n}\n\nexport function LinkedEntityCell({\n name,\n href,\n subtitle,\n meta,\n icon,\n external = false,\n onNavigate,\n className,\n ...props\n}: LinkedEntityCellProps) {\n const content = (\n <>\n <span className=\"truncate\">{name}</span>\n {external ? <ExternalLink className=\"h-3 w-3 shrink-0 opacity-60\" aria-hidden=\"true\" /> : null}\n </>\n )\n\n return (\n <div\n data-slot=\"linked-entity-cell\"\n className={cn(\"flex min-w-0 items-center gap-2\", className)}\n {...props}\n >\n {icon ? (\n <span data-slot=\"linked-entity-cell-icon\" className=\"shrink-0 text-muted-foreground\">\n {icon}\n </span>\n ) : null}\n <div className=\"min-w-0 flex-1\">\n {href ? (\n <a\n data-slot=\"linked-entity-cell-link\"\n href={href}\n target={external ? \"_blank\" : undefined}\n rel={external ? \"noreferrer\" : undefined}\n onClick={onNavigate}\n className=\"inline-flex max-w-full items-center gap-1 truncate font-medium text-foreground underline-offset-4 hover:text-primary hover:underline\"\n >\n {content}\n </a>\n ) : (\n <span data-slot=\"linked-entity-cell-name\" className=\"block truncate font-medium text-foreground\">\n {name}\n </span>\n )}\n {subtitle || meta ? (\n <div data-slot=\"linked-entity-cell-meta\" className=\"mt-0.5 truncate text-xs text-muted-foreground\">\n {subtitle}\n {subtitle && meta ? <span className=\"px-1\">·</span> : null}\n {meta}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BI,mBACE,KADF;AA1BJ,SAAS,oBAAoB;AAE7B,SAAS,UAAU;AAYZ,SAAS,iBAAiB,IAUP;AAVO,eAC/B;AAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,EAzBF,IAiBiC,IAS5B,kBAT4B,IAS5B;AAAA,IARH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAGA,QAAM,UACJ,iCACE;AAAA,wBAAC,UAAK,WAAU,YAAY,gBAAK;AAAA,IAChC,WAAW,oBAAC,gBAAa,WAAU,+BAA8B,eAAY,QAAO,IAAK;AAAA,KAC5F;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,mCAAmC,SAAS;AAAA,OACtD,QAHL;AAAA,MAKE;AAAA,eACC,oBAAC,UAAK,aAAU,2BAA0B,WAAU,kCACjD,gBACH,IACE;AAAA,QACJ,qBAAC,SAAI,WAAU,kBACZ;AAAA,iBACC;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV;AAAA,cACA,QAAQ,WAAW,WAAW;AAAA,cAC9B,KAAK,WAAW,eAAe;AAAA,cAC/B,SAAS;AAAA,cACT,WAAU;AAAA,cAET;AAAA;AAAA,UACH,IAEA,oBAAC,UAAK,aAAU,2BAA0B,WAAU,8CACjD,gBACH;AAAA,UAED,YAAY,OACX,qBAAC,SAAI,aAAU,2BAA0B,WAAU,iDAChD;AAAA;AAAA,YACA,YAAY,OAAO,oBAAC,UAAK,WAAU,QAAO,kBAAC,IAAU;AAAA,YACrD;AAAA,aACH,IACE;AAAA,WACN;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -20,6 +20,19 @@ interface MetricCardProps {
20
20
  showExternalLink?: boolean;
21
21
  showInfo?: boolean;
22
22
  }
23
+ interface KpiStripItem {
24
+ id?: string;
25
+ label: React.ReactNode;
26
+ value: React.ReactNode;
27
+ unit?: React.ReactNode;
28
+ subtitle?: React.ReactNode;
29
+ change?: MetricCardProps["change"];
30
+ }
31
+ interface KpiStripProps extends React.HTMLAttributes<HTMLDivElement> {
32
+ items: KpiStripItem[];
33
+ columns?: 2 | 3 | 4;
34
+ }
35
+ declare function KpiStrip({ items, columns, className, ...props }: KpiStripProps): React.JSX.Element;
23
36
  declare function MetricCard({ title, value, unit, subtitle, change, footerText, dataPoints, showExternalLink, showInfo, }: MetricCardProps): React.JSX.Element;
24
37
 
25
- export { MetricCard, type MetricCardProps, type MetricDataPoint };
38
+ export { KpiStrip, type KpiStripItem, type KpiStripProps, MetricCard, type MetricCardProps, type MetricDataPoint };