@adatechnology/conversations-ui 0.12.0 → 0.14.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.
Files changed (37) hide show
  1. package/dist/chunk-RLUNDWGA.js +51 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +5 -43
  4. package/dist/participant/index.d.ts +87 -2
  5. package/dist/participant/index.js +1073 -398
  6. package/dist/styles.css +218 -0
  7. package/package.json +1 -1
  8. package/src/conversationTranscript.ts +6 -2
  9. package/src/participant/ConversationThread.tsx +8 -1
  10. package/src/participant/ParticipantAuthorAvatar.tsx +18 -0
  11. package/src/participant/ParticipantBubbleBody.tsx +33 -3
  12. package/src/participant/ParticipantConversationMenu.tsx +89 -0
  13. package/src/participant/ParticipantConversations.tsx +6 -1
  14. package/src/participant/ParticipantMenuBubble.tsx +106 -0
  15. package/src/participant/ParticipantMessageBubble.test.tsx +51 -0
  16. package/src/participant/ParticipantMessageBubble.tsx +39 -7
  17. package/src/participant/ParticipantMessageInfo.tsx +70 -0
  18. package/src/participant/ParticipantMessageMenu.tsx +168 -0
  19. package/src/participant/ParticipantMessageMenuContext.tsx +109 -0
  20. package/src/participant/ParticipantSelectionBar.tsx +64 -0
  21. package/src/participant/ParticipantThread.tsx +30 -3
  22. package/src/participant/ParticipantThreadScreen.tsx +2 -0
  23. package/src/participant/ParticipantTimeline.tsx +7 -1
  24. package/src/participant/index.ts +11 -1
  25. package/src/participant/participantConversationMenuDom.test.tsx +166 -0
  26. package/src/participant/participantLabels.ts +40 -0
  27. package/src/participant/participantMessageInfoModel.test.ts +68 -0
  28. package/src/participant/participantMessageInfoModel.ts +80 -0
  29. package/src/participant/participantMessageMenuDom.test.tsx +111 -0
  30. package/src/participant/participantMessageMenuModel.test.ts +43 -0
  31. package/src/participant/participantMessageMenuModel.ts +91 -0
  32. package/src/participant/participantMessageMoment.ts +7 -0
  33. package/src/participant/participantTranscriptionText.test.ts +48 -0
  34. package/src/participant/participantTranscriptionText.ts +28 -0
  35. package/src/participant/useOpenUpwards.ts +22 -0
  36. package/src/participant/useParticipantFailedMenu.ts +3 -0
  37. package/src/styles.css +41 -0
@@ -0,0 +1,28 @@
1
+ import type { ParticipantAttachment } from '@adatechnology/conversation-contracts'
2
+
3
+ export type ReadyTranscription = { readonly filename: string; readonly text: string }
4
+
5
+ /** The audios of a message whose transcription is ready and says something, in attachment order. */
6
+ export function readyTranscriptions(attachments: readonly ParticipantAttachment[]): readonly ReadyTranscription[] {
7
+ return attachments.flatMap((attachment) => {
8
+ const transcription = attachment.kind === 'audio' ? attachment.transcription : undefined
9
+ const text = transcription?.status === 'ready' ? transcription.text.trim() : ''
10
+ return text === '' ? [] : [{ filename: attachment.filename, text }]
11
+ })
12
+ }
13
+
14
+ /** One attachment in a copy or an export: `[file]`, followed by what was said when the audio was transcribed. */
15
+ export function attachmentForCopy(attachment: ParticipantAttachment, transcriptionLabel: string): string {
16
+ const [ready] = readyTranscriptions([attachment])
17
+ return ready ? `[${attachment.filename}] ${transcriptionLabel}: ${ready.text}` : `[${attachment.filename}]`
18
+ }
19
+
20
+ /** The file name of one audio's transcription: the audio's name without its extension, then the label. */
21
+ export function transcriptionFileName(audioFilename: string, transcriptionLabel: string): string {
22
+ const base = audioFilename.replace(/\.[^.]+$/, '')
23
+ const safe = `${base} ${transcriptionLabel}`
24
+ .replace(/[\\/:*?"<>|]+/g, ' ')
25
+ .replace(/\s+/g, ' ')
26
+ .trim()
27
+ return `${safe}.txt`
28
+ }
@@ -0,0 +1,22 @@
1
+ import { useLayoutEffect, useState, type RefObject } from 'react'
2
+
3
+ const SCROLLER_SELECTOR = '.cv-p-thread__scroll'
4
+
5
+ /**
6
+ * A panel under a bubble opens downwards; when that would cross the bottom of the thread's scrolling area (the
7
+ * last messages), it opens upwards instead, so it is never cut off.
8
+ */
9
+ export function useOpenUpwards(isOpen: boolean, panelRef: RefObject<HTMLElement | null>): boolean {
10
+ const [isUpwards, setUpwards] = useState(false)
11
+ useLayoutEffect(() => {
12
+ const panel = panelRef.current
13
+ if (!isOpen || !panel) {
14
+ setUpwards(false)
15
+ return
16
+ }
17
+ const scroller = panel.closest(SCROLLER_SELECTOR)
18
+ if (!scroller) return
19
+ setUpwards(panel.getBoundingClientRect().bottom > scroller.getBoundingClientRect().bottom)
20
+ }, [isOpen, panelRef])
21
+ return isUpwards
22
+ }
@@ -21,6 +21,8 @@ export type ParticipantFailedMenuController = {
21
21
  readonly handleMenuKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void
22
22
  readonly handleBlur: (event: FocusEvent<HTMLSpanElement>) => void
23
23
  readonly close: () => void
24
+ /** Opens with the first item focused, as a right-click or a long press does. */
25
+ readonly open: () => void
24
26
  }
25
27
 
26
28
  export function useParticipantFailedMenu(): ParticipantFailedMenuController {
@@ -56,5 +58,6 @@ export function useParticipantFailedMenu(): ParticipantFailedMenuController {
56
58
  handleMenuKeyDown: (event) => handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }),
57
59
  handleBlur,
58
60
  close: () => dispatch({ type: 'close' }),
61
+ open: () => dispatch({ type: 'open', initialFocus: 'first' }),
59
62
  }
60
63
  }
package/src/styles.css CHANGED
@@ -1302,6 +1302,7 @@
1302
1302
  .cv-p-bubble__author { display: block; flex: 1 0 100%; margin-bottom: 0.15rem; font: 0.65rem 'SFMono-Regular', Consolas, monospace; text-transform: uppercase; color: var(--cv-p-i-accent); }
1303
1303
  .cv-p-bubble > .cv-p-text { flex: 0 1 auto; min-width: 0; margin-inline-end: auto; }
1304
1304
  .cv-p-bubble > .cv-p-attachment { flex: 1 0 100%; }
1305
+ .cv-p-bubble__host-actions { display: inline-flex; align-items: center; gap: 0.25rem; }
1305
1306
  .cv-p-bubble__original { flex: 1 0 100%; margin-top: 0.2rem; }
1306
1307
  .cv-p-bubble__original-toggle { display: inline-block; font-size: 0.7rem; color: var(--cv-p-i-text-muted); cursor: pointer; list-style: none; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 2px; }
1307
1308
  .cv-p-bubble__original-toggle::-webkit-details-marker { display: none; }
@@ -1327,6 +1328,45 @@
1327
1328
  .cv-p-failed-menu__list--below { bottom: auto; top: calc(100% + 0.25rem); }
1328
1329
  .cv-p-failed-menu__item--danger { color: var(--cv-p-i-danger); }
1329
1330
  .cv-p-failed-menu__item:hover, .cv-p-failed-menu__item:focus-visible { background: var(--cv-p-i-highlight); }
