Update eslint to 8.27.0
This commit is contained in:
parent
c8fb43a846
commit
98ef4c627a
499 changed files with 8995 additions and 8494 deletions
27
.eslintrc.js
27
.eslintrc.js
|
@ -94,6 +94,33 @@ const rules = {
|
||||||
// Prefer functional components with default params
|
// Prefer functional components with default params
|
||||||
'react/require-default-props': 'off',
|
'react/require-default-props': 'off',
|
||||||
|
|
||||||
|
// Empty fragments are used in adapters between backbone and react views.
|
||||||
|
'react/jsx-no-useless-fragment': ['error', {
|
||||||
|
allowExpressions: true,
|
||||||
|
}],
|
||||||
|
|
||||||
|
// Our code base has tons of arrow functions passed directly to components.
|
||||||
|
'react/jsx-no-bind': 'off',
|
||||||
|
|
||||||
|
// Does not support forwardRef
|
||||||
|
'react/no-unused-prop-types': 'off',
|
||||||
|
|
||||||
|
// Not useful for us as we have lots of complicated types.
|
||||||
|
'react/destructuring-assignment': 'off',
|
||||||
|
|
||||||
|
'react/function-component-definition': ['error', {
|
||||||
|
namedComponents: 'function-declaration',
|
||||||
|
unnamedComponents: 'arrow-function',
|
||||||
|
}],
|
||||||
|
|
||||||
|
'react/display-name': 'error',
|
||||||
|
|
||||||
|
// Allow returning values from promise executors for brevity.
|
||||||
|
'no-promise-executor-return': 'off',
|
||||||
|
|
||||||
|
// Redux ducks use this a lot
|
||||||
|
'default-param-last': 'off',
|
||||||
|
|
||||||
'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }],
|
'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }],
|
||||||
|
|
||||||
'no-restricted-syntax': [
|
'no-restricted-syntax': [
|
||||||
|
|
20
package.json
20
package.json
|
@ -259,8 +259,8 @@
|
||||||
"@types/webpack-dev-server": "3.11.3",
|
"@types/webpack-dev-server": "3.11.3",
|
||||||
"@types/websocket": "1.0.0",
|
"@types/websocket": "1.0.0",
|
||||||
"@types/yargs": "17.0.7",
|
"@types/yargs": "17.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "5.6.0",
|
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||||
"@typescript-eslint/parser": "5.6.0",
|
"@typescript-eslint/parser": "5.43.0",
|
||||||
"arraybuffer-loader": "1.0.3",
|
"arraybuffer-loader": "1.0.3",
|
||||||
"asar": "3.1.0",
|
"asar": "3.1.0",
|
||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
|
@ -281,13 +281,13 @@
|
||||||
"electron-notarize": "1.2.1",
|
"electron-notarize": "1.2.1",
|
||||||
"endanger": "7.0.4",
|
"endanger": "7.0.4",
|
||||||
"esbuild": "0.15.8",
|
"esbuild": "0.15.8",
|
||||||
"eslint": "7.7.0",
|
"eslint": "8.27.0",
|
||||||
"eslint-config-airbnb-typescript-prettier": "4.2.0",
|
"eslint-config-airbnb-typescript-prettier": "5.0.0",
|
||||||
"eslint-config-prettier": "6.11.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-import": "2.22.0",
|
"eslint-plugin-import": "2.26.0",
|
||||||
"eslint-plugin-mocha": "9.0.0",
|
"eslint-plugin-mocha": "10.1.0",
|
||||||
"eslint-plugin-more": "1.0.0",
|
"eslint-plugin-more": "1.0.5",
|
||||||
"eslint-plugin-react": "7.20.6",
|
"eslint-plugin-react": "7.31.10",
|
||||||
"file-loader": "4.2.0",
|
"file-loader": "4.2.0",
|
||||||
"html-webpack-plugin": "5.3.1",
|
"html-webpack-plugin": "5.3.1",
|
||||||
"json-to-ast": "2.1.0",
|
"json-to-ast": "2.1.0",
|
||||||
|
@ -298,7 +298,7 @@
|
||||||
"nyc": "11.4.1",
|
"nyc": "11.4.1",
|
||||||
"patch-package": "6.4.7",
|
"patch-package": "6.4.7",
|
||||||
"playwright": "1.17.1",
|
"playwright": "1.17.1",
|
||||||
"prettier": "2.6.0",
|
"prettier": "2.7.1",
|
||||||
"sass": "1.49.7",
|
"sass": "1.49.7",
|
||||||
"sass-loader": "10.2.0",
|
"sass-loader": "10.2.0",
|
||||||
"sinon": "11.1.1",
|
"sinon": "11.1.1",
|
||||||
|
|
|
@ -20,10 +20,10 @@ export type AppPropsType = Readonly<{
|
||||||
hasCustomTitleBar: boolean;
|
hasCustomTitleBar: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const App = ({
|
export function App({
|
||||||
executeMenuRole,
|
executeMenuRole,
|
||||||
hasCustomTitleBar,
|
hasCustomTitleBar,
|
||||||
}: AppPropsType): JSX.Element => {
|
}: AppPropsType): JSX.Element {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
@ -57,4 +57,4 @@ export const App = ({
|
||||||
</div>
|
</div>
|
||||||
</TitleBarContainer>
|
</TitleBarContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ const getClassName = ({ noMessage, empty }: Props) => {
|
||||||
return styles.main;
|
return styles.main;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppStage: React.ComponentType<Props> = props => {
|
export function AppStage(props: Props): JSX.Element {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
next,
|
next,
|
||||||
|
@ -101,4 +101,4 @@ export const AppStage: React.ComponentType<Props> = props => {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { StickerGrid } from '../../components/StickerGrid';
|
||||||
import { stickersDuck } from '../../store';
|
import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
|
||||||
export const DropStage: React.ComponentType = () => {
|
export function DropStage(): JSX.Element {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const stickerPaths = stickersDuck.useStickerOrder();
|
const stickerPaths = stickersDuck.useStickerOrder();
|
||||||
const stickersReady = stickersDuck.useStickersReady();
|
const stickersReady = stickersDuck.useStickersReady();
|
||||||
|
@ -40,4 +40,4 @@ export const DropStage: React.ComponentType = () => {
|
||||||
</div>
|
</div>
|
||||||
</AppStage>
|
</AppStage>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { StickerGrid } from '../../components/StickerGrid';
|
||||||
import { stickersDuck } from '../../store';
|
import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
|
||||||
export const EmojiStage: React.ComponentType = () => {
|
export function EmojiStage(): JSX.Element {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const emojisReady = stickersDuck.useEmojisReady();
|
const emojisReady = stickersDuck.useEmojisReady();
|
||||||
|
|
||||||
|
@ -26,4 +26,4 @@ export const EmojiStage: React.ComponentType = () => {
|
||||||
</div>
|
</div>
|
||||||
</AppStage>
|
</AppStage>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { ConfirmModal } from '../../components/ConfirmModal';
|
||||||
import { stickersDuck } from '../../store';
|
import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
|
||||||
export const MetaStage: React.ComponentType = () => {
|
export function MetaStage(): JSX.Element {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const actions = stickersDuck.useStickerActions();
|
const actions = stickersDuck.useStickerActions();
|
||||||
const valid = stickersDuck.useAllDataValid();
|
const valid = stickersDuck.useAllDataValid();
|
||||||
|
@ -99,4 +99,4 @@ export const MetaStage: React.ComponentType = () => {
|
||||||
</div>
|
</div>
|
||||||
</AppStage>
|
</AppStage>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
import { Intl } from '../../../ts/components/Intl';
|
import { Intl } from '../../../ts/components/Intl';
|
||||||
|
|
||||||
export const ShareStage: React.ComponentType = () => {
|
export function ShareStage(): JSX.Element {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const actions = stickersDuck.useStickerActions();
|
const actions = stickersDuck.useStickerActions();
|
||||||
const title = stickersDuck.useTitle();
|
const title = stickersDuck.useTitle();
|
||||||
|
@ -94,4 +94,4 @@ export const ShareStage: React.ComponentType = () => {
|
||||||
) : null}
|
) : null}
|
||||||
</AppStage>
|
</AppStage>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ const handleCancel = () => {
|
||||||
history.push('/add-meta');
|
history.push('/add-meta');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UploadStage: React.ComponentType = () => {
|
export function UploadStage(): JSX.Element {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const actions = stickersDuck.useStickerActions();
|
const actions = stickersDuck.useStickerActions();
|
||||||
const cover = stickersDuck.useCover();
|
const cover = stickersDuck.useCover();
|
||||||
|
@ -81,4 +81,4 @@ export const UploadStage: React.ComponentType = () => {
|
||||||
</div>
|
</div>
|
||||||
</AppStage>
|
</AppStage>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ import { ConfirmDialog } from '../elements/ConfirmDialog';
|
||||||
|
|
||||||
export type Mode = 'removable' | 'pick-emoji' | 'add';
|
export type Mode = 'removable' | 'pick-emoji' | 'add';
|
||||||
|
|
||||||
export const ConfirmModal = React.memo((props: Props) => {
|
export const ConfirmModal = React.memo(function ConfirmModalInner(
|
||||||
|
props: Props
|
||||||
|
) {
|
||||||
const { onCancel } = props;
|
const { onCancel } = props;
|
||||||
const [popperRoot, setPopperRoot] = React.useState<HTMLDivElement>();
|
const [popperRoot, setPopperRoot] = React.useState<HTMLDivElement>();
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ export type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShareButtons: React.ComponentType<Props> = React.memo(
|
export const ShareButtons: React.ComponentType<Props> = React.memo(
|
||||||
({ value }) => {
|
function ShareButtonsInner({ value }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const buttonPaths = React.useMemo<
|
const buttonPaths = React.useMemo<
|
||||||
|
|
|
@ -47,7 +47,7 @@ _StickerFrame.story = {
|
||||||
name: 'StickerFrame, add sticker',
|
name: 'StickerFrame, add sticker',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmojiSelectMode = (): JSX.Element => {
|
export function EmojiSelectMode(): JSX.Element {
|
||||||
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp');
|
||||||
const setSkinTone = action('setSkinTone');
|
const setSkinTone = action('setSkinTone');
|
||||||
const onRemove = action('onRemove');
|
const onRemove = action('onRemove');
|
||||||
|
@ -71,7 +71,7 @@ export const EmojiSelectMode = (): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
EmojiSelectMode.story = {
|
EmojiSelectMode.story = {
|
||||||
name: 'StickerFrame, emoji select mode',
|
name: 'StickerFrame, emoji select mode',
|
||||||
|
|
|
@ -66,243 +66,239 @@ const ImageHandle = SortableHandle((props: { src: string }) => (
|
||||||
<img className={styles.image} {...props} alt="Sticker" />
|
<img className={styles.image} {...props} alt="Sticker" />
|
||||||
));
|
));
|
||||||
|
|
||||||
export const StickerFrame = React.memo(
|
export const StickerFrame = React.memo(function StickerFrameInner({
|
||||||
({
|
id,
|
||||||
id,
|
emojiData,
|
||||||
emojiData,
|
image,
|
||||||
image,
|
showGuide,
|
||||||
showGuide,
|
mode,
|
||||||
mode,
|
onRemove,
|
||||||
onRemove,
|
onPickEmoji,
|
||||||
onPickEmoji,
|
skinTone,
|
||||||
skinTone,
|
onSetSkinTone,
|
||||||
onSetSkinTone,
|
onDrop,
|
||||||
onDrop,
|
}: Props) {
|
||||||
}: Props) => {
|
const i18n = useI18n();
|
||||||
const i18n = useI18n();
|
const [emojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
|
||||||
const [emojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
|
const [emojiPopperRoot, setEmojiPopperRoot] =
|
||||||
const [emojiPopperRoot, setEmojiPopperRoot] =
|
React.useState<HTMLElement | null>(null);
|
||||||
React.useState<HTMLElement | null>(null);
|
const [previewActive, setPreviewActive] = React.useState(false);
|
||||||
const [previewActive, setPreviewActive] = React.useState(false);
|
const [previewPopperRoot, setPreviewPopperRoot] =
|
||||||
const [previewPopperRoot, setPreviewPopperRoot] =
|
React.useState<HTMLElement | null>(null);
|
||||||
React.useState<HTMLElement | null>(null);
|
const timerRef = React.useRef<number>();
|
||||||
const timerRef = React.useRef<number>();
|
|
||||||
|
|
||||||
const handleToggleEmojiPicker = React.useCallback(() => {
|
const handleToggleEmojiPicker = React.useCallback(() => {
|
||||||
setEmojiPickerOpen(open => !open);
|
setEmojiPickerOpen(open => !open);
|
||||||
}, [setEmojiPickerOpen]);
|
}, [setEmojiPickerOpen]);
|
||||||
|
|
||||||
const handlePickEmoji = React.useCallback(
|
const handlePickEmoji = React.useCallback(
|
||||||
(emoji: EmojiPickDataType) => {
|
(emoji: EmojiPickDataType) => {
|
||||||
if (!id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!onPickEmoji) {
|
|
||||||
throw new Error(
|
|
||||||
'StickerFrame/handlePickEmoji: onPickEmoji was not provided!'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
onPickEmoji({ id, emoji });
|
|
||||||
setEmojiPickerOpen(false);
|
|
||||||
},
|
|
||||||
[id, onPickEmoji, setEmojiPickerOpen]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemove = React.useCallback(() => {
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!onRemove) {
|
if (!onPickEmoji) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'StickerFrame/handleRemove: onRemove was not provided!'
|
'StickerFrame/handlePickEmoji: onPickEmoji was not provided!'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
onRemove(id);
|
onPickEmoji({ id, emoji });
|
||||||
}, [onRemove, id]);
|
setEmojiPickerOpen(false);
|
||||||
|
},
|
||||||
|
[id, onPickEmoji, setEmojiPickerOpen]
|
||||||
|
);
|
||||||
|
|
||||||
const handleMouseEnter = React.useCallback(() => {
|
const handleRemove = React.useCallback(() => {
|
||||||
window.clearTimeout(timerRef.current);
|
if (!id) {
|
||||||
timerRef.current = window.setTimeout(() => {
|
return;
|
||||||
setPreviewActive(true);
|
}
|
||||||
}, 500);
|
if (!onRemove) {
|
||||||
}, [timerRef, setPreviewActive]);
|
throw new Error('StickerFrame/handleRemove: onRemove was not provided!');
|
||||||
|
}
|
||||||
|
onRemove(id);
|
||||||
|
}, [onRemove, id]);
|
||||||
|
|
||||||
const handleMouseLeave = React.useCallback(() => {
|
const handleMouseEnter = React.useCallback(() => {
|
||||||
|
window.clearTimeout(timerRef.current);
|
||||||
|
timerRef.current = window.setTimeout(() => {
|
||||||
|
setPreviewActive(true);
|
||||||
|
}, 500);
|
||||||
|
}, [timerRef, setPreviewActive]);
|
||||||
|
|
||||||
|
const handleMouseLeave = React.useCallback(() => {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
setPreviewActive(false);
|
||||||
|
}, [timerRef, setPreviewActive]);
|
||||||
|
|
||||||
|
React.useEffect(
|
||||||
|
() => () => {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
setPreviewActive(false);
|
},
|
||||||
}, [timerRef, setPreviewActive]);
|
[timerRef]
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(
|
const { createRoot, removeRoot } = React.useContext(PopperRootContext);
|
||||||
() => () => {
|
|
||||||
clearTimeout(timerRef.current);
|
|
||||||
},
|
|
||||||
[timerRef]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { createRoot, removeRoot } = React.useContext(PopperRootContext);
|
// Create popper root and handle outside clicks
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (emojiPickerOpen) {
|
||||||
|
const root = createRoot();
|
||||||
|
setEmojiPopperRoot(root);
|
||||||
|
const handleOutsideClick = ({ target }: MouseEvent) => {
|
||||||
|
if (!root.contains(target as Node)) {
|
||||||
|
setEmojiPickerOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', handleOutsideClick);
|
||||||
|
|
||||||
// Create popper root and handle outside clicks
|
return () => {
|
||||||
React.useEffect(() => {
|
removeRoot(root);
|
||||||
if (emojiPickerOpen) {
|
setEmojiPopperRoot(null);
|
||||||
const root = createRoot();
|
document.removeEventListener('click', handleOutsideClick);
|
||||||
setEmojiPopperRoot(root);
|
};
|
||||||
const handleOutsideClick = ({ target }: MouseEvent) => {
|
}
|
||||||
if (!root.contains(target as Node)) {
|
|
||||||
setEmojiPickerOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener('click', handleOutsideClick);
|
|
||||||
|
|
||||||
return () => {
|
return noop;
|
||||||
removeRoot(root);
|
}, [
|
||||||
setEmojiPopperRoot(null);
|
createRoot,
|
||||||
document.removeEventListener('click', handleOutsideClick);
|
emojiPickerOpen,
|
||||||
};
|
removeRoot,
|
||||||
}
|
setEmojiPickerOpen,
|
||||||
|
setEmojiPopperRoot,
|
||||||
|
]);
|
||||||
|
|
||||||
return noop;
|
React.useEffect(() => {
|
||||||
}, [
|
if (mode !== 'pick-emoji' && image && previewActive) {
|
||||||
createRoot,
|
const root = createRoot();
|
||||||
emojiPickerOpen,
|
setPreviewPopperRoot(root);
|
||||||
removeRoot,
|
|
||||||
setEmojiPickerOpen,
|
|
||||||
setEmojiPopperRoot,
|
|
||||||
]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
return () => {
|
||||||
if (mode !== 'pick-emoji' && image && previewActive) {
|
removeRoot(root);
|
||||||
const root = createRoot();
|
};
|
||||||
setPreviewPopperRoot(root);
|
}
|
||||||
|
|
||||||
return () => {
|
return noop;
|
||||||
removeRoot(root);
|
}, [
|
||||||
};
|
createRoot,
|
||||||
}
|
image,
|
||||||
|
mode,
|
||||||
|
previewActive,
|
||||||
|
removeRoot,
|
||||||
|
setPreviewPopperRoot,
|
||||||
|
]);
|
||||||
|
|
||||||
return noop;
|
const [dragActive, setDragActive] = React.useState<boolean>(false);
|
||||||
}, [
|
const containerClass = dragActive ? styles.dragActive : styles.container;
|
||||||
createRoot,
|
|
||||||
image,
|
|
||||||
mode,
|
|
||||||
previewActive,
|
|
||||||
removeRoot,
|
|
||||||
setPreviewPopperRoot,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [dragActive, setDragActive] = React.useState<boolean>(false);
|
return (
|
||||||
const containerClass = dragActive ? styles.dragActive : styles.container;
|
<PopperManager>
|
||||||
|
<PopperReference>
|
||||||
return (
|
{({ ref: rootRef }) => (
|
||||||
<PopperManager>
|
<div
|
||||||
<PopperReference>
|
className={containerClass}
|
||||||
{({ ref: rootRef }) => (
|
onMouseEnter={handleMouseEnter}
|
||||||
<div
|
onMouseLeave={handleMouseLeave}
|
||||||
className={containerClass}
|
ref={rootRef}
|
||||||
onMouseEnter={handleMouseEnter}
|
>
|
||||||
onMouseLeave={handleMouseLeave}
|
{
|
||||||
ref={rootRef}
|
// eslint-disable-next-line no-nested-ternary
|
||||||
>
|
mode !== 'add' ? (
|
||||||
{
|
image ? (
|
||||||
// eslint-disable-next-line no-nested-ternary
|
<ImageHandle src={image} />
|
||||||
mode !== 'add' ? (
|
) : (
|
||||||
image ? (
|
<div className={styles.spinner}>{spinnerSvg}</div>
|
||||||
<ImageHandle src={image} />
|
)
|
||||||
) : (
|
) : null
|
||||||
<div className={styles.spinner}>{spinnerSvg}</div>
|
}
|
||||||
)
|
{showGuide && mode !== 'add' ? (
|
||||||
) : null
|
<div className={styles.guide} />
|
||||||
}
|
) : null}
|
||||||
{showGuide && mode !== 'add' ? (
|
{mode === 'add' && onDrop ? (
|
||||||
<div className={styles.guide} />
|
<DropZone
|
||||||
) : null}
|
label={i18n('StickerCreator--DropStage--dragDrop')}
|
||||||
{mode === 'add' && onDrop ? (
|
onDrop={onDrop}
|
||||||
<DropZone
|
inner
|
||||||
label={i18n('StickerCreator--DropStage--dragDrop')}
|
onDragActive={setDragActive}
|
||||||
onDrop={onDrop}
|
/>
|
||||||
inner
|
) : null}
|
||||||
onDragActive={setDragActive}
|
{mode === 'removable' ? (
|
||||||
/>
|
<button
|
||||||
) : null}
|
type="button"
|
||||||
{mode === 'removable' ? (
|
aria-label={i18n('StickerCreator--DropStage--removeSticker')}
|
||||||
<button
|
className={styles.closeButton}
|
||||||
type="button"
|
onClick={handleRemove}
|
||||||
aria-label={i18n('StickerCreator--DropStage--removeSticker')}
|
// Reverse the mouseenter/leave logic for the remove button so
|
||||||
className={styles.closeButton}
|
// we don't accidentally cover the remove button
|
||||||
onClick={handleRemove}
|
onMouseEnter={handleMouseLeave}
|
||||||
// Reverse the mouseenter/leave logic for the remove button so
|
onMouseLeave={handleMouseEnter}
|
||||||
// we don't accidentally cover the remove button
|
>
|
||||||
onMouseEnter={handleMouseLeave}
|
{closeSvg}
|
||||||
onMouseLeave={handleMouseEnter}
|
</button>
|
||||||
>
|
) : null}
|
||||||
{closeSvg}
|
{mode === 'pick-emoji' ? (
|
||||||
</button>
|
<PopperManager>
|
||||||
) : null}
|
<PopperReference>
|
||||||
{mode === 'pick-emoji' ? (
|
{({ ref }) => (
|
||||||
<PopperManager>
|
<button
|
||||||
<PopperReference>
|
type="button"
|
||||||
{({ ref }) => (
|
ref={ref}
|
||||||
<button
|
className={styles.emojiButton}
|
||||||
type="button"
|
onClick={handleToggleEmojiPicker}
|
||||||
ref={ref}
|
|
||||||
className={styles.emojiButton}
|
|
||||||
onClick={handleToggleEmojiPicker}
|
|
||||||
>
|
|
||||||
{emojiData ? (
|
|
||||||
<Emoji {...emojiData} size={24} />
|
|
||||||
) : (
|
|
||||||
<AddEmoji />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</PopperReference>
|
|
||||||
{emojiPickerOpen && emojiPopperRoot
|
|
||||||
? createPortal(
|
|
||||||
<Popper placement="bottom-start">
|
|
||||||
{({ ref, style }) => (
|
|
||||||
<EmojiPicker
|
|
||||||
ref={ref}
|
|
||||||
style={{ ...style, marginTop: '8px' }}
|
|
||||||
i18n={i18n}
|
|
||||||
onPickEmoji={handlePickEmoji}
|
|
||||||
skinTone={skinTone}
|
|
||||||
onSetSkinTone={onSetSkinTone}
|
|
||||||
onClose={handleToggleEmojiPicker}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Popper>,
|
|
||||||
emojiPopperRoot
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
</PopperManager>
|
|
||||||
) : null}
|
|
||||||
{mode !== 'pick-emoji' &&
|
|
||||||
image &&
|
|
||||||
previewActive &&
|
|
||||||
previewPopperRoot
|
|
||||||
? createPortal(
|
|
||||||
<Popper
|
|
||||||
placement="bottom"
|
|
||||||
modifiers={[
|
|
||||||
{ name: 'offset', options: { offset: [undefined, 8] } },
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
{({ ref, style, arrowProps, placement }) => (
|
{emojiData ? (
|
||||||
<StickerPreview
|
<Emoji {...emojiData} size={24} />
|
||||||
ref={ref}
|
) : (
|
||||||
style={style}
|
<AddEmoji />
|
||||||
image={image}
|
|
||||||
arrowProps={arrowProps}
|
|
||||||
placement={placement}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Popper>,
|
</button>
|
||||||
previewPopperRoot
|
)}
|
||||||
)
|
</PopperReference>
|
||||||
: null}
|
{emojiPickerOpen && emojiPopperRoot
|
||||||
</div>
|
? createPortal(
|
||||||
)}
|
<Popper placement="bottom-start">
|
||||||
</PopperReference>
|
{({ ref, style }) => (
|
||||||
</PopperManager>
|
<EmojiPicker
|
||||||
);
|
ref={ref}
|
||||||
}
|
style={{ ...style, marginTop: '8px' }}
|
||||||
);
|
i18n={i18n}
|
||||||
|
onPickEmoji={handlePickEmoji}
|
||||||
|
skinTone={skinTone}
|
||||||
|
onSetSkinTone={onSetSkinTone}
|
||||||
|
onClose={handleToggleEmojiPicker}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Popper>,
|
||||||
|
emojiPopperRoot
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</PopperManager>
|
||||||
|
) : null}
|
||||||
|
{mode !== 'pick-emoji' &&
|
||||||
|
image &&
|
||||||
|
previewActive &&
|
||||||
|
previewPopperRoot
|
||||||
|
? createPortal(
|
||||||
|
<Popper
|
||||||
|
placement="bottom"
|
||||||
|
modifiers={[
|
||||||
|
{ name: 'offset', options: { offset: [undefined, 8] } },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{({ ref, style, arrowProps, placement }) => (
|
||||||
|
<StickerPreview
|
||||||
|
ref={ref}
|
||||||
|
style={style}
|
||||||
|
image={image}
|
||||||
|
arrowProps={arrowProps}
|
||||||
|
placement={placement}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Popper>,
|
||||||
|
previewPopperRoot
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</PopperReference>
|
||||||
|
</PopperManager>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -11,27 +11,29 @@ export type Props = {
|
||||||
author: string;
|
author: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StickerPackPreview = React.memo(
|
export const StickerPackPreview = React.memo(function StickerPackPreviewInner({
|
||||||
({ images, title, author }: Props) => {
|
images,
|
||||||
const i18n = useI18n();
|
title,
|
||||||
|
author,
|
||||||
|
}: Props) {
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.titleBar}>
|
<div className={styles.titleBar}>
|
||||||
{i18n('StickerCreator--Preview--title')}
|
{i18n('StickerCreator--Preview--title')}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.scroller}>
|
<div className={styles.scroller}>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{images.map(src => (
|
{images.map(src => (
|
||||||
<img key={src} className={styles.sticker} src={src} alt={src} />
|
<img key={src} className={styles.sticker} src={src} alt={src} />
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.meta}>
|
|
||||||
<div className={styles.metaTitle}>{title}</div>
|
|
||||||
<div className={styles.metaAuthor}>{author}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className={styles.meta}>
|
||||||
}
|
<div className={styles.metaTitle}>{title}</div>
|
||||||
);
|
<div className={styles.metaAuthor}>{author}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -12,7 +12,11 @@ export type Props = React.HTMLAttributes<HTMLDivElement> & {
|
||||||
|
|
||||||
const DEFAULT_DISMISS = 1e4;
|
const DEFAULT_DISMISS = 1e4;
|
||||||
|
|
||||||
export const Toaster = React.memo(({ loaf, onDismiss, className }: Props) => {
|
export const Toaster = React.memo(function ToasterInner({
|
||||||
|
loaf,
|
||||||
|
onDismiss,
|
||||||
|
className,
|
||||||
|
}: Props) {
|
||||||
const slice = last(loaf);
|
const slice = last(loaf);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
|
@ -27,11 +27,11 @@ const getClassName = ({ primary, pill }: Props) => {
|
||||||
return styles.base;
|
return styles.base;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Button: React.ComponentType<Props> = ({
|
export function Button({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...otherProps
|
...otherProps
|
||||||
}) => {
|
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -41,4 +41,4 @@ export const Button: React.ComponentType<Props> = ({
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -14,14 +14,14 @@ export type Props = {
|
||||||
readonly onCancel: () => unknown;
|
readonly onCancel: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ConfirmDialog: React.ComponentType<Props> = ({
|
export function ConfirmDialog({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
confirm,
|
confirm,
|
||||||
cancel,
|
cancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
}: Props): JSX.Element {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const cancelText = cancel || i18n('StickerCreator--ConfirmDialog--cancel');
|
const cancelText = cancel || i18n('StickerCreator--ConfirmDialog--cancel');
|
||||||
|
|
||||||
|
@ -43,4 +43,4 @@ export const ConfirmDialog: React.ComponentType<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CopyText: React.ComponentType<Props> = React.memo(
|
export const CopyText: React.ComponentType<Props> = React.memo(
|
||||||
({ label, onCopy, value }) => {
|
function CopyTextInner({ label, onCopy, value }) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const handleClick = React.useCallback(() => {
|
const handleClick = React.useCallback(() => {
|
||||||
copy(value);
|
copy(value);
|
||||||
|
|
|
@ -28,7 +28,7 @@ const getClassName = ({ inner }: Props, isDragActive: boolean) => {
|
||||||
return styles.standalone;
|
return styles.standalone;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropZone: React.ComponentType<Props> = props => {
|
export function DropZone(props: Props): JSX.Element {
|
||||||
const { inner, label, onDrop, onDragActive } = props;
|
const { inner, label, onDrop, onDragActive } = props;
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export const DropZone: React.ComponentType<Props> = props => {
|
||||||
<div
|
<div
|
||||||
{...getRootProps({ className: getClassName(props, isDragActive) })}
|
{...getRootProps({ className: getClassName(props, isDragActive) })}
|
||||||
role="button"
|
role="button"
|
||||||
area-label={label}
|
aria-label={label}
|
||||||
>
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
<svg viewBox="0 0 36 36" width="36px" height="36px">
|
<svg viewBox="0 0 36 36" width="36px" height="36px">
|
||||||
|
@ -67,4 +67,4 @@ export const DropZone: React.ComponentType<Props> = props => {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -17,28 +17,30 @@ const checkSvg = (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LabeledCheckbox = React.memo(
|
export const LabeledCheckbox = React.memo(function LabeledCheckboxInner({
|
||||||
({ children, value, onChange }: Props) => {
|
children,
|
||||||
const handleChange = React.useCallback(() => {
|
value,
|
||||||
if (onChange !== undefined) {
|
onChange,
|
||||||
onChange(!value);
|
}: Props) {
|
||||||
}
|
const handleChange = React.useCallback(() => {
|
||||||
}, [onChange, value]);
|
if (onChange !== undefined) {
|
||||||
|
onChange(!value);
|
||||||
|
}
|
||||||
|
}, [onChange, value]);
|
||||||
|
|
||||||
const className = value ? styles.checkboxChecked : styles.checkbox;
|
const className = value ? styles.checkboxChecked : styles.checkbox;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className={styles.base}>
|
<label className={styles.base}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
checked={value}
|
checked={value}
|
||||||
aria-checked={value}
|
aria-checked={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<span className={className}>{value ? checkSvg : null}</span>
|
<span className={className}>{value ? checkSvg : null}</span>
|
||||||
<Inline className={styles.label}>{children}</Inline>
|
<Inline className={styles.label}>{children}</Inline>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
|
@ -12,28 +12,31 @@ export type Props = {
|
||||||
onChange?: (value: string) => unknown;
|
onChange?: (value: string) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LabeledInput = React.memo(
|
export const LabeledInput = React.memo(function LabeledInputInner({
|
||||||
({ children, value, placeholder, onChange }: Props) => {
|
children,
|
||||||
const handleChange = React.useCallback(
|
value,
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
placeholder,
|
||||||
if (onChange !== undefined) {
|
onChange,
|
||||||
onChange(e.currentTarget.value);
|
}: Props) {
|
||||||
}
|
const handleChange = React.useCallback(
|
||||||
},
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
[onChange]
|
if (onChange !== undefined) {
|
||||||
);
|
onChange(e.currentTarget.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className={styles.container}>
|
<label className={styles.container}>
|
||||||
<Inline className={styles.label}>{children}</Inline>
|
<Inline className={styles.label}>{children}</Inline>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
|
@ -10,14 +10,11 @@ export type Props = Pick<MessageMetaProps, 'minutesAgo'> & {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageBubble: React.ComponentType<Props> = ({
|
export function MessageBubble({ children, minutesAgo }: Props): JSX.Element {
|
||||||
children,
|
|
||||||
minutesAgo,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.base}>
|
<div className={styles.base}>
|
||||||
{children}
|
{children}
|
||||||
<MessageMeta kind="bubble" minutesAgo={minutesAgo} />
|
<MessageMeta kind="bubble" minutesAgo={minutesAgo} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ const getItemClass = ({ kind }: Props) => {
|
||||||
return styles.bubble;
|
return styles.bubble;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageMeta = React.memo((props: Props) => {
|
export const MessageMeta = React.memo(function MessageMetaInner(props: Props) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const itemClass = getItemClass(props);
|
const itemClass = getItemClass(props);
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,15 @@ export type Props = MessageMetaProps & {
|
||||||
image: string;
|
image: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageSticker: React.ComponentType<Props> = ({
|
export function MessageSticker({
|
||||||
image,
|
image,
|
||||||
kind,
|
kind,
|
||||||
minutesAgo,
|
minutesAgo,
|
||||||
}) => {
|
}: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={styles.base}>
|
<div className={styles.base}>
|
||||||
<img src={image} alt="Sticker" className={styles.image} />
|
<img src={image} alt="Sticker" className={styles.image} />
|
||||||
<MessageMeta kind={kind} minutesAgo={minutesAgo} />
|
<MessageMeta kind={kind} minutesAgo={minutesAgo} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ export type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PageHeader = React.memo(({ children }: Props) => (
|
export const PageHeader = React.memo(function PageHeaderInner({
|
||||||
<H1 className={styles.base}>{children}</H1>
|
children,
|
||||||
));
|
}: Props) {
|
||||||
|
return <H1 className={styles.base}>{children}</H1>;
|
||||||
|
});
|
||||||
|
|
|
@ -11,11 +11,17 @@ export type Props = Pick<React.HTMLAttributes<HTMLDivElement>, 'className'> & {
|
||||||
readonly total: number;
|
readonly total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProgressBar = React.memo(({ className, count, total }: Props) => (
|
export const ProgressBar = React.memo(function ProgressBarInner({
|
||||||
<div className={classnames(styles.base, className)}>
|
className,
|
||||||
<div
|
count,
|
||||||
className={styles.bar}
|
total,
|
||||||
style={{ width: `${Math.floor((count / total) * 100)}%` }}
|
}: Props) {
|
||||||
/>
|
return (
|
||||||
</div>
|
<div className={classnames(styles.base, className)}>
|
||||||
));
|
<div
|
||||||
|
className={styles.bar}
|
||||||
|
style={{ width: `${Math.floor((count / total) * 100)}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -31,7 +31,9 @@ const getClassName = ({ left, right, top, bottom }: Props) => {
|
||||||
return styles.base;
|
return styles.base;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StoryRow: React.ComponentType<Props> = ({
|
export function StoryRow({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}) => <div className={getClassName(props)}>{children}</div>;
|
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
|
return <div className={getClassName(props)}>{children}</div>;
|
||||||
|
}
|
||||||
|
|
|
@ -9,12 +9,18 @@ export type Props = React.HTMLAttributes<HTMLButtonElement> & {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Toast = React.memo(({ children, className, ...rest }: Props) => (
|
export const Toast = React.memo(function ToastInner({
|
||||||
<button
|
children,
|
||||||
type="button"
|
className,
|
||||||
className={classNames(styles.base, className)}
|
...rest
|
||||||
{...rest}
|
}: Props) {
|
||||||
>
|
return (
|
||||||
{children}
|
<button
|
||||||
</button>
|
type="button"
|
||||||
));
|
className={classNames(styles.base, className)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2019-2020 Signal Messenger, LLC
|
// Copyright 2019-2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable no-script-url, jsx-a11y/anchor-is-valid */
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { text } from '@storybook/addon-knobs';
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
@ -13,7 +13,7 @@ export default {
|
||||||
title: 'Sticker Creator/elements',
|
title: 'Sticker Creator/elements',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Typography = (): JSX.Element => {
|
export function Typography(): JSX.Element {
|
||||||
const child = text('text', 'foo bar');
|
const child = text('text', 'foo bar');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -32,11 +32,9 @@ export const Typography = (): JSX.Element => {
|
||||||
<StoryRow left>
|
<StoryRow left>
|
||||||
<Text>
|
<Text>
|
||||||
{child} {child} {child} {child}{' '}
|
{child} {child} {child} {child}{' '}
|
||||||
<a href="javascript: void 0;">
|
<a href="#">Something something something dark side.</a>
|
||||||
Something something something dark side.
|
|
||||||
</a>
|
|
||||||
</Text>
|
</Text>
|
||||||
</StoryRow>
|
</StoryRow>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -18,30 +18,38 @@ export type ParagraphProps = React.HTMLAttributes<HTMLParagraphElement> & {
|
||||||
};
|
};
|
||||||
export type SpanProps = React.HTMLAttributes<HTMLSpanElement>;
|
export type SpanProps = React.HTMLAttributes<HTMLSpanElement>;
|
||||||
|
|
||||||
export const H1 = React.memo(
|
export const H1 = React.memo(function H1Inner({
|
||||||
({ children, className, ...rest }: Props & HeadingProps) => (
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: Props & HeadingProps) {
|
||||||
|
return (
|
||||||
<h1 className={classnames(styles.h1, className)} {...rest}>
|
<h1 className={classnames(styles.h1, className)} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</h1>
|
</h1>
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
export const H2 = React.memo(
|
export const H2 = React.memo(function H2Inner({
|
||||||
({ children, className, ...rest }: Props & HeadingProps) => (
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: Props & HeadingProps) {
|
||||||
|
return (
|
||||||
<h2 className={classnames(styles.h2, className)} {...rest}>
|
<h2 className={classnames(styles.h2, className)} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</h2>
|
</h2>
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
export const Text = React.memo(
|
export const Text = React.memo(function TextInner({
|
||||||
({
|
children,
|
||||||
children,
|
className,
|
||||||
className,
|
center,
|
||||||
center,
|
secondary,
|
||||||
secondary,
|
...rest
|
||||||
...rest
|
}: Props & ParagraphProps) {
|
||||||
}: Props & ParagraphProps) => (
|
return (
|
||||||
<p
|
<p
|
||||||
className={classnames(
|
className={classnames(
|
||||||
center ? styles.textCenter : styles.text,
|
center ? styles.textCenter : styles.text,
|
||||||
|
@ -52,13 +60,17 @@ export const Text = React.memo(
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</p>
|
</p>
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
export const Inline = React.memo(
|
export const Inline = React.memo(function InlineInner({
|
||||||
({ children, className, ...rest }: Props & SpanProps) => (
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: Props & SpanProps) {
|
||||||
|
return (
|
||||||
<span className={classnames(styles.text, className)} {...rest}>
|
<span className={classnames(styles.text, className)} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
export const AddEmoji = React.memo(() => (
|
export const AddEmoji = React.memo(function AddEmojiInner() {
|
||||||
<svg width="32px" height="20px" viewBox="0 0 32 20">
|
return (
|
||||||
<path d="M21.993 15.5a5.679 5.679 0 0 1-4.6-2.29.75.75 0 1 1 1.212-.883 4.266 4.266 0 0 0 6.786-.015.749.749 0 0 1 1.216.876 5.671 5.671 0 0 1-4.614 2.312zM22 2.5a7.254 7.254 0 0 0-7.5 7.5 7.254 7.254 0 0 0 7.5 7.5 7.254 7.254 0 0 0 7.5-7.5A7.254 7.254 0 0 0 22 2.5M22 1a8.751 8.751 0 0 1 9 9 8.751 8.751 0 0 1-9 9 8.751 8.751 0 0 1-9-9 8.751 8.751 0 0 1 9-9zm-2.75 5.75A1.476 1.476 0 0 0 18 8.375 1.476 1.476 0 0 0 19.25 10a1.476 1.476 0 0 0 1.25-1.625 1.476 1.476 0 0 0-1.25-1.625zm5.5 0a1.476 1.476 0 0 0-1.25 1.625A1.476 1.476 0 0 0 24.75 10 1.476 1.476 0 0 0 26 8.375a1.476 1.476 0 0 0-1.25-1.625zM10 9.25H6.75V6h-1.5v3.25H2v1.5h3.25V14h1.5v-3.25H10z" />
|
<svg width="32px" height="20px" viewBox="0 0 32 20">
|
||||||
</svg>
|
<path d="M21.993 15.5a5.679 5.679 0 0 1-4.6-2.29.75.75 0 1 1 1.212-.883 4.266 4.266 0 0 0 6.786-.015.749.749 0 0 1 1.216.876 5.671 5.671 0 0 1-4.614 2.312zM22 2.5a7.254 7.254 0 0 0-7.5 7.5 7.254 7.254 0 0 0 7.5 7.5 7.254 7.254 0 0 0 7.5-7.5A7.254 7.254 0 0 0 22 2.5M22 1a8.751 8.751 0 0 1 9 9 8.751 8.751 0 0 1-9 9 8.751 8.751 0 0 1-9-9 8.751 8.751 0 0 1 9-9zm-2.75 5.75A1.476 1.476 0 0 0 18 8.375 1.476 1.476 0 0 0 19.25 10a1.476 1.476 0 0 0 1.25-1.625 1.476 1.476 0 0 0-1.25-1.625zm5.5 0a1.476 1.476 0 0 0-1.25 1.625A1.476 1.476 0 0 0 24.75 10 1.476 1.476 0 0 0 26 8.375a1.476 1.476 0 0 0-1.25-1.625zM10 9.25H6.75V6h-1.5v3.25H2v1.5h3.25V14h1.5v-3.25H10z" />
|
||||||
));
|
</svg>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -12,17 +12,19 @@ import { I18n } from './util/i18n';
|
||||||
|
|
||||||
const { localeMessages, SignalContext } = window;
|
const { localeMessages, SignalContext } = window;
|
||||||
|
|
||||||
const ColdRoot = () => (
|
function ColdRoot() {
|
||||||
<ReduxProvider store={store}>
|
return (
|
||||||
<Router history={history}>
|
<ReduxProvider store={store}>
|
||||||
<I18n messages={localeMessages} locale={SignalContext.config.locale}>
|
<Router history={history}>
|
||||||
<App
|
<I18n messages={localeMessages} locale={SignalContext.config.locale}>
|
||||||
executeMenuRole={SignalContext.executeMenuRole}
|
<App
|
||||||
hasCustomTitleBar={SignalContext.OS.hasCustomTitleBar()}
|
executeMenuRole={SignalContext.executeMenuRole}
|
||||||
/>
|
hasCustomTitleBar={SignalContext.OS.hasCustomTitleBar()}
|
||||||
</I18n>
|
/>
|
||||||
</Router>
|
</I18n>
|
||||||
</ReduxProvider>
|
</Router>
|
||||||
);
|
</ReduxProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const Root = hot(ColdRoot);
|
export const Root = hot(ColdRoot);
|
||||||
|
|
|
@ -31,6 +31,8 @@ import type { EmojiPickDataType } from '../../../ts/components/emoji/EmojiPicker
|
||||||
import { convertShortName } from '../../../ts/components/emoji/lib';
|
import { convertShortName } from '../../../ts/components/emoji/lib';
|
||||||
import { isNotNil } from '../../../ts/util/isNotNil';
|
import { isNotNil } from '../../../ts/util/isNotNil';
|
||||||
|
|
||||||
|
type StickerEmojiData = { id: string; emoji: EmojiPickDataType };
|
||||||
|
|
||||||
export const initializeStickers = createAction<Array<string>>(
|
export const initializeStickers = createAction<Array<string>>(
|
||||||
'stickers/initializeStickers'
|
'stickers/initializeStickers'
|
||||||
);
|
);
|
||||||
|
@ -41,8 +43,7 @@ export const removeSticker = createAction<string>('stickers/removeSticker');
|
||||||
export const moveSticker = createAction<SortEnd>('stickers/moveSticker');
|
export const moveSticker = createAction<SortEnd>('stickers/moveSticker');
|
||||||
export const setCover = createAction<StickerImageData>('stickers/setCover');
|
export const setCover = createAction<StickerImageData>('stickers/setCover');
|
||||||
export const resetCover = createAction<StickerImageData>('stickers/resetCover');
|
export const resetCover = createAction<StickerImageData>('stickers/resetCover');
|
||||||
export const setEmoji =
|
export const setEmoji = createAction<StickerEmojiData>('stickers/setEmoji');
|
||||||
createAction<{ id: string; emoji: EmojiPickDataType }>('stickers/setEmoji');
|
|
||||||
export const setTitle = createAction<string>('stickers/setTitle');
|
export const setTitle = createAction<string>('stickers/setTitle');
|
||||||
export const setAuthor = createAction<string>('stickers/setAuthor');
|
export const setAuthor = createAction<string>('stickers/setAuthor');
|
||||||
export const setPackMeta = createAction<PackMetaData>('stickers/setPackMeta');
|
export const setPackMeta = createAction<PackMetaData>('stickers/setPackMeta');
|
||||||
|
|
|
@ -27,11 +27,7 @@ export type I18nProps = {
|
||||||
messages: LocaleMessagesType;
|
messages: LocaleMessagesType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const I18n = ({
|
export function I18n({ messages, locale, children }: I18nProps): JSX.Element {
|
||||||
messages,
|
|
||||||
locale,
|
|
||||||
children,
|
|
||||||
}: I18nProps): JSX.Element => {
|
|
||||||
const { icuMessages, legacyMessages } = React.useMemo(() => {
|
const { icuMessages, legacyMessages } = React.useMemo(() => {
|
||||||
return classifyMessages(messages);
|
return classifyMessages(messages);
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
@ -115,6 +111,6 @@ export const I18n = ({
|
||||||
return (
|
return (
|
||||||
<I18nContext.Provider value={getMessage}>{children}</I18nContext.Provider>
|
<I18nContext.Provider value={getMessage}>{children}</I18nContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const useI18n = (): LocalizerType => React.useContext(I18nContext);
|
export const useI18n = (): LocalizerType => React.useContext(I18nContext);
|
||||||
|
|
|
@ -18,14 +18,14 @@ export type PropsType = {
|
||||||
executeMenuRole: ExecuteMenuRoleType;
|
executeMenuRole: ExecuteMenuRoleType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const About = ({
|
export function About({
|
||||||
closeAbout,
|
closeAbout,
|
||||||
i18n,
|
i18n,
|
||||||
environment,
|
environment,
|
||||||
version,
|
version,
|
||||||
hasCustomTitleBar,
|
hasCustomTitleBar,
|
||||||
executeMenuRole,
|
executeMenuRole,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
useEscapeHandling(closeAbout);
|
useEscapeHandling(closeAbout);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
@ -63,4 +63,4 @@ export const About = ({
|
||||||
</div>
|
</div>
|
||||||
</TitleBarContainer>
|
</TitleBarContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ export default {
|
||||||
},
|
},
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/function-component-definition
|
||||||
const Template: Story<Props> = args => (
|
const Template: Story<Props> = args => (
|
||||||
<AddCaptionModal {...args} theme={React.useContext(StorybookThemeContext)} />
|
<AddCaptionModal {...args} theme={React.useContext(StorybookThemeContext)} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,14 +19,14 @@ export type Props = {
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddCaptionModal = ({
|
export function AddCaptionModal({
|
||||||
i18n,
|
i18n,
|
||||||
onClose,
|
onClose,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
draftText,
|
draftText,
|
||||||
RenderCompositionTextArea,
|
RenderCompositionTextArea,
|
||||||
theme,
|
theme,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element {
|
||||||
const [messageText, setMessageText] = React.useState('');
|
const [messageText, setMessageText] = React.useState('');
|
||||||
|
|
||||||
const [isScrolledTop, setIsScrolledTop] = React.useState(true);
|
const [isScrolledTop, setIsScrolledTop] = React.useState(true);
|
||||||
|
@ -84,4 +84,4 @@ export const AddCaptionModal = ({
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -35,13 +35,15 @@ _MaximumGroupSize.story = {
|
||||||
name: 'Maximum group size',
|
name: 'Maximum group size',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MaximumRecommendedGroupSize = (): JSX.Element => (
|
export function MaximumRecommendedGroupSize(): JSX.Element {
|
||||||
<AddGroupMemberErrorDialog
|
return (
|
||||||
{...defaultProps}
|
<AddGroupMemberErrorDialog
|
||||||
mode={AddGroupMemberErrorDialogMode.RecommendedMaximumGroupSize}
|
{...defaultProps}
|
||||||
recommendedMaximumNumberOfContacts={123}
|
mode={AddGroupMemberErrorDialogMode.RecommendedMaximumGroupSize}
|
||||||
/>
|
recommendedMaximumNumberOfContacts={123}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
MaximumRecommendedGroupSize.story = {
|
MaximumRecommendedGroupSize.story = {
|
||||||
name: 'Maximum recommended group size',
|
name: 'Maximum recommended group size',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021-2022 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { FunctionComponent, ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
@ -28,9 +28,7 @@ type PropsType = {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
} & PropsDataType;
|
} & PropsDataType;
|
||||||
|
|
||||||
export const AddGroupMemberErrorDialog: FunctionComponent<
|
export function AddGroupMemberErrorDialog(props: PropsType): JSX.Element {
|
||||||
PropsType
|
|
||||||
> = props => {
|
|
||||||
const { i18n, onClose } = props;
|
const { i18n, onClose } = props;
|
||||||
|
|
||||||
let title: string;
|
let title: string;
|
||||||
|
@ -57,4 +55,4 @@ export const AddGroupMemberErrorDialog: FunctionComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Alert body={body} i18n={i18n} onClose={onClose} title={title} />;
|
return <Alert body={body} i18n={i18n} onClose={onClose} title={title} />;
|
||||||
};
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default {
|
||||||
},
|
},
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/function-component-definition
|
||||||
const Template: Story<Props> = args => (
|
const Template: Story<Props> = args => (
|
||||||
<AddUserToAnotherGroupModal
|
<AddUserToAnotherGroupModal
|
||||||
{...args}
|
{...args}
|
||||||
|
|
|
@ -42,7 +42,7 @@ type DispatchProps = {
|
||||||
|
|
||||||
export type Props = OwnProps & DispatchProps;
|
export type Props = OwnProps & DispatchProps;
|
||||||
|
|
||||||
export const AddUserToAnotherGroupModal = ({
|
export function AddUserToAnotherGroupModal({
|
||||||
i18n,
|
i18n,
|
||||||
theme,
|
theme,
|
||||||
contact,
|
contact,
|
||||||
|
@ -51,7 +51,7 @@ export const AddUserToAnotherGroupModal = ({
|
||||||
showToast,
|
showToast,
|
||||||
candidateConversations,
|
candidateConversations,
|
||||||
regionCode,
|
regionCode,
|
||||||
}: Props): JSX.Element | null => {
|
}: Props): JSX.Element | null {
|
||||||
const [searchTerm, setSearchTerm] = React.useState('');
|
const [searchTerm, setSearchTerm] = React.useState('');
|
||||||
const [filteredConversations, setFilteredConversations] = React.useState(
|
const [filteredConversations, setFilteredConversations] = React.useState(
|
||||||
filterAndSortConversationsByRecent(candidateConversations, '', undefined)
|
filterAndSortConversationsByRecent(candidateConversations, '', undefined)
|
||||||
|
@ -177,7 +177,6 @@ export const AddUserToAnotherGroupModal = ({
|
||||||
onClickArchiveButton={noop}
|
onClickArchiveButton={noop}
|
||||||
onClickContactCheckbox={noop}
|
onClickContactCheckbox={noop}
|
||||||
onSelectConversation={setSelectedGroupId}
|
onSelectConversation={setSelectedGroupId}
|
||||||
renderMessageSearchResult={_ => <></>}
|
|
||||||
showChooseGroupMembers={noop}
|
showChooseGroupMembers={noop}
|
||||||
lookupConversationWithoutUuid={async _ => undefined}
|
lookupConversationWithoutUuid={async _ => undefined}
|
||||||
showUserNotFoundModal={noop}
|
showUserNotFoundModal={noop}
|
||||||
|
@ -223,4 +222,4 @@ export const AddUserToAnotherGroupModal = ({
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -23,67 +23,75 @@ const defaultProps = {
|
||||||
const LOREM_IPSUM =
|
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.';
|
'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 => (
|
export function TitleAndBodyAreStrings(): JSX.Element {
|
||||||
<Alert
|
return (
|
||||||
{...defaultProps}
|
<Alert
|
||||||
title="Hello world"
|
{...defaultProps}
|
||||||
body="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus."
|
title="Hello world"
|
||||||
/>
|
body="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus."
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
TitleAndBodyAreStrings.story = {
|
TitleAndBodyAreStrings.story = {
|
||||||
name: 'Title and body are strings',
|
name: 'Title and body are strings',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BodyIsAReactNode = (): JSX.Element => (
|
export function BodyIsAReactNode(): JSX.Element {
|
||||||
<Alert
|
return (
|
||||||
{...defaultProps}
|
<Alert
|
||||||
title="Hello world"
|
{...defaultProps}
|
||||||
body={
|
title="Hello world"
|
||||||
<>
|
body={
|
||||||
<span style={{ color: 'red' }}>Hello</span>{' '}
|
<>
|
||||||
<span style={{ color: 'green', fontWeight: 'bold' }}>world</span>!
|
<span style={{ color: 'red' }}>Hello</span>{' '}
|
||||||
</>
|
<span style={{ color: 'green', fontWeight: 'bold' }}>world</span>!
|
||||||
}
|
</>
|
||||||
/>
|
}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
BodyIsAReactNode.story = {
|
BodyIsAReactNode.story = {
|
||||||
name: 'Body is a ReactNode',
|
name: 'Body is a ReactNode',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongBodyWithoutTitle = (): JSX.Element => (
|
export function LongBodyWithoutTitle(): JSX.Element {
|
||||||
<Alert
|
return (
|
||||||
{...defaultProps}
|
<Alert
|
||||||
body={
|
{...defaultProps}
|
||||||
<>
|
body={
|
||||||
<p>{LOREM_IPSUM}</p>
|
<>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
</>
|
<p>{LOREM_IPSUM}</p>
|
||||||
}
|
</>
|
||||||
/>
|
}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
LongBodyWithoutTitle.story = {
|
LongBodyWithoutTitle.story = {
|
||||||
name: 'Long body (without title)',
|
name: 'Long body (without title)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongBodyWithTitle = (): JSX.Element => (
|
export function LongBodyWithTitle(): JSX.Element {
|
||||||
<Alert
|
return (
|
||||||
{...defaultProps}
|
<Alert
|
||||||
title="Hello world"
|
{...defaultProps}
|
||||||
body={
|
title="Hello world"
|
||||||
<>
|
body={
|
||||||
<p>{LOREM_IPSUM}</p>
|
<>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
</>
|
<p>{LOREM_IPSUM}</p>
|
||||||
}
|
</>
|
||||||
/>
|
}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
LongBodyWithTitle.story = {
|
LongBodyWithTitle.story = {
|
||||||
name: 'Long body (with title)',
|
name: 'Long body (with title)',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { FunctionComponent, ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
@ -17,23 +17,25 @@ type PropsType = {
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Alert: FunctionComponent<PropsType> = ({
|
export function Alert({
|
||||||
body,
|
body,
|
||||||
i18n,
|
i18n,
|
||||||
onClose,
|
onClose,
|
||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
}) => (
|
}: PropsType): JSX.Element {
|
||||||
<Modal
|
return (
|
||||||
i18n={i18n}
|
<Modal
|
||||||
modalFooter={
|
i18n={i18n}
|
||||||
<Button onClick={onClose}>{i18n('Confirmation--confirm')}</Button>
|
modalFooter={
|
||||||
}
|
<Button onClick={onClose}>{i18n('Confirmation--confirm')}</Button>
|
||||||
modalName="Alert"
|
}
|
||||||
onClose={onClose}
|
modalName="Alert"
|
||||||
theme={theme}
|
onClose={onClose}
|
||||||
title={title}
|
theme={theme}
|
||||||
>
|
title={title}
|
||||||
{body}
|
>
|
||||||
</Modal>
|
{body}
|
||||||
);
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,6 @@ function getDefaultProps(): PropsType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Hearts = (): JSX.Element => (
|
export function Hearts(): JSX.Element {
|
||||||
<AnimatedEmojiGalore {...getDefaultProps()} />
|
return <AnimatedEmojiGalore {...getDefaultProps()} />;
|
||||||
);
|
}
|
||||||
|
|
|
@ -34,10 +34,10 @@ function transform(y: number, scale: number, rotate: number): string {
|
||||||
return `translateY(${y}px) scale(${scale}) rotate(${rotate}deg)`;
|
return `translateY(${y}px) scale(${scale}) rotate(${rotate}deg)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AnimatedEmojiGalore = ({
|
export function AnimatedEmojiGalore({
|
||||||
emoji,
|
emoji,
|
||||||
onAnimationEnd,
|
onAnimationEnd,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [springs] = useSprings(NUM_EMOJIS, i => ({
|
const [springs] = useSprings(NUM_EMOJIS, i => ({
|
||||||
...to(i, onAnimationEnd),
|
...to(i, onAnimationEnd),
|
||||||
from: from(i),
|
from: from(i),
|
||||||
|
@ -69,4 +69,4 @@ export const AnimatedEmojiGalore = ({
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -15,12 +15,12 @@ type PropsType = {
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AnnouncementsOnlyGroupBanner = ({
|
export function AnnouncementsOnlyGroupBanner({
|
||||||
groupAdmins,
|
groupAdmins,
|
||||||
i18n,
|
i18n,
|
||||||
openConversation,
|
openConversation,
|
||||||
theme,
|
theme,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [isShowingAdmins, setIsShowingAdmins] = useState(false);
|
const [isShowingAdmins, setIsShowingAdmins] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -64,4 +64,4 @@ export const AnnouncementsOnlyGroupBanner = ({
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ type PropsType = {
|
||||||
viewStory: ViewStoryActionCreatorType;
|
viewStory: ViewStoryActionCreatorType;
|
||||||
} & ComponentProps<typeof Inbox>;
|
} & ComponentProps<typeof Inbox>;
|
||||||
|
|
||||||
export const App = ({
|
export function App({
|
||||||
appView,
|
appView,
|
||||||
executeMenuAction,
|
executeMenuAction,
|
||||||
executeMenuRole,
|
executeMenuRole,
|
||||||
|
@ -89,7 +89,7 @@ export const App = ({
|
||||||
toast,
|
toast,
|
||||||
toggleStoriesView,
|
toggleStoriesView,
|
||||||
viewStory,
|
viewStory,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
let contents;
|
let contents;
|
||||||
|
|
||||||
if (appView === AppViewType.Installer) {
|
if (appView === AppViewType.Installer) {
|
||||||
|
@ -183,4 +183,4 @@ export const App = ({
|
||||||
</div>
|
</div>
|
||||||
</TitleBarContainer>
|
</TitleBarContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -102,6 +102,7 @@ const sizes = Object.values(AvatarSize).filter(
|
||||||
x => typeof x === 'number'
|
x => typeof x === 'number'
|
||||||
) as Array<AvatarSize>;
|
) as Array<AvatarSize>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/function-component-definition
|
||||||
const Template: Story<Props> = args => (
|
const Template: Story<Props> = args => (
|
||||||
<>
|
<>
|
||||||
{sizes.map(size => (
|
{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 => (
|
const TemplateSingle: Story<Props> = args => (
|
||||||
<Avatar {...args} size={AvatarSize.ONE_HUNDRED_TWELVE} />
|
<Avatar {...args} size={AvatarSize.ONE_HUNDRED_TWELVE} />
|
||||||
);
|
);
|
||||||
|
@ -198,7 +200,7 @@ SearchIcon.args = createProps({
|
||||||
searchResult: true,
|
searchResult: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Colors = (): JSX.Element => {
|
export function Colors(): JSX.Element {
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -208,7 +210,7 @@ export const Colors = (): JSX.Element => {
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const BrokenColor = Template.bind({});
|
export const BrokenColor = Template.bind({});
|
||||||
BrokenColor.args = createProps({
|
BrokenColor.args = createProps({
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
// Copyright 2018-2022 Signal Messenger, LLC
|
// Copyright 2018-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type {
|
import type { CSSProperties, MouseEvent, ReactChild, ReactNode } from 'react';
|
||||||
CSSProperties,
|
|
||||||
FunctionComponent,
|
|
||||||
MouseEvent,
|
|
||||||
ReactChild,
|
|
||||||
ReactNode,
|
|
||||||
} from 'react';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
@ -98,7 +92,7 @@ const getDefaultBlur = (
|
||||||
): AvatarBlur =>
|
): AvatarBlur =>
|
||||||
shouldBlurAvatar(...args) ? AvatarBlur.BlurPicture : AvatarBlur.NoBlur;
|
shouldBlurAvatar(...args) ? AvatarBlur.BlurPicture : AvatarBlur.NoBlur;
|
||||||
|
|
||||||
export const Avatar: FunctionComponent<Props> = ({
|
export function Avatar({
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
avatarPath,
|
avatarPath,
|
||||||
badge,
|
badge,
|
||||||
|
@ -126,7 +120,7 @@ export const Avatar: FunctionComponent<Props> = ({
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
unblurredAvatarPath,
|
unblurredAvatarPath,
|
||||||
}),
|
}),
|
||||||
}) => {
|
}: Props): JSX.Element {
|
||||||
const [imageBroken, setImageBroken] = useState(false);
|
const [imageBroken, setImageBroken] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -312,7 +306,7 @@ export const Avatar: FunctionComponent<Props> = ({
|
||||||
{badgeNode}
|
{badgeNode}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
// This is only exported for testing.
|
// This is only exported for testing.
|
||||||
export function _getBadgeSize(avatarSize: number): undefined | number {
|
export function _getBadgeSize(avatarSize: number): undefined | number {
|
||||||
|
|
|
@ -23,14 +23,16 @@ export default {
|
||||||
title: 'Components/AvatarColorPicker',
|
title: 'Components/AvatarColorPicker',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (): JSX.Element => (
|
export function Default(): JSX.Element {
|
||||||
<AvatarColorPicker {...createProps()} />
|
return <AvatarColorPicker {...createProps()} />;
|
||||||
);
|
}
|
||||||
|
|
||||||
export const Selected = (): JSX.Element => (
|
export function Selected(): JSX.Element {
|
||||||
<AvatarColorPicker
|
return (
|
||||||
{...createProps({
|
<AvatarColorPicker
|
||||||
selectedColor: AvatarColors[7],
|
{...createProps({
|
||||||
})}
|
selectedColor: AvatarColors[7],
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ export type PropsType = {
|
||||||
selectedColor?: AvatarColorType;
|
selectedColor?: AvatarColorType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarColorPicker = ({
|
export function AvatarColorPicker({
|
||||||
i18n,
|
i18n,
|
||||||
onColorSelected,
|
onColorSelected,
|
||||||
selectedColor,
|
selectedColor,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="AvatarEditor__avatar-selector-title">
|
<div className="AvatarEditor__avatar-selector-title">
|
||||||
|
@ -38,4 +38,4 @@ export const AvatarColorPicker = ({
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -82,28 +82,37 @@ export default {
|
||||||
title: 'Components/AvatarEditor',
|
title: 'Components/AvatarEditor',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoAvatarGroup = (): JSX.Element => (
|
export function NoAvatarGroup(): JSX.Element {
|
||||||
<AvatarEditor
|
return (
|
||||||
{...createProps({ isGroup: true, userAvatarData: getDefaultAvatars(true) })}
|
<AvatarEditor
|
||||||
/>
|
{...createProps({
|
||||||
);
|
isGroup: true,
|
||||||
|
userAvatarData: getDefaultAvatars(true),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NoAvatarGroup.story = {
|
NoAvatarGroup.story = {
|
||||||
name: 'No Avatar (group)',
|
name: 'No Avatar (group)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoAvatarMe = (): JSX.Element => (
|
export function NoAvatarMe(): JSX.Element {
|
||||||
<AvatarEditor {...createProps({ userAvatarData: getDefaultAvatars() })} />
|
return (
|
||||||
);
|
<AvatarEditor {...createProps({ userAvatarData: getDefaultAvatars() })} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NoAvatarMe.story = {
|
NoAvatarMe.story = {
|
||||||
name: 'No Avatar (me)',
|
name: 'No Avatar (me)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HasAvatar = (): JSX.Element => (
|
export function HasAvatar(): JSX.Element {
|
||||||
<AvatarEditor
|
return (
|
||||||
{...createProps({
|
<AvatarEditor
|
||||||
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
{...createProps({
|
||||||
})}
|
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ enum EditMode {
|
||||||
Text = 'Text',
|
Text = 'Text',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AvatarEditor = ({
|
export function AvatarEditor({
|
||||||
avatarColor,
|
avatarColor,
|
||||||
avatarPath,
|
avatarPath,
|
||||||
avatarValue,
|
avatarValue,
|
||||||
|
@ -58,7 +58,7 @@ export const AvatarEditor = ({
|
||||||
userAvatarData,
|
userAvatarData,
|
||||||
replaceAvatar,
|
replaceAvatar,
|
||||||
saveAvatarToDisk,
|
saveAvatarToDisk,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [provisionalSelectedAvatar, setProvisionalSelectedAvatar] = useState<
|
const [provisionalSelectedAvatar, setProvisionalSelectedAvatar] = useState<
|
||||||
AvatarDataType | undefined
|
AvatarDataType | undefined
|
||||||
>();
|
>();
|
||||||
|
@ -298,4 +298,4 @@ export const AvatarEditor = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="AvatarEditor">{content}</div>;
|
return <div className="AvatarEditor">{content}</div>;
|
||||||
};
|
}
|
||||||
|
|
|
@ -25,24 +25,28 @@ export default {
|
||||||
title: 'Components/AvatarIconEditor',
|
title: 'Components/AvatarIconEditor',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PersonalIcon = (): JSX.Element => (
|
export function PersonalIcon(): JSX.Element {
|
||||||
<AvatarIconEditor
|
return (
|
||||||
{...createProps({
|
<AvatarIconEditor
|
||||||
avatarData: createAvatarData({
|
{...createProps({
|
||||||
color: AvatarColors[3],
|
avatarData: createAvatarData({
|
||||||
icon: PersonalAvatarIcons[0],
|
color: AvatarColors[3],
|
||||||
}),
|
icon: PersonalAvatarIcons[0],
|
||||||
})}
|
}),
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const GroupIcon = (): JSX.Element => (
|
export function GroupIcon(): JSX.Element {
|
||||||
<AvatarIconEditor
|
return (
|
||||||
{...createProps({
|
<AvatarIconEditor
|
||||||
avatarData: createAvatarData({
|
{...createProps({
|
||||||
color: AvatarColors[8],
|
avatarData: createAvatarData({
|
||||||
icon: GroupAvatarIcons[0],
|
color: AvatarColors[8],
|
||||||
}),
|
icon: GroupAvatarIcons[0],
|
||||||
})}
|
}),
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@ export type PropsType = {
|
||||||
onClose: (avatarData?: AvatarDataType) => unknown;
|
onClose: (avatarData?: AvatarDataType) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarIconEditor = ({
|
export function AvatarIconEditor({
|
||||||
avatarData: initialAvatarData,
|
avatarData: initialAvatarData,
|
||||||
i18n,
|
i18n,
|
||||||
onClose,
|
onClose,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>();
|
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>();
|
||||||
const [avatarData, setAvatarData] =
|
const [avatarData, setAvatarData] =
|
||||||
useState<AvatarDataType>(initialAvatarData);
|
useState<AvatarDataType>(initialAvatarData);
|
||||||
|
@ -81,4 +81,4 @@ export const AvatarIconEditor = ({
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -32,15 +32,17 @@ export default {
|
||||||
title: 'Components/AvatarLightbox',
|
title: 'Components/AvatarLightbox',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Group = (): JSX.Element => (
|
export function Group(): JSX.Element {
|
||||||
<AvatarLightbox
|
return (
|
||||||
{...createProps({
|
<AvatarLightbox
|
||||||
isGroup: true,
|
{...createProps({
|
||||||
})}
|
isGroup: true,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const Person = (): JSX.Element => {
|
export function Person(): JSX.Element {
|
||||||
const conversation = getDefaultConversation();
|
const conversation = getDefaultConversation();
|
||||||
return (
|
return (
|
||||||
<AvatarLightbox
|
<AvatarLightbox
|
||||||
|
@ -50,12 +52,14 @@ export const Person = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const Photo = (): JSX.Element => (
|
export function Photo(): JSX.Element {
|
||||||
<AvatarLightbox
|
return (
|
||||||
{...createProps({
|
<AvatarLightbox
|
||||||
avatarPath: '/fixtures/kitten-1-64-64.jpg',
|
{...createProps({
|
||||||
})}
|
avatarPath: '/fixtures/kitten-1-64-64.jpg',
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -17,14 +17,14 @@ export type PropsType = {
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarLightbox = ({
|
export function AvatarLightbox({
|
||||||
avatarColor,
|
avatarColor,
|
||||||
avatarPath,
|
avatarPath,
|
||||||
conversationTitle,
|
conversationTitle,
|
||||||
i18n,
|
i18n,
|
||||||
isGroup,
|
isGroup,
|
||||||
onClose,
|
onClose,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Lightbox close={onClose} i18n={i18n} media={[]}>
|
<Lightbox close={onClose} i18n={i18n} media={[]}>
|
||||||
<AvatarPreview
|
<AvatarPreview
|
||||||
|
@ -43,4 +43,4 @@ export const AvatarLightbox = ({
|
||||||
/>
|
/>
|
||||||
</Lightbox>
|
</Lightbox>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -23,21 +23,23 @@ export default {
|
||||||
title: 'Components/AvatarModalButtons',
|
title: 'Components/AvatarModalButtons',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HasChanges = (): JSX.Element => (
|
export function HasChanges(): JSX.Element {
|
||||||
<AvatarModalButtons
|
return (
|
||||||
{...createProps({
|
<AvatarModalButtons
|
||||||
hasChanges: true,
|
{...createProps({
|
||||||
})}
|
hasChanges: true,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
HasChanges.story = {
|
HasChanges.story = {
|
||||||
name: 'Has changes',
|
name: 'Has changes',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoChanges = (): JSX.Element => (
|
export function NoChanges(): JSX.Element {
|
||||||
<AvatarModalButtons {...createProps()} />
|
return <AvatarModalButtons {...createProps()} />;
|
||||||
);
|
}
|
||||||
|
|
||||||
NoChanges.story = {
|
NoChanges.story = {
|
||||||
name: 'No changes',
|
name: 'No changes',
|
||||||
|
|
|
@ -15,12 +15,12 @@ export type PropsType = {
|
||||||
onSave: () => unknown;
|
onSave: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarModalButtons = ({
|
export function AvatarModalButtons({
|
||||||
hasChanges,
|
hasChanges,
|
||||||
i18n,
|
i18n,
|
||||||
onCancel,
|
onCancel,
|
||||||
onSave,
|
onSave,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [confirmDiscardAction, setConfirmDiscardAction] = useState<
|
const [confirmDiscardAction, setConfirmDiscardAction] = useState<
|
||||||
(() => unknown) | undefined
|
(() => unknown) | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
@ -51,4 +51,4 @@ export const AvatarModalButtons = ({
|
||||||
)}
|
)}
|
||||||
</Modal.ButtonFooter>
|
</Modal.ButtonFooter>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -61,54 +61,54 @@ export default {
|
||||||
title: 'Components/Avatar Popup',
|
title: 'Components/Avatar Popup',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarOnly = (): JSX.Element => {
|
export function AvatarOnly(): JSX.Element {
|
||||||
const props = useProps();
|
const props = useProps();
|
||||||
|
|
||||||
return <AvatarPopup {...props} />;
|
return <AvatarPopup {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const HasBadge = (): JSX.Element => {
|
export function HasBadge(): JSX.Element {
|
||||||
const props = useProps({
|
const props = useProps({
|
||||||
badge: getFakeBadge(),
|
badge: getFakeBadge(),
|
||||||
title: 'Janet Yellen',
|
title: 'Janet Yellen',
|
||||||
});
|
});
|
||||||
|
|
||||||
return <AvatarPopup {...props} />;
|
return <AvatarPopup {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
HasBadge.story = {
|
HasBadge.story = {
|
||||||
name: 'Has badge',
|
name: 'Has badge',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Title = (): JSX.Element => {
|
export function Title(): JSX.Element {
|
||||||
const props = useProps({
|
const props = useProps({
|
||||||
title: 'My Great Title',
|
title: 'My Great Title',
|
||||||
});
|
});
|
||||||
|
|
||||||
return <AvatarPopup {...props} />;
|
return <AvatarPopup {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const ProfileName = (): JSX.Element => {
|
export function ProfileName(): JSX.Element {
|
||||||
const props = useProps({
|
const props = useProps({
|
||||||
profileName: 'Sam Neill',
|
profileName: 'Sam Neill',
|
||||||
});
|
});
|
||||||
|
|
||||||
return <AvatarPopup {...props} />;
|
return <AvatarPopup {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const PhoneNumber = (): JSX.Element => {
|
export function PhoneNumber(): JSX.Element {
|
||||||
const props = useProps({
|
const props = useProps({
|
||||||
profileName: 'Sam Neill',
|
profileName: 'Sam Neill',
|
||||||
phoneNumber: '(555) 867-5309',
|
phoneNumber: '(555) 867-5309',
|
||||||
});
|
});
|
||||||
|
|
||||||
return <AvatarPopup {...props} />;
|
return <AvatarPopup {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const UpdateAvailable = (): JSX.Element => {
|
export function UpdateAvailable(): JSX.Element {
|
||||||
const props = useProps({
|
const props = useProps({
|
||||||
hasPendingUpdate: true,
|
hasPendingUpdate: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return <AvatarPopup {...props} />;
|
return <AvatarPopup {...props} />;
|
||||||
};
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export type Props = {
|
||||||
name?: string;
|
name?: string;
|
||||||
} & Omit<AvatarProps, 'onClick'>;
|
} & Omit<AvatarProps, 'onClick'>;
|
||||||
|
|
||||||
export const AvatarPopup = (props: Props): JSX.Element => {
|
export function AvatarPopup(props: Props): JSX.Element {
|
||||||
const {
|
const {
|
||||||
hasPendingUpdate,
|
hasPendingUpdate,
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -121,4 +121,4 @@ export const AvatarPopup = (props: Props): JSX.Element => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -39,85 +39,97 @@ export default {
|
||||||
title: 'Components/AvatarPreview',
|
title: 'Components/AvatarPreview',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoStatePersonal = (): JSX.Element => (
|
export function NoStatePersonal(): JSX.Element {
|
||||||
<AvatarPreview
|
return (
|
||||||
{...createProps({
|
<AvatarPreview
|
||||||
avatarColor: AvatarColors[0],
|
{...createProps({
|
||||||
conversationTitle: 'Just Testing',
|
avatarColor: AvatarColors[0],
|
||||||
})}
|
conversationTitle: 'Just Testing',
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NoStatePersonal.story = {
|
NoStatePersonal.story = {
|
||||||
name: 'No state (personal)',
|
name: 'No state (personal)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoStateGroup = (): JSX.Element => (
|
export function NoStateGroup(): JSX.Element {
|
||||||
<AvatarPreview
|
return (
|
||||||
{...createProps({
|
<AvatarPreview
|
||||||
avatarColor: AvatarColors[1],
|
{...createProps({
|
||||||
isGroup: true,
|
avatarColor: AvatarColors[1],
|
||||||
})}
|
isGroup: true,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NoStateGroup.story = {
|
NoStateGroup.story = {
|
||||||
name: 'No state (group)',
|
name: 'No state (group)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoStateGroupUploadMe = (): JSX.Element => (
|
export function NoStateGroupUploadMe(): JSX.Element {
|
||||||
<AvatarPreview
|
return (
|
||||||
{...createProps({
|
<AvatarPreview
|
||||||
avatarColor: AvatarColors[1],
|
{...createProps({
|
||||||
isEditable: true,
|
avatarColor: AvatarColors[1],
|
||||||
isGroup: true,
|
isEditable: true,
|
||||||
})}
|
isGroup: true,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NoStateGroupUploadMe.story = {
|
NoStateGroupUploadMe.story = {
|
||||||
name: 'No state (group) + upload me',
|
name: 'No state (group) + upload me',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Value = (): JSX.Element => (
|
export function Value(): JSX.Element {
|
||||||
<AvatarPreview {...createProps({ avatarValue: TEST_IMAGE })} />
|
return <AvatarPreview {...createProps({ avatarValue: TEST_IMAGE })} />;
|
||||||
);
|
}
|
||||||
|
|
||||||
Value.story = {
|
Value.story = {
|
||||||
name: 'value',
|
name: 'value',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Path = (): JSX.Element => (
|
export function Path(): JSX.Element {
|
||||||
<AvatarPreview
|
return (
|
||||||
{...createProps({ avatarPath: '/fixtures/kitten-3-64-64.jpg' })}
|
<AvatarPreview
|
||||||
/>
|
{...createProps({ avatarPath: '/fixtures/kitten-3-64-64.jpg' })}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Path.story = {
|
Path.story = {
|
||||||
name: 'path',
|
name: 'path',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ValuePath = (): JSX.Element => (
|
export function ValuePath(): JSX.Element {
|
||||||
<AvatarPreview
|
return (
|
||||||
{...createProps({
|
<AvatarPreview
|
||||||
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
{...createProps({
|
||||||
avatarValue: TEST_IMAGE,
|
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
||||||
})}
|
avatarValue: TEST_IMAGE,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ValuePath.story = {
|
ValuePath.story = {
|
||||||
name: 'value & path',
|
name: 'value & path',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Style = (): JSX.Element => (
|
export function Style(): JSX.Element {
|
||||||
<AvatarPreview
|
return (
|
||||||
{...createProps({
|
<AvatarPreview
|
||||||
avatarValue: TEST_IMAGE,
|
{...createProps({
|
||||||
style: { height: 100, width: 100 },
|
avatarValue: TEST_IMAGE,
|
||||||
})}
|
style: { height: 100, width: 100 },
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Style.story = {
|
Style.story = {
|
||||||
name: 'style',
|
name: 'style',
|
||||||
|
|
|
@ -33,7 +33,7 @@ enum ImageStatus {
|
||||||
HasImage = 'has-image',
|
HasImage = 'has-image',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AvatarPreview = ({
|
export function AvatarPreview({
|
||||||
avatarColor = AvatarColors[0],
|
avatarColor = AvatarColors[0],
|
||||||
avatarPath,
|
avatarPath,
|
||||||
avatarValue,
|
avatarValue,
|
||||||
|
@ -45,7 +45,7 @@ export const AvatarPreview = ({
|
||||||
onClear,
|
onClear,
|
||||||
onClick,
|
onClick,
|
||||||
style = {},
|
style = {},
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [avatarPreview, setAvatarPreview] = useState<Uint8Array | undefined>();
|
const [avatarPreview, setAvatarPreview] = useState<Uint8Array | undefined>();
|
||||||
|
|
||||||
// Loads the initial avatarPath if one is provided, but only if we're in editable mode.
|
// 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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import type { ReactElement } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { AvatarSize } from './Avatar';
|
import type { AvatarSize } from './Avatar';
|
||||||
|
|
||||||
export const AvatarSpacer = ({
|
export function AvatarSpacer({
|
||||||
size,
|
size,
|
||||||
}: Readonly<{ size: AvatarSize }>): ReactElement => (
|
}: Readonly<{ size: AvatarSize }>): ReactElement {
|
||||||
<div style={{ minWidth: size, height: size, width: size }} />
|
return <div style={{ minWidth: size, height: size, width: size }} />;
|
||||||
);
|
}
|
||||||
|
|
|
@ -24,35 +24,41 @@ export default {
|
||||||
title: 'Components/AvatarTextEditor',
|
title: 'Components/AvatarTextEditor',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Empty = (): JSX.Element => <AvatarTextEditor {...createProps()} />;
|
export function Empty(): JSX.Element {
|
||||||
|
return <AvatarTextEditor {...createProps()} />;
|
||||||
|
}
|
||||||
|
|
||||||
export const WithData = (): JSX.Element => (
|
export function WithData(): JSX.Element {
|
||||||
<AvatarTextEditor
|
return (
|
||||||
{...createProps({
|
<AvatarTextEditor
|
||||||
avatarData: {
|
{...createProps({
|
||||||
id: '123',
|
avatarData: {
|
||||||
color: AvatarColors[6],
|
id: '123',
|
||||||
text: 'SUP',
|
color: AvatarColors[6],
|
||||||
},
|
text: 'SUP',
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
WithData.story = {
|
WithData.story = {
|
||||||
name: 'with Data',
|
name: 'with Data',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithWideCharacters = (): JSX.Element => (
|
export function WithWideCharacters(): JSX.Element {
|
||||||
<AvatarTextEditor
|
return (
|
||||||
{...createProps({
|
<AvatarTextEditor
|
||||||
avatarData: {
|
{...createProps({
|
||||||
id: '123',
|
avatarData: {
|
||||||
color: AvatarColors[6],
|
id: '123',
|
||||||
text: '‱௸𒈙',
|
color: AvatarColors[6],
|
||||||
},
|
text: '‱௸𒈙',
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
WithWideCharacters.story = {
|
WithWideCharacters.story = {
|
||||||
name: 'with wide characters',
|
name: 'with wide characters',
|
||||||
|
|
|
@ -40,12 +40,12 @@ export type PropsType = {
|
||||||
const BUBBLE_SIZE = 120;
|
const BUBBLE_SIZE = 120;
|
||||||
const MAX_LENGTH = 3;
|
const MAX_LENGTH = 3;
|
||||||
|
|
||||||
export const AvatarTextEditor = ({
|
export function AvatarTextEditor({
|
||||||
avatarData,
|
avatarData,
|
||||||
i18n,
|
i18n,
|
||||||
onCancel,
|
onCancel,
|
||||||
onDone,
|
onDone,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const initialText = useMemo(() => avatarData?.text || '', [avatarData]);
|
const initialText = useMemo(() => avatarData?.text || '', [avatarData]);
|
||||||
const initialColor = useMemo(
|
const initialColor = useMemo(
|
||||||
() => avatarData?.color || AvatarColors[0],
|
() => avatarData?.color || AvatarColors[0],
|
||||||
|
@ -194,4 +194,4 @@ export const AvatarTextEditor = ({
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -22,6 +22,6 @@ export default {
|
||||||
title: 'Components/AvatarUploadButton',
|
title: 'Components/AvatarUploadButton',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (): JSX.Element => (
|
export function Default(): JSX.Element {
|
||||||
<AvatarUploadButton {...createProps()} />
|
return <AvatarUploadButton {...createProps()} />;
|
||||||
);
|
}
|
||||||
|
|
|
@ -14,11 +14,11 @@ export type PropsType = {
|
||||||
onChange: (avatar: Uint8Array) => unknown;
|
onChange: (avatar: Uint8Array) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarUploadButton = ({
|
export function AvatarUploadButton({
|
||||||
className,
|
className,
|
||||||
i18n,
|
i18n,
|
||||||
onChange,
|
onChange,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const fileInputRef = useRef<null | HTMLInputElement>(null);
|
const fileInputRef = useRef<null | HTMLInputElement>(null);
|
||||||
|
|
||||||
const [processingFile, setProcessingFile] = useState<File | undefined>();
|
const [processingFile, setProcessingFile] = useState<File | undefined>();
|
||||||
|
@ -84,4 +84,4 @@ export const AvatarUploadButton = ({
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ type PropsType = {
|
||||||
className?: string;
|
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 hostRef = useRef<HTMLDivElement | null>(null);
|
||||||
const viewRef = useRef<Backbone.View | undefined>(undefined);
|
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 className={className} ref={hostRef} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -9,24 +9,28 @@ export default {
|
||||||
title: 'Components/BadgeDescription',
|
title: 'Components/BadgeDescription',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NormalName = (): JSX.Element => (
|
export function NormalName(): JSX.Element {
|
||||||
<BadgeDescription
|
return (
|
||||||
template="{short_name} is here! Hello, {short_name}! {short_name}, I think you're great. This is not replaced: {not_replaced}"
|
<BadgeDescription
|
||||||
firstName="Alice"
|
template="{short_name} is here! Hello, {short_name}! {short_name}, I think you're great. This is not replaced: {not_replaced}"
|
||||||
title="Should not be seen"
|
firstName="Alice"
|
||||||
/>
|
title="Should not be seen"
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NormalName.story = {
|
NormalName.story = {
|
||||||
name: 'Normal name',
|
name: 'Normal name',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NameWithRtlOverrides = (): JSX.Element => (
|
export function NameWithRtlOverrides(): JSX.Element {
|
||||||
<BadgeDescription
|
return (
|
||||||
template="Hello, {short_name}! {short_name}, I think you're great."
|
<BadgeDescription
|
||||||
title={'Flip-\u202eflop'}
|
template="Hello, {short_name}! {short_name}, I think you're great."
|
||||||
/>
|
title={'Flip-\u202eflop'}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NameWithRtlOverrides.story = {
|
NameWithRtlOverrides.story = {
|
||||||
name: 'Name with RTL overrides',
|
name: 'Name with RTL overrides',
|
||||||
|
|
|
@ -27,108 +27,120 @@ const defaultProps: ComponentProps<typeof BadgeDialog> = {
|
||||||
title: 'Alice Levine',
|
title: 'Alice Levine',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoBadgesClosedImmediately = (): JSX.Element => (
|
export function NoBadgesClosedImmediately(): JSX.Element {
|
||||||
<BadgeDialog {...defaultProps} badges={[]} />
|
return <BadgeDialog {...defaultProps} badges={[]} />;
|
||||||
);
|
}
|
||||||
|
|
||||||
NoBadgesClosedImmediately.story = {
|
NoBadgesClosedImmediately.story = {
|
||||||
name: 'No badges (closed immediately)',
|
name: 'No badges (closed immediately)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OneBadge = (): JSX.Element => (
|
export function OneBadge(): JSX.Element {
|
||||||
<BadgeDialog {...defaultProps} badges={getFakeBadges(1)} />
|
return <BadgeDialog {...defaultProps} badges={getFakeBadges(1)} />;
|
||||||
);
|
}
|
||||||
|
|
||||||
OneBadge.story = {
|
OneBadge.story = {
|
||||||
name: 'One badge',
|
name: 'One badge',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BadgeWithNoImageShouldBeImpossible = (): JSX.Element => (
|
export function BadgeWithNoImageShouldBeImpossible(): JSX.Element {
|
||||||
<BadgeDialog
|
return (
|
||||||
{...defaultProps}
|
<BadgeDialog
|
||||||
badges={[
|
{...defaultProps}
|
||||||
{
|
badges={[
|
||||||
...getFakeBadge(),
|
{
|
||||||
images: [],
|
...getFakeBadge(),
|
||||||
},
|
images: [],
|
||||||
]}
|
},
|
||||||
/>
|
]}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
BadgeWithNoImageShouldBeImpossible.story = {
|
BadgeWithNoImageShouldBeImpossible.story = {
|
||||||
name: 'Badge with no image (should be impossible)',
|
name: 'Badge with no image (should be impossible)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BadgeWithPendingImage = (): JSX.Element => (
|
export function BadgeWithPendingImage(): JSX.Element {
|
||||||
<BadgeDialog
|
return (
|
||||||
{...defaultProps}
|
<BadgeDialog
|
||||||
badges={[
|
{...defaultProps}
|
||||||
{
|
badges={[
|
||||||
...getFakeBadge(),
|
{
|
||||||
images: Array(4).fill(
|
...getFakeBadge(),
|
||||||
zipObject(
|
images: Array(4).fill(
|
||||||
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(
|
|
||||||
zipObject(
|
zipObject(
|
||||||
Object.values(BadgeImageTheme),
|
Object.values(BadgeImageTheme),
|
||||||
repeat({ url: 'https://example.com/ignored.svg' })
|
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 = {
|
BadgeWithOnlyOneLowDetailImage.story = {
|
||||||
name: 'Badge with only one, low-detail image',
|
name: 'Badge with only one, low-detail image',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FiveBadges = (): JSX.Element => (
|
export function FiveBadges(): JSX.Element {
|
||||||
<BadgeDialog {...defaultProps} badges={getFakeBadges(5)} />
|
return <BadgeDialog {...defaultProps} badges={getFakeBadges(5)} />;
|
||||||
);
|
}
|
||||||
|
|
||||||
FiveBadges.story = {
|
FiveBadges.story = {
|
||||||
name: 'Five badges',
|
name: 'Five badges',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ManyBadges = (): JSX.Element => (
|
export function ManyBadges(): JSX.Element {
|
||||||
<BadgeDialog {...defaultProps} badges={getFakeBadges(50)} />
|
return <BadgeDialog {...defaultProps} badges={getFakeBadges(50)} />;
|
||||||
);
|
}
|
||||||
|
|
||||||
ManyBadges.story = {
|
ManyBadges.story = {
|
||||||
name: 'Many badges',
|
name: 'Many badges',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ManyBadgesUserIsASubscriber = (): JSX.Element => (
|
export function ManyBadgesUserIsASubscriber(): JSX.Element {
|
||||||
<BadgeDialog {...defaultProps} areWeASubscriber badges={getFakeBadges(50)} />
|
return (
|
||||||
);
|
<BadgeDialog
|
||||||
|
{...defaultProps}
|
||||||
|
areWeASubscriber
|
||||||
|
badges={getFakeBadges(50)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ManyBadgesUserIsASubscriber.story = {
|
ManyBadgesUserIsASubscriber.story = {
|
||||||
name: 'Many badges, user is a subscriber',
|
name: 'Many badges, user is a subscriber',
|
||||||
|
|
|
@ -30,35 +30,41 @@ export default {
|
||||||
title: 'Components/BetterAvatar',
|
title: 'Components/BetterAvatar',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Text = (): JSX.Element => (
|
export function Text(): JSX.Element {
|
||||||
<BetterAvatar
|
return (
|
||||||
{...createProps({
|
<BetterAvatar
|
||||||
avatarData: createAvatarData({
|
{...createProps({
|
||||||
color: AvatarColors[0],
|
avatarData: createAvatarData({
|
||||||
text: 'AH',
|
color: AvatarColors[0],
|
||||||
}),
|
text: 'AH',
|
||||||
})}
|
}),
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const PersonalIcon = (): JSX.Element => (
|
export function PersonalIcon(): JSX.Element {
|
||||||
<BetterAvatar
|
return (
|
||||||
{...createProps({
|
<BetterAvatar
|
||||||
avatarData: createAvatarData({
|
{...createProps({
|
||||||
color: AvatarColors[1],
|
avatarData: createAvatarData({
|
||||||
icon: PersonalAvatarIcons[1],
|
color: AvatarColors[1],
|
||||||
}),
|
icon: PersonalAvatarIcons[1],
|
||||||
})}
|
}),
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const GroupIcon = (): JSX.Element => (
|
export function GroupIcon(): JSX.Element {
|
||||||
<BetterAvatar
|
return (
|
||||||
{...createProps({
|
<BetterAvatar
|
||||||
avatarData: createAvatarData({
|
{...createProps({
|
||||||
color: AvatarColors[1],
|
avatarData: createAvatarData({
|
||||||
icon: GroupAvatarIcons[1],
|
color: AvatarColors[1],
|
||||||
}),
|
icon: GroupAvatarIcons[1],
|
||||||
})}
|
}),
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -21,14 +21,14 @@ export type PropsType = {
|
||||||
size?: AvatarSize;
|
size?: AvatarSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BetterAvatar = ({
|
export function BetterAvatar({
|
||||||
avatarData,
|
avatarData,
|
||||||
i18n,
|
i18n,
|
||||||
isSelected,
|
isSelected,
|
||||||
onClick,
|
onClick,
|
||||||
onDelete,
|
onDelete,
|
||||||
size = 48,
|
size = 48,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>(
|
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>(
|
||||||
avatarData.buffer
|
avatarData.buffer
|
||||||
);
|
);
|
||||||
|
@ -115,4 +115,4 @@ export const BetterAvatar = ({
|
||||||
)}
|
)}
|
||||||
</BetterAvatarBubble>
|
</BetterAvatarBubble>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -27,32 +27,38 @@ export default {
|
||||||
title: 'Components/BetterAvatarBubble',
|
title: 'Components/BetterAvatarBubble',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Children = (): JSX.Element => (
|
export function Children(): JSX.Element {
|
||||||
<BetterAvatarBubble
|
return (
|
||||||
{...createProps({
|
<BetterAvatarBubble
|
||||||
children: <div>HI</div>,
|
{...createProps({
|
||||||
color: AvatarColors[8],
|
children: <div>HI</div>,
|
||||||
})}
|
color: AvatarColors[8],
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const Selected = (): JSX.Element => (
|
export function Selected(): JSX.Element {
|
||||||
<BetterAvatarBubble
|
return (
|
||||||
{...createProps({
|
<BetterAvatarBubble
|
||||||
color: AvatarColors[1],
|
{...createProps({
|
||||||
isSelected: true,
|
color: AvatarColors[1],
|
||||||
})}
|
isSelected: true,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const Style = (): JSX.Element => (
|
export function Style(): JSX.Element {
|
||||||
<BetterAvatarBubble
|
return (
|
||||||
{...createProps({
|
<BetterAvatarBubble
|
||||||
style: {
|
{...createProps({
|
||||||
height: 120,
|
style: {
|
||||||
width: 120,
|
height: 120,
|
||||||
},
|
width: 120,
|
||||||
color: AvatarColors[2],
|
},
|
||||||
})}
|
color: AvatarColors[2],
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ export type PropsType = {
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BetterAvatarBubble = ({
|
export function BetterAvatarBubble({
|
||||||
children,
|
children,
|
||||||
color,
|
color,
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -26,7 +26,7 @@ export const BetterAvatarBubble = ({
|
||||||
onDelete,
|
onDelete,
|
||||||
onSelect,
|
onSelect,
|
||||||
style,
|
style,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -58,4 +58,4 @@ export const BetterAvatarBubble = ({
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -10,55 +10,65 @@ export default {
|
||||||
title: 'Components/Button',
|
title: 'Components/Button',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const KitchenSink = (): JSX.Element => (
|
export function KitchenSink(): JSX.Element {
|
||||||
<>
|
return (
|
||||||
{Object.values(ButtonVariant).map(variant => (
|
<>
|
||||||
<React.Fragment key={variant}>
|
{Object.values(ButtonVariant).map(variant => (
|
||||||
{[ButtonSize.Large, ButtonSize.Medium, ButtonSize.Small].map(size => (
|
<React.Fragment key={variant}>
|
||||||
<React.Fragment key={size}>
|
{[ButtonSize.Large, ButtonSize.Medium, ButtonSize.Small].map(size => (
|
||||||
<p>
|
<React.Fragment key={size}>
|
||||||
<Button onClick={action('onClick')} size={size} variant={variant}>
|
<p>
|
||||||
{variant}
|
<Button
|
||||||
</Button>
|
onClick={action('onClick')}
|
||||||
</p>
|
size={size}
|
||||||
<p>
|
variant={variant}
|
||||||
<Button
|
>
|
||||||
disabled
|
{variant}
|
||||||
onClick={action('onClick')}
|
</Button>
|
||||||
size={size}
|
</p>
|
||||||
variant={variant}
|
<p>
|
||||||
>
|
<Button
|
||||||
{variant}
|
disabled
|
||||||
</Button>
|
onClick={action('onClick')}
|
||||||
</p>
|
size={size}
|
||||||
</React.Fragment>
|
variant={variant}
|
||||||
))}
|
>
|
||||||
</React.Fragment>
|
{variant}
|
||||||
))}
|
</Button>
|
||||||
</>
|
</p>
|
||||||
);
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
KitchenSink.story = {
|
KitchenSink.story = {
|
||||||
name: 'Kitchen sink',
|
name: 'Kitchen sink',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AriaLabel = (): JSX.Element => (
|
export function AriaLabel(): JSX.Element {
|
||||||
<Button
|
return (
|
||||||
aria-label="hello"
|
<Button
|
||||||
className="module-ForwardMessageModal__header--back"
|
aria-label="hello"
|
||||||
onClick={action('onClick')}
|
className="module-ForwardMessageModal__header--back"
|
||||||
/>
|
onClick={action('onClick')}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
AriaLabel.story = {
|
AriaLabel.story = {
|
||||||
name: 'aria-label',
|
name: 'aria-label',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomStyles = (): JSX.Element => (
|
export function CustomStyles(): JSX.Element {
|
||||||
<Button onClick={action('onClick')} style={{ transform: 'rotate(5deg)' }}>
|
return (
|
||||||
Hello world
|
<Button onClick={action('onClick')} style={{ transform: 'rotate(5deg)' }}>
|
||||||
</Button>
|
Hello world
|
||||||
);
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
CustomStyles.story = {
|
CustomStyles.story = {
|
||||||
name: 'Custom styles',
|
name: 'Custom styles',
|
||||||
|
|
|
@ -100,7 +100,7 @@ const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
|
export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
|
||||||
(props, ref) => {
|
function ButtonInner(props, ref) {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
|
|
@ -12,12 +12,12 @@ export type PropsType = {
|
||||||
color?: AvatarColorType;
|
color?: AvatarColorType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallBackgroundBlur = ({
|
export function CallBackgroundBlur({
|
||||||
avatarPath,
|
avatarPath,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
color,
|
color,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -39,4 +39,4 @@ export const CallBackgroundBlur = ({
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -121,128 +121,142 @@ export default {
|
||||||
title: 'Components/CallManager',
|
title: 'Components/CallManager',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoCall = (): JSX.Element => <CallManager {...createProps()} />;
|
export function NoCall(): JSX.Element {
|
||||||
|
return <CallManager {...createProps()} />;
|
||||||
|
}
|
||||||
|
|
||||||
export const OngoingDirectCall = (): JSX.Element => (
|
export function OngoingDirectCall(): JSX.Element {
|
||||||
<CallManager
|
return (
|
||||||
{...createProps({
|
<CallManager
|
||||||
activeCall: {
|
{...createProps({
|
||||||
...getCommonActiveCallData(),
|
activeCall: {
|
||||||
callMode: CallMode.Direct,
|
...getCommonActiveCallData(),
|
||||||
callState: CallState.Accepted,
|
callMode: CallMode.Direct,
|
||||||
peekedParticipants: [],
|
callState: CallState.Accepted,
|
||||||
remoteParticipants: [
|
peekedParticipants: [],
|
||||||
{ hasRemoteVideo: true, presenting: false, title: 'Remy' },
|
remoteParticipants: [
|
||||||
],
|
{ hasRemoteVideo: true, presenting: false, title: 'Remy' },
|
||||||
},
|
],
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const OngoingGroupCall = (): JSX.Element => (
|
export function OngoingGroupCall(): JSX.Element {
|
||||||
<CallManager
|
return (
|
||||||
{...createProps({
|
<CallManager
|
||||||
activeCall: {
|
{...createProps({
|
||||||
...getCommonActiveCallData(),
|
activeCall: {
|
||||||
callMode: CallMode.Group,
|
...getCommonActiveCallData(),
|
||||||
connectionState: GroupCallConnectionState.Connected,
|
callMode: CallMode.Group,
|
||||||
conversationsWithSafetyNumberChanges: [],
|
connectionState: GroupCallConnectionState.Connected,
|
||||||
deviceCount: 0,
|
conversationsWithSafetyNumberChanges: [],
|
||||||
joinState: GroupCallJoinState.Joined,
|
deviceCount: 0,
|
||||||
maxDevices: 5,
|
joinState: GroupCallJoinState.Joined,
|
||||||
groupMembers: [],
|
maxDevices: 5,
|
||||||
isConversationTooBigToRing: false,
|
groupMembers: [],
|
||||||
peekedParticipants: [],
|
isConversationTooBigToRing: false,
|
||||||
remoteParticipants: [],
|
peekedParticipants: [],
|
||||||
remoteAudioLevels: new Map<number, number>(),
|
remoteParticipants: [],
|
||||||
},
|
remoteAudioLevels: new Map<number, number>(),
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const RingingDirectCall = (): JSX.Element => (
|
export function RingingDirectCall(): JSX.Element {
|
||||||
<CallManager
|
return (
|
||||||
{...createProps({
|
<CallManager
|
||||||
incomingCall: {
|
{...createProps({
|
||||||
callMode: CallMode.Direct as const,
|
incomingCall: {
|
||||||
conversation: getConversation(),
|
callMode: CallMode.Direct as const,
|
||||||
isVideoCall: true,
|
conversation: getConversation(),
|
||||||
},
|
isVideoCall: true,
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
RingingDirectCall.story = {
|
RingingDirectCall.story = {
|
||||||
name: 'Ringing (direct call)',
|
name: 'Ringing (direct call)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RingingGroupCall = (): JSX.Element => (
|
export function RingingGroupCall(): JSX.Element {
|
||||||
<CallManager
|
return (
|
||||||
{...createProps({
|
<CallManager
|
||||||
incomingCall: {
|
{...createProps({
|
||||||
callMode: CallMode.Group as const,
|
incomingCall: {
|
||||||
conversation: {
|
callMode: CallMode.Group as const,
|
||||||
...getConversation(),
|
conversation: {
|
||||||
type: 'group',
|
...getConversation(),
|
||||||
title: 'Tahoe Trip',
|
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 = {
|
RingingGroupCall.story = {
|
||||||
name: 'Ringing (group call)',
|
name: 'Ringing (group call)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallRequestNeeded = (): JSX.Element => (
|
export function CallRequestNeeded(): JSX.Element {
|
||||||
<CallManager
|
return (
|
||||||
{...createProps({
|
<CallManager
|
||||||
activeCall: {
|
{...createProps({
|
||||||
...getCommonActiveCallData(),
|
activeCall: {
|
||||||
callEndedReason: CallEndedReason.RemoteHangupNeedPermission,
|
...getCommonActiveCallData(),
|
||||||
callMode: CallMode.Direct,
|
callEndedReason: CallEndedReason.RemoteHangupNeedPermission,
|
||||||
callState: CallState.Accepted,
|
callMode: CallMode.Direct,
|
||||||
peekedParticipants: [],
|
callState: CallState.Accepted,
|
||||||
remoteParticipants: [
|
peekedParticipants: [],
|
||||||
{ hasRemoteVideo: true, presenting: false, title: 'Mike' },
|
remoteParticipants: [
|
||||||
],
|
{ hasRemoteVideo: true, presenting: false, title: 'Mike' },
|
||||||
},
|
],
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const GroupCallSafetyNumberChanged = (): JSX.Element => (
|
export function GroupCallSafetyNumberChanged(): JSX.Element {
|
||||||
<CallManager
|
return (
|
||||||
{...createProps({
|
<CallManager
|
||||||
activeCall: {
|
{...createProps({
|
||||||
...getCommonActiveCallData(),
|
activeCall: {
|
||||||
callMode: CallMode.Group,
|
...getCommonActiveCallData(),
|
||||||
connectionState: GroupCallConnectionState.Connected,
|
callMode: CallMode.Group,
|
||||||
conversationsWithSafetyNumberChanges: [
|
connectionState: GroupCallConnectionState.Connected,
|
||||||
{
|
conversationsWithSafetyNumberChanges: [
|
||||||
...getDefaultConversation({
|
{
|
||||||
title: 'Aaron',
|
...getDefaultConversation({
|
||||||
}),
|
title: 'Aaron',
|
||||||
},
|
}),
|
||||||
],
|
},
|
||||||
deviceCount: 0,
|
],
|
||||||
joinState: GroupCallJoinState.Joined,
|
deviceCount: 0,
|
||||||
maxDevices: 5,
|
joinState: GroupCallJoinState.Joined,
|
||||||
groupMembers: [],
|
maxDevices: 5,
|
||||||
isConversationTooBigToRing: false,
|
groupMembers: [],
|
||||||
peekedParticipants: [],
|
isConversationTooBigToRing: false,
|
||||||
remoteParticipants: [],
|
peekedParticipants: [],
|
||||||
remoteAudioLevels: new Map<number, number>(),
|
remoteParticipants: [],
|
||||||
},
|
remoteAudioLevels: new Map<number, number>(),
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
GroupCallSafetyNumberChanged.story = {
|
GroupCallSafetyNumberChanged.story = {
|
||||||
name: 'Group call - Safety Number Changed',
|
name: 'Group call - Safety Number Changed',
|
||||||
|
|
|
@ -105,7 +105,7 @@ type ActiveCallManagerPropsType = PropsType & {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
function ActiveCallManager({
|
||||||
activeCall,
|
activeCall,
|
||||||
availableCameras,
|
availableCameras,
|
||||||
cancelCall,
|
cancelCall,
|
||||||
|
@ -137,7 +137,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
toggleScreenRecordingPermissionsDialog,
|
toggleScreenRecordingPermissionsDialog,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
toggleSpeakerView,
|
toggleSpeakerView,
|
||||||
}) => {
|
}: ActiveCallManagerPropsType): JSX.Element {
|
||||||
const {
|
const {
|
||||||
conversation,
|
conversation,
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
|
@ -374,9 +374,9 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const CallManager: React.FC<PropsType> = props => {
|
export function CallManager(props: PropsType): JSX.Element | null {
|
||||||
const {
|
const {
|
||||||
acceptCall,
|
acceptCall,
|
||||||
activeCall,
|
activeCall,
|
||||||
|
@ -449,7 +449,7 @@ export const CallManager: React.FC<PropsType> = props => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
}
|
||||||
|
|
||||||
function getShouldRing({
|
function getShouldRing({
|
||||||
activeCall,
|
activeCall,
|
||||||
|
|
|
@ -29,11 +29,11 @@ type Props = {
|
||||||
|
|
||||||
const AUTO_CLOSE_MS = 10000;
|
const AUTO_CLOSE_MS = 10000;
|
||||||
|
|
||||||
export const CallNeedPermissionScreen: React.FC<Props> = ({
|
export function CallNeedPermissionScreen({
|
||||||
conversation,
|
conversation,
|
||||||
i18n,
|
i18n,
|
||||||
close,
|
close,
|
||||||
}) => {
|
}: Props): JSX.Element {
|
||||||
const title = conversation.title || i18n('unknownContact');
|
const title = conversation.title || i18n('unknownContact');
|
||||||
|
|
||||||
const autoCloseAtRef = useRef<number>(Date.now() + AUTO_CLOSE_MS);
|
const autoCloseAtRef = useRef<number>(Date.now() + AUTO_CLOSE_MS);
|
||||||
|
@ -79,4 +79,4 @@ export const CallNeedPermissionScreen: React.FC<Props> = ({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -189,11 +189,11 @@ export default {
|
||||||
title: 'Components/CallScreen',
|
title: 'Components/CallScreen',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (): JSX.Element => {
|
export function Default(): JSX.Element {
|
||||||
return <CallScreen {...createProps()} />;
|
return <CallScreen {...createProps()} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const PreRing = (): JSX.Element => {
|
export function PreRing(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CallScreen
|
<CallScreen
|
||||||
{...createProps({
|
{...createProps({
|
||||||
|
@ -202,7 +202,7 @@ export const PreRing = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
PreRing.story = {
|
PreRing.story = {
|
||||||
name: 'Pre-Ring',
|
name: 'Pre-Ring',
|
||||||
|
@ -241,7 +241,7 @@ export const _Ended = (): JSX.Element => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HasLocalAudio = (): JSX.Element => {
|
export function HasLocalAudio(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CallScreen
|
<CallScreen
|
||||||
{...createProps({
|
{...createProps({
|
||||||
|
@ -250,13 +250,13 @@ export const HasLocalAudio = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
HasLocalAudio.story = {
|
HasLocalAudio.story = {
|
||||||
name: 'hasLocalAudio',
|
name: 'hasLocalAudio',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HasLocalVideo = (): JSX.Element => {
|
export function HasLocalVideo(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CallScreen
|
<CallScreen
|
||||||
{...createProps({
|
{...createProps({
|
||||||
|
@ -265,13 +265,13 @@ export const HasLocalVideo = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
HasLocalVideo.story = {
|
HasLocalVideo.story = {
|
||||||
name: 'hasLocalVideo',
|
name: 'hasLocalVideo',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HasRemoteVideo = (): JSX.Element => {
|
export function HasRemoteVideo(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CallScreen
|
<CallScreen
|
||||||
{...createProps({
|
{...createProps({
|
||||||
|
@ -280,34 +280,36 @@ export const HasRemoteVideo = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
HasRemoteVideo.story = {
|
HasRemoteVideo.story = {
|
||||||
name: 'hasRemoteVideo',
|
name: 'hasRemoteVideo',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall1 = (): JSX.Element => (
|
export function GroupCall1(): JSX.Element {
|
||||||
<CallScreen
|
return (
|
||||||
{...createProps({
|
<CallScreen
|
||||||
callMode: CallMode.Group,
|
{...createProps({
|
||||||
remoteParticipants: [
|
callMode: CallMode.Group,
|
||||||
{
|
remoteParticipants: [
|
||||||
demuxId: 0,
|
{
|
||||||
hasRemoteAudio: true,
|
demuxId: 0,
|
||||||
hasRemoteVideo: true,
|
hasRemoteAudio: true,
|
||||||
presenting: false,
|
hasRemoteVideo: true,
|
||||||
sharingScreen: false,
|
presenting: false,
|
||||||
videoAspectRatio: 1.3,
|
sharingScreen: false,
|
||||||
...getDefaultConversation({
|
videoAspectRatio: 1.3,
|
||||||
isBlocked: false,
|
...getDefaultConversation({
|
||||||
uuid: '72fa60e5-25fb-472d-8a56-e56867c57dda',
|
isBlocked: false,
|
||||||
title: 'Tyler',
|
uuid: '72fa60e5-25fb-472d-8a56-e56867c57dda',
|
||||||
}),
|
title: 'Tyler',
|
||||||
},
|
}),
|
||||||
],
|
},
|
||||||
})}
|
],
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
GroupCall1.story = {
|
GroupCall1.story = {
|
||||||
name: 'Group call - 1',
|
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 (
|
return (
|
||||||
<CallScreen
|
<CallScreen
|
||||||
{...createProps({
|
{...createProps({
|
||||||
|
@ -344,74 +346,80 @@ export const GroupCallMany = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCallMany.story = {
|
GroupCallMany.story = {
|
||||||
name: 'Group call - Many',
|
name: 'Group call - Many',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCallReconnecting = (): JSX.Element => (
|
export function GroupCallReconnecting(): JSX.Element {
|
||||||
<CallScreen
|
return (
|
||||||
{...createProps({
|
<CallScreen
|
||||||
callMode: CallMode.Group,
|
{...createProps({
|
||||||
connectionState: GroupCallConnectionState.Reconnecting,
|
callMode: CallMode.Group,
|
||||||
remoteParticipants: [
|
connectionState: GroupCallConnectionState.Reconnecting,
|
||||||
{
|
remoteParticipants: [
|
||||||
demuxId: 0,
|
{
|
||||||
hasRemoteAudio: true,
|
demuxId: 0,
|
||||||
hasRemoteVideo: true,
|
hasRemoteAudio: true,
|
||||||
presenting: false,
|
hasRemoteVideo: true,
|
||||||
sharingScreen: false,
|
presenting: false,
|
||||||
videoAspectRatio: 1.3,
|
sharingScreen: false,
|
||||||
...getDefaultConversation({
|
videoAspectRatio: 1.3,
|
||||||
isBlocked: false,
|
...getDefaultConversation({
|
||||||
title: 'Tyler',
|
isBlocked: false,
|
||||||
uuid: '33871c64-0c22-45ce-8aa4-0ec237ac4a31',
|
title: 'Tyler',
|
||||||
}),
|
uuid: '33871c64-0c22-45ce-8aa4-0ec237ac4a31',
|
||||||
},
|
}),
|
||||||
],
|
},
|
||||||
})}
|
],
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
GroupCallReconnecting.story = {
|
GroupCallReconnecting.story = {
|
||||||
name: 'Group call - reconnecting',
|
name: 'Group call - reconnecting',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall0 = (): JSX.Element => (
|
export function GroupCall0(): JSX.Element {
|
||||||
<CallScreen
|
return (
|
||||||
{...createProps({
|
<CallScreen
|
||||||
callMode: CallMode.Group,
|
{...createProps({
|
||||||
remoteParticipants: [],
|
callMode: CallMode.Group,
|
||||||
})}
|
remoteParticipants: [],
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
GroupCall0.story = {
|
GroupCall0.story = {
|
||||||
name: 'Group call - 0',
|
name: 'Group call - 0',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCallSomeoneIsSharingScreen = (): JSX.Element => (
|
export function GroupCallSomeoneIsSharingScreen(): JSX.Element {
|
||||||
<CallScreen
|
return (
|
||||||
{...createProps({
|
<CallScreen
|
||||||
callMode: CallMode.Group,
|
{...createProps({
|
||||||
remoteParticipants: allRemoteParticipants
|
callMode: CallMode.Group,
|
||||||
.slice(0, 5)
|
remoteParticipants: allRemoteParticipants
|
||||||
.map((participant, index) => ({
|
.slice(0, 5)
|
||||||
...participant,
|
.map((participant, index) => ({
|
||||||
presenting: index === 1,
|
...participant,
|
||||||
sharingScreen: index === 1,
|
presenting: index === 1,
|
||||||
})),
|
sharingScreen: index === 1,
|
||||||
})}
|
})),
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
GroupCallSomeoneIsSharingScreen.story = {
|
GroupCallSomeoneIsSharingScreen.story = {
|
||||||
name: 'Group call - someone is sharing screen',
|
name: 'Group call - someone is sharing screen',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCallSomeoneIsSharingScreenAndYoureReconnecting =
|
export function GroupCallSomeoneIsSharingScreenAndYoureReconnecting(): JSX.Element {
|
||||||
(): JSX.Element => (
|
return (
|
||||||
<CallScreen
|
<CallScreen
|
||||||
{...createProps({
|
{...createProps({
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
|
@ -426,6 +434,7 @@ export const GroupCallSomeoneIsSharingScreenAndYoureReconnecting =
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
GroupCallSomeoneIsSharingScreenAndYoureReconnecting.story = {
|
GroupCallSomeoneIsSharingScreenAndYoureReconnecting.story = {
|
||||||
name: "Group call - someone is sharing screen and you're reconnecting",
|
name: "Group call - someone is sharing screen and you're reconnecting",
|
||||||
|
|
|
@ -119,7 +119,7 @@ function DirectCallHeaderMessage({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CallScreen: React.FC<PropsType> = ({
|
export function CallScreen({
|
||||||
activeCall,
|
activeCall,
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
getPresentingSources,
|
getPresentingSources,
|
||||||
|
@ -143,7 +143,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
toggleScreenRecordingPermissionsDialog,
|
toggleScreenRecordingPermissionsDialog,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
toggleSpeakerView,
|
toggleSpeakerView,
|
||||||
}) => {
|
}: PropsType): JSX.Element {
|
||||||
const {
|
const {
|
||||||
conversation,
|
conversation,
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
|
@ -539,7 +539,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
function getCallModeClassSuffix(
|
function getCallModeClassSuffix(
|
||||||
callMode: CallMode.Direct | CallMode.Group
|
callMode: CallMode.Direct | CallMode.Group
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default {
|
||||||
title: 'Components/CallingAudioIndicator',
|
title: 'Components/CallingAudioIndicator',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Extreme = (): JSX.Element => {
|
export function Extreme(): JSX.Element {
|
||||||
const [audioLevel, setAudioLevel] = useState(1);
|
const [audioLevel, setAudioLevel] = useState(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -30,9 +30,9 @@ export const Extreme = (): JSX.Element => {
|
||||||
audioLevel={audioLevel}
|
audioLevel={audioLevel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const Random = (): JSX.Element => {
|
export function Random(): JSX.Element {
|
||||||
const [audioLevel, setAudioLevel] = useState(1);
|
const [audioLevel, setAudioLevel] = useState(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -51,4 +51,4 @@ export const Random = (): JSX.Element => {
|
||||||
audioLevel={audioLevel}
|
audioLevel={audioLevel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default {
|
||||||
title: 'Components/CallingButton',
|
title: 'Components/CallingButton',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const KitchenSink = (): JSX.Element => {
|
export function KitchenSink(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Object.keys(CallingButtonType).map(buttonType => (
|
{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({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.AUDIO_ON,
|
buttonType: CallingButtonType.AUDIO_ON,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const AudioOff = (): JSX.Element => {
|
export function AudioOff(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.AUDIO_OFF,
|
buttonType: CallingButtonType.AUDIO_OFF,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const AudioDisabled = (): JSX.Element => {
|
export function AudioDisabled(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.AUDIO_DISABLED,
|
buttonType: CallingButtonType.AUDIO_DISABLED,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const VideoOn = (): JSX.Element => {
|
export function VideoOn(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.VIDEO_ON,
|
buttonType: CallingButtonType.VIDEO_ON,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const VideoOff = (): JSX.Element => {
|
export function VideoOff(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.VIDEO_OFF,
|
buttonType: CallingButtonType.VIDEO_OFF,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const VideoDisabled = (): JSX.Element => {
|
export function VideoDisabled(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.VIDEO_DISABLED,
|
buttonType: CallingButtonType.VIDEO_DISABLED,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const TooltipRight = (): JSX.Element => {
|
export function TooltipRight(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
tooltipDirection: TooltipPlacement.Right,
|
tooltipDirection: TooltipPlacement.Right,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
TooltipRight.story = {
|
TooltipRight.story = {
|
||||||
name: 'Tooltip right',
|
name: 'Tooltip right',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PresentingOn = (): JSX.Element => {
|
export function PresentingOn(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.PRESENTING_ON,
|
buttonType: CallingButtonType.PRESENTING_ON,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const PresentingOff = (): JSX.Element => {
|
export function PresentingOff(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
buttonType: CallingButtonType.PRESENTING_OFF,
|
buttonType: CallingButtonType.PRESENTING_OFF,
|
||||||
});
|
});
|
||||||
return <CallingButton {...props} />;
|
return <CallingButton {...props} />;
|
||||||
};
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ export type PropsType = {
|
||||||
tooltipDirection?: TooltipPlacement;
|
tooltipDirection?: TooltipPlacement;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallingButton = ({
|
export function CallingButton({
|
||||||
buttonType,
|
buttonType,
|
||||||
i18n,
|
i18n,
|
||||||
isVisible = true,
|
isVisible = true,
|
||||||
|
@ -43,7 +43,7 @@ export const CallingButton = ({
|
||||||
onMouseEnter,
|
onMouseEnter,
|
||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
tooltipDirection,
|
tooltipDirection,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const uniqueButtonId = useMemo(() => uuid(), []);
|
const uniqueButtonId = useMemo(() => uuid(), []);
|
||||||
|
|
||||||
let classNameSuffix = '';
|
let classNameSuffix = '';
|
||||||
|
@ -145,4 +145,4 @@ export const CallingButton = ({
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -41,11 +41,11 @@ export default {
|
||||||
title: 'Components/CallingDeviceSelection',
|
title: 'Components/CallingDeviceSelection',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (): JSX.Element => {
|
export function Default(): JSX.Element {
|
||||||
return <CallingDeviceSelection {...createProps()} />;
|
return <CallingDeviceSelection {...createProps()} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const SomeDevices = (): JSX.Element => {
|
export function SomeDevices(): JSX.Element {
|
||||||
const availableSpeakers = [
|
const availableSpeakers = [
|
||||||
{
|
{
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
|
@ -72,9 +72,9 @@ export const SomeDevices = (): JSX.Element => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return <CallingDeviceSelection {...props} />;
|
return <CallingDeviceSelection {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const DefaultDevices = (): JSX.Element => {
|
export function DefaultDevices(): JSX.Element {
|
||||||
const availableSpeakers = [
|
const availableSpeakers = [
|
||||||
{
|
{
|
||||||
name: 'default (Headphones)',
|
name: 'default (Headphones)',
|
||||||
|
@ -103,9 +103,9 @@ export const DefaultDevices = (): JSX.Element => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return <CallingDeviceSelection {...props} />;
|
return <CallingDeviceSelection {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const AllDevices = (): JSX.Element => {
|
export function AllDevices(): JSX.Element {
|
||||||
const availableSpeakers = [
|
const availableSpeakers = [
|
||||||
{
|
{
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
|
@ -179,4 +179,4 @@ export const AllDevices = (): JSX.Element => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return <CallingDeviceSelection {...props} />;
|
return <CallingDeviceSelection {...props} />;
|
||||||
};
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ function createCameraChangeHandler(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CallingDeviceSelection = ({
|
export function CallingDeviceSelection({
|
||||||
availableCameras,
|
availableCameras,
|
||||||
availableMicrophones,
|
availableMicrophones,
|
||||||
availableSpeakers,
|
availableSpeakers,
|
||||||
|
@ -126,7 +126,7 @@ export const CallingDeviceSelection = ({
|
||||||
selectedMicrophone,
|
selectedMicrophone,
|
||||||
selectedSpeaker,
|
selectedSpeaker,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element {
|
||||||
const selectedMicrophoneIndex = selectedMicrophone
|
const selectedMicrophoneIndex = selectedMicrophone
|
||||||
? selectedMicrophone.index
|
? selectedMicrophone.index
|
||||||
: undefined;
|
: undefined;
|
||||||
|
@ -212,4 +212,4 @@ export const CallingDeviceSelection = ({
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -34,61 +34,73 @@ export default {
|
||||||
title: 'Components/CallingHeader',
|
title: 'Components/CallingHeader',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (): JSX.Element => <CallingHeader {...createProps()} />;
|
export function Default(): JSX.Element {
|
||||||
|
return <CallingHeader {...createProps()} />;
|
||||||
|
}
|
||||||
|
|
||||||
export const LobbyStyle = (): JSX.Element => (
|
export function LobbyStyle(): JSX.Element {
|
||||||
<CallingHeader
|
return (
|
||||||
{...createProps()}
|
<CallingHeader
|
||||||
title={undefined}
|
{...createProps()}
|
||||||
togglePip={undefined}
|
title={undefined}
|
||||||
onCancel={action('onClose')}
|
togglePip={undefined}
|
||||||
/>
|
onCancel={action('onClose')}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
LobbyStyle.story = {
|
LobbyStyle.story = {
|
||||||
name: 'Lobby style',
|
name: 'Lobby style',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithParticipants = (): JSX.Element => (
|
export function WithParticipants(): JSX.Element {
|
||||||
<CallingHeader
|
return (
|
||||||
{...createProps({
|
<CallingHeader
|
||||||
isGroupCall: true,
|
{...createProps({
|
||||||
participantCount: 10,
|
isGroupCall: true,
|
||||||
})}
|
participantCount: 10,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const WithParticipantsShown = (): JSX.Element => (
|
export function WithParticipantsShown(): JSX.Element {
|
||||||
<CallingHeader
|
return (
|
||||||
{...createProps({
|
<CallingHeader
|
||||||
isGroupCall: true,
|
{...createProps({
|
||||||
participantCount: 10,
|
isGroupCall: true,
|
||||||
showParticipantsList: true,
|
participantCount: 10,
|
||||||
})}
|
showParticipantsList: true,
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
WithParticipantsShown.story = {
|
WithParticipantsShown.story = {
|
||||||
name: 'With Participants (shown)',
|
name: 'With Participants (shown)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongTitle = (): JSX.Element => (
|
export function LongTitle(): JSX.Element {
|
||||||
<CallingHeader
|
return (
|
||||||
{...createProps({
|
<CallingHeader
|
||||||
title:
|
{...createProps({
|
||||||
'What do I got to, what do I got to do to wake you up? To shake you up, to break the structure up?',
|
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 => (
|
export function TitleWithMessage(): JSX.Element {
|
||||||
<CallingHeader
|
return (
|
||||||
{...createProps({
|
<CallingHeader
|
||||||
title: 'Hello world',
|
{...createProps({
|
||||||
message: 'Goodbye earth',
|
title: 'Hello world',
|
||||||
})}
|
message: 'Goodbye earth',
|
||||||
/>
|
})}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
TitleWithMessage.story = {
|
TitleWithMessage.story = {
|
||||||
name: 'Title with message',
|
name: 'Title with message',
|
||||||
|
|
|
@ -23,7 +23,7 @@ export type PropsType = {
|
||||||
toggleSpeakerView?: () => void;
|
toggleSpeakerView?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallingHeader = ({
|
export function CallingHeader({
|
||||||
i18n,
|
i18n,
|
||||||
isInSpeakerView,
|
isInSpeakerView,
|
||||||
isGroupCall = false,
|
isGroupCall = false,
|
||||||
|
@ -36,103 +36,110 @@ export const CallingHeader = ({
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
toggleSpeakerView,
|
toggleSpeakerView,
|
||||||
}: PropsType): JSX.Element => (
|
}: PropsType): JSX.Element {
|
||||||
<div className="module-calling__header">
|
return (
|
||||||
{title ? (
|
<div className="module-calling__header">
|
||||||
<div className="module-calling__header--header-name">{title}</div>
|
{title ? (
|
||||||
) : null}
|
<div className="module-calling__header--header-name">{title}</div>
|
||||||
{message ? (
|
) : null}
|
||||||
<div className="module-ongoing-call__header-message">{message}</div>
|
{message ? (
|
||||||
) : null}
|
<div className="module-ongoing-call__header-message">{message}</div>
|
||||||
<div className="module-calling-tools">
|
) : null}
|
||||||
{isGroupCall && participantCount ? (
|
<div className="module-calling-tools">
|
||||||
<div className="module-calling-tools__button">
|
{isGroupCall && participantCount ? (
|
||||||
<Tooltip
|
<div className="module-calling-tools__button">
|
||||||
content={i18n('calling__participants', [String(participantCount)])}
|
<Tooltip
|
||||||
theme={Theme.Dark}
|
content={i18n('calling__participants', [
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label={i18n('calling__participants', [
|
|
||||||
String(participantCount),
|
String(participantCount),
|
||||||
])}
|
])}
|
||||||
className={classNames('CallingButton__participants--container', {
|
theme={Theme.Dark}
|
||||||
'CallingButton__participants--shown': showParticipantsList,
|
|
||||||
})}
|
|
||||||
onClick={toggleParticipants}
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<i className="CallingButton__participants" />
|
<button
|
||||||
<span className="CallingButton__participants--count">
|
aria-label={i18n('calling__participants', [
|
||||||
{participantCount}
|
String(participantCount),
|
||||||
</span>
|
])}
|
||||||
</button>
|
className={classNames(
|
||||||
</Tooltip>
|
'CallingButton__participants--container',
|
||||||
</div>
|
{
|
||||||
) : null}
|
'CallingButton__participants--shown': showParticipantsList,
|
||||||
<div className="module-calling-tools__button">
|
}
|
||||||
<Tooltip
|
)}
|
||||||
content={i18n('callingDeviceSelection__settings')}
|
onClick={toggleParticipants}
|
||||||
theme={Theme.Dark}
|
type="button"
|
||||||
>
|
>
|
||||||
<button
|
<i className="CallingButton__participants" />
|
||||||
aria-label={i18n('callingDeviceSelection__settings')}
|
<span className="CallingButton__participants--count">
|
||||||
className="CallingButton__settings"
|
{participantCount}
|
||||||
onClick={toggleSettings}
|
</span>
|
||||||
type="button"
|
</button>
|
||||||
/>
|
</Tooltip>
|
||||||
</Tooltip>
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
{isGroupCall && participantCount > 2 && toggleSpeakerView && (
|
|
||||||
<div className="module-calling-tools__button">
|
<div className="module-calling-tools__button">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={i18n(
|
content={i18n('callingDeviceSelection__settings')}
|
||||||
isInSpeakerView
|
|
||||||
? 'calling__switch-view--to-grid'
|
|
||||||
: 'calling__switch-view--to-speaker'
|
|
||||||
)}
|
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
>
|
>
|
||||||
<button
|
<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
|
isInSpeakerView
|
||||||
? 'calling__switch-view--to-grid'
|
? 'calling__switch-view--to-grid'
|
||||||
: 'calling__switch-view--to-speaker'
|
: 'calling__switch-view--to-speaker'
|
||||||
)}
|
)}
|
||||||
className={
|
theme={Theme.Dark}
|
||||||
isInSpeakerView
|
>
|
||||||
? 'CallingButton__grid-view'
|
<button
|
||||||
: 'CallingButton__speaker-view'
|
aria-label={i18n(
|
||||||
}
|
isInSpeakerView
|
||||||
onClick={toggleSpeakerView}
|
? 'calling__switch-view--to-grid'
|
||||||
type="button"
|
: 'calling__switch-view--to-speaker'
|
||||||
/>
|
)}
|
||||||
</Tooltip>
|
className={
|
||||||
</div>
|
isInSpeakerView
|
||||||
)}
|
? 'CallingButton__grid-view'
|
||||||
{togglePip && (
|
: 'CallingButton__speaker-view'
|
||||||
<div className="module-calling-tools__button">
|
}
|
||||||
<Tooltip content={i18n('calling__pip--on')} theme={Theme.Dark}>
|
onClick={toggleSpeakerView}
|
||||||
<button
|
type="button"
|
||||||
aria-label={i18n('calling__pip--on')}
|
/>
|
||||||
className="CallingButton__pip"
|
</Tooltip>
|
||||||
onClick={togglePip}
|
</div>
|
||||||
type="button"
|
)}
|
||||||
/>
|
{togglePip && (
|
||||||
</Tooltip>
|
<div className="module-calling-tools__button">
|
||||||
</div>
|
<Tooltip content={i18n('calling__pip--on')} theme={Theme.Dark}>
|
||||||
)}
|
<button
|
||||||
{onCancel && (
|
aria-label={i18n('calling__pip--on')}
|
||||||
<div className="module-calling-tools__button">
|
className="CallingButton__pip"
|
||||||
<Tooltip content={i18n('cancel')} theme={Theme.Dark}>
|
onClick={togglePip}
|
||||||
<button
|
type="button"
|
||||||
aria-label={i18n('cancel')}
|
/>
|
||||||
className="CallingButton__cancel"
|
</Tooltip>
|
||||||
onClick={onCancel}
|
</div>
|
||||||
type="button"
|
)}
|
||||||
/>
|
{onCancel && (
|
||||||
</Tooltip>
|
<div className="module-calling-tools__button">
|
||||||
</div>
|
<Tooltip content={i18n('cancel')} theme={Theme.Dark}>
|
||||||
)}
|
<button
|
||||||
|
aria-label={i18n('cancel')}
|
||||||
|
className="CallingButton__cancel"
|
||||||
|
onClick={onCancel}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
|
|
|
@ -94,23 +94,23 @@ export default {
|
||||||
title: 'Components/CallingLobby',
|
title: 'Components/CallingLobby',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (): JSX.Element => {
|
export function Default(): JSX.Element {
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const NoCameraNoAvatar = (): JSX.Element => {
|
export function NoCameraNoAvatar(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
availableCameras: [],
|
availableCameras: [],
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
NoCameraNoAvatar.story = {
|
NoCameraNoAvatar.story = {
|
||||||
name: 'No Camera, no avatar',
|
name: 'No Camera, no avatar',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoCameraLocalAvatar = (): JSX.Element => {
|
export function NoCameraLocalAvatar(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
availableCameras: [],
|
availableCameras: [],
|
||||||
me: getDefaultConversation({
|
me: getDefaultConversation({
|
||||||
|
@ -121,52 +121,52 @@ export const NoCameraLocalAvatar = (): JSX.Element => {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
NoCameraLocalAvatar.story = {
|
NoCameraLocalAvatar.story = {
|
||||||
name: 'No Camera, local avatar',
|
name: 'No Camera, local avatar',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LocalVideo = (): JSX.Element => {
|
export function LocalVideo(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const InitiallyMuted = (): JSX.Element => {
|
export function InitiallyMuted(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
hasLocalAudio: false,
|
hasLocalAudio: false,
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
InitiallyMuted.story = {
|
InitiallyMuted.story = {
|
||||||
name: 'Initially muted',
|
name: 'Initially muted',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall0PeekedParticipants = (): JSX.Element => {
|
export function GroupCall0PeekedParticipants(): JSX.Element {
|
||||||
const props = createProps({ isGroupCall: true, peekedParticipants: [] });
|
const props = createProps({ isGroupCall: true, peekedParticipants: [] });
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCall0PeekedParticipants.story = {
|
GroupCall0PeekedParticipants.story = {
|
||||||
name: 'Group Call - 0 peeked participants',
|
name: 'Group Call - 0 peeked participants',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall1PeekedParticipant = (): JSX.Element => {
|
export function GroupCall1PeekedParticipant(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
isGroupCall: true,
|
isGroupCall: true,
|
||||||
peekedParticipants: [{ title: 'Sam' }].map(fakePeekedParticipant),
|
peekedParticipants: [{ title: 'Sam' }].map(fakePeekedParticipant),
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCall1PeekedParticipant.story = {
|
GroupCall1PeekedParticipant.story = {
|
||||||
name: 'Group Call - 1 peeked participant',
|
name: 'Group Call - 1 peeked participant',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall1PeekedParticipantSelf = (): JSX.Element => {
|
export function GroupCall1PeekedParticipantSelf(): JSX.Element {
|
||||||
const uuid = UUID.generate().toString();
|
const uuid = UUID.generate().toString();
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
isGroupCall: true,
|
isGroupCall: true,
|
||||||
|
@ -177,13 +177,13 @@ export const GroupCall1PeekedParticipantSelf = (): JSX.Element => {
|
||||||
peekedParticipants: [fakePeekedParticipant({ title: 'Ash', uuid })],
|
peekedParticipants: [fakePeekedParticipant({ title: 'Ash', uuid })],
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCall1PeekedParticipantSelf.story = {
|
GroupCall1PeekedParticipantSelf.story = {
|
||||||
name: 'Group Call - 1 peeked participant (self)',
|
name: 'Group Call - 1 peeked participant (self)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall4PeekedParticipants = (): JSX.Element => {
|
export function GroupCall4PeekedParticipants(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
isGroupCall: true,
|
isGroupCall: true,
|
||||||
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
|
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
|
||||||
|
@ -191,13 +191,13 @@ export const GroupCall4PeekedParticipants = (): JSX.Element => {
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCall4PeekedParticipants.story = {
|
GroupCall4PeekedParticipants.story = {
|
||||||
name: 'Group Call - 4 peeked participants',
|
name: 'Group Call - 4 peeked participants',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall4PeekedParticipantsParticipantsList = (): JSX.Element => {
|
export function GroupCall4PeekedParticipantsParticipantsList(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
isGroupCall: true,
|
isGroupCall: true,
|
||||||
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
|
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
|
||||||
|
@ -206,13 +206,13 @@ export const GroupCall4PeekedParticipantsParticipantsList = (): JSX.Element => {
|
||||||
showParticipantsList: true,
|
showParticipantsList: true,
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCall4PeekedParticipantsParticipantsList.story = {
|
GroupCall4PeekedParticipantsParticipantsList.story = {
|
||||||
name: 'Group Call - 4 peeked participants (participants list)',
|
name: 'Group Call - 4 peeked participants (participants list)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCallCallFull = (): JSX.Element => {
|
export function GroupCallCallFull(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
isGroupCall: true,
|
isGroupCall: true,
|
||||||
isCallFull: true,
|
isCallFull: true,
|
||||||
|
@ -221,19 +221,19 @@ export const GroupCallCallFull = (): JSX.Element => {
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCallCallFull.story = {
|
GroupCallCallFull.story = {
|
||||||
name: 'Group Call - call full',
|
name: 'Group Call - call full',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall0PeekedParticipantsBigGroup = (): JSX.Element => {
|
export function GroupCall0PeekedParticipantsBigGroup(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
isGroupCall: true,
|
isGroupCall: true,
|
||||||
groupMembers: times(100, () => getDefaultConversation()),
|
groupMembers: times(100, () => getDefaultConversation()),
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupCall0PeekedParticipantsBigGroup.story = {
|
GroupCall0PeekedParticipantsBigGroup.story = {
|
||||||
name: 'Group Call - 0 peeked participants, big group',
|
name: 'Group Call - 0 peeked participants, big group',
|
||||||
|
|
|
@ -63,7 +63,7 @@ export type PropsType = {
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallingLobby = ({
|
export function CallingLobby({
|
||||||
availableCameras,
|
availableCameras,
|
||||||
conversation,
|
conversation,
|
||||||
groupMembers,
|
groupMembers,
|
||||||
|
@ -86,7 +86,7 @@ export const CallingLobby = ({
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
outgoingRing,
|
outgoingRing,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const [isMutedToastVisible, setIsMutedToastVisible] = React.useState(
|
const [isMutedToastVisible, setIsMutedToastVisible] = React.useState(
|
||||||
!hasLocalAudio
|
!hasLocalAudio
|
||||||
);
|
);
|
||||||
|
@ -303,4 +303,4 @@ export const CallingLobby = ({
|
||||||
</div>
|
</div>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { FunctionComponent, ReactChild } from 'react';
|
import type { ReactChild } from 'react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
|
@ -19,6 +19,13 @@ export enum CallingLobbyJoinButtonVariant {
|
||||||
Start = 'Start',
|
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?
|
* 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
|
* 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.
|
* click the button. The button shouldn't resize in that situation.
|
||||||
*/
|
*/
|
||||||
export const CallingLobbyJoinButton: FunctionComponent<{
|
export function CallingLobbyJoinButton({
|
||||||
disabled?: boolean;
|
disabled,
|
||||||
i18n: LocalizerType;
|
i18n,
|
||||||
onClick: () => void;
|
onClick,
|
||||||
variant: CallingLobbyJoinButtonVariant;
|
variant,
|
||||||
}> = ({ disabled, i18n, onClick, variant }) => {
|
}: PropsType): JSX.Element {
|
||||||
const [width, setWidth] = useState<undefined | number>();
|
const [width, setWidth] = useState<undefined | number>();
|
||||||
const [height, setHeight] = useState<undefined | number>();
|
const [height, setHeight] = useState<undefined | number>();
|
||||||
|
|
||||||
|
@ -103,4 +110,4 @@ export const CallingLobbyJoinButton: FunctionComponent<{
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -47,16 +47,16 @@ export default {
|
||||||
title: 'Components/CallingParticipantsList',
|
title: 'Components/CallingParticipantsList',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoOne = (): JSX.Element => {
|
export function NoOne(): JSX.Element {
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
return <CallingParticipantsList {...props} />;
|
return <CallingParticipantsList {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
NoOne.story = {
|
NoOne.story = {
|
||||||
name: 'No one',
|
name: 'No one',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SoloCall = (): JSX.Element => {
|
export function SoloCall(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
participants: [
|
participants: [
|
||||||
createParticipant({
|
createParticipant({
|
||||||
|
@ -65,9 +65,9 @@ export const SoloCall = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
return <CallingParticipantsList {...props} />;
|
return <CallingParticipantsList {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const ManyParticipants = (): JSX.Element => {
|
export function ManyParticipants(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
participants: [
|
participants: [
|
||||||
createParticipant({
|
createParticipant({
|
||||||
|
@ -95,13 +95,13 @@ export const ManyParticipants = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
return <CallingParticipantsList {...props} />;
|
return <CallingParticipantsList {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const Overflow = (): JSX.Element => {
|
export function Overflow(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
participants: Array(50)
|
participants: Array(50)
|
||||||
.fill(null)
|
.fill(null)
|
||||||
.map(() => createParticipant({ title: 'Kirby' })),
|
.map(() => createParticipant({ title: 'Kirby' })),
|
||||||
});
|
});
|
||||||
return <CallingParticipantsList {...props} />;
|
return <CallingParticipantsList {...props} />;
|
||||||
};
|
}
|
||||||
|
|
|
@ -30,7 +30,12 @@ export type PropsType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallingParticipantsList = React.memo(
|
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 [root, setRoot] = React.useState<HTMLElement | null>(null);
|
||||||
|
|
||||||
const modalContainer = useContext(ModalContainerContext) ?? document.body;
|
const modalContainer = useContext(ModalContainerContext) ?? document.body;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { AvatarColors } from '../types/Colors';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { PropsType } from './CallingPip';
|
import type { PropsType } from './CallingPip';
|
||||||
import { CallingPip } from './CallingPip';
|
import { CallingPip } from './CallingPip';
|
||||||
import type { ActiveCallType } from '../types/Calling';
|
import type { ActiveDirectCallType } from '../types/Calling';
|
||||||
import {
|
import {
|
||||||
CallMode,
|
CallMode,
|
||||||
CallViewMode,
|
CallViewMode,
|
||||||
|
@ -52,7 +52,7 @@ const getCommonActiveCallData = () => ({
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultCall: ActiveCallType = {
|
const defaultCall: ActiveDirectCallType = {
|
||||||
...getCommonActiveCallData(),
|
...getCommonActiveCallData(),
|
||||||
callMode: CallMode.Direct as CallMode.Direct,
|
callMode: CallMode.Direct as CallMode.Direct,
|
||||||
callState: CallState.Accepted,
|
callState: CallState.Accepted,
|
||||||
|
@ -80,12 +80,12 @@ export default {
|
||||||
title: 'Components/CallingPip',
|
title: 'Components/CallingPip',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (): JSX.Element => {
|
export function Default(): JSX.Element {
|
||||||
const props = createProps({});
|
const props = createProps({});
|
||||||
return <CallingPip {...props} />;
|
return <CallingPip {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const ContactWithAvatarAndNoVideo = (): JSX.Element => {
|
export function ContactWithAvatarAndNoVideo(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
activeCall: {
|
activeCall: {
|
||||||
...defaultCall,
|
...defaultCall,
|
||||||
|
@ -99,13 +99,13 @@ export const ContactWithAvatarAndNoVideo = (): JSX.Element => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return <CallingPip {...props} />;
|
return <CallingPip {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
ContactWithAvatarAndNoVideo.story = {
|
ContactWithAvatarAndNoVideo.story = {
|
||||||
name: 'Contact (with avatar and no video)',
|
name: 'Contact (with avatar and no video)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ContactNoColor = (): JSX.Element => {
|
export function ContactNoColor(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
activeCall: {
|
activeCall: {
|
||||||
...defaultCall,
|
...defaultCall,
|
||||||
|
@ -116,13 +116,13 @@ export const ContactNoColor = (): JSX.Element => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return <CallingPip {...props} />;
|
return <CallingPip {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
ContactNoColor.story = {
|
ContactNoColor.story = {
|
||||||
name: 'Contact (no color)',
|
name: 'Contact (no color)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupCall = (): JSX.Element => {
|
export function GroupCall(): JSX.Element {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
activeCall: {
|
activeCall: {
|
||||||
...getCommonActiveCallData(),
|
...getCommonActiveCallData(),
|
||||||
|
@ -140,4 +140,4 @@ export const GroupCall = (): JSX.Element => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return <CallingPip {...props} />;
|
return <CallingPip {...props} />;
|
||||||
};
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ const PIP_WIDTH = 120;
|
||||||
const PIP_TOP_MARGIN = 56;
|
const PIP_TOP_MARGIN = 56;
|
||||||
const PIP_PADDING = 8;
|
const PIP_PADDING = 8;
|
||||||
|
|
||||||
export const CallingPip = ({
|
export function CallingPip({
|
||||||
activeCall,
|
activeCall,
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
hangUpActiveCall,
|
hangUpActiveCall,
|
||||||
|
@ -82,7 +82,7 @@ export const CallingPip = ({
|
||||||
switchToPresentationView,
|
switchToPresentationView,
|
||||||
switchFromPresentationView,
|
switchFromPresentationView,
|
||||||
togglePip,
|
togglePip,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
|
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
|
||||||
const localVideoRef = React.useRef(null);
|
const localVideoRef = React.useRef(null);
|
||||||
|
|
||||||
|
@ -315,4 +315,4 @@ export const CallingPip = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -27,13 +27,13 @@ import { nonRenderedRemoteParticipant } from '../util/ringrtc/nonRenderedRemoteP
|
||||||
// less than `MAX_FRAME_HEIGHT`.
|
// less than `MAX_FRAME_HEIGHT`.
|
||||||
const PIP_VIDEO_HEIGHT_PX = 120;
|
const PIP_VIDEO_HEIGHT_PX = 120;
|
||||||
|
|
||||||
const NoVideo = ({
|
function NoVideo({
|
||||||
activeCall,
|
activeCall,
|
||||||
i18n,
|
i18n,
|
||||||
}: {
|
}: {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
}): JSX.Element => {
|
}): JSX.Element {
|
||||||
const {
|
const {
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
avatarPath,
|
avatarPath,
|
||||||
|
@ -68,7 +68,7 @@ const NoVideo = ({
|
||||||
</CallBackgroundBlur>
|
</CallBackgroundBlur>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
|
@ -81,13 +81,13 @@ export type PropsType = {
|
||||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallingPipRemoteVideo = ({
|
export function CallingPipRemoteVideo({
|
||||||
activeCall,
|
activeCall,
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
i18n,
|
i18n,
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element {
|
||||||
const { conversation } = activeCall;
|
const { conversation } = activeCall;
|
||||||
|
|
||||||
const getGroupCallFrameBuffer = useGetCallingFrameBuffer();
|
const getGroupCallFrameBuffer = useGetCallingFrameBuffer();
|
||||||
|
@ -177,4 +177,4 @@ export const CallingPipRemoteVideo = ({
|
||||||
default:
|
default:
|
||||||
throw missingCaseError(activeCall);
|
throw missingCaseError(activeCall);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -24,230 +24,260 @@ export default {
|
||||||
title: 'Components/CallingPreCallInfo',
|
title: 'Components/CallingPreCallInfo',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectConversation = (): JSX.Element => (
|
export function DirectConversation(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultConversation()}
|
<CallingPreCallInfo
|
||||||
i18n={i18n}
|
conversation={getDefaultConversation()}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
ringMode={RingMode.WillRing}
|
me={getDefaultConversation()}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
DirectConversation.story = {
|
DirectConversation.story = {
|
||||||
name: 'Direct conversation',
|
name: 'Direct conversation',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Ring0 = (): JSX.Element => (
|
export function Ring0(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 0)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 0)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ring0.story = {
|
Ring0.story = {
|
||||||
name: 'Group call: Will ring 0 people',
|
name: 'Group call: Will ring 0 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Ring1 = (): JSX.Element => (
|
export function Ring1(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 1)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 1)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ring1.story = {
|
Ring1.story = {
|
||||||
name: 'Group call: Will ring 1 person',
|
name: 'Group call: Will ring 1 person',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Ring2 = (): JSX.Element => (
|
export function Ring2(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 2)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 2)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ring2.story = {
|
Ring2.story = {
|
||||||
name: 'Group call: Will ring 2 people',
|
name: 'Group call: Will ring 2 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Ring3 = (): JSX.Element => (
|
export function Ring3(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 3)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 3)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ring3.story = {
|
Ring3.story = {
|
||||||
name: 'Group call: Will ring 3 people',
|
name: 'Group call: Will ring 3 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Ring4 = (): JSX.Element => (
|
export function Ring4(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 4)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 4)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ring3.story = {
|
Ring3.story = {
|
||||||
name: 'Group call: Will ring 4 people',
|
name: 'Group call: Will ring 4 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notify0 = (): JSX.Element => (
|
export function Notify0(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 0)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 0)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillNotRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillNotRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Notify0.story = {
|
Notify0.story = {
|
||||||
name: 'Group call: Will notify 0 people',
|
name: 'Group call: Will notify 0 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notify1 = (): JSX.Element => (
|
export function Notify1(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 1)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 1)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillNotRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillNotRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Notify1.story = {
|
Notify1.story = {
|
||||||
name: 'Group call: Will notify 1 person',
|
name: 'Group call: Will notify 1 person',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notify2 = (): JSX.Element => (
|
export function Notify2(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 2)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 2)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillNotRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillNotRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Notify2.story = {
|
Notify2.story = {
|
||||||
name: 'Group call: Will notify 2 people',
|
name: 'Group call: Will notify 2 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notify3 = (): JSX.Element => (
|
export function Notify3(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 3)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 3)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillNotRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillNotRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Notify3.story = {
|
Notify3.story = {
|
||||||
name: 'Group call: Will notify 3 people',
|
name: 'Group call: Will notify 3 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notify4 = (): JSX.Element => (
|
export function Notify4(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers.slice(0, 4)}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers.slice(0, 4)}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={[]}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillNotRing}
|
peekedParticipants={[]}
|
||||||
/>
|
ringMode={RingMode.WillNotRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Notify4.story = {
|
Notify4.story = {
|
||||||
name: 'Group call: Will notify 4 people',
|
name: 'Group call: Will notify 4 people',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Peek1 = (): JSX.Element => (
|
export function Peek1(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={otherMembers.slice(0, 1)}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={otherMembers.slice(0, 1)}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Peek1.story = {
|
Peek1.story = {
|
||||||
name: 'Group call: 1 participant peeked',
|
name: 'Group call: 1 participant peeked',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Peek2 = (): JSX.Element => (
|
export function Peek2(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={otherMembers.slice(0, 2)}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={otherMembers.slice(0, 2)}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Peek2.story = {
|
Peek2.story = {
|
||||||
name: 'Group call: 2 participants peeked',
|
name: 'Group call: 2 participants peeked',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Peek3 = (): JSX.Element => (
|
export function Peek3(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={otherMembers.slice(0, 3)}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={otherMembers.slice(0, 3)}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Peek3.story = {
|
Peek3.story = {
|
||||||
name: 'Group call: 3 participants peeked',
|
name: 'Group call: 3 participants peeked',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Peek4 = (): JSX.Element => (
|
export function Peek4(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers}
|
||||||
me={getDefaultConversation()}
|
i18n={i18n}
|
||||||
peekedParticipants={otherMembers.slice(0, 4)}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={otherMembers.slice(0, 4)}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Peek4.story = {
|
Peek4.story = {
|
||||||
name: 'Group call: 4 participants peeked',
|
name: 'Group call: 4 participants peeked',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupConversationYouOnAnOtherDevice = (): JSX.Element => {
|
export function GroupConversationYouOnAnOtherDevice(): JSX.Element {
|
||||||
const me = getDefaultConversation();
|
const me = getDefaultConversation();
|
||||||
return (
|
return (
|
||||||
<CallingPreCallInfo
|
<CallingPreCallInfo
|
||||||
|
@ -259,23 +289,25 @@ export const GroupConversationYouOnAnOtherDevice = (): JSX.Element => {
|
||||||
ringMode={RingMode.WillRing}
|
ringMode={RingMode.WillRing}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
GroupConversationYouOnAnOtherDevice.story = {
|
GroupConversationYouOnAnOtherDevice.story = {
|
||||||
name: 'Group conversation, you on an other device',
|
name: 'Group conversation, you on an other device',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupConversationCallIsFull = (): JSX.Element => (
|
export function GroupConversationCallIsFull(): JSX.Element {
|
||||||
<CallingPreCallInfo
|
return (
|
||||||
conversation={getDefaultGroupConversation()}
|
<CallingPreCallInfo
|
||||||
groupMembers={otherMembers}
|
conversation={getDefaultGroupConversation()}
|
||||||
i18n={i18n}
|
groupMembers={otherMembers}
|
||||||
isCallFull
|
i18n={i18n}
|
||||||
me={getDefaultConversation()}
|
isCallFull
|
||||||
peekedParticipants={otherMembers}
|
me={getDefaultConversation()}
|
||||||
ringMode={RingMode.WillRing}
|
peekedParticipants={otherMembers}
|
||||||
/>
|
ringMode={RingMode.WillRing}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
GroupConversationCallIsFull.story = {
|
GroupConversationCallIsFull.story = {
|
||||||
name: 'Group conversation, call is full',
|
name: 'Group conversation, call is full',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { FunctionComponent } from 'react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
@ -42,7 +41,7 @@ type PropsType = {
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallingPreCallInfo: FunctionComponent<PropsType> = ({
|
export function CallingPreCallInfo({
|
||||||
conversation,
|
conversation,
|
||||||
groupMembers = [],
|
groupMembers = [],
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -50,7 +49,7 @@ export const CallingPreCallInfo: FunctionComponent<PropsType> = ({
|
||||||
me,
|
me,
|
||||||
peekedParticipants = [],
|
peekedParticipants = [],
|
||||||
ringMode,
|
ringMode,
|
||||||
}) => {
|
}: PropsType): JSX.Element {
|
||||||
let subtitle: string;
|
let subtitle: string;
|
||||||
if (ringMode === RingMode.IsRinging) {
|
if (ringMode === RingMode.IsRinging) {
|
||||||
subtitle = i18n('outgoingCallRinging');
|
subtitle = i18n('outgoingCallRinging');
|
||||||
|
@ -183,4 +182,4 @@ export const CallingPreCallInfo: FunctionComponent<PropsType> = ({
|
||||||
<div className="module-CallingPreCallInfo__subtitle">{subtitle}</div>
|
<div className="module-CallingPreCallInfo__subtitle">{subtitle}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue