@customerhero/react 2.3.0 → 2.4.1

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.
@@ -1307,6 +1307,201 @@ function StreamingCursor({ reduced }) {
1307
1307
  )
1308
1308
  ] });
1309
1309
  }
1310
+ function formatBytes(n) {
1311
+ if (n == null || n <= 0) return null;
1312
+ if (n < 1024) return `${n} B`;
1313
+ if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
1314
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
1315
+ }
1316
+ function AttachmentList({
1317
+ attachments,
1318
+ isUser,
1319
+ primaryColor,
1320
+ t
1321
+ }) {
1322
+ const containerStyle = {
1323
+ display: "flex",
1324
+ flexDirection: "column",
1325
+ gap: 6,
1326
+ marginTop: 6,
1327
+ alignItems: isUser ? "flex-end" : "flex-start"
1328
+ };
1329
+ return /* @__PURE__ */ jsx6("div", { style: containerStyle, children: attachments.map((a) => /* @__PURE__ */ jsx6(
1330
+ AttachmentTile,
1331
+ {
1332
+ attachment: a,
1333
+ isUser,
1334
+ primaryColor,
1335
+ t
1336
+ },
1337
+ a.id
1338
+ )) });
1339
+ }
1340
+ function AttachmentTile({
1341
+ attachment,
1342
+ isUser,
1343
+ primaryColor,
1344
+ t
1345
+ }) {
1346
+ const tileBg = isUser ? `${primaryColor}11` : "#f3f4f6";
1347
+ const tileBorder = isUser ? `${primaryColor}33` : "#e5e7eb";
1348
+ if (attachment.kind === "image" && attachment.url) {
1349
+ return /* @__PURE__ */ jsx6(
1350
+ "a",
1351
+ {
1352
+ href: attachment.url,
1353
+ target: "_blank",
1354
+ rel: "noopener noreferrer",
1355
+ style: { display: "inline-block", maxWidth: 220, lineHeight: 0 },
1356
+ "aria-label": attachment.filename ?? t("attachment_image_alt"),
1357
+ children: /* @__PURE__ */ jsx6(
1358
+ "img",
1359
+ {
1360
+ src: attachment.url,
1361
+ alt: attachment.filename ?? t("attachment_image_alt"),
1362
+ style: {
1363
+ display: "block",
1364
+ maxWidth: 220,
1365
+ maxHeight: 220,
1366
+ width: "auto",
1367
+ height: "auto",
1368
+ borderRadius: 12,
1369
+ border: `1px solid ${tileBorder}`,
1370
+ objectFit: "cover"
1371
+ }
1372
+ }
1373
+ )
1374
+ }
1375
+ );
1376
+ }
1377
+ if (attachment.kind === "location" && attachment.location) {
1378
+ const { latitude, longitude, label } = attachment.location;
1379
+ const href = `https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`;
1380
+ return /* @__PURE__ */ jsxs5(
1381
+ "a",
1382
+ {
1383
+ href,
1384
+ target: "_blank",
1385
+ rel: "noopener noreferrer",
1386
+ style: tileLinkStyle(tileBg, tileBorder),
1387
+ children: [
1388
+ /* @__PURE__ */ jsx6(PinIcon, {}),
1389
+ /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1390
+ /* @__PURE__ */ jsx6("span", { style: tileTitleStyle(), children: label || t("attachment_location") }),
1391
+ /* @__PURE__ */ jsxs5("span", { style: tileSubStyle(), children: [
1392
+ latitude.toFixed(4),
1393
+ ", ",
1394
+ longitude.toFixed(4)
1395
+ ] })
1396
+ ] })
1397
+ ]
1398
+ }
1399
+ );
1400
+ }
1401
+ if (!attachment.url) return null;
1402
+ const sub = [attachment.mimeType, formatBytes(attachment.sizeBytes)].filter(Boolean).join(" \xB7 ");
1403
+ return /* @__PURE__ */ jsxs5(
1404
+ "a",
1405
+ {
1406
+ href: attachment.url,
1407
+ target: "_blank",
1408
+ rel: "noopener noreferrer",
1409
+ title: t("attachment_open"),
1410
+ style: tileLinkStyle(tileBg, tileBorder),
1411
+ children: [
1412
+ /* @__PURE__ */ jsx6(FileIcon, { kind: attachment.kind }),
1413
+ /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1414
+ /* @__PURE__ */ jsx6("span", { style: tileTitleStyle(), children: attachment.filename ?? t("attachment_open") }),
1415
+ sub && /* @__PURE__ */ jsx6("span", { style: tileSubStyle(), children: sub })
1416
+ ] })
1417
+ ]
1418
+ }
1419
+ );
1420
+ }
1421
+ function tileLinkStyle(bg, border) {
1422
+ return {
1423
+ display: "flex",
1424
+ alignItems: "center",
1425
+ gap: 10,
1426
+ padding: "8px 12px",
1427
+ background: bg,
1428
+ border: `1px solid ${border}`,
1429
+ borderRadius: 12,
1430
+ textDecoration: "none",
1431
+ color: "inherit",
1432
+ maxWidth: 260
1433
+ };
1434
+ }
1435
+ function tileTitleStyle() {
1436
+ return {
1437
+ fontSize: 13,
1438
+ fontWeight: 500,
1439
+ color: "#1f2937",
1440
+ overflow: "hidden",
1441
+ textOverflow: "ellipsis",
1442
+ whiteSpace: "nowrap",
1443
+ maxWidth: 200
1444
+ };
1445
+ }
1446
+ function tileSubStyle() {
1447
+ return {
1448
+ fontSize: 11,
1449
+ color: "#6b7280",
1450
+ overflow: "hidden",
1451
+ textOverflow: "ellipsis",
1452
+ whiteSpace: "nowrap",
1453
+ maxWidth: 200
1454
+ };
1455
+ }
1456
+ function FileIcon({ kind }) {
1457
+ return /* @__PURE__ */ jsx6(
1458
+ "svg",
1459
+ {
1460
+ width: "20",
1461
+ height: "20",
1462
+ viewBox: "0 0 24 24",
1463
+ fill: "none",
1464
+ stroke: "#4b5563",
1465
+ strokeWidth: "2",
1466
+ strokeLinecap: "round",
1467
+ strokeLinejoin: "round",
1468
+ "aria-hidden": "true",
1469
+ style: { flexShrink: 0 },
1470
+ children: kind === "audio" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
1471
+ /* @__PURE__ */ jsx6("path", { d: "M9 18V5l12-2v13" }),
1472
+ /* @__PURE__ */ jsx6("circle", { cx: "6", cy: "18", r: "3" }),
1473
+ /* @__PURE__ */ jsx6("circle", { cx: "18", cy: "16", r: "3" })
1474
+ ] }) : kind === "video" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
1475
+ /* @__PURE__ */ jsx6("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" }),
1476
+ /* @__PURE__ */ jsx6("path", { d: "m22 8-6 4 6 4z" })
1477
+ ] }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
1478
+ /* @__PURE__ */ jsx6("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1479
+ /* @__PURE__ */ jsx6("polyline", { points: "14 2 14 8 20 8" })
1480
+ ] })
1481
+ }
1482
+ );
1483
+ }
1484
+ function PinIcon() {
1485
+ return /* @__PURE__ */ jsxs5(
1486
+ "svg",
1487
+ {
1488
+ width: "20",
1489
+ height: "20",
1490
+ viewBox: "0 0 24 24",
1491
+ fill: "none",
1492
+ stroke: "#4b5563",
1493
+ strokeWidth: "2",
1494
+ strokeLinecap: "round",
1495
+ strokeLinejoin: "round",
1496
+ "aria-hidden": "true",
1497
+ style: { flexShrink: 0 },
1498
+ children: [
1499
+ /* @__PURE__ */ jsx6("path", { d: "M20 10c0 7-8 13-8 13s-8-6-8-13a8 8 0 0 1 16 0Z" }),
1500
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "10", r: "3" })
1501
+ ]
1502
+ }
1503
+ );
1504
+ }
1310
1505
  function Message({
1311
1506
  message,
1312
1507
  config,
@@ -1338,8 +1533,10 @@ function Message({
1338
1533
  }
1339
1534
  };
1340
1535
  const linkColor = isUser ? "#ffffff" : config.primaryColor;
1536
+ const hasContent = message.content.trim().length > 0;
1537
+ const hasAttachments = !!message.attachments?.length;
1341
1538
  return /* @__PURE__ */ jsxs5(AnimatedMessage, { isUser, animate, reduced, children: [
1342
- /* @__PURE__ */ jsx6(
1539
+ hasContent && /* @__PURE__ */ jsx6(
1343
1540
  "div",
1344
1541
  {
1345
1542
  style: bubbleStyle,
@@ -1353,6 +1550,15 @@ function Message({
1353
1550
  ] })
1354
1551
  }
1355
1552
  ),
1553
+ hasAttachments && /* @__PURE__ */ jsx6(
1554
+ AttachmentList,
1555
+ {
1556
+ attachments: message.attachments,
1557
+ isUser,
1558
+ primaryColor: config.primaryColor,
1559
+ t
1560
+ }
1561
+ ),
1356
1562
  isUser && message.status && /* @__PURE__ */ jsx6(MessageStatusPill, { status: message.status, t }),
1357
1563
  !isUser && message.blocks?.map((block, i) => /* @__PURE__ */ jsx6(
1358
1564
  BlockRenderer,
@@ -2167,7 +2373,7 @@ function Thumbnail({
2167
2373
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
2168
2374
  };
2169
2375
  const displayName = filename ?? (isImage ? "" : "Document");
2170
- const sizeLabel = formatBytes(blob.size);
2376
+ const sizeLabel = formatBytes2(blob.size);
2171
2377
  return /* @__PURE__ */ jsxs6("div", { style: wrap, children: [
2172
2378
  isImage ? /* @__PURE__ */ jsx8("img", { src: previewUrl, alt: "", style: img }) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
2173
2379
  /* @__PURE__ */ jsx8(DocIcon, {}),
@@ -2271,7 +2477,7 @@ function DocIcon() {
2271
2477
  }
2272
2478
  );
2273
2479
  }
2274
- function formatBytes(bytes) {
2480
+ function formatBytes2(bytes) {
2275
2481
  if (!Number.isFinite(bytes) || bytes <= 0) return "";
2276
2482
  if (bytes < 1024) return `${bytes} B`;
2277
2483
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
package/dist/index.cjs CHANGED
@@ -1326,6 +1326,201 @@ function StreamingCursor({ reduced }) {
1326
1326
  )
1327
1327
  ] });
1328
1328
  }
1329
+ function formatBytes(n) {
1330
+ if (n == null || n <= 0) return null;
1331
+ if (n < 1024) return `${n} B`;
1332
+ if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
1333
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
1334
+ }
1335
+ function AttachmentList({
1336
+ attachments,
1337
+ isUser,
1338
+ primaryColor,
1339
+ t
1340
+ }) {
1341
+ const containerStyle = {
1342
+ display: "flex",
1343
+ flexDirection: "column",
1344
+ gap: 6,
1345
+ marginTop: 6,
1346
+ alignItems: isUser ? "flex-end" : "flex-start"
1347
+ };
1348
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: containerStyle, children: attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1349
+ AttachmentTile,
1350
+ {
1351
+ attachment: a,
1352
+ isUser,
1353
+ primaryColor,
1354
+ t
1355
+ },
1356
+ a.id
1357
+ )) });
1358
+ }
1359
+ function AttachmentTile({
1360
+ attachment,
1361
+ isUser,
1362
+ primaryColor,
1363
+ t
1364
+ }) {
1365
+ const tileBg = isUser ? `${primaryColor}11` : "#f3f4f6";
1366
+ const tileBorder = isUser ? `${primaryColor}33` : "#e5e7eb";
1367
+ if (attachment.kind === "image" && attachment.url) {
1368
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1369
+ "a",
1370
+ {
1371
+ href: attachment.url,
1372
+ target: "_blank",
1373
+ rel: "noopener noreferrer",
1374
+ style: { display: "inline-block", maxWidth: 220, lineHeight: 0 },
1375
+ "aria-label": attachment.filename ?? t("attachment_image_alt"),
1376
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1377
+ "img",
1378
+ {
1379
+ src: attachment.url,
1380
+ alt: attachment.filename ?? t("attachment_image_alt"),
1381
+ style: {
1382
+ display: "block",
1383
+ maxWidth: 220,
1384
+ maxHeight: 220,
1385
+ width: "auto",
1386
+ height: "auto",
1387
+ borderRadius: 12,
1388
+ border: `1px solid ${tileBorder}`,
1389
+ objectFit: "cover"
1390
+ }
1391
+ }
1392
+ )
1393
+ }
1394
+ );
1395
+ }
1396
+ if (attachment.kind === "location" && attachment.location) {
1397
+ const { latitude, longitude, label } = attachment.location;
1398
+ const href = `https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`;
1399
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1400
+ "a",
1401
+ {
1402
+ href,
1403
+ target: "_blank",
1404
+ rel: "noopener noreferrer",
1405
+ style: tileLinkStyle(tileBg, tileBorder),
1406
+ children: [
1407
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PinIcon, {}),
1408
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1409
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileTitleStyle(), children: label || t("attachment_location") }),
1410
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: tileSubStyle(), children: [
1411
+ latitude.toFixed(4),
1412
+ ", ",
1413
+ longitude.toFixed(4)
1414
+ ] })
1415
+ ] })
1416
+ ]
1417
+ }
1418
+ );
1419
+ }
1420
+ if (!attachment.url) return null;
1421
+ const sub = [attachment.mimeType, formatBytes(attachment.sizeBytes)].filter(Boolean).join(" \xB7 ");
1422
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1423
+ "a",
1424
+ {
1425
+ href: attachment.url,
1426
+ target: "_blank",
1427
+ rel: "noopener noreferrer",
1428
+ title: t("attachment_open"),
1429
+ style: tileLinkStyle(tileBg, tileBorder),
1430
+ children: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileIcon, { kind: attachment.kind }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileTitleStyle(), children: attachment.filename ?? t("attachment_open") }),
1434
+ sub && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileSubStyle(), children: sub })
1435
+ ] })
1436
+ ]
1437
+ }
1438
+ );
1439
+ }
1440
+ function tileLinkStyle(bg, border) {
1441
+ return {
1442
+ display: "flex",
1443
+ alignItems: "center",
1444
+ gap: 10,
1445
+ padding: "8px 12px",
1446
+ background: bg,
1447
+ border: `1px solid ${border}`,
1448
+ borderRadius: 12,
1449
+ textDecoration: "none",
1450
+ color: "inherit",
1451
+ maxWidth: 260
1452
+ };
1453
+ }
1454
+ function tileTitleStyle() {
1455
+ return {
1456
+ fontSize: 13,
1457
+ fontWeight: 500,
1458
+ color: "#1f2937",
1459
+ overflow: "hidden",
1460
+ textOverflow: "ellipsis",
1461
+ whiteSpace: "nowrap",
1462
+ maxWidth: 200
1463
+ };
1464
+ }
1465
+ function tileSubStyle() {
1466
+ return {
1467
+ fontSize: 11,
1468
+ color: "#6b7280",
1469
+ overflow: "hidden",
1470
+ textOverflow: "ellipsis",
1471
+ whiteSpace: "nowrap",
1472
+ maxWidth: 200
1473
+ };
1474
+ }
1475
+ function FileIcon({ kind }) {
1476
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1477
+ "svg",
1478
+ {
1479
+ width: "20",
1480
+ height: "20",
1481
+ viewBox: "0 0 24 24",
1482
+ fill: "none",
1483
+ stroke: "#4b5563",
1484
+ strokeWidth: "2",
1485
+ strokeLinecap: "round",
1486
+ strokeLinejoin: "round",
1487
+ "aria-hidden": "true",
1488
+ style: { flexShrink: 0 },
1489
+ children: kind === "audio" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1490
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M9 18V5l12-2v13" }),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "6", cy: "18", r: "3" }),
1492
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "18", cy: "16", r: "3" })
1493
+ ] }) : kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1494
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" }),
1495
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m22 8-6 4 6 4z" })
1496
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1498
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "14 2 14 8 20 8" })
1499
+ ] })
1500
+ }
1501
+ );
1502
+ }
1503
+ function PinIcon() {
1504
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1505
+ "svg",
1506
+ {
1507
+ width: "20",
1508
+ height: "20",
1509
+ viewBox: "0 0 24 24",
1510
+ fill: "none",
1511
+ stroke: "#4b5563",
1512
+ strokeWidth: "2",
1513
+ strokeLinecap: "round",
1514
+ strokeLinejoin: "round",
1515
+ "aria-hidden": "true",
1516
+ style: { flexShrink: 0 },
1517
+ children: [
1518
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M20 10c0 7-8 13-8 13s-8-6-8-13a8 8 0 0 1 16 0Z" }),
1519
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "10", r: "3" })
1520
+ ]
1521
+ }
1522
+ );
1523
+ }
1329
1524
  function Message({
1330
1525
  message,
1331
1526
  config,
@@ -1357,8 +1552,10 @@ function Message({
1357
1552
  }
1358
1553
  };
1359
1554
  const linkColor = isUser ? "#ffffff" : config.primaryColor;
1555
+ const hasContent = message.content.trim().length > 0;
1556
+ const hasAttachments = !!message.attachments?.length;
1360
1557
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AnimatedMessage, { isUser, animate, reduced, children: [
1361
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1558
+ hasContent && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1362
1559
  "div",
1363
1560
  {
1364
1561
  style: bubbleStyle,
@@ -1372,6 +1569,15 @@ function Message({
1372
1569
  ] })
1373
1570
  }
1374
1571
  ),
1572
+ hasAttachments && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1573
+ AttachmentList,
1574
+ {
1575
+ attachments: message.attachments,
1576
+ isUser,
1577
+ primaryColor: config.primaryColor,
1578
+ t
1579
+ }
1580
+ ),
1375
1581
  isUser && message.status && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageStatusPill, { status: message.status, t }),
