@handled-ai/design-system 0.9.24 → 0.9.26

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.
@@ -39,19 +39,46 @@ function getRiskBadgeStyle(risk) {
39
39
  return "text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30";
40
40
  }
41
41
  }
42
+ function deriveInitialState(init, mapFn, filterFn) {
43
+ if (!init) return {};
44
+ const entries = Object.entries(init);
45
+ const filtered = filterFn ? entries.filter(([, v]) => filterFn(v)) : entries;
46
+ return Object.fromEntries(filtered.map(([k, v]) => [k, mapFn(v)]));
47
+ }
42
48
  function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback }) {
43
49
  const [feedback, setFeedback] = React.useState(
44
- initialFeedback ? Object.fromEntries(Object.entries(initialFeedback).map(([k, v]) => [k, v.type])) : {}
50
+ () => deriveInitialState(initialFeedback, (v) => v.type)
45
51
  );
46
52
  const [feedbackText, setFeedbackText] = React.useState(
47
- initialFeedback ? Object.fromEntries(Object.entries(initialFeedback).map(([k, v]) => [k, v.detail])) : {}
53
+ () => deriveInitialState(initialFeedback, (v) => v.detail)
48
54
  );
49
55
  const [savedText, setSavedText] = React.useState(
50
- initialFeedback ? Object.fromEntries(
51
- Object.entries(initialFeedback).filter(([, v]) => v.detail).map(([k, v]) => [k, v.detail])
52
- ) : {}
56
+ () => deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail)
53
57
  );
54
58
  const [editingKey, setEditingKey] = React.useState(null);
59
+ React.useEffect(() => {
60
+ setFeedback((prev) => {
61
+ const next = deriveInitialState(initialFeedback, (v) => v.type);
62
+ if (editingKey && prev[editingKey] !== void 0) {
63
+ next[editingKey] = prev[editingKey];
64
+ }
65
+ return next;
66
+ });
67
+ setFeedbackText((prev) => {
68
+ const next = deriveInitialState(initialFeedback, (v) => v.detail);
69
+ if (editingKey && prev[editingKey] !== void 0) {
70
+ next[editingKey] = prev[editingKey];
71
+ }
72
+ return next;
73
+ });
74
+ setSavedText((prev) => {
75
+ const next = deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail);
76
+ if (editingKey && prev[editingKey] !== void 0) {
77
+ next[editingKey] = prev[editingKey];
78
+ }
79
+ return next;
80
+ });
81
+ }, [initialFeedback]);
55
82
  const handleFeedback = (factorKey, type) => {
56
83
  const newState = feedback[factorKey] === type ? null : type;
57
84
  setFeedback((prev) => __spreadProps(__spreadValues({}, prev), { [factorKey]: newState }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/score-breakdown.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\nexport interface ScoreFactor {\n key: string\n label: string\n score: number | null\n risk?: \"Low\" | \"Medium\" | \"High\"\n why: string\n}\n\nfunction getFactorBarColor(score: number) {\n if (score >= 70) return \"bg-emerald-500\"\n if (score >= 40) return \"bg-amber-500\"\n return \"bg-red-500\"\n}\n\nfunction getRiskBadgeStyle(risk: \"Low\" | \"Medium\" | \"High\") {\n switch (risk) {\n case \"Low\":\n return \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n case \"Medium\":\n return \"text-amber-600 bg-amber-50 dark:text-amber-400 dark:bg-amber-950/30\"\n case \"High\":\n return \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n }\n}\n\ninterface ScoreBreakdownProps {\n factors: ScoreFactor[]\n onFactorFeedback?: (factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n className?: string\n initialFeedback?: Record<string, { type: \"up\" | \"down\"; detail: string }>\n}\n\nfunction ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback }: ScoreBreakdownProps) {\n const [feedback, setFeedback] = React.useState<Record<string, \"up\" | \"down\" | null>>(\n initialFeedback\n ? Object.fromEntries(Object.entries(initialFeedback).map(([k, v]) => [k, v.type]))\n : {}\n )\n const [feedbackText, setFeedbackText] = React.useState<Record<string, string>>(\n initialFeedback\n ? Object.fromEntries(Object.entries(initialFeedback).map(([k, v]) => [k, v.detail]))\n : {}\n )\n const [savedText, setSavedText] = React.useState<Record<string, string>>(\n initialFeedback\n ? Object.fromEntries(\n Object.entries(initialFeedback)\n .filter(([, v]) => v.detail)\n .map(([k, v]) => [k, v.detail])\n )\n : {}\n )\n const [editingKey, setEditingKey] = React.useState<string | null>(null)\n\n const handleFeedback = (factorKey: string, type: \"up\" | \"down\") => {\n const newState = feedback[factorKey] === type ? null : type\n setFeedback((prev) => ({ ...prev, [factorKey]: newState }))\n if (newState === null) {\n setEditingKey(null)\n onFactorFeedback?.(factorKey, null)\n } else {\n setEditingKey(factorKey)\n onFactorFeedback?.(factorKey, newState)\n }\n }\n\n const submitFeedbackText = (factorKey: string) => {\n const text = (feedbackText[factorKey] ?? \"\").trim()\n if (feedback[factorKey]) {\n onFactorFeedback?.(factorKey, feedback[factorKey]!, text ?? \"\")\n if (text) {\n setSavedText((prev) => ({ ...prev, [factorKey]: text }))\n }\n }\n setEditingKey(null)\n }\n\n return (\n <div className={cn(\"border border-border rounded-lg overflow-hidden divide-y divide-border/40\", className)}>\n {factors.map((factor) => {\n const feedbackState = feedback[factor.key]\n const hasThumb = feedbackState === \"up\" || feedbackState === \"down\"\n const isEditing = editingKey === factor.key\n const saved = savedText[factor.key]\n\n return (\n <div key={factor.key} className=\"px-3 py-3 space-y-1.5\">\n {/* Row 1: Factor name + score + rating buttons */}\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[13px] font-semibold text-foreground flex-1\">{factor.label}</span>\n\n {factor.score !== null ? (\n <span className=\"text-xs font-bold text-foreground tabular-nums\">{factor.score}<span className=\"text-muted-foreground font-normal\">/100</span></span>\n ) : factor.risk ? (\n <span\n className={cn(\n \"text-[11px] font-semibold px-1.5 py-0.5 rounded\",\n getRiskBadgeStyle(factor.risk)\n )}\n >\n {factor.risk}\n </span>\n ) : null}\n\n <div className=\"flex items-center gap-0.5 ml-1\">\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"up\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"up\"\n ? \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n : \"text-muted-foreground/40 hover:text-emerald-600 hover:bg-emerald-50/50\"\n )}\n title=\"This factor is accurate\"\n >\n <ThumbsUp className=\"w-3 h-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"down\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"down\"\n ? \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n : \"text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50\"\n )}\n title=\"Report issue with this factor\"\n >\n <ThumbsDown className=\"w-3 h-3\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Score bar */}\n {factor.score !== null && (\n <div className=\"w-full h-1 bg-muted rounded-full overflow-hidden\">\n <div\n className={cn(\"h-full rounded-full\", getFactorBarColor(factor.score))}\n style={{ width: `${factor.score}%` }}\n />\n </div>\n )}\n\n {/* Row 3: Why text */}\n <p className=\"text-xs text-muted-foreground leading-relaxed\">{factor.why}</p>\n\n {/* Feedback input */}\n {hasThumb && isEditing && (\n <input\n type=\"text\"\n autoFocus\n value={feedbackText[factor.key] ?? \"\"}\n onChange={(e) =>\n setFeedbackText((prev) => ({ ...prev, [factor.key]: e.target.value }))\n }\n onKeyDown={(e) => {\n if (e.key === \"Enter\") submitFeedbackText(factor.key)\n if (e.key === \"Escape\") setEditingKey(null)\n }}\n onBlur={() => submitFeedbackText(factor.key)}\n placeholder={\n feedbackState === \"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 />\n )}\n {hasThumb && !isEditing && saved && (\n <button\n type=\"button\"\n onClick={() => {\n setFeedbackText((prev) => ({ ...prev, [factor.key]: saved }))\n setEditingKey(factor.key)\n }}\n className=\"text-[11px] text-muted-foreground/70 hover:text-muted-foreground transition-colors text-left leading-snug\"\n >\n {saved}\n </button>\n )}\n </div>\n )\n })}\n </div>\n )\n}\n\nexport { ScoreBreakdown }\nexport type { ScoreBreakdownProps }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+Fc,cAGE,YAHF;AA7Fd,YAAY,WAAW;AACvB,SAAS,UAAU,kBAAkB;AACrC,SAAS,UAAU;AAUnB,SAAS,kBAAkB,OAAe;AACxC,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAEA,SAAS,kBAAkB,MAAiC;AAC1D,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;AASA,SAAS,eAAe,EAAE,SAAS,kBAAkB,WAAW,gBAAgB,GAAwB;AACtG,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM;AAAA,IACpC,kBACI,OAAO,YAAY,OAAO,QAAQ,eAAe,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,IAC/E,CAAC;AAAA,EACP;AACA,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM;AAAA,IAC5C,kBACI,OAAO,YAAY,OAAO,QAAQ,eAAe,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,IACjF,CAAC;AAAA,EACP;AACA,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM;AAAA,IACtC,kBACI,OAAO;AAAA,MACL,OAAO,QAAQ,eAAe,EAC3B,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAC1B,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC;AAAA,IAClC,IACA,CAAC;AAAA,EACP;AACA,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAwB,IAAI;AAEtE,QAAM,iBAAiB,CAAC,WAAmB,SAAwB;AACjE,UAAM,WAAW,SAAS,SAAS,MAAM,OAAO,OAAO;AACvD,gBAAY,CAAC,SAAU,iCAAK,OAAL,EAAW,CAAC,SAAS,GAAG,SAAS,EAAE;AAC1D,QAAI,aAAa,MAAM;AACrB,oBAAc,IAAI;AAClB,2DAAmB,WAAW;AAAA,IAChC,OAAO;AACL,oBAAc,SAAS;AACvB,2DAAmB,WAAW;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,qBAAqB,CAAC,cAAsB;AAxEpD;AAyEI,UAAM,SAAQ,kBAAa,SAAS,MAAtB,YAA2B,IAAI,KAAK;AAClD,QAAI,SAAS,SAAS,GAAG;AACvB,2DAAmB,WAAW,SAAS,SAAS,GAAI,sBAAQ;AAC5D,UAAI,MAAM;AACR,qBAAa,CAAC,SAAU,iCAAK,OAAL,EAAW,CAAC,SAAS,GAAG,KAAK,EAAE;AAAA,MACzD;AAAA,IACF;AACA,kBAAc,IAAI;AAAA,EACpB;AAEA,SACE,oBAAC,SAAI,WAAW,GAAG,6EAA6E,SAAS,GACtG,kBAAQ,IAAI,CAAC,WAAW;AArF/B;AAsFQ,UAAM,gBAAgB,SAAS,OAAO,GAAG;AACzC,UAAM,WAAW,kBAAkB,QAAQ,kBAAkB;AAC7D,UAAM,YAAY,eAAe,OAAO;AACxC,UAAM,QAAQ,UAAU,OAAO,GAAG;AAElC,WACE,qBAAC,SAAqB,WAAU,yBAE9B;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,4BAAC,UAAK,WAAU,oDAAoD,iBAAO,OAAM;AAAA,QAEhF,OAAO,UAAU,OAChB,qBAAC,UAAK,WAAU,kDAAkD;AAAA,iBAAO;AAAA,UAAM,oBAAC,UAAK,WAAU,qCAAoC,kBAAI;AAAA,WAAO,IAC5I,OAAO,OACT;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,kBAAkB,OAAO,IAAI;AAAA,YAC/B;AAAA,YAEC,iBAAO;AAAA;AAAA,QACV,IACE;AAAA,QAEJ,qBAAC,SAAI,WAAU,kCACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,eAAe,OAAO,KAAK,IAAI;AAAA,cAC9C,WAAW;AAAA,gBACT;AAAA,gBACA,kBAAkB,OACd,gFACA;AAAA,cACN;AAAA,cACA,OAAM;AAAA,cAEN,8BAAC,YAAS,WAAU,WAAU;AAAA;AAAA,UAChC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,eAAe,OAAO,KAAK,MAAM;AAAA,cAChD,WAAW;AAAA,gBACT;AAAA,gBACA,kBAAkB,SACd,gEACA;AAAA,cACN;AAAA,cACA,OAAM;AAAA,cAEN,8BAAC,cAAW,WAAU,WAAU;AAAA;AAAA,UAClC;AAAA,WACF;AAAA,SACF;AAAA,MAGC,OAAO,UAAU,QAChB,oBAAC,SAAI,WAAU,oDACb;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,GAAG,uBAAuB,kBAAkB,OAAO,KAAK,CAAC;AAAA,UACpE,OAAO,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI;AAAA;AAAA,MACrC,GACF;AAAA,MAIF,oBAAC,OAAE,WAAU,iDAAiD,iBAAO,KAAI;AAAA,MAGxE,YAAY,aACX;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAS;AAAA,UACT,QAAO,kBAAa,OAAO,GAAG,MAAvB,YAA4B;AAAA,UACnC,UAAU,CAAC,MACT,gBAAgB,CAAC,SAAU,iCAAK,OAAL,EAAW,CAAC,OAAO,GAAG,GAAG,EAAE,OAAO,MAAM,EAAE;AAAA,UAEvE,WAAW,CAAC,MAAM;AAChB,gBAAI,EAAE,QAAQ,QAAS,oBAAmB,OAAO,GAAG;AACpD,gBAAI,EAAE,QAAQ,SAAU,eAAc,IAAI;AAAA,UAC5C;AAAA,UACA,QAAQ,MAAM,mBAAmB,OAAO,GAAG;AAAA,UAC3C,aACE,kBAAkB,OACd,qCACA;AAAA,UAEN,WAAU;AAAA;AAAA,MACZ;AAAA,MAED,YAAY,CAAC,aAAa,SACzB;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,4BAAgB,CAAC,SAAU,iCAAK,OAAL,EAAW,CAAC,OAAO,GAAG,GAAG,MAAM,EAAE;AAC5D,0BAAc,OAAO,GAAG;AAAA,UAC1B;AAAA,UACA,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,SA7FM,OAAO,GA+FjB;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/score-breakdown.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\nexport interface ScoreFactor {\n key: string\n label: string\n score: number | null\n risk?: \"Low\" | \"Medium\" | \"High\"\n why: string\n}\n\nfunction getFactorBarColor(score: number) {\n if (score >= 70) return \"bg-emerald-500\"\n if (score >= 40) return \"bg-amber-500\"\n return \"bg-red-500\"\n}\n\nfunction getRiskBadgeStyle(risk: \"Low\" | \"Medium\" | \"High\") {\n switch (risk) {\n case \"Low\":\n return \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n case \"Medium\":\n return \"text-amber-600 bg-amber-50 dark:text-amber-400 dark:bg-amber-950/30\"\n case \"High\":\n return \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n }\n}\n\ninterface ScoreBreakdownProps {\n factors: ScoreFactor[]\n onFactorFeedback?: (factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n className?: string\n initialFeedback?: Record<string, { type: \"up\" | \"down\"; detail: string }>\n}\n\nfunction deriveInitialState<T>(\n init: Record<string, { type: \"up\" | \"down\"; detail: string }> | undefined,\n mapFn: (v: { type: \"up\" | \"down\"; detail: string }) => T,\n filterFn?: (v: { type: \"up\" | \"down\"; detail: string }) => boolean,\n): Record<string, T> {\n if (!init) return {}\n const entries = Object.entries(init)\n const filtered = filterFn ? entries.filter(([, v]) => filterFn(v)) : entries\n return Object.fromEntries(filtered.map(([k, v]) => [k, mapFn(v)]))\n}\n\nfunction ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback }: ScoreBreakdownProps) {\n const [feedback, setFeedback] = React.useState<Record<string, \"up\" | \"down\" | null>>(\n () => deriveInitialState(initialFeedback, (v) => v.type)\n )\n const [feedbackText, setFeedbackText] = React.useState<Record<string, string>>(\n () => deriveInitialState(initialFeedback, (v) => v.detail)\n )\n const [savedText, setSavedText] = React.useState<Record<string, string>>(\n () => deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail)\n )\n const [editingKey, setEditingKey] = React.useState<string | null>(null)\n\n // Sync state when initialFeedback prop changes (e.g. async hydration).\n // Skip keys the user is actively editing to avoid clobbering in-progress input.\n React.useEffect(() => {\n setFeedback((prev) => {\n const next: Record<string, \"up\" | \"down\" | null> = deriveInitialState(initialFeedback, (v) => v.type)\n // Preserve any key the user is currently editing\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n setFeedbackText((prev) => {\n const next = deriveInitialState(initialFeedback, (v) => v.detail)\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n setSavedText((prev) => {\n const next = deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail)\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n }, [initialFeedback]) // eslint-disable-line react-hooks/exhaustive-deps -- reads editingKey as guard, not trigger\n\n const handleFeedback = (factorKey: string, type: \"up\" | \"down\") => {\n const newState = feedback[factorKey] === type ? null : type\n setFeedback((prev) => ({ ...prev, [factorKey]: newState }))\n if (newState === null) {\n setEditingKey(null)\n onFactorFeedback?.(factorKey, null)\n } else {\n setEditingKey(factorKey)\n onFactorFeedback?.(factorKey, newState)\n }\n }\n\n const submitFeedbackText = (factorKey: string) => {\n const text = (feedbackText[factorKey] ?? \"\").trim()\n if (feedback[factorKey]) {\n onFactorFeedback?.(factorKey, feedback[factorKey]!, text ?? \"\")\n if (text) {\n setSavedText((prev) => ({ ...prev, [factorKey]: text }))\n }\n }\n setEditingKey(null)\n }\n\n return (\n <div className={cn(\"border border-border rounded-lg overflow-hidden divide-y divide-border/40\", className)}>\n {factors.map((factor) => {\n const feedbackState = feedback[factor.key]\n const hasThumb = feedbackState === \"up\" || feedbackState === \"down\"\n const isEditing = editingKey === factor.key\n const saved = savedText[factor.key]\n\n return (\n <div key={factor.key} className=\"px-3 py-3 space-y-1.5\">\n {/* Row 1: Factor name + score + rating buttons */}\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[13px] font-semibold text-foreground flex-1\">{factor.label}</span>\n\n {factor.score !== null ? (\n <span className=\"text-xs font-bold text-foreground tabular-nums\">{factor.score}<span className=\"text-muted-foreground font-normal\">/100</span></span>\n ) : factor.risk ? (\n <span\n className={cn(\n \"text-[11px] font-semibold px-1.5 py-0.5 rounded\",\n getRiskBadgeStyle(factor.risk)\n )}\n >\n {factor.risk}\n </span>\n ) : null}\n\n <div className=\"flex items-center gap-0.5 ml-1\">\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"up\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"up\"\n ? \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n : \"text-muted-foreground/40 hover:text-emerald-600 hover:bg-emerald-50/50\"\n )}\n title=\"This factor is accurate\"\n >\n <ThumbsUp className=\"w-3 h-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"down\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"down\"\n ? \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n : \"text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50\"\n )}\n title=\"Report issue with this factor\"\n >\n <ThumbsDown className=\"w-3 h-3\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Score bar */}\n {factor.score !== null && (\n <div className=\"w-full h-1 bg-muted rounded-full overflow-hidden\">\n <div\n className={cn(\"h-full rounded-full\", getFactorBarColor(factor.score))}\n style={{ width: `${factor.score}%` }}\n />\n </div>\n )}\n\n {/* Row 3: Why text */}\n <p className=\"text-xs text-muted-foreground leading-relaxed\">{factor.why}</p>\n\n {/* Feedback input */}\n {hasThumb && isEditing && (\n <input\n type=\"text\"\n autoFocus\n value={feedbackText[factor.key] ?? \"\"}\n onChange={(e) =>\n setFeedbackText((prev) => ({ ...prev, [factor.key]: e.target.value }))\n }\n onKeyDown={(e) => {\n if (e.key === \"Enter\") submitFeedbackText(factor.key)\n if (e.key === \"Escape\") setEditingKey(null)\n }}\n onBlur={() => submitFeedbackText(factor.key)}\n placeholder={\n feedbackState === \"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 />\n )}\n {hasThumb && !isEditing && saved && (\n <button\n type=\"button\"\n onClick={() => {\n setFeedbackText((prev) => ({ ...prev, [factor.key]: saved }))\n setEditingKey(factor.key)\n }}\n className=\"text-[11px] text-muted-foreground/70 hover:text-muted-foreground transition-colors text-left leading-snug\"\n >\n {saved}\n </button>\n )}\n </div>\n )\n })}\n </div>\n )\n}\n\nexport { ScoreBreakdown }\nexport type { ScoreBreakdownProps }\n"],"mappings":"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","names":[]}
@@ -31,8 +31,12 @@ function Root({ children, onSubmitFeedback, initialFeedback }) {
31
31
  const [detailText, setDetailTextState] = React.useState("");
32
32
  const [notedType, setNotedType] = React.useState(null);
33
33
  const [submittedFeedback, setSubmittedFeedback] = React.useState(
34
- initialFeedback ? { type: initialFeedback.type, pills: initialFeedback.pills, detail: initialFeedback.detail } : null
34
+ initialFeedback != null ? initialFeedback : null
35
35
  );
36
+ React.useEffect(() => {
37
+ if (thumbState !== null) return;
38
+ setSubmittedFeedback(initialFeedback != null ? initialFeedback : null);
39
+ }, [initialFeedback]);
36
40
  const otherSelected = selectedPills.includes("Other");
37
41
  const hasRequiredInput = thumbState === "down" ? selectedPills.length > 0 && (!otherSelected || detailText.trim().length > 0) : selectedPills.length > 0 || detailText.trim().length > 0;
38
42
  const togglePill = React.useCallback((pill) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/score-feedback.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown, Check } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\nconst positivePills = [\n \"Right timing\",\n \"Accurate data\",\n \"Good prospect fit\",\n \"Actionable\",\n]\n\nconst negativePills = [\n \"Bad timing\",\n \"Inaccurate data\",\n \"Wrong prospect\",\n \"Already handled\",\n \"Not actionable\",\n \"Other\",\n]\n\ninterface SubmittedScoreFeedback {\n type: \"up\" | \"down\"\n pills: string[]\n detail: string\n}\n\ninterface ScoreFeedbackState {\n thumbState: \"up\" | \"down\" | null\n selectedPills: string[]\n detailText: string\n notedType: \"up\" | \"down\" | null\n submittedFeedback: SubmittedScoreFeedback | null\n otherSelected: boolean\n hasRequiredInput: boolean\n handleThumbClick: (type: \"up\" | \"down\") => void\n togglePill: (pill: string) => void\n setDetailText: (text: string) => void\n handleSubmit: () => void\n editSubmitted: () => void\n}\n\nconst ScoreFeedbackCtx = React.createContext<ScoreFeedbackState | null>(null)\n\nfunction useScoreFeedback() {\n const ctx = React.useContext(ScoreFeedbackCtx)\n if (!ctx) throw new Error(\"Must be used within ScoreFeedback.Root\")\n return ctx\n}\n\ninterface RootProps {\n children: React.ReactNode\n onSubmitFeedback?: (type: \"up\" | \"down\", pills: string[], detail: string) => void\n initialFeedback?: { type: \"up\" | \"down\"; pills: string[]; detail: string } | null\n}\n\nfunction Root({ children, onSubmitFeedback, initialFeedback }: RootProps) {\n const [thumbState, setThumbState] = React.useState<\"up\" | \"down\" | null>(null)\n const [selectedPills, setSelectedPills] = React.useState<string[]>([])\n const [detailText, setDetailTextState] = React.useState(\"\")\n const [notedType, setNotedType] = React.useState<\"up\" | \"down\" | null>(null)\n const [submittedFeedback, setSubmittedFeedback] = React.useState<SubmittedScoreFeedback | null>(\n initialFeedback ? { type: initialFeedback.type, pills: initialFeedback.pills, detail: initialFeedback.detail } : null\n )\n\n const otherSelected = selectedPills.includes(\"Other\")\n\n const hasRequiredInput =\n thumbState === \"down\"\n ? selectedPills.length > 0 && (!otherSelected || detailText.trim().length > 0)\n : selectedPills.length > 0 || detailText.trim().length > 0\n\n const togglePill = React.useCallback((pill: string) => {\n setSelectedPills((prev) =>\n prev.includes(pill) ? prev.filter((p) => p !== pill) : [...prev, pill]\n )\n }, [])\n\n const handleThumbClick = React.useCallback((type: \"up\" | \"down\") => {\n setThumbState((prev) => (prev === type ? null : type))\n setSelectedPills([])\n setDetailTextState(\"\")\n }, [])\n\n const handleSubmit = React.useCallback(() => {\n if (!thumbState) return\n onSubmitFeedback?.(thumbState, selectedPills, detailText)\n setSubmittedFeedback({ type: thumbState, pills: [...selectedPills], detail: detailText.trim() })\n setNotedType(thumbState)\n setThumbState(null)\n setSelectedPills([])\n setDetailTextState(\"\")\n setTimeout(() => setNotedType(null), 3000)\n }, [thumbState, selectedPills, detailText, onSubmitFeedback])\n\n const editSubmitted = React.useCallback(() => {\n if (!submittedFeedback) return\n setThumbState(submittedFeedback.type)\n setSelectedPills([...submittedFeedback.pills])\n setDetailTextState(submittedFeedback.detail)\n setNotedType(null)\n }, [submittedFeedback])\n\n return (\n <ScoreFeedbackCtx.Provider\n value={{\n thumbState,\n selectedPills,\n detailText,\n notedType,\n submittedFeedback,\n otherSelected,\n hasRequiredInput,\n handleThumbClick,\n togglePill,\n setDetailText: setDetailTextState,\n handleSubmit,\n editSubmitted,\n }}\n >\n {children}\n </ScoreFeedbackCtx.Provider>\n )\n}\n\nfunction Trigger({ className }: { className?: string }) {\n const { thumbState, notedType, submittedFeedback, handleThumbClick, editSubmitted } = useScoreFeedback()\n\n if (notedType || (submittedFeedback && !thumbState)) {\n return (\n <div className={cn(\"shrink-0\", className)}>\n <div className=\"flex items-center gap-1\">\n <Check className=\"w-3 h-3 text-emerald-500\" />\n <span className=\"text-[11px] text-muted-foreground\">\n {notedType\n ? notedType === \"up\" ? \"Noted\" : \"Recorded\"\n : submittedFeedback?.type === \"up\" ? \"Noted\" : \"Recorded\"}\n </span>\n </div>\n {submittedFeedback && (submittedFeedback.pills.length > 0 || submittedFeedback.detail) && (\n <button\n type=\"button\"\n onClick={editSubmitted}\n className=\"mt-1.5 w-full text-left space-y-1 group cursor-pointer\"\n >\n {submittedFeedback.pills.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {submittedFeedback.pills.map((p) => (\n <span\n key={p}\n className={cn(\n \"rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors group-hover:opacity-80\",\n submittedFeedback.type === \"up\"\n ? \"border-emerald-200/60 bg-emerald-50/50 text-emerald-700/70\"\n : \"border-red-200/60 bg-red-50/50 text-red-700/70\"\n )}\n >\n {p}\n </span>\n ))}\n </div>\n )}\n {submittedFeedback.detail && (\n <p className=\"text-[11px] text-muted-foreground/70 leading-snug group-hover:text-muted-foreground transition-colors\">{submittedFeedback.detail}</p>\n )}\n </button>\n )}\n </div>\n )\n }\n\n return (\n <div className={cn(\"flex gap-0.5 shrink-0\", className)}>\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"up\")}\n className={cn(\n \"p-1.5 rounded transition-colors\",\n thumbState === \"up\"\n ? \"bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400\"\n : \"hover:bg-muted text-muted-foreground hover:text-foreground\"\n )}\n >\n <ThumbsUp className=\"w-3.5 h-3.5\" fill={thumbState === \"up\" ? \"currentColor\" : \"none\"} />\n </button>\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"down\")}\n className={cn(\n \"p-1.5 rounded transition-colors\",\n thumbState === \"down\"\n ? \"bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400\"\n : \"hover:bg-muted text-muted-foreground hover:text-foreground\"\n )}\n >\n <ThumbsDown className=\"w-3.5 h-3.5\" fill={thumbState === \"down\" ? \"currentColor\" : \"none\"} />\n </button>\n </div>\n )\n}\n\nfunction Panel({ className }: { className?: string }) {\n const {\n thumbState,\n selectedPills,\n detailText,\n otherSelected,\n hasRequiredInput,\n togglePill,\n setDetailText,\n handleSubmit,\n } = useScoreFeedback()\n\n if (!thumbState) return null\n\n return (\n <div className={cn(\"overflow-hidden\", className)}>\n <div className=\"mt-4 pt-4 pb-1 space-y-3 border-t border-border/60\">\n <span className=\"text-[11px] font-bold text-muted-foreground/70 uppercase tracking-wider\">\n How&apos;s this score?\n </span>\n <div>\n <span className=\"text-xs text-muted-foreground mb-2 block font-medium\">\n {thumbState === \"up\" ? \"What was useful?\" : \"What\\u2019s the issue?\"}\n </span>\n <div className=\"flex flex-wrap gap-1.5\">\n {(thumbState === \"up\" ? positivePills : negativePills).map((pill) => (\n <button\n key={pill}\n type=\"button\"\n onClick={() => togglePill(pill)}\n className={cn(\n \"px-2.5 py-1 rounded-full text-[11px] font-medium border transition-colors\",\n selectedPills.includes(pill)\n ? thumbState === \"up\"\n ? \"bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800\"\n : \"bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800\"\n : \"bg-background text-muted-foreground border-border hover:bg-muted/50 hover:text-foreground\"\n )}\n >\n {pill}\n </button>\n ))}\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <input\n type=\"text\"\n value={detailText}\n onChange={(e) => setDetailText(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && hasRequiredInput) handleSubmit()\n }}\n placeholder={\n thumbState === \"up\"\n ? \"Tell us more (optional)\"\n : \"e.g., The risk factors are outdated\"\n }\n className=\"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 />\n {otherSelected && detailText.trim().length === 0 && (\n <span className=\"text-[10px] text-red-500\">\n Please describe when &ldquo;Other&rdquo; is selected\n </span>\n )}\n </div>\n\n <div className=\"flex items-center gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={!hasRequiredInput}\n className={cn(\n \"flex-1 py-1.5 rounded-md text-xs font-semibold transition-colors\",\n hasRequiredInput\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-muted text-muted-foreground cursor-not-allowed\"\n )}\n >\n Submit\n </button>\n </div>\n </div>\n </div>\n )\n}\n\nexport const ScoreFeedback = { Root, Trigger, Panel }\nexport { useScoreFeedback }\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/score-feedback.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown, Check } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\nconst positivePills = [\n \"Right timing\",\n \"Accurate data\",\n \"Good prospect fit\",\n \"Actionable\",\n]\n\nconst negativePills = [\n \"Bad timing\",\n \"Inaccurate data\",\n \"Wrong prospect\",\n \"Already handled\",\n \"Not actionable\",\n \"Other\",\n]\n\ninterface SubmittedScoreFeedback {\n type: \"up\" | \"down\"\n pills: string[]\n detail: string\n}\n\ninterface ScoreFeedbackState {\n thumbState: \"up\" | \"down\" | null\n selectedPills: string[]\n detailText: string\n notedType: \"up\" | \"down\" | null\n submittedFeedback: SubmittedScoreFeedback | null\n otherSelected: boolean\n hasRequiredInput: boolean\n handleThumbClick: (type: \"up\" | \"down\") => void\n togglePill: (pill: string) => void\n setDetailText: (text: string) => void\n handleSubmit: () => void\n editSubmitted: () => void\n}\n\nconst ScoreFeedbackCtx = React.createContext<ScoreFeedbackState | null>(null)\n\nfunction useScoreFeedback() {\n const ctx = React.useContext(ScoreFeedbackCtx)\n if (!ctx) throw new Error(\"Must be used within ScoreFeedback.Root\")\n return ctx\n}\n\ninterface RootProps {\n children: React.ReactNode\n onSubmitFeedback?: (type: \"up\" | \"down\", pills: string[], detail: string) => void\n initialFeedback?: { type: \"up\" | \"down\"; pills: string[]; detail: string } | null\n}\n\nfunction Root({ children, onSubmitFeedback, initialFeedback }: RootProps) {\n const [thumbState, setThumbState] = React.useState<\"up\" | \"down\" | null>(null)\n const [selectedPills, setSelectedPills] = React.useState<string[]>([])\n const [detailText, setDetailTextState] = React.useState(\"\")\n const [notedType, setNotedType] = React.useState<\"up\" | \"down\" | null>(null)\n const [submittedFeedback, setSubmittedFeedback] = React.useState<SubmittedScoreFeedback | null>(\n initialFeedback ?? null\n )\n\n // Sync submitted feedback when initialFeedback prop changes (e.g. async\n // detail load). Skip when the user has an in-progress edit (thumbState set).\n React.useEffect(() => {\n if (thumbState !== null) return\n setSubmittedFeedback(initialFeedback ?? null)\n }, [initialFeedback]) // eslint-disable-line react-hooks/exhaustive-deps -- intentionally omits thumbState to read it as a guard, not a trigger\n\n const otherSelected = selectedPills.includes(\"Other\")\n\n const hasRequiredInput =\n thumbState === \"down\"\n ? selectedPills.length > 0 && (!otherSelected || detailText.trim().length > 0)\n : selectedPills.length > 0 || detailText.trim().length > 0\n\n const togglePill = React.useCallback((pill: string) => {\n setSelectedPills((prev) =>\n prev.includes(pill) ? prev.filter((p) => p !== pill) : [...prev, pill]\n )\n }, [])\n\n const handleThumbClick = React.useCallback((type: \"up\" | \"down\") => {\n setThumbState((prev) => (prev === type ? null : type))\n setSelectedPills([])\n setDetailTextState(\"\")\n }, [])\n\n const handleSubmit = React.useCallback(() => {\n if (!thumbState) return\n onSubmitFeedback?.(thumbState, selectedPills, detailText)\n setSubmittedFeedback({ type: thumbState, pills: [...selectedPills], detail: detailText.trim() })\n setNotedType(thumbState)\n setThumbState(null)\n setSelectedPills([])\n setDetailTextState(\"\")\n setTimeout(() => setNotedType(null), 3000)\n }, [thumbState, selectedPills, detailText, onSubmitFeedback])\n\n const editSubmitted = React.useCallback(() => {\n if (!submittedFeedback) return\n setThumbState(submittedFeedback.type)\n setSelectedPills([...submittedFeedback.pills])\n setDetailTextState(submittedFeedback.detail)\n setNotedType(null)\n }, [submittedFeedback])\n\n return (\n <ScoreFeedbackCtx.Provider\n value={{\n thumbState,\n selectedPills,\n detailText,\n notedType,\n submittedFeedback,\n otherSelected,\n hasRequiredInput,\n handleThumbClick,\n togglePill,\n setDetailText: setDetailTextState,\n handleSubmit,\n editSubmitted,\n }}\n >\n {children}\n </ScoreFeedbackCtx.Provider>\n )\n}\n\nfunction Trigger({ className }: { className?: string }) {\n const { thumbState, notedType, submittedFeedback, handleThumbClick, editSubmitted } = useScoreFeedback()\n\n if (notedType || (submittedFeedback && !thumbState)) {\n return (\n <div className={cn(\"shrink-0\", className)}>\n <div className=\"flex items-center gap-1\">\n <Check className=\"w-3 h-3 text-emerald-500\" />\n <span className=\"text-[11px] text-muted-foreground\">\n {notedType\n ? notedType === \"up\" ? \"Noted\" : \"Recorded\"\n : submittedFeedback?.type === \"up\" ? \"Noted\" : \"Recorded\"}\n </span>\n </div>\n {submittedFeedback && (submittedFeedback.pills.length > 0 || submittedFeedback.detail) && (\n <button\n type=\"button\"\n onClick={editSubmitted}\n className=\"mt-1.5 w-full text-left space-y-1 group cursor-pointer\"\n >\n {submittedFeedback.pills.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {submittedFeedback.pills.map((p) => (\n <span\n key={p}\n className={cn(\n \"rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors group-hover:opacity-80\",\n submittedFeedback.type === \"up\"\n ? \"border-emerald-200/60 bg-emerald-50/50 text-emerald-700/70\"\n : \"border-red-200/60 bg-red-50/50 text-red-700/70\"\n )}\n >\n {p}\n </span>\n ))}\n </div>\n )}\n {submittedFeedback.detail && (\n <p className=\"text-[11px] text-muted-foreground/70 leading-snug group-hover:text-muted-foreground transition-colors\">{submittedFeedback.detail}</p>\n )}\n </button>\n )}\n </div>\n )\n }\n\n return (\n <div className={cn(\"flex gap-0.5 shrink-0\", className)}>\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"up\")}\n className={cn(\n \"p-1.5 rounded transition-colors\",\n thumbState === \"up\"\n ? \"bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400\"\n : \"hover:bg-muted text-muted-foreground hover:text-foreground\"\n )}\n >\n <ThumbsUp className=\"w-3.5 h-3.5\" fill={thumbState === \"up\" ? \"currentColor\" : \"none\"} />\n </button>\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"down\")}\n className={cn(\n \"p-1.5 rounded transition-colors\",\n thumbState === \"down\"\n ? \"bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400\"\n : \"hover:bg-muted text-muted-foreground hover:text-foreground\"\n )}\n >\n <ThumbsDown className=\"w-3.5 h-3.5\" fill={thumbState === \"down\" ? \"currentColor\" : \"none\"} />\n </button>\n </div>\n )\n}\n\nfunction Panel({ className }: { className?: string }) {\n const {\n thumbState,\n selectedPills,\n detailText,\n otherSelected,\n hasRequiredInput,\n togglePill,\n setDetailText,\n handleSubmit,\n } = useScoreFeedback()\n\n if (!thumbState) return null\n\n return (\n <div className={cn(\"overflow-hidden\", className)}>\n <div className=\"mt-4 pt-4 pb-1 space-y-3 border-t border-border/60\">\n <span className=\"text-[11px] font-bold text-muted-foreground/70 uppercase tracking-wider\">\n How&apos;s this score?\n </span>\n <div>\n <span className=\"text-xs text-muted-foreground mb-2 block font-medium\">\n {thumbState === \"up\" ? \"What was useful?\" : \"What\\u2019s the issue?\"}\n </span>\n <div className=\"flex flex-wrap gap-1.5\">\n {(thumbState === \"up\" ? positivePills : negativePills).map((pill) => (\n <button\n key={pill}\n type=\"button\"\n onClick={() => togglePill(pill)}\n className={cn(\n \"px-2.5 py-1 rounded-full text-[11px] font-medium border transition-colors\",\n selectedPills.includes(pill)\n ? thumbState === \"up\"\n ? \"bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800\"\n : \"bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800\"\n : \"bg-background text-muted-foreground border-border hover:bg-muted/50 hover:text-foreground\"\n )}\n >\n {pill}\n </button>\n ))}\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <input\n type=\"text\"\n value={detailText}\n onChange={(e) => setDetailText(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && hasRequiredInput) handleSubmit()\n }}\n placeholder={\n thumbState === \"up\"\n ? \"Tell us more (optional)\"\n : \"e.g., The risk factors are outdated\"\n }\n className=\"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 />\n {otherSelected && detailText.trim().length === 0 && (\n <span className=\"text-[10px] text-red-500\">\n Please describe when &ldquo;Other&rdquo; is selected\n </span>\n )}\n </div>\n\n <div className=\"flex items-center gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={!hasRequiredInput}\n className={cn(\n \"flex-1 py-1.5 rounded-md text-xs font-semibold transition-colors\",\n hasRequiredInput\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-muted text-muted-foreground cursor-not-allowed\"\n )}\n >\n Submit\n </button>\n </div>\n </div>\n </div>\n )\n}\n\nexport const ScoreFeedback = { Root, Trigger, Panel }\nexport { useScoreFeedback }\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -70,7 +70,7 @@ export { VolumeAnalysisChart, VolumeAnalysisChartProps, VolumeDataKey } from './
70
70
  export { MetricCardData, TopLineMetrics, TopLineMetricsProps } from './charts/top-line-metrics.js';
71
71
  export { PipelineFilterBreakdown, PipelineOverview, PipelineOverviewProps, PipelineStage, PipelineStageMetrics, PipelineStageTiming } from './charts/pipeline-overview.js';
72
72
  export { SankeyChart, SankeyData, SankeyDropOff, SankeyHoverCardData, SankeyLink, SankeyNode, SankeyStageMetrics } from './charts/sankey-chart.js';
73
- export { AccountFilterTab, AccountsViewConfig, AdminTab, AdminViewConfig, EntityPanelConfig, EntityPanelSection, InboxDetailSections, InboxViewConfig, InsightsCustomTab, InsightsViewConfig, PrototypeBrandConfig, PrototypeConfig, QueueItem, SignalScoreData, WorkQueueViewConfig } from './prototype/prototype-config.js';
73
+ export { AccountFilterTab, AccountsViewConfig, AdminTab, AdminViewConfig, EntityPanelConfig, EntityPanelSection, InboxDetailSections, InboxSortOption, InboxViewConfig, InsightsCustomTab, InsightsViewConfig, PrototypeBrandConfig, PrototypeConfig, QueueItem, SignalScoreData, WorkQueueViewConfig } from './prototype/prototype-config.js';
74
74
  export { PrototypeShell, PrototypeShellProps } from './prototype/prototype-shell.js';
75
75
  export { DetailView, DetailViewProps, PrototypeInboxView, PrototypeInboxViewProps } from './prototype/prototype-inbox-view.js';
76
76
  export { PrototypeInsightsView, PrototypeInsightsViewProps } from './prototype/prototype-insights-view.js';
@@ -1,4 +1,4 @@
1
- export { AccountFilterTab, AccountsViewConfig, AdminTab, AdminViewConfig, EntityPanelConfig, EntityPanelSection, InboxDetailSections, InboxViewConfig, InsightsCustomTab, InsightsViewConfig, PrototypeBrandConfig, PrototypeConfig, QueueItem, SignalScoreData, WorkQueueViewConfig } from './prototype-config.js';
1
+ export { AccountFilterTab, AccountsViewConfig, AdminTab, AdminViewConfig, EntityPanelConfig, EntityPanelSection, InboxDetailSections, InboxSortOption, InboxViewConfig, InsightsCustomTab, InsightsViewConfig, PrototypeBrandConfig, PrototypeConfig, QueueItem, SignalScoreData, WorkQueueViewConfig } from './prototype-config.js';
2
2
  export { PrototypeShell, PrototypeShellProps } from './prototype-shell.js';
3
3
  export { DetailView, DetailViewProps, PrototypeInboxView, PrototypeInboxViewProps } from './prototype-inbox-view.js';
4
4
  export { PrototypeInsightsView, PrototypeInsightsViewProps } from './prototype-insights-view.js';
@@ -47,6 +47,10 @@ interface SignalScoreData {
47
47
  detail: string;
48
48
  }>;
49
49
  }
50
+ interface InboxSortOption {
51
+ id: string;
52
+ label: string;
53
+ }
50
54
  interface InboxDetailSections {
51
55
  signalBrief?: boolean;
52
56
  suggestedActions?: boolean;
@@ -113,6 +117,12 @@ interface InboxViewConfig {
113
117
  };
114
118
  /** Render extra content at the end of the detail view, below the suggested actions section. */
115
119
  renderDetailExtra?: (item: QueueItem) => React.ReactNode;
120
+ /** Sort options for the inbox. When provided, a sort dropdown is rendered in the split view toolbar. */
121
+ sortOptions?: InboxSortOption[];
122
+ /** Currently active sort option id. */
123
+ activeSortId?: string;
124
+ /** Callback when the user changes the sort option. */
125
+ onSortChange?: (sortId: string) => void;
116
126
  }
117
127
  interface InsightsCustomTab {
118
128
  id: string;
@@ -260,4 +270,4 @@ interface PrototypeConfig {
260
270
  navigableViews?: string[];
261
271
  }
262
272
 
263
- export type { AccountFilterTab, AccountsViewConfig, AdminTab, AdminViewConfig, EntityPanelConfig, EntityPanelSection, InboxDetailSections, InboxViewConfig, InsightsCustomTab, InsightsViewConfig, PrototypeBrandConfig, PrototypeConfig, QueueItem, SignalScoreData, WorkQueueViewConfig };
273
+ export type { AccountFilterTab, AccountsViewConfig, AdminTab, AdminViewConfig, EntityPanelConfig, EntityPanelSection, InboxDetailSections, InboxSortOption, InboxViewConfig, InsightsCustomTab, InsightsViewConfig, PrototypeBrandConfig, PrototypeConfig, QueueItem, SignalScoreData, WorkQueueViewConfig };
@@ -45,6 +45,6 @@ interface DetailViewProps {
45
45
  onScoreFeedback?: (type: "up" | "down", pills: string[], detail: string) => void;
46
46
  }
47
47
  declare function DetailView({ item, sections, getSignalScore, buildSuggestedActions, buildSourceItems, getTimelineEvents, accountContacts, emailSignature, iconMap, onOpenEntityPanel, onOpenRecentActivity, onSuggestedActionFeedback: _onSuggestedActionFeedback, onSignalApprove, getSignalApprovalState, signalLabels, hideApproveButton, signalBriefCopy, renderDetailExtra, onScoreFeedback, }: DetailViewProps): React.JSX.Element;
48
- declare function PrototypeInboxView({ items, filterCategories, detailSections, accountContacts, buildAccountContacts, emailSignature, buildSuggestedActions: buildSuggestedActionsProp, buildSourceItems: buildSourceItemsProp, getSignalScore: getSignalScoreProp, getTimelineEvents, iconMap, hideToolbarActions, hideHoverActions, onSuggestedActionFeedback, onScoreFeedback, headerActions, onOpenEntityPanel, onOpenRecentActivity, onItemSelect, defaultViewMode, buildEntityChips, quickFilterTabs, hideAccountsButton, accountDetailsLabel, onSignalApprove, getSignalApprovalState, signalLabels, hideApproveButton, signalBriefCopy, renderDetailExtra, }: PrototypeInboxViewProps): React.JSX.Element;
48
+ declare function PrototypeInboxView({ items, filterCategories, detailSections, accountContacts, buildAccountContacts, emailSignature, buildSuggestedActions: buildSuggestedActionsProp, buildSourceItems: buildSourceItemsProp, getSignalScore: getSignalScoreProp, getTimelineEvents, iconMap, hideToolbarActions, hideHoverActions, onSuggestedActionFeedback, onScoreFeedback, headerActions, onOpenEntityPanel, onOpenRecentActivity, onItemSelect, defaultViewMode, buildEntityChips, quickFilterTabs, hideAccountsButton, accountDetailsLabel, onSignalApprove, getSignalApprovalState, signalLabels, hideApproveButton, signalBriefCopy, renderDetailExtra, sortOptions, activeSortId, onSortChange, }: PrototypeInboxViewProps): React.JSX.Element;
49
49
 
50
50
  export { DetailView, type DetailViewProps, PrototypeInboxView, type PrototypeInboxViewProps };
@@ -24,6 +24,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
24
24
  import * as React from "react";
25
25
  import {
26
26
  ArrowLeft,
27
+ ArrowUpDown,
27
28
  ChevronDown,
28
29
  ChevronRight,
29
30
  Filter,
@@ -38,6 +39,13 @@ import {
38
39
  Square,
39
40
  Tag
40
41
  } from "lucide-react";
42
+ import {
43
+ DropdownMenu,
44
+ DropdownMenuContent,
45
+ DropdownMenuRadioGroup,
46
+ DropdownMenuRadioItem,
47
+ DropdownMenuTrigger
48
+ } from "../components/dropdown-menu.js";
41
49
  import { Button } from "../components/button.js";
42
50
  import { Badge } from "../components/badge.js";
43
51
  import { Input } from "../components/input.js";
@@ -357,9 +365,12 @@ function PrototypeInboxView({
357
365
  signalLabels,
358
366
  hideApproveButton,
359
367
  signalBriefCopy,
360
- renderDetailExtra
368
+ renderDetailExtra,
369
+ sortOptions,
370
+ activeSortId,
371
+ onSortChange
361
372
  }) {
362
- var _a;
373
+ var _a, _b, _c;
363
374
  const [inboxViewMode, setInboxViewMode] = React.useState(
364
375
  defaultViewMode === "list" ? "list" : defaultViewMode === "split" ? "inbox" : "inbox"
365
376
  );
@@ -481,7 +492,7 @@ function PrototypeInboxView({
481
492
  }, [filteredItems]);
482
493
  const renderInboxRow = React.useCallback(
483
494
  (item) => /* @__PURE__ */ jsxs(Fragment, { children: [
484
- /* @__PURE__ */ jsx("span", { className: `h-2 w-2 shrink-0 rounded-full ${item.statusColor === "red" ? "bg-[#f43f5e]" : "bg-[#3b82f6]"}` }),
495
+ /* @__PURE__ */ jsx("span", { className: `h-2 w-2 shrink-0 rounded-full ${item.statusColor === "red" ? "bg-[#f43f5e]" : item.statusColor === "yellow" || item.statusColor === "amber" || item.statusColor === "orange" ? "bg-[#eab308]" : item.statusColor === "green" ? "bg-[#22c55e]" : "bg-[#9ca3af]"}` }),
485
496
  /* @__PURE__ */ jsx("span", { className: "w-[80px] shrink-0 font-mono text-xs text-muted-foreground/80", children: item.id }),
486
497
  /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground whitespace-nowrap", children: item.tag1 }),
487
498
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground", children: item.title }),
@@ -632,6 +643,14 @@ function PrototypeInboxView({
632
643
  }
633
644
  )
634
645
  ] }),
646
+ sortOptions && sortOptions.length > 0 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
647
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8 text-xs font-medium rounded-md shadow-none gap-1.5", children: [
648
+ /* @__PURE__ */ jsx(ArrowUpDown, { className: "w-3.5 h-3.5" }),
649
+ (_c = (_b = sortOptions.find((o) => o.id === activeSortId)) == null ? void 0 : _b.label) != null ? _c : "Sort",
650
+ /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3" })
651
+ ] }) }),
652
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: activeSortId != null ? activeSortId : "", onValueChange: (val) => onSortChange == null ? void 0 : onSortChange(val), children: sortOptions.map((option) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, { value: option.id, children: option.label }, option.id)) }) })
653
+ ] }),
635
654
  !hideAccountsButton && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8 text-xs font-medium rounded-md shadow-none", children: [
636
655
  /* @__PURE__ */ jsx(Building, { className: "w-3.5 h-3.5 mr-1.5" }),
637
656
  " ",
@@ -685,7 +704,7 @@ function PrototypeInboxView({
685
704
  /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-[10px] font-medium text-muted-foreground/80", children: item.time })
686
705
  ] }),
687
706
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 mt-2", children: [
688
- /* @__PURE__ */ jsx("span", { className: `w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${item.statusColor === "red" ? "bg-[#f43f5e]" : "bg-[#3b82f6]"}` }),
707
+ /* @__PURE__ */ jsx("span", { className: `w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${item.statusColor === "red" ? "bg-[#f43f5e]" : item.statusColor === "yellow" || item.statusColor === "amber" || item.statusColor === "orange" ? "bg-[#eab308]" : item.statusColor === "green" ? "bg-[#22c55e]" : "bg-[#9ca3af]"}` }),
689
708
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground leading-tight", children: item.details })
690
709
  ] }),
691
710
  buildEntityChips && (() => {