Filter chats by Unread

This commit is contained in:
yash-signal 2024-11-13 13:33:41 -06:00 committed by GitHub
parent 45e9c07125
commit a56e7d0ade
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 883 additions and 438 deletions

View file

@ -66,6 +66,8 @@ import type {
} from '../state/ducks/calling';
import { DAY, MINUTE, SECOND } from '../util/durations';
import type { StartCallData } from './ConfirmLeaveCallModal';
import { Button, ButtonVariant } from './Button';
import type { ICUJSXMessageParamsByKeyType } from '../types/Util';
function Timestamp({
i18n,
@ -153,6 +155,7 @@ type CallsListProps = Readonly<{
togglePip: () => void;
}>;
const FILTER_HEADER_ROW_HEIGHT = 50;
const CALL_LIST_ITEM_ROW_HEIGHT = 62;
const INACTIVE_CALL_LINKS_TO_PEEK = 10;
const INACTIVE_CALL_LINK_AGE_THRESHOLD = 10 * DAY;
@ -167,7 +170,11 @@ function isSameOptions(
return a.query === b.query && a.status === b.status;
}
type SpecialRows = 'CreateCallLink' | 'EmptyState';
type SpecialRows =
| 'CreateCallLink'
| 'EmptyState'
| 'FilterHeader'
| 'ClearFilterButton';
type Row = CallHistoryGroup | SpecialRows;
export function CallsList({
@ -206,25 +213,34 @@ export function CallsList({
const searchStateStatus =
searchState.options?.status ?? CallHistoryFilterStatus.All;
const hasSearchStateQuery = searchStateQuery !== '';
const searchFiltering =
hasSearchStateQuery || searchStateStatus !== CallHistoryFilterStatus.All;
const hasMissedCallFilter =
searchStateStatus === CallHistoryFilterStatus.Missed;
const searchFiltering = hasSearchStateQuery || hasMissedCallFilter;
const searchPending = searchState.state === 'pending';
const isEmpty = !searchState.results?.items?.length;
const rows = useMemo(() => {
let results: ReadonlyArray<Row> = searchState.results?.items ?? [];
if (results.length === 0 && hasSearchStateQuery) {
results = ['EmptyState'];
const rows = useMemo<ReadonlyArray<Row>>(() => {
const results: ReadonlyArray<Row> = searchState.results?.items ?? [];
if (results.length === 0 && searchFiltering) {
return hasMissedCallFilter
? ['FilterHeader', 'EmptyState', 'ClearFilterButton']
: ['EmptyState'];
}
if (!searchFiltering && canCreateCallLinks) {
results = ['CreateCallLink', ...results];
return ['CreateCallLink', ...results];
}
if (hasMissedCallFilter) {
return ['FilterHeader', ...results, 'ClearFilterButton'];
}
return results;
}, [
searchState.results?.items,
hasSearchStateQuery,
searchFiltering,
canCreateCallLinks,
hasMissedCallFilter,
]);
const rowCount = rows.length;
@ -675,10 +691,8 @@ export function CallsList({
({ index }: Index) => {
const item = rows.at(index) ?? null;
if (item === 'EmptyState') {
// arbitary large number so the empty state can be as big as it wants,
// scrolling should always be locked when the list is empty
return 9999;
if (item === 'FilterHeader') {
return FILTER_HEADER_ROW_HEIGHT;
}
return CALL_LIST_ITEM_ROW_HEIGHT;
@ -710,11 +724,23 @@ export function CallsList({
}
if (item === 'EmptyState') {
let i18nId: keyof ICUJSXMessageParamsByKeyType;
if (hasSearchStateQuery && hasMissedCallFilter) {
i18nId = 'icu:CallsList__EmptyState--hasQueryAndMissedCalls';
} else if (hasSearchStateQuery) {
i18nId = 'icu:CallsList__EmptyState--hasQuery';
} else if (hasMissedCallFilter) {
i18nId = 'icu:CallsList__EmptyState--missedCalls';
} else {
// This should never happen
i18nId = 'icu:CallsList__EmptyState--hasQuery';
}
return (
<div key={key} className="CallsList__EmptyState" style={style}>
<I18n
i18n={i18n}
id="icu:CallsList__EmptyState--hasQuery"
id={i18nId}
components={{
query: <UserText text={searchStateQuery} />,
}}
@ -723,6 +749,32 @@ export function CallsList({
);
}
if (item === 'FilterHeader') {
return (
<div key={key} style={style} className="CallsList__FilterHeader">
{i18n('icu:CallsList__FilteredByMissedHeader')}
</div>
);
}
if (item === 'ClearFilterButton') {
return (
<div key={key} style={style} className="ClearFilterButton">
<Button
variant={ButtonVariant.SecondaryAffirmative}
className={classNames('ClearFilterButton__inner', {
// The clear filter button should be closer to the emty state
// text than to the search results.
'ClearFilterButton__inner-vertical-center': !isEmpty,
})}
onClick={() => setStatusInput(CallHistoryFilterStatus.All)}
>
{i18n('icu:clearFilterButton')}
</Button>
</div>
);
}
const conversation = getConversationForItem(item);
const activeCallConversationId = activeCall?.conversationId;
@ -918,6 +970,8 @@ export function CallsList({
getIsAnybodyInCall,
getIsCallActive,
getIsInCall,
hasMissedCallFilter,
hasSearchStateQuery,
selectedCallHistoryGroup,
onChangeCallsTabSelectedView,
onCreateCallLink,
@ -927,6 +981,7 @@ export function CallsList({
toggleConfirmLeaveCallModal,
togglePip,
i18n,
isEmpty,
]
);
@ -957,20 +1012,14 @@ export function CallsList({
subtitle={i18n('icu:CallsList__EmptyState--noQuery__subtitle')}
/>
)}
{isEmpty &&
statusInput === CallHistoryFilterStatus.Missed &&
!hasSearchStateQuery && (
<NavSidebarEmpty
title={i18n('icu:CallsList__EmptyState--noQuery--missed__title')}
subtitle={i18n(
'icu:CallsList__EmptyState--noQuery--missed__subtitle'
)}
/>
)}
<NavSidebarSearchHeader>
<SearchInput
i18n={i18n}
placeholder={i18n('icu:CallsList__SearchInputPlaceholder')}
placeholder={
searchFiltering
? i18n('icu:CallsList__SearchInputPlaceholder--missed-calls')
: i18n('icu:CallsList__SearchInputPlaceholder')
}
onChange={handleSearchInputChange}
onClear={handleSearchInputClear}
value={queryInput}
@ -983,11 +1032,10 @@ export function CallsList({
>
<button
className={classNames('CallsList__ToggleFilterByMissed', {
'CallsList__ToggleFilterByMissed--pressed':
statusInput === CallHistoryFilterStatus.Missed,
'CallsList__ToggleFilterByMissed--pressed': hasMissedCallFilter,
})}
type="button"
aria-pressed={statusInput === CallHistoryFilterStatus.Missed}
aria-pressed={hasMissedCallFilter}
aria-roledescription={i18n(
'icu:CallsList__ToggleFilterByMissed__RoleDescription'
)}