New styles for various empty states

This commit is contained in:
Fedor Indutny 2024-08-13 16:34:42 -07:00 committed by GitHub
parent 74b90a5cdd
commit d70aa55a78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 295 additions and 95 deletions

View file

@ -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}

View file

@ -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>
) : (

View file

@ -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>

View file

@ -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',

View file

@ -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>
);
}

View file

@ -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>
</>
);

View file

@ -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>
);

View file

@ -66,6 +66,14 @@ export abstract class LeftPaneHelper<T> {
return undefined;
}
getBackgroundNode(
_: Readonly<{
i18n: LocalizerType;
}>
): null | ReactChild {
return null;
}
getPreRowsNode(
_: Readonly<{
clearConversationSearch: () => unknown;

View file

@ -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')}
/>
);
}