// Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback, useMemo, type FocusEvent, type ReactNode, } from 'react'; import { ChatFolderType, type ChatFolder, type ChatFolderId, } from '../../types/ChatFolder.js'; import type { LocalizerType } from '../../types/I18N.js'; import { ExperimentalAxoSegmentedControl } from '../../axo/AxoSegmentedControl.js'; import { tw } from '../../axo/tw.js'; import type { AllChatFoldersUnreadStats, UnreadStats, } from '../../util/countUnreadStats.js'; import { WidthBreakpoint } from '../_util.js'; import { AxoSelect } from '../../axo/AxoSelect.js'; import { AxoContextMenu } from '../../axo/AxoContextMenu.js'; import { getMuteValuesOptions } from '../../util/getMuteOptions.js'; import type { AllChatFoldersMutedStats, MutedStats, } from '../../util/countMutedStats.js'; import type { AxoSymbol } from '../../axo/AxoSymbol.js'; export type LeftPaneChatFoldersProps = Readonly<{ i18n: LocalizerType; navSidebarWidthBreakpoint: WidthBreakpoint | null; sortedChatFolders: ReadonlyArray; allChatFoldersUnreadStats: AllChatFoldersUnreadStats; allChatFoldersMutedStats: AllChatFoldersMutedStats; selectedChatFolder: ChatFolder | null; onSelectedChatFolderIdChange: (newValue: ChatFolderId) => void; onChatFolderMarkRead: (chatFolderId: ChatFolderId) => void; onChatFolderUpdateMute: (chatFolderId: ChatFolderId, value: number) => void; onChatFolderOpenSettings: (chatFolderId: ChatFolderId) => void; }>; function getBadgeValue( unreadStats: UnreadStats | null ): ExperimentalAxoSegmentedControl.ExperimentalItemBadgeProps['value'] | null { if (unreadStats == null) { return null; } if (unreadStats.unreadCount > 0) { return unreadStats.unreadCount + unreadStats.readChatsMarkedUnreadCount; } if (unreadStats.readChatsMarkedUnreadCount > 0) { return unreadStats.readChatsMarkedUnreadCount; } return null; } function getChatFolderLabel( i18n: LocalizerType, chatFolder: ChatFolder, preferShort: boolean ): string { if (chatFolder.folderType === ChatFolderType.ALL) { if (preferShort) { return i18n('icu:LeftPaneChatFolders__ItemLabel--All--Short'); } return i18n('icu:LeftPaneChatFolders__ItemLabel--All'); } if (chatFolder.folderType === ChatFolderType.CUSTOM) { return chatFolder.name; } return ''; } function getChatFolderIconName( chatFolder: ChatFolder | null ): AxoSymbol.IconName { if (chatFolder == null) { return 'message'; } return chatFolder.folderType === ChatFolderType.ALL ? 'message' : 'folder'; } export function LeftPaneChatFolders( props: LeftPaneChatFoldersProps ): JSX.Element | null { const { i18n, onSelectedChatFolderIdChange } = props; const handleValueChange = useCallback( (newValue: string | null) => { if (newValue != null) { onSelectedChatFolderIdChange(newValue as ChatFolderId); } }, [onSelectedChatFolderIdChange] ); const handleFocus = useCallback((event: FocusEvent) => { event.target.scrollIntoView({ behavior: 'smooth', inline: 'nearest', }); }, []); if (props.sortedChatFolders.length < 2) { return null; } if (props.navSidebarWidthBreakpoint === WidthBreakpoint.Narrow) { return (
{props.sortedChatFolders.map(chatFolder => { const unreadStats = props.allChatFoldersUnreadStats.get(chatFolder.id) ?? null; return ( ); })}
); } return (
{props.sortedChatFolders.map(chatFolder => { const unreadStats = props.allChatFoldersUnreadStats.get(chatFolder.id) ?? null; const mutedStats = props.allChatFoldersMutedStats.get(chatFolder.id) ?? null; return ( ); })}
); } const UNREAD_BADGE_MAX_COUNT = 999; function ChatFolderSelectItem(props: { i18n: LocalizerType; chatFolder: ChatFolder; unreadStats: UnreadStats | null; }): JSX.Element { const { i18n, unreadStats } = props; const badgeValue = useMemo(() => { return getBadgeValue(unreadStats); }, [unreadStats]); return ( {getChatFolderLabel(i18n, props.chatFolder, true)} {badgeValue != null && ( )} ); } function ChatFolderSegmentedControlItem(props: { i18n: LocalizerType; chatFolder: ChatFolder; unreadStats: UnreadStats | null; mutedStats: MutedStats | null; onChatFolderMarkRead: (chatFolderId: ChatFolderId) => void; onChatFolderUpdateMute: (chatFolderId: ChatFolderId, value: number) => void; onChatFolderOpenSettings: (chatFolderId: ChatFolderId) => void; }): JSX.Element { const { i18n, unreadStats } = props; const badgeValue = useMemo(() => { return getBadgeValue(unreadStats); }, [unreadStats]); return ( {getChatFolderLabel(i18n, props.chatFolder, false)} {badgeValue != null && ( )} ); } function ChatFolderSegmentedControlItemContextMenu(props: { i18n: LocalizerType; chatFolder: ChatFolder; unreadStats: UnreadStats | null; mutedStats: MutedStats | null; onChatFolderMarkRead: (chatFolderId: ChatFolderId) => void; onChatFolderUpdateMute: (chatFolderId: ChatFolderId, value: number) => void; onChatFolderOpenSettings: (chatFolderId: ChatFolderId) => void; children: ReactNode; }) { const { i18n, onChatFolderMarkRead, onChatFolderUpdateMute, onChatFolderOpenSettings, } = props; const chatFolderId = props.chatFolder.id; const muteValuesOptions = useMemo(() => { return getMuteValuesOptions(i18n); }, [i18n]); const someChatsUnread = (props.unreadStats?.unreadCount ?? 0) > 0 || (props.unreadStats?.readChatsMarkedUnreadCount ?? 0) > 0; const someChatsMuted = (props.mutedStats?.chatsMutedCount ?? 0) > 0; const someChatsUnmuted = (props.mutedStats?.chatsUnmutedCount ?? 0) > 0; const showOnlyUnmuteAll = someChatsMuted && !someChatsUnmuted; const handleChatFolderMarkRead = useCallback(() => { onChatFolderMarkRead(chatFolderId); }, [chatFolderId, onChatFolderMarkRead]); const handleChatFolderUpdateMute = useCallback( (value: number) => { onChatFolderUpdateMute(chatFolderId, value); }, [chatFolderId, onChatFolderUpdateMute] ); const handleChatFolderOpenSettings = useCallback(() => { onChatFolderOpenSettings(chatFolderId); }, [chatFolderId, onChatFolderOpenSettings]); return ( {props.children} {someChatsUnread && ( {i18n('icu:LeftPaneChatFolders__Item__ContextMenu__MarkAllRead')} )} {!showOnlyUnmuteAll && ( {i18n( 'icu:LeftPaneChatFolders__Item__ContextMenu__MuteNotifications' )} {someChatsMuted && ( {i18n( 'icu:LeftPaneChatFolders__Item__ContextMenu__MuteNotifications__UnmuteAll' )} )} {muteValuesOptions.map(option => { return ( {option.name} ); })} )} {showOnlyUnmuteAll && ( {i18n('icu:LeftPaneChatFolders__Item__ContextMenu__UnmuteAll')} )} {props.chatFolder.folderType === ChatFolderType.CUSTOM && ( {i18n('icu:LeftPaneChatFolders__Item__ContextMenu__EditFolder')} )} ); } function ContextMenuMuteNotificationsItem(props: { symbol?: AxoSymbol.IconName; value: number; onSelect: (value: number) => void; children: ReactNode; }): JSX.Element { const { value, onSelect } = props; const handleSelect = useCallback(() => { onSelect(value); }, [onSelect, value]); return ( {props.children} ); }