Upgrade Storybook

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
Jamie Kyle 2023-10-11 12:06:43 -07:00 committed by GitHub
parent 8c966dfbd8
commit 502ea174ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
328 changed files with 10777 additions and 12400 deletions

View File

@ -14,6 +14,7 @@ libtextsecure/components.js
libtextsecure/test/test.js
test/test.js
ts/protobuf/compiled.d.ts
storybook-static/**
# Third-party files
js/Mp3LameEncoder.min.js

View File

@ -300,6 +300,12 @@ module.exports = {
'local-rules/type-alias-readonlydeep': 'error',
},
},
{
files: ['ts/**/*_test.{ts,tsx}'],
rules: {
'func-names': 'off',
},
},
],
rules: {

23
.github/workflows/stories.yml vendored Normal file
View File

@ -0,0 +1,23 @@
# Copyright 2023 Signal Messenger, LLC
# SPDX-License-Identifier: AGPL-3.0-only
name: Stories
on:
push:
branches:
- development
- main
- '[0-9]+.[0-9]+.x'
pull_request:
jobs:
test:
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18.15.0'
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn build:storybook
- run: yarn run-p --race test:storybook:serve test:storybook:test

View File

@ -1,29 +0,0 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
module.exports = {
typescript: {
reactDocgen: false,
},
stories: ['../ts/components/**/*.stories.tsx'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-actions',
'@storybook/addon-controls',
'@storybook/addon-measure',
'@storybook/addon-toolbars',
'@storybook/addon-viewport',
// This must be imported last.
'@storybook/addon-interactions',
// Deprecated! Please remove when all uses have been migrated to controls.
'@storybook/addon-knobs',
],
core: {
builder: 'webpack5',
},
features: {
storyStoreV7: true,
},
};

98
.storybook/main.ts Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { StorybookConfig } from '@storybook/react-webpack5';
import { ProvidePlugin } from 'webpack';
const config: StorybookConfig = {
typescript: {
reactDocgen: false,
},
stories: ['../ts/components/**/*.stories.tsx'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-actions',
'@storybook/addon-controls',
'@storybook/addon-measure',
'@storybook/addon-toolbars',
'@storybook/addon-viewport',
'@storybook/addon-jest',
// This must be imported last.
'@storybook/addon-interactions',
],
framework: '@storybook/react-webpack5',
core: {},
features: {
storyStoreV7: true,
},
staticDirs: [
{ from: '../fonts', to: 'fonts' },
{ from: '../images', to: 'images' },
{ from: '../fixtures', to: 'fixtures' },
],
webpackFinal(config) {
config.cache = {
type: 'filesystem',
};
config.resolve!.extensions = ['.tsx', '.ts', '...'];
config.module!.rules!.unshift({
test: /\.scss$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader', options: { modules: false, url: false } },
{ loader: 'sass-loader' },
],
});
config.module!.rules!.unshift({
test: /\.css$/,
use: [
// prevent storybook defaults from being applied
],
});
config.node = { global: true };
config.externals = {
net: 'commonjs net',
vm: 'commonjs vm',
fs: 'commonjs fs',
async_hooks: 'commonjs async_hooks',
module: 'commonjs module',
stream: 'commonjs stream',
tls: 'commonjs tls',
dns: 'commonjs dns',
http: 'commonjs http',
https: 'commonjs https',
os: 'commonjs os',
constants: 'commonjs constants',
zlib: 'commonjs zlib',
'@signalapp/libsignal-client': 'commonjs @signalapp/libsignal-client',
'@signalapp/libsignal-client/zkgroup':
'commonjs @signalapp/libsignal-client/zkgroup',
'@signalapp/ringrtc': 'commonjs @signalapp/ringrtc',
'@signalapp/better-sqlite3': 'commonjs @signalapp/better-sqlite3',
electron: 'commonjs electron',
'fs-xattr': 'commonjs fs-xattr',
fsevents: 'commonjs fsevents',
'mac-screen-capture-permissions':
'commonjs mac-screen-capture-permissions',
sass: 'commonjs sass',
bufferutil: 'commonjs bufferutil',
'utf-8-validate': 'commonjs utf-8-validate',
};
config.plugins!.push(
new ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
})
);
return config;
},
};
export default config;

View File

@ -1,63 +0,0 @@
<!-- Copyright 2019 Signal Messenger, LLC -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
<!-- prettier-ignore -->
<link rel="stylesheet" href="../stylesheets/manifest.css" />
<link
href="../node_modules/@indutny/frameless-titlebar/dist/styles.css"
rel="stylesheet"
type="text/css"
/>
<script>
// eslint-disable-next-line
const noop = () => {};
window.Whisper = window.Whisper || {};
window.Whisper.events = {
on: noop,
};
window.SignalWindow = window.SignalWindow || {};
window.SignalWindow.log = {
fatal: console.error.bind(console),
error: console.error.bind(console),
warn: console.warn.bind(console),
info: console.info.bind(console),
debug: console.debug.bind(console),
trace: console.trace.bind(console),
};
window.SignalContext = {
activeWindowService: {
isActive: () => true,
registerForActive: noop,
unregisterForActive: noop,
registerForChange: noop,
unregisterForChange: noop,
},
nativeThemeListener: {
getSystemValue: async () => 'light',
subscribe: noop,
unsubscribe: noop,
},
Settings: {
themeSetting: {
getValue: async () => 'light',
},
waitForChange: () => new Promise(noop),
},
OS: {
hasCustomTitleBar: () => false,
},
usernames: {
hash: x => x,
},
config: {},
};
window.ConversationController = window.ConversationController || {};
window.ConversationController.isSignalConversationId = () => false;
window.ConversationController.onConvoMessageMount = noop;
window.getPreferredSystemLocales = () => ['en'];
window.getResolvedMessagesLocaleDirection = () => 'ltr';
</script>

View File

