Add unread count to the stories badge
This commit is contained in:
parent
ea058371ed
commit
581b841098
5 changed files with 116 additions and 63 deletions
|
@ -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;
|
||||
|
|
|
@ -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 />
|
||||
);
|
||||
|
|
|
@ -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')}
|
||||
|
|
|
@ -340,3 +340,11 @@ export const getStories = createSelector(
|
|||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const getUnreadStoriesCount = createSelector(
|
||||
getStoriesState,
|
||||
({ stories }): number => {
|
||||
return stories.filter(story => story.readStatus === ReadStatus.Unread)
|
||||
.length;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue