@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.
Files changed (76) hide show
  1. package/dist/charts/chart.d.ts +1 -1
  2. package/dist/charts/empty-chart-state.d.ts +11 -0
  3. package/dist/charts/empty-chart-state.js +70 -0
  4. package/dist/charts/empty-chart-state.js.map +1 -0
  5. package/dist/charts/index.d.ts +1 -0
  6. package/dist/charts/index.js +1 -0
  7. package/dist/charts/index.js.map +1 -1
  8. package/dist/charts/pipeline-overview.d.ts +2 -1
  9. package/dist/charts/pipeline-overview.js +32 -1
  10. package/dist/charts/pipeline-overview.js.map +1 -1
  11. package/dist/components/badge.d.ts +1 -1
  12. package/dist/components/button.d.ts +1 -1
  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 +2 -41
  20. package/dist/components/feedback-primitives.js +6 -241
  21. package/dist/components/feedback-primitives.js.map +1 -1
  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 +97 -0
  30. package/dist/components/metric-card.js.map +1 -1
  31. package/dist/components/pill.d.ts +26 -0
  32. package/dist/components/pill.js +77 -0
  33. package/dist/components/pill.js.map +1 -0
  34. package/dist/components/quick-segment.d.ts +13 -0
  35. package/dist/components/quick-segment.js +96 -0
  36. package/dist/components/quick-segment.js.map +1 -0
  37. package/dist/components/score-why-chips.d.ts +1 -1
  38. package/dist/components/score-why-chips.js +5 -26
  39. package/dist/components/score-why-chips.js.map +1 -1
  40. package/dist/components/signal-priority-popover.d.ts +1 -1
  41. package/dist/components/signal-priority-popover.js +6 -32
  42. package/dist/components/signal-priority-popover.js.map +1 -1
  43. package/dist/components/tabs.d.ts +1 -1
  44. package/dist/index.d.ts +9 -3
  45. package/dist/index.js +6 -2
  46. package/dist/index.js.map +1 -1
  47. package/dist/prototype/index.d.ts +1 -1
  48. package/dist/prototype/prototype-accounts-view.d.ts +1 -1
  49. package/dist/prototype/prototype-admin-view.d.ts +1 -1
  50. package/dist/prototype/prototype-config.d.ts +1 -1
  51. package/dist/prototype/prototype-inbox-view.d.ts +1 -1
  52. package/dist/prototype/prototype-inbox-view.js +1 -4
  53. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  54. package/dist/prototype/prototype-insights-view.d.ts +1 -1
  55. package/dist/prototype/prototype-shell.d.ts +1 -1
  56. package/dist/{signal-priority-popover-DWaAMhPI.d.ts → signal-priority-popover-DQ_VuHac.d.ts} +2 -26
  57. package/package.json +1 -3
  58. package/src/charts/__tests__/insights-charts.test.tsx +62 -0
  59. package/src/charts/empty-chart-state.tsx +44 -0
  60. package/src/charts/index.ts +1 -0
  61. package/src/charts/pipeline-overview.tsx +41 -1
  62. package/src/components/__tests__/insights-primitives.test.tsx +135 -0
  63. package/src/components/days-open-cell.tsx +50 -0
  64. package/src/components/detail-drawer.tsx +60 -0
  65. package/src/components/feedback-primitives.tsx +26 -333
  66. package/src/components/insights-filter-bar.tsx +13 -4
  67. package/src/components/linked-entity-cell.tsx +74 -0
  68. package/src/components/metric-card.tsx +98 -0
  69. package/src/components/pill.tsx +67 -0
  70. package/src/components/quick-segment.tsx +68 -0
  71. package/src/components/score-why-chips.tsx +2 -28
  72. package/src/components/signal-priority-popover.tsx +4 -44
  73. package/src/index.ts +7 -2
  74. package/src/prototype/prototype-config.ts +1 -11
  75. package/src/prototype/prototype-inbox-view.tsx +0 -3
  76. 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, Check, Pencil } from "lucide-react";
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
- setIsEditing(false);
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, setIsEditing]
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
- showPersistedIndicator ? (
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":"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","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 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