Add unread count to the stories badge
This commit is contained in:
parent
ea058371ed
commit
581b841098
|
@ -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 {
|
&__stories-icon {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -2623,9 +2641,10 @@ button.ConversationDetails__action-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 2px;
|
|
||||||
width: 32px;
|
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
padding: 2px;
|
||||||
|
position: relative;
|
||||||
|
width: 32px;
|
||||||
|
|
||||||
.module-left-pane--width-narrow & {
|
.module-left-pane--width-narrow & {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -1,77 +1,93 @@
|
||||||
// Copyright 2021-2022 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as React from 'react';
|
import type { Meta, Story } from '@storybook/react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import React from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
|
|
||||||
import { setupI18n } from '../util/setupI18n';
|
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
|
||||||
import type { PropsType } from './MainHeader';
|
import type { PropsType } from './MainHeader';
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
import { MainHeader } from './MainHeader';
|
import { MainHeader } from './MainHeader';
|
||||||
import { ThemeType } from '../types/Util';
|
import { ThemeType } from '../types/Util';
|
||||||
|
import { setupI18n } from '../util/setupI18n';
|
||||||
|
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Components/MainHeader',
|
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) =>
|
export const Stories = Template.bind({});
|
||||||
text(name, value || '');
|
Stories.args = {
|
||||||
const optionalText = (name: string, value: string | undefined) =>
|
areStoriesEnabled: true,
|
||||||
text(name, value || '') || undefined;
|
unreadStoriesCount: 6,
|
||||||
|
|
||||||
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 Name = (): JSX.Element => {
|
export const StoriesOverflow = Template.bind({});
|
||||||
const props = createProps({
|
StoriesOverflow.args = {
|
||||||
name: 'John Smith',
|
areStoriesEnabled: true,
|
||||||
title: 'John Smith',
|
unreadStoriesCount: 69,
|
||||||
});
|
|
||||||
|
|
||||||
return <MainHeader {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
profileName?: string;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
title: string;
|
title: string;
|
||||||
|
unreadStoriesCount: number;
|
||||||
|
|
||||||
showArchivedConversations: () => void;
|
showArchivedConversations: () => void;
|
||||||
startComposing: () => void;
|
startComposing: () => void;
|
||||||
|
@ -132,6 +133,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
title,
|
title,
|
||||||
toggleProfileEditor,
|
toggleProfileEditor,
|
||||||
toggleStoriesView,
|
toggleStoriesView,
|
||||||
|
unreadStoriesCount,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { showingAvatarPopup, popperRoot } = this.state;
|
const { showingAvatarPopup, popperRoot } = this.state;
|
||||||
|
|
||||||
|
@ -222,7 +224,13 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
onClick={toggleStoriesView}
|
onClick={toggleStoriesView}
|
||||||
title={i18n('stories')}
|
title={i18n('stories')}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
>
|
||||||
|
{unreadStoriesCount ? (
|
||||||
|
<span className="module-main-header__stories-badge">
|
||||||
|
{unreadStoriesCount}
|
||||||
|
</span>
|
||||||
|
) : undefined}
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
aria-label={i18n('newConversation')}
|
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';
|
} from '../selectors/user';
|
||||||
import { getMe } from '../selectors/conversations';
|
import { getMe } from '../selectors/conversations';
|
||||||
import { getStoriesEnabled } from '../selectors/items';
|
import { getStoriesEnabled } from '../selectors/items';
|
||||||
|
import { getUnreadStoriesCount } from '../selectors/stories';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
const me = getMe(state);
|
const me = getMe(state);
|
||||||
|
@ -31,6 +32,7 @@ const mapStateToProps = (state: StateType) => {
|
||||||
badge: getPreferredBadgeSelector(state)(me.badges),
|
badge: getPreferredBadgeSelector(state)(me.badges),
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
|
unreadStoriesCount: getUnreadStoriesCount(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue