// Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { Key, ReactNode } from 'react'; import React, { useEffect, useState } from 'react'; import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'react-aria-components'; import classNames from 'classnames'; import { usePopper } from 'react-popper'; import { createPortal } from 'react-dom'; import { Avatar, AvatarSize } from './Avatar'; import type { LocalizerType, ThemeType } from '../types/Util'; import type { ConversationType } from '../state/ducks/conversations'; import type { BadgeType } from '../badges/types'; import { AvatarPopup } from './AvatarPopup'; import { handleOutsideClick } from '../util/handleOutsideClick'; import type { UnreadStats } from '../state/selectors/conversations'; import { NavTab } from '../state/ducks/nav'; type NavTabProps = Readonly<{ badge?: ReactNode; iconClassName: string; id: NavTab; label: string; }>; function NavTabsItem({ badge, iconClassName, id, label }: NavTabProps) { return ( {label} {badge && {badge}} ); } export type NavTabPanelProps = Readonly<{ collapsed: boolean; onToggleCollapse(collapsed: boolean): void; }>; export type NavTabsToggleProps = Readonly<{ i18n: LocalizerType; navTabsCollapsed: boolean; onToggleNavTabsCollapse(navTabsCollapsed: boolean): void; }>; export function NavTabsToggle({ i18n, navTabsCollapsed, onToggleNavTabsCollapse, }: NavTabsToggleProps): JSX.Element { function handleToggle() { onToggleNavTabsCollapse(!navTabsCollapsed); } return ( ); } export type NavTabsProps = Readonly<{ badge: BadgeType | undefined; hasFailedStorySends: boolean; hasPendingUpdate: boolean; i18n: LocalizerType; me: ConversationType; navTabsCollapsed: boolean; onShowSettings: () => void; onStartUpdate: () => unknown; onNavTabSelected(tab: NavTab): void; onToggleNavTabsCollapse(collapsed: boolean): void; onToggleProfileEditor: () => void; renderCallsTab(props: NavTabPanelProps): JSX.Element; renderChatsTab(props: NavTabPanelProps): JSX.Element; renderStoriesTab(props: NavTabPanelProps): JSX.Element; selectedNavTab: NavTab; storiesEnabled: boolean; theme: ThemeType; unreadConversationsStats: UnreadStats; unreadStoriesCount: number; }>; export function NavTabs({ badge, hasFailedStorySends, hasPendingUpdate, i18n, me, navTabsCollapsed, onShowSettings, onStartUpdate, onNavTabSelected, onToggleNavTabsCollapse, onToggleProfileEditor, renderCallsTab, renderChatsTab, renderStoriesTab, selectedNavTab, storiesEnabled, theme, unreadConversationsStats, unreadStoriesCount, }: NavTabsProps): JSX.Element { function handleSelectionChange(key: Key) { onNavTabSelected(key as NavTab); } const [targetElement, setTargetElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const [portalElement, setPortalElement] = useState(null); const [showAvatarPopup, setShowAvatarPopup] = useState(false); const popper = usePopper(targetElement, popperElement, { placement: 'bottom-start', strategy: 'fixed', modifiers: [ { name: 'offset', options: { offset: [null, 4], }, }, ], }); useEffect(() => { const div = document.createElement('div'); document.body.appendChild(div); setPortalElement(div); return () => { div.remove(); setPortalElement(null); }; }, []); useEffect(() => { return handleOutsideClick( () => { if (!showAvatarPopup) { return false; } setShowAvatarPopup(false); return true; }, { containerElements: [portalElement, targetElement], name: 'MainHeader.showAvatarPopup', } ); }, [portalElement, targetElement, showAvatarPopup]); useEffect(() => { function handleGlobalKeyDown(event: KeyboardEvent) { if (showAvatarPopup && event.key === 'Escape') { setShowAvatarPopup(false); } } document.addEventListener('keydown', handleGlobalKeyDown, true); return () => { document.removeEventListener('keydown', handleGlobalKeyDown, true); }; }, [showAvatarPopup]); return ( {renderChatsTab} {renderCallsTab} {renderStoriesTab} ); }