1331
+ /* Menu da mensagem (opt-in `messageMenu`): seta discreta no canto do balão, à vista só com o ponteiro em cima, com
1332
+ foco ou com o menu aberto; no toque abre por toque longo e a seta não ocupa o balão. */
1333
+ .cv-p-message-menu { position: absolute; top: 0.2rem; right: 0.2rem; display: inline-flex; }
1334
+ .cv-p-message-menu__trigger { display: inline-grid; place-items: center; width: 1.5rem; height: 1.25rem; padding: 0; border: 0; border-radius: var(--cv-p-i-radius); color: var(--cv-p-i-text-muted); background: var(--cv-p-tail-fill, var(--cv-p-i-surface-raised)); box-shadow: -0.5rem 0 0.5rem var(--cv-p-tail-fill, var(--cv-p-i-surface-raised)); cursor: pointer; opacity: 0; }
1335
+ .cv-p-bubble:hover .cv-p-message-menu__trigger, .cv-p-message-menu__trigger:focus-visible, .cv-p-message-menu[data-open] .cv-p-message-menu__trigger { opacity: 1; }
1336
+ .cv-p-message-menu__trigger:hover { color: var(--cv-p-i-text); }
1337
+ @media (hover: none) { .cv-p-message-menu__trigger:not(:focus-visible) { pointer-events: none; } }
1338
+ .cv-p-message-menu__list { min-width: 11rem; }
1339
+ .cv-p-message-menu__list { bottom: auto; top: calc(100% + 0.25rem); }
1340
+ .cv-p-message-menu__list.cv-p-message-menu__list--up { top: auto; bottom: calc(100% + 0.25rem); }
1341
+ .cv-p-message-menu__item { display: flex; align-items: center; font: inherit; font-size: 0.85rem; text-transform: none; letter-spacing: normal; text-decoration: none; white-space: nowrap; }
1342
+ .cv-p-message-menu__item:disabled, .cv-p-message-menu__item[aria-disabled='true'] { color: var(--cv-p-i-text-muted); cursor: default; }
1343
+ .cv-p-bubble--menu-open { z-index: 3; }
1344
+ .cv-p-bubble:has(.cv-p-message-menu[data-open]) { z-index: 3; }
1345
+ .cv-p-conversation-menu { position: relative; display: inline-flex; }
1346
+ .cv-p-conversation-menu__trigger { display: inline-flex; align-items: center; justify-content: center; min-width: var(--cv-p-i-touch); min-height: var(--cv-p-i-touch); padding: 0; border: 0; border-radius: var(--cv-p-i-radius); color: var(--cv-p-i-text-muted); background: transparent; cursor: pointer; }
1347
+ .cv-p-conversation-menu__trigger:hover, .cv-p-conversation-menu__trigger:focus-visible, .cv-p-conversation-menu__trigger[aria-expanded='true'] { color: var(--cv-p-i-text); background: var(--cv-p-i-highlight); }
1348
+ .cv-p-conversation-menu__list { z-index: 4; }
1349
+ .cv-p-message-info { min-width: 14rem; padding: 0.5rem 0.75rem 0.65rem; color: var(--cv-p-i-text); font-size: 0.85rem; cursor: default; }
1350
+ .cv-p-message-info__head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.35rem; }
1351
+ .cv-p-message-info__title { font-weight: 600; }
1352
+ .cv-p-message-info__close { display: inline-flex; align-items: center; justify-content: center; padding: 0.2rem; border: 0; border-radius: var(--cv-p-i-radius); color: var(--cv-p-i-text-muted); background: transparent; cursor: pointer; }
1353
+ .cv-p-message-info__close:hover, .cv-p-message-info__close:focus-visible { color: var(--cv-p-i-text); background: var(--cv-p-i-highlight); }
1354
+ .cv-p-message-info__rows { display: grid; gap: 0.25rem; margin: 0; }
1355
+ .cv-p-message-info__row { display: flex; justify-content: space-between; gap: 1rem; }
1356
+ .cv-p-message-info__row dt { color: var(--cv-p-i-text-muted); }
1357
+ .cv-p-message-info__row dd { margin: 0; text-align: end; font-variant-numeric: tabular-nums; }
1358
+ /* Modo de seleção: caixa à esquerda de cada mensagem; a linha inteira marca e desmarca. */
1359
+ .cv-p-select-row { display: flex; align-items: center; gap: 0.6rem; width: 100%; cursor: pointer; border-radius: var(--cv-p-i-radius); }
1360
+ .cv-p-select-row[data-selected] { background: var(--cv-p-i-highlight); }
1361
+ .cv-p-select-row > .cv-p-bubble--mine { margin-inline-start: auto; }
1362
+ .cv-p-select-row > .cv-p-bubble-row { max-width: 92%; }
1363
+ .cv-p-select-row__check { flex: 0 0 auto; width: 1.1rem; height: 1.1rem; margin: 0 0 0 0.25rem; accent-color: var(--cv-p-i-accent); cursor: pointer; }
1364
+ .cv-p-selection-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; padding: 0.6rem 0.75rem; border-top: 1px solid var(--cv-p-i-border); background: var(--cv-p-i-surface-raised); }
1365
+ .cv-p-selection-bar__count { margin-inline-end: auto; font-weight: 600; color: var(--cv-p-i-text); }
1366
+ .cv-p-selection-bar__close, .cv-p-selection-bar__action { min-height: var(--cv-p-i-touch); padding: 0 0.75rem; border: 1px solid var(--cv-p-i-border); border-radius: var(--cv-p-i-radius); color: var(--cv-p-i-text); background: transparent; font: inherit; font-size: 0.85rem; cursor: pointer; }
1367
+ .cv-p-selection-bar__close { display: inline-grid; place-items: center; min-width: var(--cv-p-i-touch); padding: 0; border-color: transparent; }
1368
+ .cv-p-selection-bar__action:disabled { color: var(--cv-p-i-text-muted); cursor: default; }
1369
+ .cv-p-selection-bar__close:hover, .cv-p-selection-bar__action:hover:not(:disabled), .cv-p-selection-bar__close:focus-visible, .cv-p-selection-bar__action:focus-visible { background: var(--cv-p-i-highlight); }
1330
1370
  .cv-p-attachment { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.35rem; color: inherit; }
1331
1371
  .cv-p-attachment--document { flex-direction: row; justify-content: space-between; min-height: var(--cv-p-i-touch); align-items: center; text-decoration: underline; }
1332
1372
  .cv-p-attachment__image { display: block; max-width: 100%; max-height: 16rem; object-fit: contain; }
@@ -1389,6 +1429,7 @@
1389
1429
  .cv-p-thread__latest-button { animation: cv-p-latest-in 0.16s ease-out; }
1390
1430
  .cv-p-protocol__copy, .cv-p-search__input { transition: border-color 0.12s, background-color 0.12s; }
1391
1431
  .cv-p-row, .cv-p-button, .cv-p-chip, .cv-p-thread__back, .cv-p-composer__attach { transition: background-color 0.12s, border-color 0.12s; }
1432
+ .cv-p-message-menu__trigger { transition: opacity 0.12s; }
1392
1433
  }
1393
1434
 
1394
1435
  /* Scrolls. Verticais: barra fina no tema. Faixas horizontais: sem barra, com esmaecimento nas bordas. O