@aircall/blocks 0.5.1 → 0.7.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/index.js CHANGED
@@ -1,14 +1,136 @@
1
+ import { Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, InputGroup, InputGroupButton, InputGroupTextarea, Popover, PopoverContent, PopoverTrigger, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, TabsList, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsTranslation, useSidebar } from "@aircall/ds";
1
2
  import * as React from "react";
2
3
  import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
3
- import { Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, InputGroup, InputGroupButton, InputGroupTextarea, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, TabsList, Tooltip, TooltipContent, TooltipTrigger, cn, useSidebar } from "@aircall/ds";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- import { AircallLogo, ArrowUp, Check, ChevronLeft, ChevronRight, Copy, Ellipsis, LockKeyhole, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, TriangleAlert } from "@aircall/react-icons";
6
- import { cva } from "class-variance-authority";
5
+ import { AircallLogo, ArrowUp, Check, ChevronLeft, ChevronRight, Copy, Ellipsis, ExternalLink, FileText, LockKeyhole, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, ThumbsDown, ThumbsDownFilled, ThumbsUp, ThumbsUpFilled, TriangleAlert, X } from "@aircall/react-icons";
6
+ import ReactMarkdown from "react-markdown";
7
+ import remarkGfm from "remark-gfm";
7
8
  import { useCallbackRef, useControllableState, useIsMounted, useUnmount } from "@aircall/hooks";
9
+ import { cva } from "class-variance-authority";
8
10
  import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
9
11
  import { mergeProps } from "@base-ui/react/merge-props";
10
12
  import { useRender } from "@base-ui/react/use-render";
11
13
 
14
+ //#region src/i18n/locales/de.ts
15
+ const de = {
16
+ "emptyState.comingSoon.title": "Demnächst verfügbar!",
17
+ "emptyState.comingSoon.description": "Sie werden in naher Zukunft auf diese Funktion zugreifen können.",
18
+ "emptyState.noData.title": "Keine passenden Ergebnisse",
19
+ "emptyState.noData.description": "Versuchen Sie, Ihre Suche zurückzusetzen oder einen Filter zu entfernen.",
20
+ "emptyState.restrictedAccess.title": "Sie haben keinen Zugriff auf diese Seite.",
21
+ "emptyState.restrictedAccess.description": "Wenden Sie sich an einen Administrator in Ihrem Team, um Zugriff zu erhalten.",
22
+ "emptyState.unknownError.title": "Diese Seite konnte nicht geladen werden",
23
+ "emptyState.unknownError.description": "Beim Laden ist ein Problem aufgetreten. Versuchen Sie es erneut, um die Daten zu laden.",
24
+ "emptyState.notFound.title": "Diese Seite konnte nicht gefunden werden",
25
+ "emptyState.notFound.description": "Sie wurde möglicherweise verschoben, gelöscht, oder der Link ist falsch.",
26
+ "emptyState.noSupport.title": "Diese Seite wird nicht unterstützt",
27
+ "emptyState.noSupport.description": "Versuchen Sie, Ihren Browser auf die neueste Version zu aktualisieren.",
28
+ "chatbotInput.placeholder": "Stellen Sie eine Frage...",
29
+ "chatbotInput.messageLabel": "Nachricht",
30
+ "chatbotInput.stopGenerating": "Generierung stoppen",
31
+ "chatbotInput.sendMessage": "Nachricht senden"
32
+ };
33
+ var de_default = de;
34
+
35
+ //#endregion
36
+ //#region src/i18n/locales/en.ts
37
+ const en = {
38
+ "emptyState.comingSoon.title": "Coming soon!",
39
+ "emptyState.comingSoon.description": "You'll be able to access this feature in the near future.",
40
+ "emptyState.noData.title": "No results matching",
41
+ "emptyState.noData.description": "Try resetting your search or removing a filter.",
42
+ "emptyState.restrictedAccess.title": "You don't have access to this page.",
43
+ "emptyState.restrictedAccess.description": "Contact an admin on your team to get access.",
44
+ "emptyState.unknownError.title": "We couldn't load this page",
45
+ "emptyState.unknownError.description": "Something went wrong while loading. Try again to load the data.",
46
+ "emptyState.notFound.title": "We couldn't find that page",
47
+ "emptyState.notFound.description": "It may have been moved, deleted, or the link may be incorrect.",
48
+ "emptyState.noSupport.title": "We can't support this page",
49
+ "emptyState.noSupport.description": "Try updating your browser to the latest version.",
50
+ "chatbotInput.placeholder": "Ask anything...",
51
+ "chatbotInput.messageLabel": "Message",
52
+ "chatbotInput.stopGenerating": "Stop generating",
53
+ "chatbotInput.sendMessage": "Send message"
54
+ };
55
+ var en_default = en;
56
+
57
+ //#endregion
58
+ //#region src/i18n/locales/es.ts
59
+ const es = {
60
+ "emptyState.comingSoon.title": "¡Próximamente!",
61
+ "emptyState.comingSoon.description": "Podrás acceder a esta función en un futuro próximo.",
62
+ "emptyState.noData.title": "No hay resultados que coincidan",
63
+ "emptyState.noData.description": "Intenta restablecer tu búsqueda o eliminar un filtro.",
64
+ "emptyState.restrictedAccess.title": "No tienes acceso a esta página.",
65
+ "emptyState.restrictedAccess.description": "Contacta a un administrador de tu equipo para obtener acceso.",
66
+ "emptyState.unknownError.title": "No pudimos cargar esta página",
67
+ "emptyState.unknownError.description": "Algo salió mal durante la carga. Inténtalo de nuevo para cargar los datos.",
68
+ "emptyState.notFound.title": "No pudimos encontrar esa página",
69
+ "emptyState.notFound.description": "Es posible que haya sido movida, eliminada, o el enlace sea incorrecto.",
70
+ "emptyState.noSupport.title": "No podemos mostrar esta página",
71
+ "emptyState.noSupport.description": "Intenta actualizar tu navegador a la última versión.",
72
+ "chatbotInput.placeholder": "Pregunta lo que quieras...",
73
+ "chatbotInput.messageLabel": "Mensaje",
74
+ "chatbotInput.stopGenerating": "Detener la generación",
75
+ "chatbotInput.sendMessage": "Enviar mensaje"
76
+ };
77
+ var es_default = es;
78
+
79
+ //#endregion
80
+ //#region src/i18n/locales/fr.ts
81
+ const fr = {
82
+ "emptyState.comingSoon.title": "Bientôt disponible !",
83
+ "emptyState.comingSoon.description": "Vous pourrez accéder à cette fonctionnalité dans un futur proche.",
84
+ "emptyState.noData.title": "Aucun résultat correspondant",
85
+ "emptyState.noData.description": "Réinitialisez votre recherche ou supprimez un filtre.",
86
+ "emptyState.restrictedAccess.title": "Vous n'avez pas accès à cette page.",
87
+ "emptyState.restrictedAccess.description": "Contactez un administrateur de votre équipe pour obtenir l'accès.",
88
+ "emptyState.unknownError.title": "Impossible de charger cette page",
89
+ "emptyState.unknownError.description": "Un problème est survenu lors du chargement. Réessayez pour charger les données.",
90
+ "emptyState.notFound.title": "Impossible de trouver cette page",
91
+ "emptyState.notFound.description": "Elle a peut-être été déplacée, supprimée, ou le lien est incorrect.",
92
+ "emptyState.noSupport.title": "Nous ne pouvons pas prendre en charge cette page",
93
+ "emptyState.noSupport.description": "Essayez de mettre à jour votre navigateur vers la dernière version.",
94
+ "chatbotInput.placeholder": "Posez votre question...",
95
+ "chatbotInput.messageLabel": "Message",
96
+ "chatbotInput.stopGenerating": "Arrêter la génération",
97
+ "chatbotInput.sendMessage": "Envoyer un message"
98
+ };
99
+ var fr_default = fr;
100
+
101
+ //#endregion
102
+ //#region src/i18n/locales/it.ts
103
+ const it = {
104
+ "emptyState.comingSoon.title": "Prossimamente!",
105
+ "emptyState.comingSoon.description": "Potrai accedere a questa funzionalità nel prossimo futuro.",
106
+ "emptyState.noData.title": "Nessun risultato corrispondente",
107
+ "emptyState.noData.description": "Prova a reimpostare la ricerca o a rimuovere un filtro.",
108
+ "emptyState.restrictedAccess.title": "Non hai accesso a questa pagina.",
109
+ "emptyState.restrictedAccess.description": "Contatta un amministratore del tuo team per ottenere l'accesso.",
110
+ "emptyState.unknownError.title": "Impossibile caricare questa pagina",
111
+ "emptyState.unknownError.description": "Si è verificato un problema durante il caricamento. Riprova a caricare i dati.",
112
+ "emptyState.notFound.title": "Impossibile trovare quella pagina",
113
+ "emptyState.notFound.description": "Potrebbe essere stata spostata, eliminata, o il link potrebbe essere errato.",
114
+ "emptyState.noSupport.title": "Non possiamo supportare questa pagina",
115
+ "emptyState.noSupport.description": "Prova ad aggiornare il browser all'ultima versione.",
116
+ "chatbotInput.placeholder": "Chiedi qualsiasi cosa...",
117
+ "chatbotInput.messageLabel": "Messaggio",
118
+ "chatbotInput.stopGenerating": "Interrompi la generazione",
119
+ "chatbotInput.sendMessage": "Invia messaggio"
120
+ };
121
+ var it_default = it;
122
+
123
+ //#endregion
124
+ //#region src/i18n/register.ts
125
+ registerI18nNamespace("blocks", {
126
+ en: en_default,
127
+ fr: fr_default,
128
+ es: es_default,
129
+ de: de_default,
130
+ it: it_default
131
+ });
132
+
133
+ //#endregion
12
134
  //#region src/components/empty-state.tsx
13
135
  /** Generic root. Anchor of the family + escape hatch for ad-hoc empty states. */
14
136
  const EmptyState = React.forwardRef((componentProps, forwardRef) => {
@@ -102,17 +224,23 @@ function createEmptyStateVariant(config) {
102
224
  children: config.icon
103
225
  }));
104
226
  Media.displayName = `${config.name}Media`;
105
- const Title = React.forwardRef(({ children, ...componentProps }, forwardRef) => /* @__PURE__ */ jsx(EmptyStateTitle, {
106
- ref: forwardRef,
107
- ...componentProps,
108
- children: children ?? config.title
109
- }));
227
+ const Title = React.forwardRef(({ children, ...componentProps }, forwardRef) => {
228
+ const t = useDsTranslation("blocks");
229
+ return /* @__PURE__ */ jsx(EmptyStateTitle, {
230
+ ref: forwardRef,
231
+ ...componentProps,
232
+ children: children ?? t(config.titleKey)
233
+ });
234
+ });
110
235
  Title.displayName = `${config.name}Title`;
111
- const Description = React.forwardRef(({ children, ...componentProps }, forwardRef) => /* @__PURE__ */ jsx(EmptyStateDescription, {
112
- ref: forwardRef,
113
- ...componentProps,
114
- children: children ?? config.description
115
- }));
236
+ const Description = React.forwardRef(({ children, ...componentProps }, forwardRef) => {
237
+ const t = useDsTranslation("blocks");
238
+ return /* @__PURE__ */ jsx(EmptyStateDescription, {
239
+ ref: forwardRef,
240
+ ...componentProps,
241
+ children: children ?? (config.descriptionKey ? t(config.descriptionKey) : void 0)
242
+ });
243
+ });
116
244
  Description.displayName = `${config.name}Description`;
117
245
  return {
118
246
  Root,
@@ -148,8 +276,8 @@ function createEmptyStateVariant(config) {
148
276
  const ComingSoon = createEmptyStateVariant({
149
277
  name: "ComingSoon",
150
278
  icon: /* @__PURE__ */ jsx(Sprout, {}),
151
- title: "Coming soon!",
152
- description: "You’ll be able to access this feature in the near future."
279
+ titleKey: "emptyState.comingSoon.title",
280
+ descriptionKey: "emptyState.comingSoon.description"
153
281
  });
154
282
  const ComingSoonEmptyState = ComingSoon.Root;
155
283
  const ComingSoonMedia = ComingSoon.Media;
@@ -165,8 +293,8 @@ const ComingSoonDescription = ComingSoon.Description;
165
293
  const RestrictedAccess = createEmptyStateVariant({
166
294
  name: "RestrictedAccess",
167
295
  icon: /* @__PURE__ */ jsx(LockKeyhole, {}),
168
- title: "You don’t have access to this page.",
169
- description: "Contact an admin on your team to get access."
296
+ titleKey: "emptyState.restrictedAccess.title",
297
+ descriptionKey: "emptyState.restrictedAccess.description"
170
298
  });
171
299
  const RestrictedAccessEmptyState = RestrictedAccess.Root;
172
300
  const RestrictedAccessMedia = RestrictedAccess.Media;
@@ -182,8 +310,8 @@ const RestrictedAccessDescription = RestrictedAccess.Description;
182
310
  const NotFound = createEmptyStateVariant({
183
311
  name: "NotFound",
184
312
  icon: /* @__PURE__ */ jsx(Search, {}),
185
- title: "We couldn’t find that page",
186
- description: "It may have been moved, deleted, or the link may be incorrect."
313
+ titleKey: "emptyState.notFound.title",
314
+ descriptionKey: "emptyState.notFound.description"
187
315
  });
188
316
  const NotFoundEmptyState = NotFound.Root;
189
317
  const NotFoundMedia = NotFound.Media;
@@ -200,8 +328,8 @@ const NotFoundDescription = NotFound.Description;
200
328
  const NoData = createEmptyStateVariant({
201
329
  name: "NoData",
202
330
  icon: /* @__PURE__ */ jsx(Search, {}),
203
- title: "No results matching",
204
- description: "Try resetting your search or removing a filter.",
331
+ titleKey: "emptyState.noData.title",
332
+ descriptionKey: "emptyState.noData.description",
205
333
  role: "status"
206
334
  });
207
335
  const NoDataEmptyState = NoData.Root;
@@ -218,8 +346,8 @@ const NoDataDescription = NoData.Description;
218
346
  const NoSupport = createEmptyStateVariant({
219
347
  name: "NoSupport",
220
348
  icon: /* @__PURE__ */ jsx(Settings, {}),
221
- title: "We can’t support this page",
222
- description: "Try updating your browser to the latest version."
349
+ titleKey: "emptyState.noSupport.title",
350
+ descriptionKey: "emptyState.noSupport.description"
223
351
  });
224
352
  const NoSupportEmptyState = NoSupport.Root;
225
353
  const NoSupportMedia = NoSupport.Media;
@@ -236,8 +364,8 @@ const NoSupportDescription = NoSupport.Description;
236
364
  const UnknownError = createEmptyStateVariant({
237
365
  name: "UnknownError",
238
366
  icon: /* @__PURE__ */ jsx(TriangleAlert, {}),
239
- title: "We couldn’t load this page",
240
- description: "Something went wrong while loading. Try again to load the data.",
367
+ titleKey: "emptyState.unknownError.title",
368
+ descriptionKey: "emptyState.unknownError.description",
241
369
  role: "alert"
242
370
  });
243
371
  const UnknownErrorEmptyState = UnknownError.Root;
@@ -245,6 +373,290 @@ const UnknownErrorMedia = UnknownError.Media;
245
373
  const UnknownErrorTitle = UnknownError.Title;
246
374
  const UnknownErrorDescription = UnknownError.Description;
247
375
 
376
+ //#endregion
377
+ //#region src/hooks/use-copy-to-clipboard.ts
378
+ function useCopyToClipboard(value, { timeout = 5e3, onCopied } = {}) {
379
+ const [copied, setCopied] = useState(false);
380
+ const timerRef = useRef(void 0);
381
+ const getIsMounted = useIsMounted();
382
+ const onCopiedRef = useCallbackRef(onCopied);
383
+ useUnmount(() => clearTimeout(timerRef.current));
384
+ useEffect(() => {
385
+ clearTimeout(timerRef.current);
386
+ setCopied(false);
387
+ }, [value]);
388
+ return {
389
+ copy: useCallback(async () => {
390
+ try {
391
+ await navigator.clipboard.writeText(value);
392
+ if (!getIsMounted()) return;
393
+ setCopied(true);
394
+ onCopiedRef();
395
+ clearTimeout(timerRef.current);
396
+ timerRef.current = setTimeout(() => {
397
+ if (getIsMounted()) setCopied(false);
398
+ }, timeout);
399
+ } catch {}
400
+ }, [
401
+ value,
402
+ timeout,
403
+ getIsMounted,
404
+ onCopiedRef
405
+ ]),
406
+ copied
407
+ };
408
+ }
409
+
410
+ //#endregion
411
+ //#region src/components/copy-button.tsx
412
+ const CopyButtonContext = createContext(null);
413
+ function useCopyButtonContext() {
414
+ const ctx = useContext(CopyButtonContext);
415
+ if (ctx === null) throw new Error("CopyButtonIcon and CopyButtonLabel must be rendered inside a CopyButton.");
416
+ return ctx;
417
+ }
418
+ const CopyButton = React.forwardRef((componentProps, forwardRef) => {
419
+ const { value, timeout, onCopied, variant = "outline", children, ...buttonProps } = componentProps;
420
+ const { copy, copied } = useCopyToClipboard(value, {
421
+ timeout,
422
+ onCopied
423
+ });
424
+ return /* @__PURE__ */ jsx(CopyButtonContext.Provider, {
425
+ value: { copied },
426
+ children: /* @__PURE__ */ jsx(Button, {
427
+ ref: forwardRef,
428
+ onClick: () => {
429
+ copy();
430
+ },
431
+ variant,
432
+ ...buttonProps,
433
+ children
434
+ })
435
+ });
436
+ });
437
+ CopyButton.displayName = "CopyButton";
438
+ const CopyButtonIcon = React.forwardRef((componentProps, forwardRef) => {
439
+ const { className } = componentProps;
440
+ const { copied } = useCopyButtonContext();
441
+ return /* @__PURE__ */ jsx(copied ? Check : Copy, {
442
+ ref: forwardRef,
443
+ className
444
+ });
445
+ });
446
+ CopyButtonIcon.displayName = "CopyButtonIcon";
447
+ const CopyButtonLabel = React.forwardRef((componentProps, forwardRef) => {
448
+ const { label = "Copy", copiedLabel = "Copied", className } = componentProps;
449
+ const { copied } = useCopyButtonContext();
450
+ return /* @__PURE__ */ jsx("span", {
451
+ ref: forwardRef,
452
+ className: cn(className),
453
+ children: copied ? copiedLabel : label
454
+ });
455
+ });
456
+ CopyButtonLabel.displayName = "CopyButtonLabel";
457
+
458
+ //#endregion
459
+ //#region src/components/chatbot-response-block.tsx
460
+ function SourcesBadge({ sources }) {
461
+ const [open, setOpen] = useState(false);
462
+ const closeTimerRef = useRef();
463
+ const openedByHoverRef = useRef(false);
464
+ useUnmount(() => clearTimeout(closeTimerRef.current));
465
+ function handlePointerEnter(e) {
466
+ if (e.pointerType !== "mouse") return;
467
+ openedByHoverRef.current = true;
468
+ clearTimeout(closeTimerRef.current);
469
+ setOpen(true);
470
+ }
471
+ function handlePointerLeave(e) {
472
+ if (e.pointerType !== "mouse") return;
473
+ clearTimeout(closeTimerRef.current);
474
+ closeTimerRef.current = setTimeout(() => {
475
+ openedByHoverRef.current = false;
476
+ setOpen(false);
477
+ }, 150);
478
+ }
479
+ return /* @__PURE__ */ jsxs(Popover, {
480
+ open,
481
+ onOpenChange: (nextOpen, eventDetails) => {
482
+ if (!nextOpen) {
483
+ if (eventDetails.reason === "trigger-press" && openedByHoverRef.current) return;
484
+ openedByHoverRef.current = false;
485
+ clearTimeout(closeTimerRef.current);
486
+ }
487
+ setOpen(nextOpen);
488
+ },
489
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
490
+ className: "cursor-default rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
491
+ onPointerEnter: handlePointerEnter,
492
+ onPointerLeave: handlePointerLeave,
493
+ children: /* @__PURE__ */ jsx(Badge, {
494
+ className: "!rounded-full !bg-charcoal-400/20",
495
+ children: `${sources.length} ${sources.length === 1 ? "Source" : "Sources"}`
496
+ })
497
+ }), /* @__PURE__ */ jsx(PopoverContent, {
498
+ side: "top",
499
+ align: "start",
500
+ sideOffset: 8,
501
+ className: "w-80",
502
+ onPointerEnter: handlePointerEnter,
503
+ onPointerLeave: handlePointerLeave,
504
+ children: /* @__PURE__ */ jsx("div", {
505
+ role: "list",
506
+ className: "flex flex-col",
507
+ children: sources.map((source) => /* @__PURE__ */ jsxs("div", {
508
+ role: "listitem",
509
+ className: "flex items-center gap-2 px-2 py-2",
510
+ children: [
511
+ /* @__PURE__ */ jsx(FileText, { className: "size-3.5 shrink-0 text-foreground" }),
512
+ /* @__PURE__ */ jsx("span", {
513
+ className: "min-w-0 flex-1 line-clamp-2 text-sm",
514
+ children: source.title
515
+ }),
516
+ /* @__PURE__ */ jsx("a", {
517
+ href: source.url,
518
+ target: "_blank",
519
+ rel: "noreferrer",
520
+ "aria-label": `Open ${source.title}`,
521
+ className: "shrink-0 text-foreground hover:text-primary",
522
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "size-3.5" })
523
+ })
524
+ ]
525
+ }, source.url))
526
+ })
527
+ })]
528
+ });
529
+ }
530
+ function ChatbotResponseBlock({ messageId, markdown, streaming = false, sources, onFeedback, className }) {
531
+ const [feedback, setFeedback] = useState(null);
532
+ const onFeedbackRef = useCallbackRef(onFeedback);
533
+ const isMounted = useRef(false);
534
+ const feedbackRef = useRef(feedback);
535
+ feedbackRef.current = feedback;
536
+ useEffect(() => {
537
+ if (!isMounted.current) {
538
+ isMounted.current = true;
539
+ return;
540
+ }
541
+ if (feedbackRef.current === null) return;
542
+ setFeedback(null);
543
+ onFeedbackRef(null);
544
+ }, [
545
+ messageId,
546
+ markdown,
547
+ onFeedbackRef
548
+ ]);
549
+ function handleFeedback(value) {
550
+ const next = feedback === value ? null : value;
551
+ setFeedback(next);
552
+ onFeedback?.(next);
553
+ }
554
+ return /* @__PURE__ */ jsxs("div", {
555
+ "data-slot": "chatbot-response-block",
556
+ className: cn("flex flex-col gap-2", className),
557
+ children: [/* @__PURE__ */ jsx(ReactMarkdown, {
558
+ remarkPlugins: [remarkGfm],
559
+ components: {
560
+ h1: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h1", {
561
+ className: "mt-10 text-4xl font-semibold first:mt-0",
562
+ ...props
563
+ }),
564
+ h2: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h2", {
565
+ className: "mt-11 border-b border-border pb-2 text-3xl font-semibold leading-9 first:mt-0",
566
+ ...props
567
+ }),
568
+ h3: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h3", {
569
+ className: "mt-10 text-2xl font-semibold leading-8 first:mt-0",
570
+ ...props
571
+ }),
572
+ p: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("p", {
573
+ className: "mt-4 text-base leading-6 text-foreground first:mt-0",
574
+ ...props
575
+ }),
576
+ blockquote: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("blockquote", {
577
+ className: "mt-4 border-l-2 border-border pl-6 first:mt-0",
578
+ ...props
579
+ }),
580
+ ul: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ul", {
581
+ className: "mt-4 list-disc pl-6 text-base leading-6 text-foreground first:mt-0",
582
+ ...props
583
+ }),
584
+ ol: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ol", {
585
+ className: "mt-4 list-decimal pl-6 text-base leading-6 text-foreground first:mt-0",
586
+ ...props
587
+ }),
588
+ li: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("li", {
589
+ className: "mb-2 last:mb-0",
590
+ ...props
591
+ }),
592
+ a: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("a", {
593
+ className: "text-green-700 underline underline-offset-2 hover:text-green-700/80",
594
+ ...props
595
+ }),
596
+ table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", {
597
+ className: "mt-4 overflow-x-auto rounded border border-border first:mt-0",
598
+ children: /* @__PURE__ */ jsx("table", {
599
+ className: "w-full text-base leading-6",
600
+ ...props
601
+ })
602
+ }),
603
+ th: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("th", {
604
+ className: "border-t border-border px-4 py-2 text-left font-bold text-foreground",
605
+ style,
606
+ ...props
607
+ }),
608
+ td: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("td", {
609
+ className: "border-t border-border px-4 py-2 text-foreground",
610
+ style,
611
+ ...props
612
+ }),
613
+ tr: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("tr", {
614
+ className: "even:bg-muted",
615
+ ...props
616
+ })
617
+ },
618
+ children: markdown
619
+ }), !streaming && /* @__PURE__ */ jsxs("div", {
620
+ className: "flex items-center gap-1",
621
+ children: [sources && sources.length > 0 && /* @__PURE__ */ jsx("div", {
622
+ className: "flex flex-1",
623
+ children: /* @__PURE__ */ jsx(SourcesBadge, { sources })
624
+ }), /* @__PURE__ */ jsxs("div", {
625
+ className: "ml-auto flex items-center gap-1",
626
+ children: [
627
+ /* @__PURE__ */ jsx(CopyButton, {
628
+ value: markdown,
629
+ variant: "ghost",
630
+ size: "icon-sm",
631
+ "aria-label": "Copy",
632
+ children: /* @__PURE__ */ jsx(CopyButtonIcon, {})
633
+ }),
634
+ /* @__PURE__ */ jsx(Button, {
635
+ type: "button",
636
+ variant: "ghost",
637
+ size: "icon-sm",
638
+ "aria-pressed": feedback === "up",
639
+ "aria-label": "Helpful",
640
+ onClick: () => handleFeedback("up"),
641
+ className: cn(feedback === "up" && "text-primary"),
642
+ children: feedback === "up" ? /* @__PURE__ */ jsx(ThumbsUpFilled, {}) : /* @__PURE__ */ jsx(ThumbsUp, {})
643
+ }),
644
+ /* @__PURE__ */ jsx(Button, {
645
+ type: "button",
646
+ variant: "ghost",
647
+ size: "icon-sm",
648
+ "aria-pressed": feedback === "down",
649
+ "aria-label": "Not helpful",
650
+ onClick: () => handleFeedback("down"),
651
+ className: cn(feedback === "down" && "text-primary"),
652
+ children: feedback === "down" ? /* @__PURE__ */ jsx(ThumbsDownFilled, {}) : /* @__PURE__ */ jsx(ThumbsDown, {})
653
+ })
654
+ ]
655
+ })]
656
+ })]
657
+ });
658
+ }
659
+
248
660
  //#endregion
249
661
  //#region src/components/chatbot-page.tsx
250
662
  const ChatbotPage = React.forwardRef((componentProps, forwardRef) => {
@@ -258,6 +670,55 @@ const ChatbotPage = React.forwardRef((componentProps, forwardRef) => {
258
670
  });
259
671
  ChatbotPage.displayName = "ChatbotPage";
260
672
 
673
+ //#endregion
674
+ //#region src/components/chatbot-panel-header.tsx
675
+ /**
676
+ * Header bar for the chatbot panel.
677
+ *
678
+ * Always visible — the panel body collapses/expands below it. Controlled by
679
+ * the parent via `open` + `onOpenChange`; pass `onClose` to show the dismiss (×) button.
680
+ */
681
+ function ChatbotPanelHeader({ title = "Ask Aircall", open, onOpenChange, onFeedback, onClose, className, ...props }) {
682
+ return /* @__PURE__ */ jsxs("div", {
683
+ "data-slot": "chatbot-panel-header",
684
+ "data-state": open ? "open" : "closed",
685
+ className: cn("flex w-full items-center gap-2 border-b border-border bg-background p-2", className),
686
+ ...props,
687
+ children: [
688
+ /* @__PURE__ */ jsx("span", {
689
+ "aria-hidden": "true",
690
+ className: "flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground",
691
+ children: /* @__PURE__ */ jsx(PanelLeft, { className: "size-4" })
692
+ }),
693
+ /* @__PURE__ */ jsx("span", {
694
+ className: "min-w-0 flex-1 truncate text-base font-bold leading-none text-foreground",
695
+ children: title
696
+ }),
697
+ onFeedback != null && /* @__PURE__ */ jsx(Button, {
698
+ variant: "ghost",
699
+ size: "icon",
700
+ "aria-label": "Send feedback",
701
+ onClick: onFeedback,
702
+ children: /* @__PURE__ */ jsx(MessageSquare, { className: "size-4" })
703
+ }),
704
+ /* @__PURE__ */ jsx(Button, {
705
+ variant: "ghost",
706
+ size: "icon",
707
+ "aria-label": open ? "Collapse panel" : "Expand panel",
708
+ onClick: () => onOpenChange(!open),
709
+ children: open ? /* @__PURE__ */ jsx(Minus, { className: "size-4" }) : /* @__PURE__ */ jsx(Plus, { className: "size-4" })
710
+ }),
711
+ onClose != null && /* @__PURE__ */ jsx(Button, {
712
+ variant: "ghost",
713
+ size: "icon",
714
+ "aria-label": "Close panel",
715
+ onClick: onClose,
716
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
717
+ })
718
+ ]
719
+ });
720
+ }
721
+
261
722
  //#endregion
262
723
  //#region src/components/chatbot-sidebar.tsx
263
724
  const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
@@ -318,7 +779,8 @@ const textareaVariants = cva([
318
779
  } },
319
780
  defaultVariants: { size: "default" }
320
781
  });
321
- function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, placeholder = "Ask anything...", loading = false, size = "default", disabled, ...textareaProps }) {
782
+ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, placeholder, loading = false, size = "default", disabled, ...textareaProps }) {
783
+ const t = useDsTranslation("blocks");
322
784
  const [currentValue = "", setCurrentValue] = useControllableState({
323
785
  prop: value,
324
786
  defaultProp: "",
@@ -347,8 +809,8 @@ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, place
347
809
  "aria-disabled": disabled || void 0,
348
810
  children: [/* @__PURE__ */ jsx(InputGroupTextarea, {
349
811
  rows: 1,
350
- placeholder,
351
- "aria-label": "Message",
812
+ placeholder: placeholder ?? t("chatbotInput.placeholder"),
813
+ "aria-label": t("chatbotInput.messageLabel"),
352
814
  ...textareaProps,
353
815
  value: currentValue,
354
816
  disabled,
@@ -361,7 +823,7 @@ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, place
361
823
  variant: "secondary",
362
824
  size: "icon",
363
825
  className: "rounded-full",
364
- "aria-label": "Stop generating",
826
+ "aria-label": t("chatbotInput.stopGenerating"),
365
827
  disabled,
366
828
  onClick: onStop,
367
829
  children: /* @__PURE__ */ jsx(Square, {})
@@ -369,7 +831,7 @@ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, place
369
831
  variant: hasValue ? "default" : "ghost",
370
832
  size: "icon",
371
833
  className: "rounded-full",
372
- "aria-label": "Send message",
834
+ "aria-label": t("chatbotInput.sendMessage"),
373
835
  disabled: disabled || !hasValue,
374
836
  onClick: submit,
375
837
  children: /* @__PURE__ */ jsx(ArrowUp, {})
@@ -378,88 +840,6 @@ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, place
378
840
  });
379
841
  }
380
842
 
381
- //#endregion
382
- //#region src/hooks/use-copy-to-clipboard.ts
383
- function useCopyToClipboard(value, { timeout = 5e3, onCopied } = {}) {
384
- const [copied, setCopied] = useState(false);
385
- const timerRef = useRef(void 0);
386
- const getIsMounted = useIsMounted();
387
- const onCopiedRef = useCallbackRef(onCopied);
388
- useUnmount(() => clearTimeout(timerRef.current));
389
- useEffect(() => {
390
- clearTimeout(timerRef.current);
391
- setCopied(false);
392
- }, [value]);
393
- return {
394
- copy: useCallback(async () => {
395
- try {
396
- await navigator.clipboard.writeText(value);
397
- if (!getIsMounted()) return;
398
- setCopied(true);
399
- onCopiedRef();
400
- clearTimeout(timerRef.current);
401
- timerRef.current = setTimeout(() => {
402
- if (getIsMounted()) setCopied(false);
403
- }, timeout);
404
- } catch {}
405
- }, [
406
- value,
407
- timeout,
408
- getIsMounted,
409
- onCopiedRef
410
- ]),
411
- copied
412
- };
413
- }
414
-
415
- //#endregion
416
- //#region src/components/copy-button.tsx
417
- const CopyButtonContext = createContext(null);
418
- function useCopyButtonContext() {
419
- const ctx = useContext(CopyButtonContext);
420
- if (ctx === null) throw new Error("CopyButtonIcon and CopyButtonLabel must be rendered inside a CopyButton.");
421
- return ctx;
422
- }
423
- const CopyButton = React.forwardRef((componentProps, forwardRef) => {
424
- const { value, timeout, onCopied, variant = "outline", children, ...buttonProps } = componentProps;
425
- const { copy, copied } = useCopyToClipboard(value, {
426
- timeout,
427
- onCopied
428
- });
429
- return /* @__PURE__ */ jsx(CopyButtonContext.Provider, {
430
- value: { copied },
431
- children: /* @__PURE__ */ jsx(Button, {
432
- ref: forwardRef,
433
- onClick: () => {
434
- copy();
435
- },
436
- variant,
437
- ...buttonProps,
438
- children
439
- })
440
- });
441
- });
442
- CopyButton.displayName = "CopyButton";
443
- const CopyButtonIcon = React.forwardRef((componentProps, forwardRef) => {
444
- const { className } = componentProps;
445
- const { copied } = useCopyButtonContext();
446
- return /* @__PURE__ */ jsx(copied ? Check : Copy, {
447
- ref: forwardRef,
448
- className
449
- });
450
- });
451
- CopyButtonIcon.displayName = "CopyButtonIcon";
452
- const CopyButtonLabel = React.forwardRef((componentProps, forwardRef) => {
453
- const { label = "Copy", copiedLabel = "Copied", className } = componentProps;
454
- const { copied } = useCopyButtonContext();
455
- return /* @__PURE__ */ jsx("span", {
456
- ref: forwardRef,
457
- className: cn(className),
458
- children: copied ? copiedLabel : label
459
- });
460
- });
461
- CopyButtonLabel.displayName = "CopyButtonLabel";
462
-
463
843
  //#endregion
464
844
  //#region src/components/chatbot-panel-suggestion.tsx
465
845
  function ChatbotPanelSuggestion({ className, ...props }) {
@@ -1650,4 +2030,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
1650
2030
  }
1651
2031
 
1652
2032
  //#endregion
1653
- export { BetaBadge, CardSaveBar, ChatbotInput, ChatbotPage, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotSidebar, ChatbotUserMessage, ComingSoonDescription, ComingSoonEmptyState, ComingSoonMedia, ComingSoonTitle, CommonForm, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormComboboxField, FormFieldBase, FormInputField, FormMultiComboboxField, FormNumberField, FormOTPField, FormRadioGroupField, FormSelectField, FormSliderField, FormSwitchField, FormTextareaField, FormToggleGroupField, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withForm };
2033
+ export { BetaBadge, CardSaveBar, ChatbotInput, ChatbotPage, ChatbotPanelHeader, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotResponseBlock, ChatbotSidebar, ChatbotUserMessage, ComingSoonDescription, ComingSoonEmptyState, ComingSoonMedia, ComingSoonTitle, CommonForm, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormComboboxField, FormFieldBase, FormInputField, FormMultiComboboxField, FormNumberField, FormOTPField, FormRadioGroupField, FormSelectField, FormSliderField, FormSwitchField, FormTextareaField, FormToggleGroupField, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withForm };