1376
1582
  !isUser && message.blocks?.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1377
1583
  BlockRenderer,
@@ -2177,7 +2383,7 @@ function Thumbnail({
2177
2383
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
2178
2384
  };
2179
2385
  const displayName = filename ?? (isImage ? "" : "Document");
2180
- const sizeLabel = formatBytes(blob.size);
2386
+ const sizeLabel = formatBytes2(blob.size);
2181
2387
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: wrap, children: [
2182
2388
  isImage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewUrl, alt: "", style: img }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2183
2389
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocIcon, {}),
@@ -2281,7 +2487,7 @@ function DocIcon() {
2281
2487
  }
2282
2488
  );
2283
2489
  }
2284
- function formatBytes(bytes) {
2490
+ function formatBytes2(bytes) {
2285
2491
  if (!Number.isFinite(bytes) || bytes <= 0) return "";
2286
2492
  if (bytes < 1024) return `${bytes} B`;
2287
2493
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  CustomerHeroProvider,
6
6
  useChat,
7
7
  useCustomerHeroClient
8
- } from "./chunk-BB2DI7WR.js";
8
+ } from "./chunk-M7MROJ5Z.js";
9
9
 
10
10
  // src/components/chat-widget.tsx
11
11
  import { useEffect, useRef } from "react";
package/dist/preview.cjs CHANGED
@@ -1322,6 +1322,201 @@ function StreamingCursor({ reduced }) {
1322
1322
  )
1323
1323
  ] });
1324
1324
  }
1325
+ function formatBytes(n) {
1326
+ if (n == null || n <= 0) return null;
1327
+ if (n < 1024) return `${n} B`;
1328
+ if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
1329
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
1330
+ }
1331
+ function AttachmentList({
1332
+ attachments,
1333
+ isUser,
1334
+ primaryColor,
1335
+ t
1336
+ }) {
1337
+ const containerStyle = {
1338
+ display: "flex",
1339
+ flexDirection: "column",
1340
+ gap: 6,
1341
+ marginTop: 6,
1342
+ alignItems: isUser ? "flex-end" : "flex-start"
1343
+ };
1344
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: containerStyle, children: attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1345
+ AttachmentTile,
1346
+ {
1347
+ attachment: a,
1348
+ isUser,
1349
+ primaryColor,
1350
+ t
1351
+ },
1352
+ a.id
1353
+ )) });
1354
+ }
1355
+ function AttachmentTile({
1356
+ attachment,
1357
+ isUser,
1358
+ primaryColor,
1359
+ t
1360
+ }) {
1361
+ const tileBg = isUser ? `${primaryColor}11` : "#f3f4f6";
1362
+ const tileBorder = isUser ? `${primaryColor}33` : "#e5e7eb";
1363
+ if (attachment.kind === "image" && attachment.url) {
1364
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1365
+ "a",
1366
+ {
1367
+ href: attachment.url,
1368
+ target: "_blank",
1369
+ rel: "noopener noreferrer",
1370
+ style: { display: "inline-block", maxWidth: 220, lineHeight: 0 },
1371
+ "aria-label": attachment.filename ?? t("attachment_image_alt"),
1372
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1373
+ "img",
1374
+ {
1375
+ src: attachment.url,
1376
+ alt: attachment.filename ?? t("attachment_image_alt"),
1377
+ style: {
1378
+ display: "block",
1379
+ maxWidth: 220,
1380
+ maxHeight: 220,
1381
+ width: "auto",
1382
+ height: "auto",
1383
+ borderRadius: 12,
1384
+ border: `1px solid ${tileBorder}`,
1385
+ objectFit: "cover"
1386
+ }
1387
+ }
1388
+ )
1389
+ }
1390
+ );
1391
+ }
1392
+ if (attachment.kind === "location" && attachment.location) {
1393
+ const { latitude, longitude, label } = attachment.location;
1394
+ const href = `https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`;
1395
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1396
+ "a",
1397
+ {
1398
+ href,
1399
+ target: "_blank",
1400
+ rel: "noopener noreferrer",
1401
+ style: tileLinkStyle(tileBg, tileBorder),
1402
+ children: [
1403
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PinIcon, {}),
1404
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1405
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileTitleStyle(), children: label || t("attachment_location") }),
1406
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: tileSubStyle(), children: [
1407
+ latitude.toFixed(4),
1408
+ ", ",
1409
+ longitude.toFixed(4)
1410
+ ] })
1411
+ ] })
1412
+ ]
1413
+ }
1414
+ );
1415
+ }
1416
+ if (!attachment.url) return null;
1417
+ const sub = [attachment.mimeType, formatBytes(attachment.sizeBytes)].filter(Boolean).join(" \xB7 ");
1418
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1419
+ "a",
1420
+ {
1421
+ href: attachment.url,
1422
+ target: "_blank",
1423
+ rel: "noopener noreferrer",
1424
+ title: t("attachment_open"),
1425
+ style: tileLinkStyle(tileBg, tileBorder),
1426
+ children: [
1427
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileIcon, { kind: attachment.kind }),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1429
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileTitleStyle(), children: attachment.filename ?? t("attachment_open") }),
1430
+ sub && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileSubStyle(), children: sub })
1431
+ ] })
1432
+ ]
1433
+ }
1434
+ );
1435
+ }
1436
+ function tileLinkStyle(bg, border) {
1437
+ return {
1438
+ display: "flex",
1439
+ alignItems: "center",
1440
+ gap: 10,
1441
+ padding: "8px 12px",
1442
+ background: bg,
1443
+ border: `1px solid ${border}`,
1444
+ borderRadius: 12,
1445
+ textDecoration: "none",
1446
+ color: "inherit",
1447
+ maxWidth: 260
1448
+ };
1449
+ }
1450
+ function tileTitleStyle() {
1451
+ return {
1452
+ fontSize: 13,
1453
+ fontWeight: 500,
1454
+ color: "#1f2937",
1455
+ overflow: "hidden",
1456
+ textOverflow: "ellipsis",
1457
+ whiteSpace: "nowrap",
1458
+ maxWidth: 200
1459
+ };
1460
+ }
1461
+ function tileSubStyle() {
1462
+ return {
1463
+ fontSize: 11,
1464
+ color: "#6b7280",
1465
+ overflow: "hidden",
1466
+ textOverflow: "ellipsis",
1467
+ whiteSpace: "nowrap",
1468
+ maxWidth: 200
1469
+ };
1470
+ }
1471
+ function FileIcon({ kind }) {
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1473
+ "svg",
1474
+ {
1475
+ width: "20",
1476
+ height: "20",
1477
+ viewBox: "0 0 24 24",
1478
+ fill: "none",
1479
+ stroke: "#4b5563",
1480
+ strokeWidth: "2",
1481
+ strokeLinecap: "round",
1482
+ strokeLinejoin: "round",
1483
+ "aria-hidden": "true",
1484
+ style: { flexShrink: 0 },
1485
+ children: kind === "audio" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1486
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M9 18V5l12-2v13" }),
1487
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "6", cy: "18", r: "3" }),
1488
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "18", cy: "16", r: "3" })
1489
+ ] }) : kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1490
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" }),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m22 8-6 4 6 4z" })
1492
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1493
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1494
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "14 2 14 8 20 8" })
1495
+ ] })
1496
+ }
1497
+ );
1498
+ }
1499
+ function PinIcon() {
1500
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1501
+ "svg",
1502
+ {
1503
+ width: "20",
1504
+ height: "20",
1505
+ viewBox: "0 0 24 24",
1506
+ fill: "none",
1507
+ stroke: "#4b5563",
1508
+ strokeWidth: "2",
1509
+ strokeLinecap: "round",
1510
+ strokeLinejoin: "round",
1511
+ "aria-hidden": "true",
1512
+ style: { flexShrink: 0 },
1513
+ children: [
1514
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M20 10c0 7-8 13-8 13s-8-6-8-13a8 8 0 0 1 16 0Z" }),
1515
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "10", r: "3" })
1516
+ ]
1517
+ }
1518
+ );
1519
+ }
1325
1520
  function Message({
1326
1521
  message,
1327
1522
  config,
@@ -1353,8 +1548,10 @@ function Message({
1353
1548
  }
1354
1549
  };
1355
1550
  const linkColor = isUser ? "#ffffff" : config.primaryColor;
1551
+ const hasContent = message.content.trim().length > 0;
1552
+ const hasAttachments = !!message.attachments?.length;
1356
1553
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AnimatedMessage, { isUser, animate, reduced, children: [
1357
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1554
+ hasContent && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1358
1555
  "div",
1359
1556
  {
1360
1557
  style: bubbleStyle,
@@ -1368,6 +1565,15 @@ function Message({
1368
1565
  ] })
1369
1566
  }
1370
1567
  ),
1568
+ hasAttachments && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1569
+ AttachmentList,
1570
+ {
1571
+ attachments: message.attachments,
1572
+ isUser,
1573
+ primaryColor: config.primaryColor,
1574
+ t
1575
+ }
1576
+ ),
1371
1577
  isUser && message.status && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageStatusPill, { status: message.status, t }),
1372
1578
  !isUser && message.blocks?.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1373
1579
  BlockRenderer,
@@ -2173,7 +2379,7 @@ function Thumbnail({
2173
2379
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
2174
2380
  };
2175
2381
  const displayName = filename ?? (isImage ? "" : "Document");
2176
- const sizeLabel = formatBytes(blob.size);
2382
+ const sizeLabel = formatBytes2(blob.size);
2177
2383
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: wrap, children: [
2178
2384
  isImage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewUrl, alt: "", style: img }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2179
2385
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocIcon, {}),
@@ -2277,7 +2483,7 @@ function DocIcon() {
2277
2483
  }
2278
2484
  );
2279
2485
  }
2280
- function formatBytes(bytes) {
2486
+ function formatBytes2(bytes) {
2281
2487
  if (!Number.isFinite(bytes) || bytes <= 0) return "";
2282
2488
  if (bytes < 1024) return `${bytes} B`;
2283
2489
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
package/dist/preview.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  ChatWindow,
4
4
  CustomerHeroProvider,
5
5
  useCustomerHeroClient
6
- } from "./chunk-BB2DI7WR.js";
6
+ } from "./chunk-M7MROJ5Z.js";
7
7
 
8
8
  // src/preview.tsx
9
9
  import { useEffect, useMemo } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customerhero/react",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "private": false,
5
5
  "description": "React components for embedding the CustomerHero chat widget.",
6
6
  "keywords": [
@@ -68,7 +68,7 @@
68
68
  "react": ">=18"
69
69
  },
70
70
  "devDependencies": {
71
- "@customerhero/js": "^2.3.0",
71
+ "@customerhero/js": "^2.4.1",
72
72
  "@testing-library/react": "^16.1.0",
73
73
  "@types/react": "^19.0.0",
74
74
  "@types/react-dom": "^19.0.0",