@ -1,17 +1,29 @@
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import '../ts/window.d.ts';
import React from 'react';
import classnames from 'classnames';
import { withKnobs, boolean, optionsKnob } from '@storybook/addon-knobs';
import 'sanitize.css';
import '../stylesheets/manifest.scss';
import '../node_modules/@indutny/frameless-titlebar/dist/styles.css';
import * as styles from './styles.scss';
import messages from '../_locales/en/messages.json';
import { ClassyProvider } from '../ts/components/PopperRootContext';
import { StorybookThemeContext } from './StorybookThemeContext';
import { ThemeType } from '../ts/types/Util';
import { setupI18n } from '../ts/util/setupI18n';
import { HourCyclePreference } from '../ts/types/I18N';
import { Provider } from 'react-redux';
import { Store, combineReducers, createStore } from 'redux';
import { StateType } from '../ts/state/reducer';
import {
ScrollerLockContext,
createScrollerLock,
} from '../ts/hooks/useScrollLock';
const i18n = setupI18n('en', messages);
export const globalTypes = {
mode: {
@ -38,8 +50,73 @@ export const globalTypes = {
},
};
window.i18n = setupI18n('en', messages);
window.getHourCyclePreference = () => HourCyclePreference.UnknownPreference;
const mockStore: Store<StateType> = createStore(
combineReducers({
calling: (state = {}) => state,
conversations: (
state = {
conversationLookup: {},
targetedConversationPanels: {},
}
) => state,
globalModals: (state = {}) => state,
user: (state = {}) => state,
})
);
// eslint-disable-next-line
const noop = () => {};
window.Whisper = window.Whisper || {};
window.Whisper.events = {
on: noop,
};
window.SignalContext = {
i18n,
activeWindowService: {
isActive: () => true,
registerForActive: noop,
unregisterForActive: noop,
registerForChange: noop,
unregisterForChange: noop,
},
nativeThemeListener: {
getSystemTheme: () => 'light',
subscribe: noop,
unsubscribe: noop,
update: () => 'light',
},
Settings: {
themeSetting: {
getValue: async () => 'light',
setValue: async () => 'light',
},
waitForChange: () => new Promise(noop),
},
OS: {
hasCustomTitleBar: () => false,
getClassName: () => '',
platform: '',
release: '',
},
usernames: {
hash: input => Buffer.from(input),
} as any,
config: {} as any,
getHourCyclePreference: () => HourCyclePreference.UnknownPreference,
getPreferredSystemLocales: () => ['en'],
getResolvedMessagesLocaleDirection: () => 'ltr',
};
window.i18n = i18n;
window.ConversationController = window.ConversationController || {};
window.ConversationController.isSignalConversationId = () => false;
window.ConversationController.onConvoMessageMount = noop;
window.reduxStore = mockStore;
const withModeAndThemeProvider = (Story, context) => {
const theme =
@ -75,7 +152,29 @@ const withModeAndThemeProvider = (Story, context) => {
);
};
export const decorators = [withModeAndThemeProvider];
function withMockStoreProvider(Story, context) {
return (
<Provider store={mockStore}>
<Story {...context} />
</Provider>
);
}
function withScrollLockProvider(Story, context) {
return (
<ScrollerLockContext.Provider
value={createScrollerLock('MockStories', () => {})}
>
<Story {...context} />
</ScrollerLockContext.Provider>
);
}
export const decorators = [
withModeAndThemeProvider,
withMockStoreProvider,
withScrollLockProvider,
];
export const parameters = {
axe: {

View File

@ -1,53 +0,0 @@
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const webpack = require('webpack');
module.exports = ({ config }) => {
config.entry.unshift('!!style-loader!css-loader!sanitize.css');
config.cache = {
type: 'filesystem',
};
config.module.rules.unshift({
test: /\.scss$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader?modules=true&localsConvention=camelCaseOnly' },
{ loader: 'sass-loader' },
],
});
config.node = { global: true };
config.externals = {
net: 'commonjs net',
vm: 'commonjs vm',
fs: 'commonjs fs',
async_hooks: 'commonjs async_hooks',
module: 'commonjs module',
stream: 'commonjs stream',
tls: 'commonjs tls',
dns: 'commonjs dns',
http: 'commonjs http',
https: 'commonjs https',
os: 'commonjs os',
constants: 'commonjs constants',
zlib: 'commonjs zlib',
'@signalapp/libsignal-client': 'commonjs @signalapp/libsignal-client',
'@signalapp/libsignal-client/zkgroup':
'commonjs @signalapp/libsignal-client/zkgroup',
'@signalapp/ringrtc': 'commonjs @signalapp/ringrtc',
'@signalapp/better-sqlite3': 'commonjs @signalapp/better-sqlite3',
electron: 'commonjs electron',
'fs-xattr': 'commonjs fs-xattr',
fsevents: 'commonjs fsevents',
'mac-screen-capture-permissions': 'commonjs mac-screen-capture-permissions',
sass: 'commonjs sass',
bufferutil: 'commonjs bufferutil',
'utf-8-validate': 'commonjs utf-8-validate',
};
return config;
};

View File

@ -41,3 +41,6 @@ Gruntfile.js
# asset directories
!nyc/node_modules/istanbul-reports/lib/html/assets
# bad matches
!patch-package/node_modules/yaml/dist/doc

View File

@ -62,11 +62,11 @@
"clean-transpile": "yarn run clean-transpile-once && yarn run clean-transpile-once",
"open-coverage": "open coverage/lcov-report/index.html",
"ready": "npm-run-all --print-label clean-transpile generate --parallel lint lint-deps lint-intl test-node test-electron",
"dev": "run-p --print-label dev:*",
"dev:transpile": "run-p \"check:types --watch\" dev:esbuild",
"dev:esbuild": "node scripts/esbuild.js --watch",
"dev:storybook": "cross-env SIGNAL_ENV=storybook start-storybook -p 6006 -s ./",
"dev:sass": "yarn sass --watch",
"dev": "yarn build-protobuf && cross-env SIGNAL_ENV=storybook storybook dev --port 6006",
"build:storybook": "yarn build-protobuf && cross-env SIGNAL_ENV=storybook storybook build",
"test:storybook": "yarn build:storybook && run-p --race test:storybook:*",
"test:storybook:serve": "http-server storybook-static --port 6006 --silent",
"test:storybook:test": "wait-on http://127.0.0.1:6006/ --timeout 5000 && test-storybook",
"build": "run-s --print-label generate build:esbuild:prod build:release",
"build-linux": "yarn generate && yarn build:esbuild:prod && yarn build:release -- --publish=never",
"build:acknowledgments": "node scripts/generate-acknowledgments.js",
@ -184,33 +184,33 @@
"zod": "3.21.4"
},
"devDependencies": {
"@babel/core": "7.14.3",
"@babel/plugin-proposal-class-properties": "7.17.12",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.17.12",
"@babel/plugin-proposal-optional-chaining": "7.17.12",
"@babel/plugin-transform-runtime": "7.18.2",
"@babel/plugin-transform-typescript": "7.18.4",
"@babel/preset-react": "7.17.12",
"@babel/preset-typescript": "7.17.12",
"@babel/core": "7.23.0",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-transform-runtime": "7.22.15",
"@babel/plugin-transform-typescript": "7.22.15",
"@babel/preset-react": "7.22.15",
"@babel/preset-typescript": "7.23.0",
"@electron/fuses": "1.5.0",
"@formatjs/intl": "2.6.7",
"@mixer/parallel-prettier": "2.0.3",
"@signalapp/mock-server": "4.1.2",
"@storybook/addon-a11y": "6.5.6",
"@storybook/addon-actions": "6.5.6",
"@storybook/addon-controls": "6.5.6",
"@storybook/addon-interactions": "6.5.9",
"@storybook/addon-knobs": "6.4.0",
"@storybook/addon-measure": "6.5.6",
"@storybook/addon-toolbars": "6.5.6",
"@storybook/addon-viewport": "6.5.6",
"@storybook/addons": "6.5.6",
"@storybook/builder-webpack5": "6.5.15",
"@storybook/jest": "0.0.10",
"@storybook/manager-webpack5": "6.5.15",
"@storybook/react": "6.5.6",
"@storybook/testing-library": "0.0.13",
"@types/backbone": "1.4.5",
"@storybook/addon-a11y": "7.4.5",
"@storybook/addon-actions": "7.4.5",
"@storybook/addon-controls": "7.4.5",
"@storybook/addon-interactions": "7.4.5",
"@storybook/addon-jest": "7.4.5",
"@storybook/addon-measure": "7.4.5",
"@storybook/addon-toolbars": "7.4.5",
"@storybook/addon-viewport": "7.4.5",
"@storybook/addons": "7.4.5",
"@storybook/jest": "0.2.2",
"@storybook/react": "7.4.5",
"@storybook/react-webpack5": "7.4.5",
"@storybook/test-runner": "0.13.0",
"@storybook/testing-library": "0.2.2",
"@types/backbone": "1.4.16",
"@types/blueimp-load-image": "5.14.1",
"@types/chai": "4.2.18",
"@types/chai-as-promised": "7.1.4",
@ -258,7 +258,7 @@
"asar": "3.1.0",
"axe-core": "4.1.4",
"babel-core": "7.0.0-bridge.0",
"babel-loader": "8.0.6",
"babel-loader": "9.1.3",
"babel-plugin-lodash": "3.3.4",
"casual": "1.6.2",
"chai": "4.3.4",
@ -285,19 +285,23 @@
"eslint-plugin-react": "7.31.10",
"execa": "5.1.1",
"html-webpack-plugin": "5.3.1",
"http-server": "14.1.1",
"json-to-ast": "2.1.0",
"mini-css-extract-plugin": "2.7.6",
"mocha": "9.1.3",
"node-gyp": "9.0.0",
"npm-run-all": "4.1.5",
"nyc": "11.4.1",
"p-limit": "3.1.0",
"patch-package": "6.4.7",
"patch-package": "8.0.0",
"playwright": "1.33.0",
"prettier": "2.8.0",
"protobufjs-cli": "1.1.1",
"resolve-url-loader": "5.0.0",
"sass": "1.49.7",
"sass-loader": "10.2.0",
"sinon": "11.1.1",
"storybook": "7.4.5",
"style-loader": "1.0.0",
"stylelint": "15.4.0",
"stylelint-config-css-modules": "4.2.0",
@ -309,7 +313,8 @@
"ts-node": "8.3.0",
"typed-scss-modules": "4.1.1",
"typescript": "5.1.3",
"webpack": "5.76.0",
"wait-on": "7.0.1",
"webpack": "5.88.2",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.11.1"
},

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -13,8 +13,6 @@ import { getBytesSubarray } from './util/uuidToBytes';
export { HashType, CipherType };
export const UUID_BYTE_SIZE = 16;
const PROFILE_IV_LENGTH = 12; // bytes
const PROFILE_KEY_LENGTH = 32; // bytes

View File

@ -3288,13 +3288,14 @@ export async function startApp(): Promise<void> {
'onDeliveryReceipt: missing valid sourceServiceId'
);
strictAssert(sourceDevice, 'onDeliveryReceipt: missing sourceDevice');
strictAssert(sourceConversation, 'onDeliveryReceipt: missing conversation');
const attributes: MessageReceiptAttributesType = {
envelopeId: ev.deliveryReceipt.envelopeId,
removeFromMessageReceiverCache: ev.confirm,
messageSentAt: timestamp,
receiptTimestamp: envelopeTimestamp,
sourceConversationId: sourceConversation?.id,
sourceConversationId: sourceConversation.id,
sourceServiceId,
sourceDevice,
type: MessageReceipts.MessageReceiptType.Delivery,

View File

@ -5,8 +5,10 @@ import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './AddGroupMemberErrorDialog';
import {
AddGroupMemberErrorDialog,
AddGroupMemberErrorDialogMode,
@ -16,24 +18,22 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/AddGroupMemberErrorDialog',
};
} satisfies Meta<PropsType>;
const defaultProps = {
i18n,
onClose: action('onClose'),
};
export const _MaximumGroupSize = (): JSX.Element => (
<AddGroupMemberErrorDialog
{...defaultProps}
mode={AddGroupMemberErrorDialogMode.MaximumGroupSize}
maximumNumberOfContacts={123}
/>
);
_MaximumGroupSize.story = {
name: 'Maximum group size',
};
export function MaximumGroupSize(): JSX.Element {
return (
<AddGroupMemberErrorDialog
{...defaultProps}
mode={AddGroupMemberErrorDialogMode.MaximumGroupSize}
maximumNumberOfContacts={123}
/>
);
}
export function MaximumRecommendedGroupSize(): JSX.Element {
return (
@ -44,7 +44,3 @@ export function MaximumRecommendedGroupSize(): JSX.Element {
/>
);
}
MaximumRecommendedGroupSize.story = {
name: 'Maximum recommended group size',
};

View File

@ -23,7 +23,7 @@ type PropsDataType =
recommendedMaximumNumberOfContacts: number;
};
type PropsType = {
export type PropsType = {
i18n: LocalizerType;
onClose: () => void;
} & PropsDataType;

View File

@ -1,8 +1,9 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { Meta, Story } from '@storybook/react';
import React, { useContext } from 'react';
import type { Meta, StoryFn } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import type { Props } from './AddUserToAnotherGroupModal';
import enMessages from '../../_locales/en/messages.json';
@ -19,28 +20,25 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/AddUserToAnotherGroupModal',
component: AddUserToAnotherGroupModal,
argTypes: {
candidateConversations: {
defaultValue: Array.from(Array(100), () => getDefaultGroup()),
},
contact: {
defaultValue: getDefaultConversation(),
},
i18n: {
defaultValue: i18n,
},
addMembersToGroup: { action: true },
toggleAddUserToAnotherGroupModal: { action: true },
args: {
i18n,
candidateConversations: Array.from(Array(100), () => getDefaultGroup()),
contact: getDefaultConversation(),
},
} as Meta;
} satisfies Meta<Props>;
// eslint-disable-next-line react/function-component-definition
const Template: Story<Props> = args => (
<AddUserToAnotherGroupModal
{...args}
theme={React.useContext(StorybookThemeContext)}
/>
);
const Template: StoryFn<Props> = args => {
return (
<AddUserToAnotherGroupModal
{...args}
addMembersToGroup={action('addMembersToGroup')}
toggleAddUserToAnotherGroupModal={action(
'toggleAddUserToAnotherGroupModal'
)}
theme={useContext(StorybookThemeContext)}
/>
);
};
export const Modal = Template.bind({});
Modal.args = {};

View File

@ -2,18 +2,18 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './Alert';
import { Alert } from './Alert';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/Alert',
};
} satisfies Meta<PropsType>;
const defaultProps = {
i18n,
@ -33,10 +33,6 @@ export function TitleAndBodyAreStrings(): JSX.Element {
);
}
TitleAndBodyAreStrings.story = {
name: 'Title and body are strings',
};
export function BodyIsAReactNode(): JSX.Element {
return (
<Alert
@ -52,10 +48,6 @@ export function BodyIsAReactNode(): JSX.Element {
);
}
BodyIsAReactNode.story = {
name: 'Body is a ReactNode',
};
export function LongBodyWithoutTitle(): JSX.Element {
return (
<Alert
@ -72,10 +64,6 @@ export function LongBodyWithoutTitle(): JSX.Element {
);
}
LongBodyWithoutTitle.story = {
name: 'Long body (without title)',
};
export function LongBodyWithTitle(): JSX.Element {
return (
<Alert
@ -92,7 +80,3 @@ export function LongBodyWithTitle(): JSX.Element {
/>
);
}
LongBodyWithTitle.story = {
name: 'Long body (with title)',
};

View File

@ -9,7 +9,7 @@ import type { Theme } from '../util/theme';
import { Button } from './Button';
import { Modal } from './Modal';
type PropsType = {
export type PropsType = {
body: ReactNode;
i18n: LocalizerType;
onClose: () => void;

View File

@ -4,12 +4,13 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './AnimatedEmojiGalore';
import { AnimatedEmojiGalore } from './AnimatedEmojiGalore';
export default {
title: 'Components/AnimatedEmojiGalore',
};
} satisfies Meta<PropsType>;
function getDefaultProps(): PropsType {
return {

View File

@ -1,13 +1,12 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Meta, Story } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import { expect } from '@storybook/jest';
import { expect, jest } from '@storybook/jest';
import { isBoolean } from 'lodash';
import { within, userEvent } from '@storybook/testing-library';
import type { AvatarColorType } from '../types/Colors';
import type { Props } from './Avatar';
import enMessages from '../../_locales/en/messages.json';
@ -42,7 +41,6 @@ export default {
},
blur: {
control: { type: 'radio' },
defaultValue: undefined,
options: {
Undefined: undefined,
NoBlur: AvatarBlur.NoBlur,
@ -51,14 +49,12 @@ export default {
},
},
color: {
defaultValue: AvatarColors[0],
options: colorMap,
},
conversationType: {
control: { type: 'radio' },
options: conversationTypeMap,
},
onClick: { action: true },
size: {
control: false,
},
@ -68,11 +64,16 @@ export default {
},
theme: {
control: { type: 'radio' },
defaultValue: ThemeType.light,
options: ThemeType,
},
},
} as Meta;
args: {
blur: undefined,
color: AvatarColors[0],
onClick: action('onClick'),
theme: ThemeType.light,
},
} satisfies Meta<Props>;
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
acceptedMessageRequest: isBoolean(overrideProps.acceptedMessageRequest)
@ -87,7 +88,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
isMe: false,
loading: Boolean(overrideProps.loading),
noteToSelf: Boolean(overrideProps.noteToSelf),
onClick: action('onClick'),
onClick: jest.fn(action('onClick')),
onClickBadge: action('onClickBadge'),
phoneNumber: overrideProps.phoneNumber || '',
searchResult: Boolean(overrideProps.searchResult),
@ -103,16 +104,18 @@ const sizes = Object.values(AvatarSize).filter(
) as Array<AvatarSize>;
// eslint-disable-next-line react/function-component-definition
const Template: Story<Props> = args => (
<>
{sizes.map(size => (
<Avatar key={size} {...args} size={size} />
))}
</>
);
const Template: StoryFn<Props> = (args: Props) => {
return (
<>
{sizes.map(size => (
<Avatar key={size} {...args} size={size} />
))}
</>
);
};
// eslint-disable-next-line react/function-component-definition
const TemplateSingle: Story<Props> = args => (
const TemplateSingle: StoryFn<Props> = (args: Props) => (
<Avatar {...args} size={AvatarSize.EIGHTY} />
);
@ -120,72 +123,50 @@ export const Default = Template.bind({});
Default.args = createProps({
avatarPath: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
});
Default.play = async ({ args, canvasElement }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Default.play = async (context: any) => {
const { args, canvasElement } = context;
const canvas = within(canvasElement);
const [avatar] = canvas.getAllByRole('button');
await userEvent.click(avatar);
await expect(args.onClick).toHaveBeenCalled();
};
Default.story = {
name: 'Avatar',
};
export const WithBadge = Template.bind({});
WithBadge.args = createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
badge: getFakeBadge(),
});
WithBadge.story = {
name: 'With badge',
};
export const WideImage = Template.bind({});
WideImage.args = createProps({
avatarPath: '/fixtures/wide.jpg',
});
WideImage.story = {
name: 'Wide image',
};
export const OneWordName = Template.bind({});
OneWordName.args = createProps({
title: 'John',
});
OneWordName.story = {
name: 'One-word Name',
};
export const TwoWordName = Template.bind({});
TwoWordName.args = createProps({
title: 'John Smith',
});
TwoWordName.story = {
name: 'Two-word Name',
};
export const WideInitials = Template.bind({});
WideInitials.args = createProps({
title: 'Walter White',
});
WideInitials.story = {
name: 'Wide initials',
};
export const ThreeWordName = Template.bind({});
ThreeWordName.args = createProps({
title: 'Walter H. White',
});
ThreeWordName.story = {
name: 'Three-word name',
};
export const NoteToSelf = Template.bind({});
NoteToSelf.args = createProps({
noteToSelf: true,
});
NoteToSelf.story = {
name: 'Note to Self',
};
export const ContactIcon = Template.bind({});
ContactIcon.args = createProps();
@ -227,9 +208,6 @@ BrokenAvatarForGroup.args = createProps({
avatarPath: 'badimage.png',
conversationType: 'group',
});
BrokenAvatarForGroup.story = {
name: 'Broken Avatar for Group',
};
export const Loading = Template.bind({});
Loading.args = createProps({
@ -242,42 +220,26 @@ BlurredBasedOnProps.args = createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
});
BlurredBasedOnProps.story = {
name: 'Blurred based on props',
};
export const ForceBlurred = TemplateSingle.bind({});
ForceBlurred.args = createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
blur: AvatarBlur.BlurPicture,
});
ForceBlurred.story = {
name: 'Force-blurred',
};
export const BlurredWithClickToView = TemplateSingle.bind({});
BlurredWithClickToView.args = createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
blur: AvatarBlur.BlurPictureWithClickToView,
});
BlurredWithClickToView.story = {
name: 'Blurred with "click to view"',
};
export const StoryUnread = TemplateSingle.bind({});
StoryUnread.args = createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
storyRing: HasStories.Unread,
});
StoryUnread.story = {
name: 'Story: unread',
};
export const StoryRead = TemplateSingle.bind({});
StoryRead.args = createProps({
avatarPath: '/fixtures/kitten-3-64-64.jpg',
storyRing: HasStories.Read,
});
StoryRead.story = {
name: 'Story: read',
};

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -21,7 +22,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/AvatarColorPicker',
};
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
return <AvatarColorPicker {...createProps()} />;

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -80,7 +81,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/AvatarEditor',
};
} satisfies Meta<PropsType>;
export function NoAvatarGroup(): JSX.Element {
return (
@ -93,20 +94,12 @@ export function NoAvatarGroup(): JSX.Element {
);
}
NoAvatarGroup.story = {
name: 'No Avatar (group)',
};
export function NoAvatarMe(): JSX.Element {
return (
<AvatarEditor {...createProps({ userAvatarData: getDefaultAvatars() })} />
);
}
NoAvatarMe.story = {
name: 'No Avatar (me)',
};
export function HasAvatar(): JSX.Element {
return (
<AvatarEditor

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -23,7 +24,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/AvatarIconEditor',
};
} satisfies Meta<PropsType>;
export function PersonalIcon(): JSX.Element {
return (

View File

@ -2,10 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { select } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import { AvatarColors } from '../types/Colors';
import type { PropsType } from './AvatarLightbox';
@ -15,51 +13,37 @@ import { getDefaultConversation } from '../test-both/helpers/getDefaultConversat
const i18n = setupI18n('en', enMessages);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
avatarColor: select(
'Color',
AvatarColors,
overrideProps.avatarColor || AvatarColors[0]
),
avatarPath: overrideProps.avatarPath,
conversationTitle: overrideProps.conversationTitle,
i18n,
isGroup: Boolean(overrideProps.isGroup),
onClose: action('onClose'),
});
export default {
title: 'Components/AvatarLightbox',
};
component: AvatarLightbox,
argTypes: {
avatarColor: {
control: { type: 'select' },
options: AvatarColors,
},
},
args: {
i18n,
avatarColor: AvatarColors[0],
onClose: action('onClose'),
},
} satisfies Meta<PropsType>;
export function Group(): JSX.Element {
return (
<AvatarLightbox
{...createProps({
isGroup: true,
})}
/>
);
export function Group(args: PropsType): JSX.Element {
return <AvatarLightbox {...args} isGroup />;
}
export function Person(): JSX.Element {
export function Person(args: PropsType): JSX.Element {
const conversation = getDefaultConversation();
return (
<AvatarLightbox
{...createProps({
avatarColor: conversation.color,
conversationTitle: conversation.title,
})}
{...args}
avatarColor={conversation.color}
conversationTitle={conversation.title}
/>
);
}
export function Photo(): JSX.Element {
return (
<AvatarLightbox
{...createProps({
avatarPath: '/fixtures/kitten-1-64-64.jpg',
})}
/>
);
export function Photo(args: PropsType): JSX.Element {
return <AvatarLightbox {...args} avatarPath="/fixtures/kitten-1-64-64.jpg" />;
}

View File

@ -5,6 +5,7 @@ import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './AvatarModalButtons';
import { AvatarModalButtons } from './AvatarModalButtons';
@ -21,7 +22,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/AvatarModalButtons',
};
} satisfies Meta<PropsType>;
export function HasChanges(): JSX.Element {
return (
@ -33,14 +34,6 @@ export function HasChanges(): JSX.Element {
);
}
HasChanges.story = {
name: 'Has changes',
};
export function NoChanges(): JSX.Element {
return <AvatarModalButtons {...createProps()} />;
}
NoChanges.story = {
name: 'No changes',
};

View File

@ -6,6 +6,7 @@ import { chunk } from 'lodash';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './AvatarPreview';
import { AvatarPreview } from './AvatarPreview';
import { AvatarColors } from '../types/Colors';
@ -37,7 +38,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/AvatarPreview',
};
} satisfies Meta<PropsType>;
export function NoStatePersonal(): JSX.Element {
return (
@ -50,10 +51,6 @@ export function NoStatePersonal(): JSX.Element {
);
}
NoStatePersonal.story = {
name: 'No state (personal)',
};
export function NoStateGroup(): JSX.Element {
return (
<AvatarPreview
@ -65,10 +62,6 @@ export function NoStateGroup(): JSX.Element {
);
}
NoStateGroup.story = {
name: 'No state (group)',
};
export function NoStateGroupUploadMe(): JSX.Element {
return (
<AvatarPreview
@ -81,18 +74,10 @@ export function NoStateGroupUploadMe(): JSX.Element {
);
}
NoStateGroupUploadMe.story = {
name: 'No state (group) + upload me',
};
export function Value(): JSX.Element {
return <AvatarPreview {...createProps({ avatarValue: TEST_IMAGE })} />;
}
Value.story = {
name: 'value',
};
export function Path(): JSX.Element {
return (
<AvatarPreview
@ -101,11 +86,7 @@ export function Path(): JSX.Element {
);
}
Path.story = {
name: 'path',
};
export function ValuePath(): JSX.Element {
export function ValueAndPath(): JSX.Element {
return (
<AvatarPreview
{...createProps({
@ -116,10 +97,6 @@ export function ValuePath(): JSX.Element {
);
}
ValuePath.story = {
name: 'value & path',
};
export function Style(): JSX.Element {
return (
<AvatarPreview
@ -130,7 +107,3 @@ export function Style(): JSX.Element {
/>
);
}
Style.story = {
name: 'style',
};

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -22,7 +23,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/AvatarTextEditor',
};
} satisfies Meta<PropsType>;
export function Empty(): JSX.Element {
return <AvatarTextEditor {...createProps()} />;
@ -42,10 +43,6 @@ export function WithData(): JSX.Element {
);
}
WithData.story = {
name: 'with Data',
};
export function WithWideCharacters(): JSX.Element {
return (
<AvatarTextEditor
@ -59,7 +56,3 @@ export function WithWideCharacters(): JSX.Element {
/>
);
}
WithWideCharacters.story = {
name: 'with wide characters',
};

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -20,7 +21,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/AvatarUploadButton',
};
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
return <AvatarUploadButton {...createProps()} />;

View File

@ -3,11 +3,13 @@
import React from 'react';
import type { Meta } from '@storybook/react';
import type { Props } from './BadgeDescription';
import { BadgeDescription } from './BadgeDescription';
export default {
title: 'Components/BadgeDescription',
};
} satisfies Meta<Props>;
export function NormalName(): JSX.Element {
return (
@ -19,11 +21,7 @@ export function NormalName(): JSX.Element {
);
}
NormalName.story = {
name: 'Normal name',
};
export function NameWithRtlOverrides(): JSX.Element {
export function NameWithRTLOverrides(): JSX.Element {
return (
<BadgeDescription
template="Hello, {short_name}! {short_name}, I think you're great."
@ -31,7 +29,3 @@ export function NameWithRtlOverrides(): JSX.Element {
/>
);
}
NameWithRtlOverrides.story = {
name: 'Name with RTL overrides',
};

View File

@ -5,15 +5,17 @@ import type { ReactChild, ReactElement } from 'react';
import React from 'react';
import { ContactName } from './conversation/ContactName';
export type Props = Readonly<{
firstName?: string;
template: string;
title: string;
}>;
export function BadgeDescription({
firstName,
template,
title,
}: Readonly<{
firstName?: string;
template: string;
title: string;
}>): ReactElement {
}: Props): ReactElement {
const result: Array<ReactChild> = [];
let lastIndex = 0;

View File

@ -5,18 +5,20 @@ import type { ComponentProps } from 'react';
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { getFakeBadge, getFakeBadges } from '../test-both/helpers/getFakeBadge';
import { repeat, zipObject } from '../util/iterables';
import { BadgeImageTheme } from '../badges/BadgeImageTheme';
import type { PropsType } from './BadgeDialog';
import { BadgeDialog } from './BadgeDialog';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/BadgeDialog',
};
} satisfies Meta<PropsType>;
const defaultProps: ComponentProps<typeof BadgeDialog> = {
areWeASubscriber: false,
@ -31,18 +33,10 @@ export function NoBadgesClosedImmediately(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={[]} />;
}
NoBadgesClosedImmediately.story = {
name: 'No badges (closed immediately)',
};
export function OneBadge(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={getFakeBadges(1)} />;
}
OneBadge.story = {
name: 'One badge',
};
export function BadgeWithNoImageShouldBeImpossible(): JSX.Element {
return (
<BadgeDialog
@ -57,10 +51,6 @@ export function BadgeWithNoImageShouldBeImpossible(): JSX.Element {
);
}
BadgeWithNoImageShouldBeImpossible.story = {
name: 'Badge with no image (should be impossible)',
};
export function BadgeWithPendingImage(): JSX.Element {
return (
<BadgeDialog
@ -80,10 +70,6 @@ export function BadgeWithPendingImage(): JSX.Element {
);
}
BadgeWithPendingImage.story = {
name: 'Badge with pending image',
};
export function BadgeWithOnlyOneLowDetailImage(): JSX.Element {
return (
<BadgeDialog
@ -112,26 +98,14 @@ export function BadgeWithOnlyOneLowDetailImage(): JSX.Element {
);
}
BadgeWithOnlyOneLowDetailImage.story = {
name: 'Badge with only one, low-detail image',
};
export function FiveBadges(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={getFakeBadges(5)} />;
}
FiveBadges.story = {
name: 'Five badges',
};
export function ManyBadges(): JSX.Element {
return <BadgeDialog {...defaultProps} badges={getFakeBadges(50)} />;
}
ManyBadges.story = {
name: 'Many badges',
};
export function ManyBadgesUserIsASubscriber(): JSX.Element {
return (
<BadgeDialog
@ -141,7 +115,3 @@ export function ManyBadgesUserIsASubscriber(): JSX.Element {
/>
);
}
ManyBadgesUserIsASubscriber.story = {
name: 'Many badges, user is a subscriber',
};

View File

@ -15,7 +15,7 @@ import { BadgeImage } from './BadgeImage';
import { BadgeCarouselIndex } from './BadgeCarouselIndex';
import { BadgeSustainerInstructionsDialog } from './BadgeSustainerInstructionsDialog';
type PropsType = Readonly<{
export type PropsType = Readonly<{
areWeASubscriber: boolean;
badges: ReadonlyArray<BadgeType>;
firstName?: string;

View File

@ -5,6 +5,7 @@ import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import { AvatarColors } from '../types/Colors';
import { GroupAvatarIcons, PersonalAvatarIcons } from '../types/Avatar';
@ -28,7 +29,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/BetterAvatar',
};
} satisfies Meta<PropsType>;
export function Text(): JSX.Element {
return (

View File

@ -5,6 +5,7 @@ import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import { AvatarColors } from '../types/Colors';
import type { PropsType } from './BetterAvatarBubble';
@ -25,7 +26,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/BetterAvatarBubble',
};
} satisfies Meta<PropsType>;
export function Children(): JSX.Element {
return (

View File

@ -3,12 +3,13 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './Button';
import { Button, ButtonSize, ButtonVariant } from './Button';
export default {
title: 'Components/Button',
};
} satisfies Meta<PropsType>;
export function KitchenSink(): JSX.Element {
return (
@ -44,10 +45,6 @@ export function KitchenSink(): JSX.Element {
);
}
KitchenSink.story = {
name: 'Kitchen sink',
};
export function AriaLabel(): JSX.Element {
return (
<Button
@ -58,10 +55,6 @@ export function AriaLabel(): JSX.Element {
);
}
AriaLabel.story = {
name: 'aria-label',
};
export function CustomStyles(): JSX.Element {
return (
<Button onClick={action('onClick')} style={{ transform: 'rotate(5deg)' }}>
@ -69,7 +62,3 @@ export function CustomStyles(): JSX.Element {
</Button>
);
}
CustomStyles.story = {
name: 'Custom styles',
};

View File

@ -40,7 +40,7 @@ export enum ButtonIconType {
video = 'video',
}
type PropsType = {
export type PropsType = {
className?: string;
disabled?: boolean;
icon?: ButtonIconType;

View File

@ -3,8 +3,7 @@
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import { boolean, select, text } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CallManager';
import { CallManager } from './CallManager';
import {
@ -16,7 +15,6 @@ import {
GroupCallJoinState,
} from '../types/Calling';
import type { ConversationTypeType } from '../state/ducks/conversations';
import type { AvatarColorType } from '../types/Colors';
import { AvatarColors } from '../types/Colors';
import { generateAci } from '../types/ServiceId';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
@ -33,13 +31,9 @@ const getConversation = () =>
getDefaultConversation({
id: '3051234567',
avatarPath: undefined,
color: select(
'Callee color',
AvatarColors,
'ultramarine' as AvatarColorType
),
title: text('Callee Title', 'Rick Sanchez'),
name: text('Callee Name', 'Rick Sanchez'),
color: AvatarColors[0],
title: 'Rick Sanchez',
name: 'Rick Sanchez',
phoneNumber: '3051234567',
profileName: 'Rick Sanchez',
markedUnread: false,
@ -50,18 +44,14 @@ const getConversation = () =>
const getCommonActiveCallData = () => ({
conversation: getConversation(),
joinedAt: Date.now(),
hasLocalAudio: boolean('hasLocalAudio', true),
hasLocalVideo: boolean('hasLocalVideo', false),
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
viewMode: select(
'viewMode',
[CallViewMode.Grid, CallViewMode.Presentation, CallViewMode.Speaker],
CallViewMode.Grid
),
outgoingRing: boolean('outgoingRing', true),
pip: boolean('pip', false),
settingsDialogOpen: boolean('settingsDialogOpen', false),
showParticipantsList: boolean('showParticipantsList', false),
hasLocalAudio: true,
hasLocalVideo: false,
localAudioLevel: 0,
viewMode: CallViewMode.Grid,
outgoingRing: true,
pip: false,
settingsDialogOpen: false,
showParticipantsList: false,
});
const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
@ -83,12 +73,8 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
keyChangeOk: action('key-change-ok'),
me: {
...getDefaultConversation({
color: select(
'Caller color',
AvatarColors,
'ultramarine' as AvatarColorType
),
title: text('Caller Title', 'Morty Smith'),
color: AvatarColors[0],
title: 'Morty Smith',
}),
serviceId: generateAci(),
},
@ -123,7 +109,9 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/CallManager',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export function NoCall(): JSX.Element {
return <CallManager {...createProps()} />;
@ -184,10 +172,6 @@ export function RingingDirectCall(): JSX.Element {
);
}
RingingDirectCall.story = {
name: 'Ringing (direct call)',
};
export function RingingGroupCall(): JSX.Element {
return (
<CallManager
@ -212,10 +196,6 @@ export function RingingGroupCall(): JSX.Element {
);
}
RingingGroupCall.story = {
name: 'Ringing (group call)',
};
export function CallRequestNeeded(): JSX.Element {
return (
<CallManager
@ -263,7 +243,3 @@ export function GroupCallSafetyNumberChanged(): JSX.Element {
/>
);
}
GroupCallSafetyNumberChanged.story = {
name: 'Group call - Safety Number Changed',
};

View File

@ -3,9 +3,9 @@
import * as React from 'react';
import { times } from 'lodash';
import { boolean, select, number } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { GroupCallRemoteParticipantType } from '../types/Calling';
import {
CallMode,
@ -60,6 +60,7 @@ type GroupCallOverrideProps = OverridePropsBase & {
connectionState?: GroupCallConnectionState;
peekedParticipants?: Array<ConversationType>;
remoteParticipants?: Array<GroupCallRemoteParticipantType>;
remoteAudioLevel?: number;
};
const createActiveDirectCallProp = (
@ -67,18 +68,11 @@ const createActiveDirectCallProp = (
) => ({
callMode: CallMode.Direct as CallMode.Direct,
conversation,
callState: select(
'callState',
CallState,
overrideProps.callState || CallState.Accepted
),
callState: overrideProps.callState ?? CallState.Accepted,
peekedParticipants: [] as [],
remoteParticipants: [
{
hasRemoteVideo: boolean(
'hasRemoteVideo',
Boolean(overrideProps.hasRemoteVideo)
),
hasRemoteVideo: overrideProps.hasRemoteVideo ?? false,
presenting: false,
title: 'test',
},
@ -109,12 +103,7 @@ const createActiveGroupCallProp = (overrideProps: GroupCallOverrideProps) => ({
remoteAudioLevels: new Map<number, number>(
overrideProps.remoteParticipants?.map((_participant, index) => [
index,
number('remoteAudioLevel', 0, {
range: true,
min: 0,
max: 1,
step: 0.01,
}),
overrideProps.remoteAudioLevel ?? 0,
])
),
});
@ -125,24 +114,10 @@ const createActiveCallProp = (
const baseResult = {
joinedAt: Date.now(),
conversation,
hasLocalAudio: boolean(
'hasLocalAudio',
overrideProps.hasLocalAudio || false
),
hasLocalVideo: boolean(
'hasLocalVideo',
overrideProps.hasLocalVideo || false
),
localAudioLevel: select(
'localAudioLevel',
[0, 0.5, 1],
overrideProps.localAudioLevel || 0
),
viewMode: select(
'viewMode',
[CallViewMode.Grid, CallViewMode.Speaker, CallViewMode.Presentation],
overrideProps.viewMode || CallViewMode.Grid
),
hasLocalAudio: overrideProps.hasLocalAudio ?? false,
hasLocalVideo: overrideProps.hasLocalVideo ?? false,
localAudioLevel: overrideProps.localAudioLevel ?? 0,
viewMode: overrideProps.viewMode ?? CallViewMode.Grid,
outgoingRing: true,
pip: false,
settingsDialogOpen: false,
@ -184,7 +159,7 @@ const createProps = (
setLocalVideo: action('set-local-video'),
setPresenting: action('toggle-presenting'),
setRendererCanvas: action('set-renderer-canvas'),
stickyControls: boolean('stickyControls', false),
stickyControls: false,
switchToPresentationView: action('switch-to-presentation-view'),
switchFromPresentationView: action('switch-from-presentation-view'),
toggleParticipants: action('toggle-participants'),
@ -198,7 +173,9 @@ const createProps = (
export default {
title: 'Components/CallScreen',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
return <CallScreen {...createProps()} />;
@ -215,11 +192,7 @@ export function PreRing(): JSX.Element {
);
}
PreRing.story = {
name: 'Pre-Ring',
};
export const _Ringing = (): JSX.Element => {
export function Ringing(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -228,9 +201,9 @@ export const _Ringing = (): JSX.Element => {
})}
/>
);
};
}
export const _Reconnecting = (): JSX.Element => {
export function Reconnecting(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -239,9 +212,9 @@ export const _Reconnecting = (): JSX.Element => {
})}
/>
);
};
}
export const _Ended = (): JSX.Element => {
export function Ended(): JSX.Element {
return (
<CallScreen
{...createProps({
@ -250,7 +223,7 @@ export const _Ended = (): JSX.Element => {
})}
/>
);
};
}
export function HasLocalAudio(): JSX.Element {
return (
@ -263,10 +236,6 @@ export function HasLocalAudio(): JSX.Element {
);
}
HasLocalAudio.story = {
name: 'hasLocalAudio',
};
export function HasLocalVideo(): JSX.Element {
return (
<CallScreen
@ -278,10 +247,6 @@ export function HasLocalVideo(): JSX.Element {
);
}
HasLocalVideo.story = {
name: 'hasLocalVideo',
};
export function HasRemoteVideo(): JSX.Element {
return (
<CallScreen
@ -293,10 +258,6 @@ export function HasRemoteVideo(): JSX.Element {
);
}
HasRemoteVideo.story = {
name: 'hasRemoteVideo',
};
export function GroupCall1(): JSX.Element {
return (
<CallScreen
@ -323,10 +284,6 @@ export function GroupCall1(): JSX.Element {
);
}
GroupCall1.story = {
name: 'Group call - 1',
};
// We generate these upfront so that the list is stable when you move the slider.
const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({
aci: generateAci(),
@ -347,24 +304,12 @@ export function GroupCallMany(): JSX.Element {
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants.slice(
0,
number('Participant count', 40, {
range: true,
min: 0,
max: MAX_PARTICIPANTS,
step: 1,
})
),
remoteParticipants: allRemoteParticipants.slice(0, 40),
})}
/>
);
}
GroupCallMany.story = {
name: 'Group call - Many',
};
export function GroupCallReconnecting(): JSX.Element {
return (
<CallScreen
@ -392,10 +337,6 @@ export function GroupCallReconnecting(): JSX.Element {
);
}
GroupCallReconnecting.story = {
name: 'Group call - reconnecting',
};
export function GroupCall0(): JSX.Element {
return (
<CallScreen
@ -407,10 +348,6 @@ export function GroupCall0(): JSX.Element {
);
}
GroupCall0.story = {
name: 'Group call - 0',
};
export function GroupCallSomeoneIsSharingScreen(): JSX.Element {
return (
<CallScreen
@ -428,10 +365,6 @@ export function GroupCallSomeoneIsSharingScreen(): JSX.Element {
);
}
GroupCallSomeoneIsSharingScreen.story = {
name: 'Group call - someone is sharing screen',
};
export function GroupCallSomeoneIsSharingScreenAndYoureReconnecting(): JSX.Element {
return (
<CallScreen
@ -449,7 +382,3 @@ export function GroupCallSomeoneIsSharingScreenAndYoureReconnecting(): JSX.Eleme
/>
);
}
GroupCallSomeoneIsSharingScreenAndYoureReconnecting.story = {
name: "Group call - someone is sharing screen and you're reconnecting",
};

View File

@ -2,8 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState, useEffect } from 'react';
import { boolean } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import type { Props } from './CallingAudioIndicator';
import {
CallingAudioIndicator,
SPEAKING_LINGER_MS,
@ -13,9 +13,18 @@ import { useValueAtFixedRate } from '../hooks/useValueAtFixedRate';
export default {
title: 'Components/CallingAudioIndicator',
};
component: CallingAudioIndicator,
argTypes: {
hasAudio: {
control: { type: 'boolean' },
},
},
args: {
hasAudio: true,
},
} satisfies Meta<Props>;
export function Extreme(): JSX.Element {
export function Extreme(args: Props): JSX.Element {
const [audioLevel, setAudioLevel] = useState(1);
useEffect(() => {
@ -32,14 +41,14 @@ export function Extreme(): JSX.Element {
return (
<CallingAudioIndicator
hasAudio={boolean('hasAudio', true)}
hasAudio={args.hasAudio}
audioLevel={audioLevel}
shouldShowSpeaking={isSpeaking}
/>
);
}
export function Random(): JSX.Element {
export function Random(args: Props): JSX.Element {
const [audioLevel, setAudioLevel] = useState(1);
useEffect(() => {
@ -56,7 +65,7 @@ export function Random(): JSX.Element {
return (
<CallingAudioIndicator
hasAudio={boolean('hasAudio', true)}
hasAudio={args.hasAudio}
audioLevel={audioLevel}
shouldShowSpeaking={isSpeaking}
/>

View File

@ -99,15 +99,17 @@ function Bars({ audioLevel }: { audioLevel: number }): ReactElement {
);
}
export type Props = Readonly<{
hasAudio: boolean;
audioLevel: number;
shouldShowSpeaking: boolean;
}>;
export function CallingAudioIndicator({
hasAudio,
audioLevel,
shouldShowSpeaking,
}: Readonly<{
hasAudio: boolean;
audioLevel: number;
shouldShowSpeaking: boolean;
}>): ReactElement {
}: Props): ReactElement {
if (!hasAudio) {
return (
<div

View File

@ -2,9 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CallingButton';
import { CallingButton, CallingButtonType } from './CallingButton';
import { TooltipPlacement } from './Tooltip';
@ -13,101 +12,79 @@ import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
buttonType:
overrideProps.buttonType ||
select('buttonType', CallingButtonType, CallingButtonType.HANG_UP),
i18n,
onClick: action('on-click'),
onMouseEnter: action('on-mouse-enter'),
onMouseLeave: action('on-mouse-leave'),
tooltipDirection: select(
'tooltipDirection',
TooltipPlacement,
overrideProps.tooltipDirection || TooltipPlacement.Bottom
),
});
export default {
title: 'Components/CallingButton',
};
component: CallingButton,
argTypes: {
buttonType: {
control: { type: 'select' },
options: Object.values(CallingButtonType),
},
tooltipDirection: {
control: { type: 'select' },
options: Object.values(TooltipPlacement),
},
},
args: {
buttonType: CallingButtonType.HANG_UP,
i18n,
onClick: action('on-click'),
onMouseEnter: action('on-mouse-enter'),
onMouseLeave: action('on-mouse-leave'),
tooltipDirection: TooltipPlacement.Bottom,
},
} satisfies Meta<PropsType>;
export function KitchenSink(): JSX.Element {
export function KitchenSink(args: PropsType): JSX.Element {
return (
<>
{Object.keys(CallingButtonType).map(buttonType => (
<CallingButton
key={buttonType}
{...createProps({ buttonType: buttonType as CallingButtonType })}
/>
{Object.values(CallingButtonType).map(buttonType => (
<CallingButton key={buttonType} {...args} buttonType={buttonType} />
))}
</>
);
}
export function AudioOn(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.AUDIO_ON,
});
return <CallingButton {...props} />;
export function AudioOn(args: PropsType): JSX.Element {
return <CallingButton {...args} buttonType={CallingButtonType.AUDIO_ON} />;
}
export function AudioOff(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.AUDIO_OFF,
});
return <CallingButton {...props} />;
export function AudioOff(args: PropsType): JSX.Element {
return <CallingButton {...args} buttonType={CallingButtonType.AUDIO_OFF} />;
}
export function AudioDisabled(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.AUDIO_DISABLED,
});
return <CallingButton {...props} />;
export function AudioDisabled(args: PropsType): JSX.Element {
return (
<CallingButton {...args} buttonType={CallingButtonType.AUDIO_DISABLED} />
);
}
export function VideoOn(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.VIDEO_ON,
});
return <CallingButton {...props} />;
export function VideoOn(args: PropsType): JSX.Element {
return <CallingButton {...args} buttonType={CallingButtonType.VIDEO_ON} />;
}
export function VideoOff(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.VIDEO_OFF,
});
return <CallingButton {...props} />;
export function VideoOff(args: PropsType): JSX.Element {
return <CallingButton {...args} buttonType={CallingButtonType.VIDEO_OFF} />;
}
export function VideoDisabled(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.VIDEO_DISABLED,
});
return <CallingButton {...props} />;
export function VideoDisabled(args: PropsType): JSX.Element {
return (
<CallingButton {...args} buttonType={CallingButtonType.VIDEO_DISABLED} />
);
}
export function TooltipRight(): JSX.Element {
const props = createProps({
tooltipDirection: TooltipPlacement.Right,
});
return <CallingButton {...props} />;
export function TooltipRight(args: PropsType): JSX.Element {
return <CallingButton {...args} tooltipDirection={TooltipPlacement.Right} />;
}
TooltipRight.story = {
name: 'Tooltip right',
};
export function PresentingOn(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.PRESENTING_ON,
});
return <CallingButton {...props} />;
export function PresentingOn(args: PropsType): JSX.Element {
return (
<CallingButton {...args} buttonType={CallingButtonType.PRESENTING_ON} />
);
}
export function PresentingOff(): JSX.Element {
const props = createProps({
buttonType: CallingButtonType.PRESENTING_OFF,
});
return <CallingButton {...props} />;
export function PresentingOff(args: PropsType): JSX.Element {
return (
<CallingButton {...args} buttonType={CallingButtonType.PRESENTING_OFF} />
);
}

View File

@ -4,6 +4,7 @@
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { Props } from './CallingDeviceSelection';
import { CallingDeviceSelection } from './CallingDeviceSelection';
import { setupI18n } from '../util/setupI18n';
@ -39,7 +40,7 @@ const createProps = ({
export default {
title: 'Components/CallingDeviceSelection',
};
} satisfies Meta<Props>;
export function Default(): JSX.Element {
return <CallingDeviceSelection {...createProps()} />;

View File

@ -2,9 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { boolean, number } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CallingHeader';
import { CallingHeader } from './CallingHeader';
import { setupI18n } from '../util/setupI18n';
@ -12,36 +11,35 @@ import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
i18n,
isGroupCall: boolean('isGroupCall', Boolean(overrideProps.isGroupCall)),
message: overrideProps.message,
participantCount: number(
'participantCount',
overrideProps.participantCount || 0
),
showParticipantsList: boolean(
'showParticipantsList',
Boolean(overrideProps.showParticipantsList)
),
title: overrideProps.title || 'With Someone',
toggleParticipants: () => action('toggle-participants'),
togglePip: () => action('toggle-pip'),
toggleSettings: () => action('toggle-settings'),
});
export default {
title: 'Components/CallingHeader',
};
component: CallingHeader,
argTypes: {
isGroupCall: { control: { type: 'boolean' } },
participantCount: { control: { type: 'number' } },
title: { control: { type: 'text' } },
},
args: {
i18n,
isGroupCall: false,
message: '',
participantCount: 0,
showParticipantsList: false,
title: 'With Someone',
toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'),
toggleSettings: action('toggle-settings'),
},
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
return <CallingHeader {...createProps()} />;
export function Default(args: PropsType): JSX.Element {
return <CallingHeader {...args} />;
}
export function LobbyStyle(): JSX.Element {
export function LobbyStyle(args: PropsType): JSX.Element {
return (
<CallingHeader
{...createProps()}
{...args}
title={undefined}
togglePip={undefined}
onCancel={action('onClose')}
@ -49,59 +47,32 @@ export function LobbyStyle(): JSX.Element {
);
}
LobbyStyle.story = {
name: 'Lobby style',
};
export function WithParticipants(args: PropsType): JSX.Element {
return <CallingHeader {...args} isGroupCall participantCount={10} />;
}
export function WithParticipants(): JSX.Element {
export function WithParticipantsShown(args: PropsType): JSX.Element {
return (
<CallingHeader
{...createProps({
isGroupCall: true,
participantCount: 10,
})}
{...args}
isGroupCall
participantCount={10}
showParticipantsList
/>
);
}
export function WithParticipantsShown(): JSX.Element {
export function LongTitle(args: PropsType): JSX.Element {
return (
<CallingHeader
{...createProps({
isGroupCall: true,
participantCount: 10,
showParticipantsList: true,
})}
{...args}
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?"
/>
);
}
WithParticipantsShown.story = {
name: 'With Participants (shown)',
};
export function LongTitle(): JSX.Element {
export function TitleWithMessage(args: PropsType): JSX.Element {
return (
<CallingHeader
{...createProps({
title:
'What do I got to, what do I got to do to wake you up? To shake you up, to break the structure up?',
})}
/>
<CallingHeader {...args} title="Hello world" message="Goodbye earth" />
);
}
export function TitleWithMessage(): JSX.Element {
return (
<CallingHeader
{...createProps({
title: 'Hello world',
message: 'Goodbye earth',
})}
/>
);
}
TitleWithMessage.story = {
name: 'Title with message',
};

View File

@ -3,10 +3,10 @@
import * as React from 'react';
import { times } from 'lodash';
import { boolean } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { v4 as generateUuid } from 'uuid';
import type { Meta } from '@storybook/react';
import { AvatarColors } from '../types/Colors';
import type { ConversationType } from '../state/ducks/conversations';
import type { PropsType } from './CallingLobby';
@ -32,10 +32,7 @@ const camera = {
};
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
const isGroupCall = boolean(
'isGroupCall',
overrideProps.isGroupCall || false
);
const isGroupCall = overrideProps.isGroupCall ?? false;
const conversation = isGroupCall
? getDefaultConversation({
title: 'Tahoe Trip',
@ -49,19 +46,13 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
groupMembers:
overrideProps.groupMembers ||
(isGroupCall ? times(3, () => getDefaultConversation()) : undefined),
hasLocalAudio: boolean(
'hasLocalAudio',
overrideProps.hasLocalAudio ?? true
),
hasLocalVideo: boolean(
'hasLocalVideo',
overrideProps.hasLocalVideo ?? false
),
hasLocalAudio: overrideProps.hasLocalAudio ?? true,
hasLocalVideo: overrideProps.hasLocalVideo ?? false,
i18n,
isGroupCall,
isGroupCallOutboundRingEnabled: true,
isConversationTooBigToRing: false,
isCallFull: boolean('isCallFull', overrideProps.isCallFull || false),
isCallFull: overrideProps.isCallFull ?? false,
me:
overrideProps.me ||
getDefaultConversation({
@ -71,16 +62,13 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
}),
onCallCanceled: action('on-call-canceled'),
onJoinCall: action('on-join-call'),
outgoingRing: boolean('outgoingRing', Boolean(overrideProps.outgoingRing)),
outgoingRing: overrideProps.outgoingRing ?? false,
peekedParticipants: overrideProps.peekedParticipants || [],
setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'),
setLocalVideo: action('set-local-video'),
setOutgoingRing: action('set-outgoing-ring'),
showParticipantsList: boolean(
'showParticipantsList',
Boolean(overrideProps.showParticipantsList)
),
showParticipantsList: overrideProps.showParticipantsList ?? false,
toggleParticipants: action('toggle-participants'),
toggleSettings: action('toggle-settings'),
};
@ -93,7 +81,9 @@ const fakePeekedParticipant = (conversationProps: Partial<ConversationType>) =>
export default {
title: 'Components/CallingLobby',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
const props = createProps();
@ -107,10 +97,6 @@ export function NoCameraNoAvatar(): JSX.Element {
return <CallingLobby {...props} />;
}
NoCameraNoAvatar.story = {
name: 'No Camera, no avatar',
};
export function NoCameraLocalAvatar(): JSX.Element {
const props = createProps({
availableCameras: [],
@ -124,10 +110,6 @@ export function NoCameraLocalAvatar(): JSX.Element {
return <CallingLobby {...props} />;
}
NoCameraLocalAvatar.story = {
name: 'No Camera, local avatar',
};
export function LocalVideo(): JSX.Element {
const props = createProps({
hasLocalVideo: true,
@ -142,20 +124,12 @@ export function InitiallyMuted(): JSX.Element {
return <CallingLobby {...props} />;
}
InitiallyMuted.story = {
name: 'Initially muted',
};
export function GroupCall0PeekedParticipants(): JSX.Element {
export function GroupCallWithNoPeekedParticipants(): JSX.Element {
const props = createProps({ isGroupCall: true, peekedParticipants: [] });
return <CallingLobby {...props} />;
}
GroupCall0PeekedParticipants.story = {
name: 'Group Call - 0 peeked participants',
};
export function GroupCall1PeekedParticipant(): JSX.Element {
export function GroupCallWith1PeekedParticipant(): JSX.Element {
const props = createProps({
isGroupCall: true,
peekedParticipants: [{ title: 'Sam' }].map(fakePeekedParticipant),
@ -163,11 +137,7 @@ export function GroupCall1PeekedParticipant(): JSX.Element {
return <CallingLobby {...props} />;
}
GroupCall1PeekedParticipant.story = {
name: 'Group Call - 1 peeked participant',
};
export function GroupCall1PeekedParticipantSelf(): JSX.Element {
export function GroupCallWith1PeekedParticipantSelf(): JSX.Element {
const serviceId = generateAci();
const props = createProps({
isGroupCall: true,
@ -180,11 +150,7 @@ export function GroupCall1PeekedParticipantSelf(): JSX.Element {
return <CallingLobby {...props} />;
}
GroupCall1PeekedParticipantSelf.story = {
name: 'Group Call - 1 peeked participant (self)',
};
export function GroupCall4PeekedParticipants(): JSX.Element {
export function GroupCallWith4PeekedParticipants(): JSX.Element {
const props = createProps({
isGroupCall: true,
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
@ -194,11 +160,7 @@ export function GroupCall4PeekedParticipants(): JSX.Element {
return <CallingLobby {...props} />;
}
GroupCall4PeekedParticipants.story = {
name: 'Group Call - 4 peeked participants',
};
export function GroupCall4PeekedParticipantsParticipantsList(): JSX.Element {
export function GroupCallWith4PeekedParticipantsParticipantsList(): JSX.Element {
const props = createProps({
isGroupCall: true,
peekedParticipants: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'].map(title =>
@ -209,11 +171,7 @@ export function GroupCall4PeekedParticipantsParticipantsList(): JSX.Element {
return <CallingLobby {...props} />;
}
GroupCall4PeekedParticipantsParticipantsList.story = {
name: 'Group Call - 4 peeked participants (participants list)',
};
export function GroupCallCallFull(): JSX.Element {
export function GroupCallWithCallFull(): JSX.Element {
const props = createProps({
isGroupCall: true,
isCallFull: true,
@ -224,18 +182,10 @@ export function GroupCallCallFull(): JSX.Element {
return <CallingLobby {...props} />;
}
GroupCallCallFull.story = {
name: 'Group Call - call full',
};
export function GroupCall0PeekedParticipantsBigGroup(): JSX.Element {
export function GroupCallWith0PeekedParticipantsBigGroup(): JSX.Element {
const props = createProps({
isGroupCall: true,
groupMembers: times(100, () => getDefaultConversation()),
});
return <CallingLobby {...props} />;
}
GroupCall0PeekedParticipantsBigGroup.story = {
name: 'Group Call - 0 peeked participants, big group',
};

View File

@ -5,6 +5,7 @@ import * as React from 'react';
import { sample } from 'lodash';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CallingParticipantsList';
import { CallingParticipantsList } from './CallingParticipantsList';
import { AvatarColors } from '../types/Colors';
@ -47,17 +48,13 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/CallingParticipantsList',
};
} satisfies Meta<PropsType>;
export function NoOne(): JSX.Element {
const props = createProps();
return <CallingParticipantsList {...props} />;
}
NoOne.story = {
name: 'No one',
};
export function SoloCall(): JSX.Element {
const props = createProps({
participants: [

View File

@ -3,9 +3,8 @@
import * as React from 'react';
import { times } from 'lodash';
import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { AvatarColors } from '../types/Colors';
import type { ConversationType } from '../state/ducks/conversations';
import type { PropsType } from './CallingPip';
@ -35,16 +34,19 @@ const conversation: ConversationType = getDefaultConversation({
profileName: 'Rick Sanchez',
});
const getCommonActiveCallData = () => ({
type Overrides = {
hasLocalAudio?: boolean;
hasLocalVideo?: boolean;
localAudioLevel?: number;
viewMode?: CallViewMode;
};
const getCommonActiveCallData = (overrides: Overrides) => ({
conversation,
hasLocalAudio: boolean('hasLocalAudio', true),
hasLocalVideo: boolean('hasLocalVideo', false),
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
viewMode: select(
'viewMode',
[CallViewMode.Grid, CallViewMode.Speaker, CallViewMode.Presentation],
CallViewMode.Grid
),
hasLocalAudio: overrides.hasLocalAudio ?? true,
hasLocalVideo: overrides.hasLocalVideo ?? false,
localAudioLevel: overrides.localAudioLevel ?? 0,
viewMode: overrides.viewMode ?? CallViewMode.Grid,
joinedAt: Date.now(),
outgoingRing: true,
pip: true,
@ -52,92 +54,93 @@ const getCommonActiveCallData = () => ({
showParticipantsList: false,
});
const defaultCall: ActiveDirectCallType = {
...getCommonActiveCallData(),
callMode: CallMode.Direct as CallMode.Direct,
callState: CallState.Accepted,
peekedParticipants: [],
remoteParticipants: [
{ hasRemoteVideo: true, presenting: false, title: 'Arsene' },
],
const getDefaultCall = (overrides: Overrides): ActiveDirectCallType => {
return {
...getCommonActiveCallData(overrides),
callMode: CallMode.Direct as CallMode.Direct,
callState: CallState.Accepted,
peekedParticipants: [],
remoteParticipants: [
{ hasRemoteVideo: true, presenting: false, title: 'Arsene' },
],
};
};
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
activeCall: overrideProps.activeCall || defaultCall,
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
hangUpActiveCall: action('hang-up-active-call'),
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
i18n,
setGroupCallVideoRequest: action('set-group-call-video-request'),
setLocalPreview: action('set-local-preview'),
setRendererCanvas: action('set-renderer-canvas'),
switchFromPresentationView: action('switch-to-presentation-view'),
switchToPresentationView: action('switch-to-presentation-view'),
togglePip: action('toggle-pip'),
});
export default {
title: 'Components/CallingPip',
};
argTypes: {
hasLocalVideo: { control: { type: 'boolean' } },
},
args: {
activeCall: getDefaultCall({}),
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
hangUpActiveCall: action('hang-up-active-call'),
hasLocalVideo: false,
i18n,
setGroupCallVideoRequest: action('set-group-call-video-request'),
setLocalPreview: action('set-local-preview'),
setRendererCanvas: action('set-renderer-canvas'),
switchFromPresentationView: action('switch-to-presentation-view'),
switchToPresentationView: action('switch-to-presentation-view'),
togglePip: action('toggle-pip'),
},
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
const props = createProps({});
return <CallingPip {...props} />;
export function Default(args: PropsType): JSX.Element {
return <CallingPip {...args} />;
}
export function ContactWithAvatarAndNoVideo(): JSX.Element {
const props = createProps({
activeCall: {
...defaultCall,
conversation: {
...conversation,
avatarPath: 'https://www.fillmurray.com/64/64',
},
remoteParticipants: [
{ hasRemoteVideo: false, presenting: false, title: 'Julian' },
],
},
});
return <CallingPip {...props} />;
export function ContactWithAvatarAndNoVideo(args: PropsType): JSX.Element {
return (
<CallingPip
{...args}
activeCall={{
...getDefaultCall({}),
conversation: {
...conversation,
avatarPath: 'https://www.fillmurray.com/64/64',
},
remoteParticipants: [
{ hasRemoteVideo: false, presenting: false, title: 'Julian' },
],
}}
/>
);
}
ContactWithAvatarAndNoVideo.story = {
name: 'Contact (with avatar and no video)',
};
export function ContactNoColor(): JSX.Element {
const props = createProps({
activeCall: {
...defaultCall,
conversation: {
...conversation,
color: undefined,
},
},
});
return <CallingPip {...props} />;
export function ContactNoColor(args: PropsType): JSX.Element {
return (
<CallingPip
{...args}
activeCall={{
...getDefaultCall({}),
conversation: {
...conversation,
color: undefined,
},
}}
/>
);
}
ContactNoColor.story = {
name: 'Contact (no color)',
};
export function GroupCall(): JSX.Element {
const props = createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Group as CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [],
groupMembers: times(3, () => getDefaultConversation()),
isConversationTooBigToRing: false,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
deviceCount: 0,
peekedParticipants: [],
remoteParticipants: [],
remoteAudioLevels: new Map<number, number>(),
},
});
return <CallingPip {...props} />;
export function GroupCall(args: PropsType): JSX.Element {
return (
<CallingPip
{...args}
activeCall={{
...getCommonActiveCallData({}),
callMode: CallMode.Group as CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [],
groupMembers: times(3, () => getDefaultConversation()),
isConversationTooBigToRing: false,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
deviceCount: 0,
peekedParticipants: [],
remoteParticipants: [],
remoteAudioLevels: new Map<number, number>(),
}}
/>
);
}

View File

@ -3,10 +3,11 @@
import React from 'react';
import { times } from 'lodash';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import type { PropsType } from './CallingPreCallInfo';
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
const i18n = setupI18n('en', enMessages);
@ -22,7 +23,7 @@ const otherMembers = times(6, () => getDefaultConversation());
export default {
title: 'Components/CallingPreCallInfo',
};
} satisfies Meta<PropsType>;
export function DirectConversation(): JSX.Element {
return (
@ -35,10 +36,6 @@ export function DirectConversation(): JSX.Element {
);
}
DirectConversation.story = {
name: 'Direct conversation',
};
export function Ring0(): JSX.Element {
return (
<CallingPreCallInfo
@ -52,10 +49,6 @@ export function Ring0(): JSX.Element {
);
}
Ring0.story = {
name: 'Group call: Will ring 0 people',
};
export function Ring1(): JSX.Element {
return (
<CallingPreCallInfo
@ -69,10 +62,6 @@ export function Ring1(): JSX.Element {
);
}
Ring1.story = {
name: 'Group call: Will ring 1 person',
};
export function Ring2(): JSX.Element {
return (
<CallingPreCallInfo
@ -86,10 +75,6 @@ export function Ring2(): JSX.Element {
);
}
Ring2.story = {
name: 'Group call: Will ring 2 people',
};
export function Ring3(): JSX.Element {
return (
<CallingPreCallInfo
@ -103,10 +88,6 @@ export function Ring3(): JSX.Element {
);
}
Ring3.story = {
name: 'Group call: Will ring 3 people',
};
export function Ring4(): JSX.Element {
return (
<CallingPreCallInfo
@ -120,10 +101,6 @@ export function Ring4(): JSX.Element {
);
}
Ring3.story = {
name: 'Group call: Will ring 4 people',
};
export function Notify0(): JSX.Element {
return (
<CallingPreCallInfo
@ -137,10 +114,6 @@ export function Notify0(): JSX.Element {
);
}
Notify0.story = {
name: 'Group call: Will notify 0 people',
};
export function Notify1(): JSX.Element {
return (
<CallingPreCallInfo
@ -154,10 +127,6 @@ export function Notify1(): JSX.Element {
);
}
Notify1.story = {
name: 'Group call: Will notify 1 person',
};
export function Notify2(): JSX.Element {
return (
<CallingPreCallInfo
@ -171,10 +140,6 @@ export function Notify2(): JSX.Element {
);
}
Notify2.story = {
name: 'Group call: Will notify 2 people',
};
export function Notify3(): JSX.Element {
return (
<CallingPreCallInfo
@ -188,10 +153,6 @@ export function Notify3(): JSX.Element {
);
}
Notify3.story = {
name: 'Group call: Will notify 3 people',
};
export function Notify4(): JSX.Element {
return (
<CallingPreCallInfo
@ -205,10 +166,6 @@ export function Notify4(): JSX.Element {
);
}
Notify4.story = {
name: 'Group call: Will notify 4 people',
};
export function Peek1(): JSX.Element {
return (
<CallingPreCallInfo
@ -222,10 +179,6 @@ export function Peek1(): JSX.Element {
);
}
Peek1.story = {
name: 'Group call: 1 participant peeked',
};
export function Peek2(): JSX.Element {
return (
<CallingPreCallInfo
@ -239,10 +192,6 @@ export function Peek2(): JSX.Element {
);
}
Peek2.story = {
name: 'Group call: 2 participants peeked',
};
export function Peek3(): JSX.Element {
return (
<CallingPreCallInfo
@ -256,10 +205,6 @@ export function Peek3(): JSX.Element {
);
}
Peek3.story = {
name: 'Group call: 3 participants peeked',
};
export function Peek4(): JSX.Element {
return (
<CallingPreCallInfo
@ -273,10 +218,6 @@ export function Peek4(): JSX.Element {
);
}
Peek4.story = {
name: 'Group call: 4 participants peeked',
};
export function GroupConversationYouOnAnOtherDevice(): JSX.Element {
const me = getDefaultConversation();
return (
@ -291,10 +232,6 @@ export function GroupConversationYouOnAnOtherDevice(): JSX.Element {
);
}
GroupConversationYouOnAnOtherDevice.story = {
name: 'Group conversation, you on an other device',
};
export function GroupConversationCallIsFull(): JSX.Element {
return (
<CallingPreCallInfo
@ -308,7 +245,3 @@ export function GroupConversationCallIsFull(): JSX.Element {
/>
);
}
GroupConversationCallIsFull.story = {
name: 'Group conversation, call is full',
};

View File

@ -15,7 +15,7 @@ export enum RingMode {
IsRinging,
}
type PropsType = {
export type PropsType = {
conversation: Pick<
ConversationType,
| 'acceptedMessageRequest'

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CallingScreenSharingController';
import { CallingScreenSharingController } from './CallingScreenSharingController';
@ -21,7 +22,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/CallingScreenSharingController',
};
} satisfies Meta<PropsType>;
export function Controller(): JSX.Element {
return <CallingScreenSharingController {...createProps()} />;
@ -37,7 +38,3 @@ export function ReallyLongAppName(): JSX.Element {
/>
);
}
ReallyLongAppName.story = {
name: 'Really long app name',
};

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CallingSelectPresentingSourcesModal';
import { CallingSelectPresentingSourcesModal } from './CallingSelectPresentingSourcesModal';
@ -55,7 +56,7 @@ const createProps = (): PropsType => ({
export default {
title: 'Components/CallingSelectPresentingSourcesModal',
};
} satisfies Meta<PropsType>;
export function Modal(): JSX.Element {
return <CallingSelectPresentingSourcesModal {...createProps()} />;

View File

@ -3,36 +3,33 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import { boolean } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CaptchaDialog';
import { CaptchaDialog } from './CaptchaDialog';
import { Button } from './Button';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
export default {
title: 'Components/CaptchaDialog',
};
const i18n = setupI18n('en', enMessages);
export const _CaptchaDialog = (): JSX.Element => {
export default {
title: 'Components/CaptchaDialog',
argTypes: {
isPending: { control: { type: 'boolean' } },
},
args: {
i18n,
isPending: false,
onContinue: action('onContinue'),
},
} satisfies Meta<PropsType>;
export function Basic(args: PropsType): JSX.Element {
const [isSkipped, setIsSkipped] = useState(false);
if (isSkipped) {
return <Button onClick={() => setIsSkipped(false)}>Show again</Button>;
}
return (
<CaptchaDialog
i18n={i18n}
isPending={boolean('isPending', false)}
onContinue={action('onContinue')}
onSkip={() => setIsSkipped(true)}
/>
);
};
_CaptchaDialog.story = {
name: 'CaptchaDialog',
};
return <CaptchaDialog {...args} onSkip={() => setIsSkipped(true)} />;
}

View File

@ -8,7 +8,7 @@ import { Button, ButtonVariant } from './Button';
import { Modal } from './Modal';
import { Spinner } from './Spinner';
type PropsType = {
export type PropsType = {
i18n: LocalizerType;
isPending: boolean;

View File

@ -2,21 +2,45 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { select } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './ChatColorPicker';
import { ChatColorPicker } from './ChatColorPicker';
import { ConversationColors } from '../types/Colors';
import { setupI18n } from '../util/setupI18n';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/ChatColorPicker',
};
const i18n = setupI18n('en', enMessages);
argTypes: {
selectedColor: {
control: {
type: 'select',
options: ConversationColors,
},
},
},
args: {
addCustomColor: action('addCustomColor'),
colorSelected: action('colorSelected'),
editCustomColor: action('editCustomColor'),
getConversationsWithCustomColor: (_: string) => Promise.resolve([]),
i18n,
removeCustomColor: action('removeCustomColor'),
removeCustomColorOnConversations: action(
'removeCustomColorOnConversations'
),
resetAllChatColors: action('resetAllChatColors'),
resetDefaultChatColor: action('resetDefaultChatColor'),
selectedColor: 'basil',
selectedCustomColor: {},
setGlobalDefaultConversationColor: action(
'setGlobalDefaultConversationColor'
),
},
} satisfies Meta<PropsType>;
const SAMPLE_CUSTOM_COLOR = {
deg: 90,
@ -24,25 +48,8 @@ const SAMPLE_CUSTOM_COLOR = {
start: { hue: 315, saturation: 78 },
};
const createProps = (): PropsType => ({
addCustomColor: action('addCustomColor'),
colorSelected: action('colorSelected'),
editCustomColor: action('editCustomColor'),
getConversationsWithCustomColor: (_: string) => Promise.resolve([]),
i18n,
removeCustomColor: action('removeCustomColor'),
removeCustomColorOnConversations: action('removeCustomColorOnConversations'),
resetAllChatColors: action('resetAllChatColors'),
resetDefaultChatColor: action('resetDefaultChatColor'),
selectedColor: select('selectedColor', ConversationColors, 'basil' as const),
selectedCustomColor: {},
setGlobalDefaultConversationColor: action(
'setGlobalDefaultConversationColor'
),
});
export function Default(): JSX.Element {
return <ChatColorPicker {...createProps()} />;
export function Default(args: PropsType): JSX.Element {
return <ChatColorPicker {...args} />;
}
const CUSTOM_COLORS = {
@ -62,10 +69,10 @@ const CUSTOM_COLORS = {
},
};
export function CustomColors(): JSX.Element {
export function CustomColors(args: PropsType): JSX.Element {
return (
<ChatColorPicker
{...createProps()}
{...args}
customColors={CUSTOM_COLORS}
selectedColor="custom"
selectedCustomColor={{

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './Checkbox';
import { Checkbox } from './Checkbox';
@ -16,7 +17,7 @@ const createProps = (): PropsType => ({
export default {
title: 'Components/Checkbox',
};
} satisfies Meta<PropsType>;
export function Normal(): JSX.Element {
return <Checkbox {...createProps()} />;

View File

@ -3,7 +3,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { Props } from './CircleCheckbox';
import { CircleCheckbox, Variant } from './CircleCheckbox';
@ -15,7 +15,7 @@ const createProps = (): Props => ({
export default {
title: 'Components/CircleCheckbox',
};
} satisfies Meta<Props>;
export function Normal(): JSX.Element {
return <CircleCheckbox {...createProps()} />;

View File

@ -2,23 +2,19 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './ClearingData';
import { ClearingData } from './ClearingData';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/ClearingData',
};
} satisfies Meta<PropsType>;
export const _ClearingData = (): JSX.Element => (
<ClearingData deleteAllData={action('deleteAllData')} i18n={i18n} />
);
_ClearingData.story = {
name: 'Clearing data',
};
export function Basic(): JSX.Element {
return <ClearingData deleteAllData={action('deleteAllData')} i18n={i18n} />;
}

View File

@ -1,12 +1,9 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { DecoratorFunction } from '@storybook/addons';
import * as React from 'react';
import React, { useContext } from 'react';
import { action } from '@storybook/addon-actions';
import { boolean, select } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import { IMAGE_JPEG } from '../types/MIME';
import type { Props } from './CompositionArea';
import { CompositionArea } from './CompositionArea';
@ -28,287 +25,256 @@ export default {
decorators: [
// necessary for the add attachment button to render properly
storyFn => <div className="file-input">{storyFn()}</div>,
] as Array<DecoratorFunction<JSX.Element>>,
};
],
argTypes: {
recordingState: {
control: { type: 'select' },
options: Object.keys(RecordingState),
mappings: RecordingState,
},
messageRequestsEnabled: { control: { type: 'boolean' } },
announcementsOnly: { control: { type: 'boolean' } },
areWePendingApproval: { control: { type: 'boolean' } },
},
args: {
addAttachment: action('addAttachment'),
conversationId: '123',
convertDraftBodyRangesIntoHydrated: () => undefined,
discardEditMessage: action('discardEditMessage'),
focusCounter: 0,
sendCounter: 0,
i18n,
isDisabled: false,
isFormattingFlagEnabled: true,
isFormattingSpoilersFlagEnabled: true,
isFormattingEnabled: true,
messageCompositionId: '456',
sendEditedMessage: action('sendEditedMessage'),
sendMultiMediaMessage: action('sendMultiMediaMessage'),
platform: 'darwin',
processAttachments: action('processAttachments'),
removeAttachment: action('removeAttachment'),
setComposerFocus: action('setComposerFocus'),
setMessageToEdit: action('setMessageToEdit'),
setQuoteByMessageId: action('setQuoteByMessageId'),
showToast: action('showToast'),
const useProps = (overrideProps: Partial<Props> = {}): Props => ({
addAttachment: action('addAttachment'),
conversationId: '123',
convertDraftBodyRangesIntoHydrated: () => undefined,
discardEditMessage: action('discardEditMessage'),
focusCounter: 0,
sendCounter: 0,
i18n,
isDisabled: false,
isFormattingFlagEnabled:
overrideProps.isFormattingFlagEnabled === false
? overrideProps.isFormattingFlagEnabled
: true,
isFormattingSpoilersFlagEnabled:
overrideProps.isFormattingSpoilersFlagEnabled === false
? overrideProps.isFormattingSpoilersFlagEnabled
: true,
isFormattingEnabled:
overrideProps.isFormattingEnabled === false
? overrideProps.isFormattingEnabled
: true,
messageCompositionId: '456',
sendEditedMessage: action('sendEditedMessage'),
sendMultiMediaMessage: action('sendMultiMediaMessage'),
platform: 'darwin',
processAttachments: action('processAttachments'),
removeAttachment: action('removeAttachment'),
theme: React.useContext(StorybookThemeContext),
setComposerFocus: action('setComposerFocus'),
setMessageToEdit: action('setMessageToEdit'),
setQuoteByMessageId: action('setQuoteByMessageId'),
showToast: action('showToast'),
// AttachmentList
draftAttachments: [],
onClearAttachments: action('onClearAttachments'),
// AudioCapture
cancelRecording: action('cancelRecording'),
completeRecording: action('completeRecording'),
errorRecording: action('errorRecording'),
recordingState: RecordingState.Idle,
startRecording: action('startRecording'),
// StagedLinkPreview
linkPreviewLoading: false,
linkPreviewResult: undefined,
onCloseLinkPreview: action('onCloseLinkPreview'),
// Quote
quotedMessageProps: undefined,
scrollToMessage: action('scrollToMessage'),
// MediaEditor
imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
// MediaQualitySelector
setMediaQualitySetting: action('setMediaQualitySetting'),
shouldSendHighQualityAttachments: false,
// CompositionInput
onEditorStateChange: action('onEditorStateChange'),
onTextTooLong: action('onTextTooLong'),
draftText: undefined,
clearQuotedMessage: action('clearQuotedMessage'),
getPreferredBadge: () => undefined,
getQuotedMessage: action('getQuotedMessage'),
sortedGroupMembers: [],
// EmojiButton
onPickEmoji: action('onPickEmoji'),
onSetSkinTone: action('onSetSkinTone'),
recentEmojis: [],
skinTone: 1,
// StickerButton
knownPacks: [],
receivedPacks: [],
installedPacks: [],
blessedPacks: [],
recentStickers: [],
clearInstalledStickerPack: action('clearInstalledStickerPack'),
pushPanelForConversation: action('pushPanelForConversation'),
sendStickerMessage: action('sendStickerMessage'),
clearShowIntroduction: action('clearShowIntroduction'),
showPickerHint: false,
clearShowPickerHint: action('clearShowPickerHint'),
// Message Requests
conversationType: 'direct',
acceptConversation: action('acceptConversation'),
blockConversation: action('blockConversation'),
blockAndReportSpam: action('blockAndReportSpam'),
deleteConversation: action('deleteConversation'),
messageRequestsEnabled: false,
title: '',
// GroupV1 Disabled Actions
showGV2MigrationDialog: action('showGV2MigrationDialog'),
// GroupV2
announcementsOnly: false,
areWeAdmin: false,
areWePendingApproval: false,
groupAdmins: [],
cancelJoinRequest: action('cancelJoinRequest'),
showConversation: action('showConversation'),
// SMS-only
isSMSOnly: false,
isFetchingUUID: false,
renderSmartCompositionRecording: _ => <div>RECORDING</div>,
renderSmartCompositionRecordingDraft: _ => <div>RECORDING DRAFT</div>,
// Select mode
selectedMessageIds: undefined,
toggleSelectMode: action('toggleSelectMode'),
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
},
} satisfies Meta<Props>;
// AttachmentList
draftAttachments: overrideProps.draftAttachments || [],
onClearAttachments: action('onClearAttachments'),
// AudioCapture
cancelRecording: action('cancelRecording'),
completeRecording: action('completeRecording'),
errorRecording: action('errorRecording'),
recordingState: select(
'recordingState',
RecordingState,
overrideProps.recordingState || RecordingState.Idle
),
startRecording: action('startRecording'),
// StagedLinkPreview
linkPreviewLoading: Boolean(overrideProps.linkPreviewLoading),
linkPreviewResult: overrideProps.linkPreviewResult,
onCloseLinkPreview: action('onCloseLinkPreview'),
// Quote
quotedMessageProps: overrideProps.quotedMessageProps,
scrollToMessage: action('scrollToMessage'),
// MediaEditor
imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
// MediaQualitySelector
setMediaQualitySetting: action('setMediaQualitySetting'),
shouldSendHighQualityAttachments: Boolean(
overrideProps.shouldSendHighQualityAttachments
),
// CompositionInput
onEditorStateChange: action('onEditorStateChange'),
onTextTooLong: action('onTextTooLong'),
draftText: overrideProps.draftText || undefined,
clearQuotedMessage: action('clearQuotedMessage'),
getPreferredBadge: () => undefined,
getQuotedMessage: action('getQuotedMessage'),
sortedGroupMembers: [],
// EmojiButton
onPickEmoji: action('onPickEmoji'),
onSetSkinTone: action('onSetSkinTone'),
recentEmojis: [],
skinTone: 1,
// StickerButton
knownPacks: overrideProps.knownPacks || [],
receivedPacks: [],
installedPacks: [],
blessedPacks: [],
recentStickers: [],
clearInstalledStickerPack: action('clearInstalledStickerPack'),
pushPanelForConversation: action('pushPanelForConversation'),
sendStickerMessage: action('sendStickerMessage'),
clearShowIntroduction: action('clearShowIntroduction'),
showPickerHint: false,
clearShowPickerHint: action('clearShowPickerHint'),
// Message Requests
conversationType: 'direct',
acceptConversation: action('acceptConversation'),
blockConversation: action('blockConversation'),
blockAndReportSpam: action('blockAndReportSpam'),
deleteConversation: action('deleteConversation'),
messageRequestsEnabled: boolean(
'messageRequestsEnabled',
overrideProps.messageRequestsEnabled || false
),
title: '',
// GroupV1 Disabled Actions
showGV2MigrationDialog: action('showGV2MigrationDialog'),
// GroupV2
announcementsOnly: boolean(
'announcementsOnly',
Boolean(overrideProps.announcementsOnly)
),
areWeAdmin: boolean('areWeAdmin', Boolean(overrideProps.areWeAdmin)),
areWePendingApproval: boolean(
'areWePendingApproval',
Boolean(overrideProps.areWePendingApproval)
),
groupAdmins: [],
cancelJoinRequest: action('cancelJoinRequest'),
showConversation: action('showConversation'),
// SMS-only
isSMSOnly: overrideProps.isSMSOnly || false,
isFetchingUUID: overrideProps.isFetchingUUID || false,
renderSmartCompositionRecording: _ => <div>RECORDING</div>,
renderSmartCompositionRecordingDraft: _ => <div>RECORDING DRAFT</div>,
// Select mode
selectedMessageIds: undefined,
toggleSelectMode: action('toggleSelectMode'),
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
});
export function Default(): JSX.Element {
const props = useProps();
return <CompositionArea {...props} />;
export function Default(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return <CompositionArea {...args} theme={theme} />;
}
export function StartingText(): JSX.Element {
const props = useProps({
draftText: "here's some starting text",
});
return <CompositionArea {...props} />;
}
export function StickerButton(): JSX.Element {
const props = useProps({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
knownPacks: [{} as any],
});
return <CompositionArea {...props} />;
}
export function MessageRequest(): JSX.Element {
const props = useProps({
messageRequestsEnabled: true,
});
return <CompositionArea {...props} />;
}
export function SmsOnlyFetchingUuid(): JSX.Element {
const props = useProps({
isSMSOnly: true,
isFetchingUUID: true,
});
return <CompositionArea {...props} />;
}
SmsOnlyFetchingUuid.story = {
name: 'SMS-only fetching UUID',
};
export function SmsOnly(): JSX.Element {
const props = useProps({
isSMSOnly: true,
});
return <CompositionArea {...props} />;
}
SmsOnly.story = {
name: 'SMS-only',
};
export function Attachments(): JSX.Element {
const props = useProps({
draftAttachments: [
fakeDraftAttachment({
contentType: IMAGE_JPEG,
url: landscapeGreenUrl,
}),
],
});
return <CompositionArea {...props} />;
}
export function PendingApproval(): JSX.Element {
export function StartingText(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea
{...useProps({
areWePendingApproval: true,
})}
{...args}
theme={theme}
draftText="here's some starting text"
/>
);
}
AnnouncementsOnlyGroup.story = {
name: 'Announcements Only group',
};
export function AnnouncementsOnlyGroup(): JSX.Element {
export function StickerButton(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea
{...useProps({
announcementsOnly: true,
areWeAdmin: false,
})}
{...args}
theme={theme}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
knownPacks={[{} as any]}
/>
);
}
AnnouncementsOnlyGroup.story = {
name: 'Announcements Only group',
};
export function MessageRequest(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return <CompositionArea {...args} theme={theme} messageRequestsEnabled />;
}
export function Quote(): JSX.Element {
export function SmsOnlyFetchingUuid(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return <CompositionArea {...args} theme={theme} isSMSOnly isFetchingUUID />;
}
export function SmsOnly(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return <CompositionArea {...args} theme={theme} isSMSOnly />;
}
export function Attachments(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea
{...useProps({
quotedMessageProps: {
text: 'something',
conversationColor: ConversationColors[10],
conversationTitle: getDefaultConversation().title,
isGiftBadge: false,
isViewOnce: false,
referencedMessageNotFound: false,
authorTitle: 'Someone',
isFromMe: false,
{...args}
theme={theme}
draftAttachments={[
fakeDraftAttachment({
contentType: IMAGE_JPEG,
url: landscapeGreenUrl,
}),
]}
/>
);
}
export function PendingApproval(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return <CompositionArea {...args} theme={theme} areWePendingApproval />;
}
export function AnnouncementsOnlyGroup(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea
{...args}
theme={theme}
announcementsOnly
areWeAdmin={false}
/>
);
}
export function Quote(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea
{...args}
theme={theme}
quotedMessageProps={{
text: 'something',
conversationColor: ConversationColors[10],
conversationTitle: getDefaultConversation().title,
isGiftBadge: false,
isViewOnce: false,
referencedMessageNotFound: false,
authorTitle: 'Someone',
isFromMe: false,
}}
/>
);
}
export function QuoteWithPayment(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea
{...args}
theme={theme}
quotedMessageProps={{
text: '',
conversationColor: ConversationColors[10],
conversationTitle: getDefaultConversation().title,
isGiftBadge: false,
isViewOnce: false,
referencedMessageNotFound: false,
authorTitle: 'Someone',
isFromMe: false,
payment: {
kind: PaymentEventKind.Notification,
note: 'Thanks',
},
})}
}}
/>
);
}
export function QuoteWithPayment(): JSX.Element {
export function NoFormattingMenu(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea {...args} theme={theme} isFormattingEnabled={false} />
);
}
export function NoFormattingFlag(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea {...args} theme={theme} isFormattingFlagEnabled={false} />
);
}
export function NoSpoilerFormattingFlag(args: Props): JSX.Element {
const theme = useContext(StorybookThemeContext);
return (
<CompositionArea
{...useProps({
quotedMessageProps: {
text: '',
conversationColor: ConversationColors[10],
conversationTitle: getDefaultConversation().title,
isGiftBadge: false,
isViewOnce: false,
referencedMessageNotFound: false,
authorTitle: 'Someone',
isFromMe: false,
payment: {
kind: PaymentEventKind.Notification,
note: 'Thanks',
},
},
})}
/>
);
}
QuoteWithPayment.story = {
name: 'Quote with payment',
};
export function NoFormattingMenu(): JSX.Element {
return <CompositionArea {...useProps({ isFormattingEnabled: false })} />;
}
export function NoFormattingFlag(): JSX.Element {
return <CompositionArea {...useProps({ isFormattingFlagEnabled: false })} />;
}
export function NoSpoilerFormattingFlag(): JSX.Element {
return (
<CompositionArea
{...useProps({ isFormattingSpoilersFlagEnabled: false })}
{...args}
theme={theme}
isFormattingSpoilersFlagEnabled={false}
/>
);
}

View File

@ -2,11 +2,9 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import 'react-quill/dist/quill.core.css';
import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import type { Props } from './CompositionInput';
import { CompositionInput } from './CompositionInput';
@ -19,11 +17,13 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/CompositionInput',
};
argTypes: {},
args: {},
} satisfies Meta<Props>;
const useProps = (overrideProps: Partial<Props> = {}): Props => ({
i18n,
disabled: boolean('disabled', overrideProps.disabled || false),
disabled: overrideProps.disabled ?? false,
draftText: overrideProps.draftText || undefined,
draftBodyRanges: overrideProps.draftBodyRanges || [],
clearQuotedMessage: action('clearQuotedMessage'),
@ -41,7 +41,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
overrideProps.isFormattingEnabled === false
? overrideProps.isFormattingEnabled
: true,
large: boolean('large', overrideProps.large || false),
large: overrideProps.large ?? false,
onCloseLinkPreview: action('onCloseLinkPreview'),
onEditorStateChange: action('onEditorStateChange'),
onPickEmoji: action('onPickEmoji'),
@ -49,19 +49,8 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
onTextTooLong: action('onTextTooLong'),
platform: 'darwin',
sendCounter: 0,
sortedGroupMembers: overrideProps.sortedGroupMembers || [],
skinTone: select(
'skinTone',
{
skinTone0: 0,
skinTone1: 1,
skinTone2: 2,
skinTone3: 3,
skinTone4: 4,
skinTone5: 5,
},
overrideProps.skinTone || undefined
),
sortedGroupMembers: overrideProps.sortedGroupMembers ?? [],
skinTone: overrideProps.skinTone ?? undefined,
theme: React.useContext(StorybookThemeContext),
});

View File

@ -3,8 +3,9 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { Props } from './CompositionRecording';
import { CompositionRecording } from './CompositionRecording';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -13,7 +14,7 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'components/CompositionRecording',
component: CompositionRecording,
};
} satisfies Meta<Props>;
export function Default(): JSX.Element {
const [active, setActive] = useState(false);

View File

@ -3,8 +3,9 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { Props } from './CompositionRecordingDraft';
import { CompositionRecordingDraft } from './CompositionRecordingDraft';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -13,7 +14,7 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'components/CompositionRecordingDraft',
component: CompositionRecordingDraft,
};
} satisfies Meta<Props>;
export function Default(): JSX.Element {
const [isPlaying, setIsPlaying] = useState(false);

View File

@ -11,7 +11,7 @@ import * as log from '../logging/log';
import type { Size } from '../hooks/useSizeObserver';
import { SizeObserver } from '../hooks/useSizeObserver';
type Props = {
export type Props = {
i18n: LocalizerType;
audioUrl: string | undefined;
active:

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -20,7 +21,7 @@ const createProps = (): PropsType => ({
export default {
title: 'Components/ConfirmDiscardDialog',
};
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
return <ConfirmDiscardDialog {...createProps()} />;

View File

@ -4,6 +4,8 @@
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { Props } from './ConfirmationDialog';
import { ConfirmationDialog } from './ConfirmationDialog';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -12,9 +14,9 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/ConfirmationDialog',
};
} satisfies Meta<Props>;
export const _ConfirmationDialog = (): JSX.Element => {
export function Basic(): JSX.Element {
return (
<ConfirmationDialog
dialogName="test"
@ -37,11 +39,7 @@ export const _ConfirmationDialog = (): JSX.Element => {
asdf blip
</ConfirmationDialog>
);
};
_ConfirmationDialog.story = {
name: 'ConfirmationDialog',
};
}
export function CustomCancelText(): JSX.Element {
return (
@ -64,10 +62,6 @@ export function CustomCancelText(): JSX.Element {
);
}
CustomCancelText.story = {
name: 'Custom cancel text',
};
export function NoDefaultCancel(): JSX.Element {
return (
<ConfirmationDialog

View File

@ -6,6 +6,7 @@ import { times } from 'lodash';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { ContactPills } from './ContactPills';
@ -18,7 +19,7 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/Contact Pills',
};
} satisfies Meta<ContactPillPropsType>;
type ContactType = Omit<ContactPillPropsType, 'i18n' | 'onClickRemove'>;
@ -54,10 +55,6 @@ export function EmptyList(): JSX.Element {
return <ContactPills />;
}
EmptyList.story = {
name: 'Empty list',
};
export function OneContact(): JSX.Element {
return (
<ContactPills>
@ -66,10 +63,6 @@ export function OneContact(): JSX.Element {
);
}
OneContact.story = {
name: 'One contact',
};
export function ThreeContacts(): JSX.Element {
return (
<ContactPills>
@ -80,10 +73,6 @@ export function ThreeContacts(): JSX.Element {
);
}
ThreeContacts.story = {
name: 'Three contacts',
};
export function FourContactsOneWithALongName(): JSX.Element {
return (
<ContactPills>
@ -101,10 +90,6 @@ export function FourContactsOneWithALongName(): JSX.Element {
);
}
FourContactsOneWithALongName.story = {
name: 'Four contacts, one with a long name',
};
export function FiftyContacts(): JSX.Element {
return (
<ContactPills>
@ -114,7 +99,3 @@ export function FiftyContacts(): JSX.Element {
</ContactPills>
);
}
FiftyContacts.story = {
name: 'Fifty contacts',
};

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './ContextMenu';
import { ContextMenu } from './ContextMenu';
import enMessages from '../../_locales/en/messages.json';
@ -13,7 +14,7 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/ContextMenu',
};
} satisfies Meta<PropsType<unknown>>;
const getDefaultProps = (): PropsType<number> => ({
i18n,

View File

@ -4,11 +4,9 @@
import React, { useContext } from 'react';
import { times, omit } from 'lodash';
import { v4 as generateUuid } from 'uuid';
import { action } from '@storybook/addon-actions';
import { boolean, date, select, text } from '@storybook/addon-knobs';
import type { Row } from './ConversationList';
import type { Meta } from '@storybook/react';
import type { Row, PropsType } from './ConversationList';
import { ConversationList, RowType } from './ConversationList';
import { MessageSearchResult } from './conversationList/MessageSearchResult';
import type { PropsData as ConversationListItemPropsType } from './conversationList/ConversationListItem';
@ -25,7 +23,9 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/ConversationList',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
const defaultConversations: Array<ConversationListItemPropsType> = [
getDefaultConversation({
@ -105,15 +105,13 @@ function Wrapper({
);
}
export const _ArchiveButton = (): JSX.Element => (
<Wrapper
rows={[{ type: RowType.ArchiveButton, archivedConversationsCount: 123 }]}
/>
);
_ArchiveButton.story = {
name: 'Archive button',
};
export function ArchiveButton(): JSX.Element {
return (
<Wrapper
rows={[{ type: RowType.ArchiveButton, archivedConversationsCount: 123 }]}
/>
);
}
export function ContactNoteToSelf(): JSX.Element {
return (
@ -132,10 +130,6 @@ export function ContactNoteToSelf(): JSX.Element {
);
}
ContactNoteToSelf.story = {
name: 'Contact: note to self',
};
export function ContactDirect(): JSX.Element {
return (
<Wrapper
@ -144,10 +138,6 @@ export function ContactDirect(): JSX.Element {
);
}
ContactDirect.story = {
name: 'Contact: direct',
};
export function ContactDirectWithContextMenu(): JSX.Element {
return (
<Wrapper
@ -162,10 +152,6 @@ export function ContactDirectWithContextMenu(): JSX.Element {
);
}
ContactDirectWithContextMenu.story = {
name: 'Contact: context menu',
};
export function ContactDirectWithShortAbout(): JSX.Element {
return (
<Wrapper
@ -179,10 +165,6 @@ export function ContactDirectWithShortAbout(): JSX.Element {
);
}
ContactDirectWithShortAbout.story = {
name: 'Contact: direct with short about',
};
export function ContactDirectWithLongAbout(): JSX.Element {
return (
<Wrapper
@ -200,10 +182,6 @@ export function ContactDirectWithLongAbout(): JSX.Element {
);
}
ContactDirectWithLongAbout.story = {
name: 'Contact: direct with long about',
};
export function ContactGroup(): JSX.Element {
return (
<Wrapper
@ -217,10 +195,6 @@ export function ContactGroup(): JSX.Element {
);
}
ContactGroup.story = {
name: 'Contact: group',
};
export function ContactCheckboxes(): JSX.Element {
return (
<Wrapper
@ -248,10 +222,6 @@ export function ContactCheckboxes(): JSX.Element {
);
}
ContactCheckboxes.story = {
name: 'Contact checkboxes',
};
export function ContactCheckboxesDisabled(): JSX.Element {
return (
<Wrapper
@ -279,42 +249,29 @@ export function ContactCheckboxesDisabled(): JSX.Element {
);
}
ContactCheckboxesDisabled.story = {
name: 'Contact checkboxes: disabled',
};
const createConversation = (
overrideProps: Partial<ConversationListItemPropsType> = {}
): ConversationListItemPropsType => ({
...overrideProps,
acceptedMessageRequest: boolean(
'acceptedMessageRequest',
acceptedMessageRequest:
overrideProps.acceptedMessageRequest !== undefined
? overrideProps.acceptedMessageRequest
: true
),
: true,
badges: [],
isMe: boolean('isMe', overrideProps.isMe || false),
avatarPath: text('avatarPath', overrideProps.avatarPath || ''),
isMe: overrideProps.isMe ?? false,
avatarPath: overrideProps.avatarPath ?? '',
id: overrideProps.id || '',
isSelected: boolean('isSelected', overrideProps.isSelected || false),
title: text('title', overrideProps.title || 'Some Person'),
isSelected: overrideProps.isSelected ?? false,
title: overrideProps.title ?? 'Some Person',
profileName: overrideProps.profileName || 'Some Person',
type: overrideProps.type || 'direct',
markedUnread: boolean('markedUnread', overrideProps.markedUnread || false),
markedUnread: overrideProps.markedUnread ?? false,
lastMessage: overrideProps.lastMessage || {
text: text('lastMessage.text', 'Hi there!'),
status: select(
'status',
MessageStatuses.reduce((m, s) => ({ ...m, [s]: s }), {}),
'read'
),
text: 'Hi there!',
status: 'read',
deletedForEveryone: false,
},
lastUpdated: date(
'lastUpdated',
new Date(overrideProps.lastUpdated || Date.now() - 5 * 60 * 1000)
),
lastUpdated: overrideProps.lastUpdated ?? Date.now() - 5 * 60 * 1000,
sharedGroupNames: [],
});
@ -333,19 +290,11 @@ const renderConversation = (
export const ConversationName = (): JSX.Element => renderConversation();
ConversationName.story = {
name: 'Conversation: name',
};
export const ConversationNameAndAvatar = (): JSX.Element =>
renderConversation({
avatarPath: '/fixtures/kitten-1-64-64.jpg',
});
ConversationNameAndAvatar.story = {
name: 'Conversation: name and avatar',
};
export const ConversationWithYourself = (): JSX.Element =>
renderConversation({
lastMessage: {
@ -358,10 +307,6 @@ export const ConversationWithYourself = (): JSX.Element =>
isMe: true,
});
ConversationWithYourself.story = {
name: 'Conversation: with yourself',
};
export function ConversationsMessageStatuses(): JSX.Element {
return (
<Wrapper
@ -375,21 +320,13 @@ export function ConversationsMessageStatuses(): JSX.Element {
);
}
ConversationsMessageStatuses.story = {
name: 'Conversations: Message Statuses',
};
export const ConversationTypingStatus = (): JSX.Element =>
renderConversation({
typingContactIdTimestamps: {
[generateUuid()]: date('timestamp', new Date()),
[generateUuid()]: Date.now(),
},
});
ConversationTypingStatus.story = {
name: 'Conversation: Typing Status',
};
export const ConversationWithDraft = (): JSX.Element =>
renderConversation({
shouldShowDraft: true,
@ -400,19 +337,11 @@ export const ConversationWithDraft = (): JSX.Element =>
},
});
ConversationWithDraft.story = {
name: 'Conversation: With draft',
};
export const ConversationDeletedForEveryone = (): JSX.Element =>
renderConversation({
lastMessage: { deletedForEveryone: true },
});
ConversationDeletedForEveryone.story = {
name: 'Conversation: Deleted for everyone',
};
export const ConversationMessageRequest = (): JSX.Element =>
renderConversation({
acceptedMessageRequest: false,
@ -423,10 +352,6 @@ export const ConversationMessageRequest = (): JSX.Element =>
},
});
ConversationMessageRequest.story = {
name: 'Conversation: Message Request',
};
export function ConversationsUnreadCount(): JSX.Element {
return (
<Wrapper
@ -445,17 +370,9 @@ export function ConversationsUnreadCount(): JSX.Element {
);
}
ConversationsUnreadCount.story = {
name: 'Conversations: unread count',
};
export const ConversationMarkedUnread = (): JSX.Element =>
renderConversation({ markedUnread: true });
ConversationMarkedUnread.story = {
name: 'Conversation: marked unread',
};
export const ConversationSelected = (): JSX.Element =>
renderConversation({
lastMessage: {
@ -466,10 +383,6 @@ export const ConversationSelected = (): JSX.Element =>
isSelected: true,
});
ConversationSelected.story = {
name: 'Conversation: Selected',
};
export const ConversationEmojiInMessage = (): JSX.Element =>
renderConversation({
lastMessage: {
@ -479,10 +392,6 @@ export const ConversationEmojiInMessage = (): JSX.Element =>
},
});
ConversationEmojiInMessage.story = {
name: 'Conversation: Emoji in Message',
};
export const ConversationLinkInMessage = (): JSX.Element =>
renderConversation({
lastMessage: {
@ -492,10 +401,6 @@ export const ConversationLinkInMessage = (): JSX.Element =>
},
});
ConversationLinkInMessage.story = {
name: 'Conversation: Link in Message',
};
export const ConversationLongName = (): JSX.Element => {
const name =
'Long contact name. Esquire. The third. And stuff. And more! And more!';
@ -505,10 +410,6 @@ export const ConversationLongName = (): JSX.Element => {
});
};
ConversationLongName.story = {
name: 'Conversation: long name',
};
export function ConversationLongMessage(): JSX.Element {
const messages = [
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
@ -534,10 +435,6 @@ Line 4, well.`,
);
}
ConversationLongMessage.story = {
name: 'Conversation: Long Message',
};
export function ConversationsVariousTimes(): JSX.Element {
const pairs: Array<[number, string]> = [
[Date.now() - 5 * 60 * 60 * 1000, 'Five hours ago'],
@ -563,10 +460,6 @@ export function ConversationsVariousTimes(): JSX.Element {
);
}
ConversationsVariousTimes.story = {
name: 'Conversations: Various Times',
};
export function ConversationMissingDate(): JSX.Element {
const row = {
type: RowType.Conversation as const,
@ -576,10 +469,6 @@ export function ConversationMissingDate(): JSX.Element {
return <Wrapper rows={[row]} />;
}
ConversationMissingDate.story = {
name: 'Conversation: Missing Date',
};
export function ConversationMissingMessage(): JSX.Element {
const row = {
type: RowType.Conversation as const,
@ -589,10 +478,6 @@ export function ConversationMissingMessage(): JSX.Element {
return <Wrapper rows={[row]} />;
}
ConversationMissingMessage.story = {
name: 'Conversation: Missing Message',
};
export const ConversationMissingText = (): JSX.Element =>
renderConversation({
lastMessage: {
@ -602,19 +487,11 @@ export const ConversationMissingText = (): JSX.Element =>
},
});
ConversationMissingText.story = {
name: 'Conversation: Missing Text',
};
export const ConversationMutedConversation = (): JSX.Element =>
renderConversation({
muteExpiresAt: Date.now() + 1000 * 60 * 60,
});
ConversationMutedConversation.story = {
name: 'Conversation: Muted Conversation',
};
export const ConversationAtMention = (): JSX.Element =>
renderConversation({
title: 'The Rebellion',
@ -626,10 +503,6 @@ export const ConversationAtMention = (): JSX.Element =>
},
});
ConversationAtMention.story = {
name: 'Conversation: At Mention',
};
export function Headers(): JSX.Element {
return (
<Wrapper
@ -700,10 +573,6 @@ export function FindByPhoneNumber(): JSX.Element {
);
}
FindByPhoneNumber.story = {
name: 'Find by phone number',
};
export function FindByUsername(): JSX.Element {
return (
<Wrapper
@ -728,10 +597,6 @@ export function FindByUsername(): JSX.Element {
);
}
FindByUsername.story = {
name: 'Find by username',
};
export function SearchResultsLoadingSkeleton(): JSX.Element {
return (
<Wrapper
@ -746,10 +611,6 @@ export function SearchResultsLoadingSkeleton(): JSX.Element {
);
}
SearchResultsLoadingSkeleton.story = {
name: 'Search results loading skeleton',
};
export function KitchenSink(): JSX.Element {
return (
<Wrapper
@ -821,7 +682,3 @@ export function KitchenSink(): JSX.Element {
/>
);
}
KitchenSink.story = {
name: 'Kitchen sink',
};

View File

@ -4,6 +4,8 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CrashReportDialog';
import { CrashReportDialog } from './CrashReportDialog';
import { setupI18n } from '../util/setupI18n';
import { sleep } from '../util/sleep';
@ -11,11 +13,11 @@ import enMessages from '../../_locales/en/messages.json';
export default {
title: 'Components/CrashReportDialog',
};
} satisfies Meta<PropsType>;
const i18n = setupI18n('en', enMessages);
export const _CrashReportDialog = (): JSX.Element => {
export function Basic(): JSX.Element {
const [isPending, setIsPending] = useState(false);
return (
@ -31,8 +33,4 @@ export const _CrashReportDialog = (): JSX.Element => {
eraseCrashReports={action('eraseCrashReports')}
/>
);
};
_CrashReportDialog.story = {
name: 'CrashReportDialog',
};
}

View File

@ -13,7 +13,7 @@ type PropsActionsType = {
eraseCrashReports: () => void;
};
type PropsType = {
export type PropsType = {
i18n: LocalizerType;
isPending: boolean;
} & PropsActionsType;

View File

@ -5,6 +5,7 @@ import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './CustomColorEditor';
import { CustomColorEditor } from './CustomColorEditor';
@ -12,7 +13,7 @@ import { setupI18n } from '../util/setupI18n';
export default {
title: 'Components/CustomColorEditor',
};
} satisfies Meta<PropsType>;
const i18n = setupI18n('en', enMessages);

View File

@ -5,16 +5,18 @@ import type { ComponentProps } from 'react';
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './CustomizingPreferredReactionsModal';
import { CustomizingPreferredReactionsModal } from './CustomizingPreferredReactionsModal';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/CustomizingPreferredReactionsModal',
};
} satisfies Meta<PropsType>;
const defaultProps: ComponentProps<typeof CustomizingPreferredReactionsModal> =
{
@ -50,10 +52,6 @@ export function DraftEmojiSelected(): JSX.Element {
);
}
DraftEmojiSelected.story = {
name: 'Draft emoji selected',
};
export function Saving(): JSX.Element {
return <CustomizingPreferredReactionsModal {...defaultProps} isSaving />;
}
@ -61,7 +59,3 @@ export function Saving(): JSX.Element {
export function HadError(): JSX.Element {
return <CustomizingPreferredReactionsModal {...defaultProps} hadSaveError />;
}
HadError.story = {
name: 'Had error',
};

View File

@ -19,7 +19,7 @@ import { convertShortName } from './emoji/lib';
import { offsetDistanceModifier } from '../util/popperUtil';
import { handleOutsideClick } from '../util/handleOutsideClick';
type PropsType = {
export type PropsType = {
draftPreferredReactions: ReadonlyArray<string>;
hadSaveError: boolean;
i18n: LocalizerType;

View File

@ -4,6 +4,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './DebugLogWindow';
import { DebugLogWindow } from './DebugLogWindow';
@ -31,12 +32,8 @@ const createProps = (): PropsType => ({
export default {
title: 'Components/DebugLogWindow',
};
} satisfies Meta<PropsType>;
export const _DebugLogWindow = (): JSX.Element => (
<DebugLogWindow {...createProps()} />
);
_DebugLogWindow.story = {
name: 'DebugLogWindow',
};
export function Basic(): JSX.Element {
return <DebugLogWindow {...createProps()} />;
}

View File

@ -2,8 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { select } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import type { PropsType } from './DialogExpiredBuild';
import { DialogExpiredBuild } from './DialogExpiredBuild';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -14,14 +14,12 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/DialogExpiredBuild',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export const _DialogExpiredBuild = (): JSX.Element => {
const containerWidthBreakpoint = select(
'containerWidthBreakpoint',
WidthBreakpoint,
WidthBreakpoint.Wide
);
export function Basic(): JSX.Element {
const containerWidthBreakpoint = WidthBreakpoint.Wide;
return (
<FakeLeftPaneContainer containerWidthBreakpoint={containerWidthBreakpoint}>
@ -31,8 +29,4 @@ export const _DialogExpiredBuild = (): JSX.Element => {
/>
</FakeLeftPaneContainer>
);
};
_DialogExpiredBuild.story = {
name: 'DialogExpiredBuild',
};
}

View File

@ -4,6 +4,7 @@
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './DialogNetworkStatus';
import { DialogNetworkStatus } from './DialogNetworkStatus';
import { SocketStatus } from '../types/SocketStatus';
@ -27,7 +28,7 @@ const defaultProps = {
export default {
title: 'Components/DialogNetworkStatus',
};
} satisfies Meta<PropsType>;
export function KnobsPlayground(args: PropsType): JSX.Element {
/*
@ -68,10 +69,6 @@ export function ConnectingWide(): JSX.Element {
);
}
ConnectingWide.story = {
name: 'Connecting Wide',
};
export function ClosingWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -84,10 +81,6 @@ export function ClosingWide(): JSX.Element {
);
}
ClosingWide.story = {
name: 'Closing Wide',
};
export function ClosedWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -100,10 +93,6 @@ export function ClosedWide(): JSX.Element {
);
}
ClosedWide.story = {
name: 'Closed Wide',
};
export function OfflineWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -116,10 +105,6 @@ export function OfflineWide(): JSX.Element {
);
}
OfflineWide.story = {
name: 'Offline Wide',
};
export function ConnectingNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -132,10 +117,6 @@ export function ConnectingNarrow(): JSX.Element {
);
}
ConnectingNarrow.story = {
name: 'Connecting Narrow',
};
export function ClosingNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -148,10 +129,6 @@ export function ClosingNarrow(): JSX.Element {
);
}
ClosingNarrow.story = {
name: 'Closing Narrow',
};
export function ClosedNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -164,10 +141,6 @@ export function ClosedNarrow(): JSX.Element {
);
}
ClosedNarrow.story = {
name: 'Closed Narrow',
};
export function OfflineNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -179,7 +152,3 @@ export function OfflineNarrow(): JSX.Element {
</FakeLeftPaneContainer>
);
}
OfflineNarrow.story = {
name: 'Offline Narrow',
};

View File

@ -3,7 +3,8 @@
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './DialogRelink';
import { DialogRelink } from './DialogRelink';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -35,7 +36,7 @@ const permutations = [
export default {
title: 'Components/DialogRelink',
};
} satisfies Meta<PropsType>;
export function Iterations(): JSX.Element {
return (

View File

@ -2,8 +2,9 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './DialogUpdate';
import { DialogUpdate } from './DialogUpdate';
import { DialogType } from '../types/Dialogs';
import { WidthBreakpoint } from './_util';
@ -29,15 +30,13 @@ const defaultProps = {
export default {
title: 'Components/DialogUpdate',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export function KnobsPlayground(): JSX.Element {
const containerWidthBreakpoint = select(
'containerWidthBreakpoint',
WidthBreakpoint,
WidthBreakpoint.Wide
);
const dialogType = select('dialogType', DialogType, DialogType.AutoUpdate);
const containerWidthBreakpoint = WidthBreakpoint.Wide;
const dialogType = DialogType.AutoUpdate;
return (
<FakeLeftPaneContainer containerWidthBreakpoint={containerWidthBreakpoint}>
@ -64,10 +63,6 @@ export function UpdateWide(): JSX.Element {
);
}
UpdateWide.story = {
name: 'Update (Wide)',
};
export function DownloadedWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -81,10 +76,6 @@ export function DownloadedWide(): JSX.Element {
);
}
DownloadedWide.story = {
name: 'Downloaded (Wide)',
};
export function DownloadReadyWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -99,10 +90,6 @@ export function DownloadReadyWide(): JSX.Element {
);
}
DownloadReadyWide.story = {
name: 'DownloadReady (Wide)',
};
export function FullDownloadReadyWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -117,10 +104,6 @@ export function FullDownloadReadyWide(): JSX.Element {
);
}
FullDownloadReadyWide.story = {
name: 'FullDownloadReady (Wide)',
};
export function DownloadingWide(): JSX.Element {
const [downloadedSize, setDownloadedSize] = React.useState(0);
@ -153,10 +136,6 @@ export function DownloadingWide(): JSX.Element {
);
}
DownloadingWide.story = {
name: 'Downloading (Wide)',
};
export function CannotUpdateWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -170,10 +149,6 @@ export function CannotUpdateWide(): JSX.Element {
);
}
CannotUpdateWide.story = {
name: 'Cannot_Update (Wide)',
};
export function CannotUpdateBetaWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -187,10 +162,6 @@ export function CannotUpdateBetaWide(): JSX.Element {
);
}
CannotUpdateBetaWide.story = {
name: 'Cannot_Update_Beta (Wide)',
};
export function CannotUpdateRequireManualWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -204,10 +175,6 @@ export function CannotUpdateRequireManualWide(): JSX.Element {
);
}
CannotUpdateRequireManualWide.story = {
name: 'Cannot_Update_Require_Manual (Wide)',
};
export function CannotUpdateRequireManualBetaWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -221,10 +188,6 @@ export function CannotUpdateRequireManualBetaWide(): JSX.Element {
);
}
CannotUpdateRequireManualBetaWide.story = {
name: 'Cannot_Update_Require_Manual_Beta (Wide)',
};
export function MacOSReadOnlyWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -238,10 +201,6 @@ export function MacOSReadOnlyWide(): JSX.Element {
);
}
MacOSReadOnlyWide.story = {
name: 'MacOS_Read_Only (Wide)',
};
export function UnsupportedOSWide(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
@ -255,10 +214,6 @@ export function UnsupportedOSWide(): JSX.Element {
);
}
UnsupportedOSWide.story = {
name: 'UnsupportedOS (Wide)',
};
export function UpdateNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -272,10 +227,6 @@ export function UpdateNarrow(): JSX.Element {
);
}
UpdateNarrow.story = {
name: 'Update (Narrow)',
};
export function DownloadedNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -289,10 +240,6 @@ export function DownloadedNarrow(): JSX.Element {
);
}
DownloadedNarrow.story = {
name: 'Downloaded (Narrow)',
};
export function DownloadReadyNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -307,10 +254,6 @@ export function DownloadReadyNarrow(): JSX.Element {
);
}
DownloadReadyNarrow.story = {
name: 'DownloadReady (Narrow)',
};
export function FullDownloadReadyNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -325,10 +268,6 @@ export function FullDownloadReadyNarrow(): JSX.Element {
);
}
FullDownloadReadyNarrow.story = {
name: 'FullDownloadReady (Narrow)',
};
export function DownloadingNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -342,10 +281,6 @@ export function DownloadingNarrow(): JSX.Element {
);
}
DownloadingNarrow.story = {
name: 'Downloading (Narrow)',
};
export function CannotUpdateNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -359,10 +294,6 @@ export function CannotUpdateNarrow(): JSX.Element {
);
}
CannotUpdateNarrow.story = {
name: 'Cannot Update (Narrow)',
};
export function CannotUpdateBetaNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -376,10 +307,6 @@ export function CannotUpdateBetaNarrow(): JSX.Element {
);
}
CannotUpdateBetaNarrow.story = {
name: 'Cannot Update Beta (Narrow)',
};
export function CannotUpdateRequireManualNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -393,10 +320,6 @@ export function CannotUpdateRequireManualNarrow(): JSX.Element {
);
}
CannotUpdateRequireManualNarrow.story = {
name: 'Cannot_Update_Require_Manual (Narrow)',
};
export function CannotUpdateRequireManualBetaNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -410,10 +333,6 @@ export function CannotUpdateRequireManualBetaNarrow(): JSX.Element {
);
}
CannotUpdateRequireManualBetaNarrow.story = {
name: 'Cannot_Update_Require_Manual_Beta (Narrow)',
};
export function MacOSReadOnlyNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -427,10 +346,6 @@ export function MacOSReadOnlyNarrow(): JSX.Element {
);
}
MacOSReadOnlyNarrow.story = {
name: 'MacOS_Read_Only (Narrow)',
};
export function UnsupportedOSNarrow(): JSX.Element {
return (
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
@ -443,7 +358,3 @@ export function UnsupportedOSNarrow(): JSX.Element {
</FakeLeftPaneContainer>
);
}
UnsupportedOSNarrow.story = {
name: 'UnsupportedOS (Narrow)',
};

View File

@ -3,7 +3,8 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './DisappearingTimeDialog';
import { DisappearingTimeDialog } from './DisappearingTimeDialog';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
@ -12,7 +13,7 @@ import { EXPIRE_TIMERS } from '../test-both/util/expireTimers';
export default {
title: 'Components/DisappearingTimeDialog',
};
} satisfies Meta<PropsType>;
const i18n = setupI18n('en', enMessages);

View File

@ -2,7 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import type { Meta } from '@storybook/react';
import type { Props } from './DisappearingTimerSelect';
import { DisappearingTimerSelect } from './DisappearingTimerSelect';
import { setupI18n } from '../util/setupI18n';
import { DurationInSeconds } from '../util/durations';
@ -10,15 +11,15 @@ import enMessages from '../../_locales/en/messages.json';
export default {
title: 'Components/DisappearingTimerSelect',
};
} satisfies Meta<Props>;
const i18n = setupI18n('en', enMessages);
type Props = {
type Args = {
initialValue: number;
};
function TimerSelectWrap({ initialValue }: Props): JSX.Element {
function TimerSelectWrap({ initialValue }: Args): JSX.Element {
const [value, setValue] = useState(initialValue);
return (
@ -34,14 +35,6 @@ export function InitialValue1Day(): JSX.Element {
return <TimerSelectWrap initialValue={24 * 3600} />;
}
InitialValue1Day.story = {
name: 'Initial value: 1 day',
};
export function InitialValue3DaysCustomTime(): JSX.Element {
return <TimerSelectWrap initialValue={3 * 24 * 3600} />;
}
InitialValue3DaysCustomTime.story = {
name: 'Initial value 3 days (Custom time)',
};

View File

@ -2,8 +2,9 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { Meta, Story } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import enMessages from '../../_locales/en/messages.json';
import { setupI18n } from '../util/setupI18n';
import type { UsernameReservationType } from '../types/Username';
@ -29,11 +30,9 @@ export default {
argTypes: {
currentUsername: {
type: { name: 'string', required: false },
defaultValue: undefined,
},
state: {
control: { type: 'radio' },
defaultValue: State.Open,
options: {
Open: State.Open,
Closed: State.Closed,
@ -43,7 +42,6 @@ export default {
},
error: {
control: { type: 'radio' },
defaultValue: undefined,
options: {
None: undefined,
NotEnoughCharacters: UsernameReservationError.NotEnoughCharacters,
@ -54,26 +52,24 @@ export default {
General: UsernameReservationError.General,
},
},
maxUsername: {
defaultValue: 20,
},
minUsername: {
defaultValue: 3,
},
discriminator: {
reservation: {
type: { name: 'string', required: false },
defaultValue: undefined,
},
i18n: {
defaultValue: i18n,
},
onClose: { action: true },
onError: { action: true },
setUsernameReservationError: { action: true },
reserveUsername: { action: true },
confirmUsername: { action: true },
},
} as Meta;
args: {
currentUsername: undefined,
state: State.Open,
error: undefined,
maxNickname: 20,
minNickname: 3,
reservation: undefined,
i18n,
onClose: action('onClose'),
setUsernameReservationError: action('setUsernameReservationError'),
reserveUsername: action('reserveUsername'),
confirmUsername: action('confirmUsername'),
},
} satisfies Meta<PropsType>;
type ArgsType = PropsType & {
discriminator?: string;
@ -81,7 +77,7 @@ type ArgsType = PropsType & {
};
// eslint-disable-next-line react/function-component-definition
const Template: Story<ArgsType> = args => {
const Template: StoryFn<ArgsType> = args => {
let { reservation } = args;
if (!reservation && args.discriminator) {
reservation = {
@ -95,27 +91,16 @@ const Template: Story<ArgsType> = args => {
export const WithoutUsername = Template.bind({});
WithoutUsername.args = {};
WithoutUsername.story = {
name: 'without current username',
};
export const WithUsername = Template.bind({});
WithUsername.args = {};
WithUsername.story = {
name: 'with current username',
args: {
currentUsername: 'signaluser.12',
},
WithUsername.args = {
currentUsername: 'signaluser.12',
};
export const WithReservation = Template.bind({});
WithReservation.args = {};
WithReservation.story = {
name: 'with reservation',
args: {
currentUsername: 'reserved',
reservation: DEFAULT_RESERVATION,
},
WithReservation.args = {
currentUsername: 'reserved',
reservation: DEFAULT_RESERVATION,
};
export const UsernameEditingConfirming = Template.bind({});
@ -123,9 +108,6 @@ UsernameEditingConfirming.args = {
state: State.Confirming,
currentUsername: 'signaluser.12',
};
UsernameEditingConfirming.story = {
name: 'Username editing, Confirming',
};
export const UsernameEditingUsernameTaken = Template.bind({});
UsernameEditingUsernameTaken.args = {
@ -133,9 +115,6 @@ UsernameEditingUsernameTaken.args = {
error: UsernameReservationError.UsernameNotAvailable,
currentUsername: 'signaluser.12',
};
UsernameEditingUsernameTaken.story = {
name: 'Username editing, username taken',
};
export const UsernameEditingUsernameWrongCharacters = Template.bind({});
UsernameEditingUsernameWrongCharacters.args = {
@ -143,9 +122,6 @@ UsernameEditingUsernameWrongCharacters.args = {
error: UsernameReservationError.CheckCharacters,
currentUsername: 'signaluser.12',
};
UsernameEditingUsernameWrongCharacters.story = {
name: 'Username editing, Wrong Characters',
};
export const UsernameEditingUsernameTooShort = Template.bind({});
UsernameEditingUsernameTooShort.args = {
@ -153,9 +129,6 @@ UsernameEditingUsernameTooShort.args = {
error: UsernameReservationError.NotEnoughCharacters,
currentUsername: 'sig',
};
UsernameEditingUsernameTooShort.story = {
name: 'Username editing, username too short',
};
export const UsernameEditingGeneralError = Template.bind({});
UsernameEditingGeneralError.args = {
@ -163,6 +136,3 @@ UsernameEditingGeneralError.args = {
error: UsernameReservationError.General,
currentUsername: 'signaluser.12',
};
UsernameEditingGeneralError.story = {
name: 'Username editing, general error',
};

View File

@ -2,9 +2,9 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './ErrorModal';
import { ErrorModal } from './ErrorModal';
@ -14,15 +14,17 @@ import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
title: text('title', overrideProps.title || ''),
description: text('description', overrideProps.description || ''),
title: overrideProps.title ?? '',
description: overrideProps.description ?? '',
i18n,
onClose: action('onClick'),
});
export default {
title: 'Components/ErrorModal',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export function Normal(): JSX.Element {
return <ErrorModal {...createProps()} />;

View File

@ -2,10 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import { text } from '@storybook/addon-knobs';
import type { Meta } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import type { AttachmentType } from '../types/Attachment';
import type { PropsType } from './ForwardMessagesModal';
@ -15,25 +13,25 @@ import { getDefaultConversation } from '../test-both/helpers/getDefaultConversat
import { setupI18n } from '../util/setupI18n';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { CompositionTextArea } from './CompositionTextArea';
import type { MessageForwardDraft } from '../util/maybeForwardMessages';
import type { MessageForwardDraft } from '../types/ForwardDraft';
const createAttachment = (
props: Partial<AttachmentType> = {}
): AttachmentType => ({
pending: false,
path: 'fileName.jpg',
contentType: stringToMIMEType(
text('attachment contentType', props.contentType || '')
),
fileName: text('attachment fileName', props.fileName || ''),
contentType: stringToMIMEType(props.contentType ?? ''),
fileName: props.fileName ?? '',
screenshotPath: props.pending === false ? props.screenshotPath : undefined,
url: text('attachment url', props.pending === false ? props.url || '' : ''),
url: props.pending === false ? props.url ?? '' : '',
size: 3433,
});
export default {
title: 'Components/ForwardMessageModal',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
const i18n = setupI18n('en', enMessages);
@ -82,7 +80,7 @@ function getMessageForwardDraft(
attachments: overrideProps.attachments,
hasContact: Boolean(overrideProps.hasContact),
isSticker: Boolean(overrideProps.isSticker),
messageBody: text('messageBody', overrideProps.messageBody || ''),
messageBody: overrideProps.messageBody ?? '',
originalMessageId: '123',
previews: overrideProps.previews ?? [],
};
@ -102,10 +100,6 @@ export function WithText(): JSX.Element {
);
}
WithText.story = {
name: 'with text',
};
export function ASticker(): JSX.Element {
return (
<ForwardMessagesModal
@ -116,10 +110,6 @@ export function ASticker(): JSX.Element {
);
}
ASticker.story = {
name: 'a sticker',
};
export function WithAContact(): JSX.Element {
return (
<ForwardMessagesModal
@ -130,10 +120,6 @@ export function WithAContact(): JSX.Element {
);
}
WithAContact.story = {
name: 'with a contact',
};
export function LinkPreview(): JSX.Element {
return (
<ForwardMessagesModal
@ -162,10 +148,6 @@ export function LinkPreview(): JSX.Element {
);
}
LinkPreview.story = {
name: 'link preview',
};
export function MediaAttachments(): JSX.Element {
return (
<ForwardMessagesModal
@ -196,10 +178,6 @@ export function MediaAttachments(): JSX.Element {
);
}
MediaAttachments.story = {
name: 'media attachments',
};
export function AnnouncementOnlyGroupsNonAdmin(): JSX.Element {
return (
<ForwardMessagesModal
@ -213,7 +191,3 @@ export function AnnouncementOnlyGroupsNonAdmin(): JSX.Element {
/>
);
}
AnnouncementOnlyGroupsNonAdmin.story = {
name: 'announcement only groups non-admin',
};

View File

@ -27,11 +27,6 @@ import {
shouldNeverBeCalled,
asyncShouldNeverBeCalled,
} from '../util/shouldNeverBeCalled';
import type { MessageForwardDraft } from '../util/maybeForwardMessages';
import {
isDraftEditable,
isDraftForwardable,
} from '../util/maybeForwardMessages';
import type { LinkPreviewType } from '../types/message/LinkPreviews';
import { LinkPreviewSourceType } from '../types/LinkPreview';
import { ToastType } from '../types/Toast';
@ -41,6 +36,11 @@ import { BodyRange } from '../types/BodyRange';
import { UserText } from './UserText';
import { Modal } from './Modal';
import { SizeObserver } from '../hooks/useSizeObserver';
import {
isDraftEditable,
isDraftForwardable,
type MessageForwardDraft,
} from '../types/ForwardDraft';
export type DataPropsType = {
candidateConversations: ReadonlyArray<ConversationType>;

View File

@ -3,9 +3,9 @@
import React from 'react';
import { memoize, times } from 'lodash';
import { number } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './GroupCallOverflowArea';
import { GroupCallOverflowArea } from './GroupCallOverflowArea';
import { setupI18n } from '../util/setupI18n';
import { getDefaultConversationWithServiceId } from '../test-both/helpers/getDefaultConversation';
@ -34,7 +34,9 @@ const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({
export default {
title: 'Components/GroupCallOverflowArea',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
const defaultProps = {
getFrameBuffer: memoize(() => Buffer.alloc(FRAME_BUFFER_SIZE)),
@ -68,10 +70,6 @@ export function NoOverflowedParticipants(): JSX.Element {
);
}
NoOverflowedParticipants.story = {
name: 'No overflowed participants',
};
export function OneOverflowedParticipant(): JSX.Element {
return (
<Container>
@ -83,10 +81,6 @@ export function OneOverflowedParticipant(): JSX.Element {
);
}
OneOverflowedParticipant.story = {
name: 'One overflowed participant',
};
export function ThreeOverflowedParticipants(): JSX.Element {
return (
<Container>
@ -98,10 +92,6 @@ export function ThreeOverflowedParticipants(): JSX.Element {
);
}
ThreeOverflowedParticipants.story = {
name: 'Three overflowed participants',
};
export function ManyOverflowedParticipants(): JSX.Element {
return (
<Container>
@ -109,18 +99,9 @@ export function ManyOverflowedParticipants(): JSX.Element {
{...defaultProps}
overflowedParticipants={allRemoteParticipants.slice(
0,
number('Participant count', MAX_PARTICIPANTS, {
range: true,
min: 0,
max: MAX_PARTICIPANTS,
step: 1,
})
MAX_PARTICIPANTS
)}
/>
</Container>
);
}
ManyOverflowedParticipants.story = {
name: 'Many overflowed participants',
};

View File

@ -15,7 +15,7 @@ const OVERFLOW_SCROLL_BUTTON_RATIO = 0.75;
// This should be an integer, as sub-pixel widths can cause performance issues.
export const OVERFLOW_PARTICIPANT_WIDTH = 140;
type PropsType = {
export type PropsType = {
getFrameBuffer: () => Buffer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
i18n: LocalizerType;

View File

@ -2,9 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { memoize, noop } from 'lodash';
import { select } from '@storybook/addon-knobs';
import { memoize } from 'lodash';
import type { Meta } from '@storybook/react';
import type { PropsType } from './GroupCallRemoteParticipant';
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
@ -46,8 +45,9 @@ const createProps = (
} = {}
): PropsType => ({
getFrameBuffer,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getGroupCallVideoFrameSource: noop as any,
getGroupCallVideoFrameSource: () => {
return { receiveVideoFrame: () => undefined };
},
i18n,
audioLevel: 0,
remoteParticipant: {
@ -72,7 +72,9 @@ const createProps = (
export default {
title: 'Components/GroupCallRemoteParticipant',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
return (
@ -101,7 +103,7 @@ export function Speaking(): JSX.Element {
left: (120 + 10) * index,
top: 0,
width: 120,
audioLevel: select('audioLevel', [0, 0.5, 1], 0.5),
audioLevel: 0.5,
remoteParticipantsCount,
},
{ hasRemoteAudio: true, presenting }
@ -126,10 +128,6 @@ export function IsInPip(): JSX.Element {
);
}
IsInPip.story = {
name: 'isInPip',
};
export function Blocked(): JSX.Element {
return (
<GroupCallRemoteParticipant

View File

@ -2,17 +2,17 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './GroupDescriptionInput';
import { GroupDescriptionInput } from './GroupDescriptionInput';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/GroupDescriptionInput',
};
} satisfies Meta<PropsType>;
function Wrapper({
disabled,

View File

@ -6,7 +6,7 @@ import React, { forwardRef } from 'react';
import { Input } from './Input';
import type { LocalizerType } from '../types/Util';
type PropsType = {
export type PropsType = {
disabled?: boolean;
i18n: LocalizerType;
onChangeValue: (value: string) => void;

View File

@ -2,17 +2,17 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './GroupTitleInput';
import { GroupTitleInput } from './GroupTitleInput';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/GroupTitleInput',
};
} satisfies Meta<PropsType>;
function Wrapper({
disabled,

View File

@ -6,7 +6,7 @@ import React, { forwardRef } from 'react';
import { Input } from './Input';
import type { LocalizerType } from '../types/Util';
type PropsType = {
export type PropsType = {
disabled?: boolean;
i18n: LocalizerType;
onChangeValue: (value: string) => void;

View File

@ -5,6 +5,7 @@ import * as React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './GroupV1MigrationDialog';
import { GroupV1MigrationDialog } from './GroupV1MigrationDialog';
import type { ConversationType } from '../state/ducks/conversations';
@ -48,16 +49,12 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/GroupV1MigrationDialog',
};
} satisfies Meta<PropsType>;
export function NotYetMigratedBasic(): JSX.Element {
return <GroupV1MigrationDialog {...createProps()} />;
}
NotYetMigratedBasic.story = {
name: 'Not yet migrated, basic',
};
export function MigratedBasic(): JSX.Element {
return (
<GroupV1MigrationDialog
@ -68,10 +65,6 @@ export function MigratedBasic(): JSX.Element {
);
}
MigratedBasic.story = {
name: 'Migrated, basic',
};
export function MigratedYouAreInvited(): JSX.Element {
return (
<GroupV1MigrationDialog
@ -83,10 +76,6 @@ export function MigratedYouAreInvited(): JSX.Element {
);
}
MigratedYouAreInvited.story = {
name: 'Migrated, you are invited',
};
export function NotYetMigratedMultipleDroppedAndInvitedMembers(): JSX.Element {
return (
<GroupV1MigrationDialog
@ -98,10 +87,6 @@ export function NotYetMigratedMultipleDroppedAndInvitedMembers(): JSX.Element {
);
}
NotYetMigratedMultipleDroppedAndInvitedMembers.story = {
name: 'Not yet migrated, multiple dropped and invited members',
};
export function NotYetMigratedNoMembers(): JSX.Element {
return (
<GroupV1MigrationDialog
@ -113,10 +98,6 @@ export function NotYetMigratedNoMembers(): JSX.Element {
);
}
NotYetMigratedNoMembers.story = {
name: 'Not yet migrated, no members',
};
export function NotYetMigratedJustDroppedMember(): JSX.Element {
return (
<GroupV1MigrationDialog
@ -126,7 +107,3 @@ export function NotYetMigratedJustDroppedMember(): JSX.Element {
/>
);
}
NotYetMigratedJustDroppedMember.story = {
name: 'Not yet migrated, just dropped member',
};

View File

@ -2,9 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { boolean, number, text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './GroupV2JoinDialog';
import { GroupV2JoinDialog } from './GroupV2JoinDialog';
import { setupI18n } from '../util/setupI18n';
@ -13,13 +12,10 @@ import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
memberCount: number('memberCount', overrideProps.memberCount || 12),
memberCount: overrideProps.memberCount ?? 12,
avatar: overrideProps.avatar,
title: text('title', overrideProps.title || 'Random Group!'),
approvalRequired: boolean(
'approvalRequired',
overrideProps.approvalRequired || false
),
title: overrideProps.title ?? 'Random Group!',
approvalRequired: overrideProps.approvalRequired ?? false,
groupDescription: overrideProps.groupDescription,
join: action('join'),
onClose: action('onClose'),
@ -28,7 +24,9 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
export default {
title: 'Components/GroupV2JoinDialog',
};
argTypes: {},
args: {},
} satisfies Meta<PropsType>;
export function Basic(): JSX.Element {
return <GroupV2JoinDialog {...createProps()} />;
@ -45,10 +43,6 @@ export function ApprovalRequired(): JSX.Element {
);
}
ApprovalRequired.story = {
name: 'Approval required',
};
export function WithAvatar(): JSX.Element {
return (
<GroupV2JoinDialog
@ -62,10 +56,6 @@ export function WithAvatar(): JSX.Element {
);
}
WithAvatar.story = {
name: 'With avatar',
};
export function WithOneMember(): JSX.Element {
return (
<GroupV2JoinDialog
@ -77,10 +67,6 @@ export function WithOneMember(): JSX.Element {
);
}
WithOneMember.story = {
name: 'With one member',
};
export function AvatarLoadingState(): JSX.Element {
return (
<GroupV2JoinDialog
@ -94,10 +80,6 @@ export function AvatarLoadingState(): JSX.Element {
);
}
AvatarLoadingState.story = {
name: 'Avatar loading state',
};
export function Full(): JSX.Element {
return (
<GroupV2JoinDialog

View File

@ -2,13 +2,13 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { Meta } from '@storybook/react';
import { IdenticonSVGForContact, IdenticonSVGForGroup } from './IdenticonSVG';
import { AvatarColorMap } from '../types/Colors';
export default {
title: 'Components/IdenticonSVG',
};
} satisfies Meta;
export function AllColorsForContact(): JSX.Element {
const stories: Array<JSX.Element> = [];

View File

@ -2,16 +2,17 @@
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import type { Meta } from '@storybook/react';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { PropsType } from './InContactsIcon';
import { InContactsIcon } from './InContactsIcon';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/InContactsIcon',
};
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
return <InContactsIcon i18n={i18n} />;

View File

@ -7,7 +7,7 @@ import classNames from 'classnames';
import { Tooltip } from './Tooltip';
import type { LocalizerType } from '../types/Util';
type PropsType = {
export type PropsType = {
className?: string;
tooltipContainerRef?: React.RefObject<HTMLElement>;
i18n: LocalizerType;

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState, useEffect, useMemo } from 'react';
import type { Meta, Story } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import { noop } from 'lodash';
import { Inbox } from './Inbox';
@ -16,41 +16,15 @@ const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/Inbox',
argTypes: {
i18n: {
defaultValue: i18n,
},
hasInitialLoadCompleted: {
defaultValue: false,
},
daysAgo: {
control: 'select',
defaultValue: undefined,
options: [undefined, 1, 2, 3, 7, 14, 21],
},
isCustomizingPreferredReactions: {
defaultValue: false,
},
onConversationClosed: {
action: true,
},
onConversationOpened: {
action: true,
},
scrollToMessage: {
action: true,
},
showConversation: {
action: true,
},
showWhatsNewModal: {
action: true,
},
args: {
i18n,
hasInitialLoadCompleted: false,
isCustomizingPreferredReactions: false,
},
} as Meta;
} satisfies Meta<PropsType>;
// eslint-disable-next-line react/function-component-definition
const Template: Story<PropsType & { daysAgo?: number }> = ({
const Template: StoryFn<PropsType & { daysAgo?: number }> = ({
daysAgo,
...args
}) => {
@ -90,6 +64,3 @@ const Template: Story<PropsType & { daysAgo?: number }> = ({
};
export const Default = Template.bind({});
Default.story = {
name: 'Default',
};

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