New styles for various empty states
This commit is contained in:
parent
74b90a5cdd
commit
d70aa55a78
18 changed files with 295 additions and 95 deletions
|
@ -38,7 +38,7 @@ import { drop } from '../util/drop';
|
|||
import { strictAssert } from '../util/assert';
|
||||
import { UserText } from './UserText';
|
||||
import { I18n } from './I18n';
|
||||
import { NavSidebarSearchHeader } from './NavSidebar';
|
||||
import { NavSidebarSearchHeader, NavSidebarEmpty } from './NavSidebar';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
import {
|
||||
formatCallHistoryGroup,
|
||||
|
@ -205,21 +205,27 @@ export function CallsList({
|
|||
const searchStateQuery = searchState.options?.query ?? '';
|
||||
const searchStateStatus =
|
||||
searchState.options?.status ?? CallHistoryFilterStatus.All;
|
||||
const hasSearchStateQuery = searchStateQuery !== '';
|
||||
const searchFiltering =
|
||||
searchStateQuery !== '' ||
|
||||
searchStateStatus !== CallHistoryFilterStatus.All;
|
||||
hasSearchStateQuery || searchStateStatus !== CallHistoryFilterStatus.All;
|
||||
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) {
|
||||
if (results.length === 0 && hasSearchStateQuery) {
|
||||
results = ['EmptyState'];
|
||||
}
|
||||
if (!searchFiltering && canCreateCallLinks) {
|
||||
results = ['CreateCallLink', ...results];
|
||||
}
|
||||
return results;
|
||||
}, [searchState.results?.items, searchFiltering, canCreateCallLinks]);
|
||||
}, [
|
||||
searchState.results?.items,
|
||||
hasSearchStateQuery,
|
||||
searchFiltering,
|
||||
canCreateCallLinks,
|
||||
]);
|
||||
|
||||
const rowCount = rows.length;
|
||||
|
||||
|
@ -698,17 +704,13 @@ export function CallsList({
|
|||
if (item === 'EmptyState') {
|
||||
return (
|
||||
<div key={key} className="CallsList__EmptyState" style={style}>
|
||||
{searchStateQuery === '' ? (
|
||||
i18n('icu:CallsList__EmptyState--noQuery')
|
||||
) : (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:CallsList__EmptyState--hasQuery"
|
||||
components={{
|
||||
query: <UserText text={searchStateQuery} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:CallsList__EmptyState--hasQuery"
|
||||
components={{
|
||||
query: <UserText text={searchStateQuery} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -930,6 +932,22 @@ export function CallsList({
|
|||
|
||||
return (
|
||||
<>
|
||||
{isEmpty && !searchFiltering && (
|
||||
<NavSidebarEmpty
|
||||
title={i18n('icu:CallsList__EmptyState--noQuery__title')}
|
||||
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}
|
||||
|
|
|
@ -24,6 +24,7 @@ import type { WidthBreakpoint } from './_util';
|
|||
import type { CallLinkType } from '../types/CallLink';
|
||||
import type { CallStateType } from '../state/selectors/calling';
|
||||
import type { StartCallData } from './ConfirmLeaveCallModal';
|
||||
import { I18n } from './I18n';
|
||||
|
||||
enum CallsTabSidebarView {
|
||||
CallsListView,
|
||||
|
@ -316,7 +317,26 @@ export function CallsTab({
|
|||
<div className="CallsTab__EmptyState">
|
||||
<div className="CallsTab__EmptyStateIcon" />
|
||||
<p className="CallsTab__EmptyStateLabel">
|
||||
{i18n('icu:CallsTab__EmptyStateText')}
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:CallsTab__EmptyStateText--with-icon"
|
||||
components={{
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
newCallIcon: children => {
|
||||
let label: string | undefined;
|
||||
const first = children[0];
|
||||
if (typeof first === 'string') {
|
||||
label = first;
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className="CallsTab__EmptyState__ActionIcon"
|
||||
aria-label={label}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
@ -58,15 +58,16 @@ export function ChatsTab({
|
|||
) : (
|
||||
<div className="Inbox__no-conversation-open">
|
||||
{renderMiniPlayer({ shouldFlow: false })}
|
||||
<div className="module-splash-screen__logo module-img--128 module-logo-blue" />
|
||||
<h3>
|
||||
<div className="module-splash-screen__logo module-img--80 module-logo-blue" />
|
||||
<h3 className="Inbox__welcome">
|
||||
{getEnvironment() !== Environment.Staging
|
||||
? i18n('icu:welcomeToSignal')
|
||||
: 'THIS IS A STAGING DESKTOP'}
|
||||
</h3>
|
||||
<p>
|
||||
<p className="Inbox__whatsnew">
|
||||
<WhatsNewLink i18n={i18n} showWhatsNewModal={showWhatsNewModal} />
|
||||
</p>
|
||||
<div className="Inbox__padding" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -472,6 +472,10 @@ export function LeftPane({
|
|||
startSearch,
|
||||
]);
|
||||
|
||||
const backgroundNode = helper.getBackgroundNode({
|
||||
i18n,
|
||||
});
|
||||
|
||||
const preRowsNode = helper.getPreRowsNode({
|
||||
clearConversationSearch,
|
||||
clearGroupCreationError,
|
||||
|
@ -686,6 +690,7 @@ export function LeftPane({
|
|||
</>
|
||||
}
|
||||
>
|
||||
{backgroundNode}
|
||||
<nav
|
||||
className={classNames(
|
||||
'module-left-pane',
|
||||
|
|
|
@ -239,3 +239,20 @@ export function NavSidebarSearchHeader({
|
|||
}): JSX.Element {
|
||||
return <div className="NavSidebarSearchHeader">{children}</div>;
|
||||
}
|
||||
|
||||
export function NavSidebarEmpty({
|
||||
title,
|
||||
subtitle,
|
||||
}: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className="NavSidebarEmpty">
|
||||
<div className="NavSidebarEmpty__inner">
|
||||
<h3 className="NavSidebarEmpty__title">{title}</h3>
|
||||
<p className="NavSidebarEmpty__subtitle">{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { MyStoryButton } from './MyStoryButton';
|
|||
import { SearchInput } from './SearchInput';
|
||||
import { StoryListItem } from './StoryListItem';
|
||||
import { isNotNil } from '../util/isNotNil';
|
||||
import { NavSidebarSearchHeader } from './NavSidebar';
|
||||
import { NavSidebarSearchHeader, NavSidebarEmpty } from './NavSidebar';
|
||||
|
||||
const FUSE_OPTIONS: Fuse.IFuseOptions<ConversationStoryType> = {
|
||||
getFn: (story, path) => {
|
||||
|
@ -104,6 +104,12 @@ export function StoriesPane({
|
|||
}, [searchTerm, stories]);
|
||||
return (
|
||||
<>
|
||||
{!stories.length && (
|
||||
<NavSidebarEmpty
|
||||
title={i18n('icu:Stories__list__empty--title')}
|
||||
subtitle={i18n('icu:Stories__list__empty--subtitle')}
|
||||
/>
|
||||
)}
|
||||
<NavSidebarSearchHeader>
|
||||
<SearchInput
|
||||
i18n={i18n}
|
||||
|
@ -180,11 +186,6 @@ export function StoriesPane({
|
|||
))}
|
||||
</>
|
||||
)}
|
||||
{!stories.length && (
|
||||
<div className="Stories__pane__list--empty">
|
||||
{i18n('icu:Stories__list-empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -24,6 +24,7 @@ import { StoriesPane } from './StoriesPane';
|
|||
import { NavSidebar, NavSidebarActionButton } from './NavSidebar';
|
||||
import { StoriesAddStoryButton } from './StoriesAddStoryButton';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
import { I18n } from './I18n';
|
||||
import type { WidthBreakpoint } from './_util';
|
||||
import type { UnreadStats } from '../util/countUnreadStats';
|
||||
|
||||
|
@ -217,8 +218,33 @@ export function StoriesTab({
|
|||
</NavSidebar>
|
||||
)}
|
||||
<div className="Stories__placeholder">
|
||||
<div className="Stories__placeholder__stories" />
|
||||
{i18n('icu:Stories__placeholder--text')}
|
||||
<div className="Stories__placeholder__icon" />
|
||||
<div className="Stories__placeholder__text">
|
||||
{stories.length ? (
|
||||
i18n('icu:Stories__placeholder--text')
|
||||
) : (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:Stories__placeholder-with-icon--text"
|
||||
components={{
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
newStoryIcon: children => {
|
||||
let label: string | undefined;
|
||||
const first = children[0];
|
||||
if (typeof first === 'string') {
|
||||
label = first;
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className="Stories__placeholder__text__action"
|
||||
aria-label={label}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -66,6 +66,14 @@ export abstract class LeftPaneHelper<T> {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
getBackgroundNode(
|
||||
_: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
}>
|
||||
): null | ReactChild {
|
||||
return null;
|
||||
}
|
||||
|
||||
getPreRowsNode(
|
||||
_: Readonly<{
|
||||
clearConversationSearch: () => unknown;
|
||||
|
|
|
@ -5,7 +5,6 @@ import { last } from 'lodash';
|
|||
import type { ReactChild } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { I18n } from '../I18n';
|
||||
import type { ToFindType } from './LeftPaneHelper';
|
||||
import type {
|
||||
ConversationType,
|
||||
|
@ -15,6 +14,7 @@ import { LeftPaneHelper } from './LeftPaneHelper';
|
|||
import { getConversationInDirection } from './getConversationInDirection';
|
||||
import type { Row } from '../ConversationList';
|
||||
import { RowType } from '../ConversationList';
|
||||
import { NavSidebarEmpty } from '../NavSidebar';
|
||||
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { handleKeydownForSearch } from './handleKeydownForSearch';
|
||||
|
@ -121,31 +121,17 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
);
|
||||
}
|
||||
|
||||
override getPreRowsNode({
|
||||
override getBackgroundNode({
|
||||
i18n,
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
}>): ReactChild | null {
|
||||
if (this.getRowCount() === 0) {
|
||||
return (
|
||||
<div className="module-left-pane__empty">
|
||||
<div>
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:emptyInboxMessage"
|
||||
components={{
|
||||
composeIcon: (
|
||||
<span>
|
||||
<strong>{i18n('icu:composeIcon')}</strong>
|
||||
<span className="module-left-pane__empty--composer_icon">
|
||||
<i className="module-left-pane__empty--composer_icon--icon" />
|
||||
</span>
|
||||
</span>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NavSidebarEmpty
|
||||
title={i18n('icu:emptyInbox__title')}
|
||||
subtitle={i18n('icu:emptyInbox__subtitle')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue