@ews-admin/global-design-system 1.36.0 → 1.38.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/dist/components/AssistantChat/ActionTooltip.d.ts +12 -0
- package/dist/components/AssistantChat/ActionTooltip.d.ts.map +1 -0
- package/dist/components/AssistantChat/AssistantChat.d.ts.map +1 -1
- package/dist/components/AssistantChat/AssistantChat.types.d.ts +35 -1
- package/dist/components/AssistantChat/AssistantChat.types.d.ts.map +1 -1
- package/dist/components/AssistantChat/AssistantComposer.d.ts +16 -3
- package/dist/components/AssistantChat/AssistantComposer.d.ts.map +1 -1
- package/dist/components/AssistantChat/AssistantDictationBar.d.ts +28 -0
- package/dist/components/AssistantChat/AssistantDictationBar.d.ts.map +1 -0
- package/dist/components/AssistantChat/AssistantSuggestionCard.d.ts +1 -1
- package/dist/components/AssistantChat/AssistantSuggestionCard.d.ts.map +1 -1
- package/dist/components/AssistantChat/AssistantVoiceMode.d.ts +37 -0
- package/dist/components/AssistantChat/AssistantVoiceMode.d.ts.map +1 -0
- package/dist/components/AssistantChat/assistantMarkdown.d.ts.map +1 -1
- package/dist/components/AssistantChat/index.d.ts +4 -0
- package/dist/components/AssistantChat/index.d.ts.map +1 -1
- package/dist/components/AssistantLauncher/AssistantLauncher.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useSpeechRecognition.d.ts +2 -0
- package/dist/hooks/useSpeechRecognition.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +116 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +431 -57
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +433 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/AssistantChat/ActionTooltip.tsx +31 -0
- package/src/components/AssistantChat/AssistantChat.tsx +36 -4
- package/src/components/AssistantChat/AssistantChat.types.ts +25 -1
- package/src/components/AssistantChat/AssistantComposer.tsx +106 -68
- package/src/components/AssistantChat/AssistantDictationBar.tsx +268 -0
- package/src/components/AssistantChat/AssistantSuggestionCard.tsx +7 -6
- package/src/components/AssistantChat/AssistantVoiceMode.tsx +304 -0
- package/src/components/AssistantChat/assistantMarkdown.tsx +4 -1
- package/src/components/AssistantChat/index.ts +4 -0
- package/src/components/AssistantLauncher/AssistantLauncher.tsx +106 -77
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useSpeechRecognition.ts +9 -0
- package/src/index.ts +8 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ArrowUp, AudioLines, Maximize2, Mic, Minimize2, X } from "lucide-react";
|
|
2
2
|
import React, { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
3
3
|
import { listAssistantConversations, saveAssistantConversation } from "../../assistant/store";
|
|
4
|
-
import {
|
|
4
|
+
import { useSpeechSupported } from "../../hooks/useSpeechRecognition";
|
|
5
|
+
import { ActionTooltip } from "../AssistantChat/ActionTooltip";
|
|
6
|
+
import { AssistantDictationBar } from "../AssistantChat/AssistantDictationBar";
|
|
5
7
|
import { cn, generateId } from "../../utils";
|
|
6
8
|
import { AssistantChat } from "../AssistantChat/AssistantChat";
|
|
7
9
|
import type {
|
|
@@ -147,11 +149,18 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
|
|
|
147
149
|
[setOpen]
|
|
148
150
|
);
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
// One click on the bar mic (or the voice button) opens the panel straight in
|
|
153
|
+
// the voice conversation, listening at once.
|
|
154
|
+
const [voiceRequest, setVoiceRequest] = useState<{ id: string } | null>(null);
|
|
155
|
+
const openVoice = useCallback(() => {
|
|
156
|
+
setVoiceRequest({ id: generateId("v") });
|
|
157
|
+
setOpen(true);
|
|
158
|
+
}, [setOpen]);
|
|
159
|
+
const speechSupported = useSpeechSupported();
|
|
160
|
+
// The waveform button records a dictation in the bar itself (recording bar).
|
|
161
|
+
const [dictating, setDictating] = useState(false);
|
|
162
|
+
const [composerDraft, setComposerDraft] = useState<{ id: string; content: string } | null>(null);
|
|
163
|
+
const dictateLabel = chatProps.texts.dictate ?? launcherTexts.voice ?? "Dicter un message";
|
|
155
164
|
|
|
156
165
|
const handleConversationCreated = useCallback(
|
|
157
166
|
(conversation: AssistantConversationSummary) => {
|
|
@@ -218,7 +227,7 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
|
|
|
218
227
|
if (hidden) return null;
|
|
219
228
|
|
|
220
229
|
const dir = DIRECTION[chatProps.language];
|
|
221
|
-
const showMic = voice &&
|
|
230
|
+
const showMic = voice && speechSupported;
|
|
222
231
|
const expandLabel = launcherTexts.expand ?? launcherTexts.open;
|
|
223
232
|
const collapseLabel = launcherTexts.collapse ?? launcherTexts.close;
|
|
224
233
|
const texts = launcherTexts.history ? { ...chatProps.texts, history: launcherTexts.history } : chatProps.texts;
|
|
@@ -254,78 +263,89 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
|
|
|
254
263
|
>
|
|
255
264
|
{chatProps.texts.brandName.charAt(0)}
|
|
256
265
|
</button>
|
|
257
|
-
{
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
onFocus={() => setOpen(true)}
|
|
267
|
-
onKeyDown={(event) => {
|
|
268
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
269
|
-
event.preventDefault();
|
|
266
|
+
{dictating ? (
|
|
267
|
+
<AssistantDictationBar
|
|
268
|
+
language={chatProps.language}
|
|
269
|
+
texts={chatProps.texts.dictation}
|
|
270
|
+
className="min-w-0 flex-1"
|
|
271
|
+
onCancel={() => setDictating(false)}
|
|
272
|
+
onDone={(text) => {
|
|
273
|
+
setDictating(false);
|
|
274
|
+
if (text) setComposerDraft({ id: generateId("d"), content: text });
|
|
270
275
|
setOpen(true);
|
|
271
|
-
}
|
|
272
|
-
}}
|
|
273
|
-
placeholder={speech.listening ? chatProps.texts.listening : launcherTexts.open}
|
|
274
|
-
aria-label={launcherTexts.open}
|
|
275
|
-
aria-haspopup="dialog"
|
|
276
|
-
aria-controls={panelId}
|
|
277
|
-
className="min-w-0 flex-1 cursor-text bg-transparent py-2 text-lg text-ews-gray-800 placeholder:text-ews-gray-400 focus:outline-none"
|
|
278
|
-
data-testid="assistant-launcher-input"
|
|
279
|
-
/>
|
|
280
|
-
{showMic && (
|
|
281
|
-
<button
|
|
282
|
-
type="button"
|
|
283
|
-
aria-label={chatProps.texts.holdToTalk}
|
|
284
|
-
aria-pressed={speech.listening}
|
|
285
|
-
title={chatProps.texts.holdToTalk}
|
|
286
|
-
onMouseDown={speech.start}
|
|
287
|
-
onMouseUp={speech.stop}
|
|
288
|
-
onMouseLeave={speech.listening ? speech.stop : undefined}
|
|
289
|
-
onTouchStart={speech.start}
|
|
290
|
-
onTouchEnd={speech.stop}
|
|
291
|
-
onKeyDown={(event) => {
|
|
292
|
-
if (event.key === " " || event.key === "Enter") {
|
|
293
|
-
event.preventDefault();
|
|
294
|
-
if (speech.listening) speech.stop();
|
|
295
|
-
else speech.start();
|
|
296
|
-
}
|
|
297
276
|
}}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
277
|
+
onSend={(text) => {
|
|
278
|
+
setDictating(false);
|
|
279
|
+
submitFromBar(text);
|
|
280
|
+
}}
|
|
281
|
+
/>
|
|
282
|
+
) : (
|
|
283
|
+
<>
|
|
284
|
+
{/* The field opens the panel: typing happens in the panel composer, which gets the focus. */}
|
|
285
|
+
<input
|
|
286
|
+
type="text"
|
|
287
|
+
readOnly
|
|
288
|
+
value={draft}
|
|
289
|
+
onMouseDown={(event) => {
|
|
290
|
+
event.preventDefault();
|
|
291
|
+
setOpen(true);
|
|
292
|
+
}}
|
|
293
|
+
onFocus={() => setOpen(true)}
|
|
294
|
+
onKeyDown={(event) => {
|
|
295
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
296
|
+
event.preventDefault();
|
|
297
|
+
setOpen(true);
|
|
298
|
+
}
|
|
299
|
+
}}
|
|
300
|
+
placeholder={launcherTexts.open}
|
|
301
|
+
aria-label={launcherTexts.open}
|
|
302
|
+
aria-haspopup="dialog"
|
|
303
|
+
aria-controls={panelId}
|
|
304
|
+
className="min-w-0 flex-1 cursor-text bg-transparent py-2 text-lg text-ews-gray-800 placeholder:text-ews-gray-400 focus:outline-none"
|
|
305
|
+
data-testid="assistant-launcher-input"
|
|
306
|
+
/>
|
|
307
|
+
{showMic && (
|
|
308
|
+
<ActionTooltip label={chatProps.texts.holdToTalk}>
|
|
309
|
+
{(describedBy) => (
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
aria-label={chatProps.texts.holdToTalk}
|
|
313
|
+
aria-describedby={describedBy}
|
|
314
|
+
onClick={openVoice}
|
|
315
|
+
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-ews-gray-600 transition-colors hover:bg-ews-gray-100 hover:text-ews-primary focus:outline-none focus:ring-2 focus:ring-ews-primary"
|
|
316
|
+
data-testid="assistant-launcher-mic"
|
|
317
|
+
>
|
|
318
|
+
<Mic className="h-5 w-5" aria-hidden="true" />
|
|
319
|
+
</button>
|
|
320
|
+
)}
|
|
321
|
+
</ActionTooltip>
|
|
303
322
|
)}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
323
|
+
<ActionTooltip label={dictateLabel}>
|
|
324
|
+
{(describedBy) => (
|
|
325
|
+
<button
|
|
326
|
+
type="button"
|
|
327
|
+
onClick={speechSupported ? () => setDictating(true) : () => setOpen(true)}
|
|
328
|
+
aria-label={dictateLabel}
|
|
329
|
+
aria-describedby={describedBy}
|
|
330
|
+
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-ews-gray-100 text-ews-gray-700 transition-colors hover:bg-ews-gray-200 focus:outline-none focus:ring-2 focus:ring-ews-primary"
|
|
331
|
+
data-testid="assistant-launcher-voice"
|
|
332
|
+
>
|
|
333
|
+
<AudioLines className="h-5 w-5" aria-hidden="true" />
|
|
334
|
+
</button>
|
|
335
|
+
)}
|
|
336
|
+
</ActionTooltip>
|
|
337
|
+
<button
|
|
338
|
+
type="button"
|
|
339
|
+
onClick={() => submitFromBar(draft)}
|
|
340
|
+
aria-label={chatProps.texts.send}
|
|
341
|
+
title={chatProps.texts.send}
|
|
342
|
+
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-ews-primary text-white shadow-sm transition-colors hover:bg-ews-primary-hover focus:outline-none focus:ring-2 focus:ring-ews-primary focus:ring-offset-1"
|
|
343
|
+
data-testid="assistant-launcher-send"
|
|
344
|
+
>
|
|
345
|
+
<ArrowUp className="h-5 w-5" aria-hidden="true" />
|
|
346
|
+
</button>
|
|
347
|
+
</>
|
|
308
348
|
)}
|
|
309
|
-
<button
|
|
310
|
-
type="button"
|
|
311
|
-
onClick={() => setOpen(true)}
|
|
312
|
-
aria-label={launcherTexts.voice ?? launcherTexts.open}
|
|
313
|
-
title={launcherTexts.voice ?? launcherTexts.open}
|
|
314
|
-
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-ews-gray-100 text-ews-gray-700 transition-colors hover:bg-ews-gray-200 focus:outline-none focus:ring-2 focus:ring-ews-primary"
|
|
315
|
-
data-testid="assistant-launcher-voice"
|
|
316
|
-
>
|
|
317
|
-
<AudioLines className="h-5 w-5" aria-hidden="true" />
|
|
318
|
-
</button>
|
|
319
|
-
<button
|
|
320
|
-
type="button"
|
|
321
|
-
onClick={() => submitFromBar(draft)}
|
|
322
|
-
aria-label={chatProps.texts.send}
|
|
323
|
-
title={chatProps.texts.send}
|
|
324
|
-
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-ews-primary text-white shadow-sm transition-colors hover:bg-ews-primary-hover focus:outline-none focus:ring-2 focus:ring-ews-primary focus:ring-offset-1"
|
|
325
|
-
data-testid="assistant-launcher-send"
|
|
326
|
-
>
|
|
327
|
-
<ArrowUp className="h-5 w-5" aria-hidden="true" />
|
|
328
|
-
</button>
|
|
329
349
|
</div>
|
|
330
350
|
</div>
|
|
331
351
|
)}
|
|
@@ -337,7 +357,9 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
|
|
|
337
357
|
data-testid="assistant-launcher-panel"
|
|
338
358
|
>
|
|
339
359
|
<div
|
|
340
|
-
|
|
360
|
+
// Mobile: a dimmed page under a full-screen panel. From tablets up the panel floats over the
|
|
361
|
+
// page, which stays visible (no dimming); a click outside still closes it.
|
|
362
|
+
className="absolute inset-0 bg-black/40 sm:bg-transparent"
|
|
341
363
|
onClick={() => setOpen(false)}
|
|
342
364
|
data-testid="assistant-launcher-overlay"
|
|
343
365
|
/>
|
|
@@ -349,7 +371,12 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
|
|
|
349
371
|
aria-label={launcherTexts.open}
|
|
350
372
|
data-expanded={isExpanded}
|
|
351
373
|
className={cn(
|
|
352
|
-
"absolute inset-y-0 end-0 flex w-full flex-col bg-white shadow-2xl transition-[width,max-width] duration-200",
|
|
374
|
+
"absolute inset-y-0 end-0 flex w-full flex-col bg-white shadow-2xl transition-[width,max-width,height] duration-200",
|
|
375
|
+
// From tablets up: a floating card (MED-1801, like the Notiwa widget), clear of the top,
|
|
376
|
+
// the bottom and the side of the screen, rounded, with a soft border and shadow.
|
|
377
|
+
"sm:inset-y-auto sm:bottom-6 sm:end-6 sm:overflow-hidden sm:rounded-3xl sm:border sm:border-ews-gray-100",
|
|
378
|
+
"sm:shadow-[0_24px_60px_rgba(15,23,42,0.22)]",
|
|
379
|
+
isExpanded ? "sm:h-[calc(100vh-3rem)]" : "sm:h-[calc(100vh-6rem)]",
|
|
353
380
|
// Expanded: a dashboard reaching the middle of the screen (never full width), tablets and up.
|
|
354
381
|
isExpanded ? "sm:max-w-md md:w-[max(50vw,40rem)] md:max-w-none" : "sm:max-w-md",
|
|
355
382
|
panelClassName
|
|
@@ -361,6 +388,8 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
|
|
|
361
388
|
voice={voice}
|
|
362
389
|
layout={isExpanded ? "wide" : "panel"}
|
|
363
390
|
queuedPrompt={queuedPrompt}
|
|
391
|
+
voiceRequest={voiceRequest}
|
|
392
|
+
composerDraft={composerDraft}
|
|
364
393
|
conversations={hostManaged ? hostConversations : storedConversations}
|
|
365
394
|
activeConversationId={hostManaged ? hostActiveId : managedActiveId}
|
|
366
395
|
initialMessages={hostManaged ? hostInitialMessages : managedMessages}
|
package/src/hooks/index.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { useIdleLock } from "./useIdleLock";
|
|
|
4
4
|
export type { UseIdleLockOptions, UseIdleLockResult } from "./useIdleLock";
|
|
5
5
|
export { useSelectField } from "./useSelectField";
|
|
6
6
|
export type { UseSelectFieldProps } from "./useSelectField";
|
|
7
|
-
export { useSpeechRecognition, getSpeechRecognition } from "./useSpeechRecognition";
|
|
7
|
+
export { useSpeechRecognition, useSpeechSupported, getSpeechRecognition } from "./useSpeechRecognition";
|
|
8
8
|
export type { UseSpeechRecognitionOptions } from "./useSpeechRecognition";
|
|
@@ -26,6 +26,15 @@ export function getSpeechRecognition(): SpeechRecognitionCtor | undefined {
|
|
|
26
26
|
return w.SpeechRecognition ?? w.webkitSpeechRecognition;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** True once mounted in a browser that has the speech recognition API (false on the server). */
|
|
30
|
+
export function useSpeechSupported(): boolean {
|
|
31
|
+
const [supported, setSupported] = useState(false);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setSupported(getSpeechRecognition() !== undefined);
|
|
34
|
+
}, []);
|
|
35
|
+
return supported;
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
export interface UseSpeechRecognitionOptions {
|
|
30
39
|
/** `fr`, `en`, `ar` or a BCP 47 tag. */
|
|
31
40
|
language: string;
|
package/src/index.ts
CHANGED
|
@@ -65,6 +65,9 @@ export type { PinCodeInputProps } from "./components/PinCodeInput";
|
|
|
65
65
|
export {
|
|
66
66
|
AssistantChat,
|
|
67
67
|
AssistantComposer,
|
|
68
|
+
AssistantVoiceMode,
|
|
69
|
+
AssistantDictationBar,
|
|
70
|
+
toSpokenText,
|
|
68
71
|
AssistantConfirmationCard,
|
|
69
72
|
AssistantConversationList,
|
|
70
73
|
AssistantMessages,
|
|
@@ -90,6 +93,10 @@ export type {
|
|
|
90
93
|
AssistantMessagesProps,
|
|
91
94
|
AssistantSuggestion,
|
|
92
95
|
AssistantSuggestionCardProps,
|
|
96
|
+
AssistantVoiceModeProps,
|
|
97
|
+
AssistantDictationBarProps,
|
|
98
|
+
AssistantDictationTexts,
|
|
99
|
+
AssistantVoiceModeTexts,
|
|
93
100
|
AssistantTransport,
|
|
94
101
|
} from "./components/AssistantChat";
|
|
95
102
|
|
|
@@ -162,6 +169,7 @@ export {
|
|
|
162
169
|
useMediaQuery,
|
|
163
170
|
useSelectField,
|
|
164
171
|
useSpeechRecognition,
|
|
172
|
+
useSpeechSupported,
|
|
165
173
|
getSpeechRecognition,
|
|
166
174
|
} from "./hooks";
|
|
167
175
|
export type {
|