@ews-admin/global-design-system 1.38.0 → 1.39.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ews-admin/global-design-system",
3
- "version": "1.38.0",
3
+ "version": "1.39.0",
4
4
  "description": "EWS Global Design System - Reusable components for EWS applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -50,6 +50,7 @@ export const AssistantChat: React.FC<AssistantChatProps> = ({
50
50
  headerAction,
51
51
  disclaimer,
52
52
  heroBackground,
53
+ heroLogo,
53
54
  voice = true,
54
55
  pendingConfirmation = null,
55
56
  onConfirm,
@@ -322,15 +323,20 @@ export const AssistantChat: React.FC<AssistantChatProps> = ({
322
323
  className={cn(
323
324
  "relative overflow-hidden bg-ews-primary-light text-center",
324
325
  // Extra bottom room: the white sheet of cards overlaps the hero (mockup tools).
325
- compact ? "px-4 pb-12 pt-8" : "px-6 pb-16 pt-12"
326
+ compact ? "px-4 pb-20 pt-10" : "px-6 pb-24 pt-14"
326
327
  )}
327
328
  >
328
329
  {heroBackground && <div className="absolute inset-0" aria-hidden="true">{heroBackground}</div>}
329
330
  <div className="relative">
331
+ {heroLogo && (
332
+ <div className="mb-4 flex justify-center" data-testid="assistant-hero-logo">
333
+ {heroLogo}
334
+ </div>
335
+ )}
330
336
  <h2 className={cn("font-bold text-ews-gray-900", compact ? "text-xl" : "text-2xl sm:text-3xl")}>
331
337
  {texts.heroTitle}
332
338
  </h2>
333
- <p className={cn("mx-auto mt-2 max-w-xl text-ews-gray-700", compact && "text-sm")}>
339
+ <p className={cn("mx-auto mt-4 max-w-xl text-ews-gray-700", compact && "text-sm")}>
334
340
  {texts.heroSubtitle}
335
341
  </p>
336
342
  </div>
@@ -157,6 +157,8 @@ export interface AssistantChatProps {
157
157
  disclaimer?: ReactNode;
158
158
  /** Decorative image or gradient behind the hero. */
159
159
  heroBackground?: ReactNode;
160
+ /** A small logo (e.g. the app favicon) above the hero title. */
161
+ heroLogo?: ReactNode;
160
162
  /** Push-to-talk through the browser speech API. Default true; hidden anyway where unsupported. */
161
163
  voice?: boolean;
162
164
  /** Phase 2: a confirmation the assistant asks before a write. */
@@ -1,4 +1,4 @@
1
- import { ArrowUp, AudioLines, Maximize2, Mic, Minimize2, X } from "lucide-react";
1
+ import { ArrowLeft, 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
4
  import { useSpeechSupported } from "../../hooks/useSpeechRecognition";
@@ -25,6 +25,15 @@ export interface AssistantLauncherTexts {
25
25
  collapse?: string;
26
26
  /** The history button of the panel; falls back to `texts.recentConversations`. */
27
27
  history?: string;
28
+ /** The back button of an embedded page (`embed`); falls back to `close`. */
29
+ backToConversation?: string;
30
+ }
31
+
32
+ /** A page shown in the panel in place of the conversation, e.g. a booking calendar. */
33
+ export interface AssistantEmbeddedPage {
34
+ url: string;
35
+ /** Header title and accessible name of the frame. */
36
+ title: string;
28
37
  }
29
38
 
30
39
  export interface AssistantLauncherProps
@@ -49,6 +58,13 @@ export interface AssistantLauncherProps
49
58
  className?: string;
50
59
  /** Extra classes on the panel (e.g. a wider max width). */
51
60
  panelClassName?: string;
61
+ /**
62
+ * A page (iframe) shown in the panel over the conversation, which stays
63
+ * mounted underneath; the back button calls `onEmbedClose`. Null or absent
64
+ * shows the conversation.
65
+ */
66
+ embed?: AssistantEmbeddedPage | null;
67
+ onEmbedClose?: () => void;
52
68
  }
53
69
 
54
70
  const DIRECTION: Record<AssistantChatProps["language"], "ltr" | "rtl"> = {
@@ -88,6 +104,8 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
88
104
  hidden = false,
89
105
  className,
90
106
  panelClassName,
107
+ embed,
108
+ onEmbedClose,
91
109
  headerAction,
92
110
  voice = true,
93
111
  conversations: hostConversations,
@@ -371,17 +389,47 @@ export const AssistantLauncher: React.FC<AssistantLauncherProps> = ({
371
389
  aria-label={launcherTexts.open}
372
390
  data-expanded={isExpanded}
373
391
  className={cn(
374
- "absolute inset-y-0 end-0 flex w-full flex-col bg-white shadow-2xl transition-[width,max-width,height] duration-200",
392
+ "absolute inset-y-0 end-0 flex w-full flex-col bg-white shadow-2xl",
375
393
  // From tablets up: a floating card (MED-1801, like the Notiwa widget), clear of the top,
376
394
  // the bottom and the side of the screen, rounded, with a soft border and shadow.
377
395
  "sm:inset-y-auto sm:bottom-6 sm:end-6 sm:overflow-hidden sm:rounded-3xl sm:border sm:border-ews-gray-100",
378
396
  "sm:shadow-[0_24px_60px_rgba(15,23,42,0.22)]",
397
+ // Only fixed widths and heights, so expanding animates from one size to the other (a
398
+ // max-width switching to none jumped to full width first).
399
+ "sm:max-w-[calc(100vw-3rem)] sm:transition-[width,height] sm:duration-300 sm:ease-out",
379
400
  isExpanded ? "sm:h-[calc(100vh-3rem)]" : "sm:h-[calc(100vh-6rem)]",
380
401
  // Expanded: a dashboard reaching the middle of the screen (never full width), tablets and up.
381
- isExpanded ? "sm:max-w-md md:w-[max(50vw,40rem)] md:max-w-none" : "sm:max-w-md",
402
+ isExpanded ? "sm:w-[32rem] md:w-[max(50vw,40rem)]" : "sm:w-[32rem]",
382
403
  panelClassName
383
404
  )}
384
405
  >
406
+ {embed && (
407
+ <div className="absolute inset-0 z-20 flex flex-col bg-white" data-testid="assistant-launcher-embed">
408
+ <div className="flex items-center gap-2 border-b border-ews-gray-100 px-3 py-2">
409
+ <button
410
+ type="button"
411
+ onClick={onEmbedClose}
412
+ aria-label={launcherTexts.backToConversation ?? launcherTexts.close}
413
+ title={launcherTexts.backToConversation ?? launcherTexts.close}
414
+ className="rounded-full p-2 text-ews-gray-500 hover:bg-ews-gray-100 hover:text-ews-gray-900 focus:outline-none focus:ring-2 focus:ring-ews-primary"
415
+ data-testid="assistant-launcher-embed-back"
416
+ >
417
+ <ArrowLeft className="h-5 w-5 rtl:rotate-180" aria-hidden="true" />
418
+ </button>
419
+ <h2 className="min-w-0 flex-1 truncate text-base font-semibold text-ews-gray-900">{embed.title}</h2>
420
+ <button
421
+ type="button"
422
+ onClick={() => setOpen(false)}
423
+ aria-label={launcherTexts.close}
424
+ title={launcherTexts.close}
425
+ className="rounded-full p-2 text-ews-gray-500 hover:bg-ews-gray-100 hover:text-ews-gray-900 focus:outline-none focus:ring-2 focus:ring-ews-primary"
426
+ >
427
+ <X className="h-5 w-5" aria-hidden="true" />
428
+ </button>
429
+ </div>
430
+ <iframe title={embed.title} src={embed.url} className="block w-full flex-1 border-0" />
431
+ </div>
432
+ )}
385
433
  <AssistantChat
386
434
  {...chatProps}
387
435
  texts={texts}
@@ -1,2 +1,2 @@
1
1
  export { AssistantLauncher } from "./AssistantLauncher";
2
- export type { AssistantLauncherProps, AssistantLauncherTexts } from "./AssistantLauncher";
2
+ export type { AssistantEmbeddedPage, AssistantLauncherProps, AssistantLauncherTexts } from "./AssistantLauncher";
package/src/index.ts CHANGED
@@ -102,7 +102,7 @@ export type {
102
102
 
103
103
  // MED-1787: in-app launcher (bottom bar + slide-over) and the shared assistant client
104
104
  export { AssistantLauncher } from "./components/AssistantLauncher";
105
- export type { AssistantLauncherProps, AssistantLauncherTexts } from "./components/AssistantLauncher";
105
+ export type { AssistantEmbeddedPage, AssistantLauncherProps, AssistantLauncherTexts } from "./components/AssistantLauncher";
106
106
  export {
107
107
  ASSISTANT_CLIENT_ID_KEY,
108
108
  ASSISTANT_CONVERSATIONS_KEY,