Add unread count to the stories badge

This commit is contained in:
Josh Perez 2022-07-20 19:06:15 -04:00 committed by GitHub
parent ea058371ed
commit 581b841098
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 116 additions and 63 deletions

View file

@ -2614,6 +2614,24 @@ button.ConversationDetails__action-button {
}
}
&__stories-badge {
@include rounded-corners;
align-items: center;
background-color: $color-accent-red;
color: $color-white;
display: flex;
height: 16px;
justify-content: center;
min-width: 16px;
overflow: hidden;
padding: 0 2px;
position: absolute;
right: -6px;
top: -6px;
user-select: none;
z-index: $z-index-base;
}
&__stories-icon {
-webkit-app-region: no-drag;
align-items: center;
@ -2623,9 +2641,10 @@ button.ConversationDetails__action-button {
display: inline-flex;
height: 32px;
justify-content: center;
padding: 2px;
width: 32px;
margin-right: 20px;
padding: 2px;
position: relative;
width: 32px;
.module-left-pane--width-narrow & {
display: none;

View file

@ -1,77 +1,93 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta, Story } from '@storybook/react';
import React from 'react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './MainHeader';
import enMessages from '../../_locales/en/messages.json';
import { MainHeader } from './MainHeader';
import { ThemeType } from '../types/Util';
import { setupI18n } from '../util/setupI18n';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/MainHeader',
component: MainHeader,
argTypes: {
areStoriesEnabled: {
defaultValue: false,
},
avatarPath: {
defaultValue: undefined,
},
hasPendingUpdate: {
defaultValue: false,
},
i18n: {
defaultValue: i18n,
},
name: {
defaultValue: undefined,
},
phoneNumber: {
defaultValue: undefined,
},
showArchivedConversations: { action: true },
startComposing: { action: true },
startUpdate: { action: true },
theme: {
defaultValue: ThemeType.light,
},
title: {
defaultValue: '',
},
toggleProfileEditor: { action: true },
toggleStoriesView: { action: true },
unreadStoriesCount: {
defaultValue: 0,
},
},
} as Meta;
const Template: Story<PropsType> = args => <MainHeader {...args} />;
export const Basic = Template.bind({});
Basic.args = {};
export const Name = Template.bind({});
{
const { name, title } = getDefaultConversation();
Name.args = {
name,
title,
};
}
export const PhoneNumber = Template.bind({});
{
const { name, e164: phoneNumber } = getDefaultConversation();
PhoneNumber.args = {
name,
phoneNumber,
};
}
export const UpdateAvailable = Template.bind({});
UpdateAvailable.args = {
hasPendingUpdate: true,
};
const requiredText = (name: string, value: string | undefined) =>
text(name, value || '');
const optionalText = (name: string, value: string | undefined) =>
text(name, value || '') || undefined;
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
areStoriesEnabled: false,
theme: ThemeType.light,
phoneNumber: optionalText('phoneNumber', overrideProps.phoneNumber),
title: requiredText('title', overrideProps.title),
name: optionalText('name', overrideProps.name),
avatarPath: optionalText('avatarPath', overrideProps.avatarPath),
hasPendingUpdate: Boolean(overrideProps.hasPendingUpdate),
i18n,
startUpdate: action('startUpdate'),
showArchivedConversations: action('showArchivedConversations'),
startComposing: action('startComposing'),
toggleProfileEditor: action('toggleProfileEditor'),
toggleStoriesView: action('toggleStoriesView'),
});
export const Basic = (): JSX.Element => {
const props = createProps({});
return <MainHeader {...props} />;
export const Stories = Template.bind({});
Stories.args = {
areStoriesEnabled: true,
unreadStoriesCount: 6,
};
export const Name = (): JSX.Element => {
const props = createProps({
name: 'John Smith',
title: 'John Smith',
});
return <MainHeader {...props} />;
export const StoriesOverflow = Template.bind({});
StoriesOverflow.args = {
areStoriesEnabled: true,
unreadStoriesCount: 69,
};
export const PhoneNumber = (): JSX.Element => {
const props = createProps({
name: 'John Smith',
phoneNumber: '+15553004000',
});
return <MainHeader {...props} />;
};
export const UpdateAvailable = (): JSX.Element => {
const props = createProps({ hasPendingUpdate: true });
return <MainHeader {...props} />;
};
export const Stories = (): JSX.Element => (
<MainHeader {...createProps({})} areStoriesEnabled />
);

View file

@ -26,6 +26,7 @@ export type PropsType = {
profileName?: string;
theme: ThemeType;
title: string;
unreadStoriesCount: number;
showArchivedConversations: () => void;
startComposing: () => void;
@ -132,6 +133,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
title,
toggleProfileEditor,
toggleStoriesView,
unreadStoriesCount,
} = this.props;
const { showingAvatarPopup, popperRoot } = this.state;
@ -222,7 +224,13 @@ export class MainHeader extends React.Component<PropsType, StateType> {
onClick={toggleStoriesView}
title={i18n('stories')}
type="button"
/>
>
{unreadStoriesCount ? (
<span className="module-main-header__stories-badge">
{unreadStoriesCount}
</span>
) : undefined}
</button>
)}
<button
aria-label={i18n('newConversation')}

View file

@ -340,3 +340,11 @@ export const getStories = createSelector(
};
}
);
export const getUnreadStoriesCount = createSelector(
getStoriesState,
({ stories }): number => {
return stories.filter(story => story.readStatus === ReadStatus.Unread)
.length;
}
);

View file

@ -17,6 +17,7 @@ import {
} from '../selectors/user';
import { getMe } from '../selectors/conversations';
import { getStoriesEnabled } from '../selectors/items';
import { getUnreadStoriesCount } from '../selectors/stories';
const mapStateToProps = (state: StateType) => {
const me = getMe(state);
@ -31,6 +32,7 @@ const mapStateToProps = (state: StateType) => {
badge: getPreferredBadgeSelector(state)(me.badges),
theme: getTheme(state),
i18n: getIntl(state),
unreadStoriesCount: getUnreadStoriesCount(state),
};
};