@handled-ai/design-system 0.18.38 → 0.18.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts/chart.d.ts +1 -1
- package/dist/components/draft-feedback-inline.d.ts +1 -1
- package/dist/components/draft-feedback-inline.js +10 -10
- package/dist/components/draft-feedback-inline.js.map +1 -1
- package/dist/components/email-recipient-field.js +107 -166
- package/dist/components/email-recipient-field.js.map +1 -1
- package/dist/components/related-record-action-card.d.ts +19 -0
- package/dist/components/related-record-action-card.js +150 -0
- package/dist/components/related-record-action-card.js.map +1 -0
- package/dist/components/score-feedback.js +6 -6
- package/dist/components/score-feedback.js.map +1 -1
- package/dist/components/suggested-actions.js +17 -5
- package/dist/components/suggested-actions.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/draft-feedback-inline.test.tsx +72 -0
- package/src/components/__tests__/email-recipient-field.test.tsx +116 -1
- package/src/components/__tests__/related-record-action-card.test.tsx +123 -0
- package/src/components/__tests__/suggested-actions-feedback-header.test.tsx +86 -0
- package/src/components/draft-feedback-inline.tsx +13 -13
- package/src/components/email-recipient-field.tsx +55 -101
- package/src/components/related-record-action-card.tsx +169 -0
- package/src/components/score-feedback.tsx +7 -7
- package/src/components/suggested-actions.tsx +19 -5
- package/src/index.ts +1 -0
package/dist/charts/chart.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ declare function ChartTooltipContent({ active, payload, className, indicator, hi
|
|
|
48
48
|
labelClassName?: string;
|
|
49
49
|
color?: string;
|
|
50
50
|
}): React.JSX.Element | null;
|
|
51
|
-
declare const ChartLegend:
|
|
51
|
+
declare const ChartLegend: typeof RechartsPrimitive.Legend;
|
|
52
52
|
type LegendPayloadItem = {
|
|
53
53
|
value?: string;
|
|
54
54
|
dataKey?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
3
|
interface DraftFeedbackInlineProps {
|
|
4
|
-
initialDirection?:
|
|
4
|
+
initialDirection?: "up" | "down" | null;
|
|
5
5
|
onRegenerateRequest?: (pills: string[], detail: string) => void;
|
|
6
6
|
onSubmitFeedback?: (type: "up" | "down", pills: string[], detail: string) => void;
|
|
7
7
|
onDiscardRequest?: (pills: string[], detail: string) => void;
|
|
@@ -53,7 +53,7 @@ function DraftFeedbackInline({
|
|
|
53
53
|
}, [thumbState, selectedPills, detailText, onDiscardRequest]);
|
|
54
54
|
if (noted) {
|
|
55
55
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 py-1 animate-in fade-in slide-in-from-top-1 duration-200", children: [
|
|
56
|
-
/* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5 text-
|
|
56
|
+
/* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5 text-foreground" }),
|
|
57
57
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Feedback recorded" })
|
|
58
58
|
] });
|
|
59
59
|
}
|
|
@@ -63,7 +63,7 @@ function DraftFeedbackInline({
|
|
|
63
63
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-indigo-600 dark:text-indigo-400", children: "Regenerating draft..." })
|
|
64
64
|
] }) });
|
|
65
65
|
}
|
|
66
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
67
67
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
68
68
|
/* @__PURE__ */ jsx("span", { className: "text-sm text-foreground font-medium", children: "How's this draft?" }),
|
|
69
69
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
|
|
@@ -75,8 +75,8 @@ function DraftFeedbackInline({
|
|
|
75
75
|
setSelectedPills([]);
|
|
76
76
|
setDetailText("");
|
|
77
77
|
},
|
|
78
|
-
className: `p-1.5 rounded transition-colors ${thumbState === "up" ? "bg-
|
|
79
|
-
children: /* @__PURE__ */ jsx(ThumbsUp, { className: "w-4 h-4"
|
|
78
|
+
className: `p-1.5 rounded transition-colors ${thumbState === "up" ? "bg-muted text-foreground" : "hover:bg-muted text-muted-foreground hover:text-foreground"}`,
|
|
79
|
+
children: /* @__PURE__ */ jsx(ThumbsUp, { className: "w-4 h-4" })
|
|
80
80
|
}
|
|
81
81
|
),
|
|
82
82
|
/* @__PURE__ */ jsx(
|
|
@@ -87,20 +87,20 @@ function DraftFeedbackInline({
|
|
|
87
87
|
setSelectedPills([]);
|
|
88
88
|
setDetailText("");
|
|
89
89
|
},
|
|
90
|
-
className: `p-1.5 rounded transition-colors ${thumbState === "down" ? "bg-
|
|
91
|
-
children: /* @__PURE__ */ jsx(ThumbsDown, { className: "w-4 h-4"
|
|
90
|
+
className: `p-1.5 rounded transition-colors ${thumbState === "down" ? "bg-muted text-foreground" : "hover:bg-muted text-muted-foreground hover:text-foreground"}`,
|
|
91
|
+
children: /* @__PURE__ */ jsx(ThumbsDown, { className: "w-4 h-4" })
|
|
92
92
|
}
|
|
93
93
|
)
|
|
94
94
|
] })
|
|
95
95
|
] }),
|
|
96
|
-
thumbState && /* @__PURE__ */ jsxs("div", { className: "pt-
|
|
96
|
+
thumbState && /* @__PURE__ */ jsxs("div", { className: "pt-2 space-y-2.5 animate-in fade-in slide-in-from-top-2 duration-200", children: [
|
|
97
97
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
98
98
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mb-2 block font-medium", children: thumbState === "up" ? "What worked well?" : "What needs improvement?" }),
|
|
99
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-
|
|
99
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: (thumbState === "up" ? positivePills : negativePills).map((pill) => /* @__PURE__ */ jsx(
|
|
100
100
|
"button",
|
|
101
101
|
{
|
|
102
102
|
onClick: () => togglePill(pill),
|
|
103
|
-
className: `px-
|
|
103
|
+
className: `px-3 py-1.5 rounded-full text-[11px] font-medium border transition-colors ${selectedPills.includes(pill) ? thumbState === "up" ? "bg-muted text-foreground border-border" : "bg-red-50 text-red-700 border-red-200 dark:bg-red-950/30 dark:text-red-300 dark:border-red-800" : "bg-background text-muted-foreground border-border hover:bg-muted/50 hover:text-foreground"}`,
|
|
104
104
|
children: pill
|
|
105
105
|
},
|
|
106
106
|
pill
|
|
@@ -115,7 +115,7 @@ function DraftFeedbackInline({
|
|
|
115
115
|
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-indigo-500/50 focus:border-indigo-500/50 resize-none min-h-[60px]"
|
|
116
116
|
}
|
|
117
117
|
),
|
|
118
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 pt-
|
|
118
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2.5 pt-2", children: thumbState === "down" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
119
119
|
/* @__PURE__ */ jsxs(
|
|
120
120
|
"button",
|
|
121
121
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/draft-feedback-inline.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ThumbsUp,\n ThumbsDown,\n Check,\n RefreshCw,\n} from \"lucide-react\"\n\n// ---------------------------------------------------------------------------\n// DraftFeedbackInline\n// ---------------------------------------------------------------------------\n\nconst positivePills = [\"Tone\", \"Personalization\", \"Length\", \"CTA\", \"Other\"]\nconst negativePills = [\"Too formal\", \"Too casual\", \"Too long\", \"Missing context\", \"Wrong angle\", \"Factual error\", \"Other\"]\n\nexport interface DraftFeedbackInlineProps {\n initialDirection?: 'up' | 'down' | null\n onRegenerateRequest?: (pills: string[], detail: string) => void\n onSubmitFeedback?: (type: \"up\" | \"down\", pills: string[], detail: string) => void\n onDiscardRequest?: (pills: string[], detail: string) => void\n}\n\nexport function DraftFeedbackInline({\n initialDirection,\n onRegenerateRequest,\n onSubmitFeedback,\n onDiscardRequest,\n}: DraftFeedbackInlineProps) {\n const [thumbState, setThumbState] = React.useState<\"up\" | \"down\" | null>(initialDirection ?? null)\n const [selectedPills, setSelectedPills] = React.useState<string[]>([])\n const [detailText, setDetailText] = React.useState(\"\")\n const [noted, setNoted] = React.useState(false)\n const [regenerated, setRegenerated] = React.useState(false)\n\n const togglePill = React.useCallback((pill: string) => {\n setSelectedPills((prev) => (prev.includes(pill) ? prev.filter((p) => p !== pill) : [...prev, pill]))\n }, [])\n\n const handleSubmit = React.useCallback(() => {\n if (!thumbState) return\n onSubmitFeedback?.(thumbState, selectedPills, detailText)\n setNoted(true)\n setTimeout(() => {\n setThumbState(null)\n setSelectedPills([])\n setDetailText(\"\")\n setNoted(false)\n }, 3000)\n }, [thumbState, selectedPills, detailText, onSubmitFeedback])\n\n const handleRegenerate = React.useCallback(() => {\n if (!thumbState) return\n onRegenerateRequest?.(selectedPills, detailText)\n setRegenerated(true)\n setTimeout(() => {\n setThumbState(null)\n setSelectedPills([])\n setDetailText(\"\")\n setRegenerated(false)\n }, 3000)\n }, [thumbState, selectedPills, detailText, onRegenerateRequest])\n\n const handleDiscard = React.useCallback(() => {\n if (!thumbState) return\n onDiscardRequest?.(selectedPills, detailText)\n }, [thumbState, selectedPills, detailText, onDiscardRequest])\n\n if (noted) {\n return (\n <div className=\"flex items-center gap-1.5 py-1 animate-in fade-in slide-in-from-top-1 duration-200\">\n <Check className=\"w-3.5 h-3.5 text-emerald-500\" />\n <span className=\"text-xs text-muted-foreground\">Feedback recorded</span>\n </div>\n )\n }\n\n if (regenerated) {\n return (\n <div className=\"py-2 animate-in fade-in slide-in-from-top-1 duration-200\">\n <div className=\"flex items-center gap-2 px-3 py-2 rounded-md bg-indigo-50 dark:bg-indigo-950/30 border border-indigo-200 dark:border-indigo-800\">\n <RefreshCw className=\"w-3 h-3 text-indigo-500 animate-spin\" />\n <span className=\"text-xs font-medium text-indigo-600 dark:text-indigo-400\">Regenerating draft...</span>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-0\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-foreground font-medium\">How's this draft?</span>\n <div className=\"flex gap-1\">\n <button\n onClick={() => {\n setThumbState(thumbState === \"up\" ? null : \"up\")\n setSelectedPills([])\n setDetailText(\"\")\n }}\n className={`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-4 h-4\" fill={thumbState === \"up\" ? \"currentColor\" : \"none\"} />\n </button>\n <button\n onClick={() => {\n setThumbState(thumbState === \"down\" ? null : \"down\")\n setSelectedPills([])\n setDetailText(\"\")\n }}\n className={`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-4 h-4\" fill={thumbState === \"down\" ? \"currentColor\" : \"none\"} />\n </button>\n </div>\n </div>\n\n {thumbState && (\n <div className=\"pt-3 space-y-3 animate-in fade-in slide-in-from-top-2 duration-200\">\n <div>\n <span className=\"text-xs text-muted-foreground mb-2 block font-medium\">\n {thumbState === \"up\" ? \"What worked well?\" : \"What needs improvement?\"}\n </span>\n <div className=\"flex flex-wrap gap-1.5\">\n {(thumbState === \"up\" ? positivePills : negativePills).map((pill) => (\n <button\n key={pill}\n onClick={() => togglePill(pill)}\n className={`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 <textarea\n value={detailText}\n onChange={(e) => setDetailText(e.target.value)}\n placeholder={thumbState === \"up\" ? \"Add specific praise (optional)...\" : \"Provide specific instructions (optional)...\"}\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-indigo-500/50 focus:border-indigo-500/50 resize-none min-h-[60px]\"\n />\n\n <div className=\"flex items-center gap-2 pt-1\">\n {thumbState === \"down\" ? (\n <>\n <button\n onClick={handleRegenerate}\n disabled={selectedPills.length === 0 && detailText.length === 0}\n className={`flex-1 py-1.5 rounded-md text-xs font-semibold transition-colors flex items-center justify-center gap-1.5 ${\n selectedPills.length > 0 || detailText.length > 0\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-muted text-muted-foreground cursor-not-allowed\"\n }`}\n >\n <RefreshCw className=\"w-3 h-3\" />\n Regenerate draft\n </button>\n <button\n onClick={handleDiscard}\n className=\"flex-1 py-1.5 rounded-md text-xs font-medium transition-colors border bg-background text-foreground border-border hover:bg-muted/50 flex items-center justify-center gap-1.5\"\n >\n Discard draft\n </button>\n </>\n ) : (\n <button\n onClick={handleSubmit}\n className=\"flex-1 py-1.5 rounded-md text-xs font-semibold transition-colors bg-foreground text-background hover:bg-foreground/90 border-transparent\"\n >\n Submit feedback\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/draft-feedback-inline.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ThumbsUp,\n ThumbsDown,\n Check,\n RefreshCw,\n} from \"lucide-react\"\n\n// ---------------------------------------------------------------------------\n// DraftFeedbackInline\n// ---------------------------------------------------------------------------\n\nconst positivePills = [\"Tone\", \"Personalization\", \"Length\", \"CTA\", \"Other\"]\nconst negativePills = [\"Too formal\", \"Too casual\", \"Too long\", \"Missing context\", \"Wrong angle\", \"Factual error\", \"Other\"]\n\nexport interface DraftFeedbackInlineProps {\n initialDirection?: \"up\" | \"down\" | null\n onRegenerateRequest?: (pills: string[], detail: string) => void\n onSubmitFeedback?: (type: \"up\" | \"down\", pills: string[], detail: string) => void\n onDiscardRequest?: (pills: string[], detail: string) => void\n}\n\nexport function DraftFeedbackInline({\n initialDirection,\n onRegenerateRequest,\n onSubmitFeedback,\n onDiscardRequest,\n}: DraftFeedbackInlineProps) {\n const [thumbState, setThumbState] = React.useState<\"up\" | \"down\" | null>(initialDirection ?? null)\n const [selectedPills, setSelectedPills] = React.useState<string[]>([])\n const [detailText, setDetailText] = React.useState(\"\")\n const [noted, setNoted] = React.useState(false)\n const [regenerated, setRegenerated] = React.useState(false)\n\n const togglePill = React.useCallback((pill: string) => {\n setSelectedPills((prev) => (prev.includes(pill) ? prev.filter((p) => p !== pill) : [...prev, pill]))\n }, [])\n\n const handleSubmit = React.useCallback(() => {\n if (!thumbState) return\n onSubmitFeedback?.(thumbState, selectedPills, detailText)\n setNoted(true)\n setTimeout(() => {\n setThumbState(null)\n setSelectedPills([])\n setDetailText(\"\")\n setNoted(false)\n }, 3000)\n }, [thumbState, selectedPills, detailText, onSubmitFeedback])\n\n const handleRegenerate = React.useCallback(() => {\n if (!thumbState) return\n onRegenerateRequest?.(selectedPills, detailText)\n setRegenerated(true)\n setTimeout(() => {\n setThumbState(null)\n setSelectedPills([])\n setDetailText(\"\")\n setRegenerated(false)\n }, 3000)\n }, [thumbState, selectedPills, detailText, onRegenerateRequest])\n\n const handleDiscard = React.useCallback(() => {\n if (!thumbState) return\n onDiscardRequest?.(selectedPills, detailText)\n }, [thumbState, selectedPills, detailText, onDiscardRequest])\n\n if (noted) {\n return (\n <div className=\"flex items-center gap-1.5 py-1 animate-in fade-in slide-in-from-top-1 duration-200\">\n <Check className=\"w-3.5 h-3.5 text-foreground\" />\n <span className=\"text-xs text-muted-foreground\">Feedback recorded</span>\n </div>\n )\n }\n\n if (regenerated) {\n return (\n <div className=\"py-2 animate-in fade-in slide-in-from-top-1 duration-200\">\n <div className=\"flex items-center gap-2 px-3 py-2 rounded-md bg-indigo-50 dark:bg-indigo-950/30 border border-indigo-200 dark:border-indigo-800\">\n <RefreshCw className=\"w-3 h-3 text-indigo-500 animate-spin\" />\n <span className=\"text-xs font-medium text-indigo-600 dark:text-indigo-400\">Regenerating draft...</span>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-foreground font-medium\">How's this draft?</span>\n <div className=\"flex gap-1\">\n <button\n onClick={() => {\n setThumbState(thumbState === \"up\" ? null : \"up\")\n setSelectedPills([])\n setDetailText(\"\")\n }}\n className={`p-1.5 rounded transition-colors ${\n thumbState === \"up\"\n ? \"bg-muted text-foreground\"\n : \"hover:bg-muted text-muted-foreground hover:text-foreground\"\n }`}\n >\n <ThumbsUp className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => {\n setThumbState(thumbState === \"down\" ? null : \"down\")\n setSelectedPills([])\n setDetailText(\"\")\n }}\n className={`p-1.5 rounded transition-colors ${\n thumbState === \"down\"\n ? \"bg-muted text-foreground\"\n : \"hover:bg-muted text-muted-foreground hover:text-foreground\"\n }`}\n >\n <ThumbsDown className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n\n {thumbState && (\n <div className=\"pt-2 space-y-2.5 animate-in fade-in slide-in-from-top-2 duration-200\">\n <div>\n <span className=\"text-xs text-muted-foreground mb-2 block font-medium\">\n {thumbState === \"up\" ? \"What worked well?\" : \"What needs improvement?\"}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(thumbState === \"up\" ? positivePills : negativePills).map((pill) => (\n <button\n key={pill}\n onClick={() => togglePill(pill)}\n className={`px-3 py-1.5 rounded-full text-[11px] font-medium border transition-colors ${\n selectedPills.includes(pill)\n ? thumbState === \"up\"\n ? \"bg-muted text-foreground border-border\"\n : \"bg-red-50 text-red-700 border-red-200 dark:bg-red-950/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 <textarea\n value={detailText}\n onChange={(e) => setDetailText(e.target.value)}\n placeholder={thumbState === \"up\" ? \"Add specific praise (optional)...\" : \"Provide specific instructions (optional)...\"}\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-indigo-500/50 focus:border-indigo-500/50 resize-none min-h-[60px]\"\n />\n\n <div className=\"flex items-center gap-2.5 pt-2\">\n {thumbState === \"down\" ? (\n <>\n <button\n onClick={handleRegenerate}\n disabled={selectedPills.length === 0 && detailText.length === 0}\n className={`flex-1 py-1.5 rounded-md text-xs font-semibold transition-colors flex items-center justify-center gap-1.5 ${\n selectedPills.length > 0 || detailText.length > 0\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-muted text-muted-foreground cursor-not-allowed\"\n }`}\n >\n <RefreshCw className=\"w-3 h-3\" />\n Regenerate draft\n </button>\n <button\n onClick={handleDiscard}\n className=\"flex-1 py-1.5 rounded-md text-xs font-medium transition-colors border bg-background text-foreground border-border hover:bg-muted/50 flex items-center justify-center gap-1.5\"\n >\n Discard draft\n </button>\n </>\n ) : (\n <button\n onClick={handleSubmit}\n className=\"flex-1 py-1.5 rounded-md text-xs font-semibold transition-colors bg-foreground text-background hover:bg-foreground/90 border-transparent\"\n >\n Submit feedback\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -20,9 +20,9 @@ var __spreadValues = (a, b) => {
|
|
|
20
20
|
return a;
|
|
21
21
|
};
|
|
22
22
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
23
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
24
24
|
import * as React from "react";
|
|
25
|
-
import {
|
|
25
|
+
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
26
26
|
import {
|
|
27
27
|
Check,
|
|
28
28
|
ChevronDown,
|
|
@@ -90,70 +90,13 @@ function RecipientChipPill({
|
|
|
90
90
|
)
|
|
91
91
|
] });
|
|
92
92
|
}
|
|
93
|
-
function
|
|
94
|
-
triggerRef,
|
|
93
|
+
function ContactPickerContents({
|
|
95
94
|
contacts,
|
|
96
95
|
addedEmails,
|
|
97
96
|
onSelect,
|
|
98
|
-
onAddEmail
|
|
99
|
-
onClose
|
|
97
|
+
onAddEmail
|
|
100
98
|
}) {
|
|
101
|
-
const containerRef = React.useRef(null);
|
|
102
|
-
const searchRef = React.useRef(null);
|
|
103
99
|
const [query, setQuery] = React.useState("");
|
|
104
|
-
const [style, setStyle] = React.useState({
|
|
105
|
-
position: "fixed",
|
|
106
|
-
top: -9999,
|
|
107
|
-
left: -9999
|
|
108
|
-
});
|
|
109
|
-
React.useEffect(() => {
|
|
110
|
-
const trigger = triggerRef.current;
|
|
111
|
-
if (!trigger) return;
|
|
112
|
-
const rect = trigger.getBoundingClientRect();
|
|
113
|
-
const width = Math.min(448, window.innerWidth - 32);
|
|
114
|
-
let left = rect.left;
|
|
115
|
-
if (left + width > window.innerWidth - 16) {
|
|
116
|
-
left = window.innerWidth - 16 - width;
|
|
117
|
-
}
|
|
118
|
-
if (left < 16) left = 16;
|
|
119
|
-
const popoverHeight = 280;
|
|
120
|
-
const spaceBelow = window.innerHeight - rect.bottom - 4;
|
|
121
|
-
const spaceAbove = rect.top - 4;
|
|
122
|
-
const placeAbove = spaceBelow < popoverHeight && spaceAbove > spaceBelow;
|
|
123
|
-
let top = placeAbove ? rect.top - popoverHeight - 4 : rect.bottom + 4;
|
|
124
|
-
if (top < 16) top = 16;
|
|
125
|
-
setStyle({ position: "fixed", top, left, width });
|
|
126
|
-
}, [triggerRef]);
|
|
127
|
-
React.useEffect(() => {
|
|
128
|
-
var _a;
|
|
129
|
-
(_a = searchRef.current) == null ? void 0 : _a.focus();
|
|
130
|
-
const trigger = triggerRef.current;
|
|
131
|
-
return () => {
|
|
132
|
-
if (trigger && typeof trigger.focus === "function") {
|
|
133
|
-
trigger.focus();
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}, [triggerRef]);
|
|
137
|
-
React.useEffect(() => {
|
|
138
|
-
function handleMouseDown(event) {
|
|
139
|
-
var _a;
|
|
140
|
-
if (containerRef.current && !containerRef.current.contains(event.target) && !((_a = triggerRef.current) == null ? void 0 : _a.contains(event.target))) {
|
|
141
|
-
onClose();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function handleKeyDown2(event) {
|
|
145
|
-
if (event.key === "Escape") {
|
|
146
|
-
event.stopPropagation();
|
|
147
|
-
onClose();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
document.addEventListener("mousedown", handleMouseDown);
|
|
151
|
-
document.addEventListener("keydown", handleKeyDown2);
|
|
152
|
-
return () => {
|
|
153
|
-
document.removeEventListener("mousedown", handleMouseDown);
|
|
154
|
-
document.removeEventListener("keydown", handleKeyDown2);
|
|
155
|
-
};
|
|
156
|
-
}, [onClose, triggerRef]);
|
|
157
100
|
const normalizedQuery = query.trim().toLowerCase();
|
|
158
101
|
const filtered = normalizedQuery ? contacts.filter((contact) => {
|
|
159
102
|
var _a;
|
|
@@ -168,82 +111,73 @@ function ContactPickerPopover({
|
|
|
168
111
|
setQuery("");
|
|
169
112
|
}
|
|
170
113
|
}
|
|
171
|
-
return
|
|
172
|
-
/* @__PURE__ */ jsxs(
|
|
173
|
-
"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
114
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
115
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2.5 border-b border-border/50", children: [
|
|
116
|
+
/* @__PURE__ */ jsx(Search, { className: "size-4 text-muted-foreground shrink-0" }),
|
|
117
|
+
/* @__PURE__ */ jsx(
|
|
118
|
+
"input",
|
|
119
|
+
{
|
|
120
|
+
autoFocus: true,
|
|
121
|
+
value: query,
|
|
122
|
+
onChange: (event) => setQuery(event.target.value),
|
|
123
|
+
onKeyDown: handleKeyDown,
|
|
124
|
+
className: "flex-1 text-[13px] bg-transparent outline-none",
|
|
125
|
+
placeholder: "Search contacts..."
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
] }),
|
|
129
|
+
/* @__PURE__ */ jsx("div", { role: "listbox", className: "max-h-[208px] overflow-y-auto p-1", children: contacts.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "px-3 py-5 text-center text-[13px] text-muted-foreground", children: [
|
|
130
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-foreground/80", children: "No contacts for this account" }),
|
|
131
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1", children: "Type an email address above and press Enter to add a recipient." })
|
|
132
|
+
] }) : filtered.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "px-3 py-4 text-center text-[13px] text-muted-foreground", children: [
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
134
|
+
"No contact matches \u2018",
|
|
135
|
+
query,
|
|
136
|
+
"\u2019."
|
|
137
|
+
] }),
|
|
138
|
+
queryIsEmail ? /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [
|
|
139
|
+
"Press Enter to add ",
|
|
140
|
+
query,
|
|
141
|
+
"."
|
|
142
|
+
] }) : null
|
|
143
|
+
] }) : filtered.map((contact, index) => {
|
|
144
|
+
const email = contactEmail(contact);
|
|
145
|
+
const noEmail = !email || !isValidEmail(email);
|
|
146
|
+
const alreadyAdded = email ? addedEmails.has(email.toLowerCase()) : false;
|
|
147
|
+
const disabled = noEmail || alreadyAdded;
|
|
148
|
+
return /* @__PURE__ */ jsxs(
|
|
149
|
+
"div",
|
|
150
|
+
{
|
|
151
|
+
role: "option",
|
|
152
|
+
"aria-selected": false,
|
|
153
|
+
"aria-disabled": disabled,
|
|
154
|
+
onClick: () => {
|
|
155
|
+
if (!disabled) onSelect(contact);
|
|
156
|
+
},
|
|
157
|
+
className: cn(
|
|
158
|
+
"flex items-center gap-2.5 px-2 py-1.5 rounded-md cursor-pointer hover:bg-muted/60",
|
|
159
|
+
disabled && "opacity-45 pointer-events-none"
|
|
160
|
+
),
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ jsx("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-[7px] bg-muted text-[11px] font-medium text-muted-foreground", children: getInitials(contact.name, email != null ? email : "?") }),
|
|
163
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
164
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
165
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] font-medium text-foreground", children: contact.name }),
|
|
166
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: contact.role })
|
|
167
|
+
] }),
|
|
168
|
+
email ? /* @__PURE__ */ jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: email }) : null
|
|
199
169
|
] }),
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"div",
|
|
212
|
-
{
|
|
213
|
-
role: "option",
|
|
214
|
-
"aria-selected": false,
|
|
215
|
-
"aria-disabled": disabled,
|
|
216
|
-
onClick: () => {
|
|
217
|
-
if (!disabled) onSelect(contact);
|
|
218
|
-
},
|
|
219
|
-
className: cn(
|
|
220
|
-
"flex items-center gap-2.5 px-2 py-1.5 rounded-md cursor-pointer hover:bg-muted/60",
|
|
221
|
-
disabled && "opacity-45 pointer-events-none"
|
|
222
|
-
),
|
|
223
|
-
children: [
|
|
224
|
-
/* @__PURE__ */ jsx("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-[7px] bg-muted text-[11px] font-medium text-muted-foreground", children: getInitials(contact.name, email != null ? email : "?") }),
|
|
225
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
226
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
227
|
-
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] font-medium text-foreground", children: contact.name }),
|
|
228
|
-
/* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: contact.role })
|
|
229
|
-
] }),
|
|
230
|
-
email ? /* @__PURE__ */ jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: email }) : null
|
|
231
|
-
] }),
|
|
232
|
-
alreadyAdded ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10.5px] font-medium text-muted-foreground", children: "Added" }) : noEmail ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10.5px] font-medium text-muted-foreground", children: "No email" }) : null
|
|
233
|
-
]
|
|
234
|
-
},
|
|
235
|
-
`${contact.name}-${email != null ? email : index}`
|
|
236
|
-
);
|
|
237
|
-
}) }),
|
|
238
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 border-t border-border/50 text-[11px] text-muted-foreground", children: [
|
|
239
|
-
/* @__PURE__ */ jsx(CornerDownLeft, { className: "size-3 shrink-0" }),
|
|
240
|
-
/* @__PURE__ */ jsx("span", { children: "Type an address and press Enter to add someone not listed." })
|
|
241
|
-
] })
|
|
242
|
-
]
|
|
243
|
-
}
|
|
244
|
-
),
|
|
245
|
-
document.body
|
|
246
|
-
);
|
|
170
|
+
alreadyAdded ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10.5px] font-medium text-muted-foreground", children: "Added" }) : noEmail ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10.5px] font-medium text-muted-foreground", children: "No email" }) : null
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
`${contact.name}-${email != null ? email : index}`
|
|
174
|
+
);
|
|
175
|
+
}) }),
|
|
176
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 border-t border-border/50 text-[11px] text-muted-foreground", children: [
|
|
177
|
+
/* @__PURE__ */ jsx(CornerDownLeft, { className: "size-3 shrink-0" }),
|
|
178
|
+
/* @__PURE__ */ jsx("span", { children: "Type an address and press Enter to add someone not listed." })
|
|
179
|
+
] })
|
|
180
|
+
] });
|
|
247
181
|
}
|
|
248
182
|
function EmailRecipientField({
|
|
249
183
|
label,
|
|
@@ -261,7 +195,6 @@ function EmailRecipientField({
|
|
|
261
195
|
}) {
|
|
262
196
|
const [value, setValue] = React.useState("");
|
|
263
197
|
const [pickerOpen, setPickerOpen] = React.useState(false);
|
|
264
|
-
const contactsTriggerRef = React.useRef(null);
|
|
265
198
|
const hasUnconfirmed = recipients.some((r) => !r.confirmed);
|
|
266
199
|
const state = amber && hasUnconfirmed ? "amber" : "default";
|
|
267
200
|
const amberRow = state === "amber";
|
|
@@ -351,20 +284,42 @@ function EmailRecipientField({
|
|
|
351
284
|
)
|
|
352
285
|
] }),
|
|
353
286
|
showPicker || showCcBcc ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5 mt-2", children: [
|
|
354
|
-
showPicker ? /* @__PURE__ */ jsxs(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
|
|
287
|
+
showPicker ? /* @__PURE__ */ jsxs(PopoverPrimitive.Root, { open: pickerOpen, onOpenChange: setPickerOpen, children: [
|
|
288
|
+
/* @__PURE__ */ jsx(PopoverPrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
289
|
+
"button",
|
|
290
|
+
{
|
|
291
|
+
type: "button",
|
|
292
|
+
className: "inline-flex items-center gap-1 h-6 px-2.5 rounded-md border border-border bg-background text-[11px] font-medium text-muted-foreground hover:bg-muted/60 hover:text-foreground transition-colors duration-[120ms]",
|
|
293
|
+
children: [
|
|
294
|
+
/* @__PURE__ */ jsx(Users, { className: "size-3" }),
|
|
295
|
+
"Contacts",
|
|
296
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "size-3" })
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
) }),
|
|
300
|
+
/* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
301
|
+
PopoverPrimitive.Content,
|
|
302
|
+
{
|
|
303
|
+
side: "bottom",
|
|
304
|
+
align: "start",
|
|
305
|
+
sideOffset: 4,
|
|
306
|
+
collisionPadding: 16,
|
|
307
|
+
className: "z-50 w-[min(448px,calc(100vw-2rem))] rounded-lg border bg-background shadow-xl p-0",
|
|
308
|
+
children: /* @__PURE__ */ jsx(
|
|
309
|
+
ContactPickerContents,
|
|
310
|
+
{
|
|
311
|
+
contacts,
|
|
312
|
+
addedEmails: added,
|
|
313
|
+
onSelect: selectContact,
|
|
314
|
+
onAddEmail: (email) => {
|
|
315
|
+
addEmail(email);
|
|
316
|
+
setPickerOpen(false);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
) })
|
|
322
|
+
] }) : null,
|
|
368
323
|
showCcBcc ? /* @__PURE__ */ jsxs(
|
|
369
324
|
"button",
|
|
370
325
|
{
|
|
@@ -377,21 +332,7 @@ function EmailRecipientField({
|
|
|
377
332
|
]
|
|
378
333
|
}
|
|
379
334
|
) : null
|
|
380
|
-
] }) : null
|
|
381
|
-
pickerOpen ? /* @__PURE__ */ jsx(
|
|
382
|
-
ContactPickerPopover,
|
|
383
|
-
{
|
|
384
|
-
triggerRef: contactsTriggerRef,
|
|
385
|
-
contacts,
|
|
386
|
-
addedEmails: added,
|
|
387
|
-
onSelect: selectContact,
|
|
388
|
-
onAddEmail: (email) => {
|
|
389
|
-
addEmail(email);
|
|
390
|
-
setPickerOpen(false);
|
|
391
|
-
},
|
|
392
|
-
onClose: () => setPickerOpen(false)
|
|
393
|
-
}
|
|
394
|
-
) : null
|
|
335
|
+
] }) : null
|
|
395
336
|
] })
|
|
396
337
|
]
|
|
397
338
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/email-recipient-field.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { createPortal } from \"react-dom\"\nimport {\n Check,\n ChevronDown,\n CornerDownLeft,\n Plus,\n Search,\n Users,\n X,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport type { SuggestedContact } from \"./suggested-actions\"\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isValidEmail(value: string): boolean {\n return EMAIL_REGEX.test(value.trim())\n}\n\nfunction contactEmail(contact: SuggestedContact): string | undefined {\n return contact.email ?? contact.emails?.[0]\n}\n\nfunction getInitials(name: string, fallback: string): string {\n const source = name?.trim() || fallback\n return source\n .split(/[\\s@.]+/)\n .map((part) => part[0])\n .filter(Boolean)\n .slice(0, 2)\n .join(\"\")\n .toUpperCase()\n}\n\nexport interface RecipientChip {\n id: string\n email: string\n name: string\n confirmed: boolean\n}\n\nexport interface EmailRecipientFieldProps {\n label: string\n recipients: RecipientChip[]\n onRecipientsChange: (recipients: RecipientChip[]) => void\n amber?: boolean\n contacts?: SuggestedContact[]\n showPicker?: boolean\n showCcBcc?: boolean\n ccBccOpen?: boolean\n onCcBccToggle?: () => void\n addedEmails?: Set<string>\n placeholder?: string\n contactToRecipient?: (contact: SuggestedContact) => RecipientChip\n}\n\nfunction RecipientChipPill({\n recipient,\n onConfirm,\n onRemove,\n}: {\n recipient: RecipientChip\n onConfirm: () => void\n onRemove: () => void\n}) {\n const display = recipient.name || recipient.email\n\n if (!recipient.confirmed) {\n return (\n <span className=\"inline-flex items-center gap-1 h-6 px-2 rounded-md text-xs border border-amber-300 bg-amber-50 text-amber-900\">\n <span className=\"truncate max-w-[180px]\">{display}</span>\n <button\n type=\"button\"\n onClick={onConfirm}\n className=\"text-[10.5px] font-semibold px-[7px] py-0.5 rounded bg-amber-300/50 hover:bg-amber-300/85\"\n >\n Confirm\n </button>\n <button\n type=\"button\"\n aria-label={`Remove ${display}`}\n onClick={onRemove}\n className=\"inline-flex items-center justify-center size-[17px] rounded text-amber-700/80 hover:bg-amber-300/40\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n )\n }\n\n return (\n <span className=\"inline-flex items-center gap-1 h-6 px-2 rounded-md text-xs border border-border bg-muted/50 text-foreground\">\n <span className=\"inline-flex items-center justify-center size-[17px] rounded bg-emerald-50 text-emerald-700\">\n <Check className=\"size-3\" />\n </span>\n <span className=\"truncate max-w-[180px]\">{display}</span>\n <button\n type=\"button\"\n aria-label={`Remove ${display}`}\n onClick={onRemove}\n className=\"inline-flex items-center justify-center size-[17px] rounded text-muted-foreground hover:bg-muted\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n )\n}\n\nfunction ContactPickerPopover({\n triggerRef,\n contacts,\n addedEmails,\n onSelect,\n onAddEmail,\n onClose,\n}: {\n triggerRef: React.RefObject<HTMLElement | null>\n contacts: SuggestedContact[]\n addedEmails: Set<string>\n onSelect: (contact: SuggestedContact) => void\n onAddEmail: (email: string) => void\n onClose: () => void\n}) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const searchRef = React.useRef<HTMLInputElement>(null)\n const [query, setQuery] = React.useState(\"\")\n const [style, setStyle] = React.useState<React.CSSProperties>({\n position: \"fixed\",\n top: -9999,\n left: -9999,\n })\n\n React.useEffect(() => {\n const trigger = triggerRef.current\n if (!trigger) return\n const rect = trigger.getBoundingClientRect()\n const width = Math.min(448, window.innerWidth - 32)\n let left = rect.left\n if (left + width > window.innerWidth - 16) {\n left = window.innerWidth - 16 - width\n }\n if (left < 16) left = 16\n const popoverHeight = 280\n const spaceBelow = window.innerHeight - rect.bottom - 4\n const spaceAbove = rect.top - 4\n const placeAbove = spaceBelow < popoverHeight && spaceAbove > spaceBelow\n let top = placeAbove ? rect.top - popoverHeight - 4 : rect.bottom + 4\n if (top < 16) top = 16\n setStyle({ position: \"fixed\", top, left, width })\n }, [triggerRef])\n\n React.useEffect(() => {\n searchRef.current?.focus()\n const trigger = triggerRef.current\n return () => {\n if (trigger && typeof trigger.focus === \"function\") {\n trigger.focus()\n }\n }\n }, [triggerRef])\n\n React.useEffect(() => {\n function handleMouseDown(event: MouseEvent) {\n if (\n containerRef.current &&\n !containerRef.current.contains(event.target as Node) &&\n !triggerRef.current?.contains(event.target as Node)\n ) {\n onClose()\n }\n }\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n event.stopPropagation()\n onClose()\n }\n }\n document.addEventListener(\"mousedown\", handleMouseDown)\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n document.removeEventListener(\"mousedown\", handleMouseDown)\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [onClose, triggerRef])\n\n const normalizedQuery = query.trim().toLowerCase()\n const filtered = normalizedQuery\n ? contacts.filter((contact) => {\n const email = contactEmail(contact) ?? \"\"\n return (\n contact.name.toLowerCase().includes(normalizedQuery) ||\n contact.role.toLowerCase().includes(normalizedQuery) ||\n email.toLowerCase().includes(normalizedQuery)\n )\n })\n : contacts\n\n const queryIsEmail = isValidEmail(query)\n\n function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\" && queryIsEmail) {\n event.preventDefault()\n onAddEmail(query.trim())\n setQuery(\"\")\n }\n }\n\n return createPortal(\n <div\n ref={containerRef}\n style={style}\n className=\"bg-background border rounded-lg shadow-xl z-50 pointer-events-auto\"\n >\n <div className=\"flex items-center gap-2 px-3 py-2.5 border-b border-border/50\">\n <Search className=\"size-4 text-muted-foreground shrink-0\" />\n <input\n ref={searchRef}\n autoFocus\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1 text-[13px] bg-transparent outline-none\"\n placeholder=\"Search contacts...\"\n />\n </div>\n\n <div role=\"listbox\" className=\"max-h-[208px] overflow-y-auto p-1\">\n {filtered.length === 0 ? (\n <div className=\"px-3 py-4 text-center text-[13px] text-muted-foreground\">\n <div>No contact matches ‘{query}’.</div>\n {queryIsEmail ? (\n <div className=\"mt-1\">Press Enter to add {query}.</div>\n ) : null}\n </div>\n ) : (\n filtered.map((contact, index) => {\n const email = contactEmail(contact)\n const noEmail = !email || !isValidEmail(email)\n const alreadyAdded = email\n ? addedEmails.has(email.toLowerCase())\n : false\n const disabled = noEmail || alreadyAdded\n\n return (\n <div\n key={`${contact.name}-${email ?? index}`}\n role=\"option\"\n aria-selected={false}\n aria-disabled={disabled}\n onClick={() => {\n if (!disabled) onSelect(contact)\n }}\n className={cn(\n \"flex items-center gap-2.5 px-2 py-1.5 rounded-md cursor-pointer hover:bg-muted/60\",\n disabled && \"opacity-45 pointer-events-none\",\n )}\n >\n <div className=\"flex size-7 shrink-0 items-center justify-center rounded-[7px] bg-muted text-[11px] font-medium text-muted-foreground\">\n {getInitials(contact.name, email ?? \"?\")}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"truncate text-[13px] font-medium text-foreground\">\n {contact.name}\n </span>\n <span className=\"truncate text-[11px] text-muted-foreground\">\n {contact.role}\n </span>\n </div>\n {email ? (\n <div className=\"truncate text-[11px] text-muted-foreground\">\n {email}\n </div>\n ) : null}\n </div>\n {alreadyAdded ? (\n <span className=\"shrink-0 text-[10.5px] font-medium text-muted-foreground\">\n Added\n </span>\n ) : noEmail ? (\n <span className=\"shrink-0 text-[10.5px] font-medium text-muted-foreground\">\n No email\n </span>\n ) : null}\n </div>\n )\n })\n )}\n </div>\n\n <div className=\"flex items-center gap-1.5 px-3 py-2 border-t border-border/50 text-[11px] text-muted-foreground\">\n <CornerDownLeft className=\"size-3 shrink-0\" />\n <span>Type an address and press Enter to add someone not listed.</span>\n </div>\n </div>,\n document.body,\n )\n}\n\nexport function EmailRecipientField({\n label,\n recipients,\n onRecipientsChange,\n amber = false,\n contacts = [],\n showPicker = false,\n showCcBcc = false,\n ccBccOpen = false,\n onCcBccToggle,\n addedEmails,\n placeholder,\n contactToRecipient,\n}: EmailRecipientFieldProps) {\n const [value, setValue] = React.useState(\"\")\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const contactsTriggerRef = React.useRef<HTMLButtonElement>(null)\n\n const hasUnconfirmed = recipients.some((r) => !r.confirmed)\n const state: \"default\" | \"amber\" =\n amber && hasUnconfirmed ? \"amber\" : \"default\"\n const amberRow = state === \"amber\"\n\n const added = addedEmails ?? new Set<string>()\n\n const resolvedPlaceholder =\n placeholder ?? (recipients.length > 0 ? \"Add another...\" : \"Add email...\")\n\n function addEmail(email: string) {\n const trimmed = email.trim()\n if (!isValidEmail(trimmed)) return\n if (added.has(trimmed.toLowerCase())) return\n onRecipientsChange([\n ...recipients,\n { id: trimmed, email: trimmed, name: \"\", confirmed: false },\n ])\n setValue(\"\")\n }\n\n function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n if ((event.key === \"Enter\" || event.key === \",\") && isValidEmail(value)) {\n event.preventDefault()\n addEmail(value)\n return\n }\n if (event.key === \"Backspace\" && value === \"\" && recipients.length > 0) {\n event.preventDefault()\n onRecipientsChange(recipients.slice(0, -1))\n }\n }\n\n function confirmRecipient(id: string) {\n onRecipientsChange(\n recipients.map((r) => (r.id === id ? { ...r, confirmed: true } : r)),\n )\n }\n\n function removeRecipient(id: string) {\n onRecipientsChange(recipients.filter((r) => r.id !== id))\n }\n\n function selectContact(contact: SuggestedContact) {\n const recipient =\n contactToRecipient?.(contact) ??\n ({\n id: contactEmail(contact) ?? contact.name,\n email: contactEmail(contact) ?? \"\",\n name: contact.name,\n confirmed: true,\n } satisfies RecipientChip)\n onRecipientsChange([...recipients, recipient])\n setPickerOpen(false)\n }\n\n return (\n <div\n className={cn(\n \"grid grid-cols-[60px_1fr] gap-2 px-[18px] py-[9px] border-b border-border/70 items-start text-sm\",\n amberRow && \"bg-amber-50/35 border-amber-200/80\",\n )}\n >\n <div\n className={cn(\n \"text-[11px] font-semibold uppercase tracking-wide text-muted-foreground pt-[7px]\",\n amberRow && \"text-amber-700\",\n )}\n >\n {label}\n </div>\n\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap gap-1.5 items-center\">\n {recipients.map((recipient) => (\n <RecipientChipPill\n key={recipient.id}\n recipient={recipient}\n onConfirm={() => confirmRecipient(recipient.id)}\n onRemove={() => removeRecipient(recipient.id)}\n />\n ))}\n <input\n value={value}\n onChange={(event) => setValue(event.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n // Commit any valid pending email so it is not silently dropped\n // when the user clicks Send without pressing Enter/comma first.\n if (isValidEmail(value)) addEmail(value)\n }}\n placeholder={resolvedPlaceholder}\n className=\"min-w-[130px] flex-1 h-6 text-[13px] bg-transparent border-0 outline-none placeholder:text-muted-foreground\"\n />\n </div>\n\n {showPicker || showCcBcc ? (\n <div className=\"flex gap-1.5 mt-2\">\n {showPicker ? (\n <button\n ref={contactsTriggerRef}\n type=\"button\"\n onClick={() => setPickerOpen((open) => !open)}\n className=\"inline-flex items-center gap-1 h-6 px-2.5 rounded-md border border-border bg-background text-[11px] font-medium text-muted-foreground hover:bg-muted/60 hover:text-foreground transition-colors duration-[120ms]\"\n >\n <Users className=\"size-3\" />\n Contacts\n <ChevronDown className=\"size-3\" />\n </button>\n ) : null}\n {showCcBcc ? (\n <button\n type=\"button\"\n onClick={onCcBccToggle}\n className=\"inline-flex items-center gap-1 h-6 px-2.5 rounded-md border border-border bg-background text-[11px] font-medium text-muted-foreground hover:bg-muted/60 hover:text-foreground transition-colors duration-[120ms]\"\n >\n <Plus className=\"size-3\" />\n {ccBccOpen ? \"Hide Cc/Bcc\" : \"Add Cc/Bcc\"}\n </button>\n ) : null}\n </div>\n ) : null}\n\n {pickerOpen ? (\n <ContactPickerPopover\n triggerRef={contactsTriggerRef}\n contacts={contacts}\n addedEmails={added}\n onSelect={selectContact}\n onAddEmail={(email) => {\n addEmail(email)\n setPickerOpen(false)\n }}\n onClose={() => setPickerOpen(false)}\n />\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["handleKeyDown"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/email-recipient-field.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Popover as PopoverPrimitive } from \"radix-ui\"\nimport {\n Check,\n ChevronDown,\n CornerDownLeft,\n Plus,\n Search,\n Users,\n X,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport type { SuggestedContact } from \"./suggested-actions\"\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isValidEmail(value: string): boolean {\n return EMAIL_REGEX.test(value.trim())\n}\n\nfunction contactEmail(contact: SuggestedContact): string | undefined {\n return contact.email ?? contact.emails?.[0]\n}\n\nfunction getInitials(name: string, fallback: string): string {\n const source = name?.trim() || fallback\n return source\n .split(/[\\s@.]+/)\n .map((part) => part[0])\n .filter(Boolean)\n .slice(0, 2)\n .join(\"\")\n .toUpperCase()\n}\n\nexport interface RecipientChip {\n id: string\n email: string\n name: string\n confirmed: boolean\n}\n\nexport interface EmailRecipientFieldProps {\n label: string\n recipients: RecipientChip[]\n onRecipientsChange: (recipients: RecipientChip[]) => void\n amber?: boolean\n contacts?: SuggestedContact[]\n showPicker?: boolean\n showCcBcc?: boolean\n ccBccOpen?: boolean\n onCcBccToggle?: () => void\n addedEmails?: Set<string>\n placeholder?: string\n contactToRecipient?: (contact: SuggestedContact) => RecipientChip\n}\n\nfunction RecipientChipPill({\n recipient,\n onConfirm,\n onRemove,\n}: {\n recipient: RecipientChip\n onConfirm: () => void\n onRemove: () => void\n}) {\n const display = recipient.name || recipient.email\n\n if (!recipient.confirmed) {\n return (\n <span className=\"inline-flex items-center gap-1 h-6 px-2 rounded-md text-xs border border-amber-300 bg-amber-50 text-amber-900\">\n <span className=\"truncate max-w-[180px]\">{display}</span>\n <button\n type=\"button\"\n onClick={onConfirm}\n className=\"text-[10.5px] font-semibold px-[7px] py-0.5 rounded bg-amber-300/50 hover:bg-amber-300/85\"\n >\n Confirm\n </button>\n <button\n type=\"button\"\n aria-label={`Remove ${display}`}\n onClick={onRemove}\n className=\"inline-flex items-center justify-center size-[17px] rounded text-amber-700/80 hover:bg-amber-300/40\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n )\n }\n\n return (\n <span className=\"inline-flex items-center gap-1 h-6 px-2 rounded-md text-xs border border-border bg-muted/50 text-foreground\">\n <span className=\"inline-flex items-center justify-center size-[17px] rounded bg-emerald-50 text-emerald-700\">\n <Check className=\"size-3\" />\n </span>\n <span className=\"truncate max-w-[180px]\">{display}</span>\n <button\n type=\"button\"\n aria-label={`Remove ${display}`}\n onClick={onRemove}\n className=\"inline-flex items-center justify-center size-[17px] rounded text-muted-foreground hover:bg-muted\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n )\n}\n\n// Contents of the contact picker dropdown. Rendered inside a Radix\n// `Popover.Content` so its focus scope pushes onto the focus-scope stack and\n// PAUSES any parent modal's scope (e.g. the quick-action Dialog). This is what\n// makes the search input typeable: a plain `createPortal(..., document.body)`\n// element renders outside the Dialog's `DialogContent`, so the Dialog's\n// FocusScope kept yanking focus back (input un-typeable) and its modal\n// `pointer-events: none` on <body> left the portal click-dead. A stacked Radix\n// Popover layer gets `pointer-events: auto` and its own (paused-parent) focus\n// scope, fixing both. See WIT-800 / WIT-770.\nfunction ContactPickerContents({\n contacts,\n addedEmails,\n onSelect,\n onAddEmail,\n}: {\n contacts: SuggestedContact[]\n addedEmails: Set<string>\n onSelect: (contact: SuggestedContact) => void\n onAddEmail: (email: string) => void\n}) {\n const [query, setQuery] = React.useState(\"\")\n\n const normalizedQuery = query.trim().toLowerCase()\n const filtered = normalizedQuery\n ? contacts.filter((contact) => {\n const email = contactEmail(contact) ?? \"\"\n return (\n contact.name.toLowerCase().includes(normalizedQuery) ||\n contact.role.toLowerCase().includes(normalizedQuery) ||\n email.toLowerCase().includes(normalizedQuery)\n )\n })\n : contacts\n\n const queryIsEmail = isValidEmail(query)\n\n function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\" && queryIsEmail) {\n event.preventDefault()\n onAddEmail(query.trim())\n setQuery(\"\")\n }\n }\n\n return (\n <>\n <div className=\"flex items-center gap-2 px-3 py-2.5 border-b border-border/50\">\n <Search className=\"size-4 text-muted-foreground shrink-0\" />\n <input\n autoFocus\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1 text-[13px] bg-transparent outline-none\"\n placeholder=\"Search contacts...\"\n />\n </div>\n\n <div role=\"listbox\" className=\"max-h-[208px] overflow-y-auto p-1\">\n {contacts.length === 0 ? (\n <div className=\"px-3 py-5 text-center text-[13px] text-muted-foreground\">\n <div className=\"font-medium text-foreground/80\">\n No contacts for this account\n </div>\n <div className=\"mt-1\">\n Type an email address above and press Enter to add a recipient.\n </div>\n </div>\n ) : filtered.length === 0 ? (\n <div className=\"px-3 py-4 text-center text-[13px] text-muted-foreground\">\n <div>No contact matches ‘{query}’.</div>\n {queryIsEmail ? (\n <div className=\"mt-1\">Press Enter to add {query}.</div>\n ) : null}\n </div>\n ) : (\n filtered.map((contact, index) => {\n const email = contactEmail(contact)\n const noEmail = !email || !isValidEmail(email)\n const alreadyAdded = email\n ? addedEmails.has(email.toLowerCase())\n : false\n const disabled = noEmail || alreadyAdded\n\n return (\n <div\n key={`${contact.name}-${email ?? index}`}\n role=\"option\"\n aria-selected={false}\n aria-disabled={disabled}\n onClick={() => {\n if (!disabled) onSelect(contact)\n }}\n className={cn(\n \"flex items-center gap-2.5 px-2 py-1.5 rounded-md cursor-pointer hover:bg-muted/60\",\n disabled && \"opacity-45 pointer-events-none\",\n )}\n >\n <div className=\"flex size-7 shrink-0 items-center justify-center rounded-[7px] bg-muted text-[11px] font-medium text-muted-foreground\">\n {getInitials(contact.name, email ?? \"?\")}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"truncate text-[13px] font-medium text-foreground\">\n {contact.name}\n </span>\n <span className=\"truncate text-[11px] text-muted-foreground\">\n {contact.role}\n </span>\n </div>\n {email ? (\n <div className=\"truncate text-[11px] text-muted-foreground\">\n {email}\n </div>\n ) : null}\n </div>\n {alreadyAdded ? (\n <span className=\"shrink-0 text-[10.5px] font-medium text-muted-foreground\">\n Added\n </span>\n ) : noEmail ? (\n <span className=\"shrink-0 text-[10.5px] font-medium text-muted-foreground\">\n No email\n </span>\n ) : null}\n </div>\n )\n })\n )}\n </div>\n\n <div className=\"flex items-center gap-1.5 px-3 py-2 border-t border-border/50 text-[11px] text-muted-foreground\">\n <CornerDownLeft className=\"size-3 shrink-0\" />\n <span>Type an address and press Enter to add someone not listed.</span>\n </div>\n </>\n )\n}\n\nexport function EmailRecipientField({\n label,\n recipients,\n onRecipientsChange,\n amber = false,\n contacts = [],\n showPicker = false,\n showCcBcc = false,\n ccBccOpen = false,\n onCcBccToggle,\n addedEmails,\n placeholder,\n contactToRecipient,\n}: EmailRecipientFieldProps) {\n const [value, setValue] = React.useState(\"\")\n const [pickerOpen, setPickerOpen] = React.useState(false)\n\n const hasUnconfirmed = recipients.some((r) => !r.confirmed)\n const state: \"default\" | \"amber\" =\n amber && hasUnconfirmed ? \"amber\" : \"default\"\n const amberRow = state === \"amber\"\n\n const added = addedEmails ?? new Set<string>()\n\n const resolvedPlaceholder =\n placeholder ?? (recipients.length > 0 ? \"Add another...\" : \"Add email...\")\n\n function addEmail(email: string) {\n const trimmed = email.trim()\n if (!isValidEmail(trimmed)) return\n if (added.has(trimmed.toLowerCase())) return\n onRecipientsChange([\n ...recipients,\n { id: trimmed, email: trimmed, name: \"\", confirmed: false },\n ])\n setValue(\"\")\n }\n\n function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n if ((event.key === \"Enter\" || event.key === \",\") && isValidEmail(value)) {\n event.preventDefault()\n addEmail(value)\n return\n }\n if (event.key === \"Backspace\" && value === \"\" && recipients.length > 0) {\n event.preventDefault()\n onRecipientsChange(recipients.slice(0, -1))\n }\n }\n\n function confirmRecipient(id: string) {\n onRecipientsChange(\n recipients.map((r) => (r.id === id ? { ...r, confirmed: true } : r)),\n )\n }\n\n function removeRecipient(id: string) {\n onRecipientsChange(recipients.filter((r) => r.id !== id))\n }\n\n function selectContact(contact: SuggestedContact) {\n const recipient =\n contactToRecipient?.(contact) ??\n ({\n id: contactEmail(contact) ?? contact.name,\n email: contactEmail(contact) ?? \"\",\n name: contact.name,\n confirmed: true,\n } satisfies RecipientChip)\n onRecipientsChange([...recipients, recipient])\n setPickerOpen(false)\n }\n\n return (\n <div\n className={cn(\n \"grid grid-cols-[60px_1fr] gap-2 px-[18px] py-[9px] border-b border-border/70 items-start text-sm\",\n amberRow && \"bg-amber-50/35 border-amber-200/80\",\n )}\n >\n <div\n className={cn(\n \"text-[11px] font-semibold uppercase tracking-wide text-muted-foreground pt-[7px]\",\n amberRow && \"text-amber-700\",\n )}\n >\n {label}\n </div>\n\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap gap-1.5 items-center\">\n {recipients.map((recipient) => (\n <RecipientChipPill\n key={recipient.id}\n recipient={recipient}\n onConfirm={() => confirmRecipient(recipient.id)}\n onRemove={() => removeRecipient(recipient.id)}\n />\n ))}\n <input\n value={value}\n onChange={(event) => setValue(event.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n // Commit any valid pending email so it is not silently dropped\n // when the user clicks Send without pressing Enter/comma first.\n if (isValidEmail(value)) addEmail(value)\n }}\n placeholder={resolvedPlaceholder}\n className=\"min-w-[130px] flex-1 h-6 text-[13px] bg-transparent border-0 outline-none placeholder:text-muted-foreground\"\n />\n </div>\n\n {showPicker || showCcBcc ? (\n <div className=\"flex gap-1.5 mt-2\">\n {showPicker ? (\n <PopoverPrimitive.Root open={pickerOpen} onOpenChange={setPickerOpen}>\n <PopoverPrimitive.Trigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 h-6 px-2.5 rounded-md border border-border bg-background text-[11px] font-medium text-muted-foreground hover:bg-muted/60 hover:text-foreground transition-colors duration-[120ms]\"\n >\n <Users className=\"size-3\" />\n Contacts\n <ChevronDown className=\"size-3\" />\n </button>\n </PopoverPrimitive.Trigger>\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n collisionPadding={16}\n className=\"z-50 w-[min(448px,calc(100vw-2rem))] rounded-lg border bg-background shadow-xl p-0\"\n >\n <ContactPickerContents\n contacts={contacts}\n addedEmails={added}\n onSelect={selectContact}\n onAddEmail={(email) => {\n addEmail(email)\n setPickerOpen(false)\n }}\n />\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n ) : null}\n {showCcBcc ? (\n <button\n type=\"button\"\n onClick={onCcBccToggle}\n className=\"inline-flex items-center gap-1 h-6 px-2.5 rounded-md border border-border bg-background text-[11px] font-medium text-muted-foreground hover:bg-muted/60 hover:text-foreground transition-colors duration-[120ms]\"\n >\n <Plus className=\"size-3\" />\n {ccBccOpen ? \"Hide Cc/Bcc\" : \"Add Cc/Bcc\"}\n </button>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
type RelatedRecordActionCardKind = "case" | "account" | "opportunity" | "salesforce" | "generic";
|
|
4
|
+
type RelatedRecordActionIcon = "salesforce" | React.ReactNode;
|
|
5
|
+
interface RelatedRecordActionCardProps {
|
|
6
|
+
kind: RelatedRecordActionCardKind;
|
|
7
|
+
label: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
disabledReason?: string;
|
|
10
|
+
href?: string;
|
|
11
|
+
external?: boolean;
|
|
12
|
+
icon?: RelatedRecordActionIcon;
|
|
13
|
+
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
|
14
|
+
className?: string;
|
|
15
|
+
testId?: string;
|
|
16
|
+
}
|
|
17
|
+
declare function RelatedRecordActionCard({ kind, label, subtitle, disabledReason, href, external, icon, onClick, className, testId, }: RelatedRecordActionCardProps): React.JSX.Element;
|
|
18
|
+
|
|
19
|
+
export { RelatedRecordActionCard, type RelatedRecordActionCardKind, type RelatedRecordActionCardProps, type RelatedRecordActionIcon };
|