GroupsV2: Better group invite behavior
This commit is contained in:
parent
b9ff4f07d3
commit
d51a0b5ece
24 changed files with 1408 additions and 313 deletions
35
ts/components/ErrorModal.stories.tsx
Normal file
35
ts/components/ErrorModal.stories.tsx
Normal 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',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
46
ts/components/ErrorModal.tsx
Normal file
46
ts/components/ErrorModal.tsx
Normal 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>
|
||||
);
|
||||
};
|
21
ts/components/ProgressDialog.stories.tsx
Normal file
21
ts/components/ProgressDialog.stories.tsx
Normal 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} />;
|
||||
});
|
18
ts/components/ProgressDialog.tsx
Normal file
18
ts/components/ProgressDialog.tsx
Normal 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>
|
||||
);
|
||||
});
|
13
ts/components/ProgressModal.stories.tsx
Normal file
13
ts/components/ProgressModal.stories.tsx
Normal 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} />;
|
||||
});
|
35
ts/components/ProgressModal.tsx
Normal file
35
ts/components/ProgressModal.tsx
Normal 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;
|
||||
});
|
|
@ -8,6 +8,7 @@ export const SpinnerDirections = [
|
|||
'outgoing',
|
||||
'incoming',
|
||||
'on-background',
|
||||
'on-progress-dialog',
|
||||
] as const;
|
||||
export type SpinnerDirection = typeof SpinnerDirections[number];
|
||||
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue