Files
ChatApp/apps/desktop/src/components/Modal.tsx
T
byGalax 4db65993d5
Release desktop app / build (, windows-latest) (push) Has been cancelled
Release desktop app / build (--target universal-apple-darwin --bundles app,updater, macos-14) (push) Has been cancelled
feat: redesign + avatars + theme + call presence fixes (v0.6.0)
Visual:
- Light/dark theme via ThemeContext + CSS vars
- New design tokens (surface/fg/accent/line/etc.) across all pages
- Reusable Avatar component (img + letter fallback) wired into UserBar,
  ConversationHeader, ChatsPage, FriendsPage, GroupInfoPanel, IncomingCallPanel

Calls:
- Split call UI: IncomingCallPanel, InCallPanel, CallControls,
  CallParticipantTile, ScreenShareViewer
- Active speaker hook (useActiveSpeakers)
- Fix: ActiveCallBanner stayed hidden after hangup while peers in room.
  - useCallPresence: bind presence callbacks only when we own subscribe
    (Supabase forbids .on() after .subscribe() on shared dedup'd channels)
  - useCallPresence: never removeChannel — channel is shared with CallContext
    so tearing it down on ConversationHeader unmount killed live tracking
  - ActiveCallBanner: lastCallConversationId fallback so banner shows
    instantly after hangup, auto-dismiss when room confirmed empty
- Drop unused useAnyActiveCall

Bump tauri version 0.5.0 -> 0.6.0
2026-04-20 00:26:19 +02:00

63 lines
1.9 KiB
TypeScript

import { useEffect } from 'react';
import { XIcon } from './icons';
interface Props {
open: boolean;
title: string;
onClose: () => void;
children: React.ReactNode;
size?: 'md' | 'lg';
}
export function Modal({ open, title, onClose, children, size = 'md' }: Props) {
useEffect(() => {
if (!open) return;
function onKey(e: KeyboardEvent) {
if (e.key === 'Escape') onClose();
}
document.addEventListener('keydown', onKey);
const prevOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKey);
document.body.style.overflow = prevOverflow;
};
}, [open, onClose]);
if (!open) return null;
const width = size === 'lg' ? 'max-w-xl' : 'max-w-md';
return (
<div
role="dialog"
aria-modal="true"
aria-label={title}
onClick={onClose}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-6 backdrop-blur-sm animate-fade-in"
>
<div
onClick={(e) => e.stopPropagation()}
className={
'relative w-full animate-slide-up rounded-2xl border border-line bg-surface-3 shadow-xl ' +
width
}
>
<header className="flex items-center justify-between border-b border-line px-6 py-4">
<h2 className="font-display text-lg font-semibold text-fg">{title}</h2>
<button
type="button"
onClick={onClose}
aria-label="Close"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-fg-muted transition hover:bg-surface-2 hover:text-fg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
<XIcon className="h-4 w-4" />
</button>
</header>
<div className="max-h-[75vh] overflow-y-auto p-6">{children}</div>
</div>
</div>
);
}