// Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { KeyboardEventHandler, MouseEventHandler, ReactNode } from 'react'; import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import { useMove } from 'react-aria'; import { NavTabsToggle } from './NavTabs'; import type { LocalizerType } from '../types/I18N'; import { MAX_WIDTH, MIN_FULL_WIDTH, MIN_WIDTH, getWidthFromPreferredWidth, } from '../util/leftPaneWidth'; import { WidthBreakpoint, getNavSidebarWidthBreakpoint } from './_util'; import type { UnreadStats } from '../util/countUnreadStats'; export function NavSidebarActionButton({ icon, label, onClick, onKeyDown, }: { icon: ReactNode; label: ReactNode; onClick: MouseEventHandler; onKeyDown?: KeyboardEventHandler; }): JSX.Element { return ( ); } export type NavSidebarProps = Readonly<{ actions?: ReactNode; children: ReactNode; i18n: LocalizerType; hasFailedStorySends: boolean; hasPendingUpdate: boolean; hideHeader?: boolean; navTabsCollapsed: boolean; onBack?: (() => void) | null; onToggleNavTabsCollapse(navTabsCollapsed: boolean): void; preferredLeftPaneWidth: number; requiresFullWidth: boolean; savePreferredLeftPaneWidth: (width: number) => void; title: string; appUnreadStats: UnreadStats; }>; enum DragState { INITIAL, DRAGGING, DRAGEND, } export function NavSidebar({ actions, children, hideHeader, i18n, hasFailedStorySends, hasPendingUpdate, navTabsCollapsed, onBack, onToggleNavTabsCollapse, preferredLeftPaneWidth, requiresFullWidth, savePreferredLeftPaneWidth, title, appUnreadStats, }: NavSidebarProps): JSX.Element { const [dragState, setDragState] = useState(DragState.INITIAL); const [preferredWidth, setPreferredWidth] = useState(() => { return getWidthFromPreferredWidth(preferredLeftPaneWidth, { requiresFullWidth, }); }); const width = getWidthFromPreferredWidth(preferredWidth, { requiresFullWidth, }); const widthBreakpoint = getNavSidebarWidthBreakpoint(width); // `useMove` gives us keyboard and mouse dragging support. const { moveProps } = useMove({ onMoveStart() { setDragState(DragState.DRAGGING); }, onMoveEnd() { setDragState(DragState.DRAGEND); }, onMove(event) { const { deltaX, shiftKey, pointerType } = event; const isKeyboard = pointerType === 'keyboard'; const increment = isKeyboard && shiftKey ? 10 : 1; setPreferredWidth(prevWidth => { // Jump minimize for keyboard users if (isKeyboard && prevWidth === MIN_FULL_WIDTH && deltaX < 0) { return MIN_WIDTH; } // Jump maximize for keyboard users if (isKeyboard && prevWidth === MIN_WIDTH && deltaX > 0) { return MIN_FULL_WIDTH; } return prevWidth + deltaX * increment; }); }, }); useEffect(() => { // Save the preferred width when the drag ends. We can't do this in onMoveEnd // because the width is not updated yet. if (dragState === DragState.DRAGEND) { setPreferredWidth(width); savePreferredLeftPaneWidth(width); setDragState(DragState.INITIAL); } }, [ dragState, preferredLeftPaneWidth, preferredWidth, savePreferredLeftPaneWidth, width, ]); useEffect(() => { // This effect helps keep the pointer `col-resize` even when you drag past the handle. const className = 'NavSidebar__document--draggingHandle'; if (dragState === DragState.DRAGGING) { document.body.classList.add(className); return () => { document.body.classList.remove(className); }; } return undefined; }, [dragState]); return (
{!hideHeader && (
{onBack == null && navTabsCollapsed && ( )}
{onBack != null && ( )}

{title}

{actions && (
{actions}
)}
)}
{children}
{/* eslint-disable-next-line jsx-a11y/role-supports-aria-props -- See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/separator_role#focusable_separator */}
); } export function NavSidebarSearchHeader({ children, }: { children: ReactNode; }): JSX.Element { return
{children}
; }