Update eslint to 8.27.0

This commit is contained in:
Fedor Indutny 2022-11-17 16:45:19 -08:00 committed by GitHub
parent c8fb43a846
commit 98ef4c627a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
499 changed files with 8995 additions and 8494 deletions

View file

@ -18,14 +18,14 @@ export type PropsType = {
executeMenuRole: ExecuteMenuRoleType;
};
export const About = ({
export function About({
closeAbout,
i18n,
environment,
version,
hasCustomTitleBar,
executeMenuRole,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
useEscapeHandling(closeAbout);
const theme = useTheme();
@ -63,4 +63,4 @@ export const About = ({
</div>
</TitleBarContainer>
);
};
}

View file

@ -37,6 +37,7 @@ export default {
},
} as Meta;
// eslint-disable-next-line react/function-component-definition
const Template: Story<Props> = args => (
<AddCaptionModal {...args} theme={React.useContext(StorybookThemeContext)} />
);

View file

@ -19,14 +19,14 @@ export type Props = {
) => JSX.Element;
};
export const AddCaptionModal = ({
export function AddCaptionModal({
i18n,
onClose,
onSubmit,
draftText,
RenderCompositionTextArea,
theme,
}: Props): JSX.Element => {
}: Props): JSX.Element {
const [messageText, setMessageText] = React.useState('');
const [isScrolledTop, setIsScrolledTop] = React.useState(true);
@ -84,4 +84,4 @@ export const AddCaptionModal = ({
/>
</Modal>
);
};
}

View file

@ -35,13 +35,15 @@ _MaximumGroupSize.story = {
name: 'Maximum group size',
};
export const MaximumRecommendedGroupSize = (): JSX.Element => (
<AddGroupMemberErrorDialog
{...defaultProps}
mode={AddGroupMemberErrorDialogMode.RecommendedMaximumGroupSize}
recommendedMaximumNumberOfContacts={123}
/>
);
export function MaximumRecommendedGroupSize(): JSX.Element {
return (
<AddGroupMemberErrorDialog
{...defaultProps}
mode={AddGroupMemberErrorDialogMode.RecommendedMaximumGroupSize}
recommendedMaximumNumberOfContacts={123}
/>
);
}
MaximumRecommendedGroupSize.story = {
name: 'Maximum recommended group size',

View file

@ -1,7 +1,7 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent, ReactNode } from 'react';
import type { ReactNode } from 'react';
import React from 'react';
import type { LocalizerType } from '../types/Util';
@ -28,9 +28,7 @@ type PropsType = {
onClose: () => void;
} & PropsDataType;
export const AddGroupMemberErrorDialog: FunctionComponent<
PropsType
> = props => {
export function AddGroupMemberErrorDialog(props: PropsType): JSX.Element {
const { i18n, onClose } = props;
let title: string;
@ -57,4 +55,4 @@ export const AddGroupMemberErrorDialog: FunctionComponent<
}
return <Alert body={body} i18n={i18n} onClose={onClose} title={title} />;
};
}

View file

@ -39,6 +39,7 @@ export default {
},
} as Meta;
// eslint-disable-next-line react/function-component-definition
const Template: Story<Props> = args => (
<AddUserToAnotherGroupModal
{...args}

View file

@ -42,7 +42,7 @@ type DispatchProps = {
export type Props = OwnProps & DispatchProps;
export const AddUserToAnotherGroupModal = ({
export function AddUserToAnotherGroupModal({
i18n,
theme,
contact,
@ -51,7 +51,7 @@ export const AddUserToAnotherGroupModal = ({
showToast,
candidateConversations,
regionCode,
}: Props): JSX.Element | null => {
}: Props): JSX.Element | null {
const [searchTerm, setSearchTerm] = React.useState('');
const [filteredConversations, setFilteredConversations] = React.useState(
filterAndSortConversationsByRecent(candidateConversations, '', undefined)
@ -177,7 +177,6 @@ export const AddUserToAnotherGroupModal = ({
onClickArchiveButton={noop}
onClickContactCheckbox={noop}
onSelectConversation={setSelectedGroupId}
renderMessageSearchResult={_ => <></>}
showChooseGroupMembers={noop}
lookupConversationWithoutUuid={async _ => undefined}
showUserNotFoundModal={noop}
@ -223,4 +222,4 @@ export const AddUserToAnotherGroupModal = ({
)}
</>
);
};
}

View file

@ -23,67 +23,75 @@ const defaultProps = {
const LOREM_IPSUM =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.';
export const TitleAndBodyAreStrings = (): JSX.Element => (
<Alert
{...defaultProps}
title="Hello world"
body="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus."
/>
);
export function TitleAndBodyAreStrings(): JSX.Element {
return (
<Alert
{...defaultProps}
title="Hello world"
body="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus."
/>
);
}
TitleAndBodyAreStrings.story = {
name: 'Title and body are strings',
};
export const BodyIsAReactNode = (): JSX.Element => (
<Alert
{...defaultProps}
title="Hello world"
body={
<>
<span style={{ color: 'red' }}>Hello</span>{' '}
<span style={{ color: 'green', fontWeight: 'bold' }}>world</span>!
</>
}
/>
);
export function BodyIsAReactNode(): JSX.Element {
return (
<Alert
{...defaultProps}
title="Hello world"
body={
<>
<span style={{ color: 'red' }}>Hello</span>{' '}
<span style={{ color: 'green', fontWeight: 'bold' }}>world</span>!
</>
}
/>
);
}
BodyIsAReactNode.story = {
name: 'Body is a ReactNode',
};
export const LongBodyWithoutTitle = (): JSX.Element => (
<Alert
{...defaultProps}
body={
<>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
</>
}
/>
);
export function LongBodyWithoutTitle(): JSX.Element {
return (
<Alert
{...defaultProps}
body={
<>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
</>
}
/>
);
}
LongBodyWithoutTitle.story = {
name: 'Long body (without title)',
};
export const LongBodyWithTitle = (): JSX.Element => (
<Alert
{...defaultProps}
title="Hello world"
body={
<>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
</>
}
/>
);
export function LongBodyWithTitle(): JSX.Element {
return (
<Alert
{...defaultProps}
title="Hello world"
body={
<>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
<p>{LOREM_IPSUM}</p>
</>
}
/>
);
}
LongBodyWithTitle.story = {
name: 'Long body (with title)',

View file

@ -1,7 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent, ReactNode } from 'react';
import type { ReactNode } from 'react';
import React from 'react';
import type { LocalizerType } from '../types/Util';
@ -17,23 +17,25 @@ type PropsType = {
title?: string;
};
export const Alert: FunctionComponent<PropsType> = ({
export function Alert({
body,
i18n,
onClose,
theme,
title,
}) => (
<Modal
i18n={i18n}
modalFooter={
<Button onClick={onClose}>{i18n('Confirmation--confirm')}</Button>
}
modalName="Alert"
onClose={onClose}
theme={theme}
title={title}
>
{body}
</Modal>
);
}: PropsType): JSX.Element {
return (
<Modal
i18n={i18n}
modalFooter={
<Button onClick={onClose}>{i18n('Confirmation--confirm')}</Button>
}
modalName="Alert"
onClose={onClose}
theme={theme}
title={title}
>
{body}
</Modal>
);
}

View file

@ -18,6 +18,6 @@ function getDefaultProps(): PropsType {
};
}
export const Hearts = (): JSX.Element => (
<AnimatedEmojiGalore {...getDefaultProps()} />
);
export function Hearts(): JSX.Element {
return <AnimatedEmojiGalore {...getDefaultProps()} />;
}

View file

@ -34,10 +34,10 @@ function transform(y: number, scale: number, rotate: number): string {
return `translateY(${y}px) scale(${scale}) rotate(${rotate}deg)`;
}
export const AnimatedEmojiGalore = ({
export function AnimatedEmojiGalore({
emoji,
onAnimationEnd,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [springs] = useSprings(NUM_EMOJIS, i => ({
...to(i, onAnimationEnd),
from: from(i),
@ -69,4 +69,4 @@ export const AnimatedEmojiGalore = ({
))}
</>
);
};
}

View file

@ -15,12 +15,12 @@ type PropsType = {
theme: ThemeType;
};
export const AnnouncementsOnlyGroupBanner = ({
export function AnnouncementsOnlyGroupBanner({
groupAdmins,
i18n,
openConversation,
theme,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [isShowingAdmins, setIsShowingAdmins] = useState(false);
return (
@ -64,4 +64,4 @@ export const AnnouncementsOnlyGroupBanner = ({
</div>
</>
);
};
}

View file

@ -55,7 +55,7 @@ type PropsType = {
viewStory: ViewStoryActionCreatorType;
} & ComponentProps<typeof Inbox>;
export const App = ({
export function App({
appView,
executeMenuAction,
executeMenuRole,
@ -89,7 +89,7 @@ export const App = ({
toast,
toggleStoriesView,
viewStory,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
let contents;
if (appView === AppViewType.Installer) {
@ -183,4 +183,4 @@ export const App = ({
</div>
</TitleBarContainer>
);
};
}

View file

@ -102,6 +102,7 @@ const sizes = Object.values(AvatarSize).filter(
x => typeof x === 'number'
) as Array<AvatarSize>;
// eslint-disable-next-line react/function-component-definition
const Template: Story<Props> = args => (
<>
{sizes.map(size => (
@ -110,6 +111,7 @@ const Template: Story<Props> = args => (
</>
);
// eslint-disable-next-line react/function-component-definition
const TemplateSingle: Story<Props> = args => (
<Avatar {...args} size={AvatarSize.ONE_HUNDRED_TWELVE} />
);
@ -198,7 +200,7 @@ SearchIcon.args = createProps({
searchResult: true,
});
export const Colors = (): JSX.Element => {
export function Colors(): JSX.Element {
const props = createProps();
return (
@ -208,7 +210,7 @@ export const Colors = (): JSX.Element => {
))}
</>
);
};
}
export const BrokenColor = Template.bind({});
BrokenColor.args = createProps({

View file

@ -1,13 +1,7 @@
// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type {
CSSProperties,
FunctionComponent,
MouseEvent,
ReactChild,
ReactNode,
} from 'react';
import type { CSSProperties, MouseEvent, ReactChild, ReactNode } from 'react';
import React, { useEffect, useState } from 'react';
import classNames from 'classnames';
import { noop } from 'lodash';
@ -98,7 +92,7 @@ const getDefaultBlur = (
): AvatarBlur =>
shouldBlurAvatar(...args) ? AvatarBlur.BlurPicture : AvatarBlur.NoBlur;
export const Avatar: FunctionComponent<Props> = ({
export function Avatar({
acceptedMessageRequest,
avatarPath,
badge,
@ -126,7 +120,7 @@ export const Avatar: FunctionComponent<Props> = ({
sharedGroupNames,
unblurredAvatarPath,
}),
}) => {
}: Props): JSX.Element {
const [imageBroken, setImageBroken] = useState(false);
useEffect(() => {
@ -312,7 +306,7 @@ export const Avatar: FunctionComponent<Props> = ({
{badgeNode}
</div>
);
};
}
// This is only exported for testing.
export function _getBadgeSize(avatarSize: number): undefined | number {

View file

@ -23,14 +23,16 @@ export default {
title: 'Components/AvatarColorPicker',
};
export const Default = (): JSX.Element => (
<AvatarColorPicker {...createProps()} />
);
export function Default(): JSX.Element {
return <AvatarColorPicker {...createProps()} />;
}
export const Selected = (): JSX.Element => (
<AvatarColorPicker
{...createProps({
selectedColor: AvatarColors[7],
})}
/>
);
export function Selected(): JSX.Element {
return (
<AvatarColorPicker
{...createProps({
selectedColor: AvatarColors[7],
})}
/>
);
}

View file

@ -13,11 +13,11 @@ export type PropsType = {
selectedColor?: AvatarColorType;
};
export const AvatarColorPicker = ({
export function AvatarColorPicker({
i18n,
onColorSelected,
selectedColor,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
return (
<>
<div className="AvatarEditor__avatar-selector-title">
@ -38,4 +38,4 @@ export const AvatarColorPicker = ({
</div>
</>
);
};
}

View file

@ -82,28 +82,37 @@ export default {
title: 'Components/AvatarEditor',
};
export const NoAvatarGroup = (): JSX.Element => (
<AvatarEditor
{...createProps({ isGroup: true, userAvatarData: getDefaultAvatars(true) })}
/>
);
export function NoAvatarGroup(): JSX.Element {
return (
<AvatarEditor
{...createProps({
isGroup: true,
userAvatarData: getDefaultAvatars(true),
})}
/>
);
}
NoAvatarGroup.story = {
name: 'No Avatar (group)',
};
export const NoAvatarMe = (): JSX.Element => (
<AvatarEditor {...createProps({ userAvatarData: getDefaultAvatars() })} />
);
export function NoAvatarMe(): JSX.Element {
return (
<AvatarEditor {...createProps({ userAvatarData: getDefaultAvatars() })} />
);
}
NoAvatarMe.story = {
name: 'No Avatar (me)',
};
export const HasAvatar = (): JSX.Element => (
<AvatarEditor
{...createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
})}
/>
);
export function HasAvatar(): JSX.Element {
return (
<AvatarEditor
{...createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
})}
/>
);
}

View file

@ -44,7 +44,7 @@ enum EditMode {
Text = 'Text',
}
export const AvatarEditor = ({
export function AvatarEditor({
avatarColor,
avatarPath,
avatarValue,
@ -58,7 +58,7 @@ export const AvatarEditor = ({
userAvatarData,
replaceAvatar,
saveAvatarToDisk,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [provisionalSelectedAvatar, setProvisionalSelectedAvatar] = useState<
AvatarDataType | undefined
>();
@ -298,4 +298,4 @@ export const AvatarEditor = ({
}
return <div className="AvatarEditor">{content}</div>;
};
}

View file

@ -25,24 +25,28 @@ export default {
title: 'Components/AvatarIconEditor',
};
export const PersonalIcon = (): JSX.Element => (
<AvatarIconEditor
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[3],
icon: PersonalAvatarIcons[0],
}),
})}
/>
);
export function PersonalIcon(): JSX.Element {
return (
<AvatarIconEditor
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[3],
icon: PersonalAvatarIcons[0],
}),
})}
/>
);
}
export const GroupIcon = (): JSX.Element => (
<AvatarIconEditor
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[8],
icon: GroupAvatarIcons[0],
}),
})}
/>
);
export function GroupIcon(): JSX.Element {
return (
<AvatarIconEditor
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[8],
icon: GroupAvatarIcons[0],
}),
})}
/>
);
}

View file

@ -17,11 +17,11 @@ export type PropsType = {
onClose: (avatarData?: AvatarDataType) => unknown;
};
export const AvatarIconEditor = ({
export function AvatarIconEditor({
avatarData: initialAvatarData,
i18n,
onClose,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>();
const [avatarData, setAvatarData] =
useState<AvatarDataType>(initialAvatarData);
@ -81,4 +81,4 @@ export const AvatarIconEditor = ({
/>
</>
);
};
}

View file

@ -32,15 +32,17 @@ export default {
title: 'Components/AvatarLightbox',
};
export const Group = (): JSX.Element => (
<AvatarLightbox
{...createProps({
isGroup: true,
})}
/>
);
export function Group(): JSX.Element {
return (
<AvatarLightbox
{...createProps({
isGroup: true,
})}
/>
);
}
export const Person = (): JSX.Element => {
export function Person(): JSX.Element {
const conversation = getDefaultConversation();
return (
<AvatarLightbox
@ -50,12 +52,14 @@ export const Person = (): JSX.Element => {
})}
/>
);
};
}
export const Photo = (): JSX.Element => (
<AvatarLightbox
{...createProps({
avatarPath: '/fixtures/kitten-1-64-64.jpg',
})}
/>
);
export function Photo(): JSX.Element {
return (
<AvatarLightbox
{...createProps({
avatarPath: '/fixtures/kitten-1-64-64.jpg',
})}
/>
);
}

View file

@ -17,14 +17,14 @@ export type PropsType = {
onClose: () => unknown;
};
export const AvatarLightbox = ({
export function AvatarLightbox({
avatarColor,
avatarPath,
conversationTitle,
i18n,
isGroup,
onClose,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
return (
<Lightbox close={onClose} i18n={i18n} media={[]}>
<AvatarPreview
@ -43,4 +43,4 @@ export const AvatarLightbox = ({
/>
</Lightbox>
);
};
}

View file

@ -23,21 +23,23 @@ export default {
title: 'Components/AvatarModalButtons',
};
export const HasChanges = (): JSX.Element => (
<AvatarModalButtons
{...createProps({
hasChanges: true,
})}
/>
);
export function HasChanges(): JSX.Element {
return (
<AvatarModalButtons
{...createProps({
hasChanges: true,
})}
/>
);
}
HasChanges.story = {
name: 'Has changes',
};
export const NoChanges = (): JSX.Element => (
<AvatarModalButtons {...createProps()} />
);
export function NoChanges(): JSX.Element {
return <AvatarModalButtons {...createProps()} />;
}
NoChanges.story = {
name: 'No changes',

View file

@ -15,12 +15,12 @@ export type PropsType = {
onSave: () => unknown;
};
export const AvatarModalButtons = ({
export function AvatarModalButtons({
hasChanges,
i18n,
onCancel,
onSave,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [confirmDiscardAction, setConfirmDiscardAction] = useState<
(() => unknown) | undefined
>(undefined);
@ -51,4 +51,4 @@ export const AvatarModalButtons = ({
)}
</Modal.ButtonFooter>
);
};
}

View file

@ -61,54 +61,54 @@ export default {
title: 'Components/Avatar Popup',
};
export const AvatarOnly = (): JSX.Element => {
export function AvatarOnly(): JSX.Element {
const props = useProps();
return <AvatarPopup {...props} />;
};
}
export const HasBadge = (): JSX.Element => {
export function HasBadge(): JSX.Element {
const props = useProps({
badge: getFakeBadge(),
title: 'Janet Yellen',
});
return <AvatarPopup {...props} />;
};
}
HasBadge.story = {
name: 'Has badge',
};
export const Title = (): JSX.Element => {
export function Title(): JSX.Element {
const props = useProps({
title: 'My Great Title',
});
return <AvatarPopup {...props} />;
};
}
export const ProfileName = (): JSX.Element => {
export function ProfileName(): JSX.Element {
const props = useProps({
profileName: 'Sam Neill',
});
return <AvatarPopup {...props} />;
};
}
export const PhoneNumber = (): JSX.Element => {
export function PhoneNumber(): JSX.Element {
const props = useProps({
profileName: 'Sam Neill',
phoneNumber: '(555) 867-5309',
});
return <AvatarPopup {...props} />;
};
}
export const UpdateAvailable = (): JSX.Element => {
export function UpdateAvailable(): JSX.Element {
const props = useProps({
hasPendingUpdate: true,
});
return <AvatarPopup {...props} />;
};
}

View file

@ -28,7 +28,7 @@ export type Props = {
name?: string;
} & Omit<AvatarProps, 'onClick'>;
export const AvatarPopup = (props: Props): JSX.Element => {
export function AvatarPopup(props: Props): JSX.Element {
const {
hasPendingUpdate,
i18n,
@ -121,4 +121,4 @@ export const AvatarPopup = (props: Props): JSX.Element => {
)}
</div>
);
};
}

View file

@ -39,85 +39,97 @@ export default {
title: 'Components/AvatarPreview',
};
export const NoStatePersonal = (): JSX.Element => (
<AvatarPreview
{...createProps({
avatarColor: AvatarColors[0],
conversationTitle: 'Just Testing',
})}
/>
);
export function NoStatePersonal(): JSX.Element {
return (
<AvatarPreview
{...createProps({
avatarColor: AvatarColors[0],
conversationTitle: 'Just Testing',
})}
/>
);
}
NoStatePersonal.story = {
name: 'No state (personal)',
};
export const NoStateGroup = (): JSX.Element => (
<AvatarPreview
{...createProps({
avatarColor: AvatarColors[1],
isGroup: true,
})}
/>
);
export function NoStateGroup(): JSX.Element {
return (
<AvatarPreview
{...createProps({
avatarColor: AvatarColors[1],
isGroup: true,
})}
/>
);
}
NoStateGroup.story = {
name: 'No state (group)',
};
export const NoStateGroupUploadMe = (): JSX.Element => (
<AvatarPreview
{...createProps({
avatarColor: AvatarColors[1],
isEditable: true,
isGroup: true,
})}
/>
);
export function NoStateGroupUploadMe(): JSX.Element {
return (
<AvatarPreview
{...createProps({
avatarColor: AvatarColors[1],
isEditable: true,
isGroup: true,
})}
/>
);
}
NoStateGroupUploadMe.story = {
name: 'No state (group) + upload me',
};
export const Value = (): JSX.Element => (
<AvatarPreview {...createProps({ avatarValue: TEST_IMAGE })} />
);
export function Value(): JSX.Element {
return <AvatarPreview {...createProps({ avatarValue: TEST_IMAGE })} />;
}
Value.story = {
name: 'value',
};
export const Path = (): JSX.Element => (
<AvatarPreview
{...createProps({ avatarPath: '/fixtures/kitten-3-64-64.jpg' })}
/>
);
export function Path(): JSX.Element {
return (
<AvatarPreview
{...createProps({ avatarPath: '/fixtures/kitten-3-64-64.jpg' })}
/>
);
}
Path.story = {
name: 'path',
};
export const ValuePath = (): JSX.Element => (
<AvatarPreview
{...createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
avatarValue: TEST_IMAGE,
})}
/>
);
export function ValuePath(): JSX.Element {
return (
<AvatarPreview
{...createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
avatarValue: TEST_IMAGE,
})}
/>
);
}
ValuePath.story = {
name: 'value & path',
};
export const Style = (): JSX.Element => (
<AvatarPreview
{...createProps({
avatarValue: TEST_IMAGE,
style: { height: 100, width: 100 },
})}
/>
);
export function Style(): JSX.Element {
return (
<AvatarPreview
{...createProps({
avatarValue: TEST_IMAGE,
style: { height: 100, width: 100 },
})}
/>
);
}
Style.story = {
name: 'style',

View file

@ -33,7 +33,7 @@ enum ImageStatus {
HasImage = 'has-image',
}
export const AvatarPreview = ({
export function AvatarPreview({
avatarColor = AvatarColors[0],
avatarPath,
avatarValue,
@ -45,7 +45,7 @@ export const AvatarPreview = ({
onClear,
onClick,
style = {},
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [avatarPreview, setAvatarPreview] = useState<Uint8Array | undefined>();
// Loads the initial avatarPath if one is provided, but only if we're in editable mode.
@ -199,4 +199,4 @@ export const AvatarPreview = ({
</div>
</div>
);
};
}

View file

@ -5,8 +5,8 @@ import type { ReactElement } from 'react';
import React from 'react';
import type { AvatarSize } from './Avatar';
export const AvatarSpacer = ({
export function AvatarSpacer({
size,
}: Readonly<{ size: AvatarSize }>): ReactElement => (
<div style={{ minWidth: size, height: size, width: size }} />
);
}: Readonly<{ size: AvatarSize }>): ReactElement {
return <div style={{ minWidth: size, height: size, width: size }} />;
}

View file

@ -24,35 +24,41 @@ export default {
title: 'Components/AvatarTextEditor',
};
export const Empty = (): JSX.Element => <AvatarTextEditor {...createProps()} />;
export function Empty(): JSX.Element {
return <AvatarTextEditor {...createProps()} />;
}
export const WithData = (): JSX.Element => (
<AvatarTextEditor
{...createProps({
avatarData: {
id: '123',
color: AvatarColors[6],
text: 'SUP',
},
})}
/>
);
export function WithData(): JSX.Element {
return (
<AvatarTextEditor
{...createProps({
avatarData: {
id: '123',
color: AvatarColors[6],
text: 'SUP',
},
})}
/>
);
}
WithData.story = {
name: 'with Data',
};
export const WithWideCharacters = (): JSX.Element => (
<AvatarTextEditor
{...createProps({
avatarData: {
id: '123',
color: AvatarColors[6],
text: '‱௸𒈙',
},
})}
/>
);
export function WithWideCharacters(): JSX.Element {
return (
<AvatarTextEditor
{...createProps({
avatarData: {
id: '123',
color: AvatarColors[6],
text: '‱௸𒈙',
},
})}
/>
);
}
WithWideCharacters.story = {
name: 'with wide characters',

View file

@ -40,12 +40,12 @@ export type PropsType = {
const BUBBLE_SIZE = 120;
const MAX_LENGTH = 3;
export const AvatarTextEditor = ({
export function AvatarTextEditor({
avatarData,
i18n,
onCancel,
onDone,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const initialText = useMemo(() => avatarData?.text || '', [avatarData]);
const initialColor = useMemo(
() => avatarData?.color || AvatarColors[0],
@ -194,4 +194,4 @@ export const AvatarTextEditor = ({
</div>
</>
);
};
}

View file

@ -22,6 +22,6 @@ export default {
title: 'Components/AvatarUploadButton',
};
export const Default = (): JSX.Element => (
<AvatarUploadButton {...createProps()} />
);
export function Default(): JSX.Element {
return <AvatarUploadButton {...createProps()} />;
}

View file

@ -14,11 +14,11 @@ export type PropsType = {
onChange: (avatar: Uint8Array) => unknown;
};
export const AvatarUploadButton = ({
export function AvatarUploadButton({
className,
i18n,
onChange,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const fileInputRef = useRef<null | HTMLInputElement>(null);
const [processingFile, setProcessingFile] = useState<File | undefined>();
@ -84,4 +84,4 @@ export const AvatarUploadButton = ({
/>
</>
);
};
}

View file

@ -9,7 +9,7 @@ type PropsType = {
className?: string;
};
export const BackboneHost = ({ View, className }: PropsType): JSX.Element => {
export function BackboneHost({ View, className }: PropsType): JSX.Element {
const hostRef = useRef<HTMLDivElement | null>(null);
const viewRef = useRef<Backbone.View | undefined>(undefined);
@ -35,4 +35,4 @@ export const BackboneHost = ({ View, className }: PropsType): JSX.Element => {
<div className={className} ref={hostRef} />
</div>
);
};
}

View file

@ -9,24 +9,28 @@ export default {
title: 'Components/BadgeDescription',
};
export const NormalName = (): JSX.Element => (
<BadgeDescription
template="{short_name} is here! Hello, {short_name}! {short_name}, I think you're great. This is not replaced: {not_replaced}"
firstName="Alice"
title="Should not be seen"
/>
);
export function NormalName(): JSX.Element {
return (
<BadgeDescription
template="{short_name} is here! Hello, {short_name}! {short_name}, I think you're great. This is not replaced: {not_replaced}"
firstName="Alice"
title="Should not be seen"
/>
);
}
NormalName.story = {
name: 'Normal name',
};
export const NameWithRtlOverrides = (): JSX.Element => (
<BadgeDescription
template="Hello, {short_name}! {short_name}, I think you're great."
title={'Flip-\u202eflop'}
/>
);
export function NameWithRtlOverrides(): JSX.Element {
return (
<BadgeDescription
template="Hello, {short_name}! {short_name}, I think you're great."
title={'Flip-\u202eflop'}
/>
);
}
NameWithRtlOverrides.story = {
name: 'Name with RTL overrides',

View file

@ -27,108 +27,120 @@ const defaultProps: ComponentProps<typeof BadgeDialog> = {
title: 'Alice Levine',
};
export const NoBadgesClosedImmediately = (): JSX.Element => (
<BadgeDialog {...defaultProps} badges={[]} />
);
export function NoBadgesClosedImmediately(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={[]} />;
}
NoBadgesClosedImmediately.story = {
name: 'No badges (closed immediately)',
};
export const OneBadge = (): JSX.Element => (
<BadgeDialog {...defaultProps} badges={getFakeBadges(1)} />
);
export function OneBadge(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={getFakeBadges(1)} />;
}
OneBadge.story = {
name: 'One badge',
};
export const BadgeWithNoImageShouldBeImpossible = (): JSX.Element => (
<BadgeDialog
{...defaultProps}
badges={[
{
...getFakeBadge(),
images: [],
},
]}
/>
);
export function BadgeWithNoImageShouldBeImpossible(): JSX.Element {
return (
<BadgeDialog
{...defaultProps}
badges={[
{
...getFakeBadge(),
images: [],
},
]}
/>
);
}
BadgeWithNoImageShouldBeImpossible.story = {
name: 'Badge with no image (should be impossible)',
};
export const BadgeWithPendingImage = (): JSX.Element => (
<BadgeDialog
{...defaultProps}
badges={[
{
...getFakeBadge(),
images: Array(4).fill(
zipObject(
Object.values(BadgeImageTheme),
repeat({ url: 'https://example.com/ignored.svg' })
)
),
},
]}
/>
);
BadgeWithPendingImage.story = {
name: 'Badge with pending image',
};
export const BadgeWithOnlyOneLowDetailImage = (): JSX.Element => (
<BadgeDialog
{...defaultProps}
badges={[
{
...getFakeBadge(),
images: [
zipObject(
Object.values(BadgeImageTheme),
repeat({
localPath: '/fixtures/orange-heart.svg',
url: 'https://example.com/ignored.svg',
})
),
...Array(3).fill(
export function BadgeWithPendingImage(): JSX.Element {
return (
<BadgeDialog
{...defaultProps}
badges={[
{
...getFakeBadge(),
images: Array(4).fill(
zipObject(
Object.values(BadgeImageTheme),
repeat({ url: 'https://example.com/ignored.svg' })
)
),
],
},
]}
/>
);
},
]}
/>
);
}
BadgeWithPendingImage.story = {
name: 'Badge with pending image',
};
export function BadgeWithOnlyOneLowDetailImage(): JSX.Element {
return (
<BadgeDialog
{...defaultProps}
badges={[
{
...getFakeBadge(),
images: [
zipObject(
Object.values(BadgeImageTheme),
repeat({
localPath: '/fixtures/orange-heart.svg',
url: 'https://example.com/ignored.svg',
})
),
...Array(3).fill(
zipObject(
Object.values(BadgeImageTheme),
repeat({ url: 'https://example.com/ignored.svg' })
)
),
],
},
]}
/>
);
}
BadgeWithOnlyOneLowDetailImage.story = {
name: 'Badge with only one, low-detail image',
};
export const FiveBadges = (): JSX.Element => (
<BadgeDialog {...defaultProps} badges={getFakeBadges(5)} />
);
export function FiveBadges(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={getFakeBadges(5)} />;
}
FiveBadges.story = {
name: 'Five badges',
};
export const ManyBadges = (): JSX.Element => (
<BadgeDialog {...defaultProps} badges={getFakeBadges(50)} />
);
export function ManyBadges(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={getFakeBadges(50)} />;
}
ManyBadges.story = {
name: 'Many badges',
};
export const ManyBadgesUserIsASubscriber = (): JSX.Element => (
<BadgeDialog {...defaultProps} areWeASubscriber badges={getFakeBadges(50)} />
);
export function ManyBadgesUserIsASubscriber(): JSX.Element {
return (
<BadgeDialog
{...defaultProps}
areWeASubscriber
badges={getFakeBadges(50)}
/>
);
}
ManyBadgesUserIsASubscriber.story = {
name: 'Many badges, user is a subscriber',

View file

@ -30,35 +30,41 @@ export default {
title: 'Components/BetterAvatar',
};
export const Text = (): JSX.Element => (
<BetterAvatar
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[0],
text: 'AH',
}),
})}
/>
);
export function Text(): JSX.Element {
return (
<BetterAvatar
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[0],
text: 'AH',
}),
})}
/>
);
}
export const PersonalIcon = (): JSX.Element => (
<BetterAvatar
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[1],
icon: PersonalAvatarIcons[1],
}),
})}
/>
);
export function PersonalIcon(): JSX.Element {
return (
<BetterAvatar
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[1],
icon: PersonalAvatarIcons[1],
}),
})}
/>
);
}
export const GroupIcon = (): JSX.Element => (
<BetterAvatar
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[1],
icon: GroupAvatarIcons[1],
}),
})}
/>
);
export function GroupIcon(): JSX.Element {
return (
<BetterAvatar
{...createProps({
avatarData: createAvatarData({
color: AvatarColors[1],
icon: GroupAvatarIcons[1],
}),
})}
/>
);
}

View file

@ -21,14 +21,14 @@ export type PropsType = {
size?: AvatarSize;
};
export const BetterAvatar = ({
export function BetterAvatar({
avatarData,
i18n,
isSelected,
onClick,
onDelete,
size = 48,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>(
avatarData.buffer
);
@ -115,4 +115,4 @@ export const BetterAvatar = ({
)}
</BetterAvatarBubble>
);
};
}

View file

@ -27,32 +27,38 @@ export default {
title: 'Components/BetterAvatarBubble',
};
export const Children = (): JSX.Element => (
<BetterAvatarBubble
{...createProps({
children: <div>HI</div>,
color: AvatarColors[8],
})}
/>
);
export function Children(): JSX.Element {
return (
<BetterAvatarBubble
{...createProps({
children: <div>HI</div>,
color: AvatarColors[8],
})}
/>
);
}
export const Selected = (): JSX.Element => (
<BetterAvatarBubble
{...createProps({
color: AvatarColors[1],
isSelected: true,
})}
/>
);
export function Selected(): JSX.Element {
return (
<BetterAvatarBubble
{...createProps({
color: AvatarColors[1],
isSelected: true,
})}
/>
);
}
export const Style = (): JSX.Element => (
<BetterAvatarBubble
{...createProps({
style: {
height: 120,
width: 120,
},
color: AvatarColors[2],
})}
/>
);
export function Style(): JSX.Element {
return (
<BetterAvatarBubble
{...createProps({
style: {
height: 120,
width: 120,
},
color: AvatarColors[2],
})}
/>
);
}

View file

@ -18,7 +18,7 @@ export type PropsType = {
style?: CSSProperties;
};
export const BetterAvatarBubble = ({
export function BetterAvatarBubble({
children,
color,
i18n,
@ -26,7 +26,7 @@ export const BetterAvatarBubble = ({
onDelete,
onSelect,
style,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
return (
<div
className={classNames(
@ -58,4 +58,4 @@ export const BetterAvatarBubble = ({
{children}
</div>
);
};
}

View file

@ -10,55 +10,65 @@ export default {
title: 'Components/Button',
};
export const KitchenSink = (): JSX.Element => (
<>
{Object.values(ButtonVariant).map(variant => (
<React.Fragment key={variant}>
{[ButtonSize.Large, ButtonSize.Medium, ButtonSize.Small].map(size => (
<React.Fragment key={size}>
<p>
<Button onClick={action('onClick')} size={size} variant={variant}>
{variant}
</Button>
</p>
<p>
<Button
disabled
onClick={action('onClick')}
size={size}
variant={variant}
>
{variant}
</Button>
</p>
</React.Fragment>
))}
</React.Fragment>
))}
</>
);
export function KitchenSink(): JSX.Element {
return (
<>
{Object.values(ButtonVariant).map(variant => (
<React.Fragment key={variant}>
{[ButtonSize.Large, ButtonSize.Medium, ButtonSize.Small].map(size => (
<React.Fragment key={size}>
<p>
<Button
onClick={action('onClick')}
size={size}
variant={variant}
>
{variant}
</Button>
</p>
<p>
<Button
disabled
onClick={action('onClick')}
size={size}
variant={variant}
>
{variant}
</Button>
</p>
</React.Fragment>
))}
</React.Fragment>
))}
</>
);
}
KitchenSink.story = {
name: 'Kitchen sink',
};
export const AriaLabel = (): JSX.Element => (
<Button
aria-label="hello"
className="module-ForwardMessageModal__header--back"
onClick={action('onClick')}
/>
);
export function AriaLabel(): JSX.Element {
return (
<Button
aria-label="hello"
className="module-ForwardMessageModal__header--back"
onClick={action('onClick')}
/>
);
}
AriaLabel.story = {
name: 'aria-label',
};
export const CustomStyles = (): JSX.Element => (
<Button onClick={action('onClick')} style={{ transform: 'rotate(5deg)' }}>
Hello world
</Button>
);
export function CustomStyles(): JSX.Element {
return (
<Button onClick={action('onClick')} style={{ transform: 'rotate(5deg)' }}>
Hello world
</Button>
);
}
CustomStyles.story = {
name: 'Custom styles',

View file

@ -100,7 +100,7 @@ const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([
]);
export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
(props, ref) => {
function ButtonInner(props, ref) {
const {
children,
className,

View file

@ -12,12 +12,12 @@ export type PropsType = {
color?: AvatarColorType;
};
export const CallBackgroundBlur = ({
export function CallBackgroundBlur({
avatarPath,
children,
className,
color,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
return (
<div
className={classNames(
@ -39,4 +39,4 @@ export const CallBackgroundBlur = ({
{children}
</div>
);
};
}

View file

@ -121,128 +121,142 @@ export default {
title: 'Components/CallManager',
};
export const NoCall = (): JSX.Element => <CallManager {...createProps()} />;
export function NoCall(): JSX.Element {
return <CallManager {...createProps()} />;
}
export const OngoingDirectCall = (): JSX.Element => (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Direct,
callState: CallState.Accepted,
peekedParticipants: [],
remoteParticipants: [
{ hasRemoteVideo: true, presenting: false, title: 'Remy' },
],
},
})}
/>
);
export function OngoingDirectCall(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Direct,
callState: CallState.Accepted,
peekedParticipants: [],
remoteParticipants: [
{ hasRemoteVideo: true, presenting: false, title: 'Remy' },
],
},
})}
/>
);
}
export const OngoingGroupCall = (): JSX.Element => (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [],
deviceCount: 0,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
groupMembers: [],
isConversationTooBigToRing: false,
peekedParticipants: [],
remoteParticipants: [],
remoteAudioLevels: new Map<number, number>(),
},
})}
/>
);
export function OngoingGroupCall(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [],
deviceCount: 0,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
groupMembers: [],
isConversationTooBigToRing: false,
peekedParticipants: [],
remoteParticipants: [],
remoteAudioLevels: new Map<number, number>(),
},
})}
/>
);
}
export const RingingDirectCall = (): JSX.Element => (
<CallManager
{...createProps({
incomingCall: {
callMode: CallMode.Direct as const,
conversation: getConversation(),
isVideoCall: true,
},
})}
/>
);
export function RingingDirectCall(): JSX.Element {
return (
<CallManager
{...createProps({
incomingCall: {
callMode: CallMode.Direct as const,
conversation: getConversation(),
isVideoCall: true,
},
})}
/>
);
}
RingingDirectCall.story = {
name: 'Ringing (direct call)',
};
export const RingingGroupCall = (): JSX.Element => (
<CallManager
{...createProps({
incomingCall: {
callMode: CallMode.Group as const,
conversation: {
...getConversation(),
type: 'group',
title: 'Tahoe Trip',
export function RingingGroupCall(): JSX.Element {
return (
<CallManager
{...createProps({
incomingCall: {
callMode: CallMode.Group as const,
conversation: {
...getConversation(),
type: 'group',
title: 'Tahoe Trip',
},
otherMembersRung: [
{ firstName: 'Morty', title: 'Morty Smith' },
{ firstName: 'Summer', title: 'Summer Smith' },
],
ringer: { firstName: 'Rick', title: 'Rick Sanchez' },
},
otherMembersRung: [
{ firstName: 'Morty', title: 'Morty Smith' },
{ firstName: 'Summer', title: 'Summer Smith' },
],
ringer: { firstName: 'Rick', title: 'Rick Sanchez' },
},
})}
/>
);
})}
/>
);
}
RingingGroupCall.story = {
name: 'Ringing (group call)',
};
export const CallRequestNeeded = (): JSX.Element => (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callEndedReason: CallEndedReason.RemoteHangupNeedPermission,
callMode: CallMode.Direct,
callState: CallState.Accepted,
peekedParticipants: [],
remoteParticipants: [
{ hasRemoteVideo: true, presenting: false, title: 'Mike' },
],
},
})}
/>
);
export function CallRequestNeeded(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callEndedReason: CallEndedReason.RemoteHangupNeedPermission,
callMode: CallMode.Direct,
callState: CallState.Accepted,
peekedParticipants: [],
remoteParticipants: [
{ hasRemoteVideo: true, presenting: false, title: 'Mike' },
],
},
})}
/>
);
}
export const GroupCallSafetyNumberChanged = (): JSX.Element => (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [
{
...getDefaultConversation({
title: 'Aaron',
}),
},
],
deviceCount: 0,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
groupMembers: [],
isConversationTooBigToRing: false,
peekedParticipants: [],
remoteParticipants: [],
remoteAudioLevels: new Map<number, number>(),
},
})}
/>
);
export function GroupCallSafetyNumberChanged(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [
{
...getDefaultConversation({
title: 'Aaron',
}),
},
],
deviceCount: 0,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
groupMembers: [],
isConversationTooBigToRing: false,
peekedParticipants: [],
remoteParticipants: [],
remoteAudioLevels: new Map<number, number>(),
},
})}
/>
);
}
GroupCallSafetyNumberChanged.story = {
name: 'Group call - Safety Number Changed',

View file

@ -105,7 +105,7 @@ type ActiveCallManagerPropsType = PropsType & {
activeCall: ActiveCallType;
};
const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
function ActiveCallManager({
activeCall,
availableCameras,
cancelCall,
@ -137,7 +137,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
toggleScreenRecordingPermissionsDialog,
toggleSettings,
toggleSpeakerView,
}) => {
}: ActiveCallManagerPropsType): JSX.Element {
const {
conversation,
hasLocalAudio,
@ -374,9 +374,9 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
) : null}
</>
);
};
}
export const CallManager: React.FC<PropsType> = props => {
export function CallManager(props: PropsType): JSX.Element | null {
const {
acceptCall,
activeCall,
@ -449,7 +449,7 @@ export const CallManager: React.FC<PropsType> = props => {
}
return null;
};
}
function getShouldRing({
activeCall,

View file

@ -29,11 +29,11 @@ type Props = {
const AUTO_CLOSE_MS = 10000;
export const CallNeedPermissionScreen: React.FC<Props> = ({
export function CallNeedPermissionScreen({
conversation,
i18n,
close,
}) => {
}: Props): JSX.Element {
const title = conversation.title || i18n('unknownContact');
const autoCloseAtRef = useRef<number>(Date.now() + AUTO_CLOSE_MS);
@ -79,4 +79,4 @@ export const CallNeedPermissionScreen: React.FC<Props> = ({
</button>
</div>
);
};
}

View file

@ -189,11 +189,11 @@ export default {
title: 'Components/CallScreen',
};
export const Default = (): JSX.Element => {
export function Default(): JSX.Element {
return <CallScreen {...createProps()} />;
};
}
export const PreRing = (): JSX.Element => {
export function PreRing(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -202,7 +202,7 @@ export const PreRing = (): JSX.Element => {
})}
/>
);
};
}
PreRing.story = {
name: 'Pre-Ring',
@ -241,7 +241,7 @@ export const _Ended = (): JSX.Element => {
);
};
export const HasLocalAudio = (): JSX.Element => {
export function HasLocalAudio(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -250,13 +250,13 @@ export const HasLocalAudio = (): JSX.Element => {
})}
/>
);
};
}
HasLocalAudio.story = {
name: 'hasLocalAudio',
};
export const HasLocalVideo = (): JSX.Element => {
export function HasLocalVideo(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -265,13 +265,13 @@ export const HasLocalVideo = (): JSX.Element => {
})}
/>
);
};
}
HasLocalVideo.story = {
name: 'hasLocalVideo',
};
export const HasRemoteVideo = (): JSX.Element => {
export function HasRemoteVideo(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -280,34 +280,36 @@ export const HasRemoteVideo = (): JSX.Element => {
})}
/>
);
};
}
HasRemoteVideo.story = {
name: 'hasRemoteVideo',
};
export const GroupCall1 = (): JSX.Element => (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: [
{
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
presenting: false,
sharingScreen: false,
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
uuid: '72fa60e5-25fb-472d-8a56-e56867c57dda',
title: 'Tyler',
}),
},
],
})}
/>
);
export function GroupCall1(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: [
{
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
presenting: false,
sharingScreen: false,
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
uuid: '72fa60e5-25fb-472d-8a56-e56867c57dda',
title: 'Tyler',
}),
},
],
})}
/>
);
}
GroupCall1.story = {
name: 'Group call - 1',
@ -327,7 +329,7 @@ const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({
}),
}));
export const GroupCallMany = (): JSX.Element => {
export function GroupCallMany(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -344,74 +346,80 @@ export const GroupCallMany = (): JSX.Element => {
})}
/>
);
};
}
GroupCallMany.story = {
name: 'Group call - Many',
};
export const GroupCallReconnecting = (): JSX.Element => (
<CallScreen
{...createProps({
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Reconnecting,
remoteParticipants: [
{
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
presenting: false,
sharingScreen: false,
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
title: 'Tyler',
uuid: '33871c64-0c22-45ce-8aa4-0ec237ac4a31',
}),
},
],
})}
/>
);
export function GroupCallReconnecting(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Reconnecting,
remoteParticipants: [
{
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
presenting: false,
sharingScreen: false,
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
title: 'Tyler',
uuid: '33871c64-0c22-45ce-8aa4-0ec237ac4a31',
}),
},
],
})}
/>
);
}
GroupCallReconnecting.story = {
name: 'Group call - reconnecting',
};
export const GroupCall0 = (): JSX.Element => (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: [],
})}
/>
);
export function GroupCall0(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: [],
})}
/>
);
}
GroupCall0.story = {
name: 'Group call - 0',
};
export const GroupCallSomeoneIsSharingScreen = (): JSX.Element => (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
presenting: index === 1,
sharingScreen: index === 1,
})),
})}
/>
);
export function GroupCallSomeoneIsSharingScreen(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
presenting: index === 1,
sharingScreen: index === 1,
})),
})}
/>
);
}
GroupCallSomeoneIsSharingScreen.story = {
name: 'Group call - someone is sharing screen',
};
export const GroupCallSomeoneIsSharingScreenAndYoureReconnecting =
(): JSX.Element => (
export function GroupCallSomeoneIsSharingScreenAndYoureReconnecting(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
@ -426,6 +434,7 @@ export const GroupCallSomeoneIsSharingScreenAndYoureReconnecting =
})}
/>
);
}
GroupCallSomeoneIsSharingScreenAndYoureReconnecting.story = {
name: "Group call - someone is sharing screen and you're reconnecting",

View file

@ -119,7 +119,7 @@ function DirectCallHeaderMessage({
return null;
}
export const CallScreen: React.FC<PropsType> = ({
export function CallScreen({
activeCall,
getGroupCallVideoFrameSource,
getPresentingSources,
@ -143,7 +143,7 @@ export const CallScreen: React.FC<PropsType> = ({
toggleScreenRecordingPermissionsDialog,
toggleSettings,
toggleSpeakerView,
}) => {
}: PropsType): JSX.Element {
const {
conversation,
hasLocalAudio,
@ -539,7 +539,7 @@ export const CallScreen: React.FC<PropsType> = ({
</div>
</div>
);
};
}
function getCallModeClassSuffix(
callMode: CallMode.Direct | CallMode.Group

View file

@ -11,7 +11,7 @@ export default {
title: 'Components/CallingAudioIndicator',
};
export const Extreme = (): JSX.Element => {
export function Extreme(): JSX.Element {
const [audioLevel, setAudioLevel] = useState(1);
useEffect(() => {
@ -30,9 +30,9 @@ export const Extreme = (): JSX.Element => {
audioLevel={audioLevel}
/>
);
};
}
export const Random = (): JSX.Element => {
export function Random(): JSX.Element {
const [audioLevel, setAudioLevel] = useState(1);
useEffect(() => {
@ -51,4 +51,4 @@ export const Random = (): JSX.Element => {
audioLevel={audioLevel}
/>
);
};
}

View file

@ -32,7 +32,7 @@ export default {
title: 'Components/CallingButton',
};
export const KitchenSink = (): JSX.Element => {
export function KitchenSink(): JSX.Element {
return (
<>
{Object.keys(CallingButtonType).map(buttonType => (
@ -43,71 +43,71 @@ export const KitchenSink = (): JSX.Element => {
))}
</>
);
};
}
export const AudioOn = (): JSX.Element => {
export function AudioOn(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.AUDIO_ON,
});
return <CallingButton {...props} />;
};
}
export const AudioOff = (): JSX.Element => {
export function AudioOff(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.AUDIO_OFF,
});
return <CallingButton {...props} />;
};
}
export const AudioDisabled = (): JSX.Element => {
export function AudioDisabled(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.AUDIO_DISABLED,
});
return <CallingButton {...props} />;
};
}
export const VideoOn = (): JSX.Element => {
export function VideoOn(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.VIDEO_ON,
});
return <CallingButton {...props} />;
};
}
export const VideoOff = (): JSX.Element => {
export function VideoOff(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.VIDEO_OFF,
});
return <CallingButton {...props} />;
};
}
export const VideoDisabled = (): JSX.Element => {
export function VideoDisabled(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.VIDEO_DISABLED,
});
return <CallingButton {...props} />;
};
}
export const TooltipRight = (): JSX.Element => {
export function TooltipRight(): JSX.Element {
const props = createProps({
tooltipDirection: TooltipPlacement.Right,
});
return <CallingButton {...props} />;
};
}
TooltipRight.story = {
name: 'Tooltip right',
};
export const PresentingOn = (): JSX.Element => {
export function PresentingOn(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.PRESENTING_ON,
});
return <CallingButton {...props} />;
};
}
export const PresentingOff = (): JSX.Element => {
export function PresentingOff(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.PRESENTING_OFF,
});
return <CallingButton {...props} />;
};
}

View file

@ -35,7 +35,7 @@ export type PropsType = {
tooltipDirection?: TooltipPlacement;
};
export const CallingButton = ({
export function CallingButton({
buttonType,
i18n,
isVisible = true,
@ -43,7 +43,7 @@ export const CallingButton = ({
onMouseEnter,
onMouseLeave,
tooltipDirection,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const uniqueButtonId = useMemo(() => uuid(), []);
let classNameSuffix = '';
@ -145,4 +145,4 @@ export const CallingButton = ({
</div>
</Tooltip>
);
};
}

View file

@ -41,11 +41,11 @@ export default {
title: 'Components/CallingDeviceSelection',
};
export const Default = (): JSX.Element => {
export function Default(): JSX.Element {
return <CallingDeviceSelection {...createProps()} />;
};
}
export const SomeDevices = (): JSX.Element => {
export function SomeDevices(): JSX.Element {
const availableSpeakers = [
{
name: 'Default',
@ -72,9 +72,9 @@ export const SomeDevices = (): JSX.Element => {
});
return <CallingDeviceSelection {...props} />;
};
}
export const DefaultDevices = (): JSX.Element => {
export function DefaultDevices(): JSX.Element {
const availableSpeakers = [
{
name: 'default (Headphones)',
@ -103,9 +103,9 @@ export const DefaultDevices = (): JSX.Element => {
});
return <CallingDeviceSelection {...props} />;
};
}
export const AllDevices = (): JSX.Element => {
export function AllDevices(): JSX.Element {
const availableSpeakers = [
{
name: 'Default',
@ -179,4 +179,4 @@ export const AllDevices = (): JSX.Element => {
});
return <CallingDeviceSelection {...props} />;
};
}

View file

@ -116,7 +116,7 @@ function createCameraChangeHandler(
};
}
export const CallingDeviceSelection = ({
export function CallingDeviceSelection({
availableCameras,
availableMicrophones,
availableSpeakers,
@ -126,7 +126,7 @@ export const CallingDeviceSelection = ({
selectedMicrophone,
selectedSpeaker,
toggleSettings,
}: Props): JSX.Element => {
}: Props): JSX.Element {
const selectedMicrophoneIndex = selectedMicrophone
? selectedMicrophone.index
: undefined;
@ -212,4 +212,4 @@ export const CallingDeviceSelection = ({
</div>
</Modal>
);
};
}

View file

@ -34,61 +34,73 @@ export default {
title: 'Components/CallingHeader',
};
export const Default = (): JSX.Element => <CallingHeader {...createProps()} />;
export function Default(): JSX.Element {
return <CallingHeader {...createProps()} />;
}
export const LobbyStyle = (): JSX.Element => (
<CallingHeader
{...createProps()}
title={undefined}
togglePip={undefined}
onCancel={action('onClose')}
/>
);
export function LobbyStyle(): JSX.Element {
return (
<CallingHeader
{...createProps()}
title={undefined}
togglePip={undefined}
onCancel={action('onClose')}
/>
);
}
LobbyStyle.story = {
name: 'Lobby style',
};
export const WithParticipants = (): JSX.Element => (
<CallingHeader
{...createProps({
isGroupCall: true,
participantCount: 10,
})}
/>
);
export function WithParticipants(): JSX.Element {
return (
<CallingHeader
{...createProps({
isGroupCall: true,
participantCount: 10,
})}
/>
);
}
export const WithParticipantsShown = (): JSX.Element => (
<CallingHeader
{...createProps({
isGroupCall: true,
participantCount: 10,
showParticipantsList: true,
})}
/>
);
export function WithParticipantsShown(): JSX.Element {
return (
<CallingHeader
{...createProps({
isGroupCall: true,
participantCount: 10,
showParticipantsList: true,
})}
/>
);
}
WithParticipantsShown.story = {
name: 'With Participants (shown)',
};
export const LongTitle = (): JSX.Element => (
<CallingHeader
{...createProps({
title:
'What do I got to, what do I got to do to wake you up? To shake you up, to break the structure up?',
})}
/>
);
export function LongTitle(): JSX.Element {
return (
<CallingHeader
{...createProps({
title:
'What do I got to, what do I got to do to wake you up? To shake you up, to break the structure up?',
})}
/>
);
}
export const TitleWithMessage = (): JSX.Element => (
<CallingHeader
{...createProps({
title: 'Hello world',
message: 'Goodbye earth',
})}
/>
);
export function TitleWithMessage(): JSX.Element {
return (
<CallingHeader
{...createProps({
title: 'Hello world',
message: 'Goodbye earth',
})}
/>
);
}
TitleWithMessage.story = {
name: 'Title with message',

View file

@ -23,7 +23,7 @@ export type PropsType = {
toggleSpeakerView?: () => void;
};
export const CallingHeader = ({
export function CallingHeader({
i18n,
isInSpeakerView,
isGroupCall = false,
@ -36,103 +36,110 @@ export const CallingHeader = ({
togglePip,
toggleSettings,
toggleSpeakerView,
}: PropsType): JSX.Element => (
<div className="module-calling__header">
{title ? (
<div className="module-calling__header--header-name">{title}</div>
) : null}
{message ? (
<div className="module-ongoing-call__header-message">{message}</div>
) : null}
<div className="module-calling-tools">
{isGroupCall && participantCount ? (
<div className="module-calling-tools__button">
<Tooltip
content={i18n('calling__participants', [String(participantCount)])}
theme={Theme.Dark}
>
<button
aria-label={i18n('calling__participants', [
}: PropsType): JSX.Element {
return (
<div className="module-calling__header">
{title ? (
<div className="module-calling__header--header-name">{title}</div>
) : null}
{message ? (
<div className="module-ongoing-call__header-message">{message}</div>
) : null}
<div className="module-calling-tools">
{isGroupCall && participantCount ? (
<div className="module-calling-tools__button">
<Tooltip
content={i18n('calling__participants', [
String(participantCount),
])}
className={classNames('CallingButton__participants--container', {
'CallingButton__participants--shown': showParticipantsList,
})}
onClick={toggleParticipants}
type="button"
theme={Theme.Dark}
>
<i className="CallingButton__participants" />
<span className="CallingButton__participants--count">
{participantCount}
</span>
</button>
</Tooltip>
</div>
) : null}
<div className="module-calling-tools__button">
<Tooltip
content={i18n('callingDeviceSelection__settings')}
theme={Theme.Dark}
>
<button
aria-label={i18n('callingDeviceSelection__settings')}
className="CallingButton__settings"
onClick={toggleSettings}
type="button"
/>
</Tooltip>
</div>
{isGroupCall && participantCount > 2 && toggleSpeakerView && (
<button
aria-label={i18n('calling__participants', [
String(participantCount),
])}
className={classNames(
'CallingButton__participants--container',
{
'CallingButton__participants--shown': showParticipantsList,
}
)}
onClick={toggleParticipants}
type="button"
>
<i className="CallingButton__participants" />
<span className="CallingButton__participants--count">
{participantCount}
</span>
</button>
</Tooltip>
</div>
) : null}
<div className="module-calling-tools__button">
<Tooltip
content={i18n(
isInSpeakerView
? 'calling__switch-view--to-grid'
: 'calling__switch-view--to-speaker'
)}
content={i18n('callingDeviceSelection__settings')}
theme={Theme.Dark}
>
<button
aria-label={i18n(
aria-label={i18n('callingDeviceSelection__settings')}
className="CallingButton__settings"
onClick={toggleSettings}
type="button"
/>
</Tooltip>
</div>
{isGroupCall && participantCount > 2 && toggleSpeakerView && (
<div className="module-calling-tools__button">
<Tooltip
content={i18n(
isInSpeakerView
? 'calling__switch-view--to-grid'
: 'calling__switch-view--to-speaker'
)}
className={
isInSpeakerView
? 'CallingButton__grid-view'
: 'CallingButton__speaker-view'
}
onClick={toggleSpeakerView}
type="button"
/>
</Tooltip>
</div>
)}
{togglePip && (
<div className="module-calling-tools__button">
<Tooltip content={i18n('calling__pip--on')} theme={Theme.Dark}>
<button
aria-label={i18n('calling__pip--on')}
className="CallingButton__pip"
onClick={togglePip}
type="button"
/>
</Tooltip>
</div>
)}
{onCancel && (
<div className="module-calling-tools__button">
<Tooltip content={i18n('cancel')} theme={Theme.Dark}>
<button
aria-label={i18n('cancel')}
className="CallingButton__cancel"
onClick={onCancel}
type="button"
/>
</Tooltip>
</div>
)}
theme={Theme.Dark}
>
<button
aria-label={i18n(
isInSpeakerView
? 'calling__switch-view--to-grid'
: 'calling__switch-view--to-speaker'
)}
className={
isInSpeakerView
? 'CallingButton__grid-view'
: 'CallingButton__speaker-view'
}
onClick={toggleSpeakerView}
type="button"
/>
</Tooltip>
</div>
)}
{togglePip && (
<div className="module-calling-tools__button">
<Tooltip content={i18n('calling__pip--on')} theme={Theme.Dark}>
<button
aria-label={i18n('calling__pip--on')}
className="CallingButton__pip"
onClick={togglePip}
type="button"
/>
</Tooltip>
</div>
)}
{onCancel && (
<div className="module-calling-tools__button">
<Tooltip content={i18n('cancel')} theme={Theme.Dark}>
<button
aria-label={i18n('cancel')}
className="CallingButton__cancel"
onClick={onCancel}
type="button"
/>
</Tooltip>
</div>
)}
</div>
</div>
</div>
);
);
}

View file

@ -94,23 +94,23 @@ export default {
title: 'Components/CallingLobby',
};
export const Default = (): JSX.Element => {
export function Default(): JSX.Element {
const props = createProps();
return <CallingLobby {...props} />;
};
}
export const NoCameraNoAvatar = (): JSX.Element => {
export function NoCameraNoAvatar(): JSX.Element {
const props = createProps({
availableCameras: [],
});
return <CallingLobby {...props} />;
};
}
NoCameraNoAvatar.story = {
name: 'No Camera, no avatar',
};
export const NoCameraLocalAvatar = (): JSX.Element => {
export function NoCameraLocalAvatar(): JSX.Element {
const props = createProps({
availableCameras: [],
me: getDefaultConversation({
@ -121,52 +121,52 @@ export const NoCameraLocalAvatar = (): JSX.Element => {
}),
});
return <CallingLobby {...props} />;
};
}
NoCameraLocalAvatar.story = {
name: 'No Camera, local avatar',
};
export const LocalVideo = (): JSX.Element => {
export function LocalVideo(): JSX.Element {
const props = createProps({
hasLocalVideo: true,
});
return <CallingLobby {...props} />;
};
}
export const InitiallyMuted = (): JSX.Element => {
export function InitiallyMuted(): JSX.Element {
const props = createProps({
hasLocalAudio: false,
});
return <CallingLobby {...props} />;
};
}
InitiallyMuted.story = {
name: 'Initially muted',
};
export const GroupCall0PeekedParticipants = (): JSX.Element => {
export function GroupCall0PeekedParticipants(): JSX.Element {
const props = createProps({ isGroupCall: true, peekedParticipants: [] });
return <CallingLobby {...props} />;
};
}
GroupCall0PeekedParticipants.story = {
name: 'Group Call - 0 peeked participants',
};
export const GroupCall1PeekedParticipant = (): JSX.Element => {
export function GroupCall1PeekedParticipant(): JSX.Element {
const props = createProps({
isGroupCall: true,
peekedParticipants: [{ title: 'Sam' }].map(fakePeekedParticipant),
});
return <CallingLobby {...props} />;
};
}
GroupCall1PeekedParticipant.story = {
name: 'Group Call - 1 peeked participant',
};
export const GroupCall1PeekedParticipantSelf = (): JSX.Element => {
export function GroupCall1PeekedParticipantSelf(): JSX.Element {
const uuid = UUID.generate().toString();
const props = createProps({
isGroupCall: true,
@ -177,13 +177,13 @@ export const GroupCall1PeekedParticipantSelf = (): JSX.Element => {
peekedParticipants: [fakePeekedParticipant({ title: 'Ash', uuid })],
});
return <CallingLobby {...props} />;
};
}
GroupCall1PeekedParticipantSelf.story = {
name: 'Group Call - 1 peeked participant (self)',
};
export const GroupCall4PeekedParticipants = (): JSX.Element => {
export function GroupCall4PeekedParticipants(): JSX.Element {
const props = createProps({
isGroupCall: true,
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
@ -191,13 +191,13 @@ export const GroupCall4PeekedParticipants = (): JSX.Element => {
),
});
return <CallingLobby {...props} />;
};
}
GroupCall4PeekedParticipants.story = {
name: 'Group Call - 4 peeked participants',
};
export const GroupCall4PeekedParticipantsParticipantsList = (): JSX.Element => {
export function GroupCall4PeekedParticipantsParticipantsList(): JSX.Element {
const props = createProps({
isGroupCall: true,
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
@ -206,13 +206,13 @@ export const GroupCall4PeekedParticipantsParticipantsList = (): JSX.Element => {
showParticipantsList: true,
});
return <CallingLobby {...props} />;
};
}
GroupCall4PeekedParticipantsParticipantsList.story = {
name: 'Group Call - 4 peeked participants (participants list)',
};
export const GroupCallCallFull = (): JSX.Element => {
export function GroupCallCallFull(): JSX.Element {
const props = createProps({
isGroupCall: true,
isCallFull: true,
@ -221,19 +221,19 @@ export const GroupCallCallFull = (): JSX.Element => {
),
});
return <CallingLobby {...props} />;
};
}
GroupCallCallFull.story = {
name: 'Group Call - call full',
};
export const GroupCall0PeekedParticipantsBigGroup = (): JSX.Element => {
export function GroupCall0PeekedParticipantsBigGroup(): JSX.Element {
const props = createProps({
isGroupCall: true,
groupMembers: times(100, () => getDefaultConversation()),
});
return <CallingLobby {...props} />;
};
}
GroupCall0PeekedParticipantsBigGroup.story = {
name: 'Group Call - 0 peeked participants, big group',

View file

@ -63,7 +63,7 @@ export type PropsType = {
toggleSettings: () => void;
};
export const CallingLobby = ({
export function CallingLobby({
availableCameras,
conversation,
groupMembers,
@ -86,7 +86,7 @@ export const CallingLobby = ({
toggleParticipants,
toggleSettings,
outgoingRing,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [isMutedToastVisible, setIsMutedToastVisible] = React.useState(
!hasLocalAudio
);
@ -303,4 +303,4 @@ export const CallingLobby = ({
</div>
</FocusTrap>
);
};
}

View file

@ -1,7 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent, ReactChild } from 'react';
import type { ReactChild } from 'react';
import React, { useState } from 'react';
import { noop } from 'lodash';
@ -19,6 +19,13 @@ export enum CallingLobbyJoinButtonVariant {
Start = 'Start',
}
type PropsType = {
disabled?: boolean;
i18n: LocalizerType;
onClick: () => void;
variant: CallingLobbyJoinButtonVariant;
};
/**
* This component is a little weird. Why not just render a button with some children?
*
@ -29,12 +36,12 @@ export enum CallingLobbyJoinButtonVariant {
* For example, we might initially render "Join call" and then render a spinner when you
* click the button. The button shouldn't resize in that situation.
*/
export const CallingLobbyJoinButton: FunctionComponent<{
disabled?: boolean;
i18n: LocalizerType;
onClick: () => void;
variant: CallingLobbyJoinButtonVariant;
}> = ({ disabled, i18n, onClick, variant }) => {
export function CallingLobbyJoinButton({
disabled,
i18n,
onClick,
variant,
}: PropsType): JSX.Element {
const [width, setWidth] = useState<undefined | number>();
const [height, setHeight] = useState<undefined | number>();
@ -103,4 +110,4 @@ export const CallingLobbyJoinButton: FunctionComponent<{
</div>
</>
);
};
}

View file

@ -47,16 +47,16 @@ export default {
title: 'Components/CallingParticipantsList',
};
export const NoOne = (): JSX.Element => {
export function NoOne(): JSX.Element {
const props = createProps();
return <CallingParticipantsList {...props} />;
};
}
NoOne.story = {
name: 'No one',
};
export const SoloCall = (): JSX.Element => {
export function SoloCall(): JSX.Element {
const props = createProps({
participants: [
createParticipant({
@ -65,9 +65,9 @@ export const SoloCall = (): JSX.Element => {
],
});
return <CallingParticipantsList {...props} />;
};
}
export const ManyParticipants = (): JSX.Element => {
export function ManyParticipants(): JSX.Element {
const props = createProps({
participants: [
createParticipant({
@ -95,13 +95,13 @@ export const ManyParticipants = (): JSX.Element => {
],
});
return <CallingParticipantsList {...props} />;
};
}
export const Overflow = (): JSX.Element => {
export function Overflow(): JSX.Element {
const props = createProps({
participants: Array(50)
.fill(null)
.map(() => createParticipant({ title: 'Kirby' })),
});
return <CallingParticipantsList {...props} />;
};
}

View file

@ -30,7 +30,12 @@ export type PropsType = {
};
export const CallingParticipantsList = React.memo(
({ i18n, onClose, ourUuid, participants }: PropsType) => {
function CallingParticipantsListInner({
i18n,
onClose,
ourUuid,
participants,
}: PropsType) {
const [root, setRoot] = React.useState<HTMLElement | null>(null);
const modalContainer = useContext(ModalContainerContext) ?? document.body;

View file

@ -10,7 +10,7 @@ import { AvatarColors } from '../types/Colors';
import type { ConversationType } from '../state/ducks/conversations';
import type { PropsType } from './CallingPip';
import { CallingPip } from './CallingPip';
import type { ActiveCallType } from '../types/Calling';
import type { ActiveDirectCallType } from '../types/Calling';
import {
CallMode,
CallViewMode,
@ -52,7 +52,7 @@ const getCommonActiveCallData = () => ({
showParticipantsList: false,
});
const defaultCall: ActiveCallType = {
const defaultCall: ActiveDirectCallType = {
...getCommonActiveCallData(),
callMode: CallMode.Direct as CallMode.Direct,
callState: CallState.Accepted,
@ -80,12 +80,12 @@ export default {
title: 'Components/CallingPip',
};
export const Default = (): JSX.Element => {
export function Default(): JSX.Element {
const props = createProps({});
return <CallingPip {...props} />;
};
}
export const ContactWithAvatarAndNoVideo = (): JSX.Element => {
export function ContactWithAvatarAndNoVideo(): JSX.Element {
const props = createProps({
activeCall: {
...defaultCall,
@ -99,13 +99,13 @@ export const ContactWithAvatarAndNoVideo = (): JSX.Element => {
},
});
return <CallingPip {...props} />;
};
}
ContactWithAvatarAndNoVideo.story = {
name: 'Contact (with avatar and no video)',
};
export const ContactNoColor = (): JSX.Element => {
export function ContactNoColor(): JSX.Element {
const props = createProps({
activeCall: {
...defaultCall,
@ -116,13 +116,13 @@ export const ContactNoColor = (): JSX.Element => {
},
});
return <CallingPip {...props} />;
};
}
ContactNoColor.story = {
name: 'Contact (no color)',
};
export const GroupCall = (): JSX.Element => {
export function GroupCall(): JSX.Element {
const props = createProps({
activeCall: {
...getCommonActiveCallData(),
@ -140,4 +140,4 @@ export const GroupCall = (): JSX.Element => {
},
});
return <CallingPip {...props} />;
};
}

View file

@ -70,7 +70,7 @@ const PIP_WIDTH = 120;
const PIP_TOP_MARGIN = 56;
const PIP_PADDING = 8;
export const CallingPip = ({
export function CallingPip({
activeCall,
getGroupCallVideoFrameSource,
hangUpActiveCall,
@ -82,7 +82,7 @@ export const CallingPip = ({
switchToPresentationView,
switchFromPresentationView,
togglePip,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
const localVideoRef = React.useRef(null);
@ -315,4 +315,4 @@ export const CallingPip = ({
</div>
</div>
);
};
}

View file

@ -27,13 +27,13 @@ import { nonRenderedRemoteParticipant } from '../util/ringrtc/nonRenderedRemoteP
// less than `MAX_FRAME_HEIGHT`.
const PIP_VIDEO_HEIGHT_PX = 120;
const NoVideo = ({
function NoVideo({
activeCall,
i18n,
}: {
activeCall: ActiveCallType;
i18n: LocalizerType;
}): JSX.Element => {
}): JSX.Element {
const {
acceptedMessageRequest,
avatarPath,
@ -68,7 +68,7 @@ const NoVideo = ({
</CallBackgroundBlur>
</div>
);
};
}
export type PropsType = {
activeCall: ActiveCallType;
@ -81,13 +81,13 @@ export type PropsType = {
setRendererCanvas: (_: SetRendererCanvasType) => void;
};
export const CallingPipRemoteVideo = ({
export function CallingPipRemoteVideo({
activeCall,
getGroupCallVideoFrameSource,
i18n,
setGroupCallVideoRequest,
setRendererCanvas,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const { conversation } = activeCall;
const getGroupCallFrameBuffer = useGetCallingFrameBuffer();
@ -177,4 +177,4 @@ export const CallingPipRemoteVideo = ({
default:
throw missingCaseError(activeCall);
}
};
}

View file

@ -24,230 +24,260 @@ export default {
title: 'Components/CallingPreCallInfo',
};
export const DirectConversation = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultConversation()}
i18n={i18n}
me={getDefaultConversation()}
ringMode={RingMode.WillRing}
/>
);
export function DirectConversation(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultConversation()}
i18n={i18n}
me={getDefaultConversation()}
ringMode={RingMode.WillRing}
/>
);
}
DirectConversation.story = {
name: 'Direct conversation',
};
export const Ring0 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 0)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
export function Ring0(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 0)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
}
Ring0.story = {
name: 'Group call: Will ring 0 people',
};
export const Ring1 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 1)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
export function Ring1(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 1)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
}
Ring1.story = {
name: 'Group call: Will ring 1 person',
};
export const Ring2 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 2)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
export function Ring2(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 2)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
}
Ring2.story = {
name: 'Group call: Will ring 2 people',
};
export const Ring3 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 3)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
export function Ring3(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 3)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
}
Ring3.story = {
name: 'Group call: Will ring 3 people',
};
export const Ring4 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 4)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
export function Ring4(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 4)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillRing}
/>
);
}
Ring3.story = {
name: 'Group call: Will ring 4 people',
};
export const Notify0 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 0)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
export function Notify0(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 0)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
}
Notify0.story = {
name: 'Group call: Will notify 0 people',
};
export const Notify1 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 1)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
export function Notify1(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 1)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
}
Notify1.story = {
name: 'Group call: Will notify 1 person',
};
export const Notify2 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 2)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
export function Notify2(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 2)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
}
Notify2.story = {
name: 'Group call: Will notify 2 people',
};
export const Notify3 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 3)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
export function Notify3(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 3)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
}
Notify3.story = {
name: 'Group call: Will notify 3 people',
};
export const Notify4 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 4)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
export function Notify4(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers.slice(0, 4)}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={[]}
ringMode={RingMode.WillNotRing}
/>
);
}
Notify4.story = {
name: 'Group call: Will notify 4 people',
};
export const Peek1 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 1)}
ringMode={RingMode.WillRing}
/>
);
export function Peek1(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 1)}
ringMode={RingMode.WillRing}
/>
);
}
Peek1.story = {
name: 'Group call: 1 participant peeked',
};
export const Peek2 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 2)}
ringMode={RingMode.WillRing}
/>
);
export function Peek2(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 2)}
ringMode={RingMode.WillRing}
/>
);
}
Peek2.story = {
name: 'Group call: 2 participants peeked',
};
export const Peek3 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 3)}
ringMode={RingMode.WillRing}
/>
);
export function Peek3(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 3)}
ringMode={RingMode.WillRing}
/>
);
}
Peek3.story = {
name: 'Group call: 3 participants peeked',
};
export const Peek4 = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 4)}
ringMode={RingMode.WillRing}
/>
);
export function Peek4(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, 4)}
ringMode={RingMode.WillRing}
/>
);
}
Peek4.story = {
name: 'Group call: 4 participants peeked',
};
export const GroupConversationYouOnAnOtherDevice = (): JSX.Element => {
export function GroupConversationYouOnAnOtherDevice(): JSX.Element {
const me = getDefaultConversation();
return (
<CallingPreCallInfo
@ -259,23 +289,25 @@ export const GroupConversationYouOnAnOtherDevice = (): JSX.Element => {
ringMode={RingMode.WillRing}
/>
);
};
}
GroupConversationYouOnAnOtherDevice.story = {
name: 'Group conversation, you on an other device',
};
export const GroupConversationCallIsFull = (): JSX.Element => (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
isCallFull
me={getDefaultConversation()}
peekedParticipants={otherMembers}
ringMode={RingMode.WillRing}
/>
);
export function GroupConversationCallIsFull(): JSX.Element {
return (
<CallingPreCallInfo
conversation={getDefaultGroupConversation()}
groupMembers={otherMembers}
i18n={i18n}
isCallFull
me={getDefaultConversation()}
peekedParticipants={otherMembers}
ringMode={RingMode.WillRing}
/>
);
}
GroupConversationCallIsFull.story = {
name: 'Group conversation, call is full',

View file

@ -1,7 +1,6 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react';
import React from 'react';
import type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util';
@ -42,7 +41,7 @@ type PropsType = {
>;
};
export const CallingPreCallInfo: FunctionComponent<PropsType> = ({
export function CallingPreCallInfo({
conversation,
groupMembers = [],
i18n,
@ -50,7 +49,7 @@ export const CallingPreCallInfo: FunctionComponent<PropsType> = ({
me,
peekedParticipants = [],
ringMode,
}) => {
}: PropsType): JSX.Element {
let subtitle: string;
if (ringMode === RingMode.IsRinging) {
subtitle = i18n('outgoingCallRinging');
@ -183,4 +182,4 @@ export const CallingPreCallInfo: FunctionComponent<PropsType> = ({
<div className="module-CallingPreCallInfo__subtitle">{subtitle}</div>
</div>
);
};
}

View file

@ -23,11 +23,11 @@ export default {
title: 'Components/CallingScreenSharingController',
};
export const Controller = (): JSX.Element => {
export function Controller(): JSX.Element {
return <CallingScreenSharingController {...createProps()} />;
};
}
export const ReallyLongAppName = (): JSX.Element => {
export function ReallyLongAppName(): JSX.Element {
return (
<CallingScreenSharingController
{...createProps({
@ -36,7 +36,7 @@ export const ReallyLongAppName = (): JSX.Element => {
})}
/>
);
};
}
ReallyLongAppName.story = {
name: 'Really long app name',

View file

@ -12,12 +12,12 @@ export type PropsType = {
presentedSourceName: string;
};
export const CallingScreenSharingController = ({
export function CallingScreenSharingController({
i18n,
onCloseController,
onStopSharing,
presentedSourceName,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
return (
<div className="module-CallingScreenSharingController">
<div className="module-CallingScreenSharingController__text">
@ -40,4 +40,4 @@ export const CallingScreenSharingController = ({
</div>
</div>
);
};
}

View file

@ -57,6 +57,6 @@ export default {
title: 'Components/CallingSelectPresentingSourcesModal',
};
export const Modal = (): JSX.Element => {
export function Modal(): JSX.Element {
return <CallingSelectPresentingSourcesModal {...createProps()} />;
};
}

View file

@ -16,7 +16,7 @@ export type PropsType = {
setPresenting: (_?: PresentedSource) => void;
};
const Source = ({
function Source({
onSourceClick,
source,
sourceToPresent,
@ -24,7 +24,7 @@ const Source = ({
onSourceClick: (source: PresentedSource) => void;
source: PresentableSource;
sourceToPresent?: PresentedSource;
}): JSX.Element => {
}): JSX.Element {
return (
<button
className={classNames({
@ -62,13 +62,13 @@ const Source = ({
</div>
</button>
);
};
}
export const CallingSelectPresentingSourcesModal = ({
export function CallingSelectPresentingSourcesModal({
i18n,
presentingSourcesAvailable,
setPresenting,
}: PropsType): JSX.Element | null => {
}: PropsType): JSX.Element | null {
const [sourceToPresent, setSourceToPresent] = useState<
PresentedSource | undefined
>(undefined);
@ -137,4 +137,4 @@ export const CallingSelectPresentingSourcesModal = ({
</div>
</Modal>
);
};
}

View file

@ -1,27 +1,32 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react';
import React from 'react';
import classNames from 'classnames';
type PropsType = {
isVisible: boolean;
onClick: () => unknown;
children?: JSX.Element | string;
};
export const DEFAULT_LIFETIME = 5000;
export const CallingToast: FunctionComponent<PropsType> = ({
export function CallingToast({
isVisible,
onClick,
children,
}) => (
<button
className={classNames('CallingToast', !isVisible && 'CallingToast--hidden')}
type="button"
onClick={onClick}
>
{children}
</button>
);
}: PropsType): JSX.Element {
return (
<button
className={classNames(
'CallingToast',
!isVisible && 'CallingToast--hidden'
)}
type="button"
onClick={onClick}
>
{children}
</button>
);
}

View file

@ -104,7 +104,7 @@ function useScreenSharingToast({ activeCall, i18n }: PropsType): ToastType {
// In the future, this component should show toasts when users join or leave. See
// DESKTOP-902.
export const CallingToastManager: React.FC<PropsType> = props => {
export function CallingToastManager(props: PropsType): JSX.Element {
const reconnectingToast = getReconnectingToast(props);
const screenSharingToast = useScreenSharingToast(props);
@ -144,4 +144,4 @@ export const CallingToastManager: React.FC<PropsType> = props => {
{toastMessage}
</CallingToast>
);
};
}

View file

@ -41,9 +41,9 @@ const createProps = (): PropsType => ({
),
});
export const Default = (): JSX.Element => (
<ChatColorPicker {...createProps()} />
);
export function Default(): JSX.Element {
return <ChatColorPicker {...createProps()} />;
}
const CUSTOM_COLORS = {
abc: {
@ -62,14 +62,16 @@ const CUSTOM_COLORS = {
},
};
export const CustomColors = (): JSX.Element => (
<ChatColorPicker
{...createProps()}
customColors={CUSTOM_COLORS}
selectedColor="custom"
selectedCustomColor={{
id: 'ghi',
value: SAMPLE_CUSTOM_COLOR,
}}
/>
);
export function CustomColors(): JSX.Element {
return (
<ChatColorPicker
{...createProps()}
customColors={CUSTOM_COLORS}
selectedColor="custom"
selectedCustomColor={{
id: 'ghi',
value: SAMPLE_CUSTOM_COLOR,
}}
/>
);
}

View file

@ -61,7 +61,7 @@ type PropsActionType = {
export type PropsType = PropsDataType & PropsActionType;
export const ChatColorPicker = ({
export function ChatColorPicker({
addCustomColor,
colorSelected,
conversationId,
@ -77,7 +77,7 @@ export const ChatColorPicker = ({
selectedColor = ConversationColors[0],
selectedCustomColor,
setGlobalDefaultConversationColor,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [confirmResetAll, setConfirmResetAll] = useState(false);
const [confirmResetWhat, setConfirmResetWhat] = useState(false);
const [customColorToEdit, setCustomColorToEdit] = useState<
@ -264,7 +264,7 @@ export const ChatColorPicker = ({
/>
</div>
);
};
}
type CustomColorBubblePropsType = {
color: CustomColorType;
@ -280,7 +280,7 @@ type CustomColorBubblePropsType = {
onChoose: () => unknown;
};
const CustomColorBubble = ({
function CustomColorBubble({
color,
colorId,
getConversationsWithCustomColor,
@ -290,7 +290,7 @@ const CustomColorBubble = ({
onDupe,
onEdit,
onChoose,
}: CustomColorBubblePropsType): JSX.Element => {
}: CustomColorBubblePropsType): JSX.Element {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const menuRef = useRef<any | null>(null);
const [confirmDeleteCount, setConfirmDeleteCount] = useState<
@ -410,7 +410,7 @@ const CustomColorBubble = ({
</ContextMenu>
</>
);
};
}
type CustomColorEditorWrapperPropsType = {
customColorToEdit?: CustomColorDataType;
@ -419,12 +419,12 @@ type CustomColorEditorWrapperPropsType = {
onSave: (color: CustomColorType) => unknown;
};
const CustomColorEditorWrapper = ({
function CustomColorEditorWrapper({
customColorToEdit,
i18n,
onClose,
onSave,
}: CustomColorEditorWrapperPropsType): JSX.Element => {
}: CustomColorEditorWrapperPropsType): JSX.Element {
const editor = (
<CustomColorEditor
customColor={customColorToEdit?.value}
@ -447,4 +447,4 @@ const CustomColorEditorWrapper = ({
{editor}
</Modal>
);
};
}

View file

@ -18,15 +18,17 @@ export default {
title: 'Components/Checkbox',
};
export const Normal = (): JSX.Element => <Checkbox {...createProps()} />;
export const Checked = (): JSX.Element => (
<Checkbox {...createProps()} checked />
);
export function Normal(): JSX.Element {
return <Checkbox {...createProps()} />;
}
export function Checked(): JSX.Element {
return <Checkbox {...createProps()} checked />;
}
export const Description = (): JSX.Element => (
<Checkbox {...createProps()} description="This is a checkbox" />
);
export function Description(): JSX.Element {
return <Checkbox {...createProps()} description="This is a checkbox" />;
}
export const Disabled = (): JSX.Element => (
<Checkbox {...createProps()} disabled />
);
export function Disabled(): JSX.Element {
return <Checkbox {...createProps()} disabled />;
}

View file

@ -24,7 +24,7 @@ export type PropsType = {
onClick?: () => unknown;
};
export const Checkbox = ({
export function Checkbox({
checked,
children,
description,
@ -35,7 +35,7 @@ export const Checkbox = ({
name,
onChange,
onClick,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const getClassName = getClassNamesFor('Checkbox', moduleClassName);
const id = useMemo(() => `${name}::${uuid()}`, [name]);
@ -76,4 +76,4 @@ export const Checkbox = ({
</div>
</div>
);
};
}

View file

@ -120,63 +120,63 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
isFetchingUUID: overrideProps.isFetchingUUID || false,
});
export const Default = (): JSX.Element => {
export function Default(): JSX.Element {
const props = useProps();
return <CompositionArea {...props} />;
};
}
export const StartingText = (): JSX.Element => {
export function StartingText(): JSX.Element {
const props = useProps({
draftText: "here's some starting text",
});
return <CompositionArea {...props} />;
};
}
export const StickerButton = (): JSX.Element => {
export function StickerButton(): JSX.Element {
const props = useProps({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
knownPacks: [{} as any],
});
return <CompositionArea {...props} />;
};
}
export const MessageRequest = (): JSX.Element => {
export function MessageRequest(): JSX.Element {
const props = useProps({
messageRequestsEnabled: true,
});
return <CompositionArea {...props} />;
};
}
export const SmsOnlyFetchingUuid = (): JSX.Element => {
export function SmsOnlyFetchingUuid(): JSX.Element {
const props = useProps({
isSMSOnly: true,
isFetchingUUID: true,
});
return <CompositionArea {...props} />;
};
}
SmsOnlyFetchingUuid.story = {
name: 'SMS-only fetching UUID',
};
export const SmsOnly = (): JSX.Element => {
export function SmsOnly(): JSX.Element {
const props = useProps({
isSMSOnly: true,
});
return <CompositionArea {...props} />;
};
}
SmsOnly.story = {
name: 'SMS-only',
};
export const Attachments = (): JSX.Element => {
export function Attachments(): JSX.Element {
const props = useProps({
draftAttachments: [
fakeDraftAttachment({
@ -187,33 +187,37 @@ export const Attachments = (): JSX.Element => {
});
return <CompositionArea {...props} />;
};
}
export const AnnouncementsOnlyGroup = (): JSX.Element => (
<CompositionArea
{...useProps({
announcementsOnly: true,
areWeAdmin: false,
})}
/>
);
export function AnnouncementsOnlyGroup(): JSX.Element {
return (
<CompositionArea
{...useProps({
announcementsOnly: true,
areWeAdmin: false,
})}
/>
);
}
AnnouncementsOnlyGroup.story = {
name: 'Announcements Only group',
};
export const Quote = (): JSX.Element => (
<CompositionArea
{...useProps({
quotedMessageProps: {
text: 'something',
conversationColor: ConversationColors[10],
isGiftBadge: false,
isViewOnce: false,
referencedMessageNotFound: false,
authorTitle: 'Someone',
isFromMe: false,
},
})}
/>
);
export function Quote(): JSX.Element {
return (
<CompositionArea
{...useProps({
quotedMessageProps: {
text: 'something',
conversationColor: ConversationColors[10],
isGiftBadge: false,
isViewOnce: false,
referencedMessageNotFound: false,
authorTitle: 'Someone',
isFromMe: false,
},
})}
/>
);
}

View file

@ -168,7 +168,7 @@ export type Props = Pick<
Pick<GroupV2PendingApprovalActionsPropsType, 'onCancelJoinRequest'> &
OwnProps;
export const CompositionArea = ({
export function CompositionArea({
// Base props
addAttachment,
addPendingAttachment,
@ -258,7 +258,7 @@ export const CompositionArea = ({
// SMS-only contacts
isSMSOnly,
isFetchingUUID,
}: Props): JSX.Element => {
}: Props): JSX.Element {
const [disabled, setDisabled] = useState(false);
const [dirty, setDirty] = useState(false);
const [large, setLarge] = useState(false);
@ -744,4 +744,4 @@ export const CompositionArea = ({
/>
</div>
);
};
}

View file

@ -49,37 +49,37 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
theme: React.useContext(StorybookThemeContext),
});
export const Default = (): JSX.Element => {
export function Default(): JSX.Element {
const props = useProps();
return <CompositionInput {...props} />;
};
}
export const Large = (): JSX.Element => {
export function Large(): JSX.Element {
const props = useProps({
large: true,
});
return <CompositionInput {...props} />;
};
}
export const Disabled = (): JSX.Element => {
export function Disabled(): JSX.Element {
const props = useProps({
disabled: true,
});
return <CompositionInput {...props} />;
};
}
export const StartingText = (): JSX.Element => {
export function StartingText(): JSX.Element {
const props = useProps({
draftText: "here's some starting text",
});
return <CompositionInput {...props} />;
};
}
export const MultilineText = (): JSX.Element => {
export function MultilineText(): JSX.Element {
const props = useProps({
draftText: `here's some starting text
and more on another line
@ -93,9 +93,9 @@ and we're done`,
});
return <CompositionInput {...props} />;
};
}
export const Emojis = (): JSX.Element => {
export function Emojis(): JSX.Element {
const props = useProps({
draftText: `⁣😐😐😐😐😐😐😐
😐😐😐😐😐😐😐
@ -105,9 +105,9 @@ export const Emojis = (): JSX.Element => {
});
return <CompositionInput {...props} />;
};
}
export const Mentions = (): JSX.Element => {
export function Mentions(): JSX.Element {
const props = useProps({
sortedGroupMembers: [
getDefaultConversation({
@ -129,4 +129,4 @@ export const Mentions = (): JSX.Element => {
});
return <CompositionInput {...props} />;
};
}

View file

@ -46,7 +46,7 @@ export type CompositionTextAreaProps = {
* Meant for modals that need to collect a message or caption. It is
* basically a rectangle input with an emoji selector floating at the top-right
*/
export const CompositionTextArea = ({
export function CompositionTextArea({
i18n,
placeholder,
maxLength,
@ -63,7 +63,7 @@ export const CompositionTextArea = ({
theme,
recentEmojis,
skinTone,
}: CompositionTextAreaProps): JSX.Element => {
}: CompositionTextAreaProps): JSX.Element {
const inputApiRef = React.useRef<InputApi | undefined>();
const [characterCount, setCharacterCount] = React.useState(
grapheme.count(draftText)
@ -158,4 +158,4 @@ export const CompositionTextArea = ({
)}
</div>
);
};
}

View file

@ -41,7 +41,7 @@ export type PropsType = {
};
export const CompositionUpload = forwardRef<HTMLInputElement, PropsType>(
(
function CompositionUploadInner(
{
addAttachment,
addPendingAttachment,
@ -52,7 +52,7 @@ export const CompositionUpload = forwardRef<HTMLInputElement, PropsType>(
removeAttachment,
},
ref
) => {
) {
const [toastType, setToastType] = useState<
AttachmentToastType | undefined
>();

View file

@ -22,6 +22,6 @@ export default {
title: 'Components/ConfirmDiscardDialog',
};
export const Default = (): JSX.Element => (
<ConfirmDiscardDialog {...createProps()} />
);
export function Default(): JSX.Element {
return <ConfirmDiscardDialog {...createProps()} />;
}

View file

@ -11,11 +11,11 @@ export type PropsType = {
onDiscard: () => unknown;
};
export const ConfirmDiscardDialog = ({
export function ConfirmDiscardDialog({
i18n,
onClose,
onDiscard,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
return (
<ConfirmationDialog
dialogName="ConfirmDiscardDialog"
@ -32,4 +32,4 @@ export const ConfirmDiscardDialog = ({
{i18n('ConfirmDiscardDialog--discard')}
</ConfirmationDialog>
);
};
}

View file

@ -43,7 +43,7 @@ _ConfirmationDialog.story = {
name: 'ConfirmationDialog',
};
export const CustomCancelText = (): JSX.Element => {
export function CustomCancelText(): JSX.Element {
return (
<ConfirmationDialog
dialogName="test"
@ -62,13 +62,13 @@ export const CustomCancelText = (): JSX.Element => {
Because.
</ConfirmationDialog>
);
};
}
CustomCancelText.story = {
name: 'Custom cancel text',
};
export const NoDefaultCancel = (): JSX.Element => {
export function NoDefaultCancel(): JSX.Element {
return (
<ConfirmationDialog
dialogName="test"
@ -87,4 +87,4 @@ export const NoDefaultCancel = (): JSX.Element => {
No default cancel!
</ConfirmationDialog>
);
};
}

View file

@ -57,103 +57,101 @@ function getButtonVariant(
return ButtonVariant.Secondary;
}
export const ConfirmationDialog = React.memo(
({
actions = [],
dialogName,
cancelButtonVariant,
cancelText,
children,
hasXButton,
i18n,
moduleClassName,
noMouseClose,
noDefaultCancelButton,
onCancel,
onClose,
onTopOfEverything,
theme,
title,
}: Props) => {
const { close, overlayStyles, modalStyles } = useAnimated(onClose, {
getFrom: () => ({ opacity: 0, transform: 'scale(0.25)' }),
getTo: isOpen => ({ opacity: isOpen ? 1 : 0, transform: 'scale(1)' }),
});
export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
actions = [],
dialogName,
cancelButtonVariant,
cancelText,
children,
hasXButton,
i18n,
moduleClassName,
noMouseClose,
noDefaultCancelButton,
onCancel,
onClose,
onTopOfEverything,
theme,
title,
}: Props) {
const { close, overlayStyles, modalStyles } = useAnimated(onClose, {
getFrom: () => ({ opacity: 0, transform: 'scale(0.25)' }),
getTo: isOpen => ({ opacity: isOpen ? 1 : 0, transform: 'scale(1)' }),
});
const cancelAndClose = useCallback(() => {
if (onCancel) {
onCancel();
const cancelAndClose = useCallback(() => {
if (onCancel) {
onCancel();
}
close();
}, [close, onCancel]);
const handleCancel = useCallback(
(e: MouseEvent) => {
if (e.target === e.currentTarget) {
cancelAndClose();
}
close();
}, [close, onCancel]);
},
[cancelAndClose]
);
const handleCancel = useCallback(
(e: MouseEvent) => {
if (e.target === e.currentTarget) {
cancelAndClose();
}
},
[cancelAndClose]
);
const hasActions = Boolean(actions.length);
const hasActions = Boolean(actions.length);
const footer = (
<>
{!noDefaultCancelButton ? (
<Button
onClick={handleCancel}
ref={focusRef}
variant={
cancelButtonVariant ||
(hasActions ? ButtonVariant.Secondary : ButtonVariant.Primary)
}
>
{cancelText || i18n('confirmation-dialog--Cancel')}
</Button>
) : null}
{actions.map((action, i) => (
<Button
key={action.text}
onClick={() => {
action.action();
close();
}}
data-action={i}
variant={getButtonVariant(action.style)}
>
{action.text}
</Button>
))}
</>
);
const footer = (
<>
{!noDefaultCancelButton ? (
<Button
onClick={handleCancel}
ref={focusRef}
variant={
cancelButtonVariant ||
(hasActions ? ButtonVariant.Secondary : ButtonVariant.Primary)
}
>
{cancelText || i18n('confirmation-dialog--Cancel')}
</Button>
) : null}
{actions.map((action, i) => (
<Button
key={action.text}
onClick={() => {
action.action();
close();
}}
data-action={i}
variant={getButtonVariant(action.style)}
>
{action.text}
</Button>
))}
</>
);
const modalName = `ConfirmationDialog.${dialogName}`;
const modalName = `ConfirmationDialog.${dialogName}`;
return (
<ModalHost
modalName={modalName}
noMouseClose={noMouseClose}
onClose={close}
onEscape={cancelAndClose}
onTopOfEverything={onTopOfEverything}
overlayStyles={overlayStyles}
theme={theme}
>
<animated.div style={modalStyles}>
<ModalPage
modalName={modalName}
hasXButton={hasXButton}
i18n={i18n}
moduleClassName={moduleClassName}
onClose={cancelAndClose}
title={title}
modalFooter={footer}
>
{children}
</ModalPage>
</animated.div>
</ModalHost>
);
}
);
return (
<ModalHost
modalName={modalName}
noMouseClose={noMouseClose}
onClose={close}
onEscape={cancelAndClose}
onTopOfEverything={onTopOfEverything}
overlayStyles={overlayStyles}
theme={theme}
>
<animated.div style={modalStyles}>
<ModalPage
modalName={modalName}
hasXButton={hasXButton}
i18n={i18n}
moduleClassName={moduleClassName}
onClose={cancelAndClose}
title={title}
modalFooter={footer}
>
{children}
</ModalPage>
</animated.div>
</ModalHost>
);
});

View file

@ -1,7 +1,6 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react';
import React from 'react';
import type { ConversationType } from '../state/ducks/conversations';
@ -28,7 +27,7 @@ export type PropsType = {
| 'unblurredAvatarPath'
>;
export const ContactPill: FunctionComponent<PropsType> = ({
export function ContactPill({
acceptedMessageRequest,
avatarPath,
color,
@ -42,7 +41,7 @@ export const ContactPill: FunctionComponent<PropsType> = ({
title,
unblurredAvatarPath,
onClickRemove,
}) => {
}: PropsType): JSX.Element {
const removeLabel = i18n('ContactPill--remove');
return (
@ -80,4 +79,4 @@ export const ContactPill: FunctionComponent<PropsType> = ({
/>
</div>
);
};
}

View file

@ -50,60 +50,70 @@ const contactPillProps = (
onClickRemove: action('onClickRemove'),
});
export const EmptyList = (): JSX.Element => <ContactPills />;
export function EmptyList(): JSX.Element {
return <ContactPills />;
}
EmptyList.story = {
name: 'Empty list',
};
export const OneContact = (): JSX.Element => (
<ContactPills>
<ContactPill {...contactPillProps()} />
</ContactPills>
);
export function OneContact(): JSX.Element {
return (
<ContactPills>
<ContactPill {...contactPillProps()} />
</ContactPills>
);
}
OneContact.story = {
name: 'One contact',
};
export const ThreeContacts = (): JSX.Element => (
<ContactPills>
<ContactPill {...contactPillProps(contacts[0])} />
<ContactPill {...contactPillProps(contacts[1])} />
<ContactPill {...contactPillProps(contacts[2])} />
</ContactPills>
);
export function ThreeContacts(): JSX.Element {
return (
<ContactPills>
<ContactPill {...contactPillProps(contacts[0])} />
<ContactPill {...contactPillProps(contacts[1])} />
<ContactPill {...contactPillProps(contacts[2])} />
</ContactPills>
);
}
ThreeContacts.story = {
name: 'Three contacts',
};
export const FourContactsOneWithALongName = (): JSX.Element => (
<ContactPills>
<ContactPill {...contactPillProps(contacts[0])} />
<ContactPill
{...contactPillProps({
...contacts[1],
title:
'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
})}
/>
<ContactPill {...contactPillProps(contacts[2])} />
<ContactPill {...contactPillProps(contacts[3])} />
</ContactPills>
);
export function FourContactsOneWithALongName(): JSX.Element {
return (
<ContactPills>
<ContactPill {...contactPillProps(contacts[0])} />
<ContactPill
{...contactPillProps({
...contacts[1],
title:
'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
})}
/>
<ContactPill {...contactPillProps(contacts[2])} />
<ContactPill {...contactPillProps(contacts[3])} />
</ContactPills>
);
}
FourContactsOneWithALongName.story = {
name: 'Four contacts, one with a long name',
};
export const FiftyContacts = (): JSX.Element => (
<ContactPills>
{contacts.map(contact => (
<ContactPill key={contact.id} {...contactPillProps(contact)} />
))}
</ContactPills>
);
export function FiftyContacts(): JSX.Element {
return (
<ContactPills>
{contacts.map(contact => (
<ContactPill key={contact.id} {...contactPillProps(contact)} />
))}
</ContactPills>
);
}
FiftyContacts.story = {
name: 'Fifty contacts',

View file

@ -1,7 +1,7 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent, ReactNode } from 'react';
import type { ReactNode } from 'react';
import React, { useRef, useEffect, Children } from 'react';
import classNames from 'classnames';
@ -13,10 +13,10 @@ type PropsType = {
children?: ReactNode;
};
export const ContactPills: FunctionComponent<PropsType> = ({
export function ContactPills({
moduleClassName,
children,
}) => {
}: PropsType): JSX.Element {
const elRef = useRef<null | HTMLDivElement>(null);
const childCount = Children.count(children);
@ -38,4 +38,4 @@ export const ContactPills: FunctionComponent<PropsType> = ({
{children}
</div>
);
};
}

View file

@ -33,6 +33,6 @@ const getDefaultProps = (): PropsType<number> => ({
],
});
export const Default = (): JSX.Element => {
export function Default(): JSX.Element {
return <ContextMenu {...getDefaultProps()}>Menu</ContextMenu>;
};
}

View file

@ -50,10 +50,10 @@ const defaultConversations: Array<ConversationListItemPropsType> = [
getDefaultConversation(),
];
const Wrapper = ({
function Wrapper({
rows,
scrollable,
}: Readonly<{ rows: ReadonlyArray<Row>; scrollable?: boolean }>) => {
}: Readonly<{ rows: ReadonlyArray<Row>; scrollable?: boolean }>) {
const theme = useContext(StorybookThemeContext);
return (
@ -95,7 +95,7 @@ const Wrapper = ({
theme={theme}
/>
);
};
}
export const _ArchiveButton = (): JSX.Element => (
<Wrapper
@ -107,137 +107,151 @@ _ArchiveButton.story = {
name: 'Archive button',
};
export const ContactNoteToSelf = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: {
...defaultConversations[0],
isMe: true,
about: '🤠 should be ignored',
export function ContactNoteToSelf(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: {
...defaultConversations[0],
isMe: true,
about: '🤠 should be ignored',
},
},
},
]}
/>
);
]}
/>
);
}
ContactNoteToSelf.story = {
name: 'Contact: note to self',
};
export const ContactDirect = (): JSX.Element => (
<Wrapper
rows={[{ type: RowType.Contact, contact: defaultConversations[0] }]}
/>
);
export function ContactDirect(): JSX.Element {
return (
<Wrapper
rows={[{ type: RowType.Contact, contact: defaultConversations[0] }]}
/>
);
}
ContactDirect.story = {
name: 'Contact: direct',
};
export const ContactDirectWithShortAbout = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: { ...defaultConversations[0], about: '🤠 yee haw' },
},
]}
/>
);
export function ContactDirectWithShortAbout(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: { ...defaultConversations[0], about: '🤠 yee haw' },
},
]}
/>
);
}
ContactDirectWithShortAbout.story = {
name: 'Contact: direct with short about',
};
export const ContactDirectWithLongAbout = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: {
...defaultConversations[0],
about:
'🤠 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue.',
export function ContactDirectWithLongAbout(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: {
...defaultConversations[0],
about:
'🤠 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue.',
},
},
},
]}
/>
);
]}
/>
);
}
ContactDirectWithLongAbout.story = {
name: 'Contact: direct with long about',
};
export const ContactGroup = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: { ...defaultConversations[0], type: 'group' },
},
]}
/>
);
export function ContactGroup(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.Contact,
contact: { ...defaultConversations[0], type: 'group' },
},
]}
/>
);
}
ContactGroup.story = {
name: 'Contact: group',
};
export const ContactCheckboxes = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.ContactCheckbox,
contact: defaultConversations[0],
isChecked: true,
},
{
type: RowType.ContactCheckbox,
contact: defaultConversations[1],
isChecked: false,
},
{
type: RowType.ContactCheckbox,
contact: {
...defaultConversations[2],
about: '😃 Hola',
export function ContactCheckboxes(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.ContactCheckbox,
contact: defaultConversations[0],
isChecked: true,
},
isChecked: true,
},
]}
/>
);
{
type: RowType.ContactCheckbox,
contact: defaultConversations[1],
isChecked: false,
},
{
type: RowType.ContactCheckbox,
contact: {
...defaultConversations[2],
about: '😃 Hola',
},
isChecked: true,
},
]}
/>
);
}
ContactCheckboxes.story = {
name: 'Contact checkboxes',
};
export const ContactCheckboxesDisabled = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.ContactCheckbox,
contact: defaultConversations[0],
isChecked: false,
disabledReason: ContactCheckboxDisabledReason.MaximumContactsSelected,
},
{
type: RowType.ContactCheckbox,
contact: defaultConversations[2],
isChecked: true,
disabledReason: ContactCheckboxDisabledReason.MaximumContactsSelected,
},
{
type: RowType.ContactCheckbox,
contact: defaultConversations[3],
isChecked: true,
disabledReason: ContactCheckboxDisabledReason.AlreadyAdded,
},
]}
/>
);
export function ContactCheckboxesDisabled(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.ContactCheckbox,
contact: defaultConversations[0],
isChecked: false,
disabledReason: ContactCheckboxDisabledReason.MaximumContactsSelected,
},
{
type: RowType.ContactCheckbox,
contact: defaultConversations[2],
isChecked: true,
disabledReason: ContactCheckboxDisabledReason.MaximumContactsSelected,
},
{
type: RowType.ContactCheckbox,
contact: defaultConversations[3],
isChecked: true,
disabledReason: ContactCheckboxDisabledReason.AlreadyAdded,
},
]}
/>
);
}
ContactCheckboxesDisabled.story = {
name: 'Contact checkboxes: disabled',
@ -322,16 +336,18 @@ ConversationWithYourself.story = {
name: 'Conversation: with yourself',
};
export const ConversationsMessageStatuses = (): JSX.Element => (
<Wrapper
rows={MessageStatuses.map(status => ({
type: RowType.Conversation,
conversation: createConversation({
lastMessage: { text: status, status, deletedForEveryone: false },
}),
}))}
/>
);
export function ConversationsMessageStatuses(): JSX.Element {
return (
<Wrapper
rows={MessageStatuses.map(status => ({
type: RowType.Conversation,
conversation: createConversation({
lastMessage: { text: status, status, deletedForEveryone: false },
}),
}))}
/>
);
}
ConversationsMessageStatuses.story = {
name: 'Conversations: Message Statuses',
@ -379,21 +395,23 @@ ConversationMessageRequest.story = {
name: 'Conversation: Message Request',
};
export const ConversationsUnreadCount = (): JSX.Element => (
<Wrapper
rows={[4, 10, 34, 250].map(unreadCount => ({
type: RowType.Conversation,
conversation: createConversation({
lastMessage: {
text: 'Hey there!',
status: 'delivered',
deletedForEveryone: false,
},
unreadCount,
}),
}))}
/>
);
export function ConversationsUnreadCount(): JSX.Element {
return (
<Wrapper
rows={[4, 10, 34, 250].map(unreadCount => ({
type: RowType.Conversation,
conversation: createConversation({
lastMessage: {
text: 'Hey there!',
status: 'delivered',
deletedForEveryone: false,
},
unreadCount,
}),
}))}
/>
);
}
ConversationsUnreadCount.story = {
name: 'Conversations: unread count',
@ -459,7 +477,7 @@ ConversationLongName.story = {
name: 'Conversation: long name',
};
export const ConversationLongMessage = (): JSX.Element => {
export function ConversationLongMessage(): JSX.Element {
const messages = [
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
`Many lines. This is a many-line message.
@ -482,13 +500,13 @@ Line 4, well.`,
}))}
/>
);
};
}
ConversationLongMessage.story = {
name: 'Conversation: Long Message',
};
export const ConversationsVariousTimes = (): JSX.Element => {
export function ConversationsVariousTimes(): JSX.Element {
const pairs: Array<[number, string]> = [
[Date.now() - 5 * 60 * 60 * 1000, 'Five hours ago'],
[Date.now() - 24 * 60 * 60 * 1000, 'One day ago'],
@ -511,33 +529,33 @@ export const ConversationsVariousTimes = (): JSX.Element => {
}))}
/>
);
};
}
ConversationsVariousTimes.story = {
name: 'Conversations: Various Times',
};
export const ConversationMissingDate = (): JSX.Element => {
export function ConversationMissingDate(): JSX.Element {
const row = {
type: RowType.Conversation as const,
conversation: omit(createConversation(), 'lastUpdated'),
};
return <Wrapper rows={[row]} />;
};
}
ConversationMissingDate.story = {
name: 'Conversation: Missing Date',
};
export const ConversationMissingMessage = (): JSX.Element => {
export function ConversationMissingMessage(): JSX.Element {
const row = {
type: RowType.Conversation as const,
conversation: omit(createConversation(), 'lastMessage'),
};
return <Wrapper rows={[row]} />;
};
}
ConversationMissingMessage.story = {
name: 'Conversation: Missing Message',
@ -580,178 +598,188 @@ ConversationAtMention.story = {
name: 'Conversation: At Mention',
};
export const Headers = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.Header,
i18nKey: 'conversationsHeader',
},
{
type: RowType.Header,
i18nKey: 'messagesHeader',
},
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
},
{
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
},
]}
/>
);
export function Headers(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.Header,
i18nKey: 'conversationsHeader',
},
{
type: RowType.Header,
i18nKey: 'messagesHeader',
},
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
},
{
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
},
]}
/>
);
}
export const FindByPhoneNumber = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
},
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
export function FindByPhoneNumber(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
},
isFetching: false,
},
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
},
isFetching: false,
},
isFetching: true,
},
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555',
e164: '+1234555',
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
},
isFetching: true,
},
isFetching: false,
},
]}
/>
);
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555',
e164: '+1234555',
},
isFetching: false,
},
]}
/>
);
}
FindByPhoneNumber.story = {
name: 'Find by phone number',
};
export const FindByUsername = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
},
{
type: RowType.UsernameSearchResult,
username: 'jowerty',
isFetchingUsername: false,
},
{
type: RowType.UsernameSearchResult,
username: 'jowerty',
isFetchingUsername: true,
},
]}
/>
);
export function FindByUsername(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
},
{
type: RowType.UsernameSearchResult,
username: 'jowerty',
isFetchingUsername: false,
},
{
type: RowType.UsernameSearchResult,
username: 'jowerty',
isFetchingUsername: true,
},
]}
/>
);
}
FindByUsername.story = {
name: 'Find by username',
};
export const SearchResultsLoadingSkeleton = (): JSX.Element => (
<Wrapper
scrollable={false}
rows={[
{ type: RowType.SearchResultsLoadingFakeHeader },
...times(99, () => ({
type: RowType.SearchResultsLoadingFakeRow as const,
})),
]}
/>
);
export function SearchResultsLoadingSkeleton(): JSX.Element {
return (
<Wrapper
scrollable={false}
rows={[
{ type: RowType.SearchResultsLoadingFakeHeader },
...times(99, () => ({
type: RowType.SearchResultsLoadingFakeRow as const,
})),
]}
/>
);
}
SearchResultsLoadingSkeleton.story = {
name: 'Search results loading skeleton',
};
export const KitchenSink = (): JSX.Element => (
<Wrapper
rows={[
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
export function KitchenSink(): JSX.Element {
return (
<Wrapper
rows={[
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
},
isFetching: false,
},
isFetching: false,
},
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: true,
userInput: '+1(234)555 98 76',
e164: '+12345559876',
},
isFetching: true,
},
isFetching: true,
},
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: false,
userInput: '+1(234)555 98',
e164: '+123455598',
{
type: RowType.StartNewConversation,
phoneNumber: {
isValid: false,
userInput: '+1(234)555 98',
e164: '+123455598',
},
isFetching: true,
},
isFetching: true,
},
{
type: RowType.Header,
i18nKey: 'contactsHeader',
},
{
type: RowType.Contact,
contact: defaultConversations[0],
},
{
type: RowType.Header,
i18nKey: 'messagesHeader',
},
{
type: RowType.Conversation,
conversation: defaultConversations[1],
},
{
type: RowType.MessageSearchResult,
messageId: '123',
},
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
},
{
type: RowType.UsernameSearchResult,
username: 'jowerty',
isFetchingUsername: false,
},
{
type: RowType.ArchiveButton,
archivedConversationsCount: 123,
},
]}
/>
);
{
type: RowType.Header,
i18nKey: 'contactsHeader',
},
{
type: RowType.Contact,
contact: defaultConversations[0],
},
{
type: RowType.Header,
i18nKey: 'messagesHeader',
},
{
type: RowType.Conversation,
conversation: defaultConversations[1],
},
{
type: RowType.MessageSearchResult,
messageId: '123',
},
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
},
{
type: RowType.UsernameSearchResult,
username: 'jowerty',
isFetchingUsername: false,
},
{
type: RowType.ArchiveButton,
archivedConversationsCount: 123,
},
]}
/>
);
}
KitchenSink.story = {
name: 'Kitchen sink',

View file

@ -173,7 +173,7 @@ export type PropsType = {
disabledReason: undefined | ContactCheckboxDisabledReason
) => void;
onSelectConversation: (conversationId: string, messageId?: string) => void;
renderMessageSearchResult: (id: string) => JSX.Element;
renderMessageSearchResult?: (id: string) => JSX.Element;
showChooseGroupMembers: () => void;
showConversation: ShowConversationType;
} & LookupConversationWithoutUuidActionsType;
@ -182,7 +182,7 @@ const NORMAL_ROW_HEIGHT = 76;
const SELECT_ROW_HEIGHT = 52;
const HEADER_ROW_HEIGHT = 40;
export const ConversationList: React.FC<PropsType> = ({
export function ConversationList({
dimensions,
getPreferredBadge,
getRow,
@ -202,7 +202,7 @@ export const ConversationList: React.FC<PropsType> = ({
setIsFetchingUUID,
showConversation,
theme,
}) => {
}: PropsType): JSX.Element | null {
const calculateRowHeight = useCallback(
(index: number): number => {
const row = getRow(index);
@ -252,7 +252,7 @@ export const ConversationList: React.FC<PropsType> = ({
);
break;
case RowType.Blank:
result = <></>;
result = undefined;
break;
case RowType.Contact: {
const { isClickable = true } = row;
@ -381,7 +381,7 @@ export const ConversationList: React.FC<PropsType> = ({
);
break;
case RowType.MessageSearchResult:
result = <>{renderMessageSearchResult(row.messageId)}</>;
result = <>{renderMessageSearchResult?.(row.messageId)}</>;
break;
case RowType.SearchResultsLoadingFakeHeader:
result = <SearchResultsLoadingFakeHeaderComponent />;
@ -479,4 +479,4 @@ export const ConversationList: React.FC<PropsType> = ({
scrollBehavior={scrollBehavior}
/>
);
};
}

View file

@ -22,6 +22,6 @@ const createProps = (): PropsType => ({
onSave: action('onSave'),
});
export const Default = (): JSX.Element => (
<CustomColorEditor {...createProps()} />
);
export function Default(): JSX.Element {
return <CustomColorEditor {...createProps()} />;
}

View file

@ -42,12 +42,12 @@ const ULTRAMARINE_ISH: CustomColorType = {
deg: 180,
};
export const CustomColorEditor = ({
export function CustomColorEditor({
customColor = ULTRAMARINE_ISH,
i18n,
onClose,
onSave,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [color, setColor] = useState<CustomColorType>(customColor);
const [selectedColorKnob, setSelectedColorKnob] = useState<KnobType>(
KnobType.start
@ -57,135 +57,129 @@ export const CustomColorEditor = ({
color[selectedColorKnob] || ULTRAMARINE_ISH_VALUES;
return (
<>
<Tabs
initialSelectedTab={color.end ? TabViews.Gradient : TabViews.Solid}
moduleClassName="CustomColorEditor__tabs"
onTabChange={selectedTab => {
if (selectedTab === TabViews.Gradient && !color.end) {
setColor({
...color,
end: ULTRAMARINE_ISH_VALUES,
});
}
<Tabs
initialSelectedTab={color.end ? TabViews.Gradient : TabViews.Solid}
moduleClassName="CustomColorEditor__tabs"
onTabChange={selectedTab => {
if (selectedTab === TabViews.Gradient && !color.end) {
setColor({
...color,
end: ULTRAMARINE_ISH_VALUES,
});
}
if (selectedTab === TabViews.Solid && color.end) {
setColor({
...color,
end: undefined,
});
}
}}
tabs={[
{
id: TabViews.Solid,
label: i18n('CustomColorEditor__solid'),
},
{
id: TabViews.Gradient,
label: i18n('CustomColorEditor__gradient'),
},
]}
>
{({ selectedTab }) => (
<>
<div className="CustomColorEditor__messages">
<SampleMessageBubbles
backgroundStyle={getCustomColorStyle(color)}
color="custom"
i18n={i18n}
includeAnotherBubble
/>
{selectedTab === TabViews.Gradient && (
<>
<GradientDial
deg={color.deg}
knob1Style={{ backgroundColor: getHSL(color.start) }}
knob2Style={{
backgroundColor: getHSL(
color.end || ULTRAMARINE_ISH_VALUES
),
}}
onChange={deg => {
setColor({
...color,
deg,
});
}}
onClick={knob => setSelectedColorKnob(knob)}
selectedKnob={selectedColorKnob}
/>
</>
)}
</div>
<div className="CustomColorEditor__slider-container">
{i18n('CustomColorEditor__hue')}
<Slider
handleStyle={{
backgroundColor: getHSL({
hue,
saturation: 100,
}),
if (selectedTab === TabViews.Solid && color.end) {
setColor({
...color,
end: undefined,
});
}
}}
tabs={[
{
id: TabViews.Solid,
label: i18n('CustomColorEditor__solid'),
},
{
id: TabViews.Gradient,
label: i18n('CustomColorEditor__gradient'),
},
]}
>
{({ selectedTab }) => (
<>
<div className="CustomColorEditor__messages">
<SampleMessageBubbles
backgroundStyle={getCustomColorStyle(color)}
color="custom"
i18n={i18n}
includeAnotherBubble
/>
{selectedTab === TabViews.Gradient && (
<GradientDial
deg={color.deg}
knob1Style={{ backgroundColor: getHSL(color.start) }}
knob2Style={{
backgroundColor: getHSL(color.end || ULTRAMARINE_ISH_VALUES),
}}
label={i18n('CustomColorEditor__hue')}
moduleClassName="CustomColorEditor__hue-slider"
onChange={(percentage: number) => {
onChange={deg => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
hue: getValue(percentage, MAX_HUE),
},
deg,
});
}}
value={getPercentage(hue, MAX_HUE)}
onClick={knob => setSelectedColorKnob(knob)}
selectedKnob={selectedColorKnob}
/>
</div>
<div className="CustomColorEditor__slider-container">
{i18n('CustomColorEditor__saturation')}
<Slider
containerStyle={getCustomColorStyle({
deg: 180,
start: { hue, saturation: 0 },
end: { hue, saturation: 100 },
})}
handleStyle={{
backgroundColor: getHSL(
color[selectedColorKnob] || ULTRAMARINE_ISH_VALUES
),
}}
label={i18n('CustomColorEditor__saturation')}
moduleClassName="CustomColorEditor__saturation-slider"
onChange={(value: number) => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
saturation: value,
},
});
}}
value={saturation}
/>
</div>
<div className="CustomColorEditor__footer">
<Button variant={ButtonVariant.Secondary} onClick={onClose}>
{i18n('cancel')}
</Button>
<Button
onClick={() => {
onSave(color);
onClose();
}}
>
{i18n('save')}
</Button>
</div>
</>
)}
</Tabs>
</>
)}
</div>
<div className="CustomColorEditor__slider-container">
{i18n('CustomColorEditor__hue')}
<Slider
handleStyle={{
backgroundColor: getHSL({
hue,
saturation: 100,
}),
}}
label={i18n('CustomColorEditor__hue')}
moduleClassName="CustomColorEditor__hue-slider"
onChange={(percentage: number) => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
hue: getValue(percentage, MAX_HUE),
},
});
}}
value={getPercentage(hue, MAX_HUE)}
/>
</div>
<div className="CustomColorEditor__slider-container">
{i18n('CustomColorEditor__saturation')}
<Slider
containerStyle={getCustomColorStyle({
deg: 180,
start: { hue, saturation: 0 },
end: { hue, saturation: 100 },
})}
handleStyle={{
backgroundColor: getHSL(
color[selectedColorKnob] || ULTRAMARINE_ISH_VALUES
),
}}
label={i18n('CustomColorEditor__saturation')}
moduleClassName="CustomColorEditor__saturation-slider"
onChange={(value: number) => {
setColor({
...color,
[selectedColorKnob]: {
...ULTRAMARINE_ISH_VALUES,
...color[selectedColorKnob],
saturation: value,
},
});
}}
value={saturation}
/>
</div>
<div className="CustomColorEditor__footer">
<Button variant={ButtonVariant.Secondary} onClick={onClose}>
{i18n('cancel')}
</Button>
<Button
onClick={() => {
onSave(color);
onClose();
}}
>
{i18n('save')}
</Button>
</div>
</>
)}
</Tabs>
);
};
}

View file

@ -37,28 +37,30 @@ const defaultProps: ComponentProps<typeof CustomizingPreferredReactionsModal> =
skinTone: 4,
};
export const Default = (): JSX.Element => (
<CustomizingPreferredReactionsModal {...defaultProps} />
);
export function Default(): JSX.Element {
return <CustomizingPreferredReactionsModal {...defaultProps} />;
}
export const DraftEmojiSelected = (): JSX.Element => (
<CustomizingPreferredReactionsModal
{...defaultProps}
selectedDraftEmojiIndex={4}
/>
);
export function DraftEmojiSelected(): JSX.Element {
return (
<CustomizingPreferredReactionsModal
{...defaultProps}
selectedDraftEmojiIndex={4}
/>
);
}
DraftEmojiSelected.story = {
name: 'Draft emoji selected',
};
export const Saving = (): JSX.Element => (
<CustomizingPreferredReactionsModal {...defaultProps} isSaving />
);
export function Saving(): JSX.Element {
return <CustomizingPreferredReactionsModal {...defaultProps} isSaving />;
}
export const HadError = (): JSX.Element => (
<CustomizingPreferredReactionsModal {...defaultProps} hadSaveError />
);
export function HadError(): JSX.Element {
return <CustomizingPreferredReactionsModal {...defaultProps} hadSaveError />;
}
HadError.story = {
name: 'Had error',

View file

@ -41,7 +41,7 @@ enum ToastType {
Loading,
}
export const DebugLogWindow = ({
export function DebugLogWindow({
closeWindow,
downloadLog,
i18n,
@ -49,7 +49,7 @@ export const DebugLogWindow = ({
uploadLogs,
hasCustomTitleBar,
executeMenuRole,
}: PropsType): JSX.Element => {
}: PropsType): JSX.Element {
const [loadState, setLoadState] = useState<LoadState>(LoadState.NotStarted);
const [logText, setLogText] = useState<string | undefined>();
const [publicLogURL, setPublicLogURL] = useState<string | undefined>();
@ -230,4 +230,4 @@ export const DebugLogWindow = ({
</div>
</TitleBarContainer>
);
};
}

View file

@ -15,11 +15,11 @@ type PropsType = {
i18n: LocalizerType;
};
export const DialogExpiredBuild = ({
export function DialogExpiredBuild({
containerWidthBreakpoint,
hasExpired,
i18n,
}: PropsType): JSX.Element | null => {
}: PropsType): JSX.Element | null {
if (!hasExpired) {
return null;
}
@ -37,4 +37,4 @@ export const DialogExpiredBuild = ({
{i18n('expiredWarning')}{' '}
</LeftPaneDialog>
);
};
}

View file

@ -29,7 +29,7 @@ export default {
title: 'Components/DialogNetworkStatus',
};
export const KnobsPlayground = (args: PropsType): JSX.Element => {
export function KnobsPlayground(args: PropsType): JSX.Element {
/*
const socketStatus = select(
'socketStatus',
@ -48,7 +48,7 @@ export const KnobsPlayground = (args: PropsType): JSX.Element => {
<DialogNetworkStatus {...defaultProps} {...args} />
</FakeLeftPaneContainer>
);
};
}
KnobsPlayground.args = {
containerWidthBreakpoint: WidthBreakpoint.Wide,
hasNetworkDialog: true,
@ -56,113 +56,129 @@ KnobsPlayground.args = {
socketStatus: SocketStatus.CONNECTING,
};
export const ConnectingWide = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
socketStatus={SocketStatus.CONNECTING}
/>
</FakeLeftPaneContainer>
);
export function ConnectingWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
socketStatus={SocketStatus.CONNECTING}
/>
</FakeLeftPaneContainer>
);
}
ConnectingWide.story = {
name: 'Connecting Wide',
};
export const ClosingWide = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
socketStatus={SocketStatus.CLOSING}
/>
</FakeLeftPaneContainer>
);
export function ClosingWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
socketStatus={SocketStatus.CLOSING}
/>
</FakeLeftPaneContainer>
);
}
ClosingWide.story = {
name: 'Closing Wide',
};
export const ClosedWide = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
socketStatus={SocketStatus.CLOSED}
/>
</FakeLeftPaneContainer>
);
export function ClosedWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
socketStatus={SocketStatus.CLOSED}
/>
</FakeLeftPaneContainer>
);
}
ClosedWide.story = {
name: 'Closed Wide',
};
export const OfflineWide = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
isOnline={false}
/>
</FakeLeftPaneContainer>
);
export function OfflineWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Wide}
isOnline={false}
/>
</FakeLeftPaneContainer>
);
}
OfflineWide.story = {
name: 'Offline Wide',
};
export const ConnectingNarrow = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
socketStatus={SocketStatus.CONNECTING}
/>
</FakeLeftPaneContainer>
);
export function ConnectingNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
socketStatus={SocketStatus.CONNECTING}
/>
</FakeLeftPaneContainer>
);
}
ConnectingNarrow.story = {
name: 'Connecting Narrow',
};
export const ClosingNarrow = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
socketStatus={SocketStatus.CLOSING}
/>
</FakeLeftPaneContainer>
);
export function ClosingNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
socketStatus={SocketStatus.CLOSING}
/>
</FakeLeftPaneContainer>
);
}
ClosingNarrow.story = {
name: 'Closing Narrow',
};
export const ClosedNarrow = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
socketStatus={SocketStatus.CLOSED}
/>
</FakeLeftPaneContainer>
);
export function ClosedNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
socketStatus={SocketStatus.CLOSED}
/>
</FakeLeftPaneContainer>
);
}
ClosedNarrow.story = {
name: 'Closed Narrow',
};
export const OfflineNarrow = (): JSX.Element => (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
isOnline={false}
/>
</FakeLeftPaneContainer>
);
export function OfflineNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
<DialogNetworkStatus
{...defaultProps}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
isOnline={false}
/>
</FakeLeftPaneContainer>
);
}
OfflineNarrow.story = {
name: 'Offline Narrow',

View file

@ -20,14 +20,14 @@ export type PropsType = NetworkStateType & {
manualReconnect: () => void;
};
export const DialogNetworkStatus = ({
export function DialogNetworkStatus({
containerWidthBreakpoint,
hasNetworkDialog,
i18n,
isOnline,
socketStatus,
manualReconnect,
}: PropsType): JSX.Element | null => {
}: PropsType): JSX.Element | null {
const [isConnecting, setIsConnecting] = React.useState<boolean>(
socketStatus === SocketStatus.CONNECTING
);
@ -93,4 +93,4 @@ export const DialogNetworkStatus = ({
onClick={reconnect}
/>
);
};
}

View file

@ -41,15 +41,15 @@ export default {
title: 'Components/DialogRelink',
};
export const KnobsPlayground = (): JSX.Element => {
export function KnobsPlayground(): JSX.Element {
const isRegistrationDone = boolean('isRegistrationDone', false);
return (
<DialogRelink {...defaultProps} isRegistrationDone={isRegistrationDone} />
);
};
}
export const Iterations = (): JSX.Element => {
export function Iterations(): JSX.Element {
return (
<>
{permutations.map(({ props, title }) => (
@ -64,4 +64,4 @@ export const Iterations = (): JSX.Element => {
))}
</>
);
};
}

View file

@ -15,12 +15,12 @@ export type PropsType = {
relinkDevice: () => void;
};
export const DialogRelink = ({
export function DialogRelink({
containerWidthBreakpoint,
i18n,
isRegistrationDone,
relinkDevice,
}: PropsType): JSX.Element | null => {
}: PropsType): JSX.Element | null {
if (isRegistrationDone) {
return null;
}
@ -36,4 +36,4 @@ export const DialogRelink = ({
hasAction
/>
);
};
}

Some files were not shown because too many files have changed in this diff Show more