@nucleux/proactive 0.0.0-stage → 0.6.0
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/LICENSE +21 -0
- package/README.md +60 -2
- package/dist/index.cjs +253 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +231 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nucleux
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nucleux/proactive
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Proactive Suggestions — invisible AI moments that arrive exactly when needed — Nucleux agentic UI for React.
|
|
4
|
+
|
|
5
|
+
Part of **[Nucleux](https://github.com/intelligaia-open-labs/nucleux)** — agentic UI components for React. Install a single component, or the full kit via [`@nucleux/react`](https://www.npmjs.com/package/@nucleux/react).
|
|
6
|
+
|
|
7
|
+
An unsolicited suggestion raised when the agent notices something specific and
|
|
8
|
+
actionable. It leads with the finding, can show the evidence, offers exactly
|
|
9
|
+
one next action plus a one-press dismiss (× or Escape) and an optional snooze,
|
|
10
|
+
and does nothing until the person acts.
|
|
11
|
+
|
|
12
|
+
## Exports
|
|
13
|
+
|
|
14
|
+
- **`ProactiveSuggestion`**
|
|
15
|
+
- Types: `ProactiveSuggestionProps`, `ProactiveSuggestionVariant`, `ProactiveSuggestionPersist`
|
|
16
|
+
|
|
17
|
+
## Variants
|
|
18
|
+
|
|
19
|
+
| `variant` | Use |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `contextual-banner` (default) | At the top of a surface. |
|
|
22
|
+
| `inline-suggestion` | A compact action beside the row or element it is about. |
|
|
23
|
+
| `side-toast` | Fixed to the bottom-right corner (override with `className`). |
|
|
24
|
+
|
|
25
|
+
## Behaviour
|
|
26
|
+
|
|
27
|
+
- `title` (the finding), `description` (the recommendation), `reason` (the evidence), `actionLabel` + `onAction`.
|
|
28
|
+
- `onAction` may return a promise: the action shows a spinner and `pendingLabel` meanwhile.
|
|
29
|
+
- `evidenceKey` + `persist` (`"local"` default): a dismissal is remembered so the same facts never bring it back, and only one keyed suggestion shows per session.
|
|
30
|
+
- `resolved` says "Resolved" and drops the actions. It sits in a polite live region, so its arrival is announced without taking focus.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { ProactiveSuggestion } from "@nucleux/proactive";
|
|
36
|
+
|
|
37
|
+
<ProactiveSuggestion
|
|
38
|
+
agentName="Aria"
|
|
39
|
+
evidenceKey="renewals-2026-w17"
|
|
40
|
+
title="Three renewals expire this week"
|
|
41
|
+
description="Want Aria to draft outreach for each?"
|
|
42
|
+
actionLabel="Draft emails"
|
|
43
|
+
onAction={openReview}
|
|
44
|
+
onSnooze={remindTomorrow}
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm add @nucleux/proactive @nucleux/tokens
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`react` / `react-dom` are peer dependencies.
|
|
55
|
+
|
|
56
|
+
Requires the theme once in your app: `import "@nucleux/tokens/styles.css";`
|
|
57
|
+
and the Tailwind preset from `@nucleux/tokens/preset`.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT © Nucleux
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var lucideReact = require('lucide-react');
|
|
6
|
+
var utils = require('@nucleux/utils');
|
|
7
|
+
var button = require('@nucleux/button');
|
|
8
|
+
var spinner = require('@nucleux/spinner');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
|
|
11
|
+
function _interopNamespace(e) {
|
|
12
|
+
if (e && e.__esModule) return e;
|
|
13
|
+
var n = Object.create(null);
|
|
14
|
+
if (e) {
|
|
15
|
+
Object.keys(e).forEach(function (k) {
|
|
16
|
+
if (k !== 'default') {
|
|
17
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return e[k]; }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
n.default = e;
|
|
26
|
+
return Object.freeze(n);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
30
|
+
|
|
31
|
+
// src/index.tsx
|
|
32
|
+
var KEY = "nucleux:proactive:";
|
|
33
|
+
var SESSION_KEY = "nucleux:proactive:session";
|
|
34
|
+
function getStore(kind) {
|
|
35
|
+
try {
|
|
36
|
+
return typeof window === "undefined" ? null : kind === "session" ? window.sessionStorage : window.localStorage;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function read(kind, key) {
|
|
42
|
+
try {
|
|
43
|
+
return getStore(kind)?.getItem(key) ?? null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function write(kind, key, value) {
|
|
49
|
+
try {
|
|
50
|
+
getStore(kind)?.setItem(key, value);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
var ProactiveSuggestion = React__namespace.forwardRef(
|
|
55
|
+
({
|
|
56
|
+
variant = "contextual-banner",
|
|
57
|
+
title,
|
|
58
|
+
description,
|
|
59
|
+
reason,
|
|
60
|
+
agentName = "the assistant",
|
|
61
|
+
actionLabel,
|
|
62
|
+
onAction,
|
|
63
|
+
pendingLabel = "Preparing\u2026",
|
|
64
|
+
onSnooze,
|
|
65
|
+
snoozeLabel = "Not now",
|
|
66
|
+
onDismiss,
|
|
67
|
+
dismissLabel = "Dismiss suggestion",
|
|
68
|
+
open: openProp,
|
|
69
|
+
defaultOpen = true,
|
|
70
|
+
onOpenChange,
|
|
71
|
+
evidenceKey,
|
|
72
|
+
persist = "local",
|
|
73
|
+
resolved = false,
|
|
74
|
+
onImpression,
|
|
75
|
+
className,
|
|
76
|
+
onKeyDown,
|
|
77
|
+
...props
|
|
78
|
+
}, ref) => {
|
|
79
|
+
const baseId = React__namespace.useId();
|
|
80
|
+
const titleId = `${baseId}-title`;
|
|
81
|
+
const [internalOpen, setInternalOpen] = React__namespace.useState(defaultOpen);
|
|
82
|
+
const open = openProp ?? internalOpen;
|
|
83
|
+
const remembering = evidenceKey != null && persist !== "none";
|
|
84
|
+
const [allowed, setAllowed] = React__namespace.useState(!remembering);
|
|
85
|
+
const [pending, setPending] = React__namespace.useState(false);
|
|
86
|
+
const mounted = React__namespace.useRef(true);
|
|
87
|
+
React__namespace.useEffect(
|
|
88
|
+
() => () => {
|
|
89
|
+
mounted.current = false;
|
|
90
|
+
},
|
|
91
|
+
[]
|
|
92
|
+
);
|
|
93
|
+
React__namespace.useEffect(() => {
|
|
94
|
+
if (!remembering) {
|
|
95
|
+
setAllowed(true);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const dismissed = read(persist, KEY + evidenceKey);
|
|
99
|
+
const shown = read("session", SESSION_KEY);
|
|
100
|
+
setAllowed(!dismissed && (!shown || shown === evidenceKey));
|
|
101
|
+
}, [remembering, persist, evidenceKey]);
|
|
102
|
+
const visible = open && allowed;
|
|
103
|
+
const impressed = React__namespace.useRef(false);
|
|
104
|
+
React__namespace.useEffect(() => {
|
|
105
|
+
if (!visible) {
|
|
106
|
+
impressed.current = false;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (impressed.current) return;
|
|
110
|
+
impressed.current = true;
|
|
111
|
+
if (evidenceKey != null) write("session", SESSION_KEY, evidenceKey);
|
|
112
|
+
onImpression?.();
|
|
113
|
+
}, [visible, evidenceKey, onImpression]);
|
|
114
|
+
const setOpen = (next) => {
|
|
115
|
+
if (openProp === void 0) setInternalOpen(next);
|
|
116
|
+
onOpenChange?.(next);
|
|
117
|
+
};
|
|
118
|
+
const dismiss = () => {
|
|
119
|
+
if (remembering) write(persist, KEY + evidenceKey, "dismissed");
|
|
120
|
+
setOpen(false);
|
|
121
|
+
onDismiss?.();
|
|
122
|
+
};
|
|
123
|
+
const snooze = () => {
|
|
124
|
+
setOpen(false);
|
|
125
|
+
onSnooze?.();
|
|
126
|
+
};
|
|
127
|
+
const act = () => {
|
|
128
|
+
if (!onAction || pending) return;
|
|
129
|
+
const result = onAction();
|
|
130
|
+
if (result && typeof result.then === "function") {
|
|
131
|
+
setPending(true);
|
|
132
|
+
result.finally(() => {
|
|
133
|
+
if (mounted.current) setPending(false);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const handleKeyDown = (e) => {
|
|
138
|
+
onKeyDown?.(e);
|
|
139
|
+
if (e.key === "Escape") {
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
dismiss();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const label = `Suggestion from ${agentName}`;
|
|
145
|
+
const spinner$1 = /* @__PURE__ */ jsxRuntime.jsx(spinner.Spinner, { "aria-hidden": "true", role: void 0, "aria-label": void 0, className: "size-3.5 motion-reduce:animate-none" });
|
|
146
|
+
const dismissButton = /* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
+
button.Button,
|
|
148
|
+
{
|
|
149
|
+
variant: "ghost",
|
|
150
|
+
size: "icon-sm",
|
|
151
|
+
"aria-label": dismissLabel,
|
|
152
|
+
onClick: dismiss,
|
|
153
|
+
className: "size-6 shrink-0 text-muted-foreground [&_svg]:size-3.5",
|
|
154
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": "true" })
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
let body = null;
|
|
158
|
+
if (visible && variant === "inline-suggestion") {
|
|
159
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
160
|
+
"section",
|
|
161
|
+
{
|
|
162
|
+
ref,
|
|
163
|
+
"aria-label": label,
|
|
164
|
+
onKeyDown: handleKeyDown,
|
|
165
|
+
className: utils.cn("inline-flex items-center gap-1 animate-nx-fade-in motion-reduce:animate-none", className),
|
|
166
|
+
...props,
|
|
167
|
+
children: [
|
|
168
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { id: titleId, className: "sr-only", children: [
|
|
169
|
+
title,
|
|
170
|
+
description ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
171
|
+
" ",
|
|
172
|
+
description
|
|
173
|
+
] }) : null
|
|
174
|
+
] }),
|
|
175
|
+
resolved ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { "aria-hidden": "true", className: "size-3.5 text-success" }),
|
|
177
|
+
"Resolved"
|
|
178
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
180
|
+
button.Button,
|
|
181
|
+
{
|
|
182
|
+
size: "sm",
|
|
183
|
+
"aria-describedby": titleId,
|
|
184
|
+
"aria-busy": pending || void 0,
|
|
185
|
+
onClick: act,
|
|
186
|
+
className: "h-7 rounded-full px-3 text-xs",
|
|
187
|
+
leftIcon: pending ? spinner$1 : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { "aria-hidden": "true", className: "size-3.5" }),
|
|
188
|
+
children: pending ? pendingLabel : actionLabel
|
|
189
|
+
}
|
|
190
|
+
),
|
|
191
|
+
dismissButton
|
|
192
|
+
] })
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
} else if (visible) {
|
|
197
|
+
const toast = variant === "side-toast";
|
|
198
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
199
|
+
"section",
|
|
200
|
+
{
|
|
201
|
+
ref,
|
|
202
|
+
"aria-labelledby": titleId,
|
|
203
|
+
onKeyDown: handleKeyDown,
|
|
204
|
+
className: utils.cn(
|
|
205
|
+
"flex items-start gap-3 text-sm animate-nx-fade-in motion-reduce:animate-none",
|
|
206
|
+
toast ? "fixed bottom-4 right-4 z-50 w-80 max-w-[calc(100vw-2rem)] rounded-xl border border-border bg-background p-3.5 shadow-lg" : "rounded-lg border border-brand/30 bg-brand-muted/60 p-3.5",
|
|
207
|
+
className
|
|
208
|
+
),
|
|
209
|
+
...props,
|
|
210
|
+
children: [
|
|
211
|
+
!toast && /* @__PURE__ */ jsxRuntime.jsx(
|
|
212
|
+
"span",
|
|
213
|
+
{
|
|
214
|
+
"aria-hidden": "true",
|
|
215
|
+
className: "grid size-6 shrink-0 place-items-center rounded-md bg-brand text-brand-foreground [&_svg]:size-3.5",
|
|
216
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, {})
|
|
217
|
+
}
|
|
218
|
+
),
|
|
219
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
220
|
+
toast && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 text-xs font-semibold text-brand", children: [
|
|
221
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { "aria-hidden": "true", className: "size-3.5" }),
|
|
222
|
+
"Worth a look"
|
|
223
|
+
] }),
|
|
224
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { id: titleId, className: "font-semibold text-foreground", children: [
|
|
225
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sr-only", children: [
|
|
226
|
+
label,
|
|
227
|
+
": "
|
|
228
|
+
] }),
|
|
229
|
+
title
|
|
230
|
+
] }),
|
|
231
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: description }),
|
|
232
|
+
reason && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: reason }),
|
|
233
|
+
resolved ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { "aria-hidden": "true", className: "size-3.5 text-success" }),
|
|
235
|
+
"Resolved"
|
|
236
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [
|
|
237
|
+
/* @__PURE__ */ jsxRuntime.jsx(button.Button, { size: "sm", onClick: act, "aria-busy": pending || void 0, leftIcon: pending ? spinner$1 : void 0, children: pending ? pendingLabel : actionLabel }),
|
|
238
|
+
onSnooze && /* @__PURE__ */ jsxRuntime.jsx(button.Button, { size: "sm", variant: "ghost", onClick: snooze, children: snoozeLabel })
|
|
239
|
+
] })
|
|
240
|
+
] }),
|
|
241
|
+
!resolved && dismissButton
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-live": "polite", className: "contents", children: body });
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
ProactiveSuggestion.displayName = "ProactiveSuggestion";
|
|
250
|
+
|
|
251
|
+
exports.ProactiveSuggestion = ProactiveSuggestion;
|
|
252
|
+
//# sourceMappingURL=index.cjs.map
|
|
253
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"names":["React","spinner","jsx","Spinner","Button","X","jsxs","cn","Fragment","CheckCircle2","Sparkles"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from \"react\";\nimport { CheckCircle2, Sparkles, X } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Button } from \"@nucleux/button\";\nimport { Spinner } from \"@nucleux/spinner\";\n\n/** Form of the suggestion, from the spec's variants. */\nexport type ProactiveSuggestionVariant = \"contextual-banner\" | \"inline-suggestion\" | \"side-toast\";\n\n/** Where dismissals are remembered when the suggestion has an `evidenceKey`. */\nexport type ProactiveSuggestionPersist = \"none\" | \"session\" | \"local\";\n\nexport interface ProactiveSuggestionProps extends Omit<React.HTMLAttributes<HTMLElement>, \"title\"> {\n /** \"contextual-banner\" (default), \"inline-suggestion\" or \"side-toast\". */\n variant?: ProactiveSuggestionVariant;\n /** The finding, first (\"Three renewals expire this week\"). */\n title: React.ReactNode;\n /** The recommendation, phrased as a question (\"Want Aria to draft outreach for each?\"). */\n description?: React.ReactNode;\n /** The evidence it is based on (\"Acme, Globex and Initech renew before Friday.\"). */\n reason?: React.ReactNode;\n /** The agent's name, used in the accessible label and the provenance line. Defaults to \"the assistant\". */\n agentName?: string;\n /** The one next action (\"Draft emails\"). */\n actionLabel: string;\n /**\n * Called by the action. It should open a review the person confirms, not\n * act on its own. May return a promise: while it is pending the action\n * shows a spinner and `pendingLabel`.\n */\n onAction?: () => void | Promise<unknown>;\n /** Shown on the action while `onAction` is pending. Defaults to \"Preparing…\". */\n pendingLabel?: string;\n /** Shows a snooze action (\"Not now\", \"Remind me tomorrow\"); hides without remembering a dismissal. */\n onSnooze?: () => void;\n /** Label of the snooze action. Defaults to \"Not now\". */\n snoozeLabel?: string;\n /** Called when dismissed (× or Escape). One press; remembered for `evidenceKey`. */\n onDismiss?: () => void;\n /** Accessible name of the dismiss button. Defaults to \"Dismiss suggestion\". */\n dismissLabel?: string;\n /** Whether the suggestion is showing (controlled). */\n open?: boolean;\n /** Initial visibility when uncontrolled. Defaults to true. */\n defaultOpen?: boolean;\n /** Called whenever it shows or hides. */\n onOpenChange?: (open: boolean) => void;\n /**\n * Identifies the facts behind it. With a key, a dismissal is remembered so\n * the same evidence never brings it back, and only one suggestion with a\n * key is shown per browser session.\n */\n evidenceKey?: string;\n /** Where to remember dismissals of `evidenceKey`. Defaults to \"local\". */\n persist?: ProactiveSuggestionPersist;\n /** The reason went away: say \"Resolved\" and drop the actions. */\n resolved?: boolean;\n /** Called once each time it is shown. */\n onImpression?: () => void;\n}\n\nconst KEY = \"nucleux:proactive:\";\nconst SESSION_KEY = \"nucleux:proactive:session\";\n\nfunction getStore(kind: \"session\" | \"local\"): Storage | null {\n try {\n return typeof window === \"undefined\" ? null : kind === \"session\" ? window.sessionStorage : window.localStorage;\n } catch {\n return null;\n }\n}\nfunction read(kind: \"session\" | \"local\", key: string) {\n try {\n return getStore(kind)?.getItem(key) ?? null;\n } catch {\n return null;\n }\n}\nfunction write(kind: \"session\" | \"local\", key: string, value: string) {\n try {\n getStore(kind)?.setItem(key, value);\n } catch {\n /* storage unavailable: memory is best effort */\n }\n}\n\n/**\n * An unsolicited suggestion the agent raises when it notices something\n * specific and actionable — a risk, an anomaly, an overdue step. It leads\n * with the finding, can show the evidence, and offers exactly one next action\n * plus a frictionless dismiss (× or Escape) and an optional snooze; nothing\n * happens until the person acts. It is announced politely, never as a\n * dialog. Use a contextual banner at the top of a surface, an inline\n * suggestion beside the row it is about, or a side toast in the corner. With\n * an `evidenceKey` it is capped at one per session and a dismissed one never\n * comes back for the same facts.\n */\nexport const ProactiveSuggestion = React.forwardRef<HTMLElement, ProactiveSuggestionProps>(\n (\n {\n variant = \"contextual-banner\",\n title,\n description,\n reason,\n agentName = \"the assistant\",\n actionLabel,\n onAction,\n pendingLabel = \"Preparing…\",\n onSnooze,\n snoozeLabel = \"Not now\",\n onDismiss,\n dismissLabel = \"Dismiss suggestion\",\n open: openProp,\n defaultOpen = true,\n onOpenChange,\n evidenceKey,\n persist = \"local\",\n resolved = false,\n onImpression,\n className,\n onKeyDown,\n ...props\n },\n ref,\n ) => {\n const baseId = React.useId();\n const titleId = `${baseId}-title`;\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const open = openProp ?? internalOpen;\n const remembering = evidenceKey != null && persist !== \"none\";\n const [allowed, setAllowed] = React.useState(!remembering);\n const [pending, setPending] = React.useState(false);\n const mounted = React.useRef(true);\n React.useEffect(\n () => () => {\n mounted.current = false;\n },\n [],\n );\n\n React.useEffect(() => {\n if (!remembering) {\n setAllowed(true);\n return;\n }\n const dismissed = read(persist as \"session\" | \"local\", KEY + evidenceKey);\n const shown = read(\"session\", SESSION_KEY);\n setAllowed(!dismissed && (!shown || shown === evidenceKey));\n }, [remembering, persist, evidenceKey]);\n\n const visible = open && allowed;\n\n const impressed = React.useRef(false);\n React.useEffect(() => {\n if (!visible) {\n impressed.current = false;\n return;\n }\n if (impressed.current) return;\n impressed.current = true;\n if (evidenceKey != null) write(\"session\", SESSION_KEY, evidenceKey);\n onImpression?.();\n }, [visible, evidenceKey, onImpression]);\n\n const setOpen = (next: boolean) => {\n if (openProp === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n };\n const dismiss = () => {\n if (remembering) write(persist as \"session\" | \"local\", KEY + evidenceKey, \"dismissed\");\n setOpen(false);\n onDismiss?.();\n };\n const snooze = () => {\n setOpen(false);\n onSnooze?.();\n };\n const act = () => {\n if (!onAction || pending) return;\n const result = onAction();\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n setPending(true);\n (result as Promise<unknown>).finally(() => {\n if (mounted.current) setPending(false);\n });\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {\n onKeyDown?.(e);\n if (e.key === \"Escape\") {\n e.stopPropagation();\n dismiss();\n }\n };\n\n const label = `Suggestion from ${agentName}`;\n const spinner = (\n <Spinner aria-hidden=\"true\" role={undefined} aria-label={undefined} className=\"size-3.5 motion-reduce:animate-none\" />\n );\n const dismissButton = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={dismissLabel}\n onClick={dismiss}\n className=\"size-6 shrink-0 text-muted-foreground [&_svg]:size-3.5\"\n >\n <X aria-hidden=\"true\" />\n </Button>\n );\n\n let body: React.ReactNode = null;\n if (visible && variant === \"inline-suggestion\") {\n body = (\n <section\n ref={ref}\n aria-label={label}\n onKeyDown={handleKeyDown}\n className={cn(\"inline-flex items-center gap-1 animate-nx-fade-in motion-reduce:animate-none\", className)}\n {...props}\n >\n <span id={titleId} className=\"sr-only\">\n {title}\n {description ? <> {description}</> : null}\n </span>\n {resolved ? (\n <span className=\"inline-flex items-center gap-1.5 text-xs text-muted-foreground\">\n <CheckCircle2 aria-hidden=\"true\" className=\"size-3.5 text-success\" />\n Resolved\n </span>\n ) : (\n <>\n <Button\n size=\"sm\"\n aria-describedby={titleId}\n aria-busy={pending || undefined}\n onClick={act}\n className=\"h-7 rounded-full px-3 text-xs\"\n leftIcon={pending ? spinner : <Sparkles aria-hidden=\"true\" className=\"size-3.5\" />}\n >\n {pending ? pendingLabel : actionLabel}\n </Button>\n {dismissButton}\n </>\n )}\n </section>\n );\n } else if (visible) {\n const toast = variant === \"side-toast\";\n body = (\n <section\n ref={ref}\n aria-labelledby={titleId}\n onKeyDown={handleKeyDown}\n className={cn(\n \"flex items-start gap-3 text-sm animate-nx-fade-in motion-reduce:animate-none\",\n toast\n ? \"fixed bottom-4 right-4 z-50 w-80 max-w-[calc(100vw-2rem)] rounded-xl border border-border bg-background p-3.5 shadow-lg\"\n : \"rounded-lg border border-brand/30 bg-brand-muted/60 p-3.5\",\n className,\n )}\n {...props}\n >\n {!toast && (\n <span\n aria-hidden=\"true\"\n className=\"grid size-6 shrink-0 place-items-center rounded-md bg-brand text-brand-foreground [&_svg]:size-3.5\"\n >\n <Sparkles />\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {toast && (\n <p className=\"flex items-center gap-1.5 text-xs font-semibold text-brand\">\n <Sparkles aria-hidden=\"true\" className=\"size-3.5\" />\n Worth a look\n </p>\n )}\n <p id={titleId} className=\"font-semibold text-foreground\">\n <span className=\"sr-only\">{label}: </span>\n {title}\n </p>\n {description && <p className=\"text-muted-foreground\">{description}</p>}\n {reason && <p className=\"text-xs text-muted-foreground\">{reason}</p>}\n {resolved ? (\n <p className=\"mt-1 inline-flex items-center gap-1.5 text-xs text-muted-foreground\">\n <CheckCircle2 aria-hidden=\"true\" className=\"size-3.5 text-success\" />\n Resolved\n </p>\n ) : (\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <Button size=\"sm\" onClick={act} aria-busy={pending || undefined} leftIcon={pending ? spinner : undefined}>\n {pending ? pendingLabel : actionLabel}\n </Button>\n {onSnooze && (\n <Button size=\"sm\" variant=\"ghost\" onClick={snooze}>\n {snoozeLabel}\n </Button>\n )}\n </div>\n )}\n </div>\n {!resolved && dismissButton}\n </section>\n );\n }\n\n // A persistent polite live region, so a suggestion appearing is announced without stealing focus.\n return (\n <div aria-live=\"polite\" className=\"contents\">\n {body}\n </div>\n );\n },\n);\nProactiveSuggestion.displayName = \"ProactiveSuggestion\";\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/** Form of the suggestion, from the spec's variants. */
|
|
4
|
+
type ProactiveSuggestionVariant = "contextual-banner" | "inline-suggestion" | "side-toast";
|
|
5
|
+
/** Where dismissals are remembered when the suggestion has an `evidenceKey`. */
|
|
6
|
+
type ProactiveSuggestionPersist = "none" | "session" | "local";
|
|
7
|
+
interface ProactiveSuggestionProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
8
|
+
/** "contextual-banner" (default), "inline-suggestion" or "side-toast". */
|
|
9
|
+
variant?: ProactiveSuggestionVariant;
|
|
10
|
+
/** The finding, first ("Three renewals expire this week"). */
|
|
11
|
+
title: React.ReactNode;
|
|
12
|
+
/** The recommendation, phrased as a question ("Want Aria to draft outreach for each?"). */
|
|
13
|
+
description?: React.ReactNode;
|
|
14
|
+
/** The evidence it is based on ("Acme, Globex and Initech renew before Friday."). */
|
|
15
|
+
reason?: React.ReactNode;
|
|
16
|
+
/** The agent's name, used in the accessible label and the provenance line. Defaults to "the assistant". */
|
|
17
|
+
agentName?: string;
|
|
18
|
+
/** The one next action ("Draft emails"). */
|
|
19
|
+
actionLabel: string;
|
|
20
|
+
/**
|
|
21
|
+
* Called by the action. It should open a review the person confirms, not
|
|
22
|
+
* act on its own. May return a promise: while it is pending the action
|
|
23
|
+
* shows a spinner and `pendingLabel`.
|
|
24
|
+
*/
|
|
25
|
+
onAction?: () => void | Promise<unknown>;
|
|
26
|
+
/** Shown on the action while `onAction` is pending. Defaults to "Preparing…". */
|
|
27
|
+
pendingLabel?: string;
|
|
28
|
+
/** Shows a snooze action ("Not now", "Remind me tomorrow"); hides without remembering a dismissal. */
|
|
29
|
+
onSnooze?: () => void;
|
|
30
|
+
/** Label of the snooze action. Defaults to "Not now". */
|
|
31
|
+
snoozeLabel?: string;
|
|
32
|
+
/** Called when dismissed (× or Escape). One press; remembered for `evidenceKey`. */
|
|
33
|
+
onDismiss?: () => void;
|
|
34
|
+
/** Accessible name of the dismiss button. Defaults to "Dismiss suggestion". */
|
|
35
|
+
dismissLabel?: string;
|
|
36
|
+
/** Whether the suggestion is showing (controlled). */
|
|
37
|
+
open?: boolean;
|
|
38
|
+
/** Initial visibility when uncontrolled. Defaults to true. */
|
|
39
|
+
defaultOpen?: boolean;
|
|
40
|
+
/** Called whenever it shows or hides. */
|
|
41
|
+
onOpenChange?: (open: boolean) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Identifies the facts behind it. With a key, a dismissal is remembered so
|
|
44
|
+
* the same evidence never brings it back, and only one suggestion with a
|
|
45
|
+
* key is shown per browser session.
|
|
46
|
+
*/
|
|
47
|
+
evidenceKey?: string;
|
|
48
|
+
/** Where to remember dismissals of `evidenceKey`. Defaults to "local". */
|
|
49
|
+
persist?: ProactiveSuggestionPersist;
|
|
50
|
+
/** The reason went away: say "Resolved" and drop the actions. */
|
|
51
|
+
resolved?: boolean;
|
|
52
|
+
/** Called once each time it is shown. */
|
|
53
|
+
onImpression?: () => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* An unsolicited suggestion the agent raises when it notices something
|
|
57
|
+
* specific and actionable — a risk, an anomaly, an overdue step. It leads
|
|
58
|
+
* with the finding, can show the evidence, and offers exactly one next action
|
|
59
|
+
* plus a frictionless dismiss (× or Escape) and an optional snooze; nothing
|
|
60
|
+
* happens until the person acts. It is announced politely, never as a
|
|
61
|
+
* dialog. Use a contextual banner at the top of a surface, an inline
|
|
62
|
+
* suggestion beside the row it is about, or a side toast in the corner. With
|
|
63
|
+
* an `evidenceKey` it is capped at one per session and a dismissed one never
|
|
64
|
+
* comes back for the same facts.
|
|
65
|
+
*/
|
|
66
|
+
declare const ProactiveSuggestion: React.ForwardRefExoticComponent<ProactiveSuggestionProps & React.RefAttributes<HTMLElement>>;
|
|
67
|
+
|
|
68
|
+
export { ProactiveSuggestion, type ProactiveSuggestionPersist, type ProactiveSuggestionProps, type ProactiveSuggestionVariant };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/** Form of the suggestion, from the spec's variants. */
|
|
4
|
+
type ProactiveSuggestionVariant = "contextual-banner" | "inline-suggestion" | "side-toast";
|
|
5
|
+
/** Where dismissals are remembered when the suggestion has an `evidenceKey`. */
|
|
6
|
+
type ProactiveSuggestionPersist = "none" | "session" | "local";
|
|
7
|
+
interface ProactiveSuggestionProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
8
|
+
/** "contextual-banner" (default), "inline-suggestion" or "side-toast". */
|
|
9
|
+
variant?: ProactiveSuggestionVariant;
|
|
10
|
+
/** The finding, first ("Three renewals expire this week"). */
|
|
11
|
+
title: React.ReactNode;
|
|
12
|
+
/** The recommendation, phrased as a question ("Want Aria to draft outreach for each?"). */
|
|
13
|
+
description?: React.ReactNode;
|
|
14
|
+
/** The evidence it is based on ("Acme, Globex and Initech renew before Friday."). */
|
|
15
|
+
reason?: React.ReactNode;
|
|
16
|
+
/** The agent's name, used in the accessible label and the provenance line. Defaults to "the assistant". */
|
|
17
|
+
agentName?: string;
|
|
18
|
+
/** The one next action ("Draft emails"). */
|
|
19
|
+
actionLabel: string;
|
|
20
|
+
/**
|
|
21
|
+
* Called by the action. It should open a review the person confirms, not
|
|
22
|
+
* act on its own. May return a promise: while it is pending the action
|
|
23
|
+
* shows a spinner and `pendingLabel`.
|
|
24
|
+
*/
|
|
25
|
+
onAction?: () => void | Promise<unknown>;
|
|
26
|
+
/** Shown on the action while `onAction` is pending. Defaults to "Preparing…". */
|
|
27
|
+
pendingLabel?: string;
|
|
28
|
+
/** Shows a snooze action ("Not now", "Remind me tomorrow"); hides without remembering a dismissal. */
|
|
29
|
+
onSnooze?: () => void;
|
|
30
|
+
/** Label of the snooze action. Defaults to "Not now". */
|
|
31
|
+
snoozeLabel?: string;
|
|
32
|
+
/** Called when dismissed (× or Escape). One press; remembered for `evidenceKey`. */
|
|
33
|
+
onDismiss?: () => void;
|
|
34
|
+
/** Accessible name of the dismiss button. Defaults to "Dismiss suggestion". */
|
|
35
|
+
dismissLabel?: string;
|
|
36
|
+
/** Whether the suggestion is showing (controlled). */
|
|
37
|
+
open?: boolean;
|
|
38
|
+
/** Initial visibility when uncontrolled. Defaults to true. */
|
|
39
|
+
defaultOpen?: boolean;
|
|
40
|
+
/** Called whenever it shows or hides. */
|
|
41
|
+
onOpenChange?: (open: boolean) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Identifies the facts behind it. With a key, a dismissal is remembered so
|
|
44
|
+
* the same evidence never brings it back, and only one suggestion with a
|
|
45
|
+
* key is shown per browser session.
|
|
46
|
+
*/
|
|
47
|
+
evidenceKey?: string;
|
|
48
|
+
/** Where to remember dismissals of `evidenceKey`. Defaults to "local". */
|
|
49
|
+
persist?: ProactiveSuggestionPersist;
|
|
50
|
+
/** The reason went away: say "Resolved" and drop the actions. */
|
|
51
|
+
resolved?: boolean;
|
|
52
|
+
/** Called once each time it is shown. */
|
|
53
|
+
onImpression?: () => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* An unsolicited suggestion the agent raises when it notices something
|
|
57
|
+
* specific and actionable — a risk, an anomaly, an overdue step. It leads
|
|
58
|
+
* with the finding, can show the evidence, and offers exactly one next action
|
|
59
|
+
* plus a frictionless dismiss (× or Escape) and an optional snooze; nothing
|
|
60
|
+
* happens until the person acts. It is announced politely, never as a
|
|
61
|
+
* dialog. Use a contextual banner at the top of a surface, an inline
|
|
62
|
+
* suggestion beside the row it is about, or a side toast in the corner. With
|
|
63
|
+
* an `evidenceKey` it is capped at one per session and a dismissed one never
|
|
64
|
+
* comes back for the same facts.
|
|
65
|
+
*/
|
|
66
|
+
declare const ProactiveSuggestion: React.ForwardRefExoticComponent<ProactiveSuggestionProps & React.RefAttributes<HTMLElement>>;
|
|
67
|
+
|
|
68
|
+
export { ProactiveSuggestion, type ProactiveSuggestionPersist, type ProactiveSuggestionProps, type ProactiveSuggestionVariant };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { CheckCircle2, Sparkles, X } from 'lucide-react';
|
|
4
|
+
import { cn } from '@nucleux/utils';
|
|
5
|
+
import { Button } from '@nucleux/button';
|
|
6
|
+
import { Spinner } from '@nucleux/spinner';
|
|
7
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
// src/index.tsx
|
|
10
|
+
var KEY = "nucleux:proactive:";
|
|
11
|
+
var SESSION_KEY = "nucleux:proactive:session";
|
|
12
|
+
function getStore(kind) {
|
|
13
|
+
try {
|
|
14
|
+
return typeof window === "undefined" ? null : kind === "session" ? window.sessionStorage : window.localStorage;
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function read(kind, key) {
|
|
20
|
+
try {
|
|
21
|
+
return getStore(kind)?.getItem(key) ?? null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function write(kind, key, value) {
|
|
27
|
+
try {
|
|
28
|
+
getStore(kind)?.setItem(key, value);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var ProactiveSuggestion = React.forwardRef(
|
|
33
|
+
({
|
|
34
|
+
variant = "contextual-banner",
|
|
35
|
+
title,
|
|
36
|
+
description,
|
|
37
|
+
reason,
|
|
38
|
+
agentName = "the assistant",
|
|
39
|
+
actionLabel,
|
|
40
|
+
onAction,
|
|
41
|
+
pendingLabel = "Preparing\u2026",
|
|
42
|
+
onSnooze,
|
|
43
|
+
snoozeLabel = "Not now",
|
|
44
|
+
onDismiss,
|
|
45
|
+
dismissLabel = "Dismiss suggestion",
|
|
46
|
+
open: openProp,
|
|
47
|
+
defaultOpen = true,
|
|
48
|
+
onOpenChange,
|
|
49
|
+
evidenceKey,
|
|
50
|
+
persist = "local",
|
|
51
|
+
resolved = false,
|
|
52
|
+
onImpression,
|
|
53
|
+
className,
|
|
54
|
+
onKeyDown,
|
|
55
|
+
...props
|
|
56
|
+
}, ref) => {
|
|
57
|
+
const baseId = React.useId();
|
|
58
|
+
const titleId = `${baseId}-title`;
|
|
59
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
60
|
+
const open = openProp ?? internalOpen;
|
|
61
|
+
const remembering = evidenceKey != null && persist !== "none";
|
|
62
|
+
const [allowed, setAllowed] = React.useState(!remembering);
|
|
63
|
+
const [pending, setPending] = React.useState(false);
|
|
64
|
+
const mounted = React.useRef(true);
|
|
65
|
+
React.useEffect(
|
|
66
|
+
() => () => {
|
|
67
|
+
mounted.current = false;
|
|
68
|
+
},
|
|
69
|
+
[]
|
|
70
|
+
);
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (!remembering) {
|
|
73
|
+
setAllowed(true);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const dismissed = read(persist, KEY + evidenceKey);
|
|
77
|
+
const shown = read("session", SESSION_KEY);
|
|
78
|
+
setAllowed(!dismissed && (!shown || shown === evidenceKey));
|
|
79
|
+
}, [remembering, persist, evidenceKey]);
|
|
80
|
+
const visible = open && allowed;
|
|
81
|
+
const impressed = React.useRef(false);
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
if (!visible) {
|
|
84
|
+
impressed.current = false;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (impressed.current) return;
|
|
88
|
+
impressed.current = true;
|
|
89
|
+
if (evidenceKey != null) write("session", SESSION_KEY, evidenceKey);
|
|
90
|
+
onImpression?.();
|
|
91
|
+
}, [visible, evidenceKey, onImpression]);
|
|
92
|
+
const setOpen = (next) => {
|
|
93
|
+
if (openProp === void 0) setInternalOpen(next);
|
|
94
|
+
onOpenChange?.(next);
|
|
95
|
+
};
|
|
96
|
+
const dismiss = () => {
|
|
97
|
+
if (remembering) write(persist, KEY + evidenceKey, "dismissed");
|
|
98
|
+
setOpen(false);
|
|
99
|
+
onDismiss?.();
|
|
100
|
+
};
|
|
101
|
+
const snooze = () => {
|
|
102
|
+
setOpen(false);
|
|
103
|
+
onSnooze?.();
|
|
104
|
+
};
|
|
105
|
+
const act = () => {
|
|
106
|
+
if (!onAction || pending) return;
|
|
107
|
+
const result = onAction();
|
|
108
|
+
if (result && typeof result.then === "function") {
|
|
109
|
+
setPending(true);
|
|
110
|
+
result.finally(() => {
|
|
111
|
+
if (mounted.current) setPending(false);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const handleKeyDown = (e) => {
|
|
116
|
+
onKeyDown?.(e);
|
|
117
|
+
if (e.key === "Escape") {
|
|
118
|
+
e.stopPropagation();
|
|
119
|
+
dismiss();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const label = `Suggestion from ${agentName}`;
|
|
123
|
+
const spinner = /* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true", role: void 0, "aria-label": void 0, className: "size-3.5 motion-reduce:animate-none" });
|
|
124
|
+
const dismissButton = /* @__PURE__ */ jsx(
|
|
125
|
+
Button,
|
|
126
|
+
{
|
|
127
|
+
variant: "ghost",
|
|
128
|
+
size: "icon-sm",
|
|
129
|
+
"aria-label": dismissLabel,
|
|
130
|
+
onClick: dismiss,
|
|
131
|
+
className: "size-6 shrink-0 text-muted-foreground [&_svg]:size-3.5",
|
|
132
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
let body = null;
|
|
136
|
+
if (visible && variant === "inline-suggestion") {
|
|
137
|
+
body = /* @__PURE__ */ jsxs(
|
|
138
|
+
"section",
|
|
139
|
+
{
|
|
140
|
+
ref,
|
|
141
|
+
"aria-label": label,
|
|
142
|
+
onKeyDown: handleKeyDown,
|
|
143
|
+
className: cn("inline-flex items-center gap-1 animate-nx-fade-in motion-reduce:animate-none", className),
|
|
144
|
+
...props,
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ jsxs("span", { id: titleId, className: "sr-only", children: [
|
|
147
|
+
title,
|
|
148
|
+
description ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
149
|
+
" ",
|
|
150
|
+
description
|
|
151
|
+
] }) : null
|
|
152
|
+
] }),
|
|
153
|
+
resolved ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
154
|
+
/* @__PURE__ */ jsx(CheckCircle2, { "aria-hidden": "true", className: "size-3.5 text-success" }),
|
|
155
|
+
"Resolved"
|
|
156
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
157
|
+
/* @__PURE__ */ jsx(
|
|
158
|
+
Button,
|
|
159
|
+
{
|
|
160
|
+
size: "sm",
|
|
161
|
+
"aria-describedby": titleId,
|
|
162
|
+
"aria-busy": pending || void 0,
|
|
163
|
+
onClick: act,
|
|
164
|
+
className: "h-7 rounded-full px-3 text-xs",
|
|
165
|
+
leftIcon: pending ? spinner : /* @__PURE__ */ jsx(Sparkles, { "aria-hidden": "true", className: "size-3.5" }),
|
|
166
|
+
children: pending ? pendingLabel : actionLabel
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
dismissButton
|
|
170
|
+
] })
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
} else if (visible) {
|
|
175
|
+
const toast = variant === "side-toast";
|
|
176
|
+
body = /* @__PURE__ */ jsxs(
|
|
177
|
+
"section",
|
|
178
|
+
{
|
|
179
|
+
ref,
|
|
180
|
+
"aria-labelledby": titleId,
|
|
181
|
+
onKeyDown: handleKeyDown,
|
|
182
|
+
className: cn(
|
|
183
|
+
"flex items-start gap-3 text-sm animate-nx-fade-in motion-reduce:animate-none",
|
|
184
|
+
toast ? "fixed bottom-4 right-4 z-50 w-80 max-w-[calc(100vw-2rem)] rounded-xl border border-border bg-background p-3.5 shadow-lg" : "rounded-lg border border-brand/30 bg-brand-muted/60 p-3.5",
|
|
185
|
+
className
|
|
186
|
+
),
|
|
187
|
+
...props,
|
|
188
|
+
children: [
|
|
189
|
+
!toast && /* @__PURE__ */ jsx(
|
|
190
|
+
"span",
|
|
191
|
+
{
|
|
192
|
+
"aria-hidden": "true",
|
|
193
|
+
className: "grid size-6 shrink-0 place-items-center rounded-md bg-brand text-brand-foreground [&_svg]:size-3.5",
|
|
194
|
+
children: /* @__PURE__ */ jsx(Sparkles, {})
|
|
195
|
+
}
|
|
196
|
+
),
|
|
197
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
198
|
+
toast && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-xs font-semibold text-brand", children: [
|
|
199
|
+
/* @__PURE__ */ jsx(Sparkles, { "aria-hidden": "true", className: "size-3.5" }),
|
|
200
|
+
"Worth a look"
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ jsxs("p", { id: titleId, className: "font-semibold text-foreground", children: [
|
|
203
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
204
|
+
label,
|
|
205
|
+
": "
|
|
206
|
+
] }),
|
|
207
|
+
title
|
|
208
|
+
] }),
|
|
209
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description }),
|
|
210
|
+
reason && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: reason }),
|
|
211
|
+
resolved ? /* @__PURE__ */ jsxs("p", { className: "mt-1 inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
212
|
+
/* @__PURE__ */ jsx(CheckCircle2, { "aria-hidden": "true", className: "size-3.5 text-success" }),
|
|
213
|
+
"Resolved"
|
|
214
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [
|
|
215
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: act, "aria-busy": pending || void 0, leftIcon: pending ? spinner : void 0, children: pending ? pendingLabel : actionLabel }),
|
|
216
|
+
onSnooze && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: snooze, children: snoozeLabel })
|
|
217
|
+
] })
|
|
218
|
+
] }),
|
|
219
|
+
!resolved && dismissButton
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "contents", children: body });
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
ProactiveSuggestion.displayName = "ProactiveSuggestion";
|
|
228
|
+
|
|
229
|
+
export { ProactiveSuggestion };
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
231
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import * as React from \"react\";\nimport { CheckCircle2, Sparkles, X } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Button } from \"@nucleux/button\";\nimport { Spinner } from \"@nucleux/spinner\";\n\n/** Form of the suggestion, from the spec's variants. */\nexport type ProactiveSuggestionVariant = \"contextual-banner\" | \"inline-suggestion\" | \"side-toast\";\n\n/** Where dismissals are remembered when the suggestion has an `evidenceKey`. */\nexport type ProactiveSuggestionPersist = \"none\" | \"session\" | \"local\";\n\nexport interface ProactiveSuggestionProps extends Omit<React.HTMLAttributes<HTMLElement>, \"title\"> {\n /** \"contextual-banner\" (default), \"inline-suggestion\" or \"side-toast\". */\n variant?: ProactiveSuggestionVariant;\n /** The finding, first (\"Three renewals expire this week\"). */\n title: React.ReactNode;\n /** The recommendation, phrased as a question (\"Want Aria to draft outreach for each?\"). */\n description?: React.ReactNode;\n /** The evidence it is based on (\"Acme, Globex and Initech renew before Friday.\"). */\n reason?: React.ReactNode;\n /** The agent's name, used in the accessible label and the provenance line. Defaults to \"the assistant\". */\n agentName?: string;\n /** The one next action (\"Draft emails\"). */\n actionLabel: string;\n /**\n * Called by the action. It should open a review the person confirms, not\n * act on its own. May return a promise: while it is pending the action\n * shows a spinner and `pendingLabel`.\n */\n onAction?: () => void | Promise<unknown>;\n /** Shown on the action while `onAction` is pending. Defaults to \"Preparing…\". */\n pendingLabel?: string;\n /** Shows a snooze action (\"Not now\", \"Remind me tomorrow\"); hides without remembering a dismissal. */\n onSnooze?: () => void;\n /** Label of the snooze action. Defaults to \"Not now\". */\n snoozeLabel?: string;\n /** Called when dismissed (× or Escape). One press; remembered for `evidenceKey`. */\n onDismiss?: () => void;\n /** Accessible name of the dismiss button. Defaults to \"Dismiss suggestion\". */\n dismissLabel?: string;\n /** Whether the suggestion is showing (controlled). */\n open?: boolean;\n /** Initial visibility when uncontrolled. Defaults to true. */\n defaultOpen?: boolean;\n /** Called whenever it shows or hides. */\n onOpenChange?: (open: boolean) => void;\n /**\n * Identifies the facts behind it. With a key, a dismissal is remembered so\n * the same evidence never brings it back, and only one suggestion with a\n * key is shown per browser session.\n */\n evidenceKey?: string;\n /** Where to remember dismissals of `evidenceKey`. Defaults to \"local\". */\n persist?: ProactiveSuggestionPersist;\n /** The reason went away: say \"Resolved\" and drop the actions. */\n resolved?: boolean;\n /** Called once each time it is shown. */\n onImpression?: () => void;\n}\n\nconst KEY = \"nucleux:proactive:\";\nconst SESSION_KEY = \"nucleux:proactive:session\";\n\nfunction getStore(kind: \"session\" | \"local\"): Storage | null {\n try {\n return typeof window === \"undefined\" ? null : kind === \"session\" ? window.sessionStorage : window.localStorage;\n } catch {\n return null;\n }\n}\nfunction read(kind: \"session\" | \"local\", key: string) {\n try {\n return getStore(kind)?.getItem(key) ?? null;\n } catch {\n return null;\n }\n}\nfunction write(kind: \"session\" | \"local\", key: string, value: string) {\n try {\n getStore(kind)?.setItem(key, value);\n } catch {\n /* storage unavailable: memory is best effort */\n }\n}\n\n/**\n * An unsolicited suggestion the agent raises when it notices something\n * specific and actionable — a risk, an anomaly, an overdue step. It leads\n * with the finding, can show the evidence, and offers exactly one next action\n * plus a frictionless dismiss (× or Escape) and an optional snooze; nothing\n * happens until the person acts. It is announced politely, never as a\n * dialog. Use a contextual banner at the top of a surface, an inline\n * suggestion beside the row it is about, or a side toast in the corner. With\n * an `evidenceKey` it is capped at one per session and a dismissed one never\n * comes back for the same facts.\n */\nexport const ProactiveSuggestion = React.forwardRef<HTMLElement, ProactiveSuggestionProps>(\n (\n {\n variant = \"contextual-banner\",\n title,\n description,\n reason,\n agentName = \"the assistant\",\n actionLabel,\n onAction,\n pendingLabel = \"Preparing…\",\n onSnooze,\n snoozeLabel = \"Not now\",\n onDismiss,\n dismissLabel = \"Dismiss suggestion\",\n open: openProp,\n defaultOpen = true,\n onOpenChange,\n evidenceKey,\n persist = \"local\",\n resolved = false,\n onImpression,\n className,\n onKeyDown,\n ...props\n },\n ref,\n ) => {\n const baseId = React.useId();\n const titleId = `${baseId}-title`;\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const open = openProp ?? internalOpen;\n const remembering = evidenceKey != null && persist !== \"none\";\n const [allowed, setAllowed] = React.useState(!remembering);\n const [pending, setPending] = React.useState(false);\n const mounted = React.useRef(true);\n React.useEffect(\n () => () => {\n mounted.current = false;\n },\n [],\n );\n\n React.useEffect(() => {\n if (!remembering) {\n setAllowed(true);\n return;\n }\n const dismissed = read(persist as \"session\" | \"local\", KEY + evidenceKey);\n const shown = read(\"session\", SESSION_KEY);\n setAllowed(!dismissed && (!shown || shown === evidenceKey));\n }, [remembering, persist, evidenceKey]);\n\n const visible = open && allowed;\n\n const impressed = React.useRef(false);\n React.useEffect(() => {\n if (!visible) {\n impressed.current = false;\n return;\n }\n if (impressed.current) return;\n impressed.current = true;\n if (evidenceKey != null) write(\"session\", SESSION_KEY, evidenceKey);\n onImpression?.();\n }, [visible, evidenceKey, onImpression]);\n\n const setOpen = (next: boolean) => {\n if (openProp === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n };\n const dismiss = () => {\n if (remembering) write(persist as \"session\" | \"local\", KEY + evidenceKey, \"dismissed\");\n setOpen(false);\n onDismiss?.();\n };\n const snooze = () => {\n setOpen(false);\n onSnooze?.();\n };\n const act = () => {\n if (!onAction || pending) return;\n const result = onAction();\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n setPending(true);\n (result as Promise<unknown>).finally(() => {\n if (mounted.current) setPending(false);\n });\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {\n onKeyDown?.(e);\n if (e.key === \"Escape\") {\n e.stopPropagation();\n dismiss();\n }\n };\n\n const label = `Suggestion from ${agentName}`;\n const spinner = (\n <Spinner aria-hidden=\"true\" role={undefined} aria-label={undefined} className=\"size-3.5 motion-reduce:animate-none\" />\n );\n const dismissButton = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={dismissLabel}\n onClick={dismiss}\n className=\"size-6 shrink-0 text-muted-foreground [&_svg]:size-3.5\"\n >\n <X aria-hidden=\"true\" />\n </Button>\n );\n\n let body: React.ReactNode = null;\n if (visible && variant === \"inline-suggestion\") {\n body = (\n <section\n ref={ref}\n aria-label={label}\n onKeyDown={handleKeyDown}\n className={cn(\"inline-flex items-center gap-1 animate-nx-fade-in motion-reduce:animate-none\", className)}\n {...props}\n >\n <span id={titleId} className=\"sr-only\">\n {title}\n {description ? <> {description}</> : null}\n </span>\n {resolved ? (\n <span className=\"inline-flex items-center gap-1.5 text-xs text-muted-foreground\">\n <CheckCircle2 aria-hidden=\"true\" className=\"size-3.5 text-success\" />\n Resolved\n </span>\n ) : (\n <>\n <Button\n size=\"sm\"\n aria-describedby={titleId}\n aria-busy={pending || undefined}\n onClick={act}\n className=\"h-7 rounded-full px-3 text-xs\"\n leftIcon={pending ? spinner : <Sparkles aria-hidden=\"true\" className=\"size-3.5\" />}\n >\n {pending ? pendingLabel : actionLabel}\n </Button>\n {dismissButton}\n </>\n )}\n </section>\n );\n } else if (visible) {\n const toast = variant === \"side-toast\";\n body = (\n <section\n ref={ref}\n aria-labelledby={titleId}\n onKeyDown={handleKeyDown}\n className={cn(\n \"flex items-start gap-3 text-sm animate-nx-fade-in motion-reduce:animate-none\",\n toast\n ? \"fixed bottom-4 right-4 z-50 w-80 max-w-[calc(100vw-2rem)] rounded-xl border border-border bg-background p-3.5 shadow-lg\"\n : \"rounded-lg border border-brand/30 bg-brand-muted/60 p-3.5\",\n className,\n )}\n {...props}\n >\n {!toast && (\n <span\n aria-hidden=\"true\"\n className=\"grid size-6 shrink-0 place-items-center rounded-md bg-brand text-brand-foreground [&_svg]:size-3.5\"\n >\n <Sparkles />\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {toast && (\n <p className=\"flex items-center gap-1.5 text-xs font-semibold text-brand\">\n <Sparkles aria-hidden=\"true\" className=\"size-3.5\" />\n Worth a look\n </p>\n )}\n <p id={titleId} className=\"font-semibold text-foreground\">\n <span className=\"sr-only\">{label}: </span>\n {title}\n </p>\n {description && <p className=\"text-muted-foreground\">{description}</p>}\n {reason && <p className=\"text-xs text-muted-foreground\">{reason}</p>}\n {resolved ? (\n <p className=\"mt-1 inline-flex items-center gap-1.5 text-xs text-muted-foreground\">\n <CheckCircle2 aria-hidden=\"true\" className=\"size-3.5 text-success\" />\n Resolved\n </p>\n ) : (\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <Button size=\"sm\" onClick={act} aria-busy={pending || undefined} leftIcon={pending ? spinner : undefined}>\n {pending ? pendingLabel : actionLabel}\n </Button>\n {onSnooze && (\n <Button size=\"sm\" variant=\"ghost\" onClick={snooze}>\n {snoozeLabel}\n </Button>\n )}\n </div>\n )}\n </div>\n {!resolved && dismissButton}\n </section>\n );\n }\n\n // A persistent polite live region, so a suggestion appearing is announced without stealing focus.\n return (\n <div aria-live=\"polite\" className=\"contents\">\n {body}\n </div>\n );\n },\n);\nProactiveSuggestion.displayName = \"ProactiveSuggestion\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nucleux/proactive",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Proactive Suggestions — invisible AI moments that arrive exactly when needed — Nucleux agentic UI for React.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nucleux",
|
|
9
|
+
"react",
|
|
10
|
+
"ui",
|
|
11
|
+
"components",
|
|
12
|
+
"agentic",
|
|
13
|
+
"ai",
|
|
14
|
+
"tailwind",
|
|
15
|
+
"proactive"
|
|
16
|
+
],
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"require": "./dist/index.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=18",
|
|
33
|
+
"react-dom": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@nucleux/button": "0.6.0",
|
|
37
|
+
"@nucleux/spinner": "0.6.0",
|
|
38
|
+
"@nucleux/utils": "0.6.0",
|
|
39
|
+
"lucide-react": "^0.469.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"tsup": "^8.3.5",
|
|
43
|
+
"typescript": "^5.7.2",
|
|
44
|
+
"@types/react": "^18.3.18",
|
|
45
|
+
"react": "^18.3.1"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/intelligaia-open-labs/nucleux.git",
|
|
54
|
+
"directory": "packages/proactive"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/intelligaia-open-labs/nucleux#readme",
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup"
|
|
59
|
+
}
|
|
6
60
|
}
|