GroupsV2: Better group invite behavior

This commit is contained in:
Scott Nonnenberg 2020-10-06 10:06:34 -07:00 committed by Josh Perez
parent b9ff4f07d3
commit d51a0b5ece
24 changed files with 1408 additions and 313 deletions

View file

@ -0,0 +1,35 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { PropsType, ErrorModal } from './ErrorModal';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
title: text('title', overrideProps.title || ''),
description: text('description', overrideProps.description || ''),
buttonText: text('buttonText', overrideProps.buttonText || ''),
i18n,
onClose: action('onClick'),
});
storiesOf('Components/ErrorModal', module).add('Normal', () => {
return <ErrorModal {...createProps()} />;
});
storiesOf('Components/ErrorModal', module).add('Custom Strings', () => {
return (
<ErrorModal
{...createProps({
title: 'Real bad!',
description: 'Just avoid that next time, kay?',
buttonText: 'Fine',
})}
/>
);
});

View file

@ -0,0 +1,46 @@
import * as React from 'react';
import { LocalizerType } from '../types/Util';
import { ConfirmationModal } from './ConfirmationModal';
export type PropsType = {
buttonText: string;
description: string;
title: string;
onClose: () => void;
i18n: LocalizerType;
};
function focusRef(el: HTMLElement | null) {
if (el) {
el.focus();
}
}
export const ErrorModal = (props: PropsType): JSX.Element => {
const { buttonText, description, i18n, onClose, title } = props;
return (
<ConfirmationModal
actions={[]}
title={title || i18n('ErrorModal--title')}
i18n={i18n}
onClose={onClose}
>
<div className="module-error-modal__description">
{description || i18n('ErrorModal--description')}
</div>
<div className="module-error-modal__button-container">
<button
type="button"
className="module-confirmation-dialog__container__buttons__button"
onClick={onClose}
ref={focusRef}
>
{buttonText || i18n('ErrorModal--buttonText')}
</button>
</div>
</ConfirmationModal>
);
};

View file

@ -0,0 +1,21 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { ProgressDialog, PropsType } from './ProgressDialog';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const story = storiesOf('Components/ProgressDialog', module);
const i18n = setupI18n('en', enMessages);
const createProps = (): PropsType => ({
i18n,
});
story.add('Normal', () => {
const props = createProps();
return <ProgressDialog {...props} />;
});

View file

@ -0,0 +1,18 @@
import * as React from 'react';
import { LocalizerType } from '../types/Util';
import { Spinner } from './Spinner';
export type PropsType = {
readonly i18n: LocalizerType;
};
export const ProgressDialog = React.memo(({ i18n }: PropsType) => {
return (
<div className="module-progress-dialog">
<div className="module-progress-dialog__spinner">
<Spinner svgSize="normal" size="39px" direction="on-progress-dialog" />
</div>
<div className="module-progress-dialog__text">{i18n('updating')}</div>
</div>
);
});

View file

@ -0,0 +1,13 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { ProgressModal } from './ProgressModal';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
storiesOf('Components/ProgressModal', module).add('Normal', () => {
return <ProgressModal i18n={i18n} />;
});

View file

@ -0,0 +1,35 @@
import * as React from 'react';
import { createPortal } from 'react-dom';
import { ProgressDialog } from './ProgressDialog';
import { LocalizerType } from '../types/Util';
export type PropsType = {
readonly i18n: LocalizerType;
};
export const ProgressModal = React.memo(({ i18n }: PropsType) => {
const [root, setRoot] = React.useState<HTMLElement | null>(null);
// Note: We explicitly don't register for user interaction here, since this dialog
// cannot be dismissed.
React.useEffect(() => {
const div = document.createElement('div');
document.body.appendChild(div);
setRoot(div);
return () => {
document.body.removeChild(div);
setRoot(null);
};
}, []);
return root
? createPortal(
<div role="presentation" className="module-progress-dialog__overlay">
<ProgressDialog i18n={i18n} />
</div>,
root
)
: null;
});

View file

@ -8,6 +8,7 @@ export const SpinnerDirections = [
'outgoing',
'incoming',
'on-background',
'on-progress-dialog',
] as const;
export type SpinnerDirection = typeof SpinnerDirections[number];

View file

@ -81,6 +81,35 @@ storiesOf('Components/Conversation/GroupV2Change', module)
</>
);
})
.add('Create', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'create',
},
],
})}
{renderChange({
from: CONTACT_A,
details: [
{
type: 'create',
},
],
})}
{renderChange({
details: [
{
type: 'create',
},
],
})}
</>
);
})
.add('Title', () => {
return (
<>
@ -784,6 +813,28 @@ storiesOf('Components/Conversation/GroupV2Change', module)
},
],
})}
{renderChange({
from: CONTACT_B,
details: [
{
type: 'pending-remove-one',
conversationId: OUR_ID,
inviter: CONTACT_B,
},
],
})}
{renderChange({
from: CONTACT_A,
details: [
{
type: 'pending-remove-one',
conversationId: CONTACT_B,
inviter: CONTACT_A,
},
],
})}
{renderChange({
from: CONTACT_C,
details: [

View file

@ -65,10 +65,6 @@ story.add('No Image', () => {
return <StagedLinkPreview {...createProps()} />;
});
story.add('No Image', () => {
return <StagedLinkPreview {...createProps()} />;
});
story.add('Image', () => {
const props = createProps({
image: createAttachment({
@ -102,18 +98,6 @@ story.add('No Image, Long Title With Description', () => {
return <StagedLinkPreview {...props} />;
});
story.add('Image, Long Title With Description', () => {
const props = createProps({
title: LONG_TITLE,
image: createAttachment({
url: '/fixtures/kitten-4-112-112.jpg',
contentType: 'image/jpeg' as MIMEType,
}),
});
return <StagedLinkPreview {...props} />;
});
story.add('No Image, Long Title Without Description', () => {
const props = createProps({
title: LONG_TITLE,
@ -123,7 +107,7 @@ story.add('No Image, Long Title Without Description', () => {
return <StagedLinkPreview {...props} />;
});
story.add('Image, Long Title With Description', () => {
story.add('Image, Long Title Without Description', () => {
const props = createProps({
title: LONG_TITLE,
image: createAttachment({