diff --git a/packages/iios-messaging-ui/insignia-iios-messaging-ui-0.1.0.tgz b/packages/iios-messaging-ui/insignia-iios-messaging-ui-0.1.0.tgz index 9d33aca..c7622b0 100644 Binary files a/packages/iios-messaging-ui/insignia-iios-messaging-ui-0.1.0.tgz and b/packages/iios-messaging-ui/insignia-iios-messaging-ui-0.1.0.tgz differ diff --git a/packages/iios-messaging-ui/package.json b/packages/iios-messaging-ui/package.json index 802d21d..6a9d110 100644 --- a/packages/iios-messaging-ui/package.json +++ b/packages/iios-messaging-ui/package.json @@ -37,8 +37,9 @@ "test": "vitest run" }, "peerDependencies": { + "@insignia/iios-kernel-client": "*", "react": ">=18", - "@insignia/iios-kernel-client": "*" + "react-dom": ">=18" }, "peerDependenciesMeta": { "@insignia/iios-kernel-client": { @@ -50,6 +51,7 @@ "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.1.0", "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "jsdom": "^26.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/packages/iios-messaging-ui/src/components/modal-portal.tsx b/packages/iios-messaging-ui/src/components/modal-portal.tsx new file mode 100644 index 0000000..9e79dbb --- /dev/null +++ b/packages/iios-messaging-ui/src/components/modal-portal.tsx @@ -0,0 +1,35 @@ +import { useState, type ReactNode, type CSSProperties } from 'react'; +import { createPortal } from 'react-dom'; + +// The SDK theme tokens. A body-portaled node is outside the `.miu-messenger`/`.miu-inbox` +// subtree that defines these, so we copy their resolved values onto the portal root. +const THEME_VARS = [ + '--miu-bg', '--miu-panel', '--miu-panel-2', '--miu-border', + '--miu-text', '--miu-muted', '--miu-accent', '--miu-accent-text', '--miu-radius', +] as const; + +function copyThemeVars(): Record { + if (typeof document === 'undefined') return {}; + const src = document.querySelector('.miu-messenger, .miu-inbox'); + if (!src) return {}; + const cs = getComputedStyle(src); + const out: Record = {}; + for (const v of THEME_VARS) { + const val = cs.getPropertyValue(v).trim(); + if (val) out[v] = val; + } + return out; +} + +/** + * Render an overlay into document.body so it escapes the host's stacking context and overflow. + * A host wrapper like `.view { position: relative; z-index: 1 }` traps a `position: fixed` overlay + * BELOW a sibling sticky header no matter how high its z-index — the only robust fix is to leave + * that stacking context entirely. Theme tokens are copied from the live surface (captured once on + * mount, synchronously, so there is no unstyled first paint) and re-applied on the portal root. + */ +export function ModalPortal({ children }: { children: ReactNode }) { + const [vars] = useState>(copyThemeVars); + if (typeof document === 'undefined') return null; + return createPortal(
{children}
, document.body); +} diff --git a/packages/iios-messaging-ui/src/inbox/inbox.tsx b/packages/iios-messaging-ui/src/inbox/inbox.tsx index 213e75f..4cf235a 100644 --- a/packages/iios-messaging-ui/src/inbox/inbox.tsx +++ b/packages/iios-messaging-ui/src/inbox/inbox.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState, type FormEvent } from 'react'; +import { ModalPortal } from '../components/modal-portal'; import { useCompose, useInbox, useMailThread } from './hooks'; import type { InboxItem, InboxState, MailAttachment, MailPerson } from './types'; @@ -254,7 +255,8 @@ function ComposeModal({ onClose, onSent }: { onClose: () => void; onSent: () => } return ( -
+ +
e.stopPropagation()}>
New message @@ -320,6 +322,7 @@ function ComposeModal({ onClose, onSent }: { onClose: () => void; onSent: () =>
-
+
+ ); } diff --git a/packages/iios-messaging-ui/src/styles.css b/packages/iios-messaging-ui/src/styles.css index 333cad7..0172ed0 100644 --- a/packages/iios-messaging-ui/src/styles.css +++ b/packages/iios-messaging-ui/src/styles.css @@ -802,11 +802,23 @@ word-break: break-word; } -/* compose modal */ +/* compose modal — portaled to , so carry SDK theme-token defaults here (the host's + own token mapping + a synchronous copy on the portal root override these). */ +.miu-portal { + --miu-bg: #0e0e13; + --miu-panel: #16161d; + --miu-panel-2: #1d1d26; + --miu-border: #262631; + --miu-text: #e9e9ef; + --miu-muted: #9a9aa7; + --miu-accent: #fda913; + --miu-accent-text: #1a1206; + --miu-radius: 12px; +} .miu-modal-overlay { position: fixed; inset: 0; - z-index: 100; + z-index: 2147483000; background: rgba(2, 2, 6, 0.6); display: grid; place-items: center; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index faeced5..4aac1a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -340,6 +340,9 @@ importers: '@types/react': specifier: ^19.0.0 version: 19.2.17 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.2.3(@types/react@19.2.17) jsdom: specifier: ^26.0.0 version: 26.1.0