@seed-ship/mcp-ui-solid 2.5.1 → 2.5.3
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/components/ChatPrompt.cjs +93 -71
- package/dist/components/ChatPrompt.cjs.map +1 -1
- package/dist/components/ChatPrompt.d.ts +3 -1
- package/dist/components/ChatPrompt.d.ts.map +1 -1
- package/dist/components/ChatPrompt.js +95 -73
- package/dist/components/ChatPrompt.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChatPrompt.test.tsx +75 -0
- package/src/components/ChatPrompt.tsx +18 -6
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { delegateEvents, getNextElement, template, insert, createComponent, effect, setAttribute, runHydrationEvents, getNextMarker,
|
|
2
|
-
import { Switch, Match, For,
|
|
3
|
-
var _tmpl$ = /* @__PURE__ */ template(`<div class="w-full max-w-2xl mx-auto mb-2 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg overflow-hidden"role=dialog style="animation:chat-prompt-slide-up 0.2s ease-out"><div class="flex items-center justify-between px-4 py-2.5 border-b border-gray-100 dark:border-gray-700"><p class="text-sm font-medium text-gray-900 dark:text-white"></p><button
|
|
1
|
+
import { delegateEvents, getNextElement, template, insert, createComponent, effect, setAttribute, className, runHydrationEvents, getNextMarker, addEventListener, setProperty } from "solid-js/web";
|
|
2
|
+
import { Show, Switch, Match, For, createSignal } from "solid-js";
|
|
3
|
+
var _tmpl$ = /* @__PURE__ */ template(`<div class="w-full max-w-2xl mx-auto mb-2 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg overflow-hidden"role=dialog style="animation:chat-prompt-slide-up 0.2s ease-out"><div class="flex items-center justify-between px-4 py-2.5 border-b border-gray-100 dark:border-gray-700"><p class="text-sm font-medium text-gray-900 dark:text-white"></p><button></button></div><div class="px-4 py-3"></div><style>
|
|
4
4
|
@keyframes chat-prompt-slide-up {
|
|
5
5
|
from { opacity: 0; transform: translateY(8px); }
|
|
6
6
|
to { opacity: 1; transform: translateY(0); }
|
|
7
7
|
}
|
|
8
|
-
`), _tmpl$2 = /* @__PURE__ */ template(`<
|
|
8
|
+
`), _tmpl$2 = /* @__PURE__ */ template(`<svg class="w-4 h-4"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M6 18L18 6M6 6l12 12">`), _tmpl$3 = /* @__PURE__ */ template(`<div>`), _tmpl$4 = /* @__PURE__ */ template(`<span class=mr-2>`), _tmpl$5 = /* @__PURE__ */ template(`<span class="block text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-normal">`), _tmpl$6 = /* @__PURE__ */ template(`<button class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-blue-50 hover:border-blue-300 dark:hover:bg-blue-900/30 dark:hover:border-blue-600 transition-colors text-left"><!$><!/><!$><!/><!$><!/>`), _tmpl$7 = /* @__PURE__ */ template(`<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">`), _tmpl$8 = /* @__PURE__ */ template(`<div><!$><!/><div class="flex gap-2 justify-end"><button class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"></button><button>`), _tmpl$9 = /* @__PURE__ */ template(`<form class="flex flex-col gap-3"><!$><!/><div class="flex justify-end"><button type=submit class="px-4 py-2 text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors">`), _tmpl$0 = /* @__PURE__ */ template(`<span class="text-red-500 ml-0.5">*`), _tmpl$1 = /* @__PURE__ */ template(`<textarea rows=3 class="w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors">`), _tmpl$10 = /* @__PURE__ */ template(`<select class="w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors"><option value></option><!$><!/>`), _tmpl$11 = /* @__PURE__ */ template(`<input class="w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors">`), _tmpl$12 = /* @__PURE__ */ template(`<div><label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1"><!$><!/><!$><!/></label><!$><!/>`), _tmpl$13 = /* @__PURE__ */ template(`<option>`);
|
|
9
9
|
const ChatPrompt = (props) => {
|
|
10
|
+
if (!props.config) return null;
|
|
10
11
|
return (() => {
|
|
11
12
|
var _el$ = getNextElement(_tmpl$), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$2.nextSibling;
|
|
12
13
|
insert(_el$3, () => props.config.title);
|
|
@@ -20,6 +21,17 @@ const ChatPrompt = (props) => {
|
|
|
20
21
|
dismissed: true
|
|
21
22
|
});
|
|
22
23
|
};
|
|
24
|
+
insert(_el$4, createComponent(Show, {
|
|
25
|
+
get when() {
|
|
26
|
+
return props.dismissLabel;
|
|
27
|
+
},
|
|
28
|
+
get fallback() {
|
|
29
|
+
return getNextElement(_tmpl$2);
|
|
30
|
+
},
|
|
31
|
+
get children() {
|
|
32
|
+
return props.dismissLabel;
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
23
35
|
insert(_el$5, createComponent(Switch, {
|
|
24
36
|
get children() {
|
|
25
37
|
return [createComponent(Match, {
|
|
@@ -83,7 +95,17 @@ const ChatPrompt = (props) => {
|
|
|
83
95
|
})];
|
|
84
96
|
}
|
|
85
97
|
}));
|
|
86
|
-
effect(() =>
|
|
98
|
+
effect((_p$) => {
|
|
99
|
+
var _v$ = props.config.title, _v$2 = props.dismissLabel ? "px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors" : "p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors", _v$3 = props.dismissLabel || "Dismiss";
|
|
100
|
+
_v$ !== _p$.e && setAttribute(_el$, "aria-label", _p$.e = _v$);
|
|
101
|
+
_v$2 !== _p$.t && className(_el$4, _p$.t = _v$2);
|
|
102
|
+
_v$3 !== _p$.a && setAttribute(_el$4, "aria-label", _p$.a = _v$3);
|
|
103
|
+
return _p$;
|
|
104
|
+
}, {
|
|
105
|
+
e: void 0,
|
|
106
|
+
t: void 0,
|
|
107
|
+
a: void 0
|
|
108
|
+
});
|
|
87
109
|
runHydrationEvents();
|
|
88
110
|
return _el$;
|
|
89
111
|
})();
|
|
@@ -100,64 +122,64 @@ const ChoiceBody = (props) => {
|
|
|
100
122
|
}
|
|
101
123
|
};
|
|
102
124
|
return (() => {
|
|
103
|
-
var _el$
|
|
104
|
-
insert(_el$
|
|
125
|
+
var _el$7 = getNextElement(_tmpl$3);
|
|
126
|
+
insert(_el$7, createComponent(For, {
|
|
105
127
|
get each() {
|
|
106
128
|
return props.config.options;
|
|
107
129
|
},
|
|
108
130
|
children: (option) => (() => {
|
|
109
|
-
var _el$
|
|
110
|
-
_el$
|
|
111
|
-
insert(_el$
|
|
131
|
+
var _el$8 = getNextElement(_tmpl$6), _el$1 = _el$8.firstChild, [_el$10, _co$] = getNextMarker(_el$1.nextSibling), _el$11 = _el$10.nextSibling, [_el$12, _co$2] = getNextMarker(_el$11.nextSibling), _el$13 = _el$12.nextSibling, [_el$14, _co$3] = getNextMarker(_el$13.nextSibling);
|
|
132
|
+
_el$8.$$click = () => props.onSelect(option.value, option.label);
|
|
133
|
+
insert(_el$8, createComponent(Show, {
|
|
112
134
|
get when() {
|
|
113
135
|
return option.icon;
|
|
114
136
|
},
|
|
115
137
|
get children() {
|
|
116
|
-
var _el$
|
|
117
|
-
insert(_el$
|
|
118
|
-
return _el$
|
|
138
|
+
var _el$9 = getNextElement(_tmpl$4);
|
|
139
|
+
insert(_el$9, () => option.icon);
|
|
140
|
+
return _el$9;
|
|
119
141
|
}
|
|
120
|
-
}), _el$
|
|
121
|
-
insert(_el$
|
|
122
|
-
insert(_el$
|
|
142
|
+
}), _el$10, _co$);
|
|
143
|
+
insert(_el$8, () => option.label, _el$12, _co$2);
|
|
144
|
+
insert(_el$8, createComponent(Show, {
|
|
123
145
|
get when() {
|
|
124
146
|
return option.description;
|
|
125
147
|
},
|
|
126
148
|
get children() {
|
|
127
|
-
var _el$
|
|
128
|
-
insert(_el$
|
|
129
|
-
return _el$
|
|
149
|
+
var _el$0 = getNextElement(_tmpl$5);
|
|
150
|
+
insert(_el$0, () => option.description);
|
|
151
|
+
return _el$0;
|
|
130
152
|
}
|
|
131
|
-
}), _el$
|
|
153
|
+
}), _el$14, _co$3);
|
|
132
154
|
runHydrationEvents();
|
|
133
|
-
return _el$
|
|
155
|
+
return _el$8;
|
|
134
156
|
})()
|
|
135
157
|
}));
|
|
136
|
-
effect(() => className(_el$
|
|
137
|
-
return _el$
|
|
158
|
+
effect(() => className(_el$7, layoutClass()));
|
|
159
|
+
return _el$7;
|
|
138
160
|
})();
|
|
139
161
|
};
|
|
140
162
|
const ConfirmBody = (props) => {
|
|
141
163
|
const isDanger = () => props.config.variant === "danger";
|
|
142
164
|
return (() => {
|
|
143
|
-
var _el$
|
|
144
|
-
insert(_el$
|
|
165
|
+
var _el$15 = getNextElement(_tmpl$8), _el$20 = _el$15.firstChild, [_el$21, _co$4] = getNextMarker(_el$20.nextSibling), _el$17 = _el$21.nextSibling, _el$18 = _el$17.firstChild, _el$19 = _el$18.nextSibling;
|
|
166
|
+
insert(_el$15, createComponent(Show, {
|
|
145
167
|
get when() {
|
|
146
168
|
return props.config.message;
|
|
147
169
|
},
|
|
148
170
|
get children() {
|
|
149
|
-
var _el$
|
|
150
|
-
insert(_el$
|
|
151
|
-
return _el$
|
|
171
|
+
var _el$16 = getNextElement(_tmpl$7);
|
|
172
|
+
insert(_el$16, () => props.config.message);
|
|
173
|
+
return _el$16;
|
|
152
174
|
}
|
|
153
|
-
}), _el$
|
|
154
|
-
addEventListener(_el$
|
|
155
|
-
insert(_el$
|
|
156
|
-
addEventListener(_el$
|
|
157
|
-
insert(_el$
|
|
158
|
-
effect(() => className(_el$
|
|
175
|
+
}), _el$21, _co$4);
|
|
176
|
+
addEventListener(_el$18, "click", props.onCancel, true);
|
|
177
|
+
insert(_el$18, () => props.config.cancelLabel || "Cancel");
|
|
178
|
+
addEventListener(_el$19, "click", props.onConfirm, true);
|
|
179
|
+
insert(_el$19, () => props.config.confirmLabel || "Confirm");
|
|
180
|
+
effect(() => className(_el$19, `px-4 py-2 text-sm font-medium rounded-lg text-white transition-colors ${isDanger() ? "bg-red-600 hover:bg-red-700" : "bg-blue-600 hover:bg-blue-700"}`));
|
|
159
181
|
runHydrationEvents();
|
|
160
|
-
return _el$
|
|
182
|
+
return _el$15;
|
|
161
183
|
})();
|
|
162
184
|
};
|
|
163
185
|
const FormBody = (props) => {
|
|
@@ -182,86 +204,86 @@ const FormBody = (props) => {
|
|
|
182
204
|
});
|
|
183
205
|
};
|
|
184
206
|
return (() => {
|
|
185
|
-
var _el$
|
|
186
|
-
_el$
|
|
187
|
-
insert(_el$
|
|
207
|
+
var _el$22 = getNextElement(_tmpl$9), _el$25 = _el$22.firstChild, [_el$26, _co$5] = getNextMarker(_el$25.nextSibling), _el$23 = _el$26.nextSibling, _el$24 = _el$23.firstChild;
|
|
208
|
+
_el$22.addEventListener("submit", handleSubmit);
|
|
209
|
+
insert(_el$22, createComponent(For, {
|
|
188
210
|
get each() {
|
|
189
211
|
return props.config.fields;
|
|
190
212
|
},
|
|
191
213
|
children: (field) => (() => {
|
|
192
|
-
var _el$
|
|
193
|
-
insert(_el$
|
|
194
|
-
insert(_el$
|
|
214
|
+
var _el$27 = getNextElement(_tmpl$12), _el$28 = _el$27.firstChild, _el$30 = _el$28.firstChild, [_el$31, _co$6] = getNextMarker(_el$30.nextSibling), _el$32 = _el$31.nextSibling, [_el$33, _co$7] = getNextMarker(_el$32.nextSibling), _el$40 = _el$28.nextSibling, [_el$41, _co$9] = getNextMarker(_el$40.nextSibling);
|
|
215
|
+
insert(_el$28, () => field.label, _el$31, _co$6);
|
|
216
|
+
insert(_el$28, createComponent(Show, {
|
|
195
217
|
get when() {
|
|
196
218
|
return field.required;
|
|
197
219
|
},
|
|
198
220
|
get children() {
|
|
199
|
-
return getNextElement(_tmpl$
|
|
221
|
+
return getNextElement(_tmpl$0);
|
|
200
222
|
}
|
|
201
|
-
}), _el$
|
|
202
|
-
insert(_el$
|
|
223
|
+
}), _el$33, _co$7);
|
|
224
|
+
insert(_el$27, createComponent(Switch, {
|
|
203
225
|
get children() {
|
|
204
226
|
return [createComponent(Match, {
|
|
205
227
|
get when() {
|
|
206
228
|
return field.type === "textarea";
|
|
207
229
|
},
|
|
208
230
|
get children() {
|
|
209
|
-
var _el$
|
|
210
|
-
_el$
|
|
211
|
-
effect(() => setAttribute(_el$
|
|
212
|
-
effect(() => setProperty(_el$
|
|
231
|
+
var _el$34 = getNextElement(_tmpl$1);
|
|
232
|
+
_el$34.$$input = (e) => updateField(field.name, e.currentTarget.value);
|
|
233
|
+
effect(() => setAttribute(_el$34, "placeholder", field.placeholder));
|
|
234
|
+
effect(() => setProperty(_el$34, "value", formData()[field.name] || ""));
|
|
213
235
|
runHydrationEvents();
|
|
214
|
-
return _el$
|
|
236
|
+
return _el$34;
|
|
215
237
|
}
|
|
216
238
|
}), createComponent(Match, {
|
|
217
239
|
get when() {
|
|
218
240
|
return field.type === "select";
|
|
219
241
|
},
|
|
220
242
|
get children() {
|
|
221
|
-
var _el$
|
|
222
|
-
_el$
|
|
223
|
-
insert(_el$
|
|
224
|
-
insert(_el$
|
|
243
|
+
var _el$35 = getNextElement(_tmpl$10), _el$36 = _el$35.firstChild, _el$37 = _el$36.nextSibling, [_el$38, _co$8] = getNextMarker(_el$37.nextSibling);
|
|
244
|
+
_el$35.addEventListener("change", (e) => updateField(field.name, e.currentTarget.value));
|
|
245
|
+
insert(_el$36, () => field.placeholder || "Select...");
|
|
246
|
+
insert(_el$35, createComponent(For, {
|
|
225
247
|
get each() {
|
|
226
248
|
return field.options;
|
|
227
249
|
},
|
|
228
250
|
children: (opt) => (() => {
|
|
229
|
-
var _el$
|
|
230
|
-
insert(_el$
|
|
231
|
-
effect(() => setProperty(_el$
|
|
232
|
-
return _el$
|
|
251
|
+
var _el$42 = getNextElement(_tmpl$13);
|
|
252
|
+
insert(_el$42, () => opt.label);
|
|
253
|
+
effect(() => setProperty(_el$42, "value", opt.value));
|
|
254
|
+
return _el$42;
|
|
233
255
|
})()
|
|
234
|
-
}), _el$
|
|
235
|
-
effect(() => setProperty(_el$
|
|
236
|
-
return _el$
|
|
256
|
+
}), _el$38, _co$8);
|
|
257
|
+
effect(() => setProperty(_el$35, "value", formData()[field.name] || ""));
|
|
258
|
+
return _el$35;
|
|
237
259
|
}
|
|
238
260
|
}), createComponent(Match, {
|
|
239
261
|
when: true,
|
|
240
262
|
get children() {
|
|
241
|
-
var _el$
|
|
242
|
-
_el$
|
|
263
|
+
var _el$39 = getNextElement(_tmpl$11);
|
|
264
|
+
_el$39.$$input = (e) => updateField(field.name, e.currentTarget.value);
|
|
243
265
|
effect((_p$) => {
|
|
244
|
-
var _v$ = field.type === "number" ? "number" : "text", _v$
|
|
245
|
-
_v$ !== _p$.e && setAttribute(_el$
|
|
246
|
-
_v$
|
|
266
|
+
var _v$4 = field.type === "number" ? "number" : "text", _v$5 = field.placeholder;
|
|
267
|
+
_v$4 !== _p$.e && setAttribute(_el$39, "type", _p$.e = _v$4);
|
|
268
|
+
_v$5 !== _p$.t && setAttribute(_el$39, "placeholder", _p$.t = _v$5);
|
|
247
269
|
return _p$;
|
|
248
270
|
}, {
|
|
249
271
|
e: void 0,
|
|
250
272
|
t: void 0
|
|
251
273
|
});
|
|
252
|
-
effect(() => setProperty(_el$
|
|
274
|
+
effect(() => setProperty(_el$39, "value", formData()[field.name] || ""));
|
|
253
275
|
runHydrationEvents();
|
|
254
|
-
return _el$
|
|
276
|
+
return _el$39;
|
|
255
277
|
}
|
|
256
278
|
})];
|
|
257
279
|
}
|
|
258
|
-
}), _el$
|
|
259
|
-
return _el$
|
|
280
|
+
}), _el$41, _co$9);
|
|
281
|
+
return _el$27;
|
|
260
282
|
})()
|
|
261
|
-
}), _el$
|
|
262
|
-
insert(_el$
|
|
263
|
-
effect(() => setProperty(_el$
|
|
264
|
-
return _el$
|
|
283
|
+
}), _el$26, _co$5);
|
|
284
|
+
insert(_el$24, () => props.config.submitLabel || "Submit");
|
|
285
|
+
effect(() => setProperty(_el$24, "disabled", !isValid()));
|
|
286
|
+
return _el$22;
|
|
265
287
|
})();
|
|
266
288
|
};
|
|
267
289
|
delegateEvents(["click", "input"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPrompt.js","sources":["../../src/components/ChatPrompt.tsx"],"sourcesContent":["/**\n * ChatPrompt — Ephemeral structured interaction above chat input\n * v2.4.0: choice, confirm, form subtypes\n *\n * @experimental — This component may change without major bump until v2.5.0.\n *\n * Renders above the chat input. User responds → Promise resolves → prompt disappears.\n * Supports AbortSignal for cleanup on navigation (C4).\n */\n\nimport { Component, Show, For, createSignal, onCleanup, Switch, Match } from 'solid-js'\nimport type {\n ChatPromptConfig,\n ChatPromptResponse,\n ChoicePromptConfig,\n ConfirmPromptConfig,\n FormPromptConfig,\n} from '../types/chat-bus'\n\nexport interface ChatPromptProps {\n /** Prompt configuration */\n config: ChatPromptConfig\n /** Called when user responds */\n onSubmit: (response: ChatPromptResponse) => void\n /** Called when user dismisses */\n onDismiss?: () => void\n}\n\n/**\n * @experimental\n * Ephemeral interaction component — choice buttons, confirmation dialog, or quick form.\n * Designed to sit between the chat messages and the input area.\n *\n * @example\n * <ChatPrompt\n * config={{ type: 'choice', title: 'Format?', config: { options: [...] } }}\n * onSubmit={(r) => bus.events.emit('onChatPromptResponse', { streamKey, response: r })}\n * onDismiss={() => setActivePrompt(null)}\n * />\n */\nexport const ChatPrompt: Component<ChatPromptProps> = (props) => {\n return (\n <div\n class=\"w-full max-w-2xl mx-auto mb-2 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg overflow-hidden\"\n style={{ animation: 'chat-prompt-slide-up 0.2s ease-out' }}\n role=\"dialog\"\n aria-label={props.config.title}\n >\n {/* Header */}\n <div class=\"flex items-center justify-between px-4 py-2.5 border-b border-gray-100 dark:border-gray-700\">\n <p class=\"text-sm font-medium text-gray-900 dark:text-white\">{props.config.title}</p>\n <button\n onClick={() => {\n props.onDismiss?.()\n props.onSubmit({ type: props.config.type, value: '', label: '', dismissed: true })\n }}\n class=\"p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors\"\n aria-label=\"Dismiss\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Body — type-specific */}\n <div class=\"px-4 py-3\">\n <Switch>\n <Match when={props.config.type === 'choice'}>\n <ChoiceBody\n config={props.config.config as ChoicePromptConfig}\n onSelect={(value, label) => props.onSubmit({ type: 'choice', value, label })}\n />\n </Match>\n <Match when={props.config.type === 'confirm'}>\n <ConfirmBody\n config={props.config.config as ConfirmPromptConfig}\n onConfirm={() => props.onSubmit({ type: 'confirm', value: 'confirmed', label: (props.config.config as ConfirmPromptConfig).confirmLabel || 'Confirmed' })}\n onCancel={() => {\n props.onDismiss?.()\n props.onSubmit({ type: 'confirm', value: 'cancelled', label: (props.config.config as ConfirmPromptConfig).cancelLabel || 'Cancelled', dismissed: true })\n }}\n />\n </Match>\n <Match when={props.config.type === 'form'}>\n <FormBody\n config={props.config.config as FormPromptConfig}\n onSubmit={(data, label) => props.onSubmit({ type: 'form', value: data, label })}\n />\n </Match>\n </Switch>\n </div>\n\n <style>{`\n @keyframes chat-prompt-slide-up {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n `}</style>\n </div>\n )\n}\n\n// ─── Choice ──────────────────────────────────────────────────\n\nconst ChoiceBody: Component<{\n config: ChoicePromptConfig\n onSelect: (value: string, label: string) => void\n}> = (props) => {\n const layoutClass = () => {\n switch (props.config.layout) {\n case 'vertical': return 'flex flex-col gap-2'\n case 'grid': return 'grid grid-cols-2 gap-2'\n default: return 'flex flex-wrap gap-2'\n }\n }\n\n return (\n <div class={layoutClass()}>\n <For each={props.config.options}>\n {(option) => (\n <button\n onClick={() => props.onSelect(option.value, option.label)}\n class=\"px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-blue-50 hover:border-blue-300 dark:hover:bg-blue-900/30 dark:hover:border-blue-600 transition-colors text-left\"\n >\n <Show when={option.icon}>\n <span class=\"mr-2\">{option.icon}</span>\n </Show>\n {option.label}\n <Show when={option.description}>\n <span class=\"block text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-normal\">{option.description}</span>\n </Show>\n </button>\n )}\n </For>\n </div>\n )\n}\n\n// ─── Confirm ─────────────────────────────────────────────────\n\nconst ConfirmBody: Component<{\n config: ConfirmPromptConfig\n onConfirm: () => void\n onCancel: () => void\n}> = (props) => {\n const isDanger = () => props.config.variant === 'danger'\n\n return (\n <div>\n <Show when={props.config.message}>\n <p class=\"text-sm text-gray-600 dark:text-gray-400 mb-3\">{props.config.message}</p>\n </Show>\n <div class=\"flex gap-2 justify-end\">\n <button\n onClick={props.onCancel}\n class=\"px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors\"\n >\n {props.config.cancelLabel || 'Cancel'}\n </button>\n <button\n onClick={props.onConfirm}\n class={`px-4 py-2 text-sm font-medium rounded-lg text-white transition-colors ${\n isDanger()\n ? 'bg-red-600 hover:bg-red-700'\n : 'bg-blue-600 hover:bg-blue-700'\n }`}\n >\n {props.config.confirmLabel || 'Confirm'}\n </button>\n </div>\n </div>\n )\n}\n\n// ─── Form ────────────────────────────────────────────────────\n\nconst FormBody: Component<{\n config: FormPromptConfig\n onSubmit: (data: Record<string, unknown>, label: string) => void\n}> = (props) => {\n const [formData, setFormData] = createSignal<Record<string, string>>({})\n\n const updateField = (name: string, value: string) => {\n setFormData((prev) => ({ ...prev, [name]: value }))\n }\n\n const handleSubmit = (e: Event) => {\n e.preventDefault()\n const data = formData()\n // Build a human-readable label from the form values\n const label = Object.entries(data)\n .filter(([, v]) => v)\n .map(([k, v]) => `${k}: ${v}`)\n .join(', ')\n props.onSubmit(data, label || 'Form submitted')\n }\n\n const isValid = () => {\n const data = formData()\n return (props.config.fields || [])\n .filter((f) => f.required)\n .every((f) => data[f.name]?.trim())\n }\n\n return (\n <form onSubmit={handleSubmit} class=\"flex flex-col gap-3\">\n <For each={props.config.fields}>\n {(field) => (\n <div>\n <label class=\"block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1\">\n {field.label}\n <Show when={field.required}>\n <span class=\"text-red-500 ml-0.5\">*</span>\n </Show>\n </label>\n <Switch>\n <Match when={field.type === 'textarea'}>\n <textarea\n value={formData()[field.name] || ''}\n onInput={(e) => updateField(field.name, e.currentTarget.value)}\n placeholder={field.placeholder}\n rows={3}\n class=\"w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors\"\n />\n </Match>\n <Match when={field.type === 'select'}>\n <select\n value={formData()[field.name] || ''}\n onChange={(e) => updateField(field.name, e.currentTarget.value)}\n class=\"w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors\"\n >\n <option value=\"\">{field.placeholder || 'Select...'}</option>\n <For each={field.options}>\n {(opt) => <option value={opt.value}>{opt.label}</option>}\n </For>\n </select>\n </Match>\n <Match when={true}>\n <input\n type={field.type === 'number' ? 'number' : 'text'}\n value={formData()[field.name] || ''}\n onInput={(e) => updateField(field.name, e.currentTarget.value)}\n placeholder={field.placeholder}\n class=\"w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors\"\n />\n </Match>\n </Switch>\n </div>\n )}\n </For>\n <div class=\"flex justify-end\">\n <button\n type=\"submit\"\n disabled={!isValid()}\n class=\"px-4 py-2 text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors\"\n >\n {props.config.submitLabel || 'Submit'}\n </button>\n </div>\n </form>\n )\n}\n"],"names":["ChatPrompt","props","_el$","_$getNextElement","_tmpl$","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_$insert","config","title","$$click","onDismiss","onSubmit","type","value","label","dismissed","_$createComponent","Switch","children","Match","when","ChoiceBody","onSelect","ConfirmBody","onConfirm","confirmLabel","onCancel","cancelLabel","FormBody","data","_$effect","_$setAttribute","_$runHydrationEvents","layoutClass","layout","_el$6","_tmpl$2","For","each","options","option","_el$7","_tmpl$5","_el$0","_el$1","_co$","_$getNextMarker","_el$10","_el$11","_co$2","_el$12","_el$13","_co$3","Show","icon","_el$8","_tmpl$3","description","_el$9","_tmpl$4","_$className","isDanger","variant","_el$14","_tmpl$7","_el$19","_el$20","_co$4","_el$16","_el$17","_el$18","message","_el$15","_tmpl$6","_$addEventListener","formData","setFormData","createSignal","updateField","name","prev","handleSubmit","e","preventDefault","Object","entries","filter","v","map","k","join","isValid","fields","f","required","every","trim","_el$21","_tmpl$8","_el$24","_el$25","_co$5","_el$22","_el$23","addEventListener","field","_el$26","_tmpl$11","_el$27","_el$29","_el$30","_co$6","_el$31","_el$32","_co$7","_el$39","_el$40","_co$9","_tmpl$9","_el$33","_tmpl$0","$$input","currentTarget","placeholder","_$setProperty","_el$34","_tmpl$1","_el$35","_el$36","_el$37","_co$8","opt","_el$41","_tmpl$12","_el$38","_tmpl$10","_p$","_v$","_v$2","t","undefined","submitLabel","_$delegateEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatPrompt.js","sources":["../../src/components/ChatPrompt.tsx"],"sourcesContent":["/**\n * ChatPrompt — Ephemeral structured interaction above chat input\n * v2.4.0: choice, confirm, form subtypes\n *\n * @experimental — This component may change without major bump until v2.5.0.\n *\n * Renders above the chat input. User responds → Promise resolves → prompt disappears.\n * Supports AbortSignal for cleanup on navigation (C4).\n */\n\nimport { Component, Show, For, createSignal, onCleanup, Switch, Match } from 'solid-js'\nimport type {\n ChatPromptConfig,\n ChatPromptResponse,\n ChoicePromptConfig,\n ConfirmPromptConfig,\n FormPromptConfig,\n} from '../types/chat-bus'\n\nexport interface ChatPromptProps {\n /** Prompt configuration */\n config: ChatPromptConfig\n /** Called when user responds */\n onSubmit: (response: ChatPromptResponse) => void\n /** Called when user dismisses (e.g. \"send as-is\") */\n onDismiss?: () => void\n /** Label for the dismiss button (replaces X icon). Default: shows X icon. */\n dismissLabel?: string\n}\n\n/**\n * @experimental\n * Ephemeral interaction component — choice buttons, confirmation dialog, or quick form.\n * Designed to sit between the chat messages and the input area.\n *\n * @example\n * <ChatPrompt\n * config={{ type: 'choice', title: 'Format?', config: { options: [...] } }}\n * onSubmit={(r) => bus.events.emit('onChatPromptResponse', { streamKey, response: r })}\n * onDismiss={() => setActivePrompt(null)}\n * />\n */\nexport const ChatPrompt: Component<ChatPromptProps> = (props) => {\n // F1: Guard against null/undefined config (e.g. after dismiss clears state)\n if (!props.config) return null\n\n return (\n <div\n class=\"w-full max-w-2xl mx-auto mb-2 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg overflow-hidden\"\n style={{ animation: 'chat-prompt-slide-up 0.2s ease-out' }}\n role=\"dialog\"\n aria-label={props.config.title}\n >\n {/* Header */}\n <div class=\"flex items-center justify-between px-4 py-2.5 border-b border-gray-100 dark:border-gray-700\">\n <p class=\"text-sm font-medium text-gray-900 dark:text-white\">{props.config.title}</p>\n <button\n onClick={() => {\n props.onDismiss?.()\n props.onSubmit({ type: props.config.type, value: '', label: '', dismissed: true })\n }}\n class={props.dismissLabel\n ? 'px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors'\n : 'p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors'\n }\n aria-label={props.dismissLabel || 'Dismiss'}\n >\n <Show when={props.dismissLabel} fallback={\n <svg class=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n }>\n {props.dismissLabel}\n </Show>\n </button>\n </div>\n\n {/* Body — type-specific */}\n <div class=\"px-4 py-3\">\n <Switch>\n <Match when={props.config.type === 'choice'}>\n <ChoiceBody\n config={props.config.config as ChoicePromptConfig}\n onSelect={(value, label) => props.onSubmit({ type: 'choice', value, label })}\n />\n </Match>\n <Match when={props.config.type === 'confirm'}>\n <ConfirmBody\n config={props.config.config as ConfirmPromptConfig}\n onConfirm={() => props.onSubmit({ type: 'confirm', value: 'confirmed', label: (props.config.config as ConfirmPromptConfig).confirmLabel || 'Confirmed' })}\n onCancel={() => {\n props.onDismiss?.()\n props.onSubmit({ type: 'confirm', value: 'cancelled', label: (props.config.config as ConfirmPromptConfig).cancelLabel || 'Cancelled', dismissed: true })\n }}\n />\n </Match>\n <Match when={props.config.type === 'form'}>\n <FormBody\n config={props.config.config as FormPromptConfig}\n onSubmit={(data, label) => props.onSubmit({ type: 'form', value: data, label })}\n />\n </Match>\n </Switch>\n </div>\n\n <style>{`\n @keyframes chat-prompt-slide-up {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n `}</style>\n </div>\n )\n}\n\n// ─── Choice ──────────────────────────────────────────────────\n\nconst ChoiceBody: Component<{\n config: ChoicePromptConfig\n onSelect: (value: string, label: string) => void\n}> = (props) => {\n const layoutClass = () => {\n switch (props.config.layout) {\n case 'vertical': return 'flex flex-col gap-2'\n case 'grid': return 'grid grid-cols-2 gap-2'\n default: return 'flex flex-wrap gap-2'\n }\n }\n\n return (\n <div class={layoutClass()}>\n <For each={props.config.options}>\n {(option) => (\n <button\n onClick={() => props.onSelect(option.value, option.label)}\n class=\"px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-blue-50 hover:border-blue-300 dark:hover:bg-blue-900/30 dark:hover:border-blue-600 transition-colors text-left\"\n >\n <Show when={option.icon}>\n <span class=\"mr-2\">{option.icon}</span>\n </Show>\n {option.label}\n <Show when={option.description}>\n <span class=\"block text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-normal\">{option.description}</span>\n </Show>\n </button>\n )}\n </For>\n </div>\n )\n}\n\n// ─── Confirm ─────────────────────────────────────────────────\n\nconst ConfirmBody: Component<{\n config: ConfirmPromptConfig\n onConfirm: () => void\n onCancel: () => void\n}> = (props) => {\n const isDanger = () => props.config.variant === 'danger'\n\n return (\n <div>\n <Show when={props.config.message}>\n <p class=\"text-sm text-gray-600 dark:text-gray-400 mb-3\">{props.config.message}</p>\n </Show>\n <div class=\"flex gap-2 justify-end\">\n <button\n onClick={props.onCancel}\n class=\"px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors\"\n >\n {props.config.cancelLabel || 'Cancel'}\n </button>\n <button\n onClick={props.onConfirm}\n class={`px-4 py-2 text-sm font-medium rounded-lg text-white transition-colors ${\n isDanger()\n ? 'bg-red-600 hover:bg-red-700'\n : 'bg-blue-600 hover:bg-blue-700'\n }`}\n >\n {props.config.confirmLabel || 'Confirm'}\n </button>\n </div>\n </div>\n )\n}\n\n// ─── Form ────────────────────────────────────────────────────\n\nconst FormBody: Component<{\n config: FormPromptConfig\n onSubmit: (data: Record<string, unknown>, label: string) => void\n}> = (props) => {\n const [formData, setFormData] = createSignal<Record<string, string>>({})\n\n const updateField = (name: string, value: string) => {\n setFormData((prev) => ({ ...prev, [name]: value }))\n }\n\n const handleSubmit = (e: Event) => {\n e.preventDefault()\n const data = formData()\n // Build a human-readable label from the form values\n const label = Object.entries(data)\n .filter(([, v]) => v)\n .map(([k, v]) => `${k}: ${v}`)\n .join(', ')\n props.onSubmit(data, label || 'Form submitted')\n }\n\n const isValid = () => {\n const data = formData()\n return (props.config.fields || [])\n .filter((f) => f.required)\n .every((f) => data[f.name]?.trim())\n }\n\n return (\n <form onSubmit={handleSubmit} class=\"flex flex-col gap-3\">\n <For each={props.config.fields}>\n {(field) => (\n <div>\n <label class=\"block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1\">\n {field.label}\n <Show when={field.required}>\n <span class=\"text-red-500 ml-0.5\">*</span>\n </Show>\n </label>\n <Switch>\n <Match when={field.type === 'textarea'}>\n <textarea\n value={formData()[field.name] || ''}\n onInput={(e) => updateField(field.name, e.currentTarget.value)}\n placeholder={field.placeholder}\n rows={3}\n class=\"w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors\"\n />\n </Match>\n <Match when={field.type === 'select'}>\n <select\n value={formData()[field.name] || ''}\n onChange={(e) => updateField(field.name, e.currentTarget.value)}\n class=\"w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors\"\n >\n <option value=\"\">{field.placeholder || 'Select...'}</option>\n <For each={field.options}>\n {(opt) => <option value={opt.value}>{opt.label}</option>}\n </For>\n </select>\n </Match>\n <Match when={true}>\n <input\n type={field.type === 'number' ? 'number' : 'text'}\n value={formData()[field.name] || ''}\n onInput={(e) => updateField(field.name, e.currentTarget.value)}\n placeholder={field.placeholder}\n class=\"w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors\"\n />\n </Match>\n </Switch>\n </div>\n )}\n </For>\n <div class=\"flex justify-end\">\n <button\n type=\"submit\"\n disabled={!isValid()}\n class=\"px-4 py-2 text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors\"\n >\n {props.config.submitLabel || 'Submit'}\n </button>\n </div>\n </form>\n )\n}\n"],"names":["ChatPrompt","props","config","_el$","_$getNextElement","_tmpl$","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_$insert","title","$$click","onDismiss","onSubmit","type","value","label","dismissed","_$createComponent","Show","when","dismissLabel","fallback","_tmpl$2","children","Switch","Match","ChoiceBody","onSelect","ConfirmBody","onConfirm","confirmLabel","onCancel","cancelLabel","FormBody","data","_$effect","_p$","_v$","_v$2","_v$3","e","_$setAttribute","t","_$className","a","undefined","_$runHydrationEvents","layoutClass","layout","_el$7","_tmpl$3","For","each","options","option","_el$8","_tmpl$6","_el$1","_el$10","_co$","_$getNextMarker","_el$11","_el$12","_co$2","_el$13","_el$14","_co$3","icon","_el$9","_tmpl$4","description","_el$0","_tmpl$5","isDanger","variant","_el$15","_tmpl$8","_el$20","_el$21","_co$4","_el$17","_el$18","_el$19","message","_el$16","_tmpl$7","_$addEventListener","formData","setFormData","createSignal","updateField","name","prev","handleSubmit","preventDefault","Object","entries","filter","v","map","k","join","isValid","fields","f","required","every","trim","_el$22","_tmpl$9","_el$25","_el$26","_co$5","_el$23","_el$24","addEventListener","field","_el$27","_tmpl$12","_el$28","_el$30","_el$31","_co$6","_el$32","_el$33","_co$7","_el$40","_el$41","_co$9","_tmpl$0","_el$34","_tmpl$1","$$input","currentTarget","placeholder","_$setProperty","_el$35","_tmpl$10","_el$36","_el$37","_el$38","_co$8","opt","_el$42","_tmpl$13","_el$39","_tmpl$11","_v$4","_v$5","submitLabel","_$delegateEvents"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -259,6 +259,81 @@ describe('ChatPrompt', () => {
|
|
|
259
259
|
})
|
|
260
260
|
})
|
|
261
261
|
|
|
262
|
+
// ─── dismissLabel ────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
describe('dismissLabel', () => {
|
|
265
|
+
it('shows X icon by default (no dismissLabel)', () => {
|
|
266
|
+
const config: ChatPromptConfig = {
|
|
267
|
+
type: 'choice',
|
|
268
|
+
title: 'Test',
|
|
269
|
+
config: { options: [{ value: 'a', label: 'A' }] },
|
|
270
|
+
}
|
|
271
|
+
const { getByLabelText } = render(() => (
|
|
272
|
+
<ChatPrompt config={config} onSubmit={() => {}} />
|
|
273
|
+
))
|
|
274
|
+
|
|
275
|
+
expect(getByLabelText('Dismiss')).toBeDefined()
|
|
276
|
+
expect(getByLabelText('Dismiss').querySelector('svg')).not.toBeNull()
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('shows text button when dismissLabel is provided', () => {
|
|
280
|
+
const config: ChatPromptConfig = {
|
|
281
|
+
type: 'choice',
|
|
282
|
+
title: 'Test',
|
|
283
|
+
config: { options: [{ value: 'a', label: 'A' }] },
|
|
284
|
+
}
|
|
285
|
+
const { getByText, getByLabelText } = render(() => (
|
|
286
|
+
<ChatPrompt config={config} onSubmit={() => {}} dismissLabel="Send as-is" />
|
|
287
|
+
))
|
|
288
|
+
|
|
289
|
+
expect(getByText('Send as-is')).toBeDefined()
|
|
290
|
+
expect(getByLabelText('Send as-is')).toBeDefined()
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('calls onDismiss + onSubmit when dismissLabel button clicked', () => {
|
|
294
|
+
const onSubmit = vi.fn()
|
|
295
|
+
const onDismiss = vi.fn()
|
|
296
|
+
const config: ChatPromptConfig = {
|
|
297
|
+
type: 'choice',
|
|
298
|
+
title: 'Test',
|
|
299
|
+
config: { options: [{ value: 'a', label: 'A' }] },
|
|
300
|
+
}
|
|
301
|
+
const { getByText } = render(() => (
|
|
302
|
+
<ChatPrompt config={config} onSubmit={onSubmit} onDismiss={onDismiss} dismissLabel="Envoyer directement" />
|
|
303
|
+
))
|
|
304
|
+
|
|
305
|
+
fireEvent.click(getByText('Envoyer directement'))
|
|
306
|
+
|
|
307
|
+
expect(onDismiss).toHaveBeenCalled()
|
|
308
|
+
expect(onSubmit).toHaveBeenCalledWith({
|
|
309
|
+
type: 'choice',
|
|
310
|
+
value: '',
|
|
311
|
+
label: '',
|
|
312
|
+
dismissed: true,
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// ─── Null guard (F1) ──────────────────────────────────
|
|
318
|
+
|
|
319
|
+
describe('null guard', () => {
|
|
320
|
+
it('does not crash when config is null', () => {
|
|
321
|
+
expect(() => {
|
|
322
|
+
render(() => (
|
|
323
|
+
<ChatPrompt config={null as any} onSubmit={() => {}} />
|
|
324
|
+
))
|
|
325
|
+
}).not.toThrow()
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('does not crash when config is undefined', () => {
|
|
329
|
+
expect(() => {
|
|
330
|
+
render(() => (
|
|
331
|
+
<ChatPrompt config={undefined as any} onSubmit={() => {}} />
|
|
332
|
+
))
|
|
333
|
+
}).not.toThrow()
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
262
337
|
// ─── Accessibility ───────────────────────────────────
|
|
263
338
|
|
|
264
339
|
describe('accessibility', () => {
|
|
@@ -22,8 +22,10 @@ export interface ChatPromptProps {
|
|
|
22
22
|
config: ChatPromptConfig
|
|
23
23
|
/** Called when user responds */
|
|
24
24
|
onSubmit: (response: ChatPromptResponse) => void
|
|
25
|
-
/** Called when user dismisses */
|
|
25
|
+
/** Called when user dismisses (e.g. "send as-is") */
|
|
26
26
|
onDismiss?: () => void
|
|
27
|
+
/** Label for the dismiss button (replaces X icon). Default: shows X icon. */
|
|
28
|
+
dismissLabel?: string
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -39,6 +41,9 @@ export interface ChatPromptProps {
|
|
|
39
41
|
* />
|
|
40
42
|
*/
|
|
41
43
|
export const ChatPrompt: Component<ChatPromptProps> = (props) => {
|
|
44
|
+
// F1: Guard against null/undefined config (e.g. after dismiss clears state)
|
|
45
|
+
if (!props.config) return null
|
|
46
|
+
|
|
42
47
|
return (
|
|
43
48
|
<div
|
|
44
49
|
class="w-full max-w-2xl mx-auto mb-2 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg overflow-hidden"
|
|
@@ -54,12 +59,19 @@ export const ChatPrompt: Component<ChatPromptProps> = (props) => {
|
|
|
54
59
|
props.onDismiss?.()
|
|
55
60
|
props.onSubmit({ type: props.config.type, value: '', label: '', dismissed: true })
|
|
56
61
|
}}
|
|
57
|
-
class=
|
|
58
|
-
|
|
62
|
+
class={props.dismissLabel
|
|
63
|
+
? 'px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors'
|
|
64
|
+
: 'p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors'
|
|
65
|
+
}
|
|
66
|
+
aria-label={props.dismissLabel || 'Dismiss'}
|
|
59
67
|
>
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
68
|
+
<Show when={props.dismissLabel} fallback={
|
|
69
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
70
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
71
|
+
</svg>
|
|
72
|
+
}>
|
|
73
|
+
{props.dismissLabel}
|
|
74
|
+
</Show>
|
|
63
75
|
</button>
|
|
64
76
|
</div>
|
|
65
77
|
|