From 3c5c6964e27b682d735fbd597ccce0da368ba190 Mon Sep 17 00:00:00 2001 From: maaz519 Date: Thu, 23 Jul 2026 02:52:18 +0530 Subject: [PATCH 1/2] fix(messaging-ui): mail cards no longer clip + emoji picker portals above overflow (0.1.3) - Mail reader: .miu-mail-msg gets flex:0 0 auto so cards keep their natural height in the scrolling column instead of being compressed + clipped by overflow:hidden. - Reaction/emoji picker renders through a PopoverPortal to (positioned + themed) so it floats above the message list instead of being cut by the scroll container's overflow. 72 tests green. Co-Authored-By: Claude Opus 4.8 --- packages/iios-messaging-ui/package.json | 2 +- .../src/components/message-item.tsx | 38 ++++++----- .../src/components/modal-portal.tsx | 2 +- .../src/components/popover-portal.tsx | 67 +++++++++++++++++++ packages/iios-messaging-ui/src/styles.css | 18 ++--- 5 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 packages/iios-messaging-ui/src/components/popover-portal.tsx diff --git a/packages/iios-messaging-ui/package.json b/packages/iios-messaging-ui/package.json index a36ef95..2017e6a 100644 --- a/packages/iios-messaging-ui/package.json +++ b/packages/iios-messaging-ui/package.json @@ -1,6 +1,6 @@ { "name": "@insignia/iios-messaging-ui", - "version": "0.1.2", + "version": "0.1.3", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/iios-messaging-ui/src/components/message-item.tsx b/packages/iios-messaging-ui/src/components/message-item.tsx index 34c0f9f..7cb7121 100644 --- a/packages/iios-messaging-ui/src/components/message-item.tsx +++ b/packages/iios-messaging-ui/src/components/message-item.tsx @@ -1,5 +1,6 @@ -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { highlightMentions } from '../mentions'; +import { PopoverPortal } from './popover-portal'; import type { Attachment } from '../types'; import type { UiMessage } from '../hooks/use-messages'; @@ -43,6 +44,7 @@ export function MessageItem({ onOpenThread?: (messageId: string) => void; }) { const [pickerOpen, setPickerOpen] = useState(false); + const reactBtnRef = useRef(null); const m = message; return ( @@ -55,25 +57,27 @@ export function MessageItem({
{canReact ? (
- {pickerOpen ? ( -
- {REACTION_EMOJIS.map((e) => ( - - ))} -
+ setPickerOpen(false)}> +
+ {REACTION_EMOJIS.map((e) => ( + + ))} +
+
) : null}
) : null} diff --git a/packages/iios-messaging-ui/src/components/modal-portal.tsx b/packages/iios-messaging-ui/src/components/modal-portal.tsx index 9e79dbb..f17b735 100644 --- a/packages/iios-messaging-ui/src/components/modal-portal.tsx +++ b/packages/iios-messaging-ui/src/components/modal-portal.tsx @@ -8,7 +8,7 @@ const THEME_VARS = [ '--miu-text', '--miu-muted', '--miu-accent', '--miu-accent-text', '--miu-radius', ] as const; -function copyThemeVars(): Record { +export function copyThemeVars(): Record { if (typeof document === 'undefined') return {}; const src = document.querySelector('.miu-messenger, .miu-inbox'); if (!src) return {}; diff --git a/packages/iios-messaging-ui/src/components/popover-portal.tsx b/packages/iios-messaging-ui/src/components/popover-portal.tsx new file mode 100644 index 0000000..e4a182a --- /dev/null +++ b/packages/iios-messaging-ui/src/components/popover-portal.tsx @@ -0,0 +1,67 @@ +import { useEffect, useLayoutEffect, useState, type CSSProperties, type ReactNode, type RefObject } from 'react'; +import { createPortal } from 'react-dom'; +import { copyThemeVars } from './modal-portal'; + +/** + * A small popover (e.g. the reaction picker) rendered into document.body so it is never clipped by + * a scroll container's `overflow: hidden` — the reported "emoji picker goes beneath the container" + * bug. Positioned fixed just below the anchor, right-aligned to it, and re-placed on scroll/resize. + * Closes on outside pointer-down, scroll of a different element, or Escape. Carries the SDK theme + * tokens (copied from the live surface) since a body-portaled node is outside the themed subtree. + */ +export function PopoverPortal({ + anchorRef, + onClose, + children, +}: { + anchorRef: RefObject; + onClose: () => void; + children: ReactNode; +}) { + const [pos, setPos] = useState<{ top: number; left: number } | null>(null); + const [vars] = useState(copyThemeVars); + + useLayoutEffect(() => { + const el = anchorRef.current; + if (!el) return; + const place = (): void => { + const r = el.getBoundingClientRect(); + setPos({ top: r.bottom + 4, left: r.right }); + }; + place(); + window.addEventListener('resize', place); + window.addEventListener('scroll', place, true); + return () => { + window.removeEventListener('resize', place); + window.removeEventListener('scroll', place, true); + }; + }, [anchorRef]); + + useEffect(() => { + const onDown = (e: PointerEvent): void => { + const target = e.target as Node; + if (anchorRef.current?.contains(target)) return; + if ((target as Element).closest?.('.miu-popover')) return; + onClose(); + }; + const onKey = (e: KeyboardEvent): void => { + if (e.key === 'Escape') onClose(); + }; + document.addEventListener('pointerdown', onDown, true); + document.addEventListener('keydown', onKey); + return () => { + document.removeEventListener('pointerdown', onDown, true); + document.removeEventListener('keydown', onKey); + }; + }, [anchorRef, onClose]); + + if (typeof document === 'undefined' || !pos) return null; + return createPortal( +
+
+ {children} +
+
, + document.body, + ); +} diff --git a/packages/iios-messaging-ui/src/styles.css b/packages/iios-messaging-ui/src/styles.css index 34e0776..207b3e2 100644 --- a/packages/iios-messaging-ui/src/styles.css +++ b/packages/iios-messaging-ui/src/styles.css @@ -196,13 +196,13 @@ .miu-react-btn:hover { opacity: 1; } +/* Portaled to via .miu-popover so it's never clipped by a scroll container. */ +.miu-popover { + position: fixed; + z-index: 2147483000; + transform: translateX(-100%); /* right-align the picker's right edge to the anchor */ +} .miu-react-picker { - position: absolute; - /* Open downward + right-aligned so it never clips against the top of the scroll area - (the reported bug) or spills past the right edge. */ - top: 100%; - right: 0; - margin-top: 4px; display: flex; gap: 2px; padding: 4px; @@ -210,14 +210,9 @@ border: 1px solid var(--miu-border); background: var(--miu-panel); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); - z-index: 5; width: max-content; } /* On my own (right-aligned) messages, anchor the picker to the left instead so it stays in view. */ -.miu-msg.is-mine .miu-react-picker { - right: auto; - left: 0; -} .miu-react-emoji { border: none; background: none; @@ -836,6 +831,7 @@ button.miu-attach-dl:hover { gap: 12px; } .miu-mail-msg { + flex: 0 0 auto; /* don't let the flex column shrink cards — they'd clip their own content */ border: 1px solid var(--miu-border); border-radius: var(--miu-radius); background: var(--miu-panel); From 973b6a77eb1858ad380e58f02e56b0ac728699b4 Mon Sep 17 00:00:00 2001 From: maaz519 Date: Thu, 23 Jul 2026 03:06:06 +0530 Subject: [PATCH 2/2] feat(messaging-ui): Attachment carries contentRef + sizeBytes for messenger media (0.1.4) Extend the messaging Attachment type with optional storage fields so an adapter's upload() can return a sendable reference (the socket needs contentRef/mimeType/ sizeBytes to persist the message part) while keeping the display url. Enables messenger attachments end-to-end in host adapters. Co-Authored-By: Claude Opus 4.8 --- packages/iios-messaging-ui/package.json | 2 +- packages/iios-messaging-ui/src/types.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/iios-messaging-ui/package.json b/packages/iios-messaging-ui/package.json index 2017e6a..b582f56 100644 --- a/packages/iios-messaging-ui/package.json +++ b/packages/iios-messaging-ui/package.json @@ -1,6 +1,6 @@ { "name": "@insignia/iios-messaging-ui", - "version": "0.1.3", + "version": "0.1.4", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/iios-messaging-ui/src/types.ts b/packages/iios-messaging-ui/src/types.ts index 0c1d918..27724a2 100644 --- a/packages/iios-messaging-ui/src/types.ts +++ b/packages/iios-messaging-ui/src/types.ts @@ -48,9 +48,13 @@ export interface Reaction { } export interface Attachment { + /** A resolved URL for display/download. May be empty until resolved by the host. */ url: string; mime: string; name: string; + /** Storage reference — carried so send() can persist the message part (upload returns it). */ + contentRef?: string; + sizeBytes?: number; } export interface Message {