@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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use client";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { ThumbsUp, ThumbsDown
|
|
6
|
+
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
|
7
7
|
import { cn } from "../lib/utils.js";
|
|
8
8
|
const CHIP_SELECTED_CLASSES = {
|
|
9
9
|
negative: "bg-red-50 text-red-700 border-red-200",
|
|
@@ -107,10 +107,7 @@ function FeedbackFooter({
|
|
|
107
107
|
negativePrompt = "What's the issue?",
|
|
108
108
|
negativeChips = [],
|
|
109
109
|
positiveChips = [],
|
|
110
|
-
className
|
|
111
|
-
initialFeedback,
|
|
112
|
-
submittedLabel = "Saved",
|
|
113
|
-
feedbackKey
|
|
110
|
+
className
|
|
114
111
|
}) {
|
|
115
112
|
const [expanded, setExpanded] = React.useState(false);
|
|
116
113
|
const [selectedTier1, setSelectedTier1] = React.useState(null);
|
|
@@ -120,35 +117,6 @@ function FeedbackFooter({
|
|
|
120
117
|
const [activeTreeIndex, setActiveTreeIndex] = React.useState(
|
|
121
118
|
null
|
|
122
119
|
);
|
|
123
|
-
const [submitted, setSubmitted] = React.useState(false);
|
|
124
|
-
const [persisted, setPersisted] = React.useState(
|
|
125
|
-
initialFeedback != null ? initialFeedback : null
|
|
126
|
-
);
|
|
127
|
-
const isEditingRef = React.useRef(false);
|
|
128
|
-
const lastKeyRef = React.useRef(feedbackKey);
|
|
129
|
-
const setIsEditing = React.useCallback((value) => {
|
|
130
|
-
isEditingRef.current = value;
|
|
131
|
-
}, []);
|
|
132
|
-
React.useEffect(() => {
|
|
133
|
-
const keyChanged = feedbackKey !== lastKeyRef.current;
|
|
134
|
-
lastKeyRef.current = feedbackKey;
|
|
135
|
-
if (keyChanged) {
|
|
136
|
-
setPersisted(initialFeedback != null ? initialFeedback : null);
|
|
137
|
-
setSubmitted(false);
|
|
138
|
-
setExpanded(false);
|
|
139
|
-
isEditingRef.current = false;
|
|
140
|
-
if (initialFeedback) {
|
|
141
|
-
onFeedbackChange(initialFeedback.sentiment);
|
|
142
|
-
} else {
|
|
143
|
-
onFeedbackChange(null);
|
|
144
|
-
}
|
|
145
|
-
} else if (!isEditingRef.current) {
|
|
146
|
-
setPersisted(initialFeedback != null ? initialFeedback : null);
|
|
147
|
-
if (initialFeedback) {
|
|
148
|
-
onFeedbackChange(initialFeedback.sentiment);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}, [initialFeedback, feedbackKey, onFeedbackChange]);
|
|
152
120
|
const resetState = React.useCallback(() => {
|
|
153
121
|
setExpanded(false);
|
|
154
122
|
setSelectedTier1(null);
|
|
@@ -156,31 +124,15 @@ function FeedbackFooter({
|
|
|
156
124
|
setAdditionalPills([]);
|
|
157
125
|
setDetailText("");
|
|
158
126
|
setActiveTreeIndex(null);
|
|
159
|
-
|
|
160
|
-
}, [setIsEditing]);
|
|
127
|
+
}, []);
|
|
161
128
|
const handleSentimentClick = React.useCallback(
|
|
162
129
|
(sentiment) => {
|
|
163
130
|
onFeedbackChange(sentiment);
|
|
164
131
|
resetState();
|
|
165
132
|
setExpanded(true);
|
|
166
|
-
setSubmitted(false);
|
|
167
|
-
setPersisted(null);
|
|
168
|
-
setIsEditing(true);
|
|
169
133
|
},
|
|
170
|
-
[onFeedbackChange, resetState
|
|
134
|
+
[onFeedbackChange, resetState]
|
|
171
135
|
);
|
|
172
|
-
const handlePersistedClick = React.useCallback(() => {
|
|
173
|
-
var _a, _b, _c, _d;
|
|
174
|
-
if (!persisted) return;
|
|
175
|
-
onFeedbackChange(persisted.sentiment);
|
|
176
|
-
setSelectedTier1((_a = persisted.reasonTop) != null ? _a : null);
|
|
177
|
-
setSelectedTier2((_b = persisted.reasonSub) != null ? _b : null);
|
|
178
|
-
setAdditionalPills((_c = persisted.pills) != null ? _c : []);
|
|
179
|
-
setDetailText((_d = persisted.detail) != null ? _d : "");
|
|
180
|
-
setExpanded(true);
|
|
181
|
-
setSubmitted(false);
|
|
182
|
-
setIsEditing(true);
|
|
183
|
-
}, [persisted, onFeedbackChange, setIsEditing]);
|
|
184
136
|
const handleTier1Toggle = React.useCallback(
|
|
185
137
|
(chipLabel) => {
|
|
186
138
|
if (selectedTier1 === chipLabel) {
|
|
@@ -230,7 +182,6 @@ function FeedbackFooter({
|
|
|
230
182
|
pills: additionalPills,
|
|
231
183
|
detail: detailText
|
|
232
184
|
});
|
|
233
|
-
setSubmitted(true);
|
|
234
185
|
resetState();
|
|
235
186
|
}, [
|
|
236
187
|
feedback,
|
|
@@ -252,30 +203,9 @@ function FeedbackFooter({
|
|
|
252
203
|
return result;
|
|
253
204
|
}, [selectedTier1, additionalPills]);
|
|
254
205
|
const activeTree = activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null;
|
|
255
|
-
const showPersistedIndicator = persisted && !expanded && !submitted;
|
|
256
206
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
257
207
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
258
|
-
|
|
259
|
-
/* Persisted feedback indicator — clickable to reopen editor */
|
|
260
|
-
/* @__PURE__ */ jsxs(
|
|
261
|
-
"button",
|
|
262
|
-
{
|
|
263
|
-
type: "button",
|
|
264
|
-
onClick: handlePersistedClick,
|
|
265
|
-
className: "group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors",
|
|
266
|
-
"data-testid": "persisted-feedback-indicator",
|
|
267
|
-
children: [
|
|
268
|
-
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
269
|
-
persisted.ownershipLabel,
|
|
270
|
-
":"
|
|
271
|
-
] }),
|
|
272
|
-
persisted.sentiment === "positive" ? /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[11px] w-[11px]" }) : /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[11px] w-[11px]" }),
|
|
273
|
-
persisted.detail && /* @__PURE__ */ jsx("span", { className: "max-w-[200px] truncate text-muted-foreground/70", children: persisted.detail }),
|
|
274
|
-
/* @__PURE__ */ jsx(Pencil, { className: "h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" })
|
|
275
|
-
]
|
|
276
|
-
}
|
|
277
|
-
)
|
|
278
|
-
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
208
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
279
209
|
/* @__PURE__ */ jsxs(
|
|
280
210
|
"button",
|
|
281
211
|
{
|
|
@@ -305,18 +235,6 @@ function FeedbackFooter({
|
|
|
305
235
|
"Not helpful"
|
|
306
236
|
]
|
|
307
237
|
}
|
|
308
|
-
),
|
|
309
|
-
submitted && feedback && /* @__PURE__ */ jsxs(
|
|
310
|
-
"span",
|
|
311
|
-
{
|
|
312
|
-
className: "inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600",
|
|
313
|
-
role: "status",
|
|
314
|
-
"data-testid": "feedback-submitted-pill",
|
|
315
|
-
children: [
|
|
316
|
-
/* @__PURE__ */ jsx(Check, { className: "h-[11px] w-[11px]" }),
|
|
317
|
-
submittedLabel
|
|
318
|
-
]
|
|
319
|
-
}
|
|
320
238
|
)
|
|
321
239
|
] }),
|
|
322
240
|
metaText && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: metaText })
|
|
@@ -368,163 +286,10 @@ function FeedbackFooter({
|
|
|
368
286
|
] })
|
|
369
287
|
] });
|
|
370
288
|
}
|
|
371
|
-
function InlineFeedbackControl({
|
|
372
|
-
feedbackKey,
|
|
373
|
-
initialFeedback,
|
|
374
|
-
onFeedback,
|
|
375
|
-
testIdPrefix = "inline-feedback"
|
|
376
|
-
}) {
|
|
377
|
-
var _a, _b, _c, _d;
|
|
378
|
-
const [thumbState, setThumbState] = React.useState(
|
|
379
|
-
(_a = initialFeedback == null ? void 0 : initialFeedback.type) != null ? _a : null
|
|
380
|
-
);
|
|
381
|
-
const [showInput, setShowInput] = React.useState(false);
|
|
382
|
-
const [detailText, setDetailText] = React.useState((_b = initialFeedback == null ? void 0 : initialFeedback.detail) != null ? _b : "");
|
|
383
|
-
const [saved, setSaved] = React.useState(!!initialFeedback);
|
|
384
|
-
const [savedDetail, setSavedDetail] = React.useState((_c = initialFeedback == null ? void 0 : initialFeedback.detail) != null ? _c : "");
|
|
385
|
-
const ownershipLabel = (_d = initialFeedback == null ? void 0 : initialFeedback.ownershipLabel) != null ? _d : "Your feedback";
|
|
386
|
-
React.useEffect(() => {
|
|
387
|
-
if (initialFeedback) {
|
|
388
|
-
setThumbState(initialFeedback.type);
|
|
389
|
-
setSaved(true);
|
|
390
|
-
setSavedDetail(initialFeedback.detail);
|
|
391
|
-
}
|
|
392
|
-
}, [initialFeedback]);
|
|
393
|
-
const handleThumbClick = React.useCallback(
|
|
394
|
-
(type) => {
|
|
395
|
-
if (thumbState === type) {
|
|
396
|
-
setThumbState(null);
|
|
397
|
-
setShowInput(false);
|
|
398
|
-
setSaved(false);
|
|
399
|
-
onFeedback == null ? void 0 : onFeedback(feedbackKey, null);
|
|
400
|
-
} else {
|
|
401
|
-
setThumbState(type);
|
|
402
|
-
setShowInput(true);
|
|
403
|
-
setSaved(false);
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
[thumbState, feedbackKey, onFeedback]
|
|
407
|
-
);
|
|
408
|
-
const handleSubmitDetail = React.useCallback(() => {
|
|
409
|
-
if (!thumbState) return;
|
|
410
|
-
const text = detailText.trim();
|
|
411
|
-
onFeedback == null ? void 0 : onFeedback(feedbackKey, thumbState, text);
|
|
412
|
-
setSaved(true);
|
|
413
|
-
setSavedDetail(text);
|
|
414
|
-
setShowInput(false);
|
|
415
|
-
}, [thumbState, detailText, feedbackKey, onFeedback]);
|
|
416
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
417
|
-
saved && !showInput ? (
|
|
418
|
-
/* Persisted / saved indicator */
|
|
419
|
-
/* @__PURE__ */ jsxs(
|
|
420
|
-
"button",
|
|
421
|
-
{
|
|
422
|
-
type: "button",
|
|
423
|
-
onClick: () => {
|
|
424
|
-
setDetailText(savedDetail);
|
|
425
|
-
setShowInput(true);
|
|
426
|
-
setSaved(false);
|
|
427
|
-
},
|
|
428
|
-
className: "group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors",
|
|
429
|
-
"data-testid": `${testIdPrefix}-feedback-persisted-${feedbackKey}`,
|
|
430
|
-
children: [
|
|
431
|
-
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
432
|
-
ownershipLabel,
|
|
433
|
-
":"
|
|
434
|
-
] }),
|
|
435
|
-
thumbState === "up" ? /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[10px] w-[10px]" }) : /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[10px] w-[10px]" }),
|
|
436
|
-
savedDetail && /* @__PURE__ */ jsx("span", { className: "max-w-[180px] truncate text-muted-foreground/70", children: savedDetail }),
|
|
437
|
-
/* @__PURE__ */ jsx(Pencil, { className: "h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" })
|
|
438
|
-
]
|
|
439
|
-
}
|
|
440
|
-
)
|
|
441
|
-
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
442
|
-
/* @__PURE__ */ jsx(
|
|
443
|
-
"button",
|
|
444
|
-
{
|
|
445
|
-
type: "button",
|
|
446
|
-
onClick: () => handleThumbClick("up"),
|
|
447
|
-
className: cn(
|
|
448
|
-
"p-1 rounded transition-colors",
|
|
449
|
-
thumbState === "up" ? "text-foreground bg-muted" : "text-muted-foreground/40 hover:text-foreground hover:bg-muted/50"
|
|
450
|
-
),
|
|
451
|
-
title: "This is accurate",
|
|
452
|
-
"data-testid": `${testIdPrefix}-thumb-up-${feedbackKey}`,
|
|
453
|
-
children: /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[10px] w-[10px]" })
|
|
454
|
-
}
|
|
455
|
-
),
|
|
456
|
-
/* @__PURE__ */ jsx(
|
|
457
|
-
"button",
|
|
458
|
-
{
|
|
459
|
-
type: "button",
|
|
460
|
-
onClick: () => handleThumbClick("down"),
|
|
461
|
-
className: cn(
|
|
462
|
-
"p-1 rounded transition-colors",
|
|
463
|
-
thumbState === "down" ? "text-red-600 bg-red-50" : "text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50"
|
|
464
|
-
),
|
|
465
|
-
title: "Report issue",
|
|
466
|
-
"data-testid": `${testIdPrefix}-thumb-down-${feedbackKey}`,
|
|
467
|
-
children: /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[10px] w-[10px]" })
|
|
468
|
-
}
|
|
469
|
-
),
|
|
470
|
-
saved && /* @__PURE__ */ jsxs(
|
|
471
|
-
"span",
|
|
472
|
-
{
|
|
473
|
-
className: "inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600",
|
|
474
|
-
role: "status",
|
|
475
|
-
"data-testid": `${testIdPrefix}-saved-${feedbackKey}`,
|
|
476
|
-
children: [
|
|
477
|
-
/* @__PURE__ */ jsx(Check, { className: "h-[10px] w-[10px]" }),
|
|
478
|
-
"Saved"
|
|
479
|
-
]
|
|
480
|
-
}
|
|
481
|
-
)
|
|
482
|
-
] }),
|
|
483
|
-
showInput && thumbState && /* @__PURE__ */ jsxs("div", { className: "mt-1.5", children: [
|
|
484
|
-
/* @__PURE__ */ jsx(
|
|
485
|
-
"input",
|
|
486
|
-
{
|
|
487
|
-
type: "text",
|
|
488
|
-
value: detailText,
|
|
489
|
-
onChange: (e) => setDetailText(e.target.value),
|
|
490
|
-
onKeyDown: (e) => {
|
|
491
|
-
if (e.key === "Enter") handleSubmitDetail();
|
|
492
|
-
if (e.key === "Escape") setShowInput(false);
|
|
493
|
-
},
|
|
494
|
-
placeholder: thumbState === "up" ? "What\u2019s accurate? (optional)" : "What\u2019s wrong? (optional)",
|
|
495
|
-
className: "w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring",
|
|
496
|
-
"data-testid": `${testIdPrefix}-detail-input-${feedbackKey}`
|
|
497
|
-
}
|
|
498
|
-
),
|
|
499
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-1.5", children: [
|
|
500
|
-
/* @__PURE__ */ jsx(
|
|
501
|
-
"button",
|
|
502
|
-
{
|
|
503
|
-
type: "button",
|
|
504
|
-
onClick: handleSubmitDetail,
|
|
505
|
-
className: "bg-foreground text-background rounded px-2 py-0.5 text-[10px] font-semibold",
|
|
506
|
-
"data-testid": `${testIdPrefix}-submit-${feedbackKey}`,
|
|
507
|
-
children: "Submit"
|
|
508
|
-
}
|
|
509
|
-
),
|
|
510
|
-
/* @__PURE__ */ jsx(
|
|
511
|
-
"button",
|
|
512
|
-
{
|
|
513
|
-
type: "button",
|
|
514
|
-
onClick: () => setShowInput(false),
|
|
515
|
-
className: "border border-border rounded px-2 py-0.5 text-[10px] font-medium",
|
|
516
|
-
children: "Cancel"
|
|
517
|
-
}
|
|
518
|
-
)
|
|
519
|
-
] })
|
|
520
|
-
] })
|
|
521
|
-
] });
|
|
522
|
-
}
|
|
523
289
|
export {
|
|
524
290
|
FeedbackActions,
|
|
525
291
|
FeedbackChipGroup,
|
|
526
292
|
FeedbackFooter,
|
|
527
|
-
FeedbackInput
|
|
528
|
-
InlineFeedbackControl
|
|
293
|
+
FeedbackInput
|
|
529
294
|
};
|
|
530
295
|
//# sourceMappingURL=feedback-primitives.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/feedback-primitives.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown, Check, Pencil } 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 * Persisted feedback data from a previous submission, used to hydrate the\n * footer into its \"already submitted\" visual state.\n */\nexport interface PersistedFeedbackData {\n sentiment: \"positive\" | \"negative\"\n reasonTop?: string\n reasonSub?: string\n pills?: string[]\n detail?: string\n ownershipLabel: \"Your feedback\" | \"Team feedback\"\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 /** Pre-existing feedback to hydrate from (e.g. after page reload). */\n initialFeedback?: PersistedFeedbackData | null\n /** Label shown in the transient confirmation pill after submit. */\n submittedLabel?: string\n /** Stable key for syncing initialFeedback into local state. When this\n * changes, the component resets to the new initialFeedback value. */\n feedbackKey?: 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 initialFeedback,\n submittedLabel = \"Saved\",\n feedbackKey,\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 /** Transient \"Saved\" confirmation — shown after successful submit. */\n const [submitted, setSubmitted] = React.useState(false)\n /** Persisted feedback shown as a clickable indicator (survives reload). */\n const [persisted, setPersisted] = React.useState<PersistedFeedbackData | null>(\n initialFeedback ?? null,\n )\n /** Tracks whether the user is actively editing (ref to guard against prop overwrites without triggering re-syncs). */\n const isEditingRef = React.useRef(false)\n /** Track the last synced feedbackKey to detect key changes. */\n const lastKeyRef = React.useRef<string | undefined>(feedbackKey)\n\n /** Helper to update the editing ref. */\n const setIsEditing = React.useCallback((value: boolean) => {\n isEditingRef.current = value\n }, [])\n\n // Sync initialFeedback into local state via useEffect keyed on feedbackKey.\n // When feedbackKey changes, reset to new target. Preserve active edits\n // when feedbackKey stays the same.\n React.useEffect(() => {\n const keyChanged = feedbackKey !== lastKeyRef.current\n lastKeyRef.current = feedbackKey\n\n if (keyChanged) {\n // Key changed — full reset to new target\n setPersisted(initialFeedback ?? null)\n setSubmitted(false)\n setExpanded(false)\n isEditingRef.current = false\n if (initialFeedback) {\n onFeedbackChange(initialFeedback.sentiment)\n } else {\n onFeedbackChange(null)\n }\n } else if (!isEditingRef.current) {\n // Same key, not actively editing — safe to sync\n setPersisted(initialFeedback ?? null)\n if (initialFeedback) {\n onFeedbackChange(initialFeedback.sentiment)\n }\n }\n }, [initialFeedback, feedbackKey, onFeedbackChange])\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 setIsEditing(false)\n }, [setIsEditing])\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 setSubmitted(false)\n setPersisted(null)\n setIsEditing(true)\n },\n [onFeedbackChange, resetState, setIsEditing],\n )\n\n /** Open the persisted indicator for editing. */\n const handlePersistedClick = React.useCallback(() => {\n if (!persisted) return\n onFeedbackChange(persisted.sentiment)\n setSelectedTier1(persisted.reasonTop ?? null)\n setSelectedTier2(persisted.reasonSub ?? null)\n setAdditionalPills(persisted.pills ?? [])\n setDetailText(persisted.detail ?? \"\")\n setExpanded(true)\n setSubmitted(false)\n setIsEditing(true)\n }, [persisted, onFeedbackChange, setIsEditing])\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 // Show transient \"Saved\" confirmation\n setSubmitted(true)\n // Collapse expansion but keep sentiment visible\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 // Determine if we should show the persisted indicator instead of bare buttons\n const showPersistedIndicator = persisted && !expanded && !submitted\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 {showPersistedIndicator ? (\n /* Persisted feedback indicator — clickable to reopen editor */\n <button\n type=\"button\"\n onClick={handlePersistedClick}\n className=\"group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors\"\n data-testid=\"persisted-feedback-indicator\"\n >\n <span className=\"font-medium\">{persisted.ownershipLabel}:</span>\n {persisted.sentiment === \"positive\" ? (\n <ThumbsUp className=\"h-[11px] w-[11px]\" />\n ) : (\n <ThumbsDown className=\"h-[11px] w-[11px]\" />\n )}\n {persisted.detail && (\n <span className=\"max-w-[200px] truncate text-muted-foreground/70\">\n {persisted.detail}\n </span>\n )}\n <Pencil className=\"h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity\" />\n </button>\n ) : (\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 {/* Transient \"Saved\" confirmation pill */}\n {submitted && feedback && (\n <span\n className=\"inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600\"\n role=\"status\"\n data-testid=\"feedback-submitted-pill\"\n >\n <Check className=\"h-[11px] w-[11px]\" />\n {submittedLabel}\n </span>\n )}\n </div>\n )}\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\n// ---------------------------------------------------------------------------\n// InlineFeedbackControl — shared thumb+detail inline feedback widget\n// ---------------------------------------------------------------------------\n\nexport interface InlineFeedbackControlProps {\n /** Unique key identifying the feedback target (e.g. factor key). */\n feedbackKey: string\n /** Persisted/initial feedback to hydrate from. */\n initialFeedback?: { type: \"up\" | \"down\"; detail: string; ownershipLabel?: string }\n /** Called when user submits or clears feedback. */\n onFeedback?: (key: string, type: \"up\" | \"down\" | null, detail?: string) => void\n /** Test ID prefix for all sub-elements. */\n testIdPrefix?: string\n}\n\n/**\n * Compact inline thumb-up/thumb-down feedback with optional detail text.\n * Used by PriorityFactorRow and any other component that needs\n * a lightweight feedback control.\n */\nexport function InlineFeedbackControl({\n feedbackKey,\n initialFeedback,\n onFeedback,\n testIdPrefix = \"inline-feedback\",\n}: InlineFeedbackControlProps) {\n const [thumbState, setThumbState] = React.useState<\"up\" | \"down\" | null>(\n initialFeedback?.type ?? null,\n )\n const [showInput, setShowInput] = React.useState(false)\n const [detailText, setDetailText] = React.useState(initialFeedback?.detail ?? \"\")\n const [saved, setSaved] = React.useState(!!initialFeedback)\n const [savedDetail, setSavedDetail] = React.useState(initialFeedback?.detail ?? \"\")\n const ownershipLabel = initialFeedback?.ownershipLabel ?? \"Your feedback\"\n\n // Sync with initialFeedback prop changes\n React.useEffect(() => {\n if (initialFeedback) {\n setThumbState(initialFeedback.type)\n setSaved(true)\n setSavedDetail(initialFeedback.detail)\n }\n }, [initialFeedback])\n\n const handleThumbClick = React.useCallback(\n (type: \"up\" | \"down\") => {\n if (thumbState === type) {\n // Toggle off\n setThumbState(null)\n setShowInput(false)\n setSaved(false)\n onFeedback?.(feedbackKey, null)\n } else {\n setThumbState(type)\n setShowInput(true)\n setSaved(false)\n }\n },\n [thumbState, feedbackKey, onFeedback],\n )\n\n const handleSubmitDetail = React.useCallback(() => {\n if (!thumbState) return\n const text = detailText.trim()\n onFeedback?.(feedbackKey, thumbState, text)\n setSaved(true)\n setSavedDetail(text)\n setShowInput(false)\n }, [thumbState, detailText, feedbackKey, onFeedback])\n\n return (\n <div>\n {saved && !showInput ? (\n /* Persisted / saved indicator */\n <button\n type=\"button\"\n onClick={() => {\n setDetailText(savedDetail)\n setShowInput(true)\n setSaved(false)\n }}\n className=\"group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors\"\n data-testid={`${testIdPrefix}-feedback-persisted-${feedbackKey}`}\n >\n <span className=\"font-medium\">{ownershipLabel}:</span>\n {thumbState === \"up\" ? (\n <ThumbsUp className=\"h-[10px] w-[10px]\" />\n ) : (\n <ThumbsDown className=\"h-[10px] w-[10px]\" />\n )}\n {savedDetail && (\n <span className=\"max-w-[180px] truncate text-muted-foreground/70\">\n {savedDetail}\n </span>\n )}\n <Pencil className=\"h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity\" />\n </button>\n ) : (\n <div className=\"flex items-center gap-1.5\">\n {/* Inline thumb buttons */}\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"up\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n thumbState === \"up\"\n ? \"text-foreground bg-muted\"\n : \"text-muted-foreground/40 hover:text-foreground hover:bg-muted/50\",\n )}\n title=\"This is accurate\"\n data-testid={`${testIdPrefix}-thumb-up-${feedbackKey}`}\n >\n <ThumbsUp className=\"h-[10px] w-[10px]\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"down\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n thumbState === \"down\"\n ? \"text-red-600 bg-red-50\"\n : \"text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50\",\n )}\n title=\"Report issue\"\n data-testid={`${testIdPrefix}-thumb-down-${feedbackKey}`}\n >\n <ThumbsDown className=\"h-[10px] w-[10px]\" />\n </button>\n\n {/* Transient \"Saved\" pill */}\n {saved && (\n <span\n className=\"inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600\"\n role=\"status\"\n data-testid={`${testIdPrefix}-saved-${feedbackKey}`}\n >\n <Check className=\"h-[10px] w-[10px]\" />\n Saved\n </span>\n )}\n </div>\n )}\n\n {/* Inline detail input */}\n {showInput && thumbState && (\n <div className=\"mt-1.5\">\n <input\n type=\"text\"\n value={detailText}\n onChange={(e) => setDetailText(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") handleSubmitDetail()\n if (e.key === \"Escape\") setShowInput(false)\n }}\n placeholder={\n thumbState === \"up\"\n ? \"What\\u2019s accurate? (optional)\"\n : \"What\\u2019s wrong? (optional)\"\n }\n className=\"w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring\"\n data-testid={`${testIdPrefix}-detail-input-${feedbackKey}`}\n />\n <div className=\"mt-1 flex items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={handleSubmitDetail}\n className=\"bg-foreground text-background rounded px-2 py-0.5 text-[10px] font-semibold\"\n data-testid={`${testIdPrefix}-submit-${feedbackKey}`}\n >\n Submit\n </button>\n <button\n type=\"button\"\n onClick={() => setShowInput(false)}\n className=\"border border-border rounded px-2 py-0.5 text-[10px] font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";AAiFU,cAgFN,YAhFM;AA/EV,YAAY,WAAW;AACvB,SAAS,UAAU,YAAY,OAAO,cAAc;AACpD,SAAS,UAAU;AAyDnB,MAAM,wBAAiE;AAAA,EACrE,UAAU;AAAA,EACV,UAAU;AACZ;AAEA,MAAM,kBACJ;AAEK,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,SACE,oBAAC,SAAI,WAAW,GAAG,0BAA0B,SAAS,GACnD,gBAAM,IAAI,CAAC,SAAS;AACnB,UAAM,aAAa,SAAS,SAAS,IAAI;AACzC,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,SAAS,MAAM,SAAS,IAAI;AAAA,QAC5B,WAAW;AAAA,UACT;AAAA,UACA,aAAa,sBAAsB,MAAM,IAAI;AAAA,QAC/C;AAAA,QAEC;AAAA;AAAA,MARI;AAAA,IASP;AAAA,EAEJ,CAAC,GACH;AAEJ;AAcO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,MACxC,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,WAAW,UAAU;AACjC,YAAE,eAAe;AACjB,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAgBO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAyB;AACvB,SACE,qBAAC,SAAI,WAAW,GAAG,2BAA2B,SAAS,GACrD;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IACC,QACC,oBAAC,UAAK,WAAU,6CACb,gBACH;AAAA,KAEJ;AAEJ;AAyBA,MAAM,0BAAmE;AAAA,EACvE,UAAU;AAAA,EACV,UAAU;AACZ;AAEA,MAAM,wBACJ;AAEK,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,gBAAgB,CAAC;AAAA,EACjB,gBAAgB,CAAC;AAAA,EACjB;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AACF,GAAwB;AACtB,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAmB,CAAC,CAAC;AACzE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AACrD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM;AAAA,IAClD;AAAA,EACF;AAEA,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AAEtD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM;AAAA,IACtC,4CAAmB;AAAA,EACrB;AAEA,QAAM,eAAe,MAAM,OAAO,KAAK;AAEvC,QAAM,aAAa,MAAM,OAA2B,WAAW;AAG/D,QAAM,eAAe,MAAM,YAAY,CAAC,UAAmB;AACzD,iBAAa,UAAU;AAAA,EACzB,GAAG,CAAC,CAAC;AAKL,QAAM,UAAU,MAAM;AACpB,UAAM,aAAa,gBAAgB,WAAW;AAC9C,eAAW,UAAU;AAErB,QAAI,YAAY;AAEd,mBAAa,4CAAmB,IAAI;AACpC,mBAAa,KAAK;AAClB,kBAAY,KAAK;AACjB,mBAAa,UAAU;AACvB,UAAI,iBAAiB;AACnB,yBAAiB,gBAAgB,SAAS;AAAA,MAC5C,OAAO;AACL,yBAAiB,IAAI;AAAA,MACvB;AAAA,IACF,WAAW,CAAC,aAAa,SAAS;AAEhC,mBAAa,4CAAmB,IAAI;AACpC,UAAI,iBAAiB;AACnB,yBAAiB,gBAAgB,SAAS;AAAA,MAC5C;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,aAAa,gBAAgB,CAAC;AAGnD,QAAM,aAAa,MAAM,YAAY,MAAM;AACzC,gBAAY,KAAK;AACjB,qBAAiB,IAAI;AACrB,qBAAiB,IAAI;AACrB,uBAAmB,CAAC,CAAC;AACrB,kBAAc,EAAE;AAChB,uBAAmB,IAAI;AACvB,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,uBAAuB,MAAM;AAAA,IACjC,CAAC,cAAuC;AACtC,uBAAiB,SAAS;AAE1B,iBAAW;AACX,kBAAY,IAAI;AAChB,mBAAa,KAAK;AAClB,mBAAa,IAAI;AACjB,mBAAa,IAAI;AAAA,IACnB;AAAA,IACA,CAAC,kBAAkB,YAAY,YAAY;AAAA,EAC7C;AAGA,QAAM,uBAAuB,MAAM,YAAY,MAAM;AAnTvD;AAoTI,QAAI,CAAC,UAAW;AAChB,qBAAiB,UAAU,SAAS;AACpC,sBAAiB,eAAU,cAAV,YAAuB,IAAI;AAC5C,sBAAiB,eAAU,cAAV,YAAuB,IAAI;AAC5C,wBAAmB,eAAU,UAAV,YAAmB,CAAC,CAAC;AACxC,mBAAc,eAAU,WAAV,YAAoB,EAAE;AACpC,gBAAY,IAAI;AAChB,iBAAa,KAAK;AAClB,iBAAa,IAAI;AAAA,EACnB,GAAG,CAAC,WAAW,kBAAkB,YAAY,CAAC;AAE9C,QAAM,oBAAoB,MAAM;AAAA,IAC9B,CAAC,cAAsB;AACrB,UAAI,kBAAkB,WAAW;AAE/B,yBAAiB,IAAI;AACrB,yBAAiB,IAAI;AACrB,2BAAmB,IAAI;AAAA,MACzB,WAAW,kBAAkB,MAAM;AAEjC,yBAAiB,SAAS;AAC1B,yBAAiB,IAAI;AAErB,cAAM,MAAM,cAAc,UAAU,CAAC,MAAM,EAAE,UAAU,SAAS;AAChE,YAAI,QAAQ,MAAM,cAAc,GAAG,EAAE,UAAU;AAC7C,6BAAmB,GAAG;AAAA,QACxB,OAAO;AACL,6BAAmB,IAAI;AAAA,QACzB;AAAA,MACF,OAAO;AAEL;AAAA,UAAmB,CAAC,SAClB,KAAK,SAAS,SAAS,IACnB,KAAK,OAAO,CAAC,MAAM,MAAM,SAAS,IAClC,CAAC,GAAG,MAAM,SAAS;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,eAAe,aAAa;AAAA,EAC/B;AAEA,QAAM,oBAAoB,MAAM,YAAY,CAAC,YAAoB;AAC/D,qBAAiB,CAAC,SAAU,SAAS,UAAU,OAAO,OAAQ;AAAA,EAChE,GAAG,CAAC,CAAC;AAEL,QAAM,2BAA2B,MAAM;AAAA,IACrC,CAAC,SAAiB;AAChB,UAAI,kBAAkB,MAAM;AAC1B,yBAAiB,IAAI;AAAA,MACvB,WAAW,kBAAkB,MAAM;AACjC,yBAAiB,IAAI;AAAA,MACvB,OAAO;AACL;AAAA,UAAmB,CAAC,SAClB,KAAK,SAAS,IAAI,IACd,KAAK,OAAO,CAAC,MAAM,MAAM,IAAI,IAC7B,CAAC,GAAG,MAAM,IAAI;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,aAAa;AAAA,EAChB;AAEA,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,QAAI,CAAC,SAAU;AACf,aAAS;AAAA,MACP,WAAW;AAAA,MACX,WAAW,wCAAiB;AAAA,MAC5B,WAAW,wCAAiB;AAAA,MAC5B,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AAED,iBAAa,IAAI;AAEjB,eAAW;AAAA,EACb,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,eAAW;AACX,qBAAiB,IAAI;AAAA,EACvB,GAAG,CAAC,YAAY,gBAAgB,CAAC;AAGjC,QAAM,mBAAmB,MAAM,QAAQ,MAAM;AAC3C,UAAM,SAAmB,CAAC;AAC1B,QAAI,cAAe,QAAO,KAAK,aAAa;AAC5C,WAAO,KAAK,GAAG,eAAe;AAC9B,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,eAAe,CAAC;AAGnC,QAAM,aACJ,oBAAoB,OAAO,cAAc,eAAe,IAAI;AAG9D,QAAM,yBAAyB,aAAa,CAAC,YAAY,CAAC;AAE1D,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GAEvC;AAAA,yBAAC,SAAI,WAAU,qCACZ;AAAA;AAAA;AAAA,QAEC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,eAAY;AAAA,YAEZ;AAAA,mCAAC,UAAK,WAAU,eAAe;AAAA,0BAAU;AAAA,gBAAe;AAAA,iBAAC;AAAA,cACxD,UAAU,cAAc,aACvB,oBAAC,YAAS,WAAU,qBAAoB,IAExC,oBAAC,cAAW,WAAU,qBAAoB;AAAA,cAE3C,UAAU,UACT,oBAAC,UAAK,WAAU,mDACb,oBAAU,QACb;AAAA,cAEF,oBAAC,UAAO,WAAU,wEAAuE;AAAA;AAAA;AAAA,QAC3F;AAAA,UAEA,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,qBAAqB,UAAU;AAAA,YAC9C,WAAW;AAAA,cACT;AAAA,cACA,aAAa,aACT,wBAAwB,WACxB;AAAA,YACN;AAAA,YAEA;AAAA,kCAAC,YAAS,WAAU,qBAAoB;AAAA,cAAE;AAAA;AAAA;AAAA,QAE5C;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,qBAAqB,UAAU;AAAA,YAC9C,WAAW;AAAA,cACT;AAAA,cACA,aAAa,aACT,wBAAwB,WACxB;AAAA,YACN;AAAA,YAEA;AAAA,kCAAC,cAAW,WAAU,qBAAoB;AAAA,cAAE;AAAA;AAAA;AAAA,QAE9C;AAAA,QAEC,aAAa,YACZ;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,MAAK;AAAA,YACL,eAAY;AAAA,YAEZ;AAAA,kCAAC,SAAM,WAAU,qBAAoB;AAAA,cACpC;AAAA;AAAA;AAAA,QACH;AAAA,SAEJ;AAAA,MAED,YACC,oBAAC,UAAK,WAAU,qCAAqC,oBAAS;AAAA,OAElE;AAAA,IAGC,YAAY,YACX,qBAAC,SAAI,WAAU,aAEb;AAAA,0BAAC,OAAE,WAAU,iCACV,uBAAa,aAAa,iBAAiB,gBAC9C;AAAA,MAGC,aAAa,cAAc,cAAc,SAAS,KACjD,qBAAC,SAAI,WAAU,aAEb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,cAAc,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,YACvC,UAAU;AAAA,YACV,UAAU;AAAA,YACV,QAAO;AAAA;AAAA,QACT;AAAA,QAGC,cAAc,WAAW,YACxB,qBAAC,SAAI,WAAU,oBACZ;AAAA,qBAAW,aACV,oBAAC,OAAE,WAAU,qCACV,qBAAW,WACd;AAAA,UAEF;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,WAAW;AAAA,cAClB,UAAU,gBAAgB,CAAC,aAAa,IAAI,CAAC;AAAA,cAC7C,UAAU;AAAA,cACV,QAAO;AAAA;AAAA,UACT;AAAA,WACF;AAAA,SAEJ;AAAA,MAGD,aAAa,cAAc,cAAc,SAAS,KACjD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,QAAO;AAAA;AAAA,MACT;AAAA,MAIF;AAAA,QAAC;AAAA;AAAA,UACC,aAAY;AAAA,UACZ,OAAO;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA;AAAA,MACZ;AAAA,MAGA,oBAAC,mBAAgB,UAAU,cAAc,UAAU,cAAc;AAAA,OACnE;AAAA,KAEJ;AAEJ;AAsBO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAA+B;AA5jB/B;AA6jBE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM;AAAA,KACxC,wDAAiB,SAAjB,YAAyB;AAAA,EAC3B;AACA,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,UAAS,wDAAiB,WAAjB,YAA2B,EAAE;AAChF,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC,CAAC,eAAe;AAC1D,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,UAAS,wDAAiB,WAAjB,YAA2B,EAAE;AAClF,QAAM,kBAAiB,wDAAiB,mBAAjB,YAAmC;AAG1D,QAAM,UAAU,MAAM;AACpB,QAAI,iBAAiB;AACnB,oBAAc,gBAAgB,IAAI;AAClC,eAAS,IAAI;AACb,qBAAe,gBAAgB,MAAM;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,eAAe,CAAC;AAEpB,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,SAAwB;AACvB,UAAI,eAAe,MAAM;AAEvB,sBAAc,IAAI;AAClB,qBAAa,KAAK;AAClB,iBAAS,KAAK;AACd,iDAAa,aAAa;AAAA,MAC5B,OAAO;AACL,sBAAc,IAAI;AAClB,qBAAa,IAAI;AACjB,iBAAS,KAAK;AAAA,MAChB;AAAA,IACF;AAAA,IACA,CAAC,YAAY,aAAa,UAAU;AAAA,EACtC;AAEA,QAAM,qBAAqB,MAAM,YAAY,MAAM;AACjD,QAAI,CAAC,WAAY;AACjB,UAAM,OAAO,WAAW,KAAK;AAC7B,6CAAa,aAAa,YAAY;AACtC,aAAS,IAAI;AACb,mBAAe,IAAI;AACnB,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,YAAY,YAAY,aAAa,UAAU,CAAC;AAEpD,SACE,qBAAC,SACE;AAAA,aAAS,CAAC;AAAA;AAAA,MAET;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,0BAAc,WAAW;AACzB,yBAAa,IAAI;AACjB,qBAAS,KAAK;AAAA,UAChB;AAAA,UACA,WAAU;AAAA,UACV,eAAa,GAAG,YAAY,uBAAuB,WAAW;AAAA,UAE9D;AAAA,iCAAC,UAAK,WAAU,eAAe;AAAA;AAAA,cAAe;AAAA,eAAC;AAAA,YAC9C,eAAe,OACd,oBAAC,YAAS,WAAU,qBAAoB,IAExC,oBAAC,cAAW,WAAU,qBAAoB;AAAA,YAE3C,eACC,oBAAC,UAAK,WAAU,mDACb,uBACH;AAAA,YAEF,oBAAC,UAAO,WAAU,wEAAuE;AAAA;AAAA;AAAA,MAC3F;AAAA,QAEA,qBAAC,SAAI,WAAU,6BAEb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,iBAAiB,IAAI;AAAA,UACpC,WAAW;AAAA,YACT;AAAA,YACA,eAAe,OACX,6BACA;AAAA,UACN;AAAA,UACA,OAAM;AAAA,UACN,eAAa,GAAG,YAAY,aAAa,WAAW;AAAA,UAEpD,8BAAC,YAAS,WAAU,qBAAoB;AAAA;AAAA,MAC1C;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,iBAAiB,MAAM;AAAA,UACtC,WAAW;AAAA,YACT;AAAA,YACA,eAAe,SACX,2BACA;AAAA,UACN;AAAA,UACA,OAAM;AAAA,UACN,eAAa,GAAG,YAAY,eAAe,WAAW;AAAA,UAEtD,8BAAC,cAAW,WAAU,qBAAoB;AAAA;AAAA,MAC5C;AAAA,MAGC,SACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAK;AAAA,UACL,eAAa,GAAG,YAAY,UAAU,WAAW;AAAA,UAEjD;AAAA,gCAAC,SAAM,WAAU,qBAAoB;AAAA,YAAE;AAAA;AAAA;AAAA,MAEzC;AAAA,OAEJ;AAAA,IAID,aAAa,cACZ,qBAAC,SAAI,WAAU,UACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,UAC7C,WAAW,CAAC,MAAM;AAChB,gBAAI,EAAE,QAAQ,QAAS,oBAAmB;AAC1C,gBAAI,EAAE,QAAQ,SAAU,cAAa,KAAK;AAAA,UAC5C;AAAA,UACA,aACE,eAAe,OACX,qCACA;AAAA,UAEN,WAAU;AAAA,UACV,eAAa,GAAG,YAAY,iBAAiB,WAAW;AAAA;AAAA,MAC1D;AAAA,MACA,qBAAC,SAAI,WAAU,kCACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,eAAa,GAAG,YAAY,WAAW,WAAW;AAAA,YACnD;AAAA;AAAA,QAED;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,aAAa,KAAK;AAAA,YACjC,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,SACF;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|