Calls Tab & Group Call Disposition
This commit is contained in:
parent
620e85ca01
commit
1eaabb6734
139 changed files with 9182 additions and 2721 deletions
219
ts/components/NavSidebar.tsx
Normal file
219
ts/components/NavSidebar.tsx
Normal file
|
@ -0,0 +1,219 @@
|
|||
// 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';
|
||||
|
||||
export function NavSidebarActionButton({
|
||||
icon,
|
||||
label,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
}: {
|
||||
icon: ReactNode;
|
||||
label: ReactNode;
|
||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLButtonElement>;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="NavSidebar__ActionButton"
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
{icon}
|
||||
<span className="NavSidebar__ActionButtonLabel">{label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export type NavSidebarProps = Readonly<{
|
||||
actions?: ReactNode;
|
||||
children: ReactNode;
|
||||
i18n: LocalizerType;
|
||||
hideHeader?: boolean;
|
||||
navTabsCollapsed: boolean;
|
||||
onBack?: (() => void) | null;
|
||||
onToggleNavTabsCollapse(navTabsCollapsed: boolean): void;
|
||||
preferredLeftPaneWidth: number;
|
||||
requiresFullWidth: boolean;
|
||||
savePreferredLeftPaneWidth: (width: number) => void;
|
||||
title: string;
|
||||
}>;
|
||||
|
||||
enum DragState {
|
||||
INITIAL,
|
||||
DRAGGING,
|
||||
DRAGEND,
|
||||
}
|
||||
|
||||
export function NavSidebar({
|
||||
actions,
|
||||
children,
|
||||
hideHeader,
|
||||
i18n,
|
||||
navTabsCollapsed,
|
||||
onBack,
|
||||
onToggleNavTabsCollapse,
|
||||
preferredLeftPaneWidth,
|
||||
requiresFullWidth,
|
||||
savePreferredLeftPaneWidth,
|
||||
title,
|
||||
}: 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 (
|
||||
<div
|
||||
role="navigation"
|
||||
className={classNames('NavSidebar', {
|
||||
'NavSidebar--narrow': widthBreakpoint === WidthBreakpoint.Narrow,
|
||||
})}
|
||||
style={{ width }}
|
||||
>
|
||||
{!hideHeader && (
|
||||
<div className="NavSidebar__Header">
|
||||
{onBack == null && navTabsCollapsed && (
|
||||
<NavTabsToggle
|
||||
i18n={i18n}
|
||||
navTabsCollapsed={navTabsCollapsed}
|
||||
onToggleNavTabsCollapse={onToggleNavTabsCollapse}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={classNames('NavSidebar__HeaderContent', {
|
||||
'NavSidebar__HeaderContent--navTabsCollapsed': navTabsCollapsed,
|
||||
'NavSidebar__HeaderContent--withBackButton': onBack != null,
|
||||
})}
|
||||
>
|
||||
{onBack != null && (
|
||||
<button
|
||||
type="button"
|
||||
role="link"
|
||||
onClick={onBack}
|
||||
className="NavSidebar__BackButton"
|
||||
>
|
||||
<span className="NavSidebar__BackButtonLabel">
|
||||
{i18n('icu:NavSidebar__BackButtonLabel')}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<h1
|
||||
className={classNames('NavSidebar__HeaderTitle', {
|
||||
'NavSidebar__HeaderTitle--withBackButton': onBack != null,
|
||||
})}
|
||||
aria-live="assertive"
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
{actions && (
|
||||
<div className="NavSidebar__HeaderActions">{actions}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="NavSidebar__Content">{children}</div>
|
||||
|
||||
{/* 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 */}
|
||||
<div
|
||||
className={classNames('NavSidebar__DragHandle', {
|
||||
'NavSidebar__DragHandle--dragging': dragState === DragState.DRAGGING,
|
||||
})}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-valuemin={MIN_WIDTH}
|
||||
aria-valuemax={preferredLeftPaneWidth}
|
||||
aria-valuenow={MAX_WIDTH}
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/separator_role#focusable_separator
|
||||
tabIndex={0}
|
||||
{...moveProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function NavSidebarSearchHeader({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
return <div className="NavSidebarSearchHeader">{children}</div>